// finch-boot: the Finch compiler written in Finch. // // finch-boot build [-o name] compile a program // finch-boot ir 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 -- the compiler is written in Finch") print("usage:") print(" finch run [args...] build and run a program") print(" finch build [-o name] build a program") print(" finch ir print the LLVM IR") print(" finch version show the version") print("options:") print(" -O0 no optimization (faster to build)") print(" -l link a library (the same as link \"lib\" in the program)") print(" -I look for imported C headers there too") print("not in this compiler yet: -g, --target, lsp. They are in the C++ one (branch before-bootstrap).") exit(1) } fn quote(str a) -> str { return "'" + a.replace("'", "'\\''") + "'" } fn main([]str args) -> int { if args.len == 2 && (args[1] == "version" || args[1] == "--version") { print("Finch 2.4.0 (the compiler is written in Finch)") return 0 } if args.len < 3 { usage() } cmd := args[1] file := args[2] if cmd != "build" && cmd != "run" && cmd != "ir" { usage() } out := stem(file) opt := "-O2" []str extraLibs []str extraDirs []str progArgs ai := 3 while ai < args.len { a := args[ai] if cmd == "run" && progArgs.len > 0 { // after the first argument of the program, all of them are its progArgs.push(a) } else if a == "-o" && ai + 1 < args.len && cmd == "build" { out = args[ai + 1] ai += 1 } else if a == "-O0" { opt = "-O0" } else if a == "-l" && ai + 1 < args.len { extraLibs.push(libFlags(args[ai + 1])) ai += 1 } else if a.starts_with("-l") && a.len > 2 { extraLibs.push(libFlags(a.sub(2, a.len))) } else if a == "-I" && ai + 1 < args.len { extraDirs.push(args[ai + 1]) ai += 1 } else if a == "-g" || a == "--target" { print("error: '" + a + "' is not in the compiler written in Finch yet; the C++ one has it (branch before-bootstrap)") return 1 } else if cmd == "run" { progArgs.push(a) } else { usage() } ai += 1 } if cmd == "run" { out = "/tmp/finch_run_" + stem(file) } 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 = extraDirs []str libs = extraLibs 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(rt) { rt = dirOf(args[0]) + "/runtime/finch_rt.c" } if !file_exists(rt) && 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 " + opt + " -w '" + llPath + "' '" + rt + "' -o '" + out + "' -lm " + libs.join(" ")) shell("rm -f '" + llPath + "'") if code != 0 { print("error: clang failed") return 1 } if cmd == "run" { []str quoted for a in progArgs { quoted.push(quote(a)) } status := shell(quote(out) + " " + quoted.join(" ")) shell("rm -f " + quote(out)) return status } return 0 }