Labs and implementation work: specs, not scaffolding¶
Learners design their own code. A lab (and any chapter implementation task in pebble/lib/) ships a written specification and the smallest possible test contract, not pre-structured code with a TODO in every function. Deciding the data structures and the decomposition is part of learning the technique; a skeleton that already names every helper does that work for the learner.
This page is binding for every chapter, alongside DEPTH_CONTRACT.md (what the chapter must teach) and STYLE.md (how to write).
1. The rule¶
- Every lab ships a
SPEC.md(template: CHAPTER_TEMPLATE/lab/SPEC.md) with: goal, requirements, required observable behavior, input and output formats, complexity / performance targets, exactly what the tests check, milestones, and hints hidden in collapsible<details>blocks. - The only code provided for the learning objective is the test contract, in one of two forms:
- Library contract: one small public header with the exact functions and types the tests call, as few as possible. Keep it data-structure-agnostic where you can: take and return plain
std::vector/std::map/std::string, the course's provided graph/grammar types, or LLVM objects (llvm::Function &) — never an internal class the learner would otherwise design. - Black-box CLI contract: the learner writes a tool that reads a documented input format and prints a documented output format; the tests are lit + FileCheck against it. Prefer this when the output is naturally textual (tables, traces, sets).
- Infrastructure that is not the learning objective may be provided in full: a grammar-file parser in a parsing lab, a CFG reader and random-CFG generator in a dominance lab, a toy-ISA simulator in a back-end lab, benchmark drivers, test inputs, printers.
- No pre-written internal helpers, class skeletons or step-by-step function stubs for the learning objective. The learner-owned directory contains only a
README.mdpointing at the spec and one minimal source file wired into CMake. - Learners may add files freely. Wire the learner directory with
EXERCISE_DIRso new.cppfiles are picked up without editing CMake:The same works forpebble_add_lab(ll1 CHAPTER ch02 SWITCH ll1-toolkit PROVIDED_SOURCES provided/GrammarReader.cpp EXERCISE_DIR src) # globs src/**/*.cpp (CONFIGURE_DEPENDS); # solutions/labs/ch02-ll1-toolkit/src/ when switchedpebble_add_componentinpebble/lib/. - Solutions stay complete reference implementations under
solutions/<same path>/, satisfying the same contract, organized however the solution author likes (they are one possible design, not the design). - The skeleton still compiles and fails cleanly. The single stub file in the learner directory defines each contract function with
PEBBLE_TODO("chNN", "E<k>: …"); tests fail withTODO(chNN). The learner deletes or replaces that file.
./course validate warns when a lab directory has no SPEC.md, and when a chapter has more than 12 PEBBLE_TODO stubs or stubs in more than 3 files (a sign that internals, not a contract, are stubbed). The reviewer judges the rest (REVIEW_CHECKLIST.md step 8).
2. Layout¶
labs/chNN-name/
├── SPEC.md the specification (the learner starts here)
├── CMakeLists.txt pebble_add_lab(... EXERCISE_DIR src ...), tools, benchmarks
├── include/<ns>/Contract.h the test contract (library form) — the ONLY API tests use
├── provided/ infrastructure that is not the learning objective (optional)
│ └── ... readers, generators, simulators, printers
├── inputs/ sample inputs (also used by the tests)
└── src/ LEARNER-OWNED
├── README.md "Your code goes here; read ../SPEC.md. Add any files you like."
└── Stub.cpp defines the contract functions with PEBBLE_TODO (delete me)
solutions/labs/chNN-name/src/ a complete reference implementation of the same contract
tests/chNN/ unit tests against Contract.h, or lit tests against the CLI
For work inside the compiler (pebble/lib/<Component>/), the component's provided interface (pebble/include/pebble/...) is the contract; the component directory follows the same src/ + stub pattern and its spec lives in the chapter's exercises.md (one ## E<k> section per task, written as a spec with the parts below).
3. What a good spec contains¶
| Part | Content |
|---|---|
| Goal | One paragraph: what the learner builds and which lesson techniques it exercises. |
| Requirements | Numbered, testable statements ("R3: computeIdoms returns idom[r] = r and idom[v] = -1 for unreachable v"). |
| Contract | The header (or CLI usage) verbatim, with the meaning of every parameter and return value, error behavior, and ownership. |
| Formats | Input and output grammars with an example of each; exact whitespace/ordering rules when output is FileChecked. |
| Performance | Targets with inputs: "10⁵-node random CFG in < 200 ms on the CI runner", plus the asymptotic bound required. |
| What the tests check | Every test file and what it asserts (correctness vs LLVM, random differential tests, edge cases). No hidden requirements. |
| Milestones | An order of attack with a test command per milestone (./course test NN -R Milestone2). |
| Measurement | For comparison labs: what to measure, the command, and a table to fill in. |
| Hints | <details> blocks: where to start → the key idea → a design sketch (data structures named, no code). |
| Stretch goals | ★ extensions. |
4. Converting an over-scaffolded lab¶
- Write
SPEC.mdfrom the existingexercises.mdsteps and tests. - Reduce the headers the tests include to one contract header; move everything else to
solutions/(it becomes part of the reference design) or toprovided/if it is infrastructure. - Replace the learner files with
src/README.md+ onesrc/Stub.cpp; switch CMake toEXERCISE_DIR src. - Rewrite tests that poke at internals so they go through the contract (or the CLI).
- Check: skeleton build compiles and the chapter's tests fail with
TODO(chNN);PEBBLE_USE_SOLUTION=allpasses;./course validatehas no lab warnings.