mirror of
https://github.com/Nexoniarz/Finch.git
synced 2026-11-03 08:54:25 +00:00
The self-hosted compiler reads headers with boot/cimport.fch and compiles calls to C functions (variadics, char* as str, C's default promotions), enum and #define constants, extern variables like stdout, C structs laid out as C laid them (padding and fixed arrays, by pointer), .ptr of a str or array, ptr(x) and int(ptr). link "lib" asks pkg-config, link "x.c" compiles the file along. cimport and c_pointers pass. Not yet: structs by value (the calling convention) and addr(fn) for C callbacks, which is also what stops it building itself. boot.sh now says so when stage 2 can't be made and runs the tests on stage 1.
160 lines
4.1 KiB
Text
160 lines
4.1 KiB
Text
// finch-boot: the Finch compiler written in Finch.
|
|
//
|
|
// finch-boot build <file.fch> [-o name] compile a program
|
|
// finch-boot ir <file.fch> print the LLVM IR
|
|
//
|
|
// It reads the program and the modules it imports, checks it, writes LLVM IR as text
|
|
// and lets clang turn that into a program, linked with Finch's C runtime.
|
|
import lexer
|
|
import ast
|
|
import parser
|
|
import gen
|
|
import cimport
|
|
|
|
fn dirOf(str path) -> str {
|
|
i := path.len - 1
|
|
while i >= 0 && path[i] != '/' {
|
|
i -= 1
|
|
}
|
|
if i < 0 {
|
|
return "."
|
|
}
|
|
return path.sub(0, i)
|
|
}
|
|
|
|
fn stem(str path) -> str {
|
|
name := path.sub(dirOf(path).len + 1, path.len)
|
|
if dirOf(path) == "." && !path.starts_with("./") {
|
|
name = path
|
|
}
|
|
dot := name.find(".")
|
|
if dot > 0 {
|
|
return name.sub(0, dot)
|
|
}
|
|
return name
|
|
}
|
|
|
|
struct Loader {
|
|
[]ast.Program progs
|
|
[]str files
|
|
[]str loaded
|
|
}
|
|
|
|
fn load(ptr[Loader] l, str path, str module) {
|
|
if !file_exists(path) {
|
|
if module.len > 0 {
|
|
print("error: can't find the module '" + module + "' (looked for " + path + ")")
|
|
} else {
|
|
print("error: can't open '" + path + "'")
|
|
}
|
|
exit(1)
|
|
}
|
|
src := read_file(path)
|
|
file := l.files.len
|
|
l.files.push(path)
|
|
prog := parser.parse(lexer.lex(src, path), file, path)
|
|
prog.module = module
|
|
prog.path = path
|
|
imports := prog.imports
|
|
l.progs.push(prog)
|
|
for name in imports {
|
|
if !l.loaded.contains(name) {
|
|
l.loaded.push(name)
|
|
load(l, dirOf(path) + "/" + name + ".fch", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
// -lname, or whatever pkg-config says the library needs
|
|
fn libFlags(str name) -> str {
|
|
tmp := "/tmp/finch_pkg_flags.txt"
|
|
code := shell("pkg-config --libs '" + name + "' > '" + tmp + "' 2> /dev/null")
|
|
if code == 0 && file_exists(tmp) {
|
|
flags := read_file(tmp).trim()
|
|
shell("rm -f '" + tmp + "'")
|
|
if flags.len > 0 {
|
|
return flags
|
|
}
|
|
}
|
|
return "-l" + name
|
|
}
|
|
|
|
fn usage() {
|
|
print("finch-boot - the Finch compiler written in Finch")
|
|
print("usage: finch-boot build <file.fch> [-o name]")
|
|
print(" finch-boot ir <file.fch>")
|
|
exit(1)
|
|
}
|
|
|
|
fn main([]str args) -> int {
|
|
if args.len < 3 {
|
|
usage()
|
|
}
|
|
cmd := args[1]
|
|
file := args[2]
|
|
out := stem(file)
|
|
if args.len == 5 && args[3] == "-o" {
|
|
out = args[4]
|
|
} else if args.len != 3 {
|
|
usage()
|
|
}
|
|
if cmd != "build" && cmd != "ir" {
|
|
usage()
|
|
}
|
|
|
|
loader := Loader()
|
|
load(addr(loader), file, "")
|
|
|
|
// C headers, from every file; their folders are searched first for "local.h"
|
|
[]cimport.Header headers
|
|
[]str seen
|
|
[]str dirs
|
|
[]str libs
|
|
for prog in loader.progs {
|
|
d := dirOf(prog.path)
|
|
if !dirs.contains(d) {
|
|
dirs.push(d)
|
|
}
|
|
for i in 0..prog.cimports.len {
|
|
if !seen.contains(prog.cimports[i]) {
|
|
seen.push(prog.cimports[i])
|
|
headers.push(cimport.Header(path: prog.cimports[i], where: prog.cwhere[i]))
|
|
}
|
|
}
|
|
for l in prog.links {
|
|
if l.ends_with(".c") {
|
|
libs.push("'" + d + "/" + l + "'")
|
|
} else {
|
|
libs.push(libFlags(l))
|
|
}
|
|
}
|
|
}
|
|
ci := cimport.importHeaders(headers, dirs) or {
|
|
print(err)
|
|
return 1
|
|
}
|
|
ir := gen.generate(loader.progs, loader.files, ci)
|
|
if cmd == "ir" {
|
|
print(ir)
|
|
return 0
|
|
}
|
|
|
|
// the runtime is Finch's C file; FINCH_RT can point somewhere else
|
|
rt := dirOf(args[0]) + "/../runtime/finch_rt.c"
|
|
if file_exists("runtime/finch_rt.c") {
|
|
rt = "runtime/finch_rt.c"
|
|
}
|
|
if !file_exists(rt) {
|
|
print("error: can't find the Finch runtime (runtime/finch_rt.c)")
|
|
return 1
|
|
}
|
|
llPath := out + ".ll"
|
|
write_file(llPath, ir)
|
|
code := shell("clang -O2 -w '" + llPath + "' '" + rt + "' -o '" + out + "' -lm " + libs.join(" "))
|
|
shell("rm -f '" + llPath + "'")
|
|
if code != 0 {
|
|
print("error: clang failed")
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|