A small, fast programming language on LLVM with C-like syntax. - Lexer, recursive-descent parser, single-pass type checker + LLVM IR codegen, O2 pipeline - Types: int/float/bool/char/str, sized i8..u64/f32/f64, pointers ptr[T] with addr()/.value/null - Functions (fn ... -> T), if/else, while, for..in, break/continue, Go-style operator precedence - Implicit conversions only when lossless; literals adapt to their target type - Runtime checks for division by zero, MIN/-1 and null .value - C interop: import "header.h" via libclang, link "lib" with pkg-config, link-error advice - Human-readable errors with source excerpts - Test suite (34 tests), examples, docs for beginners/technicians/engineers in English and Polish Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
19 KiB
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,
korzystanie z bibliotek C i rozwiązywanie typowych problemów. Nie opisuje, jak kompilator
działa w środku. Do tego służy przewodnik dla inżynierów.
🇬🇧 English version: for-technicians.md
Spis treści
- Czym jest Finch
- Instalacja
- Polecenie finch
- Jak wygląda plik Fincha
- Zmienne
- Typy
- Operatory
- Sterowanie przebiegiem
- Funkcje
- Wskaźniki
- Biblioteki z C
- Błędy
- Rozwiązywanie problemów
- Struktura projektu i testy
- Obecne ograniczenia
1. Czym jest Finch
Finch to język kompilowany. Narzędzie finch tłumaczy plik .fn na prawdziwy program
(natywny plik wykonywalny, taki sam 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.
- Małe, samodzielne programy, które można skopiować i uruchomić.
- Bezpośredni dostęp do bibliotek C. Piszesz
import "stdio.h"i wołasz, co chcesz.
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
Finch działa obecnie na Linuksie x86-64. Testowany był z LLVM 21. Z innymi wersjami LLVM prawdopodobnie się nie skompiluje, bo API C++ LLVM zmienia się między wydaniami.
Opcja A: Nix (zalecana, nic nie instalujesz ręcznie)
git clone https://github.com/Nexoniarz/Finch.git
cd Finch
nix-shell # pobiera LLVM, libclang, cmake, ninja, pkg-config
cmake -S . -B build -G Ninja
ninja -C build
./build/finch version # finch 1.0.0 (LLVM 21.x)
Opcja B: pakiety z twojej dystrybucji
Potrzebujesz: kompilatora C/C++, CMake ≥ 3.20, Ninja (albo Make), plików deweloperskich LLVM 21
i libclang 21, a opcjonalnie pkg-config.
| Dystrybucja | Pakiety (nazwy mogą się nieco różnić zależnie od wersji) |
|---|---|
| Debian / Ubuntu | build-essential cmake ninja-build pkg-config llvm-21-dev libclang-21-dev |
| Fedora | gcc-c++ cmake ninja-build pkgconf llvm-devel clang-devel |
| Arch | base-devel cmake ninja pkgconf llvm clang |
Jeśli CMake nie znajduje LLVM, wskaż mu katalog:
cmake -S . -B build -G Ninja -DLLVM_DIR=/usr/lib/llvm-21/lib/cmake/llvm
ninja -C build
Dodanie finch do PATH (opcjonalnie)
sudo cp build/finch /usr/local/bin/
finch version
Podczas pracy Finch potrzebuje działającego kompilatora C (cc). Używa go jako linkera,
który skleja skompilowany kod w gotowy program. Inny kompilator wskażesz zmienną środowiskową CC.
3. Polecenie finch
finch run <plik.fn> skompiluj i od razu uruchom
finch build <plik.fn> [-o nazwa] skompiluj do programu (domyślna nazwa: nazwa pliku)
finch ir <plik.fn> pokaż LLVM IR (dla ciekawskich)
finch version pokaż wersję
opcje:
-l <lib> dolinkuj bibliotekę C, to samo co link "lib" w pliku
-O0 bez optymalizacji
Przykłady:
finch run hello.fn # wypisuje wynik
finch build gra.fn -o mojagra # tworzy ./mojagra
./mojagra
finch run okno.fn -l glfw # z biblioteką C
finch run przekazuje dalej kod wyjścia twojego programu. Jeśli program się wysypie,
powie jak (np. the program crashed: Segmentation fault).
4. Jak wygląda plik Fincha
// komentarz zaczyna się od dwóch ukośników
/* albo zajmuje
kilka linijek */
import "math.h" // nagłówki C (opcjonalne), zawsze na górze
link "m" // biblioteki C do dolinkowania (opcjonalne)
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( ). - 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 nieif (x > 5) {. - Funkcje mogą być w pliku w dowolnej kolejności.
maintofn main()albofn main() -> int(zwraca kod wyjścia).
5. Zmienne
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, null dla wskaźników)
x = 7 // przypisanie
x += 1 // także -= *= /= %=
- Nazwę można zadeklarować raz w funkcji. Finch nie pozwala użyć tej samej nazwy 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, …) i słowa kluczowe są zarezerwowane.
Słowa kluczowe: fn return if else while for in break continue true false null import link
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 (napis w stylu C) | "cześć\tświecie" |
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).
Wskaźniki
ptr[T] wskazuje na wartość typu T. Samo ptr wskazuje na „coś” nieznanego typu,
jak void* w C. Szczegóły w rozdziale Wskaźniki.
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, str |
ptr |
✅ |
ptr |
dowolny ptr[T] |
✅ |
| 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 (the number 300 doesn't fit in u8).
To samo dotyczy stałych z nagłówków C.
Jawna zamiana wygląda jak wywołanie funkcji o nazwie typu:
int(3.99) // 3 (obcina część ułamkową)
int(-3.99) // -3
float(7) / 2 // 3.5
u8(300) // 44 (zostawia najniższe 8 bitów)
i8(200) // -56
int('A') // 65
char(66) // 'B'
bool(0) // false (każda liczba różna od zera to true)
ptr(tekst) // str jako wskaźnik bez typu
str(p) // wskaźnik jako str
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.
Jeśli potrzebujesz więcej miejsca, najpierw zamień typ: int(x) + 10.
7. Operatory
Według priorytetu (wyższy wiąże mocniej)
| Priorytet | Operatory | Znaczenie |
|---|---|---|
| 5 | * / % << >> & |
mnożenie, dzielenie, reszta, przesunięcie bitów w lewo/prawo, bitowe AND |
| 4 | + - | ^ |
dodawanie, 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 != 0znaczy(flagi & 4) != 0. W C to samo po cichu znaczy co innego.
Uwagi:
/na liczbach całkowitych zaokrągla w stronę zera:7 / 2to3,-7 / 2to-3.%ma znak lewej strony.- Dzielenie przez zero zatrzymuje program komunikatem
runtime error: division by zeroz numerem linijki. Jeśli zero jest wpisane wprost w kod, jest to błąd już przy kompilacji. &&i||liczą prawą stronę tylko wtedy, gdy to potrzebne.- Tekst (
str) można porównywać przez==i!=(porównuje zawartość).<i>na tekstach nie działają. - Wskaźniki porównuje się przez
==i!=, także znull. - Operatory bitowe wymagają liczb całkowitych. Dla
boolużywaj&&i||. >>na liczbach ze znakiem zachowuje znak (-16 >> 2to-4). Na liczbach bez znaku wypełnia zerami.
8. Sterowanie przebiegiem
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 }
}
- Warunek musi być typu
bool.if x {z liczbą to błąd; napiszif x != 0 {. - W
for i in a..boba końce są liczbami całkowitymi obliczanymi raz, przed startem pętli.ijest typuinti nie można go zmieniać wewnątrz pętli. while true { ... }kręci się dobreakalboreturn.- Kod po
return,breaklubcontinuew tym samym bloku to błąd, bo nigdy się nie wykona.
9. Funkcje
fn nazwa(typ1 param1, typ2 param2) -> typ_wyniku {
...
return wartosc
}
fn bez_wyniku(str wiadomosc) { // bez "->": nic nie zwraca
print(wiadomosc)
}
- Parametry są kopiami. Zmiana parametru nie zmienia zmiennej u wywołującego. Do tego służą wskaźniki (niżej).
- Funkcja z
-> typmusi zwrócić wartość na każdej ścieżce. Finch to sprawdza. - Funkcji nie można definiować wewnątrz innej funkcji.
- Wbudowane nazwy, których nie możesz użyć:
print,addri wszystkie nazwy typów.
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. |
addr(x) |
Daje wskaźnik na zmienną x. |
int(x), u8(x), float(x), … |
Zamienia typy (zobacz Zamiana typów). |
10. Wskaźniki
Wskaźnik przechowuje adres wartości, czyli miejsce w pamięci, gdzie ona leży. 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 |
*p |
p.value |
wartość, na którą wskazuje p |
NULL |
null |
wskazuje donikąd |
void * |
ptr |
wskazuje na coś nieznanego typu |
fn podwoj(ptr[int] p) {
p.value = p.value * 2
}
fn main() {
x := 21
podwoj(addr(x))
print(x) // 42
ptr[int] nic = null
if nic == null {
print("pusty")
}
}
Wskaźnik może wskazywać na wskaźnik: ptr[ptr[int]], a potem pp.value.value.
Użycie .value na wskaźniku null zatrzymuje program komunikatem
runtime error: used .value on a null pointer, zamiast losowo go wysypać.
⚠️ Wskaźnika na zmienną nie wolno używać po zakończeniu funkcji, do której ta zmienna należała, bo zmienna wtedy już nie istnieje. Finch jeszcze tego nie sprawdza.
11. Biblioteki z C
Prawie każda biblioteka na Linuksie ma interfejs w C: grafika, dźwięk, sieć, bazy danych. Finch może z nich korzystać bezpośrednio.
Krok 1: zaimportuj nagłówek
import "stdio.h"
import "math.h"
import "GLFW/glfw3.h" // ścieżka w systemowych katalogach nagłówków
import "mojalib.h" // nagłówek leżący obok twojego pliku .fn
Finch czyta nagłówek przez libclang (parser C z clanga) i udostępnia:
- funkcje, także te ze zmienną liczbą argumentów, jak
printf, - wartości
enum, - proste stałe
#define, które są pojedynczą liczbą, np.M_PI,EOF,GL_COLOR_BUFFER_BIT, - zmienne globalne, np.
stdoutistderr.
Krok 2: dolinkuj bibliotekę
Nagłówek tylko opisuje funkcje. Ich właściwy kod jest w pliku biblioteki (libNAZWA.so).
Powiedz Finchowi, której biblioteki użyć, na górze pliku:
link "glfw" // używa libglfw.so
link "GL" // używa libGL.so
Wpisz samą nazwę: dla libglfw.so to glfw. Jeśli pkg-config zna tę nazwę, Finch użyje jego
ustawień (ścieżek i dodatkowych bibliotek). W przeciwnym razie przekaże linkerowi -lNAZWA.
Biblioteka standardowa C i matematyczna (libm) są dolinkowane zawsze.
Na linii poleceń -l NAZWA robi to samo co link "NAZWA".
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
the header only says the functions exist; their code lives in a library.
add this at the top of your file (with the library's real name):
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 *, const char * |
str |
int *, double *, … |
ptr[i32], ptr[f64], … |
void *, struct X *, FILE *, wskaźniki na funkcje |
ptr |
Czyli int z C to i32 w Finchu. Liczby wpisane w kod dopasowują się same (abs(-5) działa),
ale zmienną typu int z Fincha trzeba zamienić: abs(i32(x)).
Przykład
import "stdio.h"
import "math.h"
import "stdlib.h"
fn main() {
printf("%d + %d = %d\n", 2, 3, 5)
print(sqrt(2), M_PI, RAND_MAX)
ptr[i32] n = malloc(4) // poproś C o 4 bajty pamięci
n.value = 7
print(n.value)
free(n) // i oddaj je
fprintf(stderr, "to idzie na wyjście błędów\n")
}
Pełny przykład z grafiką jest w examples/window.fn: okno GLFW + OpenGL, które zmienia kolor.
examples/llvm.fn pokazuje, że Finch potrafi nawet sterować samym LLVM przez jego API w C.
Co jeszcze nie działa
- Funkcje C, które przyjmują lub zwracają
structprzez wartość, np.div(). Finch mówi o tym wprost, jeśli spróbujesz. Funkcje przyjmujące wskaźnik na strukturę działają. - Makra-funkcje (
#define MAX(a,b) ...) i makra, które nie są pojedynczą liczbą. long double, liczby 128-bitowe.- Odczytywanie pól struktury C.
12. Błędy
Błędy kompilacji
Finch zatrzymuje się na pierwszym problemie i pokazuje, gdzie jest:
gra.fn: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 ^ pod miejscem błędu.
Komunikaty są po angielsku.
Błędy w trakcie działania
Niektóre problemy widać dopiero, gdy program działa. Zamiast niezdefiniowanego zachowania (jak w C) Finch zatrzymuje się z czytelnym komunikatem na wyjściu błędów 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: used .value on a null pointer |
odczyt lub zapis p.value, gdy p jest null |
13. 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). Własny nagłówek połóż obok pliku .fn. |
… come from "x.h", but its library isn't linked |
Dopisz link "nazwa" na górze pliku. |
the library 'x' wasn't found |
Biblioteka nie jest zainstalowana albo nazwa jest zła. Na Niksie dopisz ją do shell.nix i pracuj wewnątrz nix-shell. |
the C function 'f' can't be used from Finch yet |
Używa struktury przez wartość albo innego nieobsługiwanego typu. Poszukaj wariantu, który przyjmuje wskaźniki. |
no C compiler found to link with |
Zainstaluj gcc lub clang albo ustaw CC. |
| Program się kompiluje, ale przy starcie nie znajduje biblioteki C | Został zlinkowany ze ścieżki, której system nie przeszukuje. Uruchamiaj go w tym samym nix-shell albo zainstaluj bibliotekę systemowo. |
unexpected character 'ż' |
Polska litera w nazwie. W nazwach używaj tylko a–z. |
CMake: libclang not found |
Zainstaluj pakiet deweloperski libclang albo uruchom cmake w nix-shell. |
| Budowanie kończy się błędami API LLVM | Masz inną wersję LLVM niż 21. |
| Program nigdy się nie kończy | Warunek pętli nigdy nie staje się fałszywy. Naciśnij Ctrl+C. |
14. Struktura projektu i testy
Finch/
├── src/ kompilator (C++)
├── examples/ przykładowe programy (hello, tour, types, c_import, window, llvm)
├── tests/
│ ├── run/ programy + dokładny wynik, który muszą wypisać (.out)
│ ├── fail/ programy, które muszą się nie udać, z oczekiwanym błędem w 1. linijce
│ └── run.sh skrypt uruchamiający testy
├── docs/ ta dokumentacja (en, pl)
├── shell.nix środowisko deweloperskie Nix
└── CMakeLists.txt
Testy uruchomisz po zbudowaniu:
tests/run.sh # → 34 passed, 0 failed
15. Obecne ograniczenia
Finch 1.0 to solidny rdzeń, a nie skończony język. Jeszcze nie ma:
struct, tablic, czytania z klawiatury (input()), sklejania tekstów ("a" + "b"). Na razie użyj do tego funkcji z C (scanf,snprintf,malloc…).- Automatycznego zarządzania pamięcią (w planach: zwalnianie na końcu bloku i
defer). - Modułów Fincha (na razie jeden program = jeden plik
.fn). - Platform innych niż Linux x86-64.
Plan rozwoju jest w README.