Finch 2.3.0: docs for Windows, LSP and VS Code; cheat sheets (en, pl); portable LSP test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Nexoniarz 2026-10-08 22:05:02 +02:00
commit d2693c5bac
13 changed files with 961 additions and 46 deletions

305
docs/en/cheatsheet.md Normal file
View file

@ -0,0 +1,305 @@
# Finch Cheat Sheet
Everything in Finch on one page. For explanations, see the [guides](../../README.md#documentation).
> 🇵🇱 Polska wersja: [sciaga.md](../pl/sciaga.md)
---
## The `finch` command
| Command | Does |
|---|---|
| `finch run file.fch [args…]` | compile and run (args go to the program) |
| `finch build file.fch [-o name]` | make a program (`name.exe` on Windows) |
| `finch ir file.fch` | print the LLVM IR |
| `finch lsp` | the language server (editors use it) |
| `finch version` | version |
| `-l lib` | link a C library (like `link "lib"`) |
| `--target windows` / `linux` / triple | build for another system |
| `-g` | debug info (gdb, lldb, Visual Studio) |
| `-O0` | no optimizations |
Environment: `FINCH_PATH` (folders with modules), `FINCH_CC` / `CC` (the C compiler that links).
---
## A file
```c
import "stdio.h" // a C header
import shapes // a Finch module: shapes.fch
link "m" // a C library, or "mine.c" / ".o" / ".a"
struct Point {
int x
int y = 0 // default value
}
fn main() {
print("Hello")
}
```
`main` can be `fn main()`, `fn main() -> int` (exit code) or `fn main([]str args)`.
One statement per line, no semicolons, `{` on the same line, no brackets around conditions.
---
## Variables
| Code | Meaning |
|---|---|
| `x := 5` | new variable, type inferred |
| `int x = 5` | new variable, type written |
| `int x` | zero / `""` / `[]` / `null` / the struct's defaults |
| `x = 7` | assign |
| `x += 1` `-=` `*=` `/=` `%=` | update |
---
## Types
| Type | Example |
|---|---|
| `int` (= `i64`) | `42`, `-7`, `0xFF`, `1_000_000` |
| `float` (= `f64`) | `3.14` |
| `bool` | `true`, `false` |
| `char` | `'A'`, `'\n'` |
| `str` | `"text\t\"quoted\""` |
| `i8 i16 i32 i64` | signed, sized |
| `u8 u16 u32 u64` | unsigned, sized |
| `f32 f64` | floats |
| `[]T` | array: `[1, 2, 3]`, `[]str names` |
| `ptr[T]`, `ptr` | pointer, untyped pointer |
| `Name` / `module.Name` | a struct |
Escapes: `\n \t \r \0 \\ \" \'`.
---
## Operators
| Priority | Operators |
|---|---|
| 5 | `*` `/` `%` `<<` `>>` `&` |
| 4 | `+` `-` `\|` `^` (`+` also joins text) |
| 3 | `==` `!=` `<` `<=` `>` `>=` |
| 2 | `&&` |
| 1 | `\|\|` |
| unary | `-x` `!x` `~x` |
`x & 1 == 0` means `(x & 1) == 0`.
---
## Control flow
```c
if a > b { … } else if a == b { … } else { … }
while x > 0 { … }
for i in 0..10 { … } // 0 … 9
for item in list { … } // every element (or char of a str)
break continue return value
defer cleanup() // runs when the block ends
```
---
## Functions
```c
fn add(int a, int b) -> int {
return a + b
}
fn greet(str name) { // no result
print("Hi", name)
}
```
Parameters behave like copies. Order in the file doesn't matter. A comma after the last argument is allowed.
---
## Structs
| Code | Meaning |
|---|---|
| `struct P { … }` | define (one field per line, `= default` optional) |
| `P(1, 2)` | create, all fields in order |
| `P(x: 1)` | create by name, the rest default/zero |
| `p.x`, `p.x = 5` | read, change a field |
| `b := a` | a full copy |
---
## Arrays `[]T`
| Code | Gives |
|---|---|
| `a.len` | number of elements |
| `a[i]`, `a[i] = x` | element (checked) |
| `a.push(x)` | add at the end |
| `a.pop()` | remove the last, give it back |
| `a.insert(i, x)` | put at position i |
| `a.remove(i)` | remove position i, give it back |
| `a.clear()` | empty it |
| `a.resize(n)` | length n (new elements are zero) |
| `a.find(x)` | index or -1 |
| `a.contains(x)` | bool |
| `a.sort()` | sort (numbers, chars, str) |
| `a.reverse()` | reverse |
| `a.slice(s, e)` | new array of s … e-1 |
| `a.join(sep)` | `[]str` → one str |
| `a.ptr` | address of the first element (for C) |
---
## Text `str`
| Code | Gives |
|---|---|
| `s.len` | bytes |
| `s[i]`, `s[i] = 'x'` | char |
| `a + b` | joined text |
| `s.sub(a, b)` | part a … b-1 |
| `s.find(t)` | position or -1 |
| `s.contains(t)` | bool |
| `s.starts_with(t)`, `s.ends_with(t)` | bool |
| `s.split(sep)` | `[]str` |
| `s.trim()` | no spaces at the ends |
| `s.upper()`, `s.lower()` | A–Z / a–z |
| `s.replace(a, b)` | every a → b |
| `s.repeat(n)` | n times |
| `s.bytes()` | `[]u8` |
| `s.ptr` | `ptr[char]` (for C) |
| `==` `!=` `<` `>` | compare |
---
## Conversions
| Code | Result |
|---|---|
| `int(3.9)` | `3` |
| `int("42")` | `42` (stops the program if it isn't a number) |
| `int('A')` | `65` |
| `float(7)`, `float("2.5")` | `7`, `2.5` |
| `u8(300)` | `44` (keeps the low bits) |
| `char(66)` | `'B'` |
| `bool(0)` | `false` |
| `str(42)`, `str(1.5)`, `str(true)`, `str('c')` | text |
| `str(bytes)` | `[]u8` → text |
| `str(p)` | C `char*` → text |
| `ptr(p)`, `ptr(16)` | untyped pointer, from a pointer or a number |
| `int(p)` | pointer → address |
Automatic only when nothing is lost: smaller → bigger number, any int → float, `f32` → `f64`.
---
## Built-in functions
| Function | Does |
|---|---|
| `print(a, b, …)` | print, spaces between, newline at the end |
| `input()`, `input("Question? ")` | read a line |
| `read_file(path)` | file → str |
| `write_file(path, text)` | str → file, `true` if it worked |
| `file_exists(path)` | bool |
| `delete_file(path)` | bool |
| `shell(command)` | run a command, its exit code |
| `exit(code)` | stop the program now |
| `addr(x)` | pointer to x; `addr(fn)` = C callback |
| `new(value)` | value on the heap → `ptr[T]` |
| `free(p)` | give it back |
---
## Pointers
| Finch | C |
|---|---|
| `ptr[int] p` | `int *p` |
| `addr(x)` | `&x` |
| `p.value` | `*p` |
| `p.field` | `p->field` |
| `p[i]` | `p[i]` |
| `null` | `NULL` |
| `ptr` | `void *` |
---
## Memory
- Arrays, text and structs free themselves at the end of their block. No `free` needed.
- `b := a` copies; a call's result moves; `return x` moves.
- `new(...)` + `free(...)` (often with `defer`) for linked lists, trees, sharing.
- C memory: C's own functions (`malloc` / `free`, `fclose`, …).
---
## Modules
```c
import shapes // shapes.fch next to this file (or in FINCH_PATH)
shapes.area(b) // its functions
shapes.Box(2, 3) // its structs
shapes.Box other // in declarations
```
---
## C
| Code | Meaning |
|---|---|
| `import "x.h"` | functions, structs, enums, `#define` numbers, globals |
| `link "name"` | libname.so / name.lib (pkg-config first) |
| `link "file.c"` | compile your own C file |
| `addr(myFn)` | pass a Finch function as a callback |
| C type | Finch |
|---|---|
| `int` / `long long` / `unsigned` | `i32` / `i64` / `u32` (by size) |
| `float` / `double` | `f32` / `f64` |
| `char *` (parameter, result) | `str` |
| `char *` (field, behind a pointer) | `ptr[char]` |
| `struct X` | `X` |
| `T *` | `ptr[T]` |
| `void *`, handles | `ptr` |
| `T x[N]` (in a struct) | `[N]T` |
---
## Runtime errors (the program stops with file and line)
| Message | Cause |
|---|---|
| `division by zero` | `/` or `%` by 0 |
| `index 5 is out of range (the length is 3)` | `a[5]` |
| `pop() on an empty array` | |
| `used .value on a null pointer` | `.value` / `.field` / `[i]` through `null` |
| `can't turn "x" into int` | `int("x")` |
| `can't read the file "…"` | `read_file` |
---
## VS Code (extension "Finch")
| Key / action | Does |
|---|---|
| ▶ / `Ctrl+F5` | run the file |
| `Finch: Build This File` | build; errors go to Problems |
| `F12` | go to definition |
| hover | type / signature / docs |
| `Ctrl+Space` | completion (after `.`: fields, methods, module members) |
| `Ctrl+Shift+O` | functions and structs in the file |
| `main`, `fn`, `fnr`, `struct`, `for`, `foreach`, `if`, `ife`, `while`, `input` + Tab | snippets |
| setting `finch.path` | where finch / finch.exe is |
---
## Keywords
`fn return if else while for in break continue true false null import link struct defer`

View file

@ -77,7 +77,7 @@ To check it works, type:
./build/finch version
```
You should see something like `finch 2.0.0`.
You should see something like `finch 2.3.0`.
---

View file

@ -38,7 +38,10 @@ For the language from a user's point of view, see the [technicians' guide](for-t
20. [Semantics vs. C](#20-semantics-vs-c)
21. [Function reference](#21-function-reference)
22. [Extending the compiler](#22-extending-the-compiler)
23. [Known limitations](#23-known-limitations)
23. [Targets and Windows](#23-targets-and-windows)
24. [The language server](#24-the-language-server)
25. [The VS Code extension](#25-the-vs-code-extension)
26. [Known limitations](#26-known-limitations)
---
@ -874,12 +877,102 @@ emission in `arithOp`/`compare`, an explanation in `badOperands`.
---
## 23. Known limitations
## 23. Targets and Windows
`src/target.h` holds `g_target`: the LLVM triple, `windows` / `msvc` / `cross` flags, the C compiler (`cc`)
and the executable suffix. `setTarget()` picks the C compiler: `FINCH_CC`, else `CC` (not when
cross-compiling), else `x86_64-w64-mingw32-gcc` for `--target windows` from Linux, `clang` on Windows,
`cc` on Unix. Everything platform-dependent goes through it:
- **Headers:** libclang parses with `--target=<triple>` (so `long` is 32 bits on Windows and the right
headers are used) and, on Windows, `-D_USE_MATH_DEFINES`. Include folders come from `<cc> -E -v`.
- **Processes:** `capture()` / `runCommand()` wrap `popen` / `system` (`_popen` on Windows) and decode exit
statuses (POSIX `WIFEXITED`/signals, or Windows exit codes and NTSTATUS crashes like `0xC0000005`).
`shellQuote()` quotes for `sh` (`'…'`) or `cmd.exe` (`"…"`); a command line handed to `cmd /c` that
starts with a quote gets one extra pair of quotes.
- **Linking:** no `-lm` with MSVC; `.exe` names; the runtime cache key includes the triple and the C compiler.
MSVC linker messages (`LNK2019 unresolved external symbol`, `LNK1104`/`LNK1181 cannot open file`) are
translated like GNU ld's.
- **Running:** a Windows program built on Linux runs with `wine`.
- **Debug info:** CodeView for MSVC targets, DWARF elsewhere.
- **Runtime:** no `getline` or `sys/wait.h` on Windows; `stdin`/`stdout`/`stderr` are macros there, so
`finch_std_stream(i)` gives them to Finch when a header has no `extern` variable for them.
- **Printing pointers** is the same everywhere: `null` or `0x…`.
### The Microsoft x64 calling convention
`classify()` switches on `g_target.windows`. A struct of exactly 1, 2, 4 or 8 bytes travels as **one
integer** of that size, even if it holds floats (`{float, float}` → `i64`). Any other struct is
**Indirect**: the caller copies it to its own stack and passes the copy's address (no `byval`: the callee
receives a plain pointer). Results of 1/2/4/8 bytes come back in `rax` as an integer; anything else
through a hidden `sret` pointer. Each argument takes one slot, so there is no register counting.
`cThunk()` mirrors this for callbacks.
`tests/windows.sh` builds every test with `--target windows`, runs it with Wine and compares the output:
all of them pass, including `c_structs` (which then exercises the Microsoft rules against a C library built
with MinGW). CI additionally builds `finch.exe` with MSVC on Windows Server and runs `tests/run.sh` there.
### Building finch.exe
CI uses Visual Studio 2022, Ninja and the official `clang+llvm-21.x-x86_64-pc-windows-msvc` package
(static libraries, `/MT`, so `CMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded`). Two Windows details in
`CMakeLists.txt`: that package names the DIA SDK library at the path of the machine LLVM was built on, so
it is redirected to the installed Visual Studio; and the embedded runtime is written as several raw
string pieces, because MSVC limits one literal to 16 KB. LLVM is initialized with the native target only,
so the static build doesn't pull in every backend.
---
## 24. The language server
`finch lsp` (`src/lsp.cpp`) speaks LSP over stdin/stdout with `llvm::json`. On `didOpen`/`didChange`
it runs the real front end on the document: `Loader` (with the open documents' text as overrides),
`importHeaders` (cached per set of imports, since reading headers is the slow part), and `generate()`
without optimizing or emitting. Two switches make the compiler usable inside a server:
- `g_throwErrors`: `failAt()` throws `FinchError{file, line, col, msg}` instead of printing and exiting.
The server publishes it as a diagnostic (for the right file, also inside an imported module) and clears
diagnostics that disappeared.
- `g_index`: while generating code, `Codegen::note()` records a `SymRef` for every name it resolves
(variables, parameters, fields, functions, constructors, C functions and constants, built-ins, methods,
module members): its span, a markdown hover, and the definition position. `define()` and
`declareStructs()` add `FnInfo` / `StructIndex`; `addVar()` adds `VarInfo` with the enclosing function's
line range. Positions of names come from `namePos` fields the parser records.
Requests use that index:
| Request | From |
|---|---|
| hover, definition | the `SymRef` under the cursor |
| completion | keywords, types, built-ins, variables of the enclosing function, functions, structs, imported modules, C names matching the typed prefix; after `name.`: struct fields (also through `ptr[T]`), array/str methods, or a module's members |
| signatureHelp | scans back to the unmatched `(`, counts top-level commas, looks up the Finch / C / built-in signature |
| documentSymbol | functions and structs (with fields) of the file |
Half-typed code (`p.`, `add(1, `) doesn't compile, so the server keeps the **last successful** analysis of
each document and merges its functions, structs and variables in for completion and signature help.
`tests/lsp_test.py` drives the server like an editor and checks every feature.
`src/builtins_doc.h` is the single description of built-ins and methods, used for hover, completion and
signature help (and mirrored by the cheat sheets).
---
## 25. The VS Code extension
`editors/vscode` is a plain JavaScript extension: `extension.js` starts `finch lsp` through
`vscode-languageclient` (path from the `finch.path` setting) and adds **Run** / **Build** as
`ProcessExecution` tasks (no shell quoting, the `$finch` problem matcher turns `file:line:col: error:`
lines into Problems). `syntaxes/finch.tmLanguage.json` is the TextMate grammar, tested by `npm test` with
`vscode-textmate` + `vscode-oniguruma` (the engine VS Code itself uses);
`language-configuration.json` gives comments, brackets, auto-closing and indentation; `snippets/` the
templates. CI packages it with `vsce` into `finch-lang.vsix`.
## 26. Known limitations
- No methods on structs, generics, maps, `match`, closures, error values (`int("x")`, `read_file` panic).
- Strings are bytes: `.len`, `s[i]`, `upper()` are not Unicode-aware.
- Dangling pointers (`addr` of a local that went away, use after `free`) are not detected.
- Shift amounts and float→int conversions are not range-checked (LLVM poison, as in C).
- C: unions and bit-field structs by value, function-like macros, `long double`.
- One target tested: Linux x86-64 (the ABI code is System V x86-64 specific).
- Two systems: Linux and Windows, both x86-64 (System V and Microsoft x64 ABIs). No macOS or ARM yet.
- The self-hosted compiler has no C imports, sized integers or `defer`, and doesn't free memory.

View file

@ -29,10 +29,11 @@ compiler works inside. For that, see the [guide for engineers](for-engineers.md)
16. [Modules](#16-modules)
17. [Using C libraries](#17-using-c-libraries)
18. [Errors](#18-errors)
19. [Debugging](#19-debugging)
20. [Troubleshooting](#20-troubleshooting)
21. [Project layout and tests](#21-project-layout-and-tests)
22. [Current limits](#22-current-limits)
19. [Editor: VS Code](#19-editor-vs-code)
20. [Debugging](#20-debugging)
21. [Troubleshooting](#21-troubleshooting)
22. [Project layout and tests](#22-project-layout-and-tests)
23. [Current limits](#23-current-limits)
---
@ -59,7 +60,24 @@ messages that tell you how to fix the problem.
## 2. Installing
Finch currently runs on **Linux, x86-64**. It has been tested with **LLVM 21**.
Finch runs on **Linux and Windows, x86-64**, and is built with **LLVM 21**.
### Windows
1. Download **`finch-windows-x64.zip`** from the [releases](https://github.com/Nexoniarz/Finch/releases)
and unpack it, for example to `C:\finch`. It contains `finch.exe` and `libclang.dll`.
2. Install **LLVM 21** (`LLVM-21.x.x-win64.exe` from the
[LLVM releases](https://github.com/llvm/llvm-project/releases)) and tick *Add LLVM to the system PATH*.
Finch uses its `clang` to link programs.
3. Install **Visual Studio Build Tools 2022** with *Desktop development with C++*: it provides the Windows
libraries every program is linked with.
4. Add `C:\finch` to your PATH, open a new terminal, and run `finch version`.
Programs are normal `.exe` files. To build from source on Windows instead, follow `.github/workflows/ci.yml`
(Visual Studio 2022 + the `clang+llvm-21.x-x86_64-pc-windows-msvc` package from LLVM's releases).
### Linux
Other LLVM versions will likely fail to compile, because LLVM's C++ API changes between releases.
### Option A: Nix (recommended, nothing to install by hand)
@ -70,7 +88,7 @@ cd Finch
nix-shell # downloads LLVM, libclang, clang, cmake, ninja, pkg-config
cmake -S . -B build -G Ninja
ninja -C build
./build/finch version # finch 2.0.0 (LLVM 21.x)
./build/finch version # finch 2.3.0 (LLVM 21.x)
```
### Option B: your distribution's packages
@ -91,6 +109,16 @@ cmake -S . -B build -G Ninja -DLLVM_DIR=/usr/lib/llvm-21/lib/cmake/llvm
ninja -C build
```
### Building Windows programs on Linux
With the MinGW-w64 cross compiler installed (`x86_64-w64-mingw32-gcc`; on Nix
`pkgsCross.mingwW64.buildPackages.gcc`):
```sh
finch build game.fch --target windows # game.exe
finch run game.fch --target windows # runs it with Wine, if installed
```
### Putting `finch` on your PATH (optional)
```sh
@ -667,7 +695,18 @@ Instead of undefined behavior (as in C), Finch stops with a message and exit cod
---
## 19. Debugging
## 19. Editor: VS Code
The **Finch** extension (`editors/vscode`, also attached to each release as a `.vsix` file) gives you:
highlighting, errors as you type, completion (also fields and methods after `.`), types on hover,
go to definition (F12), an outline of the file, parameter hints, snippets, and a ▶ **Run** button
(`Ctrl+F5`). Errors from **Finch: Build This File** appear in the Problems panel.
Install: Extensions → `…` → *Install from VSIX…* → pick `finch-lang-*.vsix`. The extension runs `finch`
from your PATH; if it is somewhere else, set **Finch: Path** in the settings. Other editors that speak the
Language Server Protocol (Neovim, Helix, Zed, Emacs, Sublime) can use `finch lsp` directly.
## 20. Debugging
```sh
finch build game.fch -g -O0 -o game
@ -680,11 +719,12 @@ gdb ./game
```
`-g` adds debug info (lines, functions, variables, struct fields). `-O0` keeps every variable
visible; without it the optimizer may remove some.
visible; without it the optimizer may remove some. On Windows, `-g` writes CodeView, which the
Visual Studio debugger and WinDbg read.
---
## 20. Troubleshooting
## 21. Troubleshooting
| Problem | Solution |
|---|---|
@ -695,12 +735,14 @@ visible; without it the optimizer may remove some.
| `can't find the module 'x'` | Put `x.fch` next to the importing file, or set `FINCH_PATH`. |
| `couldn't build the Finch runtime` | No working C compiler. Install gcc or clang, or set `CC`. |
| A library is not found when the program starts | It was linked from a folder the system doesn't search. Run inside the same `nix-shell`, or install it system-wide. |
| Windows: `finch` can't link (`LNK…` errors about `libcmt`, `kernel32`) | Install Visual Studio Build Tools with *Desktop development with C++*. |
| Windows: `finch.exe` doesn't start (missing `libclang.dll`) | Keep `libclang.dll` next to `finch.exe`. |
| `clang` inside `nix-shell` can't find `stdio.h` | You have an old `shell.nix`: `llvmPackages.clang` must be listed before `llvmPackages.libclang`. |
| A program never ends | A loop condition never becomes false. Press Ctrl+C. |
---
## 21. Project layout and tests
## 22. Project layout and tests
```
Finch/
@ -713,7 +755,8 @@ Finch/
│ ├── fail/ programs that must fail, with the expected error in line 1
│ ├── run.sh the test runner (MEMCHECK=1 also checks memory with valgrind)
│ └── boot.sh builds the self-hosted compiler with itself and compares
├── docs/ this documentation (en, pl)
├── editors/vscode/ the VS Code extension
├── docs/ this documentation (en, pl), including the cheat sheet
├── shell.nix the Nix development environment
└── CMakeLists.txt
```
@ -722,11 +765,13 @@ Finch/
tests/run.sh # → 59 passed, 0 failed
MEMCHECK=1 tests/run.sh # the same under valgrind: no leaks, no bad memory access
tests/boot.sh # the self-hosting check (needs clang)
tests/windows.sh # every test built for Windows and run with Wine
python3 tests/lsp_test.py # the language server
```
---
## 22. Current limits
## 23. Current limits
Finch 2.0 is a complete small language, but not a finished one. Not there yet:
@ -734,6 +779,6 @@ Finch 2.0 is a complete small language, but not a finished one. Not there yet:
- Error values: errors in `int("x")` or `read_file` stop the program. Check first (`file_exists`).
- Unicode-aware text: `.len`, `s[i]` and `upper()` work on bytes / ASCII.
- Threads.
- Platforms other than Linux x86-64.
- Platforms other than Linux and Windows on x86-64 (macOS, ARM).
See the [roadmap in the README](../../README.md#roadmap).