Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
26306ab
feat: add module declaration to parser. add tests
viastolfi Jun 18, 2026
0856068
feat: add import keyword and workflow to ast parser. add tests
viastolfi Jun 22, 2026
c9b2187
feat: add internal function parsing. add tests
viastolfi Jun 22, 2026
cc513ca
feat: add memory management for import declaration
viastolfi Jun 22, 2026
4a6b993
fix: refactor entry function for better build command integration
viastolfi Jun 23, 2026
3a7059b
feat: add dir reading to find source files
viastolfi Jun 23, 2026
cc39a44
fix: track missing files
viastolfi Jun 23, 2026
6f79029
feat: add multiple files compilation via build command (wip)
viastolfi Jun 25, 2026
063e71e
feat: add defining alias when importing functions. add tests
viastolfi Jun 25, 2026
b9efc4b
fix: allow 'nested' module declaration. add tests
viastolfi Jun 25, 2026
08f5b33
feat: add qualified function call, add tests
viastolfi Jun 25, 2026
75a1b2c
feat: add module registry building and main module compiler lookup
viastolfi Jun 25, 2026
00aa875
fix: dissociate build and single file compile mode to make compiler m…
viastolfi Jun 25, 2026
f2a8f36
feat: add pre-commit hooks to automate testing before commiting
viastolfi Jun 25, 2026
d6d2ac3
fix: typo
viastolfi Jun 25, 2026
5122a25
feat: add dependance graph build phase (wip)
viastolfi Jun 26, 2026
a92b2b8
feat: add module graph processing and topo order creation and logging
viastolfi Jun 27, 2026
a930eaf
feat: add export table build for each module
viastolfi Jun 30, 2026
b7b740c
feat: add semantic analyse for function import. add tests
viastolfi Jul 1, 2026
88391a2
feat: add function name mangling to hir. add tests
viastolfi Jul 1, 2026
6e12419
fix: reconcile valgrind tests
viastolfi Jul 1, 2026
c926e86
feat: add codegen for multiple files, linking, output in build folder
viastolfi Jul 1, 2026
b475a2e
feat: add integration test and update docs
viastolfi Jul 1, 2026
160e51e
docs: update docs
viastolfi Jul 1, 2026
304c5fc
fix: remove integration test to global test
viastolfi Jul 1, 2026
4d63dd1
ci: add step for integration test
viastolfi Jul 1, 2026
e47a8fc
fix: reconcile ci
viastolfi Jul 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/bin/bash

echo "try building projects"

make clean
make

if [ $? -ne 0 ]; then
echo "project compilation fail, please fix this"
exit 1
fi

echo "running tests"

make test

if [ $? -ne 0 ]; then
echo "tests fails"
echo "either fix them or commit using 'git commit --no-verify' if failing is intended"
exit 1
fi

echo "running valgring tests"

make valgrind-test

if [ $? -ne 0 ]; then
echo "tests fails"
echo "either fix them or commit using 'git commit --no-verify' if failing is intended"
exit 1
fi

make integration-test

if [ $? -ne 0 ];then
echo "integration tests fails"
echo "either fix them or commit using 'git commit --no-verify' if failing is intended"
exit 1
fi

echo "all tests passed"
exit 0
3 changes: 3 additions & 0 deletions .github/workflows/asan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Install dependencies
run: sudo apt-get update && sudo apt-get install -y nasm binutils

- name: Build & test with ASan/UBSan
run: |
CFLAGS="-fsanitize=address,undefined -g -O1" make asan-test || make test
Expand Down
24 changes: 24 additions & 0 deletions .github/workflows/integration-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: C Integration Tests

on:
push:
branches:
- "**"
pull_request:
branches:
- main

jobs:
test:
name: Run integration tests
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Install dependencies
run: sudo apt-get update && sudo apt-get install -y nasm binutils

- name: integration test
run: |
make integration-test
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
build/
!src/compiler/build
*.swp
*.log
a.out
Expand Down
52 changes: 49 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ CS = \
$(SRC)/backend/x86_64.c \
$(SRC)/backend/codegen.c \
$(SRC)/compiler/definition/compiler_definition.c \
$(SRC)/compiler/setup/compiler_setup.c \
$(SRC)/compiler/build/file_scanner.c \
$(SRC)/compiler/build/registry.c \
$(SRC)/compiler/build/dep_graph.c \
$(SRC)/compiler/build/export_table.c \
$(SRC)/compiler/build/import_resolver.c \

OBJ = \
$(BUILD)/cleaf.o \
Expand All @@ -23,19 +29,24 @@ OBJ = \
$(BUILD)/backend/x86_64.o \
$(BUILD)/backend/codegen.o \
$(BUILD)/compiler/definition/compiler_definition.o \
$(BUILD)/compiler/setup/compiler_setup.o \
$(BUILD)/compiler/build/file_scanner.o \
$(BUILD)/compiler/build/registry.o \
$(BUILD)/compiler/build/dep_graph.o \
$(BUILD)/compiler/build/export_table.o \
$(BUILD)/compiler/build/import_resolver.o \

CC = gcc
CFLAGS = -Wall -Wextra -g -Isrc
VALGRIND = valgrind --error-exitcode=42 --leak-check=full --show-leak-kinds=all

.PRECIOUS: build/cleaf
.PHONY: all clean test ast-test semantic-test asan-test valgrind-test hir-test codegen-test
.PHONY: all clean test ast-test semantic-test asan-test valgrind-test hir-test hir-module-test codegen-test build-test integration-test setup

all: $(BUILD)/cleaf

$(BUILD)/cleaf: $(OBJ)
$(CC) -o $@ $^ -lm
@$(BUILD)/cleaf test.clf

$(BUILD)/%.o: $(SRC)/%.c
@mkdir -p $(BUILD)
Expand All @@ -44,6 +55,8 @@ $(BUILD)/%.o: $(SRC)/%.c
@mkdir -p $(BUILD)/middleend
@mkdir -p $(BUILD)/backend
@mkdir -p $(BUILD)/compiler/definition
@mkdir -p $(BUILD)/compiler/setup
@mkdir -p $(BUILD)/compiler/build
$(CC) $(CFLAGS) -c $< -o $@

AST_TEST_SRC = $(TEST)/ast_test.c
Expand All @@ -55,15 +68,23 @@ SEM_TEST_BIN = $(BUILD)/semantic_test
HIR_TEST_SRC = $(TEST)/hir_test.c
HIR_TEST_BIN = $(BUILD)/hir_test

HIR_MODULE_TEST_SRC = $(TEST)/hir_module_test.c
HIR_MODULE_TEST_BIN = $(BUILD)/hir_module_test

CODEGEN_TEST_SRC = $(TEST)/codegen_test.c
CODEGEN_TEST_BIN = $(BUILD)/codegen_test

test: $(AST_TEST_BIN) $(SEM_TEST_BIN) $(HIR_TEST_BIN) $(CODEGEN_TEST_BIN)
BUILD_TEST_SRC = $(TEST)/build_test.c
BUILD_TEST_BIN = $(BUILD)/build_test

test: $(AST_TEST_BIN) $(SEM_TEST_BIN) $(HIR_TEST_BIN) $(HIR_MODULE_TEST_BIN) $(CODEGEN_TEST_BIN) $(BUILD_TEST_BIN) $(BUILD)/cleaf
@echo "Running tests..."
@$(AST_TEST_BIN)
@$(SEM_TEST_BIN)
@$(HIR_TEST_BIN)
@$(HIR_MODULE_TEST_BIN)
@$(CODEGEN_TEST_BIN)
@$(BUILD_TEST_BIN)

ast-test: $(AST_TEST_BIN)
@echo "Running AST tests..."
Expand All @@ -77,10 +98,22 @@ hir-test: $(HIR_TEST_BIN)
@echo "Running hir tests..."
@$(HIR_TEST_BIN) 2> test.log

hir-module-test: $(HIR_MODULE_TEST_BIN)
@echo "Running hir module (name mangling) tests..."
@$(HIR_MODULE_TEST_BIN) 2> test.log

codegen-test: $(CODEGEN_TEST_BIN)
@echo "Running codegen tests..."
@$(CODEGEN_TEST_BIN) 2> test.log

build-test: $(BUILD_TEST_BIN)
@echo "Running build tests..."
@$(BUILD_TEST_BIN) 2> test.log

integration-test: $(BUILD)/cleaf
@echo "Running integration tests (cleaf build end-to-end)..."
@./test/integration_test.sh $(BUILD)/cleaf

$(AST_TEST_BIN): $(AST_TEST_SRC) $(SRC)/frontend/ast.c $(SRC)/thirdparty/error.c
@mkdir -p $(BUILD)
@$(CC) $(CFLAGS) $^ -o $@ -lm
Expand All @@ -93,10 +126,18 @@ $(HIR_TEST_BIN): $(HIR_TEST_SRC) $(SRC)/frontend/ast.c $(SRC)/thirdparty/error.c
@mkdir -p $(BUILD)
@$(CC) $(CFLAGS) $^ -o $@ -lm

$(HIR_MODULE_TEST_BIN): $(HIR_MODULE_TEST_SRC) $(SRC)/frontend/ast.c $(SRC)/thirdparty/error.c $(SRC)/frontend/semantic.c $(SRC)/middleend/hir.c
@mkdir -p $(BUILD)
@$(CC) $(CFLAGS) $^ -o $@ -lm

$(CODEGEN_TEST_BIN): $(CODEGEN_TEST_SRC) $(SRC)/frontend/ast.c $(SRC)/thirdparty/error.c $(SRC)/frontend/semantic.c $(SRC)/middleend/hir.c $(SRC)/backend/x86_64.c $(SRC)/backend/codegen.c
@mkdir -p $(BUILD)
@$(CC) $(CFLAGS) $^ -o $@ -lm

$(BUILD_TEST_BIN): $(BUILD_TEST_SRC) $(SRC)/frontend/ast.c $(SRC)/thirdparty/error.c $(SRC)/frontend/semantic.c $(SRC)/middleend/hir.c $(SRC)/compiler/definition/compiler_definition.c $(SRC)/compiler/build/registry.c $(SRC)/compiler/build/export_table.c $(SRC)/compiler/build/import_resolver.c
@mkdir -p $(BUILD)
@$(CC) $(CFLAGS) $^ -o $@ -lm

asan-test:
CFLAGS="-fsanitize=address,undefined -g -O1" make test

Expand Down Expand Up @@ -127,8 +168,13 @@ valgrind-test:
$(VALGRIND) ./build/cleaf test/valgrind_case/semantic_control_flow_errors.clf; [ $$? -ne 42 ]
@echo "=== Testing combined errors ==="
$(VALGRIND) ./build/cleaf test/valgrind_case/combined_multiple_errors.clf; [ $$? -ne 42 ]
@echo "=== Testing multi-module build ==="
cd test/integration_case/return_value_chain && rm -rf build a.out && $(VALGRIND) ../../../build/cleaf build; [ $$? -ne 42 ]
@echo "=== All valgrind tests passed ==="

clean:
rm -rf $(BUILD)

setup:
chmod +x .githooks/pre-commit
git config core.hooksPath .githooks
81 changes: 62 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ To compile `.clf` source files with the resulting binary:

```sh
make # build ./build/cleaf
./build/cleaf <source.clf> # compile to a.out
./build/cleaf <source.clf> -o <out> # compile with a custom output name
./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
Expand Down Expand Up @@ -95,6 +96,33 @@ fn main(): int {
}
```

### 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

- [x] Lexer
Expand Down Expand Up @@ -132,38 +160,53 @@ fn main(): int {
- [x] Struct field access
- [ ] Memory safety (garbage collection or ownership model, not yet decided)
- [ ] Standard library
- [ ] Multiple source files
- [x] Multiple source files
- [x] `module`/`import` declarations (nested module paths via `::`)
- [x] `internal` visibility restriction
- [x] `cleaf build` — project-wide scan, dependency graph, topological compilation
- [x] Cross-module name mangling + multi-object codegen/link
- [ ] Arrays
- [ ] Additional primitive types

## Test coverage

The test suite contains 120 test cases totalling 248 assertions spread across the four compiler passes,
plus around 20 additional fixtures used for memory safety validation with Valgrind.
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 | 26 | 129 |
| Semantic | 56 | 85 |
| HIR | 20 | 20 |
| Codegen | 19 | 19 |
| **Total**| **120** | **248** |
| 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

```sh
make test # run all test suites
make ast-test # parser tests only
make semantic-test # semantic analysis tests only
make hir-test # HIR lowering tests only
make codegen-test # code generation tests only
make asan-test # all tests with AddressSanitizer and UBSan
make valgrind-test # memory checks on a suite of ~20 .clf fixtures
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
```
4 changes: 2 additions & 2 deletions cleaf_backlog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ x 2026-06-08 2026-06-08 Add multiple int related types for better register usage
x 2026-06-09 2026-06-09 Add "asm" function for inline assembly in the code base @global
(B) Add "sizeof" compiler function @global
(C) Add "type" keyword to create types at compile time @global
(A) Import workflow for multiple file and std compilation @global
x 2026-07-01 2026-07-01 Import workflow for multiple file and std compilation @global
x 2026-06-11 2026-06-11 Add "char" type @global
x 2026-06-18 2026-06-18 Add Array type @global
(B) Add signed type @global
x 2026-06-10 2026-06-10 Choose between mutable or const for var by default and change compiler behavior depending on this choice @global
(A) Enhance error reporting @thirdparty
x 2026-06-18 2026-06-18 choose if we pass struct var by value or pointer and implement both methods
(C) make language documentation @docs
x 2026-07-01 2026-07-01 make language documentation @docs
(D) add defer keyword @global
(A) add type casting @global
(B) add way to get var address @global
Expand Down
17 changes: 13 additions & 4 deletions docs/docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ This produces `./build/cleaf`.
## Compiling a Cleaf program

```sh
./build/cleaf <source.clf> # compile to ./a.out
./build/cleaf <source.clf> -o <out> # compile with a custom output name
./build/cleaf <source.clf> # compile to ./build/a.out
./build/cleaf <source.clf> -o <out> # compile with a custom output name (./build/<out>)
```

### Debug flags
Expand All @@ -55,7 +55,7 @@ Compile and run it:

```sh
./build/cleaf hello.clf -o hello
./hello
./build/hello
echo $? # prints 0
```

Expand All @@ -74,6 +74,15 @@ fn main(): int {

```sh
./build/cleaf example.clf -o example
./example
./build/example
echo $? # prints 7
```

## Multi-file projects

Cleaf can also compile a project spread across several `.clf` files, using a
Go/Rust-inspired module system (`module`/`import` declarations). Running
`cleaf build` in a directory scans every `.clf` file, resolves dependencies between
modules, and produces a single executable at `build/a.out`. See
[Modules and Imports](/docs/language/modules) for the full syntax and rules.

Loading
Loading