From 06bfe940fa856a43daef1f550693ab52742a643c Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Nov 2026 14:09:58 +0000 Subject: [PATCH] docs: the Polish technicians' and beginners' guides rewritten in natural Polish; vectors in every guide - dla-technikow.md and dla-poczatkujacych.md written anew from the English, in plain Polish (no calques, the same words for the same things in every guide, addressing every reader alike) - the technicians' guides (en, pl): "Variants that hold values" moved to the enums section; the examples list has wordcount and simd - src/embedded.fch packs the runtime with its updated comment Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Psc42moPLwP43Cmb9oZqSR --- docs/en/for-technicians.md | 50 +- docs/pl/dla-inzynierow.md | 2 +- docs/pl/dla-poczatkujacych.md | 410 +++++++------- docs/pl/dla-technikow.md | 971 +++++++++++++++++++--------------- src/embedded.fch | 2 +- 5 files changed, 758 insertions(+), 677 deletions(-) diff --git a/docs/en/for-technicians.md b/docs/en/for-technicians.md index 712dea1..34aa27c 100644 --- a/docs/en/for-technicians.md +++ b/docs/en/for-technicians.md @@ -1011,7 +1011,7 @@ Finch/ ├── std/ the standard library, in Finch: json, path, time, proc, fs, thread ├── runtime/ finch_rt.c: the small runtime (text, arrays, maps, input, files) ├── bootstrap/ build.sh, install.sh and the seeds (the compiler's own IR, one per system) -├── examples/ hello, tour, types, structs, todo, guess, c_import, window, opengl, vulkan, raylib, llvm +├── examples/ hello, tour, types, structs, todo, guess, wordcount, c_import, simd, window, opengl, vulkan, raylib, llvm ├── tests/ │ ├── run/ new/ programs + the exact output they must print (.out), input (.in) │ ├── fail/ new/fail/ programs that must fail, with the expected error in line 1 @@ -1025,35 +1025,12 @@ Finch/ ``` ```sh -tests/run.sh # programs, refusals, -g builds, a shared library, the language server +tests/run.sh # programs, expected errors, -g builds, a shared library, the language server MEMCHECK=1 tests/run.sh # the same under valgrind: no leaks, no bad memory access tests/cross.sh windows # every test built for Windows and run with Wine tests/cross.sh arm64 # every test built for ARM64 Linux and run with QEMU ``` -### Variants that hold values - -A variant can carry values. Make one with `Shape.Circle(2.0)`, take them out with `match`, which names them: - -```c -enum Shape { - Circle(float) - Rect(float, float) - Point -} - -fn Shape.area() -> float { - return match self { - Circle(r) -> 3.14 * r * r - Rect(w, h) -> w * h - Point -> 0.0 - } -} -``` - -`_` ignores a value. `match` must handle every variant (or end with `else`). Printing shows `Rect(2, 3.5)`. Such an enum is -copied and freed like a struct; it can't be compared with `==` (match on it). Variants of these enums can't have numbers. - --- ## 23. Enums and match @@ -1100,6 +1077,29 @@ match word { handle **every variant** or end with `else` (the compiler says which are missing); any other match needs `else`. In an expression, every arm gives the same type. +### Variants that hold values + +A variant can carry values. Make one with `Shape.Circle(2.0)`, take them out with `match`, which names them: + +```c +enum Shape { + Circle(float) + Rect(float, float) + Point +} + +fn Shape.area() -> float { + return match self { + Circle(r) -> 3.14 * r * r + Rect(w, h) -> w * h + Point -> 0.0 + } +} +``` + +`_` ignores a value. `match` must handle every variant (or end with `else`). Printing shows `Rect(2, 3.5)`. Such an enum is +copied and freed like a struct; it can't be compared with `==` (match on it). Variants of these enums can't have numbers. + ## 24. Generics Write a function or a struct once for any type, with the type as a name in square brackets: diff --git a/docs/pl/dla-inzynierow.md b/docs/pl/dla-inzynierow.md index e8ef597..425c6d0 100644 --- a/docs/pl/dla-inzynierow.md +++ b/docs/pl/dla-inzynierow.md @@ -5,7 +5,7 @@ drzewo AST i LLVM IR *w postaci tekstu*, który `clang` kompiluje i linkuje z ma czytane przez libclang z poziomu samego Fincha i jak ręcznie odtworzono konwencje wywołań C; reguły typów i własności wartości; jak kompilator buduje sam siebie z pliku startowego; i jak działa serwer języka. -Zakładam, że umiesz czytać kod w Finchu (zob. [przewodnik dla techników](dla-technikow.md)) i że widziałeś już LLVM IR. +Zakładam, że umiesz czytać kod w Finchu (zob. [przewodnik dla techników](dla-technikow.md)) i że LLVM IR nie jest ci obcy. > 🇬🇧 English version: [for-engineers.md](../en/for-engineers.md) diff --git a/docs/pl/dla-poczatkujacych.md b/docs/pl/dla-poczatkujacych.md index 5b48cf2..4f79846 100644 --- a/docs/pl/dla-poczatkujacych.md +++ b/docs/pl/dla-poczatkujacych.md @@ -1,8 +1,8 @@ # Finch dla początkujących -**Ten przewodnik jest dla ciebie, jeśli jeszcze nigdy nie pisałeś programu.** -Nie potrzebujesz żadnego doświadczenia ani matematyki ponad to, czego uczy się w podstawówce. -Czytaj powoli, po jednym rozdziale, i przepisuj każdy przykład samodzielnie. Tak zostaje w głowie najlepiej. +**Programowanie to dla ciebie coś zupełnie nowego? Ten przewodnik jest dla ciebie.** +Nie musisz nic umieć: wystarczy matematyka ze szkoły podstawowej. +Czytaj spokojnie, rozdział po rozdziale, i każdy przykład przepisuj sam: tak najlepiej zostaje w głowie. > 🇬🇧 English version: [for-beginners.md](../en/for-beginners.md) @@ -13,7 +13,7 @@ Czytaj powoli, po jednym rozdziale, i przepisuj każdy przykład samodzielnie. T 1. [Co to jest program?](#1-co-to-jest-program) 2. [Przygotowanie Fincha](#2-przygotowanie-fincha) 3. [Twój pierwszy program](#3-twój-pierwszy-program) -4. [Wypisywanie na ekran](#4-wypisywanie-na-ekran) +4. [Wypisywanie na ekranie](#4-wypisywanie-na-ekranie) 5. [Pudełka z nazwami: zmienne](#5-pudełka-z-nazwami-zmienne) 6. [Rodzaje wartości](#6-rodzaje-wartości) 7. [Liczenie](#7-liczenie) @@ -28,36 +28,35 @@ Czytaj powoli, po jednym rozdziale, i przepisuj każdy przykład samodzielnie. T 16. [Kiedy coś pójdzie nie tak](#16-kiedy-coś-pójdzie-nie-tak) 17. [Małe projekty do zrobienia](#17-małe-projekty-do-zrobienia) 18. [Ściąga](#18-ściąga) -19. [Słowniczek](#19-słowniczek) +19. [Słowa, które będziesz słyszeć](#19-słowa-które-będziesz-słyszeć) --- ## 1. Co to jest program? -Komputer jest bardzo szybki, ale nie myśli. Robi dokładnie to, co mu się każe, -krok po kroku. +Komputer jest bardzo szybki, ale nie myśli. Robi dokładnie to, co mu każesz, krok po kroku. -**Program** to spisana lista takich kroków. Trochę jak przepis kuchenny: +**Program** to spisana lista takich kroków. Trochę jak przepis: > 1. Weź dwa jajka. > 2. Wbij je do miski. > 3. Wymieszaj. -**Język programowania** to sposób zapisania takiego przepisu, który komputer rozumie. -**Finch** (po polsku: zięba) jest jednym z takich języków. Zrobiono go tak, żeby był -mały i prosty, więc nauczysz się go szybko. +**Język programowania** to sposób zapisania takiego przepisu tak, żeby komputer go zrozumiał. +**Finch** to jeden z takich języków. Powstał po to, żeby był mały i prosty, więc szybko się go nauczysz. -Komputer czyta program od góry do dołu, linijka po linijce. +Komputer czyta program od góry do dołu, linia po linii. --- ## 2. Przygotowanie Fincha -Finch działa na komputerach z **Linuksem**. Jeśli nauczyciel, rodzic albo znajomy już go -dla ciebie zainstalował, przejdź od razu do następnego rozdziału. +Finch działa na komputerach z **Linuksem**, **macOS** i **Windowsem**. Jeśli nauczyciel, rodzic albo znajomy +już go dla ciebie przygotował, przejdź do następnego rozdziału. (Na Macu i na Windowsie poproś kogoś o skorzystanie +z [przewodnika dla techników](dla-technikow.md#2-instalacja): są tam gotowe wersje Fincha na oba systemy.) -W przeciwnym razie otwórz program **Terminal** (okienko, w którym wpisuje się polecenia) -i wpisz te linijki po kolei, naciskając **Enter** po każdej: +W przeciwnym razie otwórz program **Terminal** (okno, w którym wpisuje się polecenia) i wpisz po kolei te linie, +naciskając po każdej **Enter**: ```sh git clone https://github.com/Nexoniarz/Finch.git @@ -66,24 +65,24 @@ nix-shell bootstrap/build.sh ``` -Jeśli ostatnia linijka skończy się bez słowa `error`, Finch jest gotowy. -(Jeśli komputer nie zna polecenia `nix-shell`, poproś kogoś o skorzystanie z -[przewodnika dla techników](dla-technikow.md#2-instalacja), gdzie opisano inny sposób.) +Jeśli ostatnie polecenie skończy się bez słowa `error`, Finch jest gotowy. +(Jeśli na twoim komputerze nie ma `nix-shell`, poproś kogoś o skorzystanie z +[przewodnika dla techników](dla-technikow.md#2-instalacja). Jest tam opisany inny sposób.) -Żeby sprawdzić, czy działa, wpisz: +Żeby sprawdzić, czy wszystko działa, wpisz: ```sh ./build/finch version ``` -Powinieneś zobaczyć coś w stylu `finch 2.5.0`. +Powinien pojawić się napis w rodzaju `Finch 2.6.0`. --- ## 3. Twój pierwszy program -1. Otwórz dowolny edytor tekstu (na przykład *Edytor tekstu*, *Kate*, *gedit* albo *VS Code*). -2. Wpisz dokładnie to: +1. Otwórz dowolny edytor tekstu (np. *Edytor tekstu*, *Kate*, *gedit* albo *VS Code*). +2. Przepisz dokładnie to: ```c fn main() { @@ -91,11 +90,11 @@ fn main() { } ``` -3. Zapisz plik w folderze `Finch` pod nazwą **`czesc.fch`**. Końcówka `.fch` mówi, że to program w Finchu. +3. Zapisz plik w katalogu `Finch` pod nazwą **`hello.fch`**. Końcówka `.fch` mówi, że to program w Finchu. 4. W Terminalu wpisz: ```sh -./build/finch run czesc.fch +./build/finch run hello.fch ``` Zobaczysz: @@ -104,7 +103,7 @@ Zobaczysz: Cześć! ``` -🎉 **Właśnie napisałeś i uruchomiłeś program.** +🎉 **Właśnie udało ci się napisać i uruchomić program.** ### Co właściwie napisaliśmy? @@ -114,26 +113,26 @@ fn main() { ← „tu zaczyna się główna część mojego programu } ← „tu główna część się kończy” ``` -- `fn main()` to miejsce, od którego **zaczyna się** każdy program w Finchu. Każdy program ma dokładnie jedno `main`. +- `fn main()` to miejsce, w którym **zaczyna się** każdy program w Finchu. Każdy program ma dokładnie jedno `main`. - Nawiasy klamrowe `{` i `}` obejmują wszystko, co należy do `main`, jak dwie okładki książki. - `print(...)` pokazuje coś na ekranie. -- Tekst zawsze piszemy w cudzysłowie: `"Cześć!"`. +- Tekst zawsze stoi w cudzysłowie: `"Cześć!"`. -Spacje przed `print` nie są obowiązkowe, ale dzięki nim od razu widać, co jest w środku -klamer. Zawsze je dodawaj, to dobry nawyk. +Spacje przed `print` nie są obowiązkowe, ale dzięki nim od razu widać, co jest w środku nawiasów. +Zawsze je dodawaj. To dobry nawyk. --- -## 4. Wypisywanie na ekran +## 4. Wypisywanie na ekranie -`print` umie pokazać tekst i liczby. Między kolejne rzeczy wstaw przecinek, a Finch sam doda spację: +`print` potrafi pokazać tekst i liczby. Oddziel je przecinkami, a Finch sam wstawi między nimi spacje: ```c fn main() { print("Mam", 10, "lat") print("2 + 2 =", 2 + 2) print() - print("Linijka wyżej jest pusta") + print("Linia wyżej jest pusta") } ``` @@ -143,10 +142,10 @@ Pokaże: Mam 10 lat 2 + 2 = 4 -Linijka wyżej jest pusta +Linia wyżej jest pusta ``` -Każdy `print` zaczyna nową linijkę. `print()` bez niczego w środku robi pustą linijkę. +Każde `print` zaczyna nową linię. `print()` bez niczego w środku robi pustą linię. ### Notatki dla siebie: komentarze @@ -163,8 +162,8 @@ fn main() { ## 5. Pudełka z nazwami: zmienne -Wyobraź sobie pudełko z naklejoną etykietą. Możesz coś do niego włożyć, a potem sprawdzić, -co jest w środku, czytając etykietę. W programowaniu takie pudełko nazywa się **zmienną**. +Wyobraź sobie pudełko z naklejoną etykietą. Możesz coś do niego włożyć, a później sprawdzić, co jest w środku, +czytając etykietę. W programowaniu takie pudełko nazywa się **zmienną**. ```c fn main() { @@ -173,9 +172,9 @@ fn main() { } ``` -`wiek := 10` znaczy: *„zrób nowe pudełko, nazwij je `wiek` i włóż do niego 10”*. +`wiek := 10` znaczy: *„zrób nowe pudełko, nazwij je `wiek` i włóż do niego 10”.* -Później możesz zmienić zawartość pudełka, używając samego `=`: +Później możesz zmienić zawartość pudełka, pisząc samo `=`: ```c fn main() { @@ -196,42 +195,39 @@ Punkty: 5 Punkty: 6 ``` -`punkty = punkty + 1` czytamy tak: *„weź to, co jest w `punkty`, dodaj 1 i włóż wynik z powrotem do `punkty`”*. -To samo można zapisać krócej: `punkty += 1`. +`punkty = punkty + 1` czyta się tak: *„weź to, co jest w `punkty`, dodaj 1 i włóż wynik z powrotem do `punkty`”.* +To samo można napisać krócej: `punkty += 1`. **Zapamiętaj:** -- `:=` robi **nowe** pudełko. Używasz tego tylko raz dla danej nazwy. +- `:=` robi **nowe** pudełko. Dla każdej nazwy używasz tego tylko raz. - `=` wkłada nową wartość do pudełka, **które już istnieje**. ### Nazywanie pudełek -Nazwy mogą zawierać angielskie litery `a`–`z` i `A`–`Z`, cyfry i znak `_`, ale nie mogą zaczynać się od cyfry: -`wiek`, `suma_punktow`, `gracz2` są w porządku. `2gracz` już nie. - -⚠️ **Bez polskich liter w nazwach!** `ą`, `ę`, `ł`, `ż` i inne mogą być tylko w tekście w cudzysłowie. -Pisz `zolw`, a nie `żółw`. Natomiast `print("Żółw")` działa bez problemu. - +W nazwach można używać liter od `a` do `z` i od `A` do `Z`, cyfr i znaku `_`, ale nazwa nie może zaczynać się od cyfry: +`wiek`, `suma_punktow`, `gracz2` są w porządku, a `2gracz` nie. +Polskich liter (`ą`, `ę`, `ż`…) nie wolno używać w nazwach, tylko w tekstach w cudzysłowie: `print("Łódź")` jest w porządku. Wybieraj nazwy, które mówią, co jest w środku: `cena` jest lepsza niż `c`. --- ## 6. Rodzaje wartości -Finch pilnuje, *jakiego rodzaju* rzecz jest w każdym pudełku. Ten rodzaj nazywa się **typem**. +Finch pilnuje, jaki *rodzaj* rzeczy leży w każdym pudełku. Taki rodzaj to **typ**. Najczęściej będziesz używać tych: -| Typ | Co przechowuje | Przykłady | -|---------|---------------------------|--------------------------| -| `int` | liczby całkowite | `0`, `42`, `-7` | -| `float` | liczby z przecinkiem | `3.14`, `0.5`, `-2.0` | -| `str` | tekst | `"Cześć"`, `"Ola"` | -| `bool` | tak albo nie | `true` (prawda), `false` (fałsz) | -| `char` | jeden znak | `'A'`, `'z'`, `'?'` | +| Typ | Co przechowuje | Przykłady | +|---------|-----------------------------|--------------------------| +| `int` | liczby całkowite | `0`, `42`, `-7` | +| `float` | liczby z przecinkiem | `3.14`, `0.5`, `-2.0` | +| `str` | tekst | `"Cześć"`, `"Ola"` | +| `bool` | tak albo nie | `true`, `false` | +| `char` | jedną literę albo znak | `'A'`, `'z'`, `'?'` | -⚠️ W liczbach zamiast przecinka piszemy **kropkę**: `3.14`, a nie `3,14`. Tak jest w prawie wszystkich językach programowania. +Uwaga: w programach część ułamkową oddziela się **kropką**, nie przecinkiem: `3.14`, a nie `3,14`. -Kiedy używasz `:=`, Finch sam rozpoznaje typ: +Przy `:=` Finch sam rozpoznaje typ: ```c imie := "Ola" // str @@ -240,7 +236,7 @@ wzrost := 1.42 // float lubi_koty := true // bool ``` -Typ możesz też napisać sam, przed nazwą: +Typ można też napisać samemu, przed nazwą: ```c int wiek = 10 @@ -249,24 +245,23 @@ str imie = "Ola" Oba sposoby robią to samo. Wybierz ten, który ci bardziej pasuje. -**Pudełko nie zmienia typu.** Jeśli w `wiek` są liczby całkowite, nie włożysz tam potem tekstu. -Finch zatrzyma się i powie o tym, co pomaga wcześnie wyłapywać pomyłki. +**Pudełko trzyma się swojego typu.** Jeśli w `wiek` są liczby całkowite, nie włożysz tam potem tekstu. +Finch się zatrzyma i ci o tym powie, a to pomaga wcześnie wyłapywać pomyłki. --- ## 7. Liczenie -| Znak | Co robi | Przykład | Wynik | -|------|-----------------------------|----------|-------| -| `+` | dodawanie | `7 + 2` | `9` | -| `-` | odejmowanie | `7 - 2` | `5` | -| `*` | mnożenie | `7 * 2` | `14` | -| `/` | dzielenie | `7 / 2` | `3` | -| `%` | reszta z dzielenia | `7 % 2` | `1` | +| Znak | Znaczenie | Przykład | Wynik | +|------|--------------------------------|----------|--------| +| `+` | dodawanie | `7 + 2` | `9` | +| `-` | odejmowanie | `7 - 2` | `5` | +| `*` | mnożenie | `7 * 2` | `14` | +| `/` | dzielenie | `7 / 2` | `3` | +| `%` | reszta z dzielenia | `7 % 2` | `1` | -**Uwaga na dzielenie liczb całkowitych:** `7 / 2` daje `3`, a nie `3.5`. Obie liczby są -całkowite, więc wynik też jest całkowity (część po kropce jest odrzucana). -Jeśli chcesz dostać `3.5`, użyj liczby z kropką: `7.0 / 2`. +**Uważaj przy dzieleniu liczb całkowitych:** `7 / 2` daje `3`, a nie `3.5`, bo obie liczby są całkowite, więc wynik +też jest całkowity (część po kropce znika). Jeśli chcesz `3.5`, użyj liczby z kropką: `7.0 / 2`. Mnożenie i dzielenie wykonują się przed dodawaniem i odejmowaniem, tak jak w szkole. Nawiasy zmieniają kolejność: @@ -276,28 +271,28 @@ print(2 + 3 * 4) // 14 print((2 + 3) * 4) // 20 ``` -`%` przydaje się do sprawdzania, czy liczba jest parzysta. Liczba parzysta dzielona przez 2 -nie daje reszty, więc `n % 2` wynosi `0`. +`%` przydaje się do sprawdzania, czy liczba jest parzysta: liczba parzysta dzielona przez 2 nie daje reszty, +więc `n % 2` wynosi `0`. -**Nie wolno dzielić przez zero.** Jeśli program spróbuje, zatrzyma się i powie, w której linijce to się stało. +**Nie wolno dzielić przez zero.** Jeśli program spróbuje, zatrzyma się i powie, w której linii to się stało. --- ## 8. Podejmowanie decyzji: if -Program może wybierać, co zrobić. `if` (czyt. „if”, po polsku „jeżeli”) znaczy *„zrób to tylko wtedy, gdy coś jest prawdą”*. +Program może wybierać, co zrobić. `if` znaczy *„zrób to tylko wtedy, gdy coś jest prawdą”*. ```c fn main() { temperatura := 25 if temperatura > 20 { - print("Jest ciepło, załóż koszulkę") + print("Jest ciepło, włóż koszulkę") } } ``` -Dodaj `else` („w przeciwnym razie”): +Dopisz `else`, żeby powiedzieć *„a jeśli nie, zrób to”*: ```c if temperatura > 20 { @@ -307,12 +302,11 @@ if temperatura > 20 { } ``` -A `else if` dodaje kolejne możliwości. Finch sprawdza je od góry i wykonuje tylko -**pierwszą** prawdziwą: +A `else if` daje więcej możliwości. Finch sprawdza je od góry do dołu i wykonuje tylko **pierwszą**, która jest prawdą: ```c if temperatura > 25 { - print("Gorąco!") + print("Upał!") } else if temperatura > 15 { print("Przyjemnie") } else { @@ -328,18 +322,18 @@ if temperatura > 25 { | `!=` | nie jest równe | | `<` | jest mniejsze niż | | `>` | jest większe niż | -| `<=` | jest mniejsze lub równe | -| `>=` | jest większe lub równe | +| `<=` | jest mniejsze albo równe | +| `>=` | jest większe albo równe | ⚠️ **Jedno `=` wkłada wartość do pudełka. Dwa `==` pytają „czy to jest równe?”.** -Tekst też można porównywać: `if imie == "Ola" { ... }`. +Porównywać można też teksty: `if imie == "Ola" { ... }`. ### Łączenie pytań - `&&` znaczy **i**: oba warunki muszą być prawdziwe. `if wiek > 6 && wiek < 12 { ... }` -- `||` znaczy **lub**: wystarczy, że jeden jest prawdziwy. `if dzien == "sob" || dzien == "nd" { ... }` -- `!` znaczy **nie**: odwraca prawdę i fałsz. `if !pada { ... }` +- `||` znaczy **lub**: przynajmniej jeden musi być prawdziwy. `if dzien == "sob" || dzien == "niedz" { ... }` +- `!` znaczy **nie**: zamienia prawdę w fałsz i odwrotnie. `if !pada { ... }` --- @@ -365,7 +359,7 @@ Liczba 4 Liczba 5 ``` -`for i in 1..6` znaczy: *„licz za pomocą `i` od 1 do 6, ale **bez** 6”*. +`for i in 1..6` znaczy: *„licz w `i` od 1 do 6, ale **bez** 6”*. Czyli `0..10` daje dziesięć liczb: 0, 1, 2 … 9. ### Powtarzanie, dopóki coś jest prawdą: `while` @@ -388,8 +382,8 @@ fn main() { Start! ``` -⚠️ Upewnij się, że pętla może się skończyć. Gdyby `licznik` nigdy nie malał, pętla kręciłaby się -w nieskończoność. Jeśli tak się stanie, naciśnij w Terminalu **Ctrl + C**, żeby ją zatrzymać. +⚠️ Upewnij się, że pętla może się skończyć. Gdyby `licznik` nigdy nie malał, pętla kręciłaby się bez końca. +Jeśli tak się stanie, naciśnij w Terminalu **Ctrl + C**, żeby ją zatrzymać. ### Wcześniejsze wyjście i pomijanie @@ -399,9 +393,9 @@ w nieskończoność. Jeśli tak się stanie, naciśnij w Terminalu **Ctrl + C**, ```c for i in 1..100 { if i == 5 { - break // stop, gdy dojdziemy do 5 + break // zatrzymaj się, gdy dojdziemy do 5 } - print(i) // pokaże 1 2 3 4 + print(i) // wypisze 1 2 3 4 } ``` @@ -409,7 +403,7 @@ for i in 1..100 { ## 10. Własne polecenia: funkcje -Kiedy robisz to samo w wielu miejscach, nadaj temu nazwę. Taki nazwany kawałek kodu to **funkcja**. +Kiedy robisz to samo w wielu miejscach, nadaj temu nazwę. Taki nazwany blok to **funkcja**. Jedną już znasz: `main`. ```c @@ -428,12 +422,12 @@ Cześć, Ola Cześć, Tomek ``` -`str imie` w nawiasie znaczy: *„ta funkcja potrzebuje jednego tekstu, a w środku nazywamy go `imie`”*. -To, co wpiszesz w nawias przy wywołaniu, zostanie przekazane do środka. +`str imie` w nawiasach znaczy: *„ta funkcja potrzebuje jednego tekstu i w środku będziemy go nazywać `imie`”.* +To, co wpiszesz w nawiasach przy jej użyciu, trafia do środka. -### Funkcje, które oddają odpowiedź +### Funkcje, które dają odpowiedź -Napisz `->` i typ odpowiedzi, a odpowiedź oddaj słowem `return`: +Napisz `->` i typ odpowiedzi, a odpowiedź oddaj przez `return`: ```c fn dodaj(int a, int b) -> int { @@ -446,14 +440,14 @@ fn main() { } ``` -Nie ma znaczenia, gdzie w pliku umieścisz swoje funkcje, nad czy pod `main`. +Nie ma znaczenia, gdzie w pliku umieścisz swoje funkcje, nad `main` czy pod nim. --- ## 11. Listy: wiele wartości w jednym pudełku -Czasem jedno pudełko nie wystarczy. Na liście zakupów jest wiele rzeczy. -**Lista** (programiści mówią też **tablica**) trzyma wiele wartości po kolei: +Czasem jedno pudełko nie wystarcza. Na liście zakupów jest wiele rzeczy. +**Lista** (programiści mówią też **tablica**) przechowuje wiele wartości po kolei: ```c fn main() { @@ -470,34 +464,34 @@ Mam 3 owoce Pierwszy to jabłko ``` -- Nawiasy kwadratowe `[ ]` tworzą listę. Wartości oddzielamy przecinkami. +- Nawiasy kwadratowe `[ ]` tworzą listę. Wartości oddziela się przecinkami. - `.len` mówi, ile rzeczy jest na liście. -- `owoce[0]` to **pierwsza** rzecz. Liczymy od **0**, więc `owoce[1]` to druga, a `owoce[2]` trzecia. +- `owoce[0]` to **pierwsza** rzecz. Liczy się od **0**, więc `owoce[1]` to druga, a `owoce[2]` trzecia. Jeśli zapytasz o miejsce, którego nie ma, np. `owoce[10]`, program się zatrzyma i powie: -`index 10 is out of range (the length is 3)`, czyli „indeks 10 jest poza zakresem (długość to 3)”. +`index 10 is out of range (the length is 3)` (indeks 10 jest poza listą, która ma długość 3). ### Dodawanie i zabieranie ```c fn main() { []int wyniki // pusta lista liczb całkowitych - wyniki.push(10) // dołóż 10 na koniec + wyniki.push(10) // dodaj 10 na końcu wyniki.push(25) wyniki.push(7) print(wyniki) // [10, 25, 7] - ostatni := wyniki.pop() // zabierz ostatni + ostatni := wyniki.pop() // zdejmij ostatni print(ostatni, wyniki) // 7 [10, 25] - wyniki.sort() // posortuj + wyniki.sort() print(wyniki) } ``` ### Przechodzenie po liście -`for ... in` odwiedza każdą wartość po kolei: +`for ... in` odwiedza po kolei każdą wartość: ```c fn main() { @@ -509,8 +503,8 @@ fn main() { } ``` -Jeśli chcesz też wiedzieć, *w którym miejscu* listy jesteś, daj `for` dwie nazwy: pierwsza liczy -0, 1, 2…, druga to wartość: +Jeśli chcesz też wiedzieć, *w którym miejscu* listy jesteś, podaj w `for` dwie nazwy: pierwsza liczy 0, 1, 2…, +a druga to wartość: ```c fn main() { @@ -526,21 +520,20 @@ fn main() { 3 wiśnia ``` -Listy umieją więcej: `contains` (czy zawiera), `find` (znajdź), `insert` (wstaw), `remove` (usuń), -`sort` (sortuj), `reverse` (odwróć), `join` (połącz). Wszystkie opisuje [przewodnik dla techników](dla-technikow.md#10-tablice-i-mapy). +Listy potrafią więcej: `contains`, `find`, `insert`, `remove`, `sort`, `reverse`, `join`. +Wszystkie są opisane w [przewodniku dla techników](dla-technikow.md#10-tablice-i-mapy). ### Szukanie po nazwie: mapy -Lista znajduje rzeczy po miejscu: `owoce[0]`. Czasem chcesz znaleźć coś po **nazwie**, tak jak -słowo w słowniku albo numer w książce telefonicznej. Do tego służy **mapa** (`map`): każdy -**klucz** (nazwa) ma swoją **wartość**: +Lista znajduje rzeczy po miejscu: `owoce[0]`. Czasem jednak chcesz szukać po **nazwie**, tak jak szuka się słowa +w słowniku albo numeru w książce telefonicznej. Do tego służy **mapa**: każdy **klucz** (nazwa) ma swoją **wartość**: ```c fn main() { telefon := ["Ola": "555-1234", "Jan": "555-9876"] print(telefon["Ola"]) // 555-1234 - telefon["Ewa"] = "555-0000" // nowa osoba + telefon["Ewa"] = "555-0000" // dodaj nowe imię print(telefon.len) // 3 if telefon.has("Tomek") { @@ -549,26 +542,26 @@ fn main() { print("nie ma Tomka") } - for imie, numer in telefon { // każde imię z jego numerem + for imie, numer in telefon { // każde imię razem z numerem print(imie, numer) } } ``` -- `["klucz": wartość, ...]` tworzy mapę. Klucze to zwykle tekst, ale mogą być też liczby. +- `["klucz": wartość, ...]` tworzy mapę. Kluczami są często teksty, ale mogą być też liczby. - `telefon["Ola"]` daje wartość dla klucza. Pytanie o klucz, którego nie ma, zatrzymuje program, - więc najpierw sprawdź `telefon.has("Tomek")` (czy ma) albo użyj `telefon.get("Tomek", "nie wiem")`. -- `for imie, numer in telefon` odwiedza każdy klucz z wartością, w kolejności dodawania. + więc najpierw sprawdź `telefon.has("Tomek")` albo użyj `telefon.get("Tomek", "nieznany")`. +- `for imie, numer in telefon` odwiedza każdy klucz z jego wartością, w kolejności, w jakiej je dodano. Mapy świetnie nadają się do **liczenia**. Nowy klucz zaczyna od 0, więc `+= 1` po prostu działa: ```c fn main() { - map[str]int glosy // pusta mapa: klucze to tekst, wartości to liczby + map[str]int glosy // pusta mapa: klucze to teksty, wartości to liczby całkowite for g in ["kot", "pies", "kot", "kot", "rybka"] { glosy[g] += 1 } - print(glosy) // {"kot": 3, "pies": 1, "rybka": 1} + print(glosy) // {"kot": 3, "pies": 1, "rybka": 1} } ``` @@ -576,7 +569,7 @@ fn main() { ## 12. Zabawa tekstem -Teksty sklejasz znakiem `+`: +Teksty można sklejać przez `+`: ```c fn main() { @@ -600,16 +593,16 @@ fn main() { slowo := "Finch" print(slowo.len) // 5 liter print(slowo[0]) // F (pierwsza litera) - print(slowo.upper()) // FINCH (wielkimi literami) - print(slowo.lower()) // finch (małymi literami) - print(slowo.contains("in")) // true (czy zawiera "in") + print(slowo.upper()) // FINCH + print(slowo.lower()) // finch + print(slowo.contains("in")) // true for litera in slowo { - print(litera) // F, i, n, c, h, każda w osobnej linijce + print(litera) // F, i, n, c, h, każda w osobnej linii } } ``` -⚠️ `.len` liczy bajty, a polskie litery (ą, ę, ż…) zajmują po 2 bajty. `"żółw".len` to 7, a nie 4. +(`.len` liczy bajty, a polskie litery zajmują po dwa, więc `"żółw".len` to 7, a nie 4.) --- @@ -634,15 +627,15 @@ fn main() { } ``` -Jeśli ktoś wpisze coś, co nie jest liczbą, program zatrzyma się z komunikatem -w stylu `can't turn "abc" into int` („nie da się zamienić "abc" na int”). +Jeśli ktoś wpisze coś, co nie jest liczbą, program się zatrzyma z komunikatem +w rodzaju `can't turn "abc" into int` (nie da się zamienić "abc" na liczbę). --- ## 14. Własne rodzaje wartości: struktury -Wyobraź sobie kartę w bibliotece: każda karta ma tytuł, autora i rok. -**Struktura** (`struct`) pozwala zrobić własny rodzaj wartości z nazwanymi częściami: +Pomyśl o karcie w bibliotece: każda ma tytuł, autora i rok wydania. +**Struktura** pozwala stworzyć własny rodzaj wartości z nazwanymi częściami: ```c struct Ksiazka { @@ -664,11 +657,11 @@ Hobbit napisał Tolkien Ksiazka(tytul: "Hobbit", autor: "Tolkien", rok: 1938) ``` -- `struct Ksiazka { ... }` opisuje, co ma każda książka. Pisz to **poza** `main`. +- `struct Ksiazka { ... }` opisuje, co ma każda książka. Umieść to **poza** `main`. - `Ksiazka(tytul: ..., autor: ..., rok: ...)` tworzy jedną książkę. -- `k.tytul` odczytuje jedną część, a `k.rok = 1938` ją zmienia. +- `k.tytul` odczytuje jedną część. `k.rok = 1938` ją zmienia. -Częściom możesz nadać wartość początkową. Wtedy nie trzeba ich podawać: +Częściom można nadać wartość początkową. Wtedy nie trzeba ich wypełniać: ```c struct Gracz { @@ -682,12 +675,12 @@ fn main() { } ``` -Możesz też trzymać wiele struktur na liście: `[]Ksiazka polka`, a potem `polka.push(k)`. +Wiele takich wartości można trzymać na liście: `[]Ksiazka polka`, a potem `polka.push(k)`. -### Polecenia twojej struktury: metody +### Polecenia dla struktury: metody -Struktura może mieć własne polecenia (**metody**). Napisz nazwę struktury, kropkę i nazwę polecenia. -W środku `self` (ang. „sam”) to struktura, na której użyto polecenia: +Struktura może mieć własne polecenia. Napisz nazwę struktury, kropkę i nazwę polecenia. +W środku `self` to struktura, na której polecenie użyto: ```c struct Gracz { @@ -697,7 +690,7 @@ struct Gracz { fn Gracz.trafiony() { self.zycia -= 1 - print(self.imie, "ma jeszcze", self.zycia, "życia") + print(self.imie, "- pozostałe życia:", self.zycia) } fn Gracz.zyje() -> bool { @@ -706,8 +699,8 @@ fn Gracz.zyje() -> bool { fn main() { g := Gracz(imie: "Ola") - g.trafiony() // Ola ma jeszcze 2 życia - g.trafiony() // Ola ma jeszcze 1 życia + g.trafiony() // Ola - pozostałe życia: 2 + g.trafiony() // Ola - pozostałe życia: 1 print(g.zyje()) // true } ``` @@ -731,14 +724,14 @@ fn main() { - `read_file(nazwa)` oddaje tekst z pliku. - `file_exists(nazwa)` mówi, czy plik istnieje (`true` albo `false`). -Plik pojawi się w folderze, w którym uruchomiłeś program. +Plik pojawi się w katalogu, w którym uruchomiono program. --- ## 16. Kiedy coś pójdzie nie tak -Każdy się myli przy programowaniu, nawet eksperci, codziennie. -Finch stara się tłumaczyć, co poszło nie tak, prostymi słowami (na razie po angielsku). Przykład: +Każdy, kto programuje, robi błędy, nawet eksperci, codziennie. +Finch bardzo się stara wyjaśnić prostymi słowami, co poszło nie tak. Przykład: ```c fn main() { @@ -748,68 +741,67 @@ fn main() { ``` ``` -czesc.fch:3:11: error: there is no variable named 'wiekk' +hello.fch:3:11: error: there is no variable named 'wiekk' 3 | print(wiekk) | ^ ``` Jak to czytać: -- `czesc.fch` to plik, -- `3` to numer **linijki**, -- `11` to który znak w linijce (**kolumna**), -- potem wyjaśnienie, a pod nim ta linijka ze strzałką `^` wskazującą problem. +- `hello.fch` to plik, +- `3` to numer **linii**, +- `11` mówi, jak daleko w linii (to **kolumna**), +- dalej jest wyjaśnienie, a pod nim ta linia ze znakiem `^`, który wskazuje problem. -Tutaj po prostu źle napisaliśmy `wiek`. Komunikat znaczy: „nie ma zmiennej o nazwie 'wiekk'”. +Komunikaty są po angielsku; tutaj znaczy to „nie ma zmiennej o nazwie 'wiekk'”. Po prostu literówka w `wiek`. -### Częste błędy +### Częste pomyłki | Finch mówi | Co to znaczy | Jak naprawić | |---|---|---| -| `there is no variable named 'x'` | Nie ma zmiennej `x`: literówka albo zapomniałeś ją zrobić | Sprawdź pisownię. Utwórz ją przez `x := ...` | -| `a variable named 'x' already exists here` | Użyłeś `:=` dwa razy dla tej samej nazwy | Za drugim razem użyj `=` | +| `there is no variable named 'x'` | Literówka w nazwie albo takie pudełko nigdy nie powstało | Sprawdź pisownię. Utwórz je przez `x := ...` | +| `a variable named 'x' already exists here` | Użyto `:=` dwa razy dla tej samej nazwy | Za drugim razem napisz `=` | | `'x' must be int, but this is str` | Zły rodzaj wartości dla tego pudełka | Włóż wartość właściwego rodzaju | -| `put each statement on its own line` | Dwa polecenia w jednej linijce | Wciśnij Enter między nimi | +| `put each statement on its own line` | Dwa polecenia w jednej linii | Rozdziel je Enterem | | `this '{' is never closed` | Brakuje `}` | Dopisz `}` | -| `a condition must be bool` | `if` potrzebuje pytania tak/nie | Porównaj coś: `if x > 0` | -| `unexpected character 'ż'` | Polska litera w nazwie | Zmień na zwykłą literę: `zolw` | -| `runtime error: division by zero` | Program podzielił przez 0 w trakcie działania | Sprawdź liczbę przed dzieleniem | -| `index 5 is out of range (the length is 3)` | Zapytałeś listę o miejsce, którego nie ma | Pamiętaj, że liczymy od 0; sprawdź `.len` | -| `can't turn "abc" into int` | `int(...)` dostał tekst, który nie jest liczbą | Sprawdź, co wpisał użytkownik | -| `can't use '+' on str and int` | Tekst plus liczba | Zamień liczbę na tekst przez `str(...)` | -| `'f' can fail, so say what happens then` | Użyłeś polecenia, które może się nie udać, i nie napisałeś, co wtedy | Dopisz `or ...` (niżej) | +| `a condition must be bool` | `if` potrzebuje pytania typu tak/nie | Porównaj coś: `if x > 0` | +| `runtime error: division by zero` | W trakcie działania programu nastąpiło dzielenie przez 0 | Sprawdź liczbę przed dzieleniem | +| `index 5 is out of range (the length is 3)` | Pytanie listy o miejsce, którego nie ma | Pamiętaj, że liczy się od 0; sprawdź `.len` | +| `can't turn "abc" into int` | `int(...)` dostało tekst, który nie jest liczbą | Sprawdź, co wpisał użytkownik | +| `can't use '+' on str and int` | Tekst plus liczba | Użyj `str(...)` na liczbie | +| `'f' can fail, so say what happens then` | Użyto polecenia, które może się nie udać, bez powiedzenia, co wtedy zrobić | Dopisz `or ...` (zob. niżej) | ### Rzeczy, które mogą się nie udać: `or` i `try` -Niektóre rzeczy mogą pójść źle *w trakcie działania programu*: ktoś wpisze `abc` zamiast liczby -albo pliku nie ma. Zamiast zatrzymywać program, możesz powiedzieć, co wtedy zrobić, słowem **`or`** („albo”): +Niektóre rzeczy mogą się nie udać *w trakcie działania programu*: ktoś wpisze `abc` tam, gdzie miała być liczba, +albo nie ma pliku. Zamiast zatrzymywać program, możesz przez **`or`** powiedzieć, co wtedy zrobić: ```c fn main() { - wiek := int(input("Ile masz lat? ")) or 0 // to nie liczba? weź 0 + wiek := int(input("Ile masz lat? ")) or 0 // to nie liczba? weź 0 print("Za rok będziesz mieć", wiek + 1) - notatka := read_file("notatka.txt") or "(jeszcze brak notatki)" + notatka := read_file("notatka.txt") or "(jeszcze nie ma notatki)" print(notatka) } ``` -Po `or` może też być blok. W środku `err` to komunikat, co poszło nie tak: +Po `or` może też stać blok. W jego środku `err` to komunikat, który mówi, co poszło nie tak: ```c fn main() { while true { - n := int(input("Podaj liczbę: ")) or { + n := int(input("Wybierz liczbę: ")) or { print("To nie jest liczba, spróbuj jeszcze raz") continue } - print("Wybrałeś", n) + print("Wybrana liczba:", n) break } } ``` -Twoje własne funkcje też mogą się nie udać. Dopisz `!` po typie wyniku i użyj `return error("...")`: +Twoje własne funkcje też mogą się nie udać. Postaw `!` po typie wyniku i użyj `return error("...")`: ```c fn bezpieczne_dzielenie(int a, int b) -> int! { @@ -825,15 +817,15 @@ fn main() { } ``` -Finch nie pozwoli ci zapomnieć: wywołanie `bezpieczne_dzielenie(10, 0)` bez `or` to błąd, który -przypomina, żeby to obsłużyć. (Można też przekazać problem dalej przez `try`; wyjaśnia to +Finch nie pozwoli o tym zapomnieć: wywołanie `bezpieczne_dzielenie(10, 0)` bez `or` to błąd, który każe ci to obsłużyć. +(Problem można też przekazać dalej przez `try`; wyjaśnia to [przewodnik dla techników](dla-technikow.md#błędy-jako-wartości).) --- ## 17. Małe projekty do zrobienia -Spróbuj najpierw napisać każdy sam. Do rozwiązania zajrzyj dopiero, gdy utkniesz. +Spróbuj najpierw napisać każdy sam. Do rozwiązania zajrzyj dopiero wtedy, gdy utkniesz. ### Projekt 1: Tabliczka mnożenia @@ -852,7 +844,7 @@ fn main() { ### Projekt 2: Parzysta czy nieparzysta -Dla każdej liczby od 1 do 10 napisz, czy jest parzysta, czy nieparzysta. +Dla każdej liczby od 1 do 10 pokaż, czy jest parzysta, czy nieparzysta.
Rozwiązanie @@ -871,7 +863,7 @@ fn main() { ### Projekt 3: Stopnie Celsjusza na Fahrenheita -Pokaż tabelkę od 0 °C do 100 °C co 10 stopni. Wzór: `F = C * 9 / 5 + 32`. +Pokaż tabelę od 0 °C do 100 °C co 10 stopni. Wzór: `F = C * 9 / 5 + 32`.
Rozwiązanie @@ -893,8 +885,8 @@ fn main() { ### Projekt 4: Fizz Buzz -Policz od 1 do 20. Dla liczb podzielnych przez 3 napisz `Fizz`, przez 5 napisz `Buzz`, -przez obie naraz napisz `FizzBuzz`, a w pozostałych przypadkach samą liczbę. +Policz od 1 do 20. Dla liczb podzielnych przez 3 powiedz `Fizz`, przez 5 `Buzz`, +przez obie `FizzBuzz`, a w pozostałych przypadkach samą liczbę.
Rozwiązanie @@ -917,7 +909,7 @@ fn main() { ### Projekt 5: Największa z trzech -Napisz funkcję `najwieksza(int a, int b, int c) -> int`, która oddaje największą z trzech liczb. +Napisz funkcję `najwieksza(int a, int b, int c) -> int`, która zwraca największą liczbę.
Rozwiązanie @@ -940,7 +932,8 @@ fn main() { ### Projekt 6: Zgadnij liczbę -Komputer ma tajną liczbę. Gracz zgaduje, aż trafi, a komputer za każdym razem mówi „więcej” albo „mniej”. +Komputer wybiera tajną liczbę. Gracz zgaduje, aż trafi, a komputer za każdym razem +mówi „więcej” albo „mniej”.
Rozwiązanie @@ -956,7 +949,7 @@ fn main() { } else if strzal > tajna { print("Mniej!") } else { - print("Tak! Potrzebowałeś", proby, "prób") + print("Tak! Liczba prób:", proby) return } } @@ -968,7 +961,7 @@ fn main() { ### Projekt 7: Średnia ocen -Zapytaj o 3 oceny, zapisz je na liście, a potem pokaż listę i średnią. +Zapytaj o 3 oceny, zapisz je na liście i pokaż listę oraz średnią.
Rozwiązanie @@ -998,9 +991,9 @@ fn main() { fn main() { // tu zaczyna się program print("tekst", 42) // pokaż coś - x := 5 // nowe pudełko (typ zgadnięty) + x := 5 // nowe pudełko (typ rozpoznany sam) int y = 10 // nowe pudełko (typ podany) - x = 7 // zmień zawartość + x = 7 // zmiana zawartości pudełka x += 1 // dodaj 1 do x if x > 5 { // decyzja @@ -1020,72 +1013,49 @@ fn main() { // tu zaczyna się program } } -fn dodaj(int a, int b) -> int { // własne polecenie +fn dodaj(int a, int b) -> int { // twoje własne polecenie return a + b } -struct Zwierzak { // własny rodzaj wartości +struct Zwierzak { // twój własny rodzaj wartości str imie int wiek = 1 } -// listy: liczby := [1, 2, 3] liczby.push(4) liczby[0] liczby.len for n in liczby { } for i, n in liczby { } -// mapy: wiek := ["Ola": 9] wiek["Jan"] = 10 wiek["Ola"] wiek.has("Tomek") for imie, lata in wiek { } -// tekst: "a" + "b" str(42) int("42") s.len s.upper() s.contains("x") +// listy: liczby := [1, 2, 3] liczby.push(4) liczby[0] liczby.len for n in liczby { } for i, n in liczby { } +// mapy: wiek := ["Ola": 9] wiek["Jan"] = 10 wiek["Ola"] wiek.has("Tomek") for imie, w in wiek { } +// tekst: "a" + "b" str(42) int("42") s.len s.upper() s.contains("x") // pytanie: imie := input("Imię? ") -// pliki: write_file("p.txt", tekst) read_file("p.txt") -// metody: fn Zwierzak.urosnij() { self.wiek += 1 } z.urosnij() +// pliki: write_file("p.txt", tekst) read_file("p.txt") +// metody: fn Zwierzak.urosnij() { self.wiek += 1 } z.urosnij() // porażki: n := int(tekst) or 0 x := read_file("p.txt") or { print(err) ... } ``` --- -## 19. Słowniczek +## 19. Słowa, które będziesz słyszeć | Słowo | Znaczenie | |---|---| | **program** | Lista kroków dla komputera | | **kod** | Tekst programu | | **uruchomić** | Kazać komputerowi wykonać kroki | -| **kompilować** | Przetłumaczyć kod na coś, co komputer umie uruchomić. `finch run` robi to za ciebie | -| **zmienna** | Pudełko z nazwą, w którym jest wartość | +| **skompilować** | Przetłumaczyć kod na coś, co komputer potrafi uruchomić. `finch run` robi to za ciebie | +| **zmienna** | Pudełko z nazwą, w którym leży wartość | | **typ** | Rodzaj wartości: liczba, tekst, tak/nie… | | **funkcja** | Nazwana grupa kroków, której można używać wiele razy | | **pętla** | Kroki, które się powtarzają | -| **warunek** | Pytanie tak/nie, np. `x > 5` | +| **warunek** | Pytanie typu tak/nie, np. `x > 5` | | **lista / tablica** | Wiele wartości po kolei, np. `[1, 2, 3]` | -| **indeks** | Pozycja wartości na liście, liczona od 0 | -| **struktura** (struct) | Własny rodzaj wartości złożony z nazwanych części | +| **indeks** | Miejsce wartości na liście, liczone od 0 | +| **struktura** | Twój własny rodzaj wartości z nazwanych części | | **metoda** | Polecenie należące do struktury, np. `g.trafiony()` | -| **mapa** (map) | Wartości znajdowane po kluczu (nazwie), jak w słowniku | -| **wejście** (input) | To, co użytkownik wpisuje do programu | -| **błąd** (error) | Komunikat, że coś jest nie tak, i gdzie | +| **mapa** | Wartości wyszukiwane po kluczu (nazwie), jak w słowniku | +| **wejście** (ang. *input*) | To, co ktoś wpisuje do programu | +| **błąd** (ang. *error*) | Komunikat, że coś jest nie tak, i gdzie | | **bug** | Pomyłka w programie | -### Słówka po angielsku, które spotkasz w Finchu - -| Słowo | Po polsku | -|---|---| -| `print` | wypisz | -| `if` / `else` | jeżeli / w przeciwnym razie | -| `for` … `in` | dla … w | -| `while` | dopóki | -| `break` / `continue` | przerwij / kontynuuj | -| `return` | zwróć (oddaj) | -| `true` / `false` | prawda / fałsz | -| `fn` (od *function*) | funkcja | -| `struct` | struktura | -| `push` / `pop` | dołóż / zabierz | -| `input` | wejście (pytanie) | -| `len` (od *length*) | długość | -| `error` | błąd | -| `or` | albo (co zrobić, gdy się nie uda) | -| `try` | spróbuj (a jak się nie uda, przekaż błąd dalej) | -| `self` | sam (struktura, na której użyto metody) | -| `map` | mapa (słownik) | -| `has` / `get` | ma / weź | - --- -**Co dalej?** Kiedy to wszystko wyda ci się łatwe, przejdź do +**Co dalej?** Kiedy to wszystko będzie już łatwe, przejdź do [przewodnika dla techników](dla-technikow.md). Opisuje każdą część Fincha. diff --git a/docs/pl/dla-technikow.md b/docs/pl/dla-technikow.md index 4ebceba..a925f03 100644 --- a/docs/pl/dla-technikow.md +++ b/docs/pl/dla-technikow.md @@ -1,9 +1,9 @@ # Finch dla techników -**Dla osób, które dobrze znają komputery, ale programować nie umieją albo umieją niewiele.** -Ten przewodnik opisuje instalację Fincha, obsługę polecenia `finch`, cały język, pamięć, -moduły, biblioteki C, debugowanie i rozwiązywanie typowych problemów. Nie opisuje, jak -kompilator działa w środku. Do tego służy [przewodnik dla inżynierów](dla-inzynierow.md). +**Dla osób, które dobrze znają komputer, ale niewiele albo wcale nie programowały.** +Ten przewodnik opisuje instalację Fincha, polecenie `finch`, cały język, pamięć, moduły, biblioteki C, debugowanie +i rozwiązywanie typowych problemów. Nie opisuje, jak kompilator działa od środka; o tym jest +[przewodnik dla inżynierów](dla-inzynierow.md). > 🇬🇧 English version: [for-technicians.md](../en/for-technicians.md) @@ -18,7 +18,7 @@ kompilator działa w środku. Do tego służy [przewodnik dla inżynierów](dla- 5. [Zmienne](#5-zmienne) 6. [Typy](#6-typy) 7. [Operatory](#7-operatory) -8. [Sterowanie przebiegiem](#8-sterowanie-przebiegiem) +8. [Sterowanie](#8-sterowanie) 9. [Funkcje](#9-funkcje) 10. [Tablice i mapy](#10-tablice-i-mapy) 11. [Tekst (str)](#11-tekst-str) @@ -27,12 +27,12 @@ kompilator działa w środku. Do tego służy [przewodnik dla inżynierów](dla- 14. [Wskaźniki](#14-wskaźniki) 15. [Funkcje wbudowane](#15-funkcje-wbudowane) 16. [Moduły](#16-moduły) -17. [Biblioteki z C](#17-biblioteki-z-c) +17. [Biblioteki C](#17-biblioteki-c) 18. [Błędy](#18-błędy) 19. [Edytory: VS Code, Kate i inne](#19-edytory-vs-code-kate-i-inne) 20. [Debugowanie](#20-debugowanie) 21. [Rozwiązywanie problemów](#21-rozwiązywanie-problemów) -22. [Struktura projektu i testy](#22-struktura-projektu-i-testy) +22. [Układ repozytorium i testy](#22-układ-repozytorium-i-testy) 23. [Enumy i match](#23-enumy-i-match) 24. [Typy generyczne](#24-typy-generyczne) 25. [Biblioteka standardowa](#25-biblioteka-standardowa) @@ -41,82 +41,111 @@ kompilator działa w środku. Do tego służy [przewodnik dla inżynierów](dla- 28. [Projekt: finch.toml](#28-projekt-finchtoml) 29. [Wątki](#29-wątki) 30. [C++, Rust i Android](#30-c-rust-i-android) +31. [Wektory (SIMD)](#31-wektory-simd) --- ## 1. Czym jest Finch -Finch to język **kompilowany**. Narzędzie `finch` tłumaczy plik `.fch` na prawdziwy program -(natywny plik wykonywalny, taki jak te z C). Korzysta przy tym z **LLVM**, tego samego zaplecza -kompilatorów, na którym działają clang, Rust i Swift. +Finch to **kompilowany** język programowania. Polecenie `finch` tłumaczy plik `.fch` na prawdziwy program (natywny plik +wykonywalny, taki jak programy napisane w C) za pomocą **LLVM**, tego samego zaplecza, z którego korzystają clang, +Rust i Swift. -Co to daje: +Co z tego wynika: -- **Szybkość zbliżoną do C.** Nie ma interpretera, maszyny wirtualnej ani garbage collectora. - Kontrola zakresu tablic i inne zabezpieczenia po optymalizacji prawie nic nie kosztują. +- **Szybkość bliska C.** Nie ma interpretera, maszyny wirtualnej ani garbage collectora. Sprawdzanie zakresów tablic + i inne zabezpieczenia po optymalizacji prawie nic nie kosztują. - **Małe, samodzielne programy**, które można skopiować i uruchomić. -- **Pamięć obsługiwana za ciebie.** Listy i teksty są zwalniane automatycznie na końcu bloku, - do którego należą, bez garbage collectora. -- **Bezpośredni dostęp do bibliotek C.** Piszesz `import "stdio.h"` i wołasz, co chcesz, - nawet funkcje przyjmujące struktury. +- **Pamięć zarządza się sama.** Listy i teksty są zwalniane automatycznie na końcu bloku, do którego należą, + bez udziału garbage collectora. +- **Bezpośredni dostęp do bibliotek C.** `import "stdio.h"` i możesz wołać wszystko, co tam jest, nawet funkcje + przyjmujące struktury. -Zasady projektu: jeden sposób na każdą rzecz, składnia podobna do C bez jej pułapek -i komunikaty błędów, które mówią, jak problem naprawić. +Zasady projektu: na każdą rzecz jeden sposób, składnia jak w C, ale bez jego pułapek, i komunikaty błędów, które mówią, +jak naprawić problem. --- ## 2. Instalacja -Kompilator Fincha jest napisany w Finchu (zobacz [przewodnik dla inżynierów](dla-inzynierow.md)). Działa na **Linuksie** -(x86-64 i ARM64) i buduje programy dla **Linuksa, Windowsa i macOS**. Potrzebuje **clanga 21** i **libclang** (LLVM 21), -żeby czytać nagłówki C i kompilować to, co wygeneruje. +Kompilator Fincha jest napisany w Finchu (zob. [przewodnik dla inżynierów](dla-inzynierow.md)). Działa na **Linuksie** +(x86-64 i ARM64), **macOS** (Apple Silicon i Intel) i **Windowsie** (x86-64) i na każdy z tych systemów buduje programy. +Wszędzie potrzebuje **clanga** (kompiluje nim każdy program) i **libclang** z LLVM 21 (czyta nim nagłówki C). -Kompilator nie działa jeszcze na macOS ani Windowsie: potrzebuje ziarna zrobionego z nagłówkami tego systemu -([bootstrap/README.md](../../bootstrap/README.md) tłumaczy dlaczego). Do tego czasu programy na Windows i macOS buduj z Linuksa -przez `--target` (niżej) albo użyj starszego kompilatora w C++ z gałęzi `before-bootstrap`. +### Sposób A: gotowa wersja -### Opcja A: gotowa wersja (Linux) +Pobierz archiwum dla swojego systemu z [wydań](https://github.com/Nexoniarz/Finch/releases) i rozpakuj je. +Katalogi `runtime/` i `std/` muszą leżeć obok pliku `finch` (albo zainstaluj go, zob. niżej). -Pobierz `finch-linux-x64.tar.gz` albo `finch-linux-arm64.tar.gz` z [wydań](https://github.com/Nexoniarz/Finch/releases) -i rozpakuj. Zostaw `runtime/` i `std/` obok pliku `finch` (albo zainstaluj, niżej). Potrzebny jest też `clang` 21 -z biblioteką libclang: +| System | Archiwum | Doinstaluj | +|---|---|---| +| Linux x86-64 | `finch-linux-x64.tar.gz` | clang 21 i libclang (tabela niżej) | +| Linux ARM64 (Raspberry Pi 4/5, Graviton…) | `finch-linux-arm64.tar.gz` | to samo | +| macOS, Apple Silicon | `finch-macos-arm64.tar.gz` | `xcode-select --install` (clang i nagłówki systemu), `brew install llvm@21` (libclang) | +| macOS, Intel | `finch-macos-x64.tar.gz` | to samo | +| Windows x64 | `finch-windows-x64.zip` (`finch.exe` z `libclang.dll`) | [llvm-mingw](https://github.com/mstorsjo/llvm-mingw/releases) (archiwum `ucrt-x86_64`): rozpakuj i dodaj jego katalog `bin` do PATH | + +Na Linuksie: | Dystrybucja | Pakiety | |---|---| | Debian / Ubuntu | `clang-21 libclang-21-dev` z [apt.llvm.org](https://apt.llvm.org), `pkg-config` | | Fedora | `clang clang-devel pkgconf` | | Arch | `clang pkgconf` | -| Nix | `nix-shell` w repozytorium daje wszystko | +| Nix | `nix-shell` w repozytorium daje wszystko naraz | -### Opcja B: zbuduj sam +Na Windowsie llvm-mingw daje Finchowi w jednym katalogu `clang`, nagłówki C i linker; zbudowane nim programy nie +potrzebują do działania niczego więcej. Plik `libclang.dll` trzymaj obok `finch.exe`. + +### Sposób B: zbuduj sam ```sh git clone https://github.com/Nexoniarz/Finch.git cd Finch -nix-shell # clang, libclang, pkg-config (albo zainstaluj z dystrybucji) +nix-shell # Linux: clang, libclang, pkg-config (albo zainstaluj je z dystrybucji) bootstrap/build.sh # build/finch ./build/finch version ``` -`bootstrap/build.sh` kompiluje ziarno (`bootstrap/.ll`, własny LLVM IR kompilatora) do pierwszego kompilatora, -buduje nim `src/`, buduje `src/` jeszcze raz wynikiem i sprawdza, że oba są identyczne. Jeśli libclang nie leży tam, gdzie -szuka linker: `FINCH_LIBCLANG="-L/usr/lib/llvm-21/lib -lclang-21" bootstrap/build.sh`. +`bootstrap/build.sh` kompiluje plik startowy (`bootstrap/.ll`, własny LLVM IR kompilatora) w pierwszy kompilator, +buduje nim `src/`, buduje `src/` jeszcze raz otrzymanym kompilatorem i sprawdza, czy oba wyniki są identyczne. Jeśli +linker nie znajduje libclang, wskaż ją: `FINCH_LIBCLANG="-L/usr/lib/llvm-21/lib -lclang-21" bootstrap/build.sh`. + +Na Macu, z LLVM z Homebrew: + +```sh +brew install llvm@21 +export FINCH_CLANG="$(brew --prefix llvm@21)/bin/clang" +export FINCH_LIBCLANG="-L$(brew --prefix llvm@21)/lib -lclang" +export FINCH_LLVM_INCLUDE="$(brew --prefix llvm@21)/include" +bootstrap/build.sh +``` + +Na Windowsie, w Git Bash, z llvm-mingw w PATH i oficjalnym LLVM dla Windowsa (dla `libclang.dll` i jej nagłówków) +rozpakowanym do `C:/llvm`: + +```sh +export FINCH_LIBCLANG="C:/llvm/bin/libclang.dll" +export FINCH_LLVM_INCLUDE="C:/llvm/include" +export FINCH_LDFLAGS="-LC:/llvm/bin" +bootstrap/build.sh # build/finch.exe, z libclang.dll skopiowaną obok +``` ### Budowanie na inny system -`--target` buduje program na inny system. Do linkowania Finch potrzebuje kompilatora C dla tego systemu: +`--target` buduje program na inny system. Do linkowania Finch potrzebuje kompilatora C tamtego systemu: -| `--target` | Buduje | Kompilator C | `finch run` używa | +| `--target` | Buduje | Używany kompilator C | `finch run` uruchamia przez | |---|---|---|---| -| `windows` | `.exe` dla Windowsa | `x86_64-w64-mingw32-gcc` (MinGW-w64) | Wine | +| `windows` | plik `.exe` dla Windowsa | `x86_64-w64-mingw32-gcc` (MinGW-w64) | Wine | | `arm64` | program dla Linuksa ARM64 | `aarch64-linux-gnu-gcc` | `qemu-aarch64` | | `linux` | program dla Linuksa x86-64 | `clang --target=…` | | -| `macos` | program dla macOS (na Macu: dla drugiego procesora) | `clang --target=…` | | +| `macos` | program dla macOS (na Macu: dla drugiego rodzaju procesora) | `clang --target=…` | | | dowolny triple LLVM | np. `aarch64-unknown-linux-gnu` | `clang --target=` | | ```sh finch build gra.fch --target windows # gra.exe -finch run gra.fch --target arm64 # buduje na ARM64 i uruchamia w QEMU +finch run gra.fch --target arm64 # buduje dla ARM64 i uruchamia przez QEMU FINCH_CC=aarch64-unknown-linux-gnu-gcc finch build narzedzie.fch --target arm64 # inna nazwa kompilatora ``` @@ -131,41 +160,44 @@ bootstrap/install.sh ~/.local # albo do własnego katalogu finch version ``` -Podczas działania Finch potrzebuje `clanga` (kompiluje IR każdego programu i go linkuje). Mała biblioteka uruchomieniowa -budowana jest raz i trzymana w `~/.cache/finch`. `FINCH_CC` wskazuje inny kompilator do linkowania; `FINCH_PATH` dodaje -katalogi, w których szukać modułów. +Przy pracy Finch potrzebuje `clanga` (kompiluje nim IR każdego programu i linkuje go). Mała biblioteka runtime +jest budowana raz i trzymana w `~/.cache/finch`. `FINCH_CC` wskazuje inny kompilator do linkowania; `FINCH_PATH` +dodaje katalogi, w których Finch szuka modułów. --- ## 3. Polecenie finch ``` -finch run [argumenty...] skompiluj i od razu uruchom -finch build [-o nazwa] skompiluj do programu (domyślna nazwa: nazwa pliku) -finch test uruchom jego bloki test "..." { } (rozdział 27) -finch fmt [--check] [pliki/foldery] uporządkuj wcięcia plików .fch (domyślnie wszystkich stąd w dół) -finch ir pokaż LLVM IR (dla ciekawskich) -finch lsp serwer języka, dla edytorów -finch version pokaż wersję +finch run [argumenty...] kompiluje i od razu uruchamia +finch build [-o nazwa] kompiluje do programu (domyślna nazwa: nazwa pliku) +finch test uruchamia jego bloki test "..." { } (rozdział 27) +finch fmt [--check] [pliki/katalogi] porządkuje wcięcia plików .fch (domyślnie wszystkich w bieżącym katalogu) +finch ir wypisuje LLVM IR (dla ciekawskich) +finch lsp serwer języka, dla edytorów +finch version pokazuje wersję opcje: - -l dolinkuj bibliotekę C, to samo co link "lib" w pliku - -I szukaj nagłówków C także tam + -l dolinkuj bibliotekę C, to samo co link "bib" w pliku + -I szukaj nagłówków C także tam --target zbuduj na inny system: windows, linux, arm64, macos, android albo triple LLVM - -g dodaj informacje dla debuggera (gdb / lldb / Visual Studio) + --cpu użyj wszystkich instrukcji procesora: native (tego), x86-64-v3 (AVX2), + x86-64-v4 (AVX-512), apple-m1, ... (rozdział 31) + -g dodaj informacje dla debuggera (gdb i lldb) -O0 bez optymalizacji - --trace błędy w czasie działania pokazują łańcuch wywołań (i tak włączone dla run, test, -g i -O0) - --no-trace nie pokazują (zwykły finch build go nie ma: pilnowanie łańcucha spowalnia wywołania) + --trace błędy w trakcie działania pokazują ścieżkę wywołań (i tak włączone dla run, test, -g i -O0) + --no-trace nie pokazują (zwykłe finch build ich nie pokazuje: pamiętanie ścieżki spowalnia wywołania) ``` -W folderze z plikiem `finch.toml` (rozdział 28) `finch run`, `finch build` i `finch test` nie potrzebują nazwy pliku. +W katalogu z plikiem `finch.toml` (rozdział 28) `finch run`, `finch build` i `finch test` nie potrzebują nazwy pliku. -Skompilowany program i pliki C, które buduje (`link "x.c"`), zostają w cache (`~/.cache/finch`, na Windowsie -`%LOCALAPPDATA%`). Drugie budowanie tego samego kodu albo kodu, którego pliki C się nie zmieniły, pomija tę pracę. +Skompilowany program i zbudowane przez niego pliki C (`link "x.c"`) trafiają do pamięci podręcznej (`~/.cache/finch`, +na Windowsie `%LOCALAPPDATA%`). Druga kompilacja tego samego kodu, albo kodu, którego pliki C się nie zmieniły, pomija tę pracę. -`finch fmt` zmienia tylko układ: cztery spacje na poziom `{ }`, bez spacji na końcach linii, najwyżej jedna pusta linia -z rzędu. Linie, które ciągną wyrażenie (w `( )` albo po `+`), i tekst `"""` zachowują swój kształt. `finch fmt --check` -niczego nie zmienia i kończy się błędem, gdy jakiś plik by się zmienił (dla CI). Edytory z serwerem języka formatują na żądanie. +`finch fmt` zmienia tylko układ: cztery spacje na każdy poziom `{ }`, żadnych spacji na końcach linii, najwyżej jedna +pusta linia z rzędu. Linie kontynuujące wyrażenie (w nawiasach `( )` albo po `+`) i tekst `"""` zachowują swój kształt. +`finch fmt --check` niczego nie zmienia i kończy się błędem, jeśli jakiś plik by się zmienił (przydatne w CI). +Edytory z serwerem języka formatują na żądanie. Przykłady: @@ -173,10 +205,10 @@ Przykłady: finch run hello.fch # wypisuje wynik finch run narzedzie.fch dane.txt -v # argumenty po nazwie pliku trafiają do programu finch build gra.fch -o mojagra # tworzy ./mojagra -finch build gra.fch -g -O0 # wersja dla debuggera +finch build gra.fch -g -O0 # wersja do debuggera ``` -`finch run` przekazuje dalej kod wyjścia programu. Jeśli program się wysypie, powie jak +`finch run` przekazuje kod wyjścia twojego programu. Jeśli program się wysypie, mówi jak (np. `the program crashed: Segmentation fault`). --- @@ -186,13 +218,13 @@ finch build gra.fch -g -O0 # wersja dla debuggera ```c // komentarz zaczyna się od dwóch ukośników /* albo zajmuje - kilka linijek */ + kilka linii */ -import "math.h" // nagłówki C (opcjonalne), na górze -import ksztalty // moduły Fincha (opcjonalne): ksztalty.fch obok tego pliku -link "m" // biblioteki albo pliki C do dolinkowania (opcjonalne) +import "math.h" // nagłówki C (opcjonalnie), na początku pliku +import ksztalty // moduły Fincha (opcjonalnie): ksztalty.fch obok tego pliku +link "m" // biblioteki albo pliki C do dolinkowania (opcjonalnie) -struct Punkt { // własne typy +struct Punkt { // twoje własne typy int x int y } @@ -204,32 +236,32 @@ fn main() { // tu zaczyna się program Zasady, które warto znać: -- **Jedna instrukcja na linijkę.** Średników nie ma. Długie wyrażenie możesz złamać **po** operatorze - (`a +⏎ b`) albo w dowolnym miejscu wewnątrz nawiasów `( )` lub `[ ]`. -- **Klamry są zawsze obowiązkowe** po `if`, `else`, `for`, `while`, a otwierająca `{` stoi w tej samej linijce. -- **Bez nawiasów wokół warunków:** `if x > 5 {`, a nie `if (x > 5) {`. -- Funkcje i struktury mogą być w pliku w dowolnej kolejności. +- **Jedna instrukcja w linii.** Nie ma średników. Długie wyrażenie można złamać **po** operatorze (`a +⏎ b`) + albo w dowolnym miejscu w nawiasach `( )` lub `[ ]`. +- **Klamry są zawsze obowiązkowe** po `if`, `else`, `for`, `while`, a otwierająca `{` stoi w tej samej linii. +- **Warunki bez nawiasów:** `if x > 5 {`, a nie `if (x > 5) {`. +- Funkcje i struktury mogą stać w pliku w dowolnej kolejności. - `main` to `fn main()`, `fn main() -> int` (zwraca kod wyjścia) albo `fn main([]str args)` - (dostaje argumenty z linii poleceń; `args[0]` to ścieżka samego programu). + (dostaje argumenty z wiersza poleceń; `args[0]` to ścieżka samego programu). --- ## 5. Zmienne ```c -x := 5 // nowa zmienna, typ zgadnięty (int) -int y = 10 // nowa zmienna, typ podany jawnie -int z // bez wartości: startuje od zera ("" dla str, [] dla tablic, null dla wskaźników) +x := 5 // nowa zmienna, typ wynika z wartości (int) +int y = 10 // nowa zmienna z podanym typem +int z // bez wartości: zero ("" dla str, [] dla tablic, null dla wskaźników) Punkt p // struktura bez wartości: zera albo wartości domyślne struktury x = 7 // przypisanie x += 1 // także -= *= /= %= ``` -- Nazwę można zadeklarować **raz** w funkcji. Finch nie pozwala użyć jej ponownie w bloku - wewnętrznym (nie ma „przesłaniania”), bo to częste źródło pomyłek. -- Zmienna istnieje od deklaracji do końca bloku `{ }`, w którym powstała. -- Nazwy: litery ASCII, cyfry i `_`, bez cyfry na początku. Polskie litery tylko w tekstach w cudzysłowie. +- Nazwę można zadeklarować w funkcji **tylko raz**. Finch nie pozwala użyć tej samej nazwy w bloku wewnętrznym + (nie ma „przesłaniania”), bo to częste źródło pomyłek. +- Zmienna istnieje od deklaracji do końca bloku `{ }`, w którym stoi. +- Nazwy: litery ASCII, cyfry i `_`, nie mogą zaczynać się od cyfry. Polskie litery wolno pisać tylko w tekstach. Nazwy typów (`int`, `u8`, …), funkcji wbudowanych i słowa kluczowe są zarezerwowane. **Słowa kluczowe:** `fn return if else while for in break continue true false null import link struct enum match defer or try const export` @@ -243,8 +275,8 @@ const f32 GRAWITACJA = 9.8 // z typem const TYTUL = "Moja gra" ``` -Stałą piszesz na najwyższym poziomie pliku, obok funkcji, i nie da się jej zmienić. Składa się z liczb, tekstu, -`true`/`false`, znaków, innych stałych i operatorów; stała z modułu to `ksztalty.BOKI`. +Stałą zapisuje się na najwyższym poziomie pliku, obok funkcji, i nie da się jej zmienić. Składa się z liczb, tekstów, +`true`/`false`, znaków, innych stałych i operatorów; stała z modułu to np. `ksztalty.BOKI`. --- @@ -254,18 +286,18 @@ Stałą piszesz na najwyższym poziomie pliku, obok funkcji, i nie da się jej z | Typ | Przechowuje | Przykład | |---------|-------------|----------| -| `int` | liczba całkowita, 64-bitowa (to samo co `i64`) | `42`, `-7`, `0xFF`, `1_000_000` | -| `float` | liczba z przecinkiem, 64-bitowa (to samo co `f64`) | `3.14` | +| `int` | liczbę całkowitą, 64 bity (to samo co `i64`) | `42`, `-7`, `0xFF`, `1_000_000` | +| `float` | liczbę zmiennoprzecinkową, 64 bity (to samo co `f64`) | `3.14` | | `bool` | `true` albo `false` | | | `char` | jeden znak wielkości bajtu | `'A'`, `'\n'` | | `str` | tekst | `"cześć\tświecie"` | -| `[]T` | lista (tablica) elementów `T` | `[1, 2, 3]` | -| `map[K]V` | wartości typu `V` znajdowane po kluczu typu `K` | `["a": 1, "b": 2]` | +| `[]T` | listę (tablicę) wartości typu `T` | `[1, 2, 3]` | +| `map[K]V` | wartości typu `V` wyszukiwane po kluczu typu `K` | `["a": 1, "b": 2]` | | twoje struktury | nazwane grupy wartości | `Punkt(1, 2)` | -Znaki specjalne w tekstach: `\n` nowa linia, `\t` tabulator, `\r`, `\0`, `\\`, `\"`, `\'`. +Znaki specjalne w tekstach i znakach: `\n` nowa linia, `\t` tabulator, `\r`, `\0`, `\\`, `\"`, `\'`. -### Typy z rozmiarem (do plików, sprzętu, bibliotek C) +### Typy o podanym rozmiarze (do plików, sprzętu, bibliotek C) | Ze znakiem | Zakres | Bez znaku | Zakres | |---|---|---|---| @@ -274,7 +306,9 @@ Znaki specjalne w tekstach: `\n` nowa linia, `\t` tabulator, `\r`, `\0`, `\\`, ` | `i32` | ok. ±2,1 mld | `u32` | 0 … ok. 4,29 mld | | `i64` | ok. ±9,2 × 10¹⁸ | `u64` | 0 … ok. 1,8 × 10¹⁹ | -`f32` to 32-bitowa liczba z przecinkiem (ok. 7 cyfr znaczących), `f64` to 64-bitowa (ok. 15–16). +`f32` to 32-bitowa liczba zmiennoprzecinkowa (ok. 7 cyfr znaczących), `f64` to 64-bitowa (ok. 15–16). + +Są też **wektory**, czyli kilka liczb obok siebie liczonych naraz: `f32x8`, `i8x32` i podobne (rozdział 31). ### Zamiana typów @@ -282,27 +316,27 @@ Finch zamienia typy sam **tylko wtedy, gdy nic nie może zginąć**: | Z | Na | Automatycznie? | |---|---|---| -| mniejsza liczba całkowita | większa o tej samej znakowości (`i8`→`i32`, `u8`→`u64`) | ✅ | -| bez znaku | ściśle większa ze znakiem (`u8`→`i16`, `u32`→`i64`) | ✅ | +| mniejsza liczba całkowita | większa o tym samym znaku (`i8`→`i32`, `u8`→`u64`) | ✅ | +| bez znaku | większa ze znakiem (`u8`→`i16`, `u32`→`i64`) | ✅ | | dowolna liczba całkowita | `float` / `f32` | ✅ | | `f32` | `f64` | ✅ | | `null` | dowolny wskaźnik | ✅ | -| dowolny wskaźnik | `ptr`, a `ptr` na dowolny wskaźnik | ✅ | -| wszystko inne | | ❌ napisz to jawnie: `typ(wartość)` | +| dowolny wskaźnik | `ptr` i z `ptr` na dowolny wskaźnik | ✅ | +| wszystko inne | | ❌ trzeba napisać: `typ(wartosc)` | -**Liczby wpisane w kod się dopasowują.** Liczba napisana wprost w kodzie pasuje do każdego typu, -który ją pomieści: `u8 b = 200` działa, a `u8 b = 300` to błąd. To samo dotyczy stałych z nagłówków C. +**Liczby wpisane w kod dopasowują się.** Liczba zapisana w kodzie pasuje do każdego typu, który ją pomieści: +`u8 b = 200` działa, `u8 b = 300` to błąd. Tak samo jest ze stałymi z nagłówków C. -**Jawna zamiana** wygląda jak wywołanie funkcji o nazwie typu: +**Jawne zamiany** zapisuje się nazwą typu, jak wywołanie funkcji: ```c -int(3.99) // 3 (obcina część ułamkową) +int(3.99) // 3 (ucina część ułamkową) float(7) / 2 // 3.5 -u8(300) // 44 (zostawia najniższe 8 bitów) +u8(300) // 44 (zostaje 8 najniższych bitów) int('A') // 65 char(66) // 'B' bool(0) // false (każda liczba różna od zera to true) -str(42) // "42" (też float, bool, char; str([]u8) zamienia bajty na tekst) +str(42) // "42" (także liczby zmiennoprzecinkowe, bool i znaki; str([]u8) zamienia bajty na tekst) int("42") // 42 (zatrzymuje program, jeśli tekst nie jest liczbą) float("2.5") // 2.5 ptr(p) // wskaźnik z typem jako wskaźnik bez typu @@ -311,8 +345,8 @@ int(p) // wskaźnik jako liczba (jego adres) str(p) // wskaźnik na znaki z C jako tekst ``` -**Liczby o stałym rozmiarze się „zawijają”**, jak w prawdziwym sprzęcie: `u8` równe 255 plus 1 daje 0. -Działania liczą się w typie użytych wartości, więc `u8(250) + 10` daje `4`, a nie `260`. +**Liczby o stałym rozmiarze się zawijają**, jak w prawdziwym sprzęcie: `u8` równe 255 plus 1 daje 0. +Obliczenia odbywają się w typie użytych wartości, więc `u8(250) + 10` to `4`, a nie `260`. --- @@ -322,13 +356,13 @@ Działania liczą się w typie użytych wartości, więc `u8(250) + 10` daje `4` | Priorytet | Operatory | Znaczenie | |---|---|---| -| 5 | `*` `/` `%` `<<` `>>` `&` | mnożenie, dzielenie, reszta, przesunięcia bitów, bitowe AND | -| 4 | `+` `-` `\|` `^` | dodawanie (i sklejanie tekstu), odejmowanie, bitowe OR, bitowe XOR | +| 5 | `*` `/` `%` `<<` `>>` `&` | mnożenie, dzielenie, reszta, przesunięcie w lewo/prawo, bitowe AND | +| 4 | `+` `-` `\|` `^` | dodawanie (i łączenie tekstów), odejmowanie, bitowe OR, bitowe XOR | | 3 | `==` `!=` `<` `<=` `>` `>=` | porównania | -| 2 | `&&` | logiczne I | -| 1 | `\|\|` | logiczne LUB | +| 2 | `&&` | logiczne AND | +| 1 | `\|\|` | logiczne OR | -Jednoargumentowe (przed wartością): `-x` zmiana znaku, `!x` logiczne NIE, `~x` odwrócenie wszystkich bitów. +Jednoargumentowe (przed wartością): `-x` zmiana znaku, `!x` logiczne NOT, `~x` odwrócenie wszystkich bitów. > **Celowo inaczej niż w C:** operatory bitowe wiążą mocniej niż porównania, więc > `flagi & 4 != 0` znaczy `(flagi & 4) != 0`. @@ -336,20 +370,20 @@ Jednoargumentowe (przed wartością): `-x` zmiana znaku, `!x` logiczne NIE, `~x` Uwagi: - `/` na liczbach całkowitych zaokrągla w stronę zera: `7 / 2` to `3`. `%` ma znak lewej strony. -- Dzielenie przez zero **zatrzymuje program** komunikatem `runtime error: division by zero` z numerem linijki. -- `&&` i `||` liczą prawą stronę tylko wtedy, gdy to potrzebne. -- Tekst: `+` skleja, `==` `!=` porównują treść, `<` `>` porównują alfabetycznie (bajt po bajcie). -- Wskaźniki porównuje się przez `==` i `!=`, także z `null`. `str` nigdy nie jest `null`. -- Tablic i struktur nie porównasz przez `==`; porównuj ich części. +- Dzielenie przez zero **zatrzymuje program** komunikatem `runtime error: division by zero` z numerem linii. +- `&&` i `||` obliczają prawą stronę tylko wtedy, gdy trzeba. +- Teksty: `+` łączy, `==` `!=` porównują zawartość, `<` `>` porównują alfabetycznie (bajt po bajcie). +- Wskaźniki można porównywać przez `==` i `!=`, także z `null`. `str` nigdy nie jest `null`. +- Tablic i struktur nie da się porównać przez `==`; porównaj ich części. --- -## 8. Sterowanie przebiegiem +## 8. Sterowanie -`if` może też dawać wartość; każda gałąź to jedna wartość, a `else` jest wymagane: +`if` może też dawać wartość; każda gałąź zawiera jedną wartość, a `else` jest obowiązkowe: ```c -opis := if wynik > 100 { "dużo" } else if wynik > 10 { "średnio" } else { "mało" } +ocena := if wynik > 100 { "wysoki" } else if wynik > 10 { "średni" } else { "niski" } f32 predkosc = if biegnie { 2.5 } else { 1.0 } ``` @@ -366,12 +400,12 @@ while licznik > 0 { licznik -= 1 } -for i in 0..10 { // i = 0, 1, …, 9 (koniec nie jest wliczany) +for i in 0..10 { // i = 0, 1, …, 9 (koniec nie wchodzi) if i == 3 { continue } if i == 8 { break } } -for imie in imiona { // każdy element tablicy (albo każdy znak str) +for imie in imiona { // każdy element tablicy (albo każdy znak tekstu) print(imie) } @@ -379,31 +413,30 @@ for i, imie in imiona { // razem z indeksem: 0, 1, 2, ... print(i, imie) } -for klucz, wartosc in wiek { // każdy wpis mapy, w kolejności dodawania kluczy - print(klucz, wartosc) // (for klucz in wiek: same klucze) +for klucz, wartosc in wiek { // każdy wpis mapy, w kolejności dodawania kluczy + print(klucz, wartosc) // (for klucz in wiek: same klucze) } ``` - Warunek musi być typu `bool`. `if x {` z liczbą to błąd; napisz `if x != 0 {`. -- W `for i in a..b` oba końce są liczone **raz**. `i` jest typu `int` i nie można go zmieniać w pętli. -- W `for x in lista` zmiennej `x` też nie zmienisz. Żeby zmieniać elementy, przechodź po indeksach: - `for i in 0..lista.len { lista[i] = ... }`. Dodawanie do listy w trakcie pętli jest dozwolone. +- W `for i in a..b` oba końce są obliczane **raz**. `i` jest typu `int` i nie można go zmieniać w pętli. +- W `for x in lista` też nie można zmieniać `x`. Żeby zmieniać elementy, idź po indeksach: + `for i in 0..lista.len { lista[i] = ... }`. Dodawanie do listy w trakcie pętli jest w porządku. - `while true { ... }` kręci się do `break` albo `return`. -- Kod po `return`, `break` lub `continue` w tym samym bloku to błąd, bo nigdy się nie wykona. +- Kod po `return`, `break` czy `continue` w tym samym bloku to błąd, bo nigdy się nie wykona. ### defer -`defer` uruchamia instrukcję, gdy bieżący blok się kończy, niezależnie od tego, jak się kończy: -normalnie, przez `return`, `break` lub `continue`. Kilka `defer` wykonuje się w odwrotnej kolejności. -Przydaje się do sprzątania: +`defer` wykonuje instrukcję, gdy kończy się bieżący blok, niezależnie od tego, jak się kończy: normalnie, przez +`return`, `break` albo `continue`. Kilka `defer` wykonuje się w odwrotnej kolejności. Przydaje się do sprzątania: ```c f := fopen("dane.txt", "r") defer fclose(f) -// ... używaj f; zostanie zamknięty na końcu bloku, na każdej ścieżce +// ... używaj f; zostanie zamknięty na końcu bloku, którąkolwiek drogą program z niego wyjdzie ``` -Instrukcja w `defer` nie może zawierać `return`, `break` ani `continue`. +Odroczona instrukcja nie może zawierać `return`, `break` ani `continue`. --- @@ -415,19 +448,19 @@ fn nazwa(typ1 param1, typ2 param2) -> typ_wyniku { return wartosc } -fn bez_wyniku(str wiadomosc) { // bez "->": nic nie zwraca - print(wiadomosc) +fn bez_wyniku(str komunikat) { // bez "->": niczego nie zwraca + print(komunikat) } ``` -- Argumenty zachowują się jak kopie: zmiana parametru nigdy nie zmienia zmiennej u wywołującego. +- Argumenty zachowują się jak kopie: zmiana parametru nigdy nie zmienia zmiennej wywołującego. (Finch kopiuje naprawdę tylko wtedy, gdy trzeba: funkcja, która nie zmienia tablicy, dostaje ją za darmo.) - Żeby zmienić zmienną wywołującego, przekaż wskaźnik, zobacz [Wskaźniki](#14-wskaźniki). -- Funkcja z `-> typ` musi zwrócić wartość na każdej ścieżce. Finch to sprawdza. -- Funkcji nie można definiować wewnątrz innej funkcji. -- Funkcję, która może się nie udać, zapisuje się `-> int!` (albo `-> !` bez wyniku); zobacz + Żeby zmienić zmienną wywołującego, przekaż wskaźnik: zob. [Wskaźniki](#14-wskaźniki). +- Funkcja z `-> typ` musi zwracać wartość na każdej ścieżce. Finch to sprawdza. +- Nie można zdefiniować funkcji wewnątrz innej funkcji. +- Funkcję, która może się nie udać, zapisuje się `-> int!` (albo `-> !` bez wyniku); zob. [Błędy jako wartości](#błędy-jako-wartości). -- Długie wywołania możesz rozbić na kilka linijek wewnątrz nawiasów; przecinek po ostatnim argumencie jest dozwolony. +- Długie wywołania można rozbić na kilka linii w nawiasach; przecinek po ostatnim argumencie jest dozwolony. --- @@ -440,20 +473,20 @@ siatka := [[1, 2], [3, 4]] // tablice tablic []f32 xs = [1, 2.5] // typ decyduje, czym staną się liczby ``` -| Operacja | Znaczenie | +| Działanie | Znaczenie | |---|---| | `a.len` | liczba elementów | -| `a[i]` | element `i` (od 0); poza zakresem program zatrzymuje się z czytelnym komunikatem | +| `a[i]` | element `i` (licząc od 0); indeks poza zakresem zatrzymuje program jasnym komunikatem | | `a[i] = x` | zmiana elementu | -| `a.push(x)` | dodaj na końcu | -| `a.pop()` | zabierz ostatni i go oddaj | -| `a.insert(i, x)` | wstaw `x` na pozycję `i`, przesuwając resztę | -| `a.remove(i)` | usuń pozycję `i` i ją oddaj | -| `a.clear()` | usuń wszystko | -| `a.resize(n)` | zmień długość na `n` (nowe elementy to zera) | -| `a.contains(x)`, `a.find(x)` | czy `x` jest w środku? jego indeks albo -1 (liczby, znaki, bool, str) | +| `a.push(x)` | dodanie na końcu | +| `a.pop()` | zdjęcie ostatniego elementu i zwrócenie go | +| `a.insert(i, x)` | wstawienie `x` na pozycję `i` z przesunięciem reszty | +| `a.remove(i)` | usunięcie pozycji `i` i zwrócenie jej | +| `a.clear()` | usunięcie wszystkiego | +| `a.resize(n)` | zmiana długości na `n` (nowe elementy to zera) | +| `a.contains(x)`, `a.find(x)` | czy `x` tam jest? jego indeks albo -1 (liczby, znaki, bool, str) | | `a.sort()`, `a.reverse()` | w miejscu (liczby, znaki, str) | -| `a.slice(s, e)` | nowa tablica z elementami `s` … `e-1` | +| `a.slice(p, k)` | nowa tablica z elementami `p` … `k-1` | | `a.join(sep)` | dla `[]str`: jeden tekst z `sep` między częściami | | `a.ptr` | adres pierwszego elementu (dla C) | | `print(a)` | wypisuje `[1, 2, 3]` | @@ -462,24 +495,23 @@ siatka := [[1, 2], [3, 4]] // tablice tablic ### Mapy -`map[K]V` znajduje wartości po kluczu. Kluczem może być liczba całkowita, `char`, `bool` albo `str`; -wartością cokolwiek (tablice, struktury, inne mapy). Przechodzenie pętlą idzie w kolejności, w jakiej -klucze zostały dodane po raz pierwszy. +`map[K]V` znajduje wartości po kluczu. Kluczami mogą być liczby całkowite, `char`, `bool` albo `str`; wartościami +cokolwiek (tablice, struktury, inne mapy). Przechodzenie po mapie idzie w kolejności, w jakiej klucze dodano po raz pierwszy. ```c -wiek := ["anna": 31, "bob": 25] // literał mapy: map[str]int +wiek := ["anna": 31, "bob": 25] // mapa zapisana wprost: map[str]int map[str][]str grupy // pusta mapa map[int]str nazwy = [:] // [:] to pusta mapa tam, gdzie typ jest znany ``` -| Operacja | Znaczenie | +| Działanie | Znaczenie | |---|---| | `m[k]` | wartość dla `k`; brak klucza zatrzymuje program komunikatem `the key "k" is not in the map` | -| `m[k] = v` | dodaj albo zastąp | -| `m[k] += 1`, `m[k].push(x)`, `m[k].pole = …` | zmiana brakującego klucza najpierw go dodaje, z wartością domyślną typu (0, `""`, `[]`, …) | +| `m[k] = v` | dodanie albo podmiana | +| `m[k] += 1`, `m[k].push(x)`, `m[k].pole = …` | zmiana nieistniejącego klucza najpierw go dodaje, z wartością domyślną typu (0, `""`, `[]`, …) | | `m.len` | liczba kluczy | -| `m.has(k)` | czy `k` jest w mapie? | -| `m.get(k, domyslna)` | wartość albo `domyslna` (liczona tylko w razie potrzeby) | +| `m.has(k)` | czy `k` tam jest? | +| `m.get(k, domyslna)` | wartość albo `domyslna` (obliczana tylko wtedy, gdy trzeba) | | `m.remove(k)` | usuwa `k`; `true`, jeśli był | | `m.clear()` | usuwa wszystko | | `m.keys()`, `m.values()` | nowe tablice, w kolejności dodawania | @@ -495,9 +527,9 @@ for slowo, n in ile { } ``` -Wyszukiwanie używa haszowania (średnio stały czas). Tak jak tablice, mapy są właścicielami kluczy -i wartości, kopiują się przy przypisaniu i zwalniają na końcu bloku. Nie dodawaj ani nie usuwaj kluczy -mapy wewnątrz `for` po tej samej mapie; zbierz je do tablicy i zmień mapę po pętli. +Wyszukiwanie korzysta z haszowania (średnio stały czas). Tak jak tablice, mapy są właścicielami swoich kluczy +i wartości, kopiują się przy przypisaniu i zwalniają na końcu bloku. Nie dodawaj ani nie usuwaj kluczy mapy wewnątrz +`for` po tej samej mapie; zbierz je w tablicy i zmień mapę po pętli. --- @@ -505,37 +537,37 @@ mapy wewnątrz `for` po tej samej mapie; zbierz je do tablicy i zmień mapę po ```c s := "Cześć" -s += ", świecie" // sklejanie -print(s.len, s[0]) // .len liczy bajty; ą, ś, ż… zajmują po 2 -s[0] = 'D' // zmiana znaku +s += ", świecie" // łączenie +print(s.len, s[0]) // 17 C (.len liczy bajty; ą, ś, ż, … zajmują po 2) +s[0] = 'K' // zmiana znaku ``` -| Metoda | Oddaje | +| Metoda | Zwraca | |---|---| | `s.sub(a, b)` | fragment od `a` do `b-1` | | `s.find(t)` | gdzie zaczyna się `t`, albo -1 | | `s.contains(t)`, `s.starts_with(t)`, `s.ends_with(t)` | `bool` | | `s.split(sep)` | `[]str`; `split("")` daje pojedyncze znaki | -| `s.trim()` | bez spacji i nowych linii na obu końcach | -| `s.upper()`, `s.lower()` | zmienione litery A–Z / a–z (tylko ASCII) | +| `s.trim()` | bez spacji i znaków nowej linii na obu końcach | +| `s.upper()`, `s.lower()` | zamienione A–Z / a–z (litery ASCII) | | `s.replace(a, b)` | każde `a` zamienione na `b` | -| `s.repeat(n)` | `s` n razy | +| `s.repeat(n)` | `s` powtórzone n razy | | `s.bytes()` | `[]u8` z bajtami | -| `s.ptr` | `ptr[char]` dla C (ważny, dopóki żyje `s`) | +| `s.ptr` | `ptr[char]` dla C (ważny, dopóki `s` istnieje) | -Wartości wstawia się do tekstu przez `\(...)`: dowolny kod, a `, 2` po liczbie daje dwie cyfry po przecinku. -`\` przed `(`, które ma zostać zwykłym znakiem: `"\\(to nie kod)"`. +Wartości wstawia się w tekst przez `\(...)`: dowolny kod, a po liczbie `, 2` dla dwóch cyfr po przecinku. +Jeśli `(` ma zostać zwykłym znakiem, poprzedź `\` drugim ukośnikiem: `"\\(to nie kod)"`. ```c print("hp \(hp) z \(max_hp), prędkość \(v, 2), imię \(p.imie.upper())") ``` -`str` pamięta swoją długość, więc `.len` działa natychmiast. Zawsze kończy się zerem, więc -przekazanie go do C jako `char*` działa. W drugą stronę: funkcja C, która zwraca `char *`, daje ci `str` -(kopia powstaje, gdy go zachowujesz), a `str(p)` czyta `ptr[char]` zdobyty inaczej. +`str` pamięta swoją długość, więc `.len` jest natychmiastowe, a na końcu zawsze ma bajt zerowy, więc można go przekazać +do C jako `char*`. W drugą stronę: funkcja C, która zwraca `char *`, daje ci `str` (kopia powstaje, gdy go zatrzymujesz), +a `str(p)` czyta `ptr[char]` zdobyty w inny sposób. -Tekst w kilku liniach piszesz między `"""`. Znak nowej linii zaraz po otwarciu znika, a z każdej linii znika -wcięcie zamykających cudzysłowów, więc tekst może być wcięty razem z kodem: +Tekst na kilka linii zapisuje się między `"""`. Znak nowej linii po otwierającym cudzysłowie znika, a z każdej linii +znika też wcięcie zamykającego cudzysłowu, więc tekst może mieć wcięcie razem z kodem: ```c shader := """ @@ -544,8 +576,8 @@ shader := """ """ ``` -Liczby jako tekst: `str(x)` to najkrótszy tekst, który czyta się z powrotem jako ta sama liczba (`0.1`, -`0.30000000000000004`, `1e+20`); `str(x, 2)` ma zawsze dwie cyfry po kropce (`3.14`). +Liczby jako tekst: `str(x)` to najkrótszy zapis, który da z powrotem tę samą liczbę (`0.1`, `0.30000000000000004`, +`1e+20`); `str(x, 2)` ma zawsze dwie cyfry po przecinku (`3.14`). --- @@ -556,27 +588,27 @@ struct Gracz { str imie int zycia = 3 // wartość domyślna []int wyniki - Punkt pozycja // struktury w strukturach + Punkt poz // struktury w strukturach } -g := Gracz(imie: "Ola") // po nazwie: brakujące pola dostają domyślne (albo zero) +p := Gracz(imie: "Ola") // przez nazwy: brakujące pola dostają wartość domyślną (albo zero) q := Punkt(1, 2) // po kolei: wtedy trzeba podać każde pole -g.zycia -= 1 -g.wyniki.push(10) -g.pozycja.x = 5 -print(g) // Gracz(imie: "Ola", zycia: 2, wyniki: [10], pozycja: Punkt(x: 5, y: 0)) +p.zycia -= 1 +p.wyniki.push(10) +p.poz.x = 5 +print(p) // Gracz(imie: "Ola", zycia: 2, wyniki: [10], poz: Punkt(x: 5, y: 0)) ``` -- Przypisanie albo przekazanie struktury ją kopiuje, razem z jej tablicami i tekstami. -- Struktura nie może zawierać samej siebie wprost; dla takiego pola użyj `ptr[Wezel]` albo `[]Wezel` (drzewa, listy). +- Przypisanie albo przekazanie struktury kopiuje ją, razem z jej tablicami i tekstami. +- Struktura nie może bezpośrednio zawierać samej siebie; do tego służy pole `ptr[Wezel]` albo `[]Wezel` (drzewa, listy). ### Metody -Metoda to funkcja należąca do struktury: `fn Struktura.nazwa(...)`. W środku `self` to struktura, -na której ją wywołano, i to jest sama wartość wywołującego, nie kopia: zmiany w `self` zostają. +Metoda to funkcja należąca do struktury: `fn Struktura.nazwa(...)`. W środku `self` to struktura, na której ją +wywołano, i to jest sama wartość wywołującego, a nie kopia: zmiany w `self` zostają. ```c -fn Gracz.wylecz(int ile) { +fn Gracz.ulecz(int ile) { self.zycia += ile } @@ -584,41 +616,41 @@ fn Gracz.zyje() -> bool { return self.zycia > 0 } -g := Gracz(imie: "Ola") -g.wylecz(2) // g.zycia wynosi teraz 5 -if g.zyje() { ... } +p := Gracz(imie: "Ola") +p.ulecz(2) // p.zycia wynosi teraz 5 +if p.zyje() { ... } -druzyna[0].wylecz(1) // na elemencie tablicy, polu, m[klucz], przez ptr[Gracz] ... +druzyna[0].ulecz(1) // na elemencie tablicy, polu, m[klucz], przez ptr[Gracz] ... ``` -- Metoda jest w tym samym pliku (module) co jej struktura i jest dostępna wszędzie, gdzie ta struktura. -- Metody można też dopisywać do **struktur z nagłówków C**: `fn Vector2.length() -> f32 { ... }`. +- Metoda stoi w tym samym pliku (module) co jej struktura i jest dostępna wszędzie tam, gdzie ta struktura. +- Metody można dodawać też do **struktur z nagłówków C**: `fn Vector2.length() -> f32 { ... }`. - Metoda nie może nazywać się tak jak pole. -- Wywołanie metody zmieniającej `self` na zmiennej pętli (`for g in druzyna { g.wylecz(1) }`) to błąd, - bo zmienna pętli jest tylko do odczytu; użyj `for i in 0..druzyna.len { druzyna[i].wylecz(1) }`. -- Tak jak funkcje, metody mogą się nie udać (`-> !`); zobacz [Błędy jako wartości](#błędy-jako-wartości). +- Wywołanie metody zmieniającej `self` na zmiennej pętli (`for p in druzyna { p.ulecz(1) }`) to błąd, bo zmienna pętli + jest tylko do odczytu; użyj `for i in 0..druzyna.len { druzyna[i].ulecz(1) }`. +- Metody, tak jak funkcje, mogą się nie udać (`-> !`); zob. [Błędy jako wartości](#błędy-jako-wartości). --- ## 13. Pamięć: kto co zwalnia -Tablic, tekstów i struktur nie zwalniasz nigdy. Finch robi to za ciebie, w przewidywalnym momencie: -**gdy kończy się blok, do którego wartość należy** (`}`), albo wcześniej przy `return`, `break`, `continue`. +Tablic, tekstów i struktur nigdy nie zwalniasz. Finch robi to za ciebie, w przewidywalnym momencie: +**gdy kończy się blok, do którego wartość należy** (`}`), albo wcześniej, przy `return`, `break`, `continue`. -- Zmienna **jest właścicielem** swojej wartości. Przypisanie kopiuje, więc dwie zmienne nigdy nie dzielą jednej listy. -- Wartość z wywołania funkcji albo wyrażenia jest **przenoszona** tam, gdzie ją zapisujesz, bez kopiowania. -- `return lista` przenosi zmienną poza funkcję, bez kopiowania. -- Parametry funkcji są **pożyczane**: funkcja dostaje wartość wywołującego bez kopii. Jeśli ją zmienia, - Finch najpierw robi jej prywatną kopię. Zauważysz to najwyżej po szybkości. +- Zmienna jest **właścicielem** swojej wartości. Przypisanie ją kopiuje, więc dwie zmienne nigdy nie dzielą jednej listy. +- Wartość z wywołania funkcji albo z wyrażenia jest **przenoszona** tam, gdzie trafia: bez kopii. +- `return lista` przenosi zmienną na zewnątrz funkcji: bez kopii. +- Parametry funkcji są **pożyczane**: funkcja dostaje wartość wywołującego bez kopii. Jeśli funkcja zmienia parametr, + Finch najpierw robi jej prywatną kopię. Zauważysz to najwyżej w szybkości. -Do pamięci zarządzanej samodzielnie (listy wiązane, drzewa, dzielenie między strukturami): +Dla pamięci, którą zarządzasz sam (listy wiązane, drzewa, współdzielenie między strukturami): ```c -wezel := new(Wezel(wartosc: 1)) // wstaw wartość na stertę, dostajesz ptr[Wezel] -defer free(wezel) // free(...) ją oddaje (razem z jej tablicami i tekstami) +wezel := new(Wezel(wartosc: 1)) // wartość na stercie, dostajesz ptr[Wezel] +defer free(wezel) // free(...) oddaje pamięć (i zwalnia tablice i teksty w środku) ``` -Pamięć z C (`malloc`, obiekty bibliotek) zwalniasz funkcjami z C, jak w C. +Pamięć z C (`malloc`, obiekty bibliotek) zwalnia się funkcjami C, tak jak w C. --- @@ -632,8 +664,8 @@ Wskaźnik przechowuje **adres** wartości. Finch zamiast `*` i `&` z C używa s | `&x` | `addr(x)` | adres x (także `addr(a[i])`, `addr(s.pole)`) | | `*p` | `p.value` | wartość, na którą wskazuje p | | `p->pole` | `p.pole` | pole struktury, na którą wskazuje p | -| `p[i]` | `p[i]` | indeksowanie jak w C (bez kontroli zakresu) | -| `NULL` | `null` | wskazuje donikąd | +| `p[i]` | `p[i]` | indeksowanie jak w C (bez sprawdzania zakresu) | +| `NULL` | `null` | nigdzie nie wskazuje | | `void *` | `ptr` | wskazuje na coś nieznanego typu | ```c @@ -642,14 +674,14 @@ fn podwoj(ptr[int] p) { } x := 21 -podwoj(addr(x)) // x to 42 +podwoj(addr(x)) // x wynosi 42 ``` Jeśli struktura ma własne pole o nazwie `value`, to `p.value` na wskaźniku do niej oznacza to pole. -Użycie `.value`, `.pole` albo `[i]` na wskaźniku `null` **zatrzymuje program** z czytelnym komunikatem. +Użycie `.value`, `.pole` albo `[i]` na wskaźniku `null` **zatrzymuje program** jasnym komunikatem. -⚠️ Wskaźnika na zmienną nie wolno używać po zakończeniu jej bloku. Finch tego nie sprawdza. +⚠️ Wskaźnika na zmienną nie wolno używać po zakończeniu bloku tej zmiennej. Finch tego nie sprawdza. --- @@ -657,34 +689,34 @@ Użycie `.value`, `.pole` albo `[i]` na wskaźniku `null` **zatrzymuje program** | Funkcja | Co robi | |---|---| -| `print(a, b, ...)` | Wypisuje wszystkie wartości oddzielone spacjami i przechodzi do nowej linii. Działa z każdym typem, także z tablicami i strukturami. | -| `input()`, `input("pytanie")` | Czyta jedną linijkę wpisaną przez użytkownika (bez znaku nowej linii). Na końcu wejścia daje pusty tekst. | -| `read_file(sciezka)` | Cały plik jako `str`. Jeśli nie da się go przeczytać, zatrzymuje program z czytelnym komunikatem, chyba że obsłużysz to: `read_file(p) or ...`. | -| `write_file(sciezka, tekst)` | Zapisuje (zastępuje) plik; oddaje `true`, jeśli się udało. Z `or` / `try` porażka niesie przyczynę. | +| `print(a, b, ...)` | Wypisuje wszystkie wartości oddzielone spacjami, a na końcu przechodzi do nowej linii. Działa z każdym typem, także z tablicami i strukturami. | +| `input()`, `input("pytanie")` | Wczytuje jedną linię wpisaną przez użytkownika (bez znaku nowej linii). Na końcu wejścia zwraca pusty tekst. | +| `read_file(sciezka)` | Cały plik jako `str`. Jeśli pliku nie da się przeczytać, zatrzymuje program jasnym komunikatem, chyba że to obsłużysz: `read_file(p) or ...`. | +| `write_file(sciezka, tekst)` | Zapisuje (zastępuje) plik; zwraca `true`, jeśli się udało. Z `or` / `try` niepowodzenie niesie przyczynę. | | `file_exists(sciezka)` | `true` / `false` | | `delete_file(sciezka)` | Usuwa plik; `true`, jeśli się udało (albo przyczyna, z `or` / `try`). | -| `error(komunikat)` | Porażka, którą zwraca funkcja mogąca się nie udać: `return error("...")`. | -| `shell(polecenie)` | Uruchamia polecenie powłoki i oddaje jego kod wyjścia. | +| `error(komunikat)` | Niepowodzenie, które zwraca funkcja mogąca się nie udać: `return error("...")`. | +| `shell(polecenie)` | Uruchamia polecenie powłoki i zwraca jego kod wyjścia. | | `exit(kod)` | Natychmiast kończy program. | -| `addr(x)` | Wskaźnik na `x`; `addr(funkcja)` daje C wskaźnik na funkcję (callback). | -| `new(wartosc)`, `free(p)` | Ręczna pamięć na stercie (zobacz [Pamięć](#13-pamięć-kto-co-zwalnia)). | -| `int(x)`, `str(x)`, `u8(x)`, … | Zamiany typów (zobacz [Zamiana typów](#zamiana-typów)). `str(x)` działa na każdym typie i daje ten sam tekst, co `print`. `int(tekst) or 0` obsługuje tekst, który nie jest liczbą. | +| `addr(x)` | Wskaźnik na `x`; `addr(funkcja)` daje C wskaźnik na funkcję (funkcję zwrotną). | +| `new(wartosc)`, `free(p)` | Ręczna pamięć na stercie (zob. [Pamięć](#13-pamięć-kto-co-zwalnia)). | +| `int(x)`, `str(x)`, `u8(x)`, … | Zamiany typów (zob. [Zamiana typów](#zamiana-typów)). `str(x)` działa na każdym typie i daje ten sam tekst, który pokazuje `print`. `int(tekst) or 0` obsługuje tekst, który nie jest liczbą. | --- ## 16. Moduły -Program możesz podzielić na pliki. `import ksztalty` wczytuje `ksztalty.fch` z tego samego folderu -(albo z folderu wymienionego w zmiennej środowiskowej `FINCH_PATH`, rozdzielonych `:`). +Program można podzielić na pliki. `import ksztalty` wczytuje `ksztalty.fch` z tego samego katalogu (albo z katalogu +wymienionego w zmiennej środowiskowej `FINCH_PATH`; kilka katalogów oddziela się `:`). ```c // ksztalty.fch -struct Pudlo { +struct Pudelko { float w float h } -fn pole(Pudlo p) -> float { +fn pole(Pudelko p) -> float { return p.w * p.h } ``` @@ -694,45 +726,45 @@ fn pole(Pudlo p) -> float { import ksztalty fn main() { - p := ksztalty.Pudlo(2, 3) + p := ksztalty.Pudelko(2, 3) print(ksztalty.pole(p)) - ksztalty.Pudlo inne // typ z modułu w deklaracji + ksztalty.Pudelko inne // typ z modułu w deklaracji } ``` -- Wszystkiego z modułu używasz z jego nazwą z przodu: `ksztalty.pole`, `ksztalty.Pudlo`. +- Wszystkiego z modułu używa się z nazwą modułu na początku: `ksztalty.pole`, `ksztalty.Pudelko`. - Moduł nie ma `main`. Moduły mogą importować inne moduły. -- Moduły w folderach: `import ui/przycisk` wczytuje `ui/przycisk.fch`, a używasz go jako `przycisk.rysuj()`. -- Inna nazwa: `import ui/przycisk as p` → `p.rysuj()`. Dwa importy nie mogą mieć tej samej nazwy. +- Moduły w katalogach: `import ui/button` wczytuje `ui/button.fch`, a używa się go jako `button.draw()`. +- Pod inną nazwą: `import ui/button as btn` → `btn.draw()`. Dwa importy nie mogą mieć tej samej nazwy. --- -## 17. Biblioteki z C +## 17. Biblioteki C ### Krok 1: zaimportuj nagłówek ```c import "stdio.h" import "GLFW/glfw3.h" // ścieżka w systemowych katalogach nagłówków -import "mojalib.h" // nagłówek obok twojego pliku .fch +import "mojabib.h" // nagłówek obok twojego pliku .fch ``` Finch czyta nagłówek przez **libclang** (parser C z clanga) i udostępnia: - **funkcje**, także te ze zmienną liczbą argumentów, jak `printf`, -- **struktury**, przez wartość i przez wskaźnik (`Vector2`, `SDL_Rect`, `CXCursor`, …), razem z polami, -- **wartości `enum`** i **proste stałe `#define`** (`M_PI`, `GL_COLOR_BUFFER_BIT`), -- **zmienne globalne**, np. `stdout` i `stderr`. +- **struktury**, przez wartość i przez wskaźnik (`Vector2`, `SDL_Rect`, `CXCursor`, …), razem z ich polami, +- **wartości `enum`** i **proste stałe z `#define`** (`M_PI`, `GL_COLOR_BUFFER_BIT`), +- **zmienne globalne**, jak `stdout` i `stderr`. ### Krok 2: dolinkuj bibliotekę ```c -link "glfw" // używa libglfw.so (najpierw pyta pkg-config, jeśli jest) -link "pomocnicze.c" // skompiluj i dołącz własny plik C (obok pliku .fch) +link "glfw" // używa libglfw.so (najpierw pyta pkg-config, jeśli jest zainstalowany) +link "pomocnicze.c" // kompiluje i linkuje twój plik C (obok pliku .fch) link "gotowe.a" // albo plik obiektowy / bibliotekę statyczną ``` -Jeśli zapomnisz linijki `link`, Finch powie dokładnie, czego brakuje: +Jeśli zapomnisz o linii `link`, Finch powie dokładnie, czego brakuje: ``` error: glfwInit, glfwCreateWindow, glfwMakeContextCurrent and 5 more come from "GLFW/glfw3.h", but its library isn't linked @@ -749,15 +781,15 @@ error: glfwInit, glfwCreateWindow, glfwMakeContextCurrent and 5 more come from " | `unsigned …` | `u16`, `u32`, `u64` | | `float`, `double` | `f32`, `f64` | | `_Bool` / `bool` | `bool` | -| `enum` | odpowiadająca liczba całkowita ze znakiem | +| `enum` | odpowiednia liczba całkowita ze znakiem | | `char *` jako **parametr albo wynik** | `str` | -| `char *` w strukturze albo za wskaźnikiem | `ptr[char]` (czytasz przez `str(p)`) | +| `char *` w strukturze albo za wskaźnikiem | `ptr[char]` (czyta się go przez `str(p)`) | | `struct X` przez wartość | `X`, struktura z tymi samymi polami | | `struct X *`, `int *`, … | `ptr[X]`, `ptr[i32]`, … | -| `int arr[16]` w strukturze | `[16]i32`: indeksujesz i używasz `.len` | +| `int tab[16]` w strukturze | `[16]i32`: można go indeksować i użyć `.len` | | `void *`, wskaźniki na funkcje, niekompletne struktury | `ptr` | -### Przykład: struktury i raylib +### Przykład: struktury i funkcje zwrotne ```c import "raylib.h" @@ -775,15 +807,15 @@ fn main() { } ``` -Żeby dać C funkcję do wywołania zwrotnego (np. porównywarkę dla `qsort`), użyj `addr(mojaFunkcja)`. +Żeby dać C funkcję do wywołania (jak funkcję porównującą dla `qsort`), użyj `addr(mojaFunkcja)`. Jej parametry muszą być typami C (liczby, wskaźniki, struktury C). ### Grafika: OpenGL i Vulkan Oba działają bezpośrednio; `examples/opengl.fch` i `examples/vulkan.fch` to kompletne programy. -- **OpenGL 3+** na Linuksie: nowoczesne funkcje (`glCreateShader`, `glGenVertexArrays`, …) są deklarowane - dopiero po zdefiniowaniu `GL_GLEXT_PROTOTYPES`. Finch nie definiuje makr C, więc dołączenia umieść w małym +- **OpenGL 3+** na Linuksie: nowoczesne funkcje (`glCreateShader`, `glGenVertexArrays`, …) są zadeklarowane tylko + wtedy, gdy zdefiniowane jest `GL_GLEXT_PROTOTYPES`. Finch nie definiuje makr C, więc umieść te `#include` w malutkim nagłówku obok programu i zaimportuj go: ```c @@ -794,21 +826,21 @@ Oba działają bezpośrednio; `examples/opengl.fch` i `examples/vulkan.fch` to k #include ``` - Potem `import "opengl.h"`, `link "glfw"`, `link "GL"`. Parametry wyjściowe przekazujesz przez `addr(...)` - (`glGenBuffers(1, addr(vbo))`), dane bufora przez `.ptr` (`vertices.ptr`), a przesunięcia w bajtach przez - `ptr(...)` (`glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, stride, ptr(8))`). -- **Vulkan**: `import "vulkan/vulkan.h"` i `link "vulkan"`. Struktury „create info” budujesz z nazwanymi polami - (brakujące są zerami), uchwyty to `ptr`, a listy pobierasz typowym wzorcem „dwóch wywołań”: raz po liczbę, - raz z `lista.ptr` po `lista.resize(liczba)`. Makra-funkcje, jak `VK_MAKE_API_VERSION`, nie są dostępne; - policz liczbę sam (`1 << 22` to Vulkan 1.0). + Potem `import "opengl.h"`, `link "glfw"`, `link "GL"`. Parametry wyjściowe przekazuje się przez `addr(...)` + (`glGenBuffers(1, addr(vbo))`), dane bufora przez `.ptr` (`wierzcholki.ptr`), a przesunięcia w bajtach przez + `ptr(...)` (`glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, krok, ptr(8))`). +- **Vulkan**: `import "vulkan/vulkan.h"` i `link "vulkan"`. Struktury „create info” buduje się przez nazwy pól + (brakujące są zerami), uchwyty to `ptr`, a listy pobiera się zwykłym sposobem z dwoma wywołaniami: raz po liczbę, + drugi raz z `lista.ptr` po `lista.resize(liczba)`. Makra z parametrami, takie jak `VK_MAKE_API_VERSION`, nie są + dostępne; zamiast nich policz liczbę (`1 << 22` to Vulkan 1.0). -### Co jeszcze nie działa +### Czego jeszcze nie ma -- **Unie** C i struktury z **polami bitowymi** przekazywane do funkcji C albo zwracane z niej **przez wartość** - (wskaźniki do nich działają, unie jako zmienne też: `SDL_Event e`, `e.type`, `e.key.key`, `SDL_PollEvent(addr(e))`). -- **Makra-funkcje** (`#define MAX(a, b) ...`). Makra będące liczbą, tekstem, wyrażeniem z nich - (`(1u << 3)`, `((Uint64)1)`, `(A | B)`) albo strukturą z liczb (`RAYWHITE` z raylib, `(Color){ 245, 245, 245, 255 }`) działają. -- `long double`, liczby 128-bitowe. +- **Unii** C i struktur z **polami bitowymi** przekazywanych do funkcji C albo z niej zwracanych **przez wartość** + (wskaźniki na nie działają, unie jako zmienne też: `SDL_Event e`, `e.type`, `e.key.key`, `SDL_PollEvent(addr(e))`). +- **Makr z parametrami** (`#define MAX(a, b) ...`). Działają makra będące liczbą, tekstem, wyrażeniem z nich + (`(1u << 3)`, `((Uint64)1)`, `(A | B)`) albo strukturą liczb (`RAYWHITE` z raylib, `(Color){ 245, 245, 245, 255 }`). +- `long double` i liczb 128-bitowych. --- @@ -816,12 +848,12 @@ Oba działają bezpośrednio; `examples/opengl.fch` i `examples/vulkan.fch` to k ### Błędy jako wartości -Niektóre porażki są normalne: użytkownik wpisze `abc`, pliku nie ma. Finch obsługuje je bez wyjątków -i bez ukrytych skoków. Funkcja, która może się nie udać, mówi to znakiem `!` po typie wyniku i kończy się -porażką przez `return error("komunikat")`: +Niektóre niepowodzenia są normalne: użytkownik wpisze `abc`, pliku brakuje. Finch obsługuje je bez wyjątków i bez +ukrytych skoków. Funkcja, która może się nie udać, mówi o tym znakiem `!` po typie wyniku, a niepowodzenie zgłasza +przez `return error("komunikat")`: ```c -fn port_z_tekstu(str tekst) -> int! { +fn parsuj_port(str tekst) -> int! { n := int(tekst) or { return error("'" + tekst + "' to nie liczba") } if n < 1 || n > 65535 { return error("port " + str(n) + " jest poza zakresem") @@ -829,23 +861,23 @@ fn port_z_tekstu(str tekst) -> int! { return n } -fn zapisz(str sciezka, str tekst) -> ! { // może się nie udać, nic nie oddaje +fn zapisz(str sciezka, str tekst) -> ! { // może się nie udać, niczego nie zwraca try write_file(sciezka, tekst) } ``` -Wywołujący **musi** obsłużyć porażkę, na jeden z trzech sposobów (zapomnienie to błąd kompilacji): +Wywołujący **musi** obsłużyć niepowodzenie, na jeden z trzech sposobów (zapomnienie to błąd kompilacji): -| Kod | W razie porażki | +| Kod | Gdy się nie uda | |---|---| -| `port := port_z_tekstu(s) or 8080` | użyj tej wartości (liczonej tylko przy porażce; może to być kolejne wywołanie z `or`) | -| `port := port_z_tekstu(s) or { print(err); return }` | wykonaj blok; `err` to komunikat (`str`). Blok musi wyjść (`return`, `break`, `continue`, `exit`), chyba że wynik nie jest używany. | -| `port := try port_z_tekstu(s)` | zakończ bieżącą funkcję tym samym błędem (musi też móc się nie udać) | +| `port := parsuj_port(s) or 8080` | użyj tej wartości (jest obliczana tylko przy niepowodzeniu; może to być kolejne wywołanie z `or`) | +| `port := parsuj_port(s) or { print(err); return }` | wykonaj blok; `err` to komunikat (`str`). Blok musi wyjść (`return`, `break`, `continue`, `exit`), chyba że wynik nie jest używany. | +| `port := try parsuj_port(s)` | przerwij bieżącą funkcję z tym samym błędem (ona też musi móc się nie udać) | ```c fn wczytaj(str sciezka) -> Konfig! { tekst := try read_file(sciezka) - port := try port_z_tekstu(tekst.trim()) + port := try parsuj_port(tekst.trim()) return Konfig(port: port) } @@ -855,12 +887,11 @@ fn main() -> ! { // main też może się nie udać: błąd zostan } ``` -Funkcje wbudowane, które mogą się nie udać, działają tak samo: `int(tekst)`, `float(tekst)`, `read_file`, -`write_file`, `delete_file`. Bez `or` / `try` zachowują się jak wcześniej (`int("x")` zatrzymuje program, -`write_file` oddaje `bool`). +Tak samo działają wbudowane funkcje, które mogą się nie udać: `int(tekst)`, `float(tekst)`, `read_file`, `write_file`, +`delete_file`. Bez `or` / `try` zachowują się jak dawniej (`int("x")` zatrzymuje program, `write_file` zwraca `bool`). -Pod spodem funkcja, która może się nie udać, zwraca wartość z flagą i komunikatem: bez alokacji przy -sukcesie, bez zwijania stosu, a porażka kosztuje tyle, co zbudowanie komunikatu. +Pod spodem taka funkcja zwraca wartość razem z flagą i komunikatem: przy powodzeniu nic nie jest przydzielane, +nie ma odwijania stosu, a niepowodzenie kosztuje tyle, co zbudowanie komunikatu. ### Błędy kompilacji @@ -870,12 +901,11 @@ gra.fch:12:9: error: 'wynik' must be int, but this is str | ^ ``` -Format: `plik:linijka:kolumna: error: wyjaśnienie`, a pod spodem linijka ze znakiem `^`. -Komunikaty są po angielsku. +Format: `plik:linia:kolumna: error: wyjaśnienie`, a pod spodem linia z `^` pod miejscem błędu. ### Błędy w trakcie działania -Zamiast niezdefiniowanego zachowania (jak w C) Finch zatrzymuje się z komunikatem i kodem wyjścia 1: +Zamiast niezdefiniowanego zachowania (jak w C) Finch zatrzymuje program z komunikatem i kodem wyjścia 1: | Komunikat | Przyczyna | |---|---| @@ -887,29 +917,29 @@ Zamiast niezdefiniowanego zachowania (jak w C) Finch zatrzymuje się z komunikat | `runtime error: can't turn "x" into int` | `int(...)` / `float(...)` na tekście, który nie jest liczbą | | `runtime error: can't read the file "…"` | `read_file` na brakującym albo nieczytelnym pliku (obsłuż to przez `or`) | | `runtime error: the key "x" is not in the map …` | `m["x"]`, gdy mapa nie ma takiego klucza (użyj `.has` albo `.get`) | -| `runtime error: called .f() on a null pointer` | metoda wywołana przez `null` w `ptr[T]` | +| `runtime error: called .f() on a null pointer` | metoda wywołana przez `ptr[T]` równy `null` | | `runtime error: assert failed: …` | `assert(warunek, "…")` z fałszywym warunkiem | -Pod komunikatem jest łańcuch wywołań, które do niego doprowadziły, od najgłębszego: +Pod komunikatem jest ścieżka wywołań, które do niego doprowadziły, od najgłębszego: ``` gra.fch:6: runtime error: index 3 is out of range (the length is 2) - in Enemy.next (gra.fch:6) - in step (gra.fch:10) + in Wrog.nastepny (gra.fch:6) + in krok (gra.fch:10) in main (gra.fch:15) ``` -`finch run`, `finch test` oraz budowanie z `-g` i `-O0` go mają; zwykły `finch build` go pomija, bo pilnowanie go kosztuje -czas w każdym wywołaniu (ok. 3 razy w programie złożonym z samych drobnych wywołań, dużo mniej w prawdziwym). `--trace` go dodaje. +`finch run`, `finch test` oraz kompilacje z `-g` i `-O0` ją pokazują; zwykłe `finch build` jej nie dodaje, bo +pamiętanie jej kosztuje czas przy każdym wywołaniu (ok. 3 razy wolniej w programie złożonym wyłącznie z drobnych +wywołań, w prawdziwym programie dużo mniej). `--trace` ją włącza. --- ## 19. Edytory: VS Code, Kate i inne -Wszystkie funkcje edytorów pochodzą z jednego serwera języka, `finch lsp`, który używa prawdziwego -kompilatora: błędy na bieżąco, typy i opisy po najechaniu myszą, idź do definicji, podpowiedzi (pola, -metody, metody map i tablic po `.`, zawartość modułów, nazwy z nagłówków C), konspekt pliku i podpowiedzi -parametrów. +Wszystkie funkcje edytorów pochodzą z jednego serwera języka, `finch lsp`, który korzysta z prawdziwego kompilatora: +błędy w trakcie pisania, typy i opisy po najechaniu kursorem, przejście do definicji, podpowiedzi (pola, metody, +metody map i tablic po `.`, zawartość modułów, nazwy z nagłówków C), spis zawartości pliku i podpowiedzi parametrów. ### Kate (oraz KWrite, KDevelop) @@ -917,20 +947,20 @@ parametrów. editors/kate/install.sh ``` -Instaluje podświetlanie (`~/.local/share/org.kde.syntax-highlighting/syntax/finch.xml`) i dopisuje `finch` -do ustawień klienta LSP w Kate, zachowując twoje pozostałe serwery. Uruchom Kate ponownie i włącz wtyczkę +Skrypt instaluje podświetlanie składni (`~/.local/share/org.kde.syntax-highlighting/syntax/finch.xml`) i dopisuje +`finch` do ustawień klienta LSP w Kate, nie ruszając twoich innych serwerów. Uruchom Kate ponownie i włącz wtyczkę **LSP Client**. Żeby uruchamiać programy, dodaj `finch run %f` jako cel we wtyczce **Build & Run**; komunikaty -Fincha `plik:linia:kolumna: error:` są wtedy klikalne. Szczegóły: `editors/kate/README.md`. +`plik:linia:kolumna: error:` będą wtedy klikalne. Szczegóły: `editors/kate/README.md`. ### VS Code Rozszerzenie **Finch** (`editors/vscode`, dołączane też do każdego wydania jako plik `.vsix`) daje: -podświetlanie składni, błędy na bieżąco, podpowiedzi (także pola i metody po `.`), typy po najechaniu -myszą, idź do definicji (F12), konspekt pliku, podpowiedzi parametrów, szablony i przycisk ▶ **Run** -(`Ctrl+F5`). Błędy z **Finch: Build This File** trafiają do panelu Problems. +podświetlanie, błędy w trakcie pisania, podpowiedzi (także pól i metod po `.`), typy po najechaniu kursorem, +przejście do definicji (F12), spis zawartości pliku, podpowiedzi parametrów, gotowe fragmenty kodu i przycisk +▶ **Run** (`Ctrl+F5`). Błędy z **Finch: Build This File** trafiają do panelu Problems. -Instalacja: Rozszerzenia → `…` → *Install from VSIX…* → wybierz `finch-lang-*.vsix`. Rozszerzenie uruchamia -`finch` z PATH; jeśli jest gdzie indziej, ustaw **Finch: Path** w ustawieniach. +Instalacja: Extensions → `…` → *Install from VSIX…* → wybierz `finch-lang-*.vsix`. Rozszerzenie uruchamia `finch` +z PATH; jeśli leży gdzie indziej, ustaw **Finch: Path** w ustawieniach. ### Inne @@ -944,14 +974,14 @@ finch build gra.fch -g -O0 -o gra gdb ./gra (gdb) break gra.fch:12 (gdb) run -(gdb) bt # gdzie jesteśmy, przez które funkcje +(gdb) bt # gdzie jesteśmy i przez które funkcje (gdb) info locals # wszystkie zmienne (gdb) print gracz.zycia ``` -`-g` dodaje informacje dla debuggera (linijki, funkcje, zmienne, pola struktur). `-O0` zostawia wszystkie -zmienne widoczne; bez niego optymalizator może część z nich usunąć. Na Windowsie `-g` zapisuje format -CodeView, który czyta debugger Visual Studio i WinDbg. +`-g` dodaje informacje dla debuggera (linie, funkcje, zmienne, pola struktur, wektory). `-O0` zostawia widoczną +każdą zmienną; bez tego optymalizator może niektóre usunąć. Na Windowsie to też jest DWARF: użyj `lldb` z llvm-mingw +albo gdb. --- @@ -960,73 +990,51 @@ CodeView, który czyta debugger Visual Studio i WinDbg. | Problem | Rozwiązanie | |---|---| | `can't find the C header 'x.h'` | Brakuje plików deweloperskich biblioteki. Zainstaluj je (Debian: `libx-dev`; Nix: dopisz do `shell.nix`). | -| `… come from "x.h", but its library isn't linked` | Dopisz `link "nazwa"` na górze pliku. | -| `the library 'x' wasn't found` | Nie jest zainstalowana albo nazwa jest zła. Na Niksie dopisz ją do `shell.nix` i pracuj w `nix-shell`. | -| `the C function 'f' can't be used from Finch yet` | Używa unii, pól bitowych albo innego nieobsługiwanego typu przez wartość. Poszukaj wariantu ze wskaźnikami. | +| `… come from "x.h", but its library isn't linked` | Dodaj `link "nazwa"` na początku pliku. | +| `the library 'x' wasn't found` | Biblioteka nie jest zainstalowana albo nazwa jest błędna. Na Niksie dopisz ją do `shell.nix` i pracuj w `nix-shell`. | +| `the C function 'f' can't be used from Finch yet` | Używa przez wartość unii, pól bitowych albo innego nieobsługiwanego typu. Poszukaj wariantu, który przyjmuje wskaźniki. | +| `--cpu native`: na innym komputerze program kończy się `Illegal instruction` | Został zbudowany pod procesor, na którym go budowano. Zbuduj bez `--cpu` albo ze starszym (`x86-64-v2`). | | `can't find the module 'x'` | Połóż `x.fch` obok importującego pliku albo ustaw `FINCH_PATH`. | -| `couldn't build the Finch runtime` | Brak działającego kompilatora C. Zainstaluj gcc lub clang albo ustaw `CC`. | -| Biblioteka nie znajduje się przy starcie programu | Zlinkowano ją z folderu, którego system nie przeszukuje. Uruchamiaj w tym samym `nix-shell` albo zainstaluj ją systemowo. | -| Windows: `finch` nie linkuje (błędy `LNK…` o `libcmt`, `kernel32`) | Zainstaluj Visual Studio Build Tools z *Desktop development with C++*. | +| `couldn't build the Finch runtime` | Brak działającego kompilatora C. Zainstaluj gcc albo clang, albo ustaw `CC`. | +| Program po uruchomieniu nie znajduje biblioteki | Została dolinkowana z katalogu, którego system nie przeszukuje. Uruchamiaj w tym samym `nix-shell` albo zainstaluj ją w systemie. | +| Windows: `no C compiler found to link with ('clang')` | Dodaj katalog `bin` z llvm-mingw do PATH (rozdział 2). | | Windows: `finch.exe` się nie uruchamia (brak `libclang.dll`) | Trzymaj `libclang.dll` obok `finch.exe`. | | macOS: `finch` się nie uruchamia (`Library not loaded: …libLLVM…`) | `brew install llvm@21`. | | macOS: `can't find the C header 'stdio.h'` | Zainstaluj narzędzia wiersza poleceń: `xcode-select --install`. | | `--target arm64`: `no C compiler found to link with` | Zainstaluj `aarch64-linux-gnu-gcc` (Debian: `gcc-aarch64-linux-gnu`) albo ustaw `FINCH_CC`. | -| `clang` w `nix-shell` nie widzi `stdio.h` | Masz stary `shell.nix`: `llvmPackages.clang` musi być przed `llvmPackages.libclang`. | +| `clang` w `nix-shell` nie znajduje `stdio.h` | Masz stary `shell.nix`: `llvmPackages.clang` musi stać przed `llvmPackages.libclang`. | | Program nigdy się nie kończy | Warunek pętli nigdy nie staje się fałszywy. Naciśnij Ctrl+C. | --- -## 22. Struktura projektu i testy +## 22. Układ repozytorium i testy ``` Finch/ -├── src/ kompilator, w Finchu (lekser, parser, gen: LLVM IR jako tekst, cimport: libclang, lsp, main ...) -├── std/ biblioteka standardowa, w Finchu: json, path, time, proc, fs, thread -├── runtime/ finch_rt.c: mała biblioteka uruchomieniowa (teksty, tablice, mapy, wejście, pliki) -├── bootstrap/ build.sh, install.sh i ziarna (własny IR kompilatora, po jednym na system) -├── examples/ hello, tour, types, structs, todo, guess, c_import, window, opengl, vulkan, raylib, llvm +├── src/ kompilator w Finchu (lexer, parser, gen: LLVM IR jako tekst, cimport: libclang, lsp, main ...) +├── std/ biblioteka standardowa w Finchu: json, path, time, proc, fs, thread +├── runtime/ finch_rt.c: mały runtime (teksty, tablice, mapy, wczytywanie, pliki) +├── bootstrap/ build.sh, install.sh i pliki startowe (własny IR kompilatora, osobno dla każdego systemu) +├── examples/ hello, tour, types, structs, todo, guess, wordcount, c_import, simd, window, opengl, vulkan, raylib, llvm ├── tests/ -│ ├── run/ new/ programy + dokładny wynik (.out) i wejście (.in) -│ ├── fail/ new/fail/ programy, które muszą się nie udać, z oczekiwanym błędem w 1. linijce -│ ├── tool/ finch test, finch fmt, łańcuch wywołań, projekt z finch.toml +│ ├── run/ new/ programy + dokładny wynik, który muszą wypisać (.out), wejście (.in) +│ ├── fail/ new/fail/ programy, które muszą się nie skompilować albo zatrzymać, z oczekiwanym błędem w 1. linii +│ ├── tool/ finch test, finch fmt, ścieżka wywołań, projekt z finch.toml │ ├── run.sh uruchamia testy (MEMCHECK=1 sprawdza też pamięć valgrindem) -│ ├── cross.sh każdy test zbudowany na Windows / ARM64 i uruchomiony w Wine / QEMU -│ └── lsp_test.py serwer języka, obsługiwany jak przez edytor +│ ├── cross.sh każdy test zbudowany na Windowsa / ARM64 i uruchomiony przez Wine / QEMU +│ └── lsp_test.py serwer języka, z którym test rozmawia tak jak edytor ├── editors/ rozszerzenie VS Code (vscode/), podświetlanie i konfiguracja LSP dla Kate (kate/) -├── docs/ ta dokumentacja (en, pl), łącznie ze ściągą -└── shell.nix środowisko deweloperskie Nix +├── docs/ ta dokumentacja (en, pl), razem ze ściągą +└── shell.nix środowisko programistyczne dla Niksa ``` ```sh -tests/run.sh # programy, odmowy, buildy z -g, biblioteka współdzielona, serwer języka -MEMCHECK=1 tests/run.sh # to samo pod valgrindem: bez wycieków i złych dostępów do pamięci -tests/cross.sh windows # każdy test zbudowany na Windowsa i uruchomiony w Wine -tests/cross.sh arm64 # każdy test zbudowany na Linuksa ARM64 i uruchomiony w QEMU +tests/run.sh # programy, oczekiwane błędy, kompilacje z -g, biblioteka współdzielona, serwer języka +MEMCHECK=1 tests/run.sh # to samo pod valgrindem: bez wycieków i błędnych dostępów do pamięci +tests/cross.sh windows # każdy test zbudowany na Windowsa i uruchomiony przez Wine +tests/cross.sh arm64 # każdy test zbudowany na Linuksa ARM64 i uruchomiony przez QEMU ``` -### Warianty z wartościami - -Wariant może nieść wartości. Tworzysz go przez `Shape.Circle(2.0)`, a wyciągasz `match`-em, który je nazywa: - -```c -enum Shape { - Circle(float) - Rect(float, float) - Point -} - -fn Shape.area() -> float { - return match self { - Circle(r) -> 3.14 * r * r - Rect(w, h) -> w * h - Point -> 0.0 - } -} -``` - -`_` pomija wartość. `match` musi obsłużyć każdy wariant (albo kończyć się `else`). Wypisanie pokazuje `Rect(2, 3.5)`. Taki enum -jest kopiowany i zwalniany jak struktura; nie da się go porównać przez `==` (użyj `match`). Warianty takich enumów nie mają numerów. - --- ## 23. Enumy i match @@ -1035,22 +1043,22 @@ jest kopiowany i zwalniany jak struktura; nie da się go porównać przez `==` ( ``` enum Kolor { Czerwony, Zielony, Niebieski } -enum Status { Ok = 200, Brak = 404 } // wariantowi można nadać numer +enum Status { Ok = 200, NotFound = 404 } // wariant może dostać liczbę, jeśli jej potrzebuje k := Kolor.Zielony print(k) // Zielony -print(int(Status.Brak)) // 404 -print(Kolor(2)) // Niebieski: liczba zamieniona na enum (sprawdzana: Kolor(7) to błąd) +print(int(Status.NotFound)) // 404 +print(Kolor(2)) // Niebieski: liczba zamieniona na enum (sprawdzane: Kolor(7) to błąd) ``` -Warianty pisze się `Kolor.Czerwony`. Można je porównywać przez `==` i `!=` (nie przez `<`), używać jako klucze map, -pola struktur i elementy tablic, a enum może mieć metody jak struktura: `fn Kolor.nastepny() -> Kolor { ... }`. +Warianty zapisuje się `Kolor.Czerwony`. Można je porównywać przez `==` i `!=` (nie przez `<`), używać jako kluczy map, +pól struktur i elementów tablic, a enum może mieć metody tak jak struktura: `fn Kolor.nastepny() -> Kolor { ... }`. -**match** wybiera, co zrobić, na podstawie wartości — zamiast drabinki `if`: +**match** wybiera, co zrobić, na podstawie wartości, zamiast drabinki `if`: ``` match k { - Czerwony -> print("ciepły") // w matchu na enumie wystarczy sama nazwa + Czerwony -> print("ciepły") // w match na enumie wystarczy sama nazwa Zielony, Niebieski -> print("zimny") // kilka wartości w jednej gałęzi } @@ -1065,17 +1073,41 @@ match slowo { print("pa") return } - else -> print("co?") + else -> print("słucham?") } ``` -`match` działa na enumach, liczbach całkowitych, znakach, tekście i bool. Gałęzie sprawdzane są po kolei. Match na -enumie musi obsłużyć **każdy wariant** albo kończyć się `else` (kompilator wskaże brakujące); każdy inny match -potrzebuje `else`. W wyrażeniu wszystkie gałęzie dają ten sam typ. +`match` działa na enumach, liczbach całkowitych, znakach, tekstach i wartościach bool. Gałęzie są sprawdzane po kolei. +`match` na enumie musi obsłużyć **każdy wariant** albo kończyć się `else` (kompilator mówi, których brakuje); każdy inny +`match` potrzebuje `else`. W wyrażeniu każda gałąź daje wartość tego samego typu. + +### Warianty z wartościami + +Wariant może przenosić wartości. Tworzy się go przez `Ksztalt.Kolo(2.0)`, a wartości wyjmuje przez `match`, który je nazywa: + +```c +enum Ksztalt { + Kolo(float) + Prostokat(float, float) + Punkt +} + +fn Ksztalt.pole() -> float { + return match self { + Kolo(r) -> 3.14 * r * r + Prostokat(w, h) -> w * h + Punkt -> 0.0 + } +} +``` + +`_` pomija wartość. `match` musi obsłużyć każdy wariant (albo kończyć się `else`). Wypisanie pokazuje `Prostokat(2, 3.5)`. +Taki enum kopiuje się i zwalnia jak struktura; nie da się go porównać przez `==` (użyj `match`). Warianty takich enumów +nie mogą mieć liczb. ## 24. Typy generyczne -Napisz funkcję lub strukturę raz dla dowolnego typu, z typem jako nazwą w nawiasach kwadratowych: +Funkcję albo strukturę można napisać raz dla dowolnego typu, podając typ jako nazwę w nawiasach kwadratowych: ``` fn max[T](T a, T b) -> T { @@ -1089,73 +1121,72 @@ struct Stos[T] { []T elementy } -fn Stos[T].dodaj(T x) { +fn Stos[T].push(T x) { self.elementy.push(x) } -print(max(3, 9), max("jabłko", "gruszka")) // T wynika z argumentów -print(max[float](1, 2.5)) // albo zapisane wprost, gdzie nie da się go ustalić -s := Stos[int]() // generyczną strukturę zawsze pisze się z typem -s.dodaj(7) +print(max(3, 9), max("jabłko", "gruszka")) // T wynika z argumentów +print(max[float](1, 2.5)) // albo jest podany, gdy nie da się go ustalić +s := Stos[int]() // struktura generyczna zawsze ma podany typ +s.push(7) ``` -Parametrów może być kilka (`struct Para[A, B]`), typy można zagnieżdżać (`Stos[Para[int, str]]`). Każde użycie tworzy -własną kopię kodu, więc generyk jest tak szybki jak kod napisany ręcznie. Nie ma ograniczeń: jeśli `T` nie potrafi -tego, co robi funkcja (`a > b` na strukturze), błąd mówi o tym i podaje, którą wersję budowano, np. -`(while building wiekszy[Punkt])`. +Działa kilka parametrów (`struct Para[A, B]`) i typy zagnieżdżone (`Stos[Para[int, str]]`). Każde użycie tworzy własną +kopię kodu, więc typ generyczny jest tak szybki jak kod napisany ręcznie. Nie ma ograniczeń na typ: jeśli `T` nie potrafi +tego, czego wymaga funkcja (`a > b` na strukturze), błąd o tym mówi i podaje, którą wersję budował, np. +`(while building bigger[Punkt])`. ## 25. Biblioteka standardowa -Moduły dołączone do Fincha, napisane w Finchu. `import json` szuka najpierw `json.fch` obok Twojego pliku, potem w -bibliotece — więc Twój własny plik o tej nazwie ma pierwszeństwo. +Moduły dostarczane z Finchem, napisane w Finchu. `import json` najpierw szuka `json.fch` obok twojego pliku, a dopiero +potem w bibliotece, więc twój plik o tej samej nazwie ma pierwszeństwo. | Moduł | Co robi | |---|---| -| `path` | `join`, `base`, `dir`, `ext`, `stem`, `parts`, `clean`, `is_abs`: ścieżki jako tekst, rozdzielone `/` | -| `time` | `now()`, `millis()` (zegar do mierzenia), `sleep(ms)`, `date(t)`, `local(t)`, `format(t, "%Y-%m-%d")`, `days_in_month` | -| `proc` | `run(cmd)` (kod wyjścia), `output(cmd)` (co wypisało; błąd, jeśli się nie udało), `env(nazwa, domyślna)`, `pid()`, `cwd()` | -| `json` | `parse(tekst)`, `stringify(v)`, `pretty(v)` oraz typ wartości `Json` z `get`, `at`, `set`, `push`, `has`, `str_or`, `int_or` ... | -| `fs` | `read_bytes(p)` / `write_bytes(p, dane)` (`[]u8`, dowolne bajty), `exists`, `is_dir`, `mkdir` (z folderami wyżej), `list` (posortowane nazwy), `remove_dir`, `data_dir(app)` (folder na zapisy użytkownika) | +| `path` | `join`, `base`, `dir`, `ext`, `stem`, `parts`, `clean`, `is_abs`: ścieżki jako tekst, rozdzielane `/` | +| `time` | `now()`, `millis()` (zegar do mierzenia czasu), `sleep(ms)`, `date(t)`, `local(t)`, `format(t, "%Y-%m-%d")`, `days_in_month` | +| `proc` | `run(polecenie)` (kod wyjścia), `output(polecenie)` (to, co wypisało; błąd, jeśli się nie udało), `env(nazwa, domyslna)`, `pid()`, `cwd()` | +| `json` | `parse(tekst)`, `stringify(v)`, `pretty(v)` i typ wartości `Json` z `get`, `at`, `set`, `push`, `has`, `str_or`, `int_or` ... | +| `fs` | `read_bytes(p)` / `write_bytes(p, dane)` (`[]u8`, dowolne bajty), `exists`, `is_dir`, `mkdir` (razem z katalogami nadrzędnymi), `list` (posortowane nazwy), `remove_dir`, `data_dir(aplikacja)` (katalog na zapisy użytkownika) | | `thread` | `spawn(addr(praca), addr(dane))`, `t.join()`, `mutex()` z `lock`, `unlock`, `close`, `cpus()` (rozdział 29) | -`min(a, b, ...)`, `max(a, b, ...)`, `abs(x)` i `clamp(x, od, do)` działają na dowolnych liczbach bez importu (twoja -własna funkcja o tej samej nazwie wygrywa). +`min(a, b, ...)`, `max(a, b, ...)`, `abs(x)` i `clamp(x, lo, hi)` działają na dowolnych liczbach (i wektorach) +bez importu (twoja funkcja o tej samej nazwie ma pierwszeństwo). ``` import json import path dok := try json.parse(read_file("config.json")) -nazwa := dok.get("name").str_or("bez nazwy") // brakujący klucz to null, więc łańcuch nigdy się nie psuje +nazwa := dok.get("name").str_or("bez nazwy") // brakujący klucz to null, więc łańcuch wywołań się nie wysypie dok.set("name", json.new_text(nazwa + "!")) write_file(path.join("out", "config.json"), json.pretty(dok)) ``` ## 26. Obecne ograniczenia -Jeszcze nie ma: +Czego jeszcze nie ma: - Interfejsów. -- Tekstu świadomego Unicode: `.len`, `s[i]` i `upper()` działają na bajtach / ASCII. -- Pilnowania, żeby dwa wątki nie zmieniały tej samej tablicy naraz (do tego mutex). -- Windowsa na ARM64, systemów 32-bitowych, WebAssembly. `--target android` robi kod dla Androida, ale linkowanie - z NDK i uruchomienie na urządzeniu nie były jeszcze próbowane. +- Tekstów świadomych Unicode: `.len`, `s[i]` i `upper()` działają na bajtach / ASCII. +- Zabezpieczenia przed zmienianiem tej samej tablicy przez dwa wątki naraz (użyj muteksu). +- Windowsa na ARM64, systemów 32-bitowych, WebAssembly. ### Ostrzeżenia o dużych kopiach -Struktura z 256 lub więcej bajtami zwykłych danych (liczby, struktury z liczb, `[N]T` z C) jest kopiowana bajt po bajcie, -gdy jest parametrem (`fn f(Cialo c)`) i w `for x in ciala`. Finch o tym mówi, a program i tak buduje: +Struktura z co najmniej 256 bajtami zwykłych danych (liczb, struktur z liczbami, `[N]T` z C) jest kopiowana bajt po +bajcie, gdy jest parametrem (`fn f(Cialo c)`) i w `for x in ciala`. Finch o tym ostrzega, ale i tak buduje program: ``` -gra.fch:27:11: warning: 'c' is a Cialo (264 bytes), copied on every call to waga; take ptr[Cialo] to share it instead (callers can still pass the variable as it is) -gra.fch:45:5: warning: each Cialo (264 bytes) is copied into 'x'; for i in 0..ciala.len with ciala[i] works on it in place +gra.fch:27:11: warning: 'b' is a Body (264 bytes), copied on every call to weight; take ptr[Body] to share it instead (callers can still pass the variable as it is) +gra.fch:45:5: warning: each Body (264 bytes) is copied into 'x'; for i in 0..bodies.len with bodies[i] works on it in place ``` -Teksty, tablice i mapy w strukturze się nie liczą: przekazanie struktury kopiuje tylko ich uchwyty, nie zawartość. -`b := a` nie dostaje ostrzeżenia: w kodzie widać, że to kopia. Metoda nigdy nie kopiuje `self`. Edytory pokazują +Teksty, tablice i mapy w strukturze się nie liczą: przekazanie struktury kopiuje tylko ich uchwyty, a nie zawartość. +`b := a` nie daje ostrzeżenia, bo w kodzie widać, że to kopia. Metoda nigdy nie kopiuje `self`. Edytory pokazują ostrzeżenia na żółto. -Plan rozwoju jest w [README](../../README.pl.md#plan-rozwoju). +Zob. [plan rozwoju w README](../../README.pl.md#plan-rozwoju). ## 27. Testy: bloki test i finch test @@ -1172,33 +1203,35 @@ test "obrażenia nigdy nie schodzą poniżej zera" { } ``` -`finch test gra.fch` uruchamia każdy blok `test` z pliku i z modułów, które importuje (bez biblioteki standardowej), -każdy osobno: nieudany `assert` albo dowolny błąd w czasie działania kończy tylko ten test, a następny rusza dalej. +`finch test gra.fch` uruchamia każdy blok `test` z tego pliku i z importowanych modułów (ale nie z biblioteki +standardowej), każdy osobno: nieudany `assert` albo dowolny błąd w trakcie działania kończy tylko ten jeden test, +a następny rusza dalej. ``` ok obrażenia nigdy nie schodzą poniżej zera -FAIL wczytanie poziomu - poziom.fch:31: runtime error: can't read the file "levels/1.txt" +FAIL wczytywanie poziomu + poziom.fch:31: runtime error: can't read the file "poziomy/1.txt" 1 passed, 1 failed ``` -Kod wyjścia to 1, gdy jakiś test nie przeszedł, więc CI może z tego korzystać. `finch run` i `finch build` pomijają bloki test. -`assert(warunek)` działa też poza testami: zatrzymuje program z `assert failed`, gdy warunek jest fałszywy. +Gdy któryś test się nie uda, kod wyjścia to 1, więc CI może z tego korzystać. `finch run` i `finch build` pomijają +bloki test. `assert(warunek)` działa też poza testami: zatrzymuje program komunikatem `assert failed`, gdy warunek jest fałszywy. ## 28. Projekt: finch.toml -`finch.toml` obok kodu mówi raz to, co inaczej trzeba by podawać w każdym poleceniu: +Plik `finch.toml` obok kodu zapisuje raz to, co inaczej trzeba by podawać przy każdym poleceniu: ```toml -name = "gra" # program, który robi finch build (z entry) -entry = "src/main.fch" # gdy finch run / build / test nie dostaną pliku -paths = ["src", "lib"] # foldery z modułami Fincha -include = ["vendor/SDL3/include"] # foldery z nagłówkami C (też dla własnych .c / .cpp) -links = ["SDL3", "native/glue.cpp"] # biblioteki i własne pliki .c / .cpp / .o / .a -libdirs = ["vendor/SDL3/lib"] # foldery z bibliotekami -cflags = ["-DFAST"] # dla własnych plików .c / .cpp +name = "gra" # program, który zbuduje finch build (z pliku entry) +entry = "src/main.fch" # używany, gdy finch run / build / test nie dostaną pliku +paths = ["src", "lib"] # katalogi z modułami Fincha +include = ["vendor/SDL3/include"] # katalogi z nagłówkami C (także dla twoich plików .c / .cpp) +links = ["SDL3", "native/glue.cpp"] # biblioteki i twoje pliki .c / .cpp / .o / .a +libdirs = ["vendor/SDL3/lib"] # katalogi z bibliotekami +cflags = ["-DFAST"] # dla twoich plików .c / .cpp ldflags = ["-Wl,-rpath,$ORIGIN"] # dla linkera +cpu = "native" # to samo co --cpu native (rozdział 31) [target.windows] # dodawane tylko przy budowaniu na Windowsa links = ["ws2_32"] @@ -1208,8 +1241,8 @@ libdirs = ["vendor/SDL3/lib/x64"] libdirs = ["vendor/SDL3/lib/arm64"] ``` -Foldery liczą się od `finch.toml`. Finch szuka go w folderze pliku i w folderach wyżej, więc -`finch run src/narzedzia/edytor.fch` wewnątrz projektu też z niego korzysta. Czytana jest tylko taka część TOML: +Ścieżki są liczone względem `finch.toml`. Finch szuka go w katalogu pliku i w katalogach nad nim, więc +`finch run src/narzedzia/edytor.fch` w środku projektu też z niego skorzysta. Z formatu TOML czytane jest tylko tyle: `[sekcje]`, `klucz = "tekst"`, `klucz = ["a", "b"]` (także w kilku liniach), liczby, `true`/`false` i komentarze `#`. ## 29. Wątki @@ -1222,10 +1255,10 @@ struct Kawalek { float szczyt } -fn analizuj(ptr arg) { // funkcja wątku bierze jeden ptr +fn analizuj(ptr arg) { // funkcja wątku przyjmuje jeden ptr ptr[Kawalek] k = arg - for s in k.probki { - k.szczyt = max(k.szczyt, abs(s)) + for p in k.probki { + k.szczyt = max(k.szczyt, abs(p)) } } @@ -1239,65 +1272,143 @@ fn main() { }) } for i in 0..dzialajace.len { - dzialajace[i].join() // poczekaj na wszystkie + dzialajace[i].join() // czekamy na wszystkie } } ``` -To, co dostaje wątek, jest współdzielone, nie kopiowane, więc: +To, co dostaje wątek, jest współdzielone, a nie kopiowane, więc: -- musi żyć do `join()` (tu `kawalki` żyje do końca `main`); -- nie zmieniaj tablicy, w którą wskazują wątki (bez `push`), dopóki się nie skończą; -- to, co zmienia więcej niż jeden wątek, idzie pod blokadę albo jest liczbą zmienianą przez `atomic_...`. +- musi istnieć aż do `join()` (tutaj `kawalki` żyje do końca `main`); +- nie zmieniaj tablicy, do której wskazują wątki (żadnego `push`), dopóki nie zostaną połączone; +- to, co zmienia więcej niż jeden wątek, trzeba chronić blokadą albo musi to być liczba zmieniana przez `atomic_...`. ```c m := thread.mutex() defer m.close() m.lock() -dziennik.push("gotowe") // jeden wątek naraz między lock a unlock +dziennik.push("gotowe") // między lock a unlock pracuje tylko jeden wątek naraz m.unlock() atomic_add(addr(gotowe), 1) // gotowe to int wspólny dla wątków n := atomic_load(addr(gotowe)) ``` -`atomic_add`, `atomic_sub`, `atomic_swap` zwracają starą wartość; `atomic_cas(p, stara, nowa)` wstawia `nowa` tylko -wtedy, gdy jest tam `stara`, i mówi, czy to zrobił. `thread.cpus()` mówi, ile wątków komputer wykonuje naraz. +`atomic_add`, `atomic_sub` i `atomic_swap` zwracają poprzednią wartość; `atomic_cas(p, stara, nowa)` wpisuje `nowa` +tylko wtedy, gdy jest tam `stara`, i mówi, czy to zrobiło. `thread.cpus()` mówi, ile wątków komputer wykonuje naraz. ## 30. C++, Rust i Android -Finch rozmawia z innymi językami przez C, jak każdy język. +Finch rozmawia z innymi językami przez C, tak jak każdy język. -**C++**: to, co woła Finch, wystaw przez nagłówek C, z `extern "C"` po stronie C++: +**C++**: to, co ma wołać Finch, wystaw przez nagłówek C, z `extern "C"` po stronie C++: ```cpp // glue.h #ifdef __cplusplus extern "C" { #endif -int physics_step(float dt); +int krok_fizyki(float dt); #ifdef __cplusplus } #endif ``` -i `import "glue.h"` z `link "glue.cpp"` (albo `links = ["glue.cpp"]` w `finch.toml`). Plik `.cpp` buduje kompilator C++ -(`clang++` albo `g++`, albo `FINCH_CXX`), który wtedy też linkuje, więc biblioteka C++ dochodzi sama. -Bibliotekę C++ z API w C (Box2D 3, JoltC dla Jolt, wrapper C dla PhysX) importuje się jak każdą bibliotekę C. +i `import "glue.h"` z `link "glue.cpp"` (albo `links = ["glue.cpp"]` w `finch.toml`). Plik `.cpp` kompiluje kompilator +C++ (`clang++` albo `g++`, albo `FINCH_CXX`), który potem też linkuje, więc biblioteka C++ dołącza się sama. +Bibliotekę C++ z API w C (Box2D 3, JoltC dla Jolta, nakładka C na PhysX) importuje się jak każdą bibliotekę C. -**Rust**: zrób `staticlib` i wystaw funkcje C: +**Rust**: zbuduj `staticlib` i wystaw funkcje C: ```rust // Cargo.toml: [lib] crate-type = ["staticlib"] #[no_mangle] -pub extern "C" fn path_len(x: f32, y: f32) -> f32 { (x * x + y * y).sqrt() } +pub extern "C" fn dlugosc(x: f32, y: f32) -> f32 { (x * x + y * y).sqrt() } ``` -Napisz nagłówek (`float path_len(float x, float y);` albo niech napisze go `cbindgen`), potem `import "pathfind.h"` i -`link "target/release/libpathfind.a"`. Na Linuksie statyczna biblioteka Rusta potrzebuje też `links = ["dl", "pthread"]`. +Napisz nagłówek (`float dlugosc(float x, float y);` albo wygeneruj go przez `cbindgen`), a potem `import "trasa.h"` +i `link "target/release/libtrasa.a"`. Na Linuksie biblioteka statyczna z Rusta potrzebuje też `links = ["dl", "pthread"]`. -**Android**: `finch build gra.fch --target android --shared` robi `libgra.so` dla 64-bitowego Androida na ARM (API 24 -i wyżej) clangiem z NDK, znalezionym przez `ANDROID_NDK_HOME` (albo `FINCH_CC`). Wczytaj go z aplikacji, na przykład -przez projekt Androida z SDL3 (`SDL_main` biblioteki to `export fn`). To nowość, sprawdzona tylko częściowo: kod Fincha -kompiluje się dla Androida (dane wątków idą przez emulowane TLS, którego wymaga starszy Android), ale linkowanie z NDK -i uruchomienie na urządzeniu nie były jeszcze próbowane. +**Android**: `finch build gra.fch --target android --shared` tworzy `libgra.so` dla 64-bitowego Androida na ARM +(API 24 i nowsze) clangiem z NDK, którego Finch szuka przez `ANDROID_NDK_HOME` (albo `FINCH_CC`). Wczytaj ją z aplikacji, +np. przez projekt Androida z SDL3 (`SDL_main` biblioteki to `export fn`). Dane lokalne dla wątków idą przez emulowane +TLS, którego wymagają starsze wersje Androida. + +--- + +## 31. Wektory (SIMD) + +Procesor potrafi dodać albo pomnożyć kilka liczb jedną instrukcją: 4 liczby `float` naraz przez SSE, 8 przez AVX2, +16 przez AVX-512, 4 przez NEON na ARM. To SIMD (jedna instrukcja, wiele danych) i to dzięki temu filtry obrazu, +dźwięk, fizyka i sieci neuronowe są szybkie. W Finchu to po prostu typ: **`f32x8` to 8 liczb `f32` obok siebie**. + +```c +a := f32x4(1.0, 2.0, 3.0, 4.0) +b := f32x4(0.5) // 0.5 w każdym elemencie +print(a + b) // f32x4(1.5, 2.5, 3.5, 4.5) +print(a * 2.0) // zwykła liczba działa na każdy element: f32x4(2, 4, 6, 8) +print(a.sum(), a[2], a.len) // 10 3 4 +``` + +**Typy** zapisuje się jako typ liczby, `x` i liczbę elementów: `f32x4` `f32x8` `f32x16`, `f64x2` `f64x4` `f64x8`, +`i8x16` `i8x32` `i8x64`, `i16x8` `i16x16`, `i32x4` `i32x8` `i32x16`, `i64x2` `i64x4` i to samo z `u` (bez znaku). +Od 2 do 64 elementów, razem najwyżej 512 bitów. Wektor to zwykła wartość, tak jak `int`: może być zmienną, polem +struktury, elementem tablicy, wartością w mapie i parametrem funkcji, a `print` i `str` go pokazują. + +**Tworzenie:** + +| | | +|---|---| +| `f32x8(x)` | `x` w każdym elemencie | +| `f32x8(a, b, c, d, e, f, g, h)` | po jednej liczbie na element | +| `f32x8(lista, i)` | `lista[i]` … `lista[i+7]` z `[]f32` (albo przez `ptr[f32]`); zatrzymuje program, jeśli nie wszystkie są w liście | +| `f32x8(v)` | inny 8-elementowy wektor przeliczony element po elemencie: `f32x8(calkowite)`, `i32x8(ulamki)` (ucina części ułamkowe), `i16x8(i32ki)` | + +**Działania:** + +| | | +|---|---| +| `+ - * /` | element po elemencie (`/` tylko na liczbach zmiennoprzecinkowych: x86 i ARM nie dzielą liczb całkowitych element po elemencie) | +| `& \| ^ << >> ~` | element po elemencie, na liczbach całkowitych | +| `-v` | każdy element ze zmienionym znakiem | +| `v == w`, `v != w` | `true`, jeśli wszystkie elementy są równe / jeśli któryś się różni | +| `v[i]`, `v[i] = x`, `v.len` | jeden element; liczba elementów | +| `min(v, w)`, `max(v, w)`, `abs(v)`, `clamp(v, lo, hi)` | element po elemencie; `lo`, `hi` i `w` mogą być zwykłymi liczbami | +| `v.sum()`, `v.min()`, `v.max()` | wszystkie elementy złożone w jedną liczbę | +| `v.dot(w)` | `(v * w).sum()` | +| `v.sqrt()`, `v.floor()`, `v.ceil()`, `v.round()` | każdy element (liczby zmiennoprzecinkowe) | +| `v.fma(b, c)` | `v * b + c`, jedną instrukcją tam, gdzie procesor ją ma | +| `v.store(lista, i)` | zapisuje elementy do `lista[i]` … (albo przez `ptr`) | + +`<` i inne porównania dałyby osobną odpowiedź dla każdego elementu, więc ich nie ma; użyj `min`, `max` albo `v[i] < w[i]`. + +**Prawdziwa pętla.** Zwykły schemat: tyle całych wektorów, ile się zmieści, a resztę po jednej liczbie. + +```c +fn iloczyn([]f32 a, []f32 b) -> f32 { + suma := f32x8(0.0) + i := 0 + while i + 8 <= a.len { + suma = f32x8(a, i).fma(f32x8(b, i), suma) + i += 8 + } + wynik := suma.sum() + while i < a.len { + wynik += a[i] * b[i] + i += 1 + } + return wynik +} +``` + +`examples/simd.fch` mierzy czas tej pętli i porównuje go ze zwykłą. + +**Jakie instrukcje.** Domyślnie program działa na każdym procesorze swojego systemu (na x86-64 oznacza to SSE2, więc +`f32x8` liczy się w dwóch połówkach). `--cpu native` (albo `cpu = "native"` w `finch.toml`) pozwala LLVM użyć wszystkiego, +co ma procesor, na którym budujesz: AVX2 i FMA, AVX-512 albo nowszych części NEON na ARM. Taki program potrzebuje potem +podobnego procesora. Można też wybrać poziom: `--cpu x86-64-v3` (AVX2, większość komputerów od 2015 roku), +`--cpu x86-64-v4` (AVX-512), `--cpu apple-m1`. + +Wektory szersze niż rejestry procesora LLVM dzieli na części, a ten sam kod buduje się tak samo na x86-64 i na ARM64. +`v.sum()` na liczbach zmiennoprzecinkowych dodaje elementy w kolejności, która jest najszybsza, więc ostatnie cyfry mogą +się różnić od dodawania ich po kolei. diff --git a/src/embedded.fch b/src/embedded.fch index cfef97d..68230e4 100644 --- a/src/embedded.fch +++ b/src/embedded.fch @@ -20,7 +20,7 @@ fn names() -> []str { fn texts() -> []str { []str list - list.push("// The Finch runtime: string and array helpers, input, files and runtime errors.\n// It is embedded in the compiler, compiled once into a cache folder, and linked\n// statically into every Finch program. Everything here is plain C with no state.\n//\n// Layout shared with the compiler (src/codegen.cpp):\n// str = { char *ptr; int64 len; int64 cap } ptr is always NUL-terminated\n// cap > 0 : heap memory owned by the value (cap bytes allocated)\n// cap == 0 : a string literal, valid forever, never written to\n// cap == -1: borrowed from C, must be copied before it is kept\n// []T = { T *ptr; int64 len; int64 cap } owns ptr when cap > 0\n// map[K]V: see \"maps\" below\n\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#ifdef _WIN32\n#define WIN32_LEAN_AND_MEAN\n#define NOMINMAX\n#include \n#include \n#include \n#else\n#include \n#include \n#include \n#include \n#include \n#endif\n\ntypedef struct {\n char *ptr;\n int64_t len;\n int64_t cap;\n} FStr;\n\ntypedef struct {\n void *ptr;\n int64_t len;\n int64_t cap;\n} FArr;\n\nstatic char empty[1] = \"\";\n\n// ---------- runtime errors ----------\n\n// A program that loads Finch libraries (finch build --shared) can take runtime errors over\n// instead of the process exiting: finch_module_on_fatal(handler) in each library. The handler\n// gets the message and must not return (longjmp out, or exit itself).\nstatic void (*fatal_hook)(const char *msg);\nvoid finch_on_fatal(void (*hook)(const char *msg)) { fatal_hook = hook; }\n\n// The chain of Finch calls, kept by the program itself (see @finch_trace in the compiler's output):\n// each frame is { the caller's frame, the function's name, its file, the line it is at }.\ntypedef struct FFrame {\n struct FFrame *prev;\n const char *name;\n const char *file;\n int64_t line;\n} FFrame;\nstatic void *(*trace_top)(void);\nstatic void (*trace_set)(void *);\nvoid finch_trace_init(void *(*top)(void), void (*set)(void *)) {\n trace_top = top;\n trace_set = set;\n}\n\nstatic void print_trace(void) {\n if (!trace_top) return;\n FFrame *f = trace_top();\n for (int n = 0; f; n++, f = f->prev) {\n if (n == 30) {\n fprintf(stderr, \" ...\\n\");\n break;\n }\n fprintf(stderr, \" in %s (%s:%lld)\\n\", f->name, f->file, (long long)f->line);\n }\n}\n\nstatic __attribute__((noreturn)) void fatal(const char *msg) {\n if (fatal_hook) fatal_hook(msg);\n fflush(stdout);\n fprintf(stderr, \"%s\\n\", msg);\n print_trace();\n exit(1);\n}\n\n__attribute__((noreturn, cold)) void finch_panic(const char *file, int64_t line, const char *msg) {\n char full[1024];\n snprintf(full, sizeof full, \"%s:%lld: runtime error: %s\", file, (long long)line, msg);\n fflush(stdout);\n fatal(full);\n}\n\n__attribute__((noreturn, cold)) void finch_panic_index(const char *file, int64_t line, int64_t i, int64_t len) {\n char msg[128];\n snprintf(msg, sizeof msg, \"index %lld is out of range (the length is %lld)\", (long long)i, (long long)len);\n finch_panic(file, line, msg);\n}\n\nstatic void *must(void *p) {\n if (!p) {\n fflush(stdout);\n fatal(\"runtime error: out of memory\");\n }\n return p;\n}\n\nvoid *finch_alloc(int64_t n) { return must(malloc(n ? (size_t)n : 1)); }\nvoid *finch_zalloc(int64_t n) { return must(calloc(1, n ? (size_t)n : 1)); }\n\n// ---------- assert and finch test ----------\n\n__attribute__((noreturn, cold)) void finch_assert_failed(const char *file, int64_t line, const FStr *why) {\n char msg[600];\n if (why->len) snprintf(msg, sizeof msg, \"assert failed: %.*s\", (int)(why->len > 500 ? 500 : why->len), why->ptr);\n else snprintf(msg, sizeof msg, \"assert failed\");\n finch_panic(file, line, msg);\n}\n\n// Each test runs on its own: a runtime error (a failed assert too) ends that test, and the next one runs.\nstatic jmp_buf test_jump;\nstatic char test_message[1100];\nstatic int64_t tests_passed, tests_failed;\n\nstatic void test_fatal(const char *msg) {\n snprintf(test_message, sizeof test_message, \"%s\", msg);\n longjmp(test_jump, 1);\n}\n\nvoid finch_test_run(const char *name, void (*fn)(void)) {\n void (*saved)(const char *) = fatal_hook;\n void *chain = trace_top ? trace_top() : NULL;\n test_message[0] = 0;\n fatal_hook = test_fatal;\n int failed = setjmp(test_jump);\n if (!failed) fn();\n fatal_hook = saved;\n if (trace_set) trace_set(chain); // a test that stopped early left its calls on the chain\n fflush(stdout);\n if (failed) {\n tests_failed++;\n printf(\"FAIL %s\\n %s\\n\", name, test_message);\n } else {\n tests_passed++;\n printf(\"ok %s\\n\", name);\n }\n fflush(stdout);\n}\n\nint finch_test_summary(void) {\n printf(\"\\n%lld passed, %lld failed\\n\", (long long)tests_passed, (long long)tests_failed);\n return tests_failed ? 1 : 0;\n}\n\n// ---------- strings ----------\n\nstatic void str_heap(FStr *out, const char *p, int64_t len) {\n char *m = finch_alloc(len + 1);\n if (len) memcpy(m, p, (size_t)len);\n m[len] = 0;\n out->ptr = m;\n out->len = len;\n out->cap = len + 1;\n}\n\nstatic void str_static(FStr *out, const char *lit) {\n out->ptr = (char *)lit;\n out->len = (int64_t)strlen(lit);\n out->cap = 0;\n}\n\n// A copy that can be kept: literals are shared, everything else is duplicated.\nvoid finch_str_copy(FStr *out, const FStr *s) {\n if (s->cap == 0) *out = *s;\n else str_heap(out, s->ptr, s->len);\n}\n\n// Make `s` writable (before s[i] = c).\nvoid finch_str_own(FStr *s) {\n if (s->cap > 0) return;\n FStr t;\n str_heap(&t, s->ptr, s->len);\n *s = t;\n}\n\nvoid finch_str_drop(FStr *s) {\n if (s->cap > 0) free(s->ptr);\n}\n\nvoid finch_str_from_c(FStr *out, const char *p) {\n if (!p) {\n str_static(out, empty);\n return;\n }\n out->ptr = (char *)p;\n out->len = (int64_t)strlen(p);\n out->cap = -1;\n}\n\nvoid finch_str_concat(FStr *out, const FStr *a, const FStr *b) {\n int64_t len = a->len + b->len;\n char *m = finch_alloc(len + 1);\n memcpy(m, a->ptr, (size_t)a->len);\n memcpy(m + a->len, b->ptr, (size_t)b->len);\n m[len] = 0;\n out->ptr = m;\n out->len = len;\n out->cap = len + 1;\n}\n\nint finch_str_eq(const FStr *a, const FStr *b) {\n return a->len == b->len && memcmp(a->ptr, b->ptr, (size_t)a->len) == 0;\n}\n\nint64_t finch_str_cmp(const FStr *a, const FStr *b) {\n int64_t n = a->len < b->len ? a->len : b->len;\n int c = memcmp(a->ptr, b->ptr, (size_t)n);\n if (c) return c < 0 ? -1 : 1;\n return a->len < b->len ? -1 : a->len > b->len;\n}\n\nvoid finch_str_from_int(FStr *out, int64_t v) {\n char buf[32];\n int n = snprintf(buf, sizeof buf, \"%lld\", (long long)v);\n str_heap(out, buf, n);\n}\n\nvoid finch_str_from_uint(FStr *out, uint64_t v) {\n char buf[32];\n int n = snprintf(buf, sizeof buf, \"%llu\", (unsigned long long)v);\n str_heap(out, buf, n);\n}\n\n// The shortest text that reads back as the same number (as an f32 when `single`):\n// 0.1 -> \"0.1\", 1.0/3 -> \"0.3333333333333333\", 1e8 -> \"100000000\", 1e20 -> \"1e+20\".\nstatic int fmt_float(char *buf, size_t size, double v, int single) {\n if (isnan(v)) return snprintf(buf, size, \"nan\");\n if (isinf(v)) return snprintf(buf, size, v < 0 ? \"-inf\" : \"inf\");\n int p = 1;\n for (; p < 17; p++) {\n snprintf(buf, size, \"%.*e\", p - 1, v);\n double back = strtod(buf, NULL);\n if (single ? (float)back == (float)v : back == v) break;\n }\n int exp = atoi(strchr(buf, 'e') + 1);\n if (exp < -5 || exp >= 17) return snprintf(buf, size, \"%.*g\", p, v);\n int decimals = p - 1 - exp;\n return snprintf(buf, size, \"%.*f\", decimals > 0 ? decimals : 0, v);\n}\n\nvoid finch_str_from_float(FStr *out, double v) {\n char buf[64];\n int n = fmt_float(buf, sizeof buf, v, 0);\n str_heap(out, buf, n);\n}\n\nvoid finch_str_from_f32(FStr *out, double v) {\n char buf[64];\n int n = fmt_float(buf, sizeof buf, v, 1);\n str_heap(out, buf, n);\n}\n\n// str(x, 2): a fixed number of digits after the point\nvoid finch_str_fixed(FStr *out, double v, int64_t digits) {\n char buf[400];\n if (digits < 0) digits = 0;\n if (digits > 60) digits = 60;\n int n = snprintf(buf, sizeof buf, \"%.*f\", (int)digits, v);\n str_heap(out, buf, n);\n}\n\nvoid finch_print_float(double v, int32_t single) {\n char buf[64];\n fmt_float(buf, sizeof buf, v, single);\n fputs(buf, stdout);\n}\n\nvoid finch_str_from_char(FStr *out, char c) { str_heap(out, &c, 1); }\n\nvoid finch_str_from_bool(FStr *out, int b) { str_static(out, b ? \"true\" : \"false\"); }\n\nstatic void number_error(char *msg, size_t size, const FStr *s, const char *kind) {\n snprintf(msg, size, \"can't turn \\\"%.*s\\\" into %s\", (int)(s->len > 100 ? 100 : s->len), s->ptr ? s->ptr : \"\", kind);\n}\n\nstatic int parse_int(const FStr *s, int64_t *out) {\n const char *p = s->ptr ? s->ptr : \"\";\n while (isspace((unsigned char)*p)) p++;\n char *end;\n errno = 0;\n long long v = strtoll(p, &end, 10);\n while (isspace((unsigned char)*end)) end++;\n *out = v;\n return !(end == p || *end || errno == ERANGE);\n}\n\nstatic int parse_float(const FStr *s, double *out) {\n const char *p = s->ptr ? s->ptr : \"\";\n while (isspace((unsigned char)*p)) p++;\n char *end;\n double v = strtod(p, &end);\n while (isspace((unsigned char)*end)) end++;\n *out = v;\n return !(end == p || *end);\n}\n\nint64_t finch_str_to_int(const FStr *s, const char *file, int64_t line) {\n int64_t v;\n if (!parse_int(s, &v)) {\n char msg[200];\n number_error(msg, sizeof msg, s, \"int\");\n finch_panic(file, line, msg);\n }\n return v;\n}\n\ndouble finch_str_to_float(const FStr *s, const char *file, int64_t line) {\n double v;\n if (!parse_float(s, &v)) {\n char msg[200];\n number_error(msg, sizeof msg, s, \"float\");\n finch_panic(file, line, msg);\n }\n return v;\n}\n\n// ---------- errors as values: int(s) or 0, try read_file(p) ... ----------\n// Each gives back 1 on success; on failure 0, with the message in *err.\n\nstatic void set_error(FStr *err, const char *msg) { str_heap(err, msg, (int64_t)strlen(msg)); }\n\nint finch_str_try_int(const FStr *s, int64_t *out, FStr *err) {\n if (parse_int(s, out)) return 1;\n char msg[200];\n number_error(msg, sizeof msg, s, \"int\");\n set_error(err, msg);\n *out = 0;\n return 0;\n}\n\nint finch_str_try_float(const FStr *s, double *out, FStr *err) {\n if (parse_float(s, out)) return 1;\n char msg[200];\n number_error(msg, sizeof msg, s, \"float\");\n set_error(err, msg);\n *out = 0;\n return 0;\n}\n\nstatic void file_error(FStr *err, const char *what, const FStr *path) {\n char msg[300];\n snprintf(msg, sizeof msg, \"can't %s the file \\\"%.*s\\\": %s\", what, (int)(path->len > 200 ? 200 : path->len),\n path->ptr ? path->ptr : \"\", strerror(errno));\n set_error(err, msg);\n}\n\n// when `fn main() -> !` fails\nvoid finch_report_error(const FStr *msg) {\n fflush(stdout);\n fprintf(stderr, \"error: %.*s\\n\", (int)msg->len, msg->ptr ? msg->ptr : \"\");\n finch_str_drop((FStr *)msg);\n}\n\nvoid finch_str_sub(FStr *out, const FStr *s, int64_t start, int64_t end, const char *file, int64_t line) {\n if (start < 0 || end > s->len || start > end) {\n char msg[160];\n snprintf(msg, sizeof msg, \"sub(%lld, %lld) is out of range (the length is %lld)\", (long long)start,\n (long long)end, (long long)s->len);\n finch_panic(file, line, msg);\n }\n str_heap(out, s->ptr + start, end - start);\n}\n\nint64_t finch_str_find(const FStr *s, const FStr *t) {\n if (t->len == 0) return 0;\n for (int64_t i = 0; i + t->len <= s->len; i++)\n if (s->ptr[i] == t->ptr[0] && memcmp(s->ptr + i, t->ptr, (size_t)t->len) == 0) return i;\n return -1;\n}\n\nint finch_str_starts(const FStr *s, const FStr *t) {\n return t->len <= s->len && memcmp(s->ptr, t->ptr, (size_t)t->len) == 0;\n}\n\nint finch_str_ends(const FStr *s, const FStr *t) {\n return t->len <= s->len && memcmp(s->ptr + s->len - t->len, t->ptr, (size_t)t->len) == 0;\n}\n\nvoid finch_str_trim(FStr *out, const FStr *s) {\n int64_t a = 0, b = s->len;\n while (a < b && isspace((unsigned char)s->ptr[a])) a++;\n while (b > a && isspace((unsigned char)s->ptr[b - 1])) b--;\n str_heap(out, s->ptr + a, b - a);\n}\n\nvoid finch_str_upper(FStr *out, const FStr *s) {\n str_heap(out, s->ptr, s->len);\n for (int64_t i = 0; i < out->len; i++) out->ptr[i] = (char)toupper((unsigned char)out->ptr[i]);\n}\n\nvoid finch_str_lower(FStr *out, const FStr *s) {\n str_heap(out, s->ptr, s->len);\n for (int64_t i = 0; i < out->len; i++) out->ptr[i] = (char)tolower((unsigned char)out->ptr[i]);\n}\n\nvoid finch_str_replace(FStr *out, const FStr *s, const FStr *from, const FStr *to) {\n if (from->len == 0) {\n str_heap(out, s->ptr, s->len);\n return;\n }\n int64_t count = 0;\n for (int64_t i = 0; i + from->len <= s->len;) {\n if (memcmp(s->ptr + i, from->ptr, (size_t)from->len) == 0) {\n count++;\n i += from->len;\n } else {\n i++;\n }\n }\n int64_t len = s->len + count * (to->len - from->len);\n char *m = finch_alloc(len + 1), *w = m;\n for (int64_t i = 0; i < s->len;) {\n if (i + from->len <= s->len && memcmp(s->ptr + i, from->ptr, (size_t)from->len) == 0) {\n memcpy(w, to->ptr, (size_t)to->len);\n w += to->len;\n i += from->len;\n } else {\n *w++ = s->ptr[i++];\n }\n }\n *w = 0;\n out->ptr = m;\n out->len = len;\n out->cap = len + 1;\n}\n\nstatic void arr_push_str(FArr *a, const char *p, int64_t len);\n\n// \"a,b,,c\".split(\",\") -> [\"a\", \"b\", \"\", \"c\"]; split(\"\") gives single characters.\nvoid finch_str_split(FArr *out, const FStr *s, const FStr *sep) {\n out->ptr = NULL;\n out->len = out->cap = 0;\n if (sep->len == 0) {\n for (int64_t i = 0; i < s->len; i++) arr_push_str(out, s->ptr + i, 1);\n return;\n }\n int64_t start = 0;\n for (int64_t i = 0; i + sep->len <= s->len;) {\n if (memcmp(s->ptr + i, sep->ptr, (size_t)sep->len) == 0) {\n arr_push_str(out, s->ptr + start, i - start);\n i += sep->len;\n start = i;\n } else {\n i++;\n }\n }\n arr_push_str(out, s->ptr + start, s->len - start);\n}\n\nvoid finch_str_join(FStr *out, const FArr *parts, const FStr *sep) {\n const FStr *p = parts->ptr;\n int64_t len = 0;\n for (int64_t i = 0; i < parts->len; i++) len += p[i].len + (i ? sep->len : 0);\n char *m = finch_alloc(len + 1), *w = m;\n for (int64_t i = 0; i < parts->len; i++) {\n if (i) {\n memcpy(w, sep->ptr, (size_t)sep->len);\n w += sep->len;\n }\n memcpy(w, p[i].ptr, (size_t)p[i].len);\n w += p[i].len;\n }\n *w = 0;\n out->ptr = m;\n out->len = len;\n out->cap = len + 1;\n}\n\nvoid finch_str_repeat(FStr *out, const FStr *s, int64_t n) {\n if (n < 0) n = 0;\n int64_t len = s->len * n;\n char *m = finch_alloc(len + 1);\n for (int64_t i = 0; i < n; i++) memcpy(m + i * s->len, s->ptr, (size_t)s->len);\n m[len] = 0;\n out->ptr = m;\n out->len = len;\n out->cap = len + 1;\n}\n\nvoid finch_str_from_bytes(FStr *out, const FArr *bytes) { str_heap(out, bytes->ptr, bytes->len); }\n\n// ---------- building text: print(x) and str(x) of arrays, maps and structs ----------\n// A buffer starts as an empty str and grows on the heap.\n\nvoid finch_buf_add(FStr *b, const char *p, int64_t n) {\n if (b->len + n + 1 > b->cap) {\n int64_t cap = b->cap > 0 ? b->cap * 2 : 32;\n while (cap < b->len + n + 1) cap *= 2;\n b->ptr = must(realloc(b->cap > 0 ? b->ptr : NULL, (size_t)cap));\n b->cap = cap;\n }\n if (n) memcpy(b->ptr + b->len, p, (size_t)n);\n b->len += n;\n b->ptr[b->len] = 0;\n}\n\nstatic void buf_fmt(FStr *b, const char *fmt, ...) __attribute__((format(printf, 2, 3)));\nstatic void buf_fmt(FStr *b, const char *fmt, ...) {\n char t[64];\n va_list ap;\n va_start(ap, fmt);\n int n = vsnprintf(t, sizeof t, fmt, ap);\n va_end(ap);\n finch_buf_add(b, t, n);\n}\n\nvoid finch_buf_int(FStr *b, int64_t v) { buf_fmt(b, \"%lld\", (long long)v); }\nvoid finch_buf_uint(FStr *b, uint64_t v) { buf_fmt(b, \"%llu\", (unsigned long long)v); }\nvoid finch_buf_float(FStr *b, double v) {\n char t[64];\n finch_buf_add(b, t, fmt_float(t, sizeof t, v, 0));\n}\nvoid finch_buf_f32(FStr *b, double v) {\n char t[64];\n finch_buf_add(b, t, fmt_float(t, sizeof t, v, 1));\n}\nvoid finch_buf_bool(FStr *b, int v) { finch_buf_add(b, v ? \"true\" : \"false\", v ? 4 : 5); }\nvoid finch_buf_ptr(FStr *b, const void *p) {\n if (p) buf_fmt(b, \"0x%llx\", (unsigned long long)(uintptr_t)p);\n else finch_buf_add(b, \"null\", 4);\n}\n\nvoid finch_buf_char(FStr *b, char c, int quoted) {\n if (quoted) finch_buf_add(b, \"'\", 1);\n finch_buf_add(b, &c, 1);\n if (quoted) finch_buf_add(b, \"'\", 1);\n}\n\nvoid finch_buf_str(FStr *b, const FStr *s, int quoted) {\n if (quoted) finch_buf_add(b, \"\\\"\", 1);\n finch_buf_add(b, s->ptr, s->len);\n if (quoted) finch_buf_add(b, \"\\\"\", 1);\n}\n\n// print the buffer and free it\nvoid finch_buf_print(FStr *b) {\n if (b->len) fwrite(b->ptr, 1, (size_t)b->len, stdout);\n if (b->cap > 0) free(b->ptr);\n}\n\n// ---------- arrays ----------\n\nvoid finch_arr_reserve(FArr *a, int64_t need, int64_t es) {\n if (need <= a->cap) return;\n int64_t cap = a->cap * 2;\n if (cap < 4) cap = 4;\n if (cap < need) cap = need;\n a->ptr = must(realloc(a->cap > 0 ? a->ptr : NULL, (size_t)(cap * es)));\n a->cap = cap;\n}\n\nvoid finch_arr_make(FArr *out, int64_t len, int64_t es) {\n if (len < 0) len = 0;\n out->ptr = len ? finch_zalloc(len * es) : NULL;\n out->len = len;\n out->cap = len;\n}\n\n// grow or shrink to `len`; new elements are zero\nvoid finch_arr_resize(FArr *a, int64_t len, int64_t es) {\n if (len < 0) len = 0;\n finch_arr_reserve(a, len, es);\n if (len > a->len) memset((char *)a->ptr + a->len * es, 0, (size_t)((len - a->len) * es));\n a->len = len;\n}\n\n// a byte-for-byte copy of the elements (the compiler deep-copies owning elements afterwards)\nvoid finch_arr_clone_raw(FArr *out, const FArr *a, int64_t es) {\n out->len = a->len;\n out->cap = a->len;\n out->ptr = a->len ? finch_alloc(a->len * es) : NULL;\n if (a->len) memcpy(out->ptr, a->ptr, (size_t)(a->len * es));\n}\n\nvoid finch_arr_free(FArr *a) {\n if (a->cap > 0) free(a->ptr);\n}\n\n// open a hole at i (0 <= i <= len), len grows by one\nvoid finch_arr_insert_gap(FArr *a, int64_t i, int64_t es, const char *file, int64_t line) {\n if (i < 0 || i > a->len) finch_panic_index(file, line, i, a->len);\n finch_arr_reserve(a, a->len + 1, es);\n char *p = a->ptr;\n memmove(p + (i + 1) * es, p + i * es, (size_t)((a->len - i) * es));\n a->len++;\n}\n\n// close the hole at i (the element was already moved out), len shrinks by one\nvoid finch_arr_remove_gap(FArr *a, int64_t i, int64_t es) {\n char *p = a->ptr;\n memmove(p + i * es, p + (i + 1) * es, (size_t)((a->len - i - 1) * es));\n a->len--;\n}\n\nstatic void arr_push_str(FArr *a, const char *p, int64_t len) {\n finch_arr_reserve(a, a->len + 1, (int64_t)sizeof(FStr));\n str_heap((FStr *)a->ptr + a->len, p, len);\n a->len++;\n}\n\nvoid finch_bytes_from_str(FArr *out, const FStr *s) {\n out->len = out->cap = s->len;\n out->ptr = s->len ? finch_alloc(s->len) : NULL;\n if (s->len) memcpy(out->ptr, s->ptr, (size_t)s->len);\n}\n\n// ---------- program arguments, input and files ----------\n\nvoid finch_args(FArr *out, int argc, char **argv) {\n out->len = out->cap = argc;\n out->ptr = finch_alloc((int64_t)sizeof(FStr) * (argc ? argc : 1));\n for (int i = 0; i < argc; i++) str_heap((FStr *)out->ptr + i, argv[i], (int64_t)strlen(argv[i]));\n}\n\n// after main: the arguments go\nvoid finch_args_free(FArr *a) {\n for (int64_t i = 0; i < a->len; i++) finch_str_drop((FStr *)a->ptr + i);\n finch_arr_free(a);\n}\n\n// input(\"Name? \") prints the prompt and reads one line, without the newline.\nvoid finch_input(FStr *out, const FStr *prompt) {\n if (prompt->len) fwrite(prompt->ptr, 1, (size_t)prompt->len, stdout);\n fflush(stdout);\n // read one line of any length (getline isn't available everywhere)\n size_t size = 128, n = 0;\n char *line = finch_alloc((int64_t)size);\n int c, got = 0;\n while ((c = fgetc(stdin)) != EOF) {\n got = 1;\n if (c == '\\n') break;\n if (n + 1 >= size) line = must(realloc(line, size *= 2));\n line[n++] = (char)c;\n }\n if (!got) {\n free(line);\n str_static(out, empty);\n return;\n }\n while (n > 0 && line[n - 1] == '\\r') n--;\n line[n] = 0;\n out->ptr = line;\n out->len = (int64_t)n;\n out->cap = (int64_t)size;\n}\n\nint finch_file_exists(const FStr *path) {\n FILE *f = fopen(path->ptr, \"rb\");\n if (f) fclose(f);\n return f != NULL;\n}\n\nint finch_try_read_file(FStr *out, const FStr *path, FStr *err) {\n FILE *f = fopen(path->ptr ? path->ptr : \"\", \"rb\");\n if (!f) {\n file_error(err, \"read\", path);\n return 0;\n }\n fseek(f, 0, SEEK_END);\n long size = ftell(f);\n fseek(f, 0, SEEK_SET);\n char *m = finch_alloc(size + 1);\n size_t got = fread(m, 1, (size_t)size, f);\n fclose(f);\n m[got] = 0;\n out->ptr = m;\n out->len = (int64_t)got;\n out->cap = size + 1;\n return 1;\n}\n\nvoid finch_read_file(FStr *out, const FStr *path, const char *file, int64_t line) {\n FILE *f = fopen(path->ptr ? path->ptr : \"\", \"rb\");\n if (!f) {\n char msg[300];\n snprintf(msg, sizeof msg, \"can't read the file \\\"%.*s\\\": %s\", (int)(path->len > 200 ? 200 : path->len),\n path->ptr, strerror(errno));\n finch_panic(file, line, msg);\n }\n fseek(f, 0, SEEK_END);\n long size = ftell(f);\n fseek(f, 0, SEEK_SET);\n char *m = finch_alloc(size + 1);\n size_t got = fread(m, 1, (size_t)size, f);\n fclose(f);\n m[got] = 0;\n out->ptr = m;\n out->len = (int64_t)got;\n out->cap = size + 1;\n}\n\n// shell(\"ls -l\"): run a command, give back its exit code\nint64_t finch_shell(const FStr *cmd) {\n fflush(stdout);\n int status = system(cmd->ptr ? cmd->ptr : \"\");\n#ifdef _WIN32\n return status; // Windows gives the exit code directly\n#else\n if (status == -1) return -1;\n if (WIFEXITED(status)) return WEXITSTATUS(status);\n return 128 + (WIFSIGNALED(status) ? WTERMSIG(status) : 0);\n#endif\n}\n\nint finch_delete_file(const FStr *path) { return remove(path->ptr ? path->ptr : \"\") == 0; }\n\nint finch_try_delete_file(const FStr *path, FStr *err) {\n if (remove(path->ptr ? path->ptr : \"\") == 0) return 1;\n file_error(err, \"delete\", path);\n return 0;\n}\n\n// stdin/stdout/stderr are macros on some systems (Windows), so Finch asks for them here\nvoid *finch_std_stream(int i) { return i == 0 ? (void *)stdin : i == 1 ? (void *)stdout : (void *)stderr; }\n\nint finch_write_file(const FStr *path, const FStr *text) {\n FILE *f = fopen(path->ptr ? path->ptr : \"\", \"wb\");\n if (!f) return 0;\n size_t put = text->len ? fwrite(text->ptr, 1, (size_t)text->len, f) : 0;\n return fclose(f) == 0 && put == (size_t)text->len;\n}\n\nint finch_try_write_file(const FStr *path, const FStr *text, FStr *err) {\n errno = 0;\n if (finch_write_file(path, text)) return 1;\n if (!errno) errno = EIO;\n file_error(err, \"write\", path);\n return 0;\n}\n\n// ---------- folders (std/fs.fch, through std/finch_fs.h) ----------\n\nint finch_fs_is_dir(const char *path) {\n#ifdef _WIN32\n struct _finddata_t d;\n intptr_t h = _findfirst(path, &d);\n if (h == -1) return 0;\n _findclose(h);\n return (d.attrib & _A_SUBDIR) != 0;\n#else\n struct stat st;\n return stat(path, &st) == 0 && S_ISDIR(st.st_mode);\n#endif\n}\n\nint finch_fs_exists(const char *path) {\n#ifdef _WIN32\n return _access(path, 0) == 0;\n#else\n struct stat st;\n return stat(path, &st) == 0;\n#endif\n}\n\nstatic int make_one_dir(const char *p) {\n#ifdef _WIN32\n return _mkdir(p) == 0 || errno == EEXIST;\n#else\n return mkdir(p, 0777) == 0 || errno == EEXIST;\n#endif\n}\n\n// mkdir -p: the parents too; 1 if the folder is there afterwards\nint finch_fs_mkdir(const char *path) {\n size_t n = strlen(path);\n char *p = finch_alloc((int64_t)n + 1);\n memcpy(p, path, n + 1);\n for (size_t i = 1; i < n; i++) {\n if (p[i] == '/' || p[i] == '\\\\') {\n if (p[i - 1] == ':') continue; /* C: is a drive */\n char c = p[i];\n p[i] = 0;\n make_one_dir(p);\n p[i] = c;\n }\n }\n int ok = make_one_dir(p) && finch_fs_is_dir(p);\n free(p);\n return ok;\n}\n\nint finch_fs_remove_dir(const char *path) {\n#ifdef _WIN32\n return _rmdir(path) == 0;\n#else\n return rmdir(path) == 0;\n#endif\n}\n\n// the names in a folder, one per call (\"\" at the end), without . and ..\ntypedef struct {\n#ifdef _WIN32\n intptr_t h;\n struct _finddata_t d;\n int first;\n#else\n DIR *d;\n#endif\n} FsList;\n\nvoid *finch_fs_list_open(const char *path) {\n FsList *l = finch_zalloc((int64_t)sizeof(FsList));\n#ifdef _WIN32\n size_t n = strlen(path);\n char *pat = finch_alloc((int64_t)n + 3);\n memcpy(pat, path, n);\n memcpy(pat + n, \"\\\\*\", 3);\n l->h = _findfirst(pat, &l->d);\n free(pat);\n if (l->h == -1) {\n free(l);\n return NULL;\n }\n l->first = 1;\n#else\n l->d = opendir(path);\n if (!l->d) {\n free(l);\n return NULL;\n }\n#endif\n return l;\n}\n\nconst char *finch_fs_list_next(void *handle) {\n FsList *l = handle;\n for (;;) {\n#ifdef _WIN32\n if (!l->first && _findnext(l->h, &l->d) != 0) return \"\";\n l->first = 0;\n const char *name = l->d.name;\n#else\n struct dirent *e = readdir(l->d);\n if (!e) return \"\";\n const char *name = e->d_name;\n#endif\n if (strcmp(name, \".\") != 0 && strcmp(name, \"..\") != 0) return name;\n }\n}\n\nvoid finch_fs_list_close(void *handle) {\n FsList *l = handle;\n#ifdef _WIN32\n _findclose(l->h);\n#else\n closedir(l->d);\n#endif\n free(l);\n}\n\n// where a program keeps its own files for this user (saves, settings); \"\" if unknown\n// Linux: $XDG_DATA_HOME or ~/.local/share macOS: ~/Library/Application Support Windows: %APPDATA%\nconst char *finch_fs_data_home(void) {\n static char buf[4096];\n const char *v;\n#if defined(_WIN32)\n v = getenv(\"APPDATA\");\n snprintf(buf, sizeof buf, \"%s\", v ? v : \"\");\n#elif defined(__APPLE__)\n v = getenv(\"HOME\");\n if (v) snprintf(buf, sizeof buf, \"%s/Library/Application Support\", v);\n else buf[0] = 0;\n#else\n v = getenv(\"XDG_DATA_HOME\");\n if (v && *v) snprintf(buf, sizeof buf, \"%s\", v);\n else if ((v = getenv(\"HOME\"))) snprintf(buf, sizeof buf, \"%s/.local/share\", v);\n else buf[0] = 0;\n#endif\n return buf;\n}\n\n// ---------- threads (std/thread.fch, through std/finch_thread.h) ----------\n\ntypedef struct {\n void (*fn)(void *);\n void *arg;\n} ThreadStart;\n\n#ifdef _WIN32\nstatic DWORD WINAPI thread_main(LPVOID p) {\n ThreadStart st = *(ThreadStart *)p;\n free(p);\n st.fn(st.arg);\n return 0;\n}\n#else\nstatic void *thread_main(void *p) {\n ThreadStart st = *(ThreadStart *)p;\n free(p);\n st.fn(st.arg);\n return NULL;\n}\n#endif\n\n// run fn(arg) on a new thread; NULL if it couldn't start\nvoid *finch_thread_spawn(void (*fn)(void *), void *arg) {\n ThreadStart *st = finch_alloc((int64_t)sizeof(ThreadStart));\n st->fn = fn;\n st->arg = arg;\n#ifdef _WIN32\n HANDLE h = CreateThread(NULL, 0, thread_main, st, 0, NULL);\n if (!h) {\n free(st);\n return NULL;\n }\n return h;\n#else\n pthread_t *t = finch_alloc((int64_t)sizeof(pthread_t));\n if (pthread_create(t, NULL, thread_main, st) != 0) {\n free(st);\n free(t);\n return NULL;\n }\n return t;\n#endif\n}\n\n// wait for the thread to end; 1 if that worked\nint finch_thread_join(void *t) {\n if (!t) return 0;\n#ifdef _WIN32\n int ok = WaitForSingleObject((HANDLE)t, INFINITE) == WAIT_OBJECT_0;\n CloseHandle((HANDLE)t);\n return ok;\n#else\n int ok = pthread_join(*(pthread_t *)t, NULL) == 0;\n free(t);\n return ok;\n#endif\n}\n\nvoid *finch_mutex_new(void) {\n#ifdef _WIN32\n CRITICAL_SECTION *m = finch_alloc((int64_t)sizeof(CRITICAL_SECTION));\n InitializeCriticalSection(m);\n#else\n pthread_mutex_t *m = finch_alloc((int64_t)sizeof(pthread_mutex_t));\n pthread_mutex_init(m, NULL);\n#endif\n return m;\n}\n\nvoid finch_mutex_lock(void *m) {\n#ifdef _WIN32\n EnterCriticalSection((CRITICAL_SECTION *)m);\n#else\n pthread_mutex_lock((pthread_mutex_t *)m);\n#endif\n}\n\nvoid finch_mutex_unlock(void *m) {\n#ifdef _WIN32\n LeaveCriticalSection((CRITICAL_SECTION *)m);\n#else\n pthread_mutex_unlock((pthread_mutex_t *)m);\n#endif\n}\n\nvoid finch_mutex_free(void *m) {\n if (!m) return;\n#ifdef _WIN32\n DeleteCriticalSection((CRITICAL_SECTION *)m);\n#else\n pthread_mutex_destroy((pthread_mutex_t *)m);\n#endif\n free(m);\n}\n\nint finch_cpu_count(void) {\n#ifdef _WIN32\n SYSTEM_INFO si;\n GetSystemInfo(&si);\n return (int)si.dwNumberOfProcessors;\n#else\n long n = sysconf(_SC_NPROCESSORS_ONLN);\n return n > 0 ? (int)n : 1;\n#endif\n}\n\n// ---------- maps ----------\n// map[K]V = { entries; len; used; cap; index; icap; adds }\n// entries: `used` slots of es bytes, each { uint64 hash; K key; V value }, in insertion order.\n// A removed entry keeps its slot (hash 0) until the next resize packs the slots.\n// index: icap slots (a power of two, at least 2 * cap): -1 empty, -2 removed, else an entry number.\n// adds: how many keys were ever added; a `for` over the map stops with an error if it changes.\n// The compiler stores, copies and drops keys and values; the runtime only finds and places entries.\n// Keys are compared byte for byte (numbers, chars, bools) or as text (kstr).\n\ntypedef struct {\n char *entries;\n int64_t len, used, cap;\n int64_t *index;\n int64_t icap;\n int64_t adds;\n} FMap;\n\nstatic uint64_t map_hash(const void *key, int64_t ks, int kstr) {\n const unsigned char *p;\n size_t n;\n if (kstr) {\n const FStr *s = key;\n p = (const unsigned char *)s->ptr;\n n = (size_t)s->len;\n } else {\n p = key;\n n = (size_t)ks;\n }\n uint64_t h = 1469598103934665603ULL; // FNV-1a, then mixed so the low bits spread well\n for (size_t i = 0; i < n; i++) {\n h ^= p[i];\n h *= 1099511628211ULL;\n }\n h ^= h >> 31;\n h *= 0xbf58476d1ce4e5b9ULL;\n h ^= h >> 29;\n return h | 1; // 0 marks a removed entry\n}\n\nstatic int map_key_eq(const char *a, const void *b, int64_t ks, int kstr) {\n if (!kstr) return memcmp(a, b, (size_t)ks) == 0;\n const FStr *x = (const FStr *)a, *y = b;\n return x->len == y->len && (x->len == 0 || memcmp(x->ptr, y->ptr, (size_t)x->len) == 0);\n}\n\nstatic void map_reindex(FMap *m, int64_t es) {\n if (m->len != m->used) { // pack the slots of removed entries away\n int64_t j = 0;\n for (int64_t i = 0; i < m->used; i++) {\n char *e = m->entries + i * es;\n if (*(uint64_t *)e == 0) continue;\n if (i != j) memmove(m->entries + j * es, e, (size_t)es);\n j++;\n }\n m->used = j;\n }\n int64_t icap = 16;\n while (icap < m->cap * 2) icap *= 2;\n free(m->index);\n m->index = finch_alloc(icap * (int64_t)sizeof(int64_t));\n memset(m->index, 0xff, (size_t)icap * sizeof(int64_t));\n m->icap = icap;\n for (int64_t i = 0; i < m->used; i++) {\n uint64_t h = *(uint64_t *)(m->entries + i * es);\n int64_t s = (int64_t)(h & (uint64_t)(icap - 1));\n while (m->index[s] != -1) s = (s + 1) & (icap - 1);\n m->index[s] = i;\n }\n}\n\n// the entry number of `key`, or -1\nint64_t finch_map_find(const FMap *m, const void *key, int64_t es, int64_t ks, int32_t kstr) {\n if (m->len == 0) return -1;\n uint64_t h = map_hash(key, ks, kstr);\n for (int64_t s = (int64_t)(h & (uint64_t)(m->icap - 1));; s = (s + 1) & (m->icap - 1)) {\n int64_t i = m->index[s];\n if (i == -1) return -1;\n if (i < 0) continue;\n char *e = m->entries + i * es;\n if (*(uint64_t *)e == h && map_key_eq(e + 8, key, ks, kstr)) return i;\n }\n}\n\n// The entry for `key`. A new one is zeroed except its hash (*added = 1); the compiler then stores the key.\nint64_t finch_map_slot(FMap *m, const void *key, int64_t es, int64_t ks, int32_t kstr, int32_t *added) {\n int64_t i = finch_map_find(m, key, es, ks, kstr);\n *added = i < 0;\n if (i >= 0) return i;\n if (m->used == m->cap) {\n if (m->len * 4 < m->cap * 3) { // many removed slots: pack them instead of growing\n map_reindex(m, es);\n } else {\n m->cap = m->cap ? m->cap * 2 : 8;\n m->entries = must(realloc(m->entries, (size_t)(m->cap * es)));\n map_reindex(m, es);\n }\n }\n uint64_t h = map_hash(key, ks, kstr);\n i = m->used++;\n char *e = m->entries + i * es;\n memset(e, 0, (size_t)es);\n *(uint64_t *)e = h;\n int64_t s = (int64_t)(h & (uint64_t)(m->icap - 1));\n while (m->index[s] >= 0) s = (s + 1) & (m->icap - 1);\n m->index[s] = i;\n m->len++;\n m->adds++;\n return i;\n}\n\n// Forget entry i (its key and value were already dropped by the compiler).\nvoid finch_map_remove_at(FMap *m, int64_t i, int64_t es) {\n char *e = m->entries + i * es;\n uint64_t h = *(uint64_t *)e;\n int64_t s = (int64_t)(h & (uint64_t)(m->icap - 1));\n while (m->index[s] != i) s = (s + 1) & (m->icap - 1);\n m->index[s] = -2;\n *(uint64_t *)e = 0;\n if (--m->len == 0) {\n m->used = 0;\n memset(m->index, 0xff, (size_t)m->icap * sizeof(int64_t));\n }\n}\n\nvoid finch_map_clear(FMap *m) {\n m->len = m->used = 0;\n if (m->index) memset(m->index, 0xff, (size_t)m->icap * sizeof(int64_t));\n}\n\nvoid finch_map_free(FMap *m) {\n free(m->entries);\n free(m->index);\n}\n\n// A copy of the slots and the index; the compiler then copies the keys and values that own memory.\nvoid finch_map_clone_raw(FMap *out, const FMap *m, int64_t es) {\n *out = *m;\n if (!m->cap) return;\n out->entries = finch_alloc(m->cap * es);\n memcpy(out->entries, m->entries, (size_t)(m->used * es));\n out->index = finch_alloc(m->icap * (int64_t)sizeof(int64_t));\n memcpy(out->index, m->index, (size_t)m->icap * sizeof(int64_t));\n}\n\n__attribute__((noreturn, cold)) void finch_map_changed(const char *file, int64_t line) {\n finch_panic(file, line, \"a key was added to the map while looping over it (collect the new keys, and add them after the loop)\");\n}\n\n__attribute__((noreturn, cold)) void finch_map_missing(const char *file, int64_t line, const FStr *key) {\n char msg[200];\n snprintf(msg, sizeof msg, \"the key %.*s is not in the map (check with .has(key), or use .get(key, default))\",\n (int)(key->len > 100 ? 100 : key->len), key->ptr ? key->ptr : \"\");\n finch_panic(file, line, msg);\n}\n") + list.push("// The Finch runtime: string and array helpers, input, files and runtime errors.\n// It is embedded in the compiler, compiled once into a cache folder, and linked\n// statically into every Finch program. Everything here is plain C with no state.\n//\n// Layout shared with the compiler (src/gen.fch):\n// str = { char *ptr; int64 len; int64 cap } ptr is always NUL-terminated\n// cap > 0 : heap memory owned by the value (cap bytes allocated)\n// cap == 0 : a string literal, valid forever, never written to\n// cap == -1: borrowed from C, must be copied before it is kept\n// []T = { T *ptr; int64 len; int64 cap } owns ptr when cap > 0\n// map[K]V: see \"maps\" below\n\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#ifdef _WIN32\n#define WIN32_LEAN_AND_MEAN\n#define NOMINMAX\n#include \n#include \n#include \n#else\n#include \n#include \n#include \n#include \n#include \n#endif\n\ntypedef struct {\n char *ptr;\n int64_t len;\n int64_t cap;\n} FStr;\n\ntypedef struct {\n void *ptr;\n int64_t len;\n int64_t cap;\n} FArr;\n\nstatic char empty[1] = \"\";\n\n// ---------- runtime errors ----------\n\n// A program that loads Finch libraries (finch build --shared) can take runtime errors over\n// instead of the process exiting: finch_module_on_fatal(handler) in each library. The handler\n// gets the message and must not return (longjmp out, or exit itself).\nstatic void (*fatal_hook)(const char *msg);\nvoid finch_on_fatal(void (*hook)(const char *msg)) { fatal_hook = hook; }\n\n// The chain of Finch calls, kept by the program itself (see @finch_trace in the compiler's output):\n// each frame is { the caller's frame, the function's name, its file, the line it is at }.\ntypedef struct FFrame {\n struct FFrame *prev;\n const char *name;\n const char *file;\n int64_t line;\n} FFrame;\nstatic void *(*trace_top)(void);\nstatic void (*trace_set)(void *);\nvoid finch_trace_init(void *(*top)(void), void (*set)(void *)) {\n trace_top = top;\n trace_set = set;\n}\n\nstatic void print_trace(void) {\n if (!trace_top) return;\n FFrame *f = trace_top();\n for (int n = 0; f; n++, f = f->prev) {\n if (n == 30) {\n fprintf(stderr, \" ...\\n\");\n break;\n }\n fprintf(stderr, \" in %s (%s:%lld)\\n\", f->name, f->file, (long long)f->line);\n }\n}\n\nstatic __attribute__((noreturn)) void fatal(const char *msg) {\n if (fatal_hook) fatal_hook(msg);\n fflush(stdout);\n fprintf(stderr, \"%s\\n\", msg);\n print_trace();\n exit(1);\n}\n\n__attribute__((noreturn, cold)) void finch_panic(const char *file, int64_t line, const char *msg) {\n char full[1024];\n snprintf(full, sizeof full, \"%s:%lld: runtime error: %s\", file, (long long)line, msg);\n fflush(stdout);\n fatal(full);\n}\n\n__attribute__((noreturn, cold)) void finch_panic_index(const char *file, int64_t line, int64_t i, int64_t len) {\n char msg[128];\n snprintf(msg, sizeof msg, \"index %lld is out of range (the length is %lld)\", (long long)i, (long long)len);\n finch_panic(file, line, msg);\n}\n\nstatic void *must(void *p) {\n if (!p) {\n fflush(stdout);\n fatal(\"runtime error: out of memory\");\n }\n return p;\n}\n\nvoid *finch_alloc(int64_t n) { return must(malloc(n ? (size_t)n : 1)); }\nvoid *finch_zalloc(int64_t n) { return must(calloc(1, n ? (size_t)n : 1)); }\n\n// ---------- assert and finch test ----------\n\n__attribute__((noreturn, cold)) void finch_assert_failed(const char *file, int64_t line, const FStr *why) {\n char msg[600];\n if (why->len) snprintf(msg, sizeof msg, \"assert failed: %.*s\", (int)(why->len > 500 ? 500 : why->len), why->ptr);\n else snprintf(msg, sizeof msg, \"assert failed\");\n finch_panic(file, line, msg);\n}\n\n// Each test runs on its own: a runtime error (a failed assert too) ends that test, and the next one runs.\nstatic jmp_buf test_jump;\nstatic char test_message[1100];\nstatic int64_t tests_passed, tests_failed;\n\nstatic void test_fatal(const char *msg) {\n snprintf(test_message, sizeof test_message, \"%s\", msg);\n longjmp(test_jump, 1);\n}\n\nvoid finch_test_run(const char *name, void (*fn)(void)) {\n void (*saved)(const char *) = fatal_hook;\n void *chain = trace_top ? trace_top() : NULL;\n test_message[0] = 0;\n fatal_hook = test_fatal;\n int failed = setjmp(test_jump);\n if (!failed) fn();\n fatal_hook = saved;\n if (trace_set) trace_set(chain); // a test that stopped early left its calls on the chain\n fflush(stdout);\n if (failed) {\n tests_failed++;\n printf(\"FAIL %s\\n %s\\n\", name, test_message);\n } else {\n tests_passed++;\n printf(\"ok %s\\n\", name);\n }\n fflush(stdout);\n}\n\nint finch_test_summary(void) {\n printf(\"\\n%lld passed, %lld failed\\n\", (long long)tests_passed, (long long)tests_failed);\n return tests_failed ? 1 : 0;\n}\n\n// ---------- strings ----------\n\nstatic void str_heap(FStr *out, const char *p, int64_t len) {\n char *m = finch_alloc(len + 1);\n if (len) memcpy(m, p, (size_t)len);\n m[len] = 0;\n out->ptr = m;\n out->len = len;\n out->cap = len + 1;\n}\n\nstatic void str_static(FStr *out, const char *lit) {\n out->ptr = (char *)lit;\n out->len = (int64_t)strlen(lit);\n out->cap = 0;\n}\n\n// A copy that can be kept: literals are shared, everything else is duplicated.\nvoid finch_str_copy(FStr *out, const FStr *s) {\n if (s->cap == 0) *out = *s;\n else str_heap(out, s->ptr, s->len);\n}\n\n// Make `s` writable (before s[i] = c).\nvoid finch_str_own(FStr *s) {\n if (s->cap > 0) return;\n FStr t;\n str_heap(&t, s->ptr, s->len);\n *s = t;\n}\n\nvoid finch_str_drop(FStr *s) {\n if (s->cap > 0) free(s->ptr);\n}\n\nvoid finch_str_from_c(FStr *out, const char *p) {\n if (!p) {\n str_static(out, empty);\n return;\n }\n out->ptr = (char *)p;\n out->len = (int64_t)strlen(p);\n out->cap = -1;\n}\n\nvoid finch_str_concat(FStr *out, const FStr *a, const FStr *b) {\n int64_t len = a->len + b->len;\n char *m = finch_alloc(len + 1);\n memcpy(m, a->ptr, (size_t)a->len);\n memcpy(m + a->len, b->ptr, (size_t)b->len);\n m[len] = 0;\n out->ptr = m;\n out->len = len;\n out->cap = len + 1;\n}\n\nint finch_str_eq(const FStr *a, const FStr *b) {\n return a->len == b->len && memcmp(a->ptr, b->ptr, (size_t)a->len) == 0;\n}\n\nint64_t finch_str_cmp(const FStr *a, const FStr *b) {\n int64_t n = a->len < b->len ? a->len : b->len;\n int c = memcmp(a->ptr, b->ptr, (size_t)n);\n if (c) return c < 0 ? -1 : 1;\n return a->len < b->len ? -1 : a->len > b->len;\n}\n\nvoid finch_str_from_int(FStr *out, int64_t v) {\n char buf[32];\n int n = snprintf(buf, sizeof buf, \"%lld\", (long long)v);\n str_heap(out, buf, n);\n}\n\nvoid finch_str_from_uint(FStr *out, uint64_t v) {\n char buf[32];\n int n = snprintf(buf, sizeof buf, \"%llu\", (unsigned long long)v);\n str_heap(out, buf, n);\n}\n\n// The shortest text that reads back as the same number (as an f32 when `single`):\n// 0.1 -> \"0.1\", 1.0/3 -> \"0.3333333333333333\", 1e8 -> \"100000000\", 1e20 -> \"1e+20\".\nstatic int fmt_float(char *buf, size_t size, double v, int single) {\n if (isnan(v)) return snprintf(buf, size, \"nan\");\n if (isinf(v)) return snprintf(buf, size, v < 0 ? \"-inf\" : \"inf\");\n int p = 1;\n for (; p < 17; p++) {\n snprintf(buf, size, \"%.*e\", p - 1, v);\n double back = strtod(buf, NULL);\n if (single ? (float)back == (float)v : back == v) break;\n }\n int exp = atoi(strchr(buf, 'e') + 1);\n if (exp < -5 || exp >= 17) return snprintf(buf, size, \"%.*g\", p, v);\n int decimals = p - 1 - exp;\n return snprintf(buf, size, \"%.*f\", decimals > 0 ? decimals : 0, v);\n}\n\nvoid finch_str_from_float(FStr *out, double v) {\n char buf[64];\n int n = fmt_float(buf, sizeof buf, v, 0);\n str_heap(out, buf, n);\n}\n\nvoid finch_str_from_f32(FStr *out, double v) {\n char buf[64];\n int n = fmt_float(buf, sizeof buf, v, 1);\n str_heap(out, buf, n);\n}\n\n// str(x, 2): a fixed number of digits after the point\nvoid finch_str_fixed(FStr *out, double v, int64_t digits) {\n char buf[400];\n if (digits < 0) digits = 0;\n if (digits > 60) digits = 60;\n int n = snprintf(buf, sizeof buf, \"%.*f\", (int)digits, v);\n str_heap(out, buf, n);\n}\n\nvoid finch_print_float(double v, int32_t single) {\n char buf[64];\n fmt_float(buf, sizeof buf, v, single);\n fputs(buf, stdout);\n}\n\nvoid finch_str_from_char(FStr *out, char c) { str_heap(out, &c, 1); }\n\nvoid finch_str_from_bool(FStr *out, int b) { str_static(out, b ? \"true\" : \"false\"); }\n\nstatic void number_error(char *msg, size_t size, const FStr *s, const char *kind) {\n snprintf(msg, size, \"can't turn \\\"%.*s\\\" into %s\", (int)(s->len > 100 ? 100 : s->len), s->ptr ? s->ptr : \"\", kind);\n}\n\nstatic int parse_int(const FStr *s, int64_t *out) {\n const char *p = s->ptr ? s->ptr : \"\";\n while (isspace((unsigned char)*p)) p++;\n char *end;\n errno = 0;\n long long v = strtoll(p, &end, 10);\n while (isspace((unsigned char)*end)) end++;\n *out = v;\n return !(end == p || *end || errno == ERANGE);\n}\n\nstatic int parse_float(const FStr *s, double *out) {\n const char *p = s->ptr ? s->ptr : \"\";\n while (isspace((unsigned char)*p)) p++;\n char *end;\n double v = strtod(p, &end);\n while (isspace((unsigned char)*end)) end++;\n *out = v;\n return !(end == p || *end);\n}\n\nint64_t finch_str_to_int(const FStr *s, const char *file, int64_t line) {\n int64_t v;\n if (!parse_int(s, &v)) {\n char msg[200];\n number_error(msg, sizeof msg, s, \"int\");\n finch_panic(file, line, msg);\n }\n return v;\n}\n\ndouble finch_str_to_float(const FStr *s, const char *file, int64_t line) {\n double v;\n if (!parse_float(s, &v)) {\n char msg[200];\n number_error(msg, sizeof msg, s, \"float\");\n finch_panic(file, line, msg);\n }\n return v;\n}\n\n// ---------- errors as values: int(s) or 0, try read_file(p) ... ----------\n// Each gives back 1 on success; on failure 0, with the message in *err.\n\nstatic void set_error(FStr *err, const char *msg) { str_heap(err, msg, (int64_t)strlen(msg)); }\n\nint finch_str_try_int(const FStr *s, int64_t *out, FStr *err) {\n if (parse_int(s, out)) return 1;\n char msg[200];\n number_error(msg, sizeof msg, s, \"int\");\n set_error(err, msg);\n *out = 0;\n return 0;\n}\n\nint finch_str_try_float(const FStr *s, double *out, FStr *err) {\n if (parse_float(s, out)) return 1;\n char msg[200];\n number_error(msg, sizeof msg, s, \"float\");\n set_error(err, msg);\n *out = 0;\n return 0;\n}\n\nstatic void file_error(FStr *err, const char *what, const FStr *path) {\n char msg[300];\n snprintf(msg, sizeof msg, \"can't %s the file \\\"%.*s\\\": %s\", what, (int)(path->len > 200 ? 200 : path->len),\n path->ptr ? path->ptr : \"\", strerror(errno));\n set_error(err, msg);\n}\n\n// when `fn main() -> !` fails\nvoid finch_report_error(const FStr *msg) {\n fflush(stdout);\n fprintf(stderr, \"error: %.*s\\n\", (int)msg->len, msg->ptr ? msg->ptr : \"\");\n finch_str_drop((FStr *)msg);\n}\n\nvoid finch_str_sub(FStr *out, const FStr *s, int64_t start, int64_t end, const char *file, int64_t line) {\n if (start < 0 || end > s->len || start > end) {\n char msg[160];\n snprintf(msg, sizeof msg, \"sub(%lld, %lld) is out of range (the length is %lld)\", (long long)start,\n (long long)end, (long long)s->len);\n finch_panic(file, line, msg);\n }\n str_heap(out, s->ptr + start, end - start);\n}\n\nint64_t finch_str_find(const FStr *s, const FStr *t) {\n if (t->len == 0) return 0;\n for (int64_t i = 0; i + t->len <= s->len; i++)\n if (s->ptr[i] == t->ptr[0] && memcmp(s->ptr + i, t->ptr, (size_t)t->len) == 0) return i;\n return -1;\n}\n\nint finch_str_starts(const FStr *s, const FStr *t) {\n return t->len <= s->len && memcmp(s->ptr, t->ptr, (size_t)t->len) == 0;\n}\n\nint finch_str_ends(const FStr *s, const FStr *t) {\n return t->len <= s->len && memcmp(s->ptr + s->len - t->len, t->ptr, (size_t)t->len) == 0;\n}\n\nvoid finch_str_trim(FStr *out, const FStr *s) {\n int64_t a = 0, b = s->len;\n while (a < b && isspace((unsigned char)s->ptr[a])) a++;\n while (b > a && isspace((unsigned char)s->ptr[b - 1])) b--;\n str_heap(out, s->ptr + a, b - a);\n}\n\nvoid finch_str_upper(FStr *out, const FStr *s) {\n str_heap(out, s->ptr, s->len);\n for (int64_t i = 0; i < out->len; i++) out->ptr[i] = (char)toupper((unsigned char)out->ptr[i]);\n}\n\nvoid finch_str_lower(FStr *out, const FStr *s) {\n str_heap(out, s->ptr, s->len);\n for (int64_t i = 0; i < out->len; i++) out->ptr[i] = (char)tolower((unsigned char)out->ptr[i]);\n}\n\nvoid finch_str_replace(FStr *out, const FStr *s, const FStr *from, const FStr *to) {\n if (from->len == 0) {\n str_heap(out, s->ptr, s->len);\n return;\n }\n int64_t count = 0;\n for (int64_t i = 0; i + from->len <= s->len;) {\n if (memcmp(s->ptr + i, from->ptr, (size_t)from->len) == 0) {\n count++;\n i += from->len;\n } else {\n i++;\n }\n }\n int64_t len = s->len + count * (to->len - from->len);\n char *m = finch_alloc(len + 1), *w = m;\n for (int64_t i = 0; i < s->len;) {\n if (i + from->len <= s->len && memcmp(s->ptr + i, from->ptr, (size_t)from->len) == 0) {\n memcpy(w, to->ptr, (size_t)to->len);\n w += to->len;\n i += from->len;\n } else {\n *w++ = s->ptr[i++];\n }\n }\n *w = 0;\n out->ptr = m;\n out->len = len;\n out->cap = len + 1;\n}\n\nstatic void arr_push_str(FArr *a, const char *p, int64_t len);\n\n// \"a,b,,c\".split(\",\") -> [\"a\", \"b\", \"\", \"c\"]; split(\"\") gives single characters.\nvoid finch_str_split(FArr *out, const FStr *s, const FStr *sep) {\n out->ptr = NULL;\n out->len = out->cap = 0;\n if (sep->len == 0) {\n for (int64_t i = 0; i < s->len; i++) arr_push_str(out, s->ptr + i, 1);\n return;\n }\n int64_t start = 0;\n for (int64_t i = 0; i + sep->len <= s->len;) {\n if (memcmp(s->ptr + i, sep->ptr, (size_t)sep->len) == 0) {\n arr_push_str(out, s->ptr + start, i - start);\n i += sep->len;\n start = i;\n } else {\n i++;\n }\n }\n arr_push_str(out, s->ptr + start, s->len - start);\n}\n\nvoid finch_str_join(FStr *out, const FArr *parts, const FStr *sep) {\n const FStr *p = parts->ptr;\n int64_t len = 0;\n for (int64_t i = 0; i < parts->len; i++) len += p[i].len + (i ? sep->len : 0);\n char *m = finch_alloc(len + 1), *w = m;\n for (int64_t i = 0; i < parts->len; i++) {\n if (i) {\n memcpy(w, sep->ptr, (size_t)sep->len);\n w += sep->len;\n }\n memcpy(w, p[i].ptr, (size_t)p[i].len);\n w += p[i].len;\n }\n *w = 0;\n out->ptr = m;\n out->len = len;\n out->cap = len + 1;\n}\n\nvoid finch_str_repeat(FStr *out, const FStr *s, int64_t n) {\n if (n < 0) n = 0;\n int64_t len = s->len * n;\n char *m = finch_alloc(len + 1);\n for (int64_t i = 0; i < n; i++) memcpy(m + i * s->len, s->ptr, (size_t)s->len);\n m[len] = 0;\n out->ptr = m;\n out->len = len;\n out->cap = len + 1;\n}\n\nvoid finch_str_from_bytes(FStr *out, const FArr *bytes) { str_heap(out, bytes->ptr, bytes->len); }\n\n// ---------- building text: print(x) and str(x) of arrays, maps and structs ----------\n// A buffer starts as an empty str and grows on the heap.\n\nvoid finch_buf_add(FStr *b, const char *p, int64_t n) {\n if (b->len + n + 1 > b->cap) {\n int64_t cap = b->cap > 0 ? b->cap * 2 : 32;\n while (cap < b->len + n + 1) cap *= 2;\n b->ptr = must(realloc(b->cap > 0 ? b->ptr : NULL, (size_t)cap));\n b->cap = cap;\n }\n if (n) memcpy(b->ptr + b->len, p, (size_t)n);\n b->len += n;\n b->ptr[b->len] = 0;\n}\n\nstatic void buf_fmt(FStr *b, const char *fmt, ...) __attribute__((format(printf, 2, 3)));\nstatic void buf_fmt(FStr *b, const char *fmt, ...) {\n char t[64];\n va_list ap;\n va_start(ap, fmt);\n int n = vsnprintf(t, sizeof t, fmt, ap);\n va_end(ap);\n finch_buf_add(b, t, n);\n}\n\nvoid finch_buf_int(FStr *b, int64_t v) { buf_fmt(b, \"%lld\", (long long)v); }\nvoid finch_buf_uint(FStr *b, uint64_t v) { buf_fmt(b, \"%llu\", (unsigned long long)v); }\nvoid finch_buf_float(FStr *b, double v) {\n char t[64];\n finch_buf_add(b, t, fmt_float(t, sizeof t, v, 0));\n}\nvoid finch_buf_f32(FStr *b, double v) {\n char t[64];\n finch_buf_add(b, t, fmt_float(t, sizeof t, v, 1));\n}\nvoid finch_buf_bool(FStr *b, int v) { finch_buf_add(b, v ? \"true\" : \"false\", v ? 4 : 5); }\nvoid finch_buf_ptr(FStr *b, const void *p) {\n if (p) buf_fmt(b, \"0x%llx\", (unsigned long long)(uintptr_t)p);\n else finch_buf_add(b, \"null\", 4);\n}\n\nvoid finch_buf_char(FStr *b, char c, int quoted) {\n if (quoted) finch_buf_add(b, \"'\", 1);\n finch_buf_add(b, &c, 1);\n if (quoted) finch_buf_add(b, \"'\", 1);\n}\n\nvoid finch_buf_str(FStr *b, const FStr *s, int quoted) {\n if (quoted) finch_buf_add(b, \"\\\"\", 1);\n finch_buf_add(b, s->ptr, s->len);\n if (quoted) finch_buf_add(b, \"\\\"\", 1);\n}\n\n// print the buffer and free it\nvoid finch_buf_print(FStr *b) {\n if (b->len) fwrite(b->ptr, 1, (size_t)b->len, stdout);\n if (b->cap > 0) free(b->ptr);\n}\n\n// ---------- arrays ----------\n\nvoid finch_arr_reserve(FArr *a, int64_t need, int64_t es) {\n if (need <= a->cap) return;\n int64_t cap = a->cap * 2;\n if (cap < 4) cap = 4;\n if (cap < need) cap = need;\n a->ptr = must(realloc(a->cap > 0 ? a->ptr : NULL, (size_t)(cap * es)));\n a->cap = cap;\n}\n\nvoid finch_arr_make(FArr *out, int64_t len, int64_t es) {\n if (len < 0) len = 0;\n out->ptr = len ? finch_zalloc(len * es) : NULL;\n out->len = len;\n out->cap = len;\n}\n\n// grow or shrink to `len`; new elements are zero\nvoid finch_arr_resize(FArr *a, int64_t len, int64_t es) {\n if (len < 0) len = 0;\n finch_arr_reserve(a, len, es);\n if (len > a->len) memset((char *)a->ptr + a->len * es, 0, (size_t)((len - a->len) * es));\n a->len = len;\n}\n\n// a byte-for-byte copy of the elements (the compiler deep-copies owning elements afterwards)\nvoid finch_arr_clone_raw(FArr *out, const FArr *a, int64_t es) {\n out->len = a->len;\n out->cap = a->len;\n out->ptr = a->len ? finch_alloc(a->len * es) : NULL;\n if (a->len) memcpy(out->ptr, a->ptr, (size_t)(a->len * es));\n}\n\nvoid finch_arr_free(FArr *a) {\n if (a->cap > 0) free(a->ptr);\n}\n\n// open a hole at i (0 <= i <= len), len grows by one\nvoid finch_arr_insert_gap(FArr *a, int64_t i, int64_t es, const char *file, int64_t line) {\n if (i < 0 || i > a->len) finch_panic_index(file, line, i, a->len);\n finch_arr_reserve(a, a->len + 1, es);\n char *p = a->ptr;\n memmove(p + (i + 1) * es, p + i * es, (size_t)((a->len - i) * es));\n a->len++;\n}\n\n// close the hole at i (the element was already moved out), len shrinks by one\nvoid finch_arr_remove_gap(FArr *a, int64_t i, int64_t es) {\n char *p = a->ptr;\n memmove(p + i * es, p + (i + 1) * es, (size_t)((a->len - i - 1) * es));\n a->len--;\n}\n\nstatic void arr_push_str(FArr *a, const char *p, int64_t len) {\n finch_arr_reserve(a, a->len + 1, (int64_t)sizeof(FStr));\n str_heap((FStr *)a->ptr + a->len, p, len);\n a->len++;\n}\n\nvoid finch_bytes_from_str(FArr *out, const FStr *s) {\n out->len = out->cap = s->len;\n out->ptr = s->len ? finch_alloc(s->len) : NULL;\n if (s->len) memcpy(out->ptr, s->ptr, (size_t)s->len);\n}\n\n// ---------- program arguments, input and files ----------\n\nvoid finch_args(FArr *out, int argc, char **argv) {\n out->len = out->cap = argc;\n out->ptr = finch_alloc((int64_t)sizeof(FStr) * (argc ? argc : 1));\n for (int i = 0; i < argc; i++) str_heap((FStr *)out->ptr + i, argv[i], (int64_t)strlen(argv[i]));\n}\n\n// after main: the arguments go\nvoid finch_args_free(FArr *a) {\n for (int64_t i = 0; i < a->len; i++) finch_str_drop((FStr *)a->ptr + i);\n finch_arr_free(a);\n}\n\n// input(\"Name? \") prints the prompt and reads one line, without the newline.\nvoid finch_input(FStr *out, const FStr *prompt) {\n if (prompt->len) fwrite(prompt->ptr, 1, (size_t)prompt->len, stdout);\n fflush(stdout);\n // read one line of any length (getline isn't available everywhere)\n size_t size = 128, n = 0;\n char *line = finch_alloc((int64_t)size);\n int c, got = 0;\n while ((c = fgetc(stdin)) != EOF) {\n got = 1;\n if (c == '\\n') break;\n if (n + 1 >= size) line = must(realloc(line, size *= 2));\n line[n++] = (char)c;\n }\n if (!got) {\n free(line);\n str_static(out, empty);\n return;\n }\n while (n > 0 && line[n - 1] == '\\r') n--;\n line[n] = 0;\n out->ptr = line;\n out->len = (int64_t)n;\n out->cap = (int64_t)size;\n}\n\nint finch_file_exists(const FStr *path) {\n FILE *f = fopen(path->ptr, \"rb\");\n if (f) fclose(f);\n return f != NULL;\n}\n\nint finch_try_read_file(FStr *out, const FStr *path, FStr *err) {\n FILE *f = fopen(path->ptr ? path->ptr : \"\", \"rb\");\n if (!f) {\n file_error(err, \"read\", path);\n return 0;\n }\n fseek(f, 0, SEEK_END);\n long size = ftell(f);\n fseek(f, 0, SEEK_SET);\n char *m = finch_alloc(size + 1);\n size_t got = fread(m, 1, (size_t)size, f);\n fclose(f);\n m[got] = 0;\n out->ptr = m;\n out->len = (int64_t)got;\n out->cap = size + 1;\n return 1;\n}\n\nvoid finch_read_file(FStr *out, const FStr *path, const char *file, int64_t line) {\n FILE *f = fopen(path->ptr ? path->ptr : \"\", \"rb\");\n if (!f) {\n char msg[300];\n snprintf(msg, sizeof msg, \"can't read the file \\\"%.*s\\\": %s\", (int)(path->len > 200 ? 200 : path->len),\n path->ptr, strerror(errno));\n finch_panic(file, line, msg);\n }\n fseek(f, 0, SEEK_END);\n long size = ftell(f);\n fseek(f, 0, SEEK_SET);\n char *m = finch_alloc(size + 1);\n size_t got = fread(m, 1, (size_t)size, f);\n fclose(f);\n m[got] = 0;\n out->ptr = m;\n out->len = (int64_t)got;\n out->cap = size + 1;\n}\n\n// shell(\"ls -l\"): run a command, give back its exit code\nint64_t finch_shell(const FStr *cmd) {\n fflush(stdout);\n int status = system(cmd->ptr ? cmd->ptr : \"\");\n#ifdef _WIN32\n return status; // Windows gives the exit code directly\n#else\n if (status == -1) return -1;\n if (WIFEXITED(status)) return WEXITSTATUS(status);\n return 128 + (WIFSIGNALED(status) ? WTERMSIG(status) : 0);\n#endif\n}\n\nint finch_delete_file(const FStr *path) { return remove(path->ptr ? path->ptr : \"\") == 0; }\n\nint finch_try_delete_file(const FStr *path, FStr *err) {\n if (remove(path->ptr ? path->ptr : \"\") == 0) return 1;\n file_error(err, \"delete\", path);\n return 0;\n}\n\n// stdin/stdout/stderr are macros on some systems (Windows), so Finch asks for them here\nvoid *finch_std_stream(int i) { return i == 0 ? (void *)stdin : i == 1 ? (void *)stdout : (void *)stderr; }\n\nint finch_write_file(const FStr *path, const FStr *text) {\n FILE *f = fopen(path->ptr ? path->ptr : \"\", \"wb\");\n if (!f) return 0;\n size_t put = text->len ? fwrite(text->ptr, 1, (size_t)text->len, f) : 0;\n return fclose(f) == 0 && put == (size_t)text->len;\n}\n\nint finch_try_write_file(const FStr *path, const FStr *text, FStr *err) {\n errno = 0;\n if (finch_write_file(path, text)) return 1;\n if (!errno) errno = EIO;\n file_error(err, \"write\", path);\n return 0;\n}\n\n// ---------- folders (std/fs.fch, through std/finch_fs.h) ----------\n\nint finch_fs_is_dir(const char *path) {\n#ifdef _WIN32\n struct _finddata_t d;\n intptr_t h = _findfirst(path, &d);\n if (h == -1) return 0;\n _findclose(h);\n return (d.attrib & _A_SUBDIR) != 0;\n#else\n struct stat st;\n return stat(path, &st) == 0 && S_ISDIR(st.st_mode);\n#endif\n}\n\nint finch_fs_exists(const char *path) {\n#ifdef _WIN32\n return _access(path, 0) == 0;\n#else\n struct stat st;\n return stat(path, &st) == 0;\n#endif\n}\n\nstatic int make_one_dir(const char *p) {\n#ifdef _WIN32\n return _mkdir(p) == 0 || errno == EEXIST;\n#else\n return mkdir(p, 0777) == 0 || errno == EEXIST;\n#endif\n}\n\n// mkdir -p: the parents too; 1 if the folder is there afterwards\nint finch_fs_mkdir(const char *path) {\n size_t n = strlen(path);\n char *p = finch_alloc((int64_t)n + 1);\n memcpy(p, path, n + 1);\n for (size_t i = 1; i < n; i++) {\n if (p[i] == '/' || p[i] == '\\\\') {\n if (p[i - 1] == ':') continue; /* C: is a drive */\n char c = p[i];\n p[i] = 0;\n make_one_dir(p);\n p[i] = c;\n }\n }\n int ok = make_one_dir(p) && finch_fs_is_dir(p);\n free(p);\n return ok;\n}\n\nint finch_fs_remove_dir(const char *path) {\n#ifdef _WIN32\n return _rmdir(path) == 0;\n#else\n return rmdir(path) == 0;\n#endif\n}\n\n// the names in a folder, one per call (\"\" at the end), without . and ..\ntypedef struct {\n#ifdef _WIN32\n intptr_t h;\n struct _finddata_t d;\n int first;\n#else\n DIR *d;\n#endif\n} FsList;\n\nvoid *finch_fs_list_open(const char *path) {\n FsList *l = finch_zalloc((int64_t)sizeof(FsList));\n#ifdef _WIN32\n size_t n = strlen(path);\n char *pat = finch_alloc((int64_t)n + 3);\n memcpy(pat, path, n);\n memcpy(pat + n, \"\\\\*\", 3);\n l->h = _findfirst(pat, &l->d);\n free(pat);\n if (l->h == -1) {\n free(l);\n return NULL;\n }\n l->first = 1;\n#else\n l->d = opendir(path);\n if (!l->d) {\n free(l);\n return NULL;\n }\n#endif\n return l;\n}\n\nconst char *finch_fs_list_next(void *handle) {\n FsList *l = handle;\n for (;;) {\n#ifdef _WIN32\n if (!l->first && _findnext(l->h, &l->d) != 0) return \"\";\n l->first = 0;\n const char *name = l->d.name;\n#else\n struct dirent *e = readdir(l->d);\n if (!e) return \"\";\n const char *name = e->d_name;\n#endif\n if (strcmp(name, \".\") != 0 && strcmp(name, \"..\") != 0) return name;\n }\n}\n\nvoid finch_fs_list_close(void *handle) {\n FsList *l = handle;\n#ifdef _WIN32\n _findclose(l->h);\n#else\n closedir(l->d);\n#endif\n free(l);\n}\n\n// where a program keeps its own files for this user (saves, settings); \"\" if unknown\n// Linux: $XDG_DATA_HOME or ~/.local/share macOS: ~/Library/Application Support Windows: %APPDATA%\nconst char *finch_fs_data_home(void) {\n static char buf[4096];\n const char *v;\n#if defined(_WIN32)\n v = getenv(\"APPDATA\");\n snprintf(buf, sizeof buf, \"%s\", v ? v : \"\");\n#elif defined(__APPLE__)\n v = getenv(\"HOME\");\n if (v) snprintf(buf, sizeof buf, \"%s/Library/Application Support\", v);\n else buf[0] = 0;\n#else\n v = getenv(\"XDG_DATA_HOME\");\n if (v && *v) snprintf(buf, sizeof buf, \"%s\", v);\n else if ((v = getenv(\"HOME\"))) snprintf(buf, sizeof buf, \"%s/.local/share\", v);\n else buf[0] = 0;\n#endif\n return buf;\n}\n\n// ---------- threads (std/thread.fch, through std/finch_thread.h) ----------\n\ntypedef struct {\n void (*fn)(void *);\n void *arg;\n} ThreadStart;\n\n#ifdef _WIN32\nstatic DWORD WINAPI thread_main(LPVOID p) {\n ThreadStart st = *(ThreadStart *)p;\n free(p);\n st.fn(st.arg);\n return 0;\n}\n#else\nstatic void *thread_main(void *p) {\n ThreadStart st = *(ThreadStart *)p;\n free(p);\n st.fn(st.arg);\n return NULL;\n}\n#endif\n\n// run fn(arg) on a new thread; NULL if it couldn't start\nvoid *finch_thread_spawn(void (*fn)(void *), void *arg) {\n ThreadStart *st = finch_alloc((int64_t)sizeof(ThreadStart));\n st->fn = fn;\n st->arg = arg;\n#ifdef _WIN32\n HANDLE h = CreateThread(NULL, 0, thread_main, st, 0, NULL);\n if (!h) {\n free(st);\n return NULL;\n }\n return h;\n#else\n pthread_t *t = finch_alloc((int64_t)sizeof(pthread_t));\n if (pthread_create(t, NULL, thread_main, st) != 0) {\n free(st);\n free(t);\n return NULL;\n }\n return t;\n#endif\n}\n\n// wait for the thread to end; 1 if that worked\nint finch_thread_join(void *t) {\n if (!t) return 0;\n#ifdef _WIN32\n int ok = WaitForSingleObject((HANDLE)t, INFINITE) == WAIT_OBJECT_0;\n CloseHandle((HANDLE)t);\n return ok;\n#else\n int ok = pthread_join(*(pthread_t *)t, NULL) == 0;\n free(t);\n return ok;\n#endif\n}\n\nvoid *finch_mutex_new(void) {\n#ifdef _WIN32\n CRITICAL_SECTION *m = finch_alloc((int64_t)sizeof(CRITICAL_SECTION));\n InitializeCriticalSection(m);\n#else\n pthread_mutex_t *m = finch_alloc((int64_t)sizeof(pthread_mutex_t));\n pthread_mutex_init(m, NULL);\n#endif\n return m;\n}\n\nvoid finch_mutex_lock(void *m) {\n#ifdef _WIN32\n EnterCriticalSection((CRITICAL_SECTION *)m);\n#else\n pthread_mutex_lock((pthread_mutex_t *)m);\n#endif\n}\n\nvoid finch_mutex_unlock(void *m) {\n#ifdef _WIN32\n LeaveCriticalSection((CRITICAL_SECTION *)m);\n#else\n pthread_mutex_unlock((pthread_mutex_t *)m);\n#endif\n}\n\nvoid finch_mutex_free(void *m) {\n if (!m) return;\n#ifdef _WIN32\n DeleteCriticalSection((CRITICAL_SECTION *)m);\n#else\n pthread_mutex_destroy((pthread_mutex_t *)m);\n#endif\n free(m);\n}\n\nint finch_cpu_count(void) {\n#ifdef _WIN32\n SYSTEM_INFO si;\n GetSystemInfo(&si);\n return (int)si.dwNumberOfProcessors;\n#else\n long n = sysconf(_SC_NPROCESSORS_ONLN);\n return n > 0 ? (int)n : 1;\n#endif\n}\n\n// ---------- maps ----------\n// map[K]V = { entries; len; used; cap; index; icap; adds }\n// entries: `used` slots of es bytes, each { uint64 hash; K key; V value }, in insertion order.\n// A removed entry keeps its slot (hash 0) until the next resize packs the slots.\n// index: icap slots (a power of two, at least 2 * cap): -1 empty, -2 removed, else an entry number.\n// adds: how many keys were ever added; a `for` over the map stops with an error if it changes.\n// The compiler stores, copies and drops keys and values; the runtime only finds and places entries.\n// Keys are compared byte for byte (numbers, chars, bools) or as text (kstr).\n\ntypedef struct {\n char *entries;\n int64_t len, used, cap;\n int64_t *index;\n int64_t icap;\n int64_t adds;\n} FMap;\n\nstatic uint64_t map_hash(const void *key, int64_t ks, int kstr) {\n const unsigned char *p;\n size_t n;\n if (kstr) {\n const FStr *s = key;\n p = (const unsigned char *)s->ptr;\n n = (size_t)s->len;\n } else {\n p = key;\n n = (size_t)ks;\n }\n uint64_t h = 1469598103934665603ULL; // FNV-1a, then mixed so the low bits spread well\n for (size_t i = 0; i < n; i++) {\n h ^= p[i];\n h *= 1099511628211ULL;\n }\n h ^= h >> 31;\n h *= 0xbf58476d1ce4e5b9ULL;\n h ^= h >> 29;\n return h | 1; // 0 marks a removed entry\n}\n\nstatic int map_key_eq(const char *a, const void *b, int64_t ks, int kstr) {\n if (!kstr) return memcmp(a, b, (size_t)ks) == 0;\n const FStr *x = (const FStr *)a, *y = b;\n return x->len == y->len && (x->len == 0 || memcmp(x->ptr, y->ptr, (size_t)x->len) == 0);\n}\n\nstatic void map_reindex(FMap *m, int64_t es) {\n if (m->len != m->used) { // pack the slots of removed entries away\n int64_t j = 0;\n for (int64_t i = 0; i < m->used; i++) {\n char *e = m->entries + i * es;\n if (*(uint64_t *)e == 0) continue;\n if (i != j) memmove(m->entries + j * es, e, (size_t)es);\n j++;\n }\n m->used = j;\n }\n int64_t icap = 16;\n while (icap < m->cap * 2) icap *= 2;\n free(m->index);\n m->index = finch_alloc(icap * (int64_t)sizeof(int64_t));\n memset(m->index, 0xff, (size_t)icap * sizeof(int64_t));\n m->icap = icap;\n for (int64_t i = 0; i < m->used; i++) {\n uint64_t h = *(uint64_t *)(m->entries + i * es);\n int64_t s = (int64_t)(h & (uint64_t)(icap - 1));\n while (m->index[s] != -1) s = (s + 1) & (icap - 1);\n m->index[s] = i;\n }\n}\n\n// the entry number of `key`, or -1\nint64_t finch_map_find(const FMap *m, const void *key, int64_t es, int64_t ks, int32_t kstr) {\n if (m->len == 0) return -1;\n uint64_t h = map_hash(key, ks, kstr);\n for (int64_t s = (int64_t)(h & (uint64_t)(m->icap - 1));; s = (s + 1) & (m->icap - 1)) {\n int64_t i = m->index[s];\n if (i == -1) return -1;\n if (i < 0) continue;\n char *e = m->entries + i * es;\n if (*(uint64_t *)e == h && map_key_eq(e + 8, key, ks, kstr)) return i;\n }\n}\n\n// The entry for `key`. A new one is zeroed except its hash (*added = 1); the compiler then stores the key.\nint64_t finch_map_slot(FMap *m, const void *key, int64_t es, int64_t ks, int32_t kstr, int32_t *added) {\n int64_t i = finch_map_find(m, key, es, ks, kstr);\n *added = i < 0;\n if (i >= 0) return i;\n if (m->used == m->cap) {\n if (m->len * 4 < m->cap * 3) { // many removed slots: pack them instead of growing\n map_reindex(m, es);\n } else {\n m->cap = m->cap ? m->cap * 2 : 8;\n m->entries = must(realloc(m->entries, (size_t)(m->cap * es)));\n map_reindex(m, es);\n }\n }\n uint64_t h = map_hash(key, ks, kstr);\n i = m->used++;\n char *e = m->entries + i * es;\n memset(e, 0, (size_t)es);\n *(uint64_t *)e = h;\n int64_t s = (int64_t)(h & (uint64_t)(m->icap - 1));\n while (m->index[s] >= 0) s = (s + 1) & (m->icap - 1);\n m->index[s] = i;\n m->len++;\n m->adds++;\n return i;\n}\n\n// Forget entry i (its key and value were already dropped by the compiler).\nvoid finch_map_remove_at(FMap *m, int64_t i, int64_t es) {\n char *e = m->entries + i * es;\n uint64_t h = *(uint64_t *)e;\n int64_t s = (int64_t)(h & (uint64_t)(m->icap - 1));\n while (m->index[s] != i) s = (s + 1) & (m->icap - 1);\n m->index[s] = -2;\n *(uint64_t *)e = 0;\n if (--m->len == 0) {\n m->used = 0;\n memset(m->index, 0xff, (size_t)m->icap * sizeof(int64_t));\n }\n}\n\nvoid finch_map_clear(FMap *m) {\n m->len = m->used = 0;\n if (m->index) memset(m->index, 0xff, (size_t)m->icap * sizeof(int64_t));\n}\n\nvoid finch_map_free(FMap *m) {\n free(m->entries);\n free(m->index);\n}\n\n// A copy of the slots and the index; the compiler then copies the keys and values that own memory.\nvoid finch_map_clone_raw(FMap *out, const FMap *m, int64_t es) {\n *out = *m;\n if (!m->cap) return;\n out->entries = finch_alloc(m->cap * es);\n memcpy(out->entries, m->entries, (size_t)(m->used * es));\n out->index = finch_alloc(m->icap * (int64_t)sizeof(int64_t));\n memcpy(out->index, m->index, (size_t)m->icap * sizeof(int64_t));\n}\n\n__attribute__((noreturn, cold)) void finch_map_changed(const char *file, int64_t line) {\n finch_panic(file, line, \"a key was added to the map while looping over it (collect the new keys, and add them after the loop)\");\n}\n\n__attribute__((noreturn, cold)) void finch_map_missing(const char *file, int64_t line, const FStr *key) {\n char msg[200];\n snprintf(msg, sizeof msg, \"the key %.*s is not in the map (check with .has(key), or use .get(key, default))\",\n (int)(key->len > 100 ? 100 : key->len), key->ptr ? key->ptr : \"\");\n finch_panic(file, line, msg);\n}\n") list.push("// Files and folders: bytes, folders, listing, and a place for a program's own files.\n//\n// import fs\n// data := try fs.read_bytes(\"atlas.png\") the whole file as []u8 (any bytes, NUL included)\n// try fs.write_bytes(\"out.bin\", data)\n// try fs.mkdir(\"saves/slot1\") with the folders above it\n// names := try fs.list(\"saves\") the names in a folder, sorted\n// fs.exists(p) fs.is_dir(p) try fs.remove_dir(p)\n// dir := try fs.data_dir(\"MyGame\") a folder for this user's saves, made if needed\nimport \"finch_fs.h\"\n\nfn read_bytes(str path) -> []u8! {\n text := try read_file(path)\n return text.bytes()\n}\n\nfn write_bytes(str path, []u8 data) -> ! {\n try write_file(path, str(data))\n}\n\nfn exists(str path) -> bool {\n return finch_fs_exists(path) != 0\n}\n\nfn is_dir(str path) -> bool {\n return finch_fs_is_dir(path) != 0\n}\n\nfn mkdir(str path) -> ! {\n if finch_fs_mkdir(path) == 0 {\n return error(\"can't make the folder \\\"\" + path + \"\\\"\")\n }\n}\n\n// only an empty folder\nfn remove_dir(str path) -> ! {\n if finch_fs_remove_dir(path) == 0 {\n return error(\"can't remove the folder \\\"\" + path + \"\\\" (is it empty?)\")\n }\n}\n\nfn list(str path) -> []str! {\n l := finch_fs_list_open(path)\n if l == null {\n return error(\"can't list the folder \\\"\" + path + \"\\\"\")\n }\n []str names\n while true {\n name := finch_fs_list_next(l)\n if name.len == 0 {\n break\n }\n names.push(name)\n }\n finch_fs_list_close(l)\n names.sort()\n return names\n}\n\n// The folder where a program keeps this user's files (saves, settings), made if it isn't there:\n// ~/.local/share/ on Linux, ~/Library/Application Support/ on macOS, %APPDATA%\\ on Windows.\nfn data_dir(str app) -> str! {\n home := finch_fs_data_home()\n if home.len == 0 {\n return error(\"this system doesn't say where programs keep their files\")\n }\n dir := home + \"/\" + app\n try mkdir(dir)\n return dir\n}\n") list.push("// JSON: read it, build it, write it.\n//\n// import json\n//\n// doc := try json.parse(\"{\\\"name\\\": \\\"Finch\\\", \\\"tags\\\": [\\\"fast\\\", \\\"small\\\"], \\\"stars\\\": 5}\")\n// print(doc.get(\"name\").str_or(\"\")) Finch\n// print(doc.get(\"tags\").at(1).str_or(\"\")) small\n// print(doc.get(\"stars\").int_or(0)) 5\n// print(doc.get(\"missing\").is_null()) true (a missing key is null, so chains never fail)\n//\n// out := json.new_object()\n// out.set(\"ok\", json.new_bool(true))\n// list := json.new_list()\n// list.push(json.new_number(1.5))\n// out.set(\"values\", list)\n// print(json.stringify(out)) {\"ok\":true,\"values\":[1.5]}\n// print(json.pretty(out)) the same, over several lines\n\nenum Kind { Null, Bool, Number, Text, List, Object }\n\n// One JSON value. `kind` says which of the other fields it uses: flag for Bool, num for Number,\n// text for Text, items for List, and items with keys (the same length, in the same order) for Object.\nstruct Json {\n Kind kind\n bool flag\n float num\n str text\n []Json items\n []str keys\n}\n\nfn new_null() -> Json {\n return Json(kind: Kind.Null)\n}\n\nfn new_bool(bool b) -> Json {\n return Json(kind: Kind.Bool, flag: b)\n}\n\nfn new_number(float n) -> Json {\n return Json(kind: Kind.Number, num: n)\n}\n\nfn new_text(str s) -> Json {\n return Json(kind: Kind.Text, text: s)\n}\n\nfn new_list() -> Json {\n return Json(kind: Kind.List)\n}\n\nfn new_object() -> Json {\n return Json(kind: Kind.Object)\n}\n\n// ---------- looking inside ----------\n\nfn Json.is_null() -> bool {\n return self.kind == Kind.Null\n}\n\n// the number of elements of a list, of keys of an object, of bytes of a text; 0 for the rest\nfn Json.len() -> int {\n return match self.kind {\n List, Object -> self.items.len\n Text -> self.text.len\n else -> 0\n }\n}\n\nfn Json.has(str key) -> bool {\n return self.kind == Kind.Object && self.keys.contains(key)\n}\n\n// the value of a key of an object; null if there is none (or this is not an object)\nfn Json.get(str key) -> Json {\n if self.kind != Kind.Object {\n return new_null()\n }\n i := self.keys.find(key)\n if i < 0 {\n return new_null()\n }\n return self.items[i]\n}\n\n// the element of a list; null if there is none\nfn Json.at(int i) -> Json {\n if self.kind != Kind.List || i < 0 || i >= self.items.len {\n return new_null()\n }\n return self.items[i]\n}\n\n// the value if it is of that kind, else what was given\nfn Json.str_or(str fallback) -> str {\n if self.kind == Kind.Text {\n return self.text\n }\n return fallback\n}\n\nfn Json.float_or(float fallback) -> float {\n if self.kind == Kind.Number {\n return self.num\n }\n return fallback\n}\n\nfn Json.int_or(int fallback) -> int {\n if self.kind == Kind.Number {\n return int(self.num)\n }\n return fallback\n}\n\nfn Json.bool_or(bool fallback) -> bool {\n if self.kind == Kind.Bool {\n return self.flag\n }\n return fallback\n}\n\n// the keys of an object, in the order they were written\nfn Json.names() -> []str {\n return self.keys\n}\n\n// ---------- building ----------\n\n// add to the end of a list\nfn Json.push(Json v) {\n self.items.push(v)\n}\n\n// give a key a value in an object; a key that is already there is replaced\nfn Json.set(str key, Json v) {\n i := self.keys.find(key)\n if i >= 0 {\n self.items[i] = v\n return\n }\n self.keys.push(key)\n self.items.push(v)\n}\n\n// ---------- writing ----------\n\nfn quote(str s) -> str {\n []str parts = [\"\\\"\"]\n for c in s {\n n := int(c)\n if c == '\"' {\n parts.push(\"\\\\\\\"\")\n } else if c == '\\\\' {\n parts.push(\"\\\\\\\\\")\n } else if c == '\\n' {\n parts.push(\"\\\\n\")\n } else if c == '\\r' {\n parts.push(\"\\\\r\")\n } else if c == '\\t' {\n parts.push(\"\\\\t\")\n } else if n < 32 {\n parts.push(\"\\\\u00\" + hexdigit(n / 16) + hexdigit(n % 16))\n } else {\n parts.push(str(c))\n }\n }\n parts.push(\"\\\"\")\n return parts.join(\"\")\n}\n\nfn hexdigit(int n) -> str {\n digits := \"0123456789abcdef\"\n return str(digits[n])\n}\n\nfn number_text(float n) -> str {\n if n == float(int(n)) && n > -1000000000000000.0 && n < 1000000000000000.0 {\n return str(int(n))\n }\n return str(n)\n}\n\nstruct Writer {\n []str out\n}\n\nfn write(Json j, str indent, str pad, ptr[Writer] w) {\n nl := \"\"\n inner := \"\"\n sep := \",\"\n colon := \":\"\n if indent.len > 0 {\n nl = \"\\n\"\n inner = pad + indent\n colon = \": \"\n }\n match j.kind {\n Null -> w.out.push(\"null\")\n Bool -> {\n if j.flag {\n w.out.push(\"true\")\n } else {\n w.out.push(\"false\")\n }\n }\n Number -> w.out.push(number_text(j.num))\n Text -> w.out.push(quote(j.text))\n List -> {\n if j.items.len == 0 {\n w.out.push(\"[]\")\n return\n }\n w.out.push(\"[\" + nl)\n for i, item in j.items {\n if i > 0 {\n w.out.push(sep + nl)\n }\n w.out.push(inner)\n write(item, indent, inner, w)\n }\n w.out.push(nl + pad + \"]\")\n }\n Object -> {\n if j.items.len == 0 {\n w.out.push(\"{}\")\n return\n }\n w.out.push(\"{\" + nl)\n for i, item in j.items {\n if i > 0 {\n w.out.push(sep + nl)\n }\n w.out.push(inner + quote(j.keys[i]) + colon)\n write(item, indent, inner, w)\n }\n w.out.push(nl + pad + \"}\")\n }\n }\n}\n\n// the value as text on one line\nfn stringify(Json j) -> str {\n w := Writer()\n write(j, \"\", \"\", addr(w))\n return w.out.join(\"\")\n}\n\n// the value as text over several lines, indented by two spaces\nfn pretty(Json j) -> str {\n w := Writer()\n write(j, \" \", \"\", addr(w))\n return w.out.join(\"\")\n}\n\n// ---------- reading ----------\n\nstruct Reader {\n str src\n int i\n}\n\n// the line and column of a position, for a message\nfn where(ptr[Reader] r) -> str {\n line := 1\n col := 1\n for k in 0..r.i {\n if k < r.src.len && r.src[k] == '\\n' {\n line += 1\n col = 1\n } else {\n col += 1\n }\n }\n return \"line \" + str(line) + \", column \" + str(col)\n}\n\nfn peek(ptr[Reader] r) -> char {\n if r.i < r.src.len {\n return r.src[r.i]\n }\n return char(0)\n}\n\nfn skip_space(ptr[Reader] r) {\n while r.i < r.src.len && (r.src[r.i] == ' ' || r.src[r.i] == '\\n' || r.src[r.i] == '\\t' || r.src[r.i] == '\\r') {\n r.i += 1\n }\n}\n\nfn unexpected(ptr[Reader] r, str wanted) -> str {\n if r.i >= r.src.len {\n return \"json: the text ends where \" + wanted + \" should be (\" + where(r) + \")\"\n }\n return \"json: found '\" + str(r.src[r.i]) + \"' where \" + wanted + \" should be (\" + where(r) + \")\"\n}\n\n// does `word` come next?\nfn comes(ptr[Reader] r, str word) -> bool {\n return r.src.len - r.i >= word.len && r.src.sub(r.i, r.i + word.len) == word\n}\n\nfn expect_word(ptr[Reader] r, str word) -> ! {\n if !comes(r, word) {\n return error(unexpected(r, \"'\" + word + \"'\"))\n }\n r.i += word.len\n}\n\nfn hex_value(char c) -> int {\n if c >= '0' && c <= '9' {\n return int(c) - int('0')\n }\n if c >= 'a' && c <= 'f' {\n return int(c) - int('a') + 10\n }\n if c >= 'A' && c <= 'F' {\n return int(c) - int('A') + 10\n }\n return -1\n}\n\n// a code point as UTF-8\nfn utf8(int cp) -> str {\n []u8 b\n if cp < 128 {\n b.push(u8(cp))\n } else if cp < 2048 {\n b.push(u8(192 + cp / 64))\n b.push(u8(128 + cp % 64))\n } else if cp < 65536 {\n b.push(u8(224 + cp / 4096))\n b.push(u8(128 + (cp / 64) % 64))\n b.push(u8(128 + cp % 64))\n } else {\n b.push(u8(240 + cp / 262144))\n b.push(u8(128 + (cp / 4096) % 64))\n b.push(u8(128 + (cp / 64) % 64))\n b.push(u8(128 + cp % 64))\n }\n return str(b)\n}\n\nfn read_hex4(ptr[Reader] r) -> int! {\n n := 0\n for k in 0..4 {\n if r.i >= r.src.len || hex_value(r.src[r.i]) < 0 {\n return error(unexpected(r, \"a hexadecimal digit\"))\n }\n n = n * 16 + hex_value(r.src[r.i])\n r.i += 1\n }\n return n\n}\n\nfn read_string(ptr[Reader] r) -> str! {\n r.i += 1 // the opening quote\n []str parts\n start := r.i\n while true {\n if r.i >= r.src.len {\n return error(\"json: a text is never closed (\" + where(r) + \")\")\n }\n c := r.src[r.i]\n if c == '\"' {\n parts.push(r.src.sub(start, r.i))\n r.i += 1\n return parts.join(\"\")\n }\n if int(c) < 32 {\n return error(\"json: a line break or control character inside a text (\" + where(r) + \")\")\n }\n if c != '\\\\' {\n r.i += 1\n continue\n }\n parts.push(r.src.sub(start, r.i))\n r.i += 1\n e := peek(r)\n r.i += 1\n if e == 'n' {\n parts.push(\"\\n\")\n } else if e == 't' {\n parts.push(\"\\t\")\n } else if e == 'r' {\n parts.push(\"\\r\")\n } else if e == 'b' {\n parts.push(utf8(8))\n } else if e == 'f' {\n parts.push(utf8(12))\n } else if e == '\"' || e == '\\\\' || e == '/' {\n parts.push(str(e))\n } else if e == 'u' {\n cp := try read_hex4(r)\n // a pair of 😀 stands for one character above U+FFFF\n if cp >= 55296 && cp < 56320 && comes(r, \"\\\\u\") {\n r.i += 2\n low := try read_hex4(r)\n cp = 65536 + (cp - 55296) * 1024 + (low - 56320)\n }\n parts.push(utf8(cp))\n } else {\n return error(\"json: unknown escape \\\\\" + str(e) + \" in a text (\" + where(r) + \")\")\n }\n start = r.i\n }\n}\n\nfn read_number(ptr[Reader] r) -> Json! {\n start := r.i\n if peek(r) == '-' {\n r.i += 1\n }\n while r.i < r.src.len && ((r.src[r.i] >= '0' && r.src[r.i] <= '9') || r.src[r.i] == '.' || r.src[r.i] == 'e' || r.src[r.i] == 'E' || r.src[r.i] == '+' || r.src[r.i] == '-') {\n r.i += 1\n }\n n := float(r.src.sub(start, r.i)) or {\n r.i = start\n return error(unexpected(r, \"a number\"))\n }\n return new_number(n)\n}\n\nfn read_value(ptr[Reader] r) -> Json! {\n skip_space(r)\n c := peek(r)\n if r.i >= r.src.len {\n return error(unexpected(r, \"a value\"))\n }\n if c == '{' {\n obj := new_object()\n r.i += 1\n skip_space(r)\n if peek(r) == '}' {\n r.i += 1\n return obj\n }\n while true {\n skip_space(r)\n if peek(r) != '\"' {\n return error(unexpected(r, \"a key in quotes\"))\n }\n key := try read_string(r)\n skip_space(r)\n if peek(r) != ':' {\n return error(unexpected(r, \"':'\"))\n }\n r.i += 1\n v := try read_value(r)\n obj.set(key, v)\n skip_space(r)\n if peek(r) == ',' {\n r.i += 1\n continue\n }\n if peek(r) == '}' {\n r.i += 1\n return obj\n }\n return error(unexpected(r, \"',' or '}'\"))\n }\n }\n if c == '[' {\n list := new_list()\n r.i += 1\n skip_space(r)\n if peek(r) == ']' {\n r.i += 1\n return list\n }\n while true {\n v := try read_value(r)\n list.push(v)\n skip_space(r)\n if peek(r) == ',' {\n r.i += 1\n continue\n }\n if peek(r) == ']' {\n r.i += 1\n return list\n }\n return error(unexpected(r, \"',' or ']'\"))\n }\n }\n if c == '\"' {\n s := try read_string(r)\n return new_text(s)\n }\n if c == 't' {\n try expect_word(r, \"true\")\n return new_bool(true)\n }\n if c == 'f' {\n try expect_word(r, \"false\")\n return new_bool(false)\n }\n if c == 'n' {\n try expect_word(r, \"null\")\n return new_null()\n }\n if c == '-' || (c >= '0' && c <= '9') {\n return try read_number(r)\n }\n return error(unexpected(r, \"a value\"))\n}\n\n// the JSON in `text`; an error with the line and column where it went wrong\nfn parse(str text) -> Json! {\n r := Reader(src: text)\n v := try read_value(addr(r))\n skip_space(addr(r))\n if r.i < r.src.len {\n return error(unexpected(addr(r), \"the end of the text\"))\n }\n return v\n}\n") list.push("// Paths, as text. Elements are separated by \"/\".\n//\n// import path\n// path.join(\"a\", \"b.txt\") a/b.txt\n// path.base(\"/tmp/x/a.tar.gz\") a.tar.gz path.stem(...) a.tar path.ext(...) .gz\n// path.dir(\"/tmp/x/a.txt\") /tmp/x\n// path.clean(\"a//b/../c/./d\") a/c/d\n\n// a and b with one separator between; an absolute b replaces a\nfn join(str a, str b) -> str {\n if a.len == 0 {\n return b\n }\n if b.len == 0 {\n return a\n }\n if b[0] == '/' {\n return b\n }\n if a[a.len - 1] == '/' {\n return a + b\n }\n return a + \"/\" + b\n}\n\nfn is_abs(str p) -> bool {\n return p.len > 0 && p[0] == '/'\n}\n\n// the last element: \"a/b.txt\" -> \"b.txt\", \"a/b/\" -> \"b\", \"\" -> \".\", \"/\" -> \"/\"\nfn base(str p) -> str {\n if p.len == 0 {\n return \".\"\n }\n end := p.len\n while end > 1 && p[end - 1] == '/' {\n end -= 1\n }\n if end == 1 && p[0] == '/' {\n return \"/\"\n }\n start := end\n while start > 0 && p[start - 1] != '/' {\n start -= 1\n }\n return p.sub(start, end)\n}\n\n// everything before the last element: \"a/b.txt\" -> \"a\", \"b.txt\" -> \".\", \"/b\" -> \"/\"\nfn dir(str p) -> str {\n end := p.len\n while end > 1 && p[end - 1] == '/' {\n end -= 1\n }\n i := end\n while i > 0 && p[i - 1] != '/' {\n i -= 1\n }\n if i == 0 {\n return \".\"\n }\n while i > 1 && p[i - 1] == '/' {\n i -= 1\n }\n return p.sub(0, i)\n}\n\n// the part of the last element from its last dot: \"a.tar.gz\" -> \".gz\"; \"\" when there is none\nfn ext(str p) -> str {\n b := base(p)\n i := b.len\n while i > 0 && b[i - 1] != '.' {\n i -= 1\n }\n if i <= 1 { // no dot, or a name that only starts with one (.bashrc)\n return \"\"\n }\n return b.sub(i - 1, b.len)\n}\n\n// the last element without its extension\nfn stem(str p) -> str {\n b := base(p)\n e := ext(p)\n return b.sub(0, b.len - e.len)\n}\n\n// the elements of a path: \"/a/b/c\" -> [\"a\", \"b\", \"c\"]\nfn parts(str p) -> []str {\n []str out\n for piece in p.split(\"/\") {\n if piece.len > 0 {\n out.push(piece)\n }\n }\n return out\n}\n\n// the shortest path with the same meaning: no \"//\", no \".\", and \"..\" resolved where it can be\nfn clean(str p) -> str {\n if p.len == 0 {\n return \".\"\n }\n abs := is_abs(p)\n []str out\n for piece in parts(p) {\n if piece == \".\" {\n continue\n }\n if piece == \"..\" {\n if out.len > 0 && out[out.len - 1] != \"..\" {\n out.pop()\n } else if !abs {\n out.push(\"..\")\n }\n continue\n }\n out.push(piece)\n }\n joined := out.join(\"/\")\n if abs {\n return \"/\" + joined\n }\n if joined.len == 0 {\n return \".\"\n }\n return joined\n}\n")