a standard library in Finch: path, time, proc, json

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.
This commit is contained in:
Nexoniarz 2026-10-25 20:58:26 +01:00
commit ea6d2a57e5
13 changed files with 929 additions and 9 deletions

View file

@ -193,7 +193,7 @@ fn visitField(CXCursor field, ptr data) -> i32 {
} else if ft.kind == CXType_Record && clang_getCursorKind(clang_getTypeDeclaration(ft)) == CXCursor_UnionDecl {
f.hidden = true // unions are kept as opaque bytes
} else {
m := mapType(c.imp, ft, false)
m := mapType(c.imp, ft, false, false)
if m.ok {
f.ty = m.ty
} else {
@ -242,7 +242,9 @@ fn sized(int size, bool signed) -> Mapped {
// C type -> Finch type. Not ok (with a reason) if Finch can't express it.
// `signature`: a char * is a str (a function's parameters and result); anywhere else it stays a ptr[char].
fn mapType(ptr[Importer] imp, CXType type, bool signature) -> Mapped {
// A parameter that is a plain `char *` (not const) is a buffer C writes into, and stays a ptr[char];
// only `const char *` is text going in. A result that is a char * is always text coming out.
fn mapType(ptr[Importer] imp, CXType type, bool signature, bool param) -> Mapped {
t := clang_getCanonicalType(type)
size := int(clang_Type_getSizeOf(t))
k := t.kind
@ -273,12 +275,14 @@ fn mapType(ptr[Importer] imp, CXType type, bool signature) -> Mapped {
if k == CXType_Pointer {
pointee := clang_getCanonicalType(clang_getPointeeType(t))
if (pointee.kind == CXType_Char_S || pointee.kind == CXType_Char_U) && signature {
return ok(ast.simple("str")) // char* in a signature is a C string
if !param || clang_isConstQualifiedType(pointee) != 0 {
return ok(ast.simple("str")) // a C string
}
}
if pointee.kind == CXType_Void || pointee.kind == CXType_FunctionProto || pointee.kind == CXType_FunctionNoProto {
return ok(ast.simple("ptr")) // an opaque `ptr` you can pass around
}
inner := mapType(imp, pointee, false)
inner := mapType(imp, pointee, false, false)
if !inner.ok || inner.ty.kind == "void" || inner.ty.kind == "fixed" {
return ok(ast.simple("ptr"))
}
@ -295,7 +299,7 @@ fn mapType(ptr[Importer] imp, CXType type, bool signature) -> Mapped {
return ok(ast.Type(kind: "named", name: name, module: "C"))
}
if k == CXType_ConstantArray {
inner := mapType(imp, clang_getArrayElementType(t), false)
inner := mapType(imp, clang_getArrayElementType(t), false, false)
if !inner.ok {
return inner
}
@ -447,7 +451,7 @@ fn visit(ptr[Importer] imp, CXCursor c) {
imp.out.fns[name] = f
return
}
r := mapType(imp, clang_getResultType(ft), true)
r := mapType(imp, clang_getResultType(ft), true, false)
if r.ok {
f.ret = r.ty
} else {
@ -458,7 +462,7 @@ fn visit(ptr[Importer] imp, CXCursor c) {
if f.unsupported.len > 0 {
break
}
p := mapType(imp, clang_getArgType(ft, u32(k)), true)
p := mapType(imp, clang_getArgType(ft, u32(k)), true, true)
if p.ok {
f.params.push(p.ty)
} else {
@ -471,7 +475,7 @@ fn visit(ptr[Importer] imp, CXCursor c) {
if clang_Cursor_getStorageClass(c) != CX_SC_Extern || imp.out.globals.has(name) {
return
}
g := mapType(imp, clang_getCursorType(c), false)
g := mapType(imp, clang_getCursorType(c), false, false)
if g.ok {
imp.out.globals[name] = g.ty
}

View file

@ -3729,6 +3729,14 @@ fn call(ptr[Gen] g, ast.Node c) -> Val {
} else if k == "bool" {
z := op(g, "zext i1 " + v.v + " to i32")
emit(g, "call void @finch_str_from_bool(ptr " + out + ", i32 " + z + ")")
} else if k == "ptr" {
// a C string, copied
store(g, "{ ptr, i64, i64 }", "zeroinitializer", out)
emit(g, "call void @finch_str_from_c(ptr " + out + ", ptr " + v.v + ")")
} else if k == "array" && (v.ty.elem[0].kind == "u8" || v.ty.elem[0].kind == "char") {
// bytes become text
store(g, "{ ptr, i64, i64 }", "zeroinitializer", out)
emit(g, "call void @finch_str_from_bytes(ptr " + out + ", ptr " + spill(g, v) + ")")
} else if k == "bool" || k == "char" || k == "array" || k == "map" || k == "struct" || k == "enum" {
store(g, "{ ptr, i64, i64 }", "zeroinitializer", out)
emitFormat(g, out, v.v, v.ty, false)
@ -4430,6 +4438,7 @@ fn runtimeDecls() -> str {
"declare i32 @printf(ptr, ...)",
"declare i32 @fflush(ptr)",
"declare void @finch_str_from_c(ptr, ptr)",
"declare void @finch_str_from_bytes(ptr, ptr)",
"declare void @exit(i32) noreturn",
"declare void @free(ptr)",
]

View file

@ -38,6 +38,21 @@ 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) {
@ -60,7 +75,7 @@ fn load(ptr[Loader] l, str path, str module) {
for name in imports {
if !l.loaded.contains(name) {
l.loaded.push(name)
load(l, dirOf(path) + "/" + name + ".fch", name)
load(l, findModule(l, path, name), name)
}
}
}
@ -149,6 +164,11 @@ fn main([]str args) -> int {
}
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"