finch.toml, threads and atomics, finch fmt, C++ files, struct #defines, a struct variable given for its ptr

- finch.toml: name, entry, paths, include, links, libdirs, cflags, ldflags, and [target.windows] etc.;
  finch run / build / test with no file use its entry
- import thread: spawn, join, mutex, cpus; atomic_add/sub/load/store/swap/cas built in
- finch fmt [--check]: indentation by { }, trailing spaces, empty lines; also as LSP formatting
- link "x.cpp" (or in finch.toml): built with the C++ compiler, which then links (FINCH_CXX)
- #define RAYWHITE (Color){ 245, 245, 245, 255 } imports as a Color
- push(body) where push takes ptr[Body]: the same as push(addr(body))
- --target android (NDK clang from ANDROID_NDK_HOME, -llog)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016GzNP6r8dg6WDxUVZy8wjL
This commit is contained in:
Nexoniarz 2026-10-29 11:17:10 +01:00
commit d428498efd
40 changed files with 1398 additions and 18 deletions

View file

@ -124,6 +124,12 @@ syms = c.request("textDocument/documentSymbol", {"textDocument": td})
names = sorted(s["name"] for s in syms)
check("document symbols", names == ["Point", "add", "main"], names)
messy = "fn main() {\n x := 1 \n\n\n\tprint(x)\n}"
c.notify("textDocument/didChange", {"textDocument": {"uri": uri, "version": 5}, "contentChanges": [{"text": messy}]})
fm = c.request("textDocument/formatting", {"textDocument": td, "options": {"tabSize": 4, "insertSpaces": True}})
check("formatting", fm and fm[0]["newText"] == "fn main() {\n x := 1\n\n print(x)\n}\n", fm)
c.notify("textDocument/didChange", {"textDocument": {"uri": uri, "version": 5}, "contentChanges": [{"text": src}]})
bad = src.replace("total := add(p.x, p.y)", 'total := add(p.x, "y")')
c.notify("textDocument/didChange", {"textDocument": {"uri": uri, "version": 6}, "contentChanges": [{"text": bad}]})
diags = c.diagnostics(uri)

23
tests/new/auto_addr.fch Normal file
View file

@ -0,0 +1,23 @@
struct Body {
float x
float y
}
fn push(ptr[Body] b, float dx) {
b.x += dx
}
fn Body.moved(float dx) -> Body {
return Body(x: self.x + dx, y: self.y)
}
fn main() {
b := Body(x: 1.0, y: 2.0)
push(b, 0.5) // the same as push(addr(b), 0.5)
push(addr(b), 0.5)
print(b.x)
p := addr(b)
push(p, 1.0) // already a ptr: as it is
print(b.x)
print(b.moved(1.0).x)
}

3
tests/new/auto_addr.out Normal file
View file

@ -0,0 +1,3 @@
2
3
4

View file

@ -24,4 +24,6 @@ fn main() {
t.as.f = 0.5
print(t.as.f)
print(abs(-2.5), abs(-3), min(EV_KEY, 7)) // Finch's abs, not C's int-only one
bg := RAYWHITE // #define RAYWHITE (Color){ 245, 245, 245, 255 }
print(bg.r, bg.a, DARK.g, DARK.a, HALF_UP.x, HALF_UP.y)
}

View file

@ -5,3 +5,4 @@ true 32
32 2
0.5
2.5 3 7
245 255 20 0 0.5 -1

View file

@ -22,6 +22,14 @@ typedef struct { int kind; union { int i; float f; } as; } Tagged;
#define LONG_NAME "a" "b"
#define NOT_A_CONSTANT do { } while (0)
typedef struct { unsigned char r, g, b, a; } Color;
typedef struct { float x, y; } Vec2;
#define CLITERAL(type) (type)
#define RAYWHITE CLITERAL(Color){ 245, 245, 245, 255 }
#define DARK (Color){ 20, 20, 20 }
#define HALF_UP (Vec2){ 0.5f, -1 }
#define NOT_A_STRUCT_CONST (Color){ 1, 2, (unsigned char)(long)&poll_event, 4 }
static inline int fill_key(Event *e, int key) {
e->key.type = EV_KEY;
e->key.key = key;

View file

@ -0,0 +1,13 @@
// expect: argument 'b' must be ptr[Body], but this is Body
struct Body {
float x
}
fn push(ptr[Body] b) {
b.x += 1.0
}
fn make() -> Body {
return Body(x: 1.0)
}
fn main() {
push(make())
}

View file

@ -0,0 +1,21 @@
// "\(x)" in text
struct P {
int x
float y
}
fn main() {
name := "Ada"
hp := 7
p := P(x: 3, y: 0.25)
print("hi \(name), hp \(hp), next \(hp + 1)")
print("p = (\(p.x), \(p.y, 2)) \("nested \(name)")")
map[str]int m
m["k"] = 5
print("\(m["k"]) \(str(1.0 / 3.0, 3))")
print("no code here: \\(x) and \t tab")
long := """
speed \(p.y * 4.0)
"""
print(long)
}

View file

@ -0,0 +1,5 @@
hi Ada, hp 7, next 8
p = (3, 0.25) nested Ada
5 0.333
no code here: \(x) and tab
speed 1

55
tests/new/threads.fch Normal file
View file

@ -0,0 +1,55 @@
import thread
struct Shared {
[]int seen
}
struct Job {
ptr[int] counter
int adds
ptr[thread.Mutex] lock
ptr[Shared] common
int id
}
fn work(ptr arg) {
ptr[Job] job = arg
for i in 0..job.adds {
atomic_add(job.counter, 1)
}
job.lock.lock()
job.common.seen.push(job.id)
job.lock.unlock()
}
fn main() {
count := 0
m := thread.mutex()
defer m.close()
both := Shared()
[]Job jobs
for i in 0..4 {
jobs.push(Job(counter: addr(count), adds: 100000, lock: addr(m), common: addr(both), id: i))
}
[]thread.Thread ts
for i in 0..4 {
ts.push(thread.spawn(addr(work), addr(jobs[i])) or {
print("can't start")
exit(1)
})
}
for i in 0..4 {
ts[i].join()
}
print(atomic_load(addr(count)))
print(both.seen.len)
print(thread.cpus() > 0)
u32 flags = 5
print(atomic_swap(addr(flags), 9))
print(atomic_cas(addr(flags), 9, 1))
print(atomic_cas(addr(flags), 9, 2))
atomic_store(addr(flags), 7)
print(atomic_sub(addr(flags), 2))
print(flags)
}

8
tests/new/threads.out Normal file
View file

@ -0,0 +1,8 @@
400000
4
true
5
true
false
7
5

View file

@ -56,6 +56,24 @@ for f in fail/*.fch new/fail/*.fch; do
fi
done
# the commands around the compiler: finch test, the chain of calls, finch fmt, a finch.toml project
check_tool() { # name, what it printed, the file with what it should print
if [ "$2" == "$(tr -d '\r' < "$3")" ]; then
pass=$((pass + 1))
else
failed=$((failed + 1))
echo "FAIL $1"
diff <(echo "$2") "$3" | head -20
fi
}
check_tool "finch test" "$(cd tool && "$FINCH" test test_blocks.fch 2>&1 | tr -d '\r')" tool/test_blocks.out
check_tool "the chain of calls" "$(cd tool && "$FINCH" run trace.fch 2>&1 | tr -d '\r')" tool/trace.out
cp tool/fmt_in.fch "$tmp/fmt.fch"
"$FINCH" fmt "$tmp/fmt.fch"
check_tool "finch fmt" "$(cat "$tmp/fmt.fch")" tool/fmt_out.fch
check_tool "finch fmt (again)" "$("$FINCH" fmt --check tool/fmt_out.fch 2>&1)" /dev/null
check_tool "finch.toml" "$(cd tool/project && "$FINCH" run 2>&1 | tr -d '\r')" tool/project/expected.out
# the same programs with debug info (-g -O0): the metadata has to be valid for every kind of thing a program has
for f in new/enums.fch new/generics.fch new/std_json.fch run/basics.fch run/methods_basic.fch run/maps.fch run/errors.fch \
run/defer.fch run/c_structs.fch run/memory.fch; do

24
tests/tool/fmt_in.fch Normal file
View file

@ -0,0 +1,24 @@
struct P {
int x
int y
}
fn main() {
p := P(x: 1,
y: 2)
msg := """
a {
b
"""
if p.x > 0 {
print("{ \(p.x) }") // } not counted
} else {
print(msg)
}
s := "a" +
"b"
print(s)
}

21
tests/tool/fmt_out.fch Normal file
View file

@ -0,0 +1,21 @@
struct P {
int x
int y
}
fn main() {
p := P(x: 1,
y: 2)
msg := """
a {
b
"""
if p.x > 0 {
print("{ \(p.x) }") // } not counted
} else {
print(msg)
}
s := "a" +
"b"
print(s)
}

View file

@ -0,0 +1 @@
hello world 42

View file

@ -0,0 +1,14 @@
# a project: finch run / build / test here need no file and no flags
name = "mygame"
entry = "src/main.fch"
paths = ["src"]
include = ["native"]
links = [
"native/glue.cpp", # C++, built with the C++ compiler
]
cflags = ["-DGLUE_BASE=40"]
[target.linux]
links = ["m"]
[target.windows]
links = ["ws2_32"]

View file

@ -0,0 +1,9 @@
// C++ behind a C header: extern "C" gives the function a name C (and Finch) can call
#include "glue.h"
#include <numeric>
#include <vector>
extern "C" int glue_answer(void) {
std::vector<int> v = {1, 1};
return GLUE_BASE + std::accumulate(v.begin(), v.end(), 0);
}

View file

@ -0,0 +1,7 @@
#ifdef __cplusplus
extern "C" {
#endif
int glue_answer(void);
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,3 @@
fn hello() -> str {
return "world"
}

View file

@ -0,0 +1,6 @@
import "glue.h"
import game/world
fn main() {
print("hello \(world.hello()) \(glue_answer())")
}

View file

@ -0,0 +1,30 @@
// finch test: each test "..." block runs on its own; a failed assert or a runtime error fails only that one
fn add(int a, int b) -> int {
return a + b
}
fn second([]int xs) -> int {
return xs[1]
}
test "add works" {
assert(add(2, 3) == 5)
assert(add(-1, 1) == 0, "zero")
}
test "a failing assert" {
x := add(1, 2)
assert(x == 4, "x is \(x), not 4")
}
test "a runtime error fails only this test" {
print(second([1]))
}
test "still runs after the others" {
assert("\(1.5, 2) and \(add(1, 1))" == "1.50 and 2")
}
fn main() {
print("main is not run by finch test")
}

View file

@ -0,0 +1,8 @@
ok add works
FAIL a failing assert
test_blocks.fch:17: runtime error: assert failed: x is 3, not 4
FAIL a runtime error fails only this test
test_blocks.fch:7: runtime error: index 1 is out of range (the length is 1)
ok still runs after the others
2 passed, 2 failed

16
tests/tool/trace.fch Normal file
View file

@ -0,0 +1,16 @@
struct Enemy {
[]int path
}
fn Enemy.next(int i) -> int {
return self.path[i]
}
fn step(Enemy e) -> int {
return e.next(3)
}
fn main() {
e := Enemy(path: [1, 2])
print(step(e))
}

4
tests/tool/trace.out Normal file
View file

@ -0,0 +1,4 @@
trace.fch:6: runtime error: index 3 is out of range (the length is 2)
in Enemy.next (trace.fch:6)
in step (trace.fch:10)
in main (trace.fch:15)