You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
vibed/edu/.beans/archive/edu-nc61--16-the-compilatio...

165 lines
4.3 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

---
# edu-nc61
title: §16 The Compilation Pipeline
status: completed
type: task
priority: normal
created_at: 2026-03-10T23:30:01Z
updated_at: 2026-03-10T23:30:01Z
---
## §16 The Compilation Pipeline — Stub to fill
File: `edu/src/lisp-compiler.md`, section `### 16. The Compilation Pipeline`
Replace the stub line with full content. Target 600800 words. Wire all stages into a working CLI binary. Trace the complete factorial example end-to-end.
## Learning objectives
- Implement the `compile` function that chains parse → analyse → generate
- Write a CLI `main.rs` that reads from a file or stdin and writes to stdout
- Handle and display errors from any stage
- Demonstrate the complete workflow: `.lisp``.c` → compile → run
## Content to write
### The `compile` function
In `src/main.rs` (or a `src/lib.rs`):
```rust
pub fn compile(source: &str) -> Result<String, error::CompileError> {
let exprs = parser::parse(source)?;
let exprs = analyser::analyse(exprs)?;
let c = codegen::generate(exprs);
Ok(c)
}
```
### Handling top-level non-define expressions
The code generator in §14 skipped top-level expressions that are not `define` forms (e.g., `(display (factorial 10))`). These must be emitted inside a C `main` function. Complete the `generate` function:
```rust
pub fn generate(exprs: Vec<Expr>) -> String {
let mut out = String::new();
out.push_str(PREAMBLE);
// Forward declarations
for expr in &exprs {
if let Expr::Define { name, value } = expr {
out.push_str(&gen_forward_decl(name, value));
}
}
out.push('\n');
// Function and variable definitions
for expr in &exprs {
if let Expr::Define { name, value } = expr {
match value.as_ref() {
Expr::Lambda { params, body } =>
out.push_str(&gen_function_def(name, params, body)),
_ =>
out.push_str(&gen_variable_def(name, value)),
}
}
}
// main(): emit top-level non-define expressions
out.push_str("\nint main(void) {\n");
for expr in &exprs {
if !matches!(expr, Expr::Define { .. }) {
out.push_str(&format!(" {};\n", gen_stmt(expr)));
}
}
out.push_str(" return 0;\n}\n");
out
}
```
### The CLI: `src/main.rs`
```rust
use std::{env, fs, io::{self, Read}, process};
fn main() {
let args: Vec<String> = env::args().collect();
let source = match args.get(1) {
Some(path) => fs::read_to_string(path).unwrap_or_else(|e| {
eprintln!("error reading {}: {}", path, e);
process::exit(1);
}),
None => {
let mut buf = String::new();
io::stdin().read_to_string(&mut buf).unwrap();
buf
}
};
match compile(&source) {
Ok(c_source) => print!("{}", c_source),
Err(e) => {
eprintln!("{}", e);
process::exit(1);
}
}
}
```
Explain: if a file path is given as `argv[1]`, read from it; otherwise read from stdin. Always write C to stdout. This allows `minilisp factorial.lisp > factorial.c`.
### The end-to-end workflow
Walk through the complete factorial example step by step:
```sh
# 1. Write a MiniLisp program
cat > factorial.lisp <<'EOF'
(define (factorial n)
(if (= n 0)
1
(* n (factorial (- n 1)))))
(display (factorial 10))
(newline)
EOF
# 2. Compile to C
cargo run -- factorial.lisp > factorial.c
# 3. Compile the C
cc -o factorial factorial.c
# 4. Run
./factorial
# Output: 3628800
```
Show the generated `factorial.c` in full so the reader can verify the output looks correct.
### Error handling demo
Show what happens when the compiler rejects invalid input:
```sh
echo "(define (f x) (g x))" | cargo run # g is undefined
# stderr: semantic error: undefined symbol: `g`
# exit code: 1
```
### Build and validate
```sh
cargo fmt && cargo check && cargo clippy && cargo test
```
All should pass. This is the project's first fully working state.
## Style notes
- The end-to-end workflow walkthrough is the climax of the implementation sections — give it space
- Show the generated C in full; readers deserve to see the fruit of their work
- The error demo is quick but important — confirm the pipeline fails gracefully
- End with a note of congratulation: the reader has just built a compiler