use std::sync::Arc;
use cljrs_ir::lower::lower_fn_body_destructured;
use cljrs_ir::{Inst, IrFunction};
use cljrs_reader::{Form, Parser};
use cljrs_runtime::tiered::{Env, ir_interp::interpret_ir};
use cljrs_value::{PersistentVector, Value};
fn parse_one(src: &str) -> Form {
let mut p = Parser::new(src.to_string(), "<test>".to_string());
p.parse_all()
.expect("parse")
.into_iter()
.next()
.expect("one form")
}
fn parse_body(src: &str) -> Vec<Form> {
let mut p = Parser::new(src.to_string(), "<test>".to_string());
p.parse_all().expect("parse")
}
fn run_destructured(pattern_src: &str, body_src: &str, arg: Value) -> Value {
let _mutator = cljrs_gc::register_mutator();
let pattern = parse_one(pattern_src);
let params: Vec<Arc<str>> = vec![Arc::from("__destructure_0")];
let destructures: Vec<(usize, Form)> = vec![(0, pattern)];
let body = parse_body(body_src);
let ir: IrFunction =
lower_fn_body_destructured(Some("test"), "user", ¶ms, &destructures, &body, false)
.expect("lower");
assert!(
!mentions_load_global(&ir),
"destructured body lowered to a LoadGlobal — pattern names did not bind"
);
let globals = cljrs_runtime::Runtime::builder()
.execution_mode(cljrs_runtime::ExecutionMode::TreeWalk)
.build()
.expect("runtime")
.into_globals();
let mut env = Env::new(globals.clone(), "user");
let ns: Arc<str> = Arc::from("user");
cljrs_runtime::env::callback::push_eval_context(&env);
let result = interpret_ir(&ir, vec![arg], &globals, &ns, &mut env);
cljrs_runtime::env::callback::pop_eval_context();
result.expect("interpret")
}
fn mentions_load_global(ir: &IrFunction) -> bool {
for block in &ir.blocks {
for inst in &block.insts {
if matches!(inst, Inst::LoadGlobal(..)) {
return true;
}
}
}
ir.subfunctions.iter().any(mentions_load_global)
}
fn vec_of(items: impl IntoIterator<Item = Value>) -> Value {
Value::Vector(cljrs_gc::GcPtr::new(PersistentVector::from_iter(items)))
}
fn map_of(pairs: impl IntoIterator<Item = (Value, Value)>) -> Value {
let mut m = cljrs_value::MapValue::empty();
for (k, v) in pairs {
m = m.assoc(k, v);
}
Value::Map(m)
}
#[test]
fn sequential_destructure_binds_first_element() {
let got = run_destructured("[a b]", "a", vec_of([Value::Long(10), Value::Long(3)]));
assert_eq!(got, Value::Long(10));
}
#[test]
fn sequential_destructure_binds_second_element() {
let got = run_destructured("[a b]", "b", vec_of([Value::Long(10), Value::Long(3)]));
assert_eq!(got, Value::Long(3));
}
#[test]
fn keys_destructure_binds_namespaced_symbol() {
let got = run_destructured(
"{:keys [ui/dest]}",
"dest",
map_of([(
Value::keyword(cljrs_value::Keyword::qualified("ui", "dest")),
Value::Long(2),
)]),
);
assert_eq!(got, Value::Long(2));
}
#[test]
fn keys_destructure_binds_namespaced_directive() {
let got = run_destructured(
"{:ui/keys [dest]}",
"dest",
map_of([(
Value::keyword(cljrs_value::Keyword::qualified("ui", "dest")),
Value::Long(2),
)]),
);
assert_eq!(got, Value::Long(2));
}
#[test]
fn sequential_destructure_with_rest_and_as() {
let got = run_destructured(
"[a & more :as all]",
"more",
vec_of([Value::Long(1), Value::Long(2), Value::Long(3)]),
);
match got {
Value::List(_) | Value::Cons(_) | Value::LazySeq(_) | Value::Vector(_) => {}
other => panic!("expected a rest sequence, got {other:?}"),
}
}