Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

308 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Cleaf

Work in progress. The language and compiler are not feature-complete. A number of constructs described here may not yet produce correct output, and breaking changes to the syntax or pipeline are expected.

Cleaf is a small, statically typed compiled language targeting x86-64 Linux. Source files use the .clf extension. The compiler is written in C and produces native executables via NASM and the system linker.

Motivation

Cleaf is a personal project to explore compiler construction from scratch, covering the full pipeline from lexing to native code generation without relying on LLVM or any other backend framework.

The language itself is intentionally simple and C-like, with a focus on having a clean, readable syntax while still being compiled to native machine code.

Dependencies

To build the compiler itself:

  • gcc

To compile .clf source files with the resulting binary:

  • nasm
  • ld (from binutils)

Build and Run

make              # build ./build/cleaf
./build/cleaf <source.clf>           # compile to build/a.out
./build/cleaf <source.clf> -o <out>  # compile with a custom output name (build/<out>)
./build/cleaf <source.clf> -v        # show each compilation phase and its result
./build/cleaf <source.clf> -V        # same as -v, and dump AST, HIR, and generated assembly
./build/cleaf build                  # compile a multi-file module project (see below)

Examples

Variables

Cleaf supports explicit typing and type inference via var:

fn main(): int {
    int a = 10;
    var b = a + 5;
    return b;
}

Control flow

fn main(): int {
    var result = 0;

    for (var i = 0; i < 10; ++i) {
        if (i == 5) {
            result = result + 1;
        }
    }

    while (result > 0) {
        result = result - 1;
    }

    return result;
}

Functions

fn add(int a, int b): int {
    return a + b;
}

fn main(): int {
    var x = add(3, 4);
    return x;
}

Structs

struct v2 {
    int x;
    int y;
}

fn main(): int {
    v2 point = { .x = 3, .y = 4 };
    var val = point.x;
    return val;
}

Modules and imports

Multi-file projects use a Go/Rust-inspired module system, compiled with cleaf build:

// math.clf
module math

internal fn helper(): int { return 41; }
fn add(): int { return helper(); }
// main.clf
module main

import math::add

fn main(): int {
    return add();
}

cleaf build scans every .clf file in the current directory, resolves the module dependency graph, and links everything into a single executable under build/. internal functions are only visible within their own module.

Current state

  • Lexer
  • Parser
    • Functions with typed parameters and return types
    • Typed variable declarations and var inference
    • Struct definitions
    • Composite literal initialization ({ .field = value })
    • Member access
    • Control flow: if/else, while, for
    • Arithmetic and comparison expressions
    • Unary operators (++, --, -, !)
    • Function calls
    • Arrays
    • Additional primitive types (floats, strings, booleans)
  • Semantic analysis
    • Type checking
    • Symbol resolution (undefined variables and functions)
    • Function signature validation (argument count and types)
    • Variable redefinition detection
    • Return type checking
    • Reserved keyword enforcement
    • Struct type resolution
    • Arrays
    • Additional primitive types
  • HIR lowering
    • Arithmetic and comparisons
    • Control flow
    • Function calls
    • Struct field access
  • Code generation (x86-64 Linux, NASM syntax)
    • Arithmetic
    • Control flow
    • Function calls and stack management
    • Struct field access
  • Memory safety (garbage collection or ownership model, not yet decided)
  • Standard library
  • Multiple source files
    • module/import declarations (nested module paths via ::)
    • internal visibility restriction
    • cleaf build — project-wide scan, dependency graph, topological compilation
    • Cross-module name mangling + multi-object codegen/link
  • Arrays
  • Additional primitive types

Test coverage

The test suite contains 249 test cases totalling 563 assertions spread across the compiler passes and the module build pipeline, plus a set of end-to-end integration tests and around 20 additional fixtures used for memory safety validation with Valgrind.

Suite Test cases Assertions
Parser (AST) 64 324
Semantic 106 160
HIR 35 35
HIR name mangling 3 3
Codegen 34 34
Build (imports) 7 7
Total 249 563

The semantic pass has the most coverage, reflecting the variety of error cases it handles. The parser and HIR passes cover the main language constructs. The codegen tests compare the full generated assembly output against expected fixtures for each construct.

On top of the suites above, test/integration_case/ holds end-to-end multi-module projects exercised via make integration-test: a correct 2-module build whose executable is run and checked for the expected exit code, plus three failure scenarios (internal violation, import cycle, missing main module).

Note that these numbers give a rough indication of coverage — there is no formal coverage measurement tool in place yet.

Running tests

make test              # run all test suites, including integration tests
make ast-test           # parser tests only
make semantic-test      # semantic analysis tests only
make hir-test           # HIR lowering tests only
make hir-module-test    # HIR name mangling tests only
make codegen-test       # code generation tests only
make build-test         # multi-module import/semantic tests only
make integration-test   # end-to-end `cleaf build` tests (requires nasm/ld)
make asan-test          # all tests with AddressSanitizer and UBSan
make valgrind-test      # memory checks on single-file and multi-module fixtures

About

original compiler for the cleaf programming language (wip)

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages