1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
// Cross-engine regression tests for the `mapr` HOF (short-circuiting
// Result-aware map). Pins behaviour across tree / VM / Cranelift, mirroring
// the regression_hof_3b.rs shape used for the grp/uniqby/partition/srt
// tree-bridge family.
//
// `mapr fn xs` calls fn on each element, accumulates the inner Ok values
// on the all-Ok path (returning `~(L b)`), and short-circuits on the first
// Err (returning `^e` without visiting the tail). VM and Cranelift route
// through the tree-bridge (`is_tree_bridge_eligible(Mapr, 2)`), so the
// callback dispatch is the same code path as grp/uniqby/partition/srt.
//
// Originating friction: ilo_assessment_feedback.md line 2541 (html-scraper
// rerun3, persona kept writing a `ton s:t>n;r=num s;?r{~v:v;^_:0}` helper
// because `map num xs` returns `L (R n t)` with no clean unwrap path).
use std::process::Command;
const ENGINES: &[&str] = &["--vm", "--jit"];
fn ilo() -> Command {
Command::new(env!("CARGO_BIN_EXE_ilo"))
}
fn run_ok(src: &str, engine: &str, entry: &str, extra: &[&str]) -> String {
let mut cmd = ilo();
cmd.arg(src).arg(engine).arg(entry);
for a in extra {
cmd.arg(a);
}
let out = cmd.output().expect("failed to run ilo");
assert!(
out.status.success(),
"ilo {engine} failed for `{src}` entry `{entry}`: stderr={}",
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8_lossy(&out.stdout).trim().to_string()
}
fn run_err(src: &str, engine: &str, entry: &str, extra: &[&str]) -> String {
let mut cmd = ilo();
cmd.arg(src).arg(engine).arg(entry);
for a in extra {
cmd.arg(a);
}
let out = cmd.output().expect("failed to run ilo");
assert!(
!out.status.success(),
"ilo {engine} unexpectedly succeeded for `{src}` entry `{entry}`: stdout={}",
String::from_utf8_lossy(&out.stdout)
);
String::from_utf8_lossy(&out.stderr).into_owned()
}
fn check_ok(src: &str, entry: &str, extra: &[&str], expected: &str) {
for engine in ENGINES {
let actual = run_ok(src, engine, entry, extra);
assert_eq!(
actual, expected,
"engine={engine}, src=`{src}`, entry=`{entry}` extra={extra:?}: got `{actual}`, expected `{expected}`"
);
}
}
fn check_err_contains(src: &str, entry: &str, extra: &[&str], needle: &str) {
for engine in ENGINES {
let stderr = run_err(src, engine, entry, extra);
assert!(
stderr.contains(needle),
"engine={engine}: stderr missing `{needle}`: {stderr}"
);
}
}
// ── happy path: every string parses cleanly, returns ~[1,2,3] ─────────────
#[test]
fn mapr_all_ok_returns_inner_list() {
check_ok(
r#"f>R (L n) t;mapr num ["1","2","3"]"#,
"f",
&[],
"[1, 2, 3]",
);
}
// ── empty list: trivially ~[] ─────────────────────────────────────────────
#[test]
fn mapr_empty_list_returns_empty_ok() {
check_ok(r#"f>R (L n) t;mapr num []"#, "f", &[], "[]");
}
// ── err at head: short-circuit, never visit tail ─────────────────────────
#[test]
fn mapr_first_err_short_circuits() {
check_err_contains(r#"f>R (L n) t;mapr num ["bad","2","3"]"#, "f", &[], "^bad");
}
// ── err mid-list: same short-circuit, but the err comes from the middle ──
#[test]
fn mapr_mid_err_short_circuits() {
check_err_contains(r#"f>R (L n) t;mapr num ["1","bad","3"]"#, "f", &[], "^bad");
}
// ── err at tail: same shape ──────────────────────────────────────────────
#[test]
fn mapr_tail_err_short_circuits() {
check_err_contains(r#"f>R (L n) t;mapr num ["1","2","bad"]"#, "f", &[], "^bad");
}
// ── `!` auto-unwrap threads the err up into the caller ───────────────────
//
// Bare `mapr fn xs` returns R; pairing with `!` extracts the inner list
// inside the body, and the err short-circuit propagates out of the
// surrounding function (which must also return R for `!` to typecheck).
#[test]
fn mapr_bang_ok_propagates_to_caller() {
check_ok(
r#"count xs:L t>R n t;ns=mapr! num xs;~len ns"#,
"count",
&[r#"["1","2","3"]"#],
"3",
);
}
#[test]
fn mapr_bang_err_propagates_to_caller() {
check_err_contains(
r#"count xs:L t>R n t;ns=mapr! num xs;~len ns"#,
"count",
&[r#"["1","bad","3"]"#],
"^bad",
);
}
// ── user-defined fn returning R: dispatched through the tree-bridge ──────
//
// This is the load-bearing piece for VM and Cranelift: the user fn must be
// resolvable from the tree-bridge via ACTIVE_AST_PROGRAM (the same path
// grp/uniqby/partition/srt take in PR 3b).
#[test]
fn mapr_user_fn_dispatch_cross_engine() {
// safe-div returns ^"divzero" when the divisor is zero, else ~/100 d.
// mapr accumulates the Ok divisions; any zero in the list bails.
check_ok(
r#"sd d:n>R n t;=d 0 ^"divzero";~/100 d
f>R (L n) t;mapr sd [2,4,5]"#,
"f",
&[],
"[50, 25, 20]",
);
check_err_contains(
r#"sd d:n>R n t;=d 0 ^"divzero";~/100 d
f>R (L n) t;mapr sd [2,0,5]"#,
"f",
&[],
"^divzero",
);
}
// ── verifier rejects non-fn first arg ─────────────────────────────────────
#[test]
fn mapr_non_fn_first_arg_rejected_cross_engine() {
check_err_contains(
r#"f>R (L n) t;mapr 42 ["1"]"#,
"f",
&[],
"'mapr' first arg must be a function",
);
}
// ── verifier rejects fn that doesn't return Result ───────────────────────
//
// `mapr` is for fallible fns; reaching for it with `dbl x:n>n` is a sign
// the caller wanted plain `map`. Verifier catches it before runtime so the
// agent gets a clear redirect instead of a downstream type error.
#[test]
fn mapr_non_result_fn_rejected_cross_engine() {
check_err_contains(
r#"dbl x:n>n;*x 2
f xs:L n>R (L n) t;mapr dbl xs"#,
"f",
&["[1,2]"],
"'mapr' fn must return a Result",
);
}
// ── arity rejection: 1-arg or 3-arg mapr is not (yet) accepted ───────────
#[test]
fn mapr_wrong_arity_rejected() {
// 1 arg: missing list.
check_err_contains(r#"f>R (L n) t;mapr num"#, "f", &[], "mapr");
}