Finch 2.5.0: docs (EN, PL), Kate and VS Code for the new features

- cheat sheets, technicians' and engineers' guides: "\(x)", test blocks, finch fmt, finch.toml,
  the build cache, the chain of calls, threads and atomics, C++/Rust/Android, struct #defines,
  passing structs, why there is no warning for big copies
- Kate (version 4) and VS Code: code inside text, test, min/max/abs/clamp/assert/atomic_...
- finch.toml's name is used only for its entry; other files of the project keep their own name

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:34:41 +01:00
commit bb9702aeba
16 changed files with 778 additions and 60 deletions

View file

@ -98,15 +98,17 @@ Pick the guide that fits you:
| Arrays | `nums := [1, 2, 3]`, `nums.push(4)`, `nums[0]`, `nums.len`, `for i, n in nums { }` | | Arrays | `nums := [1, 2, 3]`, `nums.push(4)`, `nums[0]`, `nums.len`, `for i, n in nums { }` |
| Maps | `ages := ["ann": 31]`, `ages["bob"] = 25`, `ages.has("cy")`, `counts[w] += 1`, `for k, v in ages { }` | | Maps | `ages := ["ann": 31]`, `ages["bob"] = 25`, `ages.has("cy")`, `counts[w] += 1`, `for k, v in ages { }` |
| Errors | `fn parse(str s) -> int! { … return error("bad") }` → `parse(s) or 0`, `parse(s) or { … }`, `try parse(s)` | | Errors | `fn parse(str s) -> int! { … return error("bad") }` → `parse(s) or 0`, `parse(s) or { … }`, `try parse(s)` |
| Text | `"a" + "b"`, `str(42)`, `int("42")`, `s.split(",")`, `s.upper()`, `s[0]` | | Text | `"hp \(hp)"`, `"a" + "b"`, `str(42)`, `int("42")`, `s.split(",")`, `s.upper()`, `s[0]` |
| Control | `if` / `else if` / `else` (also as a value), `while`, `for i in 0..10`, `break`, `continue`, `defer` | | Control | `if` / `else if` / `else` (also as a value), `while`, `for i in 0..10`, `break`, `continue`, `defer` |
| Memory | automatic for arrays, text and structs; `new(...)` / `free(...)` for your own heap structures | | Memory | automatic for arrays, text and structs; `new(...)` / `free(...)` for your own heap structures |
| Enums, match | `enum Color { Red, Green }`, `match c { Red -> ... else -> ... }` (also as a value) | | Enums, match | `enum Color { Red, Green }`, `match c { Red -> ... else -> ... }` (also as a value) |
| Generics | `fn max[T](T a, T b) -> T`, `struct Stack[T]`, `Stack[int]()` | | Generics | `fn max[T](T a, T b) -> T`, `struct Stack[T]`, `Stack[int]()` |
| Modules | `import shapes` → `shapes.area(b)`; the standard library: `json`, `path`, `time`, `proc`, `fs` | | Modules | `import shapes` → `shapes.area(b)`, `import ui/button as btn`; the standard library: `json`, `path`, `time`, `proc`, `fs`, `thread` |
| C interop | `import "stdio.h"`, `link "glfw"`, `link "mine.c"`, `addr(fn)` for callbacks | | Threads | `thread.spawn(addr(work), addr(data))`, `t.join()`, `thread.mutex()`, `atomic_add(addr(n), 1)` |
| Tests | `test "name" { assert(x == 3) }` next to the code, run with `finch test` |
| C interop | `import "stdio.h"`, `link "glfw"`, `link "mine.c"` / `"glue.cpp"`, unions, `#define`s (also `(Color){…}`), `addr(fn)` for callbacks |
| I/O | `print(...)`, `input("? ")`, `read_file`, `write_file`, `shell` | | I/O | `print(...)`, `input("? ")`, `read_file`, `write_file`, `shell` |
| Tools | `finch run/build/ir/lsp`, `--target windows/arm64/macos`, `-g` for gdb/lldb, `-O0` | | Tools | `finch run/build/test/fmt/ir/lsp`, `finch.toml` for a project, a build cache, `--target windows/arm64/macos/android`, `-g` for gdb/lldb, `-O0`, the chain of calls on runtime errors |
## Examples ## Examples
@ -147,6 +149,9 @@ Pick the guide that fits you:
- [x] Methods on structs, maps, errors as values (`!`, `or`, `try`) - [x] Methods on structs, maps, errors as values (`!`, `or`, `try`)
- [x] Generics (`fn max[T]`, `struct Stack[T]`), `match`, enums - [x] Generics (`fn max[T]`, `struct Stack[T]`), `match`, enums
- [x] A standard library in Finch: `json`, `path`, `time`, `proc`, `fs` - [x] A standard library in Finch: `json`, `path`, `time`, `proc`, `fs`
- [x] `const`, `if` as a value, `"\(x)"` in text, `"""` text, C unions and `#define` expressions
- [x] `test` blocks and `finch test`, `finch fmt`, `finch.toml`, a build cache, the chain of calls on runtime errors
- [x] Threads (`import thread`) and atomics; C++ files; `--target android` (built, not yet tried on a device)
## Layout ## Layout

View file

@ -98,15 +98,17 @@ Wybierz przewodnik dla siebie:
| Tablice | `liczby := [1, 2, 3]`, `liczby.push(4)`, `liczby[0]`, `liczby.len`, `for i, n in liczby { }` | | Tablice | `liczby := [1, 2, 3]`, `liczby.push(4)`, `liczby[0]`, `liczby.len`, `for i, n in liczby { }` |
| Mapy | `wiek := ["ania": 31]`, `wiek["bob"] = 25`, `wiek.has("cy")`, `ile[s] += 1`, `for k, v in wiek { }` | | Mapy | `wiek := ["ania": 31]`, `wiek["bob"] = 25`, `wiek.has("cy")`, `ile[s] += 1`, `for k, v in wiek { }` |
| Błędy | `fn parsuj(str s) -> int! { … return error("zły") }` → `parsuj(s) or 0`, `parsuj(s) or { … }`, `try parsuj(s)` | | Błędy | `fn parsuj(str s) -> int! { … return error("zły") }` → `parsuj(s) or 0`, `parsuj(s) or { … }`, `try parsuj(s)` |
| Tekst | `"a" + "b"`, `str(42)`, `int("42")`, `s.split(",")`, `s.upper()`, `s[0]` | | Tekst | `"hp \(hp)"`, `"a" + "b"`, `str(42)`, `int("42")`, `s.split(",")`, `s.upper()`, `s[0]` |
| Sterowanie | `if` / `else if` / `else` (też jako wartość), `while`, `for i in 0..10`, `break`, `continue`, `defer` | | Sterowanie | `if` / `else if` / `else` (też jako wartość), `while`, `for i in 0..10`, `break`, `continue`, `defer` |
| Pamięć | automatyczna dla tablic, tekstów i struktur; `new(...)` / `free(...)` dla własnych struktur na stercie | | Pamięć | automatyczna dla tablic, tekstów i struktur; `new(...)` / `free(...)` dla własnych struktur na stercie |
| Enumy, match | `enum Kolor { Czerwony, Zielony }`, `match k { Czerwony -> ... else -> ... }` (także jako wartość) | | Enumy, match | `enum Kolor { Czerwony, Zielony }`, `match k { Czerwony -> ... else -> ... }` (także jako wartość) |
| Generyki | `fn max[T](T a, T b) -> T`, `struct Stos[T]`, `Stos[int]()` | | Generyki | `fn max[T](T a, T b) -> T`, `struct Stos[T]`, `Stos[int]()` |
| Moduły | `import ksztalty` → `ksztalty.pole(p)`; biblioteka standardowa: `json`, `path`, `time`, `proc`, `fs` | | Moduły | `import ksztalty` → `ksztalty.pole(p)`, `import ui/button as btn`; biblioteka standardowa: `json`, `path`, `time`, `proc`, `fs`, `thread` |
| Współpraca z C | `import "stdio.h"`, `link "glfw"`, `link "moje.c"`, `addr(fn)` dla callbacków | | Wątki | `thread.spawn(addr(praca), addr(dane))`, `t.join()`, `thread.mutex()`, `atomic_add(addr(n), 1)` |
| Testy | `test "nazwa" { assert(x == 3) }` obok kodu, uruchamiane przez `finch test` |
| Współpraca z C | `import "stdio.h"`, `link "glfw"`, `link "moje.c"` / `"glue.cpp"`, unie, `#define` (też `(Color){…}`), `addr(fn)` dla callbacków |
| Wejście/wyjście | `print(...)`, `input("? ")`, `read_file`, `write_file`, `shell` | | Wejście/wyjście | `print(...)`, `input("? ")`, `read_file`, `write_file`, `shell` |
| Narzędzia | `finch run/build/ir/lsp`, `--target windows/arm64/macos`, `-g` dla gdb/lldb, `-O0` | | Narzędzia | `finch run/build/test/fmt/ir/lsp`, `finch.toml` dla projektu, cache budowania, `--target windows/arm64/macos/android`, `-g` dla gdb/lldb, `-O0`, łańcuch wywołań przy błędach w czasie działania |
## Przykłady ## Przykłady
@ -147,6 +149,9 @@ Wybierz przewodnik dla siebie:
- [x] Metody w strukturach, mapy, błędy jako wartości (`!`, `or`, `try`) - [x] Metody w strukturach, mapy, błędy jako wartości (`!`, `or`, `try`)
- [x] Typy generyczne (`fn max[T]`, `struct Stos[T]`), `match`, enumy - [x] Typy generyczne (`fn max[T]`, `struct Stos[T]`), `match`, enumy
- [x] Biblioteka standardowa w Finchu: `json`, `path`, `time`, `proc`, `fs` - [x] Biblioteka standardowa w Finchu: `json`, `path`, `time`, `proc`, `fs`
- [x] `const`, `if` jako wartość, `"\(x)"` w tekście, tekst `"""`, unie C i wyrażenia `#define`
- [x] Bloki `test` i `finch test`, `finch fmt`, `finch.toml`, cache budowania, łańcuch wywołań przy błędach
- [x] Wątki (`import thread`) i operacje atomowe; pliki C++; `--target android` (budowane, jeszcze nie sprawdzone na urządzeniu)
## Struktura ## Struktura

View file

@ -13,15 +13,36 @@ Everything in Finch on one page. For explanations, see the [guides](../../README
| `finch run file.fch [args…]` | compile and run (args go to the program) | | `finch run file.fch [args…]` | compile and run (args go to the program) |
| `finch build file.fch [-o name]` | make a program (`name.exe` on Windows) | | `finch build file.fch [-o name]` | make a program (`name.exe` on Windows) |
| `finch build file.fch --shared [-I dir]` | make a library (`libfile.so`, `file.dll`) that a C program loads; its `export fn`s are its C functions (numbers, bool, char, pointers) | | `finch build file.fch --shared [-I dir]` | make a library (`libfile.so`, `file.dll`) that a C program loads; its `export fn`s are its C functions (numbers, bool, char, pointers) |
| `finch test file.fch` | run its `test "…" { }` blocks, each on its own (and those of the modules it imports) |
| `finch fmt [--check] [files/folders]` | indent by `{ }` with 4 spaces, no spaces at line ends, at most one empty line (`--check`: only list what it would change) |
| `finch run` / `build` / `test` with no file | in a folder with a `finch.toml`: its `entry` |
| `finch ir file.fch` | print the LLVM IR | | `finch ir file.fch` | print the LLVM IR |
| `finch lsp` | the language server (editors use it) | | `finch lsp` | the language server (editors use it) |
| `finch version` | version | | `finch version` | version |
| `-l lib` | link a C library (like `link "lib"`) | | `-l lib` | link a C library (like `link "lib"`) |
| `--target windows` / `linux` / `arm64` / `macos` / triple | build for another system | | `--target windows` / `linux` / `arm64` / `macos` / `android` / triple | build for another system (`android`: the NDK's clang from `ANDROID_NDK_HOME`, usually with `--shared`) |
| `-g` | debug info (gdb, lldb, Visual Studio) | | `-g` | debug info (gdb, lldb, Visual Studio) |
| `-O0` | no optimizations | | `-O0` | no optimizations |
| `--trace` / `--no-trace` | runtime errors show the chain of calls; on for `run`, `test`, `-g`, `-O0`, off for a plain `build` (it costs speed) |
Environment: `FINCH_PATH` (folders with modules), `FINCH_CC` / `CC` (the C compiler that links). Environment: `FINCH_PATH` (folders with modules), `FINCH_CC` (the C compiler that links), `FINCH_CXX` (for `.cpp` files).
A program's compiled code and its `link "x.c"` files are kept in the cache (`~/.cache/finch`) until they or their headers change.
`finch.toml` (next to the code; `finch run` there needs no file and no flags):
```toml
name = "game" # what finch build makes
entry = "src/main.fch"
paths = ["src"] # folders with modules
include = ["vendor/SDL3/include"] # C headers
links = ["SDL3", "native/glue.cpp"] # libraries and your own .c / .cpp / .o / .a
libdirs = ["vendor/SDL3/lib"]
cflags = ["-DFAST"] # for your .c / .cpp files
ldflags = ["-Wl,-rpath,."]
[target.windows] # only when building for that system (also linux, macos, android, linux-arm64)
links = ["ws2_32"]
```
--- ---
@ -68,7 +89,7 @@ One statement per line, no semicolons, `{` on the same line, no brackets around
| `float` (= `f64`) | `3.14`, `1e9`, `2.5e-3` | | `float` (= `f64`) | `3.14`, `1e9`, `2.5e-3` |
| `bool` | `true`, `false` | | `bool` | `true`, `false` |
| `char` | `'A'`, `'\n'` | | `char` | `'A'`, `'\n'` |
| `str` | `"text\t\"quoted\""`, `"""` several lines `"""` (the closing quotes' indentation is removed) | | `str` | `"text\t\"quoted\""`, `"hp \(hp), speed \(v, 2)"` (code in the text; `, 2` = two digits after the point), `"""` several lines `"""` (the closing quotes' indentation is removed) |
| `i8 i16 i32 i64` | signed, sized | | `i8 i16 i32 i64` | signed, sized |
| `u8 u16 u32 u64` | unsigned, sized | | `u8 u16 u32 u64` | unsigned, sized |
| `f32 f64` | floats | | `f32 f64` | floats |
@ -226,6 +247,7 @@ Fallible built-ins: `int(s)`, `float(s)`, `read_file`, `write_file`, `delete_fil
| `s.len` | bytes | | `s.len` | bytes |
| `s[i]`, `s[i] = 'x'` | char | | `s[i]`, `s[i] = 'x'` | char |
| `a + b` | joined text | | `a + b` | joined text |
| `"x = \(x)"` | the value of any code in the text (`\(x, 2)`: a float with 2 digits) |
| `s.sub(a, b)` | part a … b-1 | | `s.sub(a, b)` | part a … b-1 |
| `s.find(t)` | position or -1 | | `s.find(t)` | position or -1 |
| `s.contains(t)` | bool | | `s.contains(t)` | bool |
@ -280,6 +302,8 @@ Automatic only when nothing is lost: smaller → bigger number, any int → floa
| `min(a, b, …)`, `max(a, b, …)`, `abs(x)`, `clamp(x, lo, hi)` | for any numbers (your own function of that name wins) | | `min(a, b, …)`, `max(a, b, …)`, `abs(x)`, `clamp(x, lo, hi)` | for any numbers (your own function of that name wins) |
| `error(message)` | `return error("…")` in a `-> T!` function | | `error(message)` | `return error("…")` in a `-> T!` function |
| `addr(x)` | pointer to x; `addr(fn)` = C callback | | `addr(x)` | pointer to x; `addr(fn)` = C callback |
| `assert(cond)`, `assert(cond, "why")` | stop with a runtime error when cond is false (in a `test` block: that test fails) |
| `atomic_add(p, n)`, `atomic_sub`, `atomic_load(p)`, `atomic_store(p, n)`, `atomic_swap(p, n)`, `atomic_cas(p, old, new)` | a whole number other threads change too, through its `ptr` (`addr(count)`) |
| `new(value)` | value on the heap → `ptr[T]` | | `new(value)` | value on the heap → `ptr[T]` |
| `free(p)` | give it back | | `free(p)` | give it back |
@ -290,7 +314,7 @@ Automatic only when nothing is lost: smaller → bigger number, any int → floa
| Finch | C | | Finch | C |
|---|---| |---|---|
| `ptr[int] p` | `int *p` | | `ptr[int] p` | `int *p` |
| `addr(x)` | `&x` | | `addr(x)` | `&x` (a struct variable given where `ptr[ThatStruct]` goes: `push(body)` = `push(addr(body))`) |
| `p.value` | `*p` | | `p.value` | `*p` |
| `p.field` | `p->field` | | `p.field` | `p->field` |
| `p[i]` | `p[i]` | | `p[i]` | `p[i]` |
@ -303,6 +327,7 @@ Automatic only when nothing is lost: smaller → bigger number, any int → floa
- Arrays, text and structs free themselves at the end of their block. No `free` needed. - Arrays, text and structs free themselves at the end of their block. No `free` needed.
- `b := a` copies; a call's result moves; `return x` moves. - `b := a` copies; a call's result moves; `return x` moves.
- Passing a struct: `fn f(Body b)` works on its own copy (the caller's stays as it was), `fn f(ptr[Body] b)` changes the caller's (`f(body)` or `f(addr(body))`), a method gets `self` by address, never a copy.
- `new(...)` + `free(...)` (often with `defer`) for linked lists, trees, sharing. - `new(...)` + `free(...)` (often with `defer`) for linked lists, trees, sharing.
- C memory: C's own functions (`malloc` / `free`, `fclose`, …). - C memory: C's own functions (`malloc` / `free`, `fclose`, …).
@ -327,7 +352,7 @@ import ui/button as btn // another name: btn.draw()
|---|---| |---|---|
| `import "x.h"` | functions, structs, enums, `#define` numbers, globals | | `import "x.h"` | functions, structs, enums, `#define` numbers, globals |
| `link "name"` | libname.so / name.lib (pkg-config first) | | `link "name"` | libname.so / name.lib (pkg-config first) |
| `link "file.c"` | compile your own C file | | `link "file.c"`, `link "glue.cpp"` | compile your own C or C++ file (C++: functions in `extern "C"`) |
| `addr(myFn)` | pass a Finch function as a callback | | `addr(myFn)` | pass a Finch function as a callback |
| C type | Finch | | C type | Finch |
@ -342,6 +367,10 @@ import ui/button as btn // another name: btn.draw()
| `T x[N]` (in a struct) | `[N]T` | | `T x[N]` (in a struct) | `[N]T` |
| `union U` | `U`: `U u`, `u.field`, `U(field: v)`, by pointer to C (not by value) | | `union U` | `U`: `U u`, `u.field`, `U(field: v)`, by pointer to C (not by value) |
| `#define N 4`, `"text"`, `(1u << 3)`, `((Uint64)2)` | constants (not function-like macros) | | `#define N 4`, `"text"`, `(1u << 3)`, `((Uint64)2)` | constants (not function-like macros) |
| `#define RAYWHITE (Color){ 245, 245, 245, 255 }` | a `Color` |
**`char *` is `str`**: a C function that takes or returns `char *` takes or gives a Finch `str` (no `.ptr`, no conversion); only a `char *` field or a `char **` stays `ptr[char]`.
C++ and Rust: through a C header. C++ with `extern "C"` (and `link "x.cpp"`), Rust with `#[no_mangle] pub extern "C" fn` built as a `staticlib` (`link "target/release/libgame.a"`) and a header (cbindgen writes one).
A C function's `char *` result is a `str` already; structs by value work with the System V, Windows x64 and ARM64 Linux calling conventions (tested in emulators; macOS by CI). A C function's `char *` result is a `str` already; structs by value work with the System V, Windows x64 and ARM64 Linux calling conventions (tested in emulators; macOS by CI).
@ -358,6 +387,26 @@ A C function's `char *` result is a `str` already; structs by value work with th
| `can't turn "x" into int` | `int("x")` | | `can't turn "x" into int` | `int("x")` |
| `can't read the file "…"` | `read_file` (without `or`) | | `can't read the file "…"` | `read_file` (without `or`) |
| `the key "x" is not in the map` | `m["x"]` | | `the key "x" is not in the map` | `m["x"]` |
| `assert failed: why` | `assert(cond, "why")` |
Below the message, the calls that led there (`finch run`, `finch test`, `-g`, `-O0`, `--trace`):
```
game.fch:6: runtime error: index 3 is out of range (the length is 2)
in Enemy.next (game.fch:6)
in step (game.fch:10)
in main (game.fch:15)
```
## Tests
```c
test "jump height" { // next to the code; finch test file.fch runs them, finch run/build leave them out
p := Player()
p.jump()
assert(p.y > 0.0, "y is \(p.y)")
}
```
--- ---
@ -407,10 +456,12 @@ s := Stack[int]() Stack[int] t Pair[str, int]("age", 31) // ne
| `path` | `join(a, b)` `base(p)` `dir(p)` `ext(p)` `stem(p)` `parts(p)` `clean(p)` `is_abs(p)` | | `path` | `join(a, b)` `base(p)` `dir(p)` `ext(p)` `stem(p)` `parts(p)` `clean(p)` `is_abs(p)` |
| `time` | `now()` `millis()` `sleep(ms)` `date(t)` `local(t)` `format(t, fmt)` `days_in_month(y, m)` | | `time` | `now()` `millis()` `sleep(ms)` `date(t)` `local(t)` `format(t, fmt)` `days_in_month(y, m)` |
| `proc` | `run(cmd)` `output(cmd)` (fails with its output) `env(name, fallback)` `pid()` `cwd()` | | `proc` | `run(cmd)` `output(cmd)` (fails with its output) `env(name, fallback)` `pid()` `cwd()` |
| `thread` | `spawn(addr(work), addr(data))` → `t.join()`; `mutex()` → `m.lock()` `m.unlock()` `m.close()`; `cpus()` (a thread's function: `fn work(ptr arg)`) |
| `fs` | `read_bytes(p)` `write_bytes(p, data)` `exists(p)` `is_dir(p)` `mkdir(p)` `list(dir)` `remove_dir(p)` `data_dir(app)` | | `fs` | `read_bytes(p)` `write_bytes(p, data)` `exists(p)` `is_dir(p)` `mkdir(p)` `list(dir)` `remove_dir(p)` `data_dir(app)` |
| `json` | `parse(text)` `stringify(v)` `pretty(v)` `new_text/number/bool/null/list/object` `v.get(k)` `v.at(i)` `v.set(k, x)` `v.push(x)` `v.has(k)` `v.str_or(d)` `v.int_or(d)` `v.float_or(d)` `v.bool_or(d)` | | `json` | `parse(text)` `stringify(v)` `pretty(v)` `new_text/number/bool/null/list/object` `v.get(k)` `v.at(i)` `v.set(k, x)` `v.push(x)` `v.has(k)` `v.str_or(d)` `v.int_or(d)` `v.float_or(d)` `v.bool_or(d)` |
## Keywords ## Keywords
`fn return if else while for in break continue true false null import link struct enum match defer or try const export` `fn return if else while for in break continue true false null import link struct enum match defer or try const export`
(`test` and `as` only where they mean something: `test "name" { }`, `import x as y`)
(and `self` inside methods) (and `self` inside methods)

View file

@ -76,7 +76,7 @@ To check it works, type:
./build/finch version ./build/finch version
``` ```
You should see something like `Finch 2.4.0`. You should see something like `Finch 2.5.0`.
--- ---

View file

@ -1,6 +1,6 @@
# Finch for Engineers # Finch for Engineers
**The internals of Finch 2.4, whose compiler is written in Finch.** How source becomes tokens, an AST and LLVM IR *as text*, **The internals of Finch 2.5, whose compiler is written in Finch.** How source becomes tokens, an AST and LLVM IR *as text*,
which `clang` compiles and links with a small C runtime; how C headers are read with libclang from Finch itself and how which `clang` compiles and links with a small C runtime; how C headers are read with libclang from Finch itself and how
the C calling conventions are reproduced by hand; the type and ownership rules; how the compiler builds itself from a the C calling conventions are reproduced by hand; the type and ownership rules; how the compiler builds itself from a
seed; and the language server. seed; and the language server.
@ -69,18 +69,20 @@ Design decisions that shape the rest:
| File | Lines | Responsibility | | File | Lines | Responsibility |
|---|---|---| |---|---|---|
| `src/lexer.fch` | 220 | Tokens, positions, indentation-free layout | | `src/lexer.fch` | 470 | Tokens, positions, `"""` text, `"\(x)"` turned into `("…" + str(x) + "…")` tokens |
| `src/ast.fch` | 120 | One `Node` struct with a `kind` string, children, type names, positions (`line`, `endLine`) | | `src/ast.fch` | 120 | One `Node` struct with a `kind` string, children, type names, positions (`line`, `endLine`) |
| `src/parser.fch` | 780 | Recursive descent: declarations, statements, expressions, generics (`[T]`), `enum`, `match`, `export`, `link` | | `src/parser.fch` | 910 | Recursive descent: declarations, statements, expressions, generics (`[T]`), `enum`, `match`, `export`, `link`, `const`, `test "…" { }` |
| `src/gen.fch` | 5200 | Type check + IR: structs, functions, methods, scopes and drops, statements, expressions, built-ins, maps, errors as values, generics (monomorphisation), enums and `match`, `-g` debug metadata, the `index` mode | | `src/gen.fch` | 6450 | Type check + IR: structs, functions, methods, scopes and drops, statements, expressions, built-ins, maps, errors as values, generics (monomorphisation), enums and `match`, `-g` debug metadata, the `index` mode |
| `src/cimport.fch` | 560 | libclang: functions, structs (fields, offsets, padding), enums, constants, globals, macros | | `src/cimport.fch` | 790 | libclang: functions, structs and unions (fields, offsets, padding), enums, constants, globals, macros (also struct-valued) |
| `src/platform.fch` | 90 | `Target`: triple, data layout, C compiler, signedness of `char` per system | | `src/platform.fch` | 90 | `Target`: triple, data layout, C compiler, signedness of `char` per system |
| `src/linker.fch` | 300 | The cache, runtime object, libraries (`link`, pkg-config), link-error advice | | `src/linker.fch` | 430 | The cache (runtime, program object, C/C++ files), libraries (`link`, pkg-config), link-error advice |
| `src/main.fch` | 310 | Driver: CLI, loader with module lookup and overlays, `build/run/ir/index/lsp` | | `src/main.fch` | 515 | Driver: CLI, loader with module lookup and overlays, `build/run/test/fmt/ir/index/lsp` |
| `src/lsp.fch` | 875 | The language server (§11) | | `src/manifest.fch` | 240 | `finch.toml`: a small TOML reader, per-target lists |
| `src/fmt.fch` | 225 | `finch fmt` and LSP formatting |
| `src/lsp.fch` | 910 | The language server (§11) |
| `src/docs.fch` | 110 | One description of the built-ins, shared by hover and completion | | `src/docs.fch` | 110 | One description of the built-ins, shared by hover and completion |
| `std/*.fch` | 900 | `json`, `path`, `time` (+`_windows`), `proc` (+`_windows`) | | `std/*.fch` | 1040 | `json`, `path`, `time` (+`_windows`), `proc` (+`_windows`), `fs`, `thread` |
| `runtime/finch_rt.c` | 760 | The C runtime | | `runtime/finch_rt.c` | 1150 | The C runtime: text, arrays, maps, files, float printing, fs, threads, the test runner, the call chain |
| `bootstrap/` | | `build.sh`, `install.sh`, the seeds (§10) | | `bootstrap/` | | `build.sh`, `install.sh`, the seeds (§10) |
--- ---
@ -166,7 +168,7 @@ offset 0, and a field is read through memory. They can't be passed by value to C
`ir` prints the IR; `index` prints the records the language server uses (§11); `--shared` makes a shared library of the `ir` prints the IR; `index` prints the records the language server uses (§11); `--shared` makes a shared library of the
`export fn`s. `export fn`s.
`--target windows|linux|arm64|macos|<triple>` changes the triple and data layout, the C `char` signedness, the ABI rules `--target windows|linux|arm64|macos|android|<triple>` changes the triple and data layout, the C `char` signedness, the ABI rules
above, the OS variant of modules (`name_windows.fch`), and the C compiler: `FINCH_CC` or `clang --target=…`. Linking above, the OS variant of modules (`name_windows.fch`), and the C compiler: `FINCH_CC` or `clang --target=…`. Linking
asks `pkg-config` for flags for `link "lib"`; when the linker fails the output is analysed and the likely missing asks `pkg-config` for flags for `link "lib"`; when the linker fails the output is analysed and the likely missing
`link "…"` is suggested. The runtime object is keyed by a hash of its source and the target. `link "…"` is suggested. The runtime object is keyed by a hash of its source and the target.
@ -176,6 +178,38 @@ compiled by `clang`, never by `CC`), `FINCH_LIBCLANG` (for `bootstrap/build.sh`)
The compiler finds `runtime/` and `std/` next to itself (`../`, `./`) or under `../share/finch/` (`bootstrap/install.sh`). The compiler finds `runtime/` and `std/` next to itself (`../`, `./`) or under `../share/finch/` (`bootstrap/install.sh`).
**The build cache** (`host.cacheDir()`: `$XDG_CACHE_HOME/finch`, `~/.cache/finch`, `%LOCALAPPDATA%\finch`):
- `ir/<hash of main file + target + flags>.o` with a `.key` holding a hash of clang, the flags and the whole IR. One slot
per program, so the cache does not grow with every edit; the same IR gives the same object without running clang.
- `c/<hash of source + compiler + flags + target>.o` for `link "x.c"` / `.cpp`, built with `-MMD`; the `.key` is a hash of
every file the `.d` lists (the source and its headers). Any change in one of them rebuilds it.
- Objects are written to a `.part<pid>` file and renamed, so two builds at once don't see half a file.
Hashes are two independent 32-bit ones (FNV-1a and djb2), 64 bits in all.
**`finch.toml`** (`src/manifest.fch`) is searched from the main file's folder (or the working folder) upwards. It is read
into `values["section.key"]` and `lists["section.key"]`; `list(key, targets)` concatenates the top-level list and those of
`[target.<os>]` and `[target.<os>-<cpu>]`. Paths are resolved against the manifest's folder. Entries feed the same
places as the command line: `paths` → the loader, `include` → `-I` (libclang and C files), `links` → `link`, `libdirs`
and `ldflags` → the link command, `cflags` → C/C++ files.
**C++ files**: a `.cpp`/`.cc`/`.cxx` in `links` is compiled by `cxxFor(cc)` (`FINCH_CXX`, or `clang` → `clang++`,
`…gcc` → `…g++`, `cc` → `c++`), and then that driver links, which brings in the C++ standard library.
**Android**: `--target android` is `aarch64-linux-android24`; the C compiler is the NDK's
`toolchains/llvm/prebuilt/<host>/bin/aarch64-linux-android24-clang` (from `ANDROID_NDK_HOME`/`ANDROID_NDK_ROOT`), the link
adds `-llog` and no `-lpthread` (bionic has it in libc). The OS variant of modules is `linux`.
**`finch test`** compiles like `run`, with `Options.test`: every function is still defined, but `@main` calls
`finch_test_run(name, fn)` for each `test` block of the user's modules (`__test_<n>`, a `void` function made by the
parser), then `finch_test_summary()`. The runtime runs each test under `setjmp`; `fatal()` (every runtime error and a
failed `assert`) `longjmp`s back through `fatal_hook`, so one test failing doesn't end the run. Memory the failed test
owned at that point is not freed.
**`finch fmt`** is line-based: it tracks `{ }`, `( )`/`[ ]`, strings (with `\( )`), `'…'`, `"""` and `/* */` per line.
A line that starts at bracket depth 0 and doesn't continue a line ending in an operator or `,` is re-indented to
4 × brace depth; other lines (continuations, `"""` bodies) are shifted by the same amount as the line that started the
statement, so their shape (and the `"""` dedent) is kept.
--- ---
## 7. Type system ## 7. Type system
@ -440,6 +474,41 @@ test (22 checks).
--- ---
### The chain of calls on runtime errors
With `Options.trace` (default for `run`, `test`, `-g`, `-O0`; `--trace`/`--no-trace`; never with `--shared`) every Finch
function gets a frame on its stack:
```llvm
%frame = alloca { ptr, ptr, ptr, i64 } ; caller's frame, name, file, line
store ptr (load @finch_trace), ...field 0
store ptr @"name", ...field 1 ; ptr @"file", field 2 ; i64 <line>, field 3
store ptr %frame, ptr @finch_trace
```
`@finch_trace` is a `thread_local` global in the program's module (initial-exec model unless shared or Windows);
each statement stores its line into field 3. After every call to a Finch function the caller stores its own frame back
into `@finch_trace`, which also undoes frames left by a callee that returned early, so no epilogue is needed on the many
return paths. C-callable wrappers (`addr(fn)` thunks, `export fn`) save and restore `@finch_trace` around their call.
`main` calls `finch_trace_init(top, set)` with two tiny functions that read and write it, which is how the runtime
(`print_trace()` in `fatal()`, and the test runner) gets at a thread-local it cannot name. Cost: about 3x on `fib(40)`,
which is nothing but calls; that is why a plain `finch build` leaves it out.
### Threads and atomics
`std/thread.fch` is a thin wrapper over the runtime (`finch_thread_spawn/join`, `finch_mutex_*`: pthreads, or
`CreateThread` and `CRITICAL_SECTION` on Windows). The thread function is any `fn f(ptr)`, passed with `addr(f)`, which
makes the C-callable thunk. `atomic_add/sub/swap` lower to `atomicrmw add/sub/xchg … seq_cst`, `atomic_load/store` to
`load/store atomic … seq_cst, align <size>`, and `atomic_cas` to `cmpxchg … seq_cst seq_cst` returning the `i1`. They
take a `ptr[T]` to any integer type. Finch's arrays, text and maps are not synchronised; sharing one between threads
needs a mutex.
### Struct arguments by address
`argWant()` (call arguments only, not assignments): when a parameter is `ptr[S]` and the argument is a plain variable
of type `S`, the argument is the variable's address, as if `addr(x)` had been written. Only variables, so nothing is
evaluated twice and temporaries are never pointed to.
## 13. Known limitations ## 13. Known limitations
- The compiler runs on Linux x86-64 and ARM64 only (no seed for macOS or Windows yet). Programs can be built for all three. - The compiler runs on Linux x86-64 and ARM64 only (no seed for macOS or Windows yet). Programs can be built for all three.
@ -449,4 +518,6 @@ test (22 checks).
- The driver uses `sh` utilities (`rm`, `mkdir`), so it needs a POSIX shell. - The driver uses `sh` utilities (`rm`, `mkdir`), so it needs a POSIX shell.
- Generics are monomorphised (code size grows with instantiations); there are no trait bounds, a wrong use is an error - Generics are monomorphised (code size grows with instantiations); there are no trait bounds, a wrong use is an error
inside the instantiated body. inside the instantiated body.
- A failed test's owned memory is not freed (the test runner `longjmp`s out of it).
- `--target android` is checked to the object file only; linking needs the NDK and has not been run on a device.

View file

@ -37,6 +37,10 @@ compiler works inside. For that, see the [guide for engineers](for-engineers.md)
24. [Generics](#24-generics) 24. [Generics](#24-generics)
25. [The standard library](#25-the-standard-library) 25. [The standard library](#25-the-standard-library)
26. [Current limits](#26-current-limits) 26. [Current limits](#26-current-limits)
27. [Tests: test blocks and finch test](#27-tests-test-blocks-and-finch-test)
28. [A project: finch.toml](#28-a-project-finchtoml)
29. [Threads](#29-threads)
30. [C++, Rust and Android](#30-c-rust-and-android)
--- ---
@ -137,17 +141,31 @@ kept in `~/.cache/finch`. `FINCH_CC` names a different compiler to link with; `F
``` ```
finch run <file.fch> [args...] compile and run right away finch run <file.fch> [args...] compile and run right away
finch build <file.fch> [-o name] compile into a program (default name: the file's name) finch build <file.fch> [-o name] compile into a program (default name: the file's name)
finch test <file.fch> run its test "..." { } blocks (section 27)
finch fmt [--check] [files/dirs] tidy the indentation of .fch files (all under here by default)
finch ir <file.fch> print the LLVM IR (for the curious) finch ir <file.fch> print the LLVM IR (for the curious)
finch lsp the language server, for editors finch lsp the language server, for editors
finch version show the version finch version show the version
options: options:
-l <lib> link a C library, same as link "lib" in the file -l <lib> link a C library, same as link "lib" in the file
--target <name> build for another system: windows, linux, arm64, macos, or an LLVM triple -I <dir> look for C headers there too
--target <name> build for another system: windows, linux, arm64, macos, android, or an LLVM triple
-g add debug info (for gdb / lldb / Visual Studio) -g add debug info (for gdb / lldb / Visual Studio)
-O0 skip optimizations -O0 skip optimizations
--trace runtime errors show the chain of calls (on anyway for run, test, -g and -O0)
--no-trace they don't (a plain finch build doesn't: keeping the chain makes calls slower)
``` ```
In a folder with a `finch.toml` (section 28), `finch run`, `finch build` and `finch test` need no file name.
The compiled program and the C files it builds (`link "x.c"`) are kept in a cache (`~/.cache/finch`, or
`%LOCALAPPDATA%` on Windows). The second build of the same code, or of code whose C files did not change, skips that work.
`finch fmt` changes only the layout: four spaces per `{ }` level, no spaces at line ends, at most one empty line in a row.
Lines that continue an expression (inside `( )` or after a `+`) and `"""` text keep their shape. `finch fmt --check`
changes nothing and fails if a file would change (for CI). Editors with the language server format on request.
Examples: Examples:
```sh ```sh
@ -503,6 +521,13 @@ s[0] = 'J' // change a character
| `s.bytes()` | `[]u8` with the bytes | | `s.bytes()` | `[]u8` with the bytes |
| `s.ptr` | a `ptr[char]` for C (valid while `s` lives) | | `s.ptr` | a `ptr[char]` for C (valid while `s` lives) |
Values go into text with `\(...)`: any code, and `, 2` after a number for two digits after the point.
A `\` before the `(` you want to keep: `"\\(not code)"`.
```c
print("hp \(hp) of \(max_hp), speed \(v, 2), name \(p.name.upper())")
```
`str` keeps its length, so `.len` is instant, and it is always NUL-terminated, so passing it `str` keeps its length, so `.len` is instant, and it is always NUL-terminated, so passing it
to C as `char*` works. The other way round, a C function that gives back a `char *` gives you a `str` to C as `char*` works. The other way round, a C function that gives back a `char *` gives you a `str`
(a copy is made when you keep it), and `str(p)` reads a `ptr[char]` you got some other way. (a copy is made when you keep it), and `str(p)` reads a `ptr[char]` you got some other way.
@ -779,8 +804,8 @@ Both work directly; `examples/opengl.fch` and `examples/vulkan.fch` are complete
- C **unions** and structs with **bit fields** passed to or returned from a C function **by value** (pointers to them - C **unions** and structs with **bit fields** passed to or returned from a C function **by value** (pointers to them
work, and so do unions as variables: `SDL_Event e`, `e.type`, `e.key.key`, `SDL_PollEvent(addr(e))`). work, and so do unions as variables: `SDL_Event e`, `e.type`, `e.key.key`, `SDL_PollEvent(addr(e))`).
- **Function-like macros** (`#define MAX(a, b) ...`) and macros that make a struct (raylib's `RAYWHITE`). - **Function-like macros** (`#define MAX(a, b) ...`). Macros that are a number, a text, an expression of them
Macros that are a number, a text or an expression of them (`(1u << 3)`, `((Uint64)1)`, `(A | B)`) work. (`(1u << 3)`, `((Uint64)1)`, `(A | B)`) or a struct of numbers (raylib's `RAYWHITE`, `(Color){ 245, 245, 245, 255 }`) work.
- `long double`, 128-bit integers. - `long double`, 128-bit integers.
--- ---
@ -860,6 +885,19 @@ Instead of undefined behavior (as in C), Finch stops with a message and exit cod
| `runtime error: can't read the file "…"` | `read_file` on a missing or unreadable file (handle it with `or`) | | `runtime error: can't read the file "…"` | `read_file` on a missing or unreadable file (handle it with `or`) |
| `runtime error: the key "x" is not in the map …` | `m["x"]` when the map has no such key (use `.has` or `.get`) | | `runtime error: the key "x" is not in the map …` | `m["x"]` when the map has no such key (use `.has` or `.get`) |
| `runtime error: called .f() on a null pointer` | a method called through a `null` `ptr[T]` | | `runtime error: called .f() on a null pointer` | a method called through a `null` `ptr[T]` |
| `runtime error: assert failed: …` | `assert(cond, "…")` with a false `cond` |
Under the message comes the chain of calls that led there, the innermost first:
```
game.fch:6: runtime error: index 3 is out of range (the length is 2)
in Enemy.next (game.fch:6)
in step (game.fch:10)
in main (game.fch:15)
```
`finch run`, `finch test`, `-g` and `-O0` builds keep it; a plain `finch build` leaves it out because keeping it costs
time in every call (about 3x in a program that is nothing but tiny calls, much less in a real one). `--trace` puts it in.
--- ---
@ -939,13 +977,14 @@ Visual Studio debugger and WinDbg read.
``` ```
Finch/ Finch/
├── src/ the compiler, in Finch (lexer, parser, gen: LLVM IR as text, cimport: libclang, lsp, main ...) ├── src/ the compiler, in Finch (lexer, parser, gen: LLVM IR as text, cimport: libclang, lsp, main ...)
├── std/ the standard library, in Finch: json, path, time, proc ├── std/ the standard library, in Finch: json, path, time, proc, fs, thread
├── runtime/ finch_rt.c: the small runtime (text, arrays, maps, input, files) ├── runtime/ finch_rt.c: the small runtime (text, arrays, maps, input, files)
├── bootstrap/ build.sh, install.sh and the seeds (the compiler's own IR, one per system) ├── bootstrap/ build.sh, install.sh and the seeds (the compiler's own IR, one per system)
├── examples/ hello, tour, types, structs, todo, guess, c_import, window, opengl, vulkan, raylib, llvm ├── examples/ hello, tour, types, structs, todo, guess, c_import, window, opengl, vulkan, raylib, llvm
├── tests/ ├── tests/
│ ├── run/ new/ programs + the exact output they must print (.out), input (.in) │ ├── run/ new/ programs + the exact output they must print (.out), input (.in)
│ ├── fail/ new/fail/ programs that must fail, with the expected error in line 1 │ ├── fail/ new/fail/ programs that must fail, with the expected error in line 1
│ ├── tool/ finch test, finch fmt, the chain of calls, a finch.toml project
│ ├── run.sh the test runner (MEMCHECK=1 also checks memory with valgrind) │ ├── run.sh the test runner (MEMCHECK=1 also checks memory with valgrind)
│ ├── cross.sh every test built for Windows / ARM64 and run under Wine / QEMU │ ├── cross.sh every test built for Windows / ARM64 and run under Wine / QEMU
│ └── lsp_test.py the language server, talked to like an editor does │ └── lsp_test.py the language server, talked to like an editor does
@ -1073,6 +1112,7 @@ library's second, so your own file of the same name wins.
| `proc` | `run(cmd)` (the exit code), `output(cmd)` (what it printed; an error if it failed), `env(name, fallback)`, `pid()`, `cwd()` | | `proc` | `run(cmd)` (the exit code), `output(cmd)` (what it printed; an error if it failed), `env(name, fallback)`, `pid()`, `cwd()` |
| `json` | `parse(text)`, `stringify(v)`, `pretty(v)`, and the value type `Json` with `get`, `at`, `set`, `push`, `has`, `str_or`, `int_or` ... | | `json` | `parse(text)`, `stringify(v)`, `pretty(v)`, and the value type `Json` with `get`, `at`, `set`, `push`, `has`, `str_or`, `int_or` ... |
| `fs` | `read_bytes(p)` / `write_bytes(p, data)` (`[]u8`, any bytes), `exists`, `is_dir`, `mkdir` (with the folders above), `list` (sorted names), `remove_dir`, `data_dir(app)` (a folder for the user's saves) | | `fs` | `read_bytes(p)` / `write_bytes(p, data)` (`[]u8`, any bytes), `exists`, `is_dir`, `mkdir` (with the folders above), `list` (sorted names), `remove_dir`, `data_dir(app)` (a folder for the user's saves) |
| `thread` | `spawn(addr(work), addr(data))`, `t.join()`, `mutex()` with `lock`, `unlock`, `close`, `cpus()` (section 29) |
`min(a, b, ...)`, `max(a, b, ...)`, `abs(x)` and `clamp(x, lo, hi)` work on any numbers without an import (a function of `min(a, b, ...)`, `max(a, b, ...)`, `abs(x)` and `clamp(x, lo, hi)` work on any numbers without an import (a function of
your own with the same name wins). your own with the same name wins).
@ -1093,7 +1133,155 @@ Not there yet:
- Interfaces. - Interfaces.
- Unicode-aware text: `.len`, `s[i]` and `upper()` work on bytes / ASCII. - Unicode-aware text: `.len`, `s[i]` and `upper()` work on bytes / ASCII.
- Threads. - Checks that stop two threads from changing the same array at once (use a mutex).
- Windows on ARM64, 32-bit systems, WebAssembly. - Windows on ARM64, 32-bit systems, WebAssembly. Android is built (`--target android`) but not yet tried on a device.
On purpose, not planned: a warning for every big struct that gets copied. A copy in Finch is always visible in the
code (`b := a`, passing a struct to a `Body b` parameter), a method never copies `self`, and `ptr[Body]` (or passing
the variable to a `ptr[Body]` parameter) shares instead. A warning that fires on ordinary code would be noise.
See the [roadmap in the README](../../README.md#roadmap). See the [roadmap in the README](../../README.md#roadmap).
## 27. Tests: test blocks and finch test
Tests go next to the code they test:
```c
fn damage(int hp, int hit) -> int {
return max(hp - hit, 0)
}
test "damage never goes below zero" {
assert(damage(5, 10) == 0)
assert(damage(5, 2) == 3, "got \(damage(5, 2))")
}
```
`finch test game.fch` runs every `test` block of the file and of the modules it imports (not of the standard library),
each on its own: a failed `assert` or any runtime error ends only that test, and the next one runs.
```
ok damage never goes below zero
FAIL loading a level
level.fch:31: runtime error: can't read the file "levels/1.txt"
1 passed, 1 failed
```
The exit code is 1 when a test failed, so CI can use it. `finch run` and `finch build` leave test blocks out.
`assert(cond)` also works outside tests: it stops the program with `assert failed` when `cond` is false.
## 28. A project: finch.toml
A `finch.toml` next to the code says once what would otherwise go on every command line:
```toml
name = "game" # the program finch build makes (from the entry)
entry = "src/main.fch" # used when finch run / build / test get no file
paths = ["src", "lib"] # folders with Finch modules
include = ["vendor/SDL3/include"] # folders with C headers (also for your .c / .cpp files)
links = ["SDL3", "native/glue.cpp"] # libraries, and your own .c / .cpp / .o / .a files
libdirs = ["vendor/SDL3/lib"] # folders with libraries
cflags = ["-DFAST"] # for your .c / .cpp files
ldflags = ["-Wl,-rpath,$ORIGIN"] # for the linker
[target.windows] # added only when building for Windows
links = ["ws2_32"]
libdirs = ["vendor/SDL3/lib/x64"]
[target.linux-arm64] # system and CPU: windows, linux, macos, android, and -x86_64 / -arm64
libdirs = ["vendor/SDL3/lib/arm64"]
```
Folders are relative to `finch.toml`. Finch looks for it in the file's folder and the folders above, so
`finch run src/tools/editor.fch` inside the project uses it too. Only this much of TOML is read: `[sections]`,
`key = "text"`, `key = ["a", "b"]` (over several lines too), numbers, `true`/`false` and `#` comments.
## 29. Threads
```c
import thread
struct Chunk {
[]float samples
float peak
}
fn analyse(ptr arg) { // a thread's function takes one ptr
ptr[Chunk] c = arg
for s in c.samples {
c.peak = max(c.peak, abs(s))
}
}
fn main() {
[]Chunk chunks = load_chunks()
[]thread.Thread running
for i in 0..chunks.len {
running.push(thread.spawn(addr(analyse), addr(chunks[i])) or {
print(err)
exit(1)
})
}
for i in 0..running.len {
running[i].join() // wait for all of them
}
}
```
What a thread gets is shared, not copied, so:
- it must live until `join()` (here `chunks` lives to the end of `main`);
- don't change the array the threads point into (no `push`) until they are joined;
- what more than one thread changes goes under a lock, or is a number changed with `atomic_...`.
```c
m := thread.mutex()
defer m.close()
m.lock()
log.push("done") // one thread at a time between lock and unlock
m.unlock()
atomic_add(addr(done), 1) // done is an int shared by the threads
n := atomic_load(addr(done))
```
`atomic_add`, `atomic_sub`, `atomic_swap` give back the old value; `atomic_cas(p, old, new)` puts `new` only if `old`
is there and says whether it did. `thread.cpus()` says how many threads the machine runs at once.
## 30. C++, Rust and Android
Finch talks to other languages through C, the way every language does.
**C++**: put what Finch calls behind a C header, with `extern "C"` on the C++ side:
```cpp
// glue.h
#ifdef __cplusplus
extern "C" {
#endif
int physics_step(float dt);
#ifdef __cplusplus
}
#endif
```
and `import "glue.h"` with `link "glue.cpp"` (or `links = ["glue.cpp"]` in `finch.toml`). The `.cpp` file is built with
the C++ compiler (`clang++` or `g++`, or `FINCH_CXX`), which then also links, so the C++ library comes along.
A C++ library that has a C API (Box2D 3, Jolt's JoltC, PhysX's C wrapper) is imported like any C library.
**Rust**: make a `staticlib` and export C functions:
```rust
// Cargo.toml: [lib] crate-type = ["staticlib"]
#[no_mangle]
pub extern "C" fn path_len(x: f32, y: f32) -> f32 { (x * x + y * y).sqrt() }
```
Write the header (`float path_len(float x, float y);`, or let `cbindgen` write it), then `import "pathfind.h"` and
`link "target/release/libpathfind.a"`. On Linux a Rust static library also needs `links = ["dl", "pthread"]`.
**Android**: `finch build game.fch --target android --shared` makes `libgame.so` for 64-bit ARM Android (API 24 and
up) with the NDK's clang, found through `ANDROID_NDK_HOME` (or `FINCH_CC`). Load it from an app, for example through
SDL3's Android project (the library's `SDL_main` is an `export fn`). This is new: it is built and checked here, but not
yet tried on a device.

View file

@ -1,6 +1,6 @@
# Finch dla inżynierów # Finch dla inżynierów
**Wnętrze Fincha 2.4, którego kompilator jest napisany w Finchu.** Jak kod źródłowy staje się tokenami, drzewem AST i **Wnętrze Fincha 2.5, którego kompilator jest napisany w Finchu.** Jak kod źródłowy staje się tokenami, drzewem AST i
LLVM IR *jako tekstem*, który `clang` kompiluje i linkuje z małym runtime'em w C; jak nagłówki C czyta libclang wywoływany LLVM IR *jako tekstem*, który `clang` kompiluje i linkuje z małym runtime'em w C; jak nagłówki C czyta libclang wywoływany
z samego Fincha i jak ręcznie odtwarzane są konwencje wywołań C; reguły typów i własności; jak kompilator buduje sam siebie z samego Fincha i jak ręcznie odtwarzane są konwencje wywołań C; reguły typów i własności; jak kompilator buduje sam siebie
z ziarna; oraz serwer języka. z ziarna; oraz serwer języka.
@ -68,18 +68,20 @@ Decyzje projektowe:
| Plik | Linie | Zadanie | | Plik | Linie | Zadanie |
|---|---|---| |---|---|---|
| `src/lexer.fch` | 220 | Tokeny i pozycje | | `src/lexer.fch` | 470 | Tokeny i pozycje, tekst `"""`, `"\(x)"` zamienione na tokeny `("…" + str(x) + "…")` |
| `src/ast.fch` | 120 | Jedna struktura `Node` z napisem `kind`, dziećmi, nazwami typów, pozycjami (`line`, `endLine`) | | `src/ast.fch` | 120 | Jedna struktura `Node` z napisem `kind`, dziećmi, nazwami typów, pozycjami (`line`, `endLine`) |
| `src/parser.fch` | 780 | Zejście rekurencyjne: deklaracje, instrukcje, wyrażenia, generyki (`[T]`), `enum`, `match`, `export`, `link` | | `src/parser.fch` | 910 | Zejście rekurencyjne: deklaracje, instrukcje, wyrażenia, generyki (`[T]`), `enum`, `match`, `export`, `link`, `const`, `test "…" { }` |
| `src/gen.fch` | 5200 | Kontrola typów + IR: struktury, funkcje, metody, zakresy i zwolnienia, instrukcje, wyrażenia, wbudowane, mapy, błędy jako wartości, generyki (monomorfizacja), enumy i `match`, metadane `-g`, tryb `index` | | `src/gen.fch` | 6450 | Kontrola typów + IR: struktury, funkcje, metody, zakresy i zwolnienia, instrukcje, wyrażenia, wbudowane, mapy, błędy jako wartości, generyki (monomorfizacja), enumy i `match`, metadane `-g`, tryb `index` |
| `src/cimport.fch` | 560 | libclang: funkcje, struktury (pola, offsety, wypełnienie), enumy, stałe, globalne, makra | | `src/cimport.fch` | 790 | libclang: funkcje, struktury i unie (pola, offsety, wypełnienie), enumy, stałe, globalne, makra (także będące strukturą) |
| `src/platform.fch` | 90 | `Target`: triple, układ danych, kompilator C, znak `char` zależnie od systemu | | `src/platform.fch` | 90 | `Target`: triple, układ danych, kompilator C, znak `char` zależnie od systemu |
| `src/linker.fch` | 300 | Pamięć podręczna, obiekt runtime'u, biblioteki (`link`, pkg-config), podpowiedzi przy błędach linkera | | `src/linker.fch` | 430 | Pamięć podręczna (runtime, obiekt programu, pliki C/C++), biblioteki (`link`, pkg-config), podpowiedzi przy błędach linkera |
| `src/main.fch` | 310 | Sterownik: CLI, loader z wyszukiwaniem modułów i nakładkami, `build/run/ir/index/lsp` | | `src/main.fch` | 515 | Sterownik: CLI, loader z wyszukiwaniem modułów i nakładkami, `build/run/test/fmt/ir/index/lsp` |
| `src/lsp.fch` | 875 | Serwer języka (§11) | | `src/manifest.fch` | 240 | `finch.toml`: mały czytnik TOML, listy zależne od celu |
| `src/fmt.fch` | 225 | `finch fmt` i formatowanie w LSP |
| `src/lsp.fch` | 910 | Serwer języka (§11) |
| `src/docs.fch` | 110 | Jeden opis wbudowanych funkcji dla podpowiedzi i uzupełniania | | `src/docs.fch` | 110 | Jeden opis wbudowanych funkcji dla podpowiedzi i uzupełniania |
| `std/*.fch` | 900 | `json`, `path`, `time` (+`_windows`), `proc` (+`_windows`) | | `std/*.fch` | 1040 | `json`, `path`, `time` (+`_windows`), `proc` (+`_windows`), `fs`, `thread` |
| `runtime/finch_rt.c` | 760 | Runtime w C | | `runtime/finch_rt.c` | 1150 | Runtime w C: tekst, tablice, mapy, pliki, wypisywanie liczb, fs, wątki, uruchamianie testów, łańcuch wywołań |
| `bootstrap/` | | `build.sh`, `install.sh`, ziarna (§10) | | `bootstrap/` | | `build.sh`, `install.sh`, ziarna (§10) |
--- ---
@ -159,7 +161,7 @@ Ten sam kod pozwala kompilatorowi wołać libclang, którego API przekazuje `CXC
`finch build|run|ir|index plik.fch [-o wyjście] [-O0] [-g] [-l biblioteka] [-I katalog] [--target cel] [--shared] [--overlay ścieżka=plik]`. `finch build|run|ir|index plik.fch [-o wyjście] [-O0] [-g] [-l biblioteka] [-I katalog] [--target cel] [--shared] [--overlay ścieżka=plik]`.
`ir` wypisuje IR; `index` wypisuje rekordy dla serwera języka (§11); `--shared` robi bibliotekę współdzieloną z `export fn`. `ir` wypisuje IR; `index` wypisuje rekordy dla serwera języka (§11); `--shared` robi bibliotekę współdzieloną z `export fn`.
`--target windows|linux|arm64|macos|<triple>` zmienia triple i układ danych, znak `char`, reguły ABI powyżej, wariant OS `--target windows|linux|arm64|macos|android|<triple>` zmienia triple i układ danych, znak `char`, reguły ABI powyżej, wariant OS
modułów (`nazwa_windows.fch`) i kompilator C: `FINCH_CC` albo `clang --target=…`. Przy linkowaniu `pkg-config` podaje flagi modułów (`nazwa_windows.fch`) i kompilator C: `FINCH_CC` albo `clang --target=…`. Przy linkowaniu `pkg-config` podaje flagi
dla `link "biblioteka"`; gdy linker zawiedzie, wynik jest analizowany i podpowiadane jest brakujące `link "…"`. Obiekt dla `link "biblioteka"`; gdy linker zawiedzie, wynik jest analizowany i podpowiadane jest brakujące `link "…"`. Obiekt
runtime'u jest kluczowany skrótem jego źródła i celu. runtime'u jest kluczowany skrótem jego źródła i celu.
@ -169,6 +171,38 @@ zawsze kompiluje `clang`, nigdy `CC`), `FINCH_LIBCLANG` (dla `bootstrap/build.sh
Kompilator znajduje `runtime/` i `std/` obok siebie (`../`, `./`) albo w `../share/finch/` (`bootstrap/install.sh`). Kompilator znajduje `runtime/` i `std/` obok siebie (`../`, `./`) albo w `../share/finch/` (`bootstrap/install.sh`).
**Pamięć podręczna budowania** (`host.cacheDir()`: `$XDG_CACHE_HOME/finch`, `~/.cache/finch`, `%LOCALAPPDATA%\finch`):
- `ir/<skrót pliku głównego + celu + flag>.o` z plikiem `.key`, w którym jest skrót clanga, flag i całego IR. Jedno miejsce
na program, więc cache nie rośnie z każdą zmianą; to samo IR daje ten sam obiekt bez uruchamiania clanga.
- `c/<skrót źródła + kompilatora + flag + celu>.o` dla `link "x.c"` / `.cpp`, budowane z `-MMD`; `.key` to skrót każdego
pliku z listy `.d` (źródła i jego nagłówków). Zmiana któregokolwiek przebudowuje obiekt.
- Obiekty są zapisywane do pliku `.part<pid>` i przemianowywane, więc dwa budowania naraz nie widzą pół pliku.
Skróty to dwa niezależne 32-bitowe (FNV-1a i djb2), razem 64 bity.
**`finch.toml`** (`src/manifest.fch`) jest szukany od folderu pliku głównego (albo bieżącego) w górę. Trafia do
`values["sekcja.klucz"]` i `lists["sekcja.klucz"]`; `list(klucz, cele)` skleja listę z najwyższego poziomu i te z
`[target.<os>]` oraz `[target.<os>-<cpu>]`. Ścieżki liczą się od folderu manifestu. Wpisy trafiają tam, gdzie opcje
z linii poleceń: `paths` → loader, `include` → `-I` (libclang i pliki C), `links` → `link`, `libdirs` i `ldflags` →
polecenie linkowania, `cflags` → pliki C/C++.
**Pliki C++**: `.cpp`/`.cc`/`.cxx` w `links` kompiluje `cxxFor(cc)` (`FINCH_CXX` albo `clang` → `clang++`,
`…gcc` → `…g++`, `cc` → `c++`), a potem ten sam sterownik linkuje, co dokłada standardową bibliotekę C++.
**Android**: `--target android` to `aarch64-linux-android24`; kompilator C to clang z NDK,
`toolchains/llvm/prebuilt/<host>/bin/aarch64-linux-android24-clang` (z `ANDROID_NDK_HOME`/`ANDROID_NDK_ROOT`), linkowanie
dodaje `-llog` i nie dodaje `-lpthread` (bionic ma go w libc). Wariant OS modułów to `linux`.
**`finch test`** kompiluje jak `run`, z `Options.test`: wszystkie funkcje są dalej definiowane, ale `@main` wywołuje
`finch_test_run(nazwa, fn)` dla każdego bloku `test` z modułów użytkownika (`__test_<n>`, funkcja `void` zrobiona przez
parser), a na końcu `finch_test_summary()`. Runtime uruchamia każdy test pod `setjmp`; `fatal()` (każdy błąd w czasie
działania i nieudany `assert`) wraca przez `longjmp` z `fatal_hook`, więc jeden nieudany test nie kończy całości.
Pamięć, którą test miał w tym momencie, nie jest zwalniana.
**`finch fmt`** działa na liniach: śledzi `{ }`, `( )`/`[ ]`, teksty (z `\( )`), `'…'`, `"""` i `/* */`. Linia, która
zaczyna się na głębokości nawiasów 0 i nie ciągnie linii zakończonej operatorem albo `,`, dostaje wcięcie 4 × głębokość
klamer; pozostałe (ciągi dalsze, wnętrza `"""`) przesuwa się o tyle samo co linię, która zaczęła instrukcję, więc ich
kształt (i ucinanie wcięcia `"""`) zostaje.
--- ---
## 7. System typów ## 7. System typów
@ -433,6 +467,41 @@ serwer podpowiada dalej z ostatniej wersji, która się skompilowała. Test akce
--- ---
### Łańcuch wywołań przy błędach w czasie działania
Z `Options.trace` (domyślnie dla `run`, `test`, `-g`, `-O0`; `--trace`/`--no-trace`; nigdy z `--shared`) każda funkcja
Fincha ma ramkę na swoim stosie:
```llvm
%frame = alloca { ptr, ptr, ptr, i64 } ; ramka wywołującego, nazwa, plik, linia
store ptr (load @finch_trace), ...pole 0
store ptr @"nazwa", ...pole 1 ; ptr @"plik", pole 2 ; i64 <linia>, pole 3
store ptr %frame, ptr @finch_trace
```
`@finch_trace` to globalna `thread_local` w module programu (model initial-exec, chyba że biblioteka albo Windows);
każda instrukcja zapisuje swoją linię do pola 3. Po każdym wywołaniu funkcji Fincha wywołujący wpisuje z powrotem swoją
ramkę do `@finch_trace`, co sprząta też ramki funkcji, która wróciła wcześniej, więc wiele ścieżek `return` nie
potrzebuje epilogu. Opakowania wołane z C (thunki `addr(fn)`, `export fn`) zapamiętują i przywracają `@finch_trace`.
`main` woła `finch_trace_init(top, set)` z dwiema małymi funkcjami do czytania i zapisu, i tak runtime (`print_trace()`
w `fatal()` oraz uruchamianie testów) dostaje się do zmiennej wątku, której nie zna z nazwy. Koszt: ok. 3 razy na
`fib(40)`, który jest samymi wywołaniami; dlatego zwykły `finch build` go pomija.
### Wątki i operacje atomowe
`std/thread.fch` to cienka warstwa nad runtime'em (`finch_thread_spawn/join`, `finch_mutex_*`: pthreads albo
`CreateThread` i `CRITICAL_SECTION` na Windowsie). Funkcja wątku to dowolna `fn f(ptr)` podana przez `addr(f)`, co robi
thunk wołany z C. `atomic_add/sub/swap` to `atomicrmw add/sub/xchg … seq_cst`, `atomic_load/store` to
`load/store atomic … seq_cst, align <rozmiar>`, a `atomic_cas` to `cmpxchg … seq_cst seq_cst` zwracające `i1`. Biorą
`ptr[T]` do dowolnej liczby całkowitej. Tablice, teksty i mapy Fincha nie są synchronizowane; dzielenie ich między
wątkami wymaga mutexu.
### Struktura jako argument przez adres
`argWant()` (tylko argumenty wywołań, nie przypisania): gdy parametr to `ptr[S]`, a argument to zwykła zmienna typu `S`,
argumentem jest adres zmiennej, jakby napisano `addr(x)`. Tylko zmienne, więc nic nie jest liczone dwa razy, a na
wartości tymczasowe nic nie wskazuje.
## 13. Znane ograniczenia ## 13. Znane ograniczenia
- Kompilator działa tylko na Linuksie x86-64 i ARM64 (brak ziarna dla macOS i Windowsa). Programy można budować na wszystkie trzy. - Kompilator działa tylko na Linuksie x86-64 i ARM64 (brak ziarna dla macOS i Windowsa). Programy można budować na wszystkie trzy.
@ -442,4 +511,6 @@ serwer podpowiada dalej z ostatniej wersji, która się skompilowała. Test akce
- Sterownik używa narzędzi `sh` (`rm`, `mkdir`), więc wymaga powłoki POSIX. - Sterownik używa narzędzi `sh` (`rm`, `mkdir`), więc wymaga powłoki POSIX.
- Generyki są monomorfizowane (kod rośnie z liczbą instancji); nie ma ograniczeń na typy, błędne użycie to błąd wewnątrz - Generyki są monomorfizowane (kod rośnie z liczbą instancji); nie ma ograniczeń na typy, błędne użycie to błąd wewnątrz
wygenerowanego ciała. wygenerowanego ciała.
- Pamięć nieudanego testu nie jest zwalniana (uruchamianie testów wychodzi z niego przez `longjmp`).
- `--target android` jest sprawdzany do pliku obiektowego; linkowanie wymaga NDK i nie było uruchamiane na urządzeniu.

View file

@ -76,7 +76,7 @@ Jeśli ostatnia linijka skończy się bez słowa `error`, Finch jest gotowy.
./build/finch version ./build/finch version
``` ```
Powinieneś zobaczyć coś w stylu `finch 2.4.0`. Powinieneś zobaczyć coś w stylu `finch 2.5.0`.
--- ---

View file

@ -37,6 +37,10 @@ kompilator działa w środku. Do tego służy [przewodnik dla inżynierów](dla-
24. [Typy generyczne](#24-typy-generyczne) 24. [Typy generyczne](#24-typy-generyczne)
25. [Biblioteka standardowa](#25-biblioteka-standardowa) 25. [Biblioteka standardowa](#25-biblioteka-standardowa)
26. [Obecne ograniczenia](#26-obecne-ograniczenia) 26. [Obecne ograniczenia](#26-obecne-ograniczenia)
27. [Testy: bloki test i finch test](#27-testy-bloki-test-i-finch-test)
28. [Projekt: finch.toml](#28-projekt-finchtoml)
29. [Wątki](#29-wątki)
30. [C++, Rust i Android](#30-c-rust-i-android)
--- ---
@ -138,17 +142,31 @@ katalogi, w których szukać modułów.
``` ```
finch run <plik.fch> [argumenty...] skompiluj i od razu uruchom finch run <plik.fch> [argumenty...] skompiluj i od razu uruchom
finch build <plik.fch> [-o nazwa] skompiluj do programu (domyślna nazwa: nazwa pliku) finch build <plik.fch> [-o nazwa] skompiluj do programu (domyślna nazwa: nazwa pliku)
finch test <plik.fch> uruchom jego bloki test "..." { } (rozdział 27)
finch fmt [--check] [pliki/foldery] uporządkuj wcięcia plików .fch (domyślnie wszystkich stąd w dół)
finch ir <plik.fch> pokaż LLVM IR (dla ciekawskich) finch ir <plik.fch> pokaż LLVM IR (dla ciekawskich)
finch lsp serwer języka, dla edytorów finch lsp serwer języka, dla edytorów
finch version pokaż wersję finch version pokaż wersję
opcje: opcje:
-l <lib> dolinkuj bibliotekę C, to samo co link "lib" w pliku -l <lib> dolinkuj bibliotekę C, to samo co link "lib" w pliku
--target <nazwa> zbuduj na inny system: windows, linux, arm64, macos albo triple LLVM -I <folder> szukaj nagłówków C także tam
--target <nazwa> zbuduj na inny system: windows, linux, arm64, macos, android albo triple LLVM
-g dodaj informacje dla debuggera (gdb / lldb / Visual Studio) -g dodaj informacje dla debuggera (gdb / lldb / Visual Studio)
-O0 bez optymalizacji -O0 bez optymalizacji
--trace błędy w czasie działania pokazują łańcuch wywołań (i tak włączone dla run, test, -g i -O0)
--no-trace nie pokazują (zwykły finch build go nie ma: pilnowanie łańcucha spowalnia wywołania)
``` ```
W folderze z plikiem `finch.toml` (rozdział 28) `finch run`, `finch build` i `finch test` nie potrzebują nazwy pliku.
Skompilowany program i pliki C, które buduje (`link "x.c"`), zostają w cache (`~/.cache/finch`, na Windowsie
`%LOCALAPPDATA%`). Drugie budowanie tego samego kodu albo kodu, którego pliki C się nie zmieniły, pomija tę pracę.
`finch fmt` zmienia tylko układ: cztery spacje na poziom `{ }`, bez spacji na końcach linii, najwyżej jedna pusta linia
z rzędu. Linie, które ciągną wyrażenie (w `( )` albo po `+`), i tekst `"""` zachowują swój kształt. `finch fmt --check`
niczego nie zmienia i kończy się błędem, gdy jakiś plik by się zmienił (dla CI). Edytory z serwerem języka formatują na żądanie.
Przykłady: Przykłady:
```sh ```sh
@ -505,6 +523,13 @@ s[0] = 'D' // zmiana znaku
| `s.bytes()` | `[]u8` z bajtami | | `s.bytes()` | `[]u8` z bajtami |
| `s.ptr` | `ptr[char]` dla C (ważny, dopóki żyje `s`) | | `s.ptr` | `ptr[char]` dla C (ważny, dopóki żyje `s`) |
Wartości wstawia się do tekstu przez `\(...)`: dowolny kod, a `, 2` po liczbie daje dwie cyfry po przecinku.
`\` przed `(`, które ma zostać zwykłym znakiem: `"\\(to nie kod)"`.
```c
print("hp \(hp) z \(max_hp), prędkość \(v, 2), imię \(p.imie.upper())")
```
`str` pamięta swoją długość, więc `.len` działa natychmiast. Zawsze kończy się zerem, więc `str` pamięta swoją długość, więc `.len` działa natychmiast. Zawsze kończy się zerem, więc
przekazanie go do C jako `char*` działa. W drugą stronę: funkcja C, która zwraca `char *`, daje ci `str` przekazanie go do C jako `char*` działa. W drugą stronę: funkcja C, która zwraca `char *`, daje ci `str`
(kopia powstaje, gdy go zachowujesz), a `str(p)` czyta `ptr[char]` zdobyty inaczej. (kopia powstaje, gdy go zachowujesz), a `str(p)` czyta `ptr[char]` zdobyty inaczej.
@ -781,8 +806,8 @@ Oba działają bezpośrednio; `examples/opengl.fch` i `examples/vulkan.fch` to k
- **Unie** C i struktury z **polami bitowymi** przekazywane do funkcji C albo zwracane z niej **przez wartość** - **Unie** C i struktury z **polami bitowymi** przekazywane do funkcji C albo zwracane z niej **przez wartość**
(wskaźniki do nich działają, unie jako zmienne też: `SDL_Event e`, `e.type`, `e.key.key`, `SDL_PollEvent(addr(e))`). (wskaźniki do nich działają, unie jako zmienne też: `SDL_Event e`, `e.type`, `e.key.key`, `SDL_PollEvent(addr(e))`).
- **Makra-funkcje** (`#define MAX(a, b) ...`) i makra, które tworzą strukturę (`RAYWHITE` z raylib). - **Makra-funkcje** (`#define MAX(a, b) ...`). Makra będące liczbą, tekstem, wyrażeniem z nich
Makra będące liczbą, tekstem albo wyrażeniem z nich (`(1u << 3)`, `((Uint64)1)`, `(A | B)`) działają. (`(1u << 3)`, `((Uint64)1)`, `(A | B)`) albo strukturą z liczb (`RAYWHITE` z raylib, `(Color){ 245, 245, 245, 255 }`) działają.
- `long double`, liczby 128-bitowe. - `long double`, liczby 128-bitowe.
--- ---
@ -863,6 +888,19 @@ Zamiast niezdefiniowanego zachowania (jak w C) Finch zatrzymuje się z komunikat
| `runtime error: can't read the file "…"` | `read_file` na brakującym albo nieczytelnym pliku (obsłuż to przez `or`) | | `runtime error: can't read the file "…"` | `read_file` na brakującym albo nieczytelnym pliku (obsłuż to przez `or`) |
| `runtime error: the key "x" is not in the map …` | `m["x"]`, gdy mapa nie ma takiego klucza (użyj `.has` albo `.get`) | | `runtime error: the key "x" is not in the map …` | `m["x"]`, gdy mapa nie ma takiego klucza (użyj `.has` albo `.get`) |
| `runtime error: called .f() on a null pointer` | metoda wywołana przez `null` w `ptr[T]` | | `runtime error: called .f() on a null pointer` | metoda wywołana przez `null` w `ptr[T]` |
| `runtime error: assert failed: …` | `assert(warunek, "…")` z fałszywym warunkiem |
Pod komunikatem jest łańcuch wywołań, które do niego doprowadziły, od najgłębszego:
```
gra.fch:6: runtime error: index 3 is out of range (the length is 2)
in Enemy.next (gra.fch:6)
in step (gra.fch:10)
in main (gra.fch:15)
```
`finch run`, `finch test` oraz budowanie z `-g` i `-O0` go mają; zwykły `finch build` go pomija, bo pilnowanie go kosztuje
czas w każdym wywołaniu (ok. 3 razy w programie złożonym z samych drobnych wywołań, dużo mniej w prawdziwym). `--trace` go dodaje.
--- ---
@ -943,13 +981,14 @@ CodeView, który czyta debugger Visual Studio i WinDbg.
``` ```
Finch/ Finch/
├── src/ kompilator, w Finchu (lekser, parser, gen: LLVM IR jako tekst, cimport: libclang, lsp, main ...) ├── src/ kompilator, w Finchu (lekser, parser, gen: LLVM IR jako tekst, cimport: libclang, lsp, main ...)
├── std/ biblioteka standardowa, w Finchu: json, path, time, proc ├── std/ biblioteka standardowa, w Finchu: json, path, time, proc, fs, thread
├── runtime/ finch_rt.c: mała biblioteka uruchomieniowa (teksty, tablice, mapy, wejście, pliki) ├── runtime/ finch_rt.c: mała biblioteka uruchomieniowa (teksty, tablice, mapy, wejście, pliki)
├── bootstrap/ build.sh, install.sh i ziarna (własny IR kompilatora, po jednym na system) ├── bootstrap/ build.sh, install.sh i ziarna (własny IR kompilatora, po jednym na system)
├── examples/ hello, tour, types, structs, todo, guess, c_import, window, opengl, vulkan, raylib, llvm ├── examples/ hello, tour, types, structs, todo, guess, c_import, window, opengl, vulkan, raylib, llvm
├── tests/ ├── tests/
│ ├── run/ new/ programy + dokładny wynik (.out) i wejście (.in) │ ├── run/ new/ programy + dokładny wynik (.out) i wejście (.in)
│ ├── fail/ new/fail/ programy, które muszą się nie udać, z oczekiwanym błędem w 1. linijce │ ├── fail/ new/fail/ programy, które muszą się nie udać, z oczekiwanym błędem w 1. linijce
│ ├── tool/ finch test, finch fmt, łańcuch wywołań, projekt z finch.toml
│ ├── run.sh uruchamia testy (MEMCHECK=1 sprawdza też pamięć valgrindem) │ ├── run.sh uruchamia testy (MEMCHECK=1 sprawdza też pamięć valgrindem)
│ ├── cross.sh każdy test zbudowany na Windows / ARM64 i uruchomiony w Wine / QEMU │ ├── cross.sh każdy test zbudowany na Windows / ARM64 i uruchomiony w Wine / QEMU
│ └── lsp_test.py serwer języka, obsługiwany jak przez edytor │ └── lsp_test.py serwer języka, obsługiwany jak przez edytor
@ -1077,6 +1116,7 @@ bibliotece — więc Twój własny plik o tej nazwie ma pierwszeństwo.
| `proc` | `run(cmd)` (kod wyjścia), `output(cmd)` (co wypisało; błąd, jeśli się nie udało), `env(nazwa, domyślna)`, `pid()`, `cwd()` | | `proc` | `run(cmd)` (kod wyjścia), `output(cmd)` (co wypisało; błąd, jeśli się nie udało), `env(nazwa, domyślna)`, `pid()`, `cwd()` |
| `json` | `parse(tekst)`, `stringify(v)`, `pretty(v)` oraz typ wartości `Json` z `get`, `at`, `set`, `push`, `has`, `str_or`, `int_or` ... | | `json` | `parse(tekst)`, `stringify(v)`, `pretty(v)` oraz typ wartości `Json` z `get`, `at`, `set`, `push`, `has`, `str_or`, `int_or` ... |
| `fs` | `read_bytes(p)` / `write_bytes(p, dane)` (`[]u8`, dowolne bajty), `exists`, `is_dir`, `mkdir` (z folderami wyżej), `list` (posortowane nazwy), `remove_dir`, `data_dir(app)` (folder na zapisy użytkownika) | | `fs` | `read_bytes(p)` / `write_bytes(p, dane)` (`[]u8`, dowolne bajty), `exists`, `is_dir`, `mkdir` (z folderami wyżej), `list` (posortowane nazwy), `remove_dir`, `data_dir(app)` (folder na zapisy użytkownika) |
| `thread` | `spawn(addr(praca), addr(dane))`, `t.join()`, `mutex()` z `lock`, `unlock`, `close`, `cpus()` (rozdział 29) |
`min(a, b, ...)`, `max(a, b, ...)`, `abs(x)` i `clamp(x, od, do)` działają na dowolnych liczbach bez importu (twoja `min(a, b, ...)`, `max(a, b, ...)`, `abs(x)` i `clamp(x, od, do)` działają na dowolnych liczbach bez importu (twoja
własna funkcja o tej samej nazwie wygrywa). własna funkcja o tej samej nazwie wygrywa).
@ -1097,7 +1137,156 @@ Jeszcze nie ma:
- Interfejsów. - Interfejsów.
- Tekstu świadomego Unicode: `.len`, `s[i]` i `upper()` działają na bajtach / ASCII. - Tekstu świadomego Unicode: `.len`, `s[i]` i `upper()` działają na bajtach / ASCII.
- Wątków. - Pilnowania, żeby dwa wątki nie zmieniały tej samej tablicy naraz (do tego mutex).
- Windowsa na ARM64, systemów 32-bitowych, WebAssembly. - Windowsa na ARM64, systemów 32-bitowych, WebAssembly. Android jest budowany (`--target android`), ale jeszcze nie
sprawdzony na urządzeniu.
Celowo nie planowane: ostrzeżenie przy każdym kopiowaniu dużej struktury. Kopię w Finchu zawsze widać w kodzie
(`b := a`, struktura podana do parametru `Cialo c`), metoda nigdy nie kopiuje `self`, a `ptr[Cialo]` (albo zmienna
podana do parametru `ptr[Cialo]`) współdzieli zamiast kopiować. Ostrzeżenie przy zwykłym kodzie byłoby szumem.
Plan rozwoju jest w [README](../../README.pl.md#plan-rozwoju). Plan rozwoju jest w [README](../../README.pl.md#plan-rozwoju).
## 27. Testy: bloki test i finch test
Testy stoją obok kodu, który sprawdzają:
```c
fn obrazenia(int hp, int cios) -> int {
return max(hp - cios, 0)
}
test "obrażenia nigdy nie schodzą poniżej zera" {
assert(obrazenia(5, 10) == 0)
assert(obrazenia(5, 2) == 3, "wyszło \(obrazenia(5, 2))")
}
```
`finch test gra.fch` uruchamia każdy blok `test` z pliku i z modułów, które importuje (bez biblioteki standardowej),
każdy osobno: nieudany `assert` albo dowolny błąd w czasie działania kończy tylko ten test, a następny rusza dalej.
```
ok obrażenia nigdy nie schodzą poniżej zera
FAIL wczytanie poziomu
poziom.fch:31: runtime error: can't read the file "levels/1.txt"
1 passed, 1 failed
```
Kod wyjścia to 1, gdy jakiś test nie przeszedł, więc CI może z tego korzystać. `finch run` i `finch build` pomijają bloki test.
`assert(warunek)` działa też poza testami: zatrzymuje program z `assert failed`, gdy warunek jest fałszywy.
## 28. Projekt: finch.toml
`finch.toml` obok kodu mówi raz to, co inaczej trzeba by podawać w każdym poleceniu:
```toml
name = "gra" # program, który robi finch build (z entry)
entry = "src/main.fch" # gdy finch run / build / test nie dostaną pliku
paths = ["src", "lib"] # foldery z modułami Fincha
include = ["vendor/SDL3/include"] # foldery z nagłówkami C (też dla własnych .c / .cpp)
links = ["SDL3", "native/glue.cpp"] # biblioteki i własne pliki .c / .cpp / .o / .a
libdirs = ["vendor/SDL3/lib"] # foldery z bibliotekami
cflags = ["-DFAST"] # dla własnych plików .c / .cpp
ldflags = ["-Wl,-rpath,$ORIGIN"] # dla linkera
[target.windows] # dodawane tylko przy budowaniu na Windowsa
links = ["ws2_32"]
libdirs = ["vendor/SDL3/lib/x64"]
[target.linux-arm64] # system i procesor: windows, linux, macos, android oraz -x86_64 / -arm64
libdirs = ["vendor/SDL3/lib/arm64"]
```
Foldery liczą się od `finch.toml`. Finch szuka go w folderze pliku i w folderach wyżej, więc
`finch run src/narzedzia/edytor.fch` wewnątrz projektu też z niego korzysta. Czytana jest tylko taka część TOML:
`[sekcje]`, `klucz = "tekst"`, `klucz = ["a", "b"]` (także w kilku liniach), liczby, `true`/`false` i komentarze `#`.
## 29. Wątki
```c
import thread
struct Kawalek {
[]float probki
float szczyt
}
fn analizuj(ptr arg) { // funkcja wątku bierze jeden ptr
ptr[Kawalek] k = arg
for s in k.probki {
k.szczyt = max(k.szczyt, abs(s))
}
}
fn main() {
[]Kawalek kawalki = wczytaj_kawalki()
[]thread.Thread dzialajace
for i in 0..kawalki.len {
dzialajace.push(thread.spawn(addr(analizuj), addr(kawalki[i])) or {
print(err)
exit(1)
})
}
for i in 0..dzialajace.len {
dzialajace[i].join() // poczekaj na wszystkie
}
}
```
To, co dostaje wątek, jest współdzielone, nie kopiowane, więc:
- musi żyć do `join()` (tu `kawalki` żyje do końca `main`);
- nie zmieniaj tablicy, w którą wskazują wątki (bez `push`), dopóki się nie skończą;
- to, co zmienia więcej niż jeden wątek, idzie pod blokadę albo jest liczbą zmienianą przez `atomic_...`.
```c
m := thread.mutex()
defer m.close()
m.lock()
dziennik.push("gotowe") // jeden wątek naraz między lock a unlock
m.unlock()
atomic_add(addr(gotowe), 1) // gotowe to int wspólny dla wątków
n := atomic_load(addr(gotowe))
```
`atomic_add`, `atomic_sub`, `atomic_swap` zwracają starą wartość; `atomic_cas(p, stara, nowa)` wstawia `nowa` tylko
wtedy, gdy jest tam `stara`, i mówi, czy to zrobił. `thread.cpus()` mówi, ile wątków komputer wykonuje naraz.
## 30. C++, Rust i Android
Finch rozmawia z innymi językami przez C, jak każdy język.
**C++**: to, co woła Finch, wystaw przez nagłówek C, z `extern "C"` po stronie C++:
```cpp
// glue.h
#ifdef __cplusplus
extern "C" {
#endif
int physics_step(float dt);
#ifdef __cplusplus
}
#endif
```
i `import "glue.h"` z `link "glue.cpp"` (albo `links = ["glue.cpp"]` w `finch.toml`). Plik `.cpp` buduje kompilator C++
(`clang++` albo `g++`, albo `FINCH_CXX`), który wtedy też linkuje, więc biblioteka C++ dochodzi sama.
Bibliotekę C++ z API w C (Box2D 3, JoltC dla Jolt, wrapper C dla PhysX) importuje się jak każdą bibliotekę C.
**Rust**: zrób `staticlib` i wystaw funkcje C:
```rust
// Cargo.toml: [lib] crate-type = ["staticlib"]
#[no_mangle]
pub extern "C" fn path_len(x: f32, y: f32) -> f32 { (x * x + y * y).sqrt() }
```
Napisz nagłówek (`float path_len(float x, float y);` albo niech napisze go `cbindgen`), potem `import "pathfind.h"` i
`link "target/release/libpathfind.a"`. Na Linuksie statyczna biblioteka Rusta potrzebuje też `links = ["dl", "pthread"]`.
**Android**: `finch build gra.fch --target android --shared` robi `libgra.so` dla 64-bitowego Androida na ARM (API 24
i wyżej) clangiem z NDK, znalezionym przez `ANDROID_NDK_HOME` (albo `FINCH_CC`). Wczytaj go z aplikacji, na przykład
przez projekt Androida z SDL3 (`SDL_main` biblioteki to `export fn`). To nowość: jest budowane i sprawdzane tutaj, ale
jeszcze nie na urządzeniu.

View file

@ -12,15 +12,37 @@ Cały Finch na jednej stronie. Wyjaśnienia są w [przewodnikach](../../README.p
|---|---| |---|---|
| `finch run plik.fch [argumenty…]` | skompiluj i uruchom (argumenty trafiają do programu) | | `finch run plik.fch [argumenty…]` | skompiluj i uruchom (argumenty trafiają do programu) |
| `finch build plik.fch [-o nazwa]` | zrób program (`nazwa.exe` na Windowsie) | | `finch build plik.fch [-o nazwa]` | zrób program (`nazwa.exe` na Windowsie) |
| `finch build plik.fch --shared` | zrób bibliotekę (`libplik.so`, `plik.dll`) dla programu w C; jej `export fn` to jej funkcje C |
| `finch test plik.fch` | uruchom jego bloki `test "…" { }`, każdy osobno (także z importowanych modułów) |
| `finch fmt [--check] [pliki/foldery]` | wcięcia wg `{ }` po 4 spacje, bez spacji na końcach linii, najwyżej jedna pusta linia (`--check`: tylko wypisz, co by zmienił) |
| `finch run` / `build` / `test` bez pliku | w folderze z `finch.toml`: jego `entry` |
| `finch ir plik.fch` | pokaż LLVM IR | | `finch ir plik.fch` | pokaż LLVM IR |
| `finch lsp` | serwer języka (korzystają z niego edytory) | | `finch lsp` | serwer języka (korzystają z niego edytory) |
| `finch version` | wersja | | `finch version` | wersja |
| `-l lib` | dolinkuj bibliotekę C (jak `link "lib"`) | | `-l lib` | dolinkuj bibliotekę C (jak `link "lib"`) |
| `--target windows` / `linux` / `arm64` / `macos` / triple | zbuduj dla innego systemu | | `--target windows` / `linux` / `arm64` / `macos` / `android` / triple | zbuduj dla innego systemu (`android`: clang z NDK wskazanego przez `ANDROID_NDK_HOME`, zwykle z `--shared`) |
| `-g` | informacje dla debuggera (gdb, lldb, Visual Studio) | | `-g` | informacje dla debuggera (gdb, lldb, Visual Studio) |
| `-O0` | bez optymalizacji | | `-O0` | bez optymalizacji |
| `--trace` / `--no-trace` | błędy w czasie działania pokazują łańcuch wywołań; włączone dla `run`, `test`, `-g`, `-O0`, wyłączone dla zwykłego `build` (kosztuje szybkość) |
Zmienne środowiskowe: `FINCH_PATH` (foldery z modułami), `FINCH_CC` / `CC` (kompilator C, który linkuje). Zmienne środowiskowe: `FINCH_PATH` (foldery z modułami), `FINCH_CC` (kompilator C, który linkuje), `FINCH_CXX` (dla plików `.cpp`).
Skompilowany kod programu i jego pliki `link "x.c"` zostają w cache (`~/.cache/finch`), dopóki one albo ich nagłówki się nie zmienią.
`finch.toml` (obok kodu; `finch run` w tym folderze nie potrzebuje pliku ani flag):
```toml
name = "gra" # co robi finch build
entry = "src/main.fch"
paths = ["src"] # foldery z modułami
include = ["vendor/SDL3/include"] # nagłówki C
links = ["SDL3", "native/glue.cpp"] # biblioteki i własne .c / .cpp / .o / .a
libdirs = ["vendor/SDL3/lib"]
cflags = ["-DFAST"] # dla własnych plików .c / .cpp
ldflags = ["-Wl,-rpath,."]
[target.windows] # tylko przy budowaniu dla tego systemu (też linux, macos, android, linux-arm64)
links = ["ws2_32"]
```
--- ---
@ -67,7 +89,7 @@ Jedna instrukcja na linijkę, bez średników, `{` w tej samej linijce, bez nawi
| `float` (= `f64`) | `3.14`, `1e9`, `2.5e-3` | | `float` (= `f64`) | `3.14`, `1e9`, `2.5e-3` |
| `bool` | `true` (prawda), `false` (fałsz) | | `bool` | `true` (prawda), `false` (fałsz) |
| `char` | `'A'`, `'\n'` | | `char` | `'A'`, `'\n'` |
| `str` | `"tekst\t\"w cudzysłowie\""`, `"""` kilka linii `"""` (wcięcie zamykających cudzysłowów znika) | | `str` | `"tekst\t\"w cudzysłowie\""`, `"hp \(hp), prędkość \(v, 2)"` (kod w tekście; `, 2` = dwie cyfry po przecinku), `"""` kilka linii `"""` (wcięcie zamykających cudzysłowów znika) |
| `i8 i16 i32 i64` | ze znakiem, z rozmiarem | | `i8 i16 i32 i64` | ze znakiem, z rozmiarem |
| `u8 u16 u32 u64` | bez znaku, z rozmiarem | | `u8 u16 u32 u64` | bez znaku, z rozmiarem |
| `f32 f64` | zmiennoprzecinkowe | | `f32 f64` | zmiennoprzecinkowe |
@ -225,6 +247,7 @@ Funkcje wbudowane, które mogą się nie udać: `int(s)`, `float(s)`, `read_file
| `s.len` | liczba bajtów (ą, ż… to 2) | | `s.len` | liczba bajtów (ą, ż… to 2) |
| `s[i]`, `s[i] = 'x'` | znak | | `s[i]`, `s[i] = 'x'` | znak |
| `a + b` | sklejony tekst | | `a + b` | sklejony tekst |
| `"x = \(x)"` | wartość dowolnego kodu w tekście (`\(x, 2)`: liczba z 2 cyframi po przecinku) |
| `s.sub(a, b)` | fragment a … b-1 | | `s.sub(a, b)` | fragment a … b-1 |
| `s.find(t)` | pozycja albo -1 | | `s.find(t)` | pozycja albo -1 |
| `s.contains(t)` | czy zawiera | | `s.contains(t)` | czy zawiera |
@ -279,6 +302,8 @@ Automatycznie tylko wtedy, gdy nic nie ginie: mniejsza → większa liczba, dowo
| `min(a, b, …)`, `max(a, b, …)`, `abs(x)`, `clamp(x, od, do)` | dla dowolnych liczb (twoja funkcja o tej nazwie wygrywa) | | `min(a, b, …)`, `max(a, b, …)`, `abs(x)`, `clamp(x, od, do)` | dla dowolnych liczb (twoja funkcja o tej nazwie wygrywa) |
| `error(komunikat)` | `return error("…")` w funkcji `-> T!` | | `error(komunikat)` | `return error("…")` w funkcji `-> T!` |
| `addr(x)` | wskaźnik na x; `addr(fn)` = callback dla C | | `addr(x)` | wskaźnik na x; `addr(fn)` = callback dla C |
| `assert(warunek)`, `assert(warunek, "dlaczego")` | zatrzymaj się z błędem, gdy warunek jest fałszywy (w bloku `test`: ten test nie przechodzi) |
| `atomic_add(p, n)`, `atomic_sub`, `atomic_load(p)`, `atomic_store(p, n)`, `atomic_swap(p, n)`, `atomic_cas(p, stara, nowa)` | liczba całkowita zmieniana też przez inne wątki, przez jej `ptr` (`addr(licznik)`) |
| `new(wartosc)` | wartość na stercie → `ptr[T]` | | `new(wartosc)` | wartość na stercie → `ptr[T]` |
| `free(p)` | oddaj ją | | `free(p)` | oddaj ją |
@ -289,7 +314,7 @@ Automatycznie tylko wtedy, gdy nic nie ginie: mniejsza → większa liczba, dowo
| Finch | C | | Finch | C |
|---|---| |---|---|
| `ptr[int] p` | `int *p` | | `ptr[int] p` | `int *p` |
| `addr(x)` | `&x` | | `addr(x)` | `&x` (zmienna-struktura podana tam, gdzie idzie `ptr[TaStruktura]`: `pchnij(cialo)` = `pchnij(addr(cialo))`) |
| `p.value` | `*p` | | `p.value` | `*p` |
| `p.pole` | `p->pole` | | `p.pole` | `p->pole` |
| `p[i]` | `p[i]` | | `p[i]` | `p[i]` |
@ -302,6 +327,7 @@ Automatycznie tylko wtedy, gdy nic nie ginie: mniejsza → większa liczba, dowo
- Tablice, teksty i struktury zwalniają się same na końcu swojego bloku. Bez `free`. - Tablice, teksty i struktury zwalniają się same na końcu swojego bloku. Bez `free`.
- `b := a` kopiuje; wynik wywołania jest przenoszony; `return x` przenosi. - `b := a` kopiuje; wynik wywołania jest przenoszony; `return x` przenosi.
- Przekazywanie struktury: `fn f(Cialo c)` pracuje na własnej kopii (ta u wywołującego się nie zmienia), `fn f(ptr[Cialo] c)` zmienia tę u wywołującego (`f(cialo)` albo `f(addr(cialo))`), metoda dostaje `self` przez adres, nigdy kopię.
- `new(...)` + `free(...)` (często z `defer`) do list wiązanych, drzew, współdzielenia. - `new(...)` + `free(...)` (często z `defer`) do list wiązanych, drzew, współdzielenia.
- Pamięć z C: funkcjami z C (`malloc` / `free`, `fclose`, …). - Pamięć z C: funkcjami z C (`malloc` / `free`, `fclose`, …).
@ -326,7 +352,7 @@ import ui/przycisk as p // inna nazwa: p.rysuj()
|---|---| |---|---|
| `import "x.h"` | funkcje, struktury, enumy, liczbowe `#define`, zmienne globalne | | `import "x.h"` | funkcje, struktury, enumy, liczbowe `#define`, zmienne globalne |
| `link "nazwa"` | libnazwa.so / nazwa.lib (najpierw pkg-config) | | `link "nazwa"` | libnazwa.so / nazwa.lib (najpierw pkg-config) |
| `link "plik.c"` | skompiluj własny plik C | | `link "plik.c"`, `link "glue.cpp"` | skompiluj własny plik C albo C++ (C++: funkcje w `extern "C"`) |
| `addr(mojaFn)` | funkcja Fincha jako callback | | `addr(mojaFn)` | funkcja Fincha jako callback |
| Typ C | Finch | | Typ C | Finch |
@ -341,6 +367,10 @@ import ui/przycisk as p // inna nazwa: p.rysuj()
| `T x[N]` (w strukturze) | `[N]T` | | `T x[N]` (w strukturze) | `[N]T` |
| `union U` | `U`: `U u`, `u.pole`, `U(pole: v)`, przez wskaźnik do C (nie przez wartość) | | `union U` | `U`: `U u`, `u.pole`, `U(pole: v)`, przez wskaźnik do C (nie przez wartość) |
| `#define N 4`, `"tekst"`, `(1u << 3)`, `((Uint64)2)` | stałe (bez makr-funkcji) | | `#define N 4`, `"tekst"`, `(1u << 3)`, `((Uint64)2)` | stałe (bez makr-funkcji) |
| `#define RAYWHITE (Color){ 245, 245, 245, 255 }` | `Color` |
**`char *` to `str`**: funkcja C, która bierze albo zwraca `char *`, bierze albo daje `str` Fincha (bez `.ptr`, bez zamiany); tylko pole `char *` i `char **` zostają `ptr[char]`.
C++ i Rust: przez nagłówek C. C++ z `extern "C"` (i `link "x.cpp"`), Rust z `#[no_mangle] pub extern "C" fn` zbudowany jako `staticlib` (`link "target/release/libgra.a"`) i nagłówek (napisze go cbindgen).
Wynik `char *` z funkcji C to od razu `str`; struktury przez wartość działają w konwencjach System V, Windows x64 i ARM64 Linux (sprawdzone w emulatorach; macOS przez CI). Wynik `char *` z funkcji C to od razu `str`; struktury przez wartość działają w konwencjach System V, Windows x64 i ARM64 Linux (sprawdzone w emulatorach; macOS przez CI).
@ -357,6 +387,26 @@ Wynik `char *` z funkcji C to od razu `str`; struktury przez wartość działaj
| `can't turn "x" into int` | `int("x")` | | `can't turn "x" into int` | `int("x")` |
| `can't read the file "…"` | `read_file` (bez `or`) | | `can't read the file "…"` | `read_file` (bez `or`) |
| `the key "x" is not in the map` | `m["x"]` | | `the key "x" is not in the map` | `m["x"]` |
| `assert failed: dlaczego` | `assert(warunek, "dlaczego")` |
Pod komunikatem wywołania, które do niego doprowadziły (`finch run`, `finch test`, `-g`, `-O0`, `--trace`):
```
gra.fch:6: runtime error: index 3 is out of range (the length is 2)
in Enemy.next (gra.fch:6)
in step (gra.fch:10)
in main (gra.fch:15)
```
## Testy
```c
test "wysokość skoku" { // obok kodu; finch test plik.fch je uruchamia, finch run/build je pomijają
g := Gracz()
g.skocz()
assert(g.y > 0.0, "y to \(g.y)")
}
```
--- ---
@ -406,6 +456,7 @@ s := Stos[int]() Stos[int] t Para[str, int]("wiek", 31) // ni
| `path` | `join(a, b)` `base(p)` `dir(p)` `ext(p)` `stem(p)` `parts(p)` `clean(p)` `is_abs(p)` | | `path` | `join(a, b)` `base(p)` `dir(p)` `ext(p)` `stem(p)` `parts(p)` `clean(p)` `is_abs(p)` |
| `time` | `now()` `millis()` `sleep(ms)` `date(t)` `local(t)` `format(t, fmt)` `days_in_month(r, m)` | | `time` | `now()` `millis()` `sleep(ms)` `date(t)` `local(t)` `format(t, fmt)` `days_in_month(r, m)` |
| `proc` | `run(cmd)` `output(cmd)` (błąd z jego wyjściem) `env(nazwa, domyślna)` `pid()` `cwd()` | | `proc` | `run(cmd)` `output(cmd)` (błąd z jego wyjściem) `env(nazwa, domyślna)` `pid()` `cwd()` |
| `thread` | `spawn(addr(praca), addr(dane))` → `t.join()`; `mutex()` → `m.lock()` `m.unlock()` `m.close()`; `cpus()` (funkcja wątku: `fn praca(ptr arg)`) |
| `fs` | `read_bytes(p)` `write_bytes(p, dane)` `exists(p)` `is_dir(p)` `mkdir(p)` `list(folder)` `remove_dir(p)` `data_dir(app)` | | `fs` | `read_bytes(p)` `write_bytes(p, dane)` `exists(p)` `is_dir(p)` `mkdir(p)` `list(folder)` `remove_dir(p)` `data_dir(app)` |
| `json` | `parse(tekst)` `stringify(v)` `pretty(v)` `new_text/number/bool/null/list/object` `v.get(k)` `v.at(i)` `v.set(k, x)` `v.push(x)` `v.has(k)` `v.str_or(d)` `v.int_or(d)` `v.float_or(d)` `v.bool_or(d)` | | `json` | `parse(tekst)` `stringify(v)` `pretty(v)` `new_text/number/bool/null/list/object` `v.get(k)` `v.at(i)` `v.set(k, x)` `v.push(x)` `v.has(k)` `v.str_or(d)` `v.int_or(d)` `v.float_or(d)` `v.bool_or(d)` |
@ -430,4 +481,6 @@ s := Stos[int]() Stos[int] t Para[str, int]("wiek", 31) // ni
| `const` | stała | | `const` | stała |
| `export` | wystaw (funkcja dla programu w C) | | `export` | wystaw (funkcja dla programu w C) |
| `self` | sam (w metodzie: struktura, na której ją wywołano) | | `self` | sam (w metodzie: struktura, na której ją wywołano) |
| `test` | test (tylko jako `test "nazwa" { }`) |
| `as` | jako (tylko w `import x as y`) |
| `map` | mapa | | `map` | mapa |

View file

@ -5,7 +5,7 @@
Install: copy to ~/.local/share/org.kde.syntax-highlighting/syntax/finch.xml Install: copy to ~/.local/share/org.kde.syntax-highlighting/syntax/finch.xml
(or run editors/kate/install.sh), then restart Kate. (or run editors/kate/install.sh), then restart Kate.
--> -->
<language name="Finch" version="3" kateversion="5.0" section="Sources" extensions="*.fch" <language name="Finch" version="4" kateversion="5.0" section="Sources" extensions="*.fch"
mimetype="text/x-finch" indenter="cstyle" author="The Finch authors" license="Apache-2.0"> mimetype="text/x-finch" indenter="cstyle" author="The Finch authors" license="Apache-2.0">
<highlighting> <highlighting>
<list name="keywords"> <list name="keywords">
@ -71,6 +71,17 @@
<item>new</item> <item>new</item>
<item>free</item> <item>free</item>
<item>error</item> <item>error</item>
<item>min</item>
<item>max</item>
<item>abs</item>
<item>clamp</item>
<item>assert</item>
<item>atomic_add</item>
<item>atomic_sub</item>
<item>atomic_load</item>
<item>atomic_store</item>
<item>atomic_swap</item>
<item>atomic_cas</item>
</list> </list>
<contexts> <contexts>
@ -85,6 +96,8 @@
<DetectChar char="'" attribute="Char" context="Char"/> <DetectChar char="'" attribute="Char" context="Char"/>
<WordDetect String="fn" attribute="Keyword" context="FunctionName"/> <WordDetect String="fn" attribute="Keyword" context="FunctionName"/>
<RegExpr String="\btest(?=\s*&quot;)" attribute="Keyword"/>
<RegExpr String="(?&lt;=\s)as(?=\s+[A-Za-z_])" attribute="Import"/>
<WordDetect String="struct" attribute="Keyword" context="StructName"/> <WordDetect String="struct" attribute="Keyword" context="StructName"/>
<keyword String="control" attribute="Control Flow"/> <keyword String="control" attribute="Control Flow"/>
<keyword String="imports" attribute="Import"/> <keyword String="imports" attribute="Import"/>
@ -134,16 +147,30 @@
</context> </context>
<context name="String" attribute="String" lineEndContext="#pop"> <context name="String" attribute="String" lineEndContext="#pop">
<RegExpr String="\\\(" attribute="Escape" context="Interpolation"/>
<RegExpr String="\\[ntr0\\&quot;']" attribute="Escape"/> <RegExpr String="\\[ntr0\\&quot;']" attribute="Escape"/>
<RegExpr String="\\." attribute="Error"/> <RegExpr String="\\." attribute="Error"/>
<DetectChar char="&quot;" attribute="String" context="#pop"/> <DetectChar char="&quot;" attribute="String" context="#pop"/>
</context> </context>
<context name="LongString" attribute="String" lineEndContext="#stay"> <context name="LongString" attribute="String" lineEndContext="#stay">
<RegExpr String="\\\(" attribute="Escape" context="Interpolation"/>
<RegExpr String="\\[ntr0\\&quot;']" attribute="Escape"/> <RegExpr String="\\[ntr0\\&quot;']" attribute="Escape"/>
<StringDetect String="&quot;&quot;&quot;" attribute="String" context="#pop" endRegion="LongString"/> <StringDetect String="&quot;&quot;&quot;" attribute="String" context="#pop" endRegion="LongString"/>
</context> </context>
<!-- "hp \(hp + 1)": code inside text, up to the ) that closes \( -->
<context name="Interpolation" attribute="Normal Text" lineEndContext="#stay">
<DetectChar char=")" attribute="Escape" context="#pop"/>
<DetectChar char="(" attribute="Symbol" context="InterpolationParen"/>
<IncludeRules context="Normal"/>
</context>
<context name="InterpolationParen" attribute="Normal Text" lineEndContext="#stay">
<DetectChar char=")" attribute="Symbol" context="#pop"/>
<DetectChar char="(" attribute="Symbol" context="InterpolationParen"/>
<IncludeRules context="Normal"/>
</context>
<context name="Char" attribute="Char" lineEndContext="#pop"> <context name="Char" attribute="Char" lineEndContext="#pop">
<RegExpr String="\\[ntr0\\&quot;']" attribute="Escape"/> <RegExpr String="\\[ntr0\\&quot;']" attribute="Escape"/>
<DetectChar char="'" attribute="Char" context="#pop"/> <DetectChar char="'" attribute="Char" context="#pop"/>

View file

@ -1,5 +1,11 @@
# Changelog # Changelog
## 2.5.0
- Highlighting for code inside text (`"hp \(hp)"`), `test "…" { }` blocks, `const`, `"""` text, `1e9`.
- Built-ins `min`, `max`, `abs`, `clamp`, `assert` and `atomic_…` highlighted and described on hover.
- Format Document (the language server runs `finch fmt` on the open file).
## 2.4.0 ## 2.4.0
- Highlighting for methods (`fn Point.move`), maps (`map[K]V`), `or`, `try`, `self` and `error(...)`. - Highlighting for methods (`fn Point.move`), maps (`map[K]V`), `or`, `try`, `self` and `error(...)`.

View file

@ -2,7 +2,7 @@
"name": "finch-lang", "name": "finch-lang",
"displayName": "Finch", "displayName": "Finch",
"description": "The Finch programming language: highlighting, errors as you type, completion, hover, go to definition, and a Run button.", "description": "The Finch programming language: highlighting, errors as you type, completion, hover, go to definition, and a Run button.",
"version": "2.4.0", "version": "2.5.0",
"publisher": "nexoniarz", "publisher": "nexoniarz",
"license": "Apache-2.0", "license": "Apache-2.0",
"icon": "images/icon.png", "icon": "images/icon.png",

View file

@ -103,6 +103,10 @@
}, },
"statements": { "statements": {
"patterns": [ "patterns": [
{
"name": "keyword.other.test.finch",
"match": "\\btest(?=\\s*\")"
},
{ {
"include": "#comments" "include": "#comments"
}, },
@ -147,7 +151,7 @@
"match": "\\b(int|float|bool|char|str|ptr|map|i8|i16|i32|i64|u8|u16|u32|u64|f32|f64)\\b" "match": "\\b(int|float|bool|char|str|ptr|map|i8|i16|i32|i64|u8|u16|u32|u64|f32|f64)\\b"
}, },
{ {
"match": "\\b(print|input|read_file|write_file|file_exists|delete_file|shell|exit|addr|new|free|error)\\s*(?=\\()", "match": "\\b(print|input|read_file|write_file|file_exists|delete_file|shell|exit|addr|new|free|error|min|max|abs|clamp|assert|atomic_add|atomic_sub|atomic_load|atomic_store|atomic_swap|atomic_cas)\\s*(?=\\()",
"captures": { "captures": {
"1": { "1": {
"name": "support.function.builtin.finch" "name": "support.function.builtin.finch"
@ -217,6 +221,9 @@
"begin": "\"", "begin": "\"",
"end": "\"|$", "end": "\"|$",
"patterns": [ "patterns": [
{
"include": "#interpolation"
},
{ {
"name": "constant.character.escape.finch", "name": "constant.character.escape.finch",
"match": "\\\\[ntr0\\\\\"']" "match": "\\\\[ntr0\\\\\"']"
@ -259,11 +266,49 @@
"begin": "\"\"\"", "begin": "\"\"\"",
"end": "\"\"\"", "end": "\"\"\"",
"patterns": [ "patterns": [
{
"include": "#interpolation"
},
{ {
"name": "constant.character.escape.finch", "name": "constant.character.escape.finch",
"match": "\\\\[ntr0\\\\\"']" "match": "\\\\[ntr0\\\\\"']"
} }
] ]
},
"interpolation": {
"name": "meta.embedded.line.finch",
"begin": "\\\\\\(",
"end": "\\)",
"beginCaptures": {
"0": {
"name": "punctuation.section.embedded.begin.finch"
}
},
"endCaptures": {
"0": {
"name": "punctuation.section.embedded.end.finch"
}
},
"patterns": [
{
"include": "#interpolation-parens"
},
{
"include": "$self"
}
]
},
"interpolation-parens": {
"begin": "\\(",
"end": "\\)",
"patterns": [
{
"include": "#interpolation-parens"
},
{
"include": "$self"
}
]
} }
} }
} }

View file

@ -26,6 +26,9 @@ const code = [
' self.x += dx', ' self.x += dx',
' return error("no")', ' return error("no")',
'}', '}',
'test "lives \\(hp + f(1)) left" {',
' assert(clamp(x, 0, 9) == 3)',
'}',
]; ];
const expect = [ // [line, token text, scope that must be present] const expect = [ // [line, token text, scope that must be present]
[0, 'import', 'keyword.control.import'], [0, '"stdio.h"', 'string.quoted.double'], [0, 'import', 'keyword.control.import'], [0, '"stdio.h"', 'string.quoted.double'],
@ -43,6 +46,9 @@ const expect = [ // [line, token text, scope that must be present]
[13, 'try', 'keyword.control'], [13, 'or', 'keyword.control'], [13, 'try', 'keyword.control'], [13, 'or', 'keyword.control'],
[14, 'self', 'variable.language.self'], [14, 'self', 'variable.language.self'],
[15, 'error', 'support.function.builtin'], [15, 'error', 'support.function.builtin'],
[17, 'test', 'keyword.other.test'], [17, '\\(', 'punctuation.section.embedded.begin'], [17, 'hp', 'meta.embedded.line'],
[17, 'f', 'meta.embedded.line'], [17, 'left', 'string.quoted.double'],
[18, 'assert', 'support.function.builtin'], [18, 'clamp', 'support.function.builtin'],
]; ];
registry.loadGrammar('source.finch').then((g) => { registry.loadGrammar('source.finch').then((g) => {
let state = vsctm.INITIAL, toks = []; let state = vsctm.INITIAL, toks = [];

View file

@ -243,7 +243,7 @@ fn format([]str args) -> int {
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.5.0 (the compiler is written in Finch)")
return 0 return 0
} }
if args.len >= 2 && args[1] == "fmt" { if args.len >= 2 && args[1] == "fmt" {
@ -274,7 +274,8 @@ fn main([]str args) -> int {
print(err) print(err)
return 1 return 1
} }
if file.len == 0 { isEntry := file.len == 0
if isEntry {
entry := man.value("entry", "") entry := man.value("entry", "")
if !man.found || entry.len == 0 { if !man.found || entry.len == 0 {
print("error: which file? (finch " + cmd + " <file.fch>, or a finch.toml with entry = \"main.fch\")") print("error: which file? (finch " + cmd + " <file.fch>, or a finch.toml with entry = \"main.fch\")")
@ -283,7 +284,7 @@ fn main([]str args) -> int {
file = man.resolve(entry) file = man.resolve(entry)
} }
out := stem(file) out := stem(file)
if man.found && man.value("name", "").len > 0 { if isEntry && man.value("name", "").len > 0 { // another file of the project keeps its own name
out = man.value("name", "") out = man.value("name", "")
} }
outGiven := false outGiven := false