| name | Building |
| description | All documentation related to building and running Project or Tests |
Building
Its likely user is using CLion, in this case in the root dir, you'll find cmake-build-debug where there are two
compilers present
TCCCompiler (the compiler that embeds Tiny CC)
- This compiler cannot create llvm ir, It translates Chemical to C and then compiles that using Tiny CC
- This compiler is faster to build, faster at compilation but generates slower code.
Compiler (the compiler that embeds LLVM/Clang and Tiny CC)
- This compiler can do anything TCCCompiler can do and more, It performs optimizations using LLVM. But you
- can also tell it to use Tiny CC with flag
--use-tcc or ask it to translate to C and then compile that using
- clang with flag
--use-c
- This compiler takes time to build, slow at compilation but generates fast code
It's likely that you don't need to rebuild the compiler, because you are working on libraries (lang/libs/<library> or lang/compiled/<library>).
To properly understand building of the compilers, You should analyze the CMakeLists.txt in the root of the repo.
If you are working on any code that doesn't interact with LLVM/Clang then you should not compile Compiler target. you should focus
on TCCCompiler which would build faster and would compile faster.
Configuration
./scripts/configure.sh
./scripts/configure.sh --no-llvm
Building Compiler
There are three CMake targets: TCCCompiler, Compiler, and ChemicalLsp.
Using build scripts (recommended)
./scripts/build.sh --tcc
./scripts/build.sh --llvm
./scripts/build.sh --lsp
./scripts/build.sh --all
./scripts/build.sh --llvm -j 16
Using make directly
make -C cmake-build-debug Compiler -j$(nproc)
make -C cmake-build-debug TCCCompiler -j$(nproc)
make -C cmake-build-debug ChemicalLsp -j$(nproc)
Notes
- Prefer building one target at a time, not all.
- The Makefile is at
cmake-build-debug/Makefile (configured by CLion).
cmake is at /opt/clion/bin/cmake/linux/x64/bin/cmake (not in PATH).
out/host contains LLVM/Clang Libraries — do not overwrite.
- If user doesn't have LLVM, use
./scripts/configure.sh --no-llvm (sets -DBUILD_COMPILER=OFF).
Building Tests
Using test script (recommended)
./scripts/test.sh --tcc
./scripts/test.sh --llvm
./scripts/test.sh --tcc --plugins
./scripts/test.sh --tcc -o my_tests
./scripts/test.sh --tcc --no-run
./scripts/test.sh --tcc --no-build
./scripts/test.sh --tcc --bt
./scripts/test.sh --tcc --bt-full
🐛 -bt / -bt-full: These flags wrap the test executable in gdb -batch mode.
On crash, they print a backtrace (and optionally registers, disassembly, locals).
Implies -g automatically. Works for both compiled and interpretation modes.
-bt → gdb -batch -ex "run" -ex "bt full" --args <program>
-bt-full → gdb -batch -ex "run" -ex "thread apply all bt full" -ex "info registers" -ex "x/16i $pc" -ex "info locals" -ex "info args" --args <program>
⚠️ --no-build warning: This flag skips rebuilding the C++ compiler binary.
Any changes to .cpp/.h files will NOT be reflected — the previously built
binary is used as-is. Only use --no-build when iterating on .ch test files or
Chemical library sources without any compiler C++ changes. To include C++ changes,
omit --no-build (or run once without it to rebuild, then you can use --no-build
for subsequent iterations).
Manual commands
cmake-build-debug/TCCCompiler "lang/tests/build.lab" -o lang/tests/build/tests-tcc.exe --mode debug_quick --no-cache
cmake-build-debug/Compiler "lang/tests/build.lab" -o lang/tests/build/tests.exe --mode debug_complete --no-cache
Interpretation Tests
./scripts/test.sh --tcc --interpret
./scripts/test.sh --tcc --interpret --no-build
./chemical lang/tests/build.lab --arg-interpret --mode debug_complete --no-cache
The --arg-interpret flag causes build.lab to create a LabJobType::Interpretation job. The compiler calls do_interpretation_job() in compiler/lab/LabBuildCompiler.cpp, which:
- Parses, symres, and typechecks
interpret/ + common/ modules
- Initializes module-level
VarInitStmt variables on the global scope
- Calls
main() via the AST interpreter directly — no object code generated
How Tests Work
Tests use a common framework at lang/tests/common/src/test.ch that works in both modes:
comptime if(intrinsics::is_interpretation()) {
intrinsics::expr_println(`${ANSI_COLOR_GREEN}Test ${total_tests + 1} [${name}] succeeded${ANSI_COLOR_RESET}`);
} else {
printf("%sTest %d [%s] succeeded %s\n", ANSI_COLOR_GREEN, total_tests + 1, name, ANSI_COLOR_RESET);
}
- Interpretation path:
intrinsics::expr_println(expr: %expressive_string) walks the expressive string's parts — StringValue literals go directly to std::cout, ${} expressions are evaluated and printed via RepresentationVisitor with interpret_representation = true (no quotes).
- Compiled path: Standard
printf with ANSI escape string constants.
Compiler flags explained
--mode debug_quick — quickly compile the project with debug info
--mode debug_complete — full debug mode for LLVM backend
--no-cache — do not rely on previously generated objects
--emit-c — write the Translated.c file to the build directory
--arg-interpret — run in interpretation mode (interpret AST directly, no codegen)
--arg-test-plugins — build library tests executable
-frecompile-plugins — recompile compiler plugins
Building Library Tests
We test most libraries in the tests above (lang/tests/build.lab) but some libraries like:
html_cbi, css_cbi, js_cbi, components
react_cbi, preact_cbi, solid_cbi, universal_cbi
These are compiler plugins, tested via a separate executable:
./scripts/test.sh --tcc --plugins
cmake-build-debug/TCCCompiler "lang/tests/build.lab" -o lang/tests/build/lib-tests-tcc.exe --mode debug_quick --no-cache --arg-test-plugins -frecompile-plugins
Helpful flags:
--plugin-mode debug_complete — compile plugins in debug mode for full stack traces
--arg-test-html, --arg-test-css, etc. — individual library tests
Building LSP
If you modify anything inside the server directory, note that it's part of the LSP target.
These modules are all LSP-related and require the LSP server running to verify:
html_ide, css_ide, js_ide, react_ide, preact_ide, solid_ide, universal_ide, md_ide
./scripts/build.sh --lsp
LSP target name is ChemicalLsp. Read CMakeLists.txt before building the LSP target.
Developing Comptime Tests
Test Structure
Interpret tests run via --arg-interpret and execute the AST interpreter directly. The entry point is:
// lang/tests/interpret/src/main.ch
public func main() {
run_common_tests(); // Tests 1-97 (core language features)
run_native_common_tests(); // Pointer arithmetic, casts, comptime pointers
print_test_stats();
}
| Module | Location | What it tests |
|---|
common_tests | lang/tests/common/ | Core features (arithmetic, loops, structs, variants, inc/dec) |
native_common_tests | lang/tests/native_common/ | Pointer operations, casts, comptime pointer arithmetic |
interpret_tests | lang/tests/interpret/ | Wrapper that imports both and runs main() |
Comptime Test Files (in lang/tests/src/comptime/)
| File | Tests |
|---|
basic.ch | Basic comptime: sum, structs, strings, enums, constructors, get_child_fn, get_line_no |
features.ch | Comptime features: bitwise ops, loops, casting, logical ops, for-in, struct mutation, destructors |
expressions.ch | comptime { } block expressions: arithmetic in comptime blocks |
satisfies.ch | intrinsics::satisfies<T, U>() type relationship tests |
is_value.ch | intrinsics::is_same_type() and is operator type identity tests |
vector.ch | intrinsics::vector<T>() vector operations |
Running Tests
./scripts/test.sh --tcc --interpret --no-build
./scripts/test.sh --tcc --no-build
./scripts/test.sh --tcc --interpret --no-build && ./scripts/test.sh --tcc --no-build
Adding a New Comptime Test
-
Create the test source in lang/tests/src/comptime/ or lang/tests/common/src/:
// lang/tests/src/comptime/my_feature.ch
comptime func my_feature(a : int, b : int) : int {
return a * b + a;
}
func test_my_feature() {
test("my feature works", () => {
return my_feature(3, 4) == 15;
});
}
-
Register the test by calling the function from the appropriate runner:
- For tests shared with runs: Add
test_my_feature(); to run_common_tests() in lang/tests/common/src/main.ch
- For tests with pointer arithmetic: Add to
run_native_common_tests() in lang/tests/native_common/src/main.ch
- For compiled-only tests: Add to
main() in lang/tests/src/tests.ch
-
Build and test: Use the commands above.
Common Interpreter Pitfalls
- Pointer bounds: The interpreter tracks
ahead/behind on PointerValues. Dereferencing past bounds returns null (not crash). Tests with pointer arithmetic reaching one-past-end may fail.
- Struct pointers (
&raw struct_val): Not supported in interpreter (returns error). Use &mut struct_val (ReferenceOfValue) instead, or parameter passing by reference.
- Function references as parameters: Functions passed as
(params) => return_type are called via FunctionDeclaration::call(). If the function reference can't be resolved, a non-fatal "function not found" error appears.
- Float/Double casts:
float→int and double→int are supported. Other float/double combinations work via standard arithmetic.
- Destructors: Structs with
@delete destructors have their destructor body interpreted when the scope exits. Empty destructors work fine.
Debugging Interpret Test Failures
- Check the test output for
[InterpretError] messages — these indicate exact failures
- Look for "cannot dereference pointer" — pointer went past allocated bounds
- Look for "function call" — a function reference couldn't be resolved
- Look for "Operation between values" — an operation between incompatible types
- Add
std::cerr << "[DEBUG] ..." << std::endl; to interpreter source files to trace specific operations
- The worst failures ("RUNTIME ERROR: invalid memory access") happen when
deref() itself crashes — we've made this non-fatal by returning getNullValue() instead
Debugging with a Single Test File
Running the full test suite on every iteration is slow. Instead of working through
the entire test suite, isolate the failing test by copying its source code
(plus the types/functions it depends on) into a single file at lang/compiled/temp.ch.
The lang/compiled/ directory is in .gitignore, so nothing there will be committed.
Workflow
- Copy the test — put the failing test's code into
lang/compiled/temp.ch
- Rebuild the compiler after any C++ changes:
./scripts/build.sh --tcc
./scripts/build.sh --llvm
- Compile the single file (choose one):
- LLVM IR inspection (Compiler target):
cmake-build-debug/Compiler "lang/compiled/temp.ch" --out-ll-all --build-dir "lang/compiled" \
-o "lang/compiled/temp.exe" --mode debug_complete --debug-ir -v --assertions -fno-unwind-tables
LLVM IR is emitted at lang/compiled/modules/main/llvm_ir.ll
- C translation (TCCCompiler target):
cmake-build-debug/TCCCompiler "lang/compiled/temp.ch" -o "lang/compiled/temp.c" -v -bm-modules
cmake-build-debug/TCCCompiler "lang/compiled/temp.ch" -o "lang/compiled/temp.exe" -v -bm-modules
- Run the executable:
./lang/compiled/temp.exe
- Inspect the generated IR / C to diagnose codegen bugs
- Add debug logs to the compiler source, rebuild, and repeat
Flags Explained
--mode debug_complete — maximum debug info in LLVM IR; omit for cleaner IR without metadata
--mode debug_quick — minimal debug info (good for TCCCompiler)
--debug-ir — don't crash on potentially bad IR
--assertions — verify the generated IR is valid
-fno-unwind-tables — cleaner IR (removes unwind data on Windows)
-v — verbose output
-bm-modules — emit build module information
⚠️ Always rebuild the compiler (./scripts/build.sh --tcc or --llvm) after
changing .cpp/.h files. The previously built binary is used otherwise and
your changes won't be reflected.
Related Skills
- Build System (
.agents/skills/build_system/SKILL.md) — Detailed internals of the Lab build system, job execution, plugin compilation, caching, dependency management
- Performance (
.agents/skills/performance/SKILL.md) — Compiler optimization patterns, parallelization strategies, arena allocation
- Testing Guide (
.agents/skills/testing/SKILL.md) — Test infrastructure, writing tests, how tests are wired in lang/tests/build.lab