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>
18 KiB
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.
🇵🇱 Polska wersja: dla-technikow.md
Contents
- What Finch is
- Installing
- The finch command
- How a Finch file looks
- Variables
- Types
- Operators
- Control flow
- Functions
- Pointers
- Using C libraries
- Errors
- Troubleshooting
- Project layout and tests
- 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)
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:
cmake -S . -B build -G Ninja -DLLVM_DIR=/usr/lib/llvm-21/lib/cmake/llvm
ninja -C build
Putting finch on your PATH (optional)
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:
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
// 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 {, notif (x > 5) {. - Functions can be in any order in the file.
mainis eitherfn main()orfn main() -> int(returns the exit code).
5. Variables
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.
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:
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 != 0means(flags & 4) != 0. In C it silently means something else.
Notes:
/on whole numbers rounds toward zero:7 / 2is3,-7 / 2is-3.%takes the sign of the left side.- Dividing by zero stops the program with
runtime error: division by zeroand 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 againstnull. - Bit operators need whole numbers. For
booluse&&and||. >>on signed numbers keeps the sign (-16 >> 2is-4). On unsigned numbers it fills with zeros.
8. Control flow
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; writeif x != 0 {. - In
for i in a..b, both ends are whole numbers evaluated once before the loop starts.iis anintand cannot be changed inside the loop. while true { ... }loops until abreakorreturn.- Code after
return,breakorcontinuein the same block is an error, because it can never run.
9. Functions
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
-> typemust 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). |
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 |
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
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, enumvalues,- simple
#defineconstants that are a single number, likeM_PI,EOF,GL_COLOR_BUFFER_BIT, - global variables, like
stdoutandstderr.
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:
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
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
structby value, such asdiv(). 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:
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
.fnfile for now). - Platforms other than Linux x86-64.
See the roadmap in the README.