mirror of
https://github.com/Nexoniarz/Finch.git
synced 2026-11-03 08:54:25 +00:00
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:
parent
d428498efd
commit
bb9702aeba
16 changed files with 778 additions and 60 deletions
13
README.md
13
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 { }` |
|
| 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
|
||||||
|
|
||||||
|
|
|
||||||
13
README.pl.md
13
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 { }` |
|
| 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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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 |
|
||||||
|
|
|
||||||
|
|
@ -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*")" attribute="Keyword"/>
|
||||||
|
<RegExpr String="(?<=\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\\"']" attribute="Escape"/>
|
<RegExpr String="\\[ntr0\\"']" attribute="Escape"/>
|
||||||
<RegExpr String="\\." attribute="Error"/>
|
<RegExpr String="\\." attribute="Error"/>
|
||||||
<DetectChar char=""" attribute="String" context="#pop"/>
|
<DetectChar char=""" 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\\"']" attribute="Escape"/>
|
<RegExpr String="\\[ntr0\\"']" attribute="Escape"/>
|
||||||
<StringDetect String=""""" attribute="String" context="#pop" endRegion="LongString"/>
|
<StringDetect String=""""" 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\\"']" attribute="Escape"/>
|
<RegExpr String="\\[ntr0\\"']" attribute="Escape"/>
|
||||||
<DetectChar char="'" attribute="Char" context="#pop"/>
|
<DetectChar char="'" attribute="Char" context="#pop"/>
|
||||||
|
|
|
||||||
|
|
@ -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(...)`.
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 = [];
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue