Skip to content

Fels

As direct as C. Memory in zones the compiler keeps from escaping. Errors you cannot ignore. A small systems language for people who like to know what their code does, with native code through LLVM and code that runs while it compiles.

A zone is an arena with a name and a block. Whatever you allocate in it lives until the block ends, and then all of it goes away in one step. You never call free, and nothing runs in the background to clean up after you.

/// How many different bytes are in `text`?
count_distinct :: fn(text: []const u8) -> usize {
zone scratch: Arena {
seen := scratch.new_slice(bool, 256);
for i in 0..256 { seen[i] = false; }
n: usize = 0;
for b in text {
if !seen[b] { seen[b] = true; n += 1; }
}
return n;
} // `seen` is gone here, all at once
}

The compiler keeps track of what belongs to a zone, so nothing slips out of its block:

error: zone-owned value from `scratch` cannot be returned
--> escape.fels:4:9
│
4 │ return buf;
│ ^^^^^^^^^^^

A function that can fail says so in its type. fail returns an error, ? passes it on and catch handles it. There are no exceptions, and nothing unwinds the stack.

ParseError :: errors {
empty,
not_a_digit: u8,
}
parse :: fn(s: []const u8) -> i32 ! ParseError {
if s.len == 0 { fail .empty; }
n := 0;
for c in s {
if c < '0' || c > '9' { fail .not_a_digit(c); }
n = n * 10 + (c - '0') as i32;
}
return n;
}
main :: fn() -> i32 {
a := parse("42") catch err { yield 0; };
return a;
}

#run calls a function while your program is being compiled, and the result goes into the binary as a constant. Macros and code quotes write code that the compiler checks like code you typed yourself.

fib :: fn(n: u64) -> u64 {
if n < 2 { return n; }
return fib(n - 1) + fib(n - 2);
}
FIB_30 :: #run fib(30); // worked out while compiling

Build and run

fels build, fels run hello.fels, and a build file that is a Fels program too.

Tests

fels test runs every #test function. --filter picks some of them.

Packages

fels add fetches a package from a Git repository, and a lock file pins its version.

Editors

fels lsp gives errors, completion, hover and go to definition. There is a Zed extension and a tree-sitter grammar.

C without glue

fels bindgen turns a C header into Fels. #extern calls a DLL, #export hands your functions to C.

Its own linker

felsld is written in Fels, writes Windows executables and DLLs, and links itself.

The imgui package brings Dear ImGui and ImPlot. This live chart is about 40 lines of Fels.

A window with two moving waves, drawn by a Fels program

Fels is young, and the language still changes. It runs on 64-bit Windows and Linux, builds programs for both, and can make UEFI applications and kernels for x86-64 and 64-bit ARM. The known issues list what is still rough, and the changelog what changed in each release.