1use crate::env::error::{EvalError, EvalResult};
4use cljrs_gc::GcPtr;
5use cljrs_reader::{Form, FormKind};
6use cljrs_value::regex::Pattern;
7use cljrs_value::value::SetValue;
8use cljrs_value::{
9 Keyword, MapValue, PersistentHashSet, PersistentList, PersistentVector, Symbol, Value,
10};
11
12pub fn expand_anon_fn(body: &[Form], span: cljrs_types::span::Span) -> Form {
16 let mut max_pos: usize = 0;
17 let mut has_rest = false;
18 find_pct_refs(body, &mut max_pos, &mut has_rest);
19
20 let s = &span;
21 let mut params: Vec<Form> = (1..=max_pos)
22 .map(|i| Form::new(FormKind::Symbol(format!("p__{i}")), s.clone()))
23 .collect();
24 if has_rest {
25 params.push(Form::new(FormKind::Symbol("&".into()), s.clone()));
26 params.push(Form::new(FormKind::Symbol("rest__".into()), s.clone()));
27 }
28
29 let new_body = rewrite_pct_refs(body, s.clone());
30
31 let body_expr = Form::new(FormKind::List(new_body), s.clone());
34
35 Form::new(
36 FormKind::List(vec![
37 Form::new(FormKind::Symbol("fn*".into()), s.clone()),
38 Form::new(FormKind::Vector(params), s.clone()),
39 body_expr,
40 ]),
41 span,
42 )
43}
44
45fn find_pct_refs(forms: &[Form], max_pos: &mut usize, has_rest: &mut bool) {
46 for form in forms {
47 find_pct_refs_form(form, max_pos, has_rest);
48 }
49}
50
51fn find_pct_refs_form(form: &Form, max_pos: &mut usize, has_rest: &mut bool) {
52 match &form.kind {
53 FormKind::Symbol(s) if (s == "%" || s == "%1") && *max_pos < 1 => {
54 *max_pos = 1;
55 }
56 FormKind::Symbol(s) if s == "%&" => {
57 *has_rest = true;
58 }
59 FormKind::Symbol(s) if s.starts_with('%') => {
60 if let Ok(n) = s[1..].parse::<usize>()
61 && n > *max_pos
62 {
63 *max_pos = n;
64 }
65 }
66 FormKind::List(c) | FormKind::Vector(c) | FormKind::Set(c) | FormKind::Map(c) => {
67 find_pct_refs(c, max_pos, has_rest);
68 }
69 FormKind::Quote(inner)
73 | FormKind::SyntaxQuote(inner)
74 | FormKind::Unquote(inner)
75 | FormKind::UnquoteSplice(inner)
76 | FormKind::Deref(inner)
77 | FormKind::Var(inner)
78 | FormKind::TaggedLiteral(_, inner) => {
79 find_pct_refs_form(inner, max_pos, has_rest);
80 }
81 FormKind::Meta(meta, inner) => {
82 find_pct_refs_form(meta, max_pos, has_rest);
83 find_pct_refs_form(inner, max_pos, has_rest);
84 }
85 FormKind::ReaderCond { clauses, .. } => {
86 find_pct_refs(clauses, max_pos, has_rest);
87 }
88 _ => {}
89 }
90}
91
92fn rewrite_pct_refs(forms: &[Form], span: cljrs_types::span::Span) -> Vec<Form> {
93 forms
94 .iter()
95 .map(|f| rewrite_pct_form(f, span.clone()))
96 .collect()
97}
98
99fn rewrite_pct_form(form: &Form, span: cljrs_types::span::Span) -> Form {
100 match &form.kind {
101 FormKind::Symbol(s) if s == "%" || s == "%1" => {
102 Form::new(FormKind::Symbol("p__1".into()), span)
103 }
104 FormKind::Symbol(s) if s == "%&" => Form::new(FormKind::Symbol("rest__".into()), span),
105 FormKind::Symbol(s) if s.starts_with('%') => {
106 if let Ok(n) = s[1..].parse::<usize>() {
107 Form::new(FormKind::Symbol(format!("p__{n}")), span)
108 } else {
109 form.clone()
110 }
111 }
112 FormKind::List(c) => {
113 let rewritten = rewrite_pct_refs(c, span.clone());
114 Form::new(FormKind::List(rewritten), span)
115 }
116 FormKind::Vector(c) => {
117 let rewritten = rewrite_pct_refs(c, span.clone());
118 Form::new(FormKind::Vector(rewritten), span)
119 }
120 FormKind::Set(c) => {
121 let rewritten = rewrite_pct_refs(c, span.clone());
122 Form::new(FormKind::Set(rewritten), span)
123 }
124 FormKind::Map(c) => {
125 let rewritten = rewrite_pct_refs(c, span.clone());
126 Form::new(FormKind::Map(rewritten), span)
127 }
128 FormKind::Quote(inner) => Form::new(
129 FormKind::Quote(Box::new(rewrite_pct_form(inner, span.clone()))),
130 span,
131 ),
132 FormKind::SyntaxQuote(inner) => Form::new(
133 FormKind::SyntaxQuote(Box::new(rewrite_pct_form(inner, span.clone()))),
134 span,
135 ),
136 FormKind::Unquote(inner) => Form::new(
137 FormKind::Unquote(Box::new(rewrite_pct_form(inner, span.clone()))),
138 span,
139 ),
140 FormKind::UnquoteSplice(inner) => Form::new(
141 FormKind::UnquoteSplice(Box::new(rewrite_pct_form(inner, span.clone()))),
142 span,
143 ),
144 FormKind::Deref(inner) => Form::new(
145 FormKind::Deref(Box::new(rewrite_pct_form(inner, span.clone()))),
146 span,
147 ),
148 FormKind::Var(inner) => Form::new(
149 FormKind::Var(Box::new(rewrite_pct_form(inner, span.clone()))),
150 span,
151 ),
152 FormKind::TaggedLiteral(tag, inner) => Form::new(
153 FormKind::TaggedLiteral(tag.clone(), Box::new(rewrite_pct_form(inner, span.clone()))),
154 span,
155 ),
156 FormKind::Meta(meta, inner) => Form::new(
157 FormKind::Meta(
158 Box::new(rewrite_pct_form(meta, span.clone())),
159 Box::new(rewrite_pct_form(inner, span.clone())),
160 ),
161 span,
162 ),
163 FormKind::ReaderCond { splicing, clauses } => Form::new(
164 FormKind::ReaderCond {
165 splicing: *splicing,
166 clauses: rewrite_pct_refs(clauses, span.clone()),
167 },
168 span,
169 ),
170 _ => form.clone(),
171 }
172}
173
174pub fn resolve_auto_forms(form: &Form, env: &crate::env::env::Env) -> EvalResult<Form> {
184 let resolve_seq = |items: &Vec<Form>| -> EvalResult<Vec<Form>> {
185 items.iter().map(|f| resolve_auto_forms(f, env)).collect()
186 };
187 let resolve_box =
188 |inner: &Form| -> EvalResult<Box<Form>> { Ok(Box::new(resolve_auto_forms(inner, env)?)) };
189 let qualify = |name: &str| -> EvalResult<String> {
190 env.globals
191 .resolve_auto_keyword(&env.current_ns, name)
192 .map_err(EvalError::Runtime)
193 };
194
195 let kind = match &form.kind {
196 FormKind::AutoKeyword(s) => FormKind::Keyword(qualify(s)?),
197 FormKind::AutoSymbol(s) => FormKind::Symbol(qualify(s)?),
198
199 FormKind::List(items) => FormKind::List(resolve_seq(items)?),
200 FormKind::Vector(items) => FormKind::Vector(resolve_seq(items)?),
201 FormKind::Map(items) => FormKind::Map(resolve_seq(items)?),
202 FormKind::Set(items) => FormKind::Set(resolve_seq(items)?),
203 FormKind::AnonFn(items) => FormKind::AnonFn(resolve_seq(items)?),
204 FormKind::ReaderCond { splicing, clauses } => FormKind::ReaderCond {
205 splicing: *splicing,
206 clauses: resolve_seq(clauses)?,
207 },
208
209 FormKind::Quote(inner) => FormKind::Quote(resolve_box(inner)?),
210 FormKind::SyntaxQuote(inner) => FormKind::SyntaxQuote(resolve_box(inner)?),
211 FormKind::Unquote(inner) => FormKind::Unquote(resolve_box(inner)?),
212 FormKind::UnquoteSplice(inner) => FormKind::UnquoteSplice(resolve_box(inner)?),
213 FormKind::Deref(inner) => FormKind::Deref(resolve_box(inner)?),
214 FormKind::Var(inner) => FormKind::Var(resolve_box(inner)?),
215 FormKind::TaggedLiteral(tag, inner) => {
216 FormKind::TaggedLiteral(tag.clone(), resolve_box(inner)?)
217 }
218 FormKind::Meta(meta, inner) => FormKind::Meta(resolve_box(meta)?, resolve_box(inner)?),
219
220 _ => return Ok(form.clone()),
221 };
222 Ok(Form::new(kind, form.span.clone()))
223}
224
225pub fn supports_meta(value: &Value) -> bool {
230 matches!(
231 value.unwrap_meta(),
232 Value::List(_)
233 | Value::Vector(_)
234 | Value::Map(_)
235 | Value::Set(_)
236 | Value::Queue(_)
237 | Value::Symbol(_)
238 | Value::Cons(_)
239 | Value::LazySeq(_)
240 | Value::Fn(_)
241 | Value::Macro(_)
242 | Value::BoundFn(_)
243 | Value::NativeFunction(_)
244 | Value::TypeInstance(_)
245 )
246}
247
248pub fn expand_meta_annotation(
259 meta: &Form,
260 general: &mut dyn FnMut(&Form) -> EvalResult<Value>,
261) -> EvalResult<Value> {
262 let entry = |k: Value, v: Value| Value::Map(MapValue::empty().assoc(k, v));
263 let expanded = match &meta.kind {
264 FormKind::Keyword(_) => entry(form_to_value(meta)?, Value::Bool(true)),
267 FormKind::AutoKeyword(_) => entry(general(meta)?, Value::Bool(true)),
268 FormKind::Symbol(_) | FormKind::Str(_) => {
271 entry(Value::keyword(Keyword::parse("tag")), form_to_value(meta)?)
272 }
273 FormKind::Quote(inner) => form_to_value(inner)?,
277 _ => general(meta)?,
278 };
279 match expanded {
282 v @ (Value::Map(_) | Value::Nil) => Ok(v),
283 other => Err(EvalError::Runtime(format!(
284 "Metadata must be Symbol, Keyword, String or Map, got {}",
285 other.type_name()
286 ))),
287 }
288}
289
290pub fn merge_meta_values(base: &Value, overlay: &Value) -> Value {
293 match (base, overlay) {
294 (Value::Map(base), Value::Map(overlay)) => {
295 let mut merged = base.clone();
296 overlay.for_each(|k, v| merged = merged.assoc(k.clone(), v.clone()));
297 Value::Map(merged)
298 }
299 _ => overlay.clone(),
300 }
301}
302
303pub fn attach_meta(value: Value, annotation: Value) -> Value {
309 if matches!(annotation, Value::Nil) || !supports_meta(&value) {
310 return value;
311 }
312 let merged = match value.get_meta() {
313 Some(existing) => merge_meta_values(existing, &annotation),
314 None => annotation,
315 };
316 value.with_meta(merged)
317}
318
319pub fn values_to_list(items: impl IntoIterator<Item = Value>) -> Value {
336 Value::List(GcPtr::new(PersistentList::from_iter(items)))
337}
338
339pub fn form_to_value(form: &Form) -> EvalResult<Value> {
340 let value = match &form.kind {
341 FormKind::Nil => Value::Nil,
342 FormKind::Bool(b) => Value::Bool(*b),
343 FormKind::Int(n) => Value::Long(*n),
344 FormKind::Float(f) => Value::Double(*f),
345 FormKind::Symbolic(f) => Value::Double(*f),
346 FormKind::Str(s) => Value::string(s.clone()),
347 FormKind::Char(c) => Value::Char(*c),
348 FormKind::BigInt(s) => crate::builtins::parse_bigint(s).unwrap_or(Value::Nil),
349 FormKind::BigDecimal(s) => crate::builtins::parse_bigdecimal(s).unwrap_or(Value::Nil),
350 FormKind::Ratio(s) => crate::builtins::parse_ratio(s).unwrap_or(Value::Nil),
351
352 FormKind::Symbol(s) => Value::symbol(Symbol::parse(s)),
353 FormKind::Keyword(s) => Value::keyword(Keyword::parse(s)),
354 FormKind::AutoKeyword(s) => Value::keyword(Keyword::simple(s.as_str())),
355 FormKind::AutoSymbol(s) => Value::symbol(Symbol::parse(s)),
356 FormKind::Regex(s) => match Pattern::new(s.as_str()) {
357 Ok(pattern) => Value::Pattern(GcPtr::new(pattern)),
358 Err(_) => Value::Nil, },
360
361 FormKind::List(forms) => {
362 let items = forms_to_values(&expand_reader_conds_cow(forms))?;
363 values_to_list(items)
364 }
365 FormKind::Vector(forms) => {
366 let items = forms_to_values(&expand_reader_conds_cow(forms))?;
367 Value::Vector(GcPtr::new(PersistentVector::from_iter(items)))
368 }
369 FormKind::Map(forms) => {
370 let forms = expand_pairs(forms).map_err(|_| map_literal_arity_error())?;
371 let mut m = MapValue::empty();
372 for [k, v] in forms.as_chunks::<2>().0 {
373 m = m.assoc(form_to_value(k)?, form_to_value(v)?);
374 }
375 Value::Map(m)
376 }
377 FormKind::Set(forms) => {
378 let mut s = PersistentHashSet::empty();
379 for f in expand_reader_conds_cow(forms).iter() {
380 s = s.conj(form_to_value(f)?);
381 }
382 Value::Set(SetValue::Hash(GcPtr::new(s)))
383 }
384
385 FormKind::Quote(inner) => reader_form_value("quote", inner)?,
387 FormKind::SyntaxQuote(inner) => reader_form_value("syntax-quote", inner)?,
388 FormKind::Unquote(inner) => reader_form_value("unquote", inner)?,
389 FormKind::UnquoteSplice(inner) => reader_form_value("unquote-splicing", inner)?,
390 FormKind::Deref(inner) => reader_form_value("deref", inner)?,
391 FormKind::Var(inner) => reader_form_value("var", inner)?,
392 FormKind::Meta(meta, inner) => {
393 let value = form_to_value(inner)?;
394 let m = expand_meta_annotation(meta, &mut form_to_value)?;
396 attach_meta(value, m)
397 }
398 FormKind::AnonFn(body) => {
399 let expanded = expand_anon_fn(body, form.span.clone());
401 form_to_value(&expanded)?
402 }
403 FormKind::TaggedLiteral(tag, inner) => match tag.as_str() {
404 "uuid" => {
405 if let FormKind::Str(s) = &inner.kind
406 && let Ok(u) = uuid::Uuid::parse_str(s)
407 {
408 Value::Uuid(u.as_u128())
409 } else {
410 form_to_value(inner)?
411 }
412 }
413 _ => form_to_value(inner)?,
414 },
415 FormKind::ReaderCond {
416 splicing: false,
417 clauses,
418 } => match select_reader_cond(clauses) {
419 Some(selected) => form_to_value(selected)?,
420 None => Value::Nil,
421 },
422 FormKind::ReaderCond { splicing: true, .. } => {
425 return Err(EvalError::Runtime(
426 "splicing reader conditional not in a sequence context".into(),
427 ));
428 }
429 };
430 Ok(value)
431}
432
433fn forms_to_values(forms: &[Form]) -> EvalResult<Vec<Value>> {
434 forms.iter().map(form_to_value).collect()
435}
436
437fn reader_form_value(sym: &str, inner: &Form) -> EvalResult<Value> {
439 Ok(Value::List(GcPtr::new(PersistentList::from_iter([
440 Value::symbol(Symbol::simple(sym)),
441 form_to_value(inner)?,
442 ]))))
443}
444
445fn map_literal_arity_error() -> EvalError {
446 EvalError::Runtime("map literal must have an even number of forms".into())
447}
448
449pub fn select_reader_cond(clauses: &[Form]) -> Option<&Form> {
452 let mut default: Option<&Form> = None;
453 for [feature, branch] in clauses.as_chunks::<2>().0 {
454 match &feature.kind {
455 FormKind::Keyword(k) if k == "rust" => return Some(branch),
456 FormKind::Keyword(k) if k == "default" => default = Some(branch),
457 _ => {}
458 }
459 }
460 default
461}
462
463pub fn expand_reader_conds(forms: &[Form]) -> Vec<Form> {
469 let mut out = Vec::with_capacity(forms.len());
470 for form in forms {
471 match &form.kind {
472 FormKind::ReaderCond {
473 splicing: true,
474 clauses,
475 } => {
476 if let Some(selected) = select_reader_cond(clauses) {
477 match &selected.kind {
478 FormKind::Vector(elems) | FormKind::List(elems) => {
479 let expanded_elems = expand_reader_conds(elems);
482 out.extend(expanded_elems);
483 }
484 _ => out.push(selected.clone()),
486 }
487 }
488 }
490 FormKind::ReaderCond {
491 splicing: false,
492 clauses,
493 } => {
494 if let Some(selected) = select_reader_cond(clauses) {
495 out.push(selected.clone());
496 }
497 }
499 _ => out.push(form.clone()),
500 }
501 }
502 out
503}
504
505pub fn expand_reader_conds_cow(forms: &[Form]) -> std::borrow::Cow<'_, [Form]> {
511 if forms
512 .iter()
513 .any(|f| matches!(f.kind, FormKind::ReaderCond { .. }))
514 {
515 std::borrow::Cow::Owned(expand_reader_conds(forms))
516 } else {
517 std::borrow::Cow::Borrowed(forms)
518 }
519}
520
521#[derive(Debug, Clone, Copy, PartialEq, Eq)]
524pub struct OddArity(pub usize);
525
526pub fn expand_pairs(forms: &[Form]) -> Result<std::borrow::Cow<'_, [Form]>, OddArity> {
532 let expanded = expand_reader_conds_cow(forms);
533 if expanded.len().is_multiple_of(2) {
534 Ok(expanded)
535 } else {
536 Err(OddArity(expanded.len()))
537 }
538}
539
540#[cfg(test)]
541mod tests {
542 use super::*;
543 use cljrs_reader::Parser;
544
545 fn parse_anon_fn(src: &str) -> Form {
546 let mut parser = Parser::new(src.to_string(), "<test>".to_string());
547 let form = parser.parse_one().unwrap().unwrap();
548 let FormKind::AnonFn(body) = form.kind else {
549 panic!("expected AnonFn, got {:?}", form.kind);
550 };
551 expand_anon_fn(&body, form.span)
552 }
553
554 fn arity(expanded: &Form) -> usize {
555 let FormKind::List(parts) = &expanded.kind else {
556 panic!("expected (fn* [...] ...)");
557 };
558 let FormKind::Vector(params) = &parts[1].kind else {
559 panic!("expected param vector");
560 };
561 params.len()
562 }
563
564 #[test]
565 fn deref_sugar_counts_as_one_arg() {
566 let expanded = parse_anon_fn("#(:x @%)");
569 assert_eq!(arity(&expanded), 1);
570
571 let FormKind::List(parts) = &expanded.kind else {
572 unreachable!()
573 };
574 let FormKind::List(body) = &parts[2].kind else {
575 panic!("expected body list");
576 };
577 let FormKind::Deref(inner) = &body[1].kind else {
578 panic!("expected deref form, got {:?}", body[1].kind);
579 };
580 assert_eq!(inner.kind, FormKind::Symbol("p__1".to_string()));
581 }
582
583 #[test]
584 fn var_and_meta_sugar_also_scanned() {
585 assert_eq!(arity(&parse_anon_fn("#(#'%)")), 1);
586 assert_eq!(arity(&parse_anon_fn("#(^:x %)")), 1);
587 assert_eq!(arity(&parse_anon_fn("#('%)")), 1);
588 }
589
590 fn parse_one_form(src: &str) -> Form {
593 let mut p = Parser::new(src.to_string(), "<test>".to_string());
594 p.parse_one().unwrap().unwrap()
595 }
596
597 fn vec_elems(src: &str) -> Vec<Form> {
598 match parse_one_form(src).kind {
599 FormKind::Vector(v) => v,
600 other => panic!("expected vector literal, got {other:?}"),
601 }
602 }
603
604 fn kinds(forms: &[Form]) -> Vec<FormKind> {
605 forms.iter().map(|f| f.kind.clone()).collect()
606 }
607
608 fn expand_src(src: &str) -> Vec<FormKind> {
610 kinds(&expand_reader_conds(&vec_elems(src)))
611 }
612
613 #[test]
614 fn splice_rust_branch_flattens_into_parent() {
615 assert_eq!(
616 expand_src("[:x #?@(:rust [:a :b]) :y]"),
617 kinds(&vec_elems("[:x :a :b :y]"))
618 );
619 }
620
621 #[test]
622 fn splice_falls_back_to_default_branch() {
623 assert_eq!(
624 expand_src("[:x #?@(:default [:a :b]) :y]"),
625 kinds(&vec_elems("[:x :a :b :y]"))
626 );
627 }
628
629 #[test]
630 fn splice_with_no_matching_branch_is_removed_not_nil() {
631 assert_eq!(
634 expand_src("[:x #?@(:clj [:a :b]) :y]"),
635 kinds(&vec_elems("[:x :y]"))
636 );
637 }
638
639 #[test]
640 fn non_splicing_conditional_selects_single_form() {
641 assert_eq!(
642 expand_src("[:x #?(:rust :a :default :b) :y]"),
643 kinds(&vec_elems("[:x :a :y]"))
644 );
645 }
646
647 #[test]
648 fn nested_conditionals_inside_spliced_branch_expand() {
649 assert_eq!(
650 expand_src("[#?@(:rust [#?(:rust :a :default :z) :b])]"),
651 kinds(&vec_elems("[:a :b]"))
652 );
653 }
654
655 #[test]
656 fn cow_borrows_when_no_conditional_present() {
657 let forms = vec_elems("[:x :y :z]");
658 assert!(matches!(
659 expand_reader_conds_cow(&forms),
660 std::borrow::Cow::Borrowed(_)
661 ));
662 }
663
664 #[test]
665 fn cow_owns_when_conditional_present() {
666 let forms = vec_elems("[:x #?@(:rust [:a]) :y]");
667 assert!(matches!(
668 expand_reader_conds_cow(&forms),
669 std::borrow::Cow::Owned(_)
670 ));
671 }
672
673 fn kw_list(names: &[String]) -> String {
674 names
675 .iter()
676 .map(|n| format!(":{n}"))
677 .collect::<Vec<_>>()
678 .join(" ")
679 }
680
681 #[derive(Clone, Debug)]
688 enum Item {
689 Plain(String),
690 NonSplice(Vec<(String, String)>),
691 Splice(Vec<(String, Vec<String>)>),
692 }
693
694 fn select_model<T>(branches: &[(String, T)]) -> Option<&T> {
695 let mut default = None;
696 for (k, v) in branches {
697 if k == "rust" {
698 return Some(v);
699 }
700 if k == "default" {
701 default = Some(v);
702 }
703 }
704 default
705 }
706
707 fn item_src(it: &Item) -> String {
708 match it {
709 Item::Plain(n) => format!(":{n}"),
710 Item::NonSplice(brs) => {
711 let inner = brs
712 .iter()
713 .map(|(k, n)| format!(":{k} :{n}"))
714 .collect::<Vec<_>>()
715 .join(" ");
716 format!("#?({inner})")
717 }
718 Item::Splice(brs) => {
719 let inner = brs
720 .iter()
721 .map(|(k, vs)| format!(":{k} [{}]", kw_list(vs)))
722 .collect::<Vec<_>>()
723 .join(" ");
724 format!("#?@({inner})")
725 }
726 }
727 }
728
729 fn item_expected(it: &Item) -> Vec<String> {
730 match it {
731 Item::Plain(n) => vec![n.clone()],
732 Item::NonSplice(brs) => select_model(brs).cloned().into_iter().collect(),
733 Item::Splice(brs) => select_model(brs).cloned().unwrap_or_default(),
734 }
735 }
736
737 fn key_strat() -> impl proptest::strategy::Strategy<Value = String> {
738 proptest::prop_oneof![
739 proptest::strategy::Just("rust".to_string()),
740 proptest::strategy::Just("clj".to_string()),
741 proptest::strategy::Just("cljs".to_string()),
742 proptest::strategy::Just("default".to_string()),
743 ]
744 }
745
746 fn item_strat() -> impl proptest::strategy::Strategy<Value = Item> {
747 use proptest::collection::vec as pvec;
748 use proptest::strategy::Strategy;
749 proptest::prop_oneof![
750 "[a-z]{1,4}".prop_map(Item::Plain),
751 pvec((key_strat(), "[a-z]{1,4}".prop_map(|s| s)), 1..4).prop_map(Item::NonSplice),
752 pvec((key_strat(), pvec("[a-z]{1,4}", 0..3)), 1..4).prop_map(Item::Splice),
753 ]
754 }
755
756 fn seq_src(items: &[Item]) -> String {
757 format!(
758 "[{}]",
759 items.iter().map(item_src).collect::<Vec<_>>().join(" ")
760 )
761 }
762
763 fn seq_expected_src(items: &[Item]) -> String {
764 let names: Vec<String> = items.iter().flat_map(item_expected).collect();
765 format!("[{}]", kw_list(&names))
766 }
767
768 proptest::proptest! {
769 #[test]
771 fn prop_splice_equals_inline(
772 pre in proptest::collection::vec("[a-z]{1,4}", 0..4),
773 mid in proptest::collection::vec("[a-z]{1,4}", 0..4),
774 suf in proptest::collection::vec("[a-z]{1,4}", 0..4),
775 ) {
776 let spliced = format!("[{} #?@(:rust [{}]) {}]",
777 kw_list(&pre), kw_list(&mid), kw_list(&suf));
778 let inlined = format!("[{} {} {}]",
779 kw_list(&pre), kw_list(&mid), kw_list(&suf));
780 proptest::prop_assert_eq!(expand_src(&spliced), kinds(&vec_elems(&inlined)));
781 }
782
783 #[test]
785 fn prop_expand_idempotent(
786 pre in proptest::collection::vec("[a-z]{1,4}", 0..4),
787 mid in proptest::collection::vec("[a-z]{1,4}", 0..4),
788 ) {
789 let src = format!("[{} #?@(:rust [{}])]", kw_list(&pre), kw_list(&mid));
790 let once = expand_reader_conds(&vec_elems(&src));
791 let twice = expand_reader_conds(&once);
792 proptest::prop_assert_eq!(kinds(&once), kinds(&twice));
793 }
794
795 #[test]
797 fn prop_no_conditional_is_identity(
798 xs in proptest::collection::vec("[a-z]{1,4}", 0..8),
799 ) {
800 let src = format!("[{}]", kw_list(&xs));
801 proptest::prop_assert_eq!(expand_src(&src), kinds(&vec_elems(&src)));
802 }
803
804 #[test]
807 fn prop_model_matches_expand(
808 items in proptest::collection::vec(item_strat(), 0..6),
809 ) {
810 proptest::prop_assert_eq!(
811 expand_src(&seq_src(&items)),
812 kinds(&vec_elems(&seq_expected_src(&items)))
813 );
814 }
815
816 #[test]
818 fn prop_no_reader_cond_survives(
819 items in proptest::collection::vec(item_strat(), 0..6),
820 ) {
821 let expanded = expand_reader_conds(&vec_elems(&seq_src(&items)));
822 let none_survive = expanded
823 .iter()
824 .all(|f| !matches!(f.kind, FormKind::ReaderCond { .. }));
825 proptest::prop_assert!(none_survive);
826 }
827
828 #[test]
830 fn prop_length_law(
831 items in proptest::collection::vec(item_strat(), 0..6),
832 ) {
833 let expected_len: usize = items.iter().map(|it| item_expected(it).len()).sum();
834 let got = expand_reader_conds(&vec_elems(&seq_src(&items)));
835 proptest::prop_assert_eq!(got.len(), expected_len);
836 }
837 }
838
839 #[derive(Clone, Debug)]
846 enum Slot {
847 Plain,
849 NonSplice { matches: bool },
851 Splice { matches: bool, n: usize },
853 }
854
855 fn slot_strat() -> impl proptest::strategy::Strategy<Value = Slot> {
856 use proptest::strategy::{Just, Strategy};
857 proptest::prop_oneof![
858 Just(Slot::Plain),
859 proptest::bool::ANY.prop_map(|matches| Slot::NonSplice { matches }),
860 (proptest::bool::ANY, 0usize..3).prop_map(|(matches, n)| Slot::Splice { matches, n }),
861 ]
862 }
863
864 fn render_slots(slots: &[Slot]) -> (String, String) {
868 let mut next = 0usize;
869 let mut name = || {
870 let n = next;
871 next += 1;
872 format!(":k{n}")
873 };
874 let (mut written, mut inlined) = (Vec::new(), Vec::new());
875 for slot in slots {
876 match *slot {
877 Slot::Plain => {
878 let k = name();
879 written.push(k.clone());
880 inlined.push(k);
881 }
882 Slot::NonSplice { matches } => {
883 let k = name();
884 let key = if matches { "rust" } else { "clj" };
885 written.push(format!("#?(:{key} {k})"));
886 if matches {
887 inlined.push(k);
888 }
889 }
890 Slot::Splice { matches, n } => {
891 let ks: Vec<String> = (0..n).map(|_| name()).collect();
892 let key = if matches { "rust" } else { "clj" };
893 written.push(format!("#?@(:{key} [{}])", ks.join(" ")));
894 if matches {
895 inlined.extend(ks);
896 }
897 }
898 }
899 }
900 (written.join(" "), inlined.join(" "))
901 }
902
903 fn quoted_value(src: &str) -> EvalResult<Value> {
904 form_to_value(&parse_one_form(src))
905 }
906
907 fn read_to_value(src: &str) -> Result<Value, String> {
912 let mut p = Parser::new(src.to_string(), "<test>".to_string());
913 let form = p
914 .parse_one()
915 .map_err(|e| e.to_string())?
916 .ok_or_else(|| "no form".to_string())?;
917 form_to_value(&form).map_err(|e| e.to_string())
918 }
919
920 proptest::proptest! {
921 #[test]
925 fn prop_expand_pairs_is_expansion_plus_parity_gate(
926 items in proptest::collection::vec(item_strat(), 0..6),
927 ) {
928 let forms = vec_elems(&seq_src(&items));
929 let expanded = expand_reader_conds(&forms);
930 match expand_pairs(&forms) {
931 Ok(got) => {
932 proptest::prop_assert!(expanded.len().is_multiple_of(2));
933 proptest::prop_assert_eq!(kinds(&got), kinds(&expanded));
934 }
935 Err(OddArity(len)) => {
936 proptest::prop_assert!(!expanded.len().is_multiple_of(2));
937 proptest::prop_assert_eq!(len, expanded.len());
938 }
939 }
940 }
941
942 #[test]
947 fn prop_quoted_splice_equals_inline_in_every_container(
948 slots in proptest::collection::vec(slot_strat(), 0..5),
949 ) {
950 let (written, inlined) = render_slots(&slots);
951 for (open, close) in [("[", "]"), ("#{", "}"), ("(", ")"), ("{", "}")] {
952 let expanded_len = expand_reader_conds(&vec_elems(&format!("[{written}]"))).len();
955 if open == "{" && !expanded_len.is_multiple_of(2) {
956 continue;
957 }
958 let got = quoted_value(&format!("{open}{written}{close}"));
959 let want = quoted_value(&format!("{open}{inlined}{close}"));
960 proptest::prop_assert_eq!(
961 got.map_err(|e| e.to_string()),
962 want.map_err(|e| e.to_string()),
963 "container {}{}", open, close
964 );
965 }
966 }
967
968 #[test]
973 fn prop_map_denotes_a_value_iff_expansion_is_even(
974 slots in proptest::collection::vec(slot_strat(), 0..5),
975 ) {
976 let (written, _) = render_slots(&slots);
977 let expanded_len = expand_reader_conds(&vec_elems(&format!("[{written}]"))).len();
978 let got = read_to_value(&format!("{{{written}}}"));
979 proptest::prop_assert_eq!(got.is_ok(), expanded_len.is_multiple_of(2), "{:?}", got);
980 }
981
982 #[test]
985 fn prop_quoted_splice_survives_nesting(
986 slots in proptest::collection::vec(slot_strat(), 0..4),
987 ) {
988 let (written, inlined) = render_slots(&slots);
989 let got = quoted_value(&format!("[:head [{written}] :tail]"));
990 let want = quoted_value(&format!("[:head [{inlined}] :tail]"));
991 proptest::prop_assert_eq!(
992 got.map_err(|e| e.to_string()),
993 want.map_err(|e| e.to_string())
994 );
995 }
996 }
997
998 #[test]
1001 fn quoted_map_splices_into_key_value_positions() {
1002 assert_eq!(
1003 quoted_value("'{:a 1 #?@(:rust [:b 2]) :c 3}").unwrap(),
1004 quoted_value("'{:a 1 :b 2 :c 3}").unwrap()
1005 );
1006 }
1007
1008 #[test]
1009 fn quoted_set_splices_its_elements() {
1010 assert_eq!(
1011 quoted_value("'#{1 #?@(:rust [2 3]) 4}").unwrap(),
1012 quoted_value("'#{1 2 3 4}").unwrap()
1013 );
1014 }
1015
1016 #[test]
1017 fn quoted_map_with_odd_expansion_errors_rather_than_truncating() {
1018 let err = quoted_value("{:a 1 #?@(:rust [:b]) :c 3}").unwrap_err();
1019 assert!(err.to_string().contains("even number of forms"), "{err}");
1020 }
1021
1022 #[test]
1023 fn uuid_tagged_literal_reads_as_a_uuid_value() {
1024 let got = quoted_value("#uuid \"f81d4fae-7dec-11d0-a765-00a0c91e6bf6\"").unwrap();
1025 assert_eq!(
1026 got,
1027 Value::Uuid(0xf81d4fae_7dec_11d0_a765_00a0c91e6bf6_u128)
1028 );
1029 }
1030
1031 #[test]
1032 fn splice_without_a_sibling_sequence_errors() {
1033 let err = quoted_value("#?@(:rust [1 2])").unwrap_err();
1034 assert!(err.to_string().contains("sequence context"), "{err}");
1035 }
1036}