From 9764b616ce2ea62b9b14ad5435181794b062f651 Mon Sep 17 00:00:00 2001 From: Brummel Date: Sun, 10 May 2026 16:25:25 +0200 Subject: [PATCH] floats iter 4.6: codegen io/print_float + examples/floats.ail.json E2E fixture --- crates/ail/tests/floats_e2e.rs | 69 ++++++++++++++++++++++++++++++++ crates/ailang-codegen/src/lib.rs | 61 ++++++++++++++++++++++++++++ examples/floats.ail.json | 64 +++++++++++++++++++++++++++++ 3 files changed, 194 insertions(+) create mode 100644 crates/ail/tests/floats_e2e.rs create mode 100644 examples/floats.ail.json diff --git a/crates/ail/tests/floats_e2e.rs b/crates/ail/tests/floats_e2e.rs new file mode 100644 index 0000000..e7fb3bc --- /dev/null +++ b/crates/ail/tests/floats_e2e.rs @@ -0,0 +1,69 @@ +//! Floats milestone iter 4.6 — end-to-end fixture. +//! +//! `examples/floats.ail.json` is built via the public `ail build` +//! CLI and run; stdout is matched against the expected three +//! lines (`4`, `42`, `-1.5`). This test exercises the full +//! pipeline — Float literal lowering, polymorphic `+` Float arm, +//! `int_to_float` (sitofp), `neg` Float (fneg), `io/print_float` +//! (printf `%g\n`). + +use std::path::PathBuf; +use std::process::Command; + +fn workspace_root() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent().unwrap() + .parent().unwrap() + .to_path_buf() +} + +#[test] +fn floats_example_prints_expected_stdout() { + let root = workspace_root(); + let example = root.join("examples").join("floats.ail.json"); + assert!(example.exists(), "expected fixture at {:?}", example); + + // Build via `cargo run -p ail -- build` to use the in-tree CLI. + let target_bin = std::env::temp_dir().join(format!( + "ailang-floats-e2e-{}", + std::process::id() + )); + + let build = Command::new("cargo") + .current_dir(&root) + .args([ + "run", "--quiet", "-p", "ail", "--", + "build", + example.to_str().unwrap(), + "-o", target_bin.to_str().unwrap(), + ]) + .output() + .expect("cargo run -p ail -- build"); + assert!( + build.status.success(), + "ail build failed:\nstdout: {}\nstderr: {}", + String::from_utf8_lossy(&build.stdout), + String::from_utf8_lossy(&build.stderr), + ); + + let run = Command::new(&target_bin) + .output() + .expect("run floats binary"); + assert!( + run.status.success(), + "floats binary exited non-zero: status {:?}\nstdout: {}\nstderr: {}", + run.status, + String::from_utf8_lossy(&run.stdout), + String::from_utf8_lossy(&run.stderr), + ); + + let stdout = String::from_utf8_lossy(&run.stdout); + let expected = "4\n42\n-1.5\n"; + assert_eq!( + stdout, expected, + "stdout mismatch.\nGot:\n{}\nExpected:\n{}", + stdout, expected, + ); + + let _ = std::fs::remove_file(&target_bin); +} diff --git a/crates/ailang-codegen/src/lib.rs b/crates/ailang-codegen/src/lib.rs index 44d21f4..ee0dc8d 100644 --- a/crates/ailang-codegen/src/lib.rs +++ b/crates/ailang-codegen/src/lib.rs @@ -2294,6 +2294,28 @@ impl<'a> Emitter<'a> { } Ok(("0".into(), "i8".into())) } + "io/print_float" => { + if args.len() != 1 { + return Err(CodegenError::Internal( + "io/print_float arity".into(), + )); + } + let (v, vty) = self.lower_term(&args[0])?; + if vty != "double" { + return Err(CodegenError::Internal( + "io/print_float needs double".into(), + )); + } + let fmt = self.intern_string("fmt_float", "%g\n"); + self.body.push_str(&format!( + " {call_kw} i32 (ptr, ...) @printf(ptr @{fmt}, double {v})\n" + )); + if tail { + self.body.push_str(" ret i8 0\n"); + self.block_terminated = true; + } + Ok(("0".into(), "i8".into())) + } "io/print_str" => { if args.len() != 1 { return Err(CodegenError::Internal( @@ -3269,4 +3291,43 @@ mod tests { assert!(ir.contains("0x7FF0000000000000"), "+inf bit pattern missing: {ir}"); assert!(ir.contains("0xFFF0000000000000"), "-inf bit pattern missing: {ir}"); } + + /// Floats iter 4.6 RED: `(do io/print_float 1.5)` lowers via + /// `printf("%g\n", v)`, parallel to `io/print_int` at line 2152. + #[test] + fn lowers_io_print_float() { + use ailang_core::ast::*; + let body = Term::Do { + op: "io/print_float".into(), + args: vec![Term::Lit { lit: Literal::Float { bits: 0x3ff8_0000_0000_0000u64 } }], + tail: false, + }; + let m = Module { + schema: ailang_core::SCHEMA.to_string(), + name: "t".into(), + imports: vec![], + defs: vec![Def::Fn(FnDef { + name: "main".into(), + ty: Type::Fn { + params: vec![], ret: Box::new(Type::unit()), effects: vec!["IO".into()], + param_modes: vec![], ret_mode: ParamMode::Implicit, + }, + params: vec![], body, suppress: vec![], doc: None, + })], + }; + let ir = emit_ir(&m).unwrap(); + assert!( + ir.contains("call i32 (ptr, ...) @printf"), + "io/print_float not emitting printf: {ir}" + ); + assert!( + ir.contains("double 0x3FF8000000000000"), + "Float arg not threaded through io/print_float: {ir}" + ); + // Verify the format string `%g\n` is interned. + assert!( + ir.contains("%g") || ir.contains("\\67"), // `g` ASCII = 67 = 0x47 + "format string `%g\\n` not interned: {ir}" + ); + } } diff --git a/examples/floats.ail.json b/examples/floats.ail.json new file mode 100644 index 0000000..598bc9a --- /dev/null +++ b/examples/floats.ail.json @@ -0,0 +1,64 @@ +{ + "schema": "ailang/v0", + "name": "floats", + "imports": [], + "defs": [ + { + "kind": "fn", + "name": "main", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_float", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { "t": "lit", "lit": { "kind": "float", "bits": "3ff8000000000000" } }, + { "t": "lit", "lit": { "kind": "float", "bits": "4004000000000000" } } + ] + } + ] + }, + "rhs": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_float", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "int_to_float" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 42 } } + ] + } + ] + }, + "rhs": { + "t": "do", + "op": "io/print_float", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "neg" }, + "args": [ + { "t": "lit", "lit": { "kind": "float", "bits": "3ff8000000000000" } } + ] + } + ] + } + } + } + } + ] +}