1use lex_ast as a;
40use lex_bytecode::{compile_program, vm::Vm, Value};
41use crate::handler::DefaultHandler;
42use crate::policy::Policy;
43use lex_types::TypeError;
44
45pub fn evaluate_examples(stages: &[a::Stage]) -> Vec<TypeError> {
54 let helpers = synthesize_helpers(stages);
55 if helpers.cases.is_empty() {
56 return Vec::new();
57 }
58
59 let mut augmented: Vec<a::Stage> = stages.to_vec();
60 augmented.extend(helpers.helper_stages);
61
62 let bc = compile_program(&augmented);
63 let bc = std::sync::Arc::new(bc);
64
65 let mut out = Vec::new();
66 for case in &helpers.cases {
67 match run_case(&bc, case) {
68 CaseOutcome::Pass => {}
69 CaseOutcome::Mismatch { expected, got } => {
70 out.push(TypeError::ExampleMismatch {
71 at_node: "n_0".into(),
72 fn_name: case.fn_name.clone(),
73 case_index: case.case_index,
74 expected,
75 got,
76 });
77 }
78 CaseOutcome::RuntimeError(msg) => {
79 out.push(TypeError::ExampleMismatch {
83 at_node: "n_0".into(),
84 fn_name: case.fn_name.clone(),
85 case_index: case.case_index,
86 expected: "(declared value)".into(),
87 got: format!("runtime error: {msg}"),
88 });
89 }
90 }
91 }
92 out
93}
94
95struct Helpers {
96 helper_stages: Vec<a::Stage>,
97 cases: Vec<Case>,
98}
99
100struct Case {
101 fn_name: String,
102 case_index: usize,
103 arg_helpers: Vec<String>,
104 expected_helper: String,
105}
106
107fn synthesize_helpers(stages: &[a::Stage]) -> Helpers {
108 let mut helper_stages = Vec::new();
109 let mut cases = Vec::new();
110
111 for stage in stages {
112 let a::Stage::FnDecl(fd) = stage else { continue };
113 if fd.examples.is_empty() {
114 continue;
115 }
116 if !fd.type_params.is_empty() {
119 continue;
120 }
121 if !fd.effects.is_empty() {
125 continue;
126 }
127 for (k, ex) in fd.examples.iter().enumerate() {
128 let mut arg_helpers = Vec::with_capacity(ex.args.len());
129 for (i, arg) in ex.args.iter().enumerate() {
130 let helper_name = format!("__ex_{}_{}_arg_{}", fd.name, k, i);
131 helper_stages.push(zero_arg_helper(&helper_name, fd.params[i].ty.clone(), arg.clone()));
132 arg_helpers.push(helper_name);
133 }
134 let expected_helper = format!("__ex_{}_{}_expected", fd.name, k);
135 helper_stages.push(zero_arg_helper(
136 &expected_helper,
137 fd.return_type.clone(),
138 ex.expected.clone(),
139 ));
140 cases.push(Case {
141 fn_name: fd.name.clone(),
142 case_index: k,
143 arg_helpers,
144 expected_helper,
145 });
146 }
147 }
148
149 Helpers { helper_stages, cases }
150}
151
152fn zero_arg_helper(name: &str, return_type: a::TypeExpr, body: a::CExpr) -> a::Stage {
153 a::Stage::FnDecl(a::FnDecl {
154 name: name.into(),
155 type_params: Vec::new(),
156 params: Vec::new(),
157 effects: Vec::new(),
158 effect_row_var: None,
159 return_type,
160 body,
161 examples: Vec::new(),
162 })
163}
164
165enum CaseOutcome {
166 Pass,
167 Mismatch { expected: String, got: String },
168 RuntimeError(String),
169}
170
171fn run_case(bc: &std::sync::Arc<lex_bytecode::Program>, case: &Case) -> CaseOutcome {
172 let mut arg_values: Vec<Value> = Vec::with_capacity(case.arg_helpers.len());
175 for helper in &case.arg_helpers {
176 match call_zero_arg(bc, helper) {
177 Ok(v) => arg_values.push(v),
178 Err(e) => return CaseOutcome::RuntimeError(format!("computing arg from `{helper}`: {e}")),
179 }
180 }
181 let expected = match call_zero_arg(bc, &case.expected_helper) {
182 Ok(v) => v,
183 Err(e) => return CaseOutcome::RuntimeError(format!("computing expected from `{}`: {e}", case.expected_helper)),
184 };
185 let got = match call_with_args(bc, &case.fn_name, arg_values) {
186 Ok(v) => v,
187 Err(e) => return CaseOutcome::RuntimeError(format!("calling `{}`: {e}", case.fn_name)),
188 };
189 if expected == got {
190 CaseOutcome::Pass
191 } else {
192 CaseOutcome::Mismatch {
193 expected: pretty_value(&expected),
194 got: pretty_value(&got),
195 }
196 }
197}
198
199fn call_zero_arg(bc: &std::sync::Arc<lex_bytecode::Program>, name: &str) -> Result<Value, String> {
200 call_with_args(bc, name, Vec::new())
201}
202
203fn call_with_args(
204 bc: &std::sync::Arc<lex_bytecode::Program>,
205 name: &str,
206 args: Vec<Value>,
207) -> Result<Value, String> {
208 let handler = DefaultHandler::new(Policy::pure()).with_program(std::sync::Arc::clone(bc));
209 let mut vm = Vm::with_handler(bc, Box::new(handler));
210 vm.set_step_limit(1_000_000);
212 vm.call(name, args).map_err(|e| format!("{e:?}"))
213}
214
215fn pretty_value(v: &Value) -> String {
220 match v {
221 Value::Int(n) => n.to_string(),
222 Value::Float(f) => f.to_string(),
223 Value::Bool(b) => b.to_string(),
224 Value::Str(s) => format!("{s:?}"),
225 Value::Unit => "()".into(),
226 Value::List(xs) => format!(
227 "[{}]",
228 xs.iter().map(pretty_value).collect::<Vec<_>>().join(", ")
229 ),
230 Value::Tuple(xs) => format!(
231 "({})",
232 xs.iter().map(pretty_value).collect::<Vec<_>>().join(", ")
233 ),
234 Value::Variant { name, args } if args.is_empty() => name.clone(),
235 Value::Variant { name, args } => format!(
236 "{}({})",
237 name,
238 args.iter().map(pretty_value).collect::<Vec<_>>().join(", ")
239 ),
240 Value::Record { fields: fs, .. } => format!(
241 "{{ {} }}",
242 fs.iter()
243 .map(|(k, v)| format!("{k}: {}", pretty_value(v)))
244 .collect::<Vec<_>>()
245 .join(", ")
246 ),
247 other => format!("{other:?}"),
248 }
249}