mirror of
https://github.com/Nexoniarz/Finch.git
synced 2026-11-03 08:54:25 +00:00
docs: the Polish cheat sheet and engineers' guide rewritten in natural Polish; the engineers' guide matches the Finch compiler
- names from the C++ compiler (declareFns, HelperScope, Fn::borrowParam, mutatingMethods) replaced by the real ones - section 8's subsections numbered 8.x; the map's LLVM type has 7 fields; @W in the index records Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Psc42moPLwP43Cmb9oZqSR
This commit is contained in:
parent
0bf66740df
commit
8495bc718d
3 changed files with 550 additions and 501 deletions
|
|
@ -114,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.
|
||||||
|
|
||||||
|
|
@ -162,7 +163,7 @@ 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.
|
||||||
|
|
||||||
|
|
@ -211,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
|
||||||
|
|
@ -223,7 +225,7 @@ 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) |
|
| `f32x8`, `i8x32`, … | `<8 x float>`, `<32 x i8>` (§7, Vectors) |
|
||||||
|
|
@ -322,11 +324,9 @@ IR's compile, so the same IR becomes SSE2, AVX2 + FMA, AVX-512 or NEON code. Lan
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 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
|
||||||
|
|
@ -350,21 +350,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:
|
||||||
|
|
||||||
|
|
@ -374,10 +374,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 {
|
||||||
|
|
@ -420,22 +420,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,
|
||||||
|
|
@ -444,8 +444,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
|
||||||
|
|
@ -485,16 +483,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 |
|
||||||
|
|
@ -507,13 +506,9 @@ 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) |
|
||||||
| 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
|
||||||
|
|
|
||||||
|
|
@ -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 widziałeś już LLVM IR.
|
||||||
Pierwsza wersja kompilatora, w C++ na API C++ LLVM, jest w gałęzi `before-bootstrap`; ten przewodnik opisuje wersję w Finchu.
|
|
||||||
|
|
||||||
> 🇬🇧 English version: [for-engineers.md](../en/for-engineers.md)
|
> 🇬🇧 English version: [for-engineers.md](../en/for-engineers.md)
|
||||||
|
|
||||||
|
|
@ -15,34 +14,34 @@ 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]
|
||||||
|
|
@ -51,158 +50,174 @@ W kompilatorze nie ma biblioteki LLVM. Pisze LLVM IR jako **tekst**, a optymaliz
|
||||||
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,133 @@ 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` | `fcmp oeq`/`icmp eq` → `<N x i1>` → `llvm.vector.reduce.and` |
|
||||||
|
| `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)` | `llvm.fmuladd` (jedna instrukcja tam, gdzie procesor ma FMA; `llvm.fma` bez FMA wołałoby funkcję z libm dla każdego elementu) |
|
||||||
|
| `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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 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 +426,101 @@ 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) |
|
||||||
| 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 +529,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ą masek (`v < w`), przetasowań ani dzielenia całkowitego.
|
||||||
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,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,23 +147,23 @@ 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 { … }`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -169,7 +172,7 @@ Także na strukturach z C: `fn Vector2.length() -> f32 { … }`.
|
||||||
```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) }
|
||||||
}
|
}
|
||||||
|
|
@ -178,14 +181,14 @@ fn zapisz(str p, str t) -> ! { // może się nie udać, bez wyniku
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
| 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,83 @@ 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` | bool: czy wszystkie elementy są równe / czy któryś się różni |
|
||||||
|
| `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()`, `v.fma(b, c)` | każdy element (liczby zmiennoprzecinkowe; `fma` = `v * b + c`) |
|
||||||
|
| `v.store(lista, i)` | zapisuje elementy do `lista[i]` … |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 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 +295,41 @@ 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) |
|
||||||
| `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 +338,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,11 +349,11 @@ 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`, …).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -338,10 +362,10 @@ Automatycznie tylko wtedy, gdy nic nie ginie: mniejsza → większa liczba, dowo
|
||||||
```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 +374,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 +389,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 +437,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 |
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue