Compare commits

..
54 changed files with 1369 additions and 4098 deletions

View file

@ -1,8 +1,8 @@
name: CI name: CI
# The compiler is written in Finch. It is built from bootstrap/<system>.ll, its own LLVM IR (see bootstrap/README.md), # The compiler is written in Finch. It is built from bootstrap/<system>.ll, its own LLVM IR (see bootstrap/README.md),
# then from itself, and tested, on every system it runs on: Linux x86-64 and ARM64, macOS ARM64 and x86-64, Windows x86-64. # then from itself. Seeds: Linux x86-64 and ARM64 (tested here), Windows x86-64 (tested under Wine, and here),
# A tag v* also makes the release: every package below is attached to it. # macOS ARM64 and x86-64 (made with Apple's headers; only checked as IR until the macos job below passes).
on: on:
push: push:
@ -75,14 +75,7 @@ jobs:
fi fi
macos: macos:
strategy: runs-on: macos-15
fail-fast: false
matrix:
include:
- { os: macos-15, arch: arm64 }
- { os: macos-15-intel, arch: x64 }
name: macos-${{ matrix.arch }}
runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install LLVM 21 (libclang) - name: Install LLVM 21 (libclang)
@ -96,14 +89,6 @@ jobs:
build/finch version build/finch version
- name: Tests - name: Tests
run: tests/run.sh run: tests/run.sh
- name: Package
run: |
mkdir -p dist/finch && cp build/finch README.md LICENSE dist/finch/ && cp -r runtime std examples dist/finch/
tar -czf finch-macos-${{ matrix.arch }}.tar.gz -C dist finch
- uses: actions/upload-artifact@v4
with:
name: finch-macos-${{ matrix.arch }}
path: finch-macos-${{ matrix.arch }}.tar.gz
windows: windows:
runs-on: windows-2022 runs-on: windows-2022
@ -131,16 +116,6 @@ jobs:
export PATH="/c/llvm-mingw-20251216-ucrt-x86_64/bin:$PATH" export PATH="/c/llvm-mingw-20251216-ucrt-x86_64/bin:$PATH"
export FINCH_LDFLAGS="-LC:/llvm/bin" export FINCH_LDFLAGS="-LC:/llvm/bin"
FINCH=../build/finch.exe tests/run.sh FINCH=../build/finch.exe tests/run.sh
- name: Package
run: |
mkdir -p dist/finch
cp build/finch.exe build/libclang.dll README.md LICENSE dist/finch/
cp -r runtime std examples dist/finch/
cd dist && 7z a -tzip finch-windows-x64.zip finch > /dev/null
- uses: actions/upload-artifact@v4
with:
name: finch-windows-x64
path: dist/finch-windows-x64.zip
vscode: vscode:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
@ -161,27 +136,3 @@ jobs:
with: with:
name: finch-vscode name: finch-vscode
path: editors/vscode/finch-lang.vsix path: editors/vscode/finch-lang.vsix
release:
if: startsWith(github.ref, 'refs/tags/v')
needs: [linux, cross, macos, windows, vscode]
runs-on: ubuntu-24.04
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true
- name: Attach the packages to the release (made if it isn't there yet)
uses: softprops/action-gh-release@v2
with:
name: Finch ${{ github.ref_name }}
generate_release_notes: true
files: |
dist/finch-linux-x64.tar.gz
dist/finch-linux-arm64.tar.gz
dist/finch-macos-arm64.tar.gz
dist/finch-macos-x64.tar.gz
dist/finch-windows-x64.zip
dist/finch-lang.vsix

View file

@ -40,8 +40,6 @@ fn main() {
Operator precedence that does not bite (`x & 1 == 0` means what it says). Operator precedence that does not bite (`x & 1 == 0` means what it says).
- **Practical.** Maps, methods, errors as values (`int(s) or 0`, `try read_file(p)`), modules, any C - **Practical.** Maps, methods, errors as values (`int(s) or 0`, `try read_file(p)`), modules, any C
library, and a language server for your editor. Runs on Linux, macOS and Windows, on x86-64 and ARM64. library, and a language server for your editor. Runs on Linux, macOS and Windows, on x86-64 and ARM64.
- **SIMD built in.** `f32x8`, `i8x32`, `f64x4`: numbers side by side, added and multiplied all at once. LLVM turns them
into SSE, AVX2, AVX-512 or NEON instructions, so one piece of code is fast on a desktop, a laptop and ARM.
- **Errors that talk like a person:** - **Errors that talk like a person:**
``` ```
game.fch:3:13: error: 'y' must be i32, but this is int (use i32(...) to convert) game.fch:3:13: error: 'y' must be i32, but this is int (use i32(...) to convert)
@ -50,17 +48,13 @@ fn main() {
``` ```
- **Any C library, directly.** `import "raylib.h"` + `link "raylib"`, and you can call it, structs by value - **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. and callbacks included. Headers are read by libclang, so there is nothing to declare by hand.
- **Written in itself.** The compiler is Finch code, and nothing else: `src/*.fch`, about 12k lines. It is built from a - **Written in itself.** The compiler is Finch code, and nothing else: `src/*.fch`, about 8.6k lines. It is built from a
seed (its own LLVM IR, `bootstrap/`) and then from itself, and the two come out identical. seed (its own LLVM IR, `bootstrap/`) and then from itself, and the two come out identical. Its first version, in C++,
is kept on the `before-bootstrap` branch.
## Quick start ## Quick start
**Ready builds** for Linux (x64, ARM64), macOS (Apple Silicon, Intel) and Windows (x64) are in the **Building it** (Linux x86-64 or ARM64; needs `clang` and libclang, LLVM 21):
[releases](https://github.com/Nexoniarz/Finch/releases). Unpack one and keep `runtime/` and `std/` next to `finch`. What
each system needs besides is in the [technicians' guide](docs/en/for-technicians.md#2-installing): clang and libclang
from LLVM 21 on Linux, `brew install llvm@21` on a Mac, llvm-mingw on Windows.
**Building it yourself** (on Linux, macOS, or Windows with Git Bash; needs `clang` and libclang, LLVM 21):
```sh ```sh
git clone https://github.com/Nexoniarz/Finch.git git clone https://github.com/Nexoniarz/Finch.git
@ -70,12 +64,13 @@ bootstrap/build.sh # the compiler is build/finch: built from th
./build/finch run examples/hello.fch ./build/finch run examples/hello.fch
./build/finch build examples/tour.fch -o tour && ./tour ./build/finch build examples/tour.fch -o tour && ./tour
tests/run.sh # every program, every expected error, -g, a shared library, the language server tests/run.sh # every program, every refusal, -g, a shared library, the language server
``` ```
The same `bootstrap/build.sh` works on a Mac (with Homebrew's `llvm@21`) and on Windows (with llvm-mingw): see Ready builds of the compiler are in the [releases](https://github.com/Nexoniarz/Finch/releases) (Linux x64 and ARM64:
[bootstrap/README.md](bootstrap/README.md). Programs for another system are built with `--target windows|linux|arm64|macos` unpack, keep `runtime/` and `std/` next to `finch`). **Programs for Windows, ARM64 and macOS** are built from Linux with
(see the [guide](docs/en/for-technicians.md#building-for-another-system)). `--target windows|arm64|macos` (see the [guide](docs/en/for-technicians.md#3-the-finch-command)); the compiler itself does not run on
macOS or Windows yet (it needs a seed made with that system's headers: see [bootstrap/README.md](bootstrap/README.md)).
Editors: **Kate** (`editors/kate/install.sh`), **VS Code** (the `finch-lang` `.vsix` from the release), or any Editors: **Kate** (`editors/kate/install.sh`), **VS Code** (the `finch-lang` `.vsix` from the release), or any
editor with LSP support (`finch lsp`). editor with LSP support (`finch lsp`).
@ -96,15 +91,13 @@ Pick the guide that fits you:
| | | | | |
|---|---| |---|---|
| Variables | `x := 5` (inferred) or `int x = 5`; `const W = 640` | | Variables | `x := 5` (inferred) or `int x = 5`; `const W = 640` |
| Types | `int` `float` `bool` `char` `str`, sized `i8`…`i64` `u8`…`u64` `f32` `f64`, arrays `[]T`, maps `map[K]V`, pointers `ptr[T]`, vectors `f32x8` `i8x32`… | | Types | `int` `float` `bool` `char` `str`, sized `i8`…`i64` `u8`…`u64` `f32` `f64`, arrays `[]T`, maps `map[K]V`, pointers `ptr[T]` |
| Functions | `fn add(int a, int b) -> int { return a + b }` | | Functions | `fn add(int a, int b) -> int { return a + b }` |
| Structs | `struct Point { … }` with one field per line (`int x`, `int y = 0`); create with `Point(1, 2)` or `Point(x: 1)` | | Structs | `struct Point { … }` with one field per line (`int x`, `int y = 0`); create with `Point(1, 2)` or `Point(x: 1)` |
| Methods | `fn Point.move(int dx) { self.x += dx }` → `p.move(3)` | | Methods | `fn Point.move(int dx) { self.x += dx }` → `p.move(3)` |
| Arrays | `nums := [1, 2, 3]`, `nums.push(4)`, `nums[0]`, `nums.len`, `for i, n in nums { }` | | Arrays | `nums := [1, 2, 3]`, `nums.push(4)`, `nums[0]`, `nums.len`, `for i, n in nums { }` |
| Maps | `ages := ["ann": 31]`, `ages["bob"] = 25`, `ages.has("cy")`, `counts[w] += 1`, `for k, v in ages { }` | | Maps | `ages := ["ann": 31]`, `ages["bob"] = 25`, `ages.has("cy")`, `counts[w] += 1`, `for k, v in ages { }` |
| Errors | `fn parse(str s) -> int! { … return error("bad") }` → `parse(s) or 0`, `parse(s) or { … }`, `try parse(s)` | | Errors | `fn parse(str s) -> int! { … return error("bad") }` → `parse(s) or 0`, `parse(s) or { … }`, `try parse(s)` |
| Vectors (SIMD) | `v := f32x8(list, i)`, `v * 2.0 + w`, `v.sum()`, `select(v > 0.0, v, 0.0)`, `t.lookup(idx)`, `acc.dot_add(a, b)`, `v.store(list, i)` |
| Processors | `--cpu native`; `@cpu("avx2") fn …` with `if cpu_has("avx2")`; floats computed as written (`fma`/`muladd` to fuse) |
| Text | `"hp \(hp)"`, `"a" + "b"`, `str(42)`, `int("42")`, `s.split(",")`, `s.upper()`, `s[0]` | | Text | `"hp \(hp)"`, `"a" + "b"`, `str(42)`, `int("42")`, `s.split(",")`, `s.upper()`, `s[0]` |
| Control | `if` / `else if` / `else` (also as a value), `while`, `for i in 0..10`, `break`, `continue`, `defer` | | Control | `if` / `else if` / `else` (also as a value), `while`, `for i in 0..10`, `break`, `continue`, `defer` |
| Memory | automatic for arrays, text and structs; `new(...)` / `free(...)` for your own heap structures | | Memory | automatic for arrays, text and structs; `new(...)` / `free(...)` for your own heap structures |
@ -115,7 +108,7 @@ Pick the guide that fits you:
| Tests | `test "name" { assert(x == 3) }` next to the code, run with `finch test` | | Tests | `test "name" { assert(x == 3) }` next to the code, run with `finch test` |
| C interop | `import "stdio.h"`, `link "glfw"`, `link "mine.c"` / `"glue.cpp"`, unions, `#define`s (also `(Color){…}`), `addr(fn)` for callbacks | | C interop | `import "stdio.h"`, `link "glfw"`, `link "mine.c"` / `"glue.cpp"`, unions, `#define`s (also `(Color){…}`), `addr(fn)` for callbacks |
| I/O | `print(...)`, `input("? ")`, `read_file`, `write_file`, `shell` | | I/O | `print(...)`, `input("? ")`, `read_file`, `write_file`, `shell` |
| Tools | `finch run/build/test/fmt/ir/lsp`, `finch.toml` for a project, a build cache, `--target windows/arm64/macos/android`, `--cpu native`, `-g` for gdb/lldb, `-O0`, the chain of calls on runtime errors | | Tools | `finch run/build/test/fmt/ir/lsp`, `finch.toml` for a project, a build cache, `--target windows/arm64/macos/android`, `-g` for gdb/lldb, `-O0`, the chain of calls on runtime errors |
## Examples ## Examples
@ -129,7 +122,6 @@ Pick the guide that fits you:
| [`examples/structs.fch`](examples/structs.fch) | structs, arrays of structs, a linked list with `new`/`free` | | [`examples/structs.fch`](examples/structs.fch) | structs, arrays of structs, a linked list with `new`/`free` |
| [`examples/types.fch`](examples/types.fch) | sized numbers and pointers | | [`examples/types.fch`](examples/types.fch) | sized numbers and pointers |
| [`examples/c_import.fch`](examples/c_import.fch) | `printf`, `math.h`, `malloc`/`free`, `stderr` | | [`examples/c_import.fch`](examples/c_import.fch) | `printf`, `math.h`, `malloc`/`free`, `stderr` |
| [`examples/simd.fch`](examples/simd.fch) | vectors: a dot product eight numbers at a time, and the time it saves |
| [`examples/window.fch`](examples/window.fch) | a GLFW + OpenGL window | | [`examples/window.fch`](examples/window.fch) | a GLFW + OpenGL window |
| [`examples/opengl.fch`](examples/opengl.fch) | modern OpenGL 3.3: shaders, buffers, a spinning triangle | | [`examples/opengl.fch`](examples/opengl.fch) | modern OpenGL 3.3: shaders, buffers, a spinning triangle |
| [`examples/vulkan.fch`](examples/vulkan.fch) | Vulkan: list the GPUs, their memory and queues, create a device | | [`examples/vulkan.fch`](examples/vulkan.fch) | Vulkan: list the GPUs, their memory and queues, create a device |
@ -159,17 +151,14 @@ Pick the guide that fits you:
- [x] A standard library in Finch: `json`, `path`, `time`, `proc`, `fs` - [x] A standard library in Finch: `json`, `path`, `time`, `proc`, `fs`
- [x] `const`, `if` as a value, `"\(x)"` in text, `"""` text, C unions and `#define` expressions - [x] `const`, `if` as a value, `"\(x)"` in text, `"""` text, C unions and `#define` expressions
- [x] `test` blocks and `finch test`, `finch fmt`, `finch.toml`, a build cache, the chain of calls on runtime errors - [x] `test` blocks and `finch test`, `finch fmt`, `finch.toml`, a build cache, the chain of calls on runtime errors
- [x] Threads (`import thread`) and atomics; C++ files; `--target android` - [x] Threads (`import thread`) and atomics; C++ files; `--target android` (compiles; NDK linking and devices untried)
- [x] The compiler on every system it builds for: Linux, macOS (Apple Silicon and Intel), Windows; releases made by CI
- [x] Vector types (`f32x8`, `i8x32`, …) for SIMD, and `--cpu native`
- [x] Masks, shuffles, byte lookup and `dot_add` for kernels; `@cpu` and `cpu_has`; `fma`/`muladd`, float bits, f16 and bf16
## Layout ## Layout
``` ```
src/ the compiler, in Finch: lexer, parser, AST, code generation (to LLVM IR as text), C import (libclang) and src/ the compiler, in Finch: lexer, parser, AST, code generation (to LLVM IR as text), C import (libclang) and
the C calling conventions, the language server, the driver the C calling conventions, the language server, the driver
std/ the standard library, in Finch: json, path, time, proc, fs, thread std/ the standard library, in Finch: json, path, time, proc, fs
runtime/ the small C runtime linked into every program runtime/ the small C runtime linked into every program
bootstrap/ build.sh and the seeds: the compiler's own LLVM IR, one per system bootstrap/ build.sh and the seeds: the compiler's own LLVM IR, one per system
examples/ example programs examples/ example programs

View file

@ -5,8 +5,8 @@
</picture> </picture>
</p> </p>
**Mały, szybki, celny.** Język programowania oparty na LLVM, ze składnią w stylu C. Nauczysz się go w godzinę, **Mały, zwinny, skuteczny.** Język programowania na LLVM ze składnią podobną do C,
a programy działają tak szybko jak w C. na tyle prosty, że nauczysz się go w godzinę, i tak szybki jak C.
> 🇬🇧 [English version](README.md) > 🇬🇧 [English version](README.md)
@ -19,7 +19,7 @@ fn przeciwprostokatna(float a, float b) -> float {
fn main() { fn main() {
imie := "Finch" imie := "Finch"
print("Cześć, tu", imie, "!") print("Cześć od", imie, "!")
for i in 1..4 { for i in 1..4 {
print(i, "→", przeciwprostokatna(float(i), 1.0)) print(i, "→", przeciwprostokatna(float(i), 1.0))
@ -29,58 +29,51 @@ fn main() {
## Dlaczego Finch ## Dlaczego Finch
- **Prosty.** Nie ma średników, plików nagłówkowych ani preprocesora. Na każdą rzecz jest jeden sposób. - **Prosty.** Bez średników, bez plików nagłówkowych, bez preprocesora. Każdą rzecz robi się na jeden sposób.
- **Szybki.** Kod przechodzi przez optymalizacje LLVM (`-O2`) i staje się zwykłym programem, bez garbage collectora. - **Szybki.** Kompiluje się przez potok `-O2` z LLVM do kodu natywnego, bez garbage collectora.
Rekurencyjne `fib(40)` liczy się ok. 0,19 s (ten sam kod w C z `clang -O2`: ok. 0,23 s), a pętle po tablicach Rekurencyjne `fib(40)` trwa ok. 0,19 s (`clang -O2` na tym samym kodzie C: ok. 0,23 s), a kod na tablicach
ze sprawdzaniem zakresu są tak samo szybkie jak w C. z kontrolą zakresu dorównuje C.
- **Pamięć bez kłopotów.** Listy, teksty i struktury są wartościami: przypisanie robi kopię, a koniec bloku sprząta. - **Pamięć bez bólu.** Listy, teksty i struktury są wartościami: przypisanie kopiuje, a koniec bloku je zwalnia.
Nie ma `free`, o którym da się zapomnieć, ani odwołań do zwolnionej pamięci. Valgrind nie znajduje błędów w żadnym teście. 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 nic to nie kosztuje.** Liczby nie zmieniają po cichu typu, a liczb ze znakiem - **Bezpieczniejszy niż C tam, gdzie to tanie.** Nie ma cichego zwężania typów ani mieszania liczb ze znakiem
i bez znaku nie da się przypadkiem pomieszać. Indeks poza tablicą, dzielenie przez zero czy `null` kończą program i bez znaku. Indeks poza zakresem, dzielenie przez zero i null zatrzymują program czytelnym błędem, zamiast
jasnym komunikatem, a nie niezdefiniowanym zachowaniem. Kolejność działań jest taka, jakiej się spodziewasz powodować niezdefiniowane zachowanie. Priorytety operatorów nie gryzą (`x & 1 == 0` znaczy to, co widać).
(`x & 1 == 0` znaczy dokładnie to, co widać). - **Praktyczny.** Mapy, metody, błędy jako wartości (`int(s) or 0`, `try read_file(p)`), moduły, każda
- **Praktyczny.** Mapy, metody, błędy jako wartości (`int(s) or 0`, `try read_file(p)`), moduły, dowolna biblioteka C biblioteka C i serwer języka dla twojego edytora. Działa na Linuksie, macOS i Windowsie, na x86-64 i ARM64.
i serwer języka dla edytora. Działa na Linuksie, macOS i Windowsie, na procesorach x86-64 i ARM64. - **Błędy mówią po ludzku:**
- **SIMD w języku.** `f32x8`, `i8x32`, `f64x4`: kilka liczb obok siebie, dodawanych i mnożonych naraz. LLVM zamienia je
na instrukcje SSE, AVX2, AVX-512 albo NEON, więc ten sam kod jest szybki na komputerze stacjonarnym, laptopie i ARM-ie.
- **Komunikaty, które da się zrozumieć:**
``` ```
gra.fch:3:13: error: 'y' must be i32, but this is int (use i32(...) to convert) gra.fch:3:13: error: 'y' must be i32, but this is int (use i32(...) to convert)
3 | i32 y = x 3 | i32 y = x
| ^ | ^
``` ```
- **Biblioteki C bez pośredników.** Wystarczy `import "raylib.h"` i `link "raylib"`, żeby z niej korzystać, łącznie - **Każda biblioteka C, bezpośrednio.** Piszesz `import "raylib.h"` i `link "raylib"`, i możesz z niej korzystać,
ze strukturami przekazywanymi przez wartość i funkcjami zwrotnymi. Nagłówki czyta libclang, więc niczego nie trzeba także ze struktur przekazywanych przez wartość i z callbacków. Nagłówki czyta libclang, więc nie trzeba niczego deklarować ręcznie.
deklarować ręcznie. - **Napisany w sobie.** Kompilator to kod w Finchu i nic więcej: `src/*.fch`, ok. 8,6 tys. linii. Buduje się go z ziarna
- **Napisany w samym sobie.** Kompilator to wyłącznie kod w Finchu: `src/*.fch`, ok. 12 tys. linii. Buduje się go (własny kod LLVM IR, `bootstrap/`), potem z samego siebie, a oba wyniki są identyczne. Pierwsza wersja, w C++,
z pliku startowego (jego własnego kodu LLVM IR, w `bootstrap/`), a potem jeszcze raz nim samym. Oba wyniki są identyczne. jest w gałęzi `before-bootstrap`.
## Na start ## Szybki start
**Gotowe kompilatory** na Linuksa (x64, ARM64), macOS (Apple Silicon, Intel) i Windowsa (x64) znajdziesz w **Budowanie** (Linux x86-64 lub ARM64; potrzebny `clang` i libclang, LLVM 21):
[wydaniach](https://github.com/Nexoniarz/Finch/releases). Rozpakuj archiwum i trzymaj katalogi `runtime/` i `std/` obok
pliku `finch`. Co jeszcze trzeba doinstalować, opisuje [przewodnik dla techników](docs/pl/dla-technikow.md#2-instalacja):
na Linuksie clang i libclang z LLVM 21, na Macu `brew install llvm@21`, na Windowsie llvm-mingw.
**Samodzielne budowanie** (na Linuksie, macOS albo Windowsie w Git Bash; potrzebne są `clang` i libclang z LLVM 21):
```sh ```sh
git clone https://github.com/Nexoniarz/Finch.git git clone https://github.com/Nexoniarz/Finch.git
cd Finch cd Finch
nix-shell # albo zainstaluj clang, libclang (LLVM 21) i pkg-config po swojemu nix-shell # albo zainstaluj samodzielnie clang + libclang (LLVM 21) + pkg-config
bootstrap/build.sh # gotowy kompilator to build/finch bootstrap/build.sh # kompilator to build/finch: z ziarna, potem z samego siebie
./build/finch run examples/hello.fch ./build/finch run examples/hello.fch
./build/finch build examples/tour.fch -o tour && ./tour ./build/finch build examples/tour.fch -o tour && ./tour
tests/run.sh # wszystkie programy testowe, oczekiwane błędy, -g, biblioteka współdzielona, serwer języka tests/run.sh # każdy program, każda odmowa, -g, biblioteka współdzielona, serwer języka
``` ```
Ten sam `bootstrap/build.sh` działa na Macu (z `llvm@21` z Homebrew) i na Windowsie (z llvm-mingw); szczegóły są w Gotowe kompilatory są w [wydaniach](https://github.com/Nexoniarz/Finch/releases) (Linux x64 i ARM64: rozpakuj, zostaw
[bootstrap/README.md](bootstrap/README.md). Program na inny system zbudujesz opcją `--target windows|linux|arm64|macos` `runtime/` i `std/` obok `finch`). **Programy dla Windowsa, ARM64 i macOS** buduje się z Linuksa przez
(zob. [przewodnik](docs/pl/dla-technikow.md#budowanie-na-inny-system)). `--target windows|arm64|macos`; sam kompilator nie działa jeszcze na macOS ani Windowsie (potrzebuje ziarna
zrobionego z nagłówkami tego systemu: patrz [bootstrap/README.md](bootstrap/README.md)).
Edytory: **Kate** (`editors/kate/install.sh`), **VS Code** (plik `.vsix` `finch-lang` z wydania) albo każdy edytor, Edytory: **Kate** (`editors/kate/install.sh`), **VS Code** (plik `.vsix` `finch-lang` z wydania) albo dowolny
który obsługuje LSP (`finch lsp`). edytor z obsługą LSP (`finch lsp`).
## Dokumentacja ## Dokumentacja
@ -88,96 +81,90 @@ Wybierz przewodnik dla siebie:
| Przewodnik | Dla kogo | Polski | English | | Przewodnik | Dla kogo | Polski | English |
|---|---|---|---| |---|---|---|---|
| **Dla początkujących** | Dla tych, którzy nigdy nie programowali: uczniów, seniorów, ciekawych świata | [dla-poczatkujacych.md](docs/pl/dla-poczatkujacych.md) | [for-beginners.md](docs/en/for-beginners.md) | | **Początkujący** | Nigdy nie programowałeś: uczniowie, seniorzy, ciekawscy | [dla-poczatkujacych.md](docs/pl/dla-poczatkujacych.md) | [for-beginners.md](docs/en/for-beginners.md) |
| **Dla techników** | Znasz komputery, ale programowanie jest dla ciebie nowe: instalacja, cały język, biblioteki C, rozwiązywanie problemów | [dla-technikow.md](docs/pl/dla-technikow.md) | [for-technicians.md](docs/en/for-technicians.md) | | **Technicy** | Znasz komputery, programowanie jest nowe: instalacja, cały język, biblioteki C, problemy | [dla-technikow.md](docs/pl/dla-technikow.md) | [for-technicians.md](docs/en/for-technicians.md) |
| **Dla inżynierów** | Wszystko: lekser, gramatyka, AST, reguły typów, generowanie IR, import przez libclang, ABI, budowa kompilatora | [dla-inzynierow.md](docs/pl/dla-inzynierow.md) | [for-engineers.md](docs/en/for-engineers.md) | | **Inżynierowie** | Wszystko: lekser, gramatyka, AST, reguły typów, tłumaczenie na IR, import przez libclang, ABI, wnętrze | [dla-inzynierow.md](docs/pl/dla-inzynierow.md) | [for-engineers.md](docs/en/for-engineers.md) |
| **Ściąga** | Wszystkie funkcje, metody, operatory i polecenia na jednej stronie | [sciaga.md](docs/pl/sciaga.md) | [cheatsheet.md](docs/en/cheatsheet.md) | | **Ściąga** | Każda funkcja, metoda, operator i polecenie na jednej stronie | [sciaga.md](docs/pl/sciaga.md) | [cheatsheet.md](docs/en/cheatsheet.md) |
## Język w pigułce ## Język w pigułce
| | | | | |
|---|---| |---|---|
| Zmienne | `x := 5` (typ wynika z wartości) albo `int x = 5`; `const W = 640` | | Zmienne | `x := 5` (typ zgadnięty) albo `int x = 5`; `const W = 640` |
| Typy | `int` `float` `bool` `char` `str`, o podanym rozmiarze: `i8`…`i64` `u8`…`u64` `f32` `f64`, tablice `[]T`, mapy `map[K]V`, wskaźniki `ptr[T]`, wektory `f32x8` `i8x32`… | | Typy | `int` `float` `bool` `char` `str`, z rozmiarem `i8`…`i64` `u8`…`u64` `f32` `f64`, tablice `[]T`, mapy `map[K]V`, wskaźniki `ptr[T]` |
| Funkcje | `fn dodaj(int a, int b) -> int { return a + b }` | | Funkcje | `fn add(int a, int b) -> int { return a + b }` |
| Struktury | `struct Punkt { … }`, jedno pole w linii (`int x`, `int y = 0`); tworzy się je przez `Punkt(1, 2)` albo `Punkt(x: 1)` | | Struktury | `struct Punkt { … }` z jednym polem na linijkę (`int x`, `int y = 0`); tworzenie przez `Punkt(1, 2)` albo `Punkt(x: 1)` |
| Metody | `fn Punkt.przesun(int dx) { self.x += dx }` → `p.przesun(3)` | | Metody | `fn Punkt.przesun(int dx) { self.x += dx }` → `p.przesun(3)` |
| Tablice | `liczby := [1, 2, 3]`, `liczby.push(4)`, `liczby[0]`, `liczby.len`, `for i, n in liczby { }` | | Tablice | `liczby := [1, 2, 3]`, `liczby.push(4)`, `liczby[0]`, `liczby.len`, `for i, n in liczby { }` |
| Mapy | `wiek := ["ania": 31]`, `wiek["bob"] = 25`, `wiek.has("cyryl")`, `ile[slowo] += 1`, `for k, v in wiek { }` | | Mapy | `wiek := ["ania": 31]`, `wiek["bob"] = 25`, `wiek.has("cy")`, `ile[s] += 1`, `for k, v in wiek { }` |
| Błędy | `fn parsuj(str s) -> int! { … return error("zła liczba") }` → `parsuj(s) or 0`, `parsuj(s) or { … }`, `try parsuj(s)` | | Błędy | `fn parsuj(str s) -> int! { … return error("zły") }` → `parsuj(s) or 0`, `parsuj(s) or { … }`, `try parsuj(s)` |
| Wektory (SIMD) | `v := f32x8(lista, i)`, `v * 2.0 + w`, `v.sum()`, `select(v > 0.0, v, 0.0)`, `t.lookup(idx)`, `acc.dot_add(a, b)`, `v.store(lista, i)` | | Tekst | `"hp \(hp)"`, `"a" + "b"`, `str(42)`, `int("42")`, `s.split(",")`, `s.upper()`, `s[0]` |
| Procesory | `--cpu native`; `@cpu("avx2") fn …` razem z `if cpu_has("avx2")`; liczby zmiennoprzecinkowe liczone tak, jak zapisane (`fma`/`muladd` do łączenia) | | Sterowanie | `if` / `else if` / `else` (też jako wartość), `while`, `for i in 0..10`, `break`, `continue`, `defer` |
| Tekst | `"hp: \(hp)"`, `"a" + "b"`, `str(42)`, `int("42")`, `s.split(",")`, `s.upper()`, `s[0]` | | Pamięć | automatyczna dla tablic, tekstów i struktur; `new(...)` / `free(...)` dla własnych struktur na stercie |
| Sterowanie | `if` / `else if` / `else` (również jako wartość), `while`, `for i in 0..10`, `break`, `continue`, `defer` | | Enumy, match | `enum Kolor { Czerwony, Zielony }`, `match k { Czerwony -> ... else -> ... }` (także jako wartość) |
| Pamięć | tablice, teksty i struktury zwalniają się same; `new(...)` / `free(...)` dla własnych struktur na stercie | | Generyki | `fn max[T](T a, T b) -> T`, `struct Stos[T]`, `Stos[int]()` |
| Enumy i `match` | `enum Kolor { Czerwony, Zielony }`, `match k { Czerwony -> ... else -> ... }` (również jako wartość) |
| Typy generyczne | `fn max[T](T a, T b) -> T`, `struct Stos[T]`, `Stos[int]()` |
| Moduły | `import ksztalty` → `ksztalty.pole(p)`, `import ui/button as btn`; biblioteka standardowa: `json`, `path`, `time`, `proc`, `fs`, `thread` | | Moduły | `import ksztalty` → `ksztalty.pole(p)`, `import ui/button as btn`; biblioteka standardowa: `json`, `path`, `time`, `proc`, `fs`, `thread` |
| Wątki | `thread.spawn(addr(praca), addr(dane))`, `t.join()`, `thread.mutex()`, `atomic_add(addr(n), 1)` | | Wątki | `thread.spawn(addr(praca), addr(dane))`, `t.join()`, `thread.mutex()`, `atomic_add(addr(n), 1)` |
| Testy | `test "nazwa" { assert(x == 3) }` tuż obok kodu, uruchamiane przez `finch test` | | Testy | `test "nazwa" { assert(x == 3) }` obok kodu, uruchamiane przez `finch test` |
| Współpraca z C | `import "stdio.h"`, `link "glfw"`, `link "moje.c"` / `"glue.cpp"`, unie, `#define` (także `(Color){…}`), `addr(fn)` jako funkcja zwrotna | | Współpraca z C | `import "stdio.h"`, `link "glfw"`, `link "moje.c"` / `"glue.cpp"`, unie, `#define` (też `(Color){…}`), `addr(fn)` dla callbacków |
| Wejście i wyjście | `print(...)`, `input("? ")`, `read_file`, `write_file`, `shell` | | Wejście/wyjście | `print(...)`, `input("? ")`, `read_file`, `write_file`, `shell` |
| Narzędzia | `finch run/build/test/fmt/ir/lsp`, `finch.toml` dla projektu, pamięć podręczna kompilacji, `--target windows/arm64/macos/android`, `--cpu native`, `-g` dla gdb i lldb, `-O0`, ścieżka wywołań przy błędzie w trakcie działania | | Narzędzia | `finch run/build/test/fmt/ir/lsp`, `finch.toml` dla projektu, cache budowania, `--target windows/arm64/macos/android`, `-g` dla gdb/lldb, `-O0`, łańcuch wywołań przy błędach w czasie działania |
## Przykłady ## Przykłady
| Plik | Co pokazuje | | Plik | Pokazuje |
|---|---| |---|---|
| [`examples/hello.fch`](examples/hello.fch) | najkrótszy program | | [`examples/hello.fch`](examples/hello.fch) | najmniejszy program |
| [`examples/tour.fch`](examples/tour.fch) | zmienne, funkcje, warunki, pętle | | [`examples/tour.fch`](examples/tour.fch) | zmienne, funkcje, warunki, pętle |
| [`examples/guess.fch`](examples/guess.fch) | gra w zgadywanie liczby: wczytywanie, pętle, `rand` z C | | [`examples/guess.fch`](examples/guess.fch) | gra w zgadywanie: wejście, pętle, `rand` z C |
| [`examples/todo.fch`](examples/todo.fch) | struktury, tablice, teksty i pliki | | [`examples/todo.fch`](examples/todo.fch) | struktury, tablice, teksty i pliki |
| [`examples/wordcount.fch`](examples/wordcount.fch) | mapy, metody i błędy jako wartości: najczęstsze słowa w pliku | | [`examples/wordcount.fch`](examples/wordcount.fch) | mapy, metody i błędy jako wartości: najczęstsze słowa w pliku |
| [`examples/structs.fch`](examples/structs.fch) | struktury, tablice struktur, lista jednokierunkowa z `new`/`free` | | [`examples/structs.fch`](examples/structs.fch) | struktury, tablice struktur, lista wiązana z `new`/`free` |
| [`examples/types.fch`](examples/types.fch) | liczby o podanym rozmiarze i wskaźniki | | [`examples/types.fch`](examples/types.fch) | liczby z rozmiarem i wskaźniki |
| [`examples/c_import.fch`](examples/c_import.fch) | `printf`, `math.h`, `malloc`/`free`, `stderr` | | [`examples/c_import.fch`](examples/c_import.fch) | `printf`, `math.h`, `malloc`/`free`, `stderr` |
| [`examples/simd.fch`](examples/simd.fch) | wektory: iloczyn skalarny liczony po osiem liczb naraz i zysk na czasie | | [`examples/window.fch`](examples/window.fch) | okno GLFW + OpenGL |
| [`examples/window.fch`](examples/window.fch) | okno GLFW z OpenGL |
| [`examples/opengl.fch`](examples/opengl.fch) | nowoczesny OpenGL 3.3: shadery, bufory, obracający się trójkąt | | [`examples/opengl.fch`](examples/opengl.fch) | nowoczesny OpenGL 3.3: shadery, bufory, obracający się trójkąt |
| [`examples/vulkan.fch`](examples/vulkan.fch) | Vulkan: lista kart graficznych z ich pamięcią i kolejkami, utworzenie urządzenia | | [`examples/vulkan.fch`](examples/vulkan.fch) | Vulkan: lista kart graficznych, ich pamięć i kolejki, tworzenie urządzenia |
| [`examples/raylib.fch`](examples/raylib.fch) | raylib i struktury C przekazywane przez wartość | | [`examples/raylib.fch`](examples/raylib.fch) | raylib ze strukturami C przez wartość |
| [`examples/llvm.fch`](examples/llvm.fch) | Finch, który buduje LLVM IR przez API LLVM-C | | [`examples/llvm.fch`](examples/llvm.fch) | Finch budujący LLVM IR przez API LLVM-C |
| [`src/`](src/) | kompilator napisany w Finchu (plik startowy do jego zbudowania leży w [`bootstrap/`](bootstrap/)) | | [`src/`](src/) | kompilator napisany w Finchu (ziarno do jego zbudowania jest w [`bootstrap/`](bootstrap/)) |
## Plan rozwoju ## Plan rozwoju
- [x] Lekser, parser, generowanie kodu LLVM, optymalizacja O2 - [x] Lekser, parser, generowanie kodu LLVM, optymalizacja O2
- [x] Typy, w tym liczby o podanym rozmiarze, wskaźniki i konwersje; funkcje; `if`/`while`/`for` - [x] Typy, w tym liczby z rozmiarem, wskaźniki, konwersje; funkcje; `if`/`while`/`for`
- [x] Czytelne błędy kompilacji; sprawdzanie w trakcie działania (dzielenie, `null`, zakres tablicy) - [x] Czytelne błędy kompilacji; kontrole w czasie działania (dzielenie, null, zakres)
- [x] Operatory bitowe z rozsądną kolejnością działań - [x] Operatory bitowe z rozsądnymi priorytetami
- [x] Nagłówki C przez libclang; `link` z pkg-config; podpowiedzi przy błędach linkera - [x] Nagłówki C przez libclang; `link` z pkg-config; porady przy błędach linkowania
- [x] `struct`, w tym struktury C przekazywane przez wartość (ABI System V) i funkcje zwrotne - [x] `struct`, także struktury C przez wartość (ABI System V) i callbacki
- [x] Tablice z `.len`, sprawdzaniem zakresu i metodami; `for x in lista` - [x] Tablice z `.len`, kontrolą zakresu i metodami; `for x in lista`
- [x] Pamięć: wartości zwalniane na końcu bloku, `defer`, `new`/`free` - [x] Pamięć: zwalnianie na końcu bloku, `defer`, `new`/`free`
- [x] Teksty: `+`, `.len`, indeksowanie, metody, `str(x)`, `int(s)`; `input()`; pliki - [x] Teksty: `+`, `.len`, indeksowanie, metody, `str(x)`, `int(s)`; `input()`; pliki
- [x] Moduły Fincha (`import nazwa`) - [x] Moduły Fincha (`import nazwa`)
- [x] Informacje dla debuggera (`-g`) - [x] Informacje dla debuggera (`-g`)
- [x] Bootstrap: kompilator Fincha napisany w Finchu, który kompiluje sam siebie - [x] Bootstrap: kompilator Fincha napisany w Finchu, który buduje sam siebie
- [x] Windows: `finch.exe`, programy `.exe`, ABI Microsoft x64; budowanie na Windowsa z Linuksa (`--target windows`) - [x] Windows: `finch.exe`, programy `.exe`, ABI Microsoft x64; budowanie na Windowsa z Linuksa (`--target windows`)
- [x] Serwer języka (`finch lsp`) i rozszerzenie do VS Code - [x] Serwer języka (`finch lsp`) i rozszerzenie VS Code
- [x] macOS i ARM64 (Linux i Apple Silicon) z ich konwencjami wywołań C; obsługa Kate - [x] macOS i ARM64 (Linux i Apple Silicon) z ich konwencjami wywołań C; obsługa Kate
- [x] Metody struktur, mapy, błędy jako wartości (`!`, `or`, `try`) - [x] Metody w strukturach, mapy, błędy jako wartości (`!`, `or`, `try`)
- [x] Typy generyczne (`fn max[T]`, `struct Stos[T]`), `match`, enumy - [x] Typy generyczne (`fn max[T]`, `struct Stos[T]`), `match`, enumy
- [x] Biblioteka standardowa w Finchu: `json`, `path`, `time`, `proc`, `fs` - [x] Biblioteka standardowa w Finchu: `json`, `path`, `time`, `proc`, `fs`
- [x] `const`, `if` jako wartość, `"\(x)"` w tekście, tekst `"""`, unie C i wyrażenia w `#define` - [x] `const`, `if` jako wartość, `"\(x)"` w tekście, tekst `"""`, unie C i wyrażenia `#define`
- [x] Bloki `test` i `finch test`, `finch fmt`, `finch.toml`, pamięć podręczna kompilacji, ścieżka wywołań przy błędach - [x] Bloki `test` i `finch test`, `finch fmt`, `finch.toml`, cache budowania, łańcuch wywołań przy błędach
- [x] Wątki (`import thread`) i operacje atomowe; pliki C++; `--target android` - [x] Wątki (`import thread`) i operacje atomowe; pliki C++; `--target android` (kompiluje się; linkowanie z NDK i urządzenia niesprawdzone)
- [x] Kompilator na każdym systemie, na który buduje: Linux, macOS (Apple Silicon i Intel), Windows; wydania robi CI
- [x] Typy wektorowe (`f32x8`, `i8x32`, …) dla SIMD i `--cpu native`
- [x] Maski, przestawianie, tabele bajtów i `dot_add` dla jąder obliczeniowych; `@cpu` i `cpu_has`; `fma`/`muladd`, bity liczb, f16 i bf16
## Co gdzie leży ## Struktura
``` ```
src/ kompilator w Finchu: lekser, parser, AST, generowanie kodu (LLVM IR jako tekst), import C (libclang) src/ kompilator, w Finchu: lekser, parser, AST, generowanie kodu (do LLVM IR jako tekst), import C (libclang)
i konwencje wywołań C, serwer języka, polecenie finch i konwencje wywołań C, serwer języka, sterownik
std/ biblioteka standardowa w Finchu: json, path, time, proc, fs, thread std/ biblioteka standardowa, w Finchu: json, path, time, proc, fs
runtime/ mały runtime w C, dołączany do każdego programu runtime/ mały runtime w C dołączany do każdego programu
bootstrap/ build.sh i pliki startowe: własny LLVM IR kompilatora, osobno dla każdego systemu bootstrap/ build.sh i ziarna: własny LLVM IR kompilatora, po jednym na system
examples/ przykładowe programy examples/ przykładowe programy
editors/ rozszerzenie do VS Code, podświetlanie składni i konfiguracja LSP dla Kate editors/ rozszerzenie VS Code, podświetlanie i konfiguracja LSP dla Kate
tests/ run/ new/ (wzorcowe wyniki), fail/ new/fail/ (oczekiwane błędy), run.sh, cross.sh, shared.sh, lsp_test.py tests/ run/ new/ (wzorcowe wyjście), fail/ new/fail/ (oczekiwane błędy), run.sh, cross.sh, shared.sh, lsp_test.py
docs/ przewodniki po angielsku (en/) i po polsku (pl/) dla trzech grup czytelników docs/ przewodniki en/ i pl/ dla trzech grup odbiorców
``` ```
## Licencja ## Licencja

View file

@ -3,16 +3,12 @@
Finch's compiler is written in Finch. To build it you need something that already runs Finch: the **seed**, which is Finch's compiler is written in Finch. To build it you need something that already runs Finch: the **seed**, which is
the compiler's own LLVM IR, one file per system: the compiler's own LLVM IR, one file per system:
| File | System | Built and tested in CI on | | File | System | Checked |
|---|---|---| |---|---|---|
| `linux-x86_64.ll` | Linux x86-64 | ubuntu-24.04 | | `linux-x86_64.ll` | Linux x86-64 | built and tested here and in CI |
| `linux-aarch64.ll` | Linux ARM64 | ubuntu-24.04-arm | | `linux-aarch64.ll` | Linux ARM64 | made with the ARM64 cross compiler; CI builds and tests it on an ARM64 runner |
| `macos-arm64.ll` | macOS, Apple Silicon | macos-15 | | `windows-x86_64.ll` | Windows x86-64 (MinGW: llvm-mingw's clang + libclang.dll) | built and run under Wine: it compiles itself, and every test program runs |
| `macos-x86_64.ll` | macOS, Intel | macos-15-intel | | `macos-arm64.ll`, `macos-x86_64.ll` | macOS | made on Linux with the macOS 11.3 SDK; checked as IR only (nobody has run them yet) |
| `windows-x86_64.ll` | Windows x86-64 (MinGW: llvm-mingw's clang + libclang.dll) | windows-2022 |
Each CI job builds the compiler from its seed, then from itself, runs every test, and packs the result; a tag `v*` attaches
the packages to the release.
`bootstrap/build.sh` compiles the seed with `clang` (and the runtime, and libclang) into `build/finch0`; `finch0` builds `bootstrap/build.sh` compiles the seed with `clang` (and the runtime, and libclang) into `build/finch0`; `finch0` builds
`src/` into `finch1`; `finch1` builds `src/` again into `build/finch`; the two must make the same IR, or the script stops. `src/` into `finch1`; `finch1` builds `src/` again into `build/finch`; the two must make the same IR, or the script stops.
@ -45,6 +41,5 @@ official LLVM for Windows); `bootstrap/winbuild.sh` turns it into `finch.exe` fo
`bootstrap/build.sh` does the same from Git Bash: put llvm-mingw's `bin` in PATH and set `FINCH_LIBCLANG` (the path of `bootstrap/build.sh` does the same from Git Bash: put llvm-mingw's `bin` in PATH and set `FINCH_LIBCLANG` (the path of
`libclang.dll`), `FINCH_LLVM_INCLUDE` (its `include` folder) and `FINCH_LDFLAGS=-L<folder of libclang.dll>`. `libclang.dll`), `FINCH_LLVM_INCLUDE` (its `include` folder) and `FINCH_LDFLAGS=-L<folder of libclang.dll>`.
macOS: the seeds were made with the macOS 11.3 SDK. `brew install llvm@21` macOS: the seeds were made with the macOS 11.3 SDK; `brew install llvm@21` gives clang and libclang; the CI job sets
gives clang and libclang; set `FINCH_CLANG`, `FINCH_LIBCLANG` and `FINCH_LLVM_INCLUDE` as the CI job does (the `FINCH_CLANG`, `FINCH_LIBCLANG` and `FINCH_LLVM_INCLUDE`. If the macOS build fails, that job is where it shows first.
[technicians' guide](../docs/en/for-technicians.md#option-b-build-it-yourself) has the lines).

View file

@ -21,9 +21,8 @@ Everything in Finch on one page. For explanations, see the [guides](../../README
| `finch version` | version | | `finch version` | version |
| `-l lib` | link a C library (like `link "lib"`) | | `-l lib` | link a C library (like `link "lib"`) |
| `--target windows` / `linux` / `arm64` / `macos` / `android` / triple | build for another system (`android`: the NDK's clang from `ANDROID_NDK_HOME`, usually with `--shared`) | | `--target windows` / `linux` / `arm64` / `macos` / `android` / triple | build for another system (`android`: the NDK's clang from `ANDROID_NDK_HOME`, usually with `--shared`) |
| `-g` | debug info (gdb, lldb) | | `-g` | debug info (gdb, lldb, Visual Studio) |
| `-O0` | no optimizations | | `-O0` | no optimizations |
| `--cpu native` / `x86-64-v3` / `apple-m1` … | use every instruction of that processor (AVX2, AVX-512, NEON); the program then needs one like it |
| `--trace` / `--no-trace` | runtime errors show the chain of calls; on for `run`, `test`, `-g`, `-O0`, off for a plain `build` (it costs speed) | | `--trace` / `--no-trace` | runtime errors show the chain of calls; on for `run`, `test`, `-g`, `-O0`, off for a plain `build` (it costs speed) |
Environment: `FINCH_PATH` (folders with modules), `FINCH_CC` (the C compiler that links), `FINCH_CXX` (for `.cpp` files). Environment: `FINCH_PATH` (folders with modules), `FINCH_CC` (the C compiler that links), `FINCH_CXX` (for `.cpp` files).
@ -40,7 +39,6 @@ links = ["SDL3", "native/glue.cpp"] # libraries and your own .c / .cpp / .o / .a
libdirs = ["vendor/SDL3/lib"] libdirs = ["vendor/SDL3/lib"]
cflags = ["-DFAST"] # for your .c / .cpp files cflags = ["-DFAST"] # for your .c / .cpp files
ldflags = ["-Wl,-rpath,."] ldflags = ["-Wl,-rpath,."]
cpu = "native" # the same as --cpu native
[target.windows] # only when building for that system (also linux, macos, android, linux-arm64) [target.windows] # only when building for that system (also linux, macos, android, linux-arm64)
links = ["ws2_32"] links = ["ws2_32"]
@ -98,7 +96,6 @@ One statement per line, no semicolons, `{` on the same line, no brackets around
| `[]T` | array: `[1, 2, 3]`, `[]str names` | | `[]T` | array: `[1, 2, 3]`, `[]str names` |
| `map[K]V` | map: `["a": 1]`, `map[str]int counts`, `[:]` | | `map[K]V` | map: `["a": 1]`, `map[str]int counts`, `[:]` |
| `ptr[T]`, `ptr` | pointer, untyped pointer | | `ptr[T]`, `ptr` | pointer, untyped pointer |
| `f32x8`, `i8x32`, `f64x4`, `u16x16` … | vector: N numbers worked on at once (SIMD), see below |
| `Name` / `module.Name` | a struct | | `Name` / `module.Name` | a struct |
Escapes: `\n \t \r \0 \\ \" \'`. Escapes: `\n \t \r \0 \\ \" \'`.
@ -243,34 +240,6 @@ Fallible built-ins: `int(s)`, `float(s)`, `read_file`, `write_file`, `delete_fil
--- ---
## Vectors `f32x8` (SIMD)
Types: `f32x4/8/16`, `f64x2/4/8`, `i8x16/32/64`, `i16x8/16/32`, `i32x4/8/16`, `i64x2/4/8`, and `u…` the same; at most 512 bits.
| Code | Gives |
|---|---|
| `f32x8(x)` | `x` in every lane |
| `f32x8(a, b, …, h)` | one number per lane |
| `f32x8(list, i)` | `list[i]` … `list[i+7]` (a `[]f32` or `ptr[f32]`) |
| `f32x8(v)` | an 8-lane vector converted lane by lane (`f32x8(ints)`, `i32x8(floats)`) |
| `v + w`, `v * 2.0`, `-v` | lane by lane; a plain number counts for every lane (`/` only on floats) |
| `& \| ^ << >> ~` | lane by lane, whole-number lanes |
| `v < w`, `v == w`, … | a mask `boolx8`: `m.all()`, `m.any()`, `m.count()`, `m & n`, `!m`, `m[i]` |
| `select(m, a, b)` | lane by lane: `a` where `m` is true, else `b` |
| `v[i]`, `v[i] = x`, `v.len` | a lane; the number of lanes |
| `min(v, w)`, `max`, `abs(v)`, `clamp(v, lo, hi)` | lane by lane |
| `v.sum()`, `v.min()`, `v.max()`, `v.dot(w)` | one number from all the lanes |
| `v.sqrt()`, `v.floor()`, `v.ceil()`, `v.round()` | every lane (float lanes) |
| `v.fma(b, c)`, `v.muladd(b, c)` | `v * b + c`: one rounding always / the fastest way here |
| `v.store(list, i)` | lanes into `list[i]` … |
| `f32x8(list, i, fill)`, `v.store_part(list, i)` | the end of a list: only the lanes inside it (the others are `fill`) |
| `v.shuffle(3, 2, 1, 0)`, `v.lo()`, `v.hi()`, `concat(a, b)` | lanes rearranged (shuffle takes numbers written in the code) |
| `table.lookup(idx)` | `table[idx % table.len]` per lane (`pshufb`, `tbl`) |
| `acc.dot_add(a, b)` | `i32x8` sums gain 4 byte products each (`u8x32` × `i8x32`): `vpdpbusd`, `usdot` where there |
| `@cpu("avx2,fma") fn …`, `cpu_has("avx2")` | a function that may use these features; check at run time before calling it |
---
## Text `str` ## Text `str`
| Code | Gives | | Code | Gives |
@ -331,9 +300,6 @@ Automatic only when nothing is lost: smaller → bigger number, any int → floa
| `shell(command)` | run a command, its exit code | | `shell(command)` | run a command, its exit code |
| `exit(code)` | stop the program now (also ends an `or { }` block) | | `exit(code)` | stop the program now (also ends an `or { }` block) |
| `min(a, b, …)`, `max(a, b, …)`, `abs(x)`, `clamp(x, lo, hi)` | for any numbers (your own function of that name wins) | | `min(a, b, …)`, `max(a, b, …)`, `abs(x)`, `clamp(x, lo, hi)` | for any numbers (your own function of that name wins) |
| `fma(a, b, c)`, `muladd(a, b, c)` | `a * b + c` with one rounding / fused where the processor can (`a * b + c` written out is never fused) |
| `f32_bits(x)`, `f32_from_bits(u)`, `f64_bits`, `f64_from_bits` | a float's bits as `u32`/`u64` and back, unchanged |
| `f16_to_f32(h)`, `f32_to_f16(x)`, `bf16_to_f32(b)`, `f32_to_bf16(x)` | half and bfloat16 numbers (in a `u16`) ↔ `f32`; also on vectors |
| `error(message)` | `return error("…")` in a `-> T!` function | | `error(message)` | `return error("…")` in a `-> T!` function |
| `addr(x)` | pointer to x; `addr(fn)` = C callback | | `addr(x)` | pointer to x; `addr(fn)` = C callback |
| `assert(cond)`, `assert(cond, "why")` | stop with a runtime error when cond is false (in a `test` block: that test fails) | | `assert(cond)`, `assert(cond, "why")` | stop with a runtime error when cond is false (in a `test` block: that test fails) |

View file

@ -53,9 +53,8 @@ The computer reads your program from top to bottom, line by line.
## 2. Getting Finch ready ## 2. Getting Finch ready
Finch runs on **Linux**, **macOS** and **Windows** computers. If a teacher, parent or friend has already set it up Finch runs on **Linux** computers. If a teacher, parent or friend has already set it up
for you, skip to the next section. (On a Mac or on Windows, ask someone to follow the for you, skip to the next section.
[guide for technicians](for-technicians.md#2-installing): it has a ready-made Finch for both.)
Otherwise, open the **Terminal** program (a window where you type commands) and type Otherwise, open the **Terminal** program (a window where you type commands) and type
these lines one by one, pressing **Enter** after each: these lines one by one, pressing **Enter** after each:
@ -77,7 +76,7 @@ To check it works, type:
./build/finch version ./build/finch version
``` ```
You should see something like `Finch 2.6.0`. You should see something like `Finch 2.5.0`.
--- ---

View file

@ -1,11 +1,13 @@
# Finch for Engineers # Finch for Engineers
**The internals of Finch 2.6, whose compiler is written in Finch.** How source becomes tokens, an AST and LLVM IR *as text*, **The internals of Finch 2.5, whose compiler is written in Finch.** How source becomes tokens, an AST and LLVM IR *as text*,
which `clang` compiles and links with a small C runtime; how C headers are read with libclang from Finch itself and how which `clang` compiles and links with a small C runtime; how C headers are read with libclang from Finch itself and how
the C calling conventions are reproduced by hand; the type and ownership rules; how the compiler builds itself from a the C calling conventions are reproduced by hand; the type and ownership rules; how the compiler builds itself from a
seed; and the language server. seed; and the language server.
Assumed: you can read Finch (see the [technicians' guide](for-technicians.md)) and have at least seen LLVM IR. Assumed: you can read Finch (see the [technicians' guide](for-technicians.md)) and have at least seen LLVM IR.
The compiler's first version, in C++ on LLVM's C++ API, is kept on the `before-bootstrap` branch; this guide describes
the Finch one.
> 🇵🇱 Polska wersja: [dla-inzynierow.md](../pl/dla-inzynierow.md) > 🇵🇱 Polska wersja: [dla-inzynierow.md](../pl/dla-inzynierow.md)
@ -32,7 +34,7 @@ Assumed: you can read Finch (see the [technicians' guide](for-technicians.md)) a
## 1. Architecture ## 1. Architecture
The compiler has no LLVM library inside it. It writes LLVM IR as **text** and lets `clang` do optimisation, code The compiler has no LLVM library inside it. It writes LLVM IR as **text** and lets `clang` do optimisation, code
generation and linking. That is what makes it small (about 12k lines) and what lets it be written in Finch itself. generation and linking. That is what makes it small (about 8k lines) and what lets it be written in Finch itself.
``` ```
main.fch ─► loader ── lex ── parse ──► Program (+ every imported module, recursively) main.fch ─► loader ── lex ── parse ──► Program (+ every imported module, recursively)
@ -58,7 +60,7 @@ Design decisions that shape the rest:
Finch type (a string kind: `int`, `f32`, `str`, `array`, `map`, `struct`, `enum`, `named`…) and flags. Finch type (a string kind: `int`, `f32`, `str`, `array`, `map`, `struct`, `enum`, `named`…) and flags.
- **Let clang do the work.** Locals are `alloca`s promoted by `mem2reg`. Optimisation is clang's `-O2` (or `-O0` with `-g`). - **Let clang do the work.** Locals are `alloca`s promoted by `mem2reg`. Optimisation is clang's `-O2` (or `-O0` with `-g`).
- **Values, not references.** Arrays, strings and structs have value semantics; ownership is decided at compile time (§8). - **Values, not references.** Arrays, strings and structs have value semantics; ownership is decided at compile time (§8).
- **A small C runtime** (`runtime/finch_rt.c`, ~1150 lines) does what is tedious in IR: strings, array growth, maps, input, - **A tiny C runtime** (`runtime/finch_rt.c`, ~760 lines) does what is tedious in IR: strings, array growth, maps, input,
files, panics. It is compiled once and cached in `~/.cache/finch`. files, panics. It is compiled once and cached in `~/.cache/finch`.
--- ---
@ -68,17 +70,17 @@ Design decisions that shape the rest:
| File | Lines | Responsibility | | File | Lines | Responsibility |
|---|---|---| |---|---|---|
| `src/lexer.fch` | 470 | Tokens, positions, `"""` text, `"\(x)"` turned into `("…" + str(x) + "…")` tokens | | `src/lexer.fch` | 470 | Tokens, positions, `"""` text, `"\(x)"` turned into `("…" + str(x) + "…")` tokens |
| `src/ast.fch` | 170 | One `Node` struct with a `kind` string, children, type names, positions (`line`, `endLine`) | | `src/ast.fch` | 120 | One `Node` struct with a `kind` string, children, type names, positions (`line`, `endLine`) |
| `src/parser.fch` | 910 | Recursive descent: declarations, statements, expressions, generics (`[T]`), `enum`, `match`, `export`, `link`, `const`, `test "…" { }` | | `src/parser.fch` | 910 | Recursive descent: declarations, statements, expressions, generics (`[T]`), `enum`, `match`, `export`, `link`, `const`, `test "…" { }` |
| `src/gen.fch` | 7030 | Type check + IR: structs, functions, methods, scopes and drops, statements, expressions, built-ins, maps, errors as values, generics (monomorphisation), enums and `match`, vectors (SIMD), `-g` debug metadata, the `index` mode | | `src/gen.fch` | 6450 | Type check + IR: structs, functions, methods, scopes and drops, statements, expressions, built-ins, maps, errors as values, generics (monomorphisation), enums and `match`, `-g` debug metadata, the `index` mode |
| `src/cimport.fch` | 790 | libclang: functions, structs and unions (fields, offsets, padding), enums, constants, globals, macros (also struct-valued) | | `src/cimport.fch` | 790 | libclang: functions, structs and unions (fields, offsets, padding), enums, constants, globals, macros (also struct-valued) |
| `src/platform.fch` | 120 | `Target`: triple, data layout, C compiler, signedness of `char` per system | | `src/platform.fch` | 90 | `Target`: triple, data layout, C compiler, signedness of `char` per system |
| `src/linker.fch` | 480 | The cache (runtime, program object, C/C++ files), libraries (`link`, pkg-config), link-error advice | | `src/linker.fch` | 430 | The cache (runtime, program object, C/C++ files), libraries (`link`, pkg-config), link-error advice |
| `src/main.fch` | 530 | Driver: CLI, loader with module lookup and overlays, `build/run/test/fmt/ir/index/lsp` | | `src/main.fch` | 515 | Driver: CLI, loader with module lookup and overlays, `build/run/test/fmt/ir/index/lsp` |
| `src/manifest.fch` | 240 | `finch.toml`: a small TOML reader, per-target lists | | `src/manifest.fch` | 240 | `finch.toml`: a small TOML reader, per-target lists |
| `src/fmt.fch` | 225 | `finch fmt` and LSP formatting | | `src/fmt.fch` | 225 | `finch fmt` and LSP formatting |
| `src/lsp.fch` | 950 | The language server (§11) | | `src/lsp.fch` | 910 | The language server (§11) |
| `src/docs.fch` | 140 | One description of the built-ins, shared by hover and completion | | `src/docs.fch` | 110 | One description of the built-ins, shared by hover and completion |
| `std/*.fch` | 1040 | `json`, `path`, `time` (+`_windows`), `proc` (+`_windows`), `fs`, `thread` | | `std/*.fch` | 1040 | `json`, `path`, `time` (+`_windows`), `proc` (+`_windows`), `fs`, `thread` |
| `runtime/finch_rt.c` | 1150 | The C runtime: text, arrays, maps, files, float printing, fs, threads, the test runner, the call chain | | `runtime/finch_rt.c` | 1150 | The C runtime: text, arrays, maps, files, float printing, fs, threads, the test runner, the call chain |
| `bootstrap/` | | `build.sh`, `install.sh`, the seeds (§10) | | `bootstrap/` | | `build.sh`, `install.sh`, the seeds (§10) |
@ -114,19 +116,18 @@ dedented by the closing line's indentation in `longString()`.
## 4. Code generation ## 4. Code generation
`gen.fch` is one big `Gen` struct plus functions over it. `generate()` runs: `gen.fch` is one big `Gen` struct plus functions over it. The passes are:
1. `declare()`: every Finch and C struct gets an LLVM type (C structs with their real padding; recursive-by-value 1. `declareStructs`: every Finch and C struct gets an LLVM type (C structs with their real padding); recursive-by-value
structs are refused), then every function gets its signature, with a *borrow analysis* for parameters (§8.2). structs are refused.
2. `define()` for each function: the body, type checked and emitted. Scopes track what must be dropped when they end. 2. `declareFns`: signatures, with a *borrow analysis* for parameters (a parameter that is never stored is passed without
Instantiating a generic adds functions while this goes on, so the loop runs until no new ones appear. a copy).
3. The C entry point `main(argc, argv)` (or, for `--shared`, `finch_module_on_fatal` and the `export fn` wrappers). 3. `define`: each body, type checked and emitted. Scopes track what must be dropped when they end (`defer` runs first).
4. `assemble()`: the declarations, struct types, string constants, helpers and bodies, as one `.ll` text. 4. `defineMainWrapper`: the C `main(argc, argv)`, and `finch_on_fatal` for embedders.
5. With `-g`, DWARF metadata is attached as the IR is written (use `-O0`).
With `-g`, DWARF metadata is attached as the IR is written (use `-O0`).
Generics are **monomorphised**: a call `max(1, 2)` infers `T`, then the template's AST is generated again with `T` bound, Generics are **monomorphised**: a call `max(1, 2)` infers `T`, then the template's AST is generated again with `T` bound,
under its shown name (`max[int]`, a symbol `max.int`). `match` over an enum is exhaustive (a missing variant is an error, `else` covers the under a mangled name (`max$int`). `match` over an enum is exhaustive (a missing variant is an error, `else` covers the
rest); on a value it is a chain of comparisons. Enum values are `i32`; `Color(n)` is range checked; printing uses a rest); on a value it is a chain of comparisons. Enum values are `i32`; `Color(n)` is range checked; printing uses a
generated name table. generated name table.
@ -163,13 +164,10 @@ offset 0, and a field is read through memory. They can't be passed by value to C
## 6. Driver, linking and targets ## 6. Driver, linking and targets
`finch build|run|ir|index file.fch [-o out] [-O0] [-g] [-l lib] [-I dir] [--target t] [--cpu c] [--shared] [--overlay path=file]`. `finch build|run|ir|index file.fch [-o out] [-O0] [-g] [-l lib] [-I dir] [--target t] [--shared] [--overlay path=file]`.
`ir` prints the IR; `index` prints the records the language server uses (§11); `--shared` makes a shared library of the `ir` prints the IR; `index` prints the records the language server uses (§11); `--shared` makes a shared library of the
`export fn`s. `export fn`s.
`--cpu <name>` (or `cpu` in `finch.toml`) adds `-march=<name>` on x86-64 or `-mcpu=<name>` on ARM64 to the clang that
compiles the IR (not the runtime); it is part of the cache key.
`--target windows|linux|arm64|macos|android|<triple>` changes the triple and data layout, the C `char` signedness, the ABI rules `--target windows|linux|arm64|macos|android|<triple>` changes the triple and data layout, the C `char` signedness, the ABI rules
above, the OS variant of modules (`name_windows.fch`), and the C compiler: `FINCH_CC` or `clang --target=…`. Linking above, the OS variant of modules (`name_windows.fch`), and the C compiler: `FINCH_CC` or `clang --target=…`. Linking
asks `pkg-config` for flags for `link "lib"`; when the linker fails the output is analysed and the likely missing asks `pkg-config` for flags for `link "lib"`; when the linker fails the output is analysed and the likely missing
@ -212,7 +210,6 @@ owned at that point is not freed.
A line that starts at bracket depth 0 and doesn't continue a line ending in an operator or `,` is re-indented to A line that starts at bracket depth 0 and doesn't continue a line ending in an operator or `,` is re-indented to
4 × brace depth; other lines (continuations, `"""` bodies) are shifted by the same amount as the line that started the 4 × brace depth; other lines (continuations, `"""` bodies) are shifted by the same amount as the line that started the
statement, so their shape (and the `"""` dedent) is kept. statement, so their shape (and the `"""` dedent) is kept.
--- ---
## 7. Type system ## 7. Type system
@ -225,10 +222,9 @@ statement, so their shape (and the `"""` dedent) is kept.
| `char` `i8` `u8` / `i16` `u16` / `i32` `u32` / `int` `u64` | `i8` / `i16` / `i32` / `i64` | | `char` `i8` `u8` / `i16` `u16` / `i32` `u32` / `int` `u64` | `i8` / `i16` / `i32` / `i64` |
| `f32` / `float` | `float` / `double` | | `f32` / `float` | `float` / `double` |
| `str`, `[]T` | `{ ptr, i64, i64 }` (pointer, length, capacity) | | `str`, `[]T` | `{ ptr, i64, i64 }` (pointer, length, capacity) |
| `map[K]V` | `{ ptr, i64, i64, i64, ptr, i64, i64 }` (entries, len, used, cap, index, icap, adds; see src/gen.fch) | | `map[K]V` | `{ ptr, i64, i64, i64, ptr, i64 }` (entries, len, used, cap, index, icap; see src/gen.fch) |
| `ptr`, `ptr[T]`, `null` | `ptr` (opaque) | | `ptr`, `ptr[T]`, `null` | `ptr` (opaque) |
| `[N]T` | `[N x T]` | | `[N]T` | `[N x T]` |
| `f32x8`, `i8x32`, … | `<8 x float>`, `<32 x i8>` (§7, Vectors) |
| Finch struct | named struct type, natural layout | | Finch struct | named struct type, natural layout |
| a method's `self` | `ptr` (the first parameter) | | a method's `self` | `ptr` (the first parameter) |
| the result of `-> T!` | `{ i1 ok, T value, str error }` (`i8` in place of `T` for `-> !`) | | the result of `-> T!` | `{ i1 ok, T value, str error }` (`i8` in place of `T` for `-> !`) |
@ -239,16 +235,11 @@ Signedness lives only in the Finch type and selects `sdiv/udiv`, `ashr/lshr`, `i
### Values ### Values
```c ```cpp
struct Val { struct Value_ { Value *v; FType type; bool literal; bool fresh; };
str v // the IR operand: %t12, 42, @.str.3, zeroinitializer
ast.Type ty
bool fresh
bool lit
}
``` ```
- **`lit`**: a number written in the code or an imported C constant. It may become any integer type - **`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 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). 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, - **`fresh`**: an owning value (str/array/owning struct) that nobody holds yet: the result of a call,
@ -290,64 +281,13 @@ Pointers/null: `icmp eq/ne ptr` only. `str`: `finch_str_eq` (length + `memcmp`)
`finch_str_cmp` for ordering. Floats: `oeq olt ole ogt oge` and `une` for `!=`. Integers/char by `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. signedness (char unsigned). Bool: equality only. Arrays/structs: error.
### Vectors ---
`ast.vecType(name)` recognises `<lane>x<N>` (lanes `i8`…`u64`, `f32`, `f64`; N = 2…64; at most 512 bits) and gives
`Type(kind: "vec", elem: [lane], count: N)`, which is an LLVM vector `<N x T>`. The parser takes such a name wherever a
type goes; a call `f32x8(…)` goes to `vecMake()`.
| Finch | IR |
|---|---|
| `f32x8(x)` | `insertelement` into lane 0 + `shufflevector … zeroinitializer` (a splat) |
| `f32x8(a, …, h)` | a chain of `insertelement` |
| `f32x8(list, i)` | bounds check of `i … i+7`, then `load <8 x float>, ptr …, align 4` (the lane's alignment) |
| `f32x8(i32x8 v)` | `sitofp`, `fptosi`, `sext`, `trunc`… on the whole vector |
| `v + w`, `v * 2.0` | `fadd`/`add`… on vectors; a scalar operand is coerced to the lane type, then splatted |
| `v[i]` | a place: `getelementptr <lane>, ptr %v, i64 i` after a bounds check; or `extractelement` on a temporary |
| `v < w`, `v == w` … | `fcmp`/`icmp` → `<N x i1>`, a mask (`boolxN`: `vec` with `bool` lanes) |
| `select(m, a, b)` | `select <N x i1>` |
| `m.any()`, `.all()`, `.count()` | `llvm.vector.reduce.or`/`.and`; `zext` + `llvm.vector.reduce.add` |
| `f32x8(list, i, fill)`, `v.store_part(list, i)` | `llvm.masked.load`/`llvm.masked.store` with the mask `<0, 1, …> < len - i` |
| `v.shuffle(…)`, `.lo()`, `.hi()`, `concat(a, b)` | `shufflevector` with a constant mask |
| `table.lookup(idx)` | `and` with `len - 1`, then `extractelement`/`insertelement` per lane: the pattern the x86 and ARM backends turn into `pshufb`/`tbl` |
| `acc.dot_add(a, b)` | `llvm.x86.avx512.vpdpbusd.{128,256,512}` or `llvm.aarch64.neon.{usdot,sdot,udot}` when the function's features have them; else `zext`/`sext`, `mul`, four strided `shufflevector`s and `add`s |
| `min`, `max`, `abs` | `llvm.minnum`/`maxnum`, `llvm.smin`/`umin`…, `llvm.fabs`/`llvm.abs` |
| `v.sum()`, `.min()`, `.max()` | `llvm.vector.reduce.fadd` (with `reassoc`), `.add`, `.fmin`, `.smin`… |
| `v.sqrt()`, `.floor()`, `.ceil()`, `.round()` | the `llvm.*` intrinsic of that name |
| `v.fma(b, c)`, `fma(a, b, c)` | `llvm.fma` (one rounding; a libm call per lane without FMA hardware) |
| `v.muladd(b, c)`, `muladd(a, b, c)` | `llvm.fmuladd` (fused where the target has FMA, `fmul` + `fadd` where not) |
| `v.store(list, i)` | bounds check, `store <8 x float> …, align 4` |
Integer `/` and `%` are refused: neither x86 nor ARM has a lane-wise integer divide, and LLVM would scalarise it.
Ordering comparisons are refused because their result would be a mask, which has no Finch type.
**Alignment.** LLVM gives `<8 x float>` an ABI alignment of 32 (its size), but `malloc` and Finch's `alloca … align 16`
promise only 16. `llty()` records every vector wider than 16 bytes in `g.wide`, and `resolveStructBody` records a struct
that holds one; `load()` and `store()` add `, align 16` for those types. Without it LLVM may emit `vmovaps` on a 16-byte
aligned address and crash.
**Which instructions** is clang's choice: `--cpu <name>` adds `-march=<name>` (x86-64) or `-mcpu=<name>` (ARM64) to the
IR's compile, so the same IR becomes SSE2, AVX2 + FMA, AVX-512 or NEON code. Lanes wider than the registers are split.
Some operations need to know the features at code generation time (`dot_add` picks `vpdpbusd` only where it exists).
`linker.cpuFeatures()` asks clang for its predefined macros with the same target and `--cpu`
(`clang -march=X -dM -E empty.c`: `__AVXVNNI__`, `__AVX512VNNI__`, `__ARM_FEATURE_MATMUL_INT8` …), maps them to Finch's
feature names and caches the answer per clang and flags; they reach `gen` as `Options.features`. `@cpu("…")` on a function
(`Node.cpu`) adds its own: `define … "target-features"="+avx2,+fma"` (with `+evex512` for any AVX-512 feature, which
LLVM 18+ needs for 512-bit registers), and `g.curFeatures` is the union while that function is generated. Features of the
other architecture are dropped (`cpuOf`). `checkCpuCall` refuses a vector wider than 128 bits as a parameter or result
between functions whose `@cpu` differ, because caller and callee would lower it to different registers.
`cpu_has(name)` calls `finch_cpu_has` in the runtime: CPUID leaves 1 and 7 with XGETBV for the AVX/AVX-512 state on x86,
`getauxval(AT_HWCAP/AT_HWCAP2)` on Linux ARM64, `sysctlbyname("hw.optional.arm.FEAT_…")` on macOS.
Clang is always run as `NIX_ENFORCE_NO_NATIVE=0 clang …` on Unix: Nix's cc-wrapper otherwise drops `-march=native`
inside `nix-shell`. If `--cpu native` still yields no SSE4.2 macro on x86, the driver warns.
--- ---
## 8. Ownership and memory ## 8. Ownership and memory
### 8.1 The model ### 9.1 The model
Every value of an **owning type** (`str`, `[]T`, or a Finch struct with an owning field) has exactly one Every value of an **owning type** (`str`, `[]T`, or a Finch struct with an owning field) has exactly one
owner: a variable, an array element, a struct field, or a heap cell from `new()`. The owner drops the owner: a variable, an array element, a struct field, or a heap cell from `new()`. The owner drops the
@ -371,21 +311,21 @@ A string literal is a fresh value with `cap = 0`: moving it costs nothing, dropp
(`release()` ignores constants), and writing into it (`s[i] = c`) first makes a heap copy (`release()` ignores constants), and writing into it (`s[i] = c`) first makes a heap copy
(`finch_str_own`). Strings from C are borrowed with `cap = -1` and always copied before being kept. (`finch_str_own`). Strings from C are borrowed with `cap = -1` and always copied before being kept.
### 8.2 Parameter borrowing: `mutates()` ### 9.2 Parameter borrowing: `mutates()`
When declaring a function, every owning parameter is checked with `mutates(name, body)`: an When declaring a function, every owning parameter is checked with `mutates(name, body)`: an
assignment whose root variable (`rootVar()`: through `.field` and `[i]`) is the parameter, a changing assignment whose root variable (`rootVar()`: through `.field` and `[i]`) is the parameter, a changing
method on it (`push pop insert remove clear resize sort reverse`), or `addr(…)` of it. Unchanged method on it (`push pop insert remove clear resize sort reverse`), or `addr(…)` of it. Unchanged
parameters are borrowed (`FnSig.borrow`); changed ones are copied once at entry. The analysis is 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. syntactic and conservative: anything that *might* change the parameter makes a copy, which is always correct.
The same analysis decides whether `for x in list` must copy elements. The same analysis decides whether `for x in list` must copy elements.
Methods take part: `declare()` first finds which methods change `self` (`mutates("self", body)`), Methods take part: `declareFns()` first finds which methods change `self` (`mutates("self", body)`),
repeating until nothing changes, because calling a changing method on `self` changes `self` too. Their repeating until nothing changes, because calling a changing method on `self` changes `self` too. Their
names go into `g.mm`, which `mutates()` treats like `push`: `p.move(1)` changes `p`. names go into `mutatingMethods`, which `mutatesExpr()` treats like `push`: `p.move(1)` changes `p`.
Only then is `borrow` computed for every function and method. Only then is `borrowParam` computed for every function and method.
### 8.3 Copy and drop ### 9.3 Copy and drop
`dropAt(addr, T)` / `copyAt(dst, src, T)` work on addresses: `dropAt(addr, T)` / `copyAt(dst, src, T)` work on addresses:
@ -395,10 +335,10 @@ Only then is `borrow` computed for every function and method.
element and then `finch_arr_free`; copies call `finch_arr_clone_raw` and then deep-copy owning element and then `finch_arr_free`; copies call `finch_arr_clone_raw` and then deep-copy owning
elements over the raw bits. Structs drop/copy only their owning fields. elements over the raw bits. Structs drop/copy only their owning fields.
Helpers are emitted in their own functions (`beginHelper()` / `endHelper()` set the function being written aside Helpers are emitted in their own functions (`HelperScope` saves the builder position and switches off
and switch off debug locations), may call each other recursively (`struct Node { []Node kids }`), and are inlined by O2. debug locations), may call each other recursively (`struct Node { []Node kids }`), and are inlined by O2.
### 8.4 What it looks like ### 9.4 What it looks like
```c ```c
fn greet(str name) -> str { fn greet(str name) -> str {
@ -441,22 +381,22 @@ The `-O0` IR of `main` (abridged):
and `greet` returns `msg` without copying (`ret … %5` after loading the slot: the variable was moved out). and `greet` returns `msg` without copying (`ret … %5` after loading the slot: the variable was moved out).
### 8.5 defer ### 9.5 defer
`defer stmt` records the statement in the current scope (`Scope.defers`, with `orders`: how many variables existed `defer stmt` pushes a `Cleanup{isDefer, body, order = current varOrder}` into the current scope; it
then) and a `Cleanup{isDefer}`; it emits nothing there. Whenever the scope's cleanups are emitted (falling off the end of the block, emits nothing there. Whenever the scope's cleanups are emitted (falling off the end of the block,
`return`, `break`/`continue` leaving it) the body is generated **at that exit**, with `deferLimit = order` `return`, `break`/`continue` leaving it) the body is generated **at that exit**, with `deferLimit = order`
so that `lookup()` hides variables declared after the defer: those are already dropped by then so that `lookup()` hides variables declared after the defer: those are already dropped by then
(cleanups run in reverse). `return`/`break`/`continue` inside a deferred statement are errors (`inDefer`). (cleanups run in reverse). `return`/`break`/`continue` inside a deferred statement are errors (`inDefer`).
Defer bodies are therefore duplicated per exit path, which is what makes them free on the normal path. Defer bodies are therefore duplicated per exit path, which is what makes them free on the normal path.
### 8.6 Manual memory ### 9.6 Manual memory
`new(v)` allocates `sizeof(T)` with `finch_alloc`, stores `own(v)` and returns `ptr[T]`. `free(p)` drops `new(v)` allocates `sizeof(T)` with `finch_alloc`, stores `own(v)` and returns `ptr[T]`. `free(p)` drops
the pointee if `T` is owning (skipping null) and calls libc `free`. Pointers never own anything: the pointee if `T` is owning (skipping null) and calls libc `free`. Pointers never own anything:
`addr(x)` can dangle after `x`'s block ends; this is not checked. `addr(x)` can dangle after `x`'s block ends; this is not checked.
### 8.7 Verifying it ### 9.7 Verifying it
`MEMCHECK=1 tests/run.sh` builds every test program and runs it under valgrind with `MEMCHECK=1 tests/run.sh` builds every test program and runs it under valgrind with
`--leak-check=full --errors-for-leak-kinds=all`: zero leaks and zero invalid accesses across all of them, `--leak-check=full --errors-for-leak-kinds=all`: zero leaks and zero invalid accesses across all of them,
@ -465,6 +405,8 @@ iteration, nested arrays of structs, and C struct passing.
--- ---
---
## 9. Generics, enums and match ## 9. Generics, enums and match
See §4 for how they are generated. For the rules a user sees, read sections 23 and 24 of the See §4 for how they are generated. For the rules a user sees, read sections 23 and 24 of the
@ -486,9 +428,8 @@ and stops unless `finch1` and `finch` produce byte-identical IR (a fixpoint: the
whatever built it). `bootstrap/build.sh seed` rewrites the seed from the current compiler; do it whenever the compiler's whatever built it). `bootstrap/build.sh seed` rewrites the seed from the current compiler; do it whenever the compiler's
own source starts to use a feature the committed seed lacks. own source starts to use a feature the committed seed lacks.
A seed is per system because the C calling convention for the libclang calls is baked into it (§5). There are seeds A seed is per system because the C calling convention for the libclang calls is baked into it (§5).
for Linux x86-64 and ARM64, macOS ARM64 and x86-64 (made with Apple's SDK headers), and Windows x86-64 (made with Linux x86-64 and Linux ARM64 have seeds. macOS and Windows would need seeds made with Apple's and Microsoft's headers.
llvm-mingw's headers). CI builds the compiler from each one on that system and runs the tests there.
--- ---
@ -504,17 +445,16 @@ mode the compiler prints, besides IR-free output, tab-separated records:
| `@V name type file line col fnFile fnLine fnEnd` | a variable and the function it lives in | | `@V name type file line col fnFile fnLine fnEnd` | a variable and the function it lives in |
| `@F module name recv file line col endLine signature params` | a function or method | | `@F module name recv file line col endLine signature params` | a function or method |
| `@S` / `@SF`, `@E`, `@I` | structs (and fields), enums, imports | | `@S` / `@SF`, `@E`, `@I` | structs (and fields), enums, imports |
| `@W` | a warning |
The first error is printed as `path:line:col: error: message` and becomes a diagnostic. When code does not compile, the The first error is printed as `path:line:col: error: message` and becomes a diagnostic. When code does not compile, the
server keeps answering completion and signature help from the last version that did. `tests/lsp_test.py` is the acceptance server keeps answering completion and signature help from the last version that did. `tests/lsp_test.py` is the acceptance
test. test (22 checks).
--- ---
## 12. Semantics vs. C ## 12. Semantics vs. C
| Situation | C | Finch | | Situation | C | Finch 2.0 |
|---|---|---| |---|---|---|
| Signed integer overflow | UB | defined wraparound (no `nsw`) | | Signed integer overflow | UB | defined wraparound (no `nsw`) |
| Integer division by zero, `INT_MIN / -1` | UB | compile error if constant, otherwise runtime error | | Integer division by zero, `INT_MIN / -1` | UB | compile error if constant, otherwise runtime error |
@ -527,22 +467,13 @@ test.
| Uninitialized variables | UB | impossible (zero or defaults) | | Uninitialized variables | UB | impossible (zero or defaults) |
| Missing return | UB if used | compile error | | Missing return | UB if used | compile error |
| Order of evaluation | mostly unspecified | left to right; in an assignment the right side first | | Order of evaluation | mostly unspecified | left to right; in an assignment the right side first |
| Shift ≥ width, out-of-range float→int | UB | poison, as in LLVM (not checked) | | Shift ≥ width, out-of-range float→int | UB | still poison (not checked yet) |
| `a * b + c` on floats | may be fused into an FMA (`-ffp-contract`) | never fused: two roundings |
### Floating point
Float operations are emitted as plain `fadd`/`fsub`/`fmul`/`fdiv`/`fneg` with **no fast-math flags**, so LLVM may not
reassociate, contract or assume no NaNs; `linker.fch` also compiles the IR with `-ffp-contract=on`, which fuses only the
`llvm.fmuladd` calls Finch asks for. The only flagged instruction is `v.sum()`'s `llvm.vector.reduce.fadd` (`reassoc`).
`fma` → `llvm.fma`, `muladd` → `llvm.fmuladd`, on scalars and vectors (`fmaCall`, `fusedOp`).
`f32_bits`/`f32_from_bits`/`f64_…` are `bitcast` (also between vectors). `f16_to_f32` is `bitcast i16 → half` + `fpext`,
`f32_to_f16` is `fptrunc` + `bitcast`: one instruction with F16C or on ARM64, `__extendhfsf2`/`__truncsfhf2` from the C
compiler's runtime library elsewhere. `bf16_to_f32` is `zext` + `shl 16` + `bitcast`; `f32_to_bf16` rounds to nearest even
in integer ops (`u + 0x7fff + ((u >> 16) & 1)`, then `>> 16`) and keeps NaNs quiet (`| 0x40`), so it needs no library.
| Dangling pointer from `addr()` / `new` + `free` | UB | still UB | | Dangling pointer from `addr()` / `new` + `free` | UB | still UB |
---
---
### The chain of calls on runtime errors ### The chain of calls on runtime errors
With `Options.trace` (default for `run`, `test`, `-g`, `-O0`; `--trace`/`--no-trace`; never with `--shared`) every Finch With `Options.trace` (default for `run`, `test`, `-g`, `-O0`; `--trace`/`--no-trace`; never with `--shared`) every Finch
@ -587,10 +518,13 @@ evaluated twice and temporaries are never pointed to.
## 13. Known limitations ## 13. Known limitations
- The compiler runs on Linux x86-64 and ARM64 only (no seed for macOS or Windows yet). Programs can be built for all three.
- macOS output is only checked as IR; Windows and ARM64 are run under Wine and qemu in CI.
- One error per run, no recovery. - One error per run, no recovery.
- `-g` debug info is best with `-O0`. - `-g` debug info is best with `-O0`.
- The driver uses `sh` utilities (`rm`, `mkdir`), so it needs a POSIX shell.
- Generics are monomorphised (code size grows with instantiations); there are no trait bounds, a wrong use is an error - Generics are monomorphised (code size grows with instantiations); there are no trait bounds, a wrong use is an error
inside the instantiated body. inside the instantiated body.
- A failed test's owned memory is not freed (the test runner `longjmp`s out of it). - A failed test's owned memory is not freed (the test runner `longjmp`s out of it).
- Vectors have no integer division, and `lookup` on baseline x86-64 (no SSSE3) is done lane by lane. - `--target android` is checked to the object file only; linking needs the NDK and has not been run on a device.

View file

@ -41,7 +41,6 @@ compiler works inside. For that, see the [guide for engineers](for-engineers.md)
28. [A project: finch.toml](#28-a-project-finchtoml) 28. [A project: finch.toml](#28-a-project-finchtoml)
29. [Threads](#29-threads) 29. [Threads](#29-threads)
30. [C++, Rust and Android](#30-c-rust-and-android) 30. [C++, Rust and Android](#30-c-rust-and-android)
31. [Vectors (SIMD)](#31-vectors-simd)
--- ---
@ -68,24 +67,19 @@ messages that tell you how to fix the problem.
## 2. Installing ## 2. Installing
The Finch compiler is written in Finch (see the [engineers' guide](for-engineers.md)). It runs on **Linux** (x86-64 and The Finch compiler is written in Finch (see the [engineers' guide](for-engineers.md)). It runs on **Linux**
ARM64), **macOS** (Apple Silicon and Intel) and **Windows** (x86-64), and builds programs for all of them. On every (x86-64 and ARM64) and builds programs for **Linux, Windows and macOS**. It needs **clang 21** and **libclang**
system it needs **clang** (it compiles each program with it) and **libclang** from LLVM 21 (it reads C headers with it). (LLVM 21) to read C headers and to compile its output.
### Option A: a ready build The compiler does not run on macOS or Windows yet; it needs a seed made with that system's headers
([bootstrap/README.md](../../bootstrap/README.md) says why). Until then, build your Windows and macOS programs from Linux
with `--target` (below), or use the older C++ compiler from the `before-bootstrap` branch.
Download the archive for your system from the [releases](https://github.com/Nexoniarz/Finch/releases) and unpack it. ### Option A: a ready build (Linux)
Keep `runtime/` and `std/` next to the `finch` file (or install it, below).
| System | Archive | Also install | Download `finch-linux-x64.tar.gz` or `finch-linux-arm64.tar.gz` from the
|---|---|---| [releases](https://github.com/Nexoniarz/Finch/releases) and unpack it. Keep `runtime/` and `std/` next to the `finch` file
| Linux x86-64 | `finch-linux-x64.tar.gz` | clang 21 and libclang (table below) | (or install it, below). You also need `clang` 21 and its libclang library:
| Linux ARM64 (Raspberry Pi 4/5, Graviton…) | `finch-linux-arm64.tar.gz` | the same |
| macOS, Apple Silicon | `finch-macos-arm64.tar.gz` | `xcode-select --install` (clang and the system headers), `brew install llvm@21` (libclang) |
| macOS, Intel | `finch-macos-x64.tar.gz` | the same |
| Windows x64 | `finch-windows-x64.zip` (`finch.exe` with `libclang.dll`) | [llvm-mingw](https://github.com/mstorsjo/llvm-mingw/releases) (the `ucrt-x86_64` zip): unpack it and put its `bin` folder on your PATH |
On Linux:
| Distribution | Packages | | Distribution | Packages |
|---|---| |---|---|
@ -94,15 +88,12 @@ On Linux:
| Arch | `clang pkgconf` | | Arch | `clang pkgconf` |
| Nix | `nix-shell` in the repository gives all of it | | Nix | `nix-shell` in the repository gives all of it |
On Windows, llvm-mingw gives Finch `clang`, the C headers and the linker in one folder; Windows programs built with it
need nothing else to run. Keep `libclang.dll` next to `finch.exe`.
### Option B: build it yourself ### Option B: build it yourself
```sh ```sh
git clone https://github.com/Nexoniarz/Finch.git git clone https://github.com/Nexoniarz/Finch.git
cd Finch cd Finch
nix-shell # Linux: clang, libclang, pkg-config (or install them with your distribution) nix-shell # clang, libclang, pkg-config (or install them with your distribution)
bootstrap/build.sh # build/finch bootstrap/build.sh # build/finch
./build/finch version ./build/finch version
``` ```
@ -111,26 +102,6 @@ bootstrap/build.sh # build/finch
`src/` with it, builds `src/` again with the result and checks the two are identical. If libclang isn't where the linker `src/` with it, builds `src/` again with the result and checks the two are identical. If libclang isn't where the linker
looks, tell it: `FINCH_LIBCLANG="-L/usr/lib/llvm-21/lib -lclang-21" bootstrap/build.sh`. looks, tell it: `FINCH_LIBCLANG="-L/usr/lib/llvm-21/lib -lclang-21" bootstrap/build.sh`.
On a Mac, with Homebrew's LLVM:
```sh
brew install llvm@21
export FINCH_CLANG="$(brew --prefix llvm@21)/bin/clang"
export FINCH_LIBCLANG="-L$(brew --prefix llvm@21)/lib -lclang"
export FINCH_LLVM_INCLUDE="$(brew --prefix llvm@21)/include"
bootstrap/build.sh
```
On Windows, in Git Bash, with llvm-mingw on the PATH and the official LLVM for Windows (for `libclang.dll` and its headers)
unpacked to `C:/llvm`:
```sh
export FINCH_LIBCLANG="C:/llvm/bin/libclang.dll"
export FINCH_LLVM_INCLUDE="C:/llvm/include"
export FINCH_LDFLAGS="-LC:/llvm/bin"
bootstrap/build.sh # build/finch.exe, with libclang.dll copied next to it
```
### Building for another system ### Building for another system
`--target` builds a program for another system. Finch needs that system's C compiler to link: `--target` builds a program for another system. Finch needs that system's C compiler to link:
@ -180,9 +151,7 @@ options:
-l <lib> link a C library, same as link "lib" in the file -l <lib> link a C library, same as link "lib" in the file
-I <dir> look for C headers there too -I <dir> look for C headers there too
--target <name> build for another system: windows, linux, arm64, macos, android, or an LLVM triple --target <name> build for another system: windows, linux, arm64, macos, android, or an LLVM triple
--cpu <name> use every instruction of a processor: native (this one), x86-64-v3 (AVX2), -g add debug info (for gdb / lldb / Visual Studio)
x86-64-v4 (AVX-512), apple-m1, ... (section 31)
-g add debug info (for gdb and lldb)
-O0 skip optimizations -O0 skip optimizations
--trace runtime errors show the chain of calls (on anyway for run, test, -g and -O0) --trace runtime errors show the chain of calls (on anyway for run, test, -g and -O0)
--no-trace they don't (a plain finch build doesn't: keeping the chain makes calls slower) --no-trace they don't (a plain finch build doesn't: keeping the chain makes calls slower)
@ -345,40 +314,6 @@ str(p) // a C char pointer as text
**Fixed-size numbers wrap around**, like real hardware: a `u8` holding 255 plus 1 becomes 0. **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`. Calculations happen in the type of the values involved, so `u8(250) + 10` is `4`, not `260`.
### Decimal numbers: what is guaranteed
`f32` and `f64` are IEEE 754 numbers, and Finch computes them **exactly as written**, the same on every processor and
with every `--cpu`:
- Each operation rounds once, in the order written. `a * b - c * d` is two multiplies and a subtract, never folded into a
fused multiply-add (C compilers often do that by default, which changes the last digits).
- Nothing is reordered or simplified: `(a + b) + c` is not turned into `a + (b + c)`, and `x * 0.0` is not assumed to be 0
(it is NaN when `x` is). The one exception is `v.sum()` on a vector, which adds the lanes in the fastest order.
When speed matters more than the last bit, ask for it by name:
| | |
|---|---|
| `fma(a, b, c)` | `a * b + c` with a single rounding, always. One instruction where the processor has FMA (with `--cpu`, on ARM64); a slower library call elsewhere. |
| `muladd(a, b, c)` | `a * b + c` the fastest way on this processor: fused where it can be, a multiply and an add where not. |
Both work on numbers and on vectors (`v.fma(b, c)`, `v.muladd(b, c)`).
**Numbers as bits.** File formats and model weights store numbers as raw bits. These look at the bits without
converting the value (all of them also work lane by lane on vectors):
| | |
|---|---|
| `f32_bits(x)` / `f32_from_bits(u)` | `f32` ↔ the `u32` with the same bits (`f32_bits(1.0)` is `0x3f800000`) |
| `f64_bits(x)` / `f64_from_bits(u)` | `f64` ↔ `u64` |
| `f16_to_f32(h)` / `f32_to_f16(x)` | half precision, kept in a `u16`, ↔ `f32` (rounded to the nearest) |
| `bf16_to_f32(b)` / `f32_to_bf16(x)` | bfloat16, kept in a `u16`, ↔ `f32` (rounded to the nearest, ties to even) |
```c
[]u16 weights = read_weights() // f16 numbers from a file
v := f16_to_f32(u16x8(weights, 0)) // 8 of them as f32, at once
```
--- ---
## 7. Operators ## 7. Operators
@ -731,8 +666,6 @@ Using `.value`, `.field` or `[i]` on a `null` pointer **stops the program** with
| `exit(code)` | Ends the program right away. | | `exit(code)` | Ends the program right away. |
| `addr(x)` | A pointer to `x`; `addr(function)` gives C a function pointer (callback). | | `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)). | | `new(value)`, `free(p)` | Manual heap memory (see [Memory](#13-memory-who-frees-what)). |
| `fma(a, b, c)`, `muladd(a, b, c)` | `a * b + c` with one rounding / the fastest way (see [Decimal numbers](#decimal-numbers-what-is-guaranteed)). |
| `f32_bits(x)`, `f32_from_bits(u)`, `f16_to_f32(h)`, … | A number's bits, and half-precision numbers (see [Decimal numbers](#decimal-numbers-what-is-guaranteed)). |
| `int(x)`, `str(x)`, `u8(x)`, … | Conversions (see [Conversions](#conversions)). `str(x)` works on every type and gives the same text `print` shows. `int(text) or 0` handles text that isn't a number. | | `int(x)`, `str(x)`, `u8(x)`, … | Conversions (see [Conversions](#conversions)). `str(x)` works on every type and gives the same text `print` shows. `int(text) or 0` handles text that isn't a number. |
--- ---
@ -1013,7 +946,8 @@ gdb ./game
``` ```
`-g` adds debug info (lines, functions, variables, struct fields). `-O0` keeps every variable `-g` adds debug info (lines, functions, variables, struct fields). `-O0` keeps every variable
visible; without it the optimizer may remove some. On Windows the debug info is DWARF as well: use the `lldb` that comes with llvm-mingw, or gdb. visible; without it the optimizer may remove some. On Windows, `-g` writes CodeView, which the
Visual Studio debugger and WinDbg read.
--- ---
@ -1025,11 +959,10 @@ visible; without it the optimizer may remove some. On Windows the debug info is
| `… come from "x.h", but its library isn't linked` | Add `link "name"` at the top of the file. | | `… 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` | Not installed, or a wrong name. On Nix, add it to `shell.nix` and run inside `nix-shell`. | | `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. | | `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. |
| `--cpu native`: the program stops with `Illegal instruction` on another computer | It was built for the processor that built it. Build without `--cpu`, or with an older one (`x86-64-v2`). |
| `can't find the module 'x'` | Put `x.fch` next to the importing file, or set `FINCH_PATH`. | | `can't find the module 'x'` | Put `x.fch` 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`. | | `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. | | 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. |
| Windows: `no C compiler found to link with ('clang')` | Put llvm-mingw's `bin` folder on your PATH (section 2). | | Windows: `finch` can't link (`LNK…` errors about `libcmt`, `kernel32`) | Install Visual Studio Build Tools with *Desktop development with C++*. |
| Windows: `finch.exe` doesn't start (missing `libclang.dll`) | Keep `libclang.dll` next to `finch.exe`. | | Windows: `finch.exe` doesn't start (missing `libclang.dll`) | Keep `libclang.dll` next to `finch.exe`. |
| macOS: `finch` doesn't start (`Library not loaded: …libLLVM…`) | `brew install llvm@21`. | | macOS: `finch` doesn't start (`Library not loaded: …libLLVM…`) | `brew install llvm@21`. |
| macOS: `can't find the C header 'stdio.h'` | Install the command line tools: `xcode-select --install`. | | macOS: `can't find the C header 'stdio.h'` | Install the command line tools: `xcode-select --install`. |
@ -1047,7 +980,7 @@ Finch/
├── std/ the standard library, in Finch: json, path, time, proc, fs, thread ├── std/ the standard library, in Finch: json, path, time, proc, fs, thread
├── runtime/ finch_rt.c: the small runtime (text, arrays, maps, input, files) ├── runtime/ finch_rt.c: the small runtime (text, arrays, maps, input, files)
├── bootstrap/ build.sh, install.sh and the seeds (the compiler's own IR, one per system) ├── bootstrap/ build.sh, install.sh and the seeds (the compiler's own IR, one per system)
├── examples/ hello, tour, types, structs, todo, guess, wordcount, c_import, simd, window, opengl, vulkan, raylib, llvm ├── examples/ hello, tour, types, structs, todo, guess, c_import, window, opengl, vulkan, raylib, llvm
├── tests/ ├── tests/
│ ├── run/ new/ programs + the exact output they must print (.out), input (.in) │ ├── run/ new/ programs + the exact output they must print (.out), input (.in)
│ ├── fail/ new/fail/ programs that must fail, with the expected error in line 1 │ ├── fail/ new/fail/ programs that must fail, with the expected error in line 1
@ -1061,12 +994,35 @@ Finch/
``` ```
```sh ```sh
tests/run.sh # programs, expected errors, -g builds, a shared library, the language server tests/run.sh # programs, refusals, -g builds, a shared library, the language server
MEMCHECK=1 tests/run.sh # the same under valgrind: no leaks, no bad memory access MEMCHECK=1 tests/run.sh # the same under valgrind: no leaks, no bad memory access
tests/cross.sh windows # every test built for Windows and run with Wine tests/cross.sh windows # every test built for Windows and run with Wine
tests/cross.sh arm64 # every test built for ARM64 Linux and run with QEMU tests/cross.sh arm64 # every test built for ARM64 Linux and run with QEMU
``` ```
### Variants that hold values
A variant can carry values. Make one with `Shape.Circle(2.0)`, take them out with `match`, which names them:
```c
enum Shape {
Circle(float)
Rect(float, float)
Point
}
fn Shape.area() -> float {
return match self {
Circle(r) -> 3.14 * r * r
Rect(w, h) -> w * h
Point -> 0.0
}
}
```
`_` ignores a value. `match` must handle every variant (or end with `else`). Printing shows `Rect(2, 3.5)`. Such an enum is
copied and freed like a struct; it can't be compared with `==` (match on it). Variants of these enums can't have numbers.
--- ---
## 23. Enums and match ## 23. Enums and match
@ -1113,29 +1069,6 @@ match word {
handle **every variant** or end with `else` (the compiler says which are missing); any other match needs `else`. handle **every variant** or end with `else` (the compiler says which are missing); any other match needs `else`.
In an expression, every arm gives the same type. In an expression, every arm gives the same type.
### Variants that hold values
A variant can carry values. Make one with `Shape.Circle(2.0)`, take them out with `match`, which names them:
```c
enum Shape {
Circle(float)
Rect(float, float)
Point
}
fn Shape.area() -> float {
return match self {
Circle(r) -> 3.14 * r * r
Rect(w, h) -> w * h
Point -> 0.0
}
}
```
`_` ignores a value. `match` must handle every variant (or end with `else`). Printing shows `Rect(2, 3.5)`. Such an enum is
copied and freed like a struct; it can't be compared with `==` (match on it). Variants of these enums can't have numbers.
## 24. Generics ## 24. Generics
Write a function or a struct once for any type, with the type as a name in square brackets: Write a function or a struct once for any type, with the type as a name in square brackets:
@ -1201,7 +1134,7 @@ Not there yet:
- Interfaces. - Interfaces.
- Unicode-aware text: `.len`, `s[i]` and `upper()` work on bytes / ASCII. - Unicode-aware text: `.len`, `s[i]` and `upper()` work on bytes / ASCII.
- Checks that stop two threads from changing the same array at once (use a mutex). - Checks that stop two threads from changing the same array at once (use a mutex).
- Windows on ARM64, 32-bit systems, WebAssembly. - Windows on ARM64, 32-bit systems, WebAssembly. `--target android` makes code for Android, but linking with the NDK and running on a device are untried.
### Warnings about big copies ### Warnings about big copies
@ -1260,7 +1193,6 @@ links = ["SDL3", "native/glue.cpp"] # libraries, and your own .c / .cpp / .o /
libdirs = ["vendor/SDL3/lib"] # folders with libraries libdirs = ["vendor/SDL3/lib"] # folders with libraries
cflags = ["-DFAST"] # for your .c / .cpp files cflags = ["-DFAST"] # for your .c / .cpp files
ldflags = ["-Wl,-rpath,$ORIGIN"] # for the linker ldflags = ["-Wl,-rpath,$ORIGIN"] # for the linker
cpu = "native" # the same as --cpu native (section 31)
[target.windows] # added only when building for Windows [target.windows] # added only when building for Windows
links = ["ws2_32"] links = ["ws2_32"]
@ -1360,195 +1292,6 @@ Write the header (`float path_len(float x, float y);`, or let `cbindgen` write i
**Android**: `finch build game.fch --target android --shared` makes `libgame.so` for 64-bit ARM Android (API 24 and **Android**: `finch build game.fch --target android --shared` makes `libgame.so` for 64-bit ARM Android (API 24 and
up) with the NDK's clang, found through `ANDROID_NDK_HOME` (or `FINCH_CC`). Load it from an app, for example through up) with the NDK's clang, found through `ANDROID_NDK_HOME` (or `FINCH_CC`). Load it from an app, for example through
SDL3's Android project (the library's `SDL_main` is an `export fn`). Thread-local data goes through the emulated TLS SDL3's Android project (the library's `SDL_main` is an `export fn`). This is new and only partly checked: Finch's
that older Android versions need. output compiles for Android (thread-local data goes through the emulated TLS older Android needs), but linking with the
NDK and running on a device have not been tried yet.
---
## 31. Vectors (SIMD)
A processor can add or multiply several numbers with one instruction: 4 floats at once with SSE, 8 with AVX2, 16 with
AVX-512, 4 with ARM's NEON. That is SIMD (single instruction, multiple data), and it is how image filters, audio, physics
and neural networks get their speed. In Finch it is a type: **`f32x8` is 8 `f32` side by side**.
```c
a := f32x4(1.0, 2.0, 3.0, 4.0)
b := f32x4(0.5) // 0.5 in every lane
print(a + b) // f32x4(1.5, 2.5, 3.5, 4.5)
print(a * 2.0) // a plain number counts for every lane: f32x4(2, 4, 6, 8)
print(a.sum(), a[2], a.len) // 10 3 4
```
**The types** are a number type, `x`, and how many lanes: `f32x4` `f32x8` `f32x16`, `f64x2` `f64x4` `f64x8`,
`i8x16` `i8x32` `i8x64`, `i16x8` `i16x16`, `i32x4` `i32x8` `i32x16`, `i64x2` `i64x4`, and the same with `u` (unsigned).
2 to 64 lanes, at most 512 bits in all. A vector is a plain value like an `int`: it goes into variables, struct fields,
arrays, maps and function parameters, and `print` and `str` show it.
**Making one:**
| | |
|---|---|
| `f32x8(x)` | `x` in every lane |
| `f32x8(a, b, c, d, e, f, g, h)` | one number per lane |
| `f32x8(list, i)` | `list[i]` … `list[i+7]` from a `[]f32` (or through a `ptr[f32]`); stops the program if they aren't all in the list |
| `f32x8(v)` | another 8-lane vector converted lane by lane: `f32x8(ints)`, `i32x8(floats)` (cuts the fractions), `i16x8(i32s)` |
**Working with it:**
| | |
|---|---|
| `+ - * /` | lane by lane (`/` on floats only: x86 and ARM can't divide whole numbers lane by lane) |
| `& \| ^ << >> ~` | lane by lane, on whole-number lanes |
| `-v` | every lane negated |
| `v[i]`, `v[i] = x`, `v.len` | one lane; the number of lanes |
| `min(v, w)`, `max(v, w)`, `abs(v)`, `clamp(v, lo, hi)` | lane by lane; `lo`, `hi` and `w` can be plain numbers |
| `v.sum()`, `v.min()`, `v.max()` | all the lanes made into one number |
| `v.dot(w)` | `(v * w).sum()` |
| `v.sqrt()`, `v.floor()`, `v.ceil()`, `v.round()` | every lane (float lanes) |
| `v.fma(b, c)` | `v * b + c` with one rounding, always (the same on every processor) |
| `v.muladd(b, c)` | `v * b + c` the fastest way here: one instruction where the processor can (the last bit may differ between processors) |
| `v.store(list, i)` | writes the lanes to `list[i]` … (or through a `ptr`) |
**A real loop.** The usual shape: as many whole vectors as fit, then the rest one by one.
```c
fn dot([]f32 a, []f32 b) -> f32 {
acc := f32x8(0.0)
i := 0
while i + 8 <= a.len {
acc = f32x8(a, i).muladd(f32x8(b, i), acc)
i += 8
}
total := acc.sum()
while i < a.len {
total += a[i] * b[i]
i += 1
}
return total
}
```
`examples/simd.fch` times this against the plain loop.
### Masks: comparing, choosing, the end of a list
Comparing vectors compares every lane, so the answer is a **mask**: one `true`/`false` a lane, of type `boolx8` (for 8 lanes).
```c
v := f32x8(-2.0, -1.0, 0.0, 1.0, 2.0, 3.0, -4.0, 5.0)
m := v > 0.0 // boolx8(false, false, false, true, true, true, false, true)
relu := select(m, v, 0.0) // v where the mask is true, 0 elsewhere
print(m.count(), m.any(), m.all()) // 4 true false
if (v == w).all() { ... } // a condition needs one bool: all() or any()
```
| | |
|---|---|
| `v < w`, `<=`, `>`, `>=`, `==`, `!=` | a mask (a plain number on one side counts for every lane) |
| `select(m, a, b)` | lane by lane: `a` where `m` is true, `b` where false (either may be a plain number) |
| `m.any()`, `m.all()`, `m.count()` | is any lane true, are all, how many |
| `m & n`, `m \| n`, `m ^ n`, `!m` | masks combined |
| `m[i]` | one lane, as a `bool` |
| `i32x8(m)`, `boolx8(v)` | true is 1; a lane that isn't 0 is true |
**The end of a list.** A list is rarely a whole number of vectors long. Two forms touch only the lanes that are inside it:
| | |
|---|---|
| `f32x8(list, i, fill)` | `list[i]` … as far as the list goes; the lanes past its end are `fill` |
| `v.store_part(list, i)` | writes only the lanes that fit in the list |
```c
fn add_one([]f32 xs) {
i := 0
while i < xs.len { // no separate loop for the rest
(f32x8(xs, i, 0.0) + 1.0).store_part(xs, i)
i += 8
}
}
```
### Moving lanes
| | |
|---|---|
| `v.shuffle(3, 2, 1, 0)` | the lanes with these numbers (written in the code), in this order; as many lanes as numbers |
| `v.lo()`, `v.hi()` | the lower and the upper half |
| `concat(a, b)` | `a`'s lanes, then `b`'s |
| `table.lookup(idx)` | for every lane of `idx`: `table[idx % table.len]`. A byte table (`u8x16`, `i8x16`) is one `pshufb` on x86, `tbl` on ARM |
Unpacking 4-bit numbers (two to a byte, as quantized models store them) is shuffle, mask and lookup:
```c
fn unpack4(u8x8 packed, i8x16 meaning) -> i8x16 {
pairs := packed.shuffle(0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7) // each byte twice
odd := u8x16(0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1) == 1
nibbles := select(odd, pairs >> 4, pairs & 15) // low nibble, then high
return meaning.lookup(nibbles) // 0..15 -> what it stands for
}
```
### Int8 dot products: dot_add
`acc.dot_add(a, b)` is the step of a quantized model's matrix multiply. `acc` is an `i32x8` of sums; `a` and `b` have four
times as many bytes (`u8x32` and `i8x32`; `i8` × `i8` and `u8` × `u8` work too). Each lane of `acc` gains the sum of four
products, `a[4k]*b[4k] + … + a[4k+3]*b[4k+3]`, exactly (no saturation), and the sums stay in the vector:
```c
acc := i32x8(0)
i := 0
while i + 32 <= weights.len {
acc = acc.dot_add(u8x32(activations, i), i8x32(weights, i))
i += 32
}
total := acc.sum() // once, at the end
```
It is one instruction where the processor has one: `vpdpbusd` with AVX-VNNI (Intel since Alder Lake, AMD Zen 5) or
AVX-512 VNNI, `usdot`/`sdot`/`udot` on ARM with the dot-product features. Elsewhere it is a few instructions
(`pmaddwd` and adds), with the same result. Which one is used depends on `--cpu` or the function's `@cpu` (below).
### Choosing the instructions: --cpu, @cpu and cpu_has
By default a program runs on every processor of its system (on x86-64 that means SSE2, so an `f32x8` is done as two
halves). Three ways to use more:
**`--cpu`, for the whole program.** `--cpu native` (or `cpu = "native"` in `finch.toml`) lets LLVM use everything the
processor that builds it has: AVX2 and FMA, AVX-512, VNNI, or NEON's newer parts on ARM. The program then needs a processor
like that one. To pick a level instead: `--cpu x86-64-v3` (AVX2, most PCs since 2015), `--cpu x86-64-v4` (AVX-512),
`--cpu alderlake`, `--cpu apple-m1`, `--cpu neoverse-v1`.
**`@cpu`, for one function, and `cpu_has` to choose at run time.** One program can carry an AVX-512 version, an AVX2
version and a plain one, and pick the best the computer it runs on has:
```c
@cpu("avx512f,avx512bw,avx512vl,avx512vnni")
fn matmul_avx512([]u8 a, []i8 b, []i32 out) { ... } // may use AVX-512 inside
@cpu("x86-64-v3")
fn matmul_avx2([]u8 a, []i8 b, []i32 out) { ... } // a level works too
fn matmul([]u8 a, []i8 b, []i32 out) {
if cpu_has("avx512vnni") && cpu_has("avx512vl") {
matmul_avx512(a, b, out)
} else if cpu_has("avx2") {
matmul_avx2(a, b, out)
} else {
matmul_plain(a, b, out)
}
}
```
- The features are x86's `sse3 ssse3 sse4.1 sse4.2 popcnt avx avx2 fma f16c bmi1 bmi2 avx512f avx512bw avx512dq avx512vl
avx512vnni avx512bf16 avx512fp16 avxvnni`, the levels `x86-64-v2`/`v3`/`v4`, and ARM's `neon fp16 dotprod i8mm bf16 sve sve2`.
- Names of the other architecture are left out when building, so the x86 and ARM versions sit side by side in one file
(and `cpu_has` says `false` for them).
- Call a `@cpu` function only after `cpu_has` said yes: on a processor without the feature it stops with *Illegal instruction*.
- A vector wider than 16 bytes (`f32x8`, `i32x16` …) can't be passed between functions with different `@cpu`: the two
sides would expect it in different registers. Pass the list (or a `ptr`) instead; Finch says so if you try.
Lanes wider than the processor's registers are split by LLVM, and the same code builds for x86-64 and ARM64 alike.
`v.sum()` on float lanes adds them in whatever order is fastest, so the last digits can differ from adding them one by one.
On NixOS, Nix's clang wrapper removes `-march=native` inside `nix-shell`. Finch tells the wrapper to keep it, so
`--cpu native` works there too; if it ever has no effect, Finch warns.

View file

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

View file

@ -1,8 +1,8 @@
# Finch dla początkujących # Finch dla początkujących
**Programowanie to dla ciebie coś zupełnie nowego? Ten przewodnik jest dla ciebie.** **Ten przewodnik jest dla ciebie, jeśli jeszcze nigdy nie pisałeś programu.**
Nie musisz nic umieć: wystarczy matematyka ze szkoły podstawowej. Nie potrzebujesz żadnego doświadczenia ani matematyki ponad to, czego uczy się w podstawówce.
Czytaj spokojnie, rozdział po rozdziale, i każdy przykład przepisuj sam: tak najlepiej zostaje w głowie. Czytaj powoli, po jednym rozdziale, i przepisuj każdy przykład samodzielnie. Tak zostaje w głowie najlepiej.
> 🇬🇧 English version: [for-beginners.md](../en/for-beginners.md) > 🇬🇧 English version: [for-beginners.md](../en/for-beginners.md)
@ -13,7 +13,7 @@ Czytaj spokojnie, rozdział po rozdziale, i każdy przykład przepisuj sam: tak
1. [Co to jest program?](#1-co-to-jest-program) 1. [Co to jest program?](#1-co-to-jest-program)
2. [Przygotowanie Fincha](#2-przygotowanie-fincha) 2. [Przygotowanie Fincha](#2-przygotowanie-fincha)
3. [Twój pierwszy program](#3-twój-pierwszy-program) 3. [Twój pierwszy program](#3-twój-pierwszy-program)
4. [Wypisywanie na ekranie](#4-wypisywanie-na-ekranie) 4. [Wypisywanie na ekran](#4-wypisywanie-na-ekran)
5. [Pudełka z nazwami: zmienne](#5-pudełka-z-nazwami-zmienne) 5. [Pudełka z nazwami: zmienne](#5-pudełka-z-nazwami-zmienne)
6. [Rodzaje wartości](#6-rodzaje-wartości) 6. [Rodzaje wartości](#6-rodzaje-wartości)
7. [Liczenie](#7-liczenie) 7. [Liczenie](#7-liczenie)
@ -28,35 +28,36 @@ Czytaj spokojnie, rozdział po rozdziale, i każdy przykład przepisuj sam: tak
16. [Kiedy coś pójdzie nie tak](#16-kiedy-coś-pójdzie-nie-tak) 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) 17. [Małe projekty do zrobienia](#17-małe-projekty-do-zrobienia)
18. [Ściąga](#18-ściąga) 18. [Ściąga](#18-ściąga)
19. [Słowa, które będziesz słyszeć](#19-słowa-które-będziesz-słyszeć) 19. [Słowniczek](#19-słowniczek)
--- ---
## 1. Co to jest program? ## 1. Co to jest program?
Komputer jest bardzo szybki, ale nie myśli. Robi dokładnie to, co mu każesz, krok po kroku. Komputer jest bardzo szybki, ale nie myśli. Robi dokładnie to, co mu się każe,
krok po kroku.
**Program** to spisana lista takich kroków. Trochę jak przepis: **Program** to spisana lista takich kroków. Trochę jak przepis kuchenny:
> 1. Weź dwa jajka. > 1. Weź dwa jajka.
> 2. Wbij je do miski. > 2. Wbij je do miski.
> 3. Wymieszaj. > 3. Wymieszaj.
**Język programowania** to sposób zapisania takiego przepisu tak, żeby komputer go zrozumiał. **Język programowania** to sposób zapisania takiego przepisu, który komputer rozumie.
**Finch** to jeden z takich języków. Powstał po to, żeby był mały i prosty, więc szybko się go nauczysz. **Finch** (po polsku: zięba) jest jednym z takich języków. Zrobiono go tak, żeby był
mały i prosty, więc nauczysz się go szybko.
Komputer czyta program od góry do dołu, linia po linii. Komputer czyta program od góry do dołu, linijka po linijce.
--- ---
## 2. Przygotowanie Fincha ## 2. Przygotowanie Fincha
Finch działa na komputerach z **Linuksem**, **macOS** i **Windowsem**. Jeśli nauczyciel, rodzic albo znajomy Finch działa na komputerach z **Linuksem**. Jeśli nauczyciel, rodzic albo znajomy już go
już go dla ciebie przygotował, przejdź do następnego rozdziału. (Na Macu i na Windowsie poproś kogoś o skorzystanie dla ciebie zainstalował, przejdź od razu do następnego rozdziału.
z [przewodnika dla techników](dla-technikow.md#2-instalacja): są tam gotowe wersje Fincha na oba systemy.)
W przeciwnym razie otwórz program **Terminal** (okno, w którym wpisuje się polecenia) i wpisz po kolei te linie, W przeciwnym razie otwórz program **Terminal** (okienko, w którym wpisuje się polecenia)
naciskając po każdej **Enter**: i wpisz te linijki po kolei, naciskając **Enter** po każdej:
```sh ```sh
git clone https://github.com/Nexoniarz/Finch.git git clone https://github.com/Nexoniarz/Finch.git
@ -65,24 +66,24 @@ nix-shell
bootstrap/build.sh bootstrap/build.sh
``` ```
Jeśli ostatnie polecenie skończy się bez słowa `error`, Finch jest gotowy. Jeśli ostatnia linijka skończy się bez słowa `error`, Finch jest gotowy.
(Jeśli na twoim komputerze nie ma `nix-shell`, poproś kogoś o skorzystanie z (Jeśli komputer nie zna polecenia `nix-shell`, poproś kogoś o skorzystanie z
[przewodnika dla techników](dla-technikow.md#2-instalacja). Jest tam opisany inny sposób.) [przewodnika dla techników](dla-technikow.md#2-instalacja), gdzie opisano inny sposób.)
Żeby sprawdzić, czy wszystko działa, wpisz: Żeby sprawdzić, czy działa, wpisz:
```sh ```sh
./build/finch version ./build/finch version
``` ```
Powinien pojawić się napis w rodzaju `Finch 2.6.0`. Powinieneś zobaczyć coś w stylu `finch 2.5.0`.
--- ---
## 3. Twój pierwszy program ## 3. Twój pierwszy program
1. Otwórz dowolny edytor tekstu (np. *Edytor tekstu*, *Kate*, *gedit* albo *VS Code*). 1. Otwórz dowolny edytor tekstu (na przykład *Edytor tekstu*, *Kate*, *gedit* albo *VS Code*).
2. Przepisz dokładnie to: 2. Wpisz dokładnie to:
```c ```c
fn main() { fn main() {
@ -90,11 +91,11 @@ fn main() {
} }
``` ```
3. Zapisz plik w katalogu `Finch` pod nazwą **`hello.fch`**. Końcówka `.fch` mówi, że to program w Finchu. 3. Zapisz plik w folderze `Finch` pod nazwą **`czesc.fch`**. Końcówka `.fch` mówi, że to program w Finchu.
4. W Terminalu wpisz: 4. W Terminalu wpisz:
```sh ```sh
./build/finch run hello.fch ./build/finch run czesc.fch
``` ```
Zobaczysz: Zobaczysz:
@ -103,7 +104,7 @@ Zobaczysz:
Cześć! Cześć!
``` ```
🎉 **Właśnie udało ci się napisać i uruchomić program.** 🎉 **Właśnie napisałeś i uruchomiłeś program.**
### Co właściwie napisaliśmy? ### Co właściwie napisaliśmy?
@ -113,26 +114,26 @@ fn main() { ← „tu zaczyna się główna część mojego programu
} ← „tu główna część się kończy” } ← „tu główna część się kończy”
``` ```
- `fn main()` to miejsce, w którym **zaczyna się** każdy program w Finchu. Każdy program ma dokładnie jedno `main`. - `fn main()` to miejsce, od którego **zaczyna się** każdy program w Finchu. Każdy program ma dokładnie jedno `main`.
- Nawiasy klamrowe `{` i `}` obejmują wszystko, co należy do `main`, jak dwie okładki książki. - Nawiasy klamrowe `{` i `}` obejmują wszystko, co należy do `main`, jak dwie okładki książki.
- `print(...)` pokazuje coś na ekranie. - `print(...)` pokazuje coś na ekranie.
- Tekst zawsze stoi w cudzysłowie: `"Cześć!"`. - Tekst zawsze piszemy w cudzysłowie: `"Cześć!"`.
Spacje przed `print` nie są obowiązkowe, ale dzięki nim od razu widać, co jest w środku nawiasów. Spacje przed `print` nie są obowiązkowe, ale dzięki nim od razu widać, co jest w środku
Zawsze je dodawaj. To dobry nawyk. klamer. Zawsze je dodawaj, to dobry nawyk.
--- ---
## 4. Wypisywanie na ekranie ## 4. Wypisywanie na ekran
`print` potrafi pokazać tekst i liczby. Oddziel je przecinkami, a Finch sam wstawi między nimi spacje: `print` umie pokazać tekst i liczby. Między kolejne rzeczy wstaw przecinek, a Finch sam doda spację:
```c ```c
fn main() { fn main() {
print("Mam", 10, "lat") print("Mam", 10, "lat")
print("2 + 2 =", 2 + 2) print("2 + 2 =", 2 + 2)
print() print()
print("Linia wyżej jest pusta") print("Linijka wyżej jest pusta")
} }
``` ```
@ -142,10 +143,10 @@ Pokaże:
Mam 10 lat Mam 10 lat
2 + 2 = 4 2 + 2 = 4
Linia wyżej jest pusta Linijka wyżej jest pusta
``` ```
Każde `print` zaczyna nową linię. `print()` bez niczego w środku robi pustą linię. Każdy `print` zaczyna nową linijkę. `print()` bez niczego w środku robi pustą linijkę.
### Notatki dla siebie: komentarze ### Notatki dla siebie: komentarze
@ -162,8 +163,8 @@ fn main() {
## 5. Pudełka z nazwami: zmienne ## 5. Pudełka z nazwami: zmienne
Wyobraź sobie pudełko z naklejoną etykietą. Możesz coś do niego włożyć, a później sprawdzić, co jest w środku, Wyobraź sobie pudełko z naklejoną etykietą. Możesz coś do niego włożyć, a potem sprawdzić,
czytając etykietę. W programowaniu takie pudełko nazywa się **zmienną**. co jest w środku, czytając etykietę. W programowaniu takie pudełko nazywa się **zmienną**.
```c ```c
fn main() { fn main() {
@ -172,9 +173,9 @@ fn main() {
} }
``` ```
`wiek := 10` znaczy: *„zrób nowe pudełko, nazwij je `wiek` i włóż do niego 10”.* `wiek := 10` znaczy: *„zrób nowe pudełko, nazwij je `wiek` i włóż do niego 10”*.
Później możesz zmienić zawartość pudełka, pisząc samo `=`: Później możesz zmienić zawartość pudełka, używając samego `=`:
```c ```c
fn main() { fn main() {
@ -195,39 +196,42 @@ Punkty: 5
Punkty: 6 Punkty: 6
``` ```
`punkty = punkty + 1` czyta się tak: *„weź to, co jest w `punkty`, dodaj 1 i włóż wynik z powrotem do `punkty`”.* `punkty = punkty + 1` czytamy tak: *„weź to, co jest w `punkty`, dodaj 1 i włóż wynik z powrotem do `punkty`”*.
To samo można napisać krócej: `punkty += 1`. To samo można zapisać krócej: `punkty += 1`.
**Zapamiętaj:** **Zapamiętaj:**
- `:=` robi **nowe** pudełko. Dla każdej nazwy używasz tego tylko raz. - `:=` robi **nowe** pudełko. Używasz tego tylko raz dla danej nazwy.
- `=` wkłada nową wartość do pudełka, **które już istnieje**. - `=` wkłada nową wartość do pudełka, **które już istnieje**.
### Nazywanie pudełek ### Nazywanie pudełek
W nazwach można używać liter od `a` do `z` i od `A` do `Z`, cyfr i znaku `_`, ale nazwa nie może zaczynać się od cyfry: Nazwy mogą zawierać angielskie litery `a`–`z` i `A`–`Z`, cyfry i znak `_`, ale nie mogą zaczynać się od cyfry:
`wiek`, `suma_punktow`, `gracz2` są w porządku, a `2gracz` nie. `wiek`, `suma_punktow`, `gracz2` są w porządku. `2gracz` już nie.
Polskich liter (`ą`, `ę`, `ż`…) nie wolno używać w nazwach, tylko w tekstach w cudzysłowie: `print("Łódź")` jest w porządku.
⚠️ **Bez polskich liter w nazwach!** `ą`, `ę`, `ł`, `ż` i inne mogą być tylko w tekście w cudzysłowie.
Pisz `zolw`, a nie `żółw`. Natomiast `print("Żółw")` działa bez problemu.
Wybieraj nazwy, które mówią, co jest w środku: `cena` jest lepsza niż `c`. Wybieraj nazwy, które mówią, co jest w środku: `cena` jest lepsza niż `c`.
--- ---
## 6. Rodzaje wartości ## 6. Rodzaje wartości
Finch pilnuje, jaki *rodzaj* rzeczy leży w każdym pudełku. Taki rodzaj to **typ**. Finch pilnuje, *jakiego rodzaju* rzecz jest w każdym pudełku. Ten rodzaj nazywa się **typem**.
Najczęściej będziesz używać tych: Najczęściej będziesz używać tych:
| Typ | Co przechowuje | Przykłady | | Typ | Co przechowuje | Przykłady |
|---------|-----------------------------|--------------------------| |---------|---------------------------|--------------------------|
| `int` | liczby całkowite | `0`, `42`, `-7` | | `int` | liczby całkowite | `0`, `42`, `-7` |
| `float` | liczby z przecinkiem | `3.14`, `0.5`, `-2.0` | | `float` | liczby z przecinkiem | `3.14`, `0.5`, `-2.0` |
| `str` | tekst | `"Cześć"`, `"Ola"` | | `str` | tekst | `"Cześć"`, `"Ola"` |
| `bool` | tak albo nie | `true`, `false` | | `bool` | tak albo nie | `true` (prawda), `false` (fałsz) |
| `char` | jedną literę albo znak | `'A'`, `'z'`, `'?'` | | `char` | jeden znak | `'A'`, `'z'`, `'?'` |
Uwaga: w programach część ułamkową oddziela się **kropką**, nie przecinkiem: `3.14`, a nie `3,14`. ⚠️ W liczbach zamiast przecinka piszemy **kropkę**: `3.14`, a nie `3,14`. Tak jest w prawie wszystkich językach programowania.
Przy `:=` Finch sam rozpoznaje typ: Kiedy używasz `:=`, Finch sam rozpoznaje typ:
```c ```c
imie := "Ola" // str imie := "Ola" // str
@ -236,7 +240,7 @@ wzrost := 1.42 // float
lubi_koty := true // bool lubi_koty := true // bool
``` ```
Typ można też napisać samemu, przed nazwą: Typ możesz też napisać sam, przed nazwą:
```c ```c
int wiek = 10 int wiek = 10
@ -245,23 +249,24 @@ str imie = "Ola"
Oba sposoby robią to samo. Wybierz ten, który ci bardziej pasuje. Oba sposoby robią to samo. Wybierz ten, który ci bardziej pasuje.
**Pudełko trzyma się swojego typu.** Jeśli w `wiek` są liczby całkowite, nie włożysz tam potem tekstu. **Pudełko nie zmienia typu.** Jeśli w `wiek` są liczby całkowite, nie włożysz tam potem tekstu.
Finch się zatrzyma i ci o tym powie, a to pomaga wcześnie wyłapywać pomyłki. Finch zatrzyma się i powie o tym, co pomaga wcześnie wyłapywać pomyłki.
--- ---
## 7. Liczenie ## 7. Liczenie
| Znak | Znaczenie | Przykład | Wynik | | Znak | Co robi | Przykład | Wynik |
|------|--------------------------------|----------|--------| |------|-----------------------------|----------|-------|
| `+` | dodawanie | `7 + 2` | `9` | | `+` | dodawanie | `7 + 2` | `9` |
| `-` | odejmowanie | `7 - 2` | `5` | | `-` | odejmowanie | `7 - 2` | `5` |
| `*` | mnożenie | `7 * 2` | `14` | | `*` | mnożenie | `7 * 2` | `14` |
| `/` | dzielenie | `7 / 2` | `3` | | `/` | dzielenie | `7 / 2` | `3` |
| `%` | reszta z dzielenia | `7 % 2` | `1` | | `%` | reszta z dzielenia | `7 % 2` | `1` |
**Uważaj przy dzieleniu liczb całkowitych:** `7 / 2` daje `3`, a nie `3.5`, bo obie liczby są całkowite, więc wynik **Uwaga na dzielenie liczb całkowitych:** `7 / 2` daje `3`, a nie `3.5`. Obie liczby są
też jest całkowity (część po kropce znika). Jeśli chcesz `3.5`, użyj liczby z kropką: `7.0 / 2`. całkowite, więc wynik też jest całkowity (część po kropce jest odrzucana).
Jeśli chcesz dostać `3.5`, użyj liczby z kropką: `7.0 / 2`.
Mnożenie i dzielenie wykonują się przed dodawaniem i odejmowaniem, tak jak w szkole. Mnożenie i dzielenie wykonują się przed dodawaniem i odejmowaniem, tak jak w szkole.
Nawiasy zmieniają kolejność: Nawiasy zmieniają kolejność:
@ -271,28 +276,28 @@ print(2 + 3 * 4) // 14
print((2 + 3) * 4) // 20 print((2 + 3) * 4) // 20
``` ```
`%` przydaje się do sprawdzania, czy liczba jest parzysta: liczba parzysta dzielona przez 2 nie daje reszty, `%` przydaje się do sprawdzania, czy liczba jest parzysta. Liczba parzysta dzielona przez 2
więc `n % 2` wynosi `0`. nie daje reszty, więc `n % 2` wynosi `0`.
**Nie wolno dzielić przez zero.** Jeśli program spróbuje, zatrzyma się i powie, w której linii to się stało. **Nie wolno dzielić przez zero.** Jeśli program spróbuje, zatrzyma się i powie, w której linijce to się stało.
--- ---
## 8. Podejmowanie decyzji: if ## 8. Podejmowanie decyzji: if
Program może wybierać, co zrobić. `if` znaczy *„zrób to tylko wtedy, gdy coś jest prawdą”*. Program może wybierać, co zrobić. `if` (czyt. „if”, po polsku „jeżeli”) znaczy *„zrób to tylko wtedy, gdy coś jest prawdą”*.
```c ```c
fn main() { fn main() {
temperatura := 25 temperatura := 25
if temperatura > 20 { if temperatura > 20 {
print("Jest ciepło, włóż koszulkę") print("Jest ciepło, załóż koszulkę")
} }
} }
``` ```
Dopisz `else`, żeby powiedzieć *„a jeśli nie, zrób to”*: Dodaj `else` („w przeciwnym razie”):
```c ```c
if temperatura > 20 { if temperatura > 20 {
@ -302,11 +307,12 @@ if temperatura > 20 {
} }
``` ```
A `else if` daje więcej możliwości. Finch sprawdza je od góry do dołu i wykonuje tylko **pierwszą**, która jest prawdą: A `else if` dodaje kolejne możliwości. Finch sprawdza je od góry i wykonuje tylko
**pierwszą** prawdziwą:
```c ```c
if temperatura > 25 { if temperatura > 25 {
print("Upał!") print("Gorąco!")
} else if temperatura > 15 { } else if temperatura > 15 {
print("Przyjemnie") print("Przyjemnie")
} else { } else {
@ -322,18 +328,18 @@ if temperatura > 25 {
| `!=` | nie jest równe | | `!=` | nie jest równe |
| `<` | jest mniejsze niż | | `<` | jest mniejsze niż |
| `>` | jest większe niż | | `>` | jest większe niż |
| `<=` | jest mniejsze albo równe | | `<=` | jest mniejsze lub równe |
| `>=` | jest większe albo równe | | `>=` | jest większe lub równe |
⚠️ **Jedno `=` wkłada wartość do pudełka. Dwa `==` pytają „czy to jest równe?”.** ⚠️ **Jedno `=` wkłada wartość do pudełka. Dwa `==` pytają „czy to jest równe?”.**
Porównywać można też teksty: `if imie == "Ola" { ... }`. Tekst też można porównywać: `if imie == "Ola" { ... }`.
### Łączenie pytań ### Łączenie pytań
- `&&` znaczy **i**: oba warunki muszą być prawdziwe. `if wiek > 6 && wiek < 12 { ... }` - `&&` znaczy **i**: oba warunki muszą być prawdziwe. `if wiek > 6 && wiek < 12 { ... }`
- `||` znaczy **lub**: przynajmniej jeden musi być prawdziwy. `if dzien == "sob" || dzien == "niedz" { ... }` - `||` znaczy **lub**: wystarczy, że jeden jest prawdziwy. `if dzien == "sob" || dzien == "nd" { ... }`
- `!` znaczy **nie**: zamienia prawdę w fałsz i odwrotnie. `if !pada { ... }` - `!` znaczy **nie**: odwraca prawdę i fałsz. `if !pada { ... }`
--- ---
@ -359,7 +365,7 @@ Liczba 4
Liczba 5 Liczba 5
``` ```
`for i in 1..6` znaczy: *„licz w `i` od 1 do 6, ale **bez** 6”*. `for i in 1..6` znaczy: *„licz za pomocą `i` od 1 do 6, ale **bez** 6”*.
Czyli `0..10` daje dziesięć liczb: 0, 1, 2 … 9. Czyli `0..10` daje dziesięć liczb: 0, 1, 2 … 9.
### Powtarzanie, dopóki coś jest prawdą: `while` ### Powtarzanie, dopóki coś jest prawdą: `while`
@ -382,8 +388,8 @@ fn main() {
Start! Start!
``` ```
⚠️ Upewnij się, że pętla może się skończyć. Gdyby `licznik` nigdy nie malał, pętla kręciłaby się bez końca. ⚠️ Upewnij się, że pętla może się skończyć. Gdyby `licznik` nigdy nie malał, pętla kręciłaby się
Jeśli tak się stanie, naciśnij w Terminalu **Ctrl + C**, żeby ją zatrzymać. w nieskończoność. Jeśli tak się stanie, naciśnij w Terminalu **Ctrl + C**, żeby ją zatrzymać.
### Wcześniejsze wyjście i pomijanie ### Wcześniejsze wyjście i pomijanie
@ -393,9 +399,9 @@ Jeśli tak się stanie, naciśnij w Terminalu **Ctrl + C**, żeby ją zatrzymać
```c ```c
for i in 1..100 { for i in 1..100 {
if i == 5 { if i == 5 {
break // zatrzymaj się, gdy dojdziemy do 5 break // stop, gdy dojdziemy do 5
} }
print(i) // wypisze 1 2 3 4 print(i) // pokaże 1 2 3 4
} }
``` ```
@ -403,7 +409,7 @@ for i in 1..100 {
## 10. Własne polecenia: funkcje ## 10. Własne polecenia: funkcje
Kiedy robisz to samo w wielu miejscach, nadaj temu nazwę. Taki nazwany blok to **funkcja**. Kiedy robisz to samo w wielu miejscach, nadaj temu nazwę. Taki nazwany kawałek kodu to **funkcja**.
Jedną już znasz: `main`. Jedną już znasz: `main`.
```c ```c
@ -422,12 +428,12 @@ Cześć, Ola
Cześć, Tomek Cześć, Tomek
``` ```
`str imie` w nawiasach znaczy: *„ta funkcja potrzebuje jednego tekstu i w środku będziemy go nazywać `imie`”.* `str imie` w nawiasie znaczy: *„ta funkcja potrzebuje jednego tekstu, a w środku nazywamy go `imie`”*.
To, co wpiszesz w nawiasach przy jej użyciu, trafia do środka. To, co wpiszesz w nawias przy wywołaniu, zostanie przekazane do środka.
### Funkcje, które dają odpowiedź ### Funkcje, które oddają odpowiedź
Napisz `->` i typ odpowiedzi, a odpowiedź oddaj przez `return`: Napisz `->` i typ odpowiedzi, a odpowiedź oddaj słowem `return`:
```c ```c
fn dodaj(int a, int b) -> int { fn dodaj(int a, int b) -> int {
@ -440,14 +446,14 @@ fn main() {
} }
``` ```
Nie ma znaczenia, gdzie w pliku umieścisz swoje funkcje, nad `main` czy pod nim. Nie ma znaczenia, gdzie w pliku umieścisz swoje funkcje, nad czy pod `main`.
--- ---
## 11. Listy: wiele wartości w jednym pudełku ## 11. Listy: wiele wartości w jednym pudełku
Czasem jedno pudełko nie wystarcza. Na liście zakupów jest wiele rzeczy. Czasem jedno pudełko nie wystarczy. Na liście zakupów jest wiele rzeczy.
**Lista** (programiści mówią też **tablica**) przechowuje wiele wartości po kolei: **Lista** (programiści mówią też **tablica**) trzyma wiele wartości po kolei:
```c ```c
fn main() { fn main() {
@ -464,34 +470,34 @@ Mam 3 owoce
Pierwszy to jabłko Pierwszy to jabłko
``` ```
- Nawiasy kwadratowe `[ ]` tworzą listę. Wartości oddziela się przecinkami. - Nawiasy kwadratowe `[ ]` tworzą listę. Wartości oddzielamy przecinkami.
- `.len` mówi, ile rzeczy jest na liście. - `.len` mówi, ile rzeczy jest na liście.
- `owoce[0]` to **pierwsza** rzecz. Liczy się od **0**, więc `owoce[1]` to druga, a `owoce[2]` trzecia. - `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: 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)` (indeks 10 jest poza listą, która ma długość 3). `index 10 is out of range (the length is 3)`, czyli „indeks 10 jest poza zakresem (długość to 3)”.
### Dodawanie i zabieranie ### Dodawanie i zabieranie
```c ```c
fn main() { fn main() {
[]int wyniki // pusta lista liczb całkowitych []int wyniki // pusta lista liczb całkowitych
wyniki.push(10) // dodaj 10 na końcu wyniki.push(10) // dołóż 10 na koniec
wyniki.push(25) wyniki.push(25)
wyniki.push(7) wyniki.push(7)
print(wyniki) // [10, 25, 7] print(wyniki) // [10, 25, 7]
ostatni := wyniki.pop() // zdejmij ostatni ostatni := wyniki.pop() // zabierz ostatni
print(ostatni, wyniki) // 7 [10, 25] print(ostatni, wyniki) // 7 [10, 25]
wyniki.sort() wyniki.sort() // posortuj
print(wyniki) print(wyniki)
} }
``` ```
### Przechodzenie po liście ### Przechodzenie po liście
`for ... in` odwiedza po kolei każdą wartość: `for ... in` odwiedza każdą wartość po kolei:
```c ```c
fn main() { fn main() {
@ -503,8 +509,8 @@ fn main() {
} }
``` ```
Jeśli chcesz też wiedzieć, *w którym miejscu* listy jesteś, podaj w `for` dwie nazwy: pierwsza liczy 0, 1, 2…, Jeśli chcesz też wiedzieć, *w którym miejscu* listy jesteś, daj `for` dwie nazwy: pierwsza liczy
a druga to wartość: 0, 1, 2…, druga to wartość:
```c ```c
fn main() { fn main() {
@ -520,20 +526,21 @@ fn main() {
3 wiśnia 3 wiśnia
``` ```
Listy potrafią więcej: `contains`, `find`, `insert`, `remove`, `sort`, `reverse`, `join`. Listy umieją więcej: `contains` (czy zawiera), `find` (znajdź), `insert` (wstaw), `remove` (usuń),
Wszystkie są opisane w [przewodniku dla techników](dla-technikow.md#10-tablice-i-mapy). `sort` (sortuj), `reverse` (odwróć), `join` (połącz). Wszystkie opisuje [przewodnik dla techników](dla-technikow.md#10-tablice-i-mapy).
### Szukanie po nazwie: mapy ### Szukanie po nazwie: mapy
Lista znajduje rzeczy po miejscu: `owoce[0]`. Czasem jednak chcesz szukać po **nazwie**, tak jak szuka się słowa Lista znajduje rzeczy po miejscu: `owoce[0]`. Czasem chcesz znaleźć coś po **nazwie**, tak jak
w słowniku albo numeru w książce telefonicznej. Do tego służy **mapa**: każdy **klucz** (nazwa) ma swoją **wartość**: słowo w słowniku albo numer w książce telefonicznej. Do tego służy **mapa** (`map`): każdy
**klucz** (nazwa) ma swoją **wartość**:
```c ```c
fn main() { fn main() {
telefon := ["Ola": "555-1234", "Jan": "555-9876"] telefon := ["Ola": "555-1234", "Jan": "555-9876"]
print(telefon["Ola"]) // 555-1234 print(telefon["Ola"]) // 555-1234
telefon["Ewa"] = "555-0000" // dodaj nowe imię telefon["Ewa"] = "555-0000" // nowa osoba
print(telefon.len) // 3 print(telefon.len) // 3
if telefon.has("Tomek") { if telefon.has("Tomek") {
@ -542,26 +549,26 @@ fn main() {
print("nie ma Tomka") print("nie ma Tomka")
} }
for imie, numer in telefon { // każde imię razem z numerem for imie, numer in telefon { // każde imię z jego numerem
print(imie, numer) print(imie, numer)
} }
} }
``` ```
- `["klucz": wartość, ...]` tworzy mapę. Kluczami są często teksty, ale mogą być też liczby. - `["klucz": wartość, ...]` tworzy mapę. Klucze to zwykle tekst, ale mogą być też liczby.
- `telefon["Ola"]` daje wartość dla klucza. Pytanie o klucz, którego nie ma, zatrzymuje program, - `telefon["Ola"]` daje wartość dla klucza. Pytanie o klucz, którego nie ma, zatrzymuje program,
więc najpierw sprawdź `telefon.has("Tomek")` albo użyj `telefon.get("Tomek", "nieznany")`. więc najpierw sprawdź `telefon.has("Tomek")` (czy ma) albo użyj `telefon.get("Tomek", "nie wiem")`.
- `for imie, numer in telefon` odwiedza każdy klucz z jego wartością, w kolejności, w jakiej je dodano. - `for imie, numer in telefon` odwiedza każdy klucz z wartością, w kolejności dodawania.
Mapy świetnie nadają się do **liczenia**. Nowy klucz zaczyna od 0, więc `+= 1` po prostu działa: Mapy świetnie nadają się do **liczenia**. Nowy klucz zaczyna od 0, więc `+= 1` po prostu działa:
```c ```c
fn main() { fn main() {
map[str]int glosy // pusta mapa: klucze to teksty, wartości to liczby całkowite map[str]int glosy // pusta mapa: klucze to tekst, wartości to liczby
for g in ["kot", "pies", "kot", "kot", "rybka"] { for g in ["kot", "pies", "kot", "kot", "rybka"] {
glosy[g] += 1 glosy[g] += 1
} }
print(glosy) // {"kot": 3, "pies": 1, "rybka": 1} print(glosy) // {"kot": 3, "pies": 1, "rybka": 1}
} }
``` ```
@ -569,7 +576,7 @@ fn main() {
## 12. Zabawa tekstem ## 12. Zabawa tekstem
Teksty można sklejać przez `+`: Teksty sklejasz znakiem `+`:
```c ```c
fn main() { fn main() {
@ -593,16 +600,16 @@ fn main() {
slowo := "Finch" slowo := "Finch"
print(slowo.len) // 5 liter print(slowo.len) // 5 liter
print(slowo[0]) // F (pierwsza litera) print(slowo[0]) // F (pierwsza litera)
print(slowo.upper()) // FINCH print(slowo.upper()) // FINCH (wielkimi literami)
print(slowo.lower()) // finch print(slowo.lower()) // finch (małymi literami)
print(slowo.contains("in")) // true print(slowo.contains("in")) // true (czy zawiera "in")
for litera in slowo { for litera in slowo {
print(litera) // F, i, n, c, h, każda w osobnej linii print(litera) // F, i, n, c, h, każda w osobnej linijce
} }
} }
``` ```
(`.len` liczy bajty, a polskie litery zajmują po dwa, więc `"żółw".len` to 7, a nie 4.) ⚠️ `.len` liczy bajty, a polskie litery (ą, ę, ż…) zajmują po 2 bajty. `"żółw".len` to 7, a nie 4.
--- ---
@ -627,15 +634,15 @@ fn main() {
} }
``` ```
Jeśli ktoś wpisze coś, co nie jest liczbą, program się zatrzyma z komunikatem Jeśli ktoś wpisze coś, co nie jest liczbą, program zatrzyma się z komunikatem
w rodzaju `can't turn "abc" into int` (nie da się zamienić "abc" na liczbę). w stylu `can't turn "abc" into int` („nie da się zamienić "abc" na int”).
--- ---
## 14. Własne rodzaje wartości: struktury ## 14. Własne rodzaje wartości: struktury
Pomyśl o karcie w bibliotece: każda ma tytuł, autora i rok wydania. Wyobraź sobie kartę w bibliotece: każda karta ma tytuł, autora i rok.
**Struktura** pozwala stworzyć własny rodzaj wartości z nazwanymi częściami: **Struktura** (`struct`) pozwala zrobić własny rodzaj wartości z nazwanymi częściami:
```c ```c
struct Ksiazka { struct Ksiazka {
@ -657,11 +664,11 @@ Hobbit napisał Tolkien
Ksiazka(tytul: "Hobbit", autor: "Tolkien", rok: 1938) Ksiazka(tytul: "Hobbit", autor: "Tolkien", rok: 1938)
``` ```
- `struct Ksiazka { ... }` opisuje, co ma każda książka. Umieść to **poza** `main`. - `struct Ksiazka { ... }` opisuje, co ma każda książka. Pisz to **poza** `main`.
- `Ksiazka(tytul: ..., autor: ..., rok: ...)` tworzy jedną książkę. - `Ksiazka(tytul: ..., autor: ..., rok: ...)` tworzy jedną książkę.
- `k.tytul` odczytuje jedną część. `k.rok = 1938` ją zmienia. - `k.tytul` odczytuje jedną część, a `k.rok = 1938` ją zmienia.
Częściom można nadać wartość początkową. Wtedy nie trzeba ich wypełniać: Częściom możesz nadać wartość początkową. Wtedy nie trzeba ich podawać:
```c ```c
struct Gracz { struct Gracz {
@ -675,12 +682,12 @@ fn main() {
} }
``` ```
Wiele takich wartości można trzymać na liście: `[]Ksiazka polka`, a potem `polka.push(k)`. Możesz też trzymać wiele struktur na liście: `[]Ksiazka polka`, a potem `polka.push(k)`.
### Polecenia dla struktury: metody ### Polecenia twojej struktury: metody
Struktura może mieć własne polecenia. Napisz nazwę struktury, kropkę i nazwę polecenia. Struktura może mieć własne polecenia (**metody**). Napisz nazwę struktury, kropkę i nazwę polecenia.
W środku `self` to struktura, na której polecenie użyto: W środku `self` (ang. „sam”) to struktura, na której użyto polecenia:
```c ```c
struct Gracz { struct Gracz {
@ -690,7 +697,7 @@ struct Gracz {
fn Gracz.trafiony() { fn Gracz.trafiony() {
self.zycia -= 1 self.zycia -= 1
print(self.imie, "- pozostałe życia:", self.zycia) print(self.imie, "ma jeszcze", self.zycia, "życia")
} }
fn Gracz.zyje() -> bool { fn Gracz.zyje() -> bool {
@ -699,8 +706,8 @@ fn Gracz.zyje() -> bool {
fn main() { fn main() {
g := Gracz(imie: "Ola") g := Gracz(imie: "Ola")
g.trafiony() // Ola - pozostałe życia: 2 g.trafiony() // Ola ma jeszcze 2 życia
g.trafiony() // Ola - pozostałe życia: 1 g.trafiony() // Ola ma jeszcze 1 życia
print(g.zyje()) // true print(g.zyje()) // true
} }
``` ```
@ -724,14 +731,14 @@ fn main() {
- `read_file(nazwa)` oddaje tekst z pliku. - `read_file(nazwa)` oddaje tekst z pliku.
- `file_exists(nazwa)` mówi, czy plik istnieje (`true` albo `false`). - `file_exists(nazwa)` mówi, czy plik istnieje (`true` albo `false`).
Plik pojawi się w katalogu, w którym uruchomiono program. Plik pojawi się w folderze, w którym uruchomiłeś program.
--- ---
## 16. Kiedy coś pójdzie nie tak ## 16. Kiedy coś pójdzie nie tak
Każdy, kto programuje, robi błędy, nawet eksperci, codziennie. Każdy się myli przy programowaniu, nawet eksperci, codziennie.
Finch bardzo się stara wyjaśnić prostymi słowami, co poszło nie tak. Przykład: Finch stara się tłumaczyć, co poszło nie tak, prostymi słowami (na razie po angielsku). Przykład:
```c ```c
fn main() { fn main() {
@ -741,67 +748,68 @@ fn main() {
``` ```
``` ```
hello.fch:3:11: error: there is no variable named 'wiekk' czesc.fch:3:11: error: there is no variable named 'wiekk'
3 | print(wiekk) 3 | print(wiekk)
| ^ | ^
``` ```
Jak to czytać: Jak to czytać:
- `hello.fch` to plik, - `czesc.fch` to plik,
- `3` to numer **linii**, - `3` to numer **linijki**,
- `11` mówi, jak daleko w linii (to **kolumna**), - `11` to który znak w linijce (**kolumna**),
- dalej jest wyjaśnienie, a pod nim ta linia ze znakiem `^`, który wskazuje problem. - potem wyjaśnienie, a pod nim ta linijka ze strzałką `^` wskazującą problem.
Komunikaty są po angielsku; tutaj znaczy to „nie ma zmiennej o nazwie 'wiekk'”. Po prostu literówka w `wiek`. Tutaj po prostu źle napisaliśmy `wiek`. Komunikat znaczy: „nie ma zmiennej o nazwie 'wiekk'”.
### Częste pomyłki ### Częste błędy
| Finch mówi | Co to znaczy | Jak naprawić | | Finch mówi | Co to znaczy | Jak naprawić |
|---|---|---| |---|---|---|
| `there is no variable named 'x'` | Literówka w nazwie albo takie pudełko nigdy nie powstało | Sprawdź pisownię. Utwórz je przez `x := ...` | | `there is no variable named 'x'` | Nie ma zmiennej `x`: literówka albo zapomniałeś ją zrobić | Sprawdź pisownię. Utwórz ją przez `x := ...` |
| `a variable named 'x' already exists here` | Użyto `:=` dwa razy dla tej samej nazwy | Za drugim razem napisz `=` | | `a variable named 'x' already exists here` | Użyłeś `:=` dwa razy dla tej samej nazwy | Za drugim razem użyj `=` |
| `'x' must be int, but this is str` | Zły rodzaj wartości dla tego pudełka | Włóż wartość właściwego rodzaju | | `'x' must be int, but this is str` | Zły rodzaj wartości dla tego pudełka | Włóż wartość właściwego rodzaju |
| `put each statement on its own line` | Dwa polecenia w jednej linii | Rozdziel je Enterem | | `put each statement on its own line` | Dwa polecenia w jednej linijce | Wciśnij Enter między nimi |
| `this '{' is never closed` | Brakuje `}` | Dopisz `}` | | `this '{' is never closed` | Brakuje `}` | Dopisz `}` |
| `a condition must be bool` | `if` potrzebuje pytania typu tak/nie | Porównaj coś: `if x > 0` | | `a condition must be bool` | `if` potrzebuje pytania tak/nie | Porównaj coś: `if x > 0` |
| `runtime error: division by zero` | W trakcie działania programu nastąpiło dzielenie przez 0 | Sprawdź liczbę przed dzieleniem | | `unexpected character 'ż'` | Polska litera w nazwie | Zmień na zwykłą literę: `zolw` |
| `index 5 is out of range (the length is 3)` | Pytanie listy o miejsce, którego nie ma | Pamiętaj, że liczy się od 0; sprawdź `.len` | | `runtime error: division by zero` | Program podzielił przez 0 w trakcie działania | Sprawdź liczbę przed dzieleniem |
| `can't turn "abc" into int` | `int(...)` dostało tekst, który nie jest liczbą | Sprawdź, co wpisał użytkownik | | `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 use '+' on str and int` | Tekst plus liczba | Użyj `str(...)` na liczbie | | `can't turn "abc" into int` | `int(...)` dostał tekst, który nie jest liczbą | Sprawdź, co wpisał użytkownik |
| `'f' can fail, so say what happens then` | Użyto polecenia, które może się nie udać, bez powiedzenia, co wtedy zrobić | Dopisz `or ...` (zob. niżej) | | `can't use '+' on str and int` | Tekst plus liczba | Zamień liczbę na tekst przez `str(...)` |
| `'f' can fail, so say what happens then` | Użyłeś polecenia, które może się nie udać, i nie napisałeś, co wtedy | Dopisz `or ...` (niżej) |
### Rzeczy, które mogą się nie udać: `or` i `try` ### Rzeczy, które mogą się nie udać: `or` i `try`
Niektóre rzeczy mogą się nie udać *w trakcie działania programu*: ktoś wpisze `abc` tam, gdzie miała być liczba, Niektóre rzeczy mogą pójść źle *w trakcie działania programu*: ktoś wpisze `abc` zamiast liczby
albo nie ma pliku. Zamiast zatrzymywać program, możesz przez **`or`** powiedzieć, co wtedy zrobić: albo pliku nie ma. Zamiast zatrzymywać program, możesz powiedzieć, co wtedy zrobić, słowem **`or`** („albo”):
```c ```c
fn main() { fn main() {
wiek := int(input("Ile masz lat? ")) or 0 // to nie liczba? weź 0 wiek := int(input("Ile masz lat? ")) or 0 // to nie liczba? weź 0
print("Za rok będziesz mieć", wiek + 1) print("Za rok będziesz mieć", wiek + 1)
notatka := read_file("notatka.txt") or "(jeszcze nie ma notatki)" notatka := read_file("notatka.txt") or "(jeszcze brak notatki)"
print(notatka) print(notatka)
} }
``` ```
Po `or` może też stać blok. W jego środku `err` to komunikat, który mówi, co poszło nie tak: Po `or` może też być blok. W środku `err` to komunikat, co poszło nie tak:
```c ```c
fn main() { fn main() {
while true { while true {
n := int(input("Wybierz liczbę: ")) or { n := int(input("Podaj liczbę: ")) or {
print("To nie jest liczba, spróbuj jeszcze raz") print("To nie jest liczba, spróbuj jeszcze raz")
continue continue
} }
print("Wybrana liczba:", n) print("Wybrałeś", n)
break break
} }
} }
``` ```
Twoje własne funkcje też mogą się nie udać. Postaw `!` po typie wyniku i użyj `return error("...")`: Twoje własne funkcje też mogą się nie udać. Dopisz `!` po typie wyniku i użyj `return error("...")`:
```c ```c
fn bezpieczne_dzielenie(int a, int b) -> int! { fn bezpieczne_dzielenie(int a, int b) -> int! {
@ -817,15 +825,15 @@ fn main() {
} }
``` ```
Finch nie pozwoli o tym zapomnieć: wywołanie `bezpieczne_dzielenie(10, 0)` bez `or` to błąd, który każe ci to obsłużyć. Finch nie pozwoli ci zapomnieć: wywołanie `bezpieczne_dzielenie(10, 0)` bez `or` to błąd, który
(Problem można też przekazać dalej przez `try`; wyjaśnia to przypomina, żeby to obsłużyć. (Można też przekazać problem dalej przez `try`; wyjaśnia to
[przewodnik dla techników](dla-technikow.md#błędy-jako-wartości).) [przewodnik dla techników](dla-technikow.md#błędy-jako-wartości).)
--- ---
## 17. Małe projekty do zrobienia ## 17. Małe projekty do zrobienia
Spróbuj najpierw napisać każdy sam. Do rozwiązania zajrzyj dopiero wtedy, gdy utkniesz. Spróbuj najpierw napisać każdy sam. Do rozwiązania zajrzyj dopiero, gdy utkniesz.
### Projekt 1: Tabliczka mnożenia ### Projekt 1: Tabliczka mnożenia
@ -844,7 +852,7 @@ fn main() {
### Projekt 2: Parzysta czy nieparzysta ### Projekt 2: Parzysta czy nieparzysta
Dla każdej liczby od 1 do 10 pokaż, czy jest parzysta, czy nieparzysta. Dla każdej liczby od 1 do 10 napisz, czy jest parzysta, czy nieparzysta.
<details><summary>Rozwiązanie</summary> <details><summary>Rozwiązanie</summary>
@ -863,7 +871,7 @@ fn main() {
### Projekt 3: Stopnie Celsjusza na Fahrenheita ### Projekt 3: Stopnie Celsjusza na Fahrenheita
Pokaż tabelę od 0 °C do 100 °C co 10 stopni. Wzór: `F = C * 9 / 5 + 32`. Pokaż tabelkę od 0 °C do 100 °C co 10 stopni. Wzór: `F = C * 9 / 5 + 32`.
<details><summary>Rozwiązanie</summary> <details><summary>Rozwiązanie</summary>
@ -885,8 +893,8 @@ fn main() {
### Projekt 4: Fizz Buzz ### Projekt 4: Fizz Buzz
Policz od 1 do 20. Dla liczb podzielnych przez 3 powiedz `Fizz`, przez 5 `Buzz`, Policz od 1 do 20. Dla liczb podzielnych przez 3 napisz `Fizz`, przez 5 napisz `Buzz`,
przez obie `FizzBuzz`, a w pozostałych przypadkach samą liczbę. przez obie naraz napisz `FizzBuzz`, a w pozostałych przypadkach samą liczbę.
<details><summary>Rozwiązanie</summary> <details><summary>Rozwiązanie</summary>
@ -909,7 +917,7 @@ fn main() {
### Projekt 5: Największa z trzech ### Projekt 5: Największa z trzech
Napisz funkcję `najwieksza(int a, int b, int c) -> int`, która zwraca największą liczbę. Napisz funkcję `najwieksza(int a, int b, int c) -> int`, która oddaje największą z trzech liczb.
<details><summary>Rozwiązanie</summary> <details><summary>Rozwiązanie</summary>
@ -932,8 +940,7 @@ fn main() {
### Projekt 6: Zgadnij liczbę ### Projekt 6: Zgadnij liczbę
Komputer wybiera tajną liczbę. Gracz zgaduje, aż trafi, a komputer za każdym razem Komputer ma tajną liczbę. Gracz zgaduje, aż trafi, a komputer za każdym razem mówi „więcej” albo „mniej”.
mówi „więcej” albo „mniej”.
<details><summary>Rozwiązanie</summary> <details><summary>Rozwiązanie</summary>
@ -949,7 +956,7 @@ fn main() {
} else if strzal > tajna { } else if strzal > tajna {
print("Mniej!") print("Mniej!")
} else { } else {
print("Tak! Liczba prób:", proby) print("Tak! Potrzebowałeś", proby, "prób")
return return
} }
} }
@ -961,7 +968,7 @@ fn main() {
### Projekt 7: Średnia ocen ### Projekt 7: Średnia ocen
Zapytaj o 3 oceny, zapisz je na liście i pokaż listę oraz średnią. Zapytaj o 3 oceny, zapisz je na liście, a potem pokaż listę i średnią.
<details><summary>Rozwiązanie</summary> <details><summary>Rozwiązanie</summary>
@ -991,9 +998,9 @@ fn main() {
fn main() { // tu zaczyna się program fn main() { // tu zaczyna się program
print("tekst", 42) // pokaż coś print("tekst", 42) // pokaż coś
x := 5 // nowe pudełko (typ rozpoznany sam) x := 5 // nowe pudełko (typ zgadnięty)
int y = 10 // nowe pudełko (typ podany) int y = 10 // nowe pudełko (typ podany)
x = 7 // zmiana zawartości pudełka x = 7 // zmień zawartość
x += 1 // dodaj 1 do x x += 1 // dodaj 1 do x
if x > 5 { // decyzja if x > 5 { // decyzja
@ -1013,49 +1020,72 @@ fn main() { // tu zaczyna się program
} }
} }
fn dodaj(int a, int b) -> int { // twoje własne polecenie fn dodaj(int a, int b) -> int { // własne polecenie
return a + b return a + b
} }
struct Zwierzak { // twój własny rodzaj wartości struct Zwierzak { // własny rodzaj wartości
str imie str imie
int wiek = 1 int wiek = 1
} }
// listy: liczby := [1, 2, 3] liczby.push(4) liczby[0] liczby.len for n in liczby { } for i, n in liczby { } // listy: liczby := [1, 2, 3] liczby.push(4) liczby[0] liczby.len for n in liczby { } for i, n in liczby { }
// mapy: wiek := ["Ola": 9] wiek["Jan"] = 10 wiek["Ola"] wiek.has("Tomek") for imie, w in wiek { } // mapy: wiek := ["Ola": 9] wiek["Jan"] = 10 wiek["Ola"] wiek.has("Tomek") for imie, lata in wiek { }
// tekst: "a" + "b" str(42) int("42") s.len s.upper() s.contains("x") // tekst: "a" + "b" str(42) int("42") s.len s.upper() s.contains("x")
// pytanie: imie := input("Imię? ") // pytanie: imie := input("Imię? ")
// pliki: write_file("p.txt", tekst) read_file("p.txt") // pliki: write_file("p.txt", tekst) read_file("p.txt")
// metody: fn Zwierzak.urosnij() { self.wiek += 1 } z.urosnij() // metody: fn Zwierzak.urosnij() { self.wiek += 1 } z.urosnij()
// porażki: n := int(tekst) or 0 x := read_file("p.txt") or { print(err) ... } // porażki: n := int(tekst) or 0 x := read_file("p.txt") or { print(err) ... }
``` ```
--- ---
## 19. Słowa, które będziesz słyszeć ## 19. Słowniczek
| Słowo | Znaczenie | | Słowo | Znaczenie |
|---|---| |---|---|
| **program** | Lista kroków dla komputera | | **program** | Lista kroków dla komputera |
| **kod** | Tekst programu | | **kod** | Tekst programu |
| **uruchomić** | Kazać komputerowi wykonać kroki | | **uruchomić** | Kazać komputerowi wykonać kroki |
| **skompilować** | Przetłumaczyć kod na coś, co komputer potrafi uruchomić. `finch run` robi to za ciebie | | **kompilować** | Przetłumaczyć kod na coś, co komputer umie uruchomić. `finch run` robi to za ciebie |
| **zmienna** | Pudełko z nazwą, w którym leży wartość | | **zmienna** | Pudełko z nazwą, w którym jest wartość |
| **typ** | Rodzaj wartości: liczba, tekst, tak/nie… | | **typ** | Rodzaj wartości: liczba, tekst, tak/nie… |
| **funkcja** | Nazwana grupa kroków, której można używać wiele razy | | **funkcja** | Nazwana grupa kroków, której można używać wiele razy |
| **pętla** | Kroki, które się powtarzają | | **pętla** | Kroki, które się powtarzają |
| **warunek** | Pytanie typu tak/nie, np. `x > 5` | | **warunek** | Pytanie tak/nie, np. `x > 5` |
| **lista / tablica** | Wiele wartości po kolei, np. `[1, 2, 3]` | | **lista / tablica** | Wiele wartości po kolei, np. `[1, 2, 3]` |
| **indeks** | Miejsce wartości na liście, liczone od 0 | | **indeks** | Pozycja wartości na liście, liczona od 0 |
| **struktura** | Twój własny rodzaj wartości z nazwanych części | | **struktura** (struct) | Własny rodzaj wartości złożony z nazwanych części |
| **metoda** | Polecenie należące do struktury, np. `g.trafiony()` | | **metoda** | Polecenie należące do struktury, np. `g.trafiony()` |
| **mapa** | Wartości wyszukiwane po kluczu (nazwie), jak w słowniku | | **mapa** (map) | Wartości znajdowane po kluczu (nazwie), jak w słowniku |
| **wejście** (ang. *input*) | To, co ktoś wpisuje do programu | | **wejście** (input) | To, co użytkownik wpisuje do programu |
| **błąd** (ang. *error*) | Komunikat, że coś jest nie tak, i gdzie | | **błąd** (error) | Komunikat, że coś jest nie tak, i gdzie |
| **bug** | Pomyłka w programie | | **bug** | Pomyłka w programie |
### Słówka po angielsku, które spotkasz w Finchu
| Słowo | Po polsku |
|---|---|
| `print` | wypisz |
| `if` / `else` | jeżeli / w przeciwnym razie |
| `for` … `in` | dla … w |
| `while` | dopóki |
| `break` / `continue` | przerwij / kontynuuj |
| `return` | zwróć (oddaj) |
| `true` / `false` | prawda / fałsz |
| `fn` (od *function*) | funkcja |
| `struct` | struktura |
| `push` / `pop` | dołóż / zabierz |
| `input` | wejście (pytanie) |
| `len` (od *length*) | długość |
| `error` | błąd |
| `or` | albo (co zrobić, gdy się nie uda) |
| `try` | spróbuj (a jak się nie uda, przekaż błąd dalej) |
| `self` | sam (struktura, na której użyto metody) |
| `map` | mapa (słownik) |
| `has` / `get` | ma / weź |
--- ---
**Co dalej?** Kiedy to wszystko będzie już łatwe, przejdź do **Co dalej?** Kiedy to wszystko wyda ci się łatwe, przejdź do
[przewodnika dla techników](dla-technikow.md). Opisuje każdą część Fincha. [przewodnika dla techników](dla-technikow.md). Opisuje każdą część Fincha.

File diff suppressed because it is too large Load diff

View file

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

View file

@ -5,7 +5,7 @@
Install: copy to ~/.local/share/org.kde.syntax-highlighting/syntax/finch.xml Install: copy to ~/.local/share/org.kde.syntax-highlighting/syntax/finch.xml
(or run editors/kate/install.sh), then restart Kate. (or run editors/kate/install.sh), then restart Kate.
--> -->
<language name="Finch" version="5" kateversion="5.0" section="Sources" extensions="*.fch" <language name="Finch" version="4" kateversion="5.0" section="Sources" extensions="*.fch"
mimetype="text/x-finch" indenter="cstyle" author="The Finch authors" license="Apache-2.0"> mimetype="text/x-finch" indenter="cstyle" author="The Finch authors" license="Apache-2.0">
<highlighting> <highlighting>
<list name="keywords"> <list name="keywords">
@ -82,19 +82,6 @@
<item>atomic_store</item> <item>atomic_store</item>
<item>atomic_swap</item> <item>atomic_swap</item>
<item>atomic_cas</item> <item>atomic_cas</item>
<item>select</item>
<item>concat</item>
<item>fma</item>
<item>muladd</item>
<item>cpu_has</item>
<item>f32_bits</item>
<item>f32_from_bits</item>
<item>f64_bits</item>
<item>f64_from_bits</item>
<item>f16_to_f32</item>
<item>f32_to_f16</item>
<item>bf16_to_f32</item>
<item>f32_to_bf16</item>
</list> </list>
<contexts> <contexts>
@ -116,9 +103,6 @@
<keyword String="imports" attribute="Import"/> <keyword String="imports" attribute="Import"/>
<keyword String="constants" attribute="Constant"/> <keyword String="constants" attribute="Constant"/>
<keyword String="types" attribute="Data Type"/> <keyword String="types" attribute="Data Type"/>
<!-- vectors: f32x8, i8x32, u16x16, f64x4 ... -->
<RegExpr String="\b([iuf](8|16|32|64)|bool)x(2|4|8|16|32|64)\b" attribute="Data Type"/>
<RegExpr String="@cpu\b" attribute="Keyword"/>
<keyword String="builtins" attribute="Built-in"/> <keyword String="builtins" attribute="Built-in"/>
<RegExpr String="\b0[xX][0-9a-fA-F_]+\b" attribute="Hex"/> <RegExpr String="\b0[xX][0-9a-fA-F_]+\b" attribute="Hex"/>

View file

@ -1,10 +1,5 @@
# Changelog # Changelog
## 2.6.0
- Vector types (`f32x8`, `i8x32`, `f64x4`, …) and masks (`boolx8`) highlighted as types; completion of their methods (`sum`, `dot_add`, `shuffle`, `lookup`, `store`, …).
- `@cpu(...)` and the new built-ins (`select`, `concat`, `fma`, `muladd`, `cpu_has`, `f32_bits`, `f16_to_f32`, …) highlighted.
## 2.5.0 ## 2.5.0
- Highlighting for code inside text (`"hp \(hp)"`), `test "…" { }` blocks, `const`, `"""` text, `1e9`. - Highlighting for code inside text (`"hp \(hp)"`), `test "…" { }` blocks, `const`, `"""` text, `1e9`.

View file

@ -1,12 +1,12 @@
{ {
"name": "finch-lang", "name": "finch-lang",
"version": "2.6.0", "version": "2.4.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "finch-lang", "name": "finch-lang",
"version": "2.6.0", "version": "2.4.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"vscode-languageclient": "^9.0.1" "vscode-languageclient": "^9.0.1"

View file

@ -2,7 +2,7 @@
"name": "finch-lang", "name": "finch-lang",
"displayName": "Finch", "displayName": "Finch",
"description": "The Finch programming language: highlighting, errors as you type, completion, hover, go to definition, and a Run button.", "description": "The Finch programming language: highlighting, errors as you type, completion, hover, go to definition, and a Run button.",
"version": "2.6.0", "version": "2.5.0",
"publisher": "nexoniarz", "publisher": "nexoniarz",
"license": "Apache-2.0", "license": "Apache-2.0",
"icon": "images/icon.png", "icon": "images/icon.png",

View file

@ -146,16 +146,12 @@
"name": "variable.language.self.finch", "name": "variable.language.self.finch",
"match": "\\bself\\b" "match": "\\bself\\b"
}, },
{
"name": "storage.modifier.attribute.finch",
"match": "@cpu\\b"
},
{ {
"name": "storage.type.primitive.finch", "name": "storage.type.primitive.finch",
"match": "\\b(int|float|bool|char|str|ptr|map|i8|i16|i32|i64|u8|u16|u32|u64|f32|f64|(?:[iuf](?:8|16|32|64)|bool)x(?:2|4|8|16|32|64))\\b" "match": "\\b(int|float|bool|char|str|ptr|map|i8|i16|i32|i64|u8|u16|u32|u64|f32|f64)\\b"
}, },
{ {
"match": "\\b(print|input|read_file|write_file|file_exists|delete_file|shell|exit|addr|new|free|error|min|max|abs|clamp|assert|atomic_add|atomic_sub|atomic_load|atomic_store|atomic_swap|atomic_cas|select|concat|fma|muladd|cpu_has|f32_bits|f32_from_bits|f64_bits|f64_from_bits|f16_to_f32|f32_to_f16|bf16_to_f32|f32_to_bf16)\\s*(?=\\()", "match": "\\b(print|input|read_file|write_file|file_exists|delete_file|shell|exit|addr|new|free|error|min|max|abs|clamp|assert|atomic_add|atomic_sub|atomic_load|atomic_store|atomic_swap|atomic_cas)\\s*(?=\\()",
"captures": { "captures": {
"1": { "1": {
"name": "support.function.builtin.finch" "name": "support.function.builtin.finch"

View file

@ -29,8 +29,6 @@ const code = [
'test "lives \\(hp + f(1)) left" {', 'test "lives \\(hp + f(1)) left" {',
' assert(clamp(x, 0, 9) == 3)', ' assert(clamp(x, 0, 9) == 3)',
'}', '}',
'fn dot(f32x8 a, i8x32 b) {',
'@cpu("avx2") fn k(boolx8 m) { select(m, a, b) }',
]; ];
const expect = [ // [line, token text, scope that must be present] const expect = [ // [line, token text, scope that must be present]
[0, 'import', 'keyword.control.import'], [0, '"stdio.h"', 'string.quoted.double'], [0, 'import', 'keyword.control.import'], [0, '"stdio.h"', 'string.quoted.double'],
@ -51,8 +49,6 @@ const expect = [ // [line, token text, scope that must be present]
[17, 'test', 'keyword.other.test'], [17, '\\(', 'punctuation.section.embedded.begin'], [17, 'hp', 'meta.embedded.line'], [17, 'test', 'keyword.other.test'], [17, '\\(', 'punctuation.section.embedded.begin'], [17, 'hp', 'meta.embedded.line'],
[17, 'f', 'meta.embedded.line'], [17, 'left', 'string.quoted.double'], [17, 'f', 'meta.embedded.line'], [17, 'left', 'string.quoted.double'],
[18, 'assert', 'support.function.builtin'], [18, 'clamp', 'support.function.builtin'], [18, 'assert', 'support.function.builtin'], [18, 'clamp', 'support.function.builtin'],
[20, 'f32x8', 'storage.type.primitive'], [20, 'i8x32', 'storage.type.primitive'],
[21, '@cpu', 'storage.modifier.attribute'], [21, 'boolx8', 'storage.type.primitive'], [21, 'select', 'support.function.builtin'],
]; ];
registry.loadGrammar('source.finch').then((g) => { registry.loadGrammar('source.finch').then((g) => {
let state = vsctm.INITIAL, toks = []; let state = vsctm.INITIAL, toks = [];

View file

@ -1,69 +0,0 @@
// Vectors (SIMD): eight f32 at once instead of one.
//
// finch run examples/simd.fch runs on any x86-64 or ARM64 processor
// finch run examples/simd.fch --cpu native uses all of this one's instructions (AVX2, AVX-512, NEON)
import time
// one number at a time
fn dot_plain([]f32 a, []f32 b) -> f32 {
f32 total = 0.0
for i in 0..a.len {
total += a[i] * b[i]
}
return total
}
// eight at a time: f32x8(list, i) takes list[i] ... list[i+7]; muladd multiplies and adds, in one step where it can
fn dot_simd([]f32 a, []f32 b) -> f32 {
acc := f32x8(0.0)
i := 0
while i + 8 <= a.len {
acc = f32x8(a, i).muladd(f32x8(b, i), acc)
i += 8
}
total := acc.sum()
while i < a.len { // the few that are left
total += a[i] * b[i]
i += 1
}
return total
}
// brighten every color: the same work on every lane, with min keeping it below 1
fn brighten([]f32 pixels, f32 amount) {
i := 0
while i + 8 <= pixels.len {
v := min(f32x8(pixels, i) * (1.0 + amount), 1.0)
v.store(pixels, i)
i += 8
}
}
fn main() {
[]f32 a
[]f32 b
for i in 0..4_000_000 {
a.push(f32(i % 100) / 100.0)
b.push(0.5)
}
start := time.millis()
x := 0.0
for round in 0..20 {
x = float(dot_plain(a, b))
}
plain := time.millis() - start
start = time.millis()
y := 0.0
for round in 0..20 {
y = float(dot_simd(a, b))
}
simd := time.millis() - start
print("one at a time: ", str(x, 1), "in", plain, "ms")
print("eight at a time:", str(y, 1), "in", simd, "ms")
brighten(a, 0.5)
print("brightened:", a[10], a[90], a.len)
}

View file

@ -2,7 +2,7 @@
// It is embedded in the compiler, compiled once into a cache folder, and linked // It is embedded in the compiler, compiled once into a cache folder, and linked
// statically into every Finch program. Everything here is plain C with no state. // statically into every Finch program. Everything here is plain C with no state.
// //
// Layout shared with the compiler (src/gen.fch): // Layout shared with the compiler (src/codegen.cpp):
// str = { char *ptr; int64 len; int64 cap } ptr is always NUL-terminated // str = { char *ptr; int64 len; int64 cap } ptr is always NUL-terminated
// cap > 0 : heap memory owned by the value (cap bytes allocated) // cap > 0 : heap memory owned by the value (cap bytes allocated)
// cap == 0 : a string literal, valid forever, never written to // cap == 0 : a string literal, valid forever, never written to
@ -1147,96 +1147,3 @@ __attribute__((noreturn, cold)) void finch_map_missing(const char *file, int64_t
(int)(key->len > 100 ? 100 : key->len), key->ptr ? key->ptr : ""); (int)(key->len > 100 ? 100 : key->len), key->ptr ? key->ptr : "");
finch_panic(file, line, msg); finch_panic(file, line, msg);
} }
// ---------- cpu_has("avx2"): what the processor running the program can do ----------
//
// x86: CPUID, and for the AVX and AVX-512 families also XGETBV (the system must save those registers, or the
// instructions fault). ARM64: the kernel's hwcaps on Linux and Android, sysctl on macOS. A name of the other
// architecture is false.
#if defined(__x86_64__) || defined(__i386__)
#include <cpuid.h>
static int x86_leaf(unsigned leaf, unsigned sub, unsigned r[4]) {
unsigned max = __get_cpuid_max(0, 0);
if (leaf > max) return 0;
__cpuid_count(leaf, sub, r[0], r[1], r[2], r[3]);
return 1;
}
static unsigned long long x86_xcr0(void) {
unsigned lo, hi;
__asm__ volatile("xgetbv" : "=a"(lo), "=d"(hi) : "c"(0));
return ((unsigned long long)hi << 32) | lo;
}
static int finch_cpu_has_c(const char *n) {
unsigned l1[4] = {0}, l7[4] = {0}, l71[4] = {0};
x86_leaf(1, 0, l1);
x86_leaf(7, 0, l7);
x86_leaf(7, 1, l71);
int osx = (l1[2] >> 27) & 1; // OSXSAVE
unsigned long long xcr0 = osx ? x86_xcr0() : 0;
int ymm = osx && (xcr0 & 6) == 6; // the system saves xmm and ymm
int zmm = ymm && (xcr0 & 0xe0) == 0xe0; // and the AVX-512 registers
if (!strcmp(n, "sse3")) return l1[2] & 1;
if (!strcmp(n, "ssse3")) return (l1[2] >> 9) & 1;
if (!strcmp(n, "sse4.1")) return (l1[2] >> 19) & 1;
if (!strcmp(n, "sse4.2")) return (l1[2] >> 20) & 1;
if (!strcmp(n, "popcnt")) return (l1[2] >> 23) & 1;
if (!strcmp(n, "avx")) return ymm && ((l1[2] >> 28) & 1);
if (!strcmp(n, "fma")) return ymm && ((l1[2] >> 12) & 1);
if (!strcmp(n, "f16c")) return ymm && ((l1[2] >> 29) & 1);
if (!strcmp(n, "avx2")) return ymm && ((l7[1] >> 5) & 1);
if (!strcmp(n, "bmi1")) return (l7[1] >> 3) & 1;
if (!strcmp(n, "bmi2")) return (l7[1] >> 8) & 1;
if (!strcmp(n, "avxvnni")) return ymm && ((l71[0] >> 4) & 1);
if (!strcmp(n, "avx512f")) return zmm && ((l7[1] >> 16) & 1);
if (!strcmp(n, "avx512dq")) return zmm && ((l7[1] >> 17) & 1);
if (!strcmp(n, "avx512bw")) return zmm && ((l7[1] >> 30) & 1);
if (!strcmp(n, "avx512vl")) return zmm && ((l7[1] >> 31) & 1);
if (!strcmp(n, "avx512vnni")) return zmm && ((l7[2] >> 11) & 1);
if (!strcmp(n, "avx512fp16")) return zmm && ((l7[3] >> 23) & 1);
if (!strcmp(n, "avx512bf16")) return zmm && ((l71[0] >> 5) & 1);
return 0;
}
#elif defined(__aarch64__) && defined(__APPLE__)
#include <sys/sysctl.h>
static int sysctl_flag(const char *name) {
int v = 0;
size_t len = sizeof v;
if (sysctlbyname(name, &v, &len, NULL, 0) != 0) return 0;
return v != 0;
}
static int finch_cpu_has_c(const char *n) {
if (!strcmp(n, "neon")) return 1;
if (!strcmp(n, "fp16")) return sysctl_flag("hw.optional.arm.FEAT_FP16");
if (!strcmp(n, "dotprod")) return sysctl_flag("hw.optional.arm.FEAT_DotProd");
if (!strcmp(n, "i8mm")) return sysctl_flag("hw.optional.arm.FEAT_I8MM");
if (!strcmp(n, "bf16")) return sysctl_flag("hw.optional.arm.FEAT_BF16");
return 0; // no SVE on Apple chips
}
#elif defined(__aarch64__) && defined(__linux__)
#include <sys/auxv.h>
static int finch_cpu_has_c(const char *n) {
unsigned long h = getauxval(AT_HWCAP), h2 = getauxval(AT_HWCAP2);
if (!strcmp(n, "neon")) return 1;
if (!strcmp(n, "fp16")) return (h >> 10) & 1; // HWCAP_ASIMDHP
if (!strcmp(n, "dotprod")) return (h >> 20) & 1; // HWCAP_ASIMDDP
if (!strcmp(n, "sve")) return (h >> 22) & 1; // HWCAP_SVE
if (!strcmp(n, "sve2")) return (h2 >> 1) & 1; // HWCAP2_SVE2
if (!strcmp(n, "i8mm")) return (h2 >> 13) & 1; // HWCAP2_I8MM
if (!strcmp(n, "bf16")) return (h2 >> 14) & 1; // HWCAP2_BF16
return 0;
}
#else
static int finch_cpu_has_c(const char *n) {
(void)n;
return 0;
}
#endif
int finch_cpu_has(const FStr *name) {
char n[32];
int64_t len = name->len < 31 ? name->len : 31;
memcpy(n, name->ptr ? name->ptr : "", (size_t)len);
n[len] = 0;
return finch_cpu_has_c(n) ? 1 : 0;
}

View file

@ -15,7 +15,4 @@ pkgs.mkShell {
vulkan-headers vulkan-headers
vulkan-loader vulkan-loader
]; ];
# Nix's clang wrapper drops -march=native unless this is 0 (finch --cpu native also tells it so itself)
NIX_ENFORCE_NO_NATIVE = 0;
} }

View file

@ -36,41 +36,6 @@ fn ptrTo(Type t) -> Type {
return out return out
} }
// f32x8, i8x32, u16x16, f64x4: N numbers of one type side by side, worked on all at once (SIMD).
// Its kind is "vec", elem[0] the type of one lane, count the number of lanes. kind "" if `name` is no such type.
fn vecType(str name) -> Type {
x := name.find("x")
if x < 2 || x + 1 >= name.len {
return Type()
}
lane := name.sub(0, x)
if !["i8", "i16", "i32", "i64", "u8", "u16", "u32", "u64", "f32", "f64", "bool"].contains(lane) {
return Type()
}
n := 0
for c in name.sub(x + 1, name.len) {
if c < '0' || c > '9' {
return Type()
}
n = n * 10 + (int(c) - int('0'))
}
if n != 2 && n != 4 && n != 8 && n != 16 && n != 32 && n != 64 {
return Type()
}
bits := int(lane.sub(1, lane.len)) or 1 // bool: a mask, one bit a lane
if n * bits > 512 {
return Type() // the widest registers (AVX-512) hold 512 bits
}
if lane == "i64" {
lane = "int"
} else if lane == "f64" {
lane = "float"
}
out := Type(kind: "vec", count: n)
out.elem.push(simple(lane))
return out
}
fn same(Type a, Type b) -> bool { fn same(Type a, Type b) -> bool {
if a.kind != b.kind { if a.kind != b.kind {
return false return false
@ -78,10 +43,10 @@ fn same(Type a, Type b) -> bool {
if a.kind == "struct" || a.kind == "named" || a.kind == "enum" { if a.kind == "struct" || a.kind == "named" || a.kind == "enum" {
return a.name == b.name && a.module == b.module return a.name == b.name && a.module == b.module
} }
if (a.kind == "fixed" || a.kind == "vec") && a.count != b.count { if a.kind == "fixed" && a.count != b.count {
return false return false
} }
if a.kind == "array" || a.kind == "ptr" || a.kind == "map" || a.kind == "fixed" || a.kind == "vec" { if a.kind == "array" || a.kind == "ptr" || a.kind == "map" || a.kind == "fixed" {
if a.elem.len != b.elem.len { if a.elem.len != b.elem.len {
return false return false
} }
@ -102,15 +67,6 @@ fn show(Type t) -> str {
if t.kind == "fixed" { if t.kind == "fixed" {
return "[" + str(t.count) + "]" + show(t.elem[0]) return "[" + str(t.count) + "]" + show(t.elem[0])
} }
if t.kind == "vec" {
lane := t.elem[0].kind
if lane == "int" {
lane = "i64"
} else if lane == "float" {
lane = "f64"
}
return lane + "x" + str(t.count)
}
if t.kind == "map" { if t.kind == "map" {
return "map[" + show(t.elem[0]) + "]" + show(t.elem[1]) return "map[" + show(t.elem[0]) + "]" + show(t.elem[1])
} }
@ -147,7 +103,6 @@ struct Node {
bool flag // decl: has a type; or: the right side is a { block } bool flag // decl: has a type; or: the right side is a { block }
bool fallible // fn: -> T! or -> ! bool fallible // fn: -> T! or -> !
bool exported // export fn: a C function of a library (finch build --shared) bool exported // export fn: a C function of a library (finch build --shared)
str cpu // @cpu("avx2,fma") fn: the processor features the function may use
[]Node kids []Node kids
[]str names // named arguments: Point(x: 1) []str names // named arguments: Point(x: 1)
int line int line

View file

@ -1,9 +1,10 @@
// C headers, read through libclang, written in Finch. // C headers, read through libclang, written in Finch.
// //
// What `import "stdio.h"` teaches the compiler: the C functions, the constants (enum values and // What `import "stdio.h"` teaches the compiler: the C functions, the constants (enum values and
// plain #define numbers), the extern variables (stdout) and the structs. It calls libclang's C API // plain #define numbers), the extern variables (stdout) and the structs. The same job as
// through Finch's own C import -- so to compile this file, the compiler reads libclang's headers // src/cimport.cpp, done by calling libclang's C API through Finch's own C import -- which is
// with this very code. // what makes this the part that closes the bootstrap: to compile this file the self-hosted
// compiler has to read libclang's headers with this very code.
// //
// C structs by value appear in types as the named type `module "C"`. // C structs by value appear in types as the named type `module "C"`.
import ast import ast

View file

@ -26,19 +26,6 @@ fn builtins() -> []Doc {
doc("max", "max(a, b, ...) -> number", "The biggest of the numbers, in the type they all fit."), doc("max", "max(a, b, ...) -> number", "The biggest of the numbers, in the type they all fit."),
doc("abs", "abs(x) -> number", "x without its minus sign (any number type)."), doc("abs", "abs(x) -> number", "x without its minus sign (any number type)."),
doc("clamp", "clamp(x, lo, hi) -> number", "x, but at least lo and at most hi."), doc("clamp", "clamp(x, lo, hi) -> number", "x, but at least lo and at most hi."),
doc("cpu_has", "cpu_has(str feature) -> bool", "Does the processor running the program have this feature (\"avx2\", \"avx512f\", \"avxvnni\", \"dotprod\", \"i8mm\" ...)? Check before calling a @cpu(...) function."),
doc("concat", "concat(V a, V b) -> V2", "One vector with a's lanes, then b's (twice as many)."),
doc("select", "select(mask, a, b) -> V", "Lane by lane: a where the mask is true, b where it is false. The mask comes from comparing vectors (v > 0.0)."),
doc("fma", "fma(a, b, c) -> float", "a * b + c with one rounding, always (numbers or vectors). a * b + c written out is never fused: two roundings."),
doc("muladd", "muladd(a, b, c) -> float", "a * b + c the fastest way here: one fused instruction where the processor has one, else a multiply and an add."),
doc("f32_bits", "f32_bits(f32 x) -> u32", "The bits of x, unchanged (also f32xN -> u32xN). f64_bits gives a u64."),
doc("f32_from_bits", "f32_from_bits(u32 bits) -> f32", "The f32 with these bits (also u32xN -> f32xN). f64_from_bits takes a u64."),
doc("f64_bits", "f64_bits(float x) -> u64", "The bits of x, unchanged (also f64xN -> u64xN)."),
doc("f64_from_bits", "f64_from_bits(u64 bits) -> float", "The float with these bits (also u64xN -> f64xN)."),
doc("f16_to_f32", "f16_to_f32(u16 half) -> f32", "A half-precision number, kept in a u16, as an f32 (also u16xN -> f32xN)."),
doc("f32_to_f16", "f32_to_f16(f32 x) -> u16", "x as a half-precision number in a u16, rounded to the nearest (also f32xN -> u16xN)."),
doc("bf16_to_f32", "bf16_to_f32(u16 b) -> f32", "A bfloat16, kept in a u16, as an f32 (also u16xN -> f32xN)."),
doc("f32_to_bf16", "f32_to_bf16(f32 x) -> u16", "x as a bfloat16 in a u16, rounded to the nearest, ties to even (also f32xN -> u16xN)."),
doc("atomic_add", "atomic_add(ptr[int] p, n) -> int", "Adds n to the number at p in one step other threads can't split; gives back what was there before."), doc("atomic_add", "atomic_add(ptr[int] p, n) -> int", "Adds n to the number at p in one step other threads can't split; gives back what was there before."),
doc("atomic_sub", "atomic_sub(ptr[int] p, n) -> int", "Takes n from the number at p in one step; gives back what was there before."), doc("atomic_sub", "atomic_sub(ptr[int] p, n) -> int", "Takes n from the number at p in one step; gives back what was there before."),
doc("atomic_load", "atomic_load(ptr[int] p) -> int", "Reads the number at p, as another thread last wrote it."), doc("atomic_load", "atomic_load(ptr[int] p) -> int", "Reads the number at p, as another thread last wrote it."),
@ -53,8 +40,7 @@ fn builtins() -> []Doc {
doc("float", "float(x) -> float", "Converts to a decimal number; parses text (int(s) or 0.0 handles bad text)."), doc("float", "float(x) -> float", "Converts to a decimal number; parses text (int(s) or 0.0 handles bad text)."),
doc("char", "char(int n) -> char", "The character with this code."), doc("char", "char(int n) -> char", "The character with this code."),
doc("bool", "bool(int n) -> bool", "true for any number but 0."), doc("bool", "bool(int n) -> bool", "true for any number but 0."),
doc("ptr", "ptr(x) -> ptr", "An untyped pointer: from a typed pointer, or from a number (an address or offset for C)."), doc("ptr", "ptr(x) -> ptr", "An untyped pointer: from a typed pointer, or from a number (an address or offset for C).")
doc("f32x8", "f32x8(x) / f32x8(a, b, ...) / f32x8(list, i) / f32x8(v)", "A vector: 8 f32 side by side, worked on all at once (SIMD). Every lane x; one value per lane; 8 numbers from list[i]; or another 8-lane vector converted. The same goes for i8x16, i32x8, f64x4 and the rest.")
] ]
} }
@ -108,42 +94,13 @@ fn mapMethods() -> []Doc {
] ]
} }
fn vecMethods() -> []Doc {
return [
doc("len", ".len -> int", "The number of lanes."),
doc("sum", "sum() -> T", "All the lanes added up (floats: in whatever order is fastest)."),
doc("min", "min() -> T", "The smallest lane."),
doc("max", "max() -> T", "The biggest lane."),
doc("dot", "dot(V w) -> T", "Lane times lane, added up: (v * w).sum()."),
doc("sqrt", "sqrt() -> V", "The square root of every lane (f32/f64 lanes)."),
doc("floor", "floor() -> V", "Every lane rounded down (f32/f64 lanes)."),
doc("ceil", "ceil() -> V", "Every lane rounded up (f32/f64 lanes)."),
doc("round", "round() -> V", "Every lane rounded to the nearest whole number, halves away from 0 (f32/f64 lanes)."),
doc("abs", "abs() -> V", "Every lane without its minus sign."),
doc("fma", "fma(V b, V c) -> V", "v * b + c with one rounding, always (f32/f64 lanes). One instruction with FMA hardware, slower without."),
doc("muladd", "muladd(V b, V c) -> V", "v * b + c the fastest way here: fused into one instruction where the processor can (f32/f64 lanes)."),
doc("store", "store([]T list, int i)", "Writes the lanes into list[i], list[i+1], ... (or through a ptr[T])."),
doc("store_part", "store_part([]T list, int i)", "Writes the lanes that fit in the list (for its last, shorter piece)."),
doc("shuffle", "shuffle(int a, int b, ...) -> V", "A vector of the lanes with these numbers (written in the code): v.shuffle(3, 2, 1, 0) reverses an f32x4."),
doc("lo", "lo() -> V", "The lower half of the lanes."),
doc("hi", "hi() -> V", "The upper half of the lanes."),
doc("lookup", "lookup(I indices) -> V", "For every lane of indices: this vector's lane (index % len). A byte-table lookup (pshufb, tbl)."),
doc("dot_add", "dot_add(a, b) -> i32xN", "On an i32xN of sums: each lane gains the sum of 4 byte products of a and b (u8/i8, 4N lanes). One instruction with AVX-VNNI, AVX-512 VNNI or ARM's dot product."),
doc("any", "any() -> bool", "A mask: is any lane true?"),
doc("all", "all() -> bool", "A mask: are all lanes true?"),
doc("count", "count() -> int", "A mask: how many lanes are true.")
]
}
fn keywords() -> []str { fn keywords() -> []str {
return ["fn", "return", "if", "else", "while", "for", "in", "break", "continue", "true", "false", "null", "import", return ["fn", "return", "if", "else", "while", "for", "in", "break", "continue", "true", "false", "null", "import",
"link", "struct", "enum", "match", "export", "defer", "or", "try", "self", "const"] "link", "struct", "enum", "match", "export", "defer", "or", "try", "self", "const"]
} }
fn typeNames() -> []str { fn typeNames() -> []str {
return ["int", "float", "bool", "char", "str", "ptr", "map", "i8", "i16", "i32", "i64", "u8", "u16", "u32", "u64", "f32", "f64", return ["int", "float", "bool", "char", "str", "ptr", "map", "i8", "i16", "i32", "i64", "u8", "u16", "u32", "u64", "f32", "f64"]
"f32x4", "f32x8", "f32x16", "f64x2", "f64x4", "f64x8", "i8x16", "i8x32", "i8x64", "u8x16", "u8x32", "u8x64",
"i16x8", "i16x16", "i16x32", "i32x4", "i32x8", "i32x16", "u32x4", "u32x8", "u32x16", "i64x2", "i64x4", "i64x8"]
} }
// the entry called `name`, or an empty Doc // the entry called `name`, or an empty Doc

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -70,7 +70,7 @@ fn operator(ptr[Lexer] lx) -> str {
return two return two
} }
} }
ones := "(){}[],.:+-*/%&|^~<>=!@" ones := "(){}[],.:+-*/%&|^~<>=!"
c := lx.src.sub(lx.i, lx.i + 1) c := lx.src.sub(lx.i, lx.i + 1)
if ones.contains(c) { if ones.contains(c) {
return c return c

View file

@ -371,7 +371,10 @@ struct Build {
// IR -> program. 0 if it worked. // IR -> program. 0 if it worked.
fn build(Build b, platform.Target tg, cimport.CImports ci) -> int { fn build(Build b, platform.Target tg, cimport.CImports ci) -> int {
clang := clangCmd() clang := getenv("FINCH_CLANG")
if clang.len == 0 {
clang = "clang"
}
// the C compiler of the runtime and the linker: the target's own when building for another system // the C compiler of the runtime and the linker: the target's own when building for another system
cc := clang cc := clang
if tg.cross { if tg.cross {
@ -388,8 +391,7 @@ fn build(Build b, platform.Target tg, cimport.CImports ci) -> int {
target = " --target=" + tg.triple target = " --target=" + tg.triple
} }
log := b.out + ".log" log := b.out + ".log"
// -ffp-contract=on: only what asks to be fused is (muladd); a * b + c stays two roundings, whatever clang's default obj := irObject(clang, target + " " + b.opt + b.extra, b.ir, b.name + "\n" + tg.triple, log) or {
obj := irObject(clang, target + " " + b.opt + " -ffp-contract=on" + b.extra, b.ir, b.name + "\n" + tg.triple, log) or {
print(err) print(err)
host.remove(log) host.remove(log)
return 1 return 1
@ -475,74 +477,3 @@ fn build(Build b, platform.Target tg, cimport.CImports ci) -> int {
} }
return 0 return 0
} }
// clang, as the program is compiled with it. Nix's clang wrapper removes -march=native when NIX_ENFORCE_NO_NATIVE
// is 1, as it is in nix-shell; --cpu native asks for it on purpose, so the wrapper is told to keep it.
fn clangCmd() -> str {
clang := getenv("FINCH_CLANG")
if clang.len == 0 {
clang = "clang"
}
if host.pathSeparator() != ";" {
return "NIX_ENFORCE_NO_NATIVE=0 " + clang
}
return clang
}
// -march=x86-64-v3 or -mcpu=apple-m1: how clang is told the processor
fn cpuFlag(platform.Target tg, str cpu) -> str {
if cpu.len == 0 {
return ""
}
if tg.arm64 {
return " -mcpu=" + quote(cpu)
}
return " -march=" + quote(cpu)
}
// What the processor the program is for can do, as Finch's feature names (avx2, avxvnni, i8mm ...): clang says it in
// the macros it predefines for that target and --cpu. Kept in the cache, one file per clang, target and cpu.
fn cpuFeatures(platform.Target tg, str cpu) -> []str {
clang := clangCmd()
flags := cpuFlag(tg, cpu)
if tg.cross || tg.windows {
flags = " --target=" + tg.triple + flags
}
dir := host.cacheDir() + "/cpu"
host.makeDirs(dir)
slot := dir + "/" + fingerprint2(clang + flags)
text := read_file(slot + ".txt") or ""
if text.len == 0 {
empty := slot + ".part" + str(host.pid()) + ".c"
out := slot + ".part" + str(host.pid()) + ".txt"
write_file(empty, "")
host.run(clang + flags + " -dM -E " + quote(empty) + " > " + quote(out) + " 2> " + host.nullDevice())
host.remove(empty)
text = read_file(out) or ""
if text.len == 0 {
host.remove(out)
[]str none
return none
}
host.move(out, slot + ".txt")
}
macros := ["__SSE3__", "__SSSE3__", "__SSE4_1__", "__SSE4_2__", "__POPCNT__", "__AVX__", "__AVX2__", "__FMA__", "__F16C__",
"__BMI__", "__BMI2__", "__AVX512F__", "__AVX512BW__", "__AVX512DQ__", "__AVX512VL__", "__AVX512VNNI__",
"__AVX512BF16__", "__AVX512FP16__", "__AVXVNNI__", "__ARM_NEON", "__ARM_FEATURE_FP16_VECTOR_ARITHMETIC",
"__ARM_FEATURE_DOTPROD", "__ARM_FEATURE_MATMUL_INT8", "__ARM_FEATURE_BF16_VECTOR_ARITHMETIC", "__ARM_FEATURE_SVE",
"__ARM_FEATURE_SVE2"]
names := ["sse3", "ssse3", "sse4.1", "sse4.2", "popcnt", "avx", "avx2", "fma", "f16c", "bmi1", "bmi2", "avx512f", "avx512bw",
"avx512dq", "avx512vl", "avx512vnni", "avx512bf16", "avx512fp16", "avxvnni", "neon", "fp16", "dotprod", "i8mm", "bf16",
"sve", "sve2"]
[]str have
for line in text.split("\n") {
parts := line.split(" ")
if parts.len >= 2 && parts[0] == "#define" {
k := macros.find(parts[1])
if k >= 0 && !have.contains(names[k]) {
have.push(names[k])
}
}
}
return have
}

View file

@ -7,7 +7,6 @@
import json import json
import host import host
import docs import docs
import ast
import fmt import fmt
import "stdio.h" import "stdio.h"
import "stdlib.h" import "stdlib.h"
@ -622,8 +621,6 @@ fn completionAt(Analysis a, str text, str path, int line, int col) -> json.Json
methodItems(addr(items), docs.strMethods()) methodItems(addr(items), docs.strMethods())
} else if type.starts_with("map[") { } else if type.starts_with("map[") {
methodItems(addr(items), docs.mapMethods()) methodItems(addr(items), docs.mapMethods())
} else if ast.vecType(type).kind.len > 0 {
methodItems(addr(items), docs.vecMethods()) // f32x8, i8x32, ...
} else { } else {
base := type base := type
for st in a.structs { for st in a.structs {

View file

@ -1,4 +1,7 @@
// finch: the Finch compiler, written in Finch (`finch` with no arguments lists the commands). // finch-boot: the Finch compiler written in Finch.
//
// finch-boot build <file.fch> [-o name] compile a program
// finch-boot ir <file.fch> print the LLVM IR
// //
// It reads the program and the modules it imports, checks it, writes LLVM IR as text // It reads the program and the modules it imports, checks it, writes LLVM IR as text
// and lets clang turn that into a program, linked with Finch's C runtime. // and lets clang turn that into a program, linked with Finch's C runtime.
@ -171,9 +174,6 @@ fn usage() {
print(" --trace runtime errors show the chain of calls (finch run, finch test, -g and -O0 do it anyway)") print(" --trace runtime errors show the chain of calls (finch run, finch test, -g and -O0 do it anyway)")
print(" --no-trace they don't (finch build without -g or -O0 doesn't, it costs speed)") print(" --no-trace they don't (finch build without -g or -O0 doesn't, it costs speed)")
print(" --target <name> build for another system: windows, linux, arm64 (Linux), macos, or an LLVM triple") print(" --target <name> build for another system: windows, linux, arm64 (Linux), macos, or an LLVM triple")
print(" --cpu <name> use all of this processor's instructions (AVX2, AVX-512, NEON...): native is the one")
print(" building it; or x86-64-v3 (AVX2), x86-64-v4 (AVX-512), apple-m1, ... The program then")
print(" needs such a processor. Default: any processor of the target system")
exit(1) exit(1)
} }
@ -243,7 +243,7 @@ fn format([]str args) -> int {
fn main([]str args) -> int { fn main([]str args) -> int {
if args.len == 2 && (args[1] == "version" || args[1] == "--version") { if args.len == 2 && (args[1] == "version" || args[1] == "--version") {
print("Finch 2.6.0 (the compiler is written in Finch)") print("Finch 2.5.0 (the compiler is written in Finch)")
return 0 return 0
} }
if args.len >= 2 && args[1] == "fmt" { if args.len >= 2 && args[1] == "fmt" {
@ -293,7 +293,6 @@ fn main([]str args) -> int {
trace := cmd != "build" // finch build: only with --trace, -g or -O0 (the chain costs speed) trace := cmd != "build" // finch build: only with --trace, -g or -O0 (the chain costs speed)
traceGiven := false traceGiven := false
targetName := "" targetName := ""
cpu := ""
opt := "-O2" opt := "-O2"
[]str extraLibs []str extraLibs
[]str extraDirs []str extraDirs
@ -336,9 +335,6 @@ fn main([]str args) -> int {
} else if a == "--target" && ai + 1 < args.len { } else if a == "--target" && ai + 1 < args.len {
targetName = args[ai + 1] targetName = args[ai + 1]
ai += 1 ai += 1
} else if a == "--cpu" && ai + 1 < args.len {
cpu = args[ai + 1]
ai += 1
} else if cmd == "run" { } else if cmd == "run" {
progArgs.push(a) progArgs.push(a)
} else { } else {
@ -422,9 +418,6 @@ fn main([]str args) -> int {
for f in man.list("cflags", targets) { for f in man.list("cflags", targets) {
cflags = cflags + " " + linker.quote(f) cflags = cflags + " " + linker.quote(f)
} }
if cpu.len == 0 {
cpu = man.value("cpu", "")
}
} }
for d in getenv("FINCH_PATH").split(host.pathSeparator()) { for d in getenv("FINCH_PATH").split(host.pathSeparator()) {
if d.len > 0 { if d.len > 0 {
@ -471,15 +464,7 @@ fn main([]str args) -> int {
testModules.push(prog.module) // your own files' tests, not the standard library's testModules.push(prog.module) // your own files' tests, not the standard library's
} }
} }
// what the processor can do (dot_add and the like pick their instructions by it); not needed for the editor's index ir := gen.generate(loader.progs, loader.files, ci, gen.Options(shared: shared, debug: debug, index: cmd == "index", cwd: host.cwd(), target: tg, test: cmd == "test", testModules: testModules, trace: trace))
[]str features
if cmd != "index" {
features = linker.cpuFeatures(tg, cpu)
if cpu == "native" && !tg.arm64 && !features.contains("sse4.2") {
print("warning: --cpu native made no difference to clang (a clang wrapper may remove -march=native; try --cpu with a name, like x86-64-v3)")
}
}
ir := gen.generate(loader.progs, loader.files, ci, gen.Options(shared: shared, debug: debug, index: cmd == "index", cwd: host.cwd(), target: tg, test: cmd == "test", testModules: testModules, trace: trace, features: features))
if cmd == "ir" || cmd == "index" { if cmd == "ir" || cmd == "index" {
print(ir) print(ir)
return 0 return 0
@ -509,7 +494,6 @@ fn main([]str args) -> int {
if debug { if debug {
extra = extra + " -g" // or clang drops the debug info that is in the IR extra = extra + " -g" // or clang drops the debug info that is in the IR
} }
extra = extra + linker.cpuFlag(tg, cpu) // x86-64 names a processor with -march, ARM with -mcpu
[]str cflagList []str cflagList
for d in dirs { for d in dirs {
cflagList.push(" -I" + linker.quote(d)) // also the program's own folders (its headers, for inline C functions) cflagList.push(" -I" + linker.quote(d)) // also the program's own folders (its headers, for inline C functions)

View file

@ -86,7 +86,7 @@ fn node(ptr[Parser] p, str kind) -> ast.Node {
fn isTypeName(str n) -> bool { fn isTypeName(str n) -> bool {
names := ["int", "float", "bool", "char", "str", "ptr", "i8", "i16", "i32", "i64", "u8", "u16", "u32", "u64", "f32", "f64"] names := ["int", "float", "bool", "char", "str", "ptr", "i8", "i16", "i32", "i64", "u8", "u16", "u32", "u64", "f32", "f64"]
return names.contains(n) || ast.vecType(n).kind.len > 0 return names.contains(n)
} }
// int, str, ptr[T], []T, Name, module.Name // int, str, ptr[T], []T, Name, module.Name
@ -117,10 +117,6 @@ fn parseType(ptr[Parser] p) -> ast.Type {
if name == "f64" { if name == "f64" {
return ast.simple("float") return ast.simple("float")
} }
vt := ast.vecType(name)
if vt.kind.len > 0 {
return vt
}
return ast.simple(name) return ast.simple(name)
} }
t := ast.Type(kind: "named", name: name) t := ast.Type(kind: "named", name: name)
@ -289,22 +285,6 @@ fn parseProgram(ptr[Parser] p) -> ast.Program {
if e.flag { if e.flag {
prog.structs.push(sumStruct(e)) // an enum with values inside is stored as a struct (see sumStruct) prog.structs.push(sumStruct(e)) // an enum with values inside is stored as a struct (see sumStruct)
} }
} else if at(p, "@") {
// @cpu("avx2,fma") fn ...: a function that may use these processor features (check with cpu_has first)
a := next(p)
name := expect(p, "ident", "an attribute name, like @cpu(\"avx2\")").text
if name != "cpu" {
fail(p, a, "the attribute here is @cpu(\"...\"): the processor features a function may use")
}
expect(p, "(", "'('")
feats := expect(p, "string", "the features in quotes, like @cpu(\"avx2,fma\")").text
expect(p, ")", "')'")
if !at(p, "fn") && !at(p, "export") {
fail(p, cur(p), "@cpu(...) goes right before a function")
}
f := parseFn(p)
f.cpu = feats
prog.fns.push(f)
} else { } else {
prog.fns.push(parseFn(p)) prog.fns.push(parseFn(p))
} }

View file

@ -1,5 +1,6 @@
// The system a program is built for, and what depends on it: the C compiler that links, the file // The system a program is built for, and what depends on it: the C compiler that links, the file
// name of the program, how it is run here, and (through the flags) the C calling convention. // name of the program, how it is run here, and (through the flags) the C calling convention.
// The same as src/target.cpp.
import host import host
import "stdlib.h" import "stdlib.h"

View file

@ -1,7 +0,0 @@
// expect: @cpu: 'avx3' isn't a feature Finch knows
@cpu("avx2,avx3")
fn f() {
}
fn main() {
f()
}

View file

@ -1,4 +0,0 @@
// expect: cpu_has: 'avx-512' isn't a feature Finch knows
fn main() {
print(cpu_has("avx-512"))
}

View file

@ -1,8 +0,0 @@
// expect: can't be passed between them (its registers differ)
@cpu("avx2")
fn double(f32x8 v) -> f32x8 {
return v * 2.0
}
fn main() {
print(double(f32x8(1.0)))
}

View file

@ -1,5 +0,0 @@
// expect: no instruction that divides whole numbers lane by lane
fn main() {
a := i32x4(8)
print(a / 2)
}

View file

@ -1,6 +0,0 @@
// expect: index 4
fn main() {
v := i32x4(1)
i := 4
print(v[i])
}

View file

@ -1,4 +0,0 @@
// expect: f32x4(a, b, ...) takes 4 numbers
fn main() {
print(f32x4(1.0, 2.0, 3.0))
}

View file

@ -1,7 +0,0 @@
// expect: use .all() or .any()
fn main() {
a := f32x4(1.0)
if a < a {
print("no")
}
}

View file

@ -1,5 +0,0 @@
// expect: the 8 numbers of the vector don't all fit in the list there
fn main() {
[]f32 data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
print(f32x8(data, 2))
}

View file

@ -1,6 +0,0 @@
// expect: can't use '+' on f32x8 and i32x8 (convert one: f32x8(...))
fn main() {
a := f32x8(1.0)
b := i32x8(1)
print(a + b)
}

View file

@ -1,5 +0,0 @@
// expect: lane 4 isn't in a f32x4 (its lanes are 0 to 3)
fn main() {
v := f32x4(1.0)
print(v.shuffle(0, 1, 2, 4))
}

View file

@ -1,6 +0,0 @@
// expect: shuffle(...) takes lane numbers written in the code
fn main() {
v := f32x4(1.0)
k := 2
print(v.shuffle(k, 0, 1, 3))
}

View file

@ -1,5 +0,0 @@
// expect: f32x8(x) puts x in every lane
fn main() {
f32x8 v = 1.0
print(v)
}

View file

@ -1,69 +0,0 @@
// The int8 dot product of quantized models: plain, with dot_add, and in @cpu functions picked by cpu_has
fn plain([]u8 a, []i8 b) -> int {
total := 0
for i in 0..a.len {
total += int(a[i]) * int(b[i])
}
return total
}
// 32 bytes a step, 8 sums kept in a vector until the end
fn with_vectors([]u8 a, []i8 b) -> int {
acc := i32x8(0)
i := 0
while i + 32 <= a.len {
acc = acc.dot_add(u8x32(a, i), i8x32(b, i))
i += 32
}
return int(acc.sum())
}
@cpu("avxvnni")
fn with_vnni([]u8 a, []i8 b) -> int {
acc := i32x8(0)
i := 0
while i + 32 <= a.len {
acc = acc.dot_add(u8x32(a, i), i8x32(b, i))
i += 32
}
return int(acc.sum())
}
@cpu("avx512f,avx512bw,avx512vl,avx512vnni,i8mm")
fn with_avx512([]u8 a, []i8 b) -> int {
acc := i32x16(0)
i := 0
while i + 64 <= a.len {
acc = acc.dot_add(u8x64(a, i), i8x64(b, i))
i += 64
}
return int(acc.sum())
}
fn best([]u8 a, []i8 b) -> int {
if cpu_has("avx512vnni") && cpu_has("avx512vl") && cpu_has("avx512bw") {
return with_avx512(a, b)
}
if cpu_has("avxvnni") {
return with_vnni(a, b)
}
return with_vectors(a, b)
}
fn main() {
[]u8 a
[]i8 b
for k in 0..1024 {
a.push(u8((k * 37 + 11) % 256))
b.push(i8((k * 53 + 7) % 256 - 128))
}
want := plain(a, b)
print(want, with_vectors(a, b) == want, best(a, b) == want)
// the extremes: 255 * -128 four times in a lane, no saturation
acc := i32x4(1, 2, 3, 4).dot_add(u8x16(255), i8x16(-128))
print(acc, i32x4(0).dot_add(i8x16(-128), i8x16(-128)), i32x4(0).dot_add(u8x16(255), u8x16(255)))
print(i32x4(0).dot_add(i8x16(-1), u8x16(200)))
print(cpu_has("avx2") || cpu_has("neon") || true)
}

View file

@ -1,4 +0,0 @@
-68096 true true
i32x4(-130559, -130558, -130557, -130556) i32x4(65536, 65536, 65536, 65536) i32x4(260100, 260100, 260100, 260100)
i32x4(-800, -800, -800, -800)
true

View file

@ -1,31 +0,0 @@
// a * b + c is never fused; fma rounds once; numbers as their bits; f16 and bf16
fn plain(float a, float b, float c) -> float {
return a * b + c
}
fn main() {
// 0.1 * 10 is 1.0000000000000000555..., which rounds to 1 before the - 1 (two roundings), but not inside fma
print(plain(0.1, 10.0, -1.0), fma(0.1, 10.0, -1.0))
print(fma(f32(0.1), 10.0, -1.0), fma(2, 3, 4))
m := muladd(1.5, 2.0, 0.25) // fused or not, exact here
print(m, f32x4(1.5).muladd(2.0, 0.25), fma(f32x4(0.1), 10.0, f32x4(-1.0))[0])
det := f32x4(0.1) * f32x4(10.0) - f32x4(1.0)
print(det[0])
// bits
print(f32_bits(1.0), f64_bits(1.0), f32_bits(-0.0))
print(f32_from_bits(0x40490fdb), f64_from_bits(f64_bits(2.5)), f32_from_bits(i32(-1082130432)))
u32 inf = 0x7f800000
print(f32_from_bits(inf), f32_bits(f32x4(1.0, 2.0, -2.0, 0.5)))
// f16
print(f16_to_f32(0x3c00), f16_to_f32(0xc000), f16_to_f32(0x7bff), f16_to_f32(0x0001))
print(f32_to_f16(0.1), f32_to_f16(65504.0), f32_to_f16(1.0e6), f16_to_f32(f32_to_f16(0.333)))
print(f16_to_f32(u16x8(0x3c00, 0x4000, 0x4200, 0x4400, 0, 0x8000, 0x3800, 0x3400)))
// bf16
print(bf16_to_f32(0x3f80), bf16_to_f32(0x4049), f32_to_bf16(3.14159))
print(f32_to_bf16(f32_from_bits(0x3f808000)), f32_to_bf16(f32_from_bits(0x3f818000)), f32_to_bf16(f32_from_bits(0x7fc00001)))
print(f32_to_bf16(f32x4(1.0, -2.0, 0.5, 3.14159)), bf16_to_f32(u16x4(0x3f80, 0xc000, 0x3f00, 0x4049)))
}

View file

@ -1,13 +0,0 @@
0 5.551115123125783e-17
1.4901161e-08 10
3.25 f32x4(3.25, 3.25, 3.25, 3.25) 1.4901161e-08
0
1065353216 4607182418800017408 2147483648
3.1415927 2.5 -1
inf u32x4(1065353216, 1073741824, 3221225472, 1056964608)
1 -2 65504 5.9604645e-08
11878 31743 31744 0.3330078
f32x8(1, 2, 3, 4, 0, -0, 0.5, 0.25)
1 3.140625 16457
16256 16258 32704
u16x4(16256, 49152, 16128, 16457) f32x4(1, -2, 0.5, 3.140625)

View file

@ -1,50 +0,0 @@
// Masks: comparisons of vectors, select, any/all/count, and the end of a list
fn relu(f32x8 v) -> f32x8 {
return select(v > 0.0, v, 0.0)
}
// the sum of a list of any length: whole vectors, then the last piece with its missing lanes as 0
fn total([]f32 xs) -> f32 {
acc := f32x8(0.0)
i := 0
while i < xs.len {
acc = acc + f32x8(xs, i, 0.0)
i += 8
}
return acc.sum()
}
// add 1 to every number of a list, in place, the last piece too
fn bump([]f32 xs) {
i := 0
while i < xs.len {
(f32x8(xs, i, 0.0) + 1.0).store_part(xs, i)
i += 8
}
}
fn main() {
v := f32x8(-2.0, -1.0, 0.0, 1.0, 2.0, 3.0, -4.0, 5.0)
m := v > 0.0
print(m, m.count(), m.any(), m.all(), m[3])
print(relu(v), select(!m, v, -1.0))
print((v >= -1.0) & (v < 3.0), (v == 0.0) | (v == 5.0), m ^ (v > 1.0))
a := i32x4(1, 5, 3, 7)
b := i32x4(4, 4, 4, 4)
print(select(a > b, a, b), (a == a).all(), (a != b).all())
u := u8x16(200)
print((u > u8x16(100)).all(), i32x4(a > b), f32x4(a < b))
k := i32x4(0, 2, 0, -1)
print(boolx4(k), boolx4(true, false, true, false))
[]f32 xs
for n in 0..13 {
xs.push(f32(n))
}
print(total(xs))
bump(xs)
print(xs)
print(f32x8(xs, 8, -1.0), f32x8(xs, 13, 9.0), f32x8(xs, 100, 7.0))
}

View file

@ -1,9 +0,0 @@
boolx8(false, false, false, true, true, true, false, true) 4 true false true
f32x8(0, 0, 0, 1, 2, 3, 0, 5) f32x8(-2, -1, 0, -1, -1, -1, -4, -1)
boolx8(false, true, true, true, true, false, false, false) boolx8(false, false, true, false, false, false, false, true) boolx8(false, false, false, true, false, false, false, false)
i32x4(4, 5, 4, 7) true true
true i32x4(0, 1, 0, 1) f32x4(1, 0, 1, 0)
boolx4(false, true, false, true) boolx4(true, false, true, false)
78
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
f32x8(9, 10, 11, 12, 13, -1, -1, -1) f32x8(9, 9, 9, 9, 9, 9, 9, 9) f32x8(7, 7, 7, 7, 7, 7, 7, 7)

View file

@ -1,27 +0,0 @@
// Moving lanes: shuffle, lo/hi, concat, and a byte table lookup (how 4-bit weights are unpacked)
// sixteen 4-bit numbers, two to a byte, made into sixteen bytes through a table of what each nibble means
fn unpack4(u8x8 packed, i8x16 table) -> i8x16 {
pairs := packed.shuffle(0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7) // u8x16: each byte twice
odd := u8x16(0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1) == 1
nibbles := select(odd, pairs >> 4, pairs & 15) // low nibble first, then the high one
return table.lookup(nibbles)
}
fn main() {
v := f32x4(1.0, 2.0, 3.0, 4.0)
print(v.shuffle(3, 2, 1, 0), v.shuffle(0, 0, 1, 1, 2, 2, 3, 3), v.shuffle(1, 2))
w := i32x8(0, 1, 2, 3, 4, 5, 6, 7)
print(w.lo(), w.hi(), concat(w.hi(), w.lo()))
print(concat(w, w).len, concat(v, v * 10.0).shuffle(0, 4, 1, 5, 2, 6, 3, 7))
table := u8x16(10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25)
print(table.lookup(u8x16(15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)))
print(table.lookup(u8x16(16, 17, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)).lo())
print(table.lookup(u8x32(3)).hi().lo().lo())
q4 := i8x16(-8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7) // what each 4-bit number means
print(unpack4(u8x8(0x10, 0x32, 0x54, 0x76, 0x98, 0xba, 0xdc, 0xfe), q4))
m := f32x4(1.0, -1.0, 2.0, -2.0) > 0.0
print(m.shuffle(1, 0, 3, 2), m.lo())
}

View file

@ -1,8 +0,0 @@
f32x4(4, 3, 2, 1) f32x8(1, 1, 2, 2, 3, 3, 4, 4) f32x2(2, 3)
i32x4(0, 1, 2, 3) i32x4(4, 5, 6, 7) i32x8(4, 5, 6, 7, 0, 1, 2, 3)
16 f32x8(1, 10, 2, 20, 3, 30, 4, 40)
u8x16(25, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24)
u8x8(10, 11, 25, 10, 10, 10, 10, 10)
u8x4(13, 13, 13, 13)
i8x16(-8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7)
boolx4(false, true, false, true) boolx2(true, false)

View file

@ -1,69 +0,0 @@
// Vectors: N numbers side by side, worked on all at once (SIMD)
struct Particle {
f32x8 pos
int id = 7
}
fn scale(f32x8 v, f32 k) -> f32x8 {
return v * k
}
// a dot product of two lists, 8 at a time, with the rest one by one
fn dot([]f32 a, []f32 b) -> f32 {
acc := f32x8(0.0)
i := 0
while i + 8 <= a.len {
acc = f32x8(a, i).muladd(f32x8(b, i), acc)
i += 8
}
total := acc.sum()
while i < a.len {
total += a[i] * b[i]
i += 1
}
return total
}
fn main() {
a := f32x4(1.0, 2.0, 3.0, 4.0)
b := f32x4(0.5)
print(a + b, a * 2.0, 10.0 - a, -a)
print(a.sum(), a.min(), a.max(), a.dot(a), a.len)
print((a * a).sqrt(), f32x4(1.5, -1.5, 2.5, -0.2).round(), f32x4(1.7).floor(), f32x4(1.2).ceil())
print(abs(f32x4(-1.0, 2.0, -3.0, 4.0)), min(a, 2.5), max(a, f32x4(2.0)), clamp(a, 1.5, 3.5))
// lanes
a[0] = 9.0
print(a[0], a[3], a)
print((a == f32x4(9.0, 2.0, 3.0, 4.0)).all(), (a != a).any())
// whole numbers, bits, conversions
n := i32x8(1, 2, 3, 4, 5, 6, 7, 8)
print(n * 3 - 1, n << 2, n & 1, ~n, n.sum(), n.max())
print(f32x8(n) / 2.0, i32x8(f32x8(2.9)), i16x8(n), u8x16(200).sum())
print(abs(i32x4(-3, 3, -7, 0)), min(u8x16(5), u8x16(9)).max())
// from lists and back
[]f32 data
for i in 0..20 {
data.push(f32(i))
}
v := f32x8(data, 4)
print(v)
(v * 10.0).store(data, 12)
print(data)
print(dot(data, data))
// in structs, arrays, functions, and str()
p := Particle(pos: f32x8(1.0))
p.pos = scale(p.pos, 3.0)
p.pos[7] = 0.5
print(p)
[]f64x4 list
list.push(f64x4(1.0, 2.0, 3.0, 4.0))
list.push(list[0] * list[0])
print(list, str(list[1]).len)
i64x2 big = i64x2(1 << 40, -5)
print(big + big)
}

View file

@ -1,15 +0,0 @@
f32x4(1.5, 2.5, 3.5, 4.5) f32x4(2, 4, 6, 8) f32x4(9, 8, 7, 6) f32x4(-1, -2, -3, -4)
10 1 4 30 4
f32x4(1, 2, 3, 4) f32x4(2, -2, 3, -0) f32x4(1, 1, 1, 1) f32x4(2, 2, 2, 2)
f32x4(1, 2, 3, 4) f32x4(1, 2, 2.5, 2.5) f32x4(2, 2, 3, 4) f32x4(1.5, 2, 3, 3.5)
9 4 f32x4(9, 2, 3, 4)
true false
i32x8(2, 5, 8, 11, 14, 17, 20, 23) i32x8(4, 8, 12, 16, 20, 24, 28, 32) i32x8(1, 0, 1, 0, 1, 0, 1, 0) i32x8(-2, -3, -4, -5, -6, -7, -8, -9) 36 8
f32x8(0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4) i32x8(2, 2, 2, 2, 2, 2, 2, 2) i16x8(1, 2, 3, 4, 5, 6, 7, 8) 128
i32x4(3, 3, 7, 0) 5
f32x8(4, 5, 6, 7, 8, 9, 10, 11)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 40, 50, 60, 70, 80, 90, 100, 110]
49706
Particle(pos: f32x8(3, 3, 3, 3, 3, 3, 3, 0.5), id: 7)
[f64x4(1, 2, 3, 4), f64x4(1, 4, 9, 16)] 18
i64x2(2199023255552, -10)