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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016GzNP6r8dg6WDxUVZy8wjL
2026-10-29 11:34:41 +01:00

1292 lines
52 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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).
> 🇬🇧 English version: [for-technicians.md](../en/for-technicians.md)
---
## Spis treści
1. [Czym jest Finch](#1-czym-jest-finch)
2. [Instalacja](#2-instalacja)
3. [Polecenie finch](#3-polecenie-finch)
4. [Jak wygląda plik Fincha](#4-jak-wygląda-plik-fincha)
5. [Zmienne](#5-zmienne)
6. [Typy](#6-typy)
7. [Operatory](#7-operatory)
8. [Sterowanie przebiegiem](#8-sterowanie-przebiegiem)
9. [Funkcje](#9-funkcje)
10. [Tablice i mapy](#10-tablice-i-mapy)
11. [Tekst (str)](#11-tekst-str)
12. [Struktury i metody](#12-struktury-i-metody)
13. [Pamięć: kto co zwalnia](#13-pamięć-kto-co-zwalnia)
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)
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)
23. [Enumy i match](#23-enumy-i-match)
24. [Typy generyczne](#24-typy-generyczne)
25. [Biblioteka standardowa](#25-biblioteka-standardowa)
26. [Obecne ograniczenia](#26-obecne-ograniczenia)
27. [Testy: bloki test i finch test](#27-testy-bloki-test-i-finch-test)
28. [Projekt: finch.toml](#28-projekt-finchtoml)
29. [Wątki](#29-wątki)
30. [C++, Rust i Android](#30-c-rust-i-android)
---
## 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.
Co to daje:
- **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ą.
- **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.
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ć.
---
## 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 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`.
### Opcja A: gotowa wersja (Linux)
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:
| 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 |
### Opcja B: zbuduj sam
```sh
git clone https://github.com/Nexoniarz/Finch.git
cd Finch
nix-shell # clang, libclang, pkg-config (albo zainstaluj z dystrybucji)
bootstrap/build.sh # build/finch
./build/finch version
```
`bootstrap/build.sh` kompiluje ziarno (`bootstrap/<system>.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`.
### Budowanie na inny system
`--target` buduje program na inny system. Do linkowania Finch potrzebuje kompilatora C dla tego systemu:
| `--target` | Buduje | Kompilator C | `finch run` używa |
|---|---|---|---|
| `windows` | `.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=…` | |
| dowolny triple LLVM | np. `aarch64-unknown-linux-gnu` | `clang --target=<triple>` | |
```sh
finch build gra.fch --target windows # gra.exe
finch run gra.fch --target arm64 # buduje na ARM64 i uruchamia w QEMU
FINCH_CC=aarch64-unknown-linux-gnu-gcc finch build narzedzie.fch --target arm64 # inna nazwa kompilatora
```
Na Niksie te kompilatory to `pkgsCross.mingwW64.buildPackages.gcc` i
`pkgsCross.aarch64-multiplatform.buildPackages.gcc`.
### Dodanie `finch` do PATH (opcjonalnie)
```sh
sudo bootstrap/install.sh # /usr/local/bin/finch oraz /usr/local/share/finch/{runtime,std}
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.
---
## 3. Polecenie finch
```
finch run <plik.fch> [argumenty...] skompiluj i od razu uruchom
finch build <plik.fch> [-o nazwa] skompiluj do programu (domyślna nazwa: nazwa pliku)
finch test <plik.fch> uruchom jego bloki test "..." { } (rozdział 27)
finch fmt [--check] [pliki/foldery] uporządkuj wcięcia plików .fch (domyślnie wszystkich stąd w dół)
finch ir <plik.fch> pokaż LLVM IR (dla ciekawskich)
finch lsp serwer języka, dla edytorów
finch version pokaż wersję
opcje:
-l <lib> dolinkuj bibliotekę C, to samo co link "lib" w pliku
-I <folder> szukaj nagłówków C także tam
--target <nazwa> zbuduj na inny system: windows, linux, arm64, macos, android albo triple LLVM
-g dodaj informacje dla debuggera (gdb / lldb / Visual Studio)
-O0 bez optymalizacji
--trace błędy w czasie działania pokazują łańcuch wywołań (i tak włączone dla run, test, -g i -O0)
--no-trace nie pokazują (zwykły finch build go nie ma: pilnowanie łańcucha spowalnia wywołania)
```
W folderze z plikiem `finch.toml` (rozdział 28) `finch run`, `finch build` i `finch test` nie potrzebują nazwy pliku.
Skompilowany program i pliki C, które buduje (`link "x.c"`), zostają w cache (`~/.cache/finch`, na Windowsie
`%LOCALAPPDATA%`). Drugie budowanie tego samego kodu albo kodu, którego pliki C się nie zmieniły, pomija tę pracę.
`finch fmt` zmienia tylko układ: cztery spacje na poziom `{ }`, bez spacji na końcach linii, najwyżej jedna pusta linia
z rzędu. Linie, które ciągną wyrażenie (w `( )` albo po `+`), i tekst `"""` zachowują swój kształt. `finch fmt --check`
niczego nie zmienia i kończy się błędem, gdy jakiś plik by się zmienił (dla CI). Edytory z serwerem języka formatują na żądanie.
Przykłady:
```sh
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 run` przekazuje dalej kod wyjścia programu. Jeśli program się wysypie, powie jak
(np. `the program crashed: Segmentation fault`).
---
## 4. Jak wygląda plik Fincha
```c
// komentarz zaczyna się od dwóch ukośników
/* albo zajmuje
kilka linijek */
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)
struct Punkt { // własne typy
int x
int y
}
fn main() { // tu zaczyna się program
print("Cześć")
}
```
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.
- `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).
---
## 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)
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.
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`
### Stałe
```c
const SZEROKOSC = 640
const WYSOKOSC = SZEROKOSC / 2 // z innych stałych
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`.
---
## 6. Typy
### Typy na co dzień
| 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` |
| `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]` |
| twoje struktury | nazwane grupy wartości | `Punkt(1, 2)` |
Znaki specjalne w tekstach: `\n` nowa linia, `\t` tabulator, `\r`, `\0`, `\\`, `\"`, `\'`.
### Typy z rozmiarem (do plików, sprzętu, bibliotek C)
| Ze znakiem | Zakres | Bez znaku | Zakres |
|---|---|---|---|
| `i8` | −128 … 127 | `u8` | 0 … 255 |
| `i16` | −32 768 … 32 767 | `u16` | 0 … 65 535 |
| `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).
### Zamiana typów
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`) | ✅ |
| 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ść)` |
**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.
**Jawna zamiana** wygląda jak wywołanie funkcji o nazwie typu:
```c
int(3.99) // 3 (obcina część ułamkową)
float(7) / 2 // 3.5
u8(300) // 44 (zostawia najniższe 8 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)
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
ptr(16) // liczba jako wskaźnik (API C, które przekazują przesunięcia jako wskaźniki, np. OpenGL)
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`.
---
## 7. Operatory
### Według priorytetu (wyższy wiąże mocniej)
| Priorytet | Operatory | Znaczenie |
|---|---|---|
| 5 | `*` `/` `%` `<<` `>>` `&` | mnożenie, dzielenie, reszta, przesunięcia bitów, bitowe AND |
| 4 | `+` `-` `\|` `^` | dodawanie (i sklejanie tekstu), odejmowanie, bitowe OR, bitowe XOR |
| 3 | `==` `!=` `<` `<=` `>` `>=` | porównania |
| 2 | `&&` | logiczne I |
| 1 | `\|\|` | logiczne LUB |
Jednoargumentowe (przed wartością): `-x` zmiana znaku, `!x` logiczne NIE, `~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`.
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.
---
## 8. Sterowanie przebiegiem
`if` może też dawać wartość; każda gałąź to jedna wartość, a `else` jest wymagane:
```c
opis := if wynik > 100 { "dużo" } else if wynik > 10 { "średnio" } else { "mało" }
f32 predkosc = if biegnie { 2.5 } else { 1.0 }
```
```c
if x > 10 {
...
} else if x > 0 {
...
} else {
...
}
while licznik > 0 {
licznik -= 1
}
for i in 0..10 { // i = 0, 1, …, 9 (koniec nie jest wliczany)
if i == 3 { continue }
if i == 8 { break }
}
for imie in imiona { // każdy element tablicy (albo każdy znak str)
print(imie)
}
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)
}
```
- 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.
- `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.
### 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:
```c
f := fopen("dane.txt", "r")
defer fclose(f)
// ... używaj f; zostanie zamknięty na końcu bloku, na każdej ścieżce
```
Instrukcja w `defer` nie może zawierać `return`, `break` ani `continue`.
---
## 9. Funkcje
```c
fn nazwa(typ1 param1, typ2 param2) -> typ_wyniku {
...
return wartosc
}
fn bez_wyniku(str wiadomosc) { // bez "->": nic nie zwraca
print(wiadomosc)
}
```
- Argumenty zachowują się jak kopie: zmiana parametru nigdy nie zmienia zmiennej u 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
[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.
---
## 10. Tablice i mapy
```c
liczby := [5, 3, 8] // tablica int
[]str imiona // pusta tablica str
siatka := [[1, 2], [3, 4]] // tablice tablic
[]f32 xs = [1, 2.5] // typ decyduje, czym staną się liczby
```
| Operacja | Znaczenie |
|---|---|
| `a.len` | liczba elementów |
| `a[i]` | element `i` (od 0); poza zakresem program zatrzymuje się z czytelnym 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.sort()`, `a.reverse()` | w miejscu (liczby, znaki, str) |
| `a.slice(s, e)` | nowa tablica z elementami `s` … `e-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]` |
**Przypisanie kopiuje:** po `b := a` zmiana `b` nie zmienia `a`.
### 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.
```c
wiek := ["anna": 31, "bob": 25] // literał mapy: map[str]int
map[str][]str grupy // pusta mapa
map[int]str nazwy = [:] // [:] to pusta mapa tam, gdzie typ jest znany
```
| Operacja | 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.len` | liczba kluczy |
| `m.has(k)` | czy `k` jest w mapie? |
| `m.get(k, domyslna)` | wartość albo `domyslna` (liczona tylko w razie potrzeby) |
| `m.remove(k)` | usuwa `k`; `true`, jeśli był |
| `m.clear()` | usuwa wszystko |
| `m.keys()`, `m.values()` | nowe tablice, w kolejności dodawania |
| `print(m)`, `str(m)` | `{"anna": 31, "bob": 25}` |
```c
map[str]int ile
for slowo in tekst.split(" ") {
ile[slowo] += 1 // liczenie: nowe słowo zaczyna od 0
}
for slowo, n in ile {
print(slowo, n)
}
```
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.
---
## 11. Tekst (str)
```c
s := "Cześć"
s += ", świecie" // sklejanie
print(s.len, s[0]) // .len liczy bajty; ą, ś, ż… zajmują po 2
s[0] = 'D' // zmiana znaku
```
| Metoda | Oddaje |
|---|---|
| `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.replace(a, b)` | każde `a` zamienione na `b` |
| `s.repeat(n)` | `s` n razy |
| `s.bytes()` | `[]u8` z bajtami |
| `s.ptr` | `ptr[char]` dla C (ważny, dopóki żyje `s`) |
Wartości wstawia się do tekstu przez `\(...)`: dowolny kod, a `, 2` po liczbie daje dwie cyfry po przecinku.
`\` przed `(`, które ma zostać zwykłym znakiem: `"\\(to nie kod)"`.
```c
print("hp \(hp) z \(max_hp), prędkość \(v, 2), imię \(p.imie.upper())")
```
`str` pamięta swoją długość, więc `.len` działa natychmiast. Zawsze kończy się zerem, więc
przekazanie go do C jako `char*` działa. W drugą stronę: funkcja C, która zwraca `char *`, daje ci `str`
(kopia powstaje, gdy go zachowujesz), a `str(p)` czyta `ptr[char]` zdobyty inaczej.
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:
```c
shader := """
#version 330
void main() { gl_Position = vec4(0.0); }
"""
```
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`).
---
## 12. Struktury i metody
```c
struct Gracz {
str imie
int zycia = 3 // wartość domyślna
[]int wyniki
Punkt pozycja // struktury w strukturach
}
g := Gracz(imie: "Ola") // po nazwie: brakujące pola dostają domyślne (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))
```
- 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).
### 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ą.
```c
fn Gracz.wylecz(int ile) {
self.zycia += ile
}
fn Gracz.zyje() -> bool {
return self.zycia > 0
}
g := Gracz(imie: "Ola")
g.wylecz(2) // g.zycia wynosi teraz 5
if g.zyje() { ... }
druzyna[0].wylecz(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 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).
---
## 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`.
- 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.
Do pamięci zarządzanej samodzielnie (listy wiązane, drzewa, 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)
```
Pamięć z C (`malloc`, obiekty bibliotek) zwalniasz funkcjami z C, jak w C.
---
## 14. Wskaźniki
Wskaźnik przechowuje **adres** wartości. Finch zamiast `*` i `&` z C używa słów:
| C | Finch | Znaczenie |
|---|---|---|
| `int *p` | `ptr[int] p` | p wskazuje na int |
| `&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 |
| `void *` | `ptr` | wskazuje na coś nieznanego typu |
```c
fn podwoj(ptr[int] p) {
p.value = p.value * 2
}
x := 21
podwoj(addr(x)) // x to 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.
⚠️ Wskaźnika na zmienną nie wolno używać po zakończeniu jej bloku. Finch tego nie sprawdza.
---
## 15. Funkcje wbudowane
| 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ę. |
| `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. |
| `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ą. |
---
## 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 `:`).
```c
// ksztalty.fch
struct Pudlo {
float w
float h
}
fn pole(Pudlo p) -> float {
return p.w * p.h
}
```
```c
// main.fch
import ksztalty
fn main() {
p := ksztalty.Pudlo(2, 3)
print(ksztalty.pole(p))
ksztalty.Pudlo inne // typ z modułu w deklaracji
}
```
- Wszystkiego z modułu używasz z jego nazwą z przodu: `ksztalty.pole`, `ksztalty.Pudlo`.
- 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.
---
## 17. Biblioteki z 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
```
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`.
### 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 "gotowe.a" // albo plik obiektowy / bibliotekę statyczną
```
Jeśli zapomnisz linijki `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
...
link "glfw"
```
### Jak typy C wyglądają w Finchu
| C | Finch |
|---|---|
| `char`, `signed char`, `unsigned char` | `char`, `i8`, `u8` |
| `short`, `int`, `long`, `long long` | `i16`, `i32`, `i64`, `i64` (według faktycznego rozmiaru) |
| `unsigned …` | `u16`, `u32`, `u64` |
| `float`, `double` | `f32`, `f64` |
| `_Bool` / `bool` | `bool` |
| `enum` | odpowiadająca liczba całkowita ze znakiem |
| `char *` jako **parametr albo wynik** | `str` |
| `char *` w strukturze albo za wskaźnikiem | `ptr[char]` (czytasz 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` |
| `void *`, wskaźniki na funkcje, niekompletne struktury | `ptr` |
### Przykład: struktury i raylib
```c
import "raylib.h"
link "raylib"
fn main() {
InitWindow(800, 450, "Finch")
while !WindowShouldClose() {
BeginDrawing()
ClearBackground(Color(r: 30, g: 30, b: 40, a: 255))
DrawCircleV(Vector2(400, 225), 50, Color(255, 136, 0, 255))
EndDrawing()
}
CloseWindow()
}
```
Żeby dać C funkcję do wywołania zwrotnego (np. porównywarkę 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
nagłówku obok programu i zaimportuj go:
```c
// opengl.h
#define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#include <GL/glext.h>
#include <GLFW/glfw3.h>
```
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).
### Co jeszcze nie działa
- **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.
---
## 18. Błędy
### 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")`:
```c
fn port_z_tekstu(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")
}
return n
}
fn zapisz(str sciezka, str tekst) -> ! { // może się nie udać, nic nie oddaje
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):
| Kod | W razie porażki |
|---|---|
| `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ć) |
```c
fn wczytaj(str sciezka) -> Konfig! {
tekst := try read_file(sciezka)
port := try port_z_tekstu(tekst.trim())
return Konfig(port: port)
}
fn main() -> ! { // main też może się nie udać: błąd zostanie wypisany, a kod wyjścia to 1
konfig := try wczytaj("app.conf")
...
}
```
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`).
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.
### Błędy kompilacji
```
gra.fch:12:9: error: 'wynik' must be int, but this is str
12 | wynik = "wysoki"
| ^
```
Format: `plik:linijka:kolumna: error: wyjaśnienie`, a pod spodem linijka ze znakiem `^`.
Komunikaty są po angielsku.
### Błędy w trakcie działania
Zamiast niezdefiniowanego zachowania (jak w C) Finch zatrzymuje się z komunikatem i kodem wyjścia 1:
| Komunikat | Przyczyna |
|---|---|
| `runtime error: division by zero` | `/` albo `%` przez zmienną równą 0 |
| `runtime error: division overflows …` | najmniejsza liczba ze znakiem podzielona przez −1 |
| `runtime error: index 5 is out of range (the length is 3)` | `a[5]` na krótszej tablicy albo tekście |
| `runtime error: pop() on an empty array` | |
| `runtime error: used .value on a null pointer` | także `.pole` i `[i]` przez `null` |
| `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: assert failed: …` | `assert(warunek, "…")` z fałszywym warunkiem |
Pod komunikatem jest łańcuch wywołań, które do niego doprowadziły, od najgłębszego:
```
gra.fch:6: runtime error: index 3 is out of range (the length is 2)
in Enemy.next (gra.fch:6)
in step (gra.fch:10)
in main (gra.fch:15)
```
`finch run`, `finch test` oraz budowanie z `-g` i `-O0` go mają; zwykły `finch build` go pomija, bo pilnowanie go kosztuje
czas w każdym wywołaniu (ok. 3 razy w programie złożonym z samych drobnych wywołań, dużo mniej w prawdziwym). `--trace` go dodaje.
---
## 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.
### Kate (oraz KWrite, KDevelop)
```sh
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ę
**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`.
### 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.
Instalacja: Rozszerzenia → `…` → *Install from VSIX…* → wybierz `finch-lang-*.vsix`. Rozszerzenie uruchamia
`finch` z PATH; jeśli jest gdzie indziej, ustaw **Finch: Path** w ustawieniach.
### Inne
Neovim, Helix, Zed, Emacs, Sublime i każdy inny edytor obsługujący Language Server Protocol:
uruchamiaj `finch lsp` dla plików `.fch` (stdin/stdout, bez opcji).
## 20. Debugowanie
```sh
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) 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.
---
## 21. Rozwiązywanie problemów
| 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. |
| `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++*. |
| 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`. |
| Program nigdy się nie kończy | Warunek pętli nigdy nie staje się fałszywy. Naciśnij Ctrl+C. |
---
## 22. Struktura projektu 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
├── tests/
│ ├── run/ new/ programy + dokładny wynik (.out) i wejście (.in)
│ ├── fail/ new/fail/ programy, które muszą się nie udać, z oczekiwanym błędem w 1. linijce
│ ├── tool/ finch test, finch fmt, łańcuch wywołań, projekt z finch.toml
│ ├── run.sh uruchamia testy (MEMCHECK=1 sprawdza też pamięć valgrindem)
│ ├── cross.sh każdy test zbudowany na Windows / ARM64 i uruchomiony w Wine / QEMU
│ └── lsp_test.py serwer języka, obsługiwany jak przez edytor
├── 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
```
```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
```
### 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
**Enum** to typ z krótką listą nazwanych wartości.
```
enum Kolor { Czerwony, Zielony, Niebieski }
enum Status { Ok = 200, Brak = 404 } // wariantowi można nadać numer
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)
```
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 { ... }`.
**match** wybiera, co zrobić, na podstawie wartości — zamiast drabinki `if`:
```
match k {
Czerwony -> print("ciepły") // w matchu na enumie wystarczy sama nazwa
Zielony, Niebieski -> print("zimny") // kilka wartości w jednej gałęzi
}
rozmiar := match n { // jako wyrażenie: daje wartość
1, 2, 3 -> "mały"
4, 5 -> "średni"
else -> "duży"
}
match slowo {
"koniec" -> {
print("pa")
return
}
else -> print("co?")
}
```
`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.
## 24. Typy generyczne
Napisz funkcję lub strukturę raz dla dowolnego typu, z typem jako nazwą w nawiasach kwadratowych:
```
fn max[T](T a, T b) -> T {
if a > b {
return a
}
return b
}
struct Stos[T] {
[]T elementy
}
fn Stos[T].dodaj(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)
```
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])`.
## 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ł | 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) |
| `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).
```
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
dok.set("name", json.new_text(nazwa + "!"))
write_file(path.join("out", "config.json"), json.pretty(dok))
```
## 26. Obecne ograniczenia
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. Android jest budowany (`--target android`), ale jeszcze nie
sprawdzony na urządzeniu.
Celowo nie planowane: ostrzeżenie przy każdym kopiowaniu dużej struktury. Kopię w Finchu zawsze widać w kodzie
(`b := a`, struktura podana do parametru `Cialo c`), metoda nigdy nie kopiuje `self`, a `ptr[Cialo]` (albo zmienna
podana do parametru `ptr[Cialo]`) współdzieli zamiast kopiować. Ostrzeżenie przy zwykłym kodzie byłoby szumem.
Plan rozwoju jest w [README](../../README.pl.md#plan-rozwoju).
## 27. Testy: bloki test i finch test
Testy stoją obok kodu, który sprawdzają:
```c
fn obrazenia(int hp, int cios) -> int {
return max(hp - cios, 0)
}
test "obrażenia nigdy nie schodzą poniżej zera" {
assert(obrazenia(5, 10) == 0)
assert(obrazenia(5, 2) == 3, "wyszło \(obrazenia(5, 2))")
}
```
`finch test gra.fch` uruchamia każdy blok `test` z pliku i z modułów, które importuje (bez biblioteki standardowej),
każdy osobno: nieudany `assert` albo dowolny błąd w czasie działania kończy tylko ten test, a następny rusza dalej.
```
ok obrażenia nigdy nie schodzą poniżej zera
FAIL wczytanie poziomu
poziom.fch:31: runtime error: can't read the file "levels/1.txt"
1 passed, 1 failed
```
Kod wyjścia to 1, gdy jakiś test nie przeszedł, więc CI może z tego korzystać. `finch run` i `finch build` pomijają bloki test.
`assert(warunek)` działa też poza testami: zatrzymuje program z `assert failed`, gdy warunek jest fałszywy.
## 28. Projekt: finch.toml
`finch.toml` obok kodu mówi raz to, co inaczej trzeba by podawać w każdym poleceniu:
```toml
name = "gra" # program, który robi finch build (z entry)
entry = "src/main.fch" # gdy finch run / build / test nie dostaną pliku
paths = ["src", "lib"] # foldery z modułami Fincha
include = ["vendor/SDL3/include"] # foldery z nagłówkami C (też dla własnych .c / .cpp)
links = ["SDL3", "native/glue.cpp"] # biblioteki i własne pliki .c / .cpp / .o / .a
libdirs = ["vendor/SDL3/lib"] # foldery z bibliotekami
cflags = ["-DFAST"] # dla własnych plików .c / .cpp
ldflags = ["-Wl,-rpath,$ORIGIN"] # dla linkera
[target.windows] # dodawane tylko przy budowaniu na Windowsa
links = ["ws2_32"]
libdirs = ["vendor/SDL3/lib/x64"]
[target.linux-arm64] # system i procesor: windows, linux, macos, android oraz -x86_64 / -arm64
libdirs = ["vendor/SDL3/lib/arm64"]
```
Foldery liczą się od `finch.toml`. Finch szuka go w folderze pliku i w folderach wyżej, więc
`finch run src/narzedzia/edytor.fch` wewnątrz projektu też z niego korzysta. Czytana jest tylko taka część TOML:
`[sekcje]`, `klucz = "tekst"`, `klucz = ["a", "b"]` (także w kilku liniach), liczby, `true`/`false` i komentarze `#`.
## 29. Wątki
```c
import thread
struct Kawalek {
[]float probki
float szczyt
}
fn analizuj(ptr arg) { // funkcja wątku bierze jeden ptr
ptr[Kawalek] k = arg
for s in k.probki {
k.szczyt = max(k.szczyt, abs(s))
}
}
fn main() {
[]Kawalek kawalki = wczytaj_kawalki()
[]thread.Thread dzialajace
for i in 0..kawalki.len {
dzialajace.push(thread.spawn(addr(analizuj), addr(kawalki[i])) or {
print(err)
exit(1)
})
}
for i in 0..dzialajace.len {
dzialajace[i].join() // poczekaj na wszystkie
}
}
```
To, co dostaje wątek, jest współdzielone, nie kopiowane, więc:
- musi żyć do `join()` (tu `kawalki` żyje do końca `main`);
- nie zmieniaj tablicy, w którą wskazują wątki (bez `push`), dopóki się nie skończą;
- to, co zmienia więcej niż jeden wątek, idzie pod blokadę albo jest liczbą zmienianą przez `atomic_...`.
```c
m := thread.mutex()
defer m.close()
m.lock()
dziennik.push("gotowe") // jeden wątek naraz między lock a unlock
m.unlock()
atomic_add(addr(gotowe), 1) // gotowe to int wspólny dla wątków
n := atomic_load(addr(gotowe))
```
`atomic_add`, `atomic_sub`, `atomic_swap` zwracają starą wartość; `atomic_cas(p, stara, nowa)` wstawia `nowa` tylko
wtedy, gdy jest tam `stara`, i mówi, czy to zrobił. `thread.cpus()` mówi, ile wątków komputer wykonuje naraz.
## 30. C++, Rust i Android
Finch rozmawia z innymi językami przez C, jak każdy język.
**C++**: to, co woła Finch, wystaw przez nagłówek C, z `extern "C"` po stronie C++:
```cpp
// glue.h
#ifdef __cplusplus
extern "C" {
#endif
int physics_step(float dt);
#ifdef __cplusplus
}
#endif
```
i `import "glue.h"` z `link "glue.cpp"` (albo `links = ["glue.cpp"]` w `finch.toml`). Plik `.cpp` buduje kompilator C++
(`clang++` albo `g++`, albo `FINCH_CXX`), który wtedy też linkuje, więc biblioteka C++ dochodzi sama.
Bibliotekę C++ z API w C (Box2D 3, JoltC dla Jolt, wrapper C dla PhysX) importuje się jak każdą bibliotekę C.
**Rust**: zrób `staticlib` i wystaw funkcje C:
```rust
// Cargo.toml: [lib] crate-type = ["staticlib"]
#[no_mangle]
pub extern "C" fn path_len(x: f32, y: f32) -> f32 { (x * x + y * y).sqrt() }
```
Napisz nagłówek (`float path_len(float x, float y);` albo niech napisze go `cbindgen`), potem `import "pathfind.h"` i
`link "target/release/libpathfind.a"`. Na Linuksie statyczna biblioteka Rusta potrzebuje też `links = ["dl", "pthread"]`.
**Android**: `finch build gra.fch --target android --shared` robi `libgra.so` dla 64-bitowego Androida na ARM (API 24
i wyżej) clangiem z NDK, znalezionym przez `ANDROID_NDK_HOME` (albo `FINCH_CC`). Wczytaj go z aplikacji, na przykład
przez projekt Androida z SDL3 (`SDL_main` biblioteki to `export fn`). To nowość: jest budowane i sprawdzane tutaj, ale
jeszcze nie na urządzeniu.