Contributing to cmetal
Thanks for your interest in contributing!
Note: learners never edit
exercises/— cmetal copies it into the gitignoredmy_exercises/workspace on first run.exercises/must only ever contain the broken, unsolved versions.
Adding an exercise
- Create the exercise file in
exercises/<topic_dir>/<name>.c - Create the matching solution in
solutions/<topic_dir>/<name>.c, then runpython3 scripts/solutions_codec.py pack— solutions are stored obfuscated as.c.encso learners aren't spoiled by accident (cmetal reveals them inmy_solutions/once an exercise passes). To edit existing solutions, run... unpackfirst, edit, re-pack. - Add an entry in
info.tomlwith progressive hints
Naming
- Topic directories:
NN_topic(e.g.01_pointers,06_strings) - Exercise files:
<topic>N.c(e.g.pointers1.c,pointers2.c)
Exercise structure
// Short description of what this exercise teaches.
#include <stdio.h>
// TODO: Fix/implement something
void function_with_bug(void) { ... }
#ifndef TEST
int main(void) {
// Interactive demo that shows the bug
}
#else
#include "cmetal_test.h"
TEST(test_name) { ASSERT_EQ(...); }
int main(void) {
RUN_TEST(test_name);
TEST_REPORT();
}
#endif
info.toml entry
[[exercises]]
name = "pointers3"
dir = "01_pointers"
test = true # compile with -DTEST and run tests
sanitizers = false # compile with ASan/UBSan
# flags = ["-O2"] # optional extra compiler flags for this exercise
# compilers = ["gcc"] # optional: restrict to compilers where the bug is
# detectable (default: all). Restricted exercises are
# skipped when cmetal runs with another --compiler.
hints = [
"First hint: the gentlest nudge",
"Second hint: more specific",
"Third hint: almost the answer",
]
Requirements for solutions
- Must compile with
-Wall -Wextra -Werror -pedantic -std=c11 - Must pass with both
gccandclang - Must pass with
-fsanitize=address,undefinedwhen sanitizers are enabled - Stick to C11 standard -- no POSIX-specific features
Language vs platform
Exercises may rely on mainstream ABI facts — 8-bit bytes, 32-bit int,
8-byte double alignment — but must not present them as C guarantees.
When an exercise depends on such an assumption, make it explicit in the
code with a _Static_assert (see ub3, structs1, bitwise1,
strings3) and phrase comments so that what the C standard guarantees
and what the target ABI provides stay distinguishable.
Fallible allocations
If an exercise's contract includes allocation failure ("returns -1 and
leaves the object untouched"), route its allocations through
CMETAL_MALLOC / CMETAL_REALLOC from include/cmetal_alloc.h
instead of calling malloc/realloc directly. In normal builds they are
plain malloc/realloc; in TEST builds a test can arm the next allocation
to fail with cmetal_fail_next_alloc() and assert the failure branch
deterministically (see memory2, structs2, function_pointers2).
Stand-alone exercises
Exercises must stand alone: solving one never requires code or concepts built in another, and every exercise — including the implementation track's — must be useful to someone who will never build an interpreter (see the editorial rule in VISION.md).
The exercise invariant
Every exercise must fail verification as shipped, and every solution must pass it. An exercise that is already green teaches nothing; CI enforces this on every push and PR:
python3 scripts/check_exercises.py
This replicates the exact cmetal verification pipeline (base flags plus
the per-exercise test, sanitizers and flags settings from
info.toml). Run it whenever you add or change an exercise — and before
pushing, so you don't accidentally publish exercises in their solved
state. You can have git run it automatically on every push:
git config core.hooksPath scripts/hooks
Running tests locally
cargo test # Rust unit + integration tests
cargo clippy -- -D warnings
python3 scripts/check_exercises.py # C exercise/solution invariant
Code style
- C: follow the existing style, 4-space indent
- Rust:
cargo fmt+cargo clippy