diff --git a/README.md b/README.md
index ffa7660..0abd7dc 100644
--- a/README.md
+++ b/README.md
@@ -25,19 +25,24 @@ fn main() {
## Why Finch
- **Simple.** No semicolons, no headers, no preprocessor. Each thing has one way to do it.
-- **Fast.** It compiles through LLVM's `-O2` pipeline to native code, with no runtime or garbage collector.
- Recursive `fib(40)` runs in about 0.19 s (`clang -O2` on the same C: about 0.23 s).
-- **Safer than C, where it is cheap.** No silent narrowing and no signed/unsigned mixing. Division by zero
- and null `.value` stop with a clear runtime error instead of undefined behavior. Operator precedence
- that does not bite (`x & 1 == 0` means what it says).
+- **Fast.** It compiles through LLVM's `-O2` pipeline to native code, with no garbage collector.
+ Recursive `fib(40)` runs in about 0.19 s (`clang -O2` on the same C: about 0.23 s); bounds-checked
+ array code matches C.
+- **Memory without the pain.** Lists, text and structs are values: assigning copies, and the end of a
+ block frees them. There is no `free` to forget, no use-after-free, and every test is clean under valgrind.
+- **Safer than C, where it is cheap.** No silent narrowing and no signed/unsigned mixing. Out-of-range
+ indexes, division by zero and null pointers stop with a clear runtime error instead of undefined behavior.
+ Operator precedence that does not bite (`x & 1 == 0` means what it says).
- **Errors that talk like a person:**
```
game.fn:3:13: error: 'y' must be i32, but this is int (use i32(...) to convert)
3 | i32 y = x
| ^
```
-- **Any C library, directly.** `import "GLFW/glfw3.h"` + `link "glfw"`, and you can call it.
- Headers are read by libclang, so there is nothing to declare by hand.
+- **Any C library, directly.** `import "raylib.h"` + `link "raylib"`, and you can call it, structs by value
+ and callbacks included. Headers are read by libclang, so there is nothing to declare by hand.
+- **Written in itself, too.** `boot/` is a Finch compiler written in Finch that compiles itself to a
+ byte-identical result.
## Quick start
@@ -52,7 +57,8 @@ ninja -C build
./build/finch run examples/hello.fn
./build/finch build examples/tour.fn -o tour && ./tour
-tests/run.sh # 34 passed, 0 failed
+tests/run.sh # 58 passed, 0 failed
+tests/boot.sh # the compiler written in Finch builds itself
```
## Documentation
@@ -70,14 +76,17 @@ Pick the guide that fits you:
| | |
|---|---|
| Variables | `x := 5` (inferred) or `int x = 5` |
-| Types | `int` `float` `bool` `char` `str`, sized `i8`…`i64` `u8`…`u64` `f32` `f64`, pointers `ptr[T]` / `ptr` |
+| Types | `int` `float` `bool` `char` `str`, sized `i8`…`i64` `u8`…`u64` `f32` `f64`, arrays `[]T`, pointers `ptr[T]` |
| Functions | `fn add(int a, int b) -> int { return a + b }` |
-| Control | `if` / `else if` / `else`, `while`, `for i in 0..10`, `break`, `continue` |
-| Pointers | `p := addr(x)`, `p.value = 5`, `null` |
-| Conversions | `int(3.9)`, `u8(x)`, `float(n)` (implicit only when nothing is lost) |
-| Operators | `+ - * / %`, `& \| ^ << >> ~`, `== != < <= > >=`, `&& \|\| !` |
-| C interop | `import "stdio.h"`, `link "glfw"` |
-| Output | `print(a, b, c)` prints anything, separated by spaces |
+| Structs | `struct Point { … }` with one field per line (`int x`, `int y = 0`); create with `Point(1, 2)` or `Point(x: 1)` |
+| Arrays | `nums := [1, 2, 3]`, `nums.push(4)`, `nums[0]`, `nums.len`, `for n in nums { }` |
+| Text | `"a" + "b"`, `str(42)`, `int("42")`, `s.split(",")`, `s.upper()`, `s[0]` |
+| Control | `if` / `else if` / `else`, `while`, `for i in 0..10`, `break`, `continue`, `defer` |
+| Memory | automatic for arrays, text and structs; `new(...)` / `free(...)` for your own heap structures |
+| Modules | `import shapes` → `shapes.area(b)` |
+| C interop | `import "stdio.h"`, `link "glfw"`, `link "mine.c"`, `addr(fn)` for callbacks |
+| I/O | `print(...)`, `input("? ")`, `read_file`, `write_file`, `shell` |
+| Tools | `finch run/build/ir`, `-g` for gdb, `-O0` |
## Examples
@@ -85,32 +94,42 @@ Pick the guide that fits you:
|---|---|
| [`examples/hello.fn`](examples/hello.fn) | the smallest program |
| [`examples/tour.fn`](examples/tour.fn) | variables, functions, conditions, loops |
+| [`examples/guess.fn`](examples/guess.fn) | a guessing game: input, loops, C's `rand` |
+| [`examples/todo.fn`](examples/todo.fn) | structs, arrays, strings and files |
+| [`examples/structs.fn`](examples/structs.fn) | structs, arrays of structs, a linked list with `new`/`free` |
| [`examples/types.fn`](examples/types.fn) | sized numbers and pointers |
| [`examples/c_import.fn`](examples/c_import.fn) | `printf`, `math.h`, `malloc`/`free`, `stderr` |
| [`examples/window.fn`](examples/window.fn) | a GLFW + OpenGL window |
+| [`examples/raylib.fn`](examples/raylib.fn) | raylib with C structs by value |
| [`examples/llvm.fn`](examples/llvm.fn) | Finch building LLVM IR through the LLVM-C API |
+| [`boot/`](boot/) | the Finch compiler, written in Finch |
## Roadmap
- [x] Lexer, parser, LLVM codegen, O2 optimization
- [x] Types incl. sized integers, pointers, conversions; functions; `if`/`while`/`for`
-- [x] Readable compile errors; runtime checks (division, null)
+- [x] Readable compile errors; runtime checks (division, null, bounds)
- [x] Bitwise operators with sane precedence
- [x] C headers through libclang; `link` with pkg-config; link-error advice
-- [ ] `struct` (and C structs by value)
-- [ ] Arrays/slices with `.len` and bounds checks
-- [ ] Memory: automatic free at the end of the owning block, `defer`, manual `free` when wanted
-- [ ] Strings: `+`, `len`, `str(x)`; `input()`
-- [ ] Finch modules (`import math`)
-- [ ] Debug info (DWARF)
-- [ ] Bootstrap: the Finch compiler written in Finch
+- [x] `struct`, including C structs by value (System V ABI) and callbacks
+- [x] Arrays with `.len`, bounds checks and methods; `for x in list`
+- [x] Memory: values freed at the end of their block, `defer`, `new`/`free`
+- [x] Strings: `+`, `.len`, indexing, methods, `str(x)`, `int(s)`; `input()`; files
+- [x] Finch modules (`import name`)
+- [x] Debug info (`-g`)
+- [x] Bootstrap: a Finch compiler written in Finch that builds itself
+- [ ] Methods on structs, maps, `match`
+- [ ] Error values instead of stopping the program
+- [ ] More platforms (ARM64, macOS)
## Layout
```
-src/ compiler: lexer, parser, AST, codegen, C import, driver (C++17, ~2.4k lines)
+src/ compiler: lexer, parser, AST, codegen, C import + ABI, driver (C++17, ~4.5k lines)
+runtime/ the small C runtime linked into every program
+boot/ the Finch compiler written in Finch (~3k lines)
examples/ example programs
-tests/ run/ (golden output), fail/ (expected errors), run.sh
+tests/ run/ (golden output), fail/ (expected errors), run.sh, boot.sh
docs/ en/ and pl/ guides for three audiences
```
diff --git a/README.pl.md b/README.pl.md
index d373542..9991354 100644
--- a/README.pl.md
+++ b/README.pl.md
@@ -25,10 +25,13 @@ fn main() {
## Dlaczego Finch
- **Prosty.** Bez średników, bez plików nagłówkowych, bez preprocesora. Każdą rzecz robi się na jeden sposób.
-- **Szybki.** Kompiluje się przez potok `-O2` z LLVM do kodu natywnego, bez środowiska uruchomieniowego i garbage collectora.
- Rekurencyjne `fib(40)` trwa ok. 0,19 s (`clang -O2` na tym samym kodzie C: ok. 0,23 s).
+- **Szybki.** Kompiluje się przez potok `-O2` z LLVM do kodu natywnego, bez garbage collectora.
+ Rekurencyjne `fib(40)` trwa ok. 0,19 s (`clang -O2` na tym samym kodzie C: ok. 0,23 s), a kod na tablicach
+ z kontrolą zakresu dorównuje C.
+- **Pamięć bez bólu.** Listy, teksty i struktury są wartościami: przypisanie kopiuje, a koniec bloku je zwalnia.
+ Nie ma `free`, o którym można zapomnieć, nie ma użycia po zwolnieniu, a każdy test jest czysty pod valgrindem.
- **Bezpieczniejszy niż C tam, gdzie to tanie.** Nie ma cichego zwężania typów ani mieszania liczb ze znakiem
- i bez znaku. Dzielenie przez zero i `.value` na null zatrzymują program czytelnym błędem, zamiast
+ i bez znaku. Indeks poza zakresem, dzielenie przez zero i null zatrzymują program czytelnym błędem, zamiast
powodować niezdefiniowane zachowanie. Priorytety operatorów nie gryzą (`x & 1 == 0` znaczy to, co widać).
- **Błędy mówią po ludzku:**
```
@@ -36,8 +39,10 @@ fn main() {
3 | i32 y = x
| ^
```
-- **Każda biblioteka C, bezpośrednio.** Piszesz `import "GLFW/glfw3.h"` i `link "glfw"`, i możesz z niej korzystać.
- Nagłówki czyta libclang, więc nie trzeba niczego deklarować ręcznie.
+- **Każda biblioteka C, bezpośrednio.** Piszesz `import "raylib.h"` i `link "raylib"`, i możesz z niej korzystać,
+ także ze struktur przekazywanych przez wartość i z callbacków. Nagłówki czyta libclang, więc nie trzeba niczego deklarować ręcznie.
+- **Napisany także w sobie.** `boot/` to kompilator Fincha napisany w Finchu, który kompiluje sam siebie
+ do identycznego co do bajtu wyniku.
## Szybki start
@@ -52,7 +57,8 @@ ninja -C build
./build/finch run examples/hello.fn
./build/finch build examples/tour.fn -o tour && ./tour
-tests/run.sh # 34 passed, 0 failed
+tests/run.sh # 58 passed, 0 failed
+tests/boot.sh # kompilator napisany w Finchu buduje sam siebie
```
## Dokumentacja
@@ -70,14 +76,17 @@ Wybierz przewodnik dla siebie:
| | |
|---|---|
| Zmienne | `x := 5` (typ zgadnięty) albo `int x = 5` |
-| Typy | `int` `float` `bool` `char` `str`, z rozmiarem `i8`…`i64` `u8`…`u64` `f32` `f64`, wskaźniki `ptr[T]` / `ptr` |
+| Typy | `int` `float` `bool` `char` `str`, z rozmiarem `i8`…`i64` `u8`…`u64` `f32` `f64`, tablice `[]T`, wskaźniki `ptr[T]` |
| Funkcje | `fn add(int a, int b) -> int { return a + b }` |
-| Sterowanie | `if` / `else if` / `else`, `while`, `for i in 0..10`, `break`, `continue` |
-| Wskaźniki | `p := addr(x)`, `p.value = 5`, `null` |
-| Konwersje | `int(3.9)`, `u8(x)`, `float(n)` (niejawne tylko wtedy, gdy nic nie ginie) |
-| Operatory | `+ - * / %`, `& \| ^ << >> ~`, `== != < <= > >=`, `&& \|\| !` |
-| Współpraca z C | `import "stdio.h"`, `link "glfw"` |
-| Wypisywanie | `print(a, b, c)` wypisuje wszystko, oddzielając spacjami |
+| Struktury | `struct Punkt { … }` z jednym polem na linijkę (`int x`, `int y = 0`); tworzenie przez `Punkt(1, 2)` albo `Punkt(x: 1)` |
+| Tablice | `liczby := [1, 2, 3]`, `liczby.push(4)`, `liczby[0]`, `liczby.len`, `for n in liczby { }` |
+| Tekst | `"a" + "b"`, `str(42)`, `int("42")`, `s.split(",")`, `s.upper()`, `s[0]` |
+| Sterowanie | `if` / `else if` / `else`, `while`, `for i in 0..10`, `break`, `continue`, `defer` |
+| Pamięć | automatyczna dla tablic, tekstów i struktur; `new(...)` / `free(...)` dla własnych struktur na stercie |
+| Moduły | `import ksztalty` → `ksztalty.pole(p)` |
+| Współpraca z C | `import "stdio.h"`, `link "glfw"`, `link "moje.c"`, `addr(fn)` dla callbacków |
+| Wejście/wyjście | `print(...)`, `input("? ")`, `read_file`, `write_file`, `shell` |
+| Narzędzia | `finch run/build/ir`, `-g` dla gdb, `-O0` |
## Przykłady
@@ -85,32 +94,42 @@ Wybierz przewodnik dla siebie:
|---|---|
| [`examples/hello.fn`](examples/hello.fn) | najmniejszy program |
| [`examples/tour.fn`](examples/tour.fn) | zmienne, funkcje, warunki, pętle |
+| [`examples/guess.fn`](examples/guess.fn) | gra w zgadywanie: wejście, pętle, `rand` z C |
+| [`examples/todo.fn`](examples/todo.fn) | struktury, tablice, teksty i pliki |
+| [`examples/structs.fn`](examples/structs.fn) | struktury, tablice struktur, lista wiązana z `new`/`free` |
| [`examples/types.fn`](examples/types.fn) | liczby z rozmiarem i wskaźniki |
| [`examples/c_import.fn`](examples/c_import.fn) | `printf`, `math.h`, `malloc`/`free`, `stderr` |
| [`examples/window.fn`](examples/window.fn) | okno GLFW + OpenGL |
+| [`examples/raylib.fn`](examples/raylib.fn) | raylib ze strukturami C przez wartość |
| [`examples/llvm.fn`](examples/llvm.fn) | Finch budujący LLVM IR przez API LLVM-C |
+| [`boot/`](boot/) | kompilator Fincha napisany w Finchu |
## Plan rozwoju
- [x] Lekser, parser, generowanie kodu LLVM, optymalizacja O2
- [x] Typy, w tym liczby z rozmiarem, wskaźniki, konwersje; funkcje; `if`/`while`/`for`
-- [x] Czytelne błędy kompilacji; kontrole w czasie działania (dzielenie, null)
+- [x] Czytelne błędy kompilacji; kontrole w czasie działania (dzielenie, null, zakres)
- [x] Operatory bitowe z rozsądnymi priorytetami
- [x] Nagłówki C przez libclang; `link` z pkg-config; porady przy błędach linkowania
-- [ ] `struct` (i struktury C przez wartość)
-- [ ] Tablice/wycinki z `.len` i kontrolą zakresu
-- [ ] Pamięć: automatyczne zwalnianie na końcu bloku, `defer`, ręczne `free` dla chętnych
-- [ ] Teksty: `+`, `len`, `str(x)`; `input()`
-- [ ] Moduły Fincha (`import math`)
-- [ ] Informacje dla debuggera (DWARF)
-- [ ] Bootstrap: kompilator Fincha napisany w Finchu
+- [x] `struct`, także struktury C przez wartość (ABI System V) i callbacki
+- [x] Tablice z `.len`, kontrolą zakresu i metodami; `for x in lista`
+- [x] Pamięć: zwalnianie na końcu bloku, `defer`, `new`/`free`
+- [x] Teksty: `+`, `.len`, indeksowanie, metody, `str(x)`, `int(s)`; `input()`; pliki
+- [x] Moduły Fincha (`import nazwa`)
+- [x] Informacje dla debuggera (`-g`)
+- [x] Bootstrap: kompilator Fincha napisany w Finchu, który buduje sam siebie
+- [ ] Metody w strukturach, mapy, `match`
+- [ ] Błędy jako wartości zamiast zatrzymywania programu
+- [ ] Więcej platform (ARM64, macOS)
## Struktura
```
-src/ kompilator: lekser, parser, AST, codegen, import C, sterownik (C++17, ok. 2,4 tys. linii)
+src/ kompilator: lekser, parser, AST, codegen, import C + ABI, sterownik (C++17, ok. 4,5 tys. linii)
+runtime/ mały runtime w C dołączany do każdego programu
+boot/ kompilator Fincha napisany w Finchu (ok. 3 tys. linii)
examples/ przykładowe programy
-tests/ run/ (wzorcowe wyjście), fail/ (oczekiwane błędy), run.sh
+tests/ run/ (wzorcowe wyjście), fail/ (oczekiwane błędy), run.sh, boot.sh
docs/ przewodniki en/ i pl/ dla trzech grup odbiorców
```
diff --git a/docs/en/for-beginners.md b/docs/en/for-beginners.md
index aa17c75..6f17b69 100644
--- a/docs/en/for-beginners.md
+++ b/docs/en/for-beginners.md
@@ -20,10 +20,15 @@ Take it slowly, one section at a time, and type every example yourself: that is
8. [Making decisions: if](#8-making-decisions-if)
9. [Repeating things: loops](#9-repeating-things-loops)
10. [Your own commands: functions](#10-your-own-commands-functions)
-11. [When something goes wrong](#11-when-something-goes-wrong)
-12. [Small projects to try](#12-small-projects-to-try)
-13. [Cheat sheet](#13-cheat-sheet)
-14. [Words you will hear](#14-words-you-will-hear)
+11. [Lists: many values in one box](#11-lists-many-values-in-one-box)
+12. [Playing with text](#12-playing-with-text)
+13. [Asking the user](#13-asking-the-user)
+14. [Your own kinds of values: structs](#14-your-own-kinds-of-values-structs)
+15. [Saving to a file](#15-saving-to-a-file)
+16. [When something goes wrong](#16-when-something-goes-wrong)
+17. [Small projects to try](#17-small-projects-to-try)
+18. [Cheat sheet](#18-cheat-sheet)
+19. [Words you will hear](#19-words-you-will-hear)
---
@@ -72,7 +77,7 @@ To check it works, type:
./build/finch version
```
-You should see something like `finch 1.0.0`.
+You should see something like `finch 2.0.0`.
---
@@ -441,7 +446,200 @@ It does not matter where in the file you put your functions, above or below `mai
---
-## 11. When something goes wrong
+## 11. Lists: many values in one box
+
+Sometimes one box is not enough. A shopping list has many things on it.
+A **list** (programmers also say **array**) keeps many values in order:
+
+```c
+fn main() {
+ fruits := ["apple", "banana", "cherry"]
+ print(fruits)
+ print("I have", fruits.len, "fruits")
+ print("The first one is", fruits[0])
+}
+```
+
+```
+["apple", "banana", "cherry"]
+I have 3 fruits
+The first one is apple
+```
+
+- Square brackets `[ ]` make a list. Commas separate the values.
+- `.len` tells you how many things are in the list.
+- `fruits[0]` is the **first** thing. Counting starts at **0**, so `fruits[1]` is the second, and `fruits[2]` the third.
+
+If you ask for a place that doesn't exist, like `fruits[10]`, the program stops and tells you:
+`index 10 is out of range (the length is 3)`.
+
+### Adding and taking away
+
+```c
+fn main() {
+ []int scores // an empty list of whole numbers
+ scores.push(10) // put 10 at the end
+ scores.push(25)
+ scores.push(7)
+ print(scores) // [10, 25, 7]
+
+ last := scores.pop() // take the last one out
+ print(last, scores) // 7 [10, 25]
+
+ scores.sort()
+ print(scores)
+}
+```
+
+### Going through a list
+
+`for ... in` visits every value, one by one:
+
+```c
+fn main() {
+ total := 0
+ for n in [3, 5, 2] {
+ total += n
+ }
+ print("Total:", total) // Total: 10
+}
+```
+
+Lists can do more: `contains`, `find`, `insert`, `remove`, `sort`, `reverse`, `join`.
+The [guide for technicians](for-technicians.md#arrays) lists them all.
+
+---
+
+## 12. Playing with text
+
+Text can be glued together with `+`:
+
+```c
+fn main() {
+ name := "Ola"
+ greeting := "Hello, " + name + "!"
+ print(greeting)
+}
+```
+
+To glue a number to text, turn the number into text first with `str(...)`:
+
+```c
+age := 10
+print("Age: " + str(age))
+```
+
+Text has handy tools too:
+
+```c
+fn main() {
+ word := "Finch"
+ print(word.len) // 5 letters
+ print(word[0]) // F (the first letter)
+ print(word.upper()) // FINCH
+ print(word.lower()) // finch
+ print(word.contains("in")) // true
+ for letter in word {
+ print(letter) // F, i, n, c, h, one per line
+ }
+}
+```
+
+---
+
+## 13. Asking the user
+
+`input` shows a question and waits until the person types an answer and presses Enter:
+
+```c
+fn main() {
+ name := input("What's your name? ")
+ print("Nice to meet you,", name)
+}
+```
+
+The answer is always **text**. If you need a number, turn it into one with `int(...)`:
+
+```c
+fn main() {
+ answer := input("How old are you? ")
+ age := int(answer)
+ print("Next year you will be", age + 1)
+}
+```
+
+If the person types something that isn't a number, the program stops with a message
+like `can't turn "abc" into int`.
+
+---
+
+## 14. Your own kinds of values: structs
+
+Imagine a card in a library: each card has a title, an author and a year.
+A **struct** lets you make your own kind of value with named parts:
+
+```c
+struct Book {
+ str title
+ str author
+ int year
+}
+
+fn main() {
+ b := Book(title: "The Hobbit", author: "Tolkien", year: 1937)
+ print(b.title, "by", b.author)
+ b.year = 1938
+ print(b)
+}
+```
+
+```
+The Hobbit by Tolkien
+Book(title: "The Hobbit", author: "Tolkien", year: 1938)
+```
+
+- `struct Book { ... }` describes what every book has. Put it **outside** `main`.
+- `Book(title: ..., author: ..., year: ...)` makes one book.
+- `b.title` reads one part. `b.year = 1938` changes it.
+
+You can give parts a starting value. Then you don't have to fill them in:
+
+```c
+struct Player {
+ str name
+ int lives = 3
+}
+
+fn main() {
+ p := Player(name: "Ola")
+ print(p.lives) // 3
+}
+```
+
+And you can keep many of them in a list: `[]Book shelf`, then `shelf.push(b)`.
+
+---
+
+## 15. Saving to a file
+
+Programs forget everything when they end. To remember things, save them to a file:
+
+```c
+fn main() {
+ write_file("note.txt", "Buy milk")
+ print(read_file("note.txt"))
+}
+```
+
+- `write_file(name, text)` saves the text in a file (and replaces what was there).
+- `read_file(name)` gives you the file's text back.
+- `file_exists(name)` tells you if the file is there (`true` or `false`).
+
+The file appears in the folder where you ran the program.
+
+---
+
+## 16. When something goes wrong
Everyone makes mistakes when programming, even experts, every day.
Finch tries hard to explain what went wrong in plain words. Example:
@@ -479,10 +677,13 @@ Here we simply misspelled `age`.
| `this '{' is never closed` | A `}` is missing | Add the `}` |
| `a condition must be bool` | `if` needs a yes/no question | Compare something: `if x > 0` |
| `runtime error: division by zero` | You divided by 0 while the program ran | Check the number before dividing |
+| `index 5 is out of range (the length is 3)` | You asked a list for a place it doesn't have | Remember counting starts at 0; check `.len` |
+| `can't turn "abc" into int` | `int(...)` got text that isn't a number | Check what the person typed |
+| `can't use '+' on str and int` | Text plus a number | Use `str(...)` on the number |
---
-## 12. Small projects to try
+## 17. Small projects to try
Try writing each one yourself first. Look at the solution only if you get stuck.
@@ -589,9 +790,60 @@ fn main() {
```
+### Project 6: Guess the number
+
+The computer picks a secret number. The player guesses until they find it,
+and the computer says "higher" or "lower" each time.
+
+Solution
+
+```c
+fn main() {
+ secret := 42
+ tries := 0
+ while true {
+ guess := int(input("Your guess: "))
+ tries += 1
+ if guess < secret {
+ print("Higher!")
+ } else if guess > secret {
+ print("Lower!")
+ } else {
+ print("Yes! You needed", tries, "tries")
+ return
+ }
+ }
+}
+```
+
+`return` inside `main` ends the program. (A version with a random number is in `examples/guess.fn`.)
+
+
+### Project 7: Class average
+
+Ask for 3 grades, keep them in a list, and show the list and the average.
+
+Solution
+
+```c
+fn main() {
+ []int grades
+ for i in 0..3 {
+ grades.push(int(input("Grade: ")))
+ }
+ sum := 0
+ for g in grades {
+ sum += g
+ }
+ print("Grades:", grades)
+ print("Average:", float(sum) / grades.len)
+}
+```
+
+
---
-## 13. Cheat sheet
+## 18. Cheat sheet
```c
// a comment
@@ -624,11 +876,21 @@ fn main() { // the program starts here
fn add(int a, int b) -> int { // your own command
return a + b
}
+
+struct Pet { // your own kind of value
+ str name
+ int age = 1
+}
+
+// lists: nums := [1, 2, 3] nums.push(4) nums[0] nums.len for n in nums { }
+// text: "a" + "b" str(42) int("42") s.len s.upper() s.contains("x")
+// ask: name := input("Name? ")
+// files: write_file("f.txt", text) read_file("f.txt")
```
---
-## 14. Words you will hear
+## 19. Words you will hear
| Word | Meaning |
|---|---|
@@ -641,6 +903,10 @@ fn add(int a, int b) -> int { // your own command
| **function** | A named group of steps you can use again |
| **loop** | Steps that repeat |
| **condition** | A yes/no question, like `x > 5` |
+| **list / array** | Many values kept in order, like `[1, 2, 3]` |
+| **index** | The position of a value in a list, starting at 0 |
+| **struct** | Your own kind of value, made of named parts |
+| **input** | What the person types into the program |
| **error** | A message saying something is wrong, and where |
| **bug** | A mistake in a program |
diff --git a/docs/en/for-engineers.md b/docs/en/for-engineers.md
index 9a2a7cf..2e60de8 100644
--- a/docs/en/for-engineers.md
+++ b/docs/en/for-engineers.md
@@ -1,10 +1,11 @@
# Finch for Engineers
-**The complete internals of the Finch 1.0 compiler.** It covers how source becomes tokens,
-tokens become an AST, and the AST becomes LLVM IR, which is then optimized, emitted and
-linked. It describes the exact type rules, how every construct lowers to IR (with real
-compiler output), how C headers are imported through libclang, the ABI details, and what
-each function in the compiler is for and how it differs from its neighbours.
+**The complete internals of Finch 2.0.** How source becomes tokens, tokens an AST, and the AST
+LLVM IR, which is then optimized, emitted and linked against a small C runtime. The exact type
+rules; the ownership model and how it lowers to copies and drops; how every construct becomes IR
+(with real compiler output); how C headers, C structs and the System V ABI are handled; modules,
+debug info, and the self-hosted compiler in `boot/`. Every function in the compiler is listed with
+what it does and how it differs from its neighbours.
Assumed: you know C/C++ and have at least seen LLVM IR.
For the language from a user's point of view, see the [technicians' guide](for-technicians.md).
@@ -20,60 +21,65 @@ For the language from a user's point of view, see the [technicians' guide](for-t
3. [Lexer](#3-lexer)
4. [Grammar and parser](#4-grammar-and-parser)
5. [AST](#5-ast)
-6. [Semantic model](#6-semantic-model)
-7. [Type system](#7-type-system)
-8. [Code generation](#8-code-generation)
-9. [Runtime checks](#9-runtime-checks)
-10. [C interop via libclang](#10-c-interop-via-libclang)
-11. [Optimization, emission, linking](#11-optimization-emission-linking)
-12. [Diagnostics](#12-diagnostics)
-13. [Build system](#13-build-system)
-14. [Testing](#14-testing)
-15. [Semantics vs. C: defined and undefined behavior](#15-semantics-vs-c-defined-and-undefined-behavior)
-16. [Function reference: who does what](#16-function-reference-who-does-what)
-17. [Extending the compiler](#17-extending-the-compiler)
-18. [Known limitations and roadmap](#18-known-limitations-and-roadmap)
+6. [Loading: files and modules](#6-loading-files-and-modules)
+7. [Semantic model](#7-semantic-model)
+8. [Type system](#8-type-system)
+9. [Ownership and memory](#9-ownership-and-memory)
+10. [Code generation](#10-code-generation)
+11. [Runtime checks](#11-runtime-checks)
+12. [The C runtime](#12-the-c-runtime)
+13. [C interop: headers, structs, ABI](#13-c-interop-headers-structs-abi)
+14. [Optimization, emission, linking](#14-optimization-emission-linking)
+15. [Debug info](#15-debug-info)
+16. [Diagnostics](#16-diagnostics)
+17. [The self-hosted compiler (boot/)](#17-the-self-hosted-compiler-boot)
+18. [Build system](#18-build-system)
+19. [Testing](#19-testing)
+20. [Semantics vs. C](#20-semantics-vs-c)
+21. [Function reference](#21-function-reference)
+22. [Extending the compiler](#22-extending-the-compiler)
+23. [Known limitations](#23-known-limitations)
---
## 1. Architecture
-Finch is a classic single-pass front end in front of LLVM. Type checking and IR generation
-are one walk over the AST. There is no separate semantic pass and no intermediate IR of its own.
+Finch is a single-pass front end in front of LLVM: type checking and IR generation are one walk
+over the AST, with no intermediate representation of its own.
```
- file.fn
- │ readFile() src/main.cpp
- ▼
- lex() ──────────► std::vector src/lexer.cpp
- ▼
- parse() ─────────► Program (AST) src/parser.cpp, src/ast.h
- │ ├─ imports ──► importHeaders() ──► CImports src/cimport.cpp (libclang)
- │ └─ links
- ▼
- hostMachine() ───► llvm::TargetMachine (triple, data layout)
- ▼
- generate() ──────► llvm::Module (type check + IRBuilder, then verifyModule) src/codegen.cpp
- ▼
- optimize() ──────► PassBuilder default pipeline (skipped with -O0)
- ▼
- emitObject() ────► file.o (legacy::PassManager + addPassesToEmitFile)
- ▼
- link() ──────────► cc file.o -o file -lm [-l… | pkg-config --libs …]
- ▼
- executable (finch run: executed from a temp file, then deleted)
+ main.fn ──► Loader::load() ── lex() ── parse() ──► Program (+ each imported module, recursively)
+ │
+ all `import "x.h"` ──► importHeaders() (libclang) ──► CImports
+ │
+ hostMachine() ──► TargetMachine (triple + data layout)│
+ ▼
+ generate(progs, cimports, tm, debug) ──► Codegen::run()
+ declareStructs() all Finch + C structs, LLVM struct bodies, cycle check
+ declareFns() every function's signature (+ "borrow" analysis of parameters)
+ define() each body: type check + IRBuilder, scopes, drops, runtime checks
+ defineMainWrapper the C `main(argc, argv)`
+ DIBuilder::finalize (with -g) ; verifyModule
+ ▼
+ optimize() PassBuilder default (skipped with -O0)
+ emitObject() legacy PM + addPassesToEmitFile ──► prog.o
+ link() cc prog.o ~/.cache/finch/rt-.o [your .c/.o/.a] -lm [pkg-config/-l…]
+ ▼
+ executable (finch run: executed from a temp file with the remaining arguments, then deleted)
```
-Design decisions that shape everything else:
+Design decisions that shape the rest:
-- **Fail fast.** The first error prints a diagnostic and calls `exit(1)` (`fail()` in `src/error.h`).
- There is no error recovery, so the AST and codegen never deal with half-valid input.
-- **One walk.** `Codegen` carries the scope, loop and function state. Every expression returns
- its IR value and its Finch type together (`Value_`).
-- **Let LLVM do the work.** Locals are `alloca`s, and `mem2reg`/SROA promote them to SSA.
- Finch never builds SSA or φ-nodes itself, except for `&&`/`||`.
-- **The system C toolchain is the runtime.** `printf`, `strcmp`, `dprintf`, `exit` come from libc.
- There is no Finch runtime library.
+- **Fail fast.** The first error prints a diagnostic and exits (`failAt()` in `src/error.h`). No error
+ recovery, so later phases never see half-valid input.
+- **One walk.** `Codegen` carries scopes, loops, the current function and module. Each expression
+ returns its IR value, its Finch type and two flags (`Value_`, §8).
+- **Let LLVM do the work.** Locals are `alloca`s promoted by `mem2reg`/SROA. Finch builds φ-nodes by
+ hand only for `&&`/`||`.
+- **Values, not references.** Arrays, strings and structs have value semantics: assignment copies, the
+ end of a block drops. Ownership is decided at compile time with no reference counting (§9).
+- **A tiny C runtime** (`runtime/finch_rt.c`, ~430 lines) does what would be tedious in IR: string
+ operations, array growth, input, files, panics. It is embedded in the compiler and cached.
---
@@ -81,1051 +87,798 @@ Design decisions that shape everything else:
| File | Lines | Responsibility |
|---|---|---|
-| `src/error.h` | ~25 | `fail(line, col, msg)`: prints `file:line:col: error:` and a caret excerpt, then exits. Holds `g_file`, `g_source`. |
-| `src/lexer.h/.cpp` | ~240 | `Tok` enum, `Token`, `lex()`, `tokName()` |
-| `src/ast.h` | ~235 | `Type`, all `Expr`/`Stmt` nodes, `FnDecl`, `Import`, `Link`, `Program` |
-| `src/parser.h/.cpp` | ~430 | Recursive-descent `Parser`, `typeFromName()` |
-| `src/cimport.h/.cpp` | ~280 | libclang header import: `importHeaders()` → `CImports` (functions, constants, globals) |
-| `src/codegen.h/.cpp` | ~900 | `Codegen`: type checking + LLVM IR generation, `generate()` |
-| `src/main.cpp` | ~260 | Driver: CLI, target machine, optimization, object emission, linking, link-error analysis, `run` |
-| `tests/run.sh` | | Golden-output and expected-failure tests |
+| `src/error.h` | 40 | `g_files` (all sources), `failAt(file, line, col, msg)`, `fail(line, col, msg)` |
+| `src/lexer.h/.cpp` | 250 | `Tok`, `Token`, `lex(fileIndex)`, `tokName()` |
+| `src/ast.h` | 310 | `Type` (incl. Array, Fixed, Struct, Named), all `Expr`/`Stmt` nodes, `FnDecl`, `StructDecl`, `Import`, `Link`, `Program` |
+| `src/parser.h/.cpp` | 540 | Recursive-descent `Parser`, `typeFromName()` |
+| `src/cimport.h/.cpp` | 420 | libclang import: functions, C structs (fields, offsets), constants, globals |
+| `src/codegen.h` | 15 | `generate()` |
+| `src/codegen_impl.h` | 265 | The `Codegen` class and its helper structs, shared by the four files below |
+| `src/codegen.cpp` | 1090 | Program, declarations, scopes and cleanups, statements, expressions, places (`ref`), operators, mutation analysis |
+| `src/codegen_types.cpp` | 500 | LLVM types, type resolution, struct layout, coercions, ownership (copy/drop helpers), printing, DWARF types |
+| `src/codegen_builtins.cpp` | 710 | Calls, constructors, built-ins, conversions, array and str methods, runtime declarations, panics |
+| `src/abi.cpp` | 310 | System V x86-64 classification, C calls with structs by value, C-callable thunks |
+| `src/main.cpp` | 390 | Driver: CLI, loader, target machine, O2, object emission, runtime cache, linking, link-error advice, `run` |
+| `runtime/finch_rt.c` | 440 | The runtime library (§12) |
+| `boot/*.fn` | 3080 | The self-hosted compiler (§17) |
---
## 3. Lexer
-`lex(src, file)` makes one linear pass over the source and returns a `std::vector`
-terminated by `Tok::End`.
+`lex(file)` makes one pass over `g_files[file].text` and returns `std::vector` ending in `Tok::End`.
```cpp
-struct Token {
- Tok kind;
- std::string text; // identifier / literal contents (escapes already decoded)
- int line, col; // 1-based
- bool newlineBefore; // a newline occurred between the previous token and this one
-};
+struct Token { Tok kind; std::string text; int line, col; bool newlineBefore; };
```
-### Newlines as statement terminators
-
-Finch has no semicolons. Instead of emitting `NEWLINE` tokens, as Python and Go do internally,
-the lexer **marks** each token with `newlineBefore`. The parser decides what that means
-(section 4). This keeps the grammar free of newline tokens, which would otherwise have to be
-skipped everywhere: in argument lists, between `}` and `else`, and so on.
-
-### Tokens
-
-- **Positions:** `line` and `col` are 1-based, and `col` counts **characters**, not bytes:
- UTF-8 continuation bytes do not advance it, so carets line up under text such as `"Błąd"`.
- An unexpected non-ASCII character is reported whole (`'ż'`), with a hint that names are ASCII-only.
-- **Identifiers / keywords:** `[A-Za-z_][A-Za-z0-9_]*`. Keywords: `fn return if else while for in break continue true false null import link`.
- **Type names are not keywords.** `int`, `u8`, `ptr`… lex as `Ident`, and the parser recognises them through `typeFromName()`.
- This keeps the lexer independent of the type list, and lets `u8(x)` parse as an ordinary call.
-- **Integers:** decimal or `0x` hex, with `_` separators (removed from `text`). Leading zeros are decimal.
- Finch has no octal, so `010` is 10. Value parsing happens in the parser (`strtoull`, base 10 or 16).
-- **Floats:** `digits.digits`. A dot is only part of a number when **followed by a digit**,
- so `0..10` lexes as `Int DotDot Int`, not `Float(0.) Dot …`.
-- **Strings / chars:** `"…"` and `'…'`. Escapes `\n \t \r \0 \\ \" \'` are decoded into `text`.
- An unknown escape, an unterminated literal or a newline inside a literal is an error. A char literal must decode to exactly one byte.
-- **Operators:** maximal munch: `->` before `-`, `<<` and `<=` before `<`, `&&` before `&`, `..` before `.`, `:=` (a lone `:` is an error that suggests `:=`).
-- **Comments:** `//` to end of line, `/* … */` (not nested; an unterminated one is an error).
+- **Newlines are not tokens.** Each token records `newlineBefore`; the parser decides what it means (§4).
+ This keeps newline handling out of every list rule in the grammar.
+- **Positions:** 1-based; `col` counts characters (UTF-8 continuation bytes don't advance it), so
+ carets line up under `"Błąd"`. A stray non-ASCII character is reported whole, with a hint that names are ASCII-only.
+- **Identifiers / keywords:** `[A-Za-z_][A-Za-z0-9_]*`. Keywords:
+ `fn return if else while for in break continue true false null import link struct defer`.
+ **Type names are not keywords**: `int`, `u8`, `ptr`… are `Ident`s recognised by the parser through
+ `typeFromName()`, so `u8(x)` is an ordinary call and the lexer doesn't depend on the type list.
+- **Numbers:** decimal or `0x` hex with `_` separators. No octal (`010` is 10). A `.` belongs to a number
+ only when a digit follows, so `0..10` is `Int DotDot Int`.
+- **Strings / chars:** escapes `\n \t \r \0 \\ \" \'`. No newline inside a literal.
+- **Operators:** maximal munch (`->` before `-`, `<<`/`<=` before `<`, `&&` before `&`, `..` before `.`,
+ `:=` before `:`). `:` alone is a token (named arguments).
+- **Comments:** `//` and non-nesting `/* */`.
---
## 4. Grammar and parser
-`Parser` (`src/parser.cpp`) is a hand-written recursive-descent parser with one token of
-lookahead (`cur()`), plus a second (`peekTok()`) in two places.
-
-### Grammar (EBNF)
+`Parser` (`src/parser.cpp`) is hand-written recursive descent with one token of lookahead, plus up to
+three in `atDeclaration()`.
```ebnf
-program = { import | link | function } ;
-import = "import" STRING ;
-link = "link" STRING ;
+program = { import | link | struct | function } ;
+import = "import" ( STRING | IDENT ) ; (* "x.h" = C header, name = Finch module *)
+link = "link" STRING ; (* "glfw" | "file.c" | "file.o" | "lib.a" *)
+struct = "struct" IDENT "{" { type IDENT [ "=" expr ] NEWLINE } "}" ;
function = "fn" IDENT "(" [ param { "," param } ] ")" [ "->" type ] block ;
param = type IDENT ;
-type = TYPENAME | "ptr" "[" type "]" ; (* TYPENAME: int float bool char str ptr i8..u64 f32 f64 *)
+type = "[" "]" type | "ptr" [ "[" type "]" ] | TYPENAME | IDENT [ "." IDENT ] ;
block = "{" { statement TERMINATOR } "}" ;
-statement = if | while | for | return | "break" | "continue" | block
- | type IDENT [ "=" expr ] (* declaration *)
- | IDENT ":=" expr (* inferred declaration *)
- | target assignop expr (* target: IDENT or postfix ending in .value *)
- | call ;
-if = "if" expr block [ "else" ( if | block ) ] ;
-while = "while" expr block ;
-for = "for" IDENT "in" expr ".." expr block ;
-return = "return" [ expr ] ; (* expr only if on the same line *)
+statement = if | while | for | return | "break" | "continue" | "defer" statement | block
+ | type IDENT [ "=" expr ] (* declaration, see atDeclaration *)
+ | IDENT ":=" expr
+ | target assignop expr (* target: var, field, element, p.value *)
+ | call | method ;
+for = "for" IDENT "in" expr ( ".." expr block | block ) ; (* range | for-each *)
assignop = "=" | "+=" | "-=" | "*=" | "/=" | "%=" ;
-expr = or ;
-or = and { "||" and } ;
-and = cmp { "&&" cmp } ;
+expr = or ; or = and { "||" and } ; and = cmp { "&&" cmp } ;
cmp = add { ( "==" | "!=" | "<" | "<=" | ">" | ">=" ) add } ;
add = mul { ( "+" | "-" | "|" | "^" ) mul } ;
mul = unary { ( "*" | "/" | "%" | "<<" | ">>" | "&" ) unary } ;
unary = ( "-" | "!" | "~" ) unary | postfix ;
-postfix = primary { "." IDENT } ;
+postfix = primary { "." IDENT [ args ] | "[" expr "]" } ;
primary = INT | FLOAT | STRING | CHAR | "true" | "false" | "null"
- | IDENT [ "(" [ expr { "," expr } ] ")" ]
- | "(" expr ")" ;
+ | IDENT [ args ] | "(" expr ")" | "[" [ expr { "," expr } [","] ] "]" ;
+args = "(" [ arg { "," arg } ] ")" ; arg = [ IDENT ":" ] expr ;
```
-### The statement terminator rule
+**Statement termination.** After a statement, `endOfStatement()` requires `}`, end of file, or a token
+with `newlineBefore`. Inside expressions `sameLine()` (`parenDepth > 0 || !newlineBefore`) decides whether
+an operator, `.`, `[` or a call's `(` continues the expression. `parenDepth` grows inside `( )` and `[ ]`
+and is saved/reset to 0 when entering `{ }`.
-`TERMINATOR` is not a token. After every statement, `endOfStatement()` requires that the next
-token is `}`, end-of-file, or has `newlineBefore == true`. Otherwise the error is
-`put each statement on its own line`.
+**Precedence is Go's**, not C's: `&` binds like `*`, and `|`/`^` like `+`, all above comparisons, so
+`x & MASK == 0` means `(x & MASK) == 0`.
-Inside expressions, `sameLine()` decides whether a binary operator, `.`, or a call's `(`
-**continues** the current expression:
+**Recognising statements.** `atDeclaration()` is true for `[` `]` (an array type), `TYPE IDENT`,
+`ptr [`, and `IDENT . IDENT IDENT` (a module's type). Otherwise the parser **parses a whole expression,
+then looks at the next token**: an assignment operator makes it a target (it must be a `Var`, `Member` or
+`Index`), otherwise it must be a call or method call, or it's `this value is computed but never used`.
-```cpp
-bool sameLine() const { return parenDepth > 0 || !cur().newlineBefore; }
-```
-
-So:
-
-```c
-a := 1 + // '+' is on this line → the expression continues to the next line
- 2
-b := (1 // inside parentheses (parenDepth > 0) newlines are ignored
- + 2)
-c := 1
--5 // '-' starts a new line → the statement ended at '1'; "-5" is then an error
-```
-
-`parenDepth` is incremented around `( … )` groups, argument lists and parameter lists. It is
-**saved and reset to 0** when entering a `{ }` block, so a block inside a parenthesised
-context, which does not occur yet, would still use line rules.
-
-A call's `(` must be on the same line as the callee. Otherwise `f⏎(x)` would silently parse as a call.
-
-### Precedence: Go's, not C's
-
-| Level | Operators |
-|---|---|
-| 5 | `*` `/` `%` `<<` `>>` `&` |
-| 4 | `+` `-` `\|` `^` |
-| 3 | `==` `!=` `<` `<=` `>` `>=` |
-| 2 | `&&` |
-| 1 | `\|\|` |
-
-All binary operators are left-associative. Comparisons are also parsed left-associatively
-(`a < b < c` parses), but the type checker then rejects comparing `bool < int`.
-C puts `&`, `^`, `|` **below** `==`, which makes `x & MASK == 0` mean `x & (MASK == 0)`.
-Finch adopts Go's table, which removes that whole class of bugs.
-
-### How statements are recognised
-
-`statement()` dispatches on the first token:
-
-1. Keywords (`if`, `while`, `for`, `return`, `break`, `continue`, `{`) dispatch directly.
- `fn`, `import` and `link` inside a block produce targeted errors.
-2. **Typed declaration:** `atDeclaration()` returns true when the current token is a type name
- and the next is an identifier (`int x`), or when it is `ptr` followed by `[` (`ptr[int] p`).
- This is the only place that needs two-token lookahead.
-3. **Inferred declaration:** `IDENT` followed by `:=`.
-4. Otherwise the parser **parses a full expression first**, then looks at the next token. If it is
- an assignment operator on the same line, the expression becomes the assignment target. It must
- be a `VarExpr` or `MemberExpr`, otherwise: `only a variable or p.value can be assigned to`.
- This "expression, then decide" approach lets arbitrary l-value forms (`pp.value.value = 1`)
- work without a separate l-value grammar.
-5. A bare expression statement must be a `CallExpr`. Anything else is
- `this value is computed but never used`.
-
-### Top level
-
-`program()` loops over `import`, `link` and `fn`. A top-level item that starts with a type name
-gets the hint `functions start with 'fn', like: fn add(int a, int b) -> int { ... }`, which catches
-C-style function definitions. `link` arguments are validated: `"libfoo.so"` or a path is
-rejected with a suggestion for the bare name.
+**Struct literals are calls**: `Point(1, 2)` or `Point(x: 1, y: 2)`. Brace-based literals (`Point{…}`)
+would be ambiguous with blocks (`if done {`); a call needs no special grammar, and named arguments
+(`IDENT ":"` in `args()`) are only accepted by constructors at code generation time.
---
## 5. AST
-All nodes live in `src/ast.h`. Expressions and statements are small class hierarchies with
-an explicit `kind` tag. Consumers `switch` on `kind` and `static_cast`, which avoids RTTI and
-`dynamic_cast` and keeps dispatch obvious.
-
-```cpp
-struct Expr { ExprKind kind; Pos pos; virtual ~Expr(); };
-using ExprPtr = std::unique_ptr;
-```
+All nodes are in `src/ast.h`: small class hierarchies with an explicit `kind`, dispatched with
+`switch` + `static_cast`.
| ExprKind | Node | Fields |
|---|---|---|
-| `Int` | `IntExpr` | `long long value` |
-| `Float` | `FloatExpr` | `double value` |
-| `Bool` / `Char` / `Str` | `BoolExpr` / `CharExpr` / `StrExpr` | `value` |
+| `Int` `Float` `Bool` `Char` `Str` | literals | `value` |
| `Null` | `NullExpr` | |
| `Var` | `VarExpr` | `name` |
-| `Unary` | `UnaryExpr` | `char op` (`-` `!` `~`), `operand` |
+| `Unary` | `UnaryExpr` | `op` (`- ! ~`), `operand` |
| `Binary` | `BinaryExpr` | `BinOp op`, `lhs`, `rhs` |
-| `Call` | `CallExpr` | `std::string callee`, `args` |
-| `Member` | `MemberExpr` | `obj`, `field` (only `value` is meaningful in 1.0) |
+| `Call` | `CallExpr` | `callee` (a name), `args`, `argNames` |
+| `Member` | `MemberExpr` | `obj`, `field` (`.x`, `.len`, `.ptr`, `.value`) |
+| `Index` | `IndexExpr` | `obj`, `index` |
+| `ArrayLit` | `ArrayLitExpr` | `elems` |
+| `Method` | `MethodExpr` | `obj`, `name`, `args`, `argNames`; also `module.fn(...)` |
-| StmtKind | Node | Fields |
-|---|---|---|
-| `Block` | `BlockStmt` | `std::vector body` |
-| `VarDecl` | `VarDeclStmt` | `hasType`, `type`, `name`, `init` (nullable if `hasType`) |
-| `Assign` | `AssignStmt` | `target` (Var/Member), `char op` (`= + - * / %`), `value` |
-| `Expr` | `ExprStmt` | `expr` (always a call) |
-| `If` | `IfStmt` | `cond`, `then`, `otherwise` (null, `BlockStmt`, or nested `IfStmt` for `else if`) |
-| `While` | `WhileStmt` | `cond`, `body` |
-| `For` | `ForStmt` | `var`, `from`, `to`, `body` |
-| `Return` | `ReturnStmt` | `value` (nullable) |
-| `Break` / `Continue` | | |
+| StmtKind | Node |
+|---|---|
+| `Block` `VarDecl` `Assign` `Expr` `If` `While` `For` `ForEach` `Return` `Break` `Continue` `Defer` | as named; `ForEachStmt` has `var`, `list`, `body`; `DeferStmt` holds one statement |
-Calls store the callee **by name**, not as an expression. Finch has no first-class functions,
-and resolving names at codegen time lets one `CallExpr` mean a built-in (`print`, `addr`),
-a conversion (`u8`), a Finch function or a C function (section 8.10).
+`math.sqrt(x)` parses as a `MethodExpr` on `VarExpr("math")`; whether `math` is a module or a variable
+is decided during code generation. Calls name their callee instead of holding an expression: Finch has
+no first-class functions, and resolving the name late lets one `CallExpr` be a built-in, a conversion
+(`u8(x)`), a struct constructor, a Finch function or a C function.
-### Example
-
-```c
-x += add(x, 2)
-```
-
-parses to:
-
-```
-AssignStmt (op '+')
-├─ target: VarExpr "x"
-└─ value: CallExpr "add"
- ├─ VarExpr "x"
- └─ IntExpr 2
-```
-
-There is **no "load" node in the AST**. Whether a `VarExpr` means "the address of x" or "the
-value in x" depends on where it appears. Codegen decides that (`place()` vs `expr()`, section 8.2).
+There is no "load" node: whether `x` means its address or its value depends on context (`ref()` vs
+`expr()`, §10.2).
### `Type`
```cpp
struct Type {
- enum Kind { Void, Bool, Char, I8, I16, I32, I64, U8, U16, U32, U64, F32, F64, Str, Ptr, Null };
+ enum Kind { Void, Bool, Char, I8, I16, I32, I64, U8, U16, U32, U64, F32, F64,
+ Str, Ptr, Null, Array, Fixed, Struct, Named };
Kind kind;
- std::shared_ptr elem; // Ptr only: pointee; null = untyped `ptr`
+ std::shared_ptr elem; // Ptr (null = untyped `ptr`), Array, Fixed
+ long long count; // Fixed
+ std::string name, module; // Named (as written) / Struct (resolved)
+ StructInfo *info; // Struct
};
```
-- `int` and `i64` are **the same type** (`I64`), and `name()` prints it as `int`. Likewise `float` is `F64`.
-- `Null` is the type of the `null` literal only. No variable can have it.
-- `Void` is the "type" of calls that return nothing, and of functions without `->`.
-- Equality is structural (`ptr[ptr[int]] == ptr[ptr[int]]`).
-- Kinds are ordered so that `isInt()` is `I8..U64`, `isSigned()` is `I8..I64`, `isUnsigned()` is `U8..U64`.
+The parser produces `Named` for struct names; `Codegen::resolve()` turns them into `Struct` with an
+`info` pointer. `Fixed` (`[N]T`) only comes from C struct fields. `int` is `I64` and `float` is `F64`.
+Equality is structural; two `Struct`s are equal iff they share `info`.
---
-## 6. Semantic model
+## 6. Loading: files and modules
-All semantic checking happens inside `Codegen` while it emits IR.
+`Loader::load(path, module, from)` in `main.cpp` reads a file, appends it to `g_files` (positions carry
+the file index in `Pos::file`), lexes and parses it, then loads each `import name` that wasn't seen yet:
+`name.fn` next to the importing file, or in a `FINCH_PATH` folder. The result is a `std::vector`,
+main file first. Cycles are fine: a module is marked loaded before its own imports are followed.
-### Functions
+All files become **one LLVM module**. Each Finch module has a `ModuleScope` (functions, structs, the set
+of modules it imports). Names are looked up in the current module; another module's names are reachable
+only qualified (`geometry.length`, `geometry.Vec`) and only if imported there. Functions are emitted as
+`finch..` (`finch.` in the main file), struct types as `finch..`.
-`Codegen::run()` runs two passes over `Program::fns`:
+---
-1. `declare()` creates every `llvm::Function` (signature only), so **definition order does not matter**
- and mutual recursion works. It also checks: duplicate names, built-in names, and `main`'s
- signature (no parameters; returns nothing, `int` or `i32`).
-2. `define()` generates each body.
+## 7. Semantic model
-Without a `main`, compilation fails with `there is no main function`.
+### Declaration order
-### Scopes
+`Codegen::run()` first declares **all structs** of all modules plus all imported C structs, resolves
+every field type and sets the LLVM bodies (`declareStructs` → `resolveStruct`), then declares **all
+function signatures** (`declare`), then defines bodies. So order inside a file and across modules never
+matters, and recursion (also mutual, also across modules) works.
-`scopes` is a `std::vector>`. A new map is pushed for
-the function's parameters, for every `{ }` block, and for every `for` loop (which holds the
-loop variable).
+`resolveStruct` detects by-value cycles with a three-state flag (`struct A { A inner }` is an error with
+the advice to use `ptr[A]` or `[]A`). Pointers and arrays of a struct don't need its layout, so
+`struct Node { []Node kids; ptr[Node] parent }` is fine: they resolve the name "shallowly" (`resolveT(…, deep=false)`).
+
+### Scopes and variables
```cpp
-struct Var { AllocaInst *slot; FType type; bool readonly; };
+struct Var { Value *slot; FType type; bool readonly; std::string readonlyWhy;
+ bool owned; int order; bool moved; };
+struct Cleanup { bool isDefer; std::string var; const Stmt *body; int order; };
+struct Scope { unordered_map vars; vector cleanups; };
```
-**No shadowing:** `addVar()` rejects a name that `lookup()` finds in *any* enclosing scope,
-not just the current one. It also rejects names of functions and built-ins. This is a
-deliberate simplicity rule: a name means one thing in a whole function body.
+A scope is pushed for the function's parameters, each `{ }`, each `for` (holds `i`), each `for … in`
+(the hidden list temporary) and each iteration of it (holds `x`).
-**Read-only:** `for` variables are `readonly`. Assigning to them, or taking `addr()` of them,
-is an error, so the loop counter cannot be changed behind the loop's back.
+- **No shadowing:** `addVar()` rejects a name found in *any* enclosing scope, a function of the module,
+ an imported module name or a built-in.
+- **Read-only:** range and for-each variables (`readonlyWhy` gives the specific message).
+- `order` numbers declarations; `defer` uses it to hide variables declared after it (§9.5).
-### Name resolution for identifiers
+### Name resolution
-For a `VarExpr name` in value position, the order is:
-
-1. a local variable (`lookup`),
-2. a C constant (`cimports.consts`: enum values, `#define` numbers),
-3. a C global (`cimports.globals`: `stdout`, …),
-4. error `there is no variable named 'name'`.
-
-For calls, the order is: `print` → `addr` → type name (conversion) → Finch function → C function → error.
-Since Finch functions are checked before C functions, **a Finch function shadows a C function
-of the same name**. The Finch one is emitted as `finch.`, so both symbols coexist at link time.
+Value position `x`: local variable → C constant → C global → (module name: error with a hint) → error.
+Call `n(...)`: built-ins (`print addr input new free exit shell read_file write_file file_exists`) →
+type names (conversion) → function of the current module → struct of the current module / C struct
+(constructor) → C function → error (with a hint if the function exists in another module, or a
+`headerHint` for well-known libc names).
---
-## 7. Type system
+## 8. Type system
### LLVM representation
-| Finch | LLVM | Notes |
-|---|---|---|
-| `bool` | `i1` | |
-| `char`, `i8`, `u8` | `i8` | signedness lives in the Finch type, not in LLVM |
-| `i16`, `u16` | `i16` | |
-| `i32`, `u32` | `i32` | |
-| `int`/`i64`, `u64` | `i64` | |
-| `f32` | `float` | |
-| `float`/`f64` | `double` | |
-| `str` | `ptr` | NUL-terminated C string |
-| `ptr`, `ptr[T]` | `ptr` | LLVM 21 has opaque pointers; the pointee type exists only in the Finch type |
-| `null` | `ptr` | `ConstantPointerNull` |
-| nothing | `void` | |
+| Finch | LLVM |
+|---|---|
+| `bool` | `i1` |
+| `char` `i8` `u8` / `i16` `u16` / `i32` `u32` / `int` `u64` | `i8` / `i16` / `i32` / `i64` |
+| `f32` / `float` | `float` / `double` |
+| `str`, `[]T` | `{ ptr, i64, i64 }` (pointer, length, capacity) |
+| `ptr`, `ptr[T]`, `null` | `ptr` (opaque) |
+| `[N]T` | `[N x T]` |
+| Finch struct | named struct type, natural layout |
+| C struct | named **packed** struct with explicit padding bytes (§13.2) |
-LLVM integers are signless. Every operation where signedness matters (`sdiv`/`udiv`,
-`srem`/`urem`, `ashr`/`lshr`, `icmp slt`/`ult`, `sext`/`zext`, `sitofp`/`uitofp`,
-`fptosi`/`fptoui`) chooses the instruction from the **Finch** type.
+Signedness lives only in the Finch type and selects `sdiv/udiv`, `ashr/lshr`, `icmp s*/u*`,
+`sext/zext`, `sitofp/uitofp`, `fptosi/fptoui`.
-### Values in codegen
+### Values
```cpp
-struct Value_ {
- Value *v; // the IR value
- FType type; // its Finch type
- bool literal; // written as a number in the source (or an imported C constant)
-};
+struct Value_ { Value *v; FType type; bool literal; bool fresh; };
```
-### Literals ("untyped constants")
+- **`literal`**: a number written in the code or an imported C constant. It may become any integer type
+ it fits (`fits()`), or `f32`. It survives unary minus and literal-only arithmetic that folds to a
+ constant; explicit conversions clear it (`u8(250)` is a typed constant, so `u8(250) + 10` wraps to 4).
+- **`fresh`**: an owning value (str/array/owning struct) that nobody holds yet: the result of a call,
+ a literal, a concatenation, a constructor. It must be stored (moved) or released (§9).
-`literal` is set on `IntExpr`, `FloatExpr` and imported C constants. It is preserved through
-unary minus and through arithmetic **between two literals** that folds to a constant
-(`arith()` sets `literal = l.literal && r.literal && isa(result)`). It is **cleared by
-explicit conversions**: `u8(250)` is a typed `u8` constant, not a literal.
+### Implicit conversions: `coerce(v, want)`
-A literal integer may become **any integer type it fits in** (`fits()` checks the range,
-accounting for the source literal's signedness). This is what makes `u8 b = 200`,
-`malloc(4 * 10)` (to `u64`) and `glClear(GL_COLOR_BUFFER_BIT)` (an `i64` macro to a `u32` parameter)
-work without casts, while `u8 b = 300` is rejected with `the number 300 doesn't fit in u8`.
-A literal `f64` may become `f32` (`f32 h = 0.5`).
-
-Why not simply check `isa`? `IRBuilder` constant-folds, so `u8(250)` is also a
-`ConstantInt`. If "is a constant" were the rule, `u8(250) + 10` would quietly widen to
-`int` and give 260, while the same code with a `u8` variable would wrap to 4. The explicit
-flag makes the behavior depend on what you *wrote*, not on what the optimizer could see.
-
-### Implicit conversions: `coerce(value, want, pos, what)`
-
-Used wherever a value flows into a slot of known type: initializers, assignments, arguments,
-return values, conditions (`want = bool`), range bounds (`want = int`). Allowed, in order:
-
-1. identical types,
-2. `null` → any `ptr…` or `str`,
-3. any `ptr…` or `str` → untyped `ptr`, and untyped `ptr` → any `ptr[T]` (the `void*` rules; this is how `ptr[i32] n = malloc(4)` works),
-4. int → int when the value is a fitting literal, **or** `widens(from, to)`:
- - same signedness, strictly more bits, **or**
- - unsigned → signed with strictly more bits,
- - (signed → unsigned never widens),
-5. any int → any float (`sitofp`/`uitofp`),
-6. `f32` → `f64` (`fpext`),
-7. literal `f64` → `f32` (`fptrunc`, constant-folded).
-
-Anything else is an error. The message suggests the explicit conversion
-(`use i32(...) to convert`) and warns when it would truncate (`it cuts off the fraction`).
+Identical types; `null` → any pointer; typed pointer ↔ untyped `ptr`; integer → integer if the value is a
+fitting literal or the type `widens()` (same signedness and more bits, or unsigned → strictly bigger
+signed); integer → float; `f32` → `f64`; literal `f64` → `f32`. Nothing else: the error suggests the
+explicit conversion. Owning types convert only to themselves. Array literals take their element type
+from the slot they flow into (`exprWant()`), so `[]f32 xs = [1, 2.5]` works.
### Binary operands: `unify(l, r)`
-Before arithmetic or comparison, both sides are brought to one type:
+A literal side adapts to the other; otherwise the side that widens grows; `i32` + `u32` is an error;
+int + float → float; f32 + f64 → f64. There is no C integer promotion: `u8 + u8` stays `u8`.
-1. same type → done,
-2. both ints: a literal side adapts to the other side if it fits; otherwise the side that
- `widens()` into the other is extended; otherwise **error** (`i32` + `u32`: neither widens
- into the other, and Finch refuses to guess),
-3. int and float → the int converts to that float type,
-4. `f32` and `f64` → both `f64`.
+### Explicit conversions: `convert()`
-The result type of arithmetic is the unified type. **There is no C-style integer promotion:**
-`u8 + u8` is computed in `u8` and wraps. This is deliberate. It is what the sized type asks for,
-and it matches Rust and Zig.
-
-### Explicit conversions: `convert(call, to)`
-
-`T(x)` is parsed as a call named after a type and lowered here:
-
-| From → To | Instruction |
+| From → To | IR |
|---|---|
-| int/char → int/char | `CreateIntCast(signed = from.isSigned())` → `trunc` / `sext` / `zext` |
-| bool → int/char | `zext` |
-| float → int | `fptosi` / `fptoui` (by target signedness) |
-| int → float | `sitofp` / `uitofp` (by source signedness) |
-| float → float | `fpext` / `fptrunc` (`CreateFPCast`) |
-| int → bool | `icmp ne x, 0` |
-| ptr/str/null → ptr…, ptr → str | no instruction (all are `ptr`) |
+| int/char ↔ int/char | `trunc`/`sext`/`zext` by source signedness |
+| bool → int | `zext` |
+| float ↔ int | `fptosi`/`fptoui`, `sitofp`/`uitofp` |
+| float ↔ float | `fpext`/`fptrunc` |
+| int → bool | `icmp ne 0` |
+| number/bool/char → str | `finch_str_from_int/uint/float/char/bool` (fresh) |
+| str → int/float | `finch_str_to_int/float` (panics on bad text) |
+| `[]u8`/`[]char` → str | `finch_str_from_bytes` |
+| ptr → str | `finch_str_from_c` (borrowed, `cap = -1`) |
+| ptr ↔ ptr | nothing |
-`fptosi` of an out-of-range value is poison in LLVM (UB in C). Finch 1.0 does not check this. See section 15.
+### Comparisons
-### Comparisons: `compare()`
-
-- **Pointers:** if either side is a `ptr…` or `null`, only `==`/`!=` are allowed and they compile to
- `icmp eq/ne ptr`. A `str` compared with `null` also compares addresses.
-- **`str == str`:** a call to `strcmp`, then `icmp eq/ne i32 %r, 0` (content equality). `<`/`>` on `str` are rejected.
-- **Floats:** `oeq olt ole ogt oge` (ordered: false if NaN) and `une` for `!=` (unordered: `NaN != NaN` is true). This matches C.
-- **Integers / char:** `eq ne`, and `slt sle sgt sge` or `ult ule ugt uge` by signedness. `char` compares unsigned.
-- **bool:** only `==`/`!=`.
+Pointers/null: `icmp eq/ne ptr` only. `str`: `finch_str_eq` (length + `memcmp`) for `==`/`!=`,
+`finch_str_cmp` for ordering. Floats: `oeq olt ole ogt oge` and `une` for `!=`. Integers/char by
+signedness (char unsigned). Bool: equality only. Arrays/structs: error.
---
-## 8. Code generation
+## 9. Ownership and memory
-`Codegen` owns an `llvm::IRBuilder<> b`, the `Module`, the function table `fns`
-(`name → {Function*, const FnDecl*}`), `scopes`, the `loops` stack and `curFn`.
+### 9.1 The model
-### 8.1 Variables are stack slots
+Every value of an **owning type** (`str`, `[]T`, or a Finch struct with an owning field) has exactly one
+owner: a variable, an array element, a struct field, or a heap cell from `new()`. The owner drops the
+value when it goes away. There is no reference counting and no garbage collector; every decision is static.
-Every variable, **including parameters**, gets an `alloca` created by `slot()`.
-`slot()` always inserts at the **top of the entry block**, using a second `IRBuilder` positioned at
-`entry.begin()`, no matter where the declaration appears. Allocas in the entry block are what
-`mem2reg`/`SROA` can promote to SSA registers, so the O2 output contains no stack traffic for
-scalars. Because each new slot goes to the very beginning, slots appear in **reverse declaration
-order** in `-O0` IR (`%b2` before `%a1` below).
+| Situation | What happens |
+|---|---|
+| `x := expr` / `T x = expr` / `x = expr` / field, element, `push` | **`own(v)`**: a fresh value is moved in; a borrowed one is deep-copied |
+| `x = …` (owning) | the new value is computed first, the old one dropped after the store |
+| end of a block, `return`, `break`, `continue` | owned variables of every scope being left are dropped (newest first), defers run |
+| `return local` | the variable is **moved out** (marked `moved` while the cleanups for that return are emitted) |
+| passing an argument | **lent**: the callee gets a shallow copy and never drops it; fresh arguments are released by the caller after the call |
+| a parameter the function changes | copied on entry and owned by the callee (decided by `mutates()`, below) |
+| a temporary nobody keeps (`print(a + b)`, `f(g())`, an expression statement) | **`release(v)`**: dropped right after use |
+| `a.pop()`, `a.remove(i)` | the element is moved out (fresh) |
+| `for x in list` | `x` borrows each element; if the body may change `list`, each element is copied instead |
-Parameters are copied into their slots at function entry (`store %a, %a1`), which makes
-parameters ordinary mutable locals.
+A string literal is a fresh value with `cap = 0`: moving it costs nothing, dropping it is skipped
+(`release()` ignores constants), and writing into it (`s[i] = c`) first makes a heap copy
+(`finch_str_own`). Strings from C are borrowed with `cap = -1` and always copied before being kept.
-### 8.2 Loads and stores: `expr()` vs `place()`
+### 9.2 Parameter borrowing: `mutates()`
-This is the heart of how memory access is generated:
+When declaring a function, every owning parameter is checked with `mutates(name, body)`: an
+assignment whose root variable (`rootVar()`: through `.field` and `[i]`) is the parameter, a changing
+method on it (`push pop insert remove clear resize sort reverse`), or `addr(…)` of it. Unchanged
+parameters are borrowed (`Fn::borrowParam`); changed ones are copied once at entry. The analysis is
+syntactic and conservative: anything that *might* change the parameter makes a copy, which is always correct.
+The same analysis decides whether `for x in list` must copy elements.
-- `place(e)` returns a **`Place {Value *addr; FType type}`**, the *address* of an l-value.
- - `VarExpr` → the variable's `alloca` (or the C global's `GlobalVariable`).
- - `MemberExpr` `p.value` → `expr(p)` (which **loads the pointer** from p's slot), plus a null check
- (section 9). The address is the loaded pointer itself, and the type is `*p.type.elem`.
-- `expr(e)` returns a **`Value_`**, an r-value. For `Var` and `Member` it calls `place()` and then
- emits **one `load`** of `ty(place.type)` from `place.addr`.
+### 9.3 Copy and drop
-So the `load` for an identifier is created in `Codegen::expr()`, case `ExprKind::Var`, as
-`b.CreateLoad(ty(pl.type), pl.addr, v.name)`. Naming it after the variable is why `-O0` IR shows
-`%x1`, `%x2` and so on (LLVM uniquifies repeated names). `p.value` costs **two** loads: one for the
-pointer, one for the pointee. `pp.value.value` costs three.
+`dropAt(addr, T)` / `copyAt(dst, src, T)` work on addresses:
-Stores are generated in exactly three places: `addVar()` (initialization), `assign()`, and the `for` step.
+- `str`: `finch_str_drop` (frees if `cap > 0`) / `finch_str_copy` (shares literals, duplicates the rest).
+- `[]T` and owning structs: generated helpers `finch.drop.` and `finch.copy.`, created once per
+ type (`dropFn`, `copyFn`, keyed by `typeKey()`, e.g. `arr_S_main_Player`). Arrays drop each owning
+ element and then `finch_arr_free`; copies call `finch_arr_clone_raw` and then deep-copy owning
+ elements over the raw bits. Structs drop/copy only their owning fields.
-### 8.3 Assignment and evaluation order
+Helpers are emitted in their own functions (`HelperScope` saves the builder position and switches off
+debug locations), may call each other recursively (`struct Node { []Node kids }`), and are inlined by O2.
+
+### 9.4 What it looks like
```c
-x += add(x, 2)
-```
+fn greet(str name) -> str {
+ msg := "Hi, " + name
+ return msg
+}
-`assign()` computes `place(target)` **first**. For compound operators it then **loads the current
-value**, then evaluates the right-hand side, then calls `arith()`, then `coerce()`s back to the
-target type, then stores. Real `-O0` output:
-
-```llvm
-define i32 @main() {
-entry:
- %x = alloca i64, align 8
- store i64 5, ptr %x, align 8 ; x := 5
- %0 = load i64, ptr %x, align 8 ; current x (for +=)
- %x1 = load i64, ptr %x, align 8 ; argument x
- %1 = call i64 @finch.add(i64 %x1, i64 2)
- %2 = add i64 %0, %1
- store i64 %2, ptr %x, align 8
- %x2 = load i64, ptr %x, align 8
- %3 = call i32 (ptr, ...) @printf(ptr @fmt, i64 %x2)
- ret i32 0
+fn main() {
+ a := ["x"]
+ b := a
+ b.push(greet("Ola"))
+ print(b[1])
}
```
-Order of evaluation in Finch is **always left to right**: the target, then the operands of a
-binary operator left to right, then arguments left to right. C leaves most of this unspecified.
-Note that for `x += f()` the old value of `x` is read **before** `f()` runs. If `f` modified `x`
-through a pointer, the modification would be overwritten. That is the same as `x = x + f()`
-read left to right.
-
-After O2 the whole program above is constant-folded into `printf(@fmt, i64 12)`.
-
-### 8.4 Functions, linkage and `main`
+The `-O0` IR of `main` (abridged):
```llvm
-define internal i64 @finch.add(i64 %a, i64 %b) {
-entry:
- %b2 = alloca i64, align 8
- %a1 = alloca i64, align 8
- store i64 %a, ptr %a1, align 8
- store i64 %b, ptr %b2, align 8
- %a3 = load i64, ptr %a1, align 8
- %b4 = load i64, ptr %b2, align 8
- %0 = add i64 %a3, %b4
- ret i64 %0
-}
+ %3 = call ptr @finch_alloc(i64 24) ; ["x"]: one element on the heap
+ store { ptr, i64, i64 } { ptr @str.1, i64 1, i64 0 }, ptr %4 ; the literal, cap 0
+ ...
+ store { ptr, i64, i64 } %7, ptr %a ; moved into a (fresh)
+ %a1 = load { ptr, i64, i64 }, ptr %a
+ store { ptr, i64, i64 } %a1, ptr %1
+ call void @finch.copy.arr_str(ptr %2, ptr %1) ; b := a copies
+ %8 = load { ptr, i64, i64 }, ptr %2
+ store { ptr, i64, i64 } %8, ptr %b
+ %9 = call { ptr, i64, i64 } @finch.greet({ ptr, i64, i64 } { ptr @str.2, i64 3, i64 0 })
+ ...
+ call void @finch_arr_reserve(ptr %b, i64 %12, i64 24) ; push: grow if needed
+ store { ptr, i64, i64 } %9, ptr %15 ; greet's result moved in, no copy
+ ...
+ %20 = icmp uge i64 1, %19 ; b[1]: bounds check
+ br i1 %20, label %out.of.range, label %in.range
+ ...
+ call void @finch.drop.arr_str(ptr %b) ; end of main: newest first
+ call void @finch.drop.arr_str(ptr %a)
+ ret void
```
-- User functions are named **`finch.`** with **internal linkage**. The dot cannot appear in a C
- identifier, so a Finch function can never collide with a libc or library symbol, and internal
- linkage lets LLVM inline, specialise or delete them freely. In C, a non-`static` function is
- external and must be kept as written. This likely contributes to recursive `fib(40)` running
- slightly faster than the same C code at `clang -O2` (about 0.19 s vs 0.23 s on the author's machine).
-- `main` is emitted as **`i32 @main()` with external linkage**. A `fn main()` returns `i32 0` on every
- exit path, including an explicit `return`. A `fn main() -> int` truncates its `i64` result with
- `CreateIntCast(…, i32, signed)`.
-- Missing return: after the body, if the current block has no terminator, `void` functions get
- `ret void` (or `ret i32 0` for `main`). Non-void functions are a compile error:
- `function 'f' can reach its end without returning int`. This is checked on the CFG that was
- actually built (section 8.5), so `if/else` that returns on both branches is accepted without any
- extra analysis.
-- Arithmetic is emitted **without `nsw`/`nuw` flags**, so integer overflow is defined two's-complement
- wraparound (section 15).
+and `greet` returns `msg` without copying (`ret … %5` after loading the slot: the variable was moved out).
-### 8.5 Control flow and the "terminated" state
+### 9.5 defer
-The builder's insert block is the single source of truth for reachability:
+`defer stmt` pushes a `Cleanup{isDefer, body, order = current varOrder}` into the current scope; it
+emits nothing there. Whenever the scope's cleanups are emitted (falling off the end of the block,
+`return`, `break`/`continue` leaving it) the body is generated **at that exit**, with `deferLimit = order`
+so that `lookup()` hides variables declared after the defer: those are already dropped by then
+(cleanups run in reverse). `return`/`break`/`continue` inside a deferred statement are errors (`inDefer`).
+Defer bodies are therefore duplicated per exit path, which is what makes them free on the normal path.
+
+### 9.6 Manual memory
+
+`new(v)` allocates `sizeof(T)` with `finch_alloc`, stores `own(v)` and returns `ptr[T]`. `free(p)` drops
+the pointee if `T` is owning (skipping null) and calls libc `free`. Pointers never own anything:
+`addr(x)` can dangle after `x`'s block ends; this is not checked.
+
+### 9.7 Verifying it
+
+`MEMCHECK=1 tests/run.sh` builds every test program and runs it under valgrind with
+`--leak-check=full --errors-for-leak-kinds=all`: zero leaks and zero invalid accesses across all of them,
+including returns from inside loops, `continue`/`break` with owned locals, defers, mutation during
+iteration, nested arrays of structs, and C struct passing.
+
+---
+
+## 10. Code generation
+
+### 10.1 Variables
+
+Every variable, including parameters, gets an `alloca` at the top of the entry block (`slot()`;
+C structs get their C alignment). `addVar()` stores the initial value, registers the variable, adds a
+drop cleanup if it owns its value, and (with `-g`) declares it to the debugger.
+
+### 10.2 Places: `ref()`, `place()`, `expr()`
```cpp
-bool terminated() { return b.GetInsertBlock()->getTerminator() != nullptr; }
+struct LRef { bool isPlace; Place pl; Value_ val; }; // an address, or (if none) a value
```
-After `return`, `break` or `continue`, the current block has a terminator.
-`blockBody()` checks `terminated()` **before each statement**. If it is set, the statement is
-unreachable and that is a compile error (`this code can never run`). No dead blocks are ever created
-for code after a jump.
+`ref(e, forWrite)` walks a `Var`/`Member`/`Index` chain and returns an **address** when one exists:
-`continueAt(bb, deadEnd)` is used after `if` and `while`. If the merge block `bb` has **no
-predecessors** (all paths returned or broke), it is erased and the builder is left in `deadEnd`,
-which is terminated. So the "terminated" state propagates outward through nested constructs, and
-the missing-return check sees it.
+- `Var` → its `alloca` (or a C global).
+- `.field` on a struct place → `getelementptr` to the field (`llvmIndex`; C structs skip padding elements).
+- through a pointer (`p.value`, `p.field`, `p[i]`) → load the pointer, check it isn't null, address the target.
+ `p.value` on a pointer to a struct that has its own `value` field means that field.
+- `a[i]` on an array/str place → load `{ptr,len,cap}`, `boundsCheck(i, len)`, `getelementptr`. For
+ `forWrite` on a `str`, `finch_str_own` first (copy-on-write for literals). `[N]T` checks against `N`.
+- `.len` / `.ptr` → values.
+- a temporary (call result…) has no address: its field/element is extracted, copied if owning, and the
+ temporary released.
-**`if / else if / else`:** blocks `then`, `else` (if present), and `endif`. `else if` is an
-`IfStmt` in `otherwise`, generated recursively inside the `else` block.
+`expr()` on these kinds calls `ref()` and loads from the address: this is where every `load` of a
+variable, field or element comes from. `place()` demands an address (assignment targets, `addr()`).
-**`while`:** blocks `while.cond`, `while.body`, `while.end`. If the condition folds to the
-constant `true`, an unconditional `br` is emitted, so `while.end` has predecessors only through
-`break`. A `while true` without `break` therefore leaves the function "terminated", and a function
-whose last statement is such a loop does not need a trailing `return`.
+### 10.3 Assignment
-**`for i in a..b`:** `a` and `b` are evaluated **once**, before the loop, and coerced to `int`.
-Blocks: `for.cond` (`icmp slt i, end`), `for.body`, `for.step` (`i + 1`), `for.end`. `continue`
-jumps to `for.step`, not `for.cond`, so the increment is never skipped. The loop variable lives in
-its own scope around the loop.
+`x = v`: evaluate `v` **first** (it may reallocate memory the target lives in: `a[0] = f()` where `f`
+pushes to `a`), then `place(target, forWrite)`, `coerce`, `own`, load the old value, store, drop the old
+one. Compound `x += v`: evaluate `v`, load the target, `arith()`, store, drop the old value (for `str +=`).
-```llvm
- store i64 0, ptr %i, align 8
- br label %for.cond
-for.cond:
- %3 = load i64, ptr %i, align 8
- %4 = icmp slt i64 %3, 2
- br i1 %4, label %for.body, label %for.end
-for.body:
- ...
- br label %for.step
-for.step:
- %6 = load i64, ptr %i, align 8
- %7 = add i64 %6, 1
- store i64 %7, ptr %i, align 8
- br label %for.cond
-for.end:
-```
+### 10.4 Functions and `main`
-**`break` / `continue`:** `loops` is a stack of `{continueTo, breakTo}`. `while` pushes
-`{while.cond, while.end}` and `for` pushes `{for.step, for.end}`. Outside a loop it is an error.
+Functions are `internal` with their natural LLVM signature (aggregates by value). Owning parameters are
+copied at entry only if `borrowParam` is false. `fn main` is the internal `finch.main`; a separate
+external `i32 main(i32, ptr)` (`defineMainWrapper`) builds `[]str args` with `finch_args` if requested,
+calls it, drops the arguments and returns the exit code. A function that falls off its end gets the
+scope cleanups and `ret void`, or the error `can reach its end without returning T`.
-### 8.6 Short-circuit `&&` / `||`: the only hand-built φ
+### 10.5 Control flow
+
+The builder's current block is the source of truth for reachability (`terminated()`): statements after
+a terminator are errors (`this code can never run`). `continueAt()` deletes a merge block nobody jumps
+to. `if`/`while`/`for` are the classic block layouts; `while true` without `break` leaves the function
+terminated (no `return` needed after it). `for i in a..b` evaluates both ends once; `continue` goes to
+`for.step`. `for x in list` reads the list's length from its address **every round** (pushing inside the
+loop is safe) and keeps the index in a stack slot. `break`/`continue` emit the cleanups of the scopes
+opened inside the loop (`Loop::scopeDepth`) before jumping.
+
+### 10.6 Short-circuit `&&` / `||`
+
+The only hand-built φ: the left side ends in block `from`, the right side in `rhsEnd`;
+`phi i1 [!isAnd, from], [rhs, rhsEnd]`.
+
+### 10.7 Calls
+
+`callFinch()` coerces arguments to parameter types (array literals via `exprWant`), passes owning ones
+shallowly, calls, then releases fresh arguments. Results of owning type are fresh.
+`construct()` builds a struct from `zeroinitializer` with `insertvalue`: positional (all fields) or named
+(missing ones take the field's default expression, evaluated in the struct's module with the caller's
+scopes swapped out, or zero). C calls are in §13.
+
+### 10.8 Printing
+
+`print` emits `printf` per argument with a format chosen by type (`%lld`, `%llu`, `%g`, `%c`, `%.*s` for
+`str` with its length, `%p`), separated by spaces, then `\n`. Arrays, fixed arrays and structs call
+generated helpers `finch.print.` that print `[1, 2]` / `Point(x: 1, y: 2)`, quoting strings and chars inside.
+
+### 10.9 Array and str methods
+
+Arrays: `push` (`finch_arr_reserve` + store + len++), `pop`/`remove` (move out, `remove_gap`),
+`insert` (`insert_gap`), `clear`/`resize` (drop the elements that go away, `finch_arr_resize`),
+`find`/`contains` (a loop with `compare(Eq)`), `slice` (new array, `copyAt` per element), `reverse`
+(swap loop), `sort` (libc `qsort` with a generated comparator `finch.cmp.`), `join` (runtime).
+Methods that change the array require an address (a variable/field/element).
+Strings: `sub find contains starts_with ends_with split trim upper lower replace repeat bytes`, all in the runtime.
+
+---
+
+## 11. Runtime checks
+
+All runtime errors go through `finch_panic(file, line, msg)` / `finch_panic_index(file, line, i, len)`
+(declared `noreturn cold`), so the failing branch is laid out cold and the check costs a compare and a
+well-predicted branch. LLVM removes checks it can prove (constant indexes in range, loop-hoisted bounds).
+
+| Check | Where |
+|---|---|
+| division/remainder by zero (constant zero: compile error) | `checkDivisor()` |
+| `MIN / -1` for signed types | `checkDivisor()` |
+| array/str/fixed-array index out of range (`icmp uge`, so negative indexes too) | `boundsCheck()` |
+| `.value`, `.field`, `[i]` through null | `member()`, `index()` |
+| `pop()` on empty, `slice()`/`sub()` out of range | methods, runtime |
+| bad text in `int(s)`/`float(s)`, unreadable file in `read_file` | runtime |
+
+A 20-million-element sieve with a bounds check on every access runs as fast as the same C (≈0.09 s):
+the checks are hoisted or folded.
+
+---
+
+## 12. The C runtime
+
+`runtime/finch_rt.c` is plain C with no global state. CMake embeds it into the compiler as a raw string
+(`rt_source.inc`); on first use `runtimeObject()` compiles it with `$CC -O2 -fPIC -c` into
+`~/.cache/finch/rt-.o`, where the hash covers the source, the Finch version and the C compiler.
+It is linked statically into every program.
+
+The shared layout:
```c
-fn check(int a, int b) -> bool {
- return a > 0 && b > 0
-}
+typedef struct { char *ptr; int64_t len; int64_t cap; } FStr; // ptr always NUL-terminated
+typedef struct { void *ptr; int64_t len; int64_t cap; } FArr;
+// str cap: > 0 heap (owned), 0 literal (static), -1 borrowed from C (copy before keeping)
```
-```llvm
- %a3 = load i64, ptr %a1, align 8
- %0 = icmp sgt i64 %a3, 0
- br i1 %0, label %and.rhs, label %and.end
-and.rhs:
- %b4 = load i64, ptr %b2, align 8
- %1 = icmp sgt i64 %b4, 0
- br label %and.end
-and.end:
- %2 = phi i1 [ false, %entry ], [ %1, %and.rhs ]
-```
-
-`logic()` records the block where the left side **ended** (`from`) and the block where the right
-side **ended** (`rhsEnd`). These are not necessarily the blocks where evaluation started, because a
-nested `&&` creates its own blocks. The φ uses `!isAnd` as the short-circuit constant (`false` for
-`&&`, `true` for `||`).
-
-### 8.7 `print`
-
-`print(a, b, …)` compiles to **one `printf` call**. The format string is built at compile time from
-the argument types, joined with spaces and ending in `\n`:
-
-| Finch type | Format | Argument conversion |
-|---|---|---|
-| signed ints | `%lld` | `sext` → `i64` |
-| unsigned ints | `%llu` | `zext` → `i64` |
-| `f32`, `f64` | `%g` | `fpext` → `double` |
-| `char` | `%c` | `zext` → `i32` |
-| `str` | `%s` | as is |
-| `bool` | `%s` | `select i1, "true", "false"` |
-| `ptr…` | `%p` | as is (glibc prints `(nil)` for null) |
-| `null` | literal text `null` | none |
-
-Since the format is generated, user strings are always passed as `%s` arguments, never as the
-format, so a `%` in user text is safe. O2 often rewrites `printf("%s\n", s)` into `puts(s)`.
-
-### 8.8 Strings
-
-A string literal is a private `unnamed_addr` constant global (`CreateGlobalString`), and its value is
-the `ptr` to it. Identical literals may be merged by the optimizer. A `str` variable declared
-without an initializer points to a shared `""` constant. Strings are **immutable and static** in
-1.0. There is no concatenation or allocation (section 18).
-
-### 8.9 `addr()` and pointers
-
-`addrOf()` requires a `Var` or `Member` argument and returns `place(arg).addr` with type
-`ptr[T]`. No load happens: `addr(x)` *is* x's alloca. The `alloca` is therefore not promotable
-by mem2reg, which is the expected cost of taking an address. `addr(p.value)` yields the pointer
-already stored in `p`, after a null check. `addr` of a `for` variable is rejected (it is read-only).
-
-### 8.10 Calls: `callFinch()` vs `callC()`
-
-Both check the argument count and `coerce()` each argument to the parameter type. The differences:
-
-| | `callFinch()` | `callC()` |
-|---|---|---|
-| Callee | `finch.`, created in `declare()` | `getOrInsertFunction(name, …)`: declared lazily on first use, reused afterwards |
-| Variadic | never | supported; extra args get C default promotions |
-| Parameter attributes | none needed | `signext`/`zeroext` on params and return values narrower than 32 bits, on both the declaration and the call site |
-| Unsupported signatures | impossible | rejected with the reason saved by cimport (`passes a C struct by value`, …) |
-| Calling `main` | rejected | n/a |
-
-Default argument promotions for the variadic part: `f32` → `double`; `bool`, `char` and integers
-narrower than 32 bits → `i32` (sign- or zero-extended). Pointers, `str` and 32/64-bit integers
-pass unchanged. This mirrors C's rules, which is what `printf`'s `va_arg` expects.
-
-Why `signext`/`zeroext`: on x86-64 System V, clang passes `char`/`short`/`_Bool` already extended to
-32 bits and marks them so. Some callees rely on it. Without the attribute LLVM would leave the upper
-bits undefined, which would be an ABI mismatch with C code compiled by clang.
+Functions take and return these **by pointer**, so their C ABI is trivial (no struct classification):
+string building (`concat`, `from_int/uint/float/char/bool`, `sub`, `trim`, `upper`, `lower`, `replace`,
+`split`, `join`, `repeat`, `from_bytes`), comparison (`eq`, `cmp`, `find`, `starts`, `ends`), ownership
+(`copy`, `own`, `drop`, `from_c`), arrays (`reserve`, `make`, `resize`, `clone_raw`, `free`,
+`insert_gap`, `remove_gap`), `args`, `input`, files, `shell`, and the panics. Allocation failure prints
+`out of memory` and exits.
---
-## 9. Runtime checks
+## 13. C interop: headers, structs, ABI
-Finch removes the two most common sources of C undefined behavior that can be checked cheaply.
-All checks share one helper function per module:
+### 13.1 Importing headers
-```llvm
-; Function Attrs: cold noinline noreturn
-define internal void @finch.panic(ptr %0, i64 %1) #0 {
-entry:
- %2 = call i32 (i32, ptr, ...) @dprintf(i32 2, ptr @panic.fmt, ptr @panic.file, i64 %1, ptr %0)
- call void @exit(i32 1)
- unreachable
-}
-```
+`importHeaders(imports, dirs)` parses each `import "x.h"` as its own translation unit from an in-memory
+`#include "x.h"`, with `-x c -std=gnu11`, `-I` for every `.fn` file's folder, and `-isystem` for each
+directory the system C compiler searches (read from `$CC -E -v -x c /dev/null`, which is what makes it work
+on NixOS). `gnu11` because glibc hides `M_PI` and friends in strict ISO mode.
-`panicFn()` creates it on first use. `panicIf(cond, msg, pos)` emits
-`br cond, %panic, %ok`, a call to `finch.panic(msg, line)` and `unreachable` in `%panic`, and
-continues in `%ok`. It writes to fd 2 with `dprintf`, so it does not depend on libc's `stderr`
-symbol. The function is `cold` and `noinline`, so branch layout favours the non-failing path and the
-check costs one compare and a predicted branch.
+The `Importer` visits top-level cursors:
-### Integer division and remainder: `checkDivisor()`
-
-```llvm
-define internal i64 @finch.div(i64 %a, i64 %b) {
- ...
- %0 = icmp eq i64 %b4, 0
- br i1 %0, label %panic, label %ok
-panic:
- call void @finch.panic(ptr @panic.msg, i64 2)
- unreachable
-ok:
- %1 = icmp eq i64 %b4, -1
- %2 = icmp eq i64 %a3, -9223372036854775808
- %3 = and i1 %2, %1
- br i1 %3, label %panic5, label %ok6
-panic5:
- call void @finch.panic(ptr @panic.msg.1, i64 2)
- unreachable
-ok6:
- %4 = sdiv i64 %a3, %b4
-```
-
-- A divisor that is a **constant zero** is a **compile-time** error.
-- A constant non-zero divisor (other than −1 for signed types) emits no check at all.
-- For signed division, `MIN / -1` overflows (it traps with SIGFPE on x86). That is checked too.
-- When everything is known, O2 removes the checks entirely: `div(10, 2)` above becomes `5` in `main`.
-
-### Null `.value`: in `place()`
-
-Every `p.value` whose pointer is not trivially non-null (not directly an `alloca` or a global)
-gets `panicIf(icmp eq p, null, "used .value on a null pointer")`. Without this, LLVM treats a load
-from null as UB and may **delete it**. Before this check existed, `print(p.value)` on a null `p`
-printed a garbage value at O2 instead of crashing.
-
----
-
-## 10. C interop via libclang
-
-`importHeaders(imports, dir)` (`src/cimport.cpp`) runs **before** codegen and produces:
-
-```cpp
-struct CImports {
- std::unordered_map fns; // name, header, ret, params, variadic, unsupported reason
- std::unordered_map consts; // enum values + numeric #defines
- std::unordered_map globals; // extern variables
-};
-```
-
-### Parsing
-
-For **each** `import "h"`, a separate translation unit is parsed from an in-memory file
-`finch_import.c` containing `#include "h"`. Separate units give precise error attribution: a missing
-header points at its own `import` line. Flags:
-
-```
--x c -std=gnu11 -I -isystem
-CXTranslationUnit_DetailedPreprocessingRecord (to see macros)
-CXTranslationUnit_SkipFunctionBodies (speed: inline bodies are irrelevant)
-```
-
-- **System include directories** come from running `$CC -E -v -x c /dev/null` and parsing the block
- between `#include <...> search starts here:` and `End of search list.`. This is what makes
- `stdio.h` resolve the same way it does for the system compiler. That matters on NixOS, where the
- cc wrapper injects store paths that an unwrapped libclang knows nothing about.
-- **`gnu11`** rather than `c11`: glibc hides `M_PI` and other non-ISO names under strict modes.
-- **Diagnostics** of severity ≥ error abort the import. `file not found` becomes
- `can't find the C header 'h'`.
-
-### The visitor
-
-`visit()` walks top-level cursors. The first declaration of a name wins.
-
-| Cursor | Action |
+| Cursor | Result |
|---|---|
-| `FunctionDecl` | Map the canonical **function type** (`clang_getArgType`, so array parameters are already decayed to pointers). `static` functions are recorded as unsupported (`static inline` has no symbol to link). `FunctionNoProto` (K&R `f()`) is unsupported. Variadic-ness comes from `clang_isFunctionTypeVariadic`. |
-| `VarDecl` with `extern` storage | → `CGlobal` if the type maps |
-| `EnumDecl` | recurse into children |
-| `EnumConstantDecl` | → `CConst` (`i64`, literal) |
-| `MacroDefinition` | skip built-ins and function-like macros; `macroValue()` tries to read a number |
+| `FunctionDecl` | `CFunc` (signature mapped in **signature context**; `static` and K&R functions recorded as unsupported) |
+| `TypedefDecl` of a record | names the record (`typedef struct {…} CXCursor` → `CXCursor`) |
+| `StructDecl` (definition) | names the record by its tag |
+| `VarDecl` `extern` | `CGlobal` (mapped in **data context**) |
+| `EnumConstantDecl`, numeric `MacroDefinition` | `CConst` (literal) |
-### Type mapping: `mapType()`
+`mapType()` works on canonical types and maps integers by size. The context decides `char *`: a `str`
+in function signatures (Finch converts at the call), but `ptr[char]` inside structs, globals and behind
+pointers (where the memory layout must stay a single pointer). Records by value become
+`Named{name, "C"}` and are defined with `defineRecord()`: every field (`clang_Type_visitFields`) with its
+byte offset, size and alignment, nested records, constant arrays (`Fixed`). Unions and bit fields are kept
+as hidden bytes and mark the struct unsupported for by-value calls; pointers to anything else (void,
+functions, incomplete structs) become `ptr`.
-Works on the **canonical** type, so typedefs (`size_t`, `uint32_t`, `GLuint`, `FILE`) are resolved.
-Integer types map **by size** (`clang_Type_getSizeOf`), not by name, so `long` becomes `i64` on LP64.
+### 13.2 Laying out C structs
-| C (canonical) | Finch |
+`resolveStruct()` builds a **packed** LLVM struct that mirrors C byte for byte: an `[n x i8]` pad before
+each field whose offset is ahead of the current position, the field itself (only if its LLVM size matches
+C's), and tail padding up to C's `sizeof`. Field accesses use the recorded `llvmIndex`. Packing makes the
+layout exact regardless of LLVM's alignment rules; allocas and temporaries get the C alignment explicitly.
+
+### 13.3 The System V x86-64 calling convention
+
+`classify(T)` (in `abi.cpp`) flattens a struct into scalar leaves with byte offsets (through nested
+structs and fixed arrays) and:
+
+- **MEMORY** if it is larger than 16 bytes or has a misaligned leaf: passed as a pointer with
+ `byval(T) align ≥8`, returned through a hidden first `sret(T)` pointer.
+- otherwise each eightbyte is **INTEGER** if any leaf in it is an integer/pointer, else **SSE**. Parts:
+ INTEGER → `iN` for the bytes that are left (`i32` for a 4-byte `Color`), SSE → `double`, `<2 x float>`
+ or `float`. One part is passed as is; two parts as two arguments, or returned as `{a, b}`.
+
+`makePlan()` walks the parameters counting the 6 integer and 8 SSE argument registers; a struct whose
+parts don't fit any more goes to the stack (MEMORY) as a whole, like clang does. Small integer
+parameters get `signext`/`zeroext`. `emitCCall()` stores each struct argument into a slot big enough for
+both the struct and its register form, loads the parts at offsets 0 and 8, calls with the planned function
+type and attributes, and rebuilds struct results the same way.
+
+`cThunk(fn)` is the reverse for `addr(fn)`: an internal function with the C signature that reassembles
+struct parameters from registers or `byval` memory, calls the Finch function and lowers its result. Only
+C-compatible signatures are accepted.
+
+`tests/run/c_structs.fn` exercises `{float,float}`, `{float,float,float}`, `{u8×4}`, `{double,int}`,
+`{i64,i64}`, a 24-byte struct, a struct with a `char[8]`, nested structs, register exhaustion with 13
+struct arguments, and callbacks taking and returning structs, against a C library compiled with
+`link "abi.c"`. The results match the same calls made from C.
+
+### 13.4 Linking C code
+
+`link "name"` tries `pkg-config --libs name`, then `-lname`. `link "file.c"` compiles the file (relative
+to the `.fn` file) with `$CC -O2 -fPIC -c` into a temporary object; `.o` and `.a` files are passed
+through. When linking fails, `linkFailed()` maps undefined symbols back to the header that declared them
+and prints the `link` line to add.
+
+---
+
+## 14. Optimization, emission, linking
+
+- **Target:** `sys::getDefaultTargetTriple()`, CPU `generic`, `Reloc::PIC_`. The data layout is set on the
+ module before any IR is built (alignment of loads/stores depends on it).
+- **Optimization:** `PassBuilder::buildPerModuleDefaultPipeline(O2)`: clang's `-O2` middle end. `-O0`
+ skips it (`finch ir file.fn -O0` shows `Codegen`'s raw output).
+- **Emission:** legacy `PassManager` + `addPassesToEmitFile(ObjectFile)`.
+- **Linking:** `$CC prog.o rt.o [user objects] -lm [libs]`. On Nix the cc wrapper turns `-L` paths into
+ `RPATH`, so programs run without `LD_LIBRARY_PATH`.
+- **`run`:** a temporary executable, run with the remaining command-line arguments, deleted afterwards;
+ a signal is reported as `the program crashed: ` with exit status `128 + n`.
+
+Performance notes: user functions are `internal`, so LLVM inlines and specializes them freely
+(recursive `fib(40)`: ≈0.19 s vs. ≈0.23 s for `clang -O2` on the same C on the author's machine);
+bounds-checked array code matches C on a sieve.
+
+---
+
+## 15. Debug info
+
+With `-g`, `Codegen` creates a `DIBuilder`, a compile unit (`DW_LANG_C`, so gdb's C expression syntax
+works for `print p.x`), a `DIFile` per source file, a `DISubprogram` per function, parameter variables
+(`createParameterVariable`, so `bt` shows `length (v=…)`) and local variables (`insertDeclare` on their
+allocas), and a `DILocation` per statement and call (`setLoc`). Types: basic types with Finch names
+(`int`, `u8`, `float`), `str`/arrays as `{ptr, len, cap}` structs, pointers, fixed arrays, and structs with
+member offsets from the LLVM `StructLayout`. Recursive structs use a replaceable forward declaration.
+Generated helpers carry no locations. Use `-g -O0` for the best experience; with O2 some variables are optimized out.
+
+---
+
+## 16. Diagnostics
+
+`failAt(file, line, col, msg)` prints `path:line:col: error: msg` and the source line with a caret
+(tabs preserved, columns counted in characters), then exits with 1. Conventions: binary operators report
+the operator, calls the callee, argument errors the argument. Messages say what is wrong and, when
+there's an obvious fix, how to fix it. There are no warnings. An LLVM verifier failure prints
+`internal compiler error (please report)` and exits with 2.
+
+---
+
+## 17. The self-hosted compiler (`boot/`)
+
+`boot/` is a second Finch compiler written in Finch (≈3,100 lines):
+
+| File | Contents |
|---|---|
-| `void` | nothing |
-| `_Bool` | `bool` |
-| `char` (either signedness) | `char` |
-| `signed char`, `short`, `int`, `long`, `long long`, `enum` | `i8`/`i16`/`i32`/`i64` by size |
-| unsigned variants | `u8`…`u64` by size |
-| `float` / `double` | `f32` / `f64` |
-| `char *` (any qualifiers) | `str` |
-| `T *` where T maps to a non-void type | `ptr[T]` |
-| `void *`, pointer to struct/union/function/unmappable | `ptr` (opaque) |
-| struct/union by value | **unsupported** ("passes a C struct by value") |
-| `long double`, `__int128`, vectors, … | **unsupported** |
+| `boot/lexer.fn` | the lexer (tokens are `{kind, text, line, col, nl}`; kinds are words, keywords and operators are their own kind) |
+| `boot/ast.fn` | `Type` (`kind`, `elem` as a 0/1-element array, `name`, `module`), one generic `Node` for everything, `Program` |
+| `boot/parser.fn` | the same grammar and newline rules as the C++ parser, precedence climbing for binary operators |
+| `boot/gen.fn` | type checker + **textual LLVM IR** generator: scopes, places, coercions, copies, helpers, printing, runtime checks, array/str methods |
+| `boot/main.fn` | loader for modules and the driver: writes `.ll`, runs `clang -O2 file.ll runtime/finch_rt.c` |
-Unsupported functions stay in the table, so calling one produces a precise error instead of
-"no such function".
+It supports the core language: `int float bool char str`, `[]T`, structs (defaults, named/positional
+constructors), `ptr[T]`/`addr`/`new`/`free`/`null`, all statements except `defer`, modules, and the
+built-ins and methods the C++ compiler has, minus C imports, sized integers and `defer`.
+It implements the same value semantics and borrow analysis (copies where the main compiler copies) but
+**never frees**: as a bootstrap compiler it leaves memory to the operating system.
-### Macros: `macroValue()`
+The compiler state is one `Gen` struct passed as `ptr[Gen]`: Finch has no globals, and passing a
+pointer is how a function changes its caller's value. Without `defer` or a map type, scopes are arrays of
+arrays searched linearly; helper functions (copy/print/compare per type) are generated by temporarily
+swapping the output buffers (`beginHelper`/`endHelper`).
-It tokenizes the macro's extent, drops the name token and comments, strips **balanced outer
-parentheses**, accepts one leading `-`, and then requires **exactly one** numeric literal. Integers
-are parsed with `strtoull(…, 0)`, i.e. **C rules**: `0x` hex and leading-zero **octal**, unlike
-Finch's own literals. Floats are anything containing `.`, `e` or `E` that is not hex. Trailing
-`u U l L f F` suffixes are accepted. That covers `M_PI`, `EOF` (`(-1)`), `RAND_MAX`, `INT_MAX`,
-`GL_*` and `GLFW_*` constants. Expressions (`(1 << 4)`), references to other macros, and strings
-are skipped.
+**The fixpoint.** `tests/boot.sh`:
-### Calling
+1. `build/finch` (C++) compiles `boot/` → **stage 1**,
+2. stage 1 compiles `boot/` → **stage 2** (and its IR),
+3. stage 2 compiles `boot/` → **stage 3** IR,
-Imported functions are declared in the module **only when called**, through
-`getOrInsertFunction`. This reuses an existing declaration if `print` already created `printf`.
-Since the signatures agree (`i32 (ptr, ...)`), there is no conflict.
-
-### Link errors
-
-When the link step fails, `linkFailed()` in `main.cpp` scans the linker's output for
-`undefined reference to \`sym'` (GNU ld) and `undefined symbol: sym` (lld). It groups the symbols by
-`CFunc::header` and prints which header they came from, with a guessed `link` line (`guessLib()`:
-basename, minus extension and trailing digits, lowercase: `GLFW/glfw3.h` → `glfw`).
-`cannot find -lfoo` becomes `the library 'foo' wasn't found`.
+and requires the stage 2 and stage 3 IR to be **byte-identical** (≈32,000 lines). It then compiles every
+test in `tests/run` that stays within the subset with the self-hosted compiler and compares the output
+with the expected one. It takes about 2.3 s for the self-hosted compiler to compile itself.
---
-## 11. Optimization, emission, linking
+## 18. Build system
-### Target
-
-`hostMachine()` creates a `TargetMachine` for `sys::getDefaultTargetTriple()` with CPU
-`"generic"`, no extra features, and `Reloc::PIC_`, which is required for the default PIE
-executables on modern distributions. Its **data layout is installed on the module before any IR
-is built**, in `Codegen`'s constructor. That matters: `IRBuilder` takes the alignment of loads and
-stores from the data layout, and without it `i64` accesses would be emitted with `align 4`.
-
-`generic` means the binary runs on any x86-64. Adding `-march=native` behavior would be a CPU
-string and feature change here.
-
-### Optimization
-
-`optimize()` builds the new pass manager pipeline:
-
-```cpp
-PassBuilder pb(tm);
-pb.register{Module,CGSCC,Function,Loop}Analyses(...);
-pb.crossRegisterProxies(lam, fam, cgam, mam);
-pb.buildPerModuleDefaultPipeline(OptimizationLevel::O2).run(mod, mam);
-```
-
-That is clang's `-O2` middle end: SROA/mem2reg, instcombine, inlining, GVN, LICM, loop
-unrolling, vectorization and so on. `-O0` skips this call, which is useful to read the IR that
-`Codegen` produced (`finch ir file.fn -O0`). Back-end codegen still runs at the TargetMachine's
-default level.
-
-### Emission
-
-`emitObject()` uses the **legacy** pass manager, because LLVM's machine-code pipeline is still only
-reachable through it: `tm->addPassesToEmitFile(pm, out, nullptr, CodeGenFileType::ObjectFile)`.
-
-### Linking
-
-```
-$CC file.o -o file -lm [pkg-config --libs NAME | -lNAME]...
-```
-
-The system C compiler driver (`cc` by default) is used as the linker. It knows the platform's crt
-files, the dynamic loader path, libc and default library paths, which would be a lot to replicate.
-`-lm` is always added. For each `link "NAME"` or `-l NAME`, `pkg-config --libs NAME` is tried
-first, so `-L` paths and dependent libraries come along. On failure it falls back to `-lNAME`.
-On Nix, the cc wrapper turns those `-L` paths into `RPATH` entries, so the result runs without
-`LD_LIBRARY_PATH`. The object file is deleted afterwards whether linking succeeded or not.
-
-### `finch run`
-
-It compiles to a temporary file (`sys::fs::createTemporaryFile`), runs it with `system()`, deletes
-it, and forwards the exit status. If the child died from a signal, it prints
-`the program crashed: ` and returns `128 + signo`, like a shell.
+- `find_package(LLVM CONFIG)`; **if the shared `LLVM` target exists Finch links that single library.**
+ `libclang.so` links `libLLVM.so` itself; linking the static component libraries as well put two copies of
+ LLVM in one process, and the duplicated global objects crashed at exit (a double free in a global
+ destructor). It also made the binary 82 MB instead of ~20 MB.
+- libclang: `find_path(clang-c/Index.h)` + `find_library(clang)`.
+- `runtime/finch_rt.c` is read at configure time into `build/rt_source.inc` as a raw string literal;
+ `CMAKE_CONFIGURE_DEPENDS` re-runs the configure step when it changes.
+- `FINCH_VERSION` from `project(VERSION 2.0.0)`. C++17, `-Wall -Wextra`, warning-free.
+- `shell.nix` lists `llvmPackages.clang` before `llvmPackages.libclang`: the latter also ships an
+ unwrapped `clang` that can't find the system headers.
---
-## 12. Diagnostics
+## 19. Testing
-`fail(line, col, msg)` is `[[noreturn]]`: it prints
-
-```
-file:line:col: error: msg
- line | source text
- | ^
-```
-
-and exits with status 1. The excerpt is taken from `g_source`, and tabs in the prefix are kept so
-the caret lines up. Conventions used across the code base:
-
-- **Positions:** binary operators report the **operator's** position; calls report the callee's;
- type errors in arguments report the **argument's** position.
-- **Messages** say what is wrong in plain words, and **how to fix it** when there is an obvious fix
- (`use int(...) to convert`, `did you mean ':='?`, `add import "math.h"`).
-- `headerHint()` maps about two dozen common libc/libm names to their header, for calls to unknown functions.
-- An internal inconsistency, if `verifyModule` ever fails, prints `internal compiler error (please report)`
- with the verifier's output and exits with status 2.
-
-The compiler has no warnings. Everything that would be a warning is either fine or an error.
+- `tests/run.sh`: every `tests/run/*.fn` with `fn main` must print exactly its `.out` (stdin from `.in`
+ if present); files without `main` are modules or helpers. Every `tests/fail/*.fn` must fail with the text
+ from its `// expect:` line. Currently **58 passed, 0 failed**.
+- `MEMCHECK=1 tests/run.sh`: the same, plus valgrind on every program (no leaks, no invalid access).
+- `tests/boot.sh`: the self-hosting fixpoint and the subset run (§17).
---
-## 13. Build system
+## 20. Semantics vs. C
-`CMakeLists.txt`:
-
-- `find_package(LLVM CONFIG)`. **If the `LLVM` target exists (a shared `libLLVM.so`), Finch links that
- one library.** This is essential, not cosmetic: `libclang.so` itself links `libLLVM.so`. Linking
- Finch against the static component libraries (`libLLVMCore.a`, …) puts a **second copy of LLVM** in
- the process. Both copies register the same global objects, and the program crashed on exit in a
- global destructor (`std::vector::~vector`, a double free). It also made the binary 82 MB
- instead of 11 MB.
-- libclang is found with `find_path(clang-c/Index.h)` and `find_library(clang)`, hinted by LLVM's dirs.
-- `FINCH_VERSION` comes from `project(VERSION …)` and is printed by `finch version`, along with `LLVM_VERSION_STRING`.
-- C++17, `-Wall -Wextra`, and the build is warning-free.
-
----
-
-## 14. Testing
-
-`tests/run.sh` (`FINCH=… ` overrides the compiler path):
-
-- `tests/run/*.fn`: run with `finch run`. Combined stdout+stderr must equal `*.out` exactly.
-- `tests/fail/*.fn`: must exit non-zero, and the output must contain the text after `// expect: ` on line 1.
- This covers compile errors, runtime panics (with line numbers), header and link failures.
- `tests/fail/lib_hint.h` declares a function that exists in no library, to test the link-hint path.
-
-Current state: **34 passed, 0 failed.** Every new error message should get a `fail/` test, and every
-feature a `run/` test.
-
----
-
-## 15. Semantics vs. C: defined and undefined behavior
-
-| Situation | C | Finch 1.0 |
+| Situation | C | Finch 2.0 |
|---|---|---|
-| Signed integer overflow | UB | **Defined wraparound** (no `nsw` flags) |
-| Unsigned overflow | wraps | wraps |
-| Integer division by zero | UB | **Compile error** if constant, otherwise **runtime error** |
-| `INT_MIN / -1` | UB | **Runtime error** |
-| Load or store through null | UB | **Runtime error** for `.value` |
-| `x & 1 == 0` | parses as `x & (1 == 0)` | parses as `(x & 1) == 0` |
-| Implicit narrowing (`int` → `char`) | silent | **Compile error** |
-| Signed/unsigned mixing | silent conversion | **Compile error** unless one side widens |
-| Using a variable before it is set | UB | Impossible: every declaration initializes (zero by default) |
-| Missing `return` in non-void function | UB if the value is used | **Compile error** |
-| Shift by ≥ bit width | UB | **Still poison** (not checked in 1.0) |
-| `float` → int out of range | UB | **Still poison** (not checked in 1.0) |
-| Dangling pointer to a dead local | UB | **Still UB** (no lifetime checks yet) |
-| Order of evaluation | mostly unspecified | Left to right |
+| Signed integer overflow | UB | defined wraparound (no `nsw`) |
+| Integer division by zero, `INT_MIN / -1` | UB | compile error if constant, otherwise runtime error |
+| Array index out of range | UB | runtime error (also for `[N]T` fields of C structs) |
+| Null dereference | UB | runtime error for `.value`, `.field`, `[i]` |
+| Use after free / double free of arrays, strings, structs | common bugs | impossible: no manual freeing of those |
+| Memory leak of arrays, strings, structs | common bug | impossible without `new` |
+| `x & 1 == 0` | `x & (1 == 0)` | `(x & 1) == 0` |
+| Implicit narrowing, signed/unsigned mixing | silent | compile errors |
+| Uninitialized variables | UB | impossible (zero or defaults) |
+| Missing return | UB if used | compile error |
+| Order of evaluation | mostly unspecified | left to right; in an assignment the right side first |
+| Shift ≥ width, out-of-range float→int | UB | still poison (not checked yet) |
+| Dangling pointer from `addr()` / `new` + `free` | UB | still UB |
---
-## 16. Function reference: who does what
-
-Groups of functions that sound alike, and how they differ.
-
-### Lexing and parsing
-
-| Function | Does | Differs from |
-|---|---|---|
-| `lex()` | text → tokens, sets `newlineBefore` | knows nothing about grammar or types |
-| `tokName()` | token kind → human name for errors | |
-| `Parser::cur()` / `peekTok()` / `next()` | current token / lookahead without consuming / consume | |
-| `accept(k)` vs `expect(k)` | consume if it matches and return a bool / consume or **fail** with "expected …, found …" | |
-| `unexpected(ctx)` | fail with "unexpected X (ctx)" at the current token | `expect` names the token it wanted; `unexpected` explains the context |
-| `sameLine()` | may the current token continue the expression? | `endOfStatement()` is the statement-level counterpart (requires a newline or `}`) |
-| `atDeclaration()` | `int x` / `ptr[...] p` lookahead | `type()` actually consumes a type |
-| `typeFromName()` vs `isTypeName()` | name → `Type` / name → bool | |
-| `statement()` vs `block()` | one statement / `{ statement* }` with terminator checks and `parenDepth` reset | |
-| `callArgs()` | shared by calls and conversions: `( expr, … )` | |
-| `primary()` / `postfix()` / `unary()` / `multiplicative()` / `additive()` / `compare()` / `andExpr()` / `orExpr()` | one function per precedence level | |
-
-### Codegen: types and values
-
-| Function | Does | Differs from |
-|---|---|---|
-| `ty(FType)` | Finch type → LLVM type | |
-| `zero(FType)` | default value for an uninitialized declaration | |
-| `widens(from, to)` | can every value of `from` be represented in `to`? (pure type rule) | `fits()` asks the same about one constant |
-| `fits(c, from, to)` | does this literal's value fit in `to`? | |
-| `coerce(v, want)` | **implicit** conversion into a known slot, or error | `convert()` is **explicit** `T(x)` and allows lossy casts; `unify()` converts **two** operands toward each other |
-| `unify(l, r)` | bring binary operands to a common type | used by `arithOp()` and `compare()` |
-| `intCast()` / `intToFloat()` | emit the signedness-correct cast | low-level helpers used by the three above |
-
-### Codegen: expressions
-
-| Function | Does | Differs from |
-|---|---|---|
-| `expr(e)` | r-value: emits code, returns `{value, type, literal}` | `place()` returns an **address** instead of loading |
-| `place(e)` | l-value address of a variable, C global or `p.value` | used by `expr()` (then loads), `assign()` (then stores), `addrOf()` (returns as is) |
-| `binary()` | dispatch by `BinOp` | |
-| `arith()` vs `arithOp()` | `arith()` wraps `arithOp()` and computes the result's `literal` flag | `arithOp()` does `unify` and emits the instruction |
-| `compare()` | all comparisons, pointer/str/float/int variants | always returns `bool` and is never a literal |
-| `logic()` | `&&`/`\|\|` with branches and a φ | the only place that builds a φ by hand |
-| `call()` | resolves a call name to one of the five kinds | |
-| `callFinch()` vs `callC()` | see section 8.10 | |
-| `print()` | builds a printf format from the types | |
-| `addrOf()` | `addr(x)` → `place(x).addr` | |
-| `convert()` | `T(x)` | |
-| `libc()` | `getOrInsertFunction`: declare-or-reuse an external function | `declare()` creates **Finch** functions with `Function::Create` |
-| `cGlobal()` | declare-or-reuse an `extern` global | |
-
-### Codegen: statements and control
-
-| Function | Does | Differs from |
-|---|---|---|
-| `declare()` vs `define()` | signature pass / body pass | |
-| `block()` vs `blockBody()` | `block()` pushes and pops a scope around `blockBody()`; `blockBody()` emits statements and rejects unreachable ones | `define()` calls `blockBody()` directly, because parameters and the outer body share one scope |
-| `stmt()` | dispatch by `StmtKind`; also handles `break`/`continue` | |
-| `varDecl()` / `assign()` | declaration with optional inference / store to an existing place | |
-| `addVar()` | create slot, store initial value, register name (with no-shadowing check) | `slot()` only creates the `alloca` |
-| `lookup()` | search all scopes, innermost first | |
-| `condition(e)` | `coerce(expr(e), bool)` with the message "a condition must be bool" | |
-| `ifStmt()` / `whileStmt()` / `forStmt()` / `returnStmt()` | section 8.5 | |
-| `terminated()` | does the current block already end in a terminator? | |
-| `continueAt(bb, deadEnd)` | move to the merge block, or delete it if unreachable | |
-| `newBlock()` | create a basic block in the current function | |
-
-### Runtime checks
-
-| Function | Does |
-|---|---|
-| `panicFn()` | get or create `finch.panic` |
-| `panicIf(cond, msg, pos)` | conditional branch to a panic block |
-| `checkDivisor(l, r, pos)` | zero and `MIN/-1` checks, or a compile-time error |
+## 21. Function reference
### Driver (`main.cpp`)
| Function | Does |
|---|---|
-| `hostMachine()` | create the `TargetMachine` |
-| `optimize()` | O2 pipeline |
-| `emitObject()` | write the `.o` |
-| `capture()` | run a shell command, collect its output and status |
-| `libFlags()` | pkg-config or `-l` for one library |
-| `link()` | run the linker, delete the `.o`, explain failures |
-| `linkFailed()` / `guessLib()` | translate linker errors into Finch advice |
+| `Loader::load` / `findModule` | read, lex, parse a file and recursively its modules / locate `name.fn` |
+| `hostMachine`, `optimize`, `emitObject` | target machine, O2 pipeline, object file |
+| `runtimeObject` | compile and cache the runtime |
+| `capture`, `libFlags`, `link` | run a command / pkg-config or `-l` / the link step, compiling `link "x.c"` files |
+| `linkFailed`, `guessLib` | turn linker output into advice |
+
+### Lexer and parser
+
+| Function | Does | Differs from |
+|---|---|---|
+| `lex` / `tokName` | text → tokens / names for messages | |
+| `accept` / `expect` / `unexpected` | consume if it matches / consume or fail / fail with context | |
+| `sameLine` / `endOfStatement` | may this token continue the expression? / did the statement end? | |
+| `atDeclaration` / `type` | lookahead for a declaration / parse a type | |
+| `args` | `( [name:] expr, … )` for calls, methods and constructors | |
+| `postfix` | `.field`, `.method(…)`, `[i]` chains | `primary` parses the start of a chain |
+
+### Codegen: program and statements (`codegen.cpp`)
+
+| Function | Does | Differs from |
+|---|---|---|
+| `declareStructs` / `declareFns` / `declare` | all types / all signatures / one signature + borrow analysis | |
+| `define` / `defineMainWrapper` | one body / the C entry point | |
+| `pushScope` / `popScope` | open / close a scope (`popScope` emits its cleanups if reachable) | |
+| `emitCleanups(downTo)` / `emitScopeCleanups(i)` | cleanups of several scopes without popping (return/break) / of one scope | |
+| `slot` / `tmpOf` / `tmp` | a variable's alloca / an anonymous one / an anonymous one holding a value | |
+| `lookup` / `addVar` | find a visible variable (respects `deferLimit`) / declare one | |
+| `stmt`, `varDecl`, `assign`, `ifStmt`, `whileStmt`, `forStmt`, `forEachStmt`, `returnStmt`, `jump` | statements | |
+| `rootVar` / `mutates` / `mutatesExpr` | the variable an l-value starts from / does a statement or expression change it? | |
+
+### Codegen: expressions
+
+| Function | Does | Differs from |
+|---|---|---|
+| `expr` / `exprWant` | an r-value / the same, with an expected type for array literals | |
+| `ref` / `place` | an address if any, else a value / an address or an error | `expr` loads from `ref` |
+| `member` / `index` | `.x`, `.len`, `.ptr`, `.value`, auto-deref / `[i]` with bounds checks and copy-on-write | |
+| `arrayLit` | `[…]` → heap array | |
+| `unary`, `binary`, `arith`/`arithOp`, `compare`, `logic` | operators (`arith` adds the literal flag) | |
+| `unify` / `coerce` / `convert` | two operands toward each other / one value into a slot / explicit `T(x)` | |
+
+### Codegen: types and ownership (`codegen_types.cpp`)
+
+| Function | Does |
+|---|---|
+| `ty`, `resolve`/`resolveT`, `findStruct`, `resolveStruct` | LLVM type; Named → Struct (deep or shallow); struct lookup; layout + cycle check |
+| `owning`, `zero`, `defaultValue` | does a type own memory; zero constant; zero or the struct's defaults |
+| `own` / `release` | something storable (move or copy) / drop an unkept fresh value |
+| `copyValue`, `dropValue`, `copyAt`, `dropAt`, `copyFn`, `dropFn`, `typeKey` | copying and dropping by value, by address, and the per-type helpers |
+| `forN` | an internal `for i in 0..n` loop used by helpers |
+| `strConst`, `cstr`, `strFromC`, `elemSize` | literal str constant; str → `char*`; `char*` → borrowed str; element size |
+| `emitPrint`, `printFn`, `printf_` | printing |
+| `diType`, `setLoc` | debug info |
+
+### Codegen: calls (`codegen_builtins.cpp`) and ABI (`abi.cpp`)
+
+| Function | Does |
+|---|---|
+| `call` / `method` | resolve a call / a method or module-qualified call |
+| `callFinch` / `construct` / `callC` | Finch function / struct constructor / C function |
+| `print`, `addrOf`, `convert`, `arrayMethod` | built-ins |
+| `rt` / `libc` | declare a runtime / libc function |
+| `fileName`, `panicIf`, `boundsCheck`, `checkDivisor` | runtime checks |
+| `classify`, `makePlan`, `declareC`, `emitCCall`, `cThunk` | System V classification, register plan, declaration, call, callback thunk |
---
-## 17. Extending the compiler
+## 22. Extending the compiler
-### A new built-in function (example: `len(str) -> int`)
+**A built-in function:** add the name to `isBuiltin()`, handle it in `call()`, implement it (in IR, or as
+a runtime function declared in `rt()`'s table), add `tests/run/` and `tests/fail/` cases. If it should
+exist in the self-hosted compiler too, add it to `boot/gen.fn`'s `call()` and `runtimeDecls()`.
-1. Add the name to `Codegen::isBuiltin()`, so it cannot be redefined.
-2. In `Codegen::call()`, dispatch `if (c.callee == "len") return len(c);`.
-3. Implement it: check the argument count, `coerce(expr(arg), Str, …)`, emit `strlen` via `libc()`,
- return `{call, I64}`. `strlen` returns `u64`; convert with `intCast` if `int` is wanted.
-4. Add `tests/run/len.fn` and its `.out`, plus a `tests/fail/` case for a wrong argument.
+**An array or str method:** add a branch in `arrayMethod()` or the str part of `method()`; if it changes
+the array, add its name to the `changing` sets in `method()` and `mutatesExpr()`, so borrowing stays correct.
-### A new binary operator
+**An operator:** lexer token + `tokName`, `BinOp`, the right precedence function in the parser, `opName`,
+emission in `arithOp`/`compare`, an explanation in `badOperands`.
-1. Lexer: add a `Tok`, its spelling in the operator `switch`, and `tokName()`.
-2. AST: add a `BinOp`.
-3. Parser: put it in the right precedence function.
-4. Codegen: `opName()`, then emit it in `arithOp()` or `compare()`, and explain bad operands in `badOperands()`.
+**A statement:** `StmtKind` + node, `Parser::statement`, `Codegen::stmt`; if it transfers control, respect
+`terminated()`, use `continueAt()` for merge blocks, and emit cleanups (`emitCleanups`) before jumping out of scopes.
-### A new statement
-
-Add a `StmtKind` and node in `ast.h`, recognise it in `Parser::statement()`, and handle it in
-`Codegen::stmt()`. If it can transfer control, make sure the result respects `terminated()` and
-uses `continueAt()` for its merge block.
-
-### Coding conventions
-
-Match the existing style: early `fail()` with a fixing hint, comments only where the *why* is not
-obvious, and one test per behavior.
+**A new owning type:** extend `owning()`, `dropAt`/`copyAt` (and their helpers), `emitPrint`, `diType`.
---
-## 18. Known limitations and roadmap
+## 23. Known limitations
-- **No aggregates yet:** `struct`, arrays/slices with `.len`, and C structs by value. These are the next
- milestones, in that order: arrays build on structs (pointer + length), and the memory model builds on both.
-- **Memory model** (agreed design): values that own heap memory are freed automatically at the end of
- the block that owns them, `defer` runs code at block exit, and `free` remains for manual control.
- Implementation sketch: a per-block cleanup list, emitted at every exit edge (normal end, `return`,
- `break`, `continue`). `blockBody()` and the `loops` stack already know every exit edge.
-- **Strings:** concatenation, `len`, `str(x)`. These depend on the memory model.
-- **Input:** `input()`.
-- **Modules:** `import name` for Finch code. One `.fn` = one program today.
-- **Unchecked UB:** shift amounts, float→int range, dangling pointers. Shift and float→int could be
- checked like division at small cost.
-- **Debug info:** no DWARF yet. `DIBuilder` would hook into `define()` and each statement's `pos`.
-- **Targets:** only the host triple, with Linux/x86-64 tested. The code is target-neutral except for
- the `cc -E -v` include discovery and `dprintf`.
-- **Bootstrapping:** rewriting the compiler in Finch is the long-term goal. Calling the LLVM-C API
- already works today: LLVM-C uses opaque handles (`LLVMModuleRef` and so on), which map to Finch's
- `ptr`. `examples/llvm.fn` builds and prints a module from Finch (`import "llvm-c/Core.h"`,
- `link "LLVM"`). What is still missing for a self-hosted compiler is structs, arrays, strings and file I/O.
+- No methods on structs, generics, maps, `match`, closures, error values (`int("x")`, `read_file` panic).
+- Strings are bytes: `.len`, `s[i]`, `upper()` are not Unicode-aware.
+- Dangling pointers (`addr` of a local that went away, use after `free`) are not detected.
+- Shift amounts and float→int conversions are not range-checked (LLVM poison, as in C).
+- C: unions and bit-field structs by value, function-like macros, `long double`.
+- One target tested: Linux x86-64 (the ABI code is System V x86-64 specific).
+- The self-hosted compiler has no C imports, sized integers or `defer`, and doesn't free memory.
diff --git a/docs/en/for-technicians.md b/docs/en/for-technicians.md
index 12eaaeb..282b752 100644
--- a/docs/en/for-technicians.md
+++ b/docs/en/for-technicians.md
@@ -1,9 +1,9 @@
# Finch for Technicians
**For people who know their way around a computer, but little or no programming.**
-This guide covers installing Finch, using the `finch` tool, the whole language, using
-C libraries, and fixing common problems. It does not cover how the compiler works inside.
-For that, see the [guide for engineers](for-engineers.md).
+This guide covers installing Finch, using the `finch` tool, the whole language, memory,
+modules, C libraries, debugging and fixing common problems. It does not cover how the
+compiler works inside. For that, see the [guide for engineers](for-engineers.md).
> 🇵🇱 Polska wersja: [dla-technikow.md](../pl/dla-technikow.md)
@@ -20,12 +20,19 @@ For that, see the [guide for engineers](for-engineers.md).
7. [Operators](#7-operators)
8. [Control flow](#8-control-flow)
9. [Functions](#9-functions)
-10. [Pointers](#10-pointers)
-11. [Using C libraries](#11-using-c-libraries)
-12. [Errors](#12-errors)
-13. [Troubleshooting](#13-troubleshooting)
-14. [Project layout and tests](#14-project-layout-and-tests)
-15. [Current limits](#15-current-limits)
+10. [Arrays](#10-arrays)
+11. [Text (str)](#11-text-str)
+12. [Structs](#12-structs)
+13. [Memory: who frees what](#13-memory-who-frees-what)
+14. [Pointers](#14-pointers)
+15. [Built-in functions](#15-built-in-functions)
+16. [Modules](#16-modules)
+17. [Using C libraries](#17-using-c-libraries)
+18. [Errors](#18-errors)
+19. [Debugging](#19-debugging)
+20. [Troubleshooting](#20-troubleshooting)
+21. [Project layout and tests](#21-project-layout-and-tests)
+22. [Current limits](#22-current-limits)
---
@@ -37,9 +44,13 @@ file into a real program (a native executable, like the ones made from C), using
What that gives you:
-- **Speed close to C.** There is no interpreter, virtual machine or garbage collector.
+- **Speed close to C.** No interpreter, virtual machine or garbage collector. Array bounds
+ checks and other safety checks cost almost nothing after optimization.
- **Small, standalone programs** that you can copy and run.
-- **Direct access to C libraries.** `import "stdio.h"` and call anything in it.
+- **Memory handled for you.** Lists and text are freed automatically at the end of the block
+ that owns them, with no garbage collector involved.
+- **Direct access to C libraries.** `import "stdio.h"` and call anything in it, even functions
+ that take structs.
The design rules: one way to do each thing, C-like syntax without C's traps, and error
messages that tell you how to fix the problem.
@@ -56,10 +67,10 @@ Other LLVM versions will likely fail to compile, because LLVM's C++ API changes
```sh
git clone https://github.com/Nexoniarz/Finch.git
cd Finch
-nix-shell # downloads LLVM, libclang, cmake, ninja, pkg-config
+nix-shell # downloads LLVM, libclang, clang, cmake, ninja, pkg-config
cmake -S . -B build -G Ninja
ninja -C build
-./build/finch version # finch 1.0.0 (LLVM 21.x)
+./build/finch version # finch 2.0.0 (LLVM 21.x)
```
### Option B: your distribution's packages
@@ -73,7 +84,7 @@ libclang 21 development files, and optionally `pkg-config`.
| Fedora | `gcc-c++ cmake ninja-build pkgconf llvm-devel clang-devel` |
| Arch | `base-devel cmake ninja pkgconf llvm clang` |
-If CMake cannot find LLVM, point it there:
+These have not been tested; if CMake cannot find LLVM, point it there:
```sh
cmake -S . -B build -G Ninja -DLLVM_DIR=/usr/lib/llvm-21/lib/cmake/llvm
@@ -87,21 +98,22 @@ sudo cp build/finch /usr/local/bin/
finch version
```
-Finch needs a working C compiler (`cc`) at run time. It uses it as the **linker** that
-turns compiled code into a program. Set the `CC` environment variable to use a different one.
+At run time Finch needs a C compiler (`cc`). It is used to link programs, and once to build
+Finch's small runtime library, which is kept in `~/.cache/finch`. Set `CC` to use a different one.
---
## 3. The finch command
```
-finch run compile and run right away
+finch run [args...] compile and run right away
finch build [-o name] compile into a program (default name: the file's name)
finch ir print the LLVM IR (for the curious)
finch version show the version
options:
-l link a C library, same as link "lib" in the file
+ -g add debug info (for gdb / lldb)
-O0 skip optimizations
```
@@ -109,13 +121,13 @@ Examples:
```sh
finch run hello.fn # prints the output
+finch run tool.fn input.txt -v # arguments after the file go to the program
finch build game.fn -o mygame # creates ./mygame
-./mygame
-finch run window.fn -l glfw # with a C library
+finch build game.fn -g -O0 # a build for the debugger
```
-`finch run` passes on your program's exit code. If the program crashes, it tells you
-how (for example `the program crashed: Segmentation fault`).
+`finch run` passes on your program's exit code. If the program crashes, it says how
+(for example `the program crashed: Segmentation fault`).
---
@@ -126,8 +138,14 @@ how (for example `the program crashed: Segmentation fault`).
/* or span
several lines */
-import "math.h" // C headers (optional), always at the top
-link "m" // C libraries to link (optional)
+import "math.h" // C headers (optional), at the top
+import shapes // Finch modules (optional): shapes.fn next to this file
+link "m" // C libraries or C files to link (optional)
+
+struct Point { // your own types
+ int x
+ int y
+}
fn main() { // the program starts here
print("Hello")
@@ -136,13 +154,14 @@ fn main() { // the program starts here
Rules worth knowing:
-- **One statement per line.** There are no semicolons. To split a long expression,
- break the line **after** an operator (`a +⏎ b`) or anywhere inside brackets `( )`.
-- **Curly braces are always required** after `if`, `else`, `for`, `while`, and the opening
- `{` goes on the same line.
+- **One statement per line.** There are no semicolons. To split a long expression, break the line
+ **after** an operator (`a +⏎ b`) or anywhere inside brackets `( )` or `[ ]`.
+- **Curly braces are always required** after `if`, `else`, `for`, `while`, and the opening `{`
+ goes on the same line.
- **No brackets around conditions:** `if x > 5 {`, not `if (x > 5) {`.
-- Functions can be in any order in the file.
-- `main` is either `fn main()` or `fn main() -> int` (returns the exit code).
+- Functions and structs can be in any order in the file.
+- `main` is `fn main()`, `fn main() -> int` (returns the exit code) or `fn main([]str args)`
+ (gets the command-line arguments; `args[0]` is the program's own path).
---
@@ -151,7 +170,8 @@ Rules worth knowing:
```c
x := 5 // new variable, type inferred (int)
int y = 10 // new variable, type written explicitly
-int z // no value: starts at zero ("" for str, null for pointers)
+int z // no value: starts at zero ("" for str, [] for arrays, null for pointers)
+Point p // a struct with no value: zero, or the struct's default values
x = 7 // assign
x += 1 // also -= *= /= %=
@@ -160,10 +180,10 @@ x += 1 // also -= *= /= %=
- A name can be declared **once** in a function. Finch does not allow reusing a name in an
inner block (no "shadowing"), because that is a common source of confusion.
- Variables exist from their declaration to the end of the `{ }` block they are in.
-- Names: ASCII letters, digits and `_`, not starting with a digit. Non-ASCII letters are allowed only inside strings. Type names (`int`, `u8`, …) and
- keywords are reserved.
+- Names: ASCII letters, digits and `_`, not starting with a digit. Non-ASCII letters are allowed only inside strings.
+ Type names (`int`, `u8`, …), built-in function names and keywords are reserved.
-**Keywords:** `fn return if else while for in break continue true false null import link`
+**Keywords:** `fn return if else while for in break continue true false null import link struct defer`
---
@@ -177,7 +197,9 @@ x += 1 // also -= *= /= %=
| `float` | decimal number, 64-bit (same as `f64`) | `3.14` |
| `bool` | `true` or `false` | |
| `char` | one byte-sized character | `'A'`, `'\n'` |
-| `str` | text (a C string) | `"hello\tworld"` |
+| `str` | text | `"hello\tworld"` |
+| `[]T` | a list (array) of `T` | `[1, 2, 3]` |
+| your structs | named groups of values | `Point(1, 2)` |
String and char escapes: `\n` new line, `\t` tab, `\r`, `\0`, `\\`, `\"`, `\'`.
@@ -192,11 +214,6 @@ String and char escapes: `\n` new line, `\t` tab, `\r`, `\0`, `\\`, `\"`, `\'`.
`f32` is a 32-bit decimal number (about 7 significant digits), `f64` a 64-bit one (about 15–16).
-### Pointers
-
-`ptr[T]` points to a value of type `T`. Plain `ptr` points to "something" with unknown
-type, like `void*` in C. See [Pointers](#10-pointers).
-
### Conversions
Finch converts automatically **only when nothing can be lost**:
@@ -208,32 +225,30 @@ Finch converts automatically **only when nothing can be lost**:
| any whole number | `float` / `f32` | ✅ |
| `f32` | `f64` | ✅ |
| `null` | any pointer | ✅ |
-| any pointer, `str` | `ptr` | ✅ |
-| `ptr` | any `ptr[T]` | ✅ |
+| any pointer | `ptr`, and `ptr` to any pointer | ✅ |
| anything else | | ❌ write it: `type(value)` |
**Number literals adapt.** A number written in the code fits into any type that can hold it:
-`u8 b = 200` works, `u8 b = 300` is an error (`the number 300 doesn't fit in u8`).
-The same applies to constants imported from C headers.
+`u8 b = 200` works, `u8 b = 300` is an error. The same applies to constants imported from C headers.
**Explicit conversions** use the type name like a function:
```c
-int(3.99) // 3 (cuts off the fraction)
-int(-3.99) // -3
-float(7) / 2 // 3.5
-u8(300) // 44 (keeps the lowest 8 bits)
-i8(200) // -56
-int('A') // 65
-char(66) // 'B'
-bool(0) // false (any non-zero number is true)
-ptr(text) // a str as an untyped pointer
-str(p) // a pointer as a str
+int(3.99) // 3 (cuts off the fraction)
+float(7) / 2 // 3.5
+u8(300) // 44 (keeps the lowest 8 bits)
+int('A') // 65
+char(66) // 'B'
+bool(0) // false (any non-zero number is true)
+str(42) // "42" (also floats, bools, chars; str([]u8) turns bytes into text)
+int("42") // 42 (stops the program if the text isn't a number)
+float("2.5") // 2.5
+ptr(p) // a typed pointer as an untyped one
+str(p) // a C char pointer as text
```
**Fixed-size numbers wrap around**, like real hardware: a `u8` holding 255 plus 1 becomes 0.
Calculations happen in the type of the values involved, so `u8(250) + 10` is `4`, not `260`.
-Convert first if you need more room: `int(x) + 10`.
---
@@ -244,7 +259,7 @@ Convert first if you need more room: `int(x) + 10`.
| Priority | Operators | Meaning |
|---|---|---|
| 5 | `*` `/` `%` `<<` `>>` `&` | multiply, divide, remainder, shift left/right, bitwise AND |
-| 4 | `+` `-` `\|` `^` | add, subtract, bitwise OR, bitwise XOR |
+| 4 | `+` `-` `\|` `^` | add (and join text), subtract, bitwise OR, bitwise XOR |
| 3 | `==` `!=` `<` `<=` `>` `>=` | comparisons |
| 2 | `&&` | logical AND |
| 1 | `\|\|` | logical OR |
@@ -252,18 +267,16 @@ Convert first if you need more room: `int(x) + 10`.
Unary (in front of a value): `-x` negate, `!x` logical NOT, `~x` flip all bits.
> **Different from C, on purpose:** bit operators bind tighter than comparisons, so
-> `flags & 4 != 0` means `(flags & 4) != 0`. In C it silently means something else.
+> `flags & 4 != 0` means `(flags & 4) != 0`.
Notes:
-- `/` on whole numbers rounds toward zero: `7 / 2` is `3`, `-7 / 2` is `-3`. `%` takes the sign of the left side.
+- `/` on whole numbers rounds toward zero: `7 / 2` is `3`. `%` takes the sign of the left side.
- Dividing by zero **stops the program** with `runtime error: division by zero` and the line number.
- If the zero is written in the code, it is a compile error instead.
- `&&` and `||` only evaluate the right side if needed.
-- Text (`str`) can be compared with `==` and `!=` (it compares the contents). `<` and `>` do not work on text.
-- Pointers can be compared with `==` and `!=`, including against `null`.
-- Bit operators need whole numbers. For `bool` use `&&` and `||`.
-- `>>` on signed numbers keeps the sign (`-16 >> 2` is `-4`). On unsigned numbers it fills with zeros.
+- Text: `+` joins, `==` `!=` compare contents, `<` `>` compare alphabetically (byte by byte).
+- Pointers can be compared with `==` and `!=`, including against `null`. A `str` is never `null`.
+- Arrays and structs can't be compared with `==`; compare their parts.
---
@@ -286,14 +299,32 @@ for i in 0..10 { // i = 0, 1, …, 9 (the end is not included)
if i == 3 { continue }
if i == 8 { break }
}
+
+for name in names { // every element of an array (or every char of a str)
+ print(name)
+}
```
- A condition must be a `bool`. `if x {` with a number is an error; write `if x != 0 {`.
-- In `for i in a..b`, both ends are whole numbers evaluated **once** before the loop starts.
- `i` is an `int` and cannot be changed inside the loop.
+- In `for i in a..b`, both ends are evaluated **once**. `i` is an `int` and cannot be changed in the loop.
+- In `for x in list`, `x` cannot be changed either. To change elements, loop over the indexes:
+ `for i in 0..list.len { list[i] = ... }`. Adding to the list inside the loop is fine.
- `while true { ... }` loops until a `break` or `return`.
- Code after `return`, `break` or `continue` in the same block is an error, because it can never run.
+### defer
+
+`defer` runs a statement when the current block ends, however it ends: normally, by `return`,
+`break` or `continue`. Several defers run in reverse order. Handy for cleanup:
+
+```c
+f := fopen("data.txt", "r")
+defer fclose(f)
+// ... use f; it is closed when the block ends, on every path
+```
+
+A deferred statement can't contain `return`, `break` or `continue`.
+
---
## 9. Functions
@@ -309,32 +340,129 @@ fn no_result(str message) { // no "->": returns nothing
}
```
-- Parameters are copies. Changing a parameter does not change the caller's variable.
- Use a pointer for that (see below).
+- Arguments behave like copies: changing a parameter never changes the caller's variable.
+ (Finch only really copies when needed: a function that doesn't change an array gets it for free.)
+ To change the caller's variable, pass a pointer: see [Pointers](#14-pointers).
- A function with `-> type` must return a value on every path. Finch checks this.
- A function cannot be defined inside another function.
-- Built-in names you cannot reuse: `print`, `addr`, and all type names.
-
-### Built-in functions
-
-| Function | What it does |
-|---|---|
-| `print(a, b, ...)` | Prints all values separated by spaces, then a new line. Works with every type. |
-| `addr(x)` | Gives a pointer to variable `x`. |
-| `int(x)`, `u8(x)`, `float(x)`, … | Converts types (see [Conversions](#conversions)). |
---
-## 10. Pointers
+## 10. Arrays
-A pointer holds the **address** of a value: where it lives in memory. Finch uses words
-instead of C's `*` and `&`:
+```c
+nums := [5, 3, 8] // an array of int
+[]str names // an empty array of str
+grid := [[1, 2], [3, 4]] // arrays of arrays
+[]f32 xs = [1, 2.5] // the type decides what the numbers become
+```
+
+| Operation | Meaning |
+|---|---|
+| `a.len` | number of elements |
+| `a[i]` | element `i` (from 0); out of range stops the program with a clear message |
+| `a[i] = x` | change an element |
+| `a.push(x)` | add at the end |
+| `a.pop()` | remove the last one and give it back |
+| `a.insert(i, x)` | put `x` at position `i`, moving the rest |
+| `a.remove(i)` | remove position `i` and give it back |
+| `a.clear()` | remove everything |
+| `a.resize(n)` | make it `n` long (new elements are zero) |
+| `a.contains(x)`, `a.find(x)` | is `x` there? its index, or -1 (numbers, chars, bools, str) |
+| `a.sort()`, `a.reverse()` | in place (numbers, chars, str) |
+| `a.slice(s, e)` | a new array with elements `s` … `e-1` |
+| `a.join(sep)` | for `[]str`: one text with `sep` between the parts |
+| `a.ptr` | the address of the first element (for C) |
+| `print(a)` | prints `[1, 2, 3]` |
+
+**Assigning copies:** after `b := a`, changing `b` does not change `a`.
+
+---
+
+## 11. Text (str)
+
+```c
+s := "Hello"
+s += ", world" // join
+print(s.len, s[0]) // 12 H (.len counts bytes; ą, ż, … take 2)
+s[0] = 'J' // change a character
+```
+
+| Method | Gives back |
+|---|---|
+| `s.sub(a, b)` | the part from `a` to `b-1` |
+| `s.find(t)` | where `t` starts, or -1 |
+| `s.contains(t)`, `s.starts_with(t)`, `s.ends_with(t)` | `bool` |
+| `s.split(sep)` | `[]str`; `split("")` gives single characters |
+| `s.trim()` | without spaces/newlines at both ends |
+| `s.upper()`, `s.lower()` | A–Z / a–z changed (ASCII letters) |
+| `s.replace(a, b)` | every `a` replaced by `b` |
+| `s.repeat(n)` | `s` n times |
+| `s.bytes()` | `[]u8` with the bytes |
+| `s.ptr` | a `ptr[char]` for C (valid while `s` lives) |
+
+`str` keeps its length, so `.len` is instant, and it is always NUL-terminated, so passing it
+to C as `char*` works.
+
+---
+
+## 12. Structs
+
+```c
+struct Player {
+ str name
+ int lives = 3 // a default value
+ []int scores
+ Point pos // structs inside structs
+}
+
+p := Player(name: "Ola") // by name: missing fields get their default (or zero)
+q := Point(1, 2) // in order: then every field must be given
+p.lives -= 1
+p.scores.push(10)
+p.pos.x = 5
+print(p) // Player(name: "Ola", lives: 2, scores: [10], pos: Point(x: 5, y: 0))
+```
+
+- Assigning or passing a struct copies it, together with its arrays and text.
+- A struct can't contain itself directly; use `ptr[Node]` or `[]Node` for that field (trees, lists).
+- Structs have no methods. Write functions that take the struct: `fn heal(Player p) -> Player`.
+
+---
+
+## 13. Memory: who frees what
+
+You never free arrays, text or structs. Finch does it for you, at a predictable moment:
+**when the block that owns the value ends** (`}`), or earlier on `return`, `break`, `continue`.
+
+- A variable **owns** its value. Assigning copies it, so two variables never share one list.
+- A value produced by a function call or an expression is **moved** where it's stored: no copy.
+- `return list` moves the variable out of the function: no copy.
+- Function parameters are **lent**: the function gets the caller's value without a copy. If the
+ function changes the parameter, Finch makes it a private copy first. You never notice except in speed.
+
+For memory you manage yourself (linked lists, trees, sharing between structures):
+
+```c
+node := new(Node(value: 1)) // put a value on the heap, get a ptr[Node]
+defer free(node) // free(...) gives it back (and frees its arrays and text)
+```
+
+Memory from C (`malloc`, library objects) is freed with C's functions, as in C.
+
+---
+
+## 14. Pointers
+
+A pointer holds the **address** of a value. Finch uses words instead of C's `*` and `&`:
| C | Finch | Meaning |
|---|---|---|
| `int *p` | `ptr[int] p` | p points to an int |
-| `&x` | `addr(x)` | the address of x |
+| `&x` | `addr(x)` | the address of x (also `addr(a[i])`, `addr(s.field)`) |
| `*p` | `p.value` | the value p points to |
+| `p->field` | `p.field` | a field of the struct p points to |
+| `p[i]` | `p[i]` | C-style indexing (no bounds check) |
| `NULL` | `null` | points nowhere |
| `void *` | `ptr` | points to something of unknown type |
@@ -343,38 +471,74 @@ fn double(ptr[int] p) {
p.value = p.value * 2
}
-fn main() {
- x := 21
- double(addr(x))
- print(x) // 42
-
- ptr[int] nothing = null
- if nothing == null {
- print("empty")
- }
-}
+x := 21
+double(addr(x)) // x is 42
```
-Pointers can point to pointers: `ptr[ptr[int]]`, then `pp.value.value`.
+If a struct has its own field called `value`, then `p.value` on a pointer to it means that field.
-Using `.value` on a `null` pointer **stops the program** with
-`runtime error: used .value on a null pointer` instead of crashing randomly.
+Using `.value`, `.field` or `[i]` on a `null` pointer **stops the program** with a clear message.
-⚠️ A pointer to a variable must not be used after the function that owns the variable
-has returned. The variable no longer exists then. Finch does not check this yet.
+⚠️ A pointer to a variable must not be used after the variable's block has ended. Finch does not check this.
---
-## 11. Using C libraries
+## 15. Built-in functions
-Almost every library on Linux has a C interface: graphics, sound, networking, databases.
-Finch can use them directly.
+| Function | What it does |
+|---|---|
+| `print(a, b, ...)` | Prints all values separated by spaces, then a new line. Works with every type, including arrays and structs. |
+| `input()`, `input("question")` | Reads one line typed by the user (without the newline). Empty text at end of input. |
+| `read_file(path)` | The whole file as `str`. Stops the program with a clear message if it can't be read. |
+| `write_file(path, text)` | Writes (replaces) the file; gives back `true` if it worked. |
+| `file_exists(path)` | `true` / `false` |
+| `shell(command)` | Runs a shell command, gives back its exit code. |
+| `exit(code)` | Ends the program right away. |
+| `addr(x)` | A pointer to `x`; `addr(function)` gives C a function pointer (callback). |
+| `new(value)`, `free(p)` | Manual heap memory (see [Memory](#13-memory-who-frees-what)). |
+| `int(x)`, `str(x)`, `u8(x)`, … | Conversions (see [Conversions](#conversions)). |
+
+---
+
+## 16. Modules
+
+Split a program into files. `import shapes` loads `shapes.fn` from the same folder (or from a
+folder listed in the `FINCH_PATH` environment variable, separated by `:`).
+
+```c
+// shapes.fn
+struct Box {
+ float w
+ float h
+}
+
+fn area(Box b) -> float {
+ return b.w * b.h
+}
+```
+
+```c
+// main.fn
+import shapes
+
+fn main() {
+ b := shapes.Box(2, 3)
+ print(shapes.area(b))
+ shapes.Box other // a module's type in a declaration
+}
+```
+
+- Everything in a module is used with the module's name in front: `shapes.area`, `shapes.Box`.
+- A module doesn't have `main`. Modules can import other modules.
+
+---
+
+## 17. Using C libraries
### Step 1: import the header
```c
import "stdio.h"
-import "math.h"
import "GLFW/glfw3.h" // a path inside the system include folders
import "mylib.h" // a header next to your .fn file
```
@@ -382,33 +546,23 @@ import "mylib.h" // a header next to your .fn file
Finch reads the header with **libclang** (clang's C parser) and makes these usable:
- **functions**, including ones with a variable number of arguments, like `printf`,
-- **`enum` values**,
-- **simple `#define` constants** that are a single number, like `M_PI`, `EOF`, `GL_COLOR_BUFFER_BIT`,
+- **structs**, by value and by pointer (`Vector2`, `SDL_Rect`, `CXCursor`, …), with their fields,
+- **`enum` values** and **simple `#define` constants** (`M_PI`, `GL_COLOR_BUFFER_BIT`),
- **global variables**, like `stdout` and `stderr`.
### Step 2: link the library
-A header only *describes* functions. Their actual code is in a library file (`libNAME.so`).
-Tell Finch which library to use, at the top of the file:
-
```c
-link "glfw" // uses libglfw.so
-link "GL" // uses libGL.so
+link "glfw" // uses libglfw.so (pkg-config is asked first, if installed)
+link "helpers.c" // compile and link your own C file (next to the .fn file)
+link "prebuilt.a" // or an object file / static library
```
-Write just the name: for `libglfw.so` that is `glfw`. If `pkg-config` knows the name, Finch uses
-its settings (paths and extra libraries). Otherwise it passes `-lNAME` to the linker.
-The C standard library and the math library (`libm`) are always linked.
-
-On the command line, `-l NAME` does the same as `link "NAME"`.
-
If you forget the `link` line, Finch tells you exactly what is missing:
```
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"
```
@@ -422,52 +576,46 @@ error: glfwInit, glfwCreateWindow, glfwMakeContextCurrent and 5 more come from "
| `float`, `double` | `f32`, `f64` |
| `_Bool` / `bool` | `bool` |
| `enum` | the matching signed integer |
-| `char *`, `const char *` | `str` |
-| `int *`, `double *`, … | `ptr[i32]`, `ptr[f64]`, … |
-| `void *`, `struct X *`, `FILE *`, function pointers | `ptr` |
+| `char *` as a **parameter or result** | `str` |
+| `char *` inside a struct or behind a pointer | `ptr[char]` (use `str(p)` to read it) |
+| `struct X` by value | `X`, a struct with the same fields |
+| `struct X *`, `int *`, … | `ptr[X]`, `ptr[i32]`, … |
+| `int arr[16]` inside a struct | `[16]i32`: index it and use `.len` |
+| `void *`, function pointers, incomplete structs | `ptr` |
-So C's `int` is Finch's `i32`. Literals adapt automatically (`abs(-5)` works),
-but a Finch `int` variable must be converted: `abs(i32(x))`.
-
-### Example
+### Example: structs and callbacks
```c
-import "stdio.h"
-import "math.h"
-import "stdlib.h"
+import "raylib.h"
+link "raylib"
fn main() {
- printf("%d + %d = %d\n", 2, 3, 5)
- print(sqrt(2), M_PI, RAND_MAX)
-
- ptr[i32] n = malloc(4) // ask C for 4 bytes of memory
- n.value = 7
- print(n.value)
- free(n) // and give them back
-
- fprintf(stderr, "this goes to the error output\n")
+ 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()
}
```
-A complete graphics example is in `examples/window.fn`: a GLFW + OpenGL window that changes color.
-`examples/llvm.fn` shows that Finch can even drive LLVM itself through its C API.
+To give C a function to call back (like `qsort`'s comparator), use `addr(myFunction)`.
+Its parameters must be C types (numbers, pointers, C structs).
### What does not work yet
-- C functions that take or return a **`struct` by value**, such as `div()`. Finch says so
- clearly if you try. Functions taking a *pointer* to a struct are fine.
-- **Function-like macros** (`#define MAX(a,b) ...`) and macros that are not a single number.
+- C **unions** by value, and structs with **bit fields** by value (pointers to them work).
+- **Function-like macros** and macros that are not a single number.
- `long double`, 128-bit integers.
-- Reading the **fields** of a C struct.
---
-## 12. Errors
+## 18. Errors
### Compile errors
-Finch stops at the first problem and shows where it is:
-
```
game.fn:12:9: error: 'score' must be int, but this is str
12 | score = "high"
@@ -478,64 +626,87 @@ Format: `file:line:column: error: explanation`, then the line with a `^` under t
### Runtime errors
-Some problems can only be seen while the program runs. Instead of undefined behavior
-(as in C), Finch stops with a clear message on the error output and exit code 1:
+Instead of undefined behavior (as in C), Finch stops with a message and exit code 1:
| Message | Cause |
|---|---|
| `runtime error: division by zero` | `/` or `%` by a variable that was 0 |
| `runtime error: division overflows …` | the smallest signed number divided by −1 |
-| `runtime error: used .value on a null pointer` | reading or writing `p.value` while `p` is `null` |
+| `runtime error: index 5 is out of range (the length is 3)` | `a[5]` on a shorter array or str |
+| `runtime error: pop() on an empty array` | |
+| `runtime error: used .value on a null pointer` | also `.field` and `[i]` through `null` |
+| `runtime error: can't turn "x" into int` | `int(...)` / `float(...)` on text that isn't a number |
+| `runtime error: can't read the file "…"` | `read_file` on a missing or unreadable file |
---
-## 13. Troubleshooting
+## 19. Debugging
+
+```sh
+finch build game.fn -g -O0 -o game
+gdb ./game
+(gdb) break game.fn:12
+(gdb) run
+(gdb) bt # where are we, through which functions
+(gdb) info locals # all variables
+(gdb) print player.lives
+```
+
+`-g` adds debug info (lines, functions, variables, struct fields). `-O0` keeps every variable
+visible; without it the optimizer may remove some.
+
+---
+
+## 20. Troubleshooting
| Problem | Solution |
|---|---|
-| `can't find the C header 'x.h'` | The library's development files are missing. Install them (on Debian: `libx-dev`; on Nix: add to `shell.nix`). For your own header, put it next to the `.fn` file. |
+| `can't find the C header 'x.h'` | The library's development files are missing. Install them (Debian: `libx-dev`; Nix: add to `shell.nix`). |
| `… come from "x.h", but its library isn't linked` | Add `link "name"` at the top of the file. |
-| `the library 'x' wasn't found` | The library is not installed, or the name is wrong. On Nix, add it to `shell.nix` and run inside `nix-shell`. |
-| `the C function 'f' can't be used from Finch yet` | It uses a struct by value, or another unsupported type. Look for a variant that takes pointers. |
-| `no C compiler found to link with` | Install gcc or clang, or set `CC`. |
-| The program runs, but a C library is not found at start | It was linked from a path the system does not search. Run it inside the same `nix-shell`, or install the library system-wide. |
-| CMake: `libclang not found` | Install the libclang dev package, or run `cmake` inside `nix-shell`. |
-| Build fails with LLVM API errors | You have a different LLVM version than 21. |
+| `the library 'x' wasn't found` | Not installed, or a wrong name. On Nix, add it to `shell.nix` and run inside `nix-shell`. |
+| `the C function 'f' can't be used from Finch yet` | It uses a union, bit fields or another unsupported type by value. Look for a variant that takes pointers. |
+| `can't find the module 'x'` | Put `x.fn` next to the importing file, or set `FINCH_PATH`. |
+| `couldn't build the Finch runtime` | No working C compiler. Install gcc or clang, or set `CC`. |
+| A library is not found when the program starts | It was linked from a folder the system doesn't search. Run inside the same `nix-shell`, or install it system-wide. |
+| `clang` inside `nix-shell` can't find `stdio.h` | You have an old `shell.nix`: `llvmPackages.clang` must be listed before `llvmPackages.libclang`. |
| A program never ends | A loop condition never becomes false. Press Ctrl+C. |
---
-## 14. Project layout and tests
+## 21. Project layout and tests
```
Finch/
├── src/ the compiler (C++)
-├── examples/ example programs (hello, tour, types, c_import, window, llvm)
+├── runtime/ finch_rt.c: the small runtime (text, arrays, input, files)
+├── boot/ the Finch compiler written in Finch (see the engineers' guide)
+├── examples/ hello, tour, types, structs, todo, guess, c_import, window, raylib, llvm
├── tests/
-│ ├── run/ programs + the exact output they must print (.out)
+│ ├── run/ programs + the exact output they must print (.out), input (.in)
│ ├── fail/ programs that must fail, with the expected error in line 1
-│ └── run.sh the test runner
+│ ├── run.sh the test runner (MEMCHECK=1 also checks memory with valgrind)
+│ └── boot.sh builds the self-hosted compiler with itself and compares
├── docs/ this documentation (en, pl)
├── shell.nix the Nix development environment
└── CMakeLists.txt
```
-Run the tests after building:
-
```sh
-tests/run.sh # → 34 passed, 0 failed
+tests/run.sh # → 58 passed, 0 failed
+MEMCHECK=1 tests/run.sh # the same under valgrind: no leaks, no bad memory access
+tests/boot.sh # the self-hosting check (needs clang)
```
---
-## 15. Current limits
+## 22. Current limits
-Finch 1.0 is a solid core, not a finished language. Not there yet:
+Finch 2.0 is a complete small language, but not a finished one. Not there yet:
-- `struct`, arrays, reading keyboard input (`input()`), string building (`"a" + "b"`).
- For now, use C functions for these (`scanf`, `snprintf`, `malloc`…).
-- Automatic memory management (planned: memory freed at the end of the block, plus `defer`).
-- Finch modules (one program = one `.fn` file for now).
+- Methods on structs, generics, maps/dictionaries, `match`. (Use arrays of structs and functions.)
+- Error values: errors in `int("x")` or `read_file` stop the program. Check first (`file_exists`).
+- Unicode-aware text: `.len`, `s[i]` and `upper()` work on bytes / ASCII.
+- Threads.
- Platforms other than Linux x86-64.
See the [roadmap in the README](../../README.md#roadmap).
diff --git a/docs/pl/dla-inzynierow.md b/docs/pl/dla-inzynierow.md
index 263e39a..e71d9b1 100644
--- a/docs/pl/dla-inzynierow.md
+++ b/docs/pl/dla-inzynierow.md
@@ -1,10 +1,11 @@
# Finch dla inżynierów
-**Kompletny opis wnętrza kompilatora Finch 1.0.** Opisuje, jak kod źródłowy staje się tokenami,
-tokeny drzewem AST, a AST kodem LLVM IR, który potem jest optymalizowany, zapisywany do pliku
-obiektowego i linkowany. Zawiera dokładne reguły typów, sposób tłumaczenia każdej konstrukcji na
-IR (z prawdziwym wyjściem kompilatora), import nagłówków C przez libclang, szczegóły ABI
-oraz opis każdej funkcji kompilatora wraz z tym, czym różni się od sąsiednich.
+**Kompletny opis wnętrza Fincha 2.0.** Jak kod źródłowy staje się tokenami, tokeny drzewem AST,
+a AST kodem LLVM IR, który potem jest optymalizowany, zapisywany i linkowany z małym runtime'em w C.
+Dokładne reguły typów; model własności i to, jak tłumaczy się na kopie i zwolnienia; jak każda
+konstrukcja staje się IR (z prawdziwym wyjściem kompilatora); obsługa nagłówków C, struktur C i ABI
+System V; moduły, informacje dla debuggera i kompilator samohostujący z `boot/`. Każda funkcja
+kompilatora jest opisana razem z tym, czym różni się od sąsiednich.
Zakładam, że znasz C/C++ i widziałeś już LLVM IR.
Język z perspektywy użytkownika opisuje [przewodnik dla techników](dla-technikow.md).
@@ -20,61 +21,65 @@ Język z perspektywy użytkownika opisuje [przewodnik dla techników](dla-techni
3. [Lekser](#3-lekser)
4. [Gramatyka i parser](#4-gramatyka-i-parser)
5. [AST](#5-ast)
-6. [Model semantyczny](#6-model-semantyczny)
-7. [System typów](#7-system-typów)
-8. [Generowanie kodu](#8-generowanie-kodu)
-9. [Kontrole w czasie działania](#9-kontrole-w-czasie-działania)
-10. [Współpraca z C przez libclang](#10-współpraca-z-c-przez-libclang)
-11. [Optymalizacja, emisja, linkowanie](#11-optymalizacja-emisja-linkowanie)
-12. [Diagnostyka](#12-diagnostyka)
-13. [System budowania](#13-system-budowania)
-14. [Testy](#14-testy)
-15. [Semantyka a C: zachowania zdefiniowane i niezdefiniowane](#15-semantyka-a-c-zachowania-zdefiniowane-i-niezdefiniowane)
-16. [Spis funkcji: kto co robi](#16-spis-funkcji-kto-co-robi)
-17. [Rozbudowa kompilatora](#17-rozbudowa-kompilatora)
-18. [Znane ograniczenia i plan](#18-znane-ograniczenia-i-plan)
+6. [Wczytywanie: pliki i moduły](#6-wczytywanie-pliki-i-moduły)
+7. [Model semantyczny](#7-model-semantyczny)
+8. [System typów](#8-system-typów)
+9. [Własność i pamięć](#9-własność-i-pamięć)
+10. [Generowanie kodu](#10-generowanie-kodu)
+11. [Kontrole w czasie działania](#11-kontrole-w-czasie-działania)
+12. [Runtime w C](#12-runtime-w-c)
+13. [Współpraca z C: nagłówki, struktury, ABI](#13-współpraca-z-c-nagłówki-struktury-abi)
+14. [Optymalizacja, emisja, linkowanie](#14-optymalizacja-emisja-linkowanie)
+15. [Informacje dla debuggera](#15-informacje-dla-debuggera)
+16. [Diagnostyka](#16-diagnostyka)
+17. [Kompilator samohostujący (boot/)](#17-kompilator-samohostujący-boot)
+18. [System budowania](#18-system-budowania)
+19. [Testy](#19-testy)
+20. [Semantyka a C](#20-semantyka-a-c)
+21. [Spis funkcji](#21-spis-funkcji)
+22. [Rozbudowa kompilatora](#22-rozbudowa-kompilatora)
+23. [Znane ograniczenia](#23-znane-ograniczenia)
---
## 1. Architektura
-Finch to klasyczny jednoprzebiegowy front-end przed LLVM. Sprawdzanie typów i generowanie IR
-odbywają się w jednym przejściu po AST. Nie ma osobnego przebiegu semantycznego ani własnej
-reprezentacji pośredniej.
+Finch to jednoprzebiegowy front-end przed LLVM: sprawdzanie typów i generowanie IR to jedno przejście
+po AST, bez własnej reprezentacji pośredniej.
```
- plik.fn
- │ readFile() src/main.cpp
- ▼
- lex() ──────────► std::vector src/lexer.cpp
- ▼
- parse() ─────────► Program (AST) src/parser.cpp, src/ast.h
- │ ├─ imports ──► importHeaders() ──► CImports src/cimport.cpp (libclang)
- │ └─ links
- ▼
- hostMachine() ───► llvm::TargetMachine (triple, data layout)
- ▼
- generate() ──────► llvm::Module (typy + IRBuilder, potem verifyModule) src/codegen.cpp
- ▼
- optimize() ──────► potok PassBuilder default (pomijany przy -O0)
- ▼
- emitObject() ────► plik.o (legacy::PassManager + addPassesToEmitFile)
- ▼
- link() ──────────► cc plik.o -o plik -lm [-l… | pkg-config --libs …]
- ▼
- plik wykonywalny (finch run: uruchamiany z pliku tymczasowego, potem usuwany)
+ main.fn ──► Loader::load() ── lex() ── parse() ──► Program (+ każdy importowany moduł, rekurencyjnie)
+ │
+ wszystkie `import "x.h"` ──► importHeaders() (libclang) ──► CImports
+ │
+ hostMachine() ──► TargetMachine (triple + data layout)│
+ ▼
+ generate(progs, cimports, tm, debug) ──► Codegen::run()
+ declareStructs() wszystkie struktury Fincha i C, ciała typów LLVM, kontrola cykli
+ declareFns() sygnatura każdej funkcji (+ analiza „pożyczania” parametrów)
+ define() każde ciało: typy + IRBuilder, zasięgi, zwalnianie, kontrole
+ defineMainWrapper C-owe `main(argc, argv)`
+ DIBuilder::finalize (z -g) ; verifyModule
+ ▼
+ optimize() PassBuilder default (pomijane przy -O0)
+ emitObject() legacy PM + addPassesToEmitFile ──► prog.o
+ link() cc prog.o ~/.cache/finch/rt-.o [twoje .c/.o/.a] -lm [pkg-config/-l…]
+ ▼
+ plik wykonywalny (finch run: uruchamiany z pliku tymczasowego z resztą argumentów, potem usuwany)
```
-Decyzje, które kształtują całą resztę:
+Decyzje, które kształtują resztę:
-- **Szybka porażka.** Pierwszy błąd wypisuje diagnostykę i wywołuje `exit(1)` (`fail()` w `src/error.h`).
- Nie ma odtwarzania po błędach, więc AST i codegen nigdy nie widzą częściowo poprawnego wejścia.
-- **Jedno przejście.** `Codegen` niesie stan zasięgów, pętli i bieżącej funkcji. Każde wyrażenie
- zwraca jednocześnie wartość IR i swój typ Fincha (`Value_`).
-- **Niech LLVM robi robotę.** Zmienne lokalne to `alloca`, a `mem2reg`/SROA zamieniają je na SSA.
- Finch nigdy sam nie buduje SSA ani węzłów φ, z wyjątkiem `&&`/`||`.
-- **Systemowy toolchain C jest środowiskiem uruchomieniowym.** `printf`, `strcmp`, `dprintf`, `exit`
- pochodzą z libc. Finch nie ma własnej biblioteki uruchomieniowej.
+- **Szybka porażka.** Pierwszy błąd wypisuje diagnostykę i kończy proces (`failAt()` w `src/error.h`).
+ Bez odtwarzania po błędach, więc dalsze fazy nigdy nie widzą częściowo poprawnego wejścia.
+- **Jedno przejście.** `Codegen` niesie zasięgi, pętle, bieżącą funkcję i moduł. Każde wyrażenie zwraca
+ wartość IR, typ Fincha i dwie flagi (`Value_`, §8).
+- **Niech LLVM robi robotę.** Zmienne lokalne to `alloca`, które `mem2reg`/SROA zamieniają na rejestry.
+ Węzły φ Finch buduje ręcznie tylko dla `&&`/`||`.
+- **Wartości, nie referencje.** Tablice, teksty i struktury mają semantykę wartości: przypisanie kopiuje,
+ koniec bloku zwalnia. Własność rozstrzyga się w czasie kompilacji, bez liczenia referencji (§9).
+- **Malutki runtime w C** (`runtime/finch_rt.c`, ok. 430 linii) robi to, co w IR byłoby żmudne: operacje na
+ tekstach, wzrost tablic, wejście, pliki, panic. Jest wbudowany w kompilator i trzymany w cache.
---
@@ -82,1055 +87,810 @@ Decyzje, które kształtują całą resztę:
| Plik | Linie | Odpowiedzialność |
|---|---|---|
-| `src/error.h` | ~25 | `fail(line, col, msg)`: wypisuje `plik:linia:kol: error:` i fragment kodu z `^`, kończy proces. Trzyma `g_file`, `g_source`. |
-| `src/lexer.h/.cpp` | ~240 | enum `Tok`, `Token`, `lex()`, `tokName()` |
-| `src/ast.h` | ~235 | `Type`, wszystkie węzły `Expr`/`Stmt`, `FnDecl`, `Import`, `Link`, `Program` |
-| `src/parser.h/.cpp` | ~430 | Parser zstępujący `Parser`, `typeFromName()` |
-| `src/cimport.h/.cpp` | ~280 | Import nagłówków przez libclang: `importHeaders()` → `CImports` (funkcje, stałe, zmienne globalne) |
-| `src/codegen.h/.cpp` | ~900 | `Codegen`: sprawdzanie typów + generowanie LLVM IR, `generate()` |
-| `src/main.cpp` | ~260 | Sterownik: CLI, maszyna docelowa, optymalizacja, emisja, linkowanie, analiza błędów linkera, `run` |
-| `tests/run.sh` | | Testy wzorcowego wyjścia i oczekiwanych błędów |
+| `src/error.h` | 40 | `g_files` (wszystkie źródła), `failAt(file, line, col, msg)`, `fail(line, col, msg)` |
+| `src/lexer.h/.cpp` | 250 | `Tok`, `Token`, `lex(indeksPliku)`, `tokName()` |
+| `src/ast.h` | 310 | `Type` (z Array, Fixed, Struct, Named), wszystkie węzły `Expr`/`Stmt`, `FnDecl`, `StructDecl`, `Import`, `Link`, `Program` |
+| `src/parser.h/.cpp` | 540 | Parser zstępujący `Parser`, `typeFromName()` |
+| `src/cimport.h/.cpp` | 420 | Import przez libclang: funkcje, struktury C (pola, przesunięcia), stałe, zmienne globalne |
+| `src/codegen.h` | 15 | `generate()` |
+| `src/codegen_impl.h` | 265 | Klasa `Codegen` i jej struktury pomocnicze, wspólne dla czterech plików poniżej |
+| `src/codegen.cpp` | 1090 | Program, deklaracje, zasięgi i sprzątanie, instrukcje, wyrażenia, miejsca (`ref`), operatory, analiza modyfikacji |
+| `src/codegen_types.cpp` | 500 | Typy LLVM, rozwiązywanie typów, układ struktur, konwersje, własność (helpery kopiowania/zwalniania), wypisywanie, typy DWARF |
+| `src/codegen_builtins.cpp` | 710 | Wywołania, konstruktory, funkcje wbudowane, konwersje, metody tablic i tekstów, deklaracje runtime'u, panic |
+| `src/abi.cpp` | 310 | Klasyfikacja System V x86-64, wywołania C ze strukturami przez wartość, wrappery wywoływalne z C |
+| `src/main.cpp` | 390 | Sterownik: CLI, ładowanie, maszyna docelowa, O2, emisja, cache runtime'u, linkowanie, porady przy błędach linkera, `run` |
+| `runtime/finch_rt.c` | 440 | Biblioteka uruchomieniowa (§12) |
+| `boot/*.fn` | 3080 | Kompilator samohostujący (§17) |
---
## 3. Lekser
-`lex(src, file)` przechodzi raz, liniowo, przez źródło i zwraca `std::vector`
-zakończony `Tok::End`.
+`lex(file)` przechodzi raz przez `g_files[file].text` i zwraca `std::vector` zakończony `Tok::End`.
```cpp
-struct Token {
- Tok kind;
- std::string text; // treść identyfikatora / literału (sekwencje \ już zdekodowane)
- int line, col; // od 1
- bool newlineBefore; // między poprzednim tokenem a tym był znak nowej linii
-};
+struct Token { Tok kind; std::string text; int line, col; bool newlineBefore; };
```
-### Nowe linie jako koniec instrukcji
-
-Finch nie ma średników. Zamiast emitować tokeny `NEWLINE`, jak robią to wewnętrznie Python i Go,
-lekser **oznacza** każdy token flagą `newlineBefore`. Co ona znaczy, decyduje parser (rozdział 4).
-Dzięki temu gramatyka nie zawiera tokenów nowej linii, które trzeba by wszędzie pomijać:
-w listach argumentów, między `}` a `else` i tak dalej.
-
-### Tokeny
-
-- **Pozycje:** `line` i `col` liczone są od 1, a `col` liczy **znaki**, nie bajty.
- Bajty kontynuacji UTF-8 jej nie zwiększają, więc `^` trafia we właściwe miejsce także w linijkach z `"Błąd"`.
- Nieoczekiwany znak spoza ASCII jest pokazywany w całości (`'ż'`), z podpowiedzią, że nazwy są tylko ASCII.
-- **Identyfikatory / słowa kluczowe:** `[A-Za-z_][A-Za-z0-9_]*`. Słowa kluczowe: `fn return if else while for in break continue true false null import link`.
- **Nazwy typów nie są słowami kluczowymi.** `int`, `u8`, `ptr`… są leksowane jako `Ident`, a parser rozpoznaje je przez `typeFromName()`.
- Lekser nie zależy więc od listy typów, a `u8(x)` parsuje się jak zwykłe wywołanie.
-- **Liczby całkowite:** dziesiętne albo szesnastkowe `0x`, z separatorami `_` (usuwanymi z `text`).
- Zera wiodące nic nie zmieniają: Finch nie ma ósemkowych, `010` to 10. Wartość liczy parser (`strtoull`, podstawa 10 lub 16).
-- **Liczby zmiennoprzecinkowe:** `cyfry.cyfry`. Kropka należy do liczby tylko wtedy, gdy **po niej jest cyfra**,
- więc `0..10` to `Int DotDot Int`, a nie `Float(0.) Dot …`.
-- **Teksty / znaki:** `"…"` i `'…'`. Sekwencje `\n \t \r \0 \\ \" \'` są dekodowane do `text`.
- Nieznana sekwencja, niezamknięty literał albo nowa linia w środku literału to błąd. Literał znakowy musi dać dokładnie jeden bajt.
-- **Operatory:** zasada najdłuższego dopasowania: `->` przed `-`, `<<` i `<=` przed `<`, `&&` przed `&`, `..` przed `.`, `:=` (samotny `:` to błąd z podpowiedzią `:=`).
-- **Komentarze:** `//` do końca linii, `/* … */` (bez zagnieżdżania; niezamknięty to błąd).
+- **Nowe linie nie są tokenami.** Każdy token zapamiętuje `newlineBefore`, a o znaczeniu decyduje
+ parser (§4). Dzięki temu obsługa nowych linii nie zaśmieca każdej reguły listowej w gramatyce.
+- **Pozycje:** od 1; `col` liczy znaki (bajty kontynuacji UTF-8 jej nie zwiększają), więc `^` trafia
+ pod `"Błąd"`. Zabłąkany znak spoza ASCII jest pokazywany w całości z podpowiedzią, że nazwy są tylko ASCII.
+- **Identyfikatory / słowa kluczowe:** `[A-Za-z_][A-Za-z0-9_]*`. Słowa kluczowe:
+ `fn return if else while for in break continue true false null import link struct defer`.
+ **Nazwy typów nie są słowami kluczowymi**: `int`, `u8`, `ptr`… to `Ident`, rozpoznawane przez parser
+ za pomocą `typeFromName()`, więc `u8(x)` jest zwykłym wywołaniem, a lekser nie zależy od listy typów.
+- **Liczby:** dziesiętne albo `0x` szesnastkowe z separatorami `_`. Bez ósemkowych (`010` to 10).
+ `.` należy do liczby tylko wtedy, gdy po niej jest cyfra, więc `0..10` to `Int DotDot Int`.
+- **Teksty / znaki:** sekwencje `\n \t \r \0 \\ \" \'`. Bez nowej linii w literale.
+- **Operatory:** najdłuższe dopasowanie (`->` przed `-`, `<<`/`<=` przed `<`, `&&` przed `&`, `..` przed `.`,
+ `:=` przed `:`). Samotny `:` to token (argumenty nazwane).
+- **Komentarze:** `//` i niezagnieżdżane `/* */`.
---
## 4. Gramatyka i parser
-`Parser` (`src/parser.cpp`) to ręcznie pisany parser zstępujący z jednym tokenem wyprzedzenia
-(`cur()`), a w dwóch miejscach z drugim (`peekTok()`).
-
-### Gramatyka (EBNF)
+`Parser` (`src/parser.cpp`) to ręcznie pisany parser zstępujący z jednym tokenem wyprzedzenia, a w
+`atDeclaration()` z maksymalnie trzema.
```ebnf
-program = { import | link | function } ;
-import = "import" STRING ;
-link = "link" STRING ;
+program = { import | link | struct | function } ;
+import = "import" ( STRING | IDENT ) ; (* "x.h" = nagłówek C, nazwa = moduł Fincha *)
+link = "link" STRING ; (* "glfw" | "plik.c" | "plik.o" | "lib.a" *)
+struct = "struct" IDENT "{" { type IDENT [ "=" expr ] NOWA_LINIA } "}" ;
function = "fn" IDENT "(" [ param { "," param } ] ")" [ "->" type ] block ;
param = type IDENT ;
-type = TYPENAME | "ptr" "[" type "]" ; (* TYPENAME: int float bool char str ptr i8..u64 f32 f64 *)
+type = "[" "]" type | "ptr" [ "[" type "]" ] | TYPENAME | IDENT [ "." IDENT ] ;
block = "{" { statement TERMINATOR } "}" ;
-statement = if | while | for | return | "break" | "continue" | block
- | type IDENT [ "=" expr ] (* deklaracja *)
- | IDENT ":=" expr (* deklaracja z wnioskowaniem *)
- | target assignop expr (* target: IDENT albo postfix kończący się .value *)
- | call ;
-if = "if" expr block [ "else" ( if | block ) ] ;
-while = "while" expr block ;
-for = "for" IDENT "in" expr ".." expr block ;
-return = "return" [ expr ] ; (* expr tylko w tej samej linii *)
+statement = if | while | for | return | "break" | "continue" | "defer" statement | block
+ | type IDENT [ "=" expr ] (* deklaracja, zobacz atDeclaration *)
+ | IDENT ":=" expr
+ | target assignop expr (* cel: zmienna, pole, element, p.value *)
+ | call | method ;
+for = "for" IDENT "in" expr ( ".." expr block | block ) ; (* zakres | po elementach *)
assignop = "=" | "+=" | "-=" | "*=" | "/=" | "%=" ;
-expr = or ;
-or = and { "||" and } ;
-and = cmp { "&&" cmp } ;
+expr = or ; or = and { "||" and } ; and = cmp { "&&" cmp } ;
cmp = add { ( "==" | "!=" | "<" | "<=" | ">" | ">=" ) add } ;
add = mul { ( "+" | "-" | "|" | "^" ) mul } ;
mul = unary { ( "*" | "/" | "%" | "<<" | ">>" | "&" ) unary } ;
unary = ( "-" | "!" | "~" ) unary | postfix ;
-postfix = primary { "." IDENT } ;
+postfix = primary { "." IDENT [ args ] | "[" expr "]" } ;
primary = INT | FLOAT | STRING | CHAR | "true" | "false" | "null"
- | IDENT [ "(" [ expr { "," expr } ] ")" ]
- | "(" expr ")" ;
+ | IDENT [ args ] | "(" expr ")" | "[" [ expr { "," expr } [","] ] "]" ;
+args = "(" [ arg { "," arg } ] ")" ; arg = [ IDENT ":" ] expr ;
```
-### Reguła końca instrukcji
+**Koniec instrukcji.** Po instrukcji `endOfStatement()` wymaga `}`, końca pliku albo tokenu
+z `newlineBefore`. Wewnątrz wyrażeń `sameLine()` (`parenDepth > 0 || !newlineBefore`) decyduje, czy
+operator, `.`, `[` albo `(` wywołania kontynuuje wyrażenie. `parenDepth` rośnie w `( )` i `[ ]`,
+a przy wejściu do `{ }` jest zapamiętywany i zerowany.
-`TERMINATOR` nie jest tokenem. Po każdej instrukcji `endOfStatement()` wymaga, żeby następny
-token był `}`, końcem pliku albo miał `newlineBefore == true`. W przeciwnym razie zgłasza błąd
-`put each statement on its own line`.
+**Priorytety są jak w Go**, nie jak w C: `&` wiąże jak `*`, a `|`/`^` jak `+`, wszystkie powyżej porównań,
+więc `x & MASKA == 0` znaczy `(x & MASKA) == 0`.
-Wewnątrz wyrażeń `sameLine()` decyduje, czy operator binarny, `.` albo `(` wywołania
-**kontynuuje** bieżące wyrażenie:
+**Rozpoznawanie instrukcji.** `atDeclaration()` zwraca prawdę dla `[` `]` (typ tablicowy), `TYP IDENT`,
+`ptr [` i `IDENT . IDENT IDENT` (typ z modułu). W pozostałych przypadkach parser **parsuje całe
+wyrażenie, a potem patrzy na następny token**: operator przypisania robi z niego cel (musi być `Var`,
+`Member` albo `Index`), a inaczej musi to być wywołanie funkcji lub metody, w przeciwnym razie
+`this value is computed but never used`.
-```cpp
-bool sameLine() const { return parenDepth > 0 || !cur().newlineBefore; }
-```
-
-Czyli:
-
-```c
-a := 1 + // '+' jest w tej linii → wyrażenie przechodzi do następnej
- 2
-b := (1 // w nawiasach (parenDepth > 0) nowe linie są ignorowane
- + 2)
-c := 1
--5 // '-' zaczyna nową linię → instrukcja skończyła się na '1'; "-5" to wtedy błąd
-```
-
-`parenDepth` rośnie wokół grup `( … )`, list argumentów i list parametrów. Przy wejściu do bloku
-`{ }` jest **zapamiętywany i zerowany**, więc blok wewnątrz nawiasów (dziś to się nie zdarza)
-i tak używałby reguł linii.
-
-`(` wywołania musi być w tej samej linii co nazwa funkcji. Inaczej `f⏎(x)` po cichu stałoby się wywołaniem.
-
-### Priorytety: jak w Go, nie jak w C
-
-| Poziom | Operatory |
-|---|---|
-| 5 | `*` `/` `%` `<<` `>>` `&` |
-| 4 | `+` `-` `\|` `^` |
-| 3 | `==` `!=` `<` `<=` `>` `>=` |
-| 2 | `&&` |
-| 1 | `\|\|` |
-
-Wszystkie operatory binarne są lewostronnie łączne. Porównania też (`a < b < c` się sparsuje),
-ale sprawdzanie typów odrzuci potem porównanie `bool < int`. C stawia `&`, `^`, `|` **poniżej** `==`,
-przez co `x & MASKA == 0` znaczy `x & (MASKA == 0)`. Finch przyjmuje tabelę z Go, co usuwa całą tę klasę błędów.
-
-### Jak rozpoznawane są instrukcje
-
-`statement()` rozgałęzia się po pierwszym tokenie:
-
-1. Słowa kluczowe (`if`, `while`, `for`, `return`, `break`, `continue`, `{`) są obsługiwane wprost.
- `fn`, `import` i `link` wewnątrz bloku dają celowane komunikaty.
-2. **Deklaracja z typem:** `atDeclaration()` zwraca prawdę, gdy bieżący token jest nazwą typu,
- a następny identyfikatorem (`int x`), albo gdy to `ptr`, a po nim `[` (`ptr[int] p`).
- To jedyne miejsce wymagające dwóch tokenów wyprzedzenia.
-3. **Deklaracja z wnioskowaniem:** `IDENT`, a po nim `:=`.
-4. W pozostałych przypadkach parser **najpierw parsuje całe wyrażenie**, a potem patrzy na następny
- token. Jeśli to operator przypisania w tej samej linii, wyrażenie staje się celem przypisania.
- Musi być `VarExpr` albo `MemberExpr`, inaczej: `only a variable or p.value can be assigned to`.
- Takie podejście („najpierw wyrażenie, potem decyzja”) obsługuje dowolne formy l-wartości
- (`pp.value.value = 1`) bez osobnej gramatyki l-wartości.
-5. Samodzielne wyrażenie jako instrukcja musi być `CallExpr`. Wszystko inne daje
- `this value is computed but never used`.
-
-### Poziom pliku
-
-`program()` przechodzi przez `import`, `link` i `fn`. Element zaczynający się nazwą typu dostaje
-podpowiedź `functions start with 'fn', like: fn add(int a, int b) -> int { ... }`, co łapie
-definicje funkcji pisane jak w C. Argument `link` jest walidowany: `"libfoo.so"` albo ścieżka
-są odrzucane z podpowiedzią samej nazwy.
+**Literały struktur to wywołania**: `Punkt(1, 2)` albo `Punkt(x: 1, y: 2)`. Literały w klamrach
+(`Punkt{…}`) byłyby niejednoznaczne z blokami (`if gotowe {`); wywołanie nie potrzebuje specjalnej
+gramatyki, a argumenty nazwane (`IDENT ":"` w `args()`) są akceptowane tylko przez konstruktory na etapie generowania kodu.
---
## 5. AST
-Wszystkie węzły są w `src/ast.h`. Wyrażenia i instrukcje to małe hierarchie klas z jawnym
-znacznikiem `kind`. Kod je przetwarzający robi `switch` po `kind` i `static_cast`, co pozwala
-obejść się bez RTTI i `dynamic_cast` i sprawia, że rozgałęzienia są oczywiste.
-
-```cpp
-struct Expr { ExprKind kind; Pos pos; virtual ~Expr(); };
-using ExprPtr = std::unique_ptr;
-```
+Wszystkie węzły są w `src/ast.h`: małe hierarchie klas z jawnym `kind`, rozgałęziane przez
+`switch` + `static_cast`.
| ExprKind | Węzeł | Pola |
|---|---|---|
-| `Int` | `IntExpr` | `long long value` |
-| `Float` | `FloatExpr` | `double value` |
-| `Bool` / `Char` / `Str` | `BoolExpr` / `CharExpr` / `StrExpr` | `value` |
+| `Int` `Float` `Bool` `Char` `Str` | literały | `value` |
| `Null` | `NullExpr` | |
| `Var` | `VarExpr` | `name` |
-| `Unary` | `UnaryExpr` | `char op` (`-` `!` `~`), `operand` |
+| `Unary` | `UnaryExpr` | `op` (`- ! ~`), `operand` |
| `Binary` | `BinaryExpr` | `BinOp op`, `lhs`, `rhs` |
-| `Call` | `CallExpr` | `std::string callee`, `args` |
-| `Member` | `MemberExpr` | `obj`, `field` (w 1.0 ma sens tylko `value`) |
+| `Call` | `CallExpr` | `callee` (nazwa), `args`, `argNames` |
+| `Member` | `MemberExpr` | `obj`, `field` (`.x`, `.len`, `.ptr`, `.value`) |
+| `Index` | `IndexExpr` | `obj`, `index` |
+| `ArrayLit` | `ArrayLitExpr` | `elems` |
+| `Method` | `MethodExpr` | `obj`, `name`, `args`, `argNames`; także `modul.fn(...)` |
-| StmtKind | Węzeł | Pola |
-|---|---|---|
-| `Block` | `BlockStmt` | `std::vector body` |
-| `VarDecl` | `VarDeclStmt` | `hasType`, `type`, `name`, `init` (może być null, gdy `hasType`) |
-| `Assign` | `AssignStmt` | `target` (Var/Member), `char op` (`= + - * / %`), `value` |
-| `Expr` | `ExprStmt` | `expr` (zawsze wywołanie) |
-| `If` | `IfStmt` | `cond`, `then`, `otherwise` (null, `BlockStmt` albo zagnieżdżony `IfStmt` dla `else if`) |
-| `While` | `WhileStmt` | `cond`, `body` |
-| `For` | `ForStmt` | `var`, `from`, `to`, `body` |
-| `Return` | `ReturnStmt` | `value` (może być null) |
-| `Break` / `Continue` | | |
+| StmtKind | Węzeł |
+|---|---|
+| `Block` `VarDecl` `Assign` `Expr` `If` `While` `For` `ForEach` `Return` `Break` `Continue` `Defer` | jak w nazwie; `ForEachStmt` ma `var`, `list`, `body`; `DeferStmt` trzyma jedną instrukcję |
-Wywołania przechowują funkcję **po nazwie**, a nie jako wyrażenie. Finch nie ma funkcji jako
-wartości, a rozwiązywanie nazw dopiero w codegenie pozwala, żeby jeden `CallExpr` oznaczał
-funkcję wbudowaną (`print`, `addr`), konwersję (`u8`), funkcję Fincha albo funkcję C (rozdział 8.10).
+`math.sqrt(x)` parsuje się jako `MethodExpr` na `VarExpr("math")`; to, czy `math` jest modułem, czy zmienną,
+rozstrzyga się przy generowaniu kodu. Wywołania nazywają funkcję, zamiast trzymać wyrażenie: Finch nie ma
+funkcji jako wartości, a późne rozwiązywanie nazwy pozwala, by jeden `CallExpr` był funkcją wbudowaną,
+konwersją (`u8(x)`), konstruktorem struktury, funkcją Fincha albo funkcją C.
-### Przykład
-
-```c
-x += add(x, 2)
-```
-
-parsuje się do:
-
-```
-AssignStmt (op '+')
-├─ target: VarExpr "x"
-└─ value: CallExpr "add"
- ├─ VarExpr "x"
- └─ IntExpr 2
-```
-
-**W AST nie ma węzła „load”.** To, czy `VarExpr` znaczy „adres x”, czy „wartość w x”, zależy
-od miejsca, w którym występuje. Decyduje o tym codegen (`place()` kontra `expr()`, rozdział 8.2).
+Nie ma węzła „load”: to, czy `x` znaczy adres, czy wartość, zależy od kontekstu (`ref()` kontra `expr()`, §10.2).
### `Type`
```cpp
struct Type {
- enum Kind { Void, Bool, Char, I8, I16, I32, I64, U8, U16, U32, U64, F32, F64, Str, Ptr, Null };
+ enum Kind { Void, Bool, Char, I8, I16, I32, I64, U8, U16, U32, U64, F32, F64,
+ Str, Ptr, Null, Array, Fixed, Struct, Named };
Kind kind;
- std::shared_ptr elem; // tylko Ptr: typ wskazywany; null = `ptr` bez typu
+ std::shared_ptr elem; // Ptr (null = `ptr` bez typu), Array, Fixed
+ long long count; // Fixed
+ std::string name, module; // Named (jak napisano) / Struct (rozwiązany)
+ StructInfo *info; // Struct
};
```
-- `int` i `i64` to **ten sam typ** (`I64`), a `name()` wypisuje go jako `int`. Analogicznie `float` to `F64`.
-- `Null` to typ wyłącznie literału `null`. Żadna zmienna nie może go mieć.
-- `Void` to „typ” wywołań, które nic nie zwracają, i funkcji bez `->`.
-- Równość jest strukturalna (`ptr[ptr[int]] == ptr[ptr[int]]`).
-- Rodzaje są ułożone tak, że `isInt()` to `I8..U64`, `isSigned()` to `I8..I64`, a `isUnsigned()` to `U8..U64`.
+Parser tworzy `Named` dla nazw struktur; `Codegen::resolve()` zamienia je na `Struct` ze wskaźnikiem
+`info`. `Fixed` (`[N]T`) pochodzi tylko z pól struktur C. `int` to `I64`, a `float` to `F64`.
+Równość jest strukturalna; dwa `Struct` są równe, gdy mają to samo `info`.
---
-## 6. Model semantyczny
+## 6. Wczytywanie: pliki i moduły
-Całe sprawdzanie semantyczne odbywa się w `Codegen` podczas emisji IR.
+`Loader::load(path, module, from)` w `main.cpp` czyta plik, dopisuje go do `g_files` (pozycje niosą
+indeks pliku w `Pos::file`), leksuje go i parsuje, a potem wczytuje każdy jeszcze niewidziany `import nazwa`:
+`nazwa.fn` obok importującego pliku albo w folderze z `FINCH_PATH`. Wynik to `std::vector`,
+z głównym plikiem na początku. Cykle nie przeszkadzają: moduł jest oznaczany jako wczytany, zanim
+zaczniemy podążać za jego importami.
-### Funkcje
+Wszystkie pliki trafiają do **jednego modułu LLVM**. Każdy moduł Fincha ma `ModuleScope` (funkcje,
+struktury, zbiór modułów, które importuje). Nazwy szuka się w bieżącym module; nazwy innego modułu
+są dostępne tylko z kwalifikatorem (`geometry.length`, `geometry.Vec`) i tylko tam, gdzie moduł
+zaimportowano. Funkcje są emitowane jako `finch..` (`finch.` w głównym pliku),
+typy struktur jako `finch..`.
-`Codegen::run()` robi dwa przejścia po `Program::fns`:
+---
-1. `declare()` tworzy każdą `llvm::Function` (tylko sygnaturę), więc **kolejność definicji nie ma
- znaczenia** i działa rekurencja wzajemna. Sprawdza też: duplikaty nazw, nazwy wbudowane i sygnaturę
- `main` (bez parametrów; nic nie zwraca, zwraca `int` albo `i32`).
-2. `define()` generuje ciała.
+## 7. Model semantyczny
-Bez `main` kompilacja kończy się błędem `there is no main function`.
+### Kolejność deklaracji
-### Zasięgi
+`Codegen::run()` najpierw deklaruje **wszystkie struktury** wszystkich modułów i zaimportowane struktury C,
+rozwiązuje typy pól i ustawia ciała typów LLVM (`declareStructs` → `resolveStruct`), potem deklaruje
+**wszystkie sygnatury funkcji** (`declare`), a dopiero potem definiuje ciała. Kolejność w pliku i między
+modułami nigdy więc nie ma znaczenia, a rekurencja (także wzajemna i między modułami) działa.
-`scopes` to `std::vector>`. Nowa mapa jest wkładana dla
-parametrów funkcji, dla każdego bloku `{ }` i dla każdej pętli `for` (trzyma zmienną pętli).
+`resolveStruct` wykrywa cykle przez wartość trójstanową flagą (`struct A { A inner }` to błąd z radą,
+by użyć `ptr[A]` albo `[]A`). Wskaźniki i tablice struktury nie potrzebują jej układu, więc
+`struct Wezel { []Wezel dzieci; ptr[Wezel] rodzic }` jest w porządku: rozwiązują nazwę „płytko”
+(`resolveT(…, deep=false)`).
+
+### Zasięgi i zmienne
```cpp
-struct Var { AllocaInst *slot; FType type; bool readonly; };
+struct Var { Value *slot; FType type; bool readonly; std::string readonlyWhy;
+ bool owned; int order; bool moved; };
+struct Cleanup { bool isDefer; std::string var; const Stmt *body; int order; };
+struct Scope { unordered_map vars; vector cleanups; };
```
-**Bez przesłaniania:** `addVar()` odrzuca nazwę, którą `lookup()` znajdzie w *dowolnym*
-zewnętrznym zasięgu, a nie tylko w bieżącym. Odrzuca też nazwy funkcji i wbudowanych.
-To świadoma reguła prostoty: w całym ciele funkcji nazwa znaczy jedną rzecz.
+Zasięg jest otwierany dla parametrów funkcji, każdego `{ }`, każdego `for` (trzyma `i`), każdego `for … in`
+(ukryta tymczasowa lista) i każdej jego iteracji (trzyma `x`).
-**Tylko do odczytu:** zmienne `for` mają `readonly`. Przypisanie do nich albo `addr()` na nich
-to błąd, więc licznika pętli nie da się zmienić za jej plecami.
+- **Bez przesłaniania:** `addVar()` odrzuca nazwę znalezioną w *dowolnym* zewnętrznym zasięgu,
+ funkcję modułu, nazwę zaimportowanego modułu i funkcję wbudowaną.
+- **Tylko do odczytu:** zmienne pętli zakresowej i pętli po elementach (`readonlyWhy` daje konkretny komunikat).
+- `order` numeruje deklaracje; `defer` używa go, by ukryć zmienne zadeklarowane po nim (§9.5).
-### Rozwiązywanie identyfikatorów
+### Rozwiązywanie nazw
-Dla `VarExpr name` w pozycji wartości kolejność jest taka:
-
-1. zmienna lokalna (`lookup`),
-2. stała C (`cimports.consts`: wartości enum, liczby z `#define`),
-3. zmienna globalna C (`cimports.globals`: `stdout`, …),
-4. błąd `there is no variable named 'name'`.
-
-Dla wywołań: `print` → `addr` → nazwa typu (konwersja) → funkcja Fincha → funkcja C → błąd.
-Ponieważ funkcje Fincha są sprawdzane przed funkcjami C, **funkcja Fincha przesłania funkcję C
-o tej samej nazwie**. Funkcja Fincha jest emitowana jako `finch.`, więc oba symbole
-współistnieją przy linkowaniu.
+`x` w pozycji wartości: zmienna lokalna → stała C → zmienna globalna C → (nazwa modułu: błąd z podpowiedzią) → błąd.
+Wywołanie `n(...)`: funkcje wbudowane (`print addr input new free exit shell read_file write_file file_exists`) →
+nazwy typów (konwersja) → funkcja bieżącego modułu → struktura bieżącego modułu / struktura C
+(konstruktor) → funkcja C → błąd (z podpowiedzią, jeśli funkcja istnieje w innym module, albo z
+`headerHint` dla znanych nazw z libc).
---
-## 7. System typów
+## 8. System typów
### Reprezentacja w LLVM
-| Finch | LLVM | Uwagi |
-|---|---|---|
-| `bool` | `i1` | |
-| `char`, `i8`, `u8` | `i8` | znakowość jest w typie Fincha, nie w LLVM |
-| `i16`, `u16` | `i16` | |
-| `i32`, `u32` | `i32` | |
-| `int`/`i64`, `u64` | `i64` | |
-| `f32` | `float` | |
-| `float`/`f64` | `double` | |
-| `str` | `ptr` | napis C zakończony zerem |
-| `ptr`, `ptr[T]` | `ptr` | LLVM 21 ma nieprzezroczyste wskaźniki; typ wskazywany istnieje tylko w typie Fincha |
-| `null` | `ptr` | `ConstantPointerNull` |
-| nic | `void` | |
+| Finch | LLVM |
+|---|---|
+| `bool` | `i1` |
+| `char` `i8` `u8` / `i16` `u16` / `i32` `u32` / `int` `u64` | `i8` / `i16` / `i32` / `i64` |
+| `f32` / `float` | `float` / `double` |
+| `str`, `[]T` | `{ ptr, i64, i64 }` (wskaźnik, długość, pojemność) |
+| `ptr`, `ptr[T]`, `null` | `ptr` (nieprzezroczysty) |
+| `[N]T` | `[N x T]` |
+| struktura Fincha | nazwany typ struct, naturalny układ |
+| struktura C | nazwany **upakowany** (packed) struct z jawnymi bajtami wypełnienia (§13.2) |
-Liczby całkowite w LLVM nie mają znaku. Każda operacja, w której znak ma znaczenie
-(`sdiv`/`udiv`, `srem`/`urem`, `ashr`/`lshr`, `icmp slt`/`ult`, `sext`/`zext`,
-`sitofp`/`uitofp`, `fptosi`/`fptoui`), wybiera instrukcję na podstawie typu **Fincha**.
+Znakowość istnieje tylko w typie Fincha i wybiera `sdiv/udiv`, `ashr/lshr`, `icmp s*/u*`,
+`sext/zext`, `sitofp/uitofp`, `fptosi/fptoui`.
-### Wartości w codegenie
+### Wartości
```cpp
-struct Value_ {
- Value *v; // wartość IR
- FType type; // jej typ w Finchu
- bool literal; // liczba wpisana w kodzie (albo zaimportowana stała C)
-};
+struct Value_ { Value *v; FType type; bool literal; bool fresh; };
```
-### Literały („stałe bez typu”)
+- **`literal`**: liczba wpisana w kod albo zaimportowana stała C. Może stać się dowolnym typem całkowitym,
+ w którym się mieści (`fits()`), albo `f32`. Przechodzi przez minus jednoargumentowy i arytmetykę samych
+ literałów zwijaną do stałej; jawne konwersje ją kasują (`u8(250)` to stała z typem, więc `u8(250) + 10` zawija się do 4).
+- **`fresh`**: wartość posiadająca pamięć (str/tablica/struktura z takimi polami), której nikt jeszcze nie
+ trzyma: wynik wywołania, literał, sklejenie, konstruktor. Trzeba ją zapisać (przenieść) albo zwolnić (§9).
-`literal` jest ustawiane dla `IntExpr`, `FloatExpr` i zaimportowanych stałych C. Przechodzi
-przez minus jednoargumentowy i przez arytmetykę **między dwoma literałami**, która zwija się do
-stałej (`arith()` ustawia `literal = l.literal && r.literal && isa(wynik)`). **Jawna
-konwersja je kasuje:** `u8(250)` to stała typu `u8`, a nie literał.
+### Konwersje niejawne: `coerce(v, want)`
-Literał całkowity może stać się **dowolnym typem całkowitym, w którym się mieści** (`fits()`
-sprawdza zakres, uwzględniając znakowość źródłowego literału). Dzięki temu `u8 b = 200`,
-`malloc(4 * 10)` (do `u64`) i `glClear(GL_COLOR_BUFFER_BIT)` (makro `i64` do parametru `u32`)
-działają bez rzutowań, a `u8 b = 300` jest odrzucane komunikatem `the number 300 doesn't fit in u8`.
-Literał `f64` może stać się `f32` (`f32 h = 0.5`).
+Identyczne typy; `null` → dowolny wskaźnik; wskaźnik z typem ↔ `ptr` bez typu; całkowita → całkowita,
+jeśli wartość to mieszczący się literał albo typ się rozszerza (`widens()`: ta sama znakowość i więcej bitów
+albo bez znaku → ściśle większa ze znakiem); całkowita → zmiennoprzecinkowa; `f32` → `f64`; literał `f64` → `f32`.
+Nic więcej: błąd podpowiada jawną konwersję. Typy posiadające pamięć konwertują się tylko na siebie.
+Literały tablic biorą typ elementu z miejsca, do którego trafiają (`exprWant()`), więc `[]f32 xs = [1, 2.5]` działa.
-Czemu nie wystarczy sprawdzać `isa`? `IRBuilder` zwija stałe, więc `u8(250)` też jest
-`ConstantInt`. Gdyby regułą było „jest stałą”, `u8(250) + 10` po cichu rozszerzyłoby się do
-`int` i dało 260, a ten sam kod ze zmienną `u8` zawinąłby się do 4. Jawna flaga sprawia, że
-zachowanie zależy od tego, co *napisałeś*, a nie od tego, co zdołał zobaczyć optymalizator.
+### Argumenty operatorów: `unify(l, r)`
-### Konwersje niejawne: `coerce(value, want, pos, what)`
+Strona-literał dopasowuje się do drugiej; w przeciwnym razie rośnie strona, która się rozszerza;
+`i32` + `u32` to błąd; całkowita + zmiennoprzecinkowa → zmiennoprzecinkowa; f32 + f64 → f64.
+Nie ma promocji całkowitych z C: `u8 + u8` zostaje `u8`.
-Używane wszędzie, gdzie wartość trafia do miejsca o znanym typie: inicjalizatory, przypisania,
-argumenty, wartości zwracane, warunki (`want = bool`), granice zakresu (`want = int`).
-Dozwolone, po kolei:
+### Konwersje jawne: `convert()`
-1. identyczne typy,
-2. `null` → dowolny `ptr…` albo `str`,
-3. dowolny `ptr…` albo `str` → `ptr` bez typu, a `ptr` bez typu → dowolny `ptr[T]` (reguły `void*`; tak działa `ptr[i32] n = malloc(4)`),
-4. int → int, gdy wartość jest mieszczącym się literałem **albo** `widens(from, to)`:
- - ta sama znakowość i ściśle więcej bitów, **albo**
- - bez znaku → ze znakiem o ściśle większej liczbie bitów,
- - (ze znakiem → bez znaku nigdy się nie rozszerza),
-5. dowolna całkowita → dowolna zmiennoprzecinkowa (`sitofp`/`uitofp`),
-6. `f32` → `f64` (`fpext`),
-7. literał `f64` → `f32` (`fptrunc`, zwijane do stałej).
-
-Wszystko inne to błąd. Komunikat podpowiada jawną konwersję (`use i32(...) to convert`)
-i ostrzega, gdy obetnie wartość (`it cuts off the fraction`).
-
-### Argumenty operatorów binarnych: `unify(l, r)`
-
-Przed arytmetyką i porównaniem obie strony są sprowadzane do jednego typu:
-
-1. ten sam typ → gotowe,
-2. obie całkowite: strona-literał dopasowuje się do drugiej, jeśli się mieści; w przeciwnym razie
- rozszerzana jest strona, która `widens()` do drugiej; w przeciwnym razie **błąd** (`i32` + `u32`:
- żadna nie rozszerza się do drugiej, a Finch nie zgaduje),
-3. całkowita i zmiennoprzecinkowa → całkowita zamieniana na ten typ zmiennoprzecinkowy,
-4. `f32` i `f64` → obie `f64`.
-
-Typ wyniku arytmetyki to ten wspólny typ. **Nie ma promocji całkowitych jak w C:**
-`u8 + u8` liczy się w `u8` i się zawija. To celowe: tego wymaga typ z rozmiarem, i tak samo działają Rust i Zig.
-
-### Konwersje jawne: `convert(call, to)`
-
-`T(x)` parsuje się jako wywołanie o nazwie typu i jest tłumaczone tutaj:
-
-| Z → Na | Instrukcja |
+| Z → Na | IR |
|---|---|
-| int/char → int/char | `CreateIntCast(signed = from.isSigned())` → `trunc` / `sext` / `zext` |
-| bool → int/char | `zext` |
-| float → int | `fptosi` / `fptoui` (wg znakowości celu) |
-| int → float | `sitofp` / `uitofp` (wg znakowości źródła) |
-| float → float | `fpext` / `fptrunc` (`CreateFPCast`) |
-| int → bool | `icmp ne x, 0` |
-| ptr/str/null → ptr…, ptr → str | brak instrukcji (wszystko to `ptr`) |
+| int/char ↔ int/char | `trunc`/`sext`/`zext` wg znakowości źródła |
+| bool → int | `zext` |
+| float ↔ int | `fptosi`/`fptoui`, `sitofp`/`uitofp` |
+| float ↔ float | `fpext`/`fptrunc` |
+| int → bool | `icmp ne 0` |
+| liczba/bool/char → str | `finch_str_from_int/uint/float/char/bool` (fresh) |
+| str → int/float | `finch_str_to_int/float` (panic przy złym tekście) |
+| `[]u8`/`[]char` → str | `finch_str_from_bytes` |
+| ptr → str | `finch_str_from_c` (pożyczony, `cap = -1`) |
+| ptr ↔ ptr | nic |
-`fptosi` dla wartości spoza zakresu to w LLVM poison (w C: UB). Finch 1.0 tego nie sprawdza, zobacz rozdział 15.
+### Porównania
-### Porównania: `compare()`
-
-- **Wskaźniki:** jeśli którakolwiek strona jest `ptr…` albo `null`, dozwolone są tylko `==`/`!=`,
- kompilowane do `icmp eq/ne ptr`. `str` porównywany z `null` też porównuje adresy.
-- **`str == str`:** wywołanie `strcmp`, a potem `icmp eq/ne i32 %r, 0` (porównanie treści). `<`/`>` na `str` są odrzucane.
-- **Liczby zmiennoprzecinkowe:** `oeq olt ole ogt oge` (uporządkowane: fałsz dla NaN) oraz `une` dla `!=`
- (nieuporządkowane: `NaN != NaN` to prawda). Tak samo jak w C.
-- **Całkowite / char:** `eq ne` oraz `slt sle sgt sge` albo `ult ule ugt uge` wg znakowości. `char` porównywany jest bez znaku.
-- **bool:** tylko `==`/`!=`.
+Wskaźniki/null: tylko `icmp eq/ne ptr`. `str`: `finch_str_eq` (długość + `memcmp`) dla `==`/`!=`,
+`finch_str_cmp` dla porządku. Liczby zmiennoprzecinkowe: `oeq olt ole ogt oge` i `une` dla `!=`.
+Całkowite i znaki wg znakowości (char bez znaku). Bool: tylko równość. Tablice/struktury: błąd.
---
-## 8. Generowanie kodu
+## 9. Własność i pamięć
-`Codegen` posiada `llvm::IRBuilder<> b`, `Module`, tablicę funkcji `fns`
-(`nazwa → {Function*, const FnDecl*}`), `scopes`, stos `loops` i `curFn`.
+### 9.1 Model
-### 8.1 Zmienne to miejsca na stosie
+Każda wartość **typu posiadającego pamięć** (`str`, `[]T` albo struktura Fincha z takim polem) ma
+dokładnie jednego właściciela: zmienną, element tablicy, pole struktury albo komórkę na stercie z `new()`.
+Właściciel zwalnia wartość, gdy sam znika. Nie ma liczenia referencji ani garbage collectora;
+każda decyzja zapada statycznie.
-Każda zmienna, **łącznie z parametrami**, dostaje `alloca` tworzoną przez `slot()`.
-`slot()` zawsze wstawia ją **na początek bloku wejściowego**, drugim `IRBuilder` ustawionym na
-`entry.begin()`, niezależnie od miejsca deklaracji. Tylko takie `alloca` `mem2reg`/`SROA` umieją
-zamienić na rejestry SSA, więc wyjście O2 nie ma ruchu na stosie dla wartości skalarnych.
-Ponieważ każde nowe miejsce trafia na sam początek, w IR z `-O0` `alloca` są w **odwrotnej kolejności
-deklaracji** (`%b2` przed `%a1` niżej).
+| Sytuacja | Co się dzieje |
+|---|---|
+| `x := expr` / `T x = expr` / `x = expr` / pole, element, `push` | **`own(v)`**: wartość fresh jest przenoszona; pożyczona jest kopiowana w głąb |
+| `x = …` (typ posiadający) | najpierw liczona jest nowa wartość, stara zwalniana po zapisie |
+| koniec bloku, `return`, `break`, `continue` | posiadane zmienne każdego opuszczanego zasięgu są zwalniane (od najnowszej), uruchamiają się defery |
+| `return lokalna` | zmienna jest **przenoszona na zewnątrz** (oznaczana jako `moved` na czas sprzątania tego returna) |
+| przekazanie argumentu | **pożyczka**: funkcja dostaje płytką kopię i nigdy jej nie zwalnia; argumenty fresh zwalnia wywołujący po wywołaniu |
+| parametr, który funkcja zmienia | kopiowany na wejściu i posiadany przez funkcję (decyduje `mutates()`, niżej) |
+| wartość tymczasowa, której nikt nie trzyma (`print(a + b)`, `f(g())`, instrukcja-wyrażenie) | **`release(v)`**: zwalniana zaraz po użyciu |
+| `a.pop()`, `a.remove(i)` | element jest przenoszony na zewnątrz (fresh) |
+| `for x in lista` | `x` pożycza kolejne elementy; jeśli ciało może zmieniać `lista`, elementy są kopiowane |
-Parametry są kopiowane do swoich miejsc na wejściu do funkcji (`store %a, %a1`), dzięki czemu
-parametry są zwykłymi, modyfikowalnymi zmiennymi lokalnymi.
+Literał tekstowy to wartość fresh z `cap = 0`: przeniesienie nic nie kosztuje, zwalnianie jest pomijane
+(`release()` ignoruje stałe), a zapis do niego (`s[i] = c`) najpierw robi kopię na stercie (`finch_str_own`).
+Teksty z C są pożyczane z `cap = -1` i zawsze kopiowane, zanim zostaną zatrzymane.
-### 8.2 Load i store: `expr()` kontra `place()`
+### 9.2 Pożyczanie parametrów: `mutates()`
-To jest serce generowania dostępów do pamięci:
+Przy deklarowaniu funkcji każdy parametr posiadający pamięć jest sprawdzany przez `mutates(nazwa, ciało)`:
+przypisanie, którego zmienna-korzeń (`rootVar()`: przez `.pole` i `[i]`) jest tym parametrem, metoda
+zmieniająca (`push pop insert remove clear resize sort reverse`) albo `addr(…)` na nim. Niezmieniane
+parametry są pożyczane (`Fn::borrowParam`), zmieniane kopiowane raz na wejściu. Analiza jest składniowa
+i zachowawcza: wszystko, co *może* zmienić parametr, powoduje kopię, co zawsze jest poprawne.
+Ta sama analiza decyduje, czy `for x in lista` musi kopiować elementy.
-- `place(e)` zwraca **`Place {Value *addr; FType type}`**, czyli *adres* l-wartości.
- - `VarExpr` → `alloca` zmiennej (albo `GlobalVariable` zmiennej globalnej C).
- - `MemberExpr` `p.value` → `expr(p)` (które **ładuje wskaźnik** z miejsca p) plus kontrola null
- (rozdział 9). Adresem jest sam załadowany wskaźnik, a typem `*p.type.elem`.
-- `expr(e)` zwraca **`Value_`**, czyli r-wartość. Dla `Var` i `Member` woła `place()`, a potem
- emituje **jeden `load`** typu `ty(place.type)` z `place.addr`.
+### 9.3 Kopiowanie i zwalnianie
-Instrukcja `load` dla identyfikatora powstaje więc w `Codegen::expr()`, w przypadku `ExprKind::Var`,
-jako `b.CreateLoad(ty(pl.type), pl.addr, v.name)`. Nazwa wzięta od zmiennej sprawia, że IR z `-O0`
-pokazuje `%x1`, `%x2` itd. (LLVM dokleja numery do powtórzonych nazw). `p.value` kosztuje **dwa**
-loady: wskaźnika i wartości wskazywanej. `pp.value.value` kosztuje trzy.
+`dropAt(addr, T)` / `copyAt(dst, src, T)` działają na adresach:
-Instrukcje store powstają w dokładnie trzech miejscach: `addVar()` (inicjalizacja), `assign()` i krok pętli `for`.
+- `str`: `finch_str_drop` (zwalnia, gdy `cap > 0`) / `finch_str_copy` (literały współdzieli, resztę duplikuje).
+- `[]T` i struktury posiadające: generowane helpery `finch.drop.` i `finch.copy.`, tworzone
+ raz na typ (`dropFn`, `copyFn`, klucz z `typeKey()`, np. `arr_S_main_Gracz`). Tablice zwalniają każdy posiadający
+ element, a potem `finch_arr_free`; kopie wołają `finch_arr_clone_raw`, a potem kopiują w głąb posiadające
+ elementy na surowe bity. Struktury zwalniają/kopiują tylko pola posiadające.
-### 8.3 Przypisanie i kolejność obliczeń
+Helpery są emitowane we własnych funkcjach (`HelperScope` zapamiętuje pozycję buildera i wyłącza lokacje
+debugowe), mogą wołać się rekurencyjnie (`struct Wezel { []Wezel dzieci }`) i są wstawiane (inline) przez O2.
+
+### 9.4 Jak to wygląda
```c
-x += add(x, 2)
-```
+fn greet(str name) -> str {
+ msg := "Hi, " + name
+ return msg
+}
-`assign()` **najpierw** liczy `place(target)`. Dla operatorów złożonych potem **ładuje bieżącą
-wartość**, następnie oblicza prawą stronę, wywołuje `arith()`, robi `coerce()` z powrotem do typu
-celu i zapisuje. Prawdziwe wyjście z `-O0`:
-
-```llvm
-define i32 @main() {
-entry:
- %x = alloca i64, align 8
- store i64 5, ptr %x, align 8 ; x := 5
- %0 = load i64, ptr %x, align 8 ; bieżące x (dla +=)
- %x1 = load i64, ptr %x, align 8 ; argument x
- %1 = call i64 @finch.add(i64 %x1, i64 2)
- %2 = add i64 %0, %1
- store i64 %2, ptr %x, align 8
- %x2 = load i64, ptr %x, align 8
- %3 = call i32 (ptr, ...) @printf(ptr @fmt, i64 %x2)
- ret i32 0
+fn main() {
+ a := ["x"]
+ b := a
+ b.push(greet("Ola"))
+ print(b[1])
}
```
-Kolejność obliczeń w Finchu jest **zawsze od lewej do prawej**: cel, potem argumenty operatora
-binarnego od lewej, potem argumenty wywołania od lewej. C zostawia większość tego nieokreśloną.
-Zauważ, że w `x += f()` stara wartość `x` jest czytana **przed** wywołaniem `f()`. Gdyby `f`
-zmieniła `x` przez wskaźnik, ta zmiana zostałaby nadpisana. To to samo co `x = x + f()`
-czytane od lewej do prawej.
-
-Po O2 cały program powyżej zwija się do `printf(@fmt, i64 12)`.
-
-### 8.4 Funkcje, linkowanie i `main`
+IR funkcji `main` z `-O0` (skrócony):
```llvm
-define internal i64 @finch.add(i64 %a, i64 %b) {
-entry:
- %b2 = alloca i64, align 8
- %a1 = alloca i64, align 8
- store i64 %a, ptr %a1, align 8
- store i64 %b, ptr %b2, align 8
- %a3 = load i64, ptr %a1, align 8
- %b4 = load i64, ptr %b2, align 8
- %0 = add i64 %a3, %b4
- ret i64 %0
-}
+ %3 = call ptr @finch_alloc(i64 24) ; ["x"]: jeden element na stercie
+ store { ptr, i64, i64 } { ptr @str.1, i64 1, i64 0 }, ptr %4 ; literał, cap 0
+ ...
+ store { ptr, i64, i64 } %7, ptr %a ; przeniesione do a (fresh)
+ %a1 = load { ptr, i64, i64 }, ptr %a
+ store { ptr, i64, i64 } %a1, ptr %1
+ call void @finch.copy.arr_str(ptr %2, ptr %1) ; b := a kopiuje
+ %8 = load { ptr, i64, i64 }, ptr %2
+ store { ptr, i64, i64 } %8, ptr %b
+ %9 = call { ptr, i64, i64 } @finch.greet({ ptr, i64, i64 } { ptr @str.2, i64 3, i64 0 })
+ ...
+ call void @finch_arr_reserve(ptr %b, i64 %12, i64 24) ; push: powiększ, jeśli trzeba
+ store { ptr, i64, i64 } %9, ptr %15 ; wynik greet przeniesiony, bez kopii
+ ...
+ %20 = icmp uge i64 1, %19 ; b[1]: kontrola zakresu
+ br i1 %20, label %out.of.range, label %in.range
+ ...
+ call void @finch.drop.arr_str(ptr %b) ; koniec main: od najnowszej
+ call void @finch.drop.arr_str(ptr %a)
+ ret void
```
-- Funkcje użytkownika nazywają się **`finch.`** i mają **wiązanie wewnętrzne** (internal linkage).
- Kropka nie może wystąpić w identyfikatorze C, więc funkcja Fincha nigdy nie zderzy się z symbolem
- libc ani biblioteki, a wiązanie wewnętrzne pozwala LLVM swobodnie je wstawiać (inline),
- specjalizować albo usuwać. W C funkcja bez `static` jest zewnętrzna i musi zostać zachowana
- tak, jak ją napisano. To prawdopodobnie jeden z powodów, dla których rekurencyjne `fib(40)`
- działa odrobinę szybciej niż ten sam kod C pod `clang -O2` (ok. 0,19 s wobec 0,23 s na maszynie autora).
-- `main` jest emitowane jako **`i32 @main()` z wiązaniem zewnętrznym**. `fn main()` zwraca `i32 0`
- na każdej ścieżce wyjścia, także przy jawnym `return`. `fn main() -> int` przycina swój wynik
- `i64` przez `CreateIntCast(…, i32, signed)`.
-- Brakujący return: jeśli po ciele funkcji bieżący blok nie ma terminatora, funkcje `void` dostają
- `ret void` (albo `ret i32 0` dla `main`). Funkcje zwracające wartość dostają błąd kompilacji:
- `function 'f' can reach its end without returning int`. Sprawdza się to na faktycznie zbudowanym
- CFG (rozdział 8.5), więc `if/else` zwracające wartość w obu gałęziach jest akceptowane bez dodatkowej analizy.
-- Arytmetyka jest emitowana **bez flag `nsw`/`nuw`**, więc przepełnienie liczb całkowitych to
- zdefiniowane zawinięcie w kodzie uzupełnień do dwóch (rozdział 15).
+a `greet` zwraca `msg` bez kopiowania (`ret … %5` po wczytaniu slotu: zmienna została przeniesiona).
-### 8.5 Sterowanie przebiegiem i stan „zakończony”
+### 9.5 defer
-Blok, do którego wstawia builder, jest jedynym źródłem prawdy o osiągalności:
+`defer instrukcja` dokłada do bieżącego zasięgu `Cleanup{isDefer, body, order = bieżące varOrder}`
+i niczego w tym miejscu nie emituje. Za każdym razem, gdy emitowane jest sprzątanie zasięgu (dojście do
+końca bloku, `return`, wyjście przez `break`/`continue`), ciało jest generowane **w tym wyjściu**, z
+`deferLimit = order`, żeby `lookup()` ukrył zmienne zadeklarowane po defer: te są wtedy już zwolnione
+(sprzątanie idzie w odwrotnej kolejności). `return`/`break`/`continue` w instrukcji defer to błędy (`inDefer`).
+Ciała defer są więc powielane na każdą ścieżkę wyjścia, przez co na zwykłej ścieżce nic nie kosztują.
+
+### 9.6 Pamięć ręczna
+
+`new(v)` alokuje `sizeof(T)` przez `finch_alloc`, zapisuje `own(v)` i zwraca `ptr[T]`. `free(p)` zwalnia
+wskazywaną wartość, jeśli `T` posiada pamięć (pomijając null), i woła `free` z libc. Wskaźniki nigdy
+niczego nie posiadają: `addr(x)` może wisieć po końcu bloku `x`; nie jest to sprawdzane.
+
+### 9.7 Weryfikacja
+
+`MEMCHECK=1 tests/run.sh` buduje każdy program testowy i uruchamia go pod valgrindem z
+`--leak-check=full --errors-for-leak-kinds=all`: zero wycieków i zero nieprawidłowych dostępów we wszystkich,
+w tym przy returnach z wnętrza pętli, `continue`/`break` z posiadanymi zmiennymi, deferach, modyfikacji
+podczas iteracji, zagnieżdżonych tablicach struktur i przekazywaniu struktur C.
+
+---
+
+## 10. Generowanie kodu
+
+### 10.1 Zmienne
+
+Każda zmienna, łącznie z parametrami, dostaje `alloca` na początku bloku wejściowego (`slot()`;
+struktury C dostają wyrównanie z C). `addVar()` zapisuje wartość początkową, rejestruje zmienną,
+dokłada sprzątanie, jeśli zmienna posiada pamięć, i (z `-g`) zgłasza ją debuggerowi.
+
+### 10.2 Miejsca: `ref()`, `place()`, `expr()`
```cpp
-bool terminated() { return b.GetInsertBlock()->getTerminator() != nullptr; }
+struct LRef { bool isPlace; Place pl; Value_ val; }; // adres albo (jeśli go nie ma) wartość
```
-Po `return`, `break` albo `continue` bieżący blok ma terminator.
-`blockBody()` sprawdza `terminated()` **przed każdą instrukcją**. Jeśli jest ustawiony, instrukcja
-jest nieosiągalna i to błąd kompilacji (`this code can never run`). Martwe bloki dla kodu po skoku nigdy nie powstają.
+`ref(e, forWrite)` przechodzi łańcuch `Var`/`Member`/`Index` i zwraca **adres**, jeśli istnieje:
-`continueAt(bb, deadEnd)` jest używane po `if` i `while`. Jeśli blok łączący `bb` **nie ma
-poprzedników** (wszystkie ścieżki wróciły albo przerwały), zostaje usunięty, a builder zostaje
-w `deadEnd`, który jest zakończony. Stan „zakończony” propaguje się więc na zewnątrz przez
-zagnieżdżone konstrukcje i widzi go sprawdzanie brakującego returna.
+- `Var` → jej `alloca` (albo zmienna globalna C).
+- `.pole` na miejscu-strukturze → `getelementptr` do pola (`llvmIndex`; struktury C pomijają elementy wypełnienia).
+- przez wskaźnik (`p.value`, `p.pole`, `p[i]`) → wczytanie wskaźnika, kontrola null, adres celu.
+ `p.value` na wskaźniku do struktury z własnym polem `value` oznacza to pole.
+- `a[i]` na miejscu-tablicy/tekście → wczytanie `{ptr,len,cap}`, `boundsCheck(i, len)`, `getelementptr`.
+ Przy `forWrite` na `str` najpierw `finch_str_own` (kopiowanie przy zapisie dla literałów). `[N]T` sprawdza względem `N`.
+- `.len` / `.ptr` → wartości.
+- wartość tymczasowa (wynik wywołania…) nie ma adresu: jej pole/element jest wyciągany, kopiowany
+ (jeśli posiada pamięć), a sama wartość zwalniana.
-**`if / else if / else`:** bloki `then`, `else` (jeśli jest) i `endif`. `else if` to `IfStmt`
-w `otherwise`, generowany rekurencyjnie wewnątrz bloku `else`.
+`expr()` dla tych rodzajów woła `ref()` i wczytuje spod adresu: stąd bierze się każdy `load` zmiennej,
+pola albo elementu. `place()` wymaga adresu (cele przypisań, `addr()`).
-**`while`:** bloki `while.cond`, `while.body`, `while.end`. Jeśli warunek zwinie się do stałej
-`true`, emitowany jest bezwarunkowy `br`, więc `while.end` ma poprzedników tylko przez `break`.
-`while true` bez `break` zostawia więc funkcję „zakończoną”, a funkcja, której ostatnią instrukcją
-jest taka pętla, nie potrzebuje końcowego `return`.
+### 10.3 Przypisanie
-**`for i in a..b`:** `a` i `b` są obliczane **raz**, przed pętlą, i zamieniane na `int`.
-Bloki: `for.cond` (`icmp slt i, end`), `for.body`, `for.step` (`i + 1`), `for.end`. `continue`
-skacze do `for.step`, a nie do `for.cond`, więc inkrementacja nigdy nie jest pomijana.
-Zmienna pętli żyje we własnym zasięgu wokół pętli.
+`x = v`: **najpierw** liczone jest `v` (może przenieść pamięć, w której żyje cel: `a[0] = f()`, gdzie `f`
+robi `push` do `a`), potem `place(cel, forWrite)`, `coerce`, `own`, wczytanie starej wartości, zapis i
+zwolnienie starej. Złożone `x += v`: liczenie `v`, wczytanie celu, `arith()`, zapis, zwolnienie starej
+wartości (dla `str +=`).
-```llvm
- store i64 0, ptr %i, align 8
- br label %for.cond
-for.cond:
- %3 = load i64, ptr %i, align 8
- %4 = icmp slt i64 %3, 2
- br i1 %4, label %for.body, label %for.end
-for.body:
- ...
- br label %for.step
-for.step:
- %6 = load i64, ptr %i, align 8
- %7 = add i64 %6, 1
- store i64 %7, ptr %i, align 8
- br label %for.cond
-for.end:
-```
+### 10.4 Funkcje i `main`
-**`break` / `continue`:** `loops` to stos par `{continueTo, breakTo}`. `while` odkłada
-`{while.cond, while.end}`, a `for` odkłada `{for.step, for.end}`. Poza pętlą to błąd.
+Funkcje są `internal` z naturalną sygnaturą LLVM (agregaty przez wartość). Parametry posiadające
+pamięć są kopiowane na wejściu tylko wtedy, gdy `borrowParam` jest fałszywe. `fn main` to wewnętrzna
+`finch.main`; osobna zewnętrzna `i32 main(i32, ptr)` (`defineMainWrapper`) buduje `[]str args` przez
+`finch_args`, jeśli trzeba, woła ją, zwalnia argumenty i zwraca kod wyjścia. Funkcja, która dochodzi do
+swojego końca, dostaje sprzątanie zasięgów i `ret void` albo błąd `can reach its end without returning T`.
-### 8.6 Skrócone `&&` / `||`: jedyne ręcznie budowane φ
+### 10.5 Sterowanie przebiegiem
+
+Bieżący blok buildera jest źródłem prawdy o osiągalności (`terminated()`): instrukcje po terminatorze to
+błąd (`this code can never run`). `continueAt()` usuwa blok łączący, do którego nikt nie skacze.
+`if`/`while`/`for` mają klasyczny układ bloków; `while true` bez `break` zostawia funkcję zakończoną
+(nie trzeba po nim `return`). `for i in a..b` liczy oba końce raz; `continue` idzie do `for.step`.
+`for x in lista` czyta długość listy z jej adresu **w każdej rundzie** (`push` w środku pętli jest bezpieczny)
+i trzyma indeks w slocie na stosie. `break`/`continue` emitują sprzątanie zasięgów otwartych wewnątrz pętli
+(`Loop::scopeDepth`) przed skokiem.
+
+### 10.6 Skrócone `&&` / `||`
+
+Jedyne ręcznie budowane φ: lewa strona kończy się w bloku `from`, prawa w `rhsEnd`;
+`phi i1 [!isAnd, from], [rhs, rhsEnd]`.
+
+### 10.7 Wywołania
+
+`callFinch()` konwertuje argumenty na typy parametrów (literały tablic przez `exprWant`), przekazuje
+posiadające płytko, woła, a potem zwalnia argumenty fresh. Wyniki typów posiadających są fresh.
+`construct()` buduje strukturę od `zeroinitializer` przez `insertvalue`: pozycyjnie (wszystkie pola)
+albo po nazwie (brakujące biorą wyrażenie domyślne pola, liczone w module struktury z podmienionymi
+zasięgami wywołującego, albo zero). Wywołania C opisuje §13.
+
+### 10.8 Wypisywanie
+
+`print` emituje `printf` dla każdego argumentu z formatem wybranym wg typu (`%lld`, `%llu`, `%g`, `%c`,
+`%.*s` dla `str` z jego długością, `%p`), oddzielając spacjami, a na końcu `\n`. Tablice, tablice o stałym
+rozmiarze i struktury wołają generowane helpery `finch.print.`, które wypisują `[1, 2]` /
+`Punkt(x: 1, y: 2)`, biorąc teksty i znaki w środku w cudzysłów.
+
+### 10.9 Metody tablic i tekstów
+
+Tablice: `push` (`finch_arr_reserve` + zapis + len++), `pop`/`remove` (przeniesienie na zewnątrz,
+`remove_gap`), `insert` (`insert_gap`), `clear`/`resize` (zwolnienie znikających elementów,
+`finch_arr_resize`), `find`/`contains` (pętla z `compare(Eq)`), `slice` (nowa tablica, `copyAt` na element),
+`reverse` (pętla zamian), `sort` (`qsort` z libc z generowaną porównywarką `finch.cmp.`), `join` (runtime).
+Metody zmieniające tablicę wymagają adresu (zmienna/pole/element).
+Teksty: `sub find contains starts_with ends_with split trim upper lower replace repeat bytes`, wszystkie w runtime.
+
+---
+
+## 11. Kontrole w czasie działania
+
+Wszystkie błędy w czasie działania idą przez `finch_panic(plik, linia, msg)` /
+`finch_panic_index(plik, linia, i, len)` (zadeklarowane `noreturn cold`), więc gałąź błędu jest układana
+jako zimna, a kontrola kosztuje porównanie i dobrze przewidywany skok. LLVM usuwa kontrole, które potrafi
+udowodnić (stałe indeksy w zakresie, kontrole wyciągnięte z pętli).
+
+| Kontrola | Gdzie |
+|---|---|
+| dzielenie/reszta przez zero (stałe zero: błąd kompilacji) | `checkDivisor()` |
+| `MIN / -1` dla typów ze znakiem | `checkDivisor()` |
+| indeks tablicy/tekstu/tablicy stałej poza zakresem (`icmp uge`, więc także ujemne) | `boundsCheck()` |
+| `.value`, `.pole`, `[i]` przez null | `member()`, `index()` |
+| `pop()` na pustej, `slice()`/`sub()` poza zakresem | metody, runtime |
+| zły tekst w `int(s)`/`float(s)`, nieczytelny plik w `read_file` | runtime |
+
+Sito na 20 milionów elementów z kontrolą zakresu przy każdym dostępie działa tak szybko jak to samo w C
+(ok. 0,09 s): kontrole są wyciągane z pętli albo zwijane.
+
+---
+
+## 12. Runtime w C
+
+`runtime/finch_rt.c` to zwykłe C bez stanu globalnego. CMake wbudowuje go w kompilator jako surowy
+napis (`rt_source.inc`); przy pierwszym użyciu `runtimeObject()` kompiluje go przez `$CC -O2 -fPIC -c`
+do `~/.cache/finch/rt-.o`, gdzie hash obejmuje źródło, wersję Fincha i kompilator C.
+Jest linkowany statycznie do każdego programu.
+
+Wspólny układ:
```c
-fn check(int a, int b) -> bool {
- return a > 0 && b > 0
-}
+typedef struct { char *ptr; int64_t len; int64_t cap; } FStr; // ptr zawsze zakończony zerem
+typedef struct { void *ptr; int64_t len; int64_t cap; } FArr;
+// cap tekstu: > 0 sterta (posiadany), 0 literał (statyczny), -1 pożyczony z C (skopiuj, zanim zatrzymasz)
```
-```llvm
- %a3 = load i64, ptr %a1, align 8
- %0 = icmp sgt i64 %a3, 0
- br i1 %0, label %and.rhs, label %and.end
-and.rhs:
- %b4 = load i64, ptr %b2, align 8
- %1 = icmp sgt i64 %b4, 0
- br label %and.end
-and.end:
- %2 = phi i1 [ false, %entry ], [ %1, %and.rhs ]
-```
-
-`logic()` zapamiętuje blok, w którym lewa strona się **skończyła** (`from`), i blok, w którym
-skończyła się prawa (`rhsEnd`). To niekoniecznie bloki, w których obliczanie się zaczęło, bo
-zagnieżdżone `&&` tworzy własne bloki. φ używa `!isAnd` jako stałej skrótu (`false` dla `&&`, `true` dla `||`).
-
-### 8.7 `print`
-
-`print(a, b, …)` kompiluje się do **jednego wywołania `printf`**. Format jest budowany w czasie
-kompilacji z typów argumentów, łączony spacjami i zakończony `\n`:
-
-| Typ Fincha | Format | Konwersja argumentu |
-|---|---|---|
-| całkowite ze znakiem | `%lld` | `sext` → `i64` |
-| całkowite bez znaku | `%llu` | `zext` → `i64` |
-| `f32`, `f64` | `%g` | `fpext` → `double` |
-| `char` | `%c` | `zext` → `i32` |
-| `str` | `%s` | bez zmian |
-| `bool` | `%s` | `select i1, "true", "false"` |
-| `ptr…` | `%p` | bez zmian (glibc wypisuje `(nil)` dla null) |
-| `null` | dosłowny tekst `null` | brak |
-
-Format jest generowany, więc teksty użytkownika zawsze idą jako argumenty `%s`, nigdy jako
-format. `%` w tekście użytkownika jest bezpieczne. O2 często zamienia `printf("%s\n", s)` na `puts(s)`.
-
-### 8.8 Teksty
-
-Literał tekstowy to prywatna stała globalna `unnamed_addr` (`CreateGlobalString`), a jego
-wartością jest `ptr` na nią. Optymalizator może scalać identyczne literały. Zmienna `str`
-zadeklarowana bez wartości wskazuje na wspólną stałą `""`. W 1.0 teksty są **niezmienne
-i statyczne**. Nie ma sklejania ani alokacji (rozdział 18).
-
-### 8.9 `addr()` i wskaźniki
-
-`addrOf()` wymaga argumentu `Var` albo `Member` i zwraca `place(arg).addr` z typem `ptr[T]`.
-Nie ma żadnego load: `addr(x)` *to jest* `alloca` zmiennej x. Takiej `alloca` mem2reg nie zamieni
-na rejestr, co jest spodziewanym kosztem pobrania adresu. `addr(p.value)` daje wskaźnik już
-zapisany w `p`, po kontroli null. `addr` zmiennej `for` jest odrzucane (jest tylko do odczytu).
-
-### 8.10 Wywołania: `callFinch()` kontra `callC()`
-
-Obie sprawdzają liczbę argumentów i robią `coerce()` każdego argumentu do typu parametru. Różnice:
-
-| | `callFinch()` | `callC()` |
-|---|---|---|
-| Wołana funkcja | `finch.`, utworzona w `declare()` | `getOrInsertFunction(nazwa, …)`: deklarowana leniwie przy pierwszym użyciu, potem używana ponownie |
-| Zmienna liczba argumentów | nigdy | obsługiwana; dodatkowe argumenty dostają domyślne promocje C |
-| Atrybuty parametrów | niepotrzebne | `signext`/`zeroext` dla parametrów i wartości zwracanych węższych niż 32 bity, na deklaracji i w miejscu wywołania |
-| Nieobsługiwane sygnatury | niemożliwe | odrzucane z powodem zapisanym przez cimport (`passes a C struct by value`, …) |
-| Wywołanie `main` | odrzucane | nie dotyczy |
-
-Domyślne promocje dla części zmiennej: `f32` → `double`; `bool`, `char` i liczby całkowite
-węższe niż 32 bity → `i32` (z rozszerzeniem znaku albo zerami). Wskaźniki, `str` i liczby
-32/64-bitowe przechodzą bez zmian. To odwzorowuje reguły C, których oczekuje `va_arg` w `printf`.
-
-Po co `signext`/`zeroext`: na x86-64 System V clang przekazuje `char`/`short`/`_Bool` już
-rozszerzone do 32 bitów i tak je oznacza. Niektóre funkcje na tym polegają. Bez atrybutu LLVM
-zostawiłby górne bity nieokreślone, co byłoby niezgodnością ABI z kodem C skompilowanym clangiem.
+Funkcje przyjmują i zwracają te struktury **przez wskaźnik**, więc ich ABI jest trywialne (bez klasyfikacji
+struktur): budowanie tekstów (`concat`, `from_int/uint/float/char/bool`, `sub`, `trim`, `upper`, `lower`,
+`replace`, `split`, `join`, `repeat`, `from_bytes`), porównania (`eq`, `cmp`, `find`, `starts`, `ends`),
+własność (`copy`, `own`, `drop`, `from_c`), tablice (`reserve`, `make`, `resize`, `clone_raw`, `free`,
+`insert_gap`, `remove_gap`), `args`, `input`, pliki, `shell` i panic. Brak pamięci wypisuje
+`out of memory` i kończy proces.
---
-## 9. Kontrole w czasie działania
+## 13. Współpraca z C: nagłówki, struktury, ABI
-Finch usuwa dwa najczęstsze źródła niezdefiniowanego zachowania w C, które da się tanio sprawdzić.
-Wszystkie kontrole korzystają z jednej funkcji pomocniczej na moduł:
+### 13.1 Import nagłówków
-```llvm
-; Function Attrs: cold noinline noreturn
-define internal void @finch.panic(ptr %0, i64 %1) #0 {
-entry:
- %2 = call i32 (i32, ptr, ...) @dprintf(i32 2, ptr @panic.fmt, ptr @panic.file, i64 %1, ptr %0)
- call void @exit(i32 1)
- unreachable
-}
-```
+`importHeaders(imports, dirs)` parsuje każdy `import "x.h"` jako osobną jednostkę translacji z pliku
+w pamięci `#include "x.h"`, z `-x c -std=gnu11`, `-I` dla folderu każdego pliku `.fn` i `-isystem` dla
+każdego katalogu przeszukiwanego przez systemowy kompilator C (odczytanego z `$CC -E -v -x c /dev/null`,
+dzięki czemu działa to na NixOS). `gnu11`, bo glibc w trybie ścisłego ISO ukrywa `M_PI` i podobne.
-`panicFn()` tworzy ją przy pierwszym użyciu. `panicIf(cond, msg, pos)` emituje
-`br cond, %panic, %ok`, wywołanie `finch.panic(msg, linia)` i `unreachable` w `%panic`,
-a dalej kontynuuje w `%ok`. Pisze na deskryptor 2 przez `dprintf`, więc nie zależy od symbolu
-`stderr` z libc. Funkcja jest `cold` i `noinline`, więc układ skoków faworyzuje ścieżkę bez
-błędu, a kontrola kosztuje jedno porównanie i dobrze przewidywany skok.
+`Importer` odwiedza kursory najwyższego poziomu:
-### Dzielenie i reszta całkowita: `checkDivisor()`
-
-```llvm
-define internal i64 @finch.div(i64 %a, i64 %b) {
- ...
- %0 = icmp eq i64 %b4, 0
- br i1 %0, label %panic, label %ok
-panic:
- call void @finch.panic(ptr @panic.msg, i64 2)
- unreachable
-ok:
- %1 = icmp eq i64 %b4, -1
- %2 = icmp eq i64 %a3, -9223372036854775808
- %3 = and i1 %2, %1
- br i1 %3, label %panic5, label %ok6
-panic5:
- call void @finch.panic(ptr @panic.msg.1, i64 2)
- unreachable
-ok6:
- %4 = sdiv i64 %a3, %b4
-```
-
-- Dzielnik będący **stałym zerem** to błąd **kompilacji**.
-- Stały dzielnik różny od zera (i od −1 dla typów ze znakiem) nie generuje żadnej kontroli.
-- Przy dzieleniu ze znakiem `MIN / -1` się przepełnia (na x86 kończy się SIGFPE). To też jest sprawdzane.
-- Gdy wszystko jest znane, O2 usuwa kontrole całkowicie: `div(10, 2)` powyżej staje się `5` w `main`.
-
-### `.value` na null: w `place()`
-
-Każde `p.value`, którego wskaźnik nie jest oczywiście niezerowy (nie jest wprost `alloca` ani
-zmienną globalną), dostaje `panicIf(icmp eq p, null, "used .value on a null pointer")`.
-Bez tego LLVM traktuje load z null jako UB i może go **usunąć**. Zanim dodano tę kontrolę,
-`print(p.value)` przy `p` równym null wypisywało śmieciową wartość pod O2 zamiast się wysypać.
-
----
-
-## 10. Współpraca z C przez libclang
-
-`importHeaders(imports, dir)` (`src/cimport.cpp`) działa **przed** codegenem i tworzy:
-
-```cpp
-struct CImports {
- std::unordered_map fns; // nazwa, nagłówek, wynik, parametry, variadic, powód braku obsługi
- std::unordered_map consts; // wartości enum + liczbowe #define
- std::unordered_map globals; // zmienne extern
-};
-```
-
-### Parsowanie
-
-Dla **każdego** `import "h"` parsowana jest osobna jednostka translacji z pliku w pamięci
-`finch_import.c` zawierającego `#include "h"`. Osobne jednostki dają dokładne przypisanie błędów:
-brakujący nagłówek wskazuje swoją linijkę `import`. Flagi:
-
-```
--x c -std=gnu11 -I -isystem
-CXTranslationUnit_DetailedPreprocessingRecord (żeby widzieć makra)
-CXTranslationUnit_SkipFunctionBodies (szybkość: ciała funkcji inline są zbędne)
-```
-
-- **Systemowe katalogi nagłówków** pochodzą z uruchomienia `$CC -E -v -x c /dev/null` i odczytania
- bloku między `#include <...> search starts here:` a `End of search list.`. Dzięki temu `stdio.h`
- rozwiązuje się tak samo jak dla systemowego kompilatora. Ma to znaczenie na NixOS, gdzie wrapper
- cc dokłada ścieżki ze store, o których „goły” libclang nic nie wie.
-- **`gnu11`** zamiast `c11`: glibc w trybie ścisłym ukrywa `M_PI` i inne nazwy spoza ISO.
-- **Diagnostyka** o wadze ≥ error przerywa import. `file not found` zamienia się w
- `can't find the C header 'h'`.
-
-### Odwiedzający
-
-`visit()` przechodzi po kursorach najwyższego poziomu. Wygrywa pierwsza deklaracja danej nazwy.
-
-| Kursor | Działanie |
+| Kursor | Wynik |
|---|---|
-| `FunctionDecl` | Mapuje kanoniczny **typ funkcji** (`clang_getArgType`, więc parametry tablicowe są już zamienione na wskaźniki). Funkcje `static` są zapisywane jako nieobsługiwane (`static inline` nie ma symbolu do zlinkowania). `FunctionNoProto` (K&R `f()`) jest nieobsługiwane. Zmienna liczba argumentów pochodzi z `clang_isFunctionTypeVariadic`. |
-| `VarDecl` z `extern` | → `CGlobal`, jeśli typ się mapuje |
-| `EnumDecl` | rekurencja do dzieci |
-| `EnumConstantDecl` | → `CConst` (`i64`, literał) |
-| `MacroDefinition` | pomija wbudowane i makra-funkcje; `macroValue()` próbuje odczytać liczbę |
+| `FunctionDecl` | `CFunc` (sygnatura mapowana w **kontekście sygnatury**; funkcje `static` i K&R zapisywane jako nieobsługiwane) |
+| `TypedefDecl` rekordu | nazywa rekord (`typedef struct {…} CXCursor` → `CXCursor`) |
+| `StructDecl` (definicja) | nazywa rekord jego tagiem |
+| `VarDecl` `extern` | `CGlobal` (mapowany w **kontekście danych**) |
+| `EnumConstantDecl`, liczbowe `MacroDefinition` | `CConst` (literał) |
-### Mapowanie typów: `mapType()`
+`mapType()` działa na typach kanonicznych i mapuje liczby całkowite wg rozmiaru. Kontekst decyduje o
+`char *`: w sygnaturach funkcji to `str` (Finch konwertuje przy wywołaniu), ale w strukturach, zmiennych
+globalnych i za wskaźnikami to `ptr[char]` (tam układ pamięci musi zostać pojedynczym wskaźnikiem).
+Rekordy przez wartość stają się `Named{nazwa, "C"}` i są definiowane przez `defineRecord()`: każde pole
+(`clang_Type_visitFields`) z przesunięciem w bajtach, rozmiarem i wyrównaniem, zagnieżdżone rekordy,
+tablice stałe (`Fixed`). Unie i pola bitowe zostają jako ukryte bajty i oznaczają strukturę jako
+nieobsługiwaną przy wywołaniach przez wartość; wskaźniki na wszystko inne (void, funkcje, niekompletne
+struktury) stają się `ptr`.
-Działa na typie **kanonicznym**, więc typedefy (`size_t`, `uint32_t`, `GLuint`, `FILE`) są rozwijane.
-Typy całkowite są mapowane **według rozmiaru** (`clang_Type_getSizeOf`), a nie nazwy, więc
-`long` staje się `i64` na LP64.
+### 13.2 Układ struktur C
-| C (kanoniczny) | Finch |
+`resolveStruct()` buduje **upakowany** struct LLVM, który odwzorowuje C bajt w bajt: wypełnienie
+`[n x i8]` przed każdym polem, którego przesunięcie wyprzedza bieżącą pozycję, samo pole (tylko jeśli jego
+rozmiar w LLVM zgadza się z C) i wypełnienie końcowe do `sizeof` z C. Dostęp do pól używa zapisanego
+`llvmIndex`. Upakowanie czyni układ dokładnym niezależnie od reguł wyrównania LLVM; alokacje i wartości
+tymczasowe dostają wyrównanie z C jawnie.
+
+### 13.3 Konwencja wywołań System V x86-64
+
+`classify(T)` (w `abi.cpp`) spłaszcza strukturę do skalarnych liści z przesunięciami w bajtach (przez
+zagnieżdżone struktury i tablice stałe) i:
+
+- **MEMORY**, jeśli jest większa niż 16 bajtów albo ma niewyrównany liść: przekazywana jako wskaźnik z
+ `byval(T) align ≥8`, zwracana przez ukryty pierwszy wskaźnik `sret(T)`.
+- w przeciwnym razie każdy eightbyte jest **INTEGER**, jeśli którykolwiek liść w nim jest liczbą
+ całkowitą/wskaźnikiem, a inaczej **SSE**. Części: INTEGER → `iN` dla pozostałych bajtów (`i32` dla
+ 4-bajtowego `Color`), SSE → `double`, `<2 x float>` albo `float`. Jedna część idzie wprost; dwie jako
+ dwa argumenty albo zwracane jako `{a, b}`.
+
+`makePlan()` przechodzi parametry, licząc 6 rejestrów całkowitych i 8 SSE; struktura, której części już się
+nie mieszczą, idzie w całości na stos (MEMORY), tak jak robi to clang. Małe parametry całkowite dostają
+`signext`/`zeroext`. `emitCCall()` zapisuje każdy argument-strukturę do slotu wystarczająco dużego dla
+struktury i jej postaci rejestrowej, wczytuje części z przesunięć 0 i 8, woła z zaplanowanym typem funkcji
+i atrybutami, a wyniki-struktury odbudowuje w ten sam sposób.
+
+`cThunk(fn)` to odwrotność dla `addr(fn)`: wewnętrzna funkcja z sygnaturą C, która składa parametry-struktury
+z rejestrów albo pamięci `byval`, woła funkcję Fincha i obniża jej wynik. Akceptowane są tylko sygnatury zgodne z C.
+
+`tests/run/c_structs.fn` sprawdza `{float,float}`, `{float,float,float}`, `{u8×4}`, `{double,int}`,
+`{i64,i64}`, strukturę 24-bajtową, strukturę z `char[8]`, zagnieżdżone struktury, wyczerpanie rejestrów
+przy 13 argumentach-strukturach oraz wywołania zwrotne przyjmujące i zwracające struktury, na bibliotece C
+kompilowanej przez `link "abi.c"`. Wyniki zgadzają się z tymi samymi wywołaniami zrobionymi z C.
+
+### 13.4 Linkowanie kodu C
+
+`link "nazwa"` próbuje `pkg-config --libs nazwa`, a potem `-lnazwa`. `link "plik.c"` kompiluje plik
+(względem pliku `.fn`) przez `$CC -O2 -fPIC -c` do tymczasowego obiektu; pliki `.o` i `.a` przechodzą
+bez zmian. Gdy linkowanie się nie uda, `linkFailed()` mapuje niezdefiniowane symbole na nagłówek, który
+je zadeklarował, i wypisuje linijkę `link` do dopisania.
+
+---
+
+## 14. Optymalizacja, emisja, linkowanie
+
+- **Cel:** `sys::getDefaultTargetTriple()`, CPU `generic`, `Reloc::PIC_`. Data layout jest ustawiany na
+ module, zanim powstanie jakikolwiek IR (od niego zależy wyrównanie loadów i store'ów).
+- **Optymalizacja:** `PassBuilder::buildPerModuleDefaultPipeline(O2)`: środkowa część `-O2` z clanga.
+ `-O0` ją pomija (`finch ir plik.fn -O0` pokazuje surowe wyjście `Codegen`).
+- **Emisja:** stary `PassManager` + `addPassesToEmitFile(ObjectFile)`.
+- **Linkowanie:** `$CC prog.o rt.o [obiekty użytkownika] -lm [biblioteki]`. Na Niksie wrapper cc zamienia
+ ścieżki `-L` na `RPATH`, więc programy działają bez `LD_LIBRARY_PATH`.
+- **`run`:** tymczasowy plik wykonywalny uruchamiany z resztą argumentów i potem usuwany; sygnał jest
+ zgłaszany jako `the program crashed: ` z kodem wyjścia `128 + n`.
+
+Uwagi o wydajności: funkcje użytkownika są `internal`, więc LLVM swobodnie je wstawia i specjalizuje
+(rekurencyjne `fib(40)`: ok. 0,19 s wobec ok. 0,23 s dla `clang -O2` na tym samym kodzie C na maszynie
+autora); kod tablicowy z kontrolą zakresu dorównuje C na sicie.
+
+---
+
+## 15. Informacje dla debuggera
+
+Z `-g` `Codegen` tworzy `DIBuilder`, jednostkę kompilacji (`DW_LANG_C`, więc składnia wyrażeń C w gdb
+działa dla `print p.x`), `DIFile` na plik źródłowy, `DISubprogram` na funkcję, zmienne-parametry
+(`createParameterVariable`, więc `bt` pokazuje `length (v=…)`), zmienne lokalne (`insertDeclare` na ich
+`alloca`) i `DILocation` na każdą instrukcję i wywołanie (`setLoc`). Typy: podstawowe z nazwami Fincha
+(`int`, `u8`, `float`), `str`/tablice jako struktury `{ptr, len, cap}`, wskaźniki, tablice stałe i struktury
+z przesunięciami pól z `StructLayout` LLVM. Struktury rekurencyjne używają zastępowalnej deklaracji
+wyprzedzającej. Generowane helpery nie mają lokacji. Najlepiej działa `-g -O0`; przy O2 część zmiennych jest usuwana.
+
+---
+
+## 16. Diagnostyka
+
+`failAt(plik, linia, kol, msg)` wypisuje `ścieżka:linia:kol: error: msg` i linijkę źródła ze znakiem `^`
+(tabulatory zachowane, kolumny liczone w znakach), a potem kończy proces z kodem 1. Konwencje: operatory
+binarne raportują operator, wywołania nazwę funkcji, błędy argumentów pozycję argumentu. Komunikaty mówią,
+co jest źle, a gdy naprawa jest oczywista, także jak to naprawić. Ostrzeżeń nie ma. Błąd weryfikatora LLVM
+wypisuje `internal compiler error (please report)` i kończy proces z kodem 2.
+
+---
+
+## 17. Kompilator samohostujący (`boot/`)
+
+`boot/` to drugi kompilator Fincha napisany w Finchu (ok. 3100 linii):
+
+| Plik | Zawartość |
|---|---|
-| `void` | nic |
-| `_Bool` | `bool` |
-| `char` (dowolna znakowość) | `char` |
-| `signed char`, `short`, `int`, `long`, `long long`, `enum` | `i8`/`i16`/`i32`/`i64` wg rozmiaru |
-| warianty `unsigned` | `u8`…`u64` wg rozmiaru |
-| `float` / `double` | `f32` / `f64` |
-| `char *` (dowolne kwalifikatory) | `str` |
-| `T *`, gdzie T mapuje się na typ niebędący void | `ptr[T]` |
-| `void *`, wskaźnik na struct/union/funkcję/typ niemapowalny | `ptr` (nieprzezroczysty) |
-| struct/union przez wartość | **nieobsługiwane** („passes a C struct by value”) |
-| `long double`, `__int128`, wektory, … | **nieobsługiwane** |
+| `boot/lexer.fn` | lekser (tokeny to `{kind, text, line, col, nl}`; rodzaje to słowa, a słowa kluczowe i operatory są swoim własnym rodzajem) |
+| `boot/ast.fn` | `Type` (`kind`, `elem` jako tablica 0/1 elementów, `name`, `module`), jeden ogólny `Node` na wszystko, `Program` |
+| `boot/parser.fn` | ta sama gramatyka i reguły nowych linii co parser C++, wspinanie się po priorytetach dla operatorów binarnych |
+| `boot/gen.fn` | sprawdzanie typów + generator **tekstowego LLVM IR**: zasięgi, miejsca, konwersje, kopie, helpery, wypisywanie, kontrole, metody tablic i tekstów |
+| `boot/main.fn` | ładowanie modułów i sterownik: zapisuje `.ll`, uruchamia `clang -O2 plik.ll runtime/finch_rt.c` |
-Funkcje nieobsługiwane zostają w tablicy, więc próba ich wywołania daje precyzyjny błąd zamiast
-„nie ma takiej funkcji”.
+Obsługuje rdzeń języka: `int float bool char str`, `[]T`, struktury (wartości domyślne, konstruktory po
+nazwie i po kolei), `ptr[T]`/`addr`/`new`/`free`/`null`, wszystkie instrukcje poza `defer`, moduły oraz
+funkcje wbudowane i metody kompilatora C++, bez importu C, liczb z rozmiarem i `defer`. Ma tę samą
+semantykę wartości i analizę pożyczania (kopiuje tam, gdzie główny kompilator), ale **nigdy nie zwalnia**:
+jako kompilator bootstrapowy zostawia pamięć systemowi operacyjnemu.
-### Makra: `macroValue()`
+Stan kompilatora to jedna struktura `Gen` przekazywana jako `ptr[Gen]`: Finch nie ma zmiennych globalnych,
+a przekazanie wskaźnika to sposób, w jaki funkcja zmienia wartość wywołującego. Bez `defer` i typu map zasięgi
+to tablice tablic przeszukiwane liniowo; funkcje pomocnicze (kopiowanie/wypisywanie/porównywanie na typ)
+są generowane przez tymczasową podmianę buforów wyjścia (`beginHelper`/`endHelper`).
-Tokenizuje zakres makra, odrzuca token nazwy i komentarze, zdejmuje **zrównoważone nawiasy
-zewnętrzne**, akceptuje jeden wiodący `-` i wymaga **dokładnie jednego** literału liczbowego.
-Liczby całkowite są czytane przez `strtoull(…, 0)`, czyli według **reguł C**: `0x` szesnastkowo
-i zero wiodące jako **ósemkowe**, inaczej niż literały samego Fincha. Zmiennoprzecinkowe to
-wszystko, co zawiera `.`, `e` lub `E` i nie jest szesnastkowe. Akceptowane są przyrostki
-`u U l L f F`. To obejmuje `M_PI`, `EOF` (`(-1)`), `RAND_MAX`, `INT_MAX` oraz stałe `GL_*` i `GLFW_*`.
-Wyrażenia (`(1 << 4)`), odwołania do innych makr i teksty są pomijane.
+**Punkt stały.** `tests/boot.sh`:
-### Wywoływanie
+1. `build/finch` (C++) kompiluje `boot/` → **stage 1**,
+2. stage 1 kompiluje `boot/` → **stage 2** (i jego IR),
+3. stage 2 kompiluje `boot/` → IR **stage 3**,
-Zaimportowane funkcje są deklarowane w module **dopiero przy wywołaniu**, przez
-`getOrInsertFunction`. To używa istniejącej deklaracji, jeśli `print` już utworzył `printf`.
-Sygnatury się zgadzają (`i32 (ptr, ...)`), więc nie ma konfliktu.
-
-### Błędy linkowania
-
-Gdy linkowanie się nie uda, `linkFailed()` w `main.cpp` przeszukuje wyjście linkera pod kątem
-`undefined reference to \`sym'` (GNU ld) i `undefined symbol: sym` (lld). Grupuje symbole według
-`CFunc::header` i wypisuje, z którego nagłówka pochodzą, razem z odgadniętą linijką `link`
-(`guessLib()`: nazwa pliku bez rozszerzenia i końcowych cyfr, małymi literami:
-`GLFW/glfw3.h` → `glfw`). `cannot find -lfoo` zamienia się w `the library 'foo' wasn't found`.
+i wymaga, żeby IR ze stage 2 i stage 3 był **identyczny co do bajtu** (ok. 32 000 linii). Potem kompiluje
+kompilatorem samohostującym każdy test z `tests/run`, który mieści się w podzbiorze języka, i porównuje
+wynik z oczekiwanym. Kompilacja samego siebie zajmuje kompilatorowi samohostującemu ok. 2,3 s.
---
-## 11. Optymalizacja, emisja, linkowanie
+## 18. System budowania
-### Cel
-
-`hostMachine()` tworzy `TargetMachine` dla `sys::getDefaultTargetTriple()` z CPU `"generic"`,
-bez dodatkowych cech i z `Reloc::PIC_`, które jest wymagane dla domyślnych plików PIE we
-współczesnych dystrybucjach. Jej **data layout jest ustawiany na module, zanim powstanie
-jakikolwiek IR**, w konstruktorze `Codegen`. To ważne: `IRBuilder` bierze wyrównanie loadów
-i store'ów z data layoutu i bez niego dostępy do `i64` byłyby emitowane z `align 4`.
-
-`generic` znaczy, że plik zadziała na każdym x86-64. Zachowanie w stylu `-march=native` wymagałoby
-zmiany nazwy CPU i cech w tym miejscu.
-
-### Optymalizacja
-
-`optimize()` buduje potok nowego menedżera przebiegów:
-
-```cpp
-PassBuilder pb(tm);
-pb.register{Module,CGSCC,Function,Loop}Analyses(...);
-pb.crossRegisterProxies(lam, fam, cgam, mam);
-pb.buildPerModuleDefaultPipeline(OptimizationLevel::O2).run(mod, mam);
-```
-
-To jest środkowa część `-O2` z clanga: SROA/mem2reg, instcombine, inlining, GVN, LICM,
-rozwijanie pętli, wektoryzacja i tak dalej. `-O0` pomija to wywołanie, co przydaje się do
-czytania IR wyprodukowanego przez `Codegen` (`finch ir plik.fn -O0`). Generator kodu maszynowego
-i tak działa na domyślnym poziomie TargetMachine.
-
-### Emisja
-
-`emitObject()` używa **starego** (legacy) menedżera przebiegów, bo potok kodu maszynowego LLVM
-jest wciąż dostępny tylko przez niego: `tm->addPassesToEmitFile(pm, out, nullptr, CodeGenFileType::ObjectFile)`.
-
-### Linkowanie
-
-```
-$CC plik.o -o plik -lm [pkg-config --libs NAZWA | -lNAZWA]...
-```
-
-Jako linker służy systemowy sterownik kompilatora C (domyślnie `cc`). Zna pliki crt platformy,
-ścieżkę dynamicznego loadera, libc i domyślne ścieżki bibliotek, a odtwarzanie tego byłoby
-pracochłonne. `-lm` jest dodawane zawsze. Dla każdego `link "NAZWA"` i `-l NAZWA` najpierw
-próbowane jest `pkg-config --libs NAZWA`, żeby dołączyć ścieżki `-L` i biblioteki zależne.
-Jeśli to się nie uda, używane jest `-lNAZWA`. Na Niksie wrapper cc zamienia ścieżki `-L`
-w wpisy `RPATH`, więc wynik działa bez `LD_LIBRARY_PATH`. Plik obiektowy jest usuwany po
-linkowaniu, niezależnie od wyniku.
-
-### `finch run`
-
-Kompiluje do pliku tymczasowego (`sys::fs::createTemporaryFile`), uruchamia go przez `system()`,
-usuwa i przekazuje dalej kod wyjścia. Jeśli proces potomny zginął od sygnału, wypisuje
-`the program crashed: ` i zwraca `128 + numer_sygnału`, jak powłoka.
+- `find_package(LLVM CONFIG)`; **jeśli istnieje współdzielony cel `LLVM`, Finch linkuje tę jedną bibliotekę.**
+ `libclang.so` sam linkuje `libLLVM.so`; linkowanie dodatkowo statycznych bibliotek komponentów wprowadzało
+ do procesu dwie kopie LLVM, a zduplikowane obiekty globalne wywracały program przy wyjściu (podwójne
+ zwolnienie w globalnym destruktorze). Do tego binarka miała 82 MB zamiast ok. 20 MB.
+- libclang: `find_path(clang-c/Index.h)` + `find_library(clang)`.
+- `runtime/finch_rt.c` jest wczytywany przy konfiguracji do `build/rt_source.inc` jako surowy literał
+ napisowy; `CMAKE_CONFIGURE_DEPENDS` ponawia konfigurację, gdy plik się zmieni.
+- `FINCH_VERSION` z `project(VERSION 2.0.0)`. C++17, `-Wall -Wextra`, bez ostrzeżeń.
+- `shell.nix` wymienia `llvmPackages.clang` przed `llvmPackages.libclang`: ten drugi dostarcza też
+ „gołego” `clang`, który nie widzi nagłówków systemowych.
---
-## 12. Diagnostyka
+## 19. Testy
-`fail(line, col, msg)` jest `[[noreturn]]`: wypisuje
-
-```
-plik:linia:kol: error: msg
- linia | tekst źródła
- | ^
-```
-
-i kończy proces z kodem 1. Fragment jest brany z `g_source`, a tabulatory w prefiksie są
-zachowywane, żeby `^` trafiło w miejsce. Konwencje w całym kodzie:
-
-- **Pozycje:** operatory binarne raportują pozycję **operatora**, wywołania pozycję nazwy funkcji,
- a błędy typów w argumentach pozycję **argumentu**.
-- **Komunikaty** mówią prostymi słowami, co jest źle, i **jak to naprawić**, gdy naprawa jest
- oczywista (`use int(...) to convert`, `did you mean ':='?`, `add import "math.h"`).
-- `headerHint()` mapuje około dwóch tuzinów popularnych nazw z libc/libm na ich nagłówki, na
- potrzeby wywołań nieznanych funkcji.
-- Wewnętrzna niespójność, jeśli `verifyModule` kiedykolwiek zawiedzie, wypisuje
- `internal compiler error (please report)` z wyjściem weryfikatora i kończy proces z kodem 2.
-
-Kompilator nie ma ostrzeżeń. Wszystko, co byłoby ostrzeżeniem, jest albo w porządku, albo błędem.
+- `tests/run.sh`: każdy `tests/run/*.fn` z `fn main` musi wypisać dokładnie swój `.out` (stdin z `.in`, jeśli
+ jest); pliki bez `main` to moduły albo pliki pomocnicze. Każdy `tests/fail/*.fn` musi się nie udać z tekstem
+ z linijki `// expect:`. Obecnie **58 passed, 0 failed**.
+- `MEMCHECK=1 tests/run.sh`: to samo plus valgrind na każdym programie (bez wycieków i złych dostępów).
+- `tests/boot.sh`: punkt stały samohostowania i przebieg na podzbiorze (§17).
---
-## 13. System budowania
+## 20. Semantyka a C
-`CMakeLists.txt`:
-
-- `find_package(LLVM CONFIG)`. **Jeśli istnieje cel `LLVM` (współdzielone `libLLVM.so`), Finch linkuje
- tę jedną bibliotekę.** To nie kosmetyka, tylko konieczność: `libclang.so` sam linkuje `libLLVM.so`.
- Linkowanie Fincha ze statycznymi bibliotekami komponentów (`libLLVMCore.a`, …) wprowadza do procesu
- **drugą kopię LLVM**. Obie kopie rejestrują te same obiekty globalne i program wywracał się przy
- wyjściu w globalnym destruktorze (`std::vector::~vector`, podwójne zwolnienie).
- Do tego binarka miała 82 MB zamiast 11 MB.
-- libclang jest szukany przez `find_path(clang-c/Index.h)` i `find_library(clang)`, z podpowiedzią katalogów LLVM.
-- `FINCH_VERSION` pochodzi z `project(VERSION …)` i jest wypisywane przez `finch version` razem z `LLVM_VERSION_STRING`.
-- C++17, `-Wall -Wextra`, a budowanie przechodzi bez ostrzeżeń.
-
----
-
-## 14. Testy
-
-`tests/run.sh` (ścieżkę kompilatora nadpisuje `FINCH=…`):
-
-- `tests/run/*.fn`: uruchamiane przez `finch run`. Połączone stdout+stderr musi być dokładnie równe `*.out`.
-- `tests/fail/*.fn`: muszą zakończyć się kodem różnym od zera, a wyjście musi zawierać tekst po
- `// expect: ` z pierwszej linii. To obejmuje błędy kompilacji, błędy w czasie działania (z numerami
- linii) oraz problemy z nagłówkami i linkowaniem. `tests/fail/lib_hint.h` deklaruje funkcję, której
- nie ma w żadnej bibliotece, żeby przetestować podpowiedź `link`.
-
-Stan obecny: **34 passed, 0 failed.** Każdy nowy komunikat błędu powinien dostać test w `fail/`,
-a każda nowa funkcjonalność test w `run/`.
-
----
-
-## 15. Semantyka a C: zachowania zdefiniowane i niezdefiniowane
-
-| Sytuacja | C | Finch 1.0 |
+| Sytuacja | C | Finch 2.0 |
|---|---|---|
-| Przepełnienie liczby ze znakiem | UB | **Zdefiniowane zawinięcie** (bez flag `nsw`) |
-| Przepełnienie liczby bez znaku | zawija się | zawija się |
-| Dzielenie całkowite przez zero | UB | **Błąd kompilacji**, jeśli stałe, w przeciwnym razie **błąd w czasie działania** |
-| `INT_MIN / -1` | UB | **Błąd w czasie działania** |
-| Load/store przez null | UB | **Błąd w czasie działania** dla `.value` |
-| `x & 1 == 0` | parsuje się jako `x & (1 == 0)` | parsuje się jako `(x & 1) == 0` |
-| Niejawne zwężenie (`int` → `char`) | po cichu | **Błąd kompilacji** |
-| Mieszanie ze znakiem / bez znaku | cicha konwersja | **Błąd kompilacji**, chyba że jedna strona się rozszerza |
-| Użycie zmiennej przed nadaniem wartości | UB | Niemożliwe: każda deklaracja inicjalizuje (domyślnie zerem) |
-| Brak `return` w funkcji z wynikiem | UB, jeśli wynik jest użyty | **Błąd kompilacji** |
-| Przesunięcie o ≥ szerokość bitową | UB | **Nadal poison** (w 1.0 niesprawdzane) |
-| `float` → int poza zakresem | UB | **Nadal poison** (w 1.0 niesprawdzane) |
-| Wiszący wskaźnik na martwą zmienną lokalną | UB | **Nadal UB** (brak kontroli czasu życia) |
-| Kolejność obliczeń | w większości nieokreślona | Od lewej do prawej |
+| Przepełnienie liczby ze znakiem | UB | zdefiniowane zawinięcie (bez `nsw`) |
+| Dzielenie całkowite przez zero, `INT_MIN / -1` | UB | błąd kompilacji, jeśli stałe, inaczej błąd w czasie działania |
+| Indeks tablicy poza zakresem | UB | błąd w czasie działania (także dla pól `[N]T` struktur C) |
+| Dereferencja null | UB | błąd w czasie działania dla `.value`, `.pole`, `[i]` |
+| Użycie po zwolnieniu / podwójne zwolnienie tablic, tekstów, struktur | częste błędy | niemożliwe: tych nie zwalnia się ręcznie |
+| Wyciek tablic, tekstów, struktur | częsty błąd | niemożliwy bez `new` |
+| `x & 1 == 0` | `x & (1 == 0)` | `(x & 1) == 0` |
+| Niejawne zwężanie, mieszanie ze znakiem / bez znaku | po cichu | błędy kompilacji |
+| Niezainicjalizowane zmienne | UB | niemożliwe (zero albo wartości domyślne) |
+| Brak return | UB, jeśli użyty | błąd kompilacji |
+| Kolejność obliczeń | w większości nieokreślona | od lewej do prawej; w przypisaniu najpierw prawa strona |
+| Przesunięcie ≥ szerokość, float→int poza zakresem | UB | nadal poison (jeszcze niesprawdzane) |
+| Wiszący wskaźnik z `addr()` / `new` + `free` | UB | nadal UB |
---
-## 16. Spis funkcji: kto co robi
-
-Grupy funkcji o podobnych nazwach i czym się różnią.
-
-### Leksowanie i parsowanie
-
-| Funkcja | Robi | Różni się od |
-|---|---|---|
-| `lex()` | tekst → tokeny, ustawia `newlineBefore` | nic nie wie o gramatyce ani typach |
-| `tokName()` | rodzaj tokenu → nazwa czytelna dla człowieka (do błędów) | |
-| `Parser::cur()` / `peekTok()` / `next()` | bieżący token / podgląd bez konsumowania / konsumpcja | |
-| `accept(k)` kontra `expect(k)` | konsumuje, jeśli pasuje, i zwraca bool / konsumuje albo **zgłasza błąd** „expected …, found …” | |
-| `unexpected(ctx)` | błąd „unexpected X (ctx)” na bieżącym tokenie | `expect` mówi, czego chciał; `unexpected` tłumaczy kontekst |
-| `sameLine()` | czy bieżący token może kontynuować wyrażenie? | `endOfStatement()` to odpowiednik na poziomie instrukcji (wymaga nowej linii albo `}`) |
-| `atDeclaration()` | podgląd `int x` / `ptr[...] p` | `type()` faktycznie konsumuje typ |
-| `typeFromName()` kontra `isTypeName()` | nazwa → `Type` / nazwa → bool | |
-| `statement()` kontra `block()` | jedna instrukcja / `{ instrukcja* }` z kontrolą końców i zerowaniem `parenDepth` | |
-| `callArgs()` | wspólne dla wywołań i konwersji: `( expr, … )` | |
-| `primary()` / `postfix()` / `unary()` / `multiplicative()` / `additive()` / `compare()` / `andExpr()` / `orExpr()` | po jednej funkcji na poziom priorytetu | |
-
-### Codegen: typy i wartości
-
-| Funkcja | Robi | Różni się od |
-|---|---|---|
-| `ty(FType)` | typ Fincha → typ LLVM | |
-| `zero(FType)` | wartość domyślna dla deklaracji bez inicjalizatora | |
-| `widens(from, to)` | czy każdą wartość `from` da się przedstawić w `to`? (czysta reguła typów) | `fits()` pyta o to samo dla jednej stałej |
-| `fits(c, from, to)` | czy wartość tego literału mieści się w `to`? | |
-| `coerce(v, want)` | **niejawna** konwersja do miejsca o znanym typie albo błąd | `convert()` to **jawne** `T(x)` i pozwala na stratne rzutowania; `unify()` zbliża do siebie **dwa** argumenty |
-| `unify(l, r)` | sprowadza argumenty binarne do wspólnego typu | używane przez `arithOp()` i `compare()` |
-| `intCast()` / `intToFloat()` | emitują rzutowanie z poprawną znakowością | niskopoziomowe pomocniki trzech powyższych |
-
-### Codegen: wyrażenia
-
-| Funkcja | Robi | Różni się od |
-|---|---|---|
-| `expr(e)` | r-wartość: emituje kod, zwraca `{wartość, typ, literal}` | `place()` zwraca **adres**, zamiast ładować |
-| `place(e)` | adres l-wartości: zmiennej, zmiennej globalnej C albo `p.value` | używane przez `expr()` (potem load), `assign()` (potem store) i `addrOf()` (zwraca bez zmian) |
-| `binary()` | rozgałęzienie po `BinOp` | |
-| `arith()` kontra `arithOp()` | `arith()` opakowuje `arithOp()` i liczy flagę `literal` wyniku | `arithOp()` robi `unify` i emituje instrukcję |
-| `compare()` | wszystkie porównania: wskaźniki/str/float/int | zawsze zwraca `bool` i nigdy nie jest literałem |
-| `logic()` | `&&`/`\|\|` ze skokami i φ | jedyne miejsce budujące φ ręcznie |
-| `call()` | zamienia nazwę wywołania na jeden z pięciu rodzajów | |
-| `callFinch()` kontra `callC()` | zobacz rozdział 8.10 | |
-| `print()` | buduje format printf z typów | |
-| `addrOf()` | `addr(x)` → `place(x).addr` | |
-| `convert()` | `T(x)` | |
-| `libc()` | `getOrInsertFunction`: zadeklaruj albo użyj istniejącej funkcji zewnętrznej | `declare()` tworzy funkcje **Fincha** przez `Function::Create` |
-| `cGlobal()` | zadeklaruj albo użyj istniejącej zmiennej `extern` | |
-
-### Codegen: instrukcje i sterowanie
-
-| Funkcja | Robi | Różni się od |
-|---|---|---|
-| `declare()` kontra `define()` | przejście sygnatur / przejście ciał | |
-| `block()` kontra `blockBody()` | `block()` otwiera i zamyka zasięg wokół `blockBody()`; `blockBody()` emituje instrukcje i odrzuca nieosiągalne | `define()` woła `blockBody()` bezpośrednio, bo parametry i zewnętrzne ciało dzielą jeden zasięg |
-| `stmt()` | rozgałęzienie po `StmtKind`; obsługuje też `break`/`continue` | |
-| `varDecl()` / `assign()` | deklaracja z opcjonalnym wnioskowaniem / zapis do istniejącego miejsca | |
-| `addVar()` | tworzy miejsce, zapisuje wartość początkową, rejestruje nazwę (z kontrolą przesłaniania) | `slot()` tylko tworzy `alloca` |
-| `lookup()` | szuka we wszystkich zasięgach, od najbardziej wewnętrznego | |
-| `condition(e)` | `coerce(expr(e), bool)` z komunikatem „a condition must be bool” | |
-| `ifStmt()` / `whileStmt()` / `forStmt()` / `returnStmt()` | rozdział 8.5 | |
-| `terminated()` | czy bieżący blok kończy się już terminatorem? | |
-| `continueAt(bb, deadEnd)` | przejdź do bloku łączącego albo usuń go, jeśli jest nieosiągalny | |
-| `newBlock()` | tworzy blok podstawowy w bieżącej funkcji | |
-
-### Kontrole w czasie działania
-
-| Funkcja | Robi |
-|---|---|
-| `panicFn()` | pobiera albo tworzy `finch.panic` |
-| `panicIf(cond, msg, pos)` | warunkowy skok do bloku z panic |
-| `checkDivisor(l, r, pos)` | kontrole zera i `MIN/-1` albo błąd kompilacji |
+## 21. Spis funkcji
### Sterownik (`main.cpp`)
| Funkcja | Robi |
|---|---|
-| `hostMachine()` | tworzy `TargetMachine` |
-| `optimize()` | potok O2 |
-| `emitObject()` | zapisuje `.o` |
-| `capture()` | uruchamia polecenie powłoki, zbiera wyjście i kod wyjścia |
-| `libFlags()` | pkg-config albo `-l` dla jednej biblioteki |
-| `link()` | uruchamia linker, usuwa `.o`, tłumaczy porażki |
-| `linkFailed()` / `guessLib()` | zamieniają błędy linkera na porady w języku Fincha |
+| `Loader::load` / `findModule` | czyta, leksuje i parsuje plik oraz rekurencyjnie jego moduły / szuka `nazwa.fn` |
+| `hostMachine`, `optimize`, `emitObject` | maszyna docelowa, potok O2, plik obiektowy |
+| `runtimeObject` | kompiluje runtime i trzyma go w cache |
+| `capture`, `libFlags`, `link` | uruchamia polecenie / pkg-config albo `-l` / linkowanie, z kompilacją plików z `link "x.c"` |
+| `linkFailed`, `guessLib` | zamieniają wyjście linkera na porady |
+
+### Lekser i parser
+
+| Funkcja | Robi | Różni się od |
+|---|---|---|
+| `lex` / `tokName` | tekst → tokeny / nazwy do komunikatów | |
+| `accept` / `expect` / `unexpected` | konsumuje, jeśli pasuje / konsumuje albo zgłasza błąd / błąd z kontekstem | |
+| `sameLine` / `endOfStatement` | czy ten token może kontynuować wyrażenie? / czy instrukcja się skończyła? | |
+| `atDeclaration` / `type` | podgląd deklaracji / parsowanie typu | |
+| `args` | `( [nazwa:] expr, … )` dla wywołań, metod i konstruktorów | |
+| `postfix` | łańcuchy `.pole`, `.metoda(…)`, `[i]` | `primary` parsuje początek łańcucha |
+
+### Codegen: program i instrukcje (`codegen.cpp`)
+
+| Funkcja | Robi | Różni się od |
+|---|---|---|
+| `declareStructs` / `declareFns` / `declare` | wszystkie typy / wszystkie sygnatury / jedna sygnatura + analiza pożyczania | |
+| `define` / `defineMainWrapper` | jedno ciało / punkt wejścia C | |
+| `pushScope` / `popScope` | otwiera / zamyka zasięg (`popScope` emituje jego sprzątanie, jeśli osiągalne) | |
+| `emitCleanups(downTo)` / `emitScopeCleanups(i)` | sprzątanie kilku zasięgów bez zamykania (return/break) / jednego zasięgu | |
+| `slot` / `tmpOf` / `tmp` | `alloca` zmiennej / anonimowa / anonimowa z wartością | |
+| `lookup` / `addVar` | szuka widocznej zmiennej (respektuje `deferLimit`) / deklaruje zmienną | |
+| `stmt`, `varDecl`, `assign`, `ifStmt`, `whileStmt`, `forStmt`, `forEachStmt`, `returnStmt`, `jump` | instrukcje | |
+| `rootVar` / `mutates` / `mutatesExpr` | zmienna, od której zaczyna się l-wartość / czy instrukcja albo wyrażenie ją zmienia? | |
+
+### Codegen: wyrażenia
+
+| Funkcja | Robi | Różni się od |
+|---|---|---|
+| `expr` / `exprWant` | r-wartość / to samo z oczekiwanym typem dla literałów tablic | |
+| `ref` / `place` | adres, jeśli jest, inaczej wartość / adres albo błąd | `expr` wczytuje z `ref` |
+| `member` / `index` | `.x`, `.len`, `.ptr`, `.value`, auto-dereferencja / `[i]` z kontrolą zakresu i kopiowaniem przy zapisie | |
+| `arrayLit` | `[…]` → tablica na stercie | |
+| `unary`, `binary`, `arith`/`arithOp`, `compare`, `logic` | operatory (`arith` dokłada flagę literału) | |
+| `unify` / `coerce` / `convert` | dwa argumenty do siebie / jedna wartość do miejsca / jawne `T(x)` | |
+
+### Codegen: typy i własność (`codegen_types.cpp`)
+
+| Funkcja | Robi |
+|---|---|
+| `ty`, `resolve`/`resolveT`, `findStruct`, `resolveStruct` | typ LLVM; Named → Struct (głęboko albo płytko); szukanie struktury; układ + kontrola cykli |
+| `owning`, `zero`, `defaultValue` | czy typ posiada pamięć; stała zerowa; zero albo wartości domyślne struktury |
+| `own` / `release` | coś do zapisania (przeniesienie albo kopia) / zwolnienie niezatrzymanej wartości fresh |
+| `copyValue`, `dropValue`, `copyAt`, `dropAt`, `copyFn`, `dropFn`, `typeKey` | kopiowanie i zwalnianie przez wartość, przez adres i helpery na typ |
+| `forN` | wewnętrzna pętla `for i in 0..n` używana przez helpery |
+| `strConst`, `cstr`, `strFromC`, `elemSize` | stała-literał str; str → `char*`; `char*` → pożyczony str; rozmiar elementu |
+| `emitPrint`, `printFn`, `printf_` | wypisywanie |
+| `diType`, `setLoc` | informacje dla debuggera |
+
+### Codegen: wywołania (`codegen_builtins.cpp`) i ABI (`abi.cpp`)
+
+| Funkcja | Robi |
+|---|---|
+| `call` / `method` | rozwiązuje wywołanie / metodę albo wywołanie z modułu |
+| `callFinch` / `construct` / `callC` | funkcja Fincha / konstruktor struktury / funkcja C |
+| `print`, `addrOf`, `convert`, `arrayMethod` | funkcje wbudowane |
+| `rt` / `libc` | deklaruje funkcję runtime'u / libc |
+| `fileName`, `panicIf`, `boundsCheck`, `checkDivisor` | kontrole w czasie działania |
+| `classify`, `makePlan`, `declareC`, `emitCCall`, `cThunk` | klasyfikacja System V, plan rejestrów, deklaracja, wywołanie, wrapper dla callbacków |
---
-## 17. Rozbudowa kompilatora
+## 22. Rozbudowa kompilatora
-### Nowa funkcja wbudowana (przykład: `len(str) -> int`)
+**Funkcja wbudowana:** dodaj nazwę do `isBuiltin()`, obsłuż ją w `call()`, zaimplementuj (w IR albo jako
+funkcję runtime'u zadeklarowaną w tabeli `rt()`), dodaj przypadki w `tests/run/` i `tests/fail/`. Jeśli ma
+istnieć też w kompilatorze samohostującym, dodaj ją do `call()` i `runtimeDecls()` w `boot/gen.fn`.
-1. Dodaj nazwę do `Codegen::isBuiltin()`, żeby nie dało się jej zdefiniować ponownie.
-2. W `Codegen::call()` dodaj rozgałęzienie `if (c.callee == "len") return len(c);`.
-3. Zaimplementuj: sprawdź liczbę argumentów, `coerce(expr(arg), Str, …)`, wyemituj `strlen` przez `libc()`,
- zwróć `{call, I64}`. `strlen` zwraca `u64`; jeśli chcesz `int`, przekonwertuj przez `intCast`.
-4. Dodaj `tests/run/len.fn` z plikiem `.out` i przypadek w `tests/fail/` dla złego argumentu.
+**Metoda tablicy albo tekstu:** dodaj gałąź w `arrayMethod()` albo w części `str` w `method()`; jeśli
+zmienia tablicę, dopisz jej nazwę do zbiorów `changing` w `method()` i `mutatesExpr()`, żeby pożyczanie
+dalej było poprawne.
-### Nowy operator binarny
+**Operator:** token leksera + `tokName`, `BinOp`, właściwa funkcja priorytetu w parserze, `opName`, emisja
+w `arithOp`/`compare` i wyjaśnienie w `badOperands`.
-1. Lekser: nowy `Tok`, jego zapis w `switch` operatorów i w `tokName()`.
-2. AST: nowy `BinOp`.
-3. Parser: umieść go w funkcji właściwego poziomu priorytetu.
-4. Codegen: `opName()`, emisja w `arithOp()` albo `compare()` i wyjaśnienie złych argumentów w `badOperands()`.
+**Instrukcja:** `StmtKind` + węzeł, `Parser::statement`, `Codegen::stmt`; jeśli przenosi sterowanie,
+respektuj `terminated()`, używaj `continueAt()` dla bloków łączących i emituj sprzątanie (`emitCleanups`)
+przed wyskokiem z zasięgów.
-### Nowa instrukcja
-
-Dodaj `StmtKind` i węzeł w `ast.h`, rozpoznaj ją w `Parser::statement()` i obsłuż w `Codegen::stmt()`.
-Jeśli może przenosić sterowanie, upewnij się, że wynik respektuje `terminated()` i używa `continueAt()`
-dla swojego bloku łączącego.
-
-### Konwencje
-
-Trzymaj się istniejącego stylu: wczesne `fail()` z podpowiedzią naprawy, komentarze tylko tam,
-gdzie *dlaczego* nie jest oczywiste, i jeden test na jedno zachowanie.
+**Nowy typ posiadający pamięć:** rozszerz `owning()`, `dropAt`/`copyAt` (i ich helpery), `emitPrint`, `diType`.
---
-## 18. Znane ograniczenia i plan
+## 23. Znane ograniczenia
-- **Brak typów złożonych:** `struct`, tablice/wycinki z `.len` i struktury C przez wartość. To kolejne
- kamienie milowe, w tej kolejności: tablice opierają się na strukturach (wskaźnik + długość),
- a model pamięci na obu.
-- **Model pamięci** (uzgodniony projekt): wartości posiadające pamięć na stercie są zwalniane
- automatycznie na końcu bloku, który je posiada, `defer` uruchamia kod przy wyjściu z bloku,
- a `free` zostaje do ręcznej kontroli. Szkic implementacji: lista sprzątania na blok, emitowana na
- każdej krawędzi wyjścia (normalny koniec, `return`, `break`, `continue`). `blockBody()` i stos
- `loops` już znają każdą krawędź wyjścia.
-- **Teksty:** sklejanie, `len`, `str(x)`. Zależą od modelu pamięci.
-- **Wejście:** `input()`.
-- **Moduły:** `import nazwa` dla kodu w Finchu. Dziś jeden `.fn` = jeden program.
-- **Niesprawdzane UB:** wielkość przesunięcia, zakres float→int, wiszące wskaźniki. Przesunięcia
- i float→int dałoby się sprawdzać jak dzielenie, niewielkim kosztem.
-- **Informacje dla debuggera:** brak DWARF. `DIBuilder` podpiąłby się w `define()` i przy `pos` każdej instrukcji.
-- **Platformy:** tylko triple hosta, testowane na Linux/x86-64. Kod jest niezależny od platformy
- z wyjątkiem wykrywania nagłówków przez `cc -E -v` i użycia `dprintf`.
-- **Bootstrap:** długoterminowym celem jest przepisanie kompilatora w Finchu. Wołanie API LLVM-C już
- działa: LLVM-C używa nieprzezroczystych uchwytów (`LLVMModuleRef` itd.), które mapują się na `ptr`
- Fincha. `examples/llvm.fn` buduje i wypisuje moduł z poziomu Fincha (`import "llvm-c/Core.h"`,
- `link "LLVM"`). Do samodzielnego kompilatora brakuje jeszcze struktur, tablic, tekstów i operacji na plikach.
+- Brak metod w strukturach, typów generycznych, map, `match`, domknięć i błędów jako wartości (`int("x")`, `read_file` robią panic).
+- Teksty to bajty: `.len`, `s[i]`, `upper()` nie znają Unicode.
+- Wiszące wskaźniki (`addr` zmiennej, która zniknęła, użycie po `free`) nie są wykrywane.
+- Wielkość przesunięć i konwersje float→int nie są sprawdzane (poison w LLVM, jak w C).
+- C: unie i struktury z polami bitowymi przez wartość, makra-funkcje, `long double`.
+- Testowana jedna platforma: Linux x86-64 (kod ABI jest specyficzny dla System V x86-64).
+- Kompilator samohostujący nie ma importu C, liczb z rozmiarem ani `defer` i nie zwalnia pamięci.
diff --git a/docs/pl/dla-poczatkujacych.md b/docs/pl/dla-poczatkujacych.md
index bd2659d..4ff632e 100644
--- a/docs/pl/dla-poczatkujacych.md
+++ b/docs/pl/dla-poczatkujacych.md
@@ -20,10 +20,15 @@ Czytaj powoli, po jednym rozdziale, i przepisuj każdy przykład samodzielnie. T
8. [Podejmowanie decyzji: if](#8-podejmowanie-decyzji-if)
9. [Powtarzanie: pętle](#9-powtarzanie-pętle)
10. [Własne polecenia: funkcje](#10-własne-polecenia-funkcje)
-11. [Kiedy coś pójdzie nie tak](#11-kiedy-coś-pójdzie-nie-tak)
-12. [Małe projekty do zrobienia](#12-małe-projekty-do-zrobienia)
-13. [Ściąga](#13-ściąga)
-14. [Słowniczek](#14-słowniczek)
+11. [Listy: wiele wartości w jednym pudełku](#11-listy-wiele-wartości-w-jednym-pudełku)
+12. [Zabawa tekstem](#12-zabawa-tekstem)
+13. [Pytanie użytkownika](#13-pytanie-użytkownika)
+14. [Własne rodzaje wartości: struktury](#14-własne-rodzaje-wartości-struktury)
+15. [Zapisywanie do pliku](#15-zapisywanie-do-pliku)
+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)
---
@@ -72,7 +77,7 @@ Jeśli ostatnia linijka skończy się bez słowa `error`, Finch jest gotowy.
./build/finch version
```
-Powinieneś zobaczyć coś w stylu `finch 1.0.0`.
+Powinieneś zobaczyć coś w stylu `finch 2.0.0`.
---
@@ -446,7 +451,202 @@ Nie ma znaczenia, gdzie w pliku umieścisz swoje funkcje, nad czy pod `main`.
---
-## 11. Kiedy coś pójdzie nie tak
+## 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:
+
+```c
+fn main() {
+ owoce := ["jabłko", "banan", "wiśnia"]
+ print(owoce)
+ print("Mam", owoce.len, "owoce")
+ print("Pierwszy to", owoce[0])
+}
+```
+
+```
+["jabłko", "banan", "wiśnia"]
+Mam 3 owoce
+Pierwszy to jabłko
+```
+
+- Nawiasy kwadratowe `[ ]` tworzą listę. Wartości oddzielamy 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.
+
+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)”.
+
+### Dodawanie i zabieranie
+
+```c
+fn main() {
+ []int wyniki // pusta lista liczb całkowitych
+ wyniki.push(10) // dołóż 10 na koniec
+ wyniki.push(25)
+ wyniki.push(7)
+ print(wyniki) // [10, 25, 7]
+
+ ostatni := wyniki.pop() // zabierz ostatni
+ print(ostatni, wyniki) // 7 [10, 25]
+
+ wyniki.sort() // posortuj
+ print(wyniki)
+}
+```
+
+### Przechodzenie po liście
+
+`for ... in` odwiedza każdą wartość po kolei:
+
+```c
+fn main() {
+ suma := 0
+ for n in [3, 5, 2] {
+ suma += n
+ }
+ print("Suma:", suma) // Suma: 10
+}
+```
+
+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#tablice).
+
+---
+
+## 12. Zabawa tekstem
+
+Teksty sklejasz znakiem `+`:
+
+```c
+fn main() {
+ imie := "Ola"
+ powitanie := "Cześć, " + imie + "!"
+ print(powitanie)
+}
+```
+
+Żeby dokleić liczbę do tekstu, najpierw zamień ją na tekst przez `str(...)`:
+
+```c
+wiek := 10
+print("Wiek: " + str(wiek))
+```
+
+Teksty mają też przydatne narzędzia:
+
+```c
+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")
+ for litera in slowo {
+ print(litera) // F, i, n, c, h, każda w osobnej linijce
+ }
+}
+```
+
+⚠️ `.len` liczy bajty, a polskie litery (ą, ę, ż…) zajmują po 2 bajty. `"żółw".len` to 7, a nie 4.
+
+---
+
+## 13. Pytanie użytkownika
+
+`input` pokazuje pytanie i czeka, aż ktoś wpisze odpowiedź i naciśnie Enter:
+
+```c
+fn main() {
+ imie := input("Jak masz na imię? ")
+ print("Miło cię poznać,", imie)
+}
+```
+
+Odpowiedź jest zawsze **tekstem**. Jeśli potrzebujesz liczby, zamień ją przez `int(...)`:
+
+```c
+fn main() {
+ odpowiedz := input("Ile masz lat? ")
+ wiek := int(odpowiedz)
+ print("Za rok będziesz mieć", wiek + 1)
+}
+```
+
+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”).
+
+---
+
+## 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:
+
+```c
+struct Ksiazka {
+ str tytul
+ str autor
+ int rok
+}
+
+fn main() {
+ k := Ksiazka(tytul: "Hobbit", autor: "Tolkien", rok: 1937)
+ print(k.tytul, "napisał", k.autor)
+ k.rok = 1938
+ print(k)
+}
+```
+
+```
+Hobbit napisał Tolkien
+Ksiazka(tytul: "Hobbit", autor: "Tolkien", rok: 1938)
+```
+
+- `struct Ksiazka { ... }` opisuje, co ma każda książka. Pisz to **poza** `main`.
+- `Ksiazka(tytul: ..., autor: ..., rok: ...)` tworzy jedną książkę.
+- `k.tytul` odczytuje jedną część, a `k.rok = 1938` ją zmienia.
+
+Częściom możesz nadać wartość początkową. Wtedy nie trzeba ich podawać:
+
+```c
+struct Gracz {
+ str imie
+ int zycia = 3
+}
+
+fn main() {
+ g := Gracz(imie: "Ola")
+ print(g.zycia) // 3
+}
+```
+
+Możesz też trzymać wiele struktur na liście: `[]Ksiazka polka`, a potem `polka.push(k)`.
+
+---
+
+## 15. Zapisywanie do pliku
+
+Program zapomina wszystko, kiedy się kończy. Żeby coś zapamiętać, zapisz to do pliku:
+
+```c
+fn main() {
+ write_file("notatka.txt", "Kupić mleko")
+ print(read_file("notatka.txt"))
+}
+```
+
+- `write_file(nazwa, tekst)` zapisuje tekst w pliku (i zastępuje to, co tam było).
+- `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.
+
+---
+
+## 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:
@@ -485,10 +685,13 @@ Tutaj po prostu źle napisaliśmy `wiek`. Komunikat znaczy: „nie ma zmiennej o
| `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(...)` |
---
-## 12. Małe projekty do zrobienia
+## 17. Małe projekty do zrobienia
Spróbuj najpierw napisać każdy sam. Do rozwiązania zajrzyj dopiero, gdy utkniesz.
@@ -595,9 +798,59 @@ 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”.
+
+Rozwiązanie
+
+```c
+fn main() {
+ tajna := 42
+ proby := 0
+ while true {
+ strzal := int(input("Twój strzał: "))
+ proby += 1
+ if strzal < tajna {
+ print("Więcej!")
+ } else if strzal > tajna {
+ print("Mniej!")
+ } else {
+ print("Tak! Potrzebowałeś", proby, "prób")
+ return
+ }
+ }
+}
+```
+
+`return` w `main` kończy program. (Wersja z losową liczbą jest w `examples/guess.fn`.)
+
+
+### Projekt 7: Średnia ocen
+
+Zapytaj o 3 oceny, zapisz je na liście, a potem pokaż listę i średnią.
+
+Rozwiązanie
+
+```c
+fn main() {
+ []int oceny
+ for i in 0..3 {
+ oceny.push(int(input("Ocena: ")))
+ }
+ suma := 0
+ for o in oceny {
+ suma += o
+ }
+ print("Oceny:", oceny)
+ print("Średnia:", float(suma) / oceny.len)
+}
+```
+
+
---
-## 13. Ściąga
+## 18. Ściąga
```c
// komentarz
@@ -630,11 +883,21 @@ fn main() { // tu zaczyna się program
fn dodaj(int a, int b) -> int { // własne polecenie
return a + b
}
+
+struct Zwierzak { // 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 { }
+// 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")
```
---
-## 14. Słowniczek
+## 19. Słowniczek
| Słowo | Znaczenie |
|---|---|
@@ -647,6 +910,10 @@ fn dodaj(int a, int b) -> int { // własne polecenie
| **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` |
+| **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 |
+| **wejście** (input) | To, co użytkownik wpisuje do programu |
| **błąd** (error) | Komunikat, że coś jest nie tak, i gdzie |
| **bug** | Pomyłka w programie |
@@ -662,6 +929,10 @@ fn dodaj(int a, int b) -> int { // własne polecenie
| `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 |
---
diff --git a/docs/pl/dla-technikow.md b/docs/pl/dla-technikow.md
index 69ac141..e638005 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,
-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](dla-inzynierow.md).
+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)
@@ -20,26 +20,37 @@ działa w środku. Do tego służy [przewodnik dla inżynierów](dla-inzynierow.
7. [Operatory](#7-operatory)
8. [Sterowanie przebiegiem](#8-sterowanie-przebiegiem)
9. [Funkcje](#9-funkcje)
-10. [Wskaźniki](#10-wskaźniki)
-11. [Biblioteki z C](#11-biblioteki-z-c)
-12. [Błędy](#12-błędy)
-13. [Rozwiązywanie problemów](#13-rozwiązywanie-problemów)
-14. [Struktura projektu i testy](#14-struktura-projektu-i-testy)
-15. [Obecne ograniczenia](#15-obecne-ograniczenia)
+10. [Tablice](#10-tablice)
+11. [Tekst (str)](#11-tekst-str)
+12. [Struktury](#12-struktury)
+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. [Debugowanie](#19-debugowanie)
+20. [Rozwiązywanie problemów](#20-rozwiązywanie-problemów)
+21. [Struktura projektu i testy](#21-struktura-projektu-i-testy)
+22. [Obecne ograniczenia](#22-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.
+(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ć.
-- **Bezpośredni dostęp do bibliotek C.** Piszesz `import "stdio.h"` i wołasz, co chcesz.
+- **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ć.
@@ -56,10 +67,10 @@ Z innymi wersjami LLVM prawdopodobnie się nie skompiluje, bo API C++ LLVM zmien
```sh
git clone https://github.com/Nexoniarz/Finch.git
cd Finch
-nix-shell # pobiera LLVM, libclang, cmake, ninja, pkg-config
+nix-shell # pobiera LLVM, libclang, clang, cmake, ninja, pkg-config
cmake -S . -B build -G Ninja
ninja -C build
-./build/finch version # finch 1.0.0 (LLVM 21.x)
+./build/finch version # finch 2.0.0 (LLVM 21.x)
```
### Opcja B: pakiety z twojej dystrybucji
@@ -73,7 +84,7 @@ i libclang 21, a opcjonalnie `pkg-config`.
| 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:
+Ta opcja nie była testowana. Jeśli CMake nie znajduje LLVM, wskaż mu katalog:
```sh
cmake -S . -B build -G Ninja -DLLVM_DIR=/usr/lib/llvm-21/lib/cmake/llvm
@@ -87,21 +98,23 @@ 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`.
+Podczas pracy Finch potrzebuje kompilatora C (`cc`). Używa go do linkowania programów i jednorazowo
+do zbudowania swojej małej biblioteki uruchomieniowej, którą trzyma w `~/.cache/finch`.
+Inny kompilator wskażesz zmienną `CC`.
---
## 3. Polecenie finch
```
-finch run skompiluj i od razu uruchom
-finch build [-o nazwa] skompiluj do programu (domyślna nazwa: nazwa pliku)
-finch ir pokaż LLVM IR (dla ciekawskich)
-finch version pokaż wersję
+finch run [argumenty...] skompiluj i od razu uruchom
+finch build [-o nazwa] skompiluj do programu (domyślna nazwa: nazwa pliku)
+finch ir pokaż LLVM IR (dla ciekawskich)
+finch version pokaż wersję
opcje:
-l dolinkuj bibliotekę C, to samo co link "lib" w pliku
+ -g dodaj informacje dla debuggera (gdb / lldb)
-O0 bez optymalizacji
```
@@ -109,13 +122,13 @@ Przykłady:
```sh
finch run hello.fn # wypisuje wynik
+finch run narzedzie.fn dane.txt -v # argumenty po nazwie pliku trafiają do programu
finch build gra.fn -o mojagra # tworzy ./mojagra
-./mojagra
-finch run okno.fn -l glfw # z biblioteką C
+finch build gra.fn -g -O0 # wersja dla debuggera
```
-`finch run` przekazuje dalej kod wyjścia twojego programu. Jeśli program się wysypie,
-powie jak (np. `the program crashed: Segmentation fault`).
+`finch run` przekazuje dalej kod wyjścia programu. Jeśli program się wysypie, powie jak
+(np. `the program crashed: Segmentation fault`).
---
@@ -126,8 +139,14 @@ powie jak (np. `the program crashed: Segmentation fault`).
/* albo zajmuje
kilka linijek */
-import "math.h" // nagłówki C (opcjonalne), zawsze na górze
-link "m" // biblioteki C do dolinkowania (opcjonalne)
+import "math.h" // nagłówki C (opcjonalne), na górze
+import ksztalty // moduły Fincha (opcjonalne): ksztalty.fn 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ść")
@@ -136,12 +155,13 @@ 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 `( )`.
+- **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 mogą być w pliku w dowolnej kolejności.
-- `main` to `fn main()` albo `fn main() -> int` (zwraca kod wyjścia).
+- 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).
---
@@ -150,19 +170,20 @@ Zasady, które warto znać:
```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, null dla wskaźników)
+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ć tej samej nazwy ponownie
- w bloku wewnętrznym (nie ma „przesłaniania”), bo to częste źródło pomyłek.
+- 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`, …) i słowa kluczowe są zarezerwowane.
+ 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`
+**Słowa kluczowe:** `fn return if else while for in break continue true false null import link struct defer`
---
@@ -176,7 +197,9 @@ x += 1 // także -= *= /= %=
| `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"` |
+| `str` | tekst | `"cześć\tświecie"` |
+| `[]T` | lista (tablica) elementów `T` | `[1, 2, 3]` |
+| twoje struktury | nazwane grupy wartości | `Punkt(1, 2)` |
Znaki specjalne w tekstach: `\n` nowa linia, `\t` tabulator, `\r`, `\0`, `\\`, `\"`, `\'`.
@@ -191,48 +214,41 @@ Znaki specjalne w tekstach: `\n` nowa linia, `\t` tabulator, `\r`, `\0`, `\\`, `
`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](#10-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`) | ✅ |
+| 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]` | ✅ |
+| 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 (`the number 300 doesn't fit in u8`).
-To samo dotyczy stałych z nagłówków C.
+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ą)
-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
+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
+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`.
-Jeśli potrzebujesz więcej miejsca, najpierw zamień typ: `int(x) + 10`.
---
@@ -242,8 +258,8 @@ Jeśli potrzebujesz więcej miejsca, najpierw zamień typ: `int(x) + 10`.
| Priorytet | Operatory | Znaczenie |
|---|---|---|
-| 5 | `*` `/` `%` `<<` `>>` `&` | mnożenie, dzielenie, reszta, przesunięcie bitów w lewo/prawo, bitowe AND |
-| 4 | `+` `-` `\|` `^` | dodawanie, odejmowanie, bitowe OR, bitowe XOR |
+| 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 |
@@ -251,18 +267,16 @@ Jeśli potrzebujesz więcej miejsca, najpierw zamień typ: `int(x) + 10`.
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`. W C to samo po cichu znaczy co innego.
+> `flagi & 4 != 0` znaczy `(flagi & 4) != 0`.
Uwagi:
-- `/` na liczbach całkowitych zaokrągla w stronę zera: `7 / 2` to `3`, `-7 / 2` to `-3`. `%` ma znak lewej strony.
+- `/` 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.
- 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 z `null`.
-- Operatory bitowe wymagają liczb całkowitych. Dla `bool` używaj `&&` i `||`.
-- `>>` na liczbach ze znakiem zachowuje znak (`-16 >> 2` to `-4`). Na liczbach bez znaku wypełnia zerami.
+- 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.
---
@@ -285,14 +299,33 @@ 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)
+}
```
- 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ą liczbami całkowitymi obliczanymi **raz**, przed startem pętli.
- `i` jest typu `int` i nie można go zmieniać wewnątrz pętli.
+- 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
@@ -308,32 +341,129 @@ fn bez_wyniku(str wiadomosc) { // bez "->": nic nie zwraca
}
```
-- Parametry są kopiami. Zmiana parametru nie zmienia zmiennej u wywołującego.
- Do tego służą wskaźniki (niżej).
+- 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.
-- Wbudowane nazwy, których nie możesz użyć: `print`, `addr` i 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](#zamiana-typów)). |
---
-## 10. Wskaźniki
+## 10. Tablice
-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
+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`.
+
+---
+
+## 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`) |
+
+`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.
+
+---
+
+## 12. Struktury
+
+```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).
+- Struktury nie mają metod. Pisz funkcje, które je przyjmują: `fn wylecz(Gracz g) -> Gracz`.
+
+---
+
+## 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 |
+| `&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 |
@@ -342,72 +472,98 @@ 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")
- }
-}
+x := 21
+podwoj(addr(x)) // x to 42
```
-Wskaźnik może wskazywać na wskaźnik: `ptr[ptr[int]]`, a potem `pp.value.value`.
+Jeśli struktura ma własne pole o nazwie `value`, to `p.value` na wskaźniku do niej oznacza to pole.
-Użycie `.value` na wskaźniku `null` **zatrzymuje program** komunikatem
-`runtime error: used .value on a null pointer`, zamiast losowo go wysypać.
+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 funkcji, do której ta zmienna należała,
-bo zmienna wtedy już nie istnieje. Finch jeszcze tego nie sprawdza.
+⚠️ Wskaźnika na zmienną nie wolno używać po zakończeniu jej bloku. Finch tego nie sprawdza.
---
-## 11. Biblioteki z C
+## 15. Funkcje wbudowane
-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.
+| 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. |
+| `write_file(sciezka, tekst)` | Zapisuje (zastępuje) plik; oddaje `true`, jeśli się udało. |
+| `file_exists(sciezka)` | `true` / `false` |
+| `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)). |
+
+---
+
+## 16. Moduły
+
+Program możesz podzielić na pliki. `import ksztalty` wczytuje `ksztalty.fn` z tego samego folderu
+(albo z folderu wymienionego w zmiennej środowiskowej `FINCH_PATH`, rozdzielonych `:`).
+
+```c
+// ksztalty.fn
+struct Pudlo {
+ float w
+ float h
+}
+
+fn pole(Pudlo p) -> float {
+ return p.w * p.h
+}
+```
+
+```c
+// main.fn
+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.
+
+---
+
+## 17. Biblioteki z C
### Krok 1: zaimportuj nagłówek
```c
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
+import "mojalib.h" // nagłówek 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`,
+- **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ę
-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:
-
```c
-link "glfw" // używa libglfw.so
-link "GL" // używa libGL.so
+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 .fn)
+link "gotowe.a" // albo plik obiektowy / bibliotekę statyczną
```
-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"
```
@@ -421,122 +577,138 @@ error: glfwInit, glfwCreateWindow, glfwMakeContextCurrent and 5 more come from "
| `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` |
+| `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` |
-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
+### Przykład: struktury i raylib
```c
-import "stdio.h"
-import "math.h"
-import "stdlib.h"
+import "raylib.h"
+link "raylib"
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")
+ 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()
}
```
-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.
+Ż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).
### Co jeszcze nie działa
-- Funkcje C, które przyjmują lub zwracają **`struct` przez 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ą.
+- **Unie** C przez wartość i struktury z **polami bitowymi** przez wartość (wskaźniki do nich działają).
+- **Makra-funkcje** i makra, które nie są pojedynczą liczbą.
- `long double`, liczby 128-bitowe.
-- Odczytywanie **pól** struktury C.
---
-## 12. Błędy
+## 18. 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.
+Format: `plik:linijka:kolumna: error: wyjaśnienie`, a pod spodem linijka ze znakiem `^`.
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:
+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: used .value on a null pointer` | odczyt lub zapis `p.value`, gdy `p` jest `null` |
+| `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 |
---
-## 13. Rozwiązywanie problemów
+## 19. Debugowanie
+
+```sh
+finch build gra.fn -g -O0 -o gra
+gdb ./gra
+(gdb) break gra.fn: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ąć.
+
+---
+
+## 20. 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`. |
+| `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` | 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. |
+| `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.fn` 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. |
+| `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. |
---
-## 14. Struktura projektu i testy
+## 21. Struktura projektu i testy
```
Finch/
├── src/ kompilator (C++)
-├── examples/ przykładowe programy (hello, tour, types, c_import, window, llvm)
+├── runtime/ finch_rt.c: mała biblioteka uruchomieniowa (teksty, tablice, wejście, pliki)
+├── boot/ kompilator Fincha napisany w Finchu (zobacz przewodnik dla inżynierów)
+├── examples/ hello, tour, types, structs, todo, guess, c_import, window, raylib, llvm
├── tests/
-│ ├── run/ programy + dokładny wynik, który muszą wypisać (.out)
+│ ├── run/ programy + dokładny wynik (.out) i wejście (.in)
│ ├── fail/ programy, które muszą się nie udać, z oczekiwanym błędem w 1. linijce
-│ └── run.sh skrypt uruchamiający testy
+│ ├── run.sh uruchamia testy (MEMCHECK=1 sprawdza też pamięć valgrindem)
+│ └── boot.sh buduje kompilator samohostujący nim samym i porównuje
├── docs/ ta dokumentacja (en, pl)
├── shell.nix środowisko deweloperskie Nix
└── CMakeLists.txt
```
-Testy uruchomisz po zbudowaniu:
-
```sh
-tests/run.sh # → 34 passed, 0 failed
+tests/run.sh # → 58 passed, 0 failed
+MEMCHECK=1 tests/run.sh # to samo pod valgrindem: bez wycieków i złych dostępów do pamięci
+tests/boot.sh # sprawdzenie samohostowania (wymaga clanga)
```
---
-## 15. Obecne ograniczenia
+## 22. Obecne ograniczenia
-Finch 1.0 to solidny rdzeń, a nie skończony język. Jeszcze nie ma:
+Finch 2.0 to kompletny mały język, ale nie skończony. 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`).
+- Metod w strukturach, typów generycznych, słowników (map), `match`. (Używaj tablic struktur i funkcji.)
+- Błędów jako wartości: błąd w `int("x")` albo `read_file` zatrzymuje program. Sprawdzaj wcześniej (`file_exists`).
+- Tekstu świadomego Unicode: `.len`, `s[i]` i `upper()` działają na bajtach / ASCII.
+- Wątków.
- Platform innych niż Linux x86-64.
Plan rozwoju jest w [README](../../README.pl.md#plan-rozwoju).
diff --git a/examples/raylib.fn b/examples/raylib.fn
new file mode 100644
index 0000000..fe44ba5
--- /dev/null
+++ b/examples/raylib.fn
@@ -0,0 +1,15 @@
+// A raylib window with a circle: C structs (Color, Vector2) passed by value.
+// Needs raylib (in shell.nix).
+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()
+}
diff --git a/notatka.txt b/notatka.txt
new file mode 100644
index 0000000..1044ac2
--- /dev/null
+++ b/notatka.txt
@@ -0,0 +1 @@
+Kupić mleko
\ No newline at end of file
diff --git a/note.txt b/note.txt
new file mode 100644
index 0000000..7d2e267
--- /dev/null
+++ b/note.txt
@@ -0,0 +1 @@
+Buy milk
\ No newline at end of file
diff --git a/shell.nix b/shell.nix
index d340bbc..7087ecf 100644
--- a/shell.nix
+++ b/shell.nix
@@ -10,8 +10,9 @@ pkgs.mkShell {
ninja
pkg-config # finds the right flags for `link "..."`
- # for examples/window.fn
+ # for examples/window.fn and examples/raylib.fn
glfw
libGL
+ raylib
];
}
diff --git a/src/codegen_types.cpp b/src/codegen_types.cpp
index 33b4158..dd57a0a 100644
--- a/src/codegen_types.cpp
+++ b/src/codegen_types.cpp
@@ -230,7 +230,8 @@ Value *Codegen::own(const Value_ &v) {
}
void Codegen::release(const Value_ &v) {
- if (v.fresh && v.v && owning(v.type)) dropValue(v.v, v.type);
+ // constants (string literals, empty arrays) own no memory: nothing to free
+ if (v.fresh && v.v && !isa(v.v) && owning(v.type)) dropValue(v.v, v.type);
}
Value *Codegen::copyValue(Value *v, const FType &t) {