Iter 9a/9b: dogfood (list_map) + ail run
Iter 9a — dogfood. examples/list_map.ail.json exercises everything Iter 1-8 shipped in one program: ADTs (IntList = Nil | Cons Int IntList), recursive fns over the ADT (map_int, print_list), pattern matching with nested Var fields, a closure (`\\x. x * 2`, no captures), fn-typed parameters, IO effects propagating through recursion, and `let`- sequencing of an effectful sub-expression inside a match arm. Result: nothing broke. The full pipeline (typecheck → IR emit → clang → run) produces "2\\n4\\n6\\n". The language is now sufficient for "small but real" programs. Iter 9b — `ail run`. Convenience subcommand that builds into a tempdir and executes, propagating the binary's exit code. Equivalent to `ail build && ./bin` in one step. Build logic factored out of `Cmd::Build` into a shared `build_to` helper used by both Build and Run. Tests: 50 green (was 49). New e2e `list_map_doubles_then_prints`. No test for `ail run` itself — the existing build_and_run helper in e2e.rs already exercises the same build+exec sequence path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+79
-42
@@ -79,6 +79,19 @@ enum Cmd {
|
||||
#[arg(long, default_value = "-O0")]
|
||||
opt: String,
|
||||
},
|
||||
/// Build into a tempdir and execute. Exits with the binary's exit code.
|
||||
/// Convenience wrapper around `build` + invocation of the resulting
|
||||
/// binary; useful in iteration loops where we don't care about the
|
||||
/// output artefact's location.
|
||||
Run {
|
||||
path: PathBuf,
|
||||
/// Optimization (e.g. `-O2`); default `-O0` for debuggability.
|
||||
#[arg(long, default_value = "-O0")]
|
||||
opt: String,
|
||||
/// Args passed through to the compiled program.
|
||||
#[arg(last = true)]
|
||||
args: Vec<String>,
|
||||
},
|
||||
/// Lists built-in operations with their signatures.
|
||||
Builtins {
|
||||
#[arg(long)]
|
||||
@@ -357,50 +370,25 @@ fn main() -> Result<()> {
|
||||
}
|
||||
}
|
||||
Cmd::Build { path, out, opt } => {
|
||||
// Iter 5c: same pipeline as `emit-ir`, but clang runs at the end.
|
||||
let ws = ailang_core::load_workspace(&path)?;
|
||||
let diags = ailang_check::check_workspace(&ws);
|
||||
if !diags.is_empty() {
|
||||
for d in &diags {
|
||||
eprintln!(
|
||||
"{}: [{}] {}{}",
|
||||
match d.severity {
|
||||
ailang_check::Severity::Error => "error",
|
||||
ailang_check::Severity::Warning => "warning",
|
||||
},
|
||||
d.code,
|
||||
d.def
|
||||
.as_ref()
|
||||
.map(|n| format!("{n}: "))
|
||||
.unwrap_or_default(),
|
||||
d.message,
|
||||
);
|
||||
}
|
||||
std::process::exit(1);
|
||||
}
|
||||
let ir = ailang_codegen::lower_workspace(&ws)?;
|
||||
let tmpdir = std::env::temp_dir().join(format!("ailang-{}", std::process::id()));
|
||||
let bin = build_to(&path, out, &opt)?;
|
||||
eprintln!("built {}", bin.display());
|
||||
}
|
||||
Cmd::Run { path, opt, args } => {
|
||||
// Iter 9b: build into a fresh tempdir per run, exec, propagate
|
||||
// exit code. The artefact dir is left around (no cleanup) so
|
||||
// it can be inspected in case of a crash; OS temp policy
|
||||
// collects them.
|
||||
let tmpdir = std::env::temp_dir().join(format!(
|
||||
"ailang-run-{}",
|
||||
std::process::id()
|
||||
));
|
||||
std::fs::create_dir_all(&tmpdir)?;
|
||||
let ll_path = tmpdir.join(format!("{}.ll", ws.entry));
|
||||
std::fs::write(&ll_path, &ir)?;
|
||||
let out_bin = out.unwrap_or_else(|| {
|
||||
Path::new(".").join(&ws.entry).with_extension("")
|
||||
});
|
||||
let status = std::process::Command::new("clang")
|
||||
.arg(&opt)
|
||||
.arg("-o")
|
||||
.arg(&out_bin)
|
||||
.arg(&ll_path)
|
||||
let bin = build_to(&path, Some(tmpdir.join("bin")), &opt)?;
|
||||
let status = std::process::Command::new(&bin)
|
||||
.args(&args)
|
||||
.status()
|
||||
.context("running clang")?;
|
||||
if !status.success() {
|
||||
anyhow::bail!(
|
||||
"clang failed (status {}); ll at {}",
|
||||
status,
|
||||
ll_path.display()
|
||||
);
|
||||
}
|
||||
eprintln!("built {}", out_bin.display());
|
||||
.with_context(|| format!("executing {}", bin.display()))?;
|
||||
std::process::exit(status.code().unwrap_or(127));
|
||||
}
|
||||
Cmd::Builtins { json } => {
|
||||
let list = ailang_check::builtins::list();
|
||||
@@ -1409,3 +1397,52 @@ fn render_workspace_diff_text(r: &WorkspaceDiffReport) -> String {
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
/// Iter 9b: shared build helper for `Cmd::Build` and `Cmd::Run`.
|
||||
/// Loads the workspace, runs the typechecker, emits IR, and links via
|
||||
/// clang. On typecheck failure, prints diagnostics to stderr and exits
|
||||
/// the process with code 1. On clang failure, returns a Result error
|
||||
/// (the .ll path is preserved for post-mortem inspection).
|
||||
fn build_to(path: &Path, out: Option<PathBuf>, opt: &str) -> Result<PathBuf> {
|
||||
let ws = ailang_core::load_workspace(path)?;
|
||||
let diags = ailang_check::check_workspace(&ws);
|
||||
if !diags.is_empty() {
|
||||
for d in &diags {
|
||||
eprintln!(
|
||||
"{}: [{}] {}{}",
|
||||
match d.severity {
|
||||
ailang_check::Severity::Error => "error",
|
||||
ailang_check::Severity::Warning => "warning",
|
||||
},
|
||||
d.code,
|
||||
d.def
|
||||
.as_ref()
|
||||
.map(|n| format!("{n}: "))
|
||||
.unwrap_or_default(),
|
||||
d.message,
|
||||
);
|
||||
}
|
||||
std::process::exit(1);
|
||||
}
|
||||
let ir = ailang_codegen::lower_workspace(&ws)?;
|
||||
let tmpdir = std::env::temp_dir().join(format!("ailang-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&tmpdir)?;
|
||||
let ll_path = tmpdir.join(format!("{}.ll", ws.entry));
|
||||
std::fs::write(&ll_path, &ir)?;
|
||||
let out_bin = out.unwrap_or_else(|| Path::new(".").join(&ws.entry).with_extension(""));
|
||||
let status = std::process::Command::new("clang")
|
||||
.arg(opt)
|
||||
.arg("-o")
|
||||
.arg(&out_bin)
|
||||
.arg(&ll_path)
|
||||
.status()
|
||||
.context("running clang")?;
|
||||
if !status.success() {
|
||||
anyhow::bail!(
|
||||
"clang failed (status {}); ll at {}",
|
||||
status,
|
||||
ll_path.display()
|
||||
);
|
||||
}
|
||||
Ok(out_bin)
|
||||
}
|
||||
|
||||
@@ -82,6 +82,17 @@ fn closure_captures_let_n() {
|
||||
assert_eq!(stdout.trim(), "42");
|
||||
}
|
||||
|
||||
/// Iter 9 dogfood: a non-trivial program that combines ADTs, recursion,
|
||||
/// closure-without-capture, fn-typed parameters, IO effects, and `let`-
|
||||
/// sequencing of effectful sub-expressions in a pattern-match arm.
|
||||
/// `map (\x. x * 2)` over `[1, 2, 3]` then print each element.
|
||||
#[test]
|
||||
fn list_map_doubles_then_prints() {
|
||||
let stdout = build_and_run("list_map.ail.json");
|
||||
let lines: Vec<&str> = stdout.lines().collect();
|
||||
assert_eq!(lines, vec!["2", "4", "6"]);
|
||||
}
|
||||
|
||||
/// Guards `ail diff`: a modified body changes the hash of `sum`, while
|
||||
/// `main` stays unchanged. Expects exit code 1, `changed` contains exactly
|
||||
/// `sum`, `unchanged` contains `main`, `added`/`removed` empty.
|
||||
|
||||
Reference in New Issue
Block a user