docs: the Polish cheat sheet and engineers' guide rewritten in natural Polish; the engineers' guide matches the Finch compiler

- names from the C++ compiler (declareFns, HelperScope, Fn::borrowParam, mutatingMethods) replaced by the real ones
- section 8's subsections numbered 8.x; the map's LLVM type has 7 fields; @W in the index records

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Psc42moPLwP43Cmb9oZqSR
This commit is contained in:
Claude 2026-11-01 14:01:56 +00:00
commit 8495bc718d
No known key found for this signature in database
3 changed files with 550 additions and 501 deletions

View file

@ -114,18 +114,19 @@ dedented by the closing line's indentation in `longString()`.
## 4. Code generation ## 4. Code generation
`gen.fch` is one big `Gen` struct plus functions over it. The passes are: `gen.fch` is one big `Gen` struct plus functions over it. `generate()` runs:
1. `declareStructs`: every Finch and C struct gets an LLVM type (C structs with their real padding); recursive-by-value 1. `declare()`: every Finch and C struct gets an LLVM type (C structs with their real padding; recursive-by-value
structs are refused. structs are refused), then every function gets its signature, with a *borrow analysis* for parameters (§8.2).
2. `declareFns`: signatures, with a *borrow analysis* for parameters (a parameter that is never stored is passed without 2. `define()` for each function: the body, type checked and emitted. Scopes track what must be dropped when they end.
a copy). Instantiating a generic adds functions while this goes on, so the loop runs until no new ones appear.
3. `define`: each body, type checked and emitted. Scopes track what must be dropped when they end (`defer` runs first). 3. The C entry point `main(argc, argv)` (or, for `--shared`, `finch_module_on_fatal` and the `export fn` wrappers).
4. `defineMainWrapper`: the C `main(argc, argv)`, and `finch_on_fatal` for embedders. 4. `assemble()`: the declarations, struct types, string constants, helpers and bodies, as one `.ll` text.
5. With `-g`, DWARF metadata is attached as the IR is written (use `-O0`).
With `-g`, DWARF metadata is attached as the IR is written (use `-O0`).
Generics are **monomorphised**: a call `max(1, 2)` infers `T`, then the template's AST is generated again with `T` bound, Generics are **monomorphised**: a call `max(1, 2)` infers `T`, then the template's AST is generated again with `T` bound,
under a mangled name (`max$int`). `match` over an enum is exhaustive (a missing variant is an error, `else` covers the under its shown name (`max[int]`, a symbol `max.int`). `match` over an enum is exhaustive (a missing variant is an error, `else` covers the
rest); on a value it is a chain of comparisons. Enum values are `i32`; `Color(n)` is range checked; printing uses a rest); on a value it is a chain of comparisons. Enum values are `i32`; `Color(n)` is range checked; printing uses a
generated name table. generated name table.
@ -162,7 +163,7 @@ offset 0, and a field is read through memory. They can't be passed by value to C
## 6. Driver, linking and targets ## 6. Driver, linking and targets
`finch build|run|ir|index file.fch [-o out] [-O0] [-g] [-l lib] [-I dir] [--target t] [--shared] [--overlay path=file]`. `finch build|run|ir|index file.fch [-o out] [-O0] [-g] [-l lib] [-I dir] [--target t] [--cpu c] [--shared] [--overlay path=file]`.
`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.
@ -211,6 +212,7 @@ owned at that point is not freed.
A line that starts at bracket depth 0 and doesn't continue a line ending in an operator or `,` is re-indented to 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 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. statement, so their shape (and the `"""` dedent) is kept.
--- ---
## 7. Type system ## 7. Type system
@ -223,7 +225,7 @@ statement, so their shape (and the `"""` dedent) is kept.
| `char` `i8` `u8` / `i16` `u16` / `i32` `u32` / `int` `u64` | `i8` / `i16` / `i32` / `i64` | | `char` `i8` `u8` / `i16` `u16` / `i32` `u32` / `int` `u64` | `i8` / `i16` / `i32` / `i64` |
| `f32` / `float` | `float` / `double` | | `f32` / `float` | `float` / `double` |
| `str`, `[]T` | `{ ptr, i64, i64 }` (pointer, length, capacity) | | `str`, `[]T` | `{ ptr, i64, i64 }` (pointer, length, capacity) |
| `map[K]V` | `{ ptr, i64, i64, i64, ptr, i64 }` (entries, len, used, cap, index, icap; see src/gen.fch) | | `map[K]V` | `{ ptr, i64, i64, i64, ptr, i64, i64 }` (entries, len, used, cap, index, icap, adds; see src/gen.fch) |
| `ptr`, `ptr[T]`, `null` | `ptr` (opaque) | | `ptr`, `ptr[T]`, `null` | `ptr` (opaque) |
| `[N]T` | `[N x T]` | | `[N]T` | `[N x T]` |
| `f32x8`, `i8x32`, … | `<8 x float>`, `<32 x i8>` (§7, Vectors) | | `f32x8`, `i8x32`, … | `<8 x float>`, `<32 x i8>` (§7, Vectors) |
@ -322,11 +324,9 @@ IR's compile, so the same IR becomes SSE2, AVX2 + FMA, AVX-512 or NEON code. Lan
--- ---
---
## 8. Ownership and memory ## 8. Ownership and memory
### 9.1 The model ### 8.1 The model
Every value of an **owning type** (`str`, `[]T`, or a Finch struct with an owning field) has exactly one Every value of an **owning type** (`str`, `[]T`, or a Finch struct with an owning field) has exactly one
owner: a variable, an array element, a struct field, or a heap cell from `new()`. The owner drops the owner: a variable, an array element, a struct field, or a heap cell from `new()`. The owner drops the
@ -350,21 +350,21 @@ A string literal is a fresh value with `cap = 0`: moving it costs nothing, dropp
(`release()` ignores constants), and writing into it (`s[i] = c`) first makes a heap copy (`release()` ignores constants), and writing into it (`s[i] = c`) first makes a heap copy
(`finch_str_own`). Strings from C are borrowed with `cap = -1` and always copied before being kept. (`finch_str_own`). Strings from C are borrowed with `cap = -1` and always copied before being kept.
### 9.2 Parameter borrowing: `mutates()` ### 8.2 Parameter borrowing: `mutates()`
When declaring a function, every owning parameter is checked with `mutates(name, body)`: an When declaring a function, every owning parameter is checked with `mutates(name, body)`: an
assignment whose root variable (`rootVar()`: through `.field` and `[i]`) is the parameter, a changing assignment whose root variable (`rootVar()`: through `.field` and `[i]`) is the parameter, a changing
method on it (`push pop insert remove clear resize sort reverse`), or `addr(…)` of it. Unchanged method on it (`push pop insert remove clear resize sort reverse`), or `addr(…)` of it. Unchanged
parameters are borrowed (`Fn::borrowParam`); changed ones are copied once at entry. The analysis is parameters are borrowed (`FnSig.borrow`); changed ones are copied once at entry. The analysis is
syntactic and conservative: anything that *might* change the parameter makes a copy, which is always correct. syntactic and conservative: anything that *might* change the parameter makes a copy, which is always correct.
The same analysis decides whether `for x in list` must copy elements. The same analysis decides whether `for x in list` must copy elements.
Methods take part: `declareFns()` first finds which methods change `self` (`mutates("self", body)`), Methods take part: `declare()` first finds which methods change `self` (`mutates("self", body)`),
repeating until nothing changes, because calling a changing method on `self` changes `self` too. Their repeating until nothing changes, because calling a changing method on `self` changes `self` too. Their
names go into `mutatingMethods`, which `mutatesExpr()` treats like `push`: `p.move(1)` changes `p`. names go into `g.mm`, which `mutates()` treats like `push`: `p.move(1)` changes `p`.
Only then is `borrowParam` computed for every function and method. Only then is `borrow` computed for every function and method.
### 9.3 Copy and drop ### 8.3 Copy and drop
`dropAt(addr, T)` / `copyAt(dst, src, T)` work on addresses: `dropAt(addr, T)` / `copyAt(dst, src, T)` work on addresses:
@ -374,10 +374,10 @@ Only then is `borrowParam` computed for every function and method.
element and then `finch_arr_free`; copies call `finch_arr_clone_raw` and then deep-copy owning element and then `finch_arr_free`; copies call `finch_arr_clone_raw` and then deep-copy owning
elements over the raw bits. Structs drop/copy only their owning fields. elements over the raw bits. Structs drop/copy only their owning fields.
Helpers are emitted in their own functions (`HelperScope` saves the builder position and switches off Helpers are emitted in their own functions (`beginHelper()` / `endHelper()` set the function being written aside
debug locations), may call each other recursively (`struct Node { []Node kids }`), and are inlined by O2. and switch off debug locations), may call each other recursively (`struct Node { []Node kids }`), and are inlined by O2.
### 9.4 What it looks like ### 8.4 What it looks like
```c ```c
fn greet(str name) -> str { fn greet(str name) -> str {
@ -420,22 +420,22 @@ The `-O0` IR of `main` (abridged):
and `greet` returns `msg` without copying (`ret … %5` after loading the slot: the variable was moved out). and `greet` returns `msg` without copying (`ret … %5` after loading the slot: the variable was moved out).
### 9.5 defer ### 8.5 defer
`defer stmt` pushes a `Cleanup{isDefer, body, order = current varOrder}` into the current scope; it `defer stmt` records the statement in the current scope (`Scope.defers`, with `orders`: how many variables existed
emits nothing there. Whenever the scope's cleanups are emitted (falling off the end of the block, then) and a `Cleanup{isDefer}`; it emits nothing there. Whenever the scope's cleanups are emitted (falling off the end of the block,
`return`, `break`/`continue` leaving it) the body is generated **at that exit**, with `deferLimit = order` `return`, `break`/`continue` leaving it) the body is generated **at that exit**, with `deferLimit = order`
so that `lookup()` hides variables declared after the defer: those are already dropped by then so that `lookup()` hides variables declared after the defer: those are already dropped by then
(cleanups run in reverse). `return`/`break`/`continue` inside a deferred statement are errors (`inDefer`). (cleanups run in reverse). `return`/`break`/`continue` inside a deferred statement are errors (`inDefer`).
Defer bodies are therefore duplicated per exit path, which is what makes them free on the normal path. Defer bodies are therefore duplicated per exit path, which is what makes them free on the normal path.
### 9.6 Manual memory ### 8.6 Manual memory
`new(v)` allocates `sizeof(T)` with `finch_alloc`, stores `own(v)` and returns `ptr[T]`. `free(p)` drops `new(v)` allocates `sizeof(T)` with `finch_alloc`, stores `own(v)` and returns `ptr[T]`. `free(p)` drops
the pointee if `T` is owning (skipping null) and calls libc `free`. Pointers never own anything: the pointee if `T` is owning (skipping null) and calls libc `free`. Pointers never own anything:
`addr(x)` can dangle after `x`'s block ends; this is not checked. `addr(x)` can dangle after `x`'s block ends; this is not checked.
### 9.7 Verifying it ### 8.7 Verifying it
`MEMCHECK=1 tests/run.sh` builds every test program and runs it under valgrind with `MEMCHECK=1 tests/run.sh` builds every test program and runs it under valgrind with
`--leak-check=full --errors-for-leak-kinds=all`: zero leaks and zero invalid accesses across all of them, `--leak-check=full --errors-for-leak-kinds=all`: zero leaks and zero invalid accesses across all of them,
@ -444,8 +444,6 @@ iteration, nested arrays of structs, and C struct passing.
--- ---
---
## 9. Generics, enums and match ## 9. Generics, enums and match
See §4 for how they are generated. For the rules a user sees, read sections 23 and 24 of the See §4 for how they are generated. For the rules a user sees, read sections 23 and 24 of the
@ -485,16 +483,17 @@ mode the compiler prints, besides IR-free output, tab-separated records:
| `@V name type file line col fnFile fnLine fnEnd` | a variable and the function it lives in | | `@V name type file line col fnFile fnLine fnEnd` | a variable and the function it lives in |
| `@F module name recv file line col endLine signature params` | a function or method | | `@F module name recv file line col endLine signature params` | a function or method |
| `@S` / `@SF`, `@E`, `@I` | structs (and fields), enums, imports | | `@S` / `@SF`, `@E`, `@I` | structs (and fields), enums, imports |
| `@W` | a warning |
The first error is printed as `path:line:col: error: message` and becomes a diagnostic. When code does not compile, the The first error is printed as `path:line:col: error: message` and becomes a diagnostic. When code does not compile, the
server keeps answering completion and signature help from the last version that did. `tests/lsp_test.py` is the acceptance server keeps answering completion and signature help from the last version that did. `tests/lsp_test.py` is the acceptance
test (22 checks). test.
--- ---
## 12. Semantics vs. C ## 12. Semantics vs. C
| Situation | C | Finch 2.0 | | Situation | C | Finch |
|---|---|---| |---|---|---|
| Signed integer overflow | UB | defined wraparound (no `nsw`) | | Signed integer overflow | UB | defined wraparound (no `nsw`) |
| Integer division by zero, `INT_MIN / -1` | UB | compile error if constant, otherwise runtime error | | Integer division by zero, `INT_MIN / -1` | UB | compile error if constant, otherwise runtime error |
@ -507,13 +506,9 @@ test (22 checks).
| Uninitialized variables | UB | impossible (zero or defaults) | | Uninitialized variables | UB | impossible (zero or defaults) |
| Missing return | UB if used | compile error | | Missing return | UB if used | compile error |
| Order of evaluation | mostly unspecified | left to right; in an assignment the right side first | | Order of evaluation | mostly unspecified | left to right; in an assignment the right side first |
| Shift ≥ width, out-of-range float→int | UB | still poison (not checked yet) | | Shift ≥ width, out-of-range float→int | UB | poison, as in LLVM (not checked) |
| Dangling pointer from `addr()` / `new` + `free` | UB | still UB | | Dangling pointer from `addr()` / `new` + `free` | UB | still UB |
---
---
### The chain of calls on runtime errors ### The chain of calls on runtime errors
With `Options.trace` (default for `run`, `test`, `-g`, `-O0`; `--trace`/`--no-trace`; never with `--shared`) every Finch With `Options.trace` (default for `run`, `test`, `-g`, `-O0`; `--trace`/`--no-trace`; never with `--shared`) every Finch

View file

@ -1,12 +1,11 @@
# Finch dla inżynierów # Finch dla inżynierów
**Wnętrze Fincha 2.5, którego kompilator jest napisany w Finchu.** Jak kod źródłowy staje się tokenami, drzewem AST i **Jak od środka działa Finch 2.6, którego kompilator jest napisany w Finchu.** Jak kod źródłowy zamienia się w tokeny,
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 drzewo AST i LLVM IR *w postaci tekstu*, który `clang` kompiluje i linkuje z małym runtime'em w C; jak nagłówki C są
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 czytane przez libclang z poziomu samego Fincha i jak ręcznie odtworzono konwencje wywołań C; reguły typów i własności
z ziarna; oraz serwer języka. wartości; jak kompilator buduje sam siebie z pliku startowego; i jak działa serwer języka.
Zakładam, że czytasz Fincha (zobacz [przewodnik dla techników](dla-technikow.md)) i widziałeś już LLVM IR. Zakładam, że umiesz czytać kod w Finchu (zob. [przewodnik dla techników](dla-technikow.md)) i że widziałeś już LLVM IR.
Pierwsza wersja kompilatora, w C++ na API C++ LLVM, jest w gałęzi `before-bootstrap`; ten przewodnik opisuje wersję w Finchu.
> 🇬🇧 English version: [for-engineers.md](../en/for-engineers.md) > 🇬🇧 English version: [for-engineers.md](../en/for-engineers.md)
@ -15,194 +14,210 @@ Pierwsza wersja kompilatora, w C++ na API C++ LLVM, jest w gałęzi `before-boot
## Spis treści ## Spis treści
1. [Architektura](#1-architektura) 1. [Architektura](#1-architektura)
2. [Drzewo źródeł](#2-drzewo-źródeł) 2. [Pliki źródłowe](#2-pliki-źródłowe)
3. [Front end: lekser, parser, moduły](#3-front-end-lekser-parser-moduły) 3. [Część frontowa: lekser, parser, moduły](#3-część-frontowa-lekser-parser-moduły)
4. [Generowanie kodu](#4-generowanie-kodu) 4. [Generowanie kodu](#4-generowanie-kodu)
5. [Import C i ABI C](#5-import-c-i-abi-c) 5. [Import z C i ABI języka C](#5-import-z-c-i-abi-języka-c)
6. [Sterownik, linkowanie i cele](#6-sterownik-linkowanie-i-cele) 6. [Sterownik, linkowanie i systemy docelowe](#6-sterownik-linkowanie-i-systemy-docelowe)
7. [System typów](#7-system-typów) 7. [System typów](#7-system-typów)
8. [Własność i pamięć](#8-własność-i-pamięć) 8. [Własność i pamięć](#8-własność-i-pamięć)
9. [Generyki, enumy i match](#9-generyki-enumy-i-match) 9. [Typy generyczne, enumy i match](#9-typy-generyczne-enumy-i-match)
10. [Bootstrap](#10-bootstrap) 10. [Bootstrap](#10-bootstrap)
11. [Serwer języka](#11-serwer-języka) 11. [Serwer języka](#11-serwer-języka)
12. [Semantyka a C](#12-semantyka-a-c) 12. [Semantyka w porównaniu z C](#12-semantyka-w-porównaniu-z-c)
13. [Znane ograniczenia](#13-znane-ograniczenia) 13. [Znane ograniczenia](#13-znane-ograniczenia)
--- ---
## 1. Architektura ## 1. Architektura
W kompilatorze nie ma biblioteki LLVM. Pisze LLVM IR jako **tekst**, a optymalizację, generowanie kodu i linkowanie zostawia W kompilatorze nie ma biblioteki LLVM. Zapisuje on LLVM IR jako **tekst**, a optymalizację, generowanie kodu maszynowego
`clangowi`. Dzięki temu jest mały (ok. 8 tys. linii) i może być napisany w samym Finchu. i linkowanie zostawia `clangowi`. Dzięki temu jest mały (ok. 12 tys. linii) i dało się go napisać w samym Finchu.
``` ```
main.fch ─► loader ── lex ── parse ──► Program (+ każdy importowany moduł, rekurencyjnie) main.fch ─► loader ── lex ── parse ──► Program (+ każdy importowany moduł, rekurencyjnie)
│ │
`import "x.h"` ─► cimport.fch (libclang wołany z Fincha) ─► funkcje, struktury, stałe C `import "x.h"` ─► cimport.fch (libclang wołany z Fincha) ─► funkcje, struktury i stałe C
│ │
▼ ▼
gen.fch ─► generate(): struktury, sygnatury, ciała funkcji gen.fch ─► generate(): deklaracje struktur i funkcji, potem ciała funkcji;
kontrola typów i IR w jednym przejściu; klasyfikacja ABI wywołań C sprawdzanie typów i emisja IR w jednym przejściu; klasyfikacja ABI dla wywołań C
│ (tekst IR) │ (tekst IR)
▼ ▼
linker.fch ─► clang -c prog.ll ─► clang prog.o rt-<hash>.o [twoje .c/.o/.a] -lm [pkg-config / -l] linker.fch ─► clang -c prog.ll ─► clang prog.o rt-<hash>.o [twoje .c/.o/.a] -lm [pkg-config / -l]
│ │
▼ ▼
plik wykonywalny (finch run: uruchamiany z pliku tymczasowego, potem usuwany) plik wykonywalny (finch run: uruchamiany z pliku tymczasowego, potem usuwany)
``` ```
Decyzje projektowe: Decyzje, od których zależy cała reszta:
- **Szybko się przerywa.** Pierwszy błąd wypisuje `plik:linia:kol: error: …` z linią źródła i strzałką, i kompilator kończy. - **Pierwszy błąd kończy pracę.** Kompilator wypisuje `plik:linia:kolumna: error: …` z linią źródła i strzałką,
Bez odzyskiwania po błędach, więc kolejne fazy nie widzą półpoprawnych danych. po czym się zatrzymuje. Nie próbuje kontynuować po błędzie, więc dalsze etapy nigdy nie widzą częściowo poprawnego wejścia.
- **Jedno przejście.** `gen.fch` sprawdza typy i emituje IR jednym przejściem po AST. Wyrażenie daje operand IR, typ Fincha - **Jedno przejście.** `gen.fch` sprawdza typy i emituje IR w jednym przejściu po AST. Każde wyrażenie daje operand IR,
(napis: `int`, `f32`, `str`, `array`, `map`, `struct`, `enum`, `named`…) i flagi. swój typ Fincha (rodzaj zapisany tekstem: `int`, `f32`, `str`, `array`, `map`, `struct`, `enum`, `vec`, `named`…) i flagi.
- **Niech clang pracuje.** Zmienne lokalne to `alloca`, zamieniane przez `mem2reg`. Optymalizacja to `-O2` clanga (albo `-O0` z `-g`). - **Resztę robi clang.** Zmienne lokalne to `alloca`, które `mem2reg` zamienia na rejestry. Optymalizuje `-O2` clanga
- **Wartości, nie referencje.** Tablice, teksty i struktury mają semantykę wartości; własność ustala się w czasie kompilacji (§8). (albo `-O0` przy `-g`).
- **Malutki runtime w C** (`runtime/finch_rt.c`, ok. 760 linii) robi to, co w IR jest uciążliwe: teksty, rośnięcie tablic, mapy, - **Wartości, nie referencje.** Tablice, teksty i struktury zachowują się jak wartości; o własności decyduje kompilator (§8).
wejście, pliki, paniki. Kompilowany raz i trzymany w `~/.cache/finch`. - **Mały runtime w C** (`runtime/finch_rt.c`, ok. 1150 linii) robi to, co w IR byłoby żmudne: teksty, powiększanie tablic,
mapy, wczytywanie, pliki, błędy w trakcie działania. Kompiluje się go raz i trzyma w `~/.cache/finch`.
--- ---
## 2. Drzewo źródeł ## 2. Pliki źródłowe
| Plik | Linie | Zadanie | | Plik | Linie | Za co odpowiada |
|---|---|---| |---|---|---|
| `src/lexer.fch` | 470 | Tokeny i pozycje, tekst `"""`, `"\(x)"` zamienione na tokeny `("…" + str(x) + "…")` | | `src/lexer.fch` | 470 | Tokeny, pozycje, tekst `"""`, zamiana `"\(x)"` 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` | 170 | Jedna struktura `Node` z rodzajem (`kind`) jako tekstem, dziećmi, nazwami typów i pozycją (`line`, `endLine`); rozpoznawanie typów wektorowych |
| `src/parser.fch` | 910 | Zejście rekurencyjne: deklaracje, instrukcje, wyrażenia, generyki (`[T]`), `enum`, `match`, `export`, `link`, `const`, `test "…" { }` | | `src/parser.fch` | 910 | Zejście rekurencyjne: deklaracje, instrukcje, wyrażenia, typy generyczne (`[T]`), `enum`, `match`, `export`, `link`, `const`, `test "…" { }` |
| `src/gen.fch` | 6450 | Kontrola typów + IR: struktury, funkcje, metody, zakresy i zwolnienia, instrukcje, wyrażenia, wbudowane, mapy, błędy jako wartości, generyki (monomorfizacja), enumy i `match`, metadane `-g`, tryb `index` | | `src/gen.fch` | 7030 | Sprawdzanie typów i IR: struktury, funkcje, metody, zasięgi i zwalnianie, instrukcje, wyrażenia, funkcje wbudowane, mapy, błędy jako wartości, typy generyczne (monomorfizacja), enumy i `match`, wektory (SIMD), metadane debuggera (`-g`), tryb `index` |
| `src/cimport.fch` | 790 | libclang: funkcje, struktury i unie (pola, offsety, wypełnienie), enumy, stałe, globalne, makra (także będące strukturą) | | `src/cimport.fch` | 790 | libclang: funkcje, struktury i unie (pola, przesunięcia, wyrównanie), enumy, stałe, zmienne globalne, makra (także o wartości struktury) |
| `src/platform.fch` | 90 | `Target`: triple, układ danych, kompilator C, znak `char` zależnie od systemu | | `src/platform.fch` | 120 | `Target`: triple, układ danych, kompilator C, znak typu `char` na danym systemie |
| `src/linker.fch` | 430 | Pamięć podręczna (runtime, obiekt programu, pliki C/C++), biblioteki (`link`, pkg-config), podpowiedzi przy błędach linkera | | `src/linker.fch` | 480 | Pamięć podręczna (runtime, obiekt programu, pliki C/C++), biblioteki (`link`, pkg-config), podpowiedzi przy błędach linkera |
| `src/main.fch` | 515 | Sterownik: CLI, loader z wyszukiwaniem modułów i nakładkami, `build/run/test/fmt/ir/index/lsp` | | `src/main.fch` | 530 | Sterownik: wiersz poleceń, wczytywanie modułów i nakładek, `build/run/test/fmt/ir/index/lsp` |
| `src/manifest.fch` | 240 | `finch.toml`: mały czytnik TOML, listy zależne od celu | | `src/manifest.fch` | 240 | `finch.toml`: mały czytnik TOML, listy dla poszczególnych systemów |
| `src/fmt.fch` | 225 | `finch fmt` i formatowanie w LSP | | `src/fmt.fch` | 225 | `finch fmt` i formatowanie w serwerze języka |
| `src/lsp.fch` | 910 | Serwer języka (§11) | | `src/lsp.fch` | 950 | Serwer języka (§11) |
| `src/docs.fch` | 110 | Jeden opis wbudowanych funkcji dla podpowiedzi i uzupełniania | | `src/docs.fch` | 140 | Jeden opis funkcji wbudowanych, wspólny dla podpowiedzi i dymków |
| `std/*.fch` | 1040 | `json`, `path`, `time` (+`_windows`), `proc` (+`_windows`), `fs`, `thread` | | `std/*.fch` | 1040 | `json`, `path`, `time` (+`_windows`), `proc` (+`_windows`), `fs`, `thread` |
| `runtime/finch_rt.c` | 1150 | Runtime w C: tekst, tablice, mapy, pliki, wypisywanie liczb, fs, wątki, uruchamianie testów, łańcuch wywołań | | `runtime/finch_rt.c` | 1150 | Runtime w C: teksty, tablice, mapy, pliki, wypisywanie liczb, fs, wątki, uruchamianie testów, ścieżka wywołań |
| `bootstrap/` | | `build.sh`, `install.sh`, ziarna (§10) | | `bootstrap/` | | `build.sh`, `install.sh`, pliki startowe (§10) |
--- ---
## 3. Front end: lekser, parser, moduły ## 3. Część frontowa: lekser, parser, moduły
**Lekser.** Koniec linii kończy instrukcję; wcięcia nic nie znaczą. Tokeny niosą plik, linię i kolumnę, z których korzysta każdy błąd. **Lekser.** Koniec linii kończy instrukcję; wcięcia nie mają znaczenia. Każdy token zna plik, linię i kolumnę, z których
korzystają wszystkie komunikaty o błędach.
**Parser.** Zejście rekurencyjne, jedna funkcja na konstrukcję. Typy są parsowane do napisu (`[]str`, `map[str]int`, **Parser.** Zejście rekurencyjne, jedna funkcja na każdą konstrukcję. Warto wiedzieć, że: typy są parsowane do nazwy
`ptr[char]`, `Stack[int]`); `fn Typ.metoda` deklaruje metodę; `fn nazwa[T]` i `struct Nazwa[T]` to szablony; `enum` i `match` (`[]str`, `map[str]int`, `ptr[char]`, `Stack[int]`, `f32x8`); `fn Typ.metoda` deklaruje metodę; `fn nazwa[T]`
mają własne węzły; `export fn` oznacza funkcję widoczną dla C; blok zapamiętuje `endLine` (dla serwera języka). Stara i `struct Nazwa[T]` deklarują szablony; `enum` i `match` mają własne węzły; `export fn` oznacza funkcję widoczną z C;
kolejność `fn nazwa(...) typ` to błąd składni z podpowiedzią. blok zapamiętuje swoją `endLine` (korzysta z niej serwer języka). Stara kolejność `fn nazwa(...) typ` daje błąd z podpowiedzią.
**Moduły.** `import nazwa` szuka kolejno `nazwa_<os>.fch` obok importującego pliku (`windows`, `linux`, `macos`), `nazwa.fch`, **Moduły.** `import nazwa` szuka po kolei: `nazwa_<system>.fch` obok importującego pliku (`windows`, `linux`, `macos`),
katalogów z `FINCH_PATH`, potem biblioteki standardowej `std/`. Każdy moduł jest parsowany raz; jego nazwy są używane jako `nazwa.fch` w tym samym miejscu, katalogów z `FINCH_PATH`, a na końcu biblioteki standardowej `std/`. Każdy moduł jest
`modul.nazwa`. `import "x.h"` trafia do importera C. parsowany raz; jego nazwy są dostępne jako `modul.nazwa`. `import "x.h"` trafia zamiast tego do importu z C.
Moduł rozpoznaje się po **pliku**: kluczem jest jego ścieżka od folderu głównego pliku (albo od folderu z `FINCH_PATH` Moduł rozpoznaje się po **pliku**: kluczem jest jego ścieżka względem katalogu głównego pliku (albo katalogu z
lub `std/`) bez `.fch` i bez końcówki `_<system>`, np. `ui/przycisk`. `Program.imports` trzyma nazwę używaną w danym `FINCH_PATH`, albo `std/`), bez `.fch` i bez końcówki `_<system>`, np. `ui/button`. `Program.imports` przechowuje nazwę,
pliku (`przycisk`, albo `p` przy `import ui/przycisk as p`), a `Program.modules` klucz; `modFor()` zamienia jedno na drugie. której używa dany plik (`button` albo `btn` przy `import ui/button as btn`), a `Program.modules` klucz; `modFor()`
Symbole używają klucza z `/` zamienionym na `$` (`@finch.ui$przycisk.rysuj`). przechodzi od jednego do drugiego. Symbole używają klucza, w którym `/` zamieniono na `$` (`@finch.ui$button.make`).
**Stałe.** `const NAZWA = wyrażenie` to węzeł najwyższego poziomu; `constValue()` liczy wyrażenie przy każdym użyciu, w **Stałe.** `const NAZWA = wyrazenie` to węzeł najwyższego poziomu; `constValue()` oblicza wyrażenie przy każdym użyciu,
module, w którym je zdefiniowano, bez zmiennych lokalnych, więc wychodzi literał (operatory na literałach całkowitych w module, w którym je zdefiniowano, bez zmiennych lokalnych, więc wynik zawsze składa się do literału (operatory na
składa `arith`). `checkConst()` dopuszcza tylko literały, nazwy, operatory, pola i konwersje liczb; `constBusy` łapie pętle. literałach całkowitych są składane w `arith`). `checkConst()` dopuszcza tylko literały, nazwy, operatory, pola
i konwersje liczbowe; `constBusy` wykrywa cykle.
**Dodatki leksera.** `1e9` / `2.5e-3` to tokeny float (tekst dostaje `.` dla LLVM); teksty `"""` tracą wcięcie **Drobiazgi leksera.** `1e9` i `2.5e-3` to tokeny zmiennoprzecinkowe (tekst jest uzupełniany o `.`, którego wymaga LLVM);
linii zamykającej w `longString()`. z tekstu `"""` funkcja `longString()` usuwa wcięcie linii zamykającej.
--- ---
## 4. Generowanie kodu ## 4. Generowanie kodu
`gen.fch` to jedna duża struktura `Gen` i funkcje na niej. Przejścia: `gen.fch` to jedna duża struktura `Gen` i funkcje, które na niej pracują. `generate()` wykonuje kolejno:
1. `declareStructs`: każda struktura Fincha i C dostaje typ LLVM (struktury C z prawdziwym wypełnieniem); rekurencja przez wartość jest odrzucana. 1. `declare()`: każda struktura Fincha i C dostaje typ LLVM (struktury C z prawdziwym wyrównaniem; struktury, które
2. `declareFns`: sygnatury, z *analizą pożyczania* parametrów (parametr, który nigdzie nie jest zapisywany, jest przekazywany bez kopii). przez wartość zawierają same siebie, są odrzucane), a potem każda funkcja dostaje sygnaturę z *analizą pożyczania*
3. `define`: każde ciało, sprawdzone i wyemitowane. Zakresy pamiętają, co zwolnić na końcu (`defer` działa pierwszy). parametrów (§8.2).
4. `defineMainWrapper`: C-owe `main(argc, argv)` i `finch_on_fatal` dla osadzających. 2. `define()` dla każdej funkcji: ciało, ze sprawdzeniem typów i emisją IR. Zasięgi pamiętają, co trzeba zwolnić przy
5. Z `-g` metadane DWARF dokładane są w trakcie pisania IR (używaj `-O0`). wyjściu. Tworzenie instancji typów generycznych dokłada w tym czasie nowe funkcje, więc pętla trwa, dopóki pojawiają się nowe.
3. Punkt wejścia C, `main(argc, argv)` (a przy `--shared`: `finch_module_on_fatal` i opakowania funkcji `export fn`).
4. `assemble()`: deklaracje, typy struktur, stałe tekstowe, funkcje pomocnicze i ciała funkcji jako jeden tekst `.ll`.
Generyki są **monomorfizowane**: wywołanie `max(1, 2)` wnioskuje `T`, potem AST szablonu generowane jest ponownie z Przy `-g` metadane DWARF są dopisywane na bieżąco, w trakcie zapisywania IR (najlepiej razem z `-O0`).
przypisanym `T`, pod zmangowaną nazwą (`max$int`). `match` na enumie jest wyczerpujący (brak wariantu to błąd, `else` łapie
resztę); na wartości to ciąg porównań. Wartości enuma to `i32`; `Color(n)` jest sprawdzane; wypisywanie używa wygenerowanej tabeli nazw.
Błędy jako wartości: `-> T!` zwraca parę (wartość, tekst błędu); `try` wraca wcześniej przy błędzie, `or` go obsługuje, `error("…")` go tworzy. Typy generyczne są **monomorfizowane**: wywołanie `max(1, 2)` wyznacza `T`, a potem AST szablonu jest generowane jeszcze
raz z podstawionym `T`, pod nazwą `max[int]` (symbol `max.int`). `match` na enumie musi być wyczerpujący (brak wariantu
to błąd, `else` obejmuje resztę); na zwykłej wartości to ciąg porównań. Wartości enumów to `i32`; `Kolor(n)` sprawdza
zakres; do wypisywania służy wygenerowana tabela nazw.
Błędy jako wartości: `-> T!` zwraca parę (wartość, tekst błędu); `try` przy błędzie wychodzi z funkcji, `or` go obsługuje,
`error("…")` go tworzy.
--- ---
## 5. Import C i ABI C ## 5. Import z C i ABI języka C
`cimport.fch` woła **libclang** wprost z Fincha (sam jest napisany z `import "clang-c/Index.h"`), parsuje nagłówek z flagami `cimport.fch` woła **libclang** bezpośrednio z Fincha (sam jest napisany z `import "clang-c/Index.h"`), parsuje nagłówek
celu i przechodzi po kursorach. Typy: parametr `const char *` → `str`, inny `char *` → `ptr[char]`, wynik `char *` → `str`; z flagami systemu docelowego i przechodzi po kursorach. Typy tłumaczy tak: parametr `const char *` → `str`, inne
liczby całkowite według rozmiaru i znaku (`char` jest bez znaku na ARM64 Linux); struktury i enumy według układu. `char *` → `ptr[char]`, wynik `char *` → `str`; liczby całkowite według rozmiaru i znaku (`char` jest bez znaku na
Linuksie ARM64); struktury i enumy według ich układu w pamięci.
LLVM IR nie realizuje za ciebie konwencji wywołań C, więc `gen.fch` ją odtwarza: LLVM IR nie realizuje sam konwencji wywołań C, więc `gen.fch` odtwarza ją ręcznie:
- `classify`: System V x86-64: struktura do 16 bajtów idzie w rejestrach według klas ośmiobajtów (INTEGER/SSE), większa w pamięci; - `classify`: System V x86-64; struktura do 16 bajtów idzie w rejestrach według klasy każdych 8 bajtów (INTEGER/SSE), większa przez pamięć;
- `classifyArm64`: AAPCS64, z jednorodnymi agregatami zmiennoprzecinkowymi (HFA); - `classifyArm64`: AAPCS64, w tym jednorodne agregaty liczb zmiennoprzecinkowych (HFA) i wariant Apple;
- reguły Microsoft x64 (struktury 1, 2, 4, 8 bajtów w rejestrze, reszta przez wskaźnik); - reguły Microsoft x64 (struktury 1, 2, 4 i 8-bajtowe przez wartość w rejestrze, pozostałe przez wskaźnik);
- `makePlan` zamienia klasyfikację w plan wywołania; `cThunk` robi z `addr(fn)` coś, co C może wywołać (callbacki). - `makePlan` zamienia klasyfikację w plan wywołania; `cThunk` sprawia, że `addr(fn)` da się wywołać z C (funkcje zwrotne).
**Makra.** `#define` będący jedną liczbą albo tekstem czyta się z tokenów. Taki, którego tokeny mogą być wyrażeniem
stałym (bez klamer, średników i słów instrukcji), liczy clang: drugie parsowanie nagłówka dodaje
`static const __auto_type __finch_m_NAZWA = (NAZWA);` dla każdego, a `clang_Cursor_Evaluate` daje liczbę całkowitą,
double albo tekst. Te, które nie są stałymi, dają tylko błędy w tym drugim parsowaniu i są pomijane.
**Unie** to `CStruct` z `isUnion`: typ LLVM to `{ [n x iA] }` (rozmiar i wyrównanie od clanga), każde pole ma
przesunięcie 0, a pole czyta się przez pamięć. Nie da się ich przekazać do C przez wartość (`unsupported` mówi dlaczego).
Ten sam kod pozwala kompilatorowi wołać libclang, którego API przekazuje `CXCursor`, `CXType` i `CXString` przez wartość. Ten sam kod pozwala kompilatorowi wołać libclang, którego API przekazuje `CXCursor`, `CXType` i `CXString` przez wartość.
**Makra.** `#define` z jedną liczbą albo jednym literałem tekstowym jest odczytywany z tokenów. Taki, którego tokeny
mogą tworzyć wyrażenie stałe (bez klamer, średników i słów kluczowych instrukcji), oblicza clang: drugie parsowanie
nagłówka dopisuje dla każdego `static const __auto_type __finch_m_NAZWA = (NAZWA);`, a `clang_Cursor_Evaluate` zwraca
liczbę całkowitą, liczbę typu double albo tekst. Makra, które nie są stałymi, dają w tym drugim parsowaniu tylko błędy
i są pomijane.
**Unie** to `CStruct` z `isUnion`: typ LLVM to `{ [n x iA] }` (rozmiar i wyrównanie podaje clang), każde pole ma
przesunięcie 0, a pole czyta się przez pamięć. Nie da się ich przekazać do C przez wartość (pole `unsupported` mówi dlaczego).
--- ---
## 6. Sterownik, linkowanie i cele ## 6. Sterownik, linkowanie i systemy docelowe
`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 wynik] [-O0] [-g] [-l bib] [-I katalog] [--target t] [--cpu c] [--shared] [--overlay sciezka=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` tworzy bibliotekę współdzieloną z funkcji
`export fn`.
`--target windows|linux|arm64|macos|android|<triple>` zmienia triple i układ danych, znak `char`, reguły ABI powyżej, wariant OS `--cpu <nazwa>` (albo `cpu` w `finch.toml`) dodaje `-march=<nazwa>` na x86-64 albo `-mcpu=<nazwa>` na ARM64 do wywołania
modułów (`nazwa_windows.fch`) i kompilator C: `FINCH_CC` albo `clang --target=…`. Przy linkowaniu `pkg-config` podaje flagi clanga, które kompiluje IR (nie runtime); wartość wchodzi do klucza pamięci podręcznej.
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.
Zmienne środowiskowe: `FINCH_PATH` (katalogi modułów), `FINCH_RT` (źródło runtime'u), `FINCH_CC` (kompilator/linker C; IR `--target windows|linux|arm64|macos|android|<triple>` zmienia triple i układ danych, znak typu `char` w C, reguły ABI
zawsze kompiluje `clang`, nigdy `CC`), `FINCH_LIBCLANG` (dla `bootstrap/build.sh`). opisane wyżej, wariant modułów dla systemu (`nazwa_windows.fch`) i kompilator C: `FINCH_CC` albo `clang --target=…`.
Przy `link "bib"` linkowanie pyta `pkg-config` o flagi; gdy linker zawiedzie, jego wyjście jest analizowane
i kompilator podpowiada brakujące `link "…"`. Obiekt runtime'u ma klucz z hasza swojego źródła i systemu docelowego.
Kompilator znajduje `runtime/` i `std/` obok siebie (`../`, `./`) albo w `../share/finch/` (`bootstrap/install.sh`). Zmienne środowiskowe: `FINCH_PATH` (katalogi modułów), `FINCH_RT` (źródło runtime'u), `FINCH_CC` (kompilator C i linker;
IR zawsze kompiluje `clang`, nigdy `CC`), `FINCH_LIBCLANG` (dla `bootstrap/build.sh`).
**Pamięć podręczna budowania** (`host.cacheDir()`: `$XDG_CACHE_HOME/finch`, `~/.cache/finch`, `%LOCALAPPDATA%\finch`): Kompilator szuka `runtime/` i `std/` obok siebie (`../`, `./`) albo w `../share/finch/` (`bootstrap/install.sh`).
- `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 **Pamięć podręczna kompilacji** (`host.cacheDir()`: `$XDG_CACHE_HOME/finch`, `~/.cache/finch`, `%LOCALAPPDATA%\finch`):
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 - `ir/<hash głównego pliku + systemu + flag>.o` z plikiem `.key`, w którym jest hash clanga, flag i całego IR. Jedno
`values["sekcja.klucz"]` i `lists["sekcja.klucz"]`; `list(klucz, cele)` skleja listę z najwyższego poziomu i te z miejsce na program, więc pamięć nie rośnie przy każdej zmianie kodu; to samo IR daje ten sam obiekt bez uruchamiania clanga.
`[target.<os>]` oraz `[target.<os>-<cpu>]`. Ścieżki liczą się od folderu manifestu. Wpisy trafiają tam, gdzie opcje - `c/<hash źródła + kompilatora + flag + systemu>.o` dla `link "x.c"` / `.cpp`, budowany z `-MMD`; `.key` to hash
z linii poleceń: `paths` → loader, `include` → `-I` (libclang i pliki C), `links` → `link`, `libdirs` i `ldflags` → wszystkich plików wymienionych w `.d` (źródła i jego nagłówków). Zmiana któregokolwiek z nich wymusza ponowną kompilację.
polecenie linkowania, `cflags` → pliki C/C++. - Obiekty są zapisywane do pliku `.part<pid>` i dopiero potem przemianowywane, więc dwie równoległe kompilacje nie
zobaczą połowy pliku. Hasze to dwa niezależne 32-bitowe (FNV-1a i djb2), razem 64 bity.
**Pliki C++**: `.cpp`/`.cc`/`.cxx` w `links` kompiluje `cxxFor(cc)` (`FINCH_CXX` albo `clang` → `clang++`, **`finch.toml`** (`src/manifest.fch`) jest szukany od katalogu głównego pliku (albo katalogu roboczego) w górę.
`…gcc` → `…g++`, `cc` → `c++`), a potem ten sam sterownik linkuje, co dokłada standardową bibliotekę C++. Trafia do `values["sekcja.klucz"]` i `lists["sekcja.klucz"]`; `list(klucz, systemy)` łączy listę z najwyższego poziomu
z listami z `[target.<system>]` i `[target.<system>-<procesor>]`. Ścieżki są liczone względem katalogu manifestu.
Wpisy trafiają tam, gdzie opcje z wiersza poleceń: `paths` → wczytywanie modułów, `include` → `-I` (libclang i pliki C),
`links` → `link`, `libdirs` i `ldflags` → polecenie linkowania, `cflags` → pliki C/C++.
**Pliki C++**: plik `.cpp`/`.cc`/`.cxx` z `links` kompiluje `cxxFor(cc)` (`FINCH_CXX` albo `clang` → `clang++`,
`…gcc` → `…g++`, `cc` → `c++`), a potem ten sam sterownik linkuje, co dołącza bibliotekę standardową C++.
**Android**: `--target android` to `aarch64-linux-android24`; kompilator C to clang z NDK, **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 `toolchains/llvm/prebuilt/<host>/bin/aarch64-linux-android24-clang` (z `ANDROID_NDK_HOME`/`ANDROID_NDK_ROOT`);
dodaje `-llog` i nie dodaje `-lpthread` (bionic ma go w libc). Wariant OS modułów to `linux`. linkowanie dodaje `-llog`, a nie dodaje `-lpthread` (bionic ma wątki w libc). Wariantem modułów jest `linux`.
**`finch test`** kompiluje jak `run`, z `Options.test`: wszystkie funkcje są dalej definiowane, ale `@main` wywołuje **`finch test`** kompiluje tak jak `run`, z `Options.test`: wszystkie funkcje są definiowane jak zwykle, ale `@main` woła
`finch_test_run(nazwa, fn)` dla każdego bloku `test` z modułów użytkownika (`__test_<n>`, funkcja `void` zrobiona przez `finch_test_run(nazwa, fn)` dla każdego bloku `test` z modułów użytkownika (`__test_<n>`, funkcja `void` utworzona przez
parser), a na końcu `finch_test_summary()`. Runtime uruchamia każdy test pod `setjmp`; `fatal()` (każdy błąd w czasie parser), a potem `finch_test_summary()`. Runtime uruchamia każdy test pod `setjmp`; `fatal()` (każdy błąd w trakcie
działania i nieudany `assert`) wraca przez `longjmp` z `fatal_hook`, więc jeden nieudany test nie kończy całości. działania i nieudany `assert`) wraca przez `longjmp` z pomocą `fatal_hook`, więc jeden nieudany test nie przerywa reszty.
Pamięć, którą test miał w tym momencie, nie jest zwalniana. Pamięć, którą test miał w chwili błędu, nie jest zwalniana.
**`finch fmt`** działa na liniach: śledzi `{ }`, `( )`/`[ ]`, teksty (razem z `\( )`), `'…'`, `"""` i `/* */`.
Linia, która zaczyna się na głębokości nawiasów 0 i nie kontynuuje linii zakończonej operatorem albo `,`, dostaje
wcięcie 4 × głębokość klamer; pozostałe (kontynuacje, wnętrza `"""`) przesuwają się o tyle samo co linia, od której
zaczęła się instrukcja, więc zachowują kształt (i usuwanie wcięcia z `"""`).
**`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
@ -215,55 +230,61 @@ kształt (i ucinanie wcięcia `"""`) zostaje.
| `char` `i8` `u8` / `i16` `u16` / `i32` `u32` / `int` `u64` | `i8` / `i16` / `i32` / `i64` | | `char` `i8` `u8` / `i16` `u16` / `i32` `u32` / `int` `u64` | `i8` / `i16` / `i32` / `i64` |
| `f32` / `float` | `float` / `double` | | `f32` / `float` | `float` / `double` |
| `str`, `[]T` | `{ ptr, i64, i64 }` (wskaźnik, długość, pojemność) | | `str`, `[]T` | `{ ptr, i64, i64 }` (wskaźnik, długość, pojemność) |
| `map[K]V` | `{ ptr, i64, i64, i64, ptr, i64 }` (entries, len, used, cap, index, icap; zob. src/gen.fch) | | `map[K]V` | `{ ptr, i64, i64, i64, ptr, i64, i64 }` (wpisy, len, used, cap, indeks, icap, adds; zob. src/gen.fch) |
| `ptr`, `ptr[T]`, `null` | `ptr` (nieprzezroczysty) | | `ptr`, `ptr[T]`, `null` | `ptr` (nieprzezroczysty) |
| `[N]T` | `[N x T]` | | `[N]T` | `[N x T]` |
| struktura Fincha | nazwany typ struct, naturalny układ | | `f32x8`, `i8x32`, … | `<8 x float>`, `<32 x i8>` (§7, Wektory) |
| `self` metody | `ptr` (pierwszy parametr) | | struktura Fincha | nazwany typ struktury, naturalny układ |
| wynik `-> T!` | `{ i1 ok, T value, str error }` (`i8` w miejscu `T` dla `-> !`) | | `self` w metodzie | `ptr` (pierwszy parametr) |
| struktura C | nazwany **upakowany** (packed) struct z jawnymi bajtami wypełnienia (§5) | | wynik `-> T!` | `{ i1 ok, T wartosc, str blad }` (`i8` zamiast `T` dla `-> !`) |
| struktura C | nazwana struktura **packed** z jawnymi bajtami wyrównania (§5) |
Znakowość istnieje tylko w typie Fincha i wybiera `sdiv/udiv`, `ashr/lshr`, `icmp s*/u*`, Znak liczby istnieje tylko w typie Fincha i wybiera `sdiv/udiv`, `ashr/lshr`, `icmp s*/u*`, `sext/zext`,
`sext/zext`, `sitofp/uitofp`, `fptosi/fptoui`. `sitofp/uitofp`, `fptosi/fptoui`.
### Wartości ### Wartości
```cpp ```c
struct Value_ { Value *v; FType type; bool literal; bool fresh; }; struct Val {
str v // operand IR: %t12, 42, @.str.3, zeroinitializer
ast.Type ty
bool fresh
bool lit
}
``` ```
- **`literal`**: liczba wpisana w kod albo zaimportowana stała C. Może stać się dowolnym typem całkowitym, - **`lit`**: liczba zapisana w kodzie albo stała zaimportowana z C. Może stać się dowolnym typem całkowitym,
w którym się mieści (`fits()`), albo `f32`. Przechodzi przez minus jednoargumentowy i arytmetykę samych w którym się mieści (`fits()`), albo `f32`. Przetrwa jednoargumentowy minus i działania na samych literałach,
literałów zwijaną do stałej; jawne konwersje ją kasują (`u8(250)` to stała z typem, więc `u8(250) + 10` zawija się do 4). które składają się do stałej; jawne konwersje ją usuwają (`u8(250)` to stała z typem, więc `u8(250) + 10` daje 4).
- **`fresh`**: wartość posiadająca pamięć (str/tablica/struktura z takimi polami), której nikt jeszcze nie - **`fresh`**: wartość posiadająca pamięć (str, tablica, struktura z takimi polami), której jeszcze nikt nie trzyma:
trzyma: wynik wywołania, literał, sklejenie, konstruktor. Trzeba ją zapisać (przenieść) albo zwolnić (§8). wynik wywołania, literał, połączenie tekstów, konstruktor. Trzeba ją zapisać (przenieść) albo zwolnić (§8).
### Konwersje niejawne: `coerce(v, want)` ### Konwersje niejawne: `coerce(v, want)`
Identyczne typy; `null` → dowolny wskaźnik; wskaźnik z typem ↔ `ptr` bez typu; całkowita → całkowita, Identyczne typy; `null` → dowolny wskaźnik; wskaźnik z typem ↔ `ptr` bez typu; liczba całkowita → całkowita, jeśli
jeśli wartość to mieszczący się literał albo typ się rozszerza (`widens()`: ta sama znakowość i więcej bitów to pasujący literał albo typ się „poszerza” (`widens()`: ten sam znak i więcej bitów, albo bez znaku → większy ze znakiem);
albo bez znaku → ściśle większa ze znakiem); całkowita → zmiennoprzecinkowa; `f32` → `f64`; literał `f64` → `f32`. liczba całkowita → zmiennoprzecinkowa; `f32` → `f64`; literał `f64` → `f32`. Nic poza tym: komunikat błędu podpowiada
Nic więcej: błąd podpowiada jawną konwersję. Typy posiadające pamięć konwertują się tylko na siebie. jawną konwersję. Typy posiadające pamięć przechodzą tylko w siebie. Literał tablicy bierze typ elementów od miejsca,
Literały tablic biorą typ elementu z miejsca, do którego trafiają (`exprWant()`), więc `[]f32 xs = [1, 2.5]` działa. do którego trafia (`exprWant()`), więc `[]f32 xs = [1, 2.5]` działa.
### Argumenty operatorów: `unify(l, r)` ### Argumenty operatorów: `unify(l, r)`
Strona-literał dopasowuje się do drugiej (także literał float obok `f32`: `x * 2.0` zostaje `f32`); w przeciwnym razie Strona, która jest literałem, dopasowuje się do drugiej (także literał zmiennoprzecinkowy obok `f32`: `x * 2.0`
rośnie strona, która się rozszerza; `i32` + `u32` to błąd; całkowita + zmiennoprzecinkowa → zmiennoprzecinkowa; zostaje `f32`); w przeciwnym razie rośnie ta strona, którą da się poszerzyć; `i32` + `u32` to błąd; całkowita +
f32 + f64 → f64. Nie ma promocji całkowitych z C: `u8 + u8` zostaje `u8`. Dwa literały całkowite składają się w literał, zmiennoprzecinkowa → zmiennoprzecinkowa; f32 + f64 → f64. Nie ma promocji liczb całkowitych jak w C: `u8 + u8`
więc `FLAG_A | FLAG_B` dalej mieści się w `u64` albo `u32`. zostaje `u8`. Dwa literały całkowite składają się w literał, więc `FLAG_A | FLAG_B` nadal mieści się w `u64` albo `u32`.
### Konwersje jawne: `convert()` ### Konwersje jawne: `convert()`
| Z → Na | IR | | Z → Na | IR |
|---|---| |---|---|
| int/char ↔ int/char | `trunc`/`sext`/`zext` wg znakowości źródła | | int/char ↔ int/char | `trunc`/`sext`/`zext` według znaku źródła |
| bool → int | `zext` | | bool → int | `zext` |
| float ↔ int | `fptosi`/`fptoui`, `sitofp`/`uitofp` | | zmiennoprzecinkowa ↔ całkowita | `fptosi`/`fptoui`, `sitofp`/`uitofp` |
| float ↔ float | `fpext`/`fptrunc` | | zmiennoprzecinkowa ↔ zmiennoprzecinkowa | `fpext`/`fptrunc` |
| int → bool | `icmp ne 0` | | int → bool | `icmp ne 0` |
| liczba/bool/char → str | `finch_str_from_int/uint/float/char/bool` (fresh) | | liczba/bool/char → str | `finch_str_from_int/uint/float/char/bool` (fresh) |
| str → int/float | `finch_str_to_int/float` (panic przy złym tekście) | | str → int/float | `finch_str_to_int/float` (przy złym tekście błąd w trakcie działania) |
| `[]u8`/`[]char` → str | `finch_str_from_bytes` | | `[]u8`/`[]char` → str | `finch_str_from_bytes` |
| ptr → str | `finch_str_from_c` (pożyczony, `cap = -1`) | | ptr → str | `finch_str_from_c` (pożyczony, `cap = -1`) |
| ptr ↔ ptr | nic | | ptr ↔ ptr | nic |
@ -271,102 +292,133 @@ więc `FLAG_A | FLAG_B` dalej mieści się w `u64` albo `u32`.
### Porównania ### Porównania
Wskaźniki/null: tylko `icmp eq/ne ptr`. `str`: `finch_str_eq` (długość + `memcmp`) dla `==`/`!=`, Wskaźniki i `null`: tylko `icmp eq/ne ptr`. `str`: `finch_str_eq` (długość + `memcmp`) dla `==`/`!=`,
`finch_str_cmp` dla porządku. Liczby zmiennoprzecinkowe: `oeq olt ole ogt oge` i `une` dla `!=`. `finch_str_cmp` dla kolejności. Liczby zmiennoprzecinkowe: `oeq olt ole ogt oge` oraz `une` dla `!=`. Liczby całkowite
Całkowite i znaki wg znakowości (char bez znaku). Bool: tylko równość. Tablice/struktury: błąd. i `char` według znaku (`char` bez znaku). Bool: tylko równość. Tablice i struktury: błąd.
--- ### Wektory
`ast.vecType(nazwa)` rozpoznaje `<element>x<N>` (elementy `i8`…`u64`, `f32`, `f64`; N = 2…64; najwyżej 512 bitów)
i zwraca `Type(kind: "vec", elem: [element], count: N)`, czyli wektor LLVM `<N x T>`. Parser przyjmuje taką nazwę
wszędzie tam, gdzie może stać typ; wywołanie `f32x8(…)` trafia do `vecMake()`.
| Finch | IR |
|---|---|
| `f32x8(x)` | `insertelement` do elementu 0 + `shufflevector … zeroinitializer` (rozpowielenie) |
| `f32x8(a, …, h)` | ciąg `insertelement` |
| `f32x8(lista, i)` | sprawdzenie zakresu `i … i+7`, potem `load <8 x float>, ptr …, align 4` (wyrównanie elementu) |
| `f32x8(i32x8 v)` | `sitofp`, `fptosi`, `sext`, `trunc`… na całym wektorze |
| `v + w`, `v * 2.0` | `fadd`/`add`… na wektorach; argument skalarny jest dopasowywany do typu elementu i rozpowielany |
| `v[i]` | miejsce w pamięci: `getelementptr <element>, ptr %v, i64 i` po sprawdzeniu zakresu; albo `extractelement` na wartości tymczasowej |
| `v == w` | `fcmp oeq`/`icmp eq` → `<N x i1>` → `llvm.vector.reduce.and` |
| `min`, `max`, `abs` | `llvm.minnum`/`maxnum`, `llvm.smin`/`umin`…, `llvm.fabs`/`llvm.abs` |
| `v.sum()`, `.min()`, `.max()` | `llvm.vector.reduce.fadd` (z `reassoc`), `.add`, `.fmin`, `.smin`… |
| `v.sqrt()`, `.floor()`, `.ceil()`, `.round()` | funkcja wewnętrzna `llvm.*` o tej nazwie |
| `v.fma(b, c)` | `llvm.fmuladd` (jedna instrukcja tam, gdzie procesor ma FMA; `llvm.fma` bez FMA wołałoby funkcję z libm dla każdego elementu) |
| `v.store(lista, i)` | sprawdzenie zakresu, `store <8 x float> …, align 4` |
Dzielenie całkowite `/` i `%` jest odrzucane: ani x86, ani ARM nie mają instrukcji dzielącej liczby całkowite element
po elemencie, więc LLVM i tak rozbiłby je na pojedyncze dzielenia. Porównania kolejności są odrzucane, bo ich wynikiem
byłaby maska, dla której Finch nie ma typu.
**Wyrównanie.** LLVM daje `<8 x float>` wyrównanie ABI równe 32 (jego rozmiarowi), a `malloc` i finchowe `alloca … align 16`
gwarantują tylko 16. `llty()` zapisuje każdy wektor szerszy niż 16 bajtów w `g.wide`, a `resolveStructBody` zapisuje tam
strukturę, która taki wektor zawiera; `load()` i `store()` dodają dla tych typów `, align 16`. Bez tego LLVM mógłby
użyć `vmovaps` na adresie wyrównanym do 16 i program by się wysypał.
**Jakie instrukcje** wybiera clang: `--cpu <nazwa>` dodaje `-march=<nazwa>` (x86-64) albo `-mcpu=<nazwa>` (ARM64)
do kompilacji IR, więc to samo IR staje się kodem SSE2, AVX2 + FMA, AVX-512 albo NEON. Wektory szersze niż rejestry
procesora są dzielone na części.
--- ---
## 8. Własność i pamięć ## 8. Własność i pamięć
### 9.1 Model ### 8.1 Model
Każda wartość **typu posiadającego pamięć** (`str`, `[]T` albo struktura Fincha z takim polem) ma Każda wartość **typu posiadającego pamięć** (`str`, `[]T` albo struktura Fincha z takim polem) ma dokładnie jednego
dokładnie jednego właściciela: zmienną, element tablicy, pole struktury albo komórkę na stercie z `new()`. właściciela: zmienną, element tablicy, pole struktury albo komórkę na stercie z `new()`. Właściciel zwalnia wartość,
Właściciel zwalnia wartość, gdy sam znika. Nie ma liczenia referencji ani garbage collectora; gdy sam znika. Nie ma liczenia referencji ani garbage collectora; wszystkie decyzje zapadają przy kompilacji.
każda decyzja zapada statycznie.
| Sytuacja | Co się dzieje | | Sytuacja | Co się dzieje |
|---|---| |---|---|
| `x := expr` / `T x = expr` / `x = expr` / pole, element, `push` | **`own(v)`**: wartość fresh jest przenoszona; pożyczona jest kopiowana w głąb | | `x := wyr` / `T x = wyr` / `x = wyr` / pole, element, `push` | **`own(v)`**: wartość świeża zostaje przeniesiona, pożyczona jest głęboko kopiowana |
| `x = …` (typ posiadający) | najpierw liczona jest nowa wartość, stara zwalniana po zapisie | | `x = …` (typ posiadający) | najpierw liczona jest nowa wartość, a stara jest zwalniana po zapisie |
| koniec bloku, `return`, `break`, `continue` | posiadane zmienne każdego opuszczanego zasięgu są zwalniane (od najnowszej), uruchamiają się defery | | koniec bloku, `return`, `break`, `continue` | zmienne we wszystkich opuszczanych zasięgach są zwalniane (od najnowszej), wykonują się `defer` |
| `return lokalna` | zmienna jest **przenoszona na zewnątrz** (oznaczana jako `moved` na czas sprzątania tego returna) | | `return zmienna_lokalna` | zmienna jest **przenoszona** (oznaczona jako `moved` na czas sprzątania przy tym `return`) |
| przekazanie argumentu | **pożyczka**: funkcja dostaje płytką kopię i nigdy jej nie zwalnia; argumenty fresh zwalnia wywołujący po wywołaniu | | przekazanie argumentu | **pożyczka**: funkcja dostaje płytką kopię i nigdy jej nie zwalnia; świeże argumenty zwalnia wywołujący po wywołaniu |
| parametr, który funkcja zmienia | kopiowany na wejściu i posiadany przez funkcję (decyduje `mutates()`, niżej) | | parametr, który funkcja zmienia | kopiowany na wejściu i należący do funkcji (decyduje `mutates()`, niżej) |
| wartość tymczasowa, której nikt nie trzyma (`print(a + b)`, `f(g())`, instrukcja-wyrażenie) | **`release(v)`**: zwalniana zaraz po użyciu | | wartość tymczasowa, której nikt nie zatrzymuje (`print(a + b)`, `f(g())`, wyrażenie jako instrukcja) | **`release(v)`**: zwalniana zaraz po użyciu |
| `a.pop()`, `a.remove(i)` | element jest przenoszony na zewnątrz (fresh) | | `a.pop()`, `a.remove(i)` | element jest przenoszony na zewnątrz (świeży) |
| `for x in lista` | `x` pożycza kolejne elementy; jeśli ciało może zmieniać `lista`, elementy są kopiowane | | `for x in lista` | `x` pożycza kolejne elementy; jeśli ciało pętli może zmienić `lista`, elementy są kopiowane |
| wywołanie metody `v.m()` | `self` **to jest** `v` (przekazywany jest jego adres): bez kopii; zmiany widzi wywołujący | | wywołanie metody `v.m()` | `self` **to jest** `v` (przekazywany jest adres): bez kopii, zmiany widzi wywołujący |
| mapy | są właścicielami kluczy i wartości tak jak tablice elementów; `m[k] = v` przenosi/kopiuje `v` i zwalnia starą wartość | | mapy | są właścicielami kluczy i wartości tak jak tablice elementów; `m[k] = v` przenosi albo kopiuje `v` i zwalnia starą wartość |
Literał tekstowy to wartość fresh z `cap = 0`: przeniesienie nic nie kosztuje, zwalnianie jest pomijane Literał tekstowy to świeża wartość z `cap = 0`: przeniesienie go nic nie kosztuje, zwalnianie jest pomijane
(`release()` ignoruje stałe), a zapis do niego (`s[i] = c`) najpierw robi kopię na stercie (`finch_str_own`). (`release()` ignoruje stałe), a zapis do niego (`s[i] = c`) najpierw robi kopię na stercie (`finch_str_own`).
Teksty z C są pożyczane z `cap = -1` i zawsze kopiowane, zanim zostaną zatrzymane. Teksty z C są pożyczone z `cap = -1` i zawsze kopiowane, zanim zostaną zatrzymane.
### 9.2 Pożyczanie parametrów: `mutates()` ### 8.2 Pożyczanie parametrów: `mutates()`
Przy deklarowaniu funkcji każdy parametr posiadający pamięć jest sprawdzany przez `mutates(nazwa, ciało)`: Przy deklarowaniu funkcji każdy parametr typu posiadającego jest sprawdzany przez `mutates(nazwa, cialo)`: przypisanie,
przypisanie, którego zmienna-korzeń (`rootVar()`: przez `.pole` i `[i]`) jest tym parametrem, metoda którego zmienną bazową (`rootVar()`: przez `.pole` i `[i]`) jest ten parametr, zmieniająca metoda na nim
zmieniająca (`push pop insert remove clear resize sort reverse`) albo `addr(…)` na nim. Niezmieniane (`push pop insert remove clear resize sort reverse`) albo `addr(…)` na nim. Niezmieniane parametry są pożyczane
parametry są pożyczane (`Fn::borrowParam`), zmieniane kopiowane raz na wejściu. Analiza jest składniowa (`FnSig.borrow`); zmieniane są kopiowane raz, na wejściu. Analiza jest składniowa i ostrożna: wszystko, co *mogłoby*
i zachowawcza: wszystko, co *może* zmienić parametr, powoduje kopię, co zawsze jest poprawne. zmienić parametr, powoduje kopię, a to zawsze jest poprawne. Ta sama analiza decyduje, czy `for x in lista` musi kopiować elementy.
Ta sama analiza decyduje, czy `for x in lista` musi kopiować elementy.
Metody biorą w tym udział: `declareFns()` najpierw ustala, które metody zmieniają `self` Metody też w tym uczestniczą: `declare()` najpierw ustala, które metody zmieniają `self` (`mutates("self", cialo)`),
(`mutates("self", ciało)`), powtarzając, aż nic się nie zmieni, bo wywołanie zmieniającej metody na `self` powtarzając to, dopóki coś się zmienia, bo wywołanie zmieniającej metody na `self` też zmienia `self`. Ich nazwy trafiają
też zmienia `self`. Ich nazwy trafiają do `mutatingMethods`, które `mutatesExpr()` traktuje jak `push`: do `g.mm`, które `mutates()` traktuje jak `push`: `p.przesun(1)` zmienia `p`. Dopiero potem dla każdej funkcji i metody
`p.przesun(1)` zmienia `p`. Dopiero potem liczone jest `borrowParam` dla każdej funkcji i metody. liczone jest `borrow`.
### 9.3 Kopiowanie i zwalnianie ### 8.3 Kopiowanie i zwalnianie
`dropAt(addr, T)` / `copyAt(dst, src, T)` działają na adresach: `dropAt(adres, T)` / `copyAt(cel, zrodlo, T)` działają na adresach:
- `str`: `finch_str_drop` (zwalnia, gdy `cap > 0`) / `finch_str_copy` (literały współdzieli, resztę duplikuje). - `str`: `finch_str_drop` (zwalnia, gdy `cap > 0`) / `finch_str_copy` (literały współdzieli, resztę duplikuje).
- `[]T` i struktury posiadające: generowane helpery `finch.drop.<klucz>` i `finch.copy.<klucz>`, tworzone - `[]T` i struktury posiadające pamięć: wygenerowane funkcje pomocnicze `finch.drop.<klucz>` i `finch.copy.<klucz>`,
raz na typ (`dropFn`, `copyFn`, klucz z `typeKey()`, np. `arr_S_main_Gracz`). Tablice zwalniają każdy posiadający tworzone raz na typ (`dropFn`, `copyFn`, klucz z `typeKey()`, np. `arr_S_main_Player`). Tablica zwalnia każdy
element, a potem `finch_arr_free`; kopie wołają `finch_arr_clone_raw`, a potem kopiują w głąb posiadające element posiadający pamięć, a potem woła `finch_arr_free`; kopia woła `finch_arr_clone_raw`, a potem głęboko kopiuje
elementy na surowe bity. Struktury zwalniają/kopiują tylko pola posiadające. takie elementy w miejsce surowych bitów. Struktury zwalniają i kopiują tylko pola posiadające pamięć.
Helpery są emitowane we własnych funkcjach (`HelperScope` zapamiętuje pozycję buildera i wyłącza lokacje Funkcje pomocnicze powstają jako osobne funkcje (`beginHelper()` / `endHelper()` odkładają na bok funkcję, która jest
debugowe), mogą wołać się rekurencyjnie (`struct Wezel { []Wezel dzieci }`) i są wstawiane (inline) przez O2. w trakcie pisania, i wyłączają lokalizacje debuggera), mogą wołać się nawzajem rekurencyjnie (`struct Node { []Node kids }`),
a O2 je wkleja w miejsce wywołań.
### 9.4 Jak to wygląda ### 8.4 Jak to wygląda
```c ```c
fn greet(str name) -> str { fn przywitaj(str imie) -> str {
msg := "Hi, " + name wiad := "Cześć, " + imie
return msg return wiad
} }
fn main() { fn main() {
a := ["x"] a := ["x"]
b := a b := a
b.push(greet("Ola")) b.push(przywitaj("Ola"))
print(b[1]) print(b[1])
} }
``` ```
IR funkcji `main` z `-O0` (skrócony): IR funkcji `main` przy `-O0` (w skrócie):
```llvm ```llvm
%3 = call ptr @finch_alloc(i64 24) ; ["x"]: jeden element na stercie %3 = call ptr @finch_alloc(i64 24) ; ["x"]: jeden element na stercie
store { ptr, i64, i64 } { ptr @str.1, i64 1, i64 0 }, ptr %4 ; literał, cap 0 store { ptr, i64, i64 } { ptr @str.1, i64 1, i64 0 }, ptr %4 ; literał, cap 0
... ...
store { ptr, i64, i64 } %7, ptr %a ; przeniesione do a (fresh) store { ptr, i64, i64 } %7, ptr %a ; przeniesione do a (świeże)
%a1 = load { ptr, i64, i64 }, ptr %a %a1 = load { ptr, i64, i64 }, ptr %a
store { ptr, i64, i64 } %a1, ptr %1 store { ptr, i64, i64 } %a1, ptr %1
call void @finch.copy.arr_str(ptr %2, ptr %1) ; b := a kopiuje call void @finch.copy.arr_str(ptr %2, ptr %1) ; b := a kopiuje
%8 = load { ptr, i64, i64 }, ptr %2 %8 = load { ptr, i64, i64 }, ptr %2
store { ptr, i64, i64 } %8, ptr %b store { ptr, i64, i64 } %8, ptr %b
%9 = call { ptr, i64, i64 } @finch.greet({ ptr, i64, i64 } { ptr @str.2, i64 3, i64 0 }) %9 = call { ptr, i64, i64 } @finch.przywitaj({ ptr, i64, i64 } { ptr @str.2, i64 3, i64 0 })
... ...
call void @finch_arr_reserve(ptr %b, i64 %12, i64 24) ; push: powiększ, jeśli trzeba call void @finch_arr_reserve(ptr %b, i64 %12, i64 24) ; push: powiększenie, jeśli trzeba
store { ptr, i64, i64 } %9, ptr %15 ; wynik greet przeniesiony, bez kopii store { ptr, i64, i64 } %9, ptr %15 ; wynik przywitaj przeniesiony, bez kopii
... ...
%20 = icmp uge i64 1, %19 ; b[1]: kontrola zakresu %20 = icmp uge i64 1, %19 ; b[1]: sprawdzenie zakresu
br i1 %20, label %out.of.range, label %in.range br i1 %20, label %out.of.range, label %in.range
... ...
call void @finch.drop.arr_str(ptr %b) ; koniec main: od najnowszej call void @finch.drop.arr_str(ptr %b) ; koniec main: od najnowszej
@ -374,103 +426,101 @@ IR funkcji `main` z `-O0` (skrócony):
ret void ret void
``` ```
a `greet` zwraca `msg` bez kopiowania (`ret … %5` po wczytaniu slotu: zmienna została przeniesiona). a `przywitaj` zwraca `wiad` bez kopiowania (`ret … %5` po wczytaniu zmiennej: została przeniesiona).
### 9.5 defer ### 8.5 defer
`defer instrukcja` dokłada do bieżącego zasięgu `Cleanup{isDefer, body, order = bieżące varOrder}` `defer instrukcja` zapisuje instrukcję w bieżącym zasięgu (`Scope.defers`, razem z `orders`: ile zmiennych wtedy
i niczego w tym miejscu nie emituje. Za każdym razem, gdy emitowane jest sprzątanie zasięgu (dojście do istniało) oraz `Cleanup{isDefer}`; w tym miejscu nic nie emituje. Ilekroć emitowane jest sprzątanie zasięgu (koniec
końca bloku, `return`, wyjście przez `break`/`continue`), ciało jest generowane **w tym wyjściu**, z bloku, `return`, wyjście przez `break`/`continue`), ciało jest generowane **przy tym wyjściu**, z `deferLimit`
`deferLimit = order`, żeby `lookup()` ukrył zmienne zadeklarowane po defer: te są wtedy już zwolnione ustawionym tak, by `lookup()` ukrywało zmienne zadeklarowane po `defer`: w tym momencie są już zwolnione (sprzątanie
(sprzątanie idzie w odwrotnej kolejności). `return`/`break`/`continue` w instrukcji defer to błędy (`inDefer`). idzie od końca). `return`/`break`/`continue` wewnątrz odroczonej instrukcji to błąd (`inDefer`). Ciała `defer` są więc
Ciała defer są więc powielane na każdą ścieżkę wyjścia, przez co na zwykłej ścieżce nic nie kosztują. powielane na każdą drogę wyjścia i dlatego na zwykłej ścieżce nic nie kosztują.
### 9.6 Pamięć ręczna ### 8.6 Pamięć zarządzana ręcznie
`new(v)` alokuje `sizeof(T)` przez `finch_alloc`, zapisuje `own(v)` i zwraca `ptr[T]`. `free(p)` zwalnia `new(v)` przydziela `sizeof(T)` przez `finch_alloc`, zapisuje `own(v)` i zwraca `ptr[T]`. `free(p)` zwalnia to, na co
wskazywaną wartość, jeśli `T` posiada pamięć (pomijając null), i woła `free` z libc. Wskaźniki nigdy wskazuje wskaźnik, jeśli `T` posiada pamięć (pomija `null`), i woła `free` z libc. Wskaźniki nigdy niczego nie posiadają:
niczego nie posiadają: `addr(x)` może wisieć po końcu bloku `x`; nie jest to sprawdzane. `addr(x)` może wskazywać w próżnię po końcu bloku `x`; tego się nie sprawdza.
### 9.7 Weryfikacja ### 8.7 Sprawdzanie
`MEMCHECK=1 tests/run.sh` buduje każdy program testowy i uruchamia go pod valgrindem z `MEMCHECK=1 tests/run.sh` buduje każdy program testowy i uruchamia go pod valgrindem z
`--leak-check=full --errors-for-leak-kinds=all`: zero wycieków i zero nieprawidłowych dostępów we wszystkich, `--leak-check=full --errors-for-leak-kinds=all`: zero wycieków i zero błędnych dostępów we wszystkich, w tym przy
w tym przy returnach z wnętrza pętli, `continue`/`break` z posiadanymi zmiennymi, deferach, modyfikacji `return` z wnętrza pętli, `continue`/`break` ze zmiennymi posiadającymi pamięć, `defer`, zmianach w trakcie iteracji,
podczas iteracji, zagnieżdżonych tablicach struktur i przekazywaniu struktur C. zagnieżdżonych tablicach struktur i przekazywaniu struktur C.
--- ---
--- ## 9. Typy generyczne, enumy i match
## 9. Generyki, enumy i match Jak są generowane, opisuje §4. Reguły widoczne dla użytkownika są w rozdziałach 23 i 24
[przewodnika dla techników](dla-technikow.md). Testy to `tests/new/generics.fch`, `tests/new/enums.fch` i oczekiwane
Jak są generowane, opisuje §4. Reguły widoczne dla użytkownika: sekcje 23 i 24 [przewodnika dla techników](dla-technikow.md). błędy w `tests/new/fail/`.
Testy: `tests/new/generics.fch`, `tests/new/enums.fch` i odmowy w `tests/new/fail/`.
--- ---
## 10. Bootstrap ## 10. Bootstrap
Kompilator budowany jest z **ziarna**: własnego IR zapisanego jako `bootstrap/<system>.ll` (zobacz Kompilator powstaje z **pliku startowego** (ang. *seed*): jego własnego IR, zapisanego w repozytorium jako
[bootstrap/README.md](../../bootstrap/README.md)). `bootstrap/build.sh`: `bootstrap/<system>.ll` (zob. [bootstrap/README.md](../../bootstrap/README.md)). `bootstrap/build.sh` robi:
``` ```
ziarno .ll ──clang──► finch0 ──buduje src/──► finch1 ──buduje src/──► build/finch plik .ll ──clang──► finch0 ──buduje src/──► finch1 ──buduje src/──► build/finch
``` ```
i przerywa, jeśli `finch1` i `finch` nie dają identycznego IR (punkt stały: kompilator kompiluje siebie tak samo, niezależnie i przerywa, jeśli `finch1` i `finch` nie dają identycznego bajt w bajt IR (punkt stały: kompilator kompiluje sam siebie
od tego, kto go zbudował). `bootstrap/build.sh seed` przepisuje ziarno z bieżącego kompilatora; rób to, gdy źródło tak samo, niezależnie od tego, co go zbudowało). `bootstrap/build.sh seed` zapisuje plik startowy na nowo z bieżącego
kompilatora zacznie używać czegoś, czego zatwierdzone ziarno nie ma. kompilatora; trzeba to zrobić, gdy kod kompilatora zaczyna używać czegoś, czego zapisany plik startowy nie zna.
Ziarno jest na system, bo zapieczono w nim konwencję wywołań C dla wywołań libclang (§5). Linux x86-64 i Linux ARM64 mają Plik startowy jest osobny dla każdego systemu, bo zawiera konwencję wywołań C dla funkcji libclang (§5). Są pliki
ziarna. Dla macOS i Windowsa trzeba by ziaren zrobionych z nagłówkami Apple i Microsoftu. dla Linuksa x86-64 i ARM64, macOS ARM64 i x86-64 (zrobione z nagłówkami SDK Apple) oraz Windowsa x86-64 (z nagłówkami
llvm-mingw). CI na każdym z tych systemów buduje z nich kompilator i uruchamia tam testy.
--- ---
## 11. Serwer języka ## 11. Serwer języka
`finch lsp` mówi JSON-RPC przez stdin/stdout (ramki `Content-Length`, `std/json`). Sam nie sprawdza typów: przy każdej zmianie `finch lsp` rozmawia przez JSON-RPC na stdin/stdout (ramki `Content-Length`, `std/json`). Sam nie sprawdza typów:
uruchamia `finch index plik.fch --overlay ścieżka=kopia …`, gdzie nakładki to niezapisany tekst edytora. W trybie `index` przy każdej zmianie uruchamia `finch index plik.fch --overlay sciezka=kopia …`, gdzie nakładki to niezapisany tekst
kompilator wypisuje rekordy rozdzielone tabulatorami: z edytora. W trybie `index` kompilator zamiast IR wypisuje rekordy rozdzielone tabulatorami:
| Rekord | Znaczenie | | Rekord | Znaczenie |
|---|---| |---|---|
| `@R plik linia kol dł plikDef liniaDef kolDef podpowiedź` | odwołanie: co pokazuje hover i gdzie jest definicja | | `@R plik linia kol dlug plikDef liniaDef kolDef dymek` | odwołanie: co pokazać w dymku i gdzie jest definicja |
| `@V nazwa typ plik linia kol plikFn liniaFn koniecFn` | zmienna i funkcja, w której żyje | | `@V nazwa typ plik linia kol plikFn liniaFn koniecFn` | zmienna i funkcja, w której żyje |
| `@F moduł nazwa odb plik linia kol koniec sygnatura parametry` | funkcja lub metoda | | `@F modul nazwa odbiorca plik linia kol koniec sygnatura parametry` | funkcja albo metoda |
| `@S` / `@SF`, `@E`, `@I` | struktury (i pola), enumy, importy | | `@S` / `@SF`, `@E`, `@I` | struktury (i pola), enumy, importy |
| `@W` | ostrzeżenie |
Pierwszy błąd wypisywany jest jako `ścieżka:linia:kol: error: komunikat` i staje się diagnostyką. Gdy kod się nie kompiluje, Pierwszy błąd jest wypisywany jako `sciezka:linia:kol: error: komunikat` i staje się diagnostyką. Gdy kod się nie
serwer podpowiada dalej z ostatniej wersji, która się skompilowała. Test akceptacyjny: `tests/lsp_test.py` (22 sprawdzenia). kompiluje, serwer nadal podpowiada uzupełnienia i parametry na podstawie ostatniej wersji, która się kompilowała.
Testem akceptacyjnym jest `tests/lsp_test.py`.
--- ---
## 12. Semantyka a C ## 12. Semantyka w porównaniu z C
| Sytuacja | C | Finch 2.0 | | Sytuacja | C | Finch |
|---|---|---| |---|---|---|
| Przepełnienie liczby ze znakiem | UB | zdefiniowane zawinięcie (bez `nsw`) | | Przepełnienie liczby całkowitej ze znakiem | UB | zdefiniowane zawinięcie (bez `nsw`) |
| Dzielenie całkowite przez zero, `INT_MIN / -1` | UB | błąd kompilacji, jeśli stałe, inaczej błąd w czasie działania | | Dzielenie całkowite przez zero, `INT_MIN / -1` | UB | błąd kompilacji, jeśli to stała; w przeciwnym razie błąd w trakcie działania |
| Indeks tablicy poza zakresem | UB | błąd w czasie działania (także dla pól `[N]T` struktur C) | | Indeks tablicy poza zakresem | UB | błąd w trakcie działania (także dla pól `[N]T` struktur C) |
| Dereferencja null | UB | błąd w czasie działania dla `.value`, `.pole`, `[i]` | | Dereferencja `null` | UB | błąd w trakcie działania dla `.value`, `.pole`, `[i]` |
| Użycie po zwolnieniu / podwójne zwolnienie tablic, tekstów, struktur | częste błędy | niemożliwe: tych nie zwalnia się ręcznie | | Użycie po zwolnieniu / podwójne zwolnienie tablic, tekstów, struktur | częste błędy | niemożliwe: tych nie zwalnia się ręcznie |
| Wyciek tablic, tekstów, struktur | częsty błąd | niemożliwy bez `new` | | Wyciek pamięci tablic, tekstów, struktur | częsty błąd | niemożliwy bez `new` |
| `x & 1 == 0` | `x & (1 == 0)` | `(x & 1) == 0` | | `x & 1 == 0` | `x & (1 == 0)` | `(x & 1) == 0` |
| Niejawne zwężanie, mieszanie ze znakiem / bez znaku | po cichu | błędy kompilacji | | Niejawne zwężanie, mieszanie liczb ze znakiem i bez | po cichu | błędy kompilacji |
| Niezainicjalizowane zmienne | UB | niemożliwe (zero albo wartości domyślne) | | Niezainicjowane zmienne | UB | niemożliwe (zero albo wartości domyślne) |
| Brak return | UB, jeśli użyty | błąd kompilacji | | Brak `return` | UB, jeśli wynik jest użyty | błąd kompilacji |
| Kolejność obliczeń | w większości nieokreślona | od lewej do prawej; w przypisaniu najpierw prawa strona | | Kolejność obliczania | w dużej mierze nieokreślona | od lewej do prawej; w przypisaniu najpierw prawa strona |
| Przesunięcie ≥ szerokość, float→int poza zakresem | UB | nadal poison (jeszcze niesprawdzane) | | Przesunięcie o ≥ szerokość, liczba zmiennoprzecinkowa poza zakresem przy zamianie na całkowitą | UB | poison, jak w LLVM (niesprawdzane) |
| Wiszący wskaźnik z `addr()` / `new` + `free` | UB | nadal UB | | Wiszący wskaźnik z `addr()` / `new` + `free` | UB | nadal UB |
--- ### Ścieżka wywołań przy błędach w trakcie działania
---
### Ł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 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: Fincha ma na stosie ramkę:
```llvm ```llvm
%frame = alloca { ptr, ptr, ptr, i64 } ; ramka wywołującego, nazwa, plik, linia %frame = alloca { ptr, ptr, ptr, i64 } ; ramka wywołującego, nazwa, plik, linia
@ -479,45 +529,44 @@ store ptr @"nazwa", ...pole 1 ; ptr @"plik", pole 2 ; i64 <linia>, pole 3
store ptr %frame, ptr @finch_trace store ptr %frame, ptr @finch_trace
``` ```
`@finch_trace` to globalna `thread_local` w module programu (model initial-exec, chyba że biblioteka albo Windows); `@finch_trace` to globalna zmienna `thread_local` w module programu (model initial-exec, chyba że to biblioteka
każda instrukcja zapisuje swoją linię do pola 3. Po każdym wywołaniu funkcji Fincha wywołujący wpisuje z powrotem swoją albo Windows); każda instrukcja zapisuje swoją linię do pola 3. Po każdym wywołaniu funkcji Fincha wywołujący zapisuje
ramkę do `@finch_trace`, co sprząta też ramki funkcji, która wróciła wcześniej, więc wiele ścieżek `return` nie z powrotem swoją ramkę do `@finch_trace`, co przy okazji usuwa ramki pozostawione przez funkcję, która wyszła wcześniej,
potrzebuje epilogu. Opakowania wołane z C (thunki `addr(fn)`, `export fn`) zapamiętują i przywracają `@finch_trace`. więc na licznych ścieżkach powrotu nie trzeba nic sprzątać. Opakowania wywoływane z C (`addr(fn)`, `export fn`)
`main` woła `finch_trace_init(top, set)` z dwiema małymi funkcjami do czytania i zapisu, i tak runtime (`print_trace()` zapamiętują i odtwarzają `@finch_trace` wokół wywołania. `main` woła `finch_trace_init(top, set)` z dwiema małymi
w `fatal()` oraz uruchamianie testów) dostaje się do zmiennej wątku, której nie zna z nazwy. Koszt: ok. 3 razy na funkcjami, które ją czytają i zapisują; tak runtime (`print_trace()` w `fatal()` i uruchamianie testów) dostaje się do
`fib(40)`, który jest samymi wywołaniami; dlatego zwykły `finch build` go pomija. zmiennej `thread_local`, której nie potrafi nazwać. Koszt: ok. 3 razy wolniej na `fib(40)`, które składa się wyłącznie
z wywołań; dlatego zwykłe `finch build` tego nie dodaje.
### Wątki i operacje atomowe ### Wątki i operacje atomowe
`std/thread.fch` to cienka warstwa nad runtime'em (`finch_thread_spawn/join`, `finch_mutex_*`: pthreads albo `std/thread.fch` to cienka warstwa nad runtime'em (`finch_thread_spawn/join`, `finch_mutex_*`: pthreads albo, na
`CreateThread` i `CRITICAL_SECTION` na Windowsie). Funkcja wątku to dowolna `fn f(ptr)` podana przez `addr(f)`, co robi Windowsie, `CreateThread` i `CRITICAL_SECTION`). Funkcja wątku to dowolna `fn f(ptr)`, przekazywana przez `addr(f)`,
thunk wołany z C. `atomic_add/sub/swap` to `atomicrmw add/sub/xchg … seq_cst`, `atomic_load/store` to co tworzy opakowanie wywoływalne z C. `atomic_add/sub/swap` stają się `atomicrmw add/sub/xchg … seq_cst`,
`load/store atomic … seq_cst, align <rozmiar>`, a `atomic_cas` to `cmpxchg … seq_cst seq_cst` zwracające `i1`. Biorą `atomic_load/store` to `load/store atomic … seq_cst, align <rozmiar>`, a `atomic_cas` to `cmpxchg … seq_cst seq_cst`
`ptr[T]` do dowolnej liczby całkowitej. Tablice, teksty i mapy Fincha nie są synchronizowane; dzielenie ich między zwracające `i1`. Przyjmują `ptr[T]` do dowolnego typu całkowitego. Tablice, teksty i mapy Fincha nie są synchronizowane;
wątkami wymaga mutexu. dzielenie ich między wątkami wymaga muteksu.
### Ostrzeżenia ### Ostrzeżenia
`warn()` wypisuje `plik:linia:kolumna: warning: …` na stderr (więc `finch ir` daje samo IR) albo, w trybie `index`, `warn()` wypisuje `plik:linia:kol: warning: …` na stderr (żeby `finch ir` pozostało czystym IR) albo, w trybie `index`,
rekord `@W`, który serwer języka publikuje z ważnością 2. Jedno na miejsce w kodzie. Są dwa: parametr-struktura oraz rekord `@W`, który serwer języka publikuje z ważnością 2. Jedno ostrzeżenie na miejsce w kodzie. Są dwa rodzaje:
element `for x in lista`, gdy ich zwykłe dane (`byteSize()`: liczby, zagnieżdżone struktury, `[N]T` z C; `str`, tablice parametr będący strukturą oraz element w `for x in lista`, jeśli ich zwykłe dane (`byteSize()`: liczby, zagnieżdżone
i mapy liczą się jako 0, bo kopiowany jest tylko ich uchwyt) mają 256 bajtów lub więcej. struktury, `[N]T` z C; `str`, tablice i mapy liczą się jako 0, bo kopiuje się tylko ich uchwyt) mają co najmniej 256 bajtów.
### Struktura jako argument przez adres ### Struktury przekazywane przez adres
`argWant()` (tylko argumenty wywołań, nie przypisania): gdy parametr to `ptr[S]`, a argument to zwykła zmienna typu `S`, `argWant()` (tylko argumenty wywołań, nie przypisania): gdy parametr ma typ `ptr[S]`, a argument jest zwykłą zmienną
argumentem jest adres zmiennej, jakby napisano `addr(x)`. Tylko zmienne, więc nic nie jest liczone dwa razy, a na typu `S`, argumentem staje się adres tej zmiennej, tak jakby napisano `addr(x)`. Dotyczy to tylko zmiennych, więc nic
wartości tymczasowe nic nie wskazuje. nie jest obliczane dwa razy, a wskaźnik nigdy nie wskazuje na wartość tymczasową.
---
## 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. - Jeden błąd na uruchomienie, bez kontynuowania po nim.
- Wynik dla macOS jest sprawdzany tylko jako IR; Windows i ARM64 uruchamiane są w CI pod Wine i qemu. - Informacje dla debuggera (`-g`) są najlepsze z `-O0`.
- Jeden błąd na uruchomienie, bez odzyskiwania. - Typy generyczne są monomorfizowane (kod rośnie z każdą instancją); nie ma ograniczeń na parametry typu, więc złe
- Informacje `-g` najlepiej z `-O0`. użycie daje błąd wewnątrz ciała instancji.
- Sterownik używa narzędzi `sh` (`rm`, `mkdir`), więc wymaga powłoki POSIX. - Pamięć, którą miał nieudany test, nie jest zwalniana (uruchamianie testów wychodzi z niego przez `longjmp`).
- Generyki są monomorfizowane (kod rośnie z liczbą instancji); nie ma ograniczeń na typy, błędne użycie to błąd wewnątrz - Wektory nie mają masek (`v < w`), przetasowań ani dzielenia całkowitego.
wygenerowanego ciała.
- Pamięć nieudanego testu nie jest zwalniana (uruchamianie testów wychodzi z niego przez `longjmp`).
- `--target android` jest sprawdzany do pliku obiektowego; linkowanie wymaga NDK i nie było uruchamiane na urządzeniu.

View file

@ -1,6 +1,6 @@
# Ściąga Fincha # Ściąga z Fincha
Cały Finch na jednej stronie. Wyjaśnienia są w [przewodnikach](../../README.pl.md#dokumentacja). Cały Finch na jednej stronie. Wyjaśnienia znajdziesz w [przewodnikach](../../README.pl.md#dokumentacja).
> 🇬🇧 English version: [cheatsheet.md](../en/cheatsheet.md) > 🇬🇧 English version: [cheatsheet.md](../en/cheatsheet.md)
@ -8,39 +8,41 @@ Cały Finch na jednej stronie. Wyjaśnienia są w [przewodnikach](../../README.p
## Polecenie `finch` ## Polecenie `finch`
| Polecenie | Robi | | Polecenie | Co robi |
|---|---| |---|---|
| `finch run plik.fch [argumenty…]` | skompiluj i uruchom (argumenty trafiają do programu) | | `finch run plik.fch [argumenty…]` | kompiluje i uruchamia (argumenty trafiają do programu) |
| `finch build plik.fch [-o nazwa]` | zrób program (`nazwa.exe` na Windowsie) | | `finch build plik.fch [-o nazwa]` | tworzy program (na Windowsie `nazwa.exe`) |
| `finch build plik.fch --shared` | zrób bibliotekę (`libplik.so`, `plik.dll`) dla programu w C; jej `export fn` to jej funkcje C | | `finch build plik.fch --shared [-I katalog]` | tworzy bibliotekę (`libplik.so`, `plik.dll`), którą wczyta program w C; jej funkcjami C są funkcje `export fn` (liczby, bool, char, wskaźniki) |
| `finch test plik.fch` | uruchom jego bloki `test "…" { }`, każdy osobno (także z importowanych modułów) | | `finch test plik.fch` | uruchamia każdy blok `test "…" { }` osobno (także w importowanych modułach) |
| `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 fmt [--check] [pliki/katalogi]` | wcięcia po 4 spacje na każdy poziom `{ }`, bez spacji na końcach linii, najwyżej jedna pusta linia z rzędu (`--check`: tylko wypisuje, co by zmienił) |
| `finch run` / `build` / `test` bez pliku | w folderze z `finch.toml`: jego `entry` | | `finch run` / `build` / `test` bez pliku | w katalogu z `finch.toml`: plik z pola `entry` |
| `finch ir plik.fch` | pokaż LLVM IR | | `finch ir plik.fch` | wypisuje 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 bib` | dolinkowuje bibliotekę C (to samo co `link "bib"`) |
| `--target windows` / `linux` / `arm64` / `macos` / `android` / triple | zbuduj dla innego systemu (`android`: clang z NDK wskazanego przez `ANDROID_NDK_HOME`, zwykle z `--shared`) | | `--target windows` / `linux` / `arm64` / `macos` / `android` / triple | buduje na inny system (`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) |
| `-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ść) | | `--cpu native` / `x86-64-v3` / `apple-m1` … | używa wszystkich instrukcji danego procesora (AVX2, AVX-512, NEON); program będzie wtedy wymagał podobnego procesora |
| `--trace` / `--no-trace` | błąd w trakcie działania pokazuje ścieżkę wywołań; domyślnie włączone dla `run`, `test`, `-g` i `-O0`, wyłączone dla zwykłego `build` (spowalnia program) |
Zmienne środowiskowe: `FINCH_PATH` (foldery z modułami), `FINCH_CC` (kompilator C, który linkuje), `FINCH_CXX` (dla plików `.cpp`). Zmienne środowiskowe: `FINCH_PATH` (katalogi 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ą. Skompilowany program i jego pliki z `link "x.c"` zostają w pamięci podręcznej (`~/.cache/finch`), dopóki nie zmienią się one same albo ich nagłówki.
`finch.toml` (obok kodu; `finch run` w tym folderze nie potrzebuje pliku ani flag): `finch.toml` (obok kodu; wtedy `finch run` nie potrzebuje ani nazwy pliku, ani opcji):
```toml ```toml
name = "gra" # co robi finch build name = "gra" # co zbuduje finch build
entry = "src/main.fch" entry = "src/main.fch"
paths = ["src"] # foldery z modułami paths = ["src"] # katalogi z modułami
include = ["vendor/SDL3/include"] # nagłówki C include = ["vendor/SDL3/include"] # nagłówki C
links = ["SDL3", "native/glue.cpp"] # biblioteki i własne .c / .cpp / .o / .a links = ["SDL3", "native/glue.cpp"] # biblioteki i twoje pliki .c / .cpp / .o / .a
libdirs = ["vendor/SDL3/lib"] libdirs = ["vendor/SDL3/lib"]
cflags = ["-DFAST"] # dla własnych plików .c / .cpp cflags = ["-DFAST"] # dla twoich plików .c / .cpp
ldflags = ["-Wl,-rpath,."] ldflags = ["-Wl,-rpath,."]
cpu = "native" # to samo co --cpu native
[target.windows] # tylko przy budowaniu dla tego systemu (też linux, macos, android, linux-arm64) [target.windows] # tylko przy budowaniu na ten system (są też linux, macos, android, linux-arm64)
links = ["ws2_32"] links = ["ws2_32"]
``` ```
@ -63,8 +65,8 @@ fn main() {
} }
``` ```
`main` to `fn main()`, `fn main() -> int` (kod wyjścia) albo `fn main([]str args)`. `main` może wyglądać tak: `fn main()`, `fn main() -> int` (zwraca kod wyjścia) albo `fn main([]str args)`.
Jedna instrukcja na linijkę, bez średników, `{` w tej samej linijce, bez nawiasów wokół warunków. Jedna instrukcja w linii, bez średników, `{` w tej samej linii, warunki bez nawiasów.
--- ---
@ -72,12 +74,12 @@ Jedna instrukcja na linijkę, bez średników, `{` w tej samej linijce, bez nawi
| Kod | Znaczenie | | Kod | Znaczenie |
|---|---| |---|---|
| `x := 5` | nowa zmienna, typ zgadnięty | | `x := 5` | nowa zmienna, typ wynika z wartości |
| `int x = 5` | nowa zmienna, typ podany | | `int x = 5` | nowa zmienna z podanym typem |
| `int x` | zero / `""` / `[]` / `null` / wartości domyślne struktury (też struktur w niej) | | `int x` | zero / `""` / `[]` / `null` / wartości domyślne struktury (także struktur w jej środku) |
| `x = 7` | przypisanie | | `x = 7` | przypisanie |
| `x += 1` `-=` `*=` `/=` `%=` | zmiana | | `x += 1` `-=` `*=` `/=` `%=` | zmiana |
| `const W = 640`, `const f32 G = 9.8` | stała (na najwyższym poziomie pliku; `ksztalty.W` z modułu) | | `const W = 640`, `const f32 G = 9.8` | stała (na najwyższym poziomie pliku; z modułu: `ksztalty.W`) |
--- ---
@ -87,15 +89,16 @@ Jedna instrukcja na linijkę, bez średników, `{` w tej samej linijce, bez nawi
|---|---| |---|---|
| `int` (= `i64`) | `42`, `-7`, `0xFF`, `1_000_000` | | `int` (= `i64`) | `42`, `-7`, `0xFF`, `1_000_000` |
| `float` (= `f64`) | `3.14`, `1e9`, `2.5e-3` | | `float` (= `f64`) | `3.14`, `1e9`, `2.5e-3` |
| `bool` | `true` (prawda), `false` (fałsz) | | `bool` | `true`, `false` |
| `char` | `'A'`, `'\n'` | | `char` | `'A'`, `'\n'` |
| `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) | | `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ącego cudzysłowu znika) |
| `i8 i16 i32 i64` | ze znakiem, z rozmiarem | | `i8 i16 i32 i64` | liczby całkowite ze znakiem, o podanym rozmiarze |
| `u8 u16 u32 u64` | bez znaku, z rozmiarem | | `u8 u16 u32 u64` | liczby całkowite bez znaku |
| `f32 f64` | zmiennoprzecinkowe | | `f32 f64` | liczby zmiennoprzecinkowe |
| `[]T` | tablica: `[1, 2, 3]`, `[]str imiona` | | `[]T` | tablica: `[1, 2, 3]`, `[]str imiona` |
| `map[K]V` | mapa: `["a": 1]`, `map[str]int ile`, `[:]` | | `map[K]V` | mapa: `["a": 1]`, `map[str]int ile`, `[:]` |
| `ptr[T]`, `ptr` | wskaźnik, wskaźnik bez typu | | `ptr[T]`, `ptr` | wskaźnik, wskaźnik bez typu |
| `f32x8`, `i8x32`, `f64x4`, `u16x16` … | wektor: N liczb liczonych naraz (SIMD), zob. niżej |
| `Nazwa` / `modul.Nazwa` | struktura | | `Nazwa` / `modul.Nazwa` | struktura |
Znaki specjalne: `\n \t \r \0 \\ \" \'`. Znaki specjalne: `\n \t \r \0 \\ \" \'`.
@ -107,11 +110,11 @@ Znaki specjalne: `\n \t \r \0 \\ \" \'`.
| Priorytet | Operatory | | Priorytet | Operatory |
|---|---| |---|---|
| 5 | `*` `/` `%` `<<` `>>` `&` | | 5 | `*` `/` `%` `<<` `>>` `&` |
| 4 | `+` `-` `\|` `^` (`+` skleja też tekst) | | 4 | `+` `-` `\|` `^` (`+` łączy też teksty) |
| 3 | `==` `!=` `<` `<=` `>` `>=` | | 3 | `==` `!=` `<` `<=` `>` `>=` |
| 2 | `&&` (i) | | 2 | `&&` |
| 1 | `\|\|` (lub) | | 1 | `\|\|` |
| jednoargumentowe | `-x` `!x` (nie) `~x` | | jednoargumentowe | `-x` `!x` `~x` |
`x & 1 == 0` znaczy `(x & 1) == 0`. `x & 1 == 0` znaczy `(x & 1) == 0`.
@ -121,14 +124,14 @@ Znaki specjalne: `\n \t \r \0 \\ \" \'`.
```c ```c
if a > b { … } else if a == b { … } else { … } if a > b { … } else if a == b { … } else { … }
x := if a > b { a } else { b } // jako wartość: jedna wartość na gałąź, else wymagane x := if a > b { a } else { b } // jako wartość: po jednej wartości w każdej gałęzi, else obowiązkowe
while x > 0 { … } while x > 0 { … }
for i in 0..10 { … } // 0 … 9 for i in 0..10 { … } // 0 … 9
for el in lista { … } // każdy element (albo znak str) for el in lista { … } // każdy element (albo znak tekstu)
for i, el in lista { … } // razem z indeksem for i, el in lista { … } // razem z indeksem
for klucz, wart in m { … } // każdy wpis mapy, w kolejności dodawania for klucz, wartosc in m { … } // każdy wpis mapy, w kolejności dodania
break continue return wartosc break continue return wartosc
defer sprzatanie() // uruchomi się na końcu bloku defer sprzatnij() // wykona się, gdy blok się skończy
``` ```
--- ---
@ -144,48 +147,48 @@ fn przywitaj(str imie) { // bez wyniku
} }
``` ```
Parametry zachowują się jak kopie. Kolejność w pliku nie ma znaczenia. Przecinek po ostatnim argumencie jest dozwolony. Parametry zachowują się jak kopie. Kolejność funkcji w pliku nie ma znaczenia. Po ostatnim argumencie może stać przecinek.
--- ---
## Metody ## Metody
```c ```c
fn Punkt.przesun(int dx, int dy) { // self to Punkt, na którym wywołano (nie kopia) fn Punkt.przesun(int dx, int dy) { // self to punkt, na którym ją wywołano (nie kopia)
self.x += dx self.x += dx
self.y += dy self.y += dy
} }
fn Punkt.odleglosc() -> float { … } fn Punkt.odleglosc() -> float { … }
p.przesun(1, 2) lista[0].przesun(1, 1) wsk_na_punkt.przesun(0, 1) p.przesun(1, 2) lista[0].przesun(1, 1) wskaznik_na_punkt.przesun(0, 1)
``` ```
Także na strukturach z C: `fn Vector2.length() -> f32 { … }`. Działają też na strukturach z C: `fn Vector2.length() -> f32 { … }`.
--- ---
## Błędy jako wartości ## Błędy jako wartości
```c ```c
fn parsuj(str s) -> int! { // ! = może się nie udać fn parsuj(str s) -> int! { // ! = może się nie udać
if s.len == 0 { if s.len == 0 {
return error("pusty") return error("pusty tekst")
} }
return int(s) or { return error("to nie liczba: " + s) } return int(s) or { return error("to nie liczba: " + s) }
} }
fn zapisz(str p, str t) -> ! { // może się nie udać, bez wyniku fn zapisz(str p, str t) -> ! { // może się nie udać, bez wyniku
try write_file(p, t) try write_file(p, t)
} }
``` ```
| Kod | Przy porażce | | Kod | Gdy się nie uda |
|---|---| |---|---|
| `x := parsuj(s) or 0` | użyj 0 | | `x := parsuj(s) or 0` | weź 0 |
| `x := parsuj(s) or { print(err); return }` | wykonaj blok (`err`: komunikat) | | `x := parsuj(s) or { print(err); return }` | wykonaj blok (`err` to komunikat) |
| `x := try parsuj(s)` | zakończ porażką też bieżącą funkcję | | `x := try parsuj(s)` | przerwij też bieżącą funkcję z tym samym błędem |
| `fn main() -> !` | porażka wypisuje `error: …`, kod wyjścia 1 | | `fn main() -> !` | błąd wypisuje `error: …`, kod wyjścia 1 |
Funkcje wbudowane, które mogą się nie udać: `int(s)`, `float(s)`, `read_file`, `write_file`, `delete_file`. Wbudowane funkcje, które mogą się nie udać: `int(s)`, `float(s)`, `read_file`, `write_file`, `delete_file`.
--- ---
@ -193,9 +196,9 @@ Funkcje wbudowane, które mogą się nie udać: `int(s)`, `float(s)`, `read_file
| Kod | Znaczenie | | Kod | Znaczenie |
|---|---| |---|---|
| `struct P { … }` | definicja (jedno pole na linijkę, `= domyślna` opcjonalnie) | | `struct P { … }` | definicja (jedno pole w linii, `= wartość` opcjonalnie) |
| `P(1, 2)` | utworzenie, wszystkie pola po kolei | | `P(1, 2)` | utworzenie, wszystkie pola po kolei |
| `P(x: 1)` | utworzenie po nazwie, reszta domyślna/zero | | `P(x: 1)` | utworzenie przez nazwy pól, reszta domyślna albo zero |
| `p.x`, `p.x = 5` | odczyt, zmiana pola | | `p.x`, `p.x = 5` | odczyt, zmiana pola |
| `b := a` | pełna kopia | | `b := a` | pełna kopia |
@ -203,62 +206,83 @@ Funkcje wbudowane, które mogą się nie udać: `int(s)`, `float(s)`, `read_file
## Tablice `[]T` ## Tablice `[]T`
| Kod | Daje | | Kod | Wynik |
|---|---| |---|---|
| `a.len` | liczba elementów | | `a.len` | liczba elementów |
| `a[i]`, `a[i] = x` | element (z kontrolą zakresu) | | `a[i]`, `a[i] = x` | element (zakres jest sprawdzany) |
| `a.push(x)` | dodaj na końcu | | `a.push(x)` | dodaje na końcu |
| `a.pop()` | zabierz ostatni i go oddaj | | `a.pop()` | zdejmuje ostatni i go zwraca |
| `a.insert(i, x)` | wstaw na pozycję i | | `a.insert(i, x)` | wstawia na pozycję i |
| `a.remove(i)` | usuń pozycję i i ją oddaj | | `a.remove(i)` | usuwa pozycję i i ją zwraca |
| `a.clear()` | wyczyść | | `a.clear()` | opróżnia |
| `a.resize(n)` | długość n (nowe elementy to zera) | | `a.resize(n)` | długość n (nowe elementy są zerami) |
| `a.find(x)` | indeks albo -1 | | `a.find(x)` | indeks albo -1 |
| `a.contains(x)` | bool | | `a.contains(x)` | bool |
| `a.sort()` | posortuj (liczby, znaki, str) | | `a.sort()` | sortuje (liczby, znaki, teksty) |
| `a.reverse()` | odwróć | | `a.reverse()` | odwraca kolejność |
| `a.slice(s, e)` | nowa tablica z s … e-1 | | `a.slice(p, k)` | nowa tablica z elementów p … k-1 |
| `a.join(sep)` | `[]str` → jeden str | | `a.join(sep)` | `[]str` → jeden tekst |
| `a.ptr` | adres pierwszego elementu (dla C) | | `a.ptr` | adres pierwszego elementu (dla C) |
--- ---
## Mapy `map[K]V` ## Mapy `map[K]V`
| Kod | Daje | | Kod | Wynik |
|---|---| |---|---|
| `m := ["a": 1, "b": 2]` | mapa (klucze: liczby, char, bool, str) | | `m := ["a": 1, "b": 2]` | mapa (klucze: liczby, char, bool, str) |
| `m[k]` | wartość (brak klucza: program się zatrzymuje) | | `m[k]` | wartość (brak klucza zatrzymuje program) |
| `m[k] = v` | dodaj / zastąp | | `m[k] = v` | dodaje albo podmienia |
| `m[k] += 1`, `m[k].push(x)` | brakujący klucz zaczyna od wartości domyślnej (0, `[]`, …) | | `m[k] += 1`, `m[k].push(x)` | brakujący klucz zaczyna od wartości domyślnej (0, `[]`, …) |
| `m.len` | liczba kluczy | | `m.len` | liczba kluczy |
| `m.has(k)` | czy jest | | `m.has(k)` | bool |
| `m.get(k, domyslna)` | wartość albo domyślna | | `m.get(k, domyslna)` | wartość albo `domyslna` |
| `m.remove(k)` | bool: czy był | | `m.remove(k)` | bool: czy klucz był |
| `m.clear()` | wyczyść | | `m.clear()` | opróżnia |
| `m.keys()`, `m.values()` | tablice, w kolejności dodawania | | `m.keys()`, `m.values()` | tablice, w kolejności dodania |
---
## Wektory `f32x8` (SIMD)
Typy: `f32x4/8/16`, `f64x2/4/8`, `i8x16/32/64`, `i16x8/16/32`, `i32x4/8/16`, `i64x2/4/8` i tak samo `u…`; najwyżej 512 bitów.
| Kod | Wynik |
|---|---|
| `f32x8(x)` | `x` w każdym elemencie |
| `f32x8(a, b, …, h)` | po jednej liczbie na element |
| `f32x8(lista, i)` | `lista[i]` … `lista[i+7]` (z `[]f32` albo `ptr[f32]`) |
| `f32x8(v)` | 8-elementowy wektor przeliczony element po elemencie (`f32x8(calkowite)`, `i32x8(ulamki)`) |
| `v + w`, `v * 2.0`, `-v` | element po elemencie; zwykła liczba działa na każdy element (`/` tylko dla liczb zmiennoprzecinkowych) |
| `& \| ^ << >> ~` | element po elemencie, dla liczb całkowitych |
| `v == w`, `v != w` | bool: czy wszystkie elementy są równe / czy któryś się różni |
| `v[i]`, `v[i] = x`, `v.len` | jeden element; liczba elementów |
| `min(v, w)`, `max`, `abs(v)`, `clamp(v, lo, hi)` | element po elemencie |
| `v.sum()`, `v.min()`, `v.max()`, `v.dot(w)` | jedna liczba ze wszystkich elementów |
| `v.sqrt()`, `v.floor()`, `v.ceil()`, `v.round()`, `v.fma(b, c)` | każdy element (liczby zmiennoprzecinkowe; `fma` = `v * b + c`) |
| `v.store(lista, i)` | zapisuje elementy do `lista[i]` … |
--- ---
## Tekst `str` ## Tekst `str`
| Kod | Daje | | Kod | Wynik |
|---|---| |---|---|
| `s.len` | liczba bajtów (ą, ż… to 2) | | `s.len` | liczba bajtów |
| `s[i]`, `s[i] = 'x'` | znak | | `s[i]`, `s[i] = 'x'` | znak |
| `a + b` | sklejony tekst | | `a + b` | połączony tekst |
| `"x = \(x)"` | wartość dowolnego kodu w tekście (`\(x, 2)`: liczba z 2 cyframi po przecinku) | | `"x = \(x)"` | wartość dowolnego kodu wstawiona w tekst (`\(x, 2)`: liczba z dwiema 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)` | bool |
| `s.starts_with(t)`, `s.ends_with(t)` | czy zaczyna / kończy się | | `s.starts_with(t)`, `s.ends_with(t)` | bool |
| `s.split(sep)` | `[]str` | | `s.split(sep)` | `[]str` |
| `s.trim()` | bez spacji na końcach | | `s.trim()` | bez spacji na końcach |
| `s.upper()`, `s.lower()` | A–Z / a–z | | `s.upper()`, `s.lower()` | A–Z / a–z |
| `s.replace(a, b)` | każde a → b | | `s.replace(a, b)` | każde a zamienione na b |
| `s.repeat(n)` | n razy | | `s.repeat(n)` | tekst n razy |
| `s.bytes()` | `[]u8` | | `s.bytes()` | `[]u8` |
| `s.ptr` | `ptr[char]` (dla C; `str` trafia też do parametru `const char *` bez zmian) | | `s.ptr` | `ptr[char]` (dla C; parametr `const char *` przyjmie też samo `str`) |
| `==` `!=` `<` `>` | porównanie | | `==` `!=` `<` `>` | porównanie |
--- ---
@ -271,41 +295,41 @@ Funkcje wbudowane, które mogą się nie udać: `int(s)`, `float(s)`, `read_file
| `int("42")` | `42` (zatrzymuje program, jeśli to nie liczba) | | `int("42")` | `42` (zatrzymuje program, jeśli to nie liczba) |
| `int('A')` | `65` | | `int('A')` | `65` |
| `float(7)`, `float("2.5")` | `7`, `2.5` | | `float(7)`, `float("2.5")` | `7`, `2.5` |
| `u8(300)` | `44` (zostawia najniższe bity) | | `u8(300)` | `44` (zostają najniższe bity) |
| `char(66)` | `'B'` | | `char(66)` | `'B'` |
| `bool(0)` | `false` | | `bool(0)` | `false` |
| `str(42)`, `str(1.5)`, `str(true)`, `str('c')` | tekst (float: najkrótszy tekst, który jest tą samą liczbą) | | `str(42)`, `str(1.5)`, `str(true)`, `str('c')` | tekst (liczba zmiennoprzecinkowa: najkrótszy zapis tej samej liczby) |
| `str(3.14159, 2)` | `"3.14"` (tyle cyfr po kropce) | | `str(3.14159, 2)` | `"3.14"` (tyle cyfr po przecinku) |
| `str([1, 2])`, `str(m)`, `str(punkt)` | ten sam tekst, który pokazuje `print` | | `str([1, 2])`, `str(m)`, `str(punkt)` | ten sam tekst, który pokazuje `print` |
| `int("x") or 0`, `float(s) or 0.0` | liczba albo wartość zapasowa | | `int("x") or 0`, `float(s) or 0.0` | liczba albo wartość zapasowa |
| `str(bajty)` | `[]u8` → tekst | | `str(bajty)` | `[]u8` → tekst |
| `str(p)` | `char*` z C → tekst | | `str(p)` | `char*` z C → tekst |
| `ptr(p)`, `ptr(16)` | wskaźnik bez typu, ze wskaźnika albo liczby | | `ptr(p)`, `ptr(16)` | wskaźnik bez typu, ze wskaźnika albo z liczby |
| `int(p)` | wskaźnik → adres | | `int(p)` | wskaźnik → adres |
Automatycznie tylko wtedy, gdy nic nie ginie: mniejsza → większa liczba, dowolna całkowita → float, `f32` → `f64`. Automatycznie tylko wtedy, gdy nic nie ginie: mniejsza liczba → większa, liczba całkowita → zmiennoprzecinkowa, `f32` → `f64`.
--- ---
## Funkcje wbudowane ## Funkcje wbudowane
| Funkcja | Robi | | Funkcja | Co robi |
|---|---| |---|---|
| `print(a, b, …)` | wypisz, spacje między, nowa linia na końcu | | `print(a, b, …)` | wypisuje wartości oddzielone spacjami i kończy linię |
| `input()`, `input("Pytanie? ")` | wczytaj linijkę | | `input()`, `input("Pytanie? ")` | wczytuje linię |
| `read_file(sciezka)` | plik → str | | `read_file(sciezka)` | plik → str |
| `write_file(sciezka, tekst)` | str → plik, `true`, jeśli się udało | | `write_file(sciezka, tekst)` | str → plik, `true`, jeśli się udało |
| `file_exists(sciezka)` | czy plik istnieje | | `file_exists(sciezka)` | bool |
| `delete_file(sciezka)` | usuń plik | | `delete_file(sciezka)` | bool |
| `shell(polecenie)` | uruchom polecenie, jego kod wyjścia | | `shell(polecenie)` | uruchamia polecenie, zwraca jego kod wyjścia |
| `exit(kod)` | zakończ program od razu (kończy też blok `or { }`) | | `exit(kod)` | od razu kończy program (kończy też blok `or { }`) |
| `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, lo, hi)` | dla dowolnych liczb (twoja funkcja o tej samej nazwie ma pierwszeństwo) |
| `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)` = funkcja zwrotna dla C |
| `assert(warunek)`, `assert(warunek, "dlaczego")` | zatrzymaj się z błędem, gdy warunek jest fałszywy (w bloku `test`: ten test nie przechodzi) | | `assert(warunek)`, `assert(warunek, "dlaczego")` | zatrzymuje program, 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)`) | | `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, którą zmieniają też 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)` | oddaje tę pamięć |
--- ---
@ -314,7 +338,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` (zmienna-struktura podana tam, gdzie idzie `ptr[TaStruktura]`: `pchnij(cialo)` = `pchnij(addr(cialo))`) | | `addr(x)` | `&x` (zmienna-struktura podana tam, gdzie oczekiwany jest `ptr[TaStruktura]`: `push(cialo)` = `push(addr(cialo))`) |
| `p.value` | `*p` | | `p.value` | `*p` |
| `p.pole` | `p->pole` | | `p.pole` | `p->pole` |
| `p[i]` | `p[i]` | | `p[i]` | `p[i]` |
@ -325,23 +349,23 @@ Automatycznie tylko wtedy, gdy nic nie ginie: mniejsza → większa liczba, dowo
## Pamięć ## Pamięć
- 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. `free` nie jest potrzebne.
- `b := a` kopiuje; wynik wywołania jest przenoszony; `return x` przenosi. - `b := a` kopiuje; wynik funkcji 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ę. Struktura z 256+ bajtami liczb przekazana przez wartość albo w `for x in lista` dostaje ostrzeżenie. - Przekazywanie struktury: `fn f(Cialo c)` pracuje na własnej kopii (struktura wywołującego się nie zmienia), `fn f(ptr[Cialo] c)` zmienia strukturę wywołującego (`f(cialo)` albo `f(addr(cialo))`), metoda dostaje `self` przez adres, nigdy jako kopię. Struktura z co najmniej 256 bajtami liczb, przekazywana przez wartość albo w `for x in lista`, dostaje ostrzeżenie.
- `new(...)` + `free(...)` (często z `defer`) do list wiązanych, drzew, współdzielenia. - `new(...)` z `free(...)` (często razem z `defer`) przydaje się do list wiązanych, drzew i współdzielenia.
- Pamięć z C: funkcjami z C (`malloc` / `free`, `fclose`, …). - Pamięć z C zwalniasz funkcjami C (`malloc` / `free`, `fclose`, …).
--- ---
## Moduły ## Moduły
```c ```c
import ksztalty // ksztalty.fch obok tego pliku (albo w FINCH_PATH) import ksztalty // ksztalty.fch obok tego pliku (albo w FINCH_PATH)
ksztalty.pole(b) // jego funkcje ksztalty.pole(b) // jego funkcje
ksztalty.Pudlo(2, 3) // jego struktury ksztalty.Pudelko(2, 3) // jego struktury
ksztalty.Pudlo inne // w deklaracjach ksztalty.Pudelko inne // w deklaracjach
import ui/przycisk // ui/przycisk.fch: przycisk.rysuj() import ui/button // ui/button.fch: button.draw()
import ui/przycisk as p // inna nazwa: p.rysuj() import ui/button as btn // pod inną nazwą: btn.draw()
``` ```
--- ---
@ -350,14 +374,14 @@ import ui/przycisk as p // inna nazwa: p.rysuj()
| Kod | Znaczenie | | Kod | Znaczenie |
|---|---| |---|---|
| `import "x.h"` | funkcje, struktury, enumy, liczbowe `#define`, zmienne globalne | | `import "x.h"` | funkcje, struktury, enumy, liczby z `#define`, zmienne globalne |
| `link "nazwa"` | libnazwa.so / nazwa.lib (najpierw pkg-config) | | `link "nazwa"` | libnazwa.so / nazwa.lib (najpierw pyta pkg-config) |
| `link "plik.c"`, `link "glue.cpp"` | skompiluj własny plik C albo C++ (C++: funkcje w `extern "C"`) | | `link "plik.c"`, `link "glue.cpp"` | kompiluje twój plik C albo C++ (w C++ funkcje w `extern "C"`) |
| `addr(mojaFn)` | funkcja Fincha jako callback | | `addr(mojaFn)` | przekazuje funkcję Fincha jako funkcję zwrotną |
| Typ C | Finch | | Typ w C | W Finchu |
|---|---| |---|---|
| `int` / `long long` / `unsigned` | `i32` / `i64` / `u32` (wg rozmiaru) | | `int` / `long long` / `unsigned` | `i32` / `i64` / `u32` (według rozmiaru) |
| `float` / `double` | `f32` / `f64` | | `float` / `double` | `f32` / `f64` |
| `char *` (parametr, wynik) | `str` | | `char *` (parametr, wynik) | `str` |
| `char *` (pole, za wskaźnikiem) | `ptr[char]` | | `char *` (pole, za wskaźnikiem) | `ptr[char]` |
@ -365,46 +389,46 @@ import ui/przycisk as p // inna nazwa: p.rysuj()
| `T *` | `ptr[T]` | | `T *` | `ptr[T]` |
| `void *`, uchwyty | `ptr` | | `void *`, uchwyty | `ptr` |
| `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)`, do C przez wskaźnik (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 z parametrami) |
| `#define RAYWHITE (Color){ 245, 245, 245, 255 }` | `Color` | | `#define RAYWHITE (Color){ 245, 245, 245, 255 }` | wartość typu `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]`. **`char *` to `str`**: funkcja C, która przyjmuje albo zwraca `char *`, przyjmuje albo zwraca `str` Fincha (bez `.ptr` i bez zamiany); tylko pole `char *` i `char **` zostają jako `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). 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"`), z nagłówkiem (może go napisać 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 *` funkcji C od razu jest `str`; struktury przez wartość działają w konwencjach wywołań System V, Windows x64 i ARM64 (Linux i macOS).
--- ---
## Błędy w czasie działania (program zatrzymuje się z plikiem i linijką) ## Błędy w trakcie działania (program kończy się z plikiem i linią)
| Komunikat | Przyczyna | | Komunikat | Przyczyna |
|---|---| |---|---|
| `division by zero` | `/` albo `%` przez 0 | | `division by zero` | `/` albo `%` przez 0 |
| `index 5 is out of range (the length is 3)` | `a[5]` | | `index 5 is out of range (the length is 3)` | `a[5]` |
| `pop() on an empty array` | `pop()` na pustej tablicy | | `pop() on an empty array` | |
| `used .value on a null pointer` | `.value` / `.pole` / `[i]` przez `null` | | `used .value on a null pointer` | `.value` / `.pole` / `[i]` przez `null` |
| `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")` | | `assert failed: dlaczego` | `assert(warunek, "dlaczego")` |
Pod komunikatem wywołania, które do niego doprowadziły (`finch run`, `finch test`, `-g`, `-O0`, `--trace`): Pod komunikatem pojawiają się 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) gra.fch:6: runtime error: index 3 is out of range (the length is 2)
in Enemy.next (gra.fch:6) in Wrog.nastepny (gra.fch:6)
in step (gra.fch:10) in krok (gra.fch:10)
in main (gra.fch:15) in main (gra.fch:15)
``` ```
## Testy ## Testy
```c ```c
test "wysokość skoku" { // obok kodu; finch test plik.fch je uruchamia, finch run/build je pomijają test "wysokość skoku" { // obok kodu; uruchamia je finch test plik.fch, finch run/build je pomijają
g := Gracz() p := Gracz()
g.skocz() p.skocz()
assert(g.y > 0.0, "y to \(g.y)") assert(p.y > 0.0, "y wynosi \(p.y)")
} }
``` ```
@ -413,74 +437,55 @@ test "wysokość skoku" { // obok kodu; finch test plik.fch je uruchamia
## Edytory ## Edytory
Kate: `editors/kate/install.sh`, potem włącz wtyczkę **LSP Client**; uruchamianie przez **Build & Run** → `finch run %f`. Kate: `editors/kate/install.sh`, potem włącz wtyczkę **LSP Client**; uruchamianie przez **Build & Run** → `finch run %f`.
Każdy edytor z LSP: `finch lsp`. Dowolny edytor z LSP: `finch lsp`.
### VS Code (rozszerzenie „Finch”) ### VS Code (rozszerzenie „Finch”)
| Klawisz / akcja | Robi | | Klawisz / akcja | Co robi |
|---|---| |---|---|
| ▶ / `Ctrl+F5` | uruchom plik | | ▶ / `Ctrl+F5` | uruchamia plik |
| `Finch: Build This File` | zbuduj; błędy trafiają do panelu Problems | | `Finch: Build This File` | buduje; błędy trafiają do panelu Problems |
| `F12` | idź do definicji | | `F12` | przejście do definicji |
| najechanie myszą | typ / sygnatura / opis | | najechanie kursorem | typ / sygnatura / opis |
| `Ctrl+Spacja` | podpowiedzi (po `.`: pola, metody, zawartość modułu) | | `Ctrl+Spacja` | podpowiedzi (po `.`: pola, metody, zawartość modułu) |
| `Ctrl+Shift+O` | funkcje i struktury w pliku | | `Ctrl+Shift+O` | funkcje i struktury w pliku |
| `main`, `fn`, `fnr`, `fnf`, `method`, `struct`, `for`, `foreach`, `forkv`, `map`, `orb`, `if`, `ife`, `while`, `input` + Tab | szablony | | `main`, `fn`, `fnr`, `fnf`, `method`, `struct`, `for`, `foreach`, `forkv`, `map`, `orb`, `if`, `ife`, `while`, `input` + Tab | gotowe fragmenty kodu |
| ustawienie `finch.path` | gdzie jest finch / finch.exe | | ustawienie `finch.path` | gdzie leży finch / finch.exe |
--- ---
## Enumy i `match` ## Enumy i `match`
``` ```
enum Kolor { Czerwony, Zielony } enum Status { Ok = 200, Brak = 404 } enum Kolor { Czerwony, Zielony, Niebieski } enum Status { Ok = 200, NotFound = 404 }
k := Kolor.Zielony int(k) Kolor(1) k == Kolor.Czerwony // tylko == i !=; Kolor(7) to błąd k := Kolor.Zielony int(k) Kolor(2) k == Kolor.Czerwony // tylko == i !=; Kolor(7) to błąd
fn Kolor.nastepny() -> Kolor { ... } // metody działają fn Kolor.nastepny() -> Kolor { ... } // metody działają
match k { Czerwony -> ... Zielony -> { ... } else -> ... } // same nazwy wariantów; każdy wariant albo else match k { Czerwony -> ... Zielony, Niebieski -> { ... } else -> ... } // same nazwy wariantów; każdy wariant albo else
x := match n { 1, 2 -> "mało" else -> "dużo" } // wyrażenie: gałęzie tego samego typu x := match n { 1, 2 -> "kilka" else -> "dużo" } // wyrażenie: każda gałąź tego samego typu
``` ```
## Typy generyczne ## Typy generyczne
``` ```
fn max[T](T a, T b) -> T { ... } max(3, 9) max[float](1, 2.5) // wynika z argumentów albo zapisane fn max[T](T a, T b) -> T { ... } max(3, 9) max[float](1, 2.5) // typ wywnioskowany albo podany
struct Stos[T] { []T elementy } fn Stos[T].dodaj(T x) { ... } // typ zawsze podaje się przy tworzeniu: struct Stos[T] { []T el } fn Stos[T].push(T x) { ... } // typ zawsze idzie z nazwą:
s := Stos[int]() Stos[int] t Para[str, int]("wiek", 31) // nigdy samo Stos() s := Stos[int]() Stos[int] t Para[str, int]("wiek", 31) // nigdy samo Stos()
``` ```
## Biblioteka standardowa (`import nazwa`; własny plik o tej nazwie ma pierwszeństwo) ## Biblioteka standardowa (`import nazwa`; twój plik o tej samej nazwie ma pierwszeństwo)
| | | | | |
|---|---| |---|---|
| `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, wzor)` `days_in_month(r, m)` |
| `proc` | `run(cmd)` `output(cmd)` (błąd z jego wyjściem) `env(nazwa, domyślna)` `pid()` `cwd()` | | `proc` | `run(polecenie)` `output(polecenie)` (przy błędzie zwraca jego wyjście) `env(nazwa, domyslna)` `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)`) | | `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(katalog)` `remove_dir(p)` `data_dir(aplikacja)` |
| `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)` |
## Słowa kluczowe ## Słowa kluczowe
| Słowo | Po polsku | `fn return if else while for in break continue true false null import link struct enum match defer or try const export`
|---|---| (`test` i `as` tylko tam, gdzie coś znaczą: `test "nazwa" { }`, `import x as y`)
| `fn` | funkcja | (oraz `self` wewnątrz metod)
| `return` | zwróć |
| `if` / `else` | jeżeli / w przeciwnym razie |
| `while` | dopóki |
| `for` … `in` | dla … w |
| `break` / `continue` | przerwij / kontynuuj |
| `true` / `false` / `null` | prawda / fałsz / nic |
| `import` / `link` | zaimportuj / dolinkuj |
| `struct` | struktura |
| `enum` | wyliczenie |
| `match` | dopasuj |
| `defer` | odłóż (na koniec bloku) |
| `or` | albo (co zrobić przy porażce) |
| `try` | spróbuj (porażkę przekaż dalej) |
| `const` | stała |
| `export` | wystaw (funkcja dla programu w C) |
| `self` | sam (w metodzie: struktura, na której ją wywołano) |
| `test` | test (tylko jako `test "nazwa" { }`) |
| `as` | jako (tylko w `import x as y`) |
| `map` | mapa |