mirror of
https://github.com/Nexoniarz/Finch.git
synced 2026-11-03 00:44:27 +00:00
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016GzNP6r8dg6WDxUVZy8wjL
1099 lines
42 KiB
Markdown
1099 lines
42 KiB
Markdown
# Finch for Technicians
|
||
|
||
**For people who know their way around a computer, but little or no programming.**
|
||
This guide covers installing Finch, using the `finch` tool, the whole language, memory,
|
||
modules, C libraries, debugging and fixing common problems. It does not cover how the
|
||
compiler works inside. For that, see the [guide for engineers](for-engineers.md).
|
||
|
||
> 🇵🇱 Polska wersja: [dla-technikow.md](../pl/dla-technikow.md)
|
||
|
||
---
|
||
|
||
## Contents
|
||
|
||
1. [What Finch is](#1-what-finch-is)
|
||
2. [Installing](#2-installing)
|
||
3. [The finch command](#3-the-finch-command)
|
||
4. [How a Finch file looks](#4-how-a-finch-file-looks)
|
||
5. [Variables](#5-variables)
|
||
6. [Types](#6-types)
|
||
7. [Operators](#7-operators)
|
||
8. [Control flow](#8-control-flow)
|
||
9. [Functions](#9-functions)
|
||
10. [Arrays and maps](#10-arrays-and-maps)
|
||
11. [Text (str)](#11-text-str)
|
||
12. [Structs and methods](#12-structs-and-methods)
|
||
13. [Memory: who frees what](#13-memory-who-frees-what)
|
||
14. [Pointers](#14-pointers)
|
||
15. [Built-in functions](#15-built-in-functions)
|
||
16. [Modules](#16-modules)
|
||
17. [Using C libraries](#17-using-c-libraries)
|
||
18. [Errors](#18-errors)
|
||
19. [Editors: VS Code, Kate and others](#19-editors-vs-code-kate-and-others)
|
||
20. [Debugging](#20-debugging)
|
||
21. [Troubleshooting](#21-troubleshooting)
|
||
22. [Project layout and tests](#22-project-layout-and-tests)
|
||
23. [Enums and match](#23-enums-and-match)
|
||
24. [Generics](#24-generics)
|
||
25. [The standard library](#25-the-standard-library)
|
||
26. [Current limits](#26-current-limits)
|
||
|
||
---
|
||
|
||
## 1. What Finch is
|
||
|
||
Finch is a **compiled** programming language. The `finch` tool translates your `.fch`
|
||
file into a real program (a native executable, like the ones made from C), using the
|
||
**LLVM** compiler toolkit, the same backend that powers clang, Rust and Swift.
|
||
|
||
What that gives you:
|
||
|
||
- **Speed close to C.** No interpreter, virtual machine or garbage collector. Array bounds
|
||
checks and other safety checks cost almost nothing after optimization.
|
||
- **Small, standalone programs** that you can copy and run.
|
||
- **Memory handled for you.** Lists and text are freed automatically at the end of the block
|
||
that owns them, with no garbage collector involved.
|
||
- **Direct access to C libraries.** `import "stdio.h"` and call anything in it, even functions
|
||
that take structs.
|
||
|
||
The design rules: one way to do each thing, C-like syntax without C's traps, and error
|
||
messages that tell you how to fix the problem.
|
||
|
||
---
|
||
|
||
## 2. Installing
|
||
|
||
The Finch compiler is written in Finch (see the [engineers' guide](for-engineers.md)). It runs on **Linux**
|
||
(x86-64 and ARM64) and builds programs for **Linux, Windows and macOS**. It needs **clang 21** and **libclang**
|
||
(LLVM 21) to read C headers and to compile its output.
|
||
|
||
The compiler does not run on macOS or Windows yet; it needs a seed made with that system's headers
|
||
([bootstrap/README.md](../../bootstrap/README.md) says why). Until then, build your Windows and macOS programs from Linux
|
||
with `--target` (below), or use the older C++ compiler from the `before-bootstrap` branch.
|
||
|
||
### Option A: a ready build (Linux)
|
||
|
||
Download `finch-linux-x64.tar.gz` or `finch-linux-arm64.tar.gz` from the
|
||
[releases](https://github.com/Nexoniarz/Finch/releases) and unpack it. Keep `runtime/` and `std/` next to the `finch` file
|
||
(or install it, below). You also need `clang` 21 and its libclang library:
|
||
|
||
| Distribution | Packages |
|
||
|---|---|
|
||
| Debian / Ubuntu | `clang-21 libclang-21-dev` from [apt.llvm.org](https://apt.llvm.org), `pkg-config` |
|
||
| Fedora | `clang clang-devel pkgconf` |
|
||
| Arch | `clang pkgconf` |
|
||
| Nix | `nix-shell` in the repository gives all of it |
|
||
|
||
### Option B: build it yourself
|
||
|
||
```sh
|
||
git clone https://github.com/Nexoniarz/Finch.git
|
||
cd Finch
|
||
nix-shell # clang, libclang, pkg-config (or install them with your distribution)
|
||
bootstrap/build.sh # build/finch
|
||
./build/finch version
|
||
```
|
||
|
||
`bootstrap/build.sh` compiles the seed (`bootstrap/<system>.ll`, the compiler's own LLVM IR) into a first compiler, builds
|
||
`src/` with it, builds `src/` again with the result and checks the two are identical. If libclang isn't where the linker
|
||
looks, tell it: `FINCH_LIBCLANG="-L/usr/lib/llvm-21/lib -lclang-21" bootstrap/build.sh`.
|
||
|
||
### Building for another system
|
||
|
||
`--target` builds a program for another system. Finch needs that system's C compiler to link:
|
||
|
||
| `--target` | Builds | C compiler used | `finch run` uses |
|
||
|---|---|---|---|
|
||
| `windows` | a Windows `.exe` | `x86_64-w64-mingw32-gcc` (MinGW-w64) | Wine |
|
||
| `arm64` | an ARM64 Linux program | `aarch64-linux-gnu-gcc` | `qemu-aarch64` |
|
||
| `linux` | an x86-64 Linux program | `clang --target=…` | |
|
||
| `macos` | a macOS program (on a Mac: for the other processor) | `clang --target=…` | |
|
||
| any LLVM triple | e.g. `aarch64-unknown-linux-gnu` | `clang --target=<triple>` | |
|
||
|
||
```sh
|
||
finch build game.fch --target windows # game.exe
|
||
finch run game.fch --target arm64 # builds for ARM64 and runs it with QEMU
|
||
FINCH_CC=aarch64-unknown-linux-gnu-gcc finch build tool.fch --target arm64 # another compiler name
|
||
```
|
||
|
||
On Nix the compilers are `pkgsCross.mingwW64.buildPackages.gcc` and
|
||
`pkgsCross.aarch64-multiplatform.buildPackages.gcc`.
|
||
|
||
### Putting `finch` on your PATH (optional)
|
||
|
||
```sh
|
||
sudo bootstrap/install.sh # /usr/local/bin/finch, and /usr/local/share/finch/{runtime,std}
|
||
bootstrap/install.sh ~/.local # or into your own folder
|
||
finch version
|
||
```
|
||
|
||
At run time Finch needs `clang` (it compiles each program's IR and links it). The small runtime library is built once and
|
||
kept in `~/.cache/finch`. `FINCH_CC` names a different compiler to link with; `FINCH_PATH` adds folders to look for modules in.
|
||
|
||
---
|
||
|
||
## 3. The finch command
|
||
|
||
```
|
||
finch run <file.fch> [args...] compile and run right away
|
||
finch build <file.fch> [-o name] compile into a program (default name: the file's name)
|
||
finch ir <file.fch> print the LLVM IR (for the curious)
|
||
finch lsp the language server, for editors
|
||
finch version show the version
|
||
|
||
options:
|
||
-l <lib> link a C library, same as link "lib" in the file
|
||
--target <name> build for another system: windows, linux, arm64, macos, or an LLVM triple
|
||
-g add debug info (for gdb / lldb / Visual Studio)
|
||
-O0 skip optimizations
|
||
```
|
||
|
||
Examples:
|
||
|
||
```sh
|
||
finch run hello.fch # prints the output
|
||
finch run tool.fch input.txt -v # arguments after the file go to the program
|
||
finch build game.fch -o mygame # creates ./mygame
|
||
finch build game.fch -g -O0 # a build for the debugger
|
||
```
|
||
|
||
`finch run` passes on your program's exit code. If the program crashes, it says how
|
||
(for example `the program crashed: Segmentation fault`).
|
||
|
||
---
|
||
|
||
## 4. How a Finch file looks
|
||
|
||
```c
|
||
// comments start with two slashes
|
||
/* or span
|
||
several lines */
|
||
|
||
import "math.h" // C headers (optional), at the top
|
||
import shapes // Finch modules (optional): shapes.fch next to this file
|
||
link "m" // C libraries or C files to link (optional)
|
||
|
||
struct Point { // your own types
|
||
int x
|
||
int y
|
||
}
|
||
|
||
fn main() { // the program starts here
|
||
print("Hello")
|
||
}
|
||
```
|
||
|
||
Rules worth knowing:
|
||
|
||
- **One statement per line.** There are no semicolons. To split a long expression, break the line
|
||
**after** an operator (`a +⏎ b`) or anywhere inside brackets `( )` or `[ ]`.
|
||
- **Curly braces are always required** after `if`, `else`, `for`, `while`, and the opening `{`
|
||
goes on the same line.
|
||
- **No brackets around conditions:** `if x > 5 {`, not `if (x > 5) {`.
|
||
- Functions and structs can be in any order in the file.
|
||
- `main` is `fn main()`, `fn main() -> int` (returns the exit code) or `fn main([]str args)`
|
||
(gets the command-line arguments; `args[0]` is the program's own path).
|
||
|
||
---
|
||
|
||
## 5. Variables
|
||
|
||
```c
|
||
x := 5 // new variable, type inferred (int)
|
||
int y = 10 // new variable, type written explicitly
|
||
int z // no value: starts at zero ("" for str, [] for arrays, null for pointers)
|
||
Point p // a struct with no value: zero, or the struct's default values
|
||
|
||
x = 7 // assign
|
||
x += 1 // also -= *= /= %=
|
||
```
|
||
|
||
- A name can be declared **once** in a function. Finch does not allow reusing a name in an
|
||
inner block (no "shadowing"), because that is a common source of confusion.
|
||
- Variables exist from their declaration to the end of the `{ }` block they are in.
|
||
- Names: ASCII letters, digits and `_`, not starting with a digit. Non-ASCII letters are allowed only inside strings.
|
||
Type names (`int`, `u8`, …), built-in function names and keywords are reserved.
|
||
|
||
**Keywords:** `fn return if else while for in break continue true false null import link struct enum match defer or try const export`
|
||
|
||
### Constants
|
||
|
||
```c
|
||
const WIDTH = 640
|
||
const HEIGHT = WIDTH / 2 // from other constants
|
||
const f32 GRAVITY = 9.8 // with a type
|
||
const TITLE = "My game"
|
||
```
|
||
|
||
A constant is written at the top level of a file, next to the functions, and can't be changed. It is made of
|
||
numbers, text, `true`/`false`, chars, other constants and operators; a module's constant is `shapes.SIDES`.
|
||
|
||
---
|
||
|
||
## 6. Types
|
||
|
||
### Everyday types
|
||
|
||
| Type | Holds | Example |
|
||
|---------|-------|---------|
|
||
| `int` | whole number, 64-bit (same as `i64`) | `42`, `-7`, `0xFF`, `1_000_000` |
|
||
| `float` | decimal number, 64-bit (same as `f64`) | `3.14` |
|
||
| `bool` | `true` or `false` | |
|
||
| `char` | one byte-sized character | `'A'`, `'\n'` |
|
||
| `str` | text | `"hello\tworld"` |
|
||
| `[]T` | a list (array) of `T` | `[1, 2, 3]` |
|
||
| `map[K]V` | values of type `V` found by a key of type `K` | `["a": 1, "b": 2]` |
|
||
| your structs | named groups of values | `Point(1, 2)` |
|
||
|
||
String and char escapes: `\n` new line, `\t` tab, `\r`, `\0`, `\\`, `\"`, `\'`.
|
||
|
||
### Sized types (for files, hardware, C libraries)
|
||
|
||
| Signed | Range | Unsigned | Range |
|
||
|---|---|---|---|
|
||
| `i8` | −128 … 127 | `u8` | 0 … 255 |
|
||
| `i16` | −32 768 … 32 767 | `u16` | 0 … 65 535 |
|
||
| `i32` | about ±2.1 billion | `u32` | 0 … about 4.29 billion |
|
||
| `i64` | about ±9.2 × 10¹⁸ | `u64` | 0 … about 1.8 × 10¹⁹ |
|
||
|
||
`f32` is a 32-bit decimal number (about 7 significant digits), `f64` a 64-bit one (about 15–16).
|
||
|
||
### Conversions
|
||
|
||
Finch converts automatically **only when nothing can be lost**:
|
||
|
||
| From | To | Automatic? |
|
||
|---|---|---|
|
||
| a smaller whole number | a bigger one of the same signedness (`i8`→`i32`, `u8`→`u64`) | ✅ |
|
||
| unsigned | a strictly bigger signed (`u8`→`i16`, `u32`→`i64`) | ✅ |
|
||
| any whole number | `float` / `f32` | ✅ |
|
||
| `f32` | `f64` | ✅ |
|
||
| `null` | any pointer | ✅ |
|
||
| any pointer | `ptr`, and `ptr` to any pointer | ✅ |
|
||
| anything else | | ❌ write it: `type(value)` |
|
||
|
||
**Number literals adapt.** A number written in the code fits into any type that can hold it:
|
||
`u8 b = 200` works, `u8 b = 300` is an error. The same applies to constants imported from C headers.
|
||
|
||
**Explicit conversions** use the type name like a function:
|
||
|
||
```c
|
||
int(3.99) // 3 (cuts off the fraction)
|
||
float(7) / 2 // 3.5
|
||
u8(300) // 44 (keeps the lowest 8 bits)
|
||
int('A') // 65
|
||
char(66) // 'B'
|
||
bool(0) // false (any non-zero number is true)
|
||
str(42) // "42" (also floats, bools, chars; str([]u8) turns bytes into text)
|
||
int("42") // 42 (stops the program if the text isn't a number)
|
||
float("2.5") // 2.5
|
||
ptr(p) // a typed pointer as an untyped one
|
||
ptr(16) // a number as a pointer (C APIs that pass offsets as pointers, like OpenGL)
|
||
int(p) // a pointer as a number (its address)
|
||
str(p) // a C char pointer as text
|
||
```
|
||
|
||
**Fixed-size numbers wrap around**, like real hardware: a `u8` holding 255 plus 1 becomes 0.
|
||
Calculations happen in the type of the values involved, so `u8(250) + 10` is `4`, not `260`.
|
||
|
||
---
|
||
|
||
## 7. Operators
|
||
|
||
### By priority (higher binds tighter)
|
||
|
||
| Priority | Operators | Meaning |
|
||
|---|---|---|
|
||
| 5 | `*` `/` `%` `<<` `>>` `&` | multiply, divide, remainder, shift left/right, bitwise AND |
|
||
| 4 | `+` `-` `\|` `^` | add (and join text), subtract, bitwise OR, bitwise XOR |
|
||
| 3 | `==` `!=` `<` `<=` `>` `>=` | comparisons |
|
||
| 2 | `&&` | logical AND |
|
||
| 1 | `\|\|` | logical OR |
|
||
|
||
Unary (in front of a value): `-x` negate, `!x` logical NOT, `~x` flip all bits.
|
||
|
||
> **Different from C, on purpose:** bit operators bind tighter than comparisons, so
|
||
> `flags & 4 != 0` means `(flags & 4) != 0`.
|
||
|
||
Notes:
|
||
|
||
- `/` on whole numbers rounds toward zero: `7 / 2` is `3`. `%` takes the sign of the left side.
|
||
- Dividing by zero **stops the program** with `runtime error: division by zero` and the line number.
|
||
- `&&` and `||` only evaluate the right side if needed.
|
||
- Text: `+` joins, `==` `!=` compare contents, `<` `>` compare alphabetically (byte by byte).
|
||
- Pointers can be compared with `==` and `!=`, including against `null`. A `str` is never `null`.
|
||
- Arrays and structs can't be compared with `==`; compare their parts.
|
||
|
||
---
|
||
|
||
## 8. Control flow
|
||
|
||
`if` can also give a value; each branch holds one value, and the `else` is needed:
|
||
|
||
```c
|
||
label := if score > 100 { "high" } else if score > 10 { "mid" } else { "low" }
|
||
f32 speed = if running { 2.5 } else { 1.0 }
|
||
```
|
||
|
||
```c
|
||
if x > 10 {
|
||
...
|
||
} else if x > 0 {
|
||
...
|
||
} else {
|
||
...
|
||
}
|
||
|
||
while count > 0 {
|
||
count -= 1
|
||
}
|
||
|
||
for i in 0..10 { // i = 0, 1, …, 9 (the end is not included)
|
||
if i == 3 { continue }
|
||
if i == 8 { break }
|
||
}
|
||
|
||
for name in names { // every element of an array (or every char of a str)
|
||
print(name)
|
||
}
|
||
|
||
for i, name in names { // with its index: 0, 1, 2, ...
|
||
print(i, name)
|
||
}
|
||
|
||
for key, value in ages { // every entry of a map, in the order the keys were added
|
||
print(key, value) // (for key in ages: just the keys)
|
||
}
|
||
```
|
||
|
||
- A condition must be a `bool`. `if x {` with a number is an error; write `if x != 0 {`.
|
||
- In `for i in a..b`, both ends are evaluated **once**. `i` is an `int` and cannot be changed in the loop.
|
||
- In `for x in list`, `x` cannot be changed either. To change elements, loop over the indexes:
|
||
`for i in 0..list.len { list[i] = ... }`. Adding to the list inside the loop is fine.
|
||
- `while true { ... }` loops until a `break` or `return`.
|
||
- Code after `return`, `break` or `continue` in the same block is an error, because it can never run.
|
||
|
||
### defer
|
||
|
||
`defer` runs a statement when the current block ends, however it ends: normally, by `return`,
|
||
`break` or `continue`. Several defers run in reverse order. Handy for cleanup:
|
||
|
||
```c
|
||
f := fopen("data.txt", "r")
|
||
defer fclose(f)
|
||
// ... use f; it is closed when the block ends, on every path
|
||
```
|
||
|
||
A deferred statement can't contain `return`, `break` or `continue`.
|
||
|
||
---
|
||
|
||
## 9. Functions
|
||
|
||
```c
|
||
fn name(type1 param1, type2 param2) -> return_type {
|
||
...
|
||
return value
|
||
}
|
||
|
||
fn no_result(str message) { // no "->": returns nothing
|
||
print(message)
|
||
}
|
||
```
|
||
|
||
- Arguments behave like copies: changing a parameter never changes the caller's variable.
|
||
(Finch only really copies when needed: a function that doesn't change an array gets it for free.)
|
||
To change the caller's variable, pass a pointer: see [Pointers](#14-pointers).
|
||
- A function with `-> type` must return a value on every path. Finch checks this.
|
||
- A function cannot be defined inside another function.
|
||
- A function that can fail is written `-> int!` (or `-> !` with no result); see
|
||
[Errors as values](#errors-as-values).
|
||
- Long calls can be split over several lines inside the brackets; a comma after the last argument is fine.
|
||
|
||
---
|
||
|
||
## 10. Arrays and maps
|
||
|
||
```c
|
||
nums := [5, 3, 8] // an array of int
|
||
[]str names // an empty array of str
|
||
grid := [[1, 2], [3, 4]] // arrays of arrays
|
||
[]f32 xs = [1, 2.5] // the type decides what the numbers become
|
||
```
|
||
|
||
| Operation | Meaning |
|
||
|---|---|
|
||
| `a.len` | number of elements |
|
||
| `a[i]` | element `i` (from 0); out of range stops the program with a clear message |
|
||
| `a[i] = x` | change an element |
|
||
| `a.push(x)` | add at the end |
|
||
| `a.pop()` | remove the last one and give it back |
|
||
| `a.insert(i, x)` | put `x` at position `i`, moving the rest |
|
||
| `a.remove(i)` | remove position `i` and give it back |
|
||
| `a.clear()` | remove everything |
|
||
| `a.resize(n)` | make it `n` long (new elements are zero) |
|
||
| `a.contains(x)`, `a.find(x)` | is `x` there? its index, or -1 (numbers, chars, bools, str) |
|
||
| `a.sort()`, `a.reverse()` | in place (numbers, chars, str) |
|
||
| `a.slice(s, e)` | a new array with elements `s` … `e-1` |
|
||
| `a.join(sep)` | for `[]str`: one text with `sep` between the parts |
|
||
| `a.ptr` | the address of the first element (for C) |
|
||
| `print(a)` | prints `[1, 2, 3]` |
|
||
|
||
**Assigning copies:** after `b := a`, changing `b` does not change `a`.
|
||
|
||
### Maps
|
||
|
||
A `map[K]V` finds values by a key. Keys can be whole numbers, `char`, `bool` or `str`; values can be
|
||
anything (arrays, structs, other maps). Iterating goes in the order the keys were first added.
|
||
|
||
```c
|
||
ages := ["anna": 31, "bob": 25] // a map literal: map[str]int
|
||
map[str][]str groups // an empty map
|
||
map[int]str names = [:] // [:] is an empty map where the type is known
|
||
```
|
||
|
||
| Operation | Meaning |
|
||
|---|---|
|
||
| `m[k]` | the value for `k`; a missing key stops the program with `the key "k" is not in the map` |
|
||
| `m[k] = v` | add or replace |
|
||
| `m[k] += 1`, `m[k].push(x)`, `m[k].field = …` | changing a missing key adds it first, with the value type's default (0, `""`, `[]`, …) |
|
||
| `m.len` | the number of keys |
|
||
| `m.has(k)` | is `k` there? |
|
||
| `m.get(k, default)` | the value, or `default` (only computed when needed) |
|
||
| `m.remove(k)` | removes `k`; `true` if it was there |
|
||
| `m.clear()` | removes everything |
|
||
| `m.keys()`, `m.values()` | new arrays, in insertion order |
|
||
| `print(m)`, `str(m)` | `{"anna": 31, "bob": 25}` |
|
||
|
||
```c
|
||
map[str]int counts
|
||
for word in text.split(" ") {
|
||
counts[word] += 1 // counting: a new word starts at 0
|
||
}
|
||
for word, n in counts {
|
||
print(word, n)
|
||
}
|
||
```
|
||
|
||
Lookups are hash-based (average constant time). Like arrays, maps own their keys and values,
|
||
copy on assignment, and are freed at the end of their block. Don't add or remove keys of a map
|
||
inside a `for` over that same map; collect them in an array and change the map after the loop.
|
||
|
||
---
|
||
|
||
## 11. Text (str)
|
||
|
||
```c
|
||
s := "Hello"
|
||
s += ", world" // join
|
||
print(s.len, s[0]) // 12 H (.len counts bytes; ą, ż, … take 2)
|
||
s[0] = 'J' // change a character
|
||
```
|
||
|
||
| Method | Gives back |
|
||
|---|---|
|
||
| `s.sub(a, b)` | the part from `a` to `b-1` |
|
||
| `s.find(t)` | where `t` starts, or -1 |
|
||
| `s.contains(t)`, `s.starts_with(t)`, `s.ends_with(t)` | `bool` |
|
||
| `s.split(sep)` | `[]str`; `split("")` gives single characters |
|
||
| `s.trim()` | without spaces/newlines at both ends |
|
||
| `s.upper()`, `s.lower()` | A–Z / a–z changed (ASCII letters) |
|
||
| `s.replace(a, b)` | every `a` replaced by `b` |
|
||
| `s.repeat(n)` | `s` n times |
|
||
| `s.bytes()` | `[]u8` with the bytes |
|
||
| `s.ptr` | a `ptr[char]` for C (valid while `s` lives) |
|
||
|
||
`str` keeps its length, so `.len` is instant, and it is always NUL-terminated, so passing it
|
||
to C as `char*` works. The other way round, a C function that gives back a `char *` gives you a `str`
|
||
(a copy is made when you keep it), and `str(p)` reads a `ptr[char]` you got some other way.
|
||
|
||
Text over several lines goes between `"""`. The line break after the opening quotes is dropped, and so is the
|
||
indentation of the closing quotes, from every line, so the text can be indented with your code:
|
||
|
||
```c
|
||
shader := """
|
||
#version 330
|
||
void main() { gl_Position = vec4(0.0); }
|
||
"""
|
||
```
|
||
|
||
Numbers as text: `str(x)` is the shortest text that reads back as the same number (`0.1`, `0.30000000000000004`,
|
||
`1e+20`); `str(x, 2)` always has two digits after the point (`3.14`).
|
||
|
||
---
|
||
|
||
## 12. Structs and methods
|
||
|
||
```c
|
||
struct Player {
|
||
str name
|
||
int lives = 3 // a default value
|
||
[]int scores
|
||
Point pos // structs inside structs
|
||
}
|
||
|
||
p := Player(name: "Ola") // by name: missing fields get their default (or zero)
|
||
q := Point(1, 2) // in order: then every field must be given
|
||
p.lives -= 1
|
||
p.scores.push(10)
|
||
p.pos.x = 5
|
||
print(p) // Player(name: "Ola", lives: 2, scores: [10], pos: Point(x: 5, y: 0))
|
||
```
|
||
|
||
- Assigning or passing a struct copies it, together with its arrays and text.
|
||
- A struct can't contain itself directly; use `ptr[Node]` or `[]Node` for that field (trees, lists).
|
||
|
||
### Methods
|
||
|
||
A method is a function that belongs to a struct: `fn Struct.name(...)`. Inside, `self` is the struct
|
||
it was called on, and it is the caller's value itself, not a copy: changes to `self` stay.
|
||
|
||
```c
|
||
fn Player.heal(int amount) {
|
||
self.lives += amount
|
||
}
|
||
|
||
fn Player.is_alive() -> bool {
|
||
return self.lives > 0
|
||
}
|
||
|
||
p := Player(name: "Ola")
|
||
p.heal(2) // p.lives is now 5
|
||
if p.is_alive() { ... }
|
||
|
||
team[0].heal(1) // on an array element, a field, m[key], through a ptr[Player] ...
|
||
```
|
||
|
||
- A method goes in the same file (module) as its struct, and is available wherever the struct is.
|
||
- Methods can also be added to **structs from C headers**: `fn Vector2.length() -> f32 { ... }`.
|
||
- A method can't have the name of a field.
|
||
- Calling a method that changes `self` on a loop variable (`for p in team { p.heal(1) }`) is an error,
|
||
because the loop variable is read-only; use `for i in 0..team.len { team[i].heal(1) }`.
|
||
- Like functions, methods can fail (`-> !`); see [Errors as values](#errors-as-values).
|
||
|
||
---
|
||
|
||
## 13. Memory: who frees what
|
||
|
||
You never free arrays, text or structs. Finch does it for you, at a predictable moment:
|
||
**when the block that owns the value ends** (`}`), or earlier on `return`, `break`, `continue`.
|
||
|
||
- A variable **owns** its value. Assigning copies it, so two variables never share one list.
|
||
- A value produced by a function call or an expression is **moved** where it's stored: no copy.
|
||
- `return list` moves the variable out of the function: no copy.
|
||
- Function parameters are **lent**: the function gets the caller's value without a copy. If the
|
||
function changes the parameter, Finch makes it a private copy first. You never notice except in speed.
|
||
|
||
For memory you manage yourself (linked lists, trees, sharing between structures):
|
||
|
||
```c
|
||
node := new(Node(value: 1)) // put a value on the heap, get a ptr[Node]
|
||
defer free(node) // free(...) gives it back (and frees its arrays and text)
|
||
```
|
||
|
||
Memory from C (`malloc`, library objects) is freed with C's functions, as in C.
|
||
|
||
---
|
||
|
||
## 14. Pointers
|
||
|
||
A pointer holds the **address** of a value. Finch uses words instead of C's `*` and `&`:
|
||
|
||
| C | Finch | Meaning |
|
||
|---|---|---|
|
||
| `int *p` | `ptr[int] p` | p points to an int |
|
||
| `&x` | `addr(x)` | the address of x (also `addr(a[i])`, `addr(s.field)`) |
|
||
| `*p` | `p.value` | the value p points to |
|
||
| `p->field` | `p.field` | a field of the struct p points to |
|
||
| `p[i]` | `p[i]` | C-style indexing (no bounds check) |
|
||
| `NULL` | `null` | points nowhere |
|
||
| `void *` | `ptr` | points to something of unknown type |
|
||
|
||
```c
|
||
fn double(ptr[int] p) {
|
||
p.value = p.value * 2
|
||
}
|
||
|
||
x := 21
|
||
double(addr(x)) // x is 42
|
||
```
|
||
|
||
If a struct has its own field called `value`, then `p.value` on a pointer to it means that field.
|
||
|
||
Using `.value`, `.field` or `[i]` on a `null` pointer **stops the program** with a clear message.
|
||
|
||
⚠️ A pointer to a variable must not be used after the variable's block has ended. Finch does not check this.
|
||
|
||
---
|
||
|
||
## 15. Built-in functions
|
||
|
||
| Function | What it does |
|
||
|---|---|
|
||
| `print(a, b, ...)` | Prints all values separated by spaces, then a new line. Works with every type, including arrays and structs. |
|
||
| `input()`, `input("question")` | Reads one line typed by the user (without the newline). Empty text at end of input. |
|
||
| `read_file(path)` | The whole file as `str`. Stops the program with a clear message if it can't be read, unless handled: `read_file(p) or ...`. |
|
||
| `write_file(path, text)` | Writes (replaces) the file; gives back `true` if it worked. With `or` / `try`, the failure carries the reason. |
|
||
| `file_exists(path)` | `true` / `false` |
|
||
| `delete_file(path)` | Deletes the file; `true` if it worked (or the reason, with `or` / `try`). |
|
||
| `error(message)` | The failure a fallible function returns: `return error("...")`. |
|
||
| `shell(command)` | Runs a shell command, gives back its exit code. |
|
||
| `exit(code)` | Ends the program right away. |
|
||
| `addr(x)` | A pointer to `x`; `addr(function)` gives C a function pointer (callback). |
|
||
| `new(value)`, `free(p)` | Manual heap memory (see [Memory](#13-memory-who-frees-what)). |
|
||
| `int(x)`, `str(x)`, `u8(x)`, … | Conversions (see [Conversions](#conversions)). `str(x)` works on every type and gives the same text `print` shows. `int(text) or 0` handles text that isn't a number. |
|
||
|
||
---
|
||
|
||
## 16. Modules
|
||
|
||
Split a program into files. `import shapes` loads `shapes.fch` from the same folder (or from a
|
||
folder listed in the `FINCH_PATH` environment variable, separated by `:`).
|
||
|
||
```c
|
||
// shapes.fch
|
||
struct Box {
|
||
float w
|
||
float h
|
||
}
|
||
|
||
fn area(Box b) -> float {
|
||
return b.w * b.h
|
||
}
|
||
```
|
||
|
||
```c
|
||
// main.fch
|
||
import shapes
|
||
|
||
fn main() {
|
||
b := shapes.Box(2, 3)
|
||
print(shapes.area(b))
|
||
shapes.Box other // a module's type in a declaration
|
||
}
|
||
```
|
||
|
||
- Everything in a module is used with the module's name in front: `shapes.area`, `shapes.Box`.
|
||
- A module doesn't have `main`. Modules can import other modules.
|
||
- Modules in folders: `import ui/button` loads `ui/button.fch` and is used as `button.draw()`.
|
||
- Another name: `import ui/button as btn` → `btn.draw()`. Two imports can't use the same name.
|
||
|
||
---
|
||
|
||
## 17. Using C libraries
|
||
|
||
### Step 1: import the header
|
||
|
||
```c
|
||
import "stdio.h"
|
||
import "GLFW/glfw3.h" // a path inside the system include folders
|
||
import "mylib.h" // a header next to your .fch file
|
||
```
|
||
|
||
Finch reads the header with **libclang** (clang's C parser) and makes these usable:
|
||
|
||
- **functions**, including ones with a variable number of arguments, like `printf`,
|
||
- **structs**, by value and by pointer (`Vector2`, `SDL_Rect`, `CXCursor`, …), with their fields,
|
||
- **`enum` values** and **simple `#define` constants** (`M_PI`, `GL_COLOR_BUFFER_BIT`),
|
||
- **global variables**, like `stdout` and `stderr`.
|
||
|
||
### Step 2: link the library
|
||
|
||
```c
|
||
link "glfw" // uses libglfw.so (pkg-config is asked first, if installed)
|
||
link "helpers.c" // compile and link your own C file (next to the .fch file)
|
||
link "prebuilt.a" // or an object file / static library
|
||
```
|
||
|
||
If you forget the `link` line, Finch tells you exactly what is missing:
|
||
|
||
```
|
||
error: glfwInit, glfwCreateWindow, glfwMakeContextCurrent and 5 more come from "GLFW/glfw3.h", but its library isn't linked
|
||
...
|
||
link "glfw"
|
||
```
|
||
|
||
### How C types appear in Finch
|
||
|
||
| C | Finch |
|
||
|---|---|
|
||
| `char`, `signed char`, `unsigned char` | `char`, `i8`, `u8` |
|
||
| `short`, `int`, `long`, `long long` | `i16`, `i32`, `i64`, `i64` (by actual size) |
|
||
| `unsigned …` | `u16`, `u32`, `u64` |
|
||
| `float`, `double` | `f32`, `f64` |
|
||
| `_Bool` / `bool` | `bool` |
|
||
| `enum` | the matching signed integer |
|
||
| `char *` as a **parameter or result** | `str` |
|
||
| `char *` inside a struct or behind a pointer | `ptr[char]` (use `str(p)` to read it) |
|
||
| `struct X` by value | `X`, a struct with the same fields |
|
||
| `struct X *`, `int *`, … | `ptr[X]`, `ptr[i32]`, … |
|
||
| `int arr[16]` inside a struct | `[16]i32`: index it and use `.len` |
|
||
| `void *`, function pointers, incomplete structs | `ptr` |
|
||
|
||
### Example: structs and callbacks
|
||
|
||
```c
|
||
import "raylib.h"
|
||
link "raylib"
|
||
|
||
fn main() {
|
||
InitWindow(800, 450, "Finch")
|
||
while !WindowShouldClose() {
|
||
BeginDrawing()
|
||
ClearBackground(Color(r: 30, g: 30, b: 40, a: 255))
|
||
DrawCircleV(Vector2(400, 225), 50, Color(255, 136, 0, 255))
|
||
EndDrawing()
|
||
}
|
||
CloseWindow()
|
||
}
|
||
```
|
||
|
||
To give C a function to call back (like `qsort`'s comparator), use `addr(myFunction)`.
|
||
Its parameters must be C types (numbers, pointers, C structs).
|
||
|
||
### Graphics: OpenGL and Vulkan
|
||
|
||
Both work directly; `examples/opengl.fch` and `examples/vulkan.fch` are complete programs.
|
||
|
||
- **OpenGL 3+** on Linux: the modern functions (`glCreateShader`, `glGenVertexArrays`, …) are only
|
||
declared when `GL_GLEXT_PROTOTYPES` is defined. Finch can't define C macros, so put the includes in a
|
||
tiny header next to your program and import that:
|
||
|
||
```c
|
||
// opengl.h
|
||
#define GL_GLEXT_PROTOTYPES
|
||
#include <GL/gl.h>
|
||
#include <GL/glext.h>
|
||
#include <GLFW/glfw3.h>
|
||
```
|
||
|
||
Then `import "opengl.h"`, `link "glfw"`, `link "GL"`. Out-parameters use `addr(...)`
|
||
(`glGenBuffers(1, addr(vbo))`), buffer data uses `.ptr` (`vertices.ptr`), and byte offsets use
|
||
`ptr(...)` (`glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, stride, ptr(8))`).
|
||
- **Vulkan**: `import "vulkan/vulkan.h"` and `link "vulkan"`. Vulkan's create-info structs are built with
|
||
named fields (missing ones are zero), handles are `ptr`, and lists come from the usual "call twice"
|
||
pattern: once for the count, once with `list.ptr` after `list.resize(count)`. Function-like macros such as
|
||
`VK_MAKE_API_VERSION` aren't available; compute the number instead (`1 << 22` is Vulkan 1.0).
|
||
|
||
### What does not work yet
|
||
|
||
- C **unions** and structs with **bit fields** passed to or returned from a C function **by value** (pointers to them
|
||
work, and so do unions as variables: `SDL_Event e`, `e.type`, `e.key.key`, `SDL_PollEvent(addr(e))`).
|
||
- **Function-like macros** (`#define MAX(a, b) ...`) and macros that make a struct (raylib's `RAYWHITE`).
|
||
Macros that are a number, a text or an expression of them (`(1u << 3)`, `((Uint64)1)`, `(A | B)`) work.
|
||
- `long double`, 128-bit integers.
|
||
|
||
---
|
||
|
||
## 18. Errors
|
||
|
||
### Errors as values
|
||
|
||
Some failures are normal: the user types `abc`, a file is missing. Finch handles them without
|
||
exceptions and without hidden control flow. A function that can fail says so with `!` after its result
|
||
type, and fails with `return error("message")`:
|
||
|
||
```c
|
||
fn parse_port(str text) -> int! {
|
||
n := int(text) or { return error("'" + text + "' is not a number") }
|
||
if n < 1 || n > 65535 {
|
||
return error("port " + str(n) + " is out of range")
|
||
}
|
||
return n
|
||
}
|
||
|
||
fn save(str path, str text) -> ! { // can fail, gives back nothing
|
||
try write_file(path, text)
|
||
}
|
||
```
|
||
|
||
The caller **must** handle the failure, in one of three ways (forgetting is a compile error):
|
||
|
||
| Code | On failure |
|
||
|---|---|
|
||
| `port := parse_port(s) or 8080` | use this value instead (it's only computed on failure; it can be another call with `or`) |
|
||
| `port := parse_port(s) or { print(err); return }` | run the block; `err` is the message (a `str`). The block must leave (`return`, `break`, `continue`, `exit`) unless the result isn't used. |
|
||
| `port := try parse_port(s)` | fail the current function with the same error (it must be fallible too) |
|
||
|
||
```c
|
||
fn load(str path) -> Config! {
|
||
text := try read_file(path)
|
||
port := try parse_port(text.trim())
|
||
return Config(port: port)
|
||
}
|
||
|
||
fn main() -> ! { // main can fail too: the error is printed, and the exit code is 1
|
||
config := try load("app.conf")
|
||
...
|
||
}
|
||
```
|
||
|
||
Built-ins that can fail work the same way: `int(text)`, `float(text)`, `read_file`, `write_file`,
|
||
`delete_file`. Without `or` / `try` they behave as before (`int("x")` stops the program,
|
||
`write_file` gives back a `bool`).
|
||
|
||
Under the hood a fallible function returns its value plus a flag and the message: no allocation on
|
||
success, no unwinding, and the cost of a failure is building its message.
|
||
|
||
### Compile errors
|
||
|
||
```
|
||
game.fch:12:9: error: 'score' must be int, but this is str
|
||
12 | score = "high"
|
||
| ^
|
||
```
|
||
|
||
Format: `file:line:column: error: explanation`, then the line with a `^` under the spot.
|
||
|
||
### Runtime errors
|
||
|
||
Instead of undefined behavior (as in C), Finch stops with a message and exit code 1:
|
||
|
||
| Message | Cause |
|
||
|---|---|
|
||
| `runtime error: division by zero` | `/` or `%` by a variable that was 0 |
|
||
| `runtime error: division overflows …` | the smallest signed number divided by −1 |
|
||
| `runtime error: index 5 is out of range (the length is 3)` | `a[5]` on a shorter array or str |
|
||
| `runtime error: pop() on an empty array` | |
|
||
| `runtime error: used .value on a null pointer` | also `.field` and `[i]` through `null` |
|
||
| `runtime error: can't turn "x" into int` | `int(...)` / `float(...)` on text that isn't a number |
|
||
| `runtime error: can't read the file "…"` | `read_file` on a missing or unreadable file (handle it with `or`) |
|
||
| `runtime error: the key "x" is not in the map …` | `m["x"]` when the map has no such key (use `.has` or `.get`) |
|
||
| `runtime error: called .f() on a null pointer` | a method called through a `null` `ptr[T]` |
|
||
|
||
---
|
||
|
||
## 19. Editors: VS Code, Kate and others
|
||
|
||
Every editor feature comes from the same language server, `finch lsp`, which uses the real compiler:
|
||
errors as you type, types and docs on hover, go to definition, completion (fields, methods, map and array
|
||
methods after `.`, module members, names from C headers), the file's outline, and parameter hints.
|
||
|
||
### Kate (and KWrite, KDevelop)
|
||
|
||
```sh
|
||
editors/kate/install.sh
|
||
```
|
||
|
||
It installs the highlighting (`~/.local/share/org.kde.syntax-highlighting/syntax/finch.xml`) and adds `finch`
|
||
to Kate's LSP client settings, keeping your other servers. Restart Kate and enable the **LSP Client** plugin.
|
||
To run programs, add `finch run %f` as a target in the **Build & Run** plugin; Finch's
|
||
`file:line:column: error:` messages are then clickable. Details: `editors/kate/README.md`.
|
||
|
||
### 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.
|
||
|
||
### Others
|
||
|
||
Neovim, Helix, Zed, Emacs, Sublime and any other editor that speaks the Language Server Protocol:
|
||
run `finch lsp` for files ending in `.fch` (stdin/stdout, no options).
|
||
|
||
## 20. Debugging
|
||
|
||
```sh
|
||
finch build game.fch -g -O0 -o game
|
||
gdb ./game
|
||
(gdb) break game.fch:12
|
||
(gdb) run
|
||
(gdb) bt # where are we, through which functions
|
||
(gdb) info locals # all variables
|
||
(gdb) print player.lives
|
||
```
|
||
|
||
`-g` adds debug info (lines, functions, variables, struct fields). `-O0` keeps every variable
|
||
visible; without it the optimizer may remove some. On Windows, `-g` writes CodeView, which the
|
||
Visual Studio debugger and WinDbg read.
|
||
|
||
---
|
||
|
||
## 21. Troubleshooting
|
||
|
||
| Problem | Solution |
|
||
|---|---|
|
||
| `can't find the C header 'x.h'` | The library's development files are missing. Install them (Debian: `libx-dev`; Nix: add to `shell.nix`). |
|
||
| `… come from "x.h", but its library isn't linked` | Add `link "name"` at the top of the file. |
|
||
| `the library 'x' wasn't found` | Not installed, or a wrong name. On Nix, add it to `shell.nix` and run inside `nix-shell`. |
|
||
| `the C function 'f' can't be used from Finch yet` | It uses a union, bit fields or another unsupported type by value. Look for a variant that takes pointers. |
|
||
| `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`. |
|
||
| macOS: `finch` doesn't start (`Library not loaded: …libLLVM…`) | `brew install llvm@21`. |
|
||
| macOS: `can't find the C header 'stdio.h'` | Install the command line tools: `xcode-select --install`. |
|
||
| `--target arm64`: `no C compiler found to link with` | Install `aarch64-linux-gnu-gcc` (Debian: `gcc-aarch64-linux-gnu`), or set `FINCH_CC`. |
|
||
| `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. |
|
||
|
||
---
|
||
|
||
## 22. Project layout and tests
|
||
|
||
```
|
||
Finch/
|
||
├── src/ the compiler, in Finch (lexer, parser, gen: LLVM IR as text, cimport: libclang, lsp, main ...)
|
||
├── std/ the standard library, in Finch: json, path, time, proc
|
||
├── runtime/ finch_rt.c: the small runtime (text, arrays, maps, input, files)
|
||
├── bootstrap/ build.sh, install.sh and the seeds (the compiler's own IR, one per system)
|
||
├── examples/ hello, tour, types, structs, todo, guess, c_import, window, opengl, vulkan, raylib, llvm
|
||
├── tests/
|
||
│ ├── run/ new/ programs + the exact output they must print (.out), input (.in)
|
||
│ ├── fail/ new/fail/ programs that must fail, with the expected error in line 1
|
||
│ ├── run.sh the test runner (MEMCHECK=1 also checks memory with valgrind)
|
||
│ ├── cross.sh every test built for Windows / ARM64 and run under Wine / QEMU
|
||
│ └── lsp_test.py the language server, talked to like an editor does
|
||
├── editors/ the VS Code extension (vscode/), Kate highlighting and LSP setup (kate/)
|
||
├── docs/ this documentation (en, pl), including the cheat sheet
|
||
└── shell.nix the Nix development environment
|
||
```
|
||
|
||
```sh
|
||
tests/run.sh # programs, refusals, -g builds, a shared library, the language server
|
||
MEMCHECK=1 tests/run.sh # the same under valgrind: no leaks, no bad memory access
|
||
tests/cross.sh windows # every test built for Windows and run with Wine
|
||
tests/cross.sh arm64 # every test built for ARM64 Linux and run with QEMU
|
||
```
|
||
|
||
### Variants that hold values
|
||
|
||
A variant can carry values. Make one with `Shape.Circle(2.0)`, take them out with `match`, which names them:
|
||
|
||
```c
|
||
enum Shape {
|
||
Circle(float)
|
||
Rect(float, float)
|
||
Point
|
||
}
|
||
|
||
fn Shape.area() -> float {
|
||
return match self {
|
||
Circle(r) -> 3.14 * r * r
|
||
Rect(w, h) -> w * h
|
||
Point -> 0.0
|
||
}
|
||
}
|
||
```
|
||
|
||
`_` ignores a value. `match` must handle every variant (or end with `else`). Printing shows `Rect(2, 3.5)`. Such an enum is
|
||
copied and freed like a struct; it can't be compared with `==` (match on it). Variants of these enums can't have numbers.
|
||
|
||
---
|
||
|
||
## 23. Enums and match
|
||
|
||
An **enum** is a type with a short list of named values.
|
||
|
||
```
|
||
enum Color { Red, Green, Blue }
|
||
enum Status { Ok = 200, NotFound = 404 } // give a variant a number if it needs one
|
||
|
||
c := Color.Green
|
||
print(c) // Green
|
||
print(int(Status.NotFound)) // 404
|
||
print(Color(2)) // Blue: a number turned into the enum (checked: Color(7) is an error)
|
||
```
|
||
|
||
Variants are written `Color.Red`. They can be compared with `==` and `!=` (not with `<`), used as map keys,
|
||
struct fields and array elements, and an enum can have methods like a struct: `fn Color.next() -> Color { ... }`.
|
||
|
||
**match** picks what to do from a value, instead of a ladder of `if`:
|
||
|
||
```
|
||
match c {
|
||
Red -> print("warm") // inside a match on an enum, the bare name is enough
|
||
Green, Blue -> print("cool") // several values for one arm
|
||
}
|
||
|
||
size := match n { // as an expression: it gives a value
|
||
1, 2, 3 -> "small"
|
||
4, 5 -> "medium"
|
||
else -> "large"
|
||
}
|
||
|
||
match word {
|
||
"quit" -> {
|
||
print("bye")
|
||
return
|
||
}
|
||
else -> print("what?")
|
||
}
|
||
```
|
||
|
||
`match` works on enums, whole numbers, chars, text and bools. The arms are tried in order. A match on an enum has to
|
||
handle **every variant** or end with `else` (the compiler says which are missing); any other match needs `else`.
|
||
In an expression, every arm gives the same type.
|
||
|
||
## 24. Generics
|
||
|
||
Write a function or a struct once for any type, with the type as a name in square brackets:
|
||
|
||
```
|
||
fn max[T](T a, T b) -> T {
|
||
if a > b {
|
||
return a
|
||
}
|
||
return b
|
||
}
|
||
|
||
struct Stack[T] {
|
||
[]T items
|
||
}
|
||
|
||
fn Stack[T].push(T x) {
|
||
self.items.push(x)
|
||
}
|
||
|
||
print(max(3, 9), max("apple", "pear")) // T is worked out from the arguments
|
||
print(max[float](1, 2.5)) // or written out, where it can't be worked out
|
||
s := Stack[int]() // a generic struct is always written with its type
|
||
s.push(7)
|
||
```
|
||
|
||
Several parameters work (`struct Pair[A, B]`), and so do nested types (`Stack[Pair[int, str]]`). Each use makes its
|
||
own copy of the code, so a generic is as fast as code written by hand. There are no constraints: if `T` can't do
|
||
what the function does (`a > b` on a struct), the error says so and tells which version it was building, like
|
||
`(while building bigger[Point])`.
|
||
|
||
## 25. The standard library
|
||
|
||
Modules that come with Finch, written in Finch. `import json` finds `json.fch` next to your file first, and the
|
||
library's second, so your own file of the same name wins.
|
||
|
||
| Module | What it does |
|
||
|---|---|
|
||
| `path` | `join`, `base`, `dir`, `ext`, `stem`, `parts`, `clean`, `is_abs`: paths as text, separated by `/` |
|
||
| `time` | `now()`, `millis()` (a clock for measuring), `sleep(ms)`, `date(t)`, `local(t)`, `format(t, "%Y-%m-%d")`, `days_in_month` |
|
||
| `proc` | `run(cmd)` (the exit code), `output(cmd)` (what it printed; an error if it failed), `env(name, fallback)`, `pid()`, `cwd()` |
|
||
| `json` | `parse(text)`, `stringify(v)`, `pretty(v)`, and the value type `Json` with `get`, `at`, `set`, `push`, `has`, `str_or`, `int_or` ... |
|
||
| `fs` | `read_bytes(p)` / `write_bytes(p, data)` (`[]u8`, any bytes), `exists`, `is_dir`, `mkdir` (with the folders above), `list` (sorted names), `remove_dir`, `data_dir(app)` (a folder for the user's saves) |
|
||
|
||
`min(a, b, ...)`, `max(a, b, ...)`, `abs(x)` and `clamp(x, lo, hi)` work on any numbers without an import (a function of
|
||
your own with the same name wins).
|
||
|
||
```
|
||
import json
|
||
import path
|
||
|
||
doc := try json.parse(read_file("config.json"))
|
||
name := doc.get("name").str_or("unnamed") // a missing key is null, so chains never fail
|
||
doc.set("name", json.new_text(name + "!"))
|
||
write_file(path.join("out", "config.json"), json.pretty(doc))
|
||
```
|
||
|
||
## 26. Current limits
|
||
|
||
Not there yet:
|
||
|
||
- Interfaces.
|
||
- Unicode-aware text: `.len`, `s[i]` and `upper()` work on bytes / ASCII.
|
||
- Threads.
|
||
- Windows on ARM64, 32-bit systems, WebAssembly.
|
||
|
||
See the [roadmap in the README](../../README.md#roadmap).
|