mirror of
https://github.com/Nexoniarz/Finch.git
synced 2026-11-03 08:54:25 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
881b70ba71 |
||
|
|
1f45bd8a01 |
||
|
|
04f1f94a21 |
||
|
|
0ceaaa27c4 |
||
|
|
5393a32a47 |
||
|
|
06bfe940fa |
||
|
|
8495bc718d |
||
|
|
0bf66740df |
||
|
|
68a9493572 |
||
|
|
a4bdeb5712 |
54 changed files with 4098 additions and 1369 deletions
55
.github/workflows/ci.yml
vendored
55
.github/workflows/ci.yml
vendored
|
|
@ -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. Seeds: Linux x86-64 and ARM64 (tested here), Windows x86-64 (tested under Wine, and here),
|
# then from itself, and tested, on every system it runs on: Linux x86-64 and ARM64, macOS ARM64 and x86-64, Windows x86-64.
|
||||||
# macOS ARM64 and x86-64 (made with Apple's headers; only checked as IR until the macos job below passes).
|
# A tag v* also makes the release: every package below is attached to it.
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
|
@ -75,7 +75,14 @@ jobs:
|
||||||
fi
|
fi
|
||||||
|
|
||||||
macos:
|
macos:
|
||||||
runs-on: macos-15
|
strategy:
|
||||||
|
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)
|
||||||
|
|
@ -89,6 +96,14 @@ 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
|
||||||
|
|
@ -116,6 +131,16 @@ 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
|
||||||
|
|
@ -136,3 +161,27 @@ 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
|
||||||
|
|
|
||||||
37
README.md
37
README.md
|
|
@ -40,6 +40,8 @@ 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)
|
||||||
|
|
@ -48,13 +50,17 @@ 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 8.6k lines. It is built from a
|
- **Written in itself.** The compiler is Finch code, and nothing else: `src/*.fch`, about 12k lines. It is built from a
|
||||||
seed (its own LLVM IR, `bootstrap/`) and then from itself, and the two come out identical. Its first version, in C++,
|
seed (its own LLVM IR, `bootstrap/`) and then from itself, and the two come out identical.
|
||||||
is kept on the `before-bootstrap` branch.
|
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
**Building it** (Linux x86-64 or ARM64; needs `clang` and libclang, LLVM 21):
|
**Ready builds** for Linux (x64, ARM64), macOS (Apple Silicon, Intel) and Windows (x64) are in the
|
||||||
|
[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
|
||||||
|
|
@ -64,13 +70,12 @@ 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 refusal, -g, a shared library, the language server
|
tests/run.sh # every program, every expected error, -g, a shared library, the language server
|
||||||
```
|
```
|
||||||
|
|
||||||
Ready builds of the compiler are in the [releases](https://github.com/Nexoniarz/Finch/releases) (Linux x64 and ARM64:
|
The same `bootstrap/build.sh` works on a Mac (with Homebrew's `llvm@21`) and on Windows (with llvm-mingw): see
|
||||||
unpack, keep `runtime/` and `std/` next to `finch`). **Programs for Windows, ARM64 and macOS** are built from Linux with
|
[bootstrap/README.md](bootstrap/README.md). Programs for another system are built with `--target windows|linux|arm64|macos`
|
||||||
`--target windows|arm64|macos` (see the [guide](docs/en/for-technicians.md#3-the-finch-command)); the compiler itself does not run on
|
(see the [guide](docs/en/for-technicians.md#building-for-another-system)).
|
||||||
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`).
|
||||||
|
|
@ -91,13 +96,15 @@ 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]` |
|
| 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`… |
|
||||||
| 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 |
|
||||||
|
|
@ -108,7 +115,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`, `-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`, `--cpu native`, `-g` for gdb/lldb, `-O0`, the chain of calls on runtime errors |
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
|
@ -122,6 +129,7 @@ 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 |
|
||||||
|
|
@ -151,14 +159,17 @@ 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` (compiles; NDK linking and devices untried)
|
- [x] Threads (`import thread`) and atomics; C++ files; `--target android`
|
||||||
|
- [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
|
std/ the standard library, in Finch: json, path, time, proc, fs, thread
|
||||||
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
|
||||||
|
|
|
||||||
177
README.pl.md
177
README.pl.md
|
|
@ -5,8 +5,8 @@
|
||||||
</picture>
|
</picture>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
**Mały, zwinny, skuteczny.** Język programowania na LLVM ze składnią podobną do C,
|
**Mały, szybki, celny.** Język programowania oparty na LLVM, ze składnią w stylu C. Nauczysz się go w godzinę,
|
||||||
na tyle prosty, że nauczysz się go w godzinę, i tak szybki jak C.
|
a programy działają tak szybko jak w 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ść od", imie, "!")
|
print("Cześć, tu", 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,51 +29,58 @@ fn main() {
|
||||||
|
|
||||||
## Dlaczego Finch
|
## Dlaczego Finch
|
||||||
|
|
||||||
- **Prosty.** Bez średników, bez plików nagłówkowych, bez preprocesora. Każdą rzecz robi się na jeden sposób.
|
- **Prosty.** Nie ma średników, plików nagłówkowych ani preprocesora. Na każdą rzecz jest jeden sposób.
|
||||||
- **Szybki.** Kompiluje się przez potok `-O2` z LLVM do kodu natywnego, bez garbage collectora.
|
- **Szybki.** Kod przechodzi przez optymalizacje LLVM (`-O2`) i staje się zwykłym programem, bez garbage collectora.
|
||||||
Rekurencyjne `fib(40)` trwa ok. 0,19 s (`clang -O2` na tym samym kodzie C: ok. 0,23 s), a kod na tablicach
|
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
|
||||||
z kontrolą zakresu dorównuje C.
|
ze sprawdzaniem zakresu są tak samo szybkie jak w C.
|
||||||
- **Pamięć bez bólu.** Listy, teksty i struktury są wartościami: przypisanie kopiuje, a koniec bloku je zwalnia.
|
- **Pamięć bez kłopotów.** Listy, teksty i struktury są wartościami: przypisanie robi kopię, a koniec bloku sprząta.
|
||||||
Nie ma `free`, o którym można zapomnieć, nie ma użycia po zwolnieniu, a każdy test jest czysty pod valgrindem.
|
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.
|
||||||
- **Bezpieczniejszy niż C tam, gdzie to tanie.** Nie ma cichego zwężania typów ani mieszania liczb ze znakiem
|
- **Bezpieczniejszy niż C tam, gdzie nic to nie kosztuje.** Liczby nie zmieniają po cichu typu, a liczb ze znakiem
|
||||||
i bez znaku. Indeks poza zakresem, dzielenie przez zero i null zatrzymują program czytelnym błędem, zamiast
|
i bez znaku nie da się przypadkiem pomieszać. Indeks poza tablicą, dzielenie przez zero czy `null` kończą program
|
||||||
powodować niezdefiniowane zachowanie. Priorytety operatorów nie gryzą (`x & 1 == 0` znaczy to, co widać).
|
jasnym komunikatem, a nie niezdefiniowanym zachowaniem. Kolejność działań jest taka, jakiej się spodziewasz
|
||||||
- **Praktyczny.** Mapy, metody, błędy jako wartości (`int(s) or 0`, `try read_file(p)`), moduły, każda
|
(`x & 1 == 0` znaczy dokładnie to, co widać).
|
||||||
biblioteka C i serwer języka dla twojego edytora. Działa na Linuksie, macOS i Windowsie, na x86-64 i ARM64.
|
- **Praktyczny.** Mapy, metody, błędy jako wartości (`int(s) or 0`, `try read_file(p)`), moduły, dowolna biblioteka C
|
||||||
- **Błędy mówią po ludzku:**
|
i serwer języka dla edytora. Działa na Linuksie, macOS i Windowsie, na procesorach x86-64 i ARM64.
|
||||||
|
- **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
|
||||||
| ^
|
| ^
|
||||||
```
|
```
|
||||||
- **Każda biblioteka C, bezpośrednio.** Piszesz `import "raylib.h"` i `link "raylib"`, i możesz z niej korzystać,
|
- **Biblioteki C bez pośredników.** Wystarczy `import "raylib.h"` i `link "raylib"`, żeby z niej korzystać, łącznie
|
||||||
także ze struktur przekazywanych przez wartość i z callbacków. Nagłówki czyta libclang, więc nie trzeba niczego deklarować ręcznie.
|
ze strukturami przekazywanymi przez wartość i funkcjami zwrotnymi. Nagłówki czyta libclang, więc niczego nie trzeba
|
||||||
- **Napisany w sobie.** Kompilator to kod w Finchu i nic więcej: `src/*.fch`, ok. 8,6 tys. linii. Buduje się go z ziarna
|
deklarować ręcznie.
|
||||||
(własny kod LLVM IR, `bootstrap/`), potem z samego siebie, a oba wyniki są identyczne. Pierwsza wersja, w C++,
|
- **Napisany w samym sobie.** Kompilator to wyłącznie kod w Finchu: `src/*.fch`, ok. 12 tys. linii. Buduje się go
|
||||||
jest w gałęzi `before-bootstrap`.
|
z pliku startowego (jego własnego kodu LLVM IR, w `bootstrap/`), a potem jeszcze raz nim samym. Oba wyniki są identyczne.
|
||||||
|
|
||||||
## Szybki start
|
## Na start
|
||||||
|
|
||||||
**Budowanie** (Linux x86-64 lub ARM64; potrzebny `clang` i libclang, LLVM 21):
|
**Gotowe kompilatory** na Linuksa (x64, ARM64), macOS (Apple Silicon, Intel) i Windowsa (x64) znajdziesz w
|
||||||
|
[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 samodzielnie clang + libclang (LLVM 21) + pkg-config
|
nix-shell # albo zainstaluj clang, libclang (LLVM 21) i pkg-config po swojemu
|
||||||
bootstrap/build.sh # kompilator to build/finch: z ziarna, potem z samego siebie
|
bootstrap/build.sh # gotowy kompilator to build/finch
|
||||||
|
|
||||||
./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 # każdy program, każda odmowa, -g, biblioteka współdzielona, serwer języka
|
tests/run.sh # wszystkie programy testowe, oczekiwane błędy, -g, biblioteka współdzielona, serwer języka
|
||||||
```
|
```
|
||||||
|
|
||||||
Gotowe kompilatory są w [wydaniach](https://github.com/Nexoniarz/Finch/releases) (Linux x64 i ARM64: rozpakuj, zostaw
|
Ten sam `bootstrap/build.sh` działa na Macu (z `llvm@21` z Homebrew) i na Windowsie (z llvm-mingw); szczegóły są w
|
||||||
`runtime/` i `std/` obok `finch`). **Programy dla Windowsa, ARM64 i macOS** buduje się z Linuksa przez
|
[bootstrap/README.md](bootstrap/README.md). Program na inny system zbudujesz opcją `--target windows|linux|arm64|macos`
|
||||||
`--target windows|arm64|macos`; sam kompilator nie działa jeszcze na macOS ani Windowsie (potrzebuje ziarna
|
(zob. [przewodnik](docs/pl/dla-technikow.md#budowanie-na-inny-system)).
|
||||||
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 dowolny
|
Edytory: **Kate** (`editors/kate/install.sh`), **VS Code** (plik `.vsix` `finch-lang` z wydania) albo każdy edytor,
|
||||||
edytor z obsługą LSP (`finch lsp`).
|
który obsługuje LSP (`finch lsp`).
|
||||||
|
|
||||||
## Dokumentacja
|
## Dokumentacja
|
||||||
|
|
||||||
|
|
@ -81,90 +88,96 @@ Wybierz przewodnik dla siebie:
|
||||||
|
|
||||||
| Przewodnik | Dla kogo | Polski | English |
|
| Przewodnik | Dla kogo | Polski | English |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| **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 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) |
|
||||||
| **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 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) |
|
||||||
| **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) |
|
| **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) |
|
||||||
| **Ściąga** | Każda funkcja, metoda, operator i polecenie na jednej stronie | [sciaga.md](docs/pl/sciaga.md) | [cheatsheet.md](docs/en/cheatsheet.md) |
|
| **Ściąga** | Wszystkie funkcje, metody, operatory i polecenia 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 zgadnięty) albo `int x = 5`; `const W = 640` |
|
| Zmienne | `x := 5` (typ wynika z wartości) albo `int x = 5`; `const W = 640` |
|
||||||
| Typy | `int` `float` `bool` `char` `str`, z rozmiarem `i8`…`i64` `u8`…`u64` `f32` `f64`, tablice `[]T`, mapy `map[K]V`, wskaźniki `ptr[T]` |
|
| 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`… |
|
||||||
| Funkcje | `fn add(int a, int b) -> int { return a + b }` |
|
| Funkcje | `fn dodaj(int a, int b) -> int { return a + b }` |
|
||||||
| Struktury | `struct Punkt { … }` z jednym polem na linijkę (`int x`, `int y = 0`); tworzenie przez `Punkt(1, 2)` albo `Punkt(x: 1)` |
|
| Struktury | `struct Punkt { … }`, jedno pole w linii (`int x`, `int y = 0`); tworzy się je 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("cy")`, `ile[s] += 1`, `for k, v in wiek { }` |
|
| Mapy | `wiek := ["ania": 31]`, `wiek["bob"] = 25`, `wiek.has("cyryl")`, `ile[slowo] += 1`, `for k, v in wiek { }` |
|
||||||
| Błędy | `fn parsuj(str s) -> int! { … return error("zły") }` → `parsuj(s) or 0`, `parsuj(s) or { … }`, `try parsuj(s)` |
|
| Błędy | `fn parsuj(str s) -> int! { … return error("zła liczba") }` → `parsuj(s) or 0`, `parsuj(s) or { … }`, `try parsuj(s)` |
|
||||||
| Tekst | `"hp \(hp)"`, `"a" + "b"`, `str(42)`, `int("42")`, `s.split(",")`, `s.upper()`, `s[0]` |
|
| 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)` |
|
||||||
| Sterowanie | `if` / `else if` / `else` (też jako wartość), `while`, `for i in 0..10`, `break`, `continue`, `defer` |
|
| Procesory | `--cpu native`; `@cpu("avx2") fn …` razem z `if cpu_has("avx2")`; liczby zmiennoprzecinkowe liczone tak, jak zapisane (`fma`/`muladd` do łączenia) |
|
||||||
| Pamięć | automatyczna dla tablic, tekstów i struktur; `new(...)` / `free(...)` dla własnych struktur na stercie |
|
| Tekst | `"hp: \(hp)"`, `"a" + "b"`, `str(42)`, `int("42")`, `s.split(",")`, `s.upper()`, `s[0]` |
|
||||||
| Enumy, match | `enum Kolor { Czerwony, Zielony }`, `match k { Czerwony -> ... else -> ... }` (także jako wartość) |
|
| Sterowanie | `if` / `else if` / `else` (również jako wartość), `while`, `for i in 0..10`, `break`, `continue`, `defer` |
|
||||||
| Generyki | `fn max[T](T a, T b) -> T`, `struct Stos[T]`, `Stos[int]()` |
|
| Pamięć | tablice, teksty i struktury zwalniają się same; `new(...)` / `free(...)` dla własnych struktur na stercie |
|
||||||
|
| 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) }` obok kodu, uruchamiane przez `finch test` |
|
| Testy | `test "nazwa" { assert(x == 3) }` tuż obok kodu, uruchamiane przez `finch test` |
|
||||||
| Współpraca z C | `import "stdio.h"`, `link "glfw"`, `link "moje.c"` / `"glue.cpp"`, unie, `#define` (też `(Color){…}`), `addr(fn)` dla callbacków |
|
| Współpraca z C | `import "stdio.h"`, `link "glfw"`, `link "moje.c"` / `"glue.cpp"`, unie, `#define` (także `(Color){…}`), `addr(fn)` jako funkcja zwrotna |
|
||||||
| Wejście/wyjście | `print(...)`, `input("? ")`, `read_file`, `write_file`, `shell` |
|
| Wejście i wyjście | `print(...)`, `input("? ")`, `read_file`, `write_file`, `shell` |
|
||||||
| 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 |
|
| 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 |
|
||||||
|
|
||||||
## Przykłady
|
## Przykłady
|
||||||
|
|
||||||
| Plik | Pokazuje |
|
| Plik | Co pokazuje |
|
||||||
|---|---|
|
|---|---|
|
||||||
| [`examples/hello.fch`](examples/hello.fch) | najmniejszy program |
|
| [`examples/hello.fch`](examples/hello.fch) | najkrótszy 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: wejście, pętle, `rand` z C |
|
| [`examples/guess.fch`](examples/guess.fch) | gra w zgadywanie liczby: wczytywanie, 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 wiązana z `new`/`free` |
|
| [`examples/structs.fch`](examples/structs.fch) | struktury, tablice struktur, lista jednokierunkowa z `new`/`free` |
|
||||||
| [`examples/types.fch`](examples/types.fch) | liczby z rozmiarem i wskaźniki |
|
| [`examples/types.fch`](examples/types.fch) | liczby o podanym rozmiarze 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/window.fch`](examples/window.fch) | okno GLFW + OpenGL |
|
| [`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 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, ich pamięć i kolejki, tworzenie urządzenia |
|
| [`examples/vulkan.fch`](examples/vulkan.fch) | Vulkan: lista kart graficznych z ich pamięcią i kolejkami, utworzenie urządzenia |
|
||||||
| [`examples/raylib.fch`](examples/raylib.fch) | raylib ze strukturami C przez wartość |
|
| [`examples/raylib.fch`](examples/raylib.fch) | raylib i struktury C przekazywane przez wartość |
|
||||||
| [`examples/llvm.fch`](examples/llvm.fch) | Finch budujący LLVM IR przez API LLVM-C |
|
| [`examples/llvm.fch`](examples/llvm.fch) | Finch, który buduje LLVM IR przez API LLVM-C |
|
||||||
| [`src/`](src/) | kompilator napisany w Finchu (ziarno do jego zbudowania jest w [`bootstrap/`](bootstrap/)) |
|
| [`src/`](src/) | kompilator napisany w Finchu (plik startowy do jego zbudowania leży 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 z rozmiarem, wskaźniki, konwersje; funkcje; `if`/`while`/`for`
|
- [x] Typy, w tym liczby o podanym rozmiarze, wskaźniki i konwersje; funkcje; `if`/`while`/`for`
|
||||||
- [x] Czytelne błędy kompilacji; kontrole w czasie działania (dzielenie, null, zakres)
|
- [x] Czytelne błędy kompilacji; sprawdzanie w trakcie działania (dzielenie, `null`, zakres tablicy)
|
||||||
- [x] Operatory bitowe z rozsądnymi priorytetami
|
- [x] Operatory bitowe z rozsądną kolejnością działań
|
||||||
- [x] Nagłówki C przez libclang; `link` z pkg-config; porady przy błędach linkowania
|
- [x] Nagłówki C przez libclang; `link` z pkg-config; podpowiedzi przy błędach linkera
|
||||||
- [x] `struct`, także struktury C przez wartość (ABI System V) i callbacki
|
- [x] `struct`, w tym struktury C przekazywane przez wartość (ABI System V) i funkcje zwrotne
|
||||||
- [x] Tablice z `.len`, kontrolą zakresu i metodami; `for x in lista`
|
- [x] Tablice z `.len`, sprawdzaniem zakresu i metodami; `for x in lista`
|
||||||
- [x] Pamięć: zwalnianie na końcu bloku, `defer`, `new`/`free`
|
- [x] Pamięć: wartości zwalniane 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 buduje sam siebie
|
- [x] Bootstrap: kompilator Fincha napisany w Finchu, który kompiluje 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 VS Code
|
- [x] Serwer języka (`finch lsp`) i rozszerzenie do 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 w strukturach, mapy, błędy jako wartości (`!`, `or`, `try`)
|
- [x] Metody struktur, 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 `#define`
|
- [x] `const`, `if` jako wartość, `"\(x)"` w tekście, tekst `"""`, unie C i wyrażenia w `#define`
|
||||||
- [x] Bloki `test` i `finch test`, `finch fmt`, `finch.toml`, cache budowania, łańcuch wywołań przy błędach
|
- [x] Bloki `test` i `finch test`, `finch fmt`, `finch.toml`, pamięć podręczna kompilacji, ścieżka wywołań przy błędach
|
||||||
- [x] Wątki (`import thread`) i operacje atomowe; pliki C++; `--target android` (kompiluje się; linkowanie z NDK i urządzenia niesprawdzone)
|
- [x] Wątki (`import thread`) i operacje atomowe; pliki C++; `--target android`
|
||||||
|
- [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
|
||||||
|
|
||||||
## Struktura
|
## Co gdzie leży
|
||||||
|
|
||||||
```
|
```
|
||||||
src/ kompilator, w Finchu: lekser, parser, AST, generowanie kodu (do LLVM IR jako tekst), import C (libclang)
|
src/ kompilator w Finchu: lekser, parser, AST, generowanie kodu (LLVM IR jako tekst), import C (libclang)
|
||||||
i konwencje wywołań C, serwer języka, sterownik
|
i konwencje wywołań C, serwer języka, polecenie finch
|
||||||
std/ biblioteka standardowa, w Finchu: json, path, time, proc, fs
|
std/ biblioteka standardowa w Finchu: json, path, time, proc, fs, thread
|
||||||
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 ziarna: własny LLVM IR kompilatora, po jednym na system
|
bootstrap/ build.sh i pliki startowe: własny LLVM IR kompilatora, osobno dla każdego systemu
|
||||||
examples/ przykładowe programy
|
examples/ przykładowe programy
|
||||||
editors/ rozszerzenie VS Code, podświetlanie i konfiguracja LSP dla Kate
|
editors/ rozszerzenie do VS Code, podświetlanie składni i konfiguracja LSP dla Kate
|
||||||
tests/ run/ new/ (wzorcowe wyjście), fail/ new/fail/ (oczekiwane błędy), run.sh, cross.sh, shared.sh, lsp_test.py
|
tests/ run/ new/ (wzorcowe wyniki), fail/ new/fail/ (oczekiwane błędy), run.sh, cross.sh, shared.sh, lsp_test.py
|
||||||
docs/ przewodniki en/ i pl/ dla trzech grup odbiorców
|
docs/ przewodniki po angielsku (en/) i po polsku (pl/) dla trzech grup czytelników
|
||||||
```
|
```
|
||||||
|
|
||||||
## Licencja
|
## Licencja
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,16 @@
|
||||||
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 | Checked |
|
| File | System | Built and tested in CI on |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `linux-x86_64.ll` | Linux x86-64 | built and tested here and in CI |
|
| `linux-x86_64.ll` | Linux x86-64 | ubuntu-24.04 |
|
||||||
| `linux-aarch64.ll` | Linux ARM64 | made with the ARM64 cross compiler; CI builds and tests it on an ARM64 runner |
|
| `linux-aarch64.ll` | Linux ARM64 | ubuntu-24.04-arm |
|
||||||
| `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-arm64.ll` | macOS, Apple Silicon | macos-15 |
|
||||||
| `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) |
|
| `macos-x86_64.ll` | macOS, Intel | macos-15-intel |
|
||||||
|
| `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.
|
||||||
|
|
@ -41,5 +45,6 @@ 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` gives clang and libclang; the CI job sets
|
macOS: the seeds were made with the macOS 11.3 SDK. `brew install llvm@21`
|
||||||
`FINCH_CLANG`, `FINCH_LIBCLANG` and `FINCH_LLVM_INCLUDE`. If the macOS build fails, that job is where it shows first.
|
gives clang and libclang; set `FINCH_CLANG`, `FINCH_LIBCLANG` and `FINCH_LLVM_INCLUDE` as the CI job does (the
|
||||||
|
[technicians' guide](../docs/en/for-technicians.md#option-b-build-it-yourself) has the lines).
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,9 @@ 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, Visual Studio) |
|
| `-g` | debug info (gdb, lldb) |
|
||||||
| `-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).
|
||||||
|
|
@ -39,6 +40,7 @@ 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"]
|
||||||
|
|
@ -96,6 +98,7 @@ 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 \\ \" \'`.
|
||||||
|
|
@ -240,6 +243,34 @@ 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 |
|
||||||
|
|
@ -300,6 +331,9 @@ 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) |
|
||||||
|
|
|
||||||
|
|
@ -53,8 +53,9 @@ The computer reads your program from top to bottom, line by line.
|
||||||
|
|
||||||
## 2. Getting Finch ready
|
## 2. Getting Finch ready
|
||||||
|
|
||||||
Finch runs on **Linux** computers. If a teacher, parent or friend has already set it up
|
Finch runs on **Linux**, **macOS** and **Windows** computers. If a teacher, parent or friend has already set it up
|
||||||
for you, skip to the next section.
|
for you, skip to the next section. (On a Mac or on Windows, ask someone to follow the
|
||||||
|
[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:
|
||||||
|
|
@ -76,7 +77,7 @@ To check it works, type:
|
||||||
./build/finch version
|
./build/finch version
|
||||||
```
|
```
|
||||||
|
|
||||||
You should see something like `Finch 2.5.0`.
|
You should see something like `Finch 2.6.0`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
# Finch for Engineers
|
# Finch for Engineers
|
||||||
|
|
||||||
**The internals of Finch 2.5, whose compiler is written in Finch.** How source becomes tokens, an AST and LLVM IR *as text*,
|
**The internals of Finch 2.6, 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)
|
||||||
|
|
||||||
|
|
@ -34,7 +32,7 @@ the Finch one.
|
||||||
## 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 8k lines) and what lets it be written in Finch itself.
|
generation and linking. That is what makes it small (about 12k 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)
|
||||||
|
|
@ -60,7 +58,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 tiny C runtime** (`runtime/finch_rt.c`, ~760 lines) does what is tedious in IR: strings, array growth, maps, input,
|
- **A small C runtime** (`runtime/finch_rt.c`, ~1150 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`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -70,17 +68,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` | 120 | One `Node` struct with a `kind` string, children, type names, positions (`line`, `endLine`) |
|
| `src/ast.fch` | 170 | 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` | 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/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/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` | 90 | `Target`: triple, data layout, C compiler, signedness of `char` per system |
|
| `src/platform.fch` | 120 | `Target`: triple, data layout, C compiler, signedness of `char` per system |
|
||||||
| `src/linker.fch` | 430 | The cache (runtime, program object, C/C++ files), libraries (`link`, pkg-config), link-error advice |
|
| `src/linker.fch` | 480 | The cache (runtime, program object, C/C++ files), libraries (`link`, pkg-config), link-error advice |
|
||||||
| `src/main.fch` | 515 | Driver: CLI, loader with module lookup and overlays, `build/run/test/fmt/ir/index/lsp` |
|
| `src/main.fch` | 530 | 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` | 910 | The language server (§11) |
|
| `src/lsp.fch` | 950 | The language server (§11) |
|
||||||
| `src/docs.fch` | 110 | One description of the built-ins, shared by hover and completion |
|
| `src/docs.fch` | 140 | 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) |
|
||||||
|
|
@ -116,18 +114,19 @@ 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. The passes are:
|
`gen.fch` is one big `Gen` struct plus functions over it. `generate()` runs:
|
||||||
|
|
||||||
1. `declareStructs`: every Finch and C struct gets an LLVM type (C structs with their real padding); recursive-by-value
|
1. `declare()`: every Finch and C struct gets an LLVM type (C structs with their real padding; recursive-by-value
|
||||||
structs are refused.
|
structs are refused), then every function gets its signature, with a *borrow analysis* for parameters (§8.2).
|
||||||
2. `declareFns`: signatures, with a *borrow analysis* for parameters (a parameter that is never stored is passed without
|
2. `define()` for each function: the body, type checked and emitted. Scopes track what must be dropped when they end.
|
||||||
a copy).
|
Instantiating a generic adds functions while this goes on, so the loop runs until no new ones appear.
|
||||||
3. `define`: each body, type checked and emitted. Scopes track what must be dropped when they end (`defer` runs first).
|
3. The C entry point `main(argc, argv)` (or, for `--shared`, `finch_module_on_fatal` and the `export fn` wrappers).
|
||||||
4. `defineMainWrapper`: the C `main(argc, argv)`, and `finch_on_fatal` for embedders.
|
4. `assemble()`: the declarations, struct types, string constants, helpers and bodies, as one `.ll` text.
|
||||||
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 a mangled name (`max$int`). `match` over an enum is exhaustive (a missing variant is an error, `else` covers the
|
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
|
||||||
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.
|
||||||
|
|
||||||
|
|
@ -164,10 +163,13 @@ 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] [--shared] [--overlay path=file]`.
|
`finch build|run|ir|index file.fch [-o out] [-O0] [-g] [-l lib] [-I dir] [--target t] [--cpu c] [--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
|
||||||
|
|
@ -210,6 +212,7 @@ 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
|
||||||
|
|
@ -222,9 +225,10 @@ 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 }` (entries, len, used, cap, index, icap; see src/gen.fch) |
|
| `map[K]V` | `{ ptr, i64, i64, i64, ptr, i64, i64 }` (entries, len, used, cap, index, icap, adds; 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 `-> !`) |
|
||||||
|
|
@ -235,11 +239,16 @@ Signedness lives only in the Finch type and selects `sdiv/udiv`, `ashr/lshr`, `i
|
||||||
|
|
||||||
### Values
|
### Values
|
||||||
|
|
||||||
```cpp
|
```c
|
||||||
struct Value_ { Value *v; FType type; bool literal; bool fresh; };
|
struct Val {
|
||||||
|
str v // the IR operand: %t12, 42, @.str.3, zeroinitializer
|
||||||
|
ast.Type ty
|
||||||
|
bool fresh
|
||||||
|
bool lit
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- **`literal`**: a number written in the code or an imported C constant. It may become any integer type
|
- **`lit`**: 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,
|
||||||
|
|
@ -281,13 +290,64 @@ 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
|
||||||
|
|
||||||
### 9.1 The model
|
### 8.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
|
||||||
|
|
@ -311,21 +371,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.
|
||||||
|
|
||||||
### 9.2 Parameter borrowing: `mutates()`
|
### 8.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 (`Fn::borrowParam`); changed ones are copied once at entry. The analysis is
|
parameters are borrowed (`FnSig.borrow`); 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: `declareFns()` first finds which methods change `self` (`mutates("self", body)`),
|
Methods take part: `declare()` 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 `mutatingMethods`, which `mutatesExpr()` treats like `push`: `p.move(1)` changes `p`.
|
names go into `g.mm`, which `mutates()` treats like `push`: `p.move(1)` changes `p`.
|
||||||
Only then is `borrowParam` computed for every function and method.
|
Only then is `borrow` computed for every function and method.
|
||||||
|
|
||||||
### 9.3 Copy and drop
|
### 8.3 Copy and drop
|
||||||
|
|
||||||
`dropAt(addr, T)` / `copyAt(dst, src, T)` work on addresses:
|
`dropAt(addr, T)` / `copyAt(dst, src, T)` work on addresses:
|
||||||
|
|
||||||
|
|
@ -335,10 +395,10 @@ Only then is `borrowParam` 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 (`HelperScope` saves the builder position and switches off
|
Helpers are emitted in their own functions (`beginHelper()` / `endHelper()` set the function being written aside
|
||||||
debug locations), may call each other recursively (`struct Node { []Node kids }`), and are inlined by O2.
|
and switch off debug locations), may call each other recursively (`struct Node { []Node kids }`), and are inlined by O2.
|
||||||
|
|
||||||
### 9.4 What it looks like
|
### 8.4 What it looks like
|
||||||
|
|
||||||
```c
|
```c
|
||||||
fn greet(str name) -> str {
|
fn greet(str name) -> str {
|
||||||
|
|
@ -381,22 +441,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).
|
||||||
|
|
||||||
### 9.5 defer
|
### 8.5 defer
|
||||||
|
|
||||||
`defer stmt` pushes a `Cleanup{isDefer, body, order = current varOrder}` into the current scope; it
|
`defer stmt` records the statement in the current scope (`Scope.defers`, with `orders`: how many variables existed
|
||||||
emits nothing there. Whenever the scope's cleanups are emitted (falling off the end of the block,
|
then) and a `Cleanup{isDefer}`; it emits nothing there. Whenever the scope's cleanups are emitted (falling off the end of the block,
|
||||||
`return`, `break`/`continue` leaving it) the body is generated **at that exit**, with `deferLimit = order`
|
`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.
|
||||||
|
|
||||||
### 9.6 Manual memory
|
### 8.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.
|
||||||
|
|
||||||
### 9.7 Verifying it
|
### 8.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,
|
||||||
|
|
@ -405,8 +465,6 @@ 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
|
||||||
|
|
@ -428,8 +486,9 @@ 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).
|
A seed is per system because the C calling convention for the libclang calls is baked into it (§5). There are seeds
|
||||||
Linux x86-64 and Linux ARM64 have seeds. macOS and Windows would need seeds made with Apple's and Microsoft's headers.
|
for Linux x86-64 and ARM64, macOS ARM64 and x86-64 (made with Apple's SDK headers), and Windows x86-64 (made with
|
||||||
|
llvm-mingw's headers). CI builds the compiler from each one on that system and runs the tests there.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -445,16 +504,17 @@ 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 (22 checks).
|
test.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 12. Semantics vs. C
|
## 12. Semantics vs. C
|
||||||
|
|
||||||
| Situation | C | Finch 2.0 |
|
| Situation | C | Finch |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 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 |
|
||||||
|
|
@ -467,13 +527,22 @@ test (22 checks).
|
||||||
| 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 | still poison (not checked yet) |
|
| Shift ≥ width, out-of-range float→int | UB | poison, as in LLVM (not checked) |
|
||||||
|
| `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
|
||||||
|
|
@ -518,13 +587,10 @@ 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).
|
||||||
- `--target android` is checked to the object file only; linking needs the NDK and has not been run on a device.
|
- Vectors have no integer division, and `lookup` on baseline x86-64 (no SSSE3) is done lane by lane.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -67,19 +68,24 @@ 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**
|
The Finch compiler is written in Finch (see the [engineers' guide](for-engineers.md)). It runs on **Linux** (x86-64 and
|
||||||
(x86-64 and ARM64) and builds programs for **Linux, Windows and macOS**. It needs **clang 21** and **libclang**
|
ARM64), **macOS** (Apple Silicon and Intel) and **Windows** (x86-64), and builds programs for all of them. On every
|
||||||
(LLVM 21) to read C headers and to compile its output.
|
system it needs **clang** (it compiles each program with it) and **libclang** from LLVM 21 (it reads C headers with it).
|
||||||
|
|
||||||
The compiler does not run on macOS or Windows yet; it needs a seed made with that system's headers
|
### Option A: a ready build
|
||||||
([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.
|
|
||||||
|
|
||||||
### Option A: a ready build (Linux)
|
Download the archive for your system from the [releases](https://github.com/Nexoniarz/Finch/releases) and unpack it.
|
||||||
|
Keep `runtime/` and `std/` next to the `finch` file (or install it, below).
|
||||||
|
|
||||||
Download `finch-linux-x64.tar.gz` or `finch-linux-arm64.tar.gz` from the
|
| System | Archive | Also install |
|
||||||
[releases](https://github.com/Nexoniarz/Finch/releases) and unpack it. Keep `runtime/` and `std/` next to the `finch` file
|
|---|---|---|
|
||||||
(or install it, below). You also need `clang` 21 and its libclang library:
|
| Linux x86-64 | `finch-linux-x64.tar.gz` | clang 21 and libclang (table below) |
|
||||||
|
| 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 |
|
||||||
|---|---|
|
|---|---|
|
||||||
|
|
@ -88,12 +94,15 @@ Download `finch-linux-x64.tar.gz` or `finch-linux-arm64.tar.gz` from the
|
||||||
| 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 # clang, libclang, pkg-config (or install them with your distribution)
|
nix-shell # Linux: 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
|
||||||
```
|
```
|
||||||
|
|
@ -102,6 +111,26 @@ 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:
|
||||||
|
|
@ -151,7 +180,9 @@ 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
|
||||||
-g add debug info (for gdb / lldb / Visual Studio)
|
--cpu <name> use every instruction of a processor: native (this one), x86-64-v3 (AVX2),
|
||||||
|
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)
|
||||||
|
|
@ -314,6 +345,40 @@ 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
|
||||||
|
|
@ -666,6 +731,8 @@ 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. |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -946,8 +1013,7 @@ 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, `-g` writes CodeView, which the
|
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.
|
||||||
Visual Studio debugger and WinDbg read.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -959,10 +1025,11 @@ Visual Studio debugger and WinDbg read.
|
||||||
| `… 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: `finch` can't link (`LNK…` errors about `libcmt`, `kernel32`) | Install Visual Studio Build Tools with *Desktop development with C++*. |
|
| Windows: `no C compiler found to link with ('clang')` | Put llvm-mingw's `bin` folder on your PATH (section 2). |
|
||||||
| 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`. |
|
||||||
|
|
@ -980,7 +1047,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, c_import, window, opengl, vulkan, raylib, llvm
|
├── examples/ hello, tour, types, structs, todo, guess, wordcount, c_import, simd, 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
|
||||||
|
|
@ -994,35 +1061,12 @@ Finch/
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
tests/run.sh # programs, refusals, -g builds, a shared library, the language server
|
tests/run.sh # programs, expected errors, -g builds, a shared library, the language server
|
||||||
MEMCHECK=1 tests/run.sh # the same under valgrind: no leaks, no bad memory access
|
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
|
||||||
|
|
@ -1069,6 +1113,29 @@ 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:
|
||||||
|
|
@ -1134,7 +1201,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. `--target android` makes code for Android, but linking with the NDK and running on a device are untried.
|
- Windows on ARM64, 32-bit systems, WebAssembly.
|
||||||
|
|
||||||
### Warnings about big copies
|
### Warnings about big copies
|
||||||
|
|
||||||
|
|
@ -1193,6 +1260,7 @@ 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"]
|
||||||
|
|
@ -1292,6 +1360,195 @@ 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`). This is new and only partly checked: Finch's
|
SDL3's Android project (the library's `SDL_main` is an `export fn`). Thread-local data goes through the emulated TLS
|
||||||
output compiles for Android (thread-local data goes through the emulated TLS older Android needs), but linking with the
|
that older Android versions need.
|
||||||
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.
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
# Finch dla inżynierów
|
# Finch dla inżynierów
|
||||||
|
|
||||||
**Wnętrze Fincha 2.5, którego kompilator jest napisany w Finchu.** Jak kod źródłowy staje się tokenami, drzewem AST i
|
**Jak od środka działa Finch 2.6, którego kompilator jest napisany w Finchu.** Jak kod źródłowy zamienia się w tokeny,
|
||||||
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
|
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ą
|
||||||
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
|
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 ziarna; oraz serwer języka.
|
wartości; jak kompilator buduje sam siebie z pliku startowego; i jak działa serwer języka.
|
||||||
|
|
||||||
Zakładam, że czytasz Fincha (zobacz [przewodnik dla techników](dla-technikow.md)) i widziałeś już LLVM IR.
|
Zakładam, że umiesz czytać kod w Finchu (zob. [przewodnik dla techników](dla-technikow.md)) i że LLVM IR nie jest ci obcy.
|
||||||
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)
|
||||||
|
|
||||||
|
|
@ -15,194 +14,210 @@ Pierwsza wersja kompilatora, w C++ na API C++ LLVM, jest w gałęzi `before-boot
|
||||||
## Spis treści
|
## Spis treści
|
||||||
|
|
||||||
1. [Architektura](#1-architektura)
|
1. [Architektura](#1-architektura)
|
||||||
2. [Drzewo źródeł](#2-drzewo-źródeł)
|
2. [Pliki źródłowe](#2-pliki-źródłowe)
|
||||||
3. [Front end: lekser, parser, moduły](#3-front-end-lekser-parser-moduły)
|
3. [Część frontowa: lekser, parser, moduły](#3-część-frontowa-lekser-parser-moduły)
|
||||||
4. [Generowanie kodu](#4-generowanie-kodu)
|
4. [Generowanie kodu](#4-generowanie-kodu)
|
||||||
5. [Import C i ABI C](#5-import-c-i-abi-c)
|
5. [Import z C i ABI języka C](#5-import-z-c-i-abi-języka-c)
|
||||||
6. [Sterownik, linkowanie i cele](#6-sterownik-linkowanie-i-cele)
|
6. [Sterownik, linkowanie i systemy docelowe](#6-sterownik-linkowanie-i-systemy-docelowe)
|
||||||
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. [Generyki, enumy i match](#9-generyki-enumy-i-match)
|
9. [Typy generyczne, enumy i match](#9-typy-generyczne-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 a C](#12-semantyka-a-c)
|
12. [Semantyka w porównaniu z C](#12-semantyka-w-porównaniu-z-c)
|
||||||
13. [Znane ograniczenia](#13-znane-ograniczenia)
|
13. [Znane ograniczenia](#13-znane-ograniczenia)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Architektura
|
## 1. Architektura
|
||||||
|
|
||||||
W kompilatorze nie ma biblioteki LLVM. Pisze LLVM IR jako **tekst**, a optymalizację, generowanie kodu i linkowanie zostawia
|
W kompilatorze nie ma biblioteki LLVM. Zapisuje on LLVM IR jako **tekst**, a optymalizację, generowanie kodu maszynowego
|
||||||
`clangowi`. Dzięki temu jest mały (ok. 8 tys. linii) i może być napisany w samym Finchu.
|
i linkowanie zostawia `clangowi`. Dzięki temu jest mały (ok. 12 tys. linii) i dało się go napisać 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, stałe C
|
`import "x.h"` ─► cimport.fch (libclang wołany z Fincha) ─► funkcje, struktury i stałe C
|
||||||
│
|
│
|
||||||
▼
|
▼
|
||||||
gen.fch ─► generate(): struktury, sygnatury, ciała funkcji
|
gen.fch ─► generate(): deklaracje struktur i funkcji, potem ciała funkcji;
|
||||||
kontrola typów i IR w jednym przejściu; klasyfikacja ABI wywołań C
|
sprawdzanie typów i emisja IR w jednym przejściu; klasyfikacja ABI dla 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 projektowe:
|
Decyzje, od których zależy cała reszta:
|
||||||
|
|
||||||
- **Szybko się przerywa.** Pierwszy błąd wypisuje `plik:linia:kol: error: …` z linią źródła i strzałką, i kompilator kończy.
|
- **Pierwszy błąd kończy pracę.** Kompilator wypisuje `plik:linia:kolumna: error: …` z linią źródła i strzałką,
|
||||||
Bez odzyskiwania po błędach, więc kolejne fazy nie widzą półpoprawnych danych.
|
po czym się zatrzymuje. Nie próbuje kontynuować po błędzie, więc dalsze etapy nigdy nie widzą częściowo poprawnego wejścia.
|
||||||
- **Jedno przejście.** `gen.fch` sprawdza typy i emituje IR jednym przejściem po AST. Wyrażenie daje operand IR, typ Fincha
|
- **Jedno przejście.** `gen.fch` sprawdza typy i emituje IR w jednym przejściu po AST. Każde wyrażenie daje operand IR,
|
||||||
(napis: `int`, `f32`, `str`, `array`, `map`, `struct`, `enum`, `named`…) i flagi.
|
swój typ Fincha (rodzaj zapisany tekstem: `int`, `f32`, `str`, `array`, `map`, `struct`, `enum`, `vec`, `named`…) i flagi.
|
||||||
- **Niech clang pracuje.** Zmienne lokalne to `alloca`, zamieniane przez `mem2reg`. Optymalizacja to `-O2` clanga (albo `-O0` z `-g`).
|
- **Resztę robi clang.** Zmienne lokalne to `alloca`, które `mem2reg` zamienia na rejestry. Optymalizuje `-O2` clanga
|
||||||
- **Wartości, nie referencje.** Tablice, teksty i struktury mają semantykę wartości; własność ustala się w czasie kompilacji (§8).
|
(albo `-O0` przy `-g`).
|
||||||
- **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,
|
- **Wartości, nie referencje.** Tablice, teksty i struktury zachowują się jak wartości; o własności decyduje kompilator (§8).
|
||||||
wejście, pliki, paniki. Kompilowany raz i trzymany w `~/.cache/finch`.
|
- **Mały runtime w C** (`runtime/finch_rt.c`, ok. 1150 linii) robi to, co w IR byłoby żmudne: teksty, powiększanie tablic,
|
||||||
|
mapy, wczytywanie, pliki, błędy w trakcie działania. Kompiluje się go raz i trzyma w `~/.cache/finch`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. Drzewo źródeł
|
## 2. Pliki źródłowe
|
||||||
|
|
||||||
| Plik | Linie | Zadanie |
|
| Plik | Linie | Za co odpowiada |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `src/lexer.fch` | 470 | Tokeny i pozycje, tekst `"""`, `"\(x)"` zamienione na tokeny `("…" + str(x) + "…")` |
|
| `src/lexer.fch` | 470 | Tokeny, pozycje, tekst `"""`, zamiana `"\(x)"` na tokeny `("…" + str(x) + "…")` |
|
||||||
| `src/ast.fch` | 120 | Jedna struktura `Node` z napisem `kind`, dziećmi, nazwami typów, pozycjami (`line`, `endLine`) |
|
| `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/parser.fch` | 910 | Zejście rekurencyjne: deklaracje, instrukcje, wyrażenia, generyki (`[T]`), `enum`, `match`, `export`, `link`, `const`, `test "…" { }` |
|
| `src/parser.fch` | 910 | Zejście rekurencyjne: deklaracje, instrukcje, wyrażenia, typy generyczne (`[T]`), `enum`, `match`, `export`, `link`, `const`, `test "…" { }` |
|
||||||
| `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/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/cimport.fch` | 790 | libclang: funkcje, struktury i unie (pola, offsety, wypełnienie), enumy, stałe, globalne, makra (także będące strukturą) |
|
| `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/platform.fch` | 90 | `Target`: triple, układ danych, kompilator C, znak `char` zależnie od systemu |
|
| `src/platform.fch` | 120 | `Target`: triple, układ danych, kompilator C, znak typu `char` na danym systemie |
|
||||||
| `src/linker.fch` | 430 | Pamięć podręczna (runtime, obiekt programu, pliki C/C++), biblioteki (`link`, pkg-config), podpowiedzi przy błędach linkera |
|
| `src/linker.fch` | 480 | Pamięć podręczna (runtime, obiekt programu, pliki C/C++), biblioteki (`link`, pkg-config), podpowiedzi przy błędach linkera |
|
||||||
| `src/main.fch` | 515 | Sterownik: CLI, loader z wyszukiwaniem modułów i nakładkami, `build/run/test/fmt/ir/index/lsp` |
|
| `src/main.fch` | 530 | Sterownik: wiersz poleceń, wczytywanie modułów i nakładek, `build/run/test/fmt/ir/index/lsp` |
|
||||||
| `src/manifest.fch` | 240 | `finch.toml`: mały czytnik TOML, listy zależne od celu |
|
| `src/manifest.fch` | 240 | `finch.toml`: mały czytnik TOML, listy dla poszczególnych systemów |
|
||||||
| `src/fmt.fch` | 225 | `finch fmt` i formatowanie w LSP |
|
| `src/fmt.fch` | 225 | `finch fmt` i formatowanie w serwerze języka |
|
||||||
| `src/lsp.fch` | 910 | Serwer języka (§11) |
|
| `src/lsp.fch` | 950 | Serwer języka (§11) |
|
||||||
| `src/docs.fch` | 110 | Jeden opis wbudowanych funkcji dla podpowiedzi i uzupełniania |
|
| `src/docs.fch` | 140 | Jeden opis funkcji wbudowanych, wspólny dla podpowiedzi i dymków |
|
||||||
| `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: tekst, tablice, mapy, pliki, wypisywanie liczb, fs, wątki, uruchamianie testów, łańcuch wywołań |
|
| `runtime/finch_rt.c` | 1150 | Runtime w C: teksty, tablice, mapy, pliki, wypisywanie liczb, fs, wątki, uruchamianie testów, ścieżka wywołań |
|
||||||
| `bootstrap/` | | `build.sh`, `install.sh`, ziarna (§10) |
|
| `bootstrap/` | | `build.sh`, `install.sh`, pliki startowe (§10) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Front end: lekser, parser, moduły
|
## 3. Część frontowa: lekser, parser, moduły
|
||||||
|
|
||||||
**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.
|
**Lekser.** Koniec linii kończy instrukcję; wcięcia nie mają znaczenia. Każdy token zna plik, linię i kolumnę, z których
|
||||||
|
korzystają wszystkie komunikaty o błędach.
|
||||||
|
|
||||||
**Parser.** Zejście rekurencyjne, jedna funkcja na konstrukcję. Typy są parsowane do napisu (`[]str`, `map[str]int`,
|
**Parser.** Zejście rekurencyjne, jedna funkcja na każdą konstrukcję. Warto wiedzieć, że: typy są parsowane do nazwy
|
||||||
`ptr[char]`, `Stack[int]`); `fn Typ.metoda` deklaruje metodę; `fn nazwa[T]` i `struct Nazwa[T]` to szablony; `enum` i `match`
|
(`[]str`, `map[str]int`, `ptr[char]`, `Stack[int]`, `f32x8`); `fn Typ.metoda` deklaruje metodę; `fn nazwa[T]`
|
||||||
mają własne węzły; `export fn` oznacza funkcję widoczną dla C; blok zapamiętuje `endLine` (dla serwera języka). Stara
|
i `struct Nazwa[T]` deklarują szablony; `enum` i `match` mają własne węzły; `export fn` oznacza funkcję widoczną z C;
|
||||||
kolejność `fn nazwa(...) typ` to błąd składni z podpowiedzią.
|
blok zapamiętuje swoją `endLine` (korzysta z niej serwer języka). Stara kolejność `fn nazwa(...) typ` daje błąd z podpowiedzią.
|
||||||
|
|
||||||
**Moduły.** `import nazwa` szuka kolejno `nazwa_<os>.fch` obok importującego pliku (`windows`, `linux`, `macos`), `nazwa.fch`,
|
**Moduły.** `import nazwa` szuka po kolei: `nazwa_<system>.fch` obok importującego pliku (`windows`, `linux`, `macos`),
|
||||||
katalogów z `FINCH_PATH`, potem biblioteki standardowej `std/`. Każdy moduł jest parsowany raz; jego nazwy są używane jako
|
`nazwa.fch` w tym samym miejscu, katalogów z `FINCH_PATH`, a na końcu biblioteki standardowej `std/`. Każdy moduł jest
|
||||||
`modul.nazwa`. `import "x.h"` trafia do importera C.
|
parsowany raz; jego nazwy są dostępne jako `modul.nazwa`. `import "x.h"` trafia zamiast tego do importu z C.
|
||||||
|
|
||||||
Moduł rozpoznaje się po **pliku**: kluczem jest jego ścieżka od folderu głównego pliku (albo od folderu z `FINCH_PATH`
|
Moduł rozpoznaje się po **pliku**: kluczem jest jego ścieżka względem katalogu głównego pliku (albo katalogu z
|
||||||
lub `std/`) bez `.fch` i bez końcówki `_<system>`, np. `ui/przycisk`. `Program.imports` trzyma nazwę używaną w danym
|
`FINCH_PATH`, albo `std/`), bez `.fch` i bez końcówki `_<system>`, np. `ui/button`. `Program.imports` przechowuje nazwę,
|
||||||
pliku (`przycisk`, albo `p` przy `import ui/przycisk as p`), a `Program.modules` klucz; `modFor()` zamienia jedno na drugie.
|
której używa dany plik (`button` albo `btn` przy `import ui/button as btn`), a `Program.modules` klucz; `modFor()`
|
||||||
Symbole używają klucza z `/` zamienionym na `$` (`@finch.ui$przycisk.rysuj`).
|
przechodzi od jednego do drugiego. Symbole używają klucza, w którym `/` zamieniono na `$` (`@finch.ui$button.make`).
|
||||||
|
|
||||||
**Stałe.** `const NAZWA = wyrażenie` to węzeł najwyższego poziomu; `constValue()` liczy wyrażenie przy każdym użyciu, w
|
**Stałe.** `const NAZWA = wyrazenie` to węzeł najwyższego poziomu; `constValue()` oblicza wyrażenie przy każdym użyciu,
|
||||||
module, w którym je zdefiniowano, bez zmiennych lokalnych, więc wychodzi literał (operatory na literałach całkowitych
|
w module, w którym je zdefiniowano, bez zmiennych lokalnych, więc wynik zawsze składa się do literału (operatory na
|
||||||
składa `arith`). `checkConst()` dopuszcza tylko literały, nazwy, operatory, pola i konwersje liczb; `constBusy` łapie pętle.
|
literałach całkowitych są składane w `arith`). `checkConst()` dopuszcza tylko literały, nazwy, operatory, pola
|
||||||
|
i konwersje liczbowe; `constBusy` wykrywa cykle.
|
||||||
|
|
||||||
**Dodatki leksera.** `1e9` / `2.5e-3` to tokeny float (tekst dostaje `.` dla LLVM); teksty `"""` tracą wcięcie
|
**Drobiazgi leksera.** `1e9` i `2.5e-3` to tokeny zmiennoprzecinkowe (tekst jest uzupełniany o `.`, którego wymaga LLVM);
|
||||||
linii zamykającej w `longString()`.
|
z tekstu `"""` funkcja `longString()` usuwa wcięcie linii zamykającej.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Generowanie kodu
|
## 4. Generowanie kodu
|
||||||
|
|
||||||
`gen.fch` to jedna duża struktura `Gen` i funkcje na niej. Przejścia:
|
`gen.fch` to jedna duża struktura `Gen` i funkcje, które na niej pracują. `generate()` wykonuje kolejno:
|
||||||
|
|
||||||
1. `declareStructs`: każda struktura Fincha i C dostaje typ LLVM (struktury C z prawdziwym wypełnieniem); rekurencja przez wartość jest odrzucana.
|
1. `declare()`: każda struktura Fincha i C dostaje typ LLVM (struktury C z prawdziwym wyrównaniem; struktury, które
|
||||||
2. `declareFns`: sygnatury, z *analizą pożyczania* parametrów (parametr, który nigdzie nie jest zapisywany, jest przekazywany bez kopii).
|
przez wartość zawierają same siebie, są odrzucane), a potem każda funkcja dostaje sygnaturę z *analizą pożyczania*
|
||||||
3. `define`: każde ciało, sprawdzone i wyemitowane. Zakresy pamiętają, co zwolnić na końcu (`defer` działa pierwszy).
|
parametrów (§8.2).
|
||||||
4. `defineMainWrapper`: C-owe `main(argc, argv)` i `finch_on_fatal` dla osadzających.
|
2. `define()` dla każdej funkcji: ciało, ze sprawdzeniem typów i emisją IR. Zasięgi pamiętają, co trzeba zwolnić przy
|
||||||
5. Z `-g` metadane DWARF dokładane są w trakcie pisania IR (używaj `-O0`).
|
wyjściu. Tworzenie instancji typów generycznych dokłada w tym czasie nowe funkcje, więc pętla trwa, dopóki pojawiają się nowe.
|
||||||
|
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`.
|
||||||
|
|
||||||
Generyki są **monomorfizowane**: wywołanie `max(1, 2)` wnioskuje `T`, potem AST szablonu generowane jest ponownie z
|
Przy `-g` metadane DWARF są dopisywane na bieżąco, w trakcie zapisywania IR (najlepiej razem z `-O0`).
|
||||||
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.
|
|
||||||
|
|
||||||
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.
|
Typy generyczne są **monomorfizowane**: wywołanie `max(1, 2)` wyznacza `T`, a potem AST szablonu jest generowane jeszcze
|
||||||
|
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 C i ABI C
|
## 5. Import z C i ABI języka C
|
||||||
|
|
||||||
`cimport.fch` woła **libclang** wprost z Fincha (sam jest napisany z `import "clang-c/Index.h"`), parsuje nagłówek z flagami
|
`cimport.fch` woła **libclang** bezpośrednio z Fincha (sam jest napisany z `import "clang-c/Index.h"`), parsuje nagłówek
|
||||||
celu i przechodzi po kursorach. Typy: parametr `const char *` → `str`, inny `char *` → `ptr[char]`, wynik `char *` → `str`;
|
z flagami systemu docelowego i przechodzi po kursorach. Typy tłumaczy tak: parametr `const char *` → `str`, inne
|
||||||
liczby całkowite według rozmiaru i znaku (`char` jest bez znaku na ARM64 Linux); struktury i enumy według układu.
|
`char *` → `ptr[char]`, wynik `char *` → `str`; liczby całkowite według rozmiaru i znaku (`char` jest bez znaku na
|
||||||
|
Linuksie ARM64); struktury i enumy według ich układu w pamięci.
|
||||||
|
|
||||||
LLVM IR nie realizuje za ciebie konwencji wywołań C, więc `gen.fch` ją odtwarza:
|
LLVM IR nie realizuje sam konwencji wywołań C, więc `gen.fch` odtwarza ją ręcznie:
|
||||||
|
|
||||||
- `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;
|
- `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ęć;
|
||||||
- `classifyArm64`: AAPCS64, z jednorodnymi agregatami zmiennoprzecinkowymi (HFA);
|
- `classifyArm64`: AAPCS64, w tym jednorodne agregaty liczb zmiennoprzecinkowych (HFA) i wariant Apple;
|
||||||
- reguły Microsoft x64 (struktury 1, 2, 4, 8 bajtów w rejestrze, reszta przez wskaźnik);
|
- reguły Microsoft x64 (struktury 1, 2, 4 i 8-bajtowe przez wartość w rejestrze, pozostałe przez wskaźnik);
|
||||||
- `makePlan` zamienia klasyfikację w plan wywołania; `cThunk` robi z `addr(fn)` coś, co C może wywołać (callbacki).
|
- `makePlan` zamienia klasyfikację w plan wywołania; `cThunk` sprawia, że `addr(fn)` da się wywołać z C (funkcje zwrotne).
|
||||||
|
|
||||||
**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 cele
|
## 6. Sterownik, linkowanie i systemy docelowe
|
||||||
|
|
||||||
`finch build|run|ir|index plik.fch [-o wyjście] [-O0] [-g] [-l biblioteka] [-I katalog] [--target cel] [--shared] [--overlay ścieżka=plik]`.
|
`finch build|run|ir|index plik.fch [-o wynik] [-O0] [-g] [-l bib] [-I katalog] [--target t] [--cpu c] [--shared] [--overlay sciezka=plik]`.
|
||||||
`ir` wypisuje IR; `index` wypisuje rekordy dla serwera języka (§11); `--shared` robi bibliotekę współdzieloną z `export fn`.
|
`ir` wypisuje IR; `index` wypisuje rekordy dla serwera języka (§11); `--shared` tworzy bibliotekę współdzieloną z funkcji
|
||||||
|
`export fn`.
|
||||||
|
|
||||||
`--target windows|linux|arm64|macos|android|<triple>` zmienia triple i układ danych, znak `char`, reguły ABI powyżej, wariant OS
|
`--cpu <nazwa>` (albo `cpu` w `finch.toml`) dodaje `-march=<nazwa>` na x86-64 albo `-mcpu=<nazwa>` na ARM64 do wywołania
|
||||||
modułów (`nazwa_windows.fch`) i kompilator C: `FINCH_CC` albo `clang --target=…`. Przy linkowaniu `pkg-config` podaje flagi
|
clanga, które kompiluje IR (nie runtime); wartość wchodzi do klucza pamięci podręcznej.
|
||||||
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.
|
|
||||||
|
|
||||||
Zmienne środowiskowe: `FINCH_PATH` (katalogi modułów), `FINCH_RT` (źródło runtime'u), `FINCH_CC` (kompilator/linker C; IR
|
`--target windows|linux|arm64|macos|android|<triple>` zmienia triple i układ danych, znak typu `char` w C, reguły ABI
|
||||||
zawsze kompiluje `clang`, nigdy `CC`), `FINCH_LIBCLANG` (dla `bootstrap/build.sh`).
|
opisane wyżej, wariant modułów dla systemu (`nazwa_windows.fch`) i kompilator C: `FINCH_CC` albo `clang --target=…`.
|
||||||
|
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.
|
||||||
|
|
||||||
Kompilator znajduje `runtime/` i `std/` obok siebie (`../`, `./`) albo w `../share/finch/` (`bootstrap/install.sh`).
|
Zmienne środowiskowe: `FINCH_PATH` (katalogi modułów), `FINCH_RT` (źródło runtime'u), `FINCH_CC` (kompilator C i linker;
|
||||||
|
IR zawsze kompiluje `clang`, nigdy `CC`), `FINCH_LIBCLANG` (dla `bootstrap/build.sh`).
|
||||||
|
|
||||||
**Pamięć podręczna budowania** (`host.cacheDir()`: `$XDG_CACHE_HOME/finch`, `~/.cache/finch`, `%LOCALAPPDATA%\finch`):
|
Kompilator szuka `runtime/` i `std/` obok siebie (`../`, `./`) albo w `../share/finch/` (`bootstrap/install.sh`).
|
||||||
|
|
||||||
- `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
|
**Pamięć podręczna kompilacji** (`host.cacheDir()`: `$XDG_CACHE_HOME/finch`, `~/.cache/finch`, `%LOCALAPPDATA%\finch`):
|
||||||
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.
|
|
||||||
|
|
||||||
**`finch.toml`** (`src/manifest.fch`) jest szukany od folderu pliku głównego (albo bieżącego) w górę. Trafia do
|
- `ir/<hash głównego pliku + systemu + flag>.o` z plikiem `.key`, w którym jest hash clanga, flag i całego IR. Jedno
|
||||||
`values["sekcja.klucz"]` i `lists["sekcja.klucz"]`; `list(klucz, cele)` skleja listę z najwyższego poziomu i te z
|
miejsce na program, więc pamięć nie rośnie przy każdej zmianie kodu; to samo IR daje ten sam obiekt bez uruchamiania clanga.
|
||||||
`[target.<os>]` oraz `[target.<os>-<cpu>]`. Ścieżki liczą się od folderu manifestu. Wpisy trafiają tam, gdzie opcje
|
- `c/<hash źródła + kompilatora + flag + systemu>.o` dla `link "x.c"` / `.cpp`, budowany z `-MMD`; `.key` to hash
|
||||||
z linii poleceń: `paths` → loader, `include` → `-I` (libclang i pliki C), `links` → `link`, `libdirs` i `ldflags` →
|
wszystkich plików wymienionych w `.d` (źródła i jego nagłówków). Zmiana któregokolwiek z nich wymusza ponowną kompilację.
|
||||||
polecenie linkowania, `cflags` → pliki C/C++.
|
- Obiekty są zapisywane do pliku `.part<pid>` i dopiero potem przemianowywane, więc dwie równoległe kompilacje nie
|
||||||
|
zobaczą połowy pliku. Hasze to dwa niezależne 32-bitowe (FNV-1a i djb2), razem 64 bity.
|
||||||
|
|
||||||
**Pliki C++**: `.cpp`/`.cc`/`.cxx` w `links` kompiluje `cxxFor(cc)` (`FINCH_CXX` albo `clang` → `clang++`,
|
**`finch.toml`** (`src/manifest.fch`) jest szukany od katalogu głównego pliku (albo katalogu roboczego) w górę.
|
||||||
`…gcc` → `…g++`, `cc` → `c++`), a potem ten sam sterownik linkuje, co dokłada standardową bibliotekę C++.
|
Trafia do `values["sekcja.klucz"]` i `lists["sekcja.klucz"]`; `list(klucz, systemy)` łączy listę z najwyższego poziomu
|
||||||
|
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`), linkowanie
|
`toolchains/llvm/prebuilt/<host>/bin/aarch64-linux-android24-clang` (z `ANDROID_NDK_HOME`/`ANDROID_NDK_ROOT`);
|
||||||
dodaje `-llog` i nie dodaje `-lpthread` (bionic ma go w libc). Wariant OS modułów to `linux`.
|
linkowanie dodaje `-llog`, a nie dodaje `-lpthread` (bionic ma wątki w libc). Wariantem modułów jest `linux`.
|
||||||
|
|
||||||
**`finch test`** kompiluje jak `run`, z `Options.test`: wszystkie funkcje są dalej definiowane, ale `@main` wywołuje
|
**`finch test`** kompiluje tak jak `run`, z `Options.test`: wszystkie funkcje są definiowane jak zwykle, ale `@main` woła
|
||||||
`finch_test_run(nazwa, fn)` dla każdego bloku `test` z modułów użytkownika (`__test_<n>`, funkcja `void` zrobiona przez
|
`finch_test_run(nazwa, fn)` dla każdego bloku `test` z modułów użytkownika (`__test_<n>`, funkcja `void` utworzona przez
|
||||||
parser), a na końcu `finch_test_summary()`. Runtime uruchamia każdy test pod `setjmp`; `fatal()` (każdy błąd w czasie
|
parser), a potem `finch_test_summary()`. Runtime uruchamia każdy test pod `setjmp`; `fatal()` (każdy błąd w trakcie
|
||||||
działania i nieudany `assert`) wraca przez `longjmp` z `fatal_hook`, więc jeden nieudany test nie kończy całości.
|
działania i nieudany `assert`) wraca przez `longjmp` z pomocą `fatal_hook`, więc jeden nieudany test nie przerywa reszty.
|
||||||
Pamięć, którą test miał w tym momencie, nie jest zwalniana.
|
Pamięć, którą test miał w chwili błędu, 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
|
||||||
|
|
@ -215,55 +230,61 @@ kształt (i ucinanie wcięcia `"""`) zostaje.
|
||||||
| `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 }` (entries, len, used, cap, index, icap; zob. src/gen.fch) |
|
| `map[K]V` | `{ ptr, i64, i64, i64, ptr, i64, i64 }` (wpisy, len, used, cap, indeks, icap, adds; 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]` |
|
||||||
| struktura Fincha | nazwany typ struct, naturalny układ |
|
| `f32x8`, `i8x32`, … | `<8 x float>`, `<32 x i8>` (§7, Wektory) |
|
||||||
| `self` metody | `ptr` (pierwszy parametr) |
|
| struktura Fincha | nazwany typ struktury, naturalny układ |
|
||||||
| wynik `-> T!` | `{ i1 ok, T value, str error }` (`i8` w miejscu `T` dla `-> !`) |
|
| `self` w metodzie | `ptr` (pierwszy parametr) |
|
||||||
| struktura C | nazwany **upakowany** (packed) struct z jawnymi bajtami wypełnienia (§5) |
|
| wynik `-> T!` | `{ i1 ok, T wartosc, str blad }` (`i8` zamiast `T` dla `-> !`) |
|
||||||
|
| struktura C | nazwana struktura **packed** z jawnymi bajtami wyrównania (§5) |
|
||||||
|
|
||||||
Znakowość istnieje tylko w typie Fincha i wybiera `sdiv/udiv`, `ashr/lshr`, `icmp s*/u*`,
|
Znak liczby istnieje tylko w typie Fincha i wybiera `sdiv/udiv`, `ashr/lshr`, `icmp s*/u*`, `sext/zext`,
|
||||||
`sext/zext`, `sitofp/uitofp`, `fptosi/fptoui`.
|
`sitofp/uitofp`, `fptosi/fptoui`.
|
||||||
|
|
||||||
### Wartości
|
### Wartości
|
||||||
|
|
||||||
```cpp
|
```c
|
||||||
struct Value_ { Value *v; FType type; bool literal; bool fresh; };
|
struct Val {
|
||||||
|
str v // operand IR: %t12, 42, @.str.3, zeroinitializer
|
||||||
|
ast.Type ty
|
||||||
|
bool fresh
|
||||||
|
bool lit
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- **`literal`**: liczba wpisana w kod albo zaimportowana stała C. Może stać się dowolnym typem całkowitym,
|
- **`lit`**: liczba zapisana w kodzie albo stała zaimportowana z C. Może stać się dowolnym typem całkowitym,
|
||||||
w którym się mieści (`fits()`), albo `f32`. Przechodzi przez minus jednoargumentowy i arytmetykę samych
|
w którym się mieści (`fits()`), albo `f32`. Przetrwa jednoargumentowy minus i działania na samych literałach,
|
||||||
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).
|
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).
|
||||||
- **`fresh`**: wartość posiadająca pamięć (str/tablica/struktura z takimi polami), której nikt jeszcze nie
|
- **`fresh`**: wartość posiadająca pamięć (str, tablica, struktura z takimi polami), której jeszcze nikt nie trzyma:
|
||||||
trzyma: wynik wywołania, literał, sklejenie, konstruktor. Trzeba ją zapisać (przenieść) albo zwolnić (§8).
|
wynik wywołania, literał, połączenie tekstów, 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; całkowita → całkowita,
|
Identyczne typy; `null` → dowolny wskaźnik; wskaźnik z typem ↔ `ptr` bez typu; liczba całkowita → całkowita, jeśli
|
||||||
jeśli wartość to mieszczący się literał albo typ się rozszerza (`widens()`: ta sama znakowość i więcej bitów
|
to pasujący literał albo typ się „poszerza” (`widens()`: ten sam znak i więcej bitów, albo bez znaku → większy ze znakiem);
|
||||||
albo bez znaku → ściśle większa ze znakiem); całkowita → zmiennoprzecinkowa; `f32` → `f64`; literał `f64` → `f32`.
|
liczba całkowita → zmiennoprzecinkowa; `f32` → `f64`; literał `f64` → `f32`. Nic poza tym: komunikat błędu podpowiada
|
||||||
Nic więcej: błąd podpowiada jawną konwersję. Typy posiadające pamięć konwertują się tylko na siebie.
|
jawną konwersję. Typy posiadające pamięć przechodzą tylko w siebie. Literał tablicy bierze typ elementów od miejsca,
|
||||||
Literały tablic biorą typ elementu z miejsca, do którego trafiają (`exprWant()`), więc `[]f32 xs = [1, 2.5]` działa.
|
do którego trafia (`exprWant()`), więc `[]f32 xs = [1, 2.5]` działa.
|
||||||
|
|
||||||
### Argumenty operatorów: `unify(l, r)`
|
### Argumenty operatorów: `unify(l, r)`
|
||||||
|
|
||||||
Strona-literał dopasowuje się do drugiej (także literał float obok `f32`: `x * 2.0` zostaje `f32`); w przeciwnym razie
|
Strona, która jest literałem, dopasowuje się do drugiej (także literał zmiennoprzecinkowy obok `f32`: `x * 2.0`
|
||||||
rośnie strona, która się rozszerza; `i32` + `u32` to błąd; całkowita + zmiennoprzecinkowa → zmiennoprzecinkowa;
|
zostaje `f32`); w przeciwnym razie rośnie ta strona, którą da się poszerzyć; `i32` + `u32` to błąd; całkowita +
|
||||||
f32 + f64 → f64. Nie ma promocji całkowitych z C: `u8 + u8` zostaje `u8`. Dwa literały całkowite składają się w literał,
|
zmiennoprzecinkowa → zmiennoprzecinkowa; f32 + f64 → f64. Nie ma promocji liczb całkowitych jak w C: `u8 + u8`
|
||||||
więc `FLAG_A | FLAG_B` dalej mieści się w `u64` albo `u32`.
|
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`.
|
||||||
|
|
||||||
### Konwersje jawne: `convert()`
|
### Konwersje jawne: `convert()`
|
||||||
|
|
||||||
| Z → Na | IR |
|
| Z → Na | IR |
|
||||||
|---|---|
|
|---|---|
|
||||||
| int/char ↔ int/char | `trunc`/`sext`/`zext` wg znakowości źródła |
|
| int/char ↔ int/char | `trunc`/`sext`/`zext` według znaku źródła |
|
||||||
| bool → int | `zext` |
|
| bool → int | `zext` |
|
||||||
| float ↔ int | `fptosi`/`fptoui`, `sitofp`/`uitofp` |
|
| zmiennoprzecinkowa ↔ całkowita | `fptosi`/`fptoui`, `sitofp`/`uitofp` |
|
||||||
| float ↔ float | `fpext`/`fptrunc` |
|
| zmiennoprzecinkowa ↔ zmiennoprzecinkowa | `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` (panic przy złym tekście) |
|
| str → int/float | `finch_str_to_int/float` (przy złym tekście błąd w trakcie działania) |
|
||||||
| `[]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 |
|
||||||
|
|
@ -271,102 +292,154 @@ więc `FLAG_A | FLAG_B` dalej mieści się w `u64` albo `u32`.
|
||||||
|
|
||||||
### Porównania
|
### Porównania
|
||||||
|
|
||||||
Wskaźniki/null: tylko `icmp eq/ne ptr`. `str`: `finch_str_eq` (długość + `memcmp`) dla `==`/`!=`,
|
Wskaźniki i `null`: tylko `icmp eq/ne ptr`. `str`: `finch_str_eq` (długość + `memcmp`) dla `==`/`!=`,
|
||||||
`finch_str_cmp` dla porządku. Liczby zmiennoprzecinkowe: `oeq olt ole ogt oge` i `une` dla `!=`.
|
`finch_str_cmp` dla kolejności. Liczby zmiennoprzecinkowe: `oeq olt ole ogt oge` oraz `une` dla `!=`. Liczby całkowite
|
||||||
Całkowite i znaki wg znakowości (char bez znaku). Bool: tylko równość. Tablice/struktury: błąd.
|
i `char` według znaku (`char` bez znaku). Bool: tylko równość. Tablice i 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ęć
|
||||||
|
|
||||||
### 9.1 Model
|
### 8.1 Model
|
||||||
|
|
||||||
Każda wartość **typu posiadającego pamięć** (`str`, `[]T` albo struktura Fincha z takim polem) ma
|
Każda wartość **typu posiadającego pamięć** (`str`, `[]T` albo struktura Fincha z takim polem) ma dokładnie jednego
|
||||||
dokładnie jednego właściciela: zmienną, element tablicy, pole struktury albo komórkę na stercie z `new()`.
|
właściciela: zmienną, element tablicy, pole struktury albo komórkę na stercie z `new()`. Właściciel zwalnia wartość,
|
||||||
Właściciel zwalnia wartość, gdy sam znika. Nie ma liczenia referencji ani garbage collectora;
|
gdy sam znika. Nie ma liczenia referencji ani garbage collectora; wszystkie decyzje zapadają przy kompilacji.
|
||||||
każda decyzja zapada statycznie.
|
|
||||||
|
|
||||||
| Sytuacja | Co się dzieje |
|
| Sytuacja | Co się dzieje |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `x := expr` / `T x = expr` / `x = expr` / pole, element, `push` | **`own(v)`**: wartość fresh jest przenoszona; pożyczona jest kopiowana w głąb |
|
| `x := wyr` / `T x = wyr` / `x = wyr` / pole, element, `push` | **`own(v)`**: wartość świeża zostaje przeniesiona, pożyczona jest głęboko kopiowana |
|
||||||
| `x = …` (typ posiadający) | najpierw liczona jest nowa wartość, stara zwalniana po zapisie |
|
| `x = …` (typ posiadający) | najpierw liczona jest nowa wartość, a stara jest zwalniana po zapisie |
|
||||||
| koniec bloku, `return`, `break`, `continue` | posiadane zmienne każdego opuszczanego zasięgu są zwalniane (od najnowszej), uruchamiają się defery |
|
| koniec bloku, `return`, `break`, `continue` | zmienne we wszystkich opuszczanych zasięgach są zwalniane (od najnowszej), wykonują się `defer` |
|
||||||
| `return lokalna` | zmienna jest **przenoszona na zewnątrz** (oznaczana jako `moved` na czas sprzątania tego returna) |
|
| `return zmienna_lokalna` | zmienna jest **przenoszona** (oznaczona jako `moved` na czas sprzątania przy tym `return`) |
|
||||||
| przekazanie argumentu | **pożyczka**: funkcja dostaje płytką kopię i nigdy jej nie zwalnia; argumenty fresh zwalnia wywołujący po wywołaniu |
|
| przekazanie argumentu | **pożyczka**: funkcja dostaje płytką kopię i nigdy jej nie zwalnia; świeże argumenty zwalnia wywołujący po wywołaniu |
|
||||||
| parametr, który funkcja zmienia | kopiowany na wejściu i posiadany przez funkcję (decyduje `mutates()`, niżej) |
|
| parametr, który funkcja zmienia | kopiowany na wejściu i należący do funkcji (decyduje `mutates()`, niżej) |
|
||||||
| wartość tymczasowa, której nikt nie trzyma (`print(a + b)`, `f(g())`, instrukcja-wyrażenie) | **`release(v)`**: zwalniana zaraz po użyciu |
|
| wartość tymczasowa, której nikt nie zatrzymuje (`print(a + b)`, `f(g())`, wyrażenie jako instrukcja) | **`release(v)`**: zwalniana zaraz po użyciu |
|
||||||
| `a.pop()`, `a.remove(i)` | element jest przenoszony na zewnątrz (fresh) |
|
| `a.pop()`, `a.remove(i)` | element jest przenoszony na zewnątrz (świeży) |
|
||||||
| `for x in lista` | `x` pożycza kolejne elementy; jeśli ciało może zmieniać `lista`, elementy są kopiowane |
|
| `for x in lista` | `x` pożycza kolejne elementy; jeśli ciało pętli może zmienić `lista`, elementy są kopiowane |
|
||||||
| wywołanie metody `v.m()` | `self` **to jest** `v` (przekazywany jest jego adres): bez kopii; zmiany widzi wywołujący |
|
| wywołanie metody `v.m()` | `self` **to jest** `v` (przekazywany jest 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/kopiuje `v` i zwalnia starą wartość |
|
| 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ść |
|
||||||
|
|
||||||
Literał tekstowy to wartość fresh z `cap = 0`: przeniesienie nic nie kosztuje, zwalnianie jest pomijane
|
Literał tekstowy to świeża wartość z `cap = 0`: przeniesienie go 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życzane z `cap = -1` i zawsze kopiowane, zanim zostaną zatrzymane.
|
Teksty z C są pożyczone z `cap = -1` i zawsze kopiowane, zanim zostaną zatrzymane.
|
||||||
|
|
||||||
### 9.2 Pożyczanie parametrów: `mutates()`
|
### 8.2 Pożyczanie parametrów: `mutates()`
|
||||||
|
|
||||||
Przy deklarowaniu funkcji każdy parametr posiadający pamięć jest sprawdzany przez `mutates(nazwa, ciało)`:
|
Przy deklarowaniu funkcji każdy parametr typu posiadającego jest sprawdzany przez `mutates(nazwa, cialo)`: przypisanie,
|
||||||
przypisanie, którego zmienna-korzeń (`rootVar()`: przez `.pole` i `[i]`) jest tym parametrem, metoda
|
którego zmienną bazową (`rootVar()`: przez `.pole` i `[i]`) jest ten parametr, zmieniająca metoda na nim
|
||||||
zmieniająca (`push pop insert remove clear resize sort reverse`) albo `addr(…)` na nim. Niezmieniane
|
(`push pop insert remove clear resize sort reverse`) albo `addr(…)` na nim. Niezmieniane parametry są pożyczane
|
||||||
parametry są pożyczane (`Fn::borrowParam`), zmieniane kopiowane raz na wejściu. Analiza jest składniowa
|
(`FnSig.borrow`); zmieniane są kopiowane raz, na wejściu. Analiza jest składniowa i ostrożna: wszystko, co *mogłoby*
|
||||||
i zachowawcza: wszystko, co *może* zmienić parametr, powoduje kopię, co zawsze jest poprawne.
|
zmienić parametr, powoduje kopię, a to zawsze jest poprawne. Ta sama analiza decyduje, czy `for x in lista` musi kopiować elementy.
|
||||||
Ta sama analiza decyduje, czy `for x in lista` musi kopiować elementy.
|
|
||||||
|
|
||||||
Metody biorą w tym udział: `declareFns()` najpierw ustala, które metody zmieniają `self`
|
Metody też w tym uczestniczą: `declare()` najpierw ustala, które metody zmieniają `self` (`mutates("self", cialo)`),
|
||||||
(`mutates("self", ciało)`), powtarzając, aż nic się nie zmieni, bo wywołanie zmieniającej metody na `self`
|
powtarzając to, dopóki coś się zmienia, bo wywołanie zmieniającej metody na `self` też zmienia `self`. Ich nazwy trafiają
|
||||||
też zmienia `self`. Ich nazwy trafiają do `mutatingMethods`, które `mutatesExpr()` traktuje jak `push`:
|
do `g.mm`, które `mutates()` traktuje jak `push`: `p.przesun(1)` zmienia `p`. Dopiero potem dla każdej funkcji i metody
|
||||||
`p.przesun(1)` zmienia `p`. Dopiero potem liczone jest `borrowParam` dla każdej funkcji i metody.
|
liczone jest `borrow`.
|
||||||
|
|
||||||
### 9.3 Kopiowanie i zwalnianie
|
### 8.3 Kopiowanie i zwalnianie
|
||||||
|
|
||||||
`dropAt(addr, T)` / `copyAt(dst, src, T)` działają na adresach:
|
`dropAt(adres, T)` / `copyAt(cel, zrodlo, 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: generowane helpery `finch.drop.<klucz>` i `finch.copy.<klucz>`, tworzone
|
- `[]T` i struktury posiadające pamięć: wygenerowane funkcje pomocnicze `finch.drop.<klucz>` i `finch.copy.<klucz>`,
|
||||||
raz na typ (`dropFn`, `copyFn`, klucz z `typeKey()`, np. `arr_S_main_Gracz`). Tablice zwalniają każdy posiadający
|
tworzone raz na typ (`dropFn`, `copyFn`, klucz z `typeKey()`, np. `arr_S_main_Player`). Tablica zwalnia każdy
|
||||||
element, a potem `finch_arr_free`; kopie wołają `finch_arr_clone_raw`, a potem kopiują w głąb posiadające
|
element posiadający pamięć, a potem woła `finch_arr_free`; kopia woła `finch_arr_clone_raw`, a potem głęboko kopiuje
|
||||||
elementy na surowe bity. Struktury zwalniają/kopiują tylko pola posiadające.
|
takie elementy w miejsce surowych bitów. Struktury zwalniają i kopiują tylko pola posiadające pamięć.
|
||||||
|
|
||||||
Helpery są emitowane we własnych funkcjach (`HelperScope` zapamiętuje pozycję buildera i wyłącza lokacje
|
Funkcje pomocnicze powstają jako osobne funkcje (`beginHelper()` / `endHelper()` odkładają na bok funkcję, która jest
|
||||||
debugowe), mogą wołać się rekurencyjnie (`struct Wezel { []Wezel dzieci }`) i są wstawiane (inline) przez O2.
|
w trakcie pisania, i wyłączają lokalizacje debuggera), mogą wołać się nawzajem rekurencyjnie (`struct Node { []Node kids }`),
|
||||||
|
a O2 je wkleja w miejsce wywołań.
|
||||||
|
|
||||||
### 9.4 Jak to wygląda
|
### 8.4 Jak to wygląda
|
||||||
|
|
||||||
```c
|
```c
|
||||||
fn greet(str name) -> str {
|
fn przywitaj(str imie) -> str {
|
||||||
msg := "Hi, " + name
|
wiad := "Cześć, " + imie
|
||||||
return msg
|
return wiad
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
a := ["x"]
|
a := ["x"]
|
||||||
b := a
|
b := a
|
||||||
b.push(greet("Ola"))
|
b.push(przywitaj("Ola"))
|
||||||
print(b[1])
|
print(b[1])
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
IR funkcji `main` z `-O0` (skrócony):
|
IR funkcji `main` przy `-O0` (w skrócie):
|
||||||
|
|
||||||
```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 (fresh)
|
store { ptr, i64, i64 } %7, ptr %a ; przeniesione do a (świeże)
|
||||||
%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.greet({ ptr, i64, i64 } { ptr @str.2, i64 3, i64 0 })
|
%9 = call { ptr, i64, i64 } @finch.przywitaj({ ptr, i64, i64 } { ptr @str.2, i64 3, i64 0 })
|
||||||
...
|
...
|
||||||
call void @finch_arr_reserve(ptr %b, i64 %12, i64 24) ; push: powiększ, jeśli trzeba
|
call void @finch_arr_reserve(ptr %b, i64 %12, i64 24) ; push: powiększenie, jeśli trzeba
|
||||||
store { ptr, i64, i64 } %9, ptr %15 ; wynik greet przeniesiony, bez kopii
|
store { ptr, i64, i64 } %9, ptr %15 ; wynik przywitaj przeniesiony, bez kopii
|
||||||
...
|
...
|
||||||
%20 = icmp uge i64 1, %19 ; b[1]: kontrola zakresu
|
%20 = icmp uge i64 1, %19 ; b[1]: sprawdzenie 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
|
||||||
|
|
@ -374,103 +447,116 @@ IR funkcji `main` z `-O0` (skrócony):
|
||||||
ret void
|
ret void
|
||||||
```
|
```
|
||||||
|
|
||||||
a `greet` zwraca `msg` bez kopiowania (`ret … %5` po wczytaniu slotu: zmienna została przeniesiona).
|
a `przywitaj` zwraca `wiad` bez kopiowania (`ret … %5` po wczytaniu zmiennej: została przeniesiona).
|
||||||
|
|
||||||
### 9.5 defer
|
### 8.5 defer
|
||||||
|
|
||||||
`defer instrukcja` dokłada do bieżącego zasięgu `Cleanup{isDefer, body, order = bieżące varOrder}`
|
`defer instrukcja` zapisuje instrukcję w bieżącym zasięgu (`Scope.defers`, razem z `orders`: ile zmiennych wtedy
|
||||||
i niczego w tym miejscu nie emituje. Za każdym razem, gdy emitowane jest sprzątanie zasięgu (dojście do
|
istniało) oraz `Cleanup{isDefer}`; w tym miejscu nic nie emituje. Ilekroć emitowane jest sprzątanie zasięgu (koniec
|
||||||
końca bloku, `return`, wyjście przez `break`/`continue`), ciało jest generowane **w tym wyjściu**, z
|
bloku, `return`, wyjście przez `break`/`continue`), ciało jest generowane **przy tym wyjściu**, z `deferLimit`
|
||||||
`deferLimit = order`, żeby `lookup()` ukrył zmienne zadeklarowane po defer: te są wtedy już zwolnione
|
ustawionym tak, by `lookup()` ukrywało zmienne zadeklarowane po `defer`: w tym momencie są już zwolnione (sprzątanie
|
||||||
(sprzątanie idzie w odwrotnej kolejności). `return`/`break`/`continue` w instrukcji defer to błędy (`inDefer`).
|
idzie od końca). `return`/`break`/`continue` wewnątrz odroczonej instrukcji to błąd (`inDefer`). Ciała `defer` są więc
|
||||||
Ciała defer są więc powielane na każdą ścieżkę wyjścia, przez co na zwykłej ścieżce nic nie kosztują.
|
powielane na każdą drogę wyjścia i dlatego na zwykłej ścieżce nic nie kosztują.
|
||||||
|
|
||||||
### 9.6 Pamięć ręczna
|
### 8.6 Pamięć zarządzana ręcznie
|
||||||
|
|
||||||
`new(v)` alokuje `sizeof(T)` przez `finch_alloc`, zapisuje `own(v)` i zwraca `ptr[T]`. `free(p)` zwalnia
|
`new(v)` przydziela `sizeof(T)` przez `finch_alloc`, zapisuje `own(v)` i zwraca `ptr[T]`. `free(p)` zwalnia to, na co
|
||||||
wskazywaną wartość, jeśli `T` posiada pamięć (pomijając null), i woła `free` z libc. Wskaźniki nigdy
|
wskazuje wskaźnik, jeśli `T` posiada pamięć (pomija `null`), i woła `free` z libc. Wskaźniki nigdy niczego nie posiadają:
|
||||||
niczego nie posiadają: `addr(x)` może wisieć po końcu bloku `x`; nie jest to sprawdzane.
|
`addr(x)` może wskazywać w próżnię po końcu bloku `x`; tego się nie sprawdza.
|
||||||
|
|
||||||
### 9.7 Weryfikacja
|
### 8.7 Sprawdzanie
|
||||||
|
|
||||||
`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 nieprawidłowych dostępów we wszystkich,
|
`--leak-check=full --errors-for-leak-kinds=all`: zero wycieków i zero błędnych dostępów we wszystkich, w tym przy
|
||||||
w tym przy returnach z wnętrza pętli, `continue`/`break` z posiadanymi zmiennymi, deferach, modyfikacji
|
`return` z wnętrza pętli, `continue`/`break` ze zmiennymi posiadającymi pamięć, `defer`, zmianach w trakcie iteracji,
|
||||||
podczas iteracji, zagnieżdżonych tablicach struktur i przekazywaniu struktur C.
|
zagnieżdżonych tablicach struktur i przekazywaniu struktur C.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
---
|
## 9. Typy generyczne, enumy i match
|
||||||
|
|
||||||
## 9. Generyki, enumy i match
|
Jak są generowane, opisuje §4. Reguły widoczne dla użytkownika są w rozdziałach 23 i 24
|
||||||
|
[przewodnika dla techników](dla-technikow.md). Testy to `tests/new/generics.fch`, `tests/new/enums.fch` i oczekiwane
|
||||||
Jak są generowane, opisuje §4. Reguły widoczne dla użytkownika: sekcje 23 i 24 [przewodnika dla techników](dla-technikow.md).
|
błędy w `tests/new/fail/`.
|
||||||
Testy: `tests/new/generics.fch`, `tests/new/enums.fch` i odmowy w `tests/new/fail/`.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 10. Bootstrap
|
## 10. Bootstrap
|
||||||
|
|
||||||
Kompilator budowany jest z **ziarna**: własnego IR zapisanego jako `bootstrap/<system>.ll` (zobacz
|
Kompilator powstaje z **pliku startowego** (ang. *seed*): jego własnego IR, zapisanego w repozytorium jako
|
||||||
[bootstrap/README.md](../../bootstrap/README.md)). `bootstrap/build.sh`:
|
`bootstrap/<system>.ll` (zob. [bootstrap/README.md](../../bootstrap/README.md)). `bootstrap/build.sh` robi:
|
||||||
|
|
||||||
```
|
```
|
||||||
ziarno .ll ──clang──► finch0 ──buduje src/──► finch1 ──buduje src/──► build/finch
|
plik .ll ──clang──► finch0 ──buduje src/──► finch1 ──buduje src/──► build/finch
|
||||||
```
|
```
|
||||||
|
|
||||||
i przerywa, jeśli `finch1` i `finch` nie dają identycznego IR (punkt stały: kompilator kompiluje siebie tak samo, niezależnie
|
i przerywa, jeśli `finch1` i `finch` nie dają identycznego bajt w bajt IR (punkt stały: kompilator kompiluje sam siebie
|
||||||
od tego, kto go zbudował). `bootstrap/build.sh seed` przepisuje ziarno z bieżącego kompilatora; rób to, gdy źródło
|
tak samo, niezależnie od tego, co go zbudowało). `bootstrap/build.sh seed` zapisuje plik startowy na nowo z bieżącego
|
||||||
kompilatora zacznie używać czegoś, czego zatwierdzone ziarno nie ma.
|
kompilatora; trzeba to zrobić, gdy kod kompilatora zaczyna używać czegoś, czego zapisany plik startowy nie zna.
|
||||||
|
|
||||||
Ziarno jest na system, bo zapieczono w nim konwencję wywołań C dla wywołań libclang (§5). Linux x86-64 i Linux ARM64 mają
|
Plik startowy jest osobny dla każdego systemu, bo zawiera konwencję wywołań C dla funkcji libclang (§5). Są pliki
|
||||||
ziarna. Dla macOS i Windowsa trzeba by ziaren zrobionych z nagłówkami Apple i Microsoftu.
|
dla Linuksa x86-64 i ARM64, macOS ARM64 i x86-64 (zrobione z nagłówkami SDK Apple) oraz Windowsa x86-64 (z nagłówkami
|
||||||
|
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` mówi JSON-RPC przez stdin/stdout (ramki `Content-Length`, `std/json`). Sam nie sprawdza typów: przy każdej zmianie
|
`finch lsp` rozmawia przez JSON-RPC na stdin/stdout (ramki `Content-Length`, `std/json`). Sam nie sprawdza typów:
|
||||||
uruchamia `finch index plik.fch --overlay ścieżka=kopia …`, gdzie nakładki to niezapisany tekst edytora. W trybie `index`
|
przy każdej zmianie uruchamia `finch index plik.fch --overlay sciezka=kopia …`, gdzie nakładki to niezapisany tekst
|
||||||
kompilator wypisuje rekordy rozdzielone tabulatorami:
|
z edytora. W trybie `index` kompilator zamiast IR wypisuje rekordy rozdzielone tabulatorami:
|
||||||
|
|
||||||
| Rekord | Znaczenie |
|
| Rekord | Znaczenie |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `@R plik linia kol dł plikDef liniaDef kolDef podpowiedź` | odwołanie: co pokazuje hover i gdzie jest definicja |
|
| `@R plik linia kol dlug plikDef liniaDef kolDef dymek` | odwołanie: co pokazać w dymku 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 moduł nazwa odb plik linia kol koniec sygnatura parametry` | funkcja lub metoda |
|
| `@F modul nazwa odbiorca plik linia kol koniec sygnatura parametry` | funkcja albo 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 wypisywany jest jako `ścieżka:linia:kol: error: komunikat` i staje się diagnostyką. Gdy kod się nie kompiluje,
|
Pierwszy błąd jest wypisywany jako `sciezka:linia:kol: error: komunikat` i staje się diagnostyką. Gdy kod się nie
|
||||||
serwer podpowiada dalej z ostatniej wersji, która się skompilowała. Test akceptacyjny: `tests/lsp_test.py` (22 sprawdzenia).
|
kompiluje, serwer nadal podpowiada uzupełnienia i parametry na podstawie ostatniej wersji, która się kompilowała.
|
||||||
|
Testem akceptacyjnym jest `tests/lsp_test.py`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 12. Semantyka a C
|
## 12. Semantyka w porównaniu z C
|
||||||
|
|
||||||
| Sytuacja | C | Finch 2.0 |
|
| Sytuacja | C | Finch |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Przepełnienie liczby ze znakiem | UB | zdefiniowane zawinięcie (bez `nsw`) |
|
| Przepełnienie liczby całkowitej ze znakiem | UB | zdefiniowane zawinięcie (bez `nsw`) |
|
||||||
| Dzielenie całkowite przez zero, `INT_MIN / -1` | UB | błąd kompilacji, jeśli stałe, inaczej błąd w czasie działania |
|
| 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 |
|
||||||
| Indeks tablicy poza zakresem | UB | błąd w czasie działania (także dla pól `[N]T` struktur C) |
|
| Indeks tablicy poza zakresem | UB | błąd w trakcie działania (także dla pól `[N]T` struktur C) |
|
||||||
| Dereferencja null | UB | błąd w czasie działania dla `.value`, `.pole`, `[i]` |
|
| Dereferencja `null` | UB | błąd w trakcie 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 tablic, tekstów, struktur | częsty błąd | niemożliwy bez `new` |
|
| Wyciek pamięci 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 ze znakiem / bez znaku | po cichu | błędy kompilacji |
|
| Niejawne zwężanie, mieszanie liczb ze znakiem i bez | po cichu | błędy kompilacji |
|
||||||
| Niezainicjalizowane zmienne | UB | niemożliwe (zero albo wartości domyślne) |
|
| Niezainicjowane zmienne | UB | niemożliwe (zero albo wartości domyślne) |
|
||||||
| Brak return | UB, jeśli użyty | błąd kompilacji |
|
| Brak `return` | UB, jeśli wynik jest użyty | błąd kompilacji |
|
||||||
| Kolejność obliczeń | w większości nieokreślona | od lewej do prawej; w przypisaniu najpierw prawa strona |
|
| Kolejność obliczania | w dużej mierze nieokreślona | od lewej do prawej; w przypisaniu najpierw prawa strona |
|
||||||
| Przesunięcie ≥ szerokość, float→int poza zakresem | UB | nadal poison (jeszcze niesprawdzane) |
|
| Przesunięcie o ≥ szerokość, liczba zmiennoprzecinkowa poza zakresem przy zamianie na całkowitą | UB | poison, jak w LLVM (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 ramkę na swoim stosie:
|
Fincha ma na stosie ramkę:
|
||||||
|
|
||||||
```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
|
||||||
|
|
@ -479,45 +565,44 @@ 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 `thread_local` w module programu (model initial-exec, chyba że biblioteka albo Windows);
|
`@finch_trace` to globalna zmienna `thread_local` w module programu (model initial-exec, chyba że to biblioteka
|
||||||
każda instrukcja zapisuje swoją linię do pola 3. Po każdym wywołaniu funkcji Fincha wywołujący wpisuje z powrotem swoją
|
albo Windows); każda instrukcja zapisuje swoją linię do pola 3. Po każdym wywołaniu funkcji Fincha wywołujący zapisuje
|
||||||
ramkę do `@finch_trace`, co sprząta też ramki funkcji, która wróciła wcześniej, więc wiele ścieżek `return` nie
|
z powrotem swoją ramkę do `@finch_trace`, co przy okazji usuwa ramki pozostawione przez funkcję, która wyszła wcześniej,
|
||||||
potrzebuje epilogu. Opakowania wołane z C (thunki `addr(fn)`, `export fn`) zapamiętują i przywracają `@finch_trace`.
|
więc na licznych ścieżkach powrotu nie trzeba nic sprzątać. Opakowania wywoływane z C (`addr(fn)`, `export fn`)
|
||||||
`main` woła `finch_trace_init(top, set)` z dwiema małymi funkcjami do czytania i zapisu, i tak runtime (`print_trace()`
|
zapamiętują i odtwarzają `@finch_trace` wokół wywołania. `main` woła `finch_trace_init(top, set)` z dwiema małymi
|
||||||
w `fatal()` oraz uruchamianie testów) dostaje się do zmiennej wątku, której nie zna z nazwy. Koszt: ok. 3 razy na
|
funkcjami, które ją czytają i zapisują; tak runtime (`print_trace()` w `fatal()` i uruchamianie testów) dostaje się do
|
||||||
`fib(40)`, który jest samymi wywołaniami; dlatego zwykły `finch build` go pomija.
|
zmiennej `thread_local`, której nie potrafi nazwać. Koszt: ok. 3 razy wolniej na `fib(40)`, które składa się wyłącznie
|
||||||
|
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
|
`std/thread.fch` to cienka warstwa nad runtime'em (`finch_thread_spawn/join`, `finch_mutex_*`: pthreads albo, na
|
||||||
`CreateThread` i `CRITICAL_SECTION` na Windowsie). Funkcja wątku to dowolna `fn f(ptr)` podana przez `addr(f)`, co robi
|
Windowsie, `CreateThread` i `CRITICAL_SECTION`). Funkcja wątku to dowolna `fn f(ptr)`, przekazywana przez `addr(f)`,
|
||||||
thunk wołany z C. `atomic_add/sub/swap` to `atomicrmw add/sub/xchg … seq_cst`, `atomic_load/store` to
|
co tworzy opakowanie wywoływalne z C. `atomic_add/sub/swap` stają się `atomicrmw add/sub/xchg … seq_cst`,
|
||||||
`load/store atomic … seq_cst, align <rozmiar>`, a `atomic_cas` to `cmpxchg … seq_cst seq_cst` zwracające `i1`. Biorą
|
`atomic_load/store` to `load/store atomic … seq_cst, align <rozmiar>`, a `atomic_cas` to `cmpxchg … seq_cst seq_cst`
|
||||||
`ptr[T]` do dowolnej liczby całkowitej. Tablice, teksty i mapy Fincha nie są synchronizowane; dzielenie ich między
|
zwracające `i1`. Przyjmują `ptr[T]` do dowolnego typu całkowitego. Tablice, teksty i mapy Fincha nie są synchronizowane;
|
||||||
wątkami wymaga mutexu.
|
dzielenie ich między wątkami wymaga muteksu.
|
||||||
|
|
||||||
### Ostrzeżenia
|
### Ostrzeżenia
|
||||||
|
|
||||||
`warn()` wypisuje `plik:linia:kolumna: warning: …` na stderr (więc `finch ir` daje samo IR) albo, w trybie `index`,
|
`warn()` wypisuje `plik:linia:kol: warning: …` na stderr (żeby `finch ir` pozostało czystym IR) albo, w trybie `index`,
|
||||||
rekord `@W`, który serwer języka publikuje z ważnością 2. Jedno na miejsce w kodzie. Są dwa: parametr-struktura oraz
|
rekord `@W`, który serwer języka publikuje z ważnością 2. Jedno ostrzeżenie na miejsce w kodzie. Są dwa rodzaje:
|
||||||
element `for x in lista`, gdy ich zwykłe dane (`byteSize()`: liczby, zagnieżdżone struktury, `[N]T` z C; `str`, tablice
|
parametr będący strukturą oraz element w `for x in lista`, jeśli ich zwykłe dane (`byteSize()`: liczby, zagnieżdżone
|
||||||
i mapy liczą się jako 0, bo kopiowany jest tylko ich uchwyt) mają 256 bajtów lub więcej.
|
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.
|
||||||
|
|
||||||
### Struktura jako argument przez adres
|
### Struktury przekazywane przez adres
|
||||||
|
|
||||||
`argWant()` (tylko argumenty wywołań, nie przypisania): gdy parametr to `ptr[S]`, a argument to zwykła zmienna typu `S`,
|
`argWant()` (tylko argumenty wywołań, nie przypisania): gdy parametr ma typ `ptr[S]`, a argument jest zwykłą zmienną
|
||||||
argumentem jest adres zmiennej, jakby napisano `addr(x)`. Tylko zmienne, więc nic nie jest liczone dwa razy, a na
|
typu `S`, argumentem staje się adres tej zmiennej, tak jakby napisano `addr(x)`. Dotyczy to tylko zmiennych, więc nic
|
||||||
wartości tymczasowe nic nie wskazuje.
|
nie jest obliczane dwa razy, a wskaźnik nigdy nie wskazuje na wartość tymczasową.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 13. Znane ograniczenia
|
## 13. Znane ograniczenia
|
||||||
|
|
||||||
- Kompilator działa tylko na Linuksie x86-64 i ARM64 (brak ziarna dla macOS i Windowsa). Programy można budować na wszystkie trzy.
|
- Jeden błąd na uruchomienie, bez kontynuowania po nim.
|
||||||
- Wynik dla macOS jest sprawdzany tylko jako IR; Windows i ARM64 uruchamiane są w CI pod Wine i qemu.
|
- Informacje dla debuggera (`-g`) są najlepsze z `-O0`.
|
||||||
- Jeden błąd na uruchomienie, bez odzyskiwania.
|
- Typy generyczne są monomorfizowane (kod rośnie z każdą instancją); nie ma ograniczeń na parametry typu, więc złe
|
||||||
- Informacje `-g` najlepiej z `-O0`.
|
użycie daje błąd wewnątrz ciała instancji.
|
||||||
- Sterownik używa narzędzi `sh` (`rm`, `mkdir`), więc wymaga powłoki POSIX.
|
- Pamięć, którą miał nieudany test, nie jest zwalniana (uruchamianie testów wychodzi z niego przez `longjmp`).
|
||||||
- Generyki są monomorfizowane (kod rośnie z liczbą instancji); nie ma ograniczeń na typy, błędne użycie to błąd wewnątrz
|
- Wektory nie mają dzielenia całkowitego, a `lookup` na podstawowym x86-64 (bez SSSE3) działa element po elemencie.
|
||||||
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.
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
# Finch dla początkujących
|
# Finch dla początkujących
|
||||||
|
|
||||||
**Ten przewodnik jest dla ciebie, jeśli jeszcze nigdy nie pisałeś programu.**
|
**Programowanie to dla ciebie coś zupełnie nowego? Ten przewodnik jest dla ciebie.**
|
||||||
Nie potrzebujesz żadnego doświadczenia ani matematyki ponad to, czego uczy się w podstawówce.
|
Nie musisz nic umieć: wystarczy matematyka ze szkoły podstawowej.
|
||||||
Czytaj powoli, po jednym rozdziale, i przepisuj każdy przykład samodzielnie. Tak zostaje w głowie najlepiej.
|
Czytaj spokojnie, rozdział po rozdziale, i każdy przykład przepisuj sam: tak najlepiej zostaje w głowie.
|
||||||
|
|
||||||
> 🇬🇧 English version: [for-beginners.md](../en/for-beginners.md)
|
> 🇬🇧 English version: [for-beginners.md](../en/for-beginners.md)
|
||||||
|
|
||||||
|
|
@ -13,7 +13,7 @@ Czytaj powoli, po jednym rozdziale, i przepisuj każdy przykład samodzielnie. T
|
||||||
1. [Co to jest program?](#1-co-to-jest-program)
|
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 ekran](#4-wypisywanie-na-ekran)
|
4. [Wypisywanie na ekranie](#4-wypisywanie-na-ekranie)
|
||||||
5. [Pudełka z nazwami: zmienne](#5-pudełka-z-nazwami-zmienne)
|
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,36 +28,35 @@ Czytaj powoli, po jednym rozdziale, i przepisuj każdy przykład samodzielnie. T
|
||||||
16. [Kiedy coś pójdzie nie tak](#16-kiedy-coś-pójdzie-nie-tak)
|
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łowniczek](#19-słowniczek)
|
19. [Słowa, które będziesz słyszeć](#19-słowa-które-będziesz-słyszeć)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Co to jest program?
|
## 1. Co to jest program?
|
||||||
|
|
||||||
Komputer jest bardzo szybki, ale nie myśli. Robi dokładnie to, co mu się każe,
|
Komputer jest bardzo szybki, ale nie myśli. Robi dokładnie to, co mu każesz, krok po kroku.
|
||||||
krok po kroku.
|
|
||||||
|
|
||||||
**Program** to spisana lista takich kroków. Trochę jak przepis kuchenny:
|
**Program** to spisana lista takich kroków. Trochę jak przepis:
|
||||||
|
|
||||||
> 1. Weź dwa jajka.
|
> 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, który komputer rozumie.
|
**Język programowania** to sposób zapisania takiego przepisu tak, żeby komputer go zrozumiał.
|
||||||
**Finch** (po polsku: zięba) jest jednym z takich języków. Zrobiono go tak, żeby był
|
**Finch** to jeden z takich języków. Powstał po to, żeby był mały i prosty, więc szybko się go nauczysz.
|
||||||
mały i prosty, więc nauczysz się go szybko.
|
|
||||||
|
|
||||||
Komputer czyta program od góry do dołu, linijka po linijce.
|
Komputer czyta program od góry do dołu, linia po linii.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. Przygotowanie Fincha
|
## 2. Przygotowanie Fincha
|
||||||
|
|
||||||
Finch działa na komputerach z **Linuksem**. Jeśli nauczyciel, rodzic albo znajomy już go
|
Finch działa na komputerach z **Linuksem**, **macOS** i **Windowsem**. Jeśli nauczyciel, rodzic albo znajomy
|
||||||
dla ciebie zainstalował, przejdź od razu do następnego rozdziału.
|
już go dla ciebie przygotował, przejdź do następnego rozdziału. (Na Macu i na Windowsie poproś kogoś o skorzystanie
|
||||||
|
z [przewodnika dla techników](dla-technikow.md#2-instalacja): są tam gotowe wersje Fincha na oba systemy.)
|
||||||
|
|
||||||
W przeciwnym razie otwórz program **Terminal** (okienko, w którym wpisuje się polecenia)
|
W przeciwnym razie otwórz program **Terminal** (okno, w którym wpisuje się polecenia) i wpisz po kolei te linie,
|
||||||
i wpisz te linijki po kolei, naciskając **Enter** po każdej:
|
naciskając po każdej **Enter**:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/Nexoniarz/Finch.git
|
git clone https://github.com/Nexoniarz/Finch.git
|
||||||
|
|
@ -66,24 +65,24 @@ nix-shell
|
||||||
bootstrap/build.sh
|
bootstrap/build.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Jeśli ostatnia linijka skończy się bez słowa `error`, Finch jest gotowy.
|
Jeśli ostatnie polecenie skończy się bez słowa `error`, Finch jest gotowy.
|
||||||
(Jeśli komputer nie zna polecenia `nix-shell`, poproś kogoś o skorzystanie z
|
(Jeśli na twoim komputerze nie ma `nix-shell`, poproś kogoś o skorzystanie z
|
||||||
[przewodnika dla techników](dla-technikow.md#2-instalacja), gdzie opisano inny sposób.)
|
[przewodnika dla techników](dla-technikow.md#2-instalacja). Jest tam opisany inny sposób.)
|
||||||
|
|
||||||
Żeby sprawdzić, czy działa, wpisz:
|
Żeby sprawdzić, czy wszystko działa, wpisz:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./build/finch version
|
./build/finch version
|
||||||
```
|
```
|
||||||
|
|
||||||
Powinieneś zobaczyć coś w stylu `finch 2.5.0`.
|
Powinien pojawić się napis w rodzaju `Finch 2.6.0`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Twój pierwszy program
|
## 3. Twój pierwszy program
|
||||||
|
|
||||||
1. Otwórz dowolny edytor tekstu (na przykład *Edytor tekstu*, *Kate*, *gedit* albo *VS Code*).
|
1. Otwórz dowolny edytor tekstu (np. *Edytor tekstu*, *Kate*, *gedit* albo *VS Code*).
|
||||||
2. Wpisz dokładnie to:
|
2. Przepisz dokładnie to:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
@ -91,11 +90,11 @@ fn main() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Zapisz plik w folderze `Finch` pod nazwą **`czesc.fch`**. Końcówka `.fch` mówi, że to program w Finchu.
|
3. Zapisz plik w katalogu `Finch` pod nazwą **`hello.fch`**. Końcówka `.fch` mówi, że to program w Finchu.
|
||||||
4. W Terminalu wpisz:
|
4. W Terminalu wpisz:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./build/finch run czesc.fch
|
./build/finch run hello.fch
|
||||||
```
|
```
|
||||||
|
|
||||||
Zobaczysz:
|
Zobaczysz:
|
||||||
|
|
@ -104,7 +103,7 @@ Zobaczysz:
|
||||||
Cześć!
|
Cześć!
|
||||||
```
|
```
|
||||||
|
|
||||||
🎉 **Właśnie napisałeś i uruchomiłeś program.**
|
🎉 **Właśnie udało ci się napisać i uruchomić program.**
|
||||||
|
|
||||||
### Co właściwie napisaliśmy?
|
### Co właściwie napisaliśmy?
|
||||||
|
|
||||||
|
|
@ -114,26 +113,26 @@ fn main() { ← „tu zaczyna się główna część mojego programu
|
||||||
} ← „tu główna część się kończy”
|
} ← „tu główna część się kończy”
|
||||||
```
|
```
|
||||||
|
|
||||||
- `fn main()` to miejsce, od którego **zaczyna się** każdy program w Finchu. Każdy program ma dokładnie jedno `main`.
|
- `fn main()` to miejsce, w którym **zaczyna się** każdy program w Finchu. Każdy program ma dokładnie jedno `main`.
|
||||||
- Nawiasy klamrowe `{` i `}` obejmują wszystko, co należy do `main`, jak dwie okładki książki.
|
- 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 piszemy w cudzysłowie: `"Cześć!"`.
|
- Tekst zawsze stoi w cudzysłowie: `"Cześć!"`.
|
||||||
|
|
||||||
Spacje przed `print` nie są obowiązkowe, ale dzięki nim od razu widać, co jest w środku
|
Spacje przed `print` nie są obowiązkowe, ale dzięki nim od razu widać, co jest w środku nawiasów.
|
||||||
klamer. Zawsze je dodawaj, to dobry nawyk.
|
Zawsze je dodawaj. To dobry nawyk.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Wypisywanie na ekran
|
## 4. Wypisywanie na ekranie
|
||||||
|
|
||||||
`print` umie pokazać tekst i liczby. Między kolejne rzeczy wstaw przecinek, a Finch sam doda spację:
|
`print` potrafi pokazać tekst i liczby. Oddziel je przecinkami, a Finch sam wstawi między nimi spacje:
|
||||||
|
|
||||||
```c
|
```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("Linijka wyżej jest pusta")
|
print("Linia wyżej jest pusta")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -143,10 +142,10 @@ Pokaże:
|
||||||
Mam 10 lat
|
Mam 10 lat
|
||||||
2 + 2 = 4
|
2 + 2 = 4
|
||||||
|
|
||||||
Linijka wyżej jest pusta
|
Linia wyżej jest pusta
|
||||||
```
|
```
|
||||||
|
|
||||||
Każdy `print` zaczyna nową linijkę. `print()` bez niczego w środku robi pustą linijkę.
|
Każde `print` zaczyna nową linię. `print()` bez niczego w środku robi pustą linię.
|
||||||
|
|
||||||
### Notatki dla siebie: komentarze
|
### Notatki dla siebie: komentarze
|
||||||
|
|
||||||
|
|
@ -163,8 +162,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 potem sprawdzić,
|
Wyobraź sobie pudełko z naklejoną etykietą. Możesz coś do niego włożyć, a później sprawdzić, co jest w środku,
|
||||||
co jest w środku, czytając etykietę. W programowaniu takie pudełko nazywa się **zmienną**.
|
czytając etykietę. W programowaniu takie pudełko nazywa się **zmienną**.
|
||||||
|
|
||||||
```c
|
```c
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
@ -173,9 +172,9 @@ fn main() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`wiek := 10` znaczy: *„zrób nowe pudełko, nazwij je `wiek` i włóż do niego 10”*.
|
`wiek := 10` znaczy: *„zrób nowe pudełko, nazwij je `wiek` i włóż do niego 10”.*
|
||||||
|
|
||||||
Później możesz zmienić zawartość pudełka, używając samego `=`:
|
Później możesz zmienić zawartość pudełka, pisząc samo `=`:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
@ -196,42 +195,39 @@ Punkty: 5
|
||||||
Punkty: 6
|
Punkty: 6
|
||||||
```
|
```
|
||||||
|
|
||||||
`punkty = punkty + 1` czytamy tak: *„weź to, co jest w `punkty`, dodaj 1 i włóż wynik z powrotem do `punkty`”*.
|
`punkty = punkty + 1` czyta się tak: *„weź to, co jest w `punkty`, dodaj 1 i włóż wynik z powrotem do `punkty`”.*
|
||||||
To samo można zapisać krócej: `punkty += 1`.
|
To samo można napisać krócej: `punkty += 1`.
|
||||||
|
|
||||||
**Zapamiętaj:**
|
**Zapamiętaj:**
|
||||||
|
|
||||||
- `:=` robi **nowe** pudełko. Używasz tego tylko raz dla danej nazwy.
|
- `:=` robi **nowe** pudełko. Dla każdej nazwy używasz tego tylko raz.
|
||||||
- `=` wkłada nową wartość do pudełka, **które już istnieje**.
|
- `=` wkłada nową wartość do pudełka, **które już istnieje**.
|
||||||
|
|
||||||
### Nazywanie pudełek
|
### Nazywanie pudełek
|
||||||
|
|
||||||
Nazwy mogą zawierać angielskie litery `a`–`z` i `A`–`Z`, cyfry i znak `_`, ale nie mogą zaczynać się od cyfry:
|
W nazwach można używać liter od `a` do `z` i od `A` do `Z`, cyfr i znaku `_`, ale nazwa nie może zaczynać się od cyfry:
|
||||||
`wiek`, `suma_punktow`, `gracz2` są w porządku. `2gracz` już nie.
|
`wiek`, `suma_punktow`, `gracz2` są w porządku, a `2gracz` nie.
|
||||||
|
Polskich liter (`ą`, `ę`, `ż`…) nie wolno używać w nazwach, tylko w tekstach w cudzysłowie: `print("Łódź")` jest w porządku.
|
||||||
⚠️ **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, *jakiego rodzaju* rzecz jest w każdym pudełku. Ten rodzaj nazywa się **typem**.
|
Finch pilnuje, jaki *rodzaj* rzeczy leży w każdym pudełku. Taki rodzaj to **typ**.
|
||||||
Najczęściej będziesz używać tych:
|
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` (prawda), `false` (fałsz) |
|
| `bool` | tak albo nie | `true`, `false` |
|
||||||
| `char` | jeden znak | `'A'`, `'z'`, `'?'` |
|
| `char` | jedną literę albo znak | `'A'`, `'z'`, `'?'` |
|
||||||
|
|
||||||
⚠️ W liczbach zamiast przecinka piszemy **kropkę**: `3.14`, a nie `3,14`. Tak jest w prawie wszystkich językach programowania.
|
Uwaga: w programach część ułamkową oddziela się **kropką**, nie przecinkiem: `3.14`, a nie `3,14`.
|
||||||
|
|
||||||
Kiedy używasz `:=`, Finch sam rozpoznaje typ:
|
Przy `:=` Finch sam rozpoznaje typ:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
imie := "Ola" // str
|
imie := "Ola" // str
|
||||||
|
|
@ -240,7 +236,7 @@ wzrost := 1.42 // float
|
||||||
lubi_koty := true // bool
|
lubi_koty := true // bool
|
||||||
```
|
```
|
||||||
|
|
||||||
Typ możesz też napisać sam, przed nazwą:
|
Typ można też napisać samemu, przed nazwą:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
int wiek = 10
|
int wiek = 10
|
||||||
|
|
@ -249,24 +245,23 @@ 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 nie zmienia typu.** Jeśli w `wiek` są liczby całkowite, nie włożysz tam potem tekstu.
|
**Pudełko trzyma się swojego typu.** Jeśli w `wiek` są liczby całkowite, nie włożysz tam potem tekstu.
|
||||||
Finch zatrzyma się i powie o tym, co pomaga wcześnie wyłapywać pomyłki.
|
Finch się zatrzyma i ci o tym powie, a to pomaga wcześnie wyłapywać pomyłki.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. Liczenie
|
## 7. Liczenie
|
||||||
|
|
||||||
| Znak | Co robi | Przykład | Wynik |
|
| Znak | Znaczenie | 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` |
|
||||||
|
|
||||||
**Uwaga na dzielenie liczb całkowitych:** `7 / 2` daje `3`, a nie `3.5`. Obie liczby są
|
**Uważaj przy dzieleniu liczb całkowitych:** `7 / 2` daje `3`, a nie `3.5`, bo obie liczby są całkowite, więc wynik
|
||||||
całkowite, więc wynik też jest całkowity (część po kropce jest odrzucana).
|
też jest całkowity (część po kropce znika). Jeśli chcesz `3.5`, użyj liczby z kropką: `7.0 / 2`.
|
||||||
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ść:
|
||||||
|
|
@ -276,28 +271,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
|
`%` przydaje się do sprawdzania, czy liczba jest parzysta: liczba parzysta dzielona przez 2 nie daje reszty,
|
||||||
nie daje reszty, więc `n % 2` wynosi `0`.
|
więc `n % 2` wynosi `0`.
|
||||||
|
|
||||||
**Nie wolno dzielić przez zero.** Jeśli program spróbuje, zatrzyma się i powie, w której linijce to się stało.
|
**Nie wolno dzielić przez zero.** Jeśli program spróbuje, zatrzyma się i powie, w której linii to się stało.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8. Podejmowanie decyzji: if
|
## 8. Podejmowanie decyzji: if
|
||||||
|
|
||||||
Program może wybierać, co zrobić. `if` (czyt. „if”, po polsku „jeżeli”) znaczy *„zrób to tylko wtedy, gdy coś jest prawdą”*.
|
Program może wybierać, co zrobić. `if` znaczy *„zrób to tylko wtedy, gdy coś jest prawdą”*.
|
||||||
|
|
||||||
```c
|
```c
|
||||||
fn main() {
|
fn main() {
|
||||||
temperatura := 25
|
temperatura := 25
|
||||||
|
|
||||||
if temperatura > 20 {
|
if temperatura > 20 {
|
||||||
print("Jest ciepło, załóż koszulkę")
|
print("Jest ciepło, włóż koszulkę")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Dodaj `else` („w przeciwnym razie”):
|
Dopisz `else`, żeby powiedzieć *„a jeśli nie, zrób to”*:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
if temperatura > 20 {
|
if temperatura > 20 {
|
||||||
|
|
@ -307,12 +302,11 @@ if temperatura > 20 {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
A `else if` dodaje kolejne możliwości. Finch sprawdza je od góry i wykonuje tylko
|
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ą:
|
||||||
**pierwszą** prawdziwą:
|
|
||||||
|
|
||||||
```c
|
```c
|
||||||
if temperatura > 25 {
|
if temperatura > 25 {
|
||||||
print("Gorąco!")
|
print("Upał!")
|
||||||
} else if temperatura > 15 {
|
} else if temperatura > 15 {
|
||||||
print("Przyjemnie")
|
print("Przyjemnie")
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -328,18 +322,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 lub równe |
|
| `<=` | jest mniejsze albo równe |
|
||||||
| `>=` | jest większe lub równe |
|
| `>=` | jest większe albo 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?”.**
|
||||||
|
|
||||||
Tekst też można porównywać: `if imie == "Ola" { ... }`.
|
Porównywać można też teksty: `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**: wystarczy, że jeden jest prawdziwy. `if dzien == "sob" || dzien == "nd" { ... }`
|
- `||` znaczy **lub**: przynajmniej jeden musi być prawdziwy. `if dzien == "sob" || dzien == "niedz" { ... }`
|
||||||
- `!` znaczy **nie**: odwraca prawdę i fałsz. `if !pada { ... }`
|
- `!` znaczy **nie**: zamienia prawdę w fałsz i odwrotnie. `if !pada { ... }`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -365,7 +359,7 @@ Liczba 4
|
||||||
Liczba 5
|
Liczba 5
|
||||||
```
|
```
|
||||||
|
|
||||||
`for i in 1..6` znaczy: *„licz za pomocą `i` od 1 do 6, ale **bez** 6”*.
|
`for i in 1..6` znaczy: *„licz w `i` od 1 do 6, ale **bez** 6”*.
|
||||||
Czyli `0..10` daje dziesięć liczb: 0, 1, 2 … 9.
|
Czyli `0..10` daje dziesięć liczb: 0, 1, 2 … 9.
|
||||||
|
|
||||||
### Powtarzanie, dopóki coś jest prawdą: `while`
|
### Powtarzanie, dopóki coś jest prawdą: `while`
|
||||||
|
|
@ -388,8 +382,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ę
|
⚠️ Upewnij się, że pętla może się skończyć. Gdyby `licznik` nigdy nie malał, pętla kręciłaby się bez końca.
|
||||||
w nieskończoność. Jeśli tak się stanie, naciśnij w Terminalu **Ctrl + C**, żeby ją zatrzymać.
|
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
|
||||||
|
|
||||||
|
|
@ -399,9 +393,9 @@ w nieskończoność. Jeśli tak się stanie, naciśnij w Terminalu **Ctrl + C**,
|
||||||
```c
|
```c
|
||||||
for i in 1..100 {
|
for i in 1..100 {
|
||||||
if i == 5 {
|
if i == 5 {
|
||||||
break // stop, gdy dojdziemy do 5
|
break // zatrzymaj się, gdy dojdziemy do 5
|
||||||
}
|
}
|
||||||
print(i) // pokaże 1 2 3 4
|
print(i) // wypisze 1 2 3 4
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -409,7 +403,7 @@ for i in 1..100 {
|
||||||
|
|
||||||
## 10. Własne polecenia: funkcje
|
## 10. Własne polecenia: funkcje
|
||||||
|
|
||||||
Kiedy robisz to samo w wielu miejscach, nadaj temu nazwę. Taki nazwany kawałek kodu to **funkcja**.
|
Kiedy robisz to samo w wielu miejscach, nadaj temu nazwę. Taki nazwany blok to **funkcja**.
|
||||||
Jedną już znasz: `main`.
|
Jedną już znasz: `main`.
|
||||||
|
|
||||||
```c
|
```c
|
||||||
|
|
@ -428,12 +422,12 @@ Cześć, Ola
|
||||||
Cześć, Tomek
|
Cześć, Tomek
|
||||||
```
|
```
|
||||||
|
|
||||||
`str imie` w nawiasie znaczy: *„ta funkcja potrzebuje jednego tekstu, a w środku nazywamy go `imie`”*.
|
`str imie` w nawiasach znaczy: *„ta funkcja potrzebuje jednego tekstu i w środku będziemy go nazywać `imie`”.*
|
||||||
To, co wpiszesz w nawias przy wywołaniu, zostanie przekazane do środka.
|
To, co wpiszesz w nawiasach przy jej użyciu, trafia do środka.
|
||||||
|
|
||||||
### Funkcje, które oddają odpowiedź
|
### Funkcje, które dają odpowiedź
|
||||||
|
|
||||||
Napisz `->` i typ odpowiedzi, a odpowiedź oddaj słowem `return`:
|
Napisz `->` i typ odpowiedzi, a odpowiedź oddaj przez `return`:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
fn dodaj(int a, int b) -> int {
|
fn dodaj(int a, int b) -> int {
|
||||||
|
|
@ -446,14 +440,14 @@ fn main() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Nie ma znaczenia, gdzie w pliku umieścisz swoje funkcje, nad czy pod `main`.
|
Nie ma znaczenia, gdzie w pliku umieścisz swoje funkcje, nad `main` czy pod nim.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 11. Listy: wiele wartości w jednym pudełku
|
## 11. Listy: wiele wartości w jednym pudełku
|
||||||
|
|
||||||
Czasem jedno pudełko nie wystarczy. Na liście zakupów jest wiele rzeczy.
|
Czasem jedno pudełko nie wystarcza. Na liście zakupów jest wiele rzeczy.
|
||||||
**Lista** (programiści mówią też **tablica**) trzyma wiele wartości po kolei:
|
**Lista** (programiści mówią też **tablica**) przechowuje wiele wartości po kolei:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
@ -470,34 +464,34 @@ Mam 3 owoce
|
||||||
Pierwszy to jabłko
|
Pierwszy to jabłko
|
||||||
```
|
```
|
||||||
|
|
||||||
- Nawiasy kwadratowe `[ ]` tworzą listę. Wartości oddzielamy przecinkami.
|
- Nawiasy kwadratowe `[ ]` tworzą listę. Wartości oddziela się przecinkami.
|
||||||
- `.len` mówi, ile rzeczy jest na liście.
|
- `.len` mówi, ile rzeczy jest na liście.
|
||||||
- `owoce[0]` to **pierwsza** rzecz. Liczymy od **0**, więc `owoce[1]` to druga, a `owoce[2]` trzecia.
|
- `owoce[0]` to **pierwsza** rzecz. Liczy się od **0**, więc `owoce[1]` to druga, a `owoce[2]` trzecia.
|
||||||
|
|
||||||
Jeśli zapytasz o miejsce, którego nie ma, np. `owoce[10]`, program się zatrzyma i powie:
|
Jeśli zapytasz o miejsce, którego nie ma, np. `owoce[10]`, program się zatrzyma i powie:
|
||||||
`index 10 is out of range (the length is 3)`, czyli „indeks 10 jest poza zakresem (długość to 3)”.
|
`index 10 is out of range (the length is 3)` (indeks 10 jest poza listą, która ma długość 3).
|
||||||
|
|
||||||
### Dodawanie i zabieranie
|
### 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) // dołóż 10 na koniec
|
wyniki.push(10) // dodaj 10 na końcu
|
||||||
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() // zabierz ostatni
|
ostatni := wyniki.pop() // zdejmij ostatni
|
||||||
print(ostatni, wyniki) // 7 [10, 25]
|
print(ostatni, wyniki) // 7 [10, 25]
|
||||||
|
|
||||||
wyniki.sort() // posortuj
|
wyniki.sort()
|
||||||
print(wyniki)
|
print(wyniki)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Przechodzenie po liście
|
### Przechodzenie po liście
|
||||||
|
|
||||||
`for ... in` odwiedza każdą wartość po kolei:
|
`for ... in` odwiedza po kolei każdą wartość:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
@ -509,8 +503,8 @@ fn main() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Jeśli chcesz też wiedzieć, *w którym miejscu* listy jesteś, daj `for` dwie nazwy: pierwsza liczy
|
Jeśli chcesz też wiedzieć, *w którym miejscu* listy jesteś, podaj w `for` dwie nazwy: pierwsza liczy 0, 1, 2…,
|
||||||
0, 1, 2…, druga to wartość:
|
a druga to wartość:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
@ -526,21 +520,20 @@ fn main() {
|
||||||
3 wiśnia
|
3 wiśnia
|
||||||
```
|
```
|
||||||
|
|
||||||
Listy umieją więcej: `contains` (czy zawiera), `find` (znajdź), `insert` (wstaw), `remove` (usuń),
|
Listy potrafią więcej: `contains`, `find`, `insert`, `remove`, `sort`, `reverse`, `join`.
|
||||||
`sort` (sortuj), `reverse` (odwróć), `join` (połącz). Wszystkie opisuje [przewodnik dla techników](dla-technikow.md#10-tablice-i-mapy).
|
Wszystkie są opisane w [przewodniku 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 chcesz znaleźć coś po **nazwie**, tak jak
|
Lista znajduje rzeczy po miejscu: `owoce[0]`. Czasem jednak chcesz szukać po **nazwie**, tak jak szuka się słowa
|
||||||
słowo w słowniku albo numer w książce telefonicznej. Do tego służy **mapa** (`map`): każdy
|
w słowniku albo numeru w książce telefonicznej. Do tego służy **mapa**: każdy **klucz** (nazwa) ma swoją **wartość**:
|
||||||
**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" // nowa osoba
|
telefon["Ewa"] = "555-0000" // dodaj nowe imię
|
||||||
print(telefon.len) // 3
|
print(telefon.len) // 3
|
||||||
|
|
||||||
if telefon.has("Tomek") {
|
if telefon.has("Tomek") {
|
||||||
|
|
@ -549,26 +542,26 @@ fn main() {
|
||||||
print("nie ma Tomka")
|
print("nie ma Tomka")
|
||||||
}
|
}
|
||||||
|
|
||||||
for imie, numer in telefon { // każde imię z jego numerem
|
for imie, numer in telefon { // każde imię razem z numerem
|
||||||
print(imie, numer)
|
print(imie, numer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- `["klucz": wartość, ...]` tworzy mapę. Klucze to zwykle tekst, ale mogą być też liczby.
|
- `["klucz": wartość, ...]` tworzy mapę. Kluczami są często teksty, ale mogą być też liczby.
|
||||||
- `telefon["Ola"]` daje wartość dla klucza. Pytanie o klucz, którego nie ma, zatrzymuje program,
|
- `telefon["Ola"]` daje wartość dla klucza. Pytanie o klucz, którego nie ma, zatrzymuje program,
|
||||||
więc najpierw sprawdź `telefon.has("Tomek")` (czy ma) albo użyj `telefon.get("Tomek", "nie wiem")`.
|
więc najpierw sprawdź `telefon.has("Tomek")` albo użyj `telefon.get("Tomek", "nieznany")`.
|
||||||
- `for imie, numer in telefon` odwiedza każdy klucz z wartością, w kolejności dodawania.
|
- `for imie, numer in telefon` odwiedza każdy klucz z jego wartością, w kolejności, w jakiej je dodano.
|
||||||
|
|
||||||
Mapy świetnie nadają się do **liczenia**. Nowy klucz zaczyna od 0, więc `+= 1` po prostu działa:
|
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 tekst, wartości to liczby
|
map[str]int glosy // pusta mapa: klucze to teksty, wartości to liczby całkowite
|
||||||
for g in ["kot", "pies", "kot", "kot", "rybka"] {
|
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}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -576,7 +569,7 @@ fn main() {
|
||||||
|
|
||||||
## 12. Zabawa tekstem
|
## 12. Zabawa tekstem
|
||||||
|
|
||||||
Teksty sklejasz znakiem `+`:
|
Teksty można sklejać przez `+`:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
@ -600,16 +593,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 (wielkimi literami)
|
print(slowo.upper()) // FINCH
|
||||||
print(slowo.lower()) // finch (małymi literami)
|
print(slowo.lower()) // finch
|
||||||
print(slowo.contains("in")) // true (czy zawiera "in")
|
print(slowo.contains("in")) // true
|
||||||
for litera in slowo {
|
for litera in slowo {
|
||||||
print(litera) // F, i, n, c, h, każda w osobnej linijce
|
print(litera) // F, i, n, c, h, każda w osobnej linii
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
⚠️ `.len` liczy bajty, a polskie litery (ą, ę, ż…) zajmują po 2 bajty. `"żółw".len` to 7, a nie 4.
|
(`.len` liczy bajty, a polskie litery zajmują po dwa, więc `"żółw".len` to 7, a nie 4.)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -634,15 +627,15 @@ fn main() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Jeśli ktoś wpisze coś, co nie jest liczbą, program zatrzyma się z komunikatem
|
Jeśli ktoś wpisze coś, co nie jest liczbą, program się zatrzyma z komunikatem
|
||||||
w stylu `can't turn "abc" into int` („nie da się zamienić "abc" na int”).
|
w rodzaju `can't turn "abc" into int` (nie da się zamienić "abc" na liczbę).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 14. Własne rodzaje wartości: struktury
|
## 14. Własne rodzaje wartości: struktury
|
||||||
|
|
||||||
Wyobraź sobie kartę w bibliotece: każda karta ma tytuł, autora i rok.
|
Pomyśl o karcie w bibliotece: każda ma tytuł, autora i rok wydania.
|
||||||
**Struktura** (`struct`) pozwala zrobić własny rodzaj wartości z nazwanymi częściami:
|
**Struktura** pozwala stworzyć własny rodzaj wartości z nazwanymi częściami:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
struct Ksiazka {
|
struct Ksiazka {
|
||||||
|
|
@ -664,11 +657,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. Pisz to **poza** `main`.
|
- `struct Ksiazka { ... }` opisuje, co ma każda książka. Umieść to **poza** `main`.
|
||||||
- `Ksiazka(tytul: ..., autor: ..., rok: ...)` tworzy jedną książkę.
|
- `Ksiazka(tytul: ..., autor: ..., rok: ...)` tworzy jedną książkę.
|
||||||
- `k.tytul` odczytuje jedną część, a `k.rok = 1938` ją zmienia.
|
- `k.tytul` odczytuje jedną część. `k.rok = 1938` ją zmienia.
|
||||||
|
|
||||||
Częściom możesz nadać wartość początkową. Wtedy nie trzeba ich podawać:
|
Częściom można nadać wartość początkową. Wtedy nie trzeba ich wypełniać:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
struct Gracz {
|
struct Gracz {
|
||||||
|
|
@ -682,12 +675,12 @@ fn main() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Możesz też trzymać wiele struktur na liście: `[]Ksiazka polka`, a potem `polka.push(k)`.
|
Wiele takich wartości można trzymać na liście: `[]Ksiazka polka`, a potem `polka.push(k)`.
|
||||||
|
|
||||||
### Polecenia twojej struktury: metody
|
### Polecenia dla struktury: metody
|
||||||
|
|
||||||
Struktura może mieć własne polecenia (**metody**). Napisz nazwę struktury, kropkę i nazwę polecenia.
|
Struktura może mieć własne polecenia. Napisz nazwę struktury, kropkę i nazwę polecenia.
|
||||||
W środku `self` (ang. „sam”) to struktura, na której użyto polecenia:
|
W środku `self` to struktura, na której polecenie użyto:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
struct Gracz {
|
struct Gracz {
|
||||||
|
|
@ -697,7 +690,7 @@ struct Gracz {
|
||||||
|
|
||||||
fn Gracz.trafiony() {
|
fn Gracz.trafiony() {
|
||||||
self.zycia -= 1
|
self.zycia -= 1
|
||||||
print(self.imie, "ma jeszcze", self.zycia, "życia")
|
print(self.imie, "- pozostałe życia:", self.zycia)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn Gracz.zyje() -> bool {
|
fn Gracz.zyje() -> bool {
|
||||||
|
|
@ -706,8 +699,8 @@ fn Gracz.zyje() -> bool {
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
g := Gracz(imie: "Ola")
|
g := Gracz(imie: "Ola")
|
||||||
g.trafiony() // Ola ma jeszcze 2 życia
|
g.trafiony() // Ola - pozostałe życia: 2
|
||||||
g.trafiony() // Ola ma jeszcze 1 życia
|
g.trafiony() // Ola - pozostałe życia: 1
|
||||||
print(g.zyje()) // true
|
print(g.zyje()) // true
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
@ -731,14 +724,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 folderze, w którym uruchomiłeś program.
|
Plik pojawi się w katalogu, w którym uruchomiono program.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 16. Kiedy coś pójdzie nie tak
|
## 16. Kiedy coś pójdzie nie tak
|
||||||
|
|
||||||
Każdy się myli przy programowaniu, nawet eksperci, codziennie.
|
Każdy, kto programuje, robi błędy, nawet eksperci, codziennie.
|
||||||
Finch stara się tłumaczyć, co poszło nie tak, prostymi słowami (na razie po angielsku). Przykład:
|
Finch bardzo się stara wyjaśnić prostymi słowami, co poszło nie tak. Przykład:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
@ -748,68 +741,67 @@ fn main() {
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
czesc.fch:3:11: error: there is no variable named 'wiekk'
|
hello.fch:3:11: error: there is no variable named 'wiekk'
|
||||||
3 | print(wiekk)
|
3 | print(wiekk)
|
||||||
| ^
|
| ^
|
||||||
```
|
```
|
||||||
|
|
||||||
Jak to czytać:
|
Jak to czytać:
|
||||||
|
|
||||||
- `czesc.fch` to plik,
|
- `hello.fch` to plik,
|
||||||
- `3` to numer **linijki**,
|
- `3` to numer **linii**,
|
||||||
- `11` to który znak w linijce (**kolumna**),
|
- `11` mówi, jak daleko w linii (to **kolumna**),
|
||||||
- potem wyjaśnienie, a pod nim ta linijka ze strzałką `^` wskazującą problem.
|
- dalej jest wyjaśnienie, a pod nim ta linia ze znakiem `^`, który wskazuje problem.
|
||||||
|
|
||||||
Tutaj po prostu źle napisaliśmy `wiek`. Komunikat znaczy: „nie ma zmiennej o nazwie 'wiekk'”.
|
Komunikaty są po angielsku; tutaj znaczy to „nie ma zmiennej o nazwie 'wiekk'”. Po prostu literówka w `wiek`.
|
||||||
|
|
||||||
### Częste błędy
|
### Częste pomyłki
|
||||||
|
|
||||||
| Finch mówi | Co to znaczy | Jak naprawić |
|
| Finch mówi | Co to znaczy | Jak naprawić |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `there is no variable named 'x'` | Nie ma zmiennej `x`: literówka albo zapomniałeś ją zrobić | Sprawdź pisownię. Utwórz ją przez `x := ...` |
|
| `there is no variable named 'x'` | Literówka w nazwie albo takie pudełko nigdy nie powstało | Sprawdź pisownię. Utwórz je przez `x := ...` |
|
||||||
| `a variable named 'x' already exists here` | Użyłeś `:=` dwa razy dla tej samej nazwy | Za drugim razem użyj `=` |
|
| `a variable named 'x' already exists here` | Użyto `:=` dwa razy dla tej samej nazwy | Za drugim razem napisz `=` |
|
||||||
| `'x' must be int, but this is str` | Zły rodzaj wartości dla tego pudełka | Włóż wartość właściwego rodzaju |
|
| `'x' must be int, but this is str` | Zły rodzaj wartości dla tego pudełka | Włóż wartość właściwego rodzaju |
|
||||||
| `put each statement on its own line` | Dwa polecenia w jednej linijce | Wciśnij Enter między nimi |
|
| `put each statement on its own line` | Dwa polecenia w jednej linii | Rozdziel je Enterem |
|
||||||
| `this '{' is never closed` | Brakuje `}` | Dopisz `}` |
|
| `this '{' is never closed` | Brakuje `}` | Dopisz `}` |
|
||||||
| `a condition must be bool` | `if` potrzebuje pytania tak/nie | Porównaj coś: `if x > 0` |
|
| `a condition must be bool` | `if` potrzebuje pytania typu tak/nie | Porównaj coś: `if x > 0` |
|
||||||
| `unexpected character 'ż'` | Polska litera w nazwie | Zmień na zwykłą literę: `zolw` |
|
| `runtime error: division by zero` | W trakcie działania programu nastąpiło dzielenie przez 0 | Sprawdź liczbę przed dzieleniem |
|
||||||
| `runtime error: division by zero` | Program podzielił przez 0 w trakcie działania | Sprawdź liczbę przed dzieleniem |
|
| `index 5 is out of range (the length is 3)` | Pytanie listy o miejsce, którego nie ma | Pamiętaj, że liczy się od 0; sprawdź `.len` |
|
||||||
| `index 5 is out of range (the length is 3)` | Zapytałeś listę o miejsce, którego nie ma | Pamiętaj, że liczymy od 0; sprawdź `.len` |
|
| `can't turn "abc" into int` | `int(...)` dostało tekst, który nie jest liczbą | Sprawdź, co wpisał użytkownik |
|
||||||
| `can't turn "abc" into int` | `int(...)` dostał tekst, który nie jest liczbą | Sprawdź, co wpisał użytkownik |
|
| `can't use '+' on str and int` | Tekst plus liczba | Użyj `str(...)` na liczbie |
|
||||||
| `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żyto polecenia, które może się nie udać, bez powiedzenia, co wtedy zrobić | Dopisz `or ...` (zob. niżej) |
|
||||||
| `'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ą pójść źle *w trakcie działania programu*: ktoś wpisze `abc` zamiast liczby
|
Niektóre rzeczy mogą się nie udać *w trakcie działania programu*: ktoś wpisze `abc` tam, gdzie miała być liczba,
|
||||||
albo pliku nie ma. Zamiast zatrzymywać program, możesz powiedzieć, co wtedy zrobić, słowem **`or`** („albo”):
|
albo nie ma pliku. Zamiast zatrzymywać program, możesz przez **`or`** powiedzieć, co wtedy zrobić:
|
||||||
|
|
||||||
```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 brak notatki)"
|
notatka := read_file("notatka.txt") or "(jeszcze nie ma notatki)"
|
||||||
print(notatka)
|
print(notatka)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Po `or` może też być blok. W środku `err` to komunikat, co poszło nie tak:
|
Po `or` może też stać blok. W jego środku `err` to komunikat, który mówi, co poszło nie tak:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
fn main() {
|
fn main() {
|
||||||
while true {
|
while true {
|
||||||
n := int(input("Podaj liczbę: ")) or {
|
n := int(input("Wybierz liczbę: ")) or {
|
||||||
print("To nie jest liczba, spróbuj jeszcze raz")
|
print("To nie jest liczba, spróbuj jeszcze raz")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
print("Wybrałeś", n)
|
print("Wybrana liczba:", n)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Twoje własne funkcje też mogą się nie udać. Dopisz `!` po typie wyniku i użyj `return error("...")`:
|
Twoje własne funkcje też mogą się nie udać. Postaw `!` po typie wyniku i użyj `return error("...")`:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
fn bezpieczne_dzielenie(int a, int b) -> int! {
|
fn bezpieczne_dzielenie(int a, int b) -> int! {
|
||||||
|
|
@ -825,15 +817,15 @@ fn main() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Finch nie pozwoli ci zapomnieć: wywołanie `bezpieczne_dzielenie(10, 0)` bez `or` to błąd, który
|
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ć.
|
||||||
przypomina, żeby to obsłużyć. (Można też przekazać problem dalej przez `try`; wyjaśnia to
|
(Problem można też przekazać 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, gdy utkniesz.
|
Spróbuj najpierw napisać każdy sam. Do rozwiązania zajrzyj dopiero wtedy, gdy utkniesz.
|
||||||
|
|
||||||
### Projekt 1: Tabliczka mnożenia
|
### Projekt 1: Tabliczka mnożenia
|
||||||
|
|
||||||
|
|
@ -852,7 +844,7 @@ fn main() {
|
||||||
|
|
||||||
### Projekt 2: Parzysta czy nieparzysta
|
### Projekt 2: Parzysta czy nieparzysta
|
||||||
|
|
||||||
Dla każdej liczby od 1 do 10 napisz, czy jest parzysta, czy nieparzysta.
|
Dla każdej liczby od 1 do 10 pokaż, czy jest parzysta, czy nieparzysta.
|
||||||
|
|
||||||
<details><summary>Rozwiązanie</summary>
|
<details><summary>Rozwiązanie</summary>
|
||||||
|
|
||||||
|
|
@ -871,7 +863,7 @@ fn main() {
|
||||||
|
|
||||||
### Projekt 3: Stopnie Celsjusza na Fahrenheita
|
### Projekt 3: Stopnie Celsjusza na Fahrenheita
|
||||||
|
|
||||||
Pokaż tabelkę od 0 °C do 100 °C co 10 stopni. Wzór: `F = C * 9 / 5 + 32`.
|
Pokaż tabelę od 0 °C do 100 °C co 10 stopni. Wzór: `F = C * 9 / 5 + 32`.
|
||||||
|
|
||||||
<details><summary>Rozwiązanie</summary>
|
<details><summary>Rozwiązanie</summary>
|
||||||
|
|
||||||
|
|
@ -893,8 +885,8 @@ fn main() {
|
||||||
|
|
||||||
### Projekt 4: Fizz Buzz
|
### Projekt 4: Fizz Buzz
|
||||||
|
|
||||||
Policz od 1 do 20. Dla liczb podzielnych przez 3 napisz `Fizz`, przez 5 napisz `Buzz`,
|
Policz od 1 do 20. Dla liczb podzielnych przez 3 powiedz `Fizz`, przez 5 `Buzz`,
|
||||||
przez obie naraz napisz `FizzBuzz`, a w pozostałych przypadkach samą liczbę.
|
przez obie `FizzBuzz`, a w pozostałych przypadkach samą liczbę.
|
||||||
|
|
||||||
<details><summary>Rozwiązanie</summary>
|
<details><summary>Rozwiązanie</summary>
|
||||||
|
|
||||||
|
|
@ -917,7 +909,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 oddaje największą z trzech liczb.
|
Napisz funkcję `najwieksza(int a, int b, int c) -> int`, która zwraca największą liczbę.
|
||||||
|
|
||||||
<details><summary>Rozwiązanie</summary>
|
<details><summary>Rozwiązanie</summary>
|
||||||
|
|
||||||
|
|
@ -940,7 +932,8 @@ fn main() {
|
||||||
|
|
||||||
### Projekt 6: Zgadnij liczbę
|
### Projekt 6: Zgadnij liczbę
|
||||||
|
|
||||||
Komputer ma tajną liczbę. Gracz zgaduje, aż trafi, a komputer za każdym razem mówi „więcej” albo „mniej”.
|
Komputer wybiera tajną liczbę. Gracz zgaduje, aż trafi, a komputer za każdym razem
|
||||||
|
mówi „więcej” albo „mniej”.
|
||||||
|
|
||||||
<details><summary>Rozwiązanie</summary>
|
<details><summary>Rozwiązanie</summary>
|
||||||
|
|
||||||
|
|
@ -956,7 +949,7 @@ fn main() {
|
||||||
} else if strzal > tajna {
|
} else if strzal > tajna {
|
||||||
print("Mniej!")
|
print("Mniej!")
|
||||||
} else {
|
} else {
|
||||||
print("Tak! Potrzebowałeś", proby, "prób")
|
print("Tak! Liczba prób:", proby)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -968,7 +961,7 @@ fn main() {
|
||||||
|
|
||||||
### Projekt 7: Średnia ocen
|
### Projekt 7: Średnia ocen
|
||||||
|
|
||||||
Zapytaj o 3 oceny, zapisz je na liście, a potem pokaż listę i średnią.
|
Zapytaj o 3 oceny, zapisz je na liście i pokaż listę oraz średnią.
|
||||||
|
|
||||||
<details><summary>Rozwiązanie</summary>
|
<details><summary>Rozwiązanie</summary>
|
||||||
|
|
||||||
|
|
@ -998,9 +991,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 zgadnięty)
|
x := 5 // nowe pudełko (typ rozpoznany sam)
|
||||||
int y = 10 // nowe pudełko (typ podany)
|
int y = 10 // nowe pudełko (typ podany)
|
||||||
x = 7 // zmień zawartość
|
x = 7 // zmiana zawartości pudełka
|
||||||
x += 1 // dodaj 1 do x
|
x += 1 // dodaj 1 do x
|
||||||
|
|
||||||
if x > 5 { // decyzja
|
if x > 5 { // decyzja
|
||||||
|
|
@ -1020,72 +1013,49 @@ fn main() { // tu zaczyna się program
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dodaj(int a, int b) -> int { // własne polecenie
|
fn dodaj(int a, int b) -> int { // twoje własne polecenie
|
||||||
return a + b
|
return a + b
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Zwierzak { // własny rodzaj wartości
|
struct Zwierzak { // twój 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, lata in wiek { }
|
// mapy: wiek := ["Ola": 9] wiek["Jan"] = 10 wiek["Ola"] wiek.has("Tomek") for imie, w in wiek { }
|
||||||
// tekst: "a" + "b" str(42) int("42") s.len s.upper() s.contains("x")
|
// 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łowniczek
|
## 19. Słowa, które będziesz słyszeć
|
||||||
|
|
||||||
| 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 |
|
||||||
| **kompilować** | Przetłumaczyć kod na coś, co komputer umie uruchomić. `finch run` robi to za ciebie |
|
| **skompilować** | Przetłumaczyć kod na coś, co komputer potrafi uruchomić. `finch run` robi to za ciebie |
|
||||||
| **zmienna** | Pudełko z nazwą, w którym jest wartość |
|
| **zmienna** | Pudełko z nazwą, w którym leży 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 tak/nie, np. `x > 5` |
|
| **warunek** | Pytanie typu 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** | Pozycja wartości na liście, liczona od 0 |
|
| **indeks** | Miejsce wartości na liście, liczone od 0 |
|
||||||
| **struktura** (struct) | Własny rodzaj wartości złożony z nazwanych części |
|
| **struktura** | Twój własny rodzaj wartości z nazwanych części |
|
||||||
| **metoda** | Polecenie należące do struktury, np. `g.trafiony()` |
|
| **metoda** | Polecenie należące do struktury, np. `g.trafiony()` |
|
||||||
| **mapa** (map) | Wartości znajdowane po kluczu (nazwie), jak w słowniku |
|
| **mapa** | Wartości wyszukiwane po kluczu (nazwie), jak w słowniku |
|
||||||
| **wejście** (input) | To, co użytkownik wpisuje do programu |
|
| **wejście** (ang. *input*) | To, co ktoś wpisuje do programu |
|
||||||
| **błąd** (error) | Komunikat, że coś jest nie tak, i gdzie |
|
| **błąd** (ang. *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 wyda ci się łatwe, przejdź do
|
**Co dalej?** Kiedy to wszystko będzie już łatwe, przejdź do
|
||||||
[przewodnika dla techników](dla-technikow.md). Opisuje każdą część Fincha.
|
[przewodnika dla techników](dla-technikow.md). Opisuje każdą część Fincha.
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
||||||
# Ściąga Fincha
|
# Ściąga z Fincha
|
||||||
|
|
||||||
Cały Finch na jednej stronie. Wyjaśnienia są w [przewodnikach](../../README.pl.md#dokumentacja).
|
Cały Finch na jednej stronie. Wyjaśnienia znajdziesz w [przewodnikach](../../README.pl.md#dokumentacja).
|
||||||
|
|
||||||
> 🇬🇧 English version: [cheatsheet.md](../en/cheatsheet.md)
|
> 🇬🇧 English version: [cheatsheet.md](../en/cheatsheet.md)
|
||||||
|
|
||||||
|
|
@ -8,39 +8,41 @@ Cały Finch na jednej stronie. Wyjaśnienia są w [przewodnikach](../../README.p
|
||||||
|
|
||||||
## Polecenie `finch`
|
## Polecenie `finch`
|
||||||
|
|
||||||
| Polecenie | Robi |
|
| Polecenie | Co robi |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `finch run plik.fch [argumenty…]` | skompiluj i uruchom (argumenty trafiają do programu) |
|
| `finch run plik.fch [argumenty…]` | kompiluje i uruchamia (argumenty trafiają do programu) |
|
||||||
| `finch build plik.fch [-o nazwa]` | zrób program (`nazwa.exe` na Windowsie) |
|
| `finch build plik.fch [-o nazwa]` | tworzy program (na Windowsie `nazwa.exe`) |
|
||||||
| `finch build plik.fch --shared` | zrób bibliotekę (`libplik.so`, `plik.dll`) dla programu w C; jej `export fn` to jej funkcje C |
|
| `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 test plik.fch` | uruchom jego bloki `test "…" { }`, każdy osobno (także z importowanych modułów) |
|
| `finch test plik.fch` | uruchamia każdy blok `test "…" { }` osobno (także w importowanych modułach) |
|
||||||
| `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 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 run` / `build` / `test` bez pliku | w folderze z `finch.toml`: jego `entry` |
|
| `finch run` / `build` / `test` bez pliku | w katalogu z `finch.toml`: plik z pola `entry` |
|
||||||
| `finch ir plik.fch` | pokaż LLVM IR |
|
| `finch ir plik.fch` | wypisuje 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 lib` | dolinkuj bibliotekę C (jak `link "lib"`) |
|
| `-l bib` | dolinkowuje bibliotekę C (to samo co `link "bib"`) |
|
||||||
| `--target windows` / `linux` / `arm64` / `macos` / `android` / triple | zbuduj dla innego systemu (`android`: clang z NDK wskazanego przez `ANDROID_NDK_HOME`, zwykle z `--shared`) |
|
| `--target windows` / `linux` / `arm64` / `macos` / `android` / triple | buduje na inny system (`android`: clang z NDK wskazanego przez `ANDROID_NDK_HOME`, zwykle z `--shared`) |
|
||||||
| `-g` | informacje dla debuggera (gdb, lldb, Visual Studio) |
|
| `-g` | informacje dla debuggera (gdb, lldb) |
|
||||||
| `-O0` | bez optymalizacji |
|
| `-O0` | bez optymalizacji |
|
||||||
| `--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ść) |
|
| `--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łą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` (foldery z modułami), `FINCH_CC` (kompilator C, który linkuje), `FINCH_CXX` (dla plików `.cpp`).
|
Zmienne środowiskowe: `FINCH_PATH` (katalogi z modułami), `FINCH_CC` (kompilator C, który linkuje), `FINCH_CXX` (dla plików `.cpp`).
|
||||||
Skompilowany kod programu i jego pliki `link "x.c"` zostają w cache (`~/.cache/finch`), dopóki one albo ich nagłówki się nie zmienią.
|
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.
|
||||||
|
|
||||||
`finch.toml` (obok kodu; `finch run` w tym folderze nie potrzebuje pliku ani flag):
|
`finch.toml` (obok kodu; wtedy `finch run` nie potrzebuje ani nazwy pliku, ani opcji):
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
name = "gra" # co robi finch build
|
name = "gra" # co zbuduje finch build
|
||||||
entry = "src/main.fch"
|
entry = "src/main.fch"
|
||||||
paths = ["src"] # foldery z modułami
|
paths = ["src"] # katalogi z modułami
|
||||||
include = ["vendor/SDL3/include"] # nagłówki C
|
include = ["vendor/SDL3/include"] # nagłówki C
|
||||||
links = ["SDL3", "native/glue.cpp"] # biblioteki i własne .c / .cpp / .o / .a
|
links = ["SDL3", "native/glue.cpp"] # biblioteki i twoje pliki .c / .cpp / .o / .a
|
||||||
libdirs = ["vendor/SDL3/lib"]
|
libdirs = ["vendor/SDL3/lib"]
|
||||||
cflags = ["-DFAST"] # dla własnych plików .c / .cpp
|
cflags = ["-DFAST"] # dla twoich plików .c / .cpp
|
||||||
ldflags = ["-Wl,-rpath,."]
|
ldflags = ["-Wl,-rpath,."]
|
||||||
|
cpu = "native" # to samo co --cpu native
|
||||||
|
|
||||||
[target.windows] # tylko przy budowaniu dla tego systemu (też linux, macos, android, linux-arm64)
|
[target.windows] # tylko przy budowaniu na ten system (są też linux, macos, android, linux-arm64)
|
||||||
links = ["ws2_32"]
|
links = ["ws2_32"]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -63,8 +65,8 @@ fn main() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`main` to `fn main()`, `fn main() -> int` (kod wyjścia) albo `fn main([]str args)`.
|
`main` może wyglądać tak: `fn main()`, `fn main() -> int` (zwraca kod wyjścia) albo `fn main([]str args)`.
|
||||||
Jedna instrukcja na linijkę, bez średników, `{` w tej samej linijce, bez nawiasów wokół warunków.
|
Jedna instrukcja w linii, bez średników, `{` w tej samej linii, warunki bez nawiasów.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -72,12 +74,12 @@ Jedna instrukcja na linijkę, bez średników, `{` w tej samej linijce, bez nawi
|
||||||
|
|
||||||
| Kod | Znaczenie |
|
| Kod | Znaczenie |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `x := 5` | nowa zmienna, typ zgadnięty |
|
| `x := 5` | nowa zmienna, typ wynika z wartości |
|
||||||
| `int x = 5` | nowa zmienna, typ podany |
|
| `int x = 5` | nowa zmienna z podanym typem |
|
||||||
| `int x` | zero / `""` / `[]` / `null` / wartości domyślne struktury (też struktur w niej) |
|
| `int x` | zero / `""` / `[]` / `null` / wartości domyślne struktury (także struktur w jej środku) |
|
||||||
| `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; `ksztalty.W` z modułu) |
|
| `const W = 640`, `const f32 G = 9.8` | stała (na najwyższym poziomie pliku; z modułu: `ksztalty.W`) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -87,15 +89,16 @@ Jedna instrukcja na linijkę, bez średników, `{` w tej samej linijce, bez nawi
|
||||||
|---|---|
|
|---|---|
|
||||||
| `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` (prawda), `false` (fałsz) |
|
| `bool` | `true`, `false` |
|
||||||
| `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ących cudzysłowów 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ącego cudzysłowu znika) |
|
||||||
| `i8 i16 i32 i64` | ze znakiem, z rozmiarem |
|
| `i8 i16 i32 i64` | liczby całkowite ze znakiem, o podanym rozmiarze |
|
||||||
| `u8 u16 u32 u64` | bez znaku, z rozmiarem |
|
| `u8 u16 u32 u64` | liczby całkowite bez znaku |
|
||||||
| `f32 f64` | zmiennoprzecinkowe |
|
| `f32 f64` | liczby 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 \\ \" \'`.
|
||||||
|
|
@ -107,11 +110,11 @@ Znaki specjalne: `\n \t \r \0 \\ \" \'`.
|
||||||
| Priorytet | Operatory |
|
| Priorytet | Operatory |
|
||||||
|---|---|
|
|---|---|
|
||||||
| 5 | `*` `/` `%` `<<` `>>` `&` |
|
| 5 | `*` `/` `%` `<<` `>>` `&` |
|
||||||
| 4 | `+` `-` `\|` `^` (`+` skleja też tekst) |
|
| 4 | `+` `-` `\|` `^` (`+` łączy też teksty) |
|
||||||
| 3 | `==` `!=` `<` `<=` `>` `>=` |
|
| 3 | `==` `!=` `<` `<=` `>` `>=` |
|
||||||
| 2 | `&&` (i) |
|
| 2 | `&&` |
|
||||||
| 1 | `\|\|` (lub) |
|
| 1 | `\|\|` |
|
||||||
| jednoargumentowe | `-x` `!x` (nie) `~x` |
|
| jednoargumentowe | `-x` `!x` `~x` |
|
||||||
|
|
||||||
`x & 1 == 0` znaczy `(x & 1) == 0`.
|
`x & 1 == 0` znaczy `(x & 1) == 0`.
|
||||||
|
|
||||||
|
|
@ -121,14 +124,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ść: jedna wartość na gałąź, else wymagane
|
x := if a > b { a } else { b } // jako wartość: po jednej wartości w każdej gałęzi, else obowiązkowe
|
||||||
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 str)
|
for el in lista { … } // każdy element (albo znak tekstu)
|
||||||
for i, el in lista { … } // razem z indeksem
|
for i, el in lista { … } // razem z indeksem
|
||||||
for klucz, wart in m { … } // każdy wpis mapy, w kolejności dodawania
|
for klucz, wartosc in m { … } // każdy wpis mapy, w kolejności dodania
|
||||||
break continue return wartosc
|
break continue return wartosc
|
||||||
defer sprzatanie() // uruchomi się na końcu bloku
|
defer sprzatnij() // wykona się, gdy blok się skończy
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -144,48 +147,48 @@ fn przywitaj(str imie) { // bez wyniku
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Parametry zachowują się jak kopie. Kolejność w pliku nie ma znaczenia. Przecinek po ostatnim argumencie jest dozwolony.
|
Parametry zachowują się jak kopie. Kolejność funkcji w pliku nie ma znaczenia. Po ostatnim argumencie może stać przecinek.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Metody
|
## Metody
|
||||||
|
|
||||||
```c
|
```c
|
||||||
fn Punkt.przesun(int dx, int dy) { // self to Punkt, na którym wywołano (nie kopia)
|
fn Punkt.przesun(int dx, int dy) { // self to punkt, na którym ją 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) wsk_na_punkt.przesun(0, 1)
|
p.przesun(1, 2) lista[0].przesun(1, 1) wskaznik_na_punkt.przesun(0, 1)
|
||||||
```
|
```
|
||||||
|
|
||||||
Także na strukturach z C: `fn Vector2.length() -> f32 { … }`.
|
Działają też 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")
|
return error("pusty tekst")
|
||||||
}
|
}
|
||||||
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 | Przy porażce |
|
| Kod | Gdy się nie uda |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `x := parsuj(s) or 0` | użyj 0 |
|
| `x := parsuj(s) or 0` | weź 0 |
|
||||||
| `x := parsuj(s) or { print(err); return }` | wykonaj blok (`err`: komunikat) |
|
| `x := parsuj(s) or { print(err); return }` | wykonaj blok (`err` to komunikat) |
|
||||||
| `x := try parsuj(s)` | zakończ porażką też bieżącą funkcję |
|
| `x := try parsuj(s)` | przerwij też bieżącą funkcję z tym samym błędem |
|
||||||
| `fn main() -> !` | porażka wypisuje `error: …`, kod wyjścia 1 |
|
| `fn main() -> !` | błąd wypisuje `error: …`, kod wyjścia 1 |
|
||||||
|
|
||||||
Funkcje wbudowane, które mogą się nie udać: `int(s)`, `float(s)`, `read_file`, `write_file`, `delete_file`.
|
Wbudowane funkcje, które mogą się nie udać: `int(s)`, `float(s)`, `read_file`, `write_file`, `delete_file`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -193,9 +196,9 @@ Funkcje wbudowane, które mogą się nie udać: `int(s)`, `float(s)`, `read_file
|
||||||
|
|
||||||
| Kod | Znaczenie |
|
| Kod | Znaczenie |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `struct P { … }` | definicja (jedno pole na linijkę, `= domyślna` opcjonalnie) |
|
| `struct P { … }` | definicja (jedno pole w linii, `= wartość` opcjonalnie) |
|
||||||
| `P(1, 2)` | utworzenie, wszystkie pola po kolei |
|
| `P(1, 2)` | utworzenie, wszystkie pola po kolei |
|
||||||
| `P(x: 1)` | utworzenie po nazwie, reszta domyślna/zero |
|
| `P(x: 1)` | utworzenie przez nazwy pól, reszta domyślna albo 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 |
|
||||||
|
|
||||||
|
|
@ -203,62 +206,90 @@ Funkcje wbudowane, które mogą się nie udać: `int(s)`, `float(s)`, `read_file
|
||||||
|
|
||||||
## Tablice `[]T`
|
## Tablice `[]T`
|
||||||
|
|
||||||
| Kod | Daje |
|
| Kod | Wynik |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `a.len` | liczba elementów |
|
| `a.len` | liczba elementów |
|
||||||
| `a[i]`, `a[i] = x` | element (z kontrolą zakresu) |
|
| `a[i]`, `a[i] = x` | element (zakres jest sprawdzany) |
|
||||||
| `a.push(x)` | dodaj na końcu |
|
| `a.push(x)` | dodaje na końcu |
|
||||||
| `a.pop()` | zabierz ostatni i go oddaj |
|
| `a.pop()` | zdejmuje ostatni i go zwraca |
|
||||||
| `a.insert(i, x)` | wstaw na pozycję i |
|
| `a.insert(i, x)` | wstawia na pozycję i |
|
||||||
| `a.remove(i)` | usuń pozycję i i ją oddaj |
|
| `a.remove(i)` | usuwa pozycję i i ją zwraca |
|
||||||
| `a.clear()` | wyczyść |
|
| `a.clear()` | opróżnia |
|
||||||
| `a.resize(n)` | długość n (nowe elementy to zera) |
|
| `a.resize(n)` | długość n (nowe elementy są zerami) |
|
||||||
| `a.find(x)` | indeks albo -1 |
|
| `a.find(x)` | indeks albo -1 |
|
||||||
| `a.contains(x)` | bool |
|
| `a.contains(x)` | bool |
|
||||||
| `a.sort()` | posortuj (liczby, znaki, str) |
|
| `a.sort()` | sortuje (liczby, znaki, teksty) |
|
||||||
| `a.reverse()` | odwróć |
|
| `a.reverse()` | odwraca kolejność |
|
||||||
| `a.slice(s, e)` | nowa tablica z s … e-1 |
|
| `a.slice(p, k)` | nowa tablica z elementów p … k-1 |
|
||||||
| `a.join(sep)` | `[]str` → jeden str |
|
| `a.join(sep)` | `[]str` → jeden tekst |
|
||||||
| `a.ptr` | adres pierwszego elementu (dla C) |
|
| `a.ptr` | adres pierwszego elementu (dla C) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Mapy `map[K]V`
|
## Mapy `map[K]V`
|
||||||
|
|
||||||
| Kod | Daje |
|
| Kod | Wynik |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `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: program się zatrzymuje) |
|
| `m[k]` | wartość (brak klucza zatrzymuje program) |
|
||||||
| `m[k] = v` | dodaj / zastąp |
|
| `m[k] = v` | dodaje albo podmienia |
|
||||||
| `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)` | czy jest |
|
| `m.has(k)` | bool |
|
||||||
| `m.get(k, domyslna)` | wartość albo domyślna |
|
| `m.get(k, domyslna)` | wartość albo `domyslna` |
|
||||||
| `m.remove(k)` | bool: czy był |
|
| `m.remove(k)` | bool: czy klucz był |
|
||||||
| `m.clear()` | wyczyść |
|
| `m.clear()` | opróżnia |
|
||||||
| `m.keys()`, `m.values()` | tablice, w kolejności dodawania |
|
| `m.keys()`, `m.values()` | tablice, w kolejności dodania |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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 | Daje |
|
| Kod | Wynik |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `s.len` | liczba bajtów (ą, ż… to 2) |
|
| `s.len` | liczba bajtów |
|
||||||
| `s[i]`, `s[i] = 'x'` | znak |
|
| `s[i]`, `s[i] = 'x'` | znak |
|
||||||
| `a + b` | sklejony tekst |
|
| `a + b` | połączony tekst |
|
||||||
| `"x = \(x)"` | wartość dowolnego kodu w tekście (`\(x, 2)`: liczba z 2 cyframi po przecinku) |
|
| `"x = \(x)"` | wartość dowolnego kodu wstawiona w tekst (`\(x, 2)`: liczba z dwiema 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)` | czy zawiera |
|
| `s.contains(t)` | bool |
|
||||||
| `s.starts_with(t)`, `s.ends_with(t)` | czy zaczyna / kończy się |
|
| `s.starts_with(t)`, `s.ends_with(t)` | bool |
|
||||||
| `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 → b |
|
| `s.replace(a, b)` | każde a zamienione na b |
|
||||||
| `s.repeat(n)` | n razy |
|
| `s.repeat(n)` | tekst n razy |
|
||||||
| `s.bytes()` | `[]u8` |
|
| `s.bytes()` | `[]u8` |
|
||||||
| `s.ptr` | `ptr[char]` (dla C; `str` trafia też do parametru `const char *` bez zmian) |
|
| `s.ptr` | `ptr[char]` (dla C; parametr `const char *` przyjmie też samo `str`) |
|
||||||
| `==` `!=` `<` `>` | porównanie |
|
| `==` `!=` `<` `>` | porównanie |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -271,41 +302,44 @@ Funkcje wbudowane, które mogą się nie udać: `int(s)`, `float(s)`, `read_file
|
||||||
| `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` (zostawia najniższe bity) |
|
| `u8(300)` | `44` (zostają 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 (float: najkrótszy tekst, który jest tą samą liczbą) |
|
| `str(42)`, `str(1.5)`, `str(true)`, `str('c')` | tekst (liczba zmiennoprzecinkowa: najkrótszy zapis tej samej liczby) |
|
||||||
| `str(3.14159, 2)` | `"3.14"` (tyle cyfr po kropce) |
|
| `str(3.14159, 2)` | `"3.14"` (tyle cyfr po przecinku) |
|
||||||
| `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 liczby |
|
| `ptr(p)`, `ptr(16)` | wskaźnik bez typu, ze wskaźnika albo z liczby |
|
||||||
| `int(p)` | wskaźnik → adres |
|
| `int(p)` | wskaźnik → adres |
|
||||||
|
|
||||||
Automatycznie tylko wtedy, gdy nic nie ginie: mniejsza → większa liczba, dowolna całkowita → float, `f32` → `f64`.
|
Automatycznie tylko wtedy, gdy nic nie ginie: mniejsza liczba → większa, liczba całkowita → zmiennoprzecinkowa, `f32` → `f64`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Funkcje wbudowane
|
## Funkcje wbudowane
|
||||||
|
|
||||||
| Funkcja | Robi |
|
| Funkcja | Co robi |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `print(a, b, …)` | wypisz, spacje między, nowa linia na końcu |
|
| `print(a, b, …)` | wypisuje wartości oddzielone spacjami i kończy linię |
|
||||||
| `input()`, `input("Pytanie? ")` | wczytaj linijkę |
|
| `input()`, `input("Pytanie? ")` | wczytuje linię |
|
||||||
| `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)` | czy plik istnieje |
|
| `file_exists(sciezka)` | bool |
|
||||||
| `delete_file(sciezka)` | usuń plik |
|
| `delete_file(sciezka)` | bool |
|
||||||
| `shell(polecenie)` | uruchom polecenie, jego kod wyjścia |
|
| `shell(polecenie)` | uruchamia polecenie, zwraca jego kod wyjścia |
|
||||||
| `exit(kod)` | zakończ program od razu (kończy też blok `or { }`) |
|
| `exit(kod)` | od razu kończy program (kończy też blok `or { }`) |
|
||||||
| `min(a, b, …)`, `max(a, b, …)`, `abs(x)`, `clamp(x, od, do)` | dla dowolnych liczb (twoja funkcja o tej nazwie wygrywa) |
|
| `min(a, b, …)`, `max(a, b, …)`, `abs(x)`, `clamp(x, lo, hi)` | dla dowolnych liczb (twoja funkcja o tej samej nazwie ma pierwszeństwo) |
|
||||||
|
| `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)` = callback dla C |
|
| `addr(x)` | wskaźnik na x; `addr(fn)` = funkcja zwrotna dla C |
|
||||||
| `assert(warunek)`, `assert(warunek, "dlaczego")` | zatrzymaj się z błędem, gdy warunek jest fałszywy (w bloku `test`: ten test nie przechodzi) |
|
| `assert(warunek)`, `assert(warunek, "dlaczego")` | zatrzymuje program, 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 zmieniana też przez 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, którą zmieniają też inne wątki, przez jej `ptr` (`addr(licznik)`) |
|
||||||
| `new(wartosc)` | wartość na stercie → `ptr[T]` |
|
| `new(wartosc)` | wartość na stercie → `ptr[T]` |
|
||||||
| `free(p)` | oddaj ją |
|
| `free(p)` | oddaje tę pamięć |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -314,7 +348,7 @@ Automatycznie tylko wtedy, gdy nic nie ginie: mniejsza → większa liczba, dowo
|
||||||
| Finch | C |
|
| Finch | C |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `ptr[int] p` | `int *p` |
|
| `ptr[int] p` | `int *p` |
|
||||||
| `addr(x)` | `&x` (zmienna-struktura podana tam, gdzie idzie `ptr[TaStruktura]`: `pchnij(cialo)` = `pchnij(addr(cialo))`) |
|
| `addr(x)` | `&x` (zmienna-struktura podana tam, gdzie oczekiwany jest `ptr[TaStruktura]`: `push(cialo)` = `push(addr(cialo))`) |
|
||||||
| `p.value` | `*p` |
|
| `p.value` | `*p` |
|
||||||
| `p.pole` | `p->pole` |
|
| `p.pole` | `p->pole` |
|
||||||
| `p[i]` | `p[i]` |
|
| `p[i]` | `p[i]` |
|
||||||
|
|
@ -325,23 +359,23 @@ Automatycznie tylko wtedy, gdy nic nie ginie: mniejsza → większa liczba, dowo
|
||||||
|
|
||||||
## Pamięć
|
## Pamięć
|
||||||
|
|
||||||
- Tablice, teksty i struktury zwalniają się same na końcu swojego bloku. Bez `free`.
|
- Tablice, teksty i struktury zwalniają się same na końcu swojego bloku. `free` nie jest potrzebne.
|
||||||
- `b := a` kopiuje; wynik wywołania jest przenoszony; `return x` przenosi.
|
- `b := a` kopiuje; wynik funkcji jest przenoszony; `return x` przenosi.
|
||||||
- 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.
|
- 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.
|
||||||
- `new(...)` + `free(...)` (często z `defer`) do list wiązanych, drzew, współdzielenia.
|
- `new(...)` z `free(...)` (często razem z `defer`) przydaje się do list wiązanych, drzew i współdzielenia.
|
||||||
- Pamięć z C: funkcjami z C (`malloc` / `free`, `fclose`, …).
|
- Pamięć z C zwalniasz funkcjami 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.Pudlo(2, 3) // jego struktury
|
ksztalty.Pudelko(2, 3) // jego struktury
|
||||||
ksztalty.Pudlo inne // w deklaracjach
|
ksztalty.Pudelko inne // w deklaracjach
|
||||||
import ui/przycisk // ui/przycisk.fch: przycisk.rysuj()
|
import ui/button // ui/button.fch: button.draw()
|
||||||
import ui/przycisk as p // inna nazwa: p.rysuj()
|
import ui/button as btn // pod inną nazwą: btn.draw()
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -350,14 +384,14 @@ import ui/przycisk as p // inna nazwa: p.rysuj()
|
||||||
|
|
||||||
| Kod | Znaczenie |
|
| Kod | Znaczenie |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `import "x.h"` | funkcje, struktury, enumy, liczbowe `#define`, zmienne globalne |
|
| `import "x.h"` | funkcje, struktury, enumy, liczby z `#define`, zmienne globalne |
|
||||||
| `link "nazwa"` | libnazwa.so / nazwa.lib (najpierw pkg-config) |
|
| `link "nazwa"` | libnazwa.so / nazwa.lib (najpierw pyta pkg-config) |
|
||||||
| `link "plik.c"`, `link "glue.cpp"` | skompiluj własny plik C albo C++ (C++: funkcje w `extern "C"`) |
|
| `link "plik.c"`, `link "glue.cpp"` | kompiluje twój plik C albo C++ (w C++ funkcje w `extern "C"`) |
|
||||||
| `addr(mojaFn)` | funkcja Fincha jako callback |
|
| `addr(mojaFn)` | przekazuje funkcję Fincha jako funkcję zwrotną |
|
||||||
|
|
||||||
| Typ C | Finch |
|
| Typ w C | W Finchu |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `int` / `long long` / `unsigned` | `i32` / `i64` / `u32` (wg rozmiaru) |
|
| `int` / `long long` / `unsigned` | `i32` / `i64` / `u32` (według 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]` |
|
||||||
|
|
@ -365,46 +399,46 @@ import ui/przycisk as p // inna nazwa: p.rysuj()
|
||||||
| `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)`, przez wskaźnik do C (nie przez wartość) |
|
| `union U` | `U`: `U u`, `u.pole`, `U(pole: v)`, do C przez wskaźnik (nie przez wartość) |
|
||||||
| `#define N 4`, `"tekst"`, `(1u << 3)`, `((Uint64)2)` | stałe (bez makr-funkcji) |
|
| `#define N 4`, `"tekst"`, `(1u << 3)`, `((Uint64)2)` | stałe (bez makr z parametrami) |
|
||||||
| `#define RAYWHITE (Color){ 245, 245, 245, 255 }` | `Color` |
|
| `#define RAYWHITE (Color){ 245, 245, 245, 255 }` | wartość typu `Color` |
|
||||||
|
|
||||||
**`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]`.
|
**`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]`.
|
||||||
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).
|
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).
|
||||||
|
|
||||||
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).
|
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).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Błędy w czasie działania (program zatrzymuje się z plikiem i linijką)
|
## Błędy w trakcie działania (program kończy się z plikiem i linią)
|
||||||
|
|
||||||
| 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()` na pustej tablicy |
|
| `pop() on an empty array` | |
|
||||||
| `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 wywołania, które do niego doprowadziły (`finch run`, `finch test`, `-g`, `-O0`, `--trace`):
|
Pod komunikatem pojawiają się 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 Enemy.next (gra.fch:6)
|
in Wrog.nastepny (gra.fch:6)
|
||||||
in step (gra.fch:10)
|
in krok (gra.fch:10)
|
||||||
in main (gra.fch:15)
|
in main (gra.fch:15)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Testy
|
## Testy
|
||||||
|
|
||||||
```c
|
```c
|
||||||
test "wysokość skoku" { // obok kodu; finch test plik.fch je uruchamia, finch run/build je pomijają
|
test "wysokość skoku" { // obok kodu; uruchamia je finch test plik.fch, finch run/build je pomijają
|
||||||
g := Gracz()
|
p := Gracz()
|
||||||
g.skocz()
|
p.skocz()
|
||||||
assert(g.y > 0.0, "y to \(g.y)")
|
assert(p.y > 0.0, "y wynosi \(p.y)")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -413,74 +447,55 @@ test "wysokość skoku" { // obok kodu; finch test plik.fch je uruchamia
|
||||||
## 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`.
|
||||||
Każdy edytor z LSP: `finch lsp`.
|
Dowolny edytor z LSP: `finch lsp`.
|
||||||
|
|
||||||
### VS Code (rozszerzenie „Finch”)
|
### VS Code (rozszerzenie „Finch”)
|
||||||
|
|
||||||
| Klawisz / akcja | Robi |
|
| Klawisz / akcja | Co robi |
|
||||||
|---|---|
|
|---|---|
|
||||||
| ▶ / `Ctrl+F5` | uruchom plik |
|
| ▶ / `Ctrl+F5` | uruchamia plik |
|
||||||
| `Finch: Build This File` | zbuduj; błędy trafiają do panelu Problems |
|
| `Finch: Build This File` | buduje; błędy trafiają do panelu Problems |
|
||||||
| `F12` | idź do definicji |
|
| `F12` | przejście do definicji |
|
||||||
| najechanie myszą | typ / sygnatura / opis |
|
| najechanie kursorem | 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 | szablony |
|
| `main`, `fn`, `fnr`, `fnf`, `method`, `struct`, `for`, `foreach`, `forkv`, `map`, `orb`, `if`, `ife`, `while`, `input` + Tab | gotowe fragmenty kodu |
|
||||||
| ustawienie `finch.path` | gdzie jest finch / finch.exe |
|
| ustawienie `finch.path` | gdzie leży finch / finch.exe |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Enumy i `match`
|
## Enumy i `match`
|
||||||
|
|
||||||
```
|
```
|
||||||
enum Kolor { Czerwony, Zielony } enum Status { Ok = 200, Brak = 404 }
|
enum Kolor { Czerwony, Zielony, Niebieski } enum Status { Ok = 200, NotFound = 404 }
|
||||||
k := Kolor.Zielony int(k) Kolor(1) k == Kolor.Czerwony // tylko == i !=; Kolor(7) to błąd
|
k := Kolor.Zielony int(k) Kolor(2) 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 -> { ... } else -> ... } // same nazwy wariantów; każdy wariant albo else
|
match k { Czerwony -> ... Zielony, Niebieski -> { ... } else -> ... } // same nazwy wariantów; każdy wariant albo else
|
||||||
x := match n { 1, 2 -> "mało" else -> "dużo" } // wyrażenie: gałęzie tego samego typu
|
x := match n { 1, 2 -> "kilka" else -> "dużo" } // wyrażenie: każda gałąź tego samego typu
|
||||||
```
|
```
|
||||||
|
|
||||||
## Typy generyczne
|
## Typy generyczne
|
||||||
|
|
||||||
```
|
```
|
||||||
fn max[T](T a, T b) -> T { ... } max(3, 9) max[float](1, 2.5) // wynika z argumentów albo zapisane
|
fn max[T](T a, T b) -> T { ... } max(3, 9) max[float](1, 2.5) // typ wywnioskowany albo podany
|
||||||
struct Stos[T] { []T elementy } fn Stos[T].dodaj(T x) { ... } // typ zawsze podaje się przy tworzeniu:
|
struct Stos[T] { []T el } fn Stos[T].push(T x) { ... } // typ zawsze idzie z nazwą:
|
||||||
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`; własny plik o tej nazwie ma pierwszeństwo)
|
## Biblioteka standardowa (`import nazwa`; twój plik o tej samej 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, fmt)` `days_in_month(r, m)` |
|
| `time` | `now()` `millis()` `sleep(ms)` `date(t)` `local(t)` `format(t, wzor)` `days_in_month(r, m)` |
|
||||||
| `proc` | `run(cmd)` `output(cmd)` (błąd z jego wyjściem) `env(nazwa, domyślna)` `pid()` `cwd()` |
|
| `proc` | `run(polecenie)` `output(polecenie)` (przy błędzie zwraca jego wyjście) `env(nazwa, domyslna)` `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(folder)` `remove_dir(p)` `data_dir(app)` |
|
| `fs` | `read_bytes(p)` `write_bytes(p, dane)` `exists(p)` `is_dir(p)` `mkdir(p)` `list(katalog)` `remove_dir(p)` `data_dir(aplikacja)` |
|
||||||
| `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
|
||||||
|
|
||||||
| Słowo | Po polsku |
|
`fn return if else while for in break continue true false null import link struct enum match defer or try const export`
|
||||||
|---|---|
|
(`test` i `as` tylko tam, gdzie coś znaczą: `test "nazwa" { }`, `import x as y`)
|
||||||
| `fn` | funkcja |
|
(oraz `self` wewnątrz metod)
|
||||||
| `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 |
|
|
||||||
|
|
|
||||||
|
|
@ -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="4" kateversion="5.0" section="Sources" extensions="*.fch"
|
<language name="Finch" version="5" 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,6 +82,19 @@
|
||||||
<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>
|
||||||
|
|
@ -103,6 +116,9 @@
|
||||||
<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"/>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
# 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`.
|
||||||
|
|
|
||||||
4
editors/vscode/package-lock.json
generated
4
editors/vscode/package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "finch-lang",
|
"name": "finch-lang",
|
||||||
"version": "2.4.0",
|
"version": "2.6.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "finch-lang",
|
"name": "finch-lang",
|
||||||
"version": "2.4.0",
|
"version": "2.6.0",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"vscode-languageclient": "^9.0.1"
|
"vscode-languageclient": "^9.0.1"
|
||||||
|
|
|
||||||
|
|
@ -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.5.0",
|
"version": "2.6.0",
|
||||||
"publisher": "nexoniarz",
|
"publisher": "nexoniarz",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"icon": "images/icon.png",
|
"icon": "images/icon.png",
|
||||||
|
|
|
||||||
|
|
@ -147,11 +147,15 @@
|
||||||
"match": "\\bself\\b"
|
"match": "\\bself\\b"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "storage.type.primitive.finch",
|
"name": "storage.modifier.attribute.finch",
|
||||||
"match": "\\b(int|float|bool|char|str|ptr|map|i8|i16|i32|i64|u8|u16|u32|u64|f32|f64)\\b"
|
"match": "@cpu\\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)\\s*(?=\\()",
|
"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(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*(?=\\()",
|
||||||
"captures": {
|
"captures": {
|
||||||
"1": {
|
"1": {
|
||||||
"name": "support.function.builtin.finch"
|
"name": "support.function.builtin.finch"
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,8 @@ 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'],
|
||||||
|
|
@ -49,6 +51,8 @@ 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 = [];
|
||||||
|
|
|
||||||
69
examples/simd.fch
Normal file
69
examples/simd.fch
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
@ -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/codegen.cpp):
|
// Layout shared with the compiler (src/gen.fch):
|
||||||
// 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,3 +1147,96 @@ __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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,4 +15,7 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
49
src/ast.fch
49
src/ast.fch
|
|
@ -36,6 +36,41 @@ 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
|
||||||
|
|
@ -43,10 +78,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.count != b.count {
|
if (a.kind == "fixed" || a.kind == "vec") && a.count != b.count {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if a.kind == "array" || a.kind == "ptr" || a.kind == "map" || a.kind == "fixed" {
|
if a.kind == "array" || a.kind == "ptr" || a.kind == "map" || a.kind == "fixed" || a.kind == "vec" {
|
||||||
if a.elem.len != b.elem.len {
|
if a.elem.len != b.elem.len {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -67,6 +102,15 @@ 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])
|
||||||
}
|
}
|
||||||
|
|
@ -103,6 +147,7 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
// 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. The same job as
|
// plain #define numbers), the extern variables (stdout) and the structs. It calls libclang's C API
|
||||||
// src/cimport.cpp, done by calling libclang's C API through Finch's own C import -- which is
|
// through Finch's own C import -- so to compile this file, the compiler reads libclang's headers
|
||||||
// what makes this the part that closes the bootstrap: to compile this file the self-hosted
|
// with this very code.
|
||||||
// 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
|
||||||
|
|
|
||||||
47
src/docs.fch
47
src/docs.fch
|
|
@ -26,6 +26,19 @@ 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."),
|
||||||
|
|
@ -40,7 +53,8 @@ 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.")
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,13 +108,42 @@ 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
1241
src/gen.fch
1241
src/gen.fch
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -371,10 +371,7 @@ 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 := getenv("FINCH_CLANG")
|
clang := clangCmd()
|
||||||
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 {
|
||||||
|
|
@ -391,7 +388,8 @@ 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"
|
||||||
obj := irObject(clang, target + " " + b.opt + b.extra, b.ir, b.name + "\n" + tg.triple, log) or {
|
// -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 + " -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
|
||||||
|
|
@ -477,3 +475,74 @@ 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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
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"
|
||||||
|
|
@ -621,6 +622,8 @@ 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 {
|
||||||
|
|
|
||||||
28
src/main.fch
28
src/main.fch
|
|
@ -1,7 +1,4 @@
|
||||||
// finch-boot: the Finch compiler written in Finch.
|
// finch: the Finch compiler, written in Finch (`finch` with no arguments lists the commands).
|
||||||
//
|
|
||||||
// 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.
|
||||||
|
|
@ -174,6 +171,9 @@ 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.5.0 (the compiler is written in Finch)")
|
print("Finch 2.6.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,6 +293,7 @@ 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
|
||||||
|
|
@ -335,6 +336,9 @@ 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 {
|
||||||
|
|
@ -418,6 +422,9 @@ 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 {
|
||||||
|
|
@ -464,7 +471,15 @@ 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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))
|
// what the processor can do (dot_add and the like pick their instructions by it); not needed for the editor's index
|
||||||
|
[]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
|
||||||
|
|
@ -494,6 +509,7 @@ 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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
return names.contains(n) || ast.vecType(n).kind.len > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// int, str, ptr[T], []T, Name, module.Name
|
// int, str, ptr[T], []T, Name, module.Name
|
||||||
|
|
@ -117,6 +117,10 @@ 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)
|
||||||
|
|
@ -285,6 +289,22 @@ 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))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
// 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"
|
||||||
|
|
||||||
|
|
|
||||||
7
tests/fail/cpu_attr_name.fch
Normal file
7
tests/fail/cpu_attr_name.fch
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
// expect: @cpu: 'avx3' isn't a feature Finch knows
|
||||||
|
@cpu("avx2,avx3")
|
||||||
|
fn f() {
|
||||||
|
}
|
||||||
|
fn main() {
|
||||||
|
f()
|
||||||
|
}
|
||||||
4
tests/fail/cpu_has_name.fch
Normal file
4
tests/fail/cpu_has_name.fch
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
// expect: cpu_has: 'avx-512' isn't a feature Finch knows
|
||||||
|
fn main() {
|
||||||
|
print(cpu_has("avx-512"))
|
||||||
|
}
|
||||||
8
tests/fail/cpu_wide_vector.fch
Normal file
8
tests/fail/cpu_wide_vector.fch
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
// 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)))
|
||||||
|
}
|
||||||
5
tests/fail/vec_intdiv.fch
Normal file
5
tests/fail/vec_intdiv.fch
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
// expect: no instruction that divides whole numbers lane by lane
|
||||||
|
fn main() {
|
||||||
|
a := i32x4(8)
|
||||||
|
print(a / 2)
|
||||||
|
}
|
||||||
6
tests/fail/vec_lane_index.fch
Normal file
6
tests/fail/vec_lane_index.fch
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
// expect: index 4
|
||||||
|
fn main() {
|
||||||
|
v := i32x4(1)
|
||||||
|
i := 4
|
||||||
|
print(v[i])
|
||||||
|
}
|
||||||
4
tests/fail/vec_lanes.fch
Normal file
4
tests/fail/vec_lanes.fch
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
// expect: f32x4(a, b, ...) takes 4 numbers
|
||||||
|
fn main() {
|
||||||
|
print(f32x4(1.0, 2.0, 3.0))
|
||||||
|
}
|
||||||
7
tests/fail/vec_less.fch
Normal file
7
tests/fail/vec_less.fch
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
// expect: use .all() or .any()
|
||||||
|
fn main() {
|
||||||
|
a := f32x4(1.0)
|
||||||
|
if a < a {
|
||||||
|
print("no")
|
||||||
|
}
|
||||||
|
}
|
||||||
5
tests/fail/vec_load_end.fch
Normal file
5
tests/fail/vec_load_end.fch
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
// 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))
|
||||||
|
}
|
||||||
6
tests/fail/vec_mix.fch
Normal file
6
tests/fail/vec_mix.fch
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
// expect: can't use '+' on f32x8 and i32x8 (convert one: f32x8(...))
|
||||||
|
fn main() {
|
||||||
|
a := f32x8(1.0)
|
||||||
|
b := i32x8(1)
|
||||||
|
print(a + b)
|
||||||
|
}
|
||||||
5
tests/fail/vec_shuffle_range.fch
Normal file
5
tests/fail/vec_shuffle_range.fch
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
// 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))
|
||||||
|
}
|
||||||
6
tests/fail/vec_shuffle_var.fch
Normal file
6
tests/fail/vec_shuffle_var.fch
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
// expect: shuffle(...) takes lane numbers written in the code
|
||||||
|
fn main() {
|
||||||
|
v := f32x4(1.0)
|
||||||
|
k := 2
|
||||||
|
print(v.shuffle(k, 0, 1, 3))
|
||||||
|
}
|
||||||
5
tests/fail/vec_type.fch
Normal file
5
tests/fail/vec_type.fch
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
// expect: f32x8(x) puts x in every lane
|
||||||
|
fn main() {
|
||||||
|
f32x8 v = 1.0
|
||||||
|
print(v)
|
||||||
|
}
|
||||||
69
tests/run/dot_int8.fch
Normal file
69
tests/run/dot_int8.fch
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
4
tests/run/dot_int8.out
Normal file
4
tests/run/dot_int8.out
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
-68096 true true
|
||||||
|
i32x4(-130559, -130558, -130557, -130556) i32x4(65536, 65536, 65536, 65536) i32x4(260100, 260100, 260100, 260100)
|
||||||
|
i32x4(-800, -800, -800, -800)
|
||||||
|
true
|
||||||
31
tests/run/floatbits.fch
Normal file
31
tests/run/floatbits.fch
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
// 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)))
|
||||||
|
}
|
||||||
13
tests/run/floatbits.out
Normal file
13
tests/run/floatbits.out
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
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)
|
||||||
50
tests/run/masks.fch
Normal file
50
tests/run/masks.fch
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
// 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))
|
||||||
|
}
|
||||||
9
tests/run/masks.out
Normal file
9
tests/run/masks.out
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
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)
|
||||||
27
tests/run/shuffles.fch
Normal file
27
tests/run/shuffles.fch
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
// 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())
|
||||||
|
}
|
||||||
8
tests/run/shuffles.out
Normal file
8
tests/run/shuffles.out
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
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)
|
||||||
69
tests/run/vectors.fch
Normal file
69
tests/run/vectors.fch
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
15
tests/run/vectors.out
Normal file
15
tests/run/vectors.out
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
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)
|
||||||
Loading…
Reference in a new issue