mirror of
https://github.com/Nexoniarz/Finch.git
synced 2026-11-03 00:44:27 +00:00
import json import path import time import proc -- found in std/ after the program's own files, so a program's own json.fch always wins. path: join, base, dir, ext, stem, parts, clean, is_abs. time: now, millis, sleep, date, local, format, days_in_month (clock_gettime, strftime through C import). proc: run, output (an error with the command's output when it fails), env, pid, cwd. json: parse (errors with line and column), stringify, pretty, new_*, get/at/set/push/has/names and str_or/int_or/float_or/bool_or; \u escapes with surrogate pairs. The C importer now leaves a plain (non-const) char * parameter a ptr[char], a buffer C writes into; only const char * is text going in. str(ptr) and str([]u8) are in the self-hosted compiler. boot.sh: 40 passed, 0 failed.
238 lines
7 KiB
Text
238 lines
7 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
|
|
str std // the standard library's folder, or ""
|
|
}
|
|
|
|
// 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 {
|
|
local := dirOf(from) + "/" + name + ".fch"
|
|
if file_exists(local) || l.std.len == 0 {
|
|
return local
|
|
}
|
|
shared := l.std + "/" + name + ".fch"
|
|
if file_exists(shared) {
|
|
return shared
|
|
}
|
|
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 <file.fch> [args...] build and run a program")
|
|
print(" finch build <file.fch> [-o name] build a program")
|
|
print(" finch ir <file.fch> print the LLVM IR")
|
|
print(" finch version show the version")
|
|
print("options:")
|
|
print(" -O0 no optimization (faster to build)")
|
|
print(" -l <lib> link a library (the same as link \"lib\" in the program)")
|
|
print(" -I <dir> 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()
|
|
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
|
|
}
|
|
}
|
|
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
|
|
}
|