Finch/docs/en/for-technicians.md
Nexoniarz 0591cd20f3 Finch 1.0.0
A small, fast programming language on LLVM with C-like syntax.

- Lexer, recursive-descent parser, single-pass type checker + LLVM IR codegen, O2 pipeline
- Types: int/float/bool/char/str, sized i8..u64/f32/f64, pointers ptr[T] with addr()/.value/null
- Functions (fn ... -> T), if/else, while, for..in, break/continue, Go-style operator precedence
- Implicit conversions only when lossless; literals adapt to their target type
- Runtime checks for division by zero, MIN/-1 and null .value
- C interop: import "header.h" via libclang, link "lib" with pkg-config, link-error advice
- Human-readable errors with source excerpts
- Test suite (34 tests), examples, docs for beginners/technicians/engineers in English and Polish

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 18:21:06 +02:00

541 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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).
> 🇵🇱 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. [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)
---
## 1. What Finch is
Finch is a **compiled** programming language. The `finch` tool translates your `.fn`
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.** There is no interpreter, virtual machine or garbage collector.
- **Small, standalone programs** that you can copy and run.
- **Direct access to C libraries.** `import "stdio.h"` and call anything in it.
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
Finch currently runs on **Linux, x86-64**. It has been tested with **LLVM 21**.
Other LLVM versions will likely fail to compile, because LLVM's C++ API changes between releases.
### Option A: Nix (recommended, nothing to install by hand)
```sh
git clone https://github.com/Nexoniarz/Finch.git
cd Finch
nix-shell # downloads LLVM, libclang, cmake, ninja, pkg-config
cmake -S . -B build -G Ninja
ninja -C build
./build/finch version # finch 1.0.0 (LLVM 21.x)
```
### Option B: your distribution's packages
You need: a C/C++ compiler, CMake ≥ 3.20, Ninja (or Make), LLVM 21 development files,
libclang 21 development files, and optionally `pkg-config`.
| Distribution | Packages (names can differ slightly by version) |
|---|---|
| Debian / Ubuntu | `build-essential cmake ninja-build pkg-config llvm-21-dev libclang-21-dev` |
| 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:
```sh
cmake -S . -B build -G Ninja -DLLVM_DIR=/usr/lib/llvm-21/lib/cmake/llvm
ninja -C build
```
### Putting `finch` on your PATH (optional)
```sh
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.
---
## 3. The finch command
```
finch run <file.fn> 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
-O0 skip optimizations
```
Examples:
```sh
finch run hello.fn # prints the output
finch build game.fn -o mygame # creates ./mygame
./mygame
finch run window.fn -l glfw # with a C library
```
`finch run` passes on your program's exit code. If the program crashes, it tells you
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), always at the top
link "m" // C libraries to link (optional)
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 `( )`.
- **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).
---
## 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, null for pointers)
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`, …) and
keywords are reserved.
**Keywords:** `fn return if else while for in break continue true false null import link`
---
## 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 (a C string) | `"hello\tworld"` |
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).
### 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**:
| 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, `str` | `ptr` | ✅ |
| `ptr` | any `ptr[T]` | ✅ |
| 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.
**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
```
**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`.
---
## 7. Operators
### By priority (higher binds tighter)
| Priority | Operators | Meaning |
|---|---|---|
| 5 | `*` `/` `%` `<<` `>>` `&` | multiply, divide, remainder, shift left/right, bitwise AND |
| 4 | `+` `-` `\|` `^` | add, 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`. In C it silently means something else.
Notes:
- `/` on whole numbers rounds toward zero: `7 / 2` is `3`, `-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.
---
## 8. Control flow
```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 }
}
```
- 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.
- `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.
---
## 9. Functions
```c
fn name(type1 param1, type2 param2) -> return_type {
...
return value
}
fn no_result(str message) { // no "->": returns nothing
print(message)
}
```
- Parameters are copies. Changing a parameter does not change the caller's variable.
Use a pointer for that (see below).
- 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
A pointer holds the **address** of a value: where it lives in memory. 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 |
| `*p` | `p.value` | the value p points to |
| `NULL` | `null` | points nowhere |
| `void *` | `ptr` | points to something of unknown type |
```c
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")
}
}
```
Pointers can point to pointers: `ptr[ptr[int]]`, then `pp.value.value`.
Using `.value` on a `null` pointer **stops the program** with
`runtime error: used .value on a null pointer` instead of crashing randomly.
⚠️ 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.
---
## 11. Using C libraries
Almost every library on Linux has a C interface: graphics, sound, networking, databases.
Finch can use them directly.
### 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
```
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`,
- **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
```
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"
```
### 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 *`, `const char *` | `str` |
| `int *`, `double *`, … | `ptr[i32]`, `ptr[f64]`, … |
| `void *`, `struct X *`, `FILE *`, function pointers | `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
```c
import "stdio.h"
import "math.h"
import "stdlib.h"
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")
}
```
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.
### 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.
- `long double`, 128-bit integers.
- Reading the **fields** of a C struct.
---
## 12. 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"
| ^
```
Format: `file:line:column: error: explanation`, then the line with a `^` under the spot.
### 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:
| 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` |
---
## 13. 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. |
| `… 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. |
| A program never ends | A loop condition never becomes false. Press Ctrl+C. |
---
## 14. Project layout and tests
```
Finch/
├── src/ the compiler (C++)
├── examples/ example programs (hello, tour, types, c_import, window, llvm)
├── tests/
│ ├── run/ programs + the exact output they must print (.out)
│ ├── fail/ programs that must fail, with the expected error in line 1
│ └── run.sh the test runner
├── 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
```
---
## 15. Current limits
Finch 1.0 is a solid core, not a finished language. 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).
- Platforms other than Linux x86-64.
See the [roadmap in the README](../../README.md#roadmap).