Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions examples/mget-default.ilo
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
-- mget m k ?? d — one-line extract-with-default for map lookup.
--
-- `mget` returns an Optional (nil if the key is missing). Combined with
-- `??` you get a terse default in a single statement:
--
-- v = mget m k ?? 0
--
-- The parser is arity-aware: `mget` takes exactly 2 args, so `??` binds
-- to the whole `(mget m k)` result, not to the key. No need to bind the
-- raw lookup into a temporary first. Works for literal keys, variable
-- keys, path-access keys, call-result keys, parenthesised keys, and any
-- value type (numbers, text, lists, maps).

-- Literal key, value present.
hit>n;m=mset mmap "k" 5;mget m "k" ?? 0

-- Literal key, value missing — default applies.
miss>n;m=mmap;mget m "missing" ?? 99

-- Variable key, value present.
varkey>n;m=mset mmap "k" 5;k="k";mget m k ?? 0

-- Variable key, missing — default applies.
varkey-miss>n;m=mmap;k="missing";mget m k ?? 7

-- Text-typed value with a text default.
textval>t;m=mset mmap "k" "hi";mget m "k" ?? "default"

-- Text default reached when the key is absent.
textval-miss>t;m=mmap;mget m "absent" ?? "default"

-- Path-access key — `ks.0` is evaluated as the key, then `?? 0` applies
-- to the whole `mget` result.
pathkey>n;m=mset mmap "k" 5;ks=["k"];mget m ks.0 ?? 0

-- run: hit
-- out: 5
-- run: miss
-- out: 99
-- run: varkey
-- out: 5
-- run: varkey-miss
-- out: 7
-- run: textval
-- out: hi
-- run: textval-miss
-- out: default
-- run: pathkey
-- out: 5
132 changes: 132 additions & 0 deletions tests/regression_mget_default.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Regression coverage for `mget m k ?? d` — one-line map-lookup with default.
//
// Older persona reports (lines 699, 705, 926 of ilo_assessment_feedback.md)
// claimed `mget m k??0` parsed as `mget(m, k??0)`, forcing a two-step
// `r=mget m k;v=r??0` bind. The arity-aware call parser landed in 06477c5
// fixed this — `mget` is registered with fixed arity 2, so the parser
// stops consuming args after the key and `??` falls out as infix
// nil-coalesce on the whole call result.
//
// These tests pin that behaviour across every engine (tree, VM,
// Cranelift) and across the variants personas actually wrote: literal
// key, variable key, path-access key, call-result key, parenthesised
// key, with both numeric and text defaults.

use std::process::Command;

fn ilo() -> Command {
Command::new(env!("CARGO_BIN_EXE_ilo"))
}

fn run_file(engine: &str, src: &str, entry: &str) -> String {
use std::sync::atomic::{AtomicU64, Ordering};
static COUNTER: AtomicU64 = AtomicU64::new(0);
let seq = COUNTER.fetch_add(1, Ordering::SeqCst);
let path = std::env::temp_dir().join(format!(
"ilo_mget_default_{}_{}.ilo",
std::process::id(),
seq
));
std::fs::write(&path, src).unwrap();
let out = ilo()
.args([path.to_str().unwrap(), engine, entry])
.output()
.expect("failed to run ilo");
assert!(
out.status.success(),
"ilo {engine} failed for `{src}`: stderr={}",
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8_lossy(&out.stdout).trim().to_string()
}

// Literal key, value present — `(mget m "k") ?? 0` → 5.
const LITERAL_HIT: &str = r#"f>n;m=mset mmap "k" 5;mget m "k" ?? 0"#;
// Literal key, value missing — `(mget m "missing") ?? 99` → 99.
const LITERAL_MISS: &str = r#"f>n;m=mmap;mget m "missing" ?? 99"#;
// Variable key, value present.
const VARKEY_HIT: &str = r#"f>n;m=mset mmap "k" 5;k="k";mget m k ?? 0"#;
// Variable key, value missing (the exact shape from persona line 699).
const VARKEY_MISS: &str = r#"f>n;em=mmap;k="x";mget em k ?? 0"#;
// Text-typed default, text value present.
const TEXT_HIT: &str = r#"f>t;m=mset mmap "k" "hi";mget m "k" ?? "default""#;
// Text default reached on missing key — confirms `??` typing flows
// through `mget`'s `O T` return.
const TEXT_MISS: &str = r#"f>t;m=mmap;mget m "absent" ?? "default""#;
// Path-access key (`ks.0`) — confirms the parser doesn't greedily
// swallow `.0` or `?? 0` into the key expression.
const PATHKEY_HIT: &str = r#"f>n;m=mset mmap "k" 5;ks=["k"];mget m ks.0 ?? 0"#;
// Call-result key (`str 5`) — confirms the parser stops the key
// expression at mget's second argument boundary, not at `??`.
const CALLKEY_HIT: &str = r#"f>n;m=mset mmap "5" 7;mget m str 5 ?? 0"#;
// Parenthesised key — defensive lower bound on the precedence.
const PARENKEY_HIT: &str = r#"f>n;m=mset mmap "5" 7;mget m (str 5) ?? 0"#;
// Note: bare-chained `mget m "a" ?? mget m "b" ?? 99` does NOT parse
// today — the arity-aware call parser doesn't extend through a `??`
// boundary, so the second `mget` slurps `m "b" ?? 99` as three args.
// Bind each lookup into a temp first (`a=mget m "a";b=mget m "b";a??b??99`)
// or parenthesise. Logged as a separate adjacent finding.

fn check_all(engine: &str) {
assert_eq!(
run_file(engine, LITERAL_HIT, "f"),
"5",
"literal hit engine={engine}"
);
assert_eq!(
run_file(engine, LITERAL_MISS, "f"),
"99",
"literal miss engine={engine}"
);
assert_eq!(
run_file(engine, VARKEY_HIT, "f"),
"5",
"varkey hit engine={engine}"
);
assert_eq!(
run_file(engine, VARKEY_MISS, "f"),
"0",
"varkey miss engine={engine}"
);
assert_eq!(
run_file(engine, TEXT_HIT, "f"),
"hi",
"text hit engine={engine}"
);
assert_eq!(
run_file(engine, TEXT_MISS, "f"),
"default",
"text miss engine={engine}"
);
assert_eq!(
run_file(engine, PATHKEY_HIT, "f"),
"5",
"pathkey hit engine={engine}"
);
assert_eq!(
run_file(engine, CALLKEY_HIT, "f"),
"7",
"callkey hit engine={engine}"
);
assert_eq!(
run_file(engine, PARENKEY_HIT, "f"),
"7",
"parenkey hit engine={engine}"
);
}

#[test]
fn mget_default_tree() {
check_all("--run-tree");
}

#[test]
fn mget_default_vm() {
check_all("--run-vm");
}

#[test]
#[cfg(feature = "cranelift")]
fn mget_default_cranelift() {
check_all("--run-cranelift");
}
Loading