# 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 build file.fch --shared [-I dir]` | make a library (`libfile.so`, `file.dll`) that a C program loads; its `export fn`s are its C functions (numbers, bool, char, pointers) | | `finch test file.fch` | run its `test "…" { }` blocks, each on its own (and those of the modules it imports) | | `finch fmt [--check] [files/folders]` | indent by `{ }` with 4 spaces, no spaces at line ends, at most one empty line (`--check`: only list what it would change) | | `finch run` / `build` / `test` with no file | in a folder with a `finch.toml`: its `entry` | | `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` / `arm64` / `macos` / `android` / triple | build for another system (`android`: the NDK's clang from `ANDROID_NDK_HOME`, usually with `--shared`) | | `-g` | debug info (gdb, lldb, Visual Studio) | | `-O0` | no optimizations | | `--trace` / `--no-trace` | runtime errors show the chain of calls; on for `run`, `test`, `-g`, `-O0`, off for a plain `build` (it costs speed) | Environment: `FINCH_PATH` (folders with modules), `FINCH_CC` (the C compiler that links), `FINCH_CXX` (for `.cpp` files). A program's compiled code and its `link "x.c"` files are kept in the cache (`~/.cache/finch`) until they or their headers change. `finch.toml` (next to the code; `finch run` there needs no file and no flags): ```toml name = "game" # what finch build makes entry = "src/main.fch" paths = ["src"] # folders with modules include = ["vendor/SDL3/include"] # C headers links = ["SDL3", "native/glue.cpp"] # libraries and your own .c / .cpp / .o / .a libdirs = ["vendor/SDL3/lib"] cflags = ["-DFAST"] # for your .c / .cpp files ldflags = ["-Wl,-rpath,."] [target.windows] # only when building for that system (also linux, macos, android, linux-arm64) links = ["ws2_32"] ``` --- ## 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 (also of the structs inside it) | | `x = 7` | assign | | `x += 1` `-=` `*=` `/=` `%=` | update | | `const W = 640`, `const f32 G = 9.8` | a constant (top level of a file; `shapes.W` from a module) | --- ## Types | Type | Example | |---|---| | `int` (= `i64`) | `42`, `-7`, `0xFF`, `1_000_000` | | `float` (= `f64`) | `3.14`, `1e9`, `2.5e-3` | | `bool` | `true`, `false` | | `char` | `'A'`, `'\n'` | | `str` | `"text\t\"quoted\""`, `"hp \(hp), speed \(v, 2)"` (code in the text; `, 2` = two digits after the point), `"""` several lines `"""` (the closing quotes' indentation is removed) | | `i8 i16 i32 i64` | signed, sized | | `u8 u16 u32 u64` | unsigned, sized | | `f32 f64` | floats | | `[]T` | array: `[1, 2, 3]`, `[]str names` | | `map[K]V` | map: `["a": 1]`, `map[str]int counts`, `[:]` | | `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 { … } x := if a > b { a } else { b } // as a value: one value per branch, else needed while x > 0 { … } for i in 0..10 { … } // 0 … 9 for item in list { … } // every element (or char of a str) for i, item in list { … } // with its index for key, value in m { … } // every map entry, in insertion order 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. --- ## Methods ```c fn Point.move(int dx, int dy) { // self is the Point it's called on (not a copy) self.x += dx self.y += dy } fn Point.dist() -> float { … } p.move(1, 2) list[0].move(1, 1) ptr_to_point.move(0, 1) ``` Also on structs from C: `fn Vector2.length() -> f32 { … }`. --- ## Errors as values ```c fn parse(str s) -> int! { // ! = can fail if s.len == 0 { return error("empty") } return int(s) or { return error("not a number: " + s) } } fn save(str p, str t) -> ! { // can fail, no result try write_file(p, t) } ``` | Code | On failure | |---|---| | `x := parse(s) or 0` | use 0 | | `x := parse(s) or { print(err); return }` | run the block (`err`: the message) | | `x := try parse(s)` | fail the current function too | | `fn main() -> !` | a failure prints `error: …`, exit code 1 | Fallible built-ins: `int(s)`, `float(s)`, `read_file`, `write_file`, `delete_file`. --- ## 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) | --- ## Maps `map[K]V` | Code | Gives | |---|---| | `m := ["a": 1, "b": 2]` | a map (keys: numbers, char, bool, str) | | `m[k]` | value (missing key: the program stops) | | `m[k] = v` | add / replace | | `m[k] += 1`, `m[k].push(x)` | a missing key starts at the default (0, `[]`, …) | | `m.len` | number of keys | | `m.has(k)` | bool | | `m.get(k, default)` | value or default | | `m.remove(k)` | bool: was it there | | `m.clear()` | empty it | | `m.keys()`, `m.values()` | arrays, insertion order | --- ## Text `str` | Code | Gives | |---|---| | `s.len` | bytes | | `s[i]`, `s[i] = 'x'` | char | | `a + b` | joined text | | `"x = \(x)"` | the value of any code in the text (`\(x, 2)`: a float with 2 digits) | | `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; a `str` also goes to a `const char *` parameter as it is) | | `==` `!=` `<` `>` | 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 (a float: the shortest text that is the same number) | | `str(3.14159, 2)` | `"3.14"` (that many digits after the point) | | `str([1, 2])`, `str(m)`, `str(point)` | the same text `print` shows | | `int("x") or 0`, `float(s) or 0.0` | a number, or the fallback | | `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 (also ends an `or { }` block) | | `min(a, b, …)`, `max(a, b, …)`, `abs(x)`, `clamp(x, lo, hi)` | for any numbers (your own function of that name wins) | | `error(message)` | `return error("…")` in a `-> T!` function | | `addr(x)` | pointer to x; `addr(fn)` = C callback | | `assert(cond)`, `assert(cond, "why")` | stop with a runtime error when cond is false (in a `test` block: that test fails) | | `atomic_add(p, n)`, `atomic_sub`, `atomic_load(p)`, `atomic_store(p, n)`, `atomic_swap(p, n)`, `atomic_cas(p, old, new)` | a whole number other threads change too, through its `ptr` (`addr(count)`) | | `new(value)` | value on the heap β†’ `ptr[T]` | | `free(p)` | give it back | --- ## Pointers | Finch | C | |---|---| | `ptr[int] p` | `int *p` | | `addr(x)` | `&x` (a struct variable given where `ptr[ThatStruct]` goes: `push(body)` = `push(addr(body))`) | | `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. - Passing a struct: `fn f(Body b)` works on its own copy (the caller's stays as it was), `fn f(ptr[Body] b)` changes the caller's (`f(body)` or `f(addr(body))`), a method gets `self` by address, never a copy. - `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 import ui/button // ui/button.fch: button.draw() import ui/button as btn // another name: btn.draw() ``` --- ## C | Code | Meaning | |---|---| | `import "x.h"` | functions, structs, enums, `#define` numbers, globals | | `link "name"` | libname.so / name.lib (pkg-config first) | | `link "file.c"`, `link "glue.cpp"` | compile your own C or C++ file (C++: functions in `extern "C"`) | | `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` | | `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) | | `#define RAYWHITE (Color){ 245, 245, 245, 255 }` | a `Color` | **`char *` is `str`**: a C function that takes or returns `char *` takes or gives a Finch `str` (no `.ptr`, no conversion); only a `char *` field or a `char **` stays `ptr[char]`. C++ and Rust: through a C header. C++ with `extern "C"` (and `link "x.cpp"`), Rust with `#[no_mangle] pub extern "C" fn` built as a `staticlib` (`link "target/release/libgame.a"`) and a header (cbindgen writes one). 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). --- ## 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` (without `or`) | | `the key "x" is not in the map` | `m["x"]` | | `assert failed: why` | `assert(cond, "why")` | Below the message, the calls that led there (`finch run`, `finch test`, `-g`, `-O0`, `--trace`): ``` game.fch:6: runtime error: index 3 is out of range (the length is 2) in Enemy.next (game.fch:6) in step (game.fch:10) in main (game.fch:15) ``` ## Tests ```c test "jump height" { // next to the code; finch test file.fch runs them, finch run/build leave them out p := Player() p.jump() assert(p.y > 0.0, "y is \(p.y)") } ``` --- ## Editors Kate: `editors/kate/install.sh`, then enable the **LSP Client** plugin; run with **Build & Run** β†’ `finch run %f`. Any LSP editor: `finch lsp`. ### 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`, `fnf`, `method`, `struct`, `for`, `foreach`, `forkv`, `map`, `orb`, `if`, `ife`, `while`, `input` + Tab | snippets | | setting `finch.path` | where finch / finch.exe is | --- ## Enums and `match` ``` enum Color { Red, Green, Blue } enum Status { Ok = 200, NotFound = 404 } c := Color.Green int(c) Color(2) c == Color.Red // == and != only; Color(7) is an error fn Color.next() -> Color { ... } // methods work match c { Red -> ... Green, Blue -> { ... } else -> ... } // bare variant names; every variant or else x := match n { 1, 2 -> "few" else -> "many" } // an expression: every arm the same type ``` ## Generics ``` fn max[T](T a, T b) -> T { ... } max(3, 9) max[float](1, 2.5) // worked out, or written out struct Stack[T] { []T items } fn Stack[T].push(T x) { ... } // the type goes with it: s := Stack[int]() Stack[int] t Pair[str, int]("age", 31) // never just Stack() ``` ## Standard library (`import name`; your own file of that name wins) | | | |---|---| | `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(y, m)` | | `proc` | `run(cmd)` `output(cmd)` (fails with its output) `env(name, fallback)` `pid()` `cwd()` | | `thread` | `spawn(addr(work), addr(data))` β†’ `t.join()`; `mutex()` β†’ `m.lock()` `m.unlock()` `m.close()`; `cpus()` (a thread's function: `fn work(ptr arg)`) | | `fs` | `read_bytes(p)` `write_bytes(p, data)` `exists(p)` `is_dir(p)` `mkdir(p)` `list(dir)` `remove_dir(p)` `data_dir(app)` | | `json` | `parse(text)` `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)` | ## Keywords `fn return if else while for in break continue true false null import link struct enum match defer or try const export` (`test` and `as` only where they mean something: `test "name" { }`, `import x as y`) (and `self` inside methods)