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>
This commit is contained in:
Nexoniarz 2026-10-08 17:10:45 +02:00
commit 0591cd20f3
73 changed files with 8066 additions and 0 deletions

View file

@ -0,0 +1,3 @@
// expect: write just the library's name
link "libm.so"
fn main() {}

View file

@ -0,0 +1,4 @@
// expect: for true/false use '&&'
fn main() {
print(true & false)
}

View file

@ -0,0 +1,5 @@
// expect: a condition must be bool
fn main() {
if 1 {
}
}

View file

@ -0,0 +1,4 @@
// expect: division by zero
fn main() {
print(1 / 0)
}

View file

@ -0,0 +1,5 @@
// expect: 4: runtime error: division by zero
fn main() {
zero := 0
print(10 / zero)
}

View file

@ -0,0 +1,5 @@
// expect: cuts off the fraction
fn main() {
float f = 2.5
int n = f
}

View file

@ -0,0 +1,4 @@
// expect: import "math.h"
fn main() {
print(sqrt(2))
}

5
tests/fail/lib_hint.fn Normal file
View file

@ -0,0 +1,5 @@
// expect: link "lib_hint"
import "lib_hint.h"
fn main() {
print(finch_test_missing(1))
}

1
tests/fail/lib_hint.h Normal file
View file

@ -0,0 +1 @@
int finch_test_missing(int x);

View file

@ -0,0 +1,4 @@
// expect: doesn't fit in u8
fn main() {
u8 b = 300
}

View file

@ -0,0 +1,6 @@
// expect: can't be changed
fn main() {
for i in 0..3 {
i = 5
}
}

View file

@ -0,0 +1,3 @@
// expect: can't find the C header
import "nope_not_here.h"
fn main() {}

View file

@ -0,0 +1,3 @@
// expect: the library 'definitely_not_a_lib' wasn't found
link "definitely_not_a_lib"
fn main() {}

View file

@ -0,0 +1,5 @@
// expect: can reach its end without returning int
fn f() -> int {
print(1)
}
fn main() {}

View file

@ -0,0 +1,6 @@
// expect: can't use '+' on i32 and u32
fn main() {
i32 a = 1
u32 b = 2
print(a + b)
}

2
tests/fail/no_main.fn Normal file
View file

@ -0,0 +1,2 @@
// expect: there is no main function
fn helper() {}

View file

@ -0,0 +1,4 @@
// expect: unexpected character 'ż' (names use only
fn main() {
żółw := 1
}

5
tests/fail/null_value.fn Normal file
View file

@ -0,0 +1,5 @@
// expect: 4: runtime error: used .value on a null pointer
fn main() {
ptr[int] p = null
print(p.value)
}

View file

@ -0,0 +1,4 @@
// expect: functions start with 'fn'
int add(int a) {
return a
}

5
tests/fail/redeclare.fn Normal file
View file

@ -0,0 +1,5 @@
// expect: already exists
fn main() {
x := 1
x := 2
}

View file

@ -0,0 +1,4 @@
// expect: put each statement on its own line
fn main() {
x := 1 print(x)
}

View file

@ -0,0 +1,4 @@
// expect: 'x' must be int, but this is str
fn main() {
int x = "hi"
}

View file

@ -0,0 +1,3 @@
// expect: never closed
fn main() {
print(1)

View file

@ -0,0 +1,4 @@
// expect: there is no variable named 'y'
fn main() {
print(y)
}

View file

@ -0,0 +1,5 @@
// expect: this code can never run
fn main() {
return
print(1)
}

View file

@ -0,0 +1,4 @@
// expect: null needs a type
fn main() {
p := null
}

View file

@ -0,0 +1,6 @@
// expect: this ptr has no type
import "stdlib.h"
fn main() {
p := malloc(8)
print(p.value)
}

36
tests/run.sh Executable file
View file

@ -0,0 +1,36 @@
#!/usr/bin/env bash
# Finch test suite.
# tests/run/*.fn must compile and print exactly what is in the matching .out file
# tests/fail/*.fn must fail; the first line is "// expect: <text that must appear in the error>"
set -u
cd "$(dirname "$0")"
FINCH=${FINCH:-../build/finch}
pass=0; failed=0
for f in run/*.fn; do
want="${f%.fn}.out"
got=$("$FINCH" run "$f" 2>&1)
if [ "$got" == "$(cat "$want")" ]; then
pass=$((pass + 1))
else
failed=$((failed + 1))
echo "FAIL $f"
diff <(echo "$got") "$want" | head -20
fi
done
for f in fail/*.fn; do
expect=$(head -1 "$f" | sed -n 's|^// expect: ||p')
got=$("$FINCH" run "$f" 2>&1)
status=$?
if [ $status -ne 0 ] && [[ "$got" == *"$expect"* ]]; then
pass=$((pass + 1))
else
failed=$((failed + 1))
echo "FAIL $f (wanted: $expect)"
echo "$got" | head -5
fi
done
echo "$pass passed, $failed failed"
[ $failed -eq 0 ]

18
tests/run/basics.fn Normal file
View file

@ -0,0 +1,18 @@
fn main() {
int a = 7
b := 2
print(a + b, a - b, a * b, a / b, a % b)
print(-a / b, -a % b)
x := 1.5
print(x * 2, x + 1, 7 / 2.0)
print(true && false, true || false, !true)
print('A', "text", 1 < 2, 2 <= 2, 3 > 4, 4 >= 4, 1 == 1, 1 != 1)
a += 3
a -= 1
a *= 2
a /= 3
a %= 4
print(a)
print()
print("tab\there", "quote\"inside")
}

8
tests/run/basics.out Normal file
View file

@ -0,0 +1,8 @@
9 5 14 3 1
-3 -1
3 2.5 3.5
false true false
A text true true false true true false
2
tab here quote"inside

9
tests/run/bits.fn Normal file
View file

@ -0,0 +1,9 @@
fn main() {
print(6 & 3, 6 | 3, 6 ^ 3, 1 << 4, 256 >> 2, ~0)
print(5 & 1 == 1)
u8 flags = 0
flags = flags | 4
flags = flags | 1
print(flags, flags & 4 != 0, flags & 2 != 0)
print(-16 >> 2, u8(240) >> 4)
}

4
tests/run/bits.out Normal file
View file

@ -0,0 +1,4 @@
2 7 5 16 64 -1
true
5 true false
-4 15

16
tests/run/cimport.fn Normal file
View file

@ -0,0 +1,16 @@
import "stdio.h"
import "math.h"
import "stdlib.h"
import "string.h"
fn main() {
printf("%d-%s\n", 42, "printf")
print(sqrt(16), pow(2, 10), floor(2.7))
print(strlen("finch"), abs(-5), atoi("123"))
print(EOF, RAND_MAX > 0, M_PI > 3.14)
ptr[i32] n = malloc(4)
n.value = 7
print(n.value)
free(n)
fflush(stdout)
}

5
tests/run/cimport.out Normal file
View file

@ -0,0 +1,5 @@
42-printf
4 1024 2
5 5 123
-1 true true
7

42
tests/run/control.fn Normal file
View file

@ -0,0 +1,42 @@
fn classify(int n) -> str {
if n < 0 {
return "negative"
} else if n == 0 {
return "zero"
} else {
return "positive"
}
}
fn main() {
print(classify(-5), classify(0), classify(5))
total := 0
for i in 0..100 {
if i % 2 == 0 {
continue
}
if i > 10 {
break
}
total += i
}
print("odd sum below 10:", total)
n := 0
while true {
n += 1
if n == 5 {
break
}
}
print("n:", n)
for i in 3..3 {
print("never")
}
{
inner := "blocks have their own scope"
print(inner)
}
}

4
tests/run/control.out Normal file
View file

@ -0,0 +1,4 @@
negative zero positive
odd sum below 10: 25
n: 5
blocks have their own scope

4
tests/run/exitcode.fn Normal file
View file

@ -0,0 +1,4 @@
fn main() -> int {
print("exiting")
return 0
}

1
tests/run/exitcode.out Normal file
View file

@ -0,0 +1 @@
exiting

28
tests/run/functions.fn Normal file
View file

@ -0,0 +1,28 @@
fn fib(int n) -> int {
if n < 2 {
return n
}
return fib(n - 1) + fib(n - 2)
}
fn later() -> int {
return defined_below()
}
fn defined_below() -> int {
return 42
}
fn greet(str who) {
if who == "" {
return
}
print("hi", who)
}
fn main() {
print(fib(20))
print(later())
greet("")
greet("finch")
}

3
tests/run/functions.out Normal file
View file

@ -0,0 +1,3 @@
6765
42
hi finch

25
tests/run/pointers.fn Normal file
View file

@ -0,0 +1,25 @@
fn double(ptr[int] p) {
p.value = p.value * 2
}
fn swap(ptr[int] a, ptr[int] b) {
t := a.value
a.value = b.value
b.value = t
}
fn main() {
x := 21
double(addr(x))
print(x)
a := 1
b := 2
swap(addr(a), addr(b))
print(a, b)
p := addr(a)
pp := addr(p)
pp.value.value = 99
print(a)
ptr[int] none = null
print(none == null, p != null, none)
}

4
tests/run/pointers.out Normal file
View file

@ -0,0 +1,4 @@
42
2 1
99
true true (nil)

17
tests/run/types.fn Normal file
View file

@ -0,0 +1,17 @@
fn main() {
u8 small = 255
small += 1
print(small)
i8 tiny = -128
print(tiny, i8(200), u8(-1), u16(70000))
i32 p = 1000
i64 wide = p
print(wide, p + 1)
u32 big = 4000000000
print(big, big / 3)
f32 half = 0.5
print(half * 2, float(half))
print(int(3.99), int(-3.99), float(7) / 2, int('A'), char(66), bool(0), bool(5))
print(0xFF, 1_000_000)
print(u8(250) + 10)
}

8
tests/run/types.out Normal file
View file

@ -0,0 +1,8 @@
0
-128 -56 255 4464
1000 1001
4000000000 1333333333
1 0.5
3 -3 3.5 65 B false true
255 1000000
4