// 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 import platform import "stdlib.h" 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 str os // "windows", "macos" or "linux": a module may have a version for each (time_windows.fch) str std // the standard library's folder, or "" []str paths // FINCH_PATH: more folders to look for modules in } // import json: the file next to the one that imports it, else the standard library's. // A program's own json.fch wins over the library's. fn findModule(ptr[Loader] l, str from, str name) -> str { // in each folder, the version for this system first: time_windows.fch, then time.fch local := dirOf(from) + "/" + name + ".fch" if file_exists(dirOf(from) + "/" + name + "_" + l.os + ".fch") { return dirOf(from) + "/" + name + "_" + l.os + ".fch" } if file_exists(local) { return local } for d in l.paths { if file_exists(d + "/" + name + "_" + l.os + ".fch") { return d + "/" + name + "_" + l.os + ".fch" } if file_exists(d + "/" + name + ".fch") { return d + "/" + name + ".fch" } } if l.std.len > 0 { if file_exists(l.std + "/" + name + "_" + l.os + ".fch") { return l.std + "/" + name + "_" + l.os + ".fch" } if file_exists(l.std + "/" + name + ".fch") { return l.std + "/" + name + ".fch" } } return local } 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, findModule(l, path, name), 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(" -g debug info (for gdb and lldb; use -O0 with it)") print(" --target build for another system: windows, linux, arm64 (Linux), macos, or an LLVM triple") print("not in this compiler yet: lsp. It is 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) outGiven := false shared := false debug := false targetName := "" 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] outGiven = true ai += 1 } else if a == "--shared" && cmd == "build" { shared = true } 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" { debug = true } else if a == "--target" && ai + 1 < args.len { targetName = args[ai + 1] ai += 1 } else if cmd == "run" { progArgs.push(a) } else { usage() } ai += 1 } tg := platform.select(targetName) if cmd == "run" { out = "/tmp/finch_run_" + stem(file) + tg.exe } else if shared && !outGiven { if tg.windows { out = out + ".dll" } else if tg.darwin { out = "lib" + out + ".dylib" } else { out = "lib" + out + ".so" } } else if !outGiven { out = out + tg.exe } loader := Loader() loader.os = "linux" if tg.windows { loader.os = "windows" } else if tg.darwin { loader.os = "macos" } for cand in [dirOf(args[0]) + "/../std", dirOf(args[0]) + "/std", "std"] { if loader.std.len == 0 && file_exists(cand + "/path.fch") { loader.std = cand } } for d in getenv("FINCH_PATH").split(":") { if d.len > 0 { loader.paths.push(d) } } 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)) } } } triple := "" if tg.cross { triple = tg.triple } ci := cimport.importHeaders(headers, dirs, triple, tg.windows, tg.cc) or { print(err) return 1 } ir := gen.generate(loader.progs, loader.files, ci, gen.Options(shared: shared, debug: debug, cwd: getenv("PWD"), target: tg)) if cmd == "ir" { print(ir) return 0 } // the runtime is Finch's C file; FINCH_RT can point somewhere else rt := getenv("FINCH_RT") if rt.len == 0 { 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) // the IR is always compiled by clang (CC may be gcc, which can't read it); FINCH_CC picks the linker for another system clang := getenv("FINCH_CC") if clang.len == 0 || tg.cross { clang = "clang" } extra := "" if shared { extra = " -fPIC" } if debug { extra = extra + " -g" // or clang drops the debug info that is in the IR } code := 0 if tg.cross { // an object for the target, then the target's own C compiler links it with the runtime obj := out + ".o" code = shell("clang --target=" + tg.triple + " " + opt + extra + " -w -c '" + llPath + "' -o '" + obj + "'") if code == 0 { linkShared := "" if shared { linkShared = " -shared" } code = shell(tg.cc + " '" + obj + "' '" + rt + "' -o '" + out + "' -lm" + linkShared + " " + libs.join(" ")) } shell("rm -f '" + obj + "'") } else { if shared { extra = extra + " -shared" } code = shell(clang + " " + opt + extra + " -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(tg.runner + quote(out) + " " + quoted.join(" ")) shell("rm -f " + quote(out)) return status } return 0 }