use std::collections::HashMap;
use std::sync::Arc;
use relon_codegen_cranelift::AotEvaluator;
use relon_codegen_llvm::LlvmAotEvaluator;
use relon_eval_api::{Evaluator, Value};
use relon_evaluator::{Context, Scope, TreeWalkEvaluator};
use relon_ir::ir::{Op, TaggedOp};
use relon_parser::parse_document;
const SRC: &str = "#schema R { String result: * }\n\
#main(Int i) -> R\n\
{ result: (keys[i] where { keys: [\"a\",\"b\",\"c\",\"d\",\"e\"] }) }";
fn flatten_ops(src: &str) -> Vec<Op> {
let options = relon_analyzer::AnalyzeOptions {
strict_mode: false,
..Default::default()
};
let lowered = relon_ir::compile(src, &options).expect("frontend compile");
let mut out = Vec::new();
for func in &lowered.module.funcs {
collect(&func.body, &mut out);
}
out
}
fn collect(body: &[TaggedOp], out: &mut Vec<Op>) {
for t in body {
out.push(t.op.clone());
match &t.op {
Op::Block { body, .. } | Op::Loop { body, .. } => collect(body, out),
Op::If {
then_body,
else_body,
..
} => {
collect(then_body, out);
collect(else_body, out);
}
_ => {}
}
}
}
fn result_of(v: &Value) -> String {
let dict = match v {
Value::Dict(d) => d,
other => panic!("expected schema Dict return, got {other:?}"),
};
match dict.map.get("result").expect("return dict has `result`") {
Value::String(s) => s.to_string(),
other => panic!("expected String `result`, got {other:?}"),
}
}
fn tree_walk(i: i64) -> String {
let node = parse_document(SRC).expect("parse SRC");
let analyzed = Arc::new(relon_analyzer::analyze(&node));
let mut ctx = Context::new()
.with_root(node)
.with_analyzed(Arc::clone(&analyzed));
TreeWalkEvaluator::prepare_in_place(&mut ctx);
let walker = TreeWalkEvaluator::new(Arc::new(ctx));
let scope = Arc::new(Scope::default());
let mut args = HashMap::new();
args.insert("i".to_string(), Value::Int(i));
result_of(&walker.run_main(&scope, args).expect("tree-walk run_main"))
}
#[test]
fn const_list_string_index_lowers_to_expected_ir() {
let ops = flatten_ops(SRC);
assert!(
ops.iter().any(|o| matches!(o, Op::ConstListString { .. })),
"SRC must lower to Op::ConstListString; ops:\n{ops:#?}"
);
assert!(
ops.iter()
.any(|o| matches!(o, Op::LoadI32AtAbsolute { .. })),
"index must load the String handle via LoadI32AtAbsolute; ops:\n{ops:#?}"
);
assert!(
ops.iter().any(|o| matches!(
o,
Op::EmitTailRecordFromAbsoluteAddr {
ty: relon_ir::ir::IrType::String
}
)),
"String return must copy via EmitTailRecordFromAbsoluteAddr{{String}}; ops:\n{ops:#?}"
);
assert!(
!ops.iter().any(|o| matches!(o, Op::ListGetByIntIdx { .. })),
"static codegen must NOT emit Op::ListGetByIntIdx; ops:\n{ops:#?}"
);
}
#[test]
fn const_list_string_codegen_parity() {
AotEvaluator::from_source(SRC)
.unwrap_or_else(|e| panic!("cranelift golden must compile SRC:\n{SRC}\nerr: {e:?}"));
LlvmAotEvaluator::from_source(SRC)
.unwrap_or_else(|e| panic!("llvm backend must compile SRC:\n{SRC}\nerr: {e:?}"));
}
#[test]
fn const_list_string_index_three_way() {
let llvm = LlvmAotEvaluator::from_source(SRC).expect("llvm compiles");
let cl = AotEvaluator::from_source(SRC).expect("cranelift compiles");
for (i, want) in [(0, "a"), (1, "b"), (2, "c"), (3, "d"), (4, "e")] {
let oracle = tree_walk(i);
assert_eq!(oracle, want, "tree-walk oracle sanity at i={i}");
let mut a = HashMap::new();
a.insert("i".to_string(), Value::Int(i));
let got_llvm = result_of(&llvm.run_main(a.clone()).expect("llvm run_main"));
let got_cl = result_of(&cl.run_main(a).expect("cranelift run_main"));
assert_eq!(
got_cl, oracle,
"cranelift List<String> index diverged at i={i}"
);
assert_eq!(
got_llvm, oracle,
"llvm List<String> index diverged at i={i}"
);
}
}