20a-overall-architecture
TL;DR (5 lines)
- Stages are ownership boundaries.
- Data movement matters as much as stage names.
- Small compiler examples should still show multiple layers.
- Broken stage structure should remain visible in invalid examples.
- A mental model is a data-flow model.
Frequent mistakes
- Describing a pipeline as a list of names with no data movement.
- Collapsing stages so tightly that diagnostics lose their context.
- Teaching advanced compiler topics without a small staged example.
Prerequisites: book/chapters/20a-architecture-globale.html. See also: book/chapters/20a-architecture-globale.html, book/chapters/27-grammar.html, book/chapters/31-build-errors.html.
Concrete Problem
Compiler-facing chapters become vague when they talk about stages but never show how data crosses stage boundaries.
Red Thread (Single Project)
One small compiler-like flow reads a source input, validates shape, transforms state, and produces an exit-oriented result.
For what
This chapter helps the reader build a mental model of pipeline boundaries.
Work in this chapter
You will inspect a staged flow, name what each stage owns, then compare it to a collapsed design that hides those boundaries.
Coherent example
space demo/compiler
pick ParseState {
case Parsed(nodes: int),
case Failed(code: int),
}
proc parse(size: int) -> ParseState {
if size <= 0 { give ParseState.Failed(11) }
give ParseState.Parsed(size)
}
proc lower(state: ParseState) -> int {
match state {
case Parsed(nodes) { give nodes }
case Failed(code) { give code }
else { give 70 }
}
}
export *
Complete examples
Each block below is a complete reading unit with its own boundary, data shape, and observable result.
Primary coherent example
This is the compact chapter anchor used by the surrounding explanation.
space demo/compiler
pick ParseState {
case Parsed(nodes: int),
case Failed(code: int),
}
proc parse(size: int) -> ParseState {
if size <= 0 { give ParseState.Failed(11) }
give ParseState.Parsed(size)
}
proc lower(state: ParseState) -> int {
match state {
case Parsed(nodes) { give nodes }
case Failed(code) { give code }
else { give 70 }
}
}
export *
Staged compiler flow
This program keeps parse and lower stages visibly separate.
space examples/compiler/stages
pick Stage {
case Parsed(nodes: int),
case Failed(code: int),
}
proc parse(size: int) -> Stage {
if size <= 0 { give Stage.Failed(11) }
give Stage.Parsed(size)
}
proc emit(stage: Stage) -> int {
match stage {
case Parsed(nodes) { give nodes }
case Failed(code) { give code }
else { give 70 }
}
}
proc main(args: list[string]) -> int {
give emit(parse(3))
}
export *
Intermediate representation boundary
This variant names the intermediate state before producing an exit code.
space examples/compiler/ir
form IrUnit {
nodes: int
warnings: int
}
proc lower(nodes: int) -> IrUnit {
if nodes < 0 { give IrUnit { nodes: 0, warnings: 1 } }
give IrUnit { nodes: nodes, warnings: 0 }
}
proc finish(unit: IrUnit) -> int {
if unit.warnings > 0 { give 20 }
give unit.nodes
}
export *
Immediate work for this chapter
- Separate source input, intermediate representation, and emitted result.
- Make stage ownership visible in the data passed between procedures.
- Keep pipeline examples small but multi-stage.
- Keep the first code block complete and aligned with current Vitte docs syntax.
- Keep the invalid block focused on one broken contract only.
- Replace generic prose with one concrete rule visible in the code.
- Keep every example small enough to review from top to bottom.
Chapter override: 20a-overall-architecture.html
Dedicated problem
20a-overall-architecture needs a concrete production-grade anchor around source input, intermediate representation, and emitted result. The page should keep the examples, diagnostics, and review rules tied to that exact boundary instead of drifting back to generic tutorial prose.
Specific complete examples
20a-overall-architecture chapter anchor
The primary example is promoted here as the first chapter-specific production reading unit.
space demo/compiler
pick ParseState {
case Parsed(nodes: int),
case Failed(code: int),
}
proc parse(size: int) -> ParseState {
if size <= 0 { give ParseState.Failed(11) }
give ParseState.Parsed(size)
}
proc lower(state: ParseState) -> int {
match state {
case Parsed(nodes) { give nodes }
case Failed(code) { give code }
else { give 70 }
}
}
export *
Staged compiler flow
This program keeps parse and lower stages visibly separate.
space examples/compiler/stages
pick Stage {
case Parsed(nodes: int),
case Failed(code: int),
}
proc parse(size: int) -> Stage {
if size <= 0 { give Stage.Failed(11) }
give Stage.Parsed(size)
}
proc emit(stage: Stage) -> int {
match stage {
case Parsed(nodes) { give nodes }
case Failed(code) { give code }
else { give 70 }
}
}
proc main(args: list[string]) -> int {
give emit(parse(3))
}
export *
Intermediate representation boundary
This variant names the intermediate state before producing an exit code.
space examples/compiler/ir
form IrUnit {
nodes: int
warnings: int
}
proc lower(nodes: int) -> IrUnit {
if nodes < 0 { give IrUnit { nodes: 0, warnings: 1 } }
give IrUnit { nodes: nodes, warnings: 0 }
}
proc finish(unit: IrUnit) -> int {
if unit.warnings > 0 { give 20 }
give unit.nodes
}
export *
Risks and diagnostics
| Risk | Diagnostic signal | Action |
|---|---|---|
| Boundary drift | The chapter loses sight of source input, intermediate representation, and emitted result. | Restate the boundary beside the first code block and every invalid case. |
| Generic prose | A paragraph would still be true in another chapter. | Replace it with a code-specific rule from this page. |
| Weak diagnostic | The failure does not point back to the chapter contract. | Reduce the invalid case until one failure explains the rule. |
Review checklist
- The first example is complete and aligned with Vitte docs syntax.
- The production section names where this construct belongs in real code.
- The risk table connects each failure to a diagnostic or review action.
- The avoid list rejects broad misuse without adding quiz-like prompts.
Production use
- Use this chapter when a code review needs to preserve source input, intermediate representation, and emitted result.
- Keep examples small enough to copy into fixtures or docs smoke tests.
- Treat the invalid case as regression material for future docs checks.
What to avoid
- Do not add a second topic that hides the chapter's main contract.
- Do not expand examples by adding unrelated subsystems.
- Do not rely on prose when a small Vitte block can show the rule.
Global explanation
Pipeline chapters are most useful when they keep stage ownership explicit. The reader should know what the parse stage owns, what the transform stage owns, and where errors cross boundaries.
Invalid case
proc broken_pipeline(size: int) -> int {
match size
case 0 { give 0 }
}
}
This invalid case is intentionally small. It exists to isolate the contract failure that the chapter is trying to teach.
Common pitfalls
- Describing a pipeline as a list of names with no data movement.
- Collapsing stages so tightly that diagnostics lose their context.
- Teaching advanced compiler topics without a small staged example.
Short exercise
Add one extra stage to the example and explain what data it receives and what it returns.
Summary in 5 points
- Stages are ownership boundaries.
- Data movement matters as much as stage names.
- Small compiler examples should still show multiple layers.
- Broken stage structure should remain visible in invalid examples.
- A mental model is a data-flow model.
See also
Next best action
Extend the coherent example by one small, justified step and keep the same contract visible from input to output.
Chapter deep dive
20a-overall-architecture keeps compiler stages owned and inspectable. The chapter is written for a reader mapping language constructs to implementation stages.
The practical boundary is: source input, intermediate representation, and emitted result. Keep that boundary in view while reading the example, the invalid case, and the exercise.
Role in the learning path
Compiler-facing chapters become vague when they talk about stages but never show how data crosses stage boundaries.
One small compiler-like flow reads a source input, validates shape, transforms state, and produces an exit-oriented result.
This chapter helps the reader build a mental model of pipeline boundaries.
Profile-specific deep dive
Stage ownership
- Parse, lower, analyze, and emit stages should exchange named data shapes.
- Each stage should own one responsibility and one output contract.
- Collapsed stages make diagnostics and testing harder.
Pipeline evidence
- Show the source input, intermediate state, and emitted result.
- Keep failure variants available across stage boundaries.
- Tests should pin at least one successful path and one failed stage.
Reading the valid example
space demo/compiler: names the ownership boundary before any behavior appears.pick ParseState {: makes possible outcomes explicit instead of encoding them as magic values.case Parsed(nodes: int),: names one outcome that callers must be ready to handle.case Failed(code: int),: names one outcome that callers must be ready to handle.}: supports the chapter contract without adding hidden behavior.proc parse(size: int) -> ParseState {: states the callable contract: inputs first, result shape last.if size <= 0 { give ParseState.Failed(11) }: guards a failure or edge case before the nominal result.give ParseState.Parsed(size): ends the local path with an explicit result.}: supports the chapter contract without adding hidden behavior.proc lower(state: ParseState) -> int {: states the callable contract: inputs first, result shape last.match state {: supports the chapter contract without adding hidden behavior.case Parsed(nodes) { give nodes }: names one outcome that callers must be ready to handle.case Failed(code) { give code }: names one outcome that callers must be ready to handle.else { give 70 }: supports the chapter contract without adding hidden behavior.}: supports the chapter contract without adding hidden behavior.}: supports the chapter contract without adding hidden behavior.export *: supports the chapter contract without adding hidden behavior.
Lesson from the invalid example
proc broken_pipeline(size: int) -> int {: this line helps isolate the failure because it states the callable contract: inputs first, result shape last.match size: this line helps isolate the failure because it supports the chapter contract without adding hidden behavior.case 0 { give 0 }: this line helps isolate the failure because it names one outcome that callers must be ready to handle.}: this line helps isolate the failure because it supports the chapter contract without adding hidden behavior.}: this line helps isolate the failure because it supports the chapter contract without adding hidden behavior.
Engineering decisions to preserve
- Name the boundary before changing code: Stages are ownership boundaries.
- Keep the smallest example executable: Data movement matters as much as stage names.
- Make the invalid path explain one failure only: Small compiler examples should still show multiple layers.
- Prefer a visible contract over an implied convention: Broken stage structure should remain visible in invalid examples.
- Leave a review anchor that another maintainer can verify: A mental model is a data-flow model.
Context-specific review criteria
- The page makes the source input, intermediate representation, and emitted result boundary visible before the first code block.
- The intended reader, a reader mapping language constructs to implementation stages, can follow the valid example through named contracts instead of memorized tokens.
- The invalid example fails for the same reason the prose discusses.
- The exercise extends the same contract instead of introducing an unrelated concept.
- The next chapter can reuse the vocabulary introduced here without redefining it.
- The chapter stays specific enough that its title materially changes the meaning of the page.
- Every warning connects to a concrete code shape.
- The summary leaves one durable engineering rule behind.
Contract matrix
| Concern | Chapter rule | Evidence to keep |
|---|---|---|
| Ownership | Code belongs behind the boundary named by the chapter. | The chapter keeps ownership visible through source input, intermediate representation, and emitted result. |
| Input contract | The procedure receives a shape that is named before branching. | The valid example names the accepted shape before branching. |
| Nominal path | The clean path remains readable without hidden state. | The successful result can be found without reading hidden state. |
| Failure path | The invalid case isolates one failure reason. | The broken example has one main reason to fail. |
| Naming | Names explain the domain rather than only the mechanism. | Names remain tied to the chapter goal. |
| Types | Types remove ambiguity from values and results. | Fields and return values carry domain meaning. |
| Control flow | Branches stay traceable from guard to result. | Guards appear before the result they protect. |
| Module boundary | The public surface stays smaller than implementation detail. | The public surface remains smaller than the implementation detail. |
| Diagnostic value | The failure path points back to the exact contract. | The invalid example points back to the exact contract. |
| Test value | Regression evidence covers one passing path and one failing path. | One passing case and one failing case cover the lesson. |
| Refactor value | Implementation cleanup preserves the result shape. | The result shape stays stable during local cleanup. |
| Publication value | The chapter leaves one concrete engineering rule. | The chapter leaves one concrete engineering rule. |
Rewrite path for this chapter
- Rewrite the opening paragraph so it names source input, intermediate representation, and emitted result before naming syntax.
- Keep the valid example small enough that the full contract fits on screen.
- Move any broad claim back to a specific line in the example.
- Preserve one invalid case that fails for the chapter's main reason.
- Add one sentence explaining why the invalid case is not a random error.
- Make every pitfall actionable by naming the code shape it damages.
- Keep the exercise inside the same domain as the example.
- Avoid introducing a second unrelated project just to show variety.
- Use the summary to restate the chapter rule, not the table of contents.
- Check that the next chapter can build on this vocabulary.
- Remove any sentence that would still be true in every other chapter.
- Keep the last action small, local, and testable.
Diagnostic anchors
- The first inspected line is the one that declares the chapter's main contract.
- The central type, field, procedure, or branch carries the chapter's main idea.
- The invalid example includes a sentence-level explanation of its failure.
- Refactors preserve the detail that would otherwise mislead a future reader.
- The behavior that must stay stable is named before implementation changes begin.
- Vague names are replaced before they become review friction.
- Regression coverage protects the chapter's main contract.
- Implementation details stay out of public API unless the chapter explicitly teaches that surface.
- Beginner-facing diagnostics point to the contract, not to a random syntax detail.
- The next chapter can assume one clearly named concept from this page.
When extending this chapter
- Extend toward a reader mapping language constructs to implementation stages, not toward a broader catalog of features.
- Add a second example only if it sharpens the same contract.
- Prefer a small variant over a new subsystem.
- Keep prose close to code; every abstract claim should point to a visible shape.
- Do not hide a new concept in the exercise.
- If a paragraph explains policy, add the concrete code boundary it protects.
- If a paragraph explains syntax, add the semantic reason the syntax matters.
- If a paragraph explains architecture, identify the owner of each boundary.
- If a paragraph explains failure, keep the failing line close to the explanation.
- Stop expanding when the chapter has one complete, testable lesson.
Failure modes to avoid
- Describing a pipeline as a list of names with no data movement.
- Collapsing stages so tightly that diagnostics lose their context.
- Teaching advanced compiler topics without a small staged example.
Practice scenario
Start from the coherent example in 20a-overall-architecture. Change one identifier, one guard, and one returned value. After each change, write down whether the public contract is still the same contract or a new one.
If the contract changed, update the type or result shape first. If only the implementation changed, keep the external name stable and add one regression note explaining what should not change again.
Before moving on
- You can state the chapter role: keeps compiler stages owned and inspectable.
- You can point to the main boundary: source input, intermediate representation, and emitted result.
- You can connect the invalid case to the problem statement: Compiler-facing chapters become vague when they talk about stages but never show how data crosses stage boundaries.
- You can perform the exercise: Add one extra stage to the example and explain what data it receives and what it returns.