Skip to content

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

  1. 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.
  2. The only code provided for the learning objective is the test contract, in one of two forms:
  3. 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.
  4. 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).
  5. 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.
  6. 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.md pointing at the spec and one minimal source file wired into CMake.
  7. Learners may add files freely. Wire the learner directory with EXERCISE_DIR so new .cpp files are picked up without editing CMake:
    pebble_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 switched
    
    The same works for pebble_add_component in pebble/lib/.
  8. 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).
  9. 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 with TODO(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

  1. Write SPEC.md from the existing exercises.md steps and tests.
  2. Reduce the headers the tests include to one contract header; move everything else to solutions/ (it becomes part of the reference design) or to provided/ if it is infrastructure.
  3. Replace the learner files with src/README.md + one src/Stub.cpp; switch CMake to EXERCISE_DIR src.
  4. Rewrite tests that poke at internals so they go through the contract (or the CLI).
  5. Check: skeleton build compiles and the chapter's tests fail with TODO(chNN); PEBBLE_USE_SOLUTION=all passes; ./course validate has no lab warnings.