Docs for Finch 2.0: beginners, technicians, engineers (en, pl); READMEs; raylib example

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Nexoniarz 2026-10-08 19:20:46 +02:00
commit 3e55da2645
13 changed files with 2590 additions and 2140 deletions

View file

@ -20,10 +20,15 @@ Take it slowly, one section at a time, and type every example yourself: that is
8. [Making decisions: if](#8-making-decisions-if)
9. [Repeating things: loops](#9-repeating-things-loops)
10. [Your own commands: functions](#10-your-own-commands-functions)
11. [When something goes wrong](#11-when-something-goes-wrong)
12. [Small projects to try](#12-small-projects-to-try)
13. [Cheat sheet](#13-cheat-sheet)
14. [Words you will hear](#14-words-you-will-hear)
11. [Lists: many values in one box](#11-lists-many-values-in-one-box)
12. [Playing with text](#12-playing-with-text)
13. [Asking the user](#13-asking-the-user)
14. [Your own kinds of values: structs](#14-your-own-kinds-of-values-structs)
15. [Saving to a file](#15-saving-to-a-file)
16. [When something goes wrong](#16-when-something-goes-wrong)
17. [Small projects to try](#17-small-projects-to-try)
18. [Cheat sheet](#18-cheat-sheet)
19. [Words you will hear](#19-words-you-will-hear)
---
@ -72,7 +77,7 @@ To check it works, type:
./build/finch version
```
You should see something like `finch 1.0.0`.
You should see something like `finch 2.0.0`.
---
@ -441,7 +446,200 @@ It does not matter where in the file you put your functions, above or below `mai
---
## 11. When something goes wrong
## 11. Lists: many values in one box
Sometimes one box is not enough. A shopping list has many things on it.
A **list** (programmers also say **array**) keeps many values in order:
```c
fn main() {
fruits := ["apple", "banana", "cherry"]
print(fruits)
print("I have", fruits.len, "fruits")
print("The first one is", fruits[0])
}
```
```
["apple", "banana", "cherry"]
I have 3 fruits
The first one is apple
```
- Square brackets `[ ]` make a list. Commas separate the values.
- `.len` tells you how many things are in the list.
- `fruits[0]` is the **first** thing. Counting starts at **0**, so `fruits[1]` is the second, and `fruits[2]` the third.
If you ask for a place that doesn't exist, like `fruits[10]`, the program stops and tells you:
`index 10 is out of range (the length is 3)`.
### Adding and taking away
```c
fn main() {
[]int scores // an empty list of whole numbers
scores.push(10) // put 10 at the end
scores.push(25)
scores.push(7)
print(scores) // [10, 25, 7]
last := scores.pop() // take the last one out
print(last, scores) // 7 [10, 25]
scores.sort()
print(scores)
}
```
### Going through a list
`for ... in` visits every value, one by one:
```c
fn main() {
total := 0
for n in [3, 5, 2] {
total += n
}
print("Total:", total) // Total: 10
}
```
Lists can do more: `contains`, `find`, `insert`, `remove`, `sort`, `reverse`, `join`.
The [guide for technicians](for-technicians.md#arrays) lists them all.
---
## 12. Playing with text
Text can be glued together with `+`:
```c
fn main() {
name := "Ola"
greeting := "Hello, " + name + "!"
print(greeting)
}
```
To glue a number to text, turn the number into text first with `str(...)`:
```c
age := 10
print("Age: " + str(age))
```
Text has handy tools too:
```c
fn main() {
word := "Finch"
print(word.len) // 5 letters
print(word[0]) // F (the first letter)
print(word.upper()) // FINCH
print(word.lower()) // finch
print(word.contains("in")) // true
for letter in word {
print(letter) // F, i, n, c, h, one per line
}
}
```
---
## 13. Asking the user
`input` shows a question and waits until the person types an answer and presses Enter:
```c
fn main() {
name := input("What's your name? ")
print("Nice to meet you,", name)
}
```
The answer is always **text**. If you need a number, turn it into one with `int(...)`:
```c
fn main() {
answer := input("How old are you? ")
age := int(answer)
print("Next year you will be", age + 1)
}
```
If the person types something that isn't a number, the program stops with a message
like `can't turn "abc" into int`.
---
## 14. Your own kinds of values: structs
Imagine a card in a library: each card has a title, an author and a year.
A **struct** lets you make your own kind of value with named parts:
```c
struct Book {
str title
str author
int year
}
fn main() {
b := Book(title: "The Hobbit", author: "Tolkien", year: 1937)
print(b.title, "by", b.author)
b.year = 1938
print(b)
}
```
```
The Hobbit by Tolkien
Book(title: "The Hobbit", author: "Tolkien", year: 1938)
```
- `struct Book { ... }` describes what every book has. Put it **outside** `main`.
- `Book(title: ..., author: ..., year: ...)` makes one book.
- `b.title` reads one part. `b.year = 1938` changes it.
You can give parts a starting value. Then you don't have to fill them in:
```c
struct Player {
str name
int lives = 3
}
fn main() {
p := Player(name: "Ola")
print(p.lives) // 3
}
```
And you can keep many of them in a list: `[]Book shelf`, then `shelf.push(b)`.
---
## 15. Saving to a file
Programs forget everything when they end. To remember things, save them to a file:
```c
fn main() {
write_file("note.txt", "Buy milk")
print(read_file("note.txt"))
}
```
- `write_file(name, text)` saves the text in a file (and replaces what was there).
- `read_file(name)` gives you the file's text back.
- `file_exists(name)` tells you if the file is there (`true` or `false`).
The file appears in the folder where you ran the program.
---
## 16. When something goes wrong
Everyone makes mistakes when programming, even experts, every day.
Finch tries hard to explain what went wrong in plain words. Example:
@ -479,10 +677,13 @@ Here we simply misspelled `age`.
| `this '{' is never closed` | A `}` is missing | Add the `}` |
| `a condition must be bool` | `if` needs a yes/no question | Compare something: `if x > 0` |
| `runtime error: division by zero` | You divided by 0 while the program ran | Check the number before dividing |
| `index 5 is out of range (the length is 3)` | You asked a list for a place it doesn't have | Remember counting starts at 0; check `.len` |
| `can't turn "abc" into int` | `int(...)` got text that isn't a number | Check what the person typed |
| `can't use '+' on str and int` | Text plus a number | Use `str(...)` on the number |
---
## 12. Small projects to try
## 17. Small projects to try
Try writing each one yourself first. Look at the solution only if you get stuck.
@ -589,9 +790,60 @@ fn main() {
```
</details>
### Project 6: Guess the number
The computer picks a secret number. The player guesses until they find it,
and the computer says "higher" or "lower" each time.
<details><summary>Solution</summary>
```c
fn main() {
secret := 42
tries := 0
while true {
guess := int(input("Your guess: "))
tries += 1
if guess < secret {
print("Higher!")
} else if guess > secret {
print("Lower!")
} else {
print("Yes! You needed", tries, "tries")
return
}
}
}
```
`return` inside `main` ends the program. (A version with a random number is in `examples/guess.fn`.)
</details>
### Project 7: Class average
Ask for 3 grades, keep them in a list, and show the list and the average.
<details><summary>Solution</summary>
```c
fn main() {
[]int grades
for i in 0..3 {
grades.push(int(input("Grade: ")))
}
sum := 0
for g in grades {
sum += g
}
print("Grades:", grades)
print("Average:", float(sum) / grades.len)
}
```
</details>
---
## 13. Cheat sheet
## 18. Cheat sheet
```c
// a comment
@ -624,11 +876,21 @@ fn main() { // the program starts here
fn add(int a, int b) -> int { // your own command
return a + b
}
struct Pet { // your own kind of value
str name
int age = 1
}
// lists: nums := [1, 2, 3] nums.push(4) nums[0] nums.len for n in nums { }
// text: "a" + "b" str(42) int("42") s.len s.upper() s.contains("x")
// ask: name := input("Name? ")
// files: write_file("f.txt", text) read_file("f.txt")
```
---
## 14. Words you will hear
## 19. Words you will hear
| Word | Meaning |
|---|---|
@ -641,6 +903,10 @@ fn add(int a, int b) -> int { // your own command
| **function** | A named group of steps you can use again |
| **loop** | Steps that repeat |
| **condition** | A yes/no question, like `x > 5` |
| **list / array** | Many values kept in order, like `[1, 2, 3]` |
| **index** | The position of a value in a list, starting at 0 |
| **struct** | Your own kind of value, made of named parts |
| **input** | What the person types into the program |
| **error** | A message saying something is wrong, and where |
| **bug** | A mistake in a program |

File diff suppressed because it is too large Load diff

View file

@ -1,9 +1,9 @@
# 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, using
C libraries, and fixing common problems. It does not cover how the compiler works inside.
For that, see the [guide for engineers](for-engineers.md).
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)
@ -20,12 +20,19 @@ For that, see the [guide for engineers](for-engineers.md).
7. [Operators](#7-operators)
8. [Control flow](#8-control-flow)
9. [Functions](#9-functions)
10. [Pointers](#10-pointers)
11. [Using C libraries](#11-using-c-libraries)
12. [Errors](#12-errors)
13. [Troubleshooting](#13-troubleshooting)
14. [Project layout and tests](#14-project-layout-and-tests)
15. [Current limits](#15-current-limits)
10. [Arrays](#10-arrays)
11. [Text (str)](#11-text-str)
12. [Structs](#12-structs)
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. [Debugging](#19-debugging)
20. [Troubleshooting](#20-troubleshooting)
21. [Project layout and tests](#21-project-layout-and-tests)
22. [Current limits](#22-current-limits)
---
@ -37,9 +44,13 @@ file into a real program (a native executable, like the ones made from C), using
What that gives you:
- **Speed close to C.** There is no interpreter, virtual machine or garbage collector.
- **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.
- **Direct access to C libraries.** `import "stdio.h"` and call anything in it.
- **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.
@ -56,10 +67,10 @@ Other LLVM versions will likely fail to compile, because LLVM's C++ API changes
```sh
git clone https://github.com/Nexoniarz/Finch.git
cd Finch
nix-shell # downloads LLVM, libclang, cmake, ninja, pkg-config
nix-shell # downloads LLVM, libclang, clang, cmake, ninja, pkg-config
cmake -S . -B build -G Ninja
ninja -C build
./build/finch version # finch 1.0.0 (LLVM 21.x)
./build/finch version # finch 2.0.0 (LLVM 21.x)
```
### Option B: your distribution's packages
@ -73,7 +84,7 @@ libclang 21 development files, and optionally `pkg-config`.
| Fedora | `gcc-c++ cmake ninja-build pkgconf llvm-devel clang-devel` |
| Arch | `base-devel cmake ninja pkgconf llvm clang` |
If CMake cannot find LLVM, point it there:
These have not been tested; if CMake cannot find LLVM, point it there:
```sh
cmake -S . -B build -G Ninja -DLLVM_DIR=/usr/lib/llvm-21/lib/cmake/llvm
@ -87,21 +98,22 @@ sudo cp build/finch /usr/local/bin/
finch version
```
Finch needs a working C compiler (`cc`) at run time. It uses it as the **linker** that
turns compiled code into a program. Set the `CC` environment variable to use a different one.
At run time Finch needs a C compiler (`cc`). It is used to link programs, and once to build
Finch's small runtime library, which is kept in `~/.cache/finch`. Set `CC` to use a different one.
---
## 3. The finch command
```
finch run <file.fn> compile and run right away
finch run <file.fn> [args...] compile and run right away
finch build <file.fn> [-o name] compile into a program (default name: the file's name)
finch ir <file.fn> print the LLVM IR (for the curious)
finch version show the version
options:
-l <lib> link a C library, same as link "lib" in the file
-g add debug info (for gdb / lldb)
-O0 skip optimizations
```
@ -109,13 +121,13 @@ Examples:
```sh
finch run hello.fn # prints the output
finch run tool.fn input.txt -v # arguments after the file go to the program
finch build game.fn -o mygame # creates ./mygame
./mygame
finch run window.fn -l glfw # with a C library
finch build game.fn -g -O0 # a build for the debugger
```
`finch run` passes on your program's exit code. If the program crashes, it tells you
how (for example `the program crashed: Segmentation fault`).
`finch run` passes on your program's exit code. If the program crashes, it says how
(for example `the program crashed: Segmentation fault`).
---
@ -126,8 +138,14 @@ how (for example `the program crashed: Segmentation fault`).
/* or span
several lines */
import "math.h" // C headers (optional), always at the top
link "m" // C libraries to link (optional)
import "math.h" // C headers (optional), at the top
import shapes // Finch modules (optional): shapes.fn 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")
@ -136,13 +154,14 @@ fn main() { // the program starts here
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 `( )`.
- **Curly braces are always required** after `if`, `else`, `for`, `while`, and the opening
`{` goes on the same line.
- **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 can be in any order in the file.
- `main` is either `fn main()` or `fn main() -> int` (returns the exit code).
- 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).
---
@ -151,7 +170,8 @@ Rules worth knowing:
```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, null for pointers)
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 -= *= /= %=
@ -160,10 +180,10 @@ 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`, …) and
keywords are reserved.
- 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`
**Keywords:** `fn return if else while for in break continue true false null import link struct defer`
---
@ -177,7 +197,9 @@ x += 1 // also -= *= /= %=
| `float` | decimal number, 64-bit (same as `f64`) | `3.14` |
| `bool` | `true` or `false` | |
| `char` | one byte-sized character | `'A'`, `'\n'` |
| `str` | text (a C string) | `"hello\tworld"` |
| `str` | text | `"hello\tworld"` |
| `[]T` | a list (array) of `T` | `[1, 2, 3]` |
| your structs | named groups of values | `Point(1, 2)` |
String and char escapes: `\n` new line, `\t` tab, `\r`, `\0`, `\\`, `\"`, `\'`.
@ -192,11 +214,6 @@ String and char escapes: `\n` new line, `\t` tab, `\r`, `\0`, `\\`, `\"`, `\'`.
`f32` is a 32-bit decimal number (about 7 significant digits), `f64` a 64-bit one (about 15–16).
### Pointers
`ptr[T]` points to a value of type `T`. Plain `ptr` points to "something" with unknown
type, like `void*` in C. See [Pointers](#10-pointers).
### Conversions
Finch converts automatically **only when nothing can be lost**:
@ -208,32 +225,30 @@ Finch converts automatically **only when nothing can be lost**:
| any whole number | `float` / `f32` | ✅ |
| `f32` | `f64` | ✅ |
| `null` | any pointer | ✅ |
| any pointer, `str` | `ptr` | ✅ |
| `ptr` | any `ptr[T]` | ✅ |
| 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 number 300 doesn't fit in u8`).
The same applies to constants imported from C headers.
`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)
int(-3.99) // -3
float(7) / 2 // 3.5
u8(300) // 44 (keeps the lowest 8 bits)
i8(200) // -56
int('A') // 65
char(66) // 'B'
bool(0) // false (any non-zero number is true)
ptr(text) // a str as an untyped pointer
str(p) // a pointer as a str
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
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`.
Convert first if you need more room: `int(x) + 10`.
---
@ -244,7 +259,7 @@ Convert first if you need more room: `int(x) + 10`.
| Priority | Operators | Meaning |
|---|---|---|
| 5 | `*` `/` `%` `<<` `>>` `&` | multiply, divide, remainder, shift left/right, bitwise AND |
| 4 | `+` `-` `\|` `^` | add, subtract, bitwise OR, bitwise XOR |
| 4 | `+` `-` `\|` `^` | add (and join text), subtract, bitwise OR, bitwise XOR |
| 3 | `==` `!=` `<` `<=` `>` `>=` | comparisons |
| 2 | `&&` | logical AND |
| 1 | `\|\|` | logical OR |
@ -252,18 +267,16 @@ Convert first if you need more room: `int(x) + 10`.
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`. In C it silently means something else.
> `flags & 4 != 0` means `(flags & 4) != 0`.
Notes:
- `/` on whole numbers rounds toward zero: `7 / 2` is `3`, `-7 / 2` is `-3`. `%` takes the sign of the left side.
- `/` 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.
If the zero is written in the code, it is a compile error instead.
- `&&` and `||` only evaluate the right side if needed.
- Text (`str`) can be compared with `==` and `!=` (it compares the contents). `<` and `>` do not work on text.
- Pointers can be compared with `==` and `!=`, including against `null`.
- Bit operators need whole numbers. For `bool` use `&&` and `||`.
- `>>` on signed numbers keeps the sign (`-16 >> 2` is `-4`). On unsigned numbers it fills with zeros.
- 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.
---
@ -286,14 +299,32 @@ 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)
}
```
- 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 whole numbers evaluated **once** before the loop starts.
`i` is an `int` and cannot be changed inside the loop.
- 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
@ -309,32 +340,129 @@ fn no_result(str message) { // no "->": returns nothing
}
```
- Parameters are copies. Changing a parameter does not change the caller's variable.
Use a pointer for that (see below).
- 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.
- Built-in names you cannot reuse: `print`, `addr`, and all type names.
### Built-in functions
| Function | What it does |
|---|---|
| `print(a, b, ...)` | Prints all values separated by spaces, then a new line. Works with every type. |
| `addr(x)` | Gives a pointer to variable `x`. |
| `int(x)`, `u8(x)`, `float(x)`, … | Converts types (see [Conversions](#conversions)). |
---
## 10. Pointers
## 10. Arrays
A pointer holds the **address** of a value: where it lives in memory. Finch uses words
instead of C's `*` and `&`:
```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`.
---
## 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.
---
## 12. Structs
```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).
- Structs have no methods. Write functions that take the struct: `fn heal(Player p) -> Player`.
---
## 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 |
| `&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 |
@ -343,38 +471,74 @@ fn double(ptr[int] p) {
p.value = p.value * 2
}
fn main() {
x := 21
double(addr(x))
print(x) // 42
ptr[int] nothing = null
if nothing == null {
print("empty")
}
}
x := 21
double(addr(x)) // x is 42
```
Pointers can point to pointers: `ptr[ptr[int]]`, then `pp.value.value`.
If a struct has its own field called `value`, then `p.value` on a pointer to it means that field.
Using `.value` on a `null` pointer **stops the program** with
`runtime error: used .value on a null pointer` instead of crashing randomly.
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 function that owns the variable
has returned. The variable no longer exists then. Finch does not check this yet.
⚠️ A pointer to a variable must not be used after the variable's block has ended. Finch does not check this.
---
## 11. Using C libraries
## 15. Built-in functions
Almost every library on Linux has a C interface: graphics, sound, networking, databases.
Finch can use them directly.
| 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. |
| `write_file(path, text)` | Writes (replaces) the file; gives back `true` if it worked. |
| `file_exists(path)` | `true` / `false` |
| `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)). |
---
## 16. Modules
Split a program into files. `import shapes` loads `shapes.fn` from the same folder (or from a
folder listed in the `FINCH_PATH` environment variable, separated by `:`).
```c
// shapes.fn
struct Box {
float w
float h
}
fn area(Box b) -> float {
return b.w * b.h
}
```
```c
// main.fn
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.
---
## 17. Using C libraries
### Step 1: import the header
```c
import "stdio.h"
import "math.h"
import "GLFW/glfw3.h" // a path inside the system include folders
import "mylib.h" // a header next to your .fn file
```
@ -382,33 +546,23 @@ import "mylib.h" // a header next to your .fn 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`,
- **`enum` values**,
- **simple `#define` constants** that are a single number, like `M_PI`, `EOF`, `GL_COLOR_BUFFER_BIT`,
- **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
A header only *describes* functions. Their actual code is in a library file (`libNAME.so`).
Tell Finch which library to use, at the top of the file:
```c
link "glfw" // uses libglfw.so
link "GL" // uses libGL.so
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 .fn file)
link "prebuilt.a" // or an object file / static library
```
Write just the name: for `libglfw.so` that is `glfw`. If `pkg-config` knows the name, Finch uses
its settings (paths and extra libraries). Otherwise it passes `-lNAME` to the linker.
The C standard library and the math library (`libm`) are always linked.
On the command line, `-l NAME` does the same as `link "NAME"`.
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
the header only says the functions exist; their code lives in a library.
add this at the top of your file (with the library's real name):
...
link "glfw"
```
@ -422,52 +576,46 @@ error: glfwInit, glfwCreateWindow, glfwMakeContextCurrent and 5 more come from "
| `float`, `double` | `f32`, `f64` |
| `_Bool` / `bool` | `bool` |
| `enum` | the matching signed integer |
| `char *`, `const char *` | `str` |
| `int *`, `double *`, … | `ptr[i32]`, `ptr[f64]`, … |
| `void *`, `struct X *`, `FILE *`, function pointers | `ptr` |
| `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` |
So C's `int` is Finch's `i32`. Literals adapt automatically (`abs(-5)` works),
but a Finch `int` variable must be converted: `abs(i32(x))`.
### Example
### Example: structs and callbacks
```c
import "stdio.h"
import "math.h"
import "stdlib.h"
import "raylib.h"
link "raylib"
fn main() {
printf("%d + %d = %d\n", 2, 3, 5)
print(sqrt(2), M_PI, RAND_MAX)
ptr[i32] n = malloc(4) // ask C for 4 bytes of memory
n.value = 7
print(n.value)
free(n) // and give them back
fprintf(stderr, "this goes to the error output\n")
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()
}
```
A complete graphics example is in `examples/window.fn`: a GLFW + OpenGL window that changes color.
`examples/llvm.fn` shows that Finch can even drive LLVM itself through its C API.
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).
### What does not work yet
- C functions that take or return a **`struct` by value**, such as `div()`. Finch says so
clearly if you try. Functions taking a *pointer* to a struct are fine.
- **Function-like macros** (`#define MAX(a,b) ...`) and macros that are not a single number.
- C **unions** by value, and structs with **bit fields** by value (pointers to them work).
- **Function-like macros** and macros that are not a single number.
- `long double`, 128-bit integers.
- Reading the **fields** of a C struct.
---
## 12. Errors
## 18. Errors
### Compile errors
Finch stops at the first problem and shows where it is:
```
game.fn:12:9: error: 'score' must be int, but this is str
12 | score = "high"
@ -478,64 +626,87 @@ Format: `file:line:column: error: explanation`, then the line with a `^` under t
### Runtime errors
Some problems can only be seen while the program runs. Instead of undefined behavior
(as in C), Finch stops with a clear message on the error output and exit code 1:
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: used .value on a null pointer` | reading or writing `p.value` while `p` is `null` |
| `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 |
---
## 13. Troubleshooting
## 19. Debugging
```sh
finch build game.fn -g -O0 -o game
gdb ./game
(gdb) break game.fn: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.
---
## 20. Troubleshooting
| Problem | Solution |
|---|---|
| `can't find the C header 'x.h'` | The library's development files are missing. Install them (on Debian: `libx-dev`; on Nix: add to `shell.nix`). For your own header, put it next to the `.fn` file. |
| `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` | The library is not installed, or the name is wrong. 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 struct by value, or another unsupported type. Look for a variant that takes pointers. |
| `no C compiler found to link with` | Install gcc or clang, or set `CC`. |
| The program runs, but a C library is not found at start | It was linked from a path the system does not search. Run it inside the same `nix-shell`, or install the library system-wide. |
| CMake: `libclang not found` | Install the libclang dev package, or run `cmake` inside `nix-shell`. |
| Build fails with LLVM API errors | You have a different LLVM version than 21. |
| `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.fn` 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. |
| `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. |
---
## 14. Project layout and tests
## 21. Project layout and tests
```
Finch/
├── src/ the compiler (C++)
├── examples/ example programs (hello, tour, types, c_import, window, llvm)
├── runtime/ finch_rt.c: the small runtime (text, arrays, input, files)
├── boot/ the Finch compiler written in Finch (see the engineers' guide)
├── examples/ hello, tour, types, structs, todo, guess, c_import, window, raylib, llvm
├── tests/
│ ├── run/ programs + the exact output they must print (.out)
│ ├── run/ programs + the exact output they must print (.out), input (.in)
│ ├── fail/ programs that must fail, with the expected error in line 1
│ └── run.sh the test runner
│ ├── 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)
├── shell.nix the Nix development environment
└── CMakeLists.txt
```
Run the tests after building:
```sh
tests/run.sh # → 34 passed, 0 failed
tests/run.sh # → 58 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)
```
---
## 15. Current limits
## 22. Current limits
Finch 1.0 is a solid core, not a finished language. Not there yet:
Finch 2.0 is a complete small language, but not a finished one. Not there yet:
- `struct`, arrays, reading keyboard input (`input()`), string building (`"a" + "b"`).
For now, use C functions for these (`scanf`, `snprintf`, `malloc`…).
- Automatic memory management (planned: memory freed at the end of the block, plus `defer`).
- Finch modules (one program = one `.fn` file for now).
- Methods on structs, generics, maps/dictionaries, `match`. (Use arrays of structs and functions.)
- 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.
See the [roadmap in the README](../../README.md#roadmap).