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

35
tests/new/std_json.fch Normal file
View file

@ -0,0 +1,35 @@
import json
fn main() -> ! {
doc := try json.parse("{\"name\": \"Finch\", \"tags\": [\"fast\", \"small\"], \"stars\": 5, \"pi\": 3.25, \"ok\": true, \"none\": null, \"nested\": {\"deep\": [1, [2, 3]]}}")
print(doc.get("name").str_or(""), doc.get("tags").at(1).str_or(""), doc.get("stars").int_or(0), doc.get("pi").float_or(0.0))
print(doc.get("ok").bool_or(false), doc.get("none").is_null(), doc.get("missing").is_null(), doc.has("name"), doc.has("nope"))
print(doc.get("nested").get("deep").at(1).at(0).int_or(-1), doc.get("tags").len(), doc.names())
print(json.stringify(doc))
out := json.new_object()
out.set("greeting", json.new_text("hi \"there\"\n"))
list := json.new_list()
list.push(json.new_number(1))
list.push(json.new_number(2.5))
list.push(json.new_bool(false))
list.push(json.new_null())
out.set("values", list)
out.set("greeting", json.new_text("replaced"))
out.set("empty", json.new_object())
print(json.stringify(out))
print(json.pretty(out))
again := try json.parse(json.pretty(out))
print(json.stringify(again) == json.stringify(out))
print((try json.parse("\"caf\\u00e9 \\ud83d\\ude00 \\/\"")).str_or(""))
print((try json.parse(" [ ] ")).len(), (try json.parse("-12.5e1")).float_or(0.0))
for bad in ["{\"a\": }", "[1, 2", "{\"a\" 1}", "tru", "[1] x", "\"open", "", "[1,\n oops]"] {
json.parse(bad) or {
print(err)
continue
}
print("parsed?!", bad)
}
}

26
tests/new/std_json.out Normal file
View file

@ -0,0 +1,26 @@
Finch small 5 3.25
true true true true false
2 2 ["name", "tags", "stars", "pi", "ok", "none", "nested"]
{"name":"Finch","tags":["fast","small"],"stars":5,"pi":3.25,"ok":true,"none":null,"nested":{"deep":[1,[2,3]]}}
{"greeting":"replaced","values":[1,2.5,false,null],"empty":{}}
{
"greeting": "replaced",
"values": [
1,
2.5,
false,
null
],
"empty": {}
}
true
café 😀 /
0 -125
json: found '}' where a value should be (line 1, column 7)
json: the text ends where ',' or ']' should be (line 1, column 6)
json: found '1' where ':' should be (line 1, column 6)
json: found 't' where 'true' should be (line 1, column 1)
json: found 'x' where the end of the text should be (line 1, column 5)
json: a text is never closed (line 1, column 6)
json: the text ends where a value should be (line 1, column 1)
json: found 'o' where a value should be (line 2, column 3)

10
tests/new/std_path.fch Normal file
View file

@ -0,0 +1,10 @@
import path
fn main() {
print(path.join("a", "b.txt"), path.join("a/", "b"), path.join("", "x"), path.join("a", "/abs"))
print(path.base("/tmp/x/a.tar.gz"), path.base("a/b/"), path.base(""), path.base("/"))
print(path.dir("/tmp/x/a.txt"), path.dir("b.txt"), path.dir("/b"), path.dir("a/b/"))
print(path.ext("a.tar.gz"), path.ext("noext"), path.ext(".bashrc"), path.stem("/x/a.tar.gz"))
print(path.clean("a//b/../c/./d"), path.clean("/../a"), path.clean("../../a"), path.clean(""), path.clean("a/.."))
print(path.parts("/a//b/c/"), path.is_abs("/a"), path.is_abs("a"))
}

6
tests/new/std_path.out Normal file
View file

@ -0,0 +1,6 @@
a/b.txt a/b x /abs
a.tar.gz b . /
/tmp/x . / a
.gz a.tar
a/c/d /a ../../a . .
["a", "b", "c"] true false

View file

@ -0,0 +1,19 @@
import time
import proc
fn main() -> ! {
d := time.date(86400 * 365 + 3661)
print(d.year, d.month, d.day, d.hour, d.minute, d.second, d.weekday, d.yearday)
print(time.days_in_month(2024, 2), time.days_in_month(2023, 2), time.days_in_month(1900, 2), time.days_in_month(2000, 2), time.days_in_month(2024, 4))
a := time.millis()
time.sleep(30)
waited := time.millis() - a
print(waited >= 25 && waited < 500)
print(time.now() > 1700000000, time.format(0, "%Y") == "1970" || time.format(0, "%Y") == "1969")
print((try proc.output("echo hello; echo world")).trim())
print(proc.run("exit 3"))
bad := proc.output("echo oops; exit 2") or err
print(bad)
print(proc.env("FINCH_SURELY_NOT_SET", "fallback"), proc.pid() > 0, proc.cwd().len > 0)
}

View file

@ -0,0 +1,9 @@
1971 1 1 1 1 1 5 1
29 28 28 29 30
true
true true
hello
world
3
'echo oops; exit 2' exited with code 2: oops
fallback true true