mirror of
https://github.com/Nexoniarz/Finch.git
synced 2026-11-03 00:44:27 +00:00
The compiler carries its runtime and std inside itself; host module for the system it runs on
src/embedded.fch (made by tools/pack.fch, checked by tests/run.sh) holds runtime/finch_rt.c and std/; when they aren't beside the binary they're written to the cache folder once. All the sh/cmd specifics of the driver (quoting, temp dir, mkdir, rm, mv, null device, path separator, cache dir, binary stdio) moved into host.fch / host_windows.fch. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015f73FesGZGYPkmDvUcj6QX
This commit is contained in:
parent
a0c54a1252
commit
837ad45177
12 changed files with 354 additions and 59 deletions
|
|
@ -6,8 +6,8 @@
|
||||||
# bootstrap/build.sh seed also write bootstrap/<system>.ll anew (do this when the compiler got a feature
|
# bootstrap/build.sh seed also write bootstrap/<system>.ll anew (do this when the compiler got a feature
|
||||||
# its own source uses, or the seed is old: the next build starts from it)
|
# its own source uses, or the seed is old: the next build starts from it)
|
||||||
#
|
#
|
||||||
# Needs clang and libclang (the C headers are read through it). The compiler is then in build/finch; it looks for the
|
# Needs clang and libclang (the C headers are read through it). The compiler is then in build/finch. It carries the
|
||||||
# runtime and the standard library next to build/, in runtime/ and std/.
|
# runtime and the standard library inside itself (src/embedded.fch), so it runs from anywhere.
|
||||||
set -eu
|
set -eu
|
||||||
cd "$(dirname "$0")/.."
|
cd "$(dirname "$0")/.."
|
||||||
mkdir -p build
|
mkdir -p build
|
||||||
|
|
@ -28,6 +28,8 @@ export FINCH_RT=$PWD/runtime/finch_rt.c
|
||||||
|
|
||||||
echo "bootstrap: seed ($seed) -> build/finch0"
|
echo "bootstrap: seed ($seed) -> build/finch0"
|
||||||
$CLANG -O1 -w "bootstrap/$seed.ll" runtime/finch_rt.c -o build/finch0 -lm $LIBS
|
$CLANG -O1 -w "bootstrap/$seed.ll" runtime/finch_rt.c -o build/finch0 -lm $LIBS
|
||||||
|
echo "bootstrap: build/finch0 packs the runtime and std into src/embedded.fch"
|
||||||
|
bootstrap/pack.sh build/finch0
|
||||||
echo "bootstrap: build/finch0 builds the compiler -> build/finch1"
|
echo "bootstrap: build/finch0 builds the compiler -> build/finch1"
|
||||||
build/finch0 build src/main.fch -o build/finch1
|
build/finch0 build src/main.fch -o build/finch1
|
||||||
echo "bootstrap: build/finch1 builds the compiler -> build/finch"
|
echo "bootstrap: build/finch1 builds the compiler -> build/finch"
|
||||||
|
|
|
||||||
6
bootstrap/pack.sh
Executable file
6
bootstrap/pack.sh
Executable file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# Writes src/embedded.fch: the runtime and the standard library as text, so the compiler can carry them inside itself.
|
||||||
|
# bootstrap/pack.sh <a Finch compiler> [out.fch]
|
||||||
|
set -eu
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
FINCH_RT=$PWD/runtime/finch_rt.c "$1" run tools/pack.fch "${2:-src/embedded.fch}" runtime/finch_rt.c std/*.fch std/*.h
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
//
|
//
|
||||||
// C structs by value appear in types as the named type `module "C"`.
|
// C structs by value appear in types as the named type `module "C"`.
|
||||||
import ast
|
import ast
|
||||||
|
import host
|
||||||
import "clang-c/Index.h"
|
import "clang-c/Index.h"
|
||||||
link "clang"
|
link "clang"
|
||||||
|
|
||||||
|
|
@ -101,8 +102,11 @@ fn fixed(ast.Type elem, int count) -> ast.Type {
|
||||||
// Ask the system C compiler where it looks for headers, so we find the same stdio.h it would.
|
// Ask the system C compiler where it looks for headers, so we find the same stdio.h it would.
|
||||||
fn systemIncludeArgs(str cc) -> []str {
|
fn systemIncludeArgs(str cc) -> []str {
|
||||||
[]str dirs
|
[]str dirs
|
||||||
tmp := "/tmp/finch_cc_dirs.txt"
|
tmp := host.tempDir() + "/finch_cc_dirs_" + str(host.pid()) + ".txt"
|
||||||
shell(cc + " -E -v -x c /dev/null 2> '" + tmp + "' > /dev/null")
|
empty := host.tempDir() + "/finch_empty_" + str(host.pid()) + ".c"
|
||||||
|
write_file(empty, "")
|
||||||
|
host.run(cc + " -E -v -x c " + host.quote(empty) + " 2> " + host.quote(tmp) + " > " + host.nullDevice())
|
||||||
|
host.remove(empty)
|
||||||
if !file_exists(tmp) {
|
if !file_exists(tmp) {
|
||||||
return dirs
|
return dirs
|
||||||
}
|
}
|
||||||
|
|
@ -126,7 +130,7 @@ fn systemIncludeArgs(str cc) -> []str {
|
||||||
dirs.push("-isystem" + l)
|
dirs.push("-isystem" + l)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
shell("rm -f '" + tmp + "'")
|
host.remove(tmp)
|
||||||
return dirs
|
return dirs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
28
src/embedded.fch
Normal file
28
src/embedded.fch
Normal file
File diff suppressed because one or more lines are too long
77
src/host.fch
Normal file
77
src/host.fch
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
// What differs between the systems the compiler itself runs on: how a command line is quoted and run,
|
||||||
|
// where temporary files go, making and removing files. This one is for Linux and macOS (name_windows.fch has
|
||||||
|
// the Windows one).
|
||||||
|
import "stdlib.h"
|
||||||
|
import "unistd.h"
|
||||||
|
|
||||||
|
fn quote(str a) -> str {
|
||||||
|
return "'" + a.replace("'", "'\\''") + "'"
|
||||||
|
}
|
||||||
|
|
||||||
|
// run a command line through the shell; its exit code
|
||||||
|
fn run(str cmd) -> int {
|
||||||
|
return shell(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nullDevice() -> str {
|
||||||
|
return "/dev/null"
|
||||||
|
}
|
||||||
|
|
||||||
|
// the folder for temporary files
|
||||||
|
fn tempDir() -> str {
|
||||||
|
t := getenv("TMPDIR")
|
||||||
|
if t.len > 0 {
|
||||||
|
if t.ends_with("/") {
|
||||||
|
return t.sub(0, t.len - 1)
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
return "/tmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pathSeparator() -> str {
|
||||||
|
return ":"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pid() -> int {
|
||||||
|
return int(getpid())
|
||||||
|
}
|
||||||
|
|
||||||
|
// the folder the compiler was started in
|
||||||
|
fn cwd() -> str {
|
||||||
|
return getenv("PWD")
|
||||||
|
}
|
||||||
|
|
||||||
|
// where files that are made once and kept go
|
||||||
|
fn cacheDir() -> str {
|
||||||
|
x := getenv("XDG_CACHE_HOME")
|
||||||
|
if x.len > 0 {
|
||||||
|
return x + "/finch"
|
||||||
|
}
|
||||||
|
h := getenv("HOME")
|
||||||
|
if h.len > 0 {
|
||||||
|
return h + "/.cache/finch"
|
||||||
|
}
|
||||||
|
return tempDir() + "/finch-cache"
|
||||||
|
}
|
||||||
|
|
||||||
|
// the editor protocol is bytes, not text lines
|
||||||
|
fn binaryStdio() {
|
||||||
|
}
|
||||||
|
|
||||||
|
fn makeDirs(str path) {
|
||||||
|
shell("mkdir -p " + quote(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(str path) {
|
||||||
|
delete_file(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn removeTree(str path) {
|
||||||
|
shell("rm -rf " + quote(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
// move a file over another
|
||||||
|
fn move(str from, str to) {
|
||||||
|
shell("mv " + quote(from) + " " + quote(to))
|
||||||
|
}
|
||||||
97
src/host_windows.fch
Normal file
97
src/host_windows.fch
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
// What differs between the systems the compiler itself runs on: how a command line is quoted and run,
|
||||||
|
// where temporary files go, making and removing files. This one is for Windows (host.fch is for Linux and macOS).
|
||||||
|
import "stdlib.h"
|
||||||
|
import "stdio.h"
|
||||||
|
import "process.h"
|
||||||
|
import "direct.h"
|
||||||
|
import "io.h"
|
||||||
|
|
||||||
|
// a Windows argument: in double quotes, with the quotes inside doubled up the way CreateProcess reads them
|
||||||
|
fn quote(str a) -> str {
|
||||||
|
return "\"" + a.replace("\"", "\\\"") + "\""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paths use / inside the compiler; cmd.exe wants \
|
||||||
|
fn native(str path) -> str {
|
||||||
|
return path.replace("/", "\\")
|
||||||
|
}
|
||||||
|
|
||||||
|
// run a command line through cmd.exe; its exit code.
|
||||||
|
// system() runs `cmd /c <line>`, and cmd drops the first and last quote of a line that starts with one: so the line
|
||||||
|
// gets one more pair around it.
|
||||||
|
fn run(str cmd) -> int {
|
||||||
|
return shell("\"" + cmd + "\"")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nullDevice() -> str {
|
||||||
|
return "NUL"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tempDir() -> str {
|
||||||
|
for name in ["TEMP", "TMP"] {
|
||||||
|
t := getenv(name)
|
||||||
|
if t.len > 0 {
|
||||||
|
return t.replace("\\", "/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pathSeparator() -> str {
|
||||||
|
return ";"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pid() -> int {
|
||||||
|
return int(getpid())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cwd() -> str {
|
||||||
|
here := _getcwd(null, 0) // C allocates the buffer
|
||||||
|
if here.len == 0 {
|
||||||
|
return "."
|
||||||
|
}
|
||||||
|
return here.replace("\\", "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cacheDir() -> str {
|
||||||
|
x := getenv("LOCALAPPDATA")
|
||||||
|
if x.len > 0 {
|
||||||
|
return x.replace("\\", "/") + "/finch"
|
||||||
|
}
|
||||||
|
return tempDir() + "/finch-cache"
|
||||||
|
}
|
||||||
|
|
||||||
|
// the editor protocol is bytes, not text lines: no \r\n translation on stdin and stdout
|
||||||
|
fn binaryStdio() {
|
||||||
|
_setmode(0, 32768) // _O_BINARY
|
||||||
|
_setmode(1, 32768)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn makeDirs(str path) {
|
||||||
|
p := path.replace("\\", "/")
|
||||||
|
at := 0
|
||||||
|
for part in p.split("/") {
|
||||||
|
if at > 0 {
|
||||||
|
at += 1
|
||||||
|
}
|
||||||
|
at += part.len
|
||||||
|
if part.len == 0 || part.ends_with(":") {
|
||||||
|
continue // "" before a leading / and the drive letter
|
||||||
|
}
|
||||||
|
_mkdir(p.sub(0, at).ptr) // fails when it is there already, which is fine
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(str path) {
|
||||||
|
delete_file(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn removeTree(str path) {
|
||||||
|
shell("\"rmdir /s /q " + quote(native(path)) + " 2> NUL\"")
|
||||||
|
}
|
||||||
|
|
||||||
|
// move a file over another
|
||||||
|
fn move(str from, str to) {
|
||||||
|
delete_file(to)
|
||||||
|
rename(from.ptr, to.ptr)
|
||||||
|
}
|
||||||
|
|
@ -2,8 +2,7 @@
|
||||||
// and C files the program asks for, and turn the linker's complaints into something a person can act on.
|
// and C files the program asks for, and turn the linker's complaints into something a person can act on.
|
||||||
import platform
|
import platform
|
||||||
import cimport
|
import cimport
|
||||||
import "stdlib.h"
|
import host
|
||||||
import "unistd.h"
|
|
||||||
|
|
||||||
// 32-bit FNV-1a, as hex: names the cached runtime object after what it was built from
|
// 32-bit FNV-1a, as hex: names the cached runtime object after what it was built from
|
||||||
fn fingerprint(str text) -> str {
|
fn fingerprint(str text) -> str {
|
||||||
|
|
@ -20,20 +19,7 @@ fn fingerprint(str text) -> str {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn quote(str a) -> str {
|
fn quote(str a) -> str {
|
||||||
return "'" + a.replace("'", "'\\''") + "'"
|
return host.quote(a)
|
||||||
}
|
|
||||||
|
|
||||||
// where files that are made once and kept go: ~/.cache/finch
|
|
||||||
fn cacheDir() -> str {
|
|
||||||
x := getenv("XDG_CACHE_HOME")
|
|
||||||
if x.len > 0 {
|
|
||||||
return x + "/finch"
|
|
||||||
}
|
|
||||||
h := getenv("HOME")
|
|
||||||
if h.len > 0 {
|
|
||||||
return h + "/.cache/finch"
|
|
||||||
}
|
|
||||||
return "/tmp/finch-cache"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// "-lname", or whatever pkg-config says the library needs
|
// "-lname", or whatever pkg-config says the library needs
|
||||||
|
|
@ -42,11 +28,11 @@ fn libFlags(str name, platform.Target tg) -> str {
|
||||||
return "-framework " + quote(name.sub(0, name.len - 10)) // macOS: link "Cocoa.framework"
|
return "-framework " + quote(name.sub(0, name.len - 10)) // macOS: link "Cocoa.framework"
|
||||||
}
|
}
|
||||||
if !tg.cross {
|
if !tg.cross {
|
||||||
tmp := "/tmp/finch_pkg_flags.txt"
|
tmp := host.tempDir() + "/finch_pkg_flags_" + str(host.pid()) + ".txt"
|
||||||
code := shell("pkg-config --libs " + quote(name) + " > '" + tmp + "' 2> /dev/null")
|
code := host.run("pkg-config --libs " + quote(name) + " > " + quote(tmp) + " 2> " + host.nullDevice())
|
||||||
if code == 0 && file_exists(tmp) {
|
if code == 0 && file_exists(tmp) {
|
||||||
flags := (read_file(tmp) or "").trim()
|
flags := (read_file(tmp) or "").trim()
|
||||||
shell("rm -f '" + tmp + "'")
|
host.remove(tmp)
|
||||||
if flags.len > 0 {
|
if flags.len > 0 {
|
||||||
return flags
|
return flags
|
||||||
}
|
}
|
||||||
|
|
@ -185,25 +171,27 @@ fn linkFailed(str output, cimport.CImports ci, platform.Target tg) -> int {
|
||||||
// The runtime, compiled once per system and compiler and kept in the cache folder.
|
// The runtime, compiled once per system and compiler and kept in the cache folder.
|
||||||
fn runtimeObject(str rt, platform.Target tg, str cc) -> str! {
|
fn runtimeObject(str rt, platform.Target tg, str cc) -> str! {
|
||||||
source := try read_file(rt)
|
source := try read_file(rt)
|
||||||
dir := cacheDir()
|
dir := host.cacheDir()
|
||||||
obj := dir + "/rt-" + fingerprint(source + cc + tg.triple + "2.5") + ".o"
|
obj := dir + "/rt-" + fingerprint(source + cc + tg.triple + "2.5") + ".o"
|
||||||
if file_exists(obj) {
|
if file_exists(obj) {
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
shell("mkdir -p " + quote(dir))
|
host.makeDirs(dir)
|
||||||
part := obj + ".part" + str(int(getpid()))
|
part := obj + ".part" + str(host.pid())
|
||||||
pic := " -fPIC"
|
pic := " -fPIC"
|
||||||
if tg.windows {
|
if tg.windows {
|
||||||
pic = ""
|
pic = ""
|
||||||
}
|
}
|
||||||
log := part + ".log"
|
log := part + ".log"
|
||||||
code := shell(cc + " -O2" + pic + " -c " + quote(rt) + " -o " + quote(part) + " > " + quote(log) + " 2>&1")
|
code := host.run(cc + " -O2" + pic + " -c " + quote(rt) + " -o " + quote(part) + " > " + quote(log) + " 2>&1")
|
||||||
if code != 0 {
|
if code != 0 {
|
||||||
text := read_file(log) or ""
|
text := read_file(log) or ""
|
||||||
shell("rm -f " + quote(part) + " " + quote(log))
|
host.remove(part)
|
||||||
|
host.remove(log)
|
||||||
return error(text + "error: couldn't build the Finch runtime with '" + cc + "' (is a C compiler installed?)")
|
return error(text + "error: couldn't build the Finch runtime with '" + cc + "' (is a C compiler installed?)")
|
||||||
}
|
}
|
||||||
shell("rm -f " + quote(log) + " && mv " + quote(part) + " " + quote(obj))
|
host.remove(log)
|
||||||
|
host.move(part, obj)
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -242,12 +230,13 @@ fn build(Build b, platform.Target tg, cimport.CImports ci) -> int {
|
||||||
target = " --target=" + tg.triple
|
target = " --target=" + tg.triple
|
||||||
}
|
}
|
||||||
log := b.out + ".log"
|
log := b.out + ".log"
|
||||||
code := shell(clang + target + " " + b.opt + b.extra + " -w -c " + quote(ll) + " -o " + quote(obj) + " > " + quote(log) + " 2>&1")
|
code := host.run(clang + target + " " + b.opt + b.extra + " -w -c " + quote(ll) + " -o " + quote(obj) + " > " + quote(log) + " 2>&1")
|
||||||
shell("rm -f " + quote(ll))
|
host.remove(ll)
|
||||||
if code != 0 {
|
if code != 0 {
|
||||||
print(read_file(log) or "")
|
print(read_file(log) or "")
|
||||||
print("error: clang couldn't compile the program's IR")
|
print("error: clang couldn't compile the program's IR")
|
||||||
shell("rm -f " + quote(obj) + " " + quote(log))
|
host.remove(obj)
|
||||||
|
host.remove(log)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
cmd := cc + " " + quote(obj) + " " + quote(rtobj) + " -o " + quote(b.out)
|
cmd := cc + " " + quote(obj) + " " + quote(rtobj) + " -o " + quote(b.out)
|
||||||
|
|
@ -270,10 +259,12 @@ fn build(Build b, platform.Target tg, cimport.CImports ci) -> int {
|
||||||
if tg.windows {
|
if tg.windows {
|
||||||
pic = ""
|
pic = ""
|
||||||
}
|
}
|
||||||
c := shell(cc + " -O2" + pic + " -c " + quote(l) + " -o " + quote(o) + " > " + quote(log) + " 2>&1")
|
c := host.run(cc + " -O2" + pic + " -c " + quote(l) + " -o " + quote(o) + " > " + quote(log) + " 2>&1")
|
||||||
if c != 0 {
|
if c != 0 {
|
||||||
print((read_file(log) or "") + "error: couldn't compile " + l)
|
print((read_file(log) or "") + "error: couldn't compile " + l)
|
||||||
shell("rm -f " + quote(obj) + " " + quote(log) + " " + quote(o))
|
host.remove(obj)
|
||||||
|
host.remove(log)
|
||||||
|
host.remove(o)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
temps.push(o)
|
temps.push(o)
|
||||||
|
|
@ -284,11 +275,12 @@ fn build(Build b, platform.Target tg, cimport.CImports ci) -> int {
|
||||||
cmd = cmd + " " + libFlags(l, tg)
|
cmd = cmd + " " + libFlags(l, tg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
status := shell(cmd + " > " + quote(log) + " 2>&1")
|
status := host.run(cmd + " > " + quote(log) + " 2>&1")
|
||||||
output := read_file(log) or ""
|
output := read_file(log) or ""
|
||||||
shell("rm -f " + quote(obj) + " " + quote(log))
|
host.remove(obj)
|
||||||
|
host.remove(log)
|
||||||
for t in temps {
|
for t in temps {
|
||||||
shell("rm -f " + quote(t))
|
host.remove(t)
|
||||||
}
|
}
|
||||||
if status == 127 {
|
if status == 127 {
|
||||||
print("error: no C compiler found to link with ('" + cc + "'; install one, or set FINCH_CC)")
|
print("error: no C compiler found to link with ('" + cc + "'; install one, or set FINCH_CC)")
|
||||||
|
|
|
||||||
13
src/lsp.fch
13
src/lsp.fch
|
|
@ -5,9 +5,9 @@
|
||||||
// It asks the compiler instead of being one: for every change it runs `finch index` on the text the editor has
|
// It asks the compiler instead of being one: for every change it runs `finch index` on the text the editor has
|
||||||
// (see the index records in gen.fch), and answers from what comes back.
|
// (see the index records in gen.fch), and answers from what comes back.
|
||||||
import json
|
import json
|
||||||
|
import host
|
||||||
import docs
|
import docs
|
||||||
import "stdio.h"
|
import "stdio.h"
|
||||||
import "unistd.h"
|
|
||||||
import "stdlib.h"
|
import "stdlib.h"
|
||||||
|
|
||||||
// ---------- what one run of the compiler told ----------
|
// ---------- what one run of the compiler told ----------
|
||||||
|
|
@ -325,17 +325,17 @@ fn parseOutput(str output) -> Analysis {
|
||||||
|
|
||||||
fn Server.analyse(str path) {
|
fn Server.analyse(str path) {
|
||||||
// the editor's text, as files the compiler can read: it asks for the file by its real name
|
// the editor's text, as files the compiler can read: it asks for the file by its real name
|
||||||
shell("mkdir -p '" + self.tmp + "'")
|
host.makeDirs(self.tmp)
|
||||||
overlays := ""
|
overlays := ""
|
||||||
n := 0
|
n := 0
|
||||||
for p in self.docs.keys() {
|
for p in self.docs.keys() {
|
||||||
copy := self.tmp + "/doc" + str(n) + ".fch"
|
copy := self.tmp + "/doc" + str(n) + ".fch"
|
||||||
write_file(copy, self.docs[p])
|
write_file(copy, self.docs[p])
|
||||||
overlays = overlays + " --overlay '" + p + "=" + copy + "'"
|
overlays = overlays + " --overlay " + host.quote(p + "=" + copy)
|
||||||
n += 1
|
n += 1
|
||||||
}
|
}
|
||||||
out := self.tmp + "/out.txt"
|
out := self.tmp + "/out.txt"
|
||||||
shell("'" + self.self + "' index '" + path + "'" + overlays + " > '" + out + "' 2>&1")
|
host.run(host.quote(self.self) + " index " + host.quote(path) + overlays + " > " + host.quote(out) + " 2>&1")
|
||||||
a := parseOutput(read_file(out) or "")
|
a := parseOutput(read_file(out) or "")
|
||||||
if !a.diag.has {
|
if !a.diag.has {
|
||||||
self.good[path] = a
|
self.good[path] = a
|
||||||
|
|
@ -863,13 +863,14 @@ fn Server.answer(json.Json msg) {
|
||||||
|
|
||||||
// finch lsp
|
// finch lsp
|
||||||
fn run(str self) -> int {
|
fn run(str self) -> int {
|
||||||
s := Server(self: self, tmp: "/tmp/finch_lsp_" + str(int(getpid())))
|
host.binaryStdio()
|
||||||
|
s := Server(self: self, tmp: host.tempDir() + "/finch_lsp_" + str(host.pid()))
|
||||||
while true {
|
while true {
|
||||||
msg := readMessage() or {
|
msg := readMessage() or {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
s.answer(msg)
|
s.answer(msg)
|
||||||
}
|
}
|
||||||
shell("rm -rf '" + s.tmp + "'")
|
host.removeTree(s.tmp)
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
35
src/main.fch
35
src/main.fch
|
|
@ -13,6 +13,8 @@ import cimport
|
||||||
import platform
|
import platform
|
||||||
import linker
|
import linker
|
||||||
import lsp
|
import lsp
|
||||||
|
import host
|
||||||
|
import embedded
|
||||||
import "stdlib.h"
|
import "stdlib.h"
|
||||||
|
|
||||||
fn dirOf(str path) -> str {
|
fn dirOf(str path) -> str {
|
||||||
|
|
@ -129,6 +131,23 @@ fn usage() {
|
||||||
exit(1)
|
exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The runtime and the standard library are carried inside the compiler (embedded.fch). When they aren't found as
|
||||||
|
// files next to it, they are written to the cache folder, once, and used from there.
|
||||||
|
fn unpack() -> str {
|
||||||
|
names := embedded.names()
|
||||||
|
dir := host.cacheDir() + "/files-" + linker.fingerprint(embedded.texts().join("\n"))
|
||||||
|
if file_exists(dir + "/done") {
|
||||||
|
return dir
|
||||||
|
}
|
||||||
|
texts := embedded.texts()
|
||||||
|
for i in 0..names.len {
|
||||||
|
host.makeDirs(dirOf(dir + "/" + names[i]))
|
||||||
|
write_file(dir + "/" + names[i], texts[i])
|
||||||
|
}
|
||||||
|
write_file(dir + "/done", "")
|
||||||
|
return dir
|
||||||
|
}
|
||||||
|
|
||||||
fn main([]str args) -> int {
|
fn main([]str args) -> int {
|
||||||
if args.len == 2 && (args[1] == "version" || args[1] == "--version") {
|
if args.len == 2 && (args[1] == "version" || args[1] == "--version") {
|
||||||
print("Finch 2.4.0 (the compiler is written in Finch)")
|
print("Finch 2.4.0 (the compiler is written in Finch)")
|
||||||
|
|
@ -196,7 +215,7 @@ fn main([]str args) -> int {
|
||||||
}
|
}
|
||||||
tg := platform.select(targetName)
|
tg := platform.select(targetName)
|
||||||
if cmd == "run" {
|
if cmd == "run" {
|
||||||
out = "/tmp/finch_run_" + stem(file) + tg.exe
|
out = host.tempDir() + "/finch_run_" + str(host.pid()) + "_" + stem(file) + tg.exe
|
||||||
} else if shared && !outGiven {
|
} else if shared && !outGiven {
|
||||||
if tg.windows {
|
if tg.windows {
|
||||||
out = out + ".dll"
|
out = out + ".dll"
|
||||||
|
|
@ -221,9 +240,12 @@ fn main([]str args) -> int {
|
||||||
loader.std = cand
|
loader.std = cand
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if loader.std.len == 0 {
|
||||||
|
loader.std = unpack() + "/std"
|
||||||
|
}
|
||||||
loader.overlayPaths = overlayPaths
|
loader.overlayPaths = overlayPaths
|
||||||
loader.overlayFiles = overlayFiles
|
loader.overlayFiles = overlayFiles
|
||||||
for d in getenv("FINCH_PATH").split(":") {
|
for d in getenv("FINCH_PATH").split(host.pathSeparator()) {
|
||||||
if d.len > 0 {
|
if d.len > 0 {
|
||||||
loader.paths.push(d)
|
loader.paths.push(d)
|
||||||
}
|
}
|
||||||
|
|
@ -262,7 +284,7 @@ fn main([]str args) -> int {
|
||||||
print(err)
|
print(err)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
ir := gen.generate(loader.progs, loader.files, ci, gen.Options(shared: shared, debug: debug, index: cmd == "index", cwd: getenv("PWD"), target: tg))
|
ir := gen.generate(loader.progs, loader.files, ci, gen.Options(shared: shared, debug: debug, index: cmd == "index", cwd: host.cwd(), target: tg))
|
||||||
if cmd == "ir" || cmd == "index" {
|
if cmd == "ir" || cmd == "index" {
|
||||||
print(ir)
|
print(ir)
|
||||||
return 0
|
return 0
|
||||||
|
|
@ -283,8 +305,7 @@ fn main([]str args) -> int {
|
||||||
rt = "runtime/finch_rt.c"
|
rt = "runtime/finch_rt.c"
|
||||||
}
|
}
|
||||||
if !file_exists(rt) {
|
if !file_exists(rt) {
|
||||||
print("error: can't find the Finch runtime (runtime/finch_rt.c)")
|
rt = unpack() + "/runtime/finch_rt.c"
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
extra := ""
|
extra := ""
|
||||||
if shared {
|
if shared {
|
||||||
|
|
@ -301,8 +322,8 @@ fn main([]str args) -> int {
|
||||||
for a in progArgs {
|
for a in progArgs {
|
||||||
quoted.push(linker.quote(a))
|
quoted.push(linker.quote(a))
|
||||||
}
|
}
|
||||||
status := shell(tg.runner + linker.quote(out) + " " + quoted.join(" "))
|
status := host.run(tg.runner + linker.quote(out) + " " + quoted.join(" "))
|
||||||
shell("rm -f " + linker.quote(out))
|
host.remove(out)
|
||||||
return status
|
return status
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// The system a program is built for, and what depends on it: the C compiler that links, the file
|
// The system a program is built for, and what depends on it: the C compiler that links, the file
|
||||||
// name of the program, how it is run here, and (through the flags) the C calling convention.
|
// name of the program, how it is run here, and (through the flags) the C calling convention.
|
||||||
// The same as src/target.cpp.
|
// The same as src/target.cpp.
|
||||||
|
import host
|
||||||
import "stdlib.h"
|
import "stdlib.h"
|
||||||
|
|
||||||
struct Target {
|
struct Target {
|
||||||
|
|
@ -22,10 +23,10 @@ fn firstLine(str text) -> str {
|
||||||
|
|
||||||
// what clang calls the system it runs on, like x86_64-pc-linux-gnu
|
// what clang calls the system it runs on, like x86_64-pc-linux-gnu
|
||||||
fn hostTriple() -> str {
|
fn hostTriple() -> str {
|
||||||
tmp := "/tmp/finch_triple.txt"
|
tmp := host.tempDir() + "/finch_triple_" + str(host.pid()) + ".txt"
|
||||||
shell("clang -dumpmachine > '" + tmp + "' 2> /dev/null")
|
host.run("clang -dumpmachine > " + host.quote(tmp) + " 2> " + host.nullDevice())
|
||||||
out := read_file(tmp) or ""
|
out := read_file(tmp) or ""
|
||||||
shell("rm -f '" + tmp + "'")
|
host.remove(tmp)
|
||||||
if out.trim().len == 0 {
|
if out.trim().len == 0 {
|
||||||
return "x86_64-unknown-linux-gnu"
|
return "x86_64-unknown-linux-gnu"
|
||||||
}
|
}
|
||||||
|
|
@ -34,10 +35,10 @@ fn hostTriple() -> str {
|
||||||
|
|
||||||
// "windows", "linux", "macos", "arm64" (ARM64 Linux), or a full LLVM triple; "" = this system
|
// "windows", "linux", "macos", "arm64" (ARM64 Linux), or a full LLVM triple; "" = this system
|
||||||
fn select(str name) -> Target {
|
fn select(str name) -> Target {
|
||||||
host := hostTriple()
|
hostName := hostTriple()
|
||||||
t := name
|
t := name
|
||||||
if name.len == 0 {
|
if name.len == 0 {
|
||||||
t = host
|
t = hostName
|
||||||
} else if name == "windows" {
|
} else if name == "windows" {
|
||||||
t = "x86_64-w64-mingw32"
|
t = "x86_64-w64-mingw32"
|
||||||
} else if name == "linux" {
|
} else if name == "linux" {
|
||||||
|
|
@ -45,7 +46,7 @@ fn select(str name) -> Target {
|
||||||
} else if name == "arm64" || name == "aarch64" || name == "linux-arm64" {
|
} else if name == "arm64" || name == "aarch64" || name == "linux-arm64" {
|
||||||
t = "aarch64-unknown-linux-gnu"
|
t = "aarch64-unknown-linux-gnu"
|
||||||
} else if name == "macos" {
|
} else if name == "macos" {
|
||||||
if host.starts_with("aarch64") || host.starts_with("arm64") {
|
if hostName.starts_with("aarch64") || hostName.starts_with("arm64") {
|
||||||
t = "arm64-apple-macosx14.0.0"
|
t = "arm64-apple-macosx14.0.0"
|
||||||
} else {
|
} else {
|
||||||
t = "x86_64-apple-macosx14.0.0"
|
t = "x86_64-apple-macosx14.0.0"
|
||||||
|
|
@ -56,8 +57,8 @@ fn select(str name) -> Target {
|
||||||
tg.msvc = t.contains("msvc")
|
tg.msvc = t.contains("msvc")
|
||||||
tg.darwin = t.contains("apple") || t.contains("darwin") || t.contains("macos")
|
tg.darwin = t.contains("apple") || t.contains("darwin") || t.contains("macos")
|
||||||
tg.arm64 = t.starts_with("aarch64") || t.starts_with("arm64")
|
tg.arm64 = t.starts_with("aarch64") || t.starts_with("arm64")
|
||||||
hostArm := host.starts_with("aarch64") || host.starts_with("arm64")
|
hostArm := hostName.starts_with("aarch64") || hostName.starts_with("arm64")
|
||||||
hostDarwin := host.contains("apple") || host.contains("darwin")
|
hostDarwin := hostName.contains("apple") || hostName.contains("darwin")
|
||||||
tg.cross = tg.windows || tg.darwin != hostDarwin || tg.arm64 != hostArm
|
tg.cross = tg.windows || tg.darwin != hostDarwin || tg.arm64 != hostArm
|
||||||
tg.exe = ""
|
tg.exe = ""
|
||||||
if tg.windows {
|
if tg.windows {
|
||||||
|
|
@ -73,6 +74,8 @@ fn select(str name) -> Target {
|
||||||
tg.cc = "aarch64-linux-gnu-gcc"
|
tg.cc = "aarch64-linux-gnu-gcc"
|
||||||
} else if tg.cross {
|
} else if tg.cross {
|
||||||
tg.cc = "clang --target=" + t
|
tg.cc = "clang --target=" + t
|
||||||
|
} else if host.pathSeparator() == ";" {
|
||||||
|
tg.cc = "clang" // Windows has no cc
|
||||||
} else {
|
} else {
|
||||||
tg.cc = "cc"
|
tg.cc = "cc"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
tests/run.sh
10
tests/run.sh
|
|
@ -10,6 +10,7 @@ set -u
|
||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")"
|
||||||
FINCH=${FINCH:-../build/finch}
|
FINCH=${FINCH:-../build/finch}
|
||||||
FINCH="$(cd "$(dirname "$FINCH")" && pwd)/$(basename "$FINCH")" # tests/shared.sh runs from another folder
|
FINCH="$(cd "$(dirname "$FINCH")" && pwd)/$(basename "$FINCH")" # tests/shared.sh runs from another folder
|
||||||
|
FINCH_ROOT="$(cd .. && pwd)"
|
||||||
export FINCH_RT=${FINCH_RT:-$PWD/../runtime/finch_rt.c}
|
export FINCH_RT=${FINCH_RT:-$PWD/../runtime/finch_rt.c}
|
||||||
pass=0; failed=0
|
pass=0; failed=0
|
||||||
tmp=$(mktemp -d)
|
tmp=$(mktemp -d)
|
||||||
|
|
@ -78,6 +79,15 @@ else
|
||||||
head -5 "$tmp/shared.out"
|
head -5 "$tmp/shared.out"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# the runtime and std carried inside the compiler are the files as they are now
|
||||||
|
"$FINCH_ROOT/bootstrap/pack.sh" "$FINCH" "$tmp/embedded.fch" 2>/dev/null
|
||||||
|
if cmp -s "$tmp/embedded.fch" "$FINCH_ROOT/src/embedded.fch"; then
|
||||||
|
pass=$((pass + 1))
|
||||||
|
else
|
||||||
|
failed=$((failed + 1))
|
||||||
|
echo "FAIL src/embedded.fch is out of date (run bootstrap/build.sh)"
|
||||||
|
fi
|
||||||
|
|
||||||
# the language server, talked to the way an editor does
|
# the language server, talked to the way an editor does
|
||||||
if command -v python3 > /dev/null 2>&1; then
|
if command -v python3 > /dev/null 2>&1; then
|
||||||
if FINCH="$FINCH" python3 lsp_test.py > "$tmp/lsp.out" 2>&1; then
|
if FINCH="$FINCH" python3 lsp_test.py > "$tmp/lsp.out" 2>&1; then
|
||||||
|
|
|
||||||
54
tools/pack.fch
Normal file
54
tools/pack.fch
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
// finch run tools/pack.fch src/embedded.fch runtime/finch_rt.c std/json.fch ...
|
||||||
|
// Writes a Finch module that holds the given files as text, so the compiler can carry its runtime and standard library
|
||||||
|
// inside itself (bootstrap/build.sh runs this; tests/run.sh checks the result is up to date).
|
||||||
|
|
||||||
|
fn literal(str text) -> str {
|
||||||
|
[]str parts = ["\""]
|
||||||
|
for c in text {
|
||||||
|
if c == '\\' {
|
||||||
|
parts.push("\\\\")
|
||||||
|
} else if c == '"' {
|
||||||
|
parts.push("\\\"")
|
||||||
|
} else if c == '\n' {
|
||||||
|
parts.push("\\n")
|
||||||
|
} else if c == '\t' {
|
||||||
|
parts.push("\\t")
|
||||||
|
} else if c == '\r' {
|
||||||
|
parts.push("\\r")
|
||||||
|
} else {
|
||||||
|
parts.push(str(c))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parts.push("\"")
|
||||||
|
return parts.join("")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main([]str args) -> int {
|
||||||
|
if args.len < 3 {
|
||||||
|
print("usage: pack out.fch file...")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
[]str out = [
|
||||||
|
"// The runtime and the standard library, as text. Made by tools/pack.fch: don't edit it, run bootstrap/build.sh.",
|
||||||
|
"// Used when the compiler can't find runtime/ and std/ next to itself.",
|
||||||
|
"",
|
||||||
|
"fn names() -> []str {",
|
||||||
|
" []str list",
|
||||||
|
]
|
||||||
|
for i in 2..args.len {
|
||||||
|
out.push(" list.push(" + literal(args[i]) + ")")
|
||||||
|
}
|
||||||
|
out.push(" return list")
|
||||||
|
out.push("}")
|
||||||
|
out.push("")
|
||||||
|
out.push("fn texts() -> []str {")
|
||||||
|
out.push(" []str list")
|
||||||
|
for i in 2..args.len {
|
||||||
|
out.push(" list.push(" + literal(read_file(args[i])) + ")")
|
||||||
|
}
|
||||||
|
out.push(" return list")
|
||||||
|
out.push("}")
|
||||||
|
out.push("")
|
||||||
|
write_file(args[1], out.join("\n"))
|
||||||
|
return 0
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue