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")]
|
#[arg(long, default_value = "-O0")]
|
||||||
opt: String,
|
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.
|
/// Lists built-in operations with their signatures.
|
||||||
Builtins {
|
Builtins {
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
@@ -357,50 +370,25 @@ fn main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Cmd::Build { path, out, opt } => {
|
Cmd::Build { path, out, opt } => {
|
||||||
// Iter 5c: same pipeline as `emit-ir`, but clang runs at the end.
|
let bin = build_to(&path, out, &opt)?;
|
||||||
let ws = ailang_core::load_workspace(&path)?;
|
eprintln!("built {}", bin.display());
|
||||||
let diags = ailang_check::check_workspace(&ws);
|
}
|
||||||
if !diags.is_empty() {
|
Cmd::Run { path, opt, args } => {
|
||||||
for d in &diags {
|
// Iter 9b: build into a fresh tempdir per run, exec, propagate
|
||||||
eprintln!(
|
// exit code. The artefact dir is left around (no cleanup) so
|
||||||
"{}: [{}] {}{}",
|
// it can be inspected in case of a crash; OS temp policy
|
||||||
match d.severity {
|
// collects them.
|
||||||
ailang_check::Severity::Error => "error",
|
let tmpdir = std::env::temp_dir().join(format!(
|
||||||
ailang_check::Severity::Warning => "warning",
|
"ailang-run-{}",
|
||||||
},
|
std::process::id()
|
||||||
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)?;
|
std::fs::create_dir_all(&tmpdir)?;
|
||||||
let ll_path = tmpdir.join(format!("{}.ll", ws.entry));
|
let bin = build_to(&path, Some(tmpdir.join("bin")), &opt)?;
|
||||||
std::fs::write(&ll_path, &ir)?;
|
let status = std::process::Command::new(&bin)
|
||||||
let out_bin = out.unwrap_or_else(|| {
|
.args(&args)
|
||||||
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()
|
.status()
|
||||||
.context("running clang")?;
|
.with_context(|| format!("executing {}", bin.display()))?;
|
||||||
if !status.success() {
|
std::process::exit(status.code().unwrap_or(127));
|
||||||
anyhow::bail!(
|
|
||||||
"clang failed (status {}); ll at {}",
|
|
||||||
status,
|
|
||||||
ll_path.display()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
eprintln!("built {}", out_bin.display());
|
|
||||||
}
|
}
|
||||||
Cmd::Builtins { json } => {
|
Cmd::Builtins { json } => {
|
||||||
let list = ailang_check::builtins::list();
|
let list = ailang_check::builtins::list();
|
||||||
@@ -1409,3 +1397,52 @@ fn render_workspace_diff_text(r: &WorkspaceDiffReport) -> String {
|
|||||||
|
|
||||||
out
|
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");
|
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
|
/// Guards `ail diff`: a modified body changes the hash of `sum`, while
|
||||||
/// `main` stays unchanged. Expects exit code 1, `changed` contains exactly
|
/// `main` stays unchanged. Expects exit code 1, `changed` contains exactly
|
||||||
/// `sum`, `unchanged` contains `main`, `added`/`removed` empty.
|
/// `sum`, `unchanged` contains `main`, `added`/`removed` empty.
|
||||||
|
|||||||
@@ -0,0 +1,207 @@
|
|||||||
|
{
|
||||||
|
"schema": "ailang/v0",
|
||||||
|
"name": "list_map",
|
||||||
|
"imports": [],
|
||||||
|
"defs": [
|
||||||
|
{
|
||||||
|
"kind": "type",
|
||||||
|
"name": "IntList",
|
||||||
|
"doc": "Singly-linked list of Int, boxed.",
|
||||||
|
"ctors": [
|
||||||
|
{ "name": "Nil", "fields": [] },
|
||||||
|
{
|
||||||
|
"name": "Cons",
|
||||||
|
"fields": [
|
||||||
|
{ "k": "con", "name": "Int" },
|
||||||
|
{ "k": "con", "name": "IntList" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "fn",
|
||||||
|
"name": "map_int",
|
||||||
|
"type": {
|
||||||
|
"k": "fn",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"k": "fn",
|
||||||
|
"params": [{ "k": "con", "name": "Int" }],
|
||||||
|
"ret": { "k": "con", "name": "Int" },
|
||||||
|
"effects": []
|
||||||
|
},
|
||||||
|
{ "k": "con", "name": "IntList" }
|
||||||
|
],
|
||||||
|
"ret": { "k": "con", "name": "IntList" },
|
||||||
|
"effects": []
|
||||||
|
},
|
||||||
|
"params": ["f", "xs"],
|
||||||
|
"doc": "Apply f to every element. Recursive on the tail.",
|
||||||
|
"body": {
|
||||||
|
"t": "match",
|
||||||
|
"scrutinee": { "t": "var", "name": "xs" },
|
||||||
|
"arms": [
|
||||||
|
{
|
||||||
|
"pat": { "p": "ctor", "ctor": "Nil", "fields": [] },
|
||||||
|
"body": {
|
||||||
|
"t": "ctor",
|
||||||
|
"type": "IntList",
|
||||||
|
"ctor": "Nil",
|
||||||
|
"args": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pat": {
|
||||||
|
"p": "ctor",
|
||||||
|
"ctor": "Cons",
|
||||||
|
"fields": [
|
||||||
|
{ "p": "var", "name": "h" },
|
||||||
|
{ "p": "var", "name": "t" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"t": "ctor",
|
||||||
|
"type": "IntList",
|
||||||
|
"ctor": "Cons",
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"t": "app",
|
||||||
|
"fn": { "t": "var", "name": "f" },
|
||||||
|
"args": [{ "t": "var", "name": "h" }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"t": "app",
|
||||||
|
"fn": { "t": "var", "name": "map_int" },
|
||||||
|
"args": [
|
||||||
|
{ "t": "var", "name": "f" },
|
||||||
|
{ "t": "var", "name": "t" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "fn",
|
||||||
|
"name": "print_list",
|
||||||
|
"type": {
|
||||||
|
"k": "fn",
|
||||||
|
"params": [{ "k": "con", "name": "IntList" }],
|
||||||
|
"ret": { "k": "con", "name": "Unit" },
|
||||||
|
"effects": ["IO"]
|
||||||
|
},
|
||||||
|
"params": ["xs"],
|
||||||
|
"doc": "Print each Int on its own line.",
|
||||||
|
"body": {
|
||||||
|
"t": "match",
|
||||||
|
"scrutinee": { "t": "var", "name": "xs" },
|
||||||
|
"arms": [
|
||||||
|
{
|
||||||
|
"pat": { "p": "ctor", "ctor": "Nil", "fields": [] },
|
||||||
|
"body": { "t": "lit", "lit": { "kind": "unit" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pat": {
|
||||||
|
"p": "ctor",
|
||||||
|
"ctor": "Cons",
|
||||||
|
"fields": [
|
||||||
|
{ "p": "var", "name": "h" },
|
||||||
|
{ "p": "var", "name": "t" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"t": "let",
|
||||||
|
"name": "_print_h",
|
||||||
|
"value": {
|
||||||
|
"t": "do",
|
||||||
|
"op": "io/print_int",
|
||||||
|
"args": [{ "t": "var", "name": "h" }]
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"t": "app",
|
||||||
|
"fn": { "t": "var", "name": "print_list" },
|
||||||
|
"args": [{ "t": "var", "name": "t" }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "fn",
|
||||||
|
"name": "main",
|
||||||
|
"type": {
|
||||||
|
"k": "fn",
|
||||||
|
"params": [],
|
||||||
|
"ret": { "k": "con", "name": "Unit" },
|
||||||
|
"effects": ["IO"]
|
||||||
|
},
|
||||||
|
"params": [],
|
||||||
|
"doc": "Build [1,2,3], double each, print result.",
|
||||||
|
"body": {
|
||||||
|
"t": "let",
|
||||||
|
"name": "xs",
|
||||||
|
"value": {
|
||||||
|
"t": "ctor",
|
||||||
|
"type": "IntList",
|
||||||
|
"ctor": "Cons",
|
||||||
|
"args": [
|
||||||
|
{ "t": "lit", "lit": { "kind": "int", "value": 1 } },
|
||||||
|
{
|
||||||
|
"t": "ctor",
|
||||||
|
"type": "IntList",
|
||||||
|
"ctor": "Cons",
|
||||||
|
"args": [
|
||||||
|
{ "t": "lit", "lit": { "kind": "int", "value": 2 } },
|
||||||
|
{
|
||||||
|
"t": "ctor",
|
||||||
|
"type": "IntList",
|
||||||
|
"ctor": "Cons",
|
||||||
|
"args": [
|
||||||
|
{ "t": "lit", "lit": { "kind": "int", "value": 3 } },
|
||||||
|
{
|
||||||
|
"t": "ctor",
|
||||||
|
"type": "IntList",
|
||||||
|
"ctor": "Nil",
|
||||||
|
"args": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"t": "app",
|
||||||
|
"fn": { "t": "var", "name": "print_list" },
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"t": "app",
|
||||||
|
"fn": { "t": "var", "name": "map_int" },
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"t": "lam",
|
||||||
|
"params": ["x"],
|
||||||
|
"paramTypes": [{ "k": "con", "name": "Int" }],
|
||||||
|
"retType": { "k": "con", "name": "Int" },
|
||||||
|
"effects": [],
|
||||||
|
"body": {
|
||||||
|
"t": "app",
|
||||||
|
"fn": { "t": "var", "name": "*" },
|
||||||
|
"args": [
|
||||||
|
{ "t": "var", "name": "x" },
|
||||||
|
{ "t": "lit", "lit": { "kind": "int", "value": 2 } }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ "t": "var", "name": "xs" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user