Modules are known by their file: import button from ui/menu.fch and import ui/button are one module, two util.fch are two; min/max/abs/clamp win over C's int-only abs; completion after an import's other name; hover docs; engineers' guides

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016GzNP6r8dg6WDxUVZy8wjL
This commit is contained in:
Nexoniarz 2026-10-29 09:52:04 +01:00
commit 3ba2dcd5d6
16 changed files with 152 additions and 18 deletions

View file

@ -343,7 +343,7 @@ import ui/button as btn // another name: btn.draw()
| `union U` | `U`: `U u`, `u.field`, `U(field: v)`, by pointer to C (not by value) |
| `#define N 4`, `"text"`, `(1u << 3)`, `((Uint64)2)` | constants (not function-like macros) |
A C function's `char *` result is a `str` already; structs by value work on every target (System V, Windows x64, ARM64).
A C function's `char *` result is a `str` already; structs by value work with the System V, Windows x64 and ARM64 Linux calling conventions (tested in emulators; macOS by CI).
---

View file

@ -98,6 +98,18 @@ its `endLine` (used by the language server). The old `fn name(...) type` order i
`name.fch` there, the folders of `FINCH_PATH`, then the standard library `std/`. Each module is parsed once; its names
are used as `module.name`. `import "x.h"` goes to the C importer instead.
A module is identified by its **file**: the key is its path from the main file's folder (or from a `FINCH_PATH` folder or
`std/`) without `.fch` and without an `_<os>` suffix, e.g. `ui/button`. `Program.imports` holds the name each file uses
(`button`, or `btn` for `import ui/button as btn`) and `Program.modules` the key; `modFor()` maps one to the other.
Symbols use the key with `/` turned into `$` (`@finch.ui$button.make`).
**Constants.** `const NAME = expr` is a top-level node; `constValue()` evaluates the expression at each use, in the
defining module with no local scopes, so it folds to a literal (integer literal operators are folded in `arith`).
`checkConst()` allows only literals, names, operators, members and numeric conversions; `constBusy` catches cycles.
**Lexer extras.** `1e9` / `2.5e-3` are float tokens (the text is normalised to have a `.` for LLVM); `"""` strings are
dedented by the closing line's indentation in `longString()`.
---
## 4. Code generation
@ -138,6 +150,14 @@ LLVM IR does not do the C calling convention for you, so `gen.fch` reproduces it
The same code is what lets the compiler call libclang, whose API passes `CXCursor`, `CXType` and `CXString` by value.
**Macros.** A `#define` that is one number or a string literal is read from its tokens. One whose tokens could be a
constant expression (no braces, semicolons or statement keywords) is evaluated by clang: a second parse of the header
adds `static const __auto_type __finch_m_NAME = (NAME);` for each, and `clang_Cursor_Evaluate` gives an int, a double
or a string. Those that are not constants only produce errors in that second parse and are dropped.
**Unions** are a `CStruct` with `isUnion`: the LLVM type is `{ [n x iA] }` (size and alignment from clang), every field has
offset 0, and a field is read through memory. They can't be passed by value to C (`unsupported` says why).
---
## 6. Driver, linking and targets
@ -201,8 +221,9 @@ from the slot they flow into (`exprWant()`), so `[]f32 xs = [1, 2.5]` works.
### Binary operands: `unify(l, r)`
A literal side adapts to the other; otherwise the side that widens grows; `i32` + `u32` is an error;
int + float → float; f32 + f64 → f64. There is no C integer promotion: `u8 + u8` stays `u8`.
A literal side adapts to the other (also a float literal next to an `f32`: `x * 2.0` stays `f32`); otherwise the side
that widens grows; `i32` + `u32` is an error; int + float → float; f32 + f64 → f64. There is no C integer promotion:
`u8 + u8` stays `u8`. Two integer literals fold to a literal, so `FLAG_A | FLAG_B` still fits a `u64` or `u32`.
### Explicit conversions: `convert()`

View file

@ -97,6 +97,18 @@ kolejność `fn nazwa(...) typ` to błąd składni z podpowiedzią.
katalogów z `FINCH_PATH`, potem biblioteki standardowej `std/`. Każdy moduł jest parsowany raz; jego nazwy są używane jako
`modul.nazwa`. `import "x.h"` trafia do importera C.
Moduł rozpoznaje się po **pliku**: kluczem jest jego ścieżka od folderu głównego pliku (albo od folderu z `FINCH_PATH`
lub `std/`) bez `.fch` i bez końcówki `_<system>`, np. `ui/przycisk`. `Program.imports` trzyma nazwę używaną w danym
pliku (`przycisk`, albo `p` przy `import ui/przycisk as p`), a `Program.modules` klucz; `modFor()` zamienia jedno na drugie.
Symbole używają klucza z `/` zamienionym na `$` (`@finch.ui$przycisk.rysuj`).
**Stałe.** `const NAZWA = wyrażenie` to węzeł najwyższego poziomu; `constValue()` liczy wyrażenie przy każdym użyciu, w
module, w którym je zdefiniowano, bez zmiennych lokalnych, więc wychodzi literał (operatory na literałach całkowitych
składa `arith`). `checkConst()` dopuszcza tylko literały, nazwy, operatory, pola i konwersje liczb; `constBusy` łapie pętle.
**Dodatki leksera.** `1e9` / `2.5e-3` to tokeny float (tekst dostaje `.` dla LLVM); teksty `"""` tracą wcięcie
linii zamykającej w `longString()`.
---
## 4. Generowanie kodu
@ -130,6 +142,14 @@ LLVM IR nie realizuje za ciebie konwencji wywołań C, więc `gen.fch` ją odtwa
- reguły Microsoft x64 (struktury 1, 2, 4, 8 bajtów w rejestrze, reszta przez wskaźnik);
- `makePlan` zamienia klasyfikację w plan wywołania; `cThunk` robi z `addr(fn)` coś, co C może wywołać (callbacki).
**Makra.** `#define` będący jedną liczbą albo tekstem czyta się z tokenów. Taki, którego tokeny mogą być wyrażeniem
stałym (bez klamer, średników i słów instrukcji), liczy clang: drugie parsowanie nagłówka dodaje
`static const __auto_type __finch_m_NAZWA = (NAZWA);` dla każdego, a `clang_Cursor_Evaluate` daje liczbę całkowitą,
double albo tekst. Te, które nie są stałymi, dają tylko błędy w tym drugim parsowaniu i są pomijane.
**Unie** to `CStruct` z `isUnion`: typ LLVM to `{ [n x iA] }` (rozmiar i wyrównanie od clanga), każde pole ma
przesunięcie 0, a pole czyta się przez pamięć. Nie da się ich przekazać do C przez wartość (`unsupported` mówi dlaczego).
Ten sam kod pozwala kompilatorowi wołać libclang, którego API przekazuje `CXCursor`, `CXType` i `CXString` przez wartość.
---
@ -194,9 +214,10 @@ Literały tablic biorą typ elementu z miejsca, do którego trafiają (`exprWant
### Argumenty operatorów: `unify(l, r)`
Strona-literał dopasowuje się do drugiej; w przeciwnym razie rośnie strona, która się rozszerza;
`i32` + `u32` to błąd; całkowita + zmiennoprzecinkowa → zmiennoprzecinkowa; f32 + f64 → f64.
Nie ma promocji całkowitych z C: `u8 + u8` zostaje `u8`.
Strona-literał dopasowuje się do drugiej (także literał float obok `f32`: `x * 2.0` zostaje `f32`); w przeciwnym razie
rośnie strona, która się rozszerza; `i32` + `u32` to błąd; całkowita + zmiennoprzecinkowa → zmiennoprzecinkowa;
f32 + f64 → f64. Nie ma promocji całkowitych z C: `u8 + u8` zostaje `u8`. Dwa literały całkowite składają się w literał,
więc `FLAG_A | FLAG_B` dalej mieści się w `u64` albo `u32`.
### Konwersje jawne: `convert()`

View file

@ -342,7 +342,7 @@ import ui/przycisk as p // inna nazwa: p.rysuj()
| `union U` | `U`: `U u`, `u.pole`, `U(pole: v)`, przez wskaźnik do C (nie przez wartość) |
| `#define N 4`, `"tekst"`, `(1u << 3)`, `((Uint64)2)` | stałe (bez makr-funkcji) |
Wynik `char *` z funkcji C to od razu `str`; struktury przez wartość działają na każdym systemie (System V, Windows x64, ARM64).
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).
---