diff --git a/README.md b/README.md index e11d1d0..edec82a 100644 --- a/README.md +++ b/README.md @@ -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 { }` | | 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)` | -| 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` | | 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) | | 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` | -| C interop | `import "stdio.h"`, `link "glfw"`, `link "mine.c"`, `addr(fn)` for callbacks | +| Modules | `import shapes` → `shapes.area(b)`, `import ui/button as btn`; the standard library: `json`, `path`, `time`, `proc`, `fs`, `thread` | +| 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` | -| 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 @@ -147,6 +149,9 @@ Pick the guide that fits you: - [x] Methods on structs, maps, errors as values (`!`, `or`, `try`) - [x] Generics (`fn max[T]`, `struct Stack[T]`), `match`, enums - [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 diff --git a/README.pl.md b/README.pl.md index 61eadf9..b4c2651 100644 --- a/README.pl.md +++ b/README.pl.md @@ -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 { }` | | 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)` | -| 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` | | 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ść) | | 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` | -| Współpraca z C | `import "stdio.h"`, `link "glfw"`, `link "moje.c"`, `addr(fn)` dla callbacków | +| Moduły | `import ksztalty` → `ksztalty.pole(p)`, `import ui/button as btn`; biblioteka standardowa: `json`, `path`, `time`, `proc`, `fs`, `thread` | +| 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` | -| 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 @@ -147,6 +149,9 @@ Wybierz przewodnik dla siebie: - [x] Metody w strukturach, mapy, błędy jako wartości (`!`, `or`, `try`) - [x] Typy generyczne (`fn max[T]`, `struct Stos[T]`), `match`, enumy - [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 diff --git a/docs/en/cheatsheet.md b/docs/en/cheatsheet.md index b747bfa..3497dc6 100644 --- a/docs/en/cheatsheet.md +++ b/docs/en/cheatsheet.md @@ -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 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 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 lsp` | the language server (editors use it) | | `finch version` | version | | `-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) | | `-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` | | `bool` | `true`, `false` | | `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 | | `u8 u16 u32 u64` | unsigned, sized | | `f32 f64` | floats | @@ -226,6 +247,7 @@ Fallible built-ins: `int(s)`, `float(s)`, `read_file`, `write_file`, `delete_fil | `s.len` | bytes | | `s[i]`, `s[i] = 'x'` | char | | `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.find(t)` | position or -1 | | `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) | | `error(message)` | `return error("…")` in a `-> T!` function | | `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]` | | `free(p)` | give it back | @@ -290,7 +314,7 @@ Automatic only when nothing is lost: smaller → bigger number, any int → floa | Finch | C | |---|---| | `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.field` | `p->field` | | `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. - `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. - 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 | | `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 | | C type | Finch | @@ -342,6 +367,10 @@ import ui/button as btn // another name: btn.draw() | `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) | | `#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). @@ -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 read the file "…"` | `read_file` (without `or`) | | `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)` | | `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()` | +| `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)` | | `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 `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) diff --git a/docs/en/for-beginners.md b/docs/en/for-beginners.md index 60f32b4..fd3bedc 100644 --- a/docs/en/for-beginners.md +++ b/docs/en/for-beginners.md @@ -76,7 +76,7 @@ To check it works, type: ./build/finch version ``` -You should see something like `Finch 2.4.0`. +You should see something like `Finch 2.5.0`. --- diff --git a/docs/en/for-engineers.md b/docs/en/for-engineers.md index 53a92a5..d80aefc 100644 --- a/docs/en/for-engineers.md +++ b/docs/en/for-engineers.md @@ -1,6 +1,6 @@ # 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 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. @@ -69,18 +69,20 @@ Design decisions that shape the rest: | 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/parser.fch` | 780 | Recursive descent: declarations, statements, expressions, generics (`[T]`), `enum`, `match`, `export`, `link` | -| `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/cimport.fch` | 560 | libclang: functions, structs (fields, offsets, padding), enums, constants, globals, macros | +| `src/parser.fch` | 910 | Recursive descent: declarations, statements, expressions, generics (`[T]`), `enum`, `match`, `export`, `link`, `const`, `test "…" { }` | +| `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` | 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/linker.fch` | 300 | The cache, runtime object, 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/lsp.fch` | 875 | The language server (§11) | +| `src/linker.fch` | 430 | The cache (runtime, program object, C/C++ files), libraries (`link`, pkg-config), link-error advice | +| `src/main.fch` | 515 | Driver: CLI, loader with module lookup and overlays, `build/run/test/fmt/ir/index/lsp` | +| `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 | -| `std/*.fch` | 900 | `json`, `path`, `time` (+`_windows`), `proc` (+`_windows`) | -| `runtime/finch_rt.c` | 760 | The C runtime | +| `std/*.fch` | 1040 | `json`, `path`, `time` (+`_windows`), `proc` (+`_windows`), `fs`, `thread` | +| `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) | --- @@ -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 `export fn`s. -`--target windows|linux|arm64|macos|` changes the triple and data layout, the C `char` signedness, the ABI rules +`--target windows|linux|arm64|macos|android|` 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 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. @@ -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 build cache** (`host.cacheDir()`: `$XDG_CACHE_HOME/finch`, `~/.cache/finch`, `%LOCALAPPDATA%\finch`): + +- `ir/.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/.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` 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.]` and `[target.-]`. 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//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_`, 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 @@ -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 , 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 `, 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 - 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. - Generics are monomorphised (code size grows with instantiations); there are no trait bounds, a wrong use is an error 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. diff --git a/docs/en/for-technicians.md b/docs/en/for-technicians.md index ac480ac..2615007 100644 --- a/docs/en/for-technicians.md +++ b/docs/en/for-technicians.md @@ -37,6 +37,10 @@ compiler works inside. For that, see the [guide for engineers](for-engineers.md) 24. [Generics](#24-generics) 25. [The standard library](#25-the-standard-library) 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 [args...] compile and run right away finch build [-o name] compile into a program (default name: the file's name) +finch test run its test "..." { } blocks (section 27) +finch fmt [--check] [files/dirs] tidy the indentation of .fch files (all under here by default) finch ir print the LLVM IR (for the curious) finch lsp the language server, for editors finch version show the version options: -l link a C library, same as link "lib" in the file - --target build for another system: windows, linux, arm64, macos, or an LLVM triple + -I look for C headers there too + --target build for another system: windows, linux, arm64, macos, android, or an LLVM triple -g add debug info (for gdb / lldb / Visual Studio) -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: ```sh @@ -503,6 +521,13 @@ s[0] = 'J' // change a character | `s.bytes()` | `[]u8` with the bytes | | `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 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. @@ -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 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`). - Macros that are a number, a text or an expression of them (`(1u << 3)`, `((Uint64)1)`, `(A | B)`) work. +- **Function-like macros** (`#define MAX(a, b) ...`). Macros that are a number, a text, an expression of them + (`(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. --- @@ -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: 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: 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/ ├── 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) ├── 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 ├── tests/ │ ├── 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 +│ ├── 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) │ ├── 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 @@ -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()` | | `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) | +| `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 your own with the same name wins). @@ -1093,7 +1133,155 @@ Not there yet: - Interfaces. - Unicode-aware text: `.len`, `s[i]` and `upper()` work on bytes / ASCII. -- Threads. -- Windows on ARM64, 32-bit systems, WebAssembly. +- Checks that stop two threads from changing the same array at once (use a mutex). +- 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). + +## 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. diff --git a/docs/pl/dla-inzynierow.md b/docs/pl/dla-inzynierow.md index f56e647..a8319f6 100644 --- a/docs/pl/dla-inzynierow.md +++ b/docs/pl/dla-inzynierow.md @@ -1,6 +1,6 @@ # 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 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. @@ -68,18 +68,20 @@ Decyzje projektowe: | 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/parser.fch` | 780 | Zejście rekurencyjne: deklaracje, instrukcje, wyrażenia, generyki (`[T]`), `enum`, `match`, `export`, `link` | -| `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/cimport.fch` | 560 | libclang: funkcje, struktury (pola, offsety, wypełnienie), enumy, stałe, globalne, makra | +| `src/parser.fch` | 910 | Zejście rekurencyjne: deklaracje, instrukcje, wyrażenia, generyki (`[T]`), `enum`, `match`, `export`, `link`, `const`, `test "…" { }` | +| `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` | 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/linker.fch` | 300 | Pamięć podręczna, obiekt runtime'u, 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/lsp.fch` | 875 | Serwer języka (§11) | +| `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` | 515 | Sterownik: CLI, loader z wyszukiwaniem modułów i nakładkami, `build/run/test/fmt/ir/index/lsp` | +| `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 | -| `std/*.fch` | 900 | `json`, `path`, `time` (+`_windows`), `proc` (+`_windows`) | -| `runtime/finch_rt.c` | 760 | Runtime w C | +| `std/*.fch` | 1040 | `json`, `path`, `time` (+`_windows`), `proc` (+`_windows`), `fs`, `thread` | +| `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) | --- @@ -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]`. `ir` wypisuje IR; `index` wypisuje rekordy dla serwera języka (§11); `--shared` robi bibliotekę współdzieloną z `export fn`. -`--target windows|linux|arm64|macos|` zmienia triple i układ danych, znak `char`, reguły ABI powyżej, wariant OS +`--target windows|linux|arm64|macos|android|` 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 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. @@ -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`). +**Pamięć podręczna budowania** (`host.cacheDir()`: `$XDG_CACHE_HOME/finch`, `~/.cache/finch`, `%LOCALAPPDATA%\finch`): + +- `ir/.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/.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` 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.]` oraz `[target.-]`. Ś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//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_`, 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 @@ -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 , 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 `, 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 - 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. - 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. +- 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. diff --git a/docs/pl/dla-poczatkujacych.md b/docs/pl/dla-poczatkujacych.md index a0d5355..5b48cf2 100644 --- a/docs/pl/dla-poczatkujacych.md +++ b/docs/pl/dla-poczatkujacych.md @@ -76,7 +76,7 @@ Jeśli ostatnia linijka skończy się bez słowa `error`, Finch jest gotowy. ./build/finch version ``` -Powinieneś zobaczyć coś w stylu `finch 2.4.0`. +Powinieneś zobaczyć coś w stylu `finch 2.5.0`. --- diff --git a/docs/pl/dla-technikow.md b/docs/pl/dla-technikow.md index 20e6361..cfd1021 100644 --- a/docs/pl/dla-technikow.md +++ b/docs/pl/dla-technikow.md @@ -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) 25. [Biblioteka standardowa](#25-biblioteka-standardowa) 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 [argumenty...] skompiluj i od razu uruchom finch build [-o nazwa] skompiluj do programu (domyślna nazwa: nazwa pliku) +finch test 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 pokaż LLVM IR (dla ciekawskich) finch lsp serwer języka, dla edytorów finch version pokaż wersję opcje: -l dolinkuj bibliotekę C, to samo co link "lib" w pliku - --target zbuduj na inny system: windows, linux, arm64, macos albo triple LLVM + -I szukaj nagłówków C także tam + --target zbuduj na inny system: windows, linux, arm64, macos, android albo triple LLVM -g dodaj informacje dla debuggera (gdb / lldb / Visual Studio) -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: ```sh @@ -505,6 +523,13 @@ s[0] = 'D' // zmiana znaku | `s.bytes()` | `[]u8` z bajtami | | `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 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. @@ -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ść** (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 będące liczbą, tekstem albo wyrażeniem z nich (`(1u << 3)`, `((Uint64)1)`, `(A | B)`) działają. +- **Makra-funkcje** (`#define MAX(a, b) ...`). Makra będące liczbą, tekstem, wyrażeniem z nich + (`(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. --- @@ -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: 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: 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/ ├── 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) ├── 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 ├── tests/ │ ├── 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 +│ ├── tool/ finch test, finch fmt, łańcuch wywołań, projekt z finch.toml │ ├── run.sh uruchamia testy (MEMCHECK=1 sprawdza też pamięć valgrindem) │ ├── 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 @@ -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()` | | `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) | +| `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 własna funkcja o tej samej nazwie wygrywa). @@ -1097,7 +1137,156 @@ Jeszcze nie ma: - Interfejsów. - Tekstu świadomego Unicode: `.len`, `s[i]` i `upper()` działają na bajtach / ASCII. -- Wątków. -- Windowsa na ARM64, systemów 32-bitowych, WebAssembly. +- Pilnowania, żeby dwa wątki nie zmieniały tej samej tablicy naraz (do tego mutex). +- 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). + +## 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. diff --git a/docs/pl/sciaga.md b/docs/pl/sciaga.md index c77f1c2..6123cf1 100644 --- a/docs/pl/sciaga.md +++ b/docs/pl/sciaga.md @@ -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 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 lsp` | serwer języka (korzystają z niego edytory) | | `finch version` | wersja | | `-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) | | `-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` | | `bool` | `true` (prawda), `false` (fałsz) | | `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 | | `u8 u16 u32 u64` | bez znaku, z rozmiarem | | `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[i]`, `s[i] = 'x'` | znak | | `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.find(t)` | pozycja albo -1 | | `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) | | `error(komunikat)` | `return error("…")` w funkcji `-> T!` | | `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]` | | `free(p)` | oddaj ją | @@ -289,7 +314,7 @@ Automatycznie tylko wtedy, gdy nic nie ginie: mniejsza → większa liczba, dowo | Finch | C | |---|---| | `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.pole` | `p->pole` | | `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`. - `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. - 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 | | `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 | | Typ C | Finch | @@ -341,6 +367,10 @@ import ui/przycisk as p // inna nazwa: p.rysuj() | `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ść) | | `#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). @@ -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 read the file "…"` | `read_file` (bez `or`) | | `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)` | | `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()` | +| `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)` | | `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 | | `export` | wystaw (funkcja dla programu w C) | | `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 | diff --git a/editors/kate/finch.xml b/editors/kate/finch.xml index baef0e0..883a6e0 100644 --- a/editors/kate/finch.xml +++ b/editors/kate/finch.xml @@ -5,7 +5,7 @@ Install: copy to ~/.local/share/org.kde.syntax-highlighting/syntax/finch.xml (or run editors/kate/install.sh), then restart Kate. --> - @@ -71,6 +71,17 @@ new free error + min + max + abs + clamp + assert + atomic_add + atomic_sub + atomic_load + atomic_store + atomic_swap + atomic_cas @@ -85,6 +96,8 @@ + + @@ -134,16 +147,30 @@ + + + + + + + + + + + + + + diff --git a/editors/vscode/CHANGELOG.md b/editors/vscode/CHANGELOG.md index 168a8cd..563cf05 100644 --- a/editors/vscode/CHANGELOG.md +++ b/editors/vscode/CHANGELOG.md @@ -1,5 +1,11 @@ # 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 - Highlighting for methods (`fn Point.move`), maps (`map[K]V`), `or`, `try`, `self` and `error(...)`. diff --git a/editors/vscode/package.json b/editors/vscode/package.json index 2d01781..40c7715 100644 --- a/editors/vscode/package.json +++ b/editors/vscode/package.json @@ -2,7 +2,7 @@ "name": "finch-lang", "displayName": "Finch", "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", "license": "Apache-2.0", "icon": "images/icon.png", diff --git a/editors/vscode/syntaxes/finch.tmLanguage.json b/editors/vscode/syntaxes/finch.tmLanguage.json index 417e942..9154a4e 100644 --- a/editors/vscode/syntaxes/finch.tmLanguage.json +++ b/editors/vscode/syntaxes/finch.tmLanguage.json @@ -103,6 +103,10 @@ }, "statements": { "patterns": [ + { + "name": "keyword.other.test.finch", + "match": "\\btest(?=\\s*\")" + }, { "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(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": { "1": { "name": "support.function.builtin.finch" @@ -217,6 +221,9 @@ "begin": "\"", "end": "\"|$", "patterns": [ + { + "include": "#interpolation" + }, { "name": "constant.character.escape.finch", "match": "\\\\[ntr0\\\\\"']" @@ -259,11 +266,49 @@ "begin": "\"\"\"", "end": "\"\"\"", "patterns": [ + { + "include": "#interpolation" + }, { "name": "constant.character.escape.finch", "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" + } + ] } } } diff --git a/editors/vscode/test/grammar.js b/editors/vscode/test/grammar.js index 3d5ba77..bf2aae7 100644 --- a/editors/vscode/test/grammar.js +++ b/editors/vscode/test/grammar.js @@ -26,6 +26,9 @@ const code = [ ' self.x += dx', ' 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] [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'], [14, 'self', 'variable.language.self'], [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) => { let state = vsctm.INITIAL, toks = []; diff --git a/src/main.fch b/src/main.fch index f4fd6f1..b256fba 100644 --- a/src/main.fch +++ b/src/main.fch @@ -243,7 +243,7 @@ fn format([]str args) -> int { fn main([]str args) -> int { 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 } if args.len >= 2 && args[1] == "fmt" { @@ -274,7 +274,8 @@ fn main([]str args) -> int { print(err) return 1 } - if file.len == 0 { + isEntry := file.len == 0 + if isEntry { entry := man.value("entry", "") if !man.found || entry.len == 0 { print("error: which file? (finch " + cmd + " , or a finch.toml with entry = \"main.fch\")") @@ -283,7 +284,7 @@ fn main([]str args) -> int { file = man.resolve(entry) } 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", "") } outGiven := false