use std::sync::Arc;
use cljrs_reader::form::{Form, FormKind};
use cljrs_runtime::builtins::form::resolve_auto_forms;
use cljrs_runtime::env::env::Env;
use cljrs_types::span::Span;
use proptest::prelude::*;
use proptest::test_runner::TestRunner;
fn span() -> Span {
Span::new(Arc::new("<proptest>".to_string()), 0, 0, 1, 1)
}
fn form(kind: FormKind) -> Form {
Form::new(kind, span())
}
fn bare_name() -> impl Strategy<Value = String> {
"[a-z][a-z0-9]{0,5}"
}
fn any_form() -> impl Strategy<Value = Form> {
let leaf = prop_oneof![
bare_name().prop_map(|s| form(FormKind::AutoKeyword(s))),
bare_name().prop_map(|s| form(FormKind::AutoSymbol(s))),
bare_name().prop_map(|s| form(FormKind::Symbol(s))),
bare_name().prop_map(|s| form(FormKind::Keyword(s))),
any::<i64>().prop_map(|i| form(FormKind::Int(i))),
Just(form(FormKind::Nil)),
];
leaf.prop_recursive(4, 48, 3, |inner| {
let seqs = prop_oneof![
prop::collection::vec(inner.clone(), 0..3).prop_map(|v| form(FormKind::List(v))),
prop::collection::vec(inner.clone(), 0..3).prop_map(|v| form(FormKind::Vector(v))),
prop::collection::vec(inner.clone(), 0..3).prop_map(|v| form(FormKind::Set(v))),
prop::collection::vec(inner.clone(), 0..3).prop_map(|v| form(FormKind::AnonFn(v))),
prop::collection::vec(inner.clone(), 0..2).prop_map(|v| {
let pairs = v.iter().cloned().chain(v.iter().cloned()).collect();
form(FormKind::Map(pairs))
}),
prop::collection::vec(inner.clone(), 0..3).prop_map(|clauses| form(
FormKind::ReaderCond {
splicing: false,
clauses
}
)),
];
let wrappers = prop_oneof![
inner
.clone()
.prop_map(|f| form(FormKind::Quote(Box::new(f)))),
inner
.clone()
.prop_map(|f| form(FormKind::SyntaxQuote(Box::new(f)))),
inner
.clone()
.prop_map(|f| form(FormKind::Unquote(Box::new(f)))),
inner
.clone()
.prop_map(|f| form(FormKind::UnquoteSplice(Box::new(f)))),
inner
.clone()
.prop_map(|f| form(FormKind::Deref(Box::new(f)))),
inner.clone().prop_map(|f| form(FormKind::Var(Box::new(f)))),
(inner.clone(), inner.clone())
.prop_map(|(m, f)| form(FormKind::Meta(Box::new(m), Box::new(f)))),
(bare_name(), inner)
.prop_map(|(tag, f)| form(FormKind::TaggedLiteral(tag, Box::new(f)))),
];
prop_oneof![seqs, wrappers]
})
}
fn unresolved(f: &Form) -> usize {
let seq = |forms: &[Form]| forms.iter().map(unresolved).sum::<usize>();
match &f.kind {
FormKind::AutoKeyword(_) | FormKind::AutoSymbol(_) => 1,
FormKind::List(v)
| FormKind::Vector(v)
| FormKind::Map(v)
| FormKind::Set(v)
| FormKind::AnonFn(v) => seq(v),
FormKind::ReaderCond { clauses, .. } => seq(clauses),
FormKind::Quote(inner)
| FormKind::SyntaxQuote(inner)
| FormKind::Unquote(inner)
| FormKind::UnquoteSplice(inner)
| FormKind::Deref(inner)
| FormKind::Var(inner)
| FormKind::TaggedLiteral(_, inner) => unresolved(inner),
FormKind::Meta(meta, inner) => unresolved(meta) + unresolved(inner),
FormKind::Nil
| FormKind::Bool(_)
| FormKind::Int(_)
| FormKind::BigInt(_)
| FormKind::Float(_)
| FormKind::BigDecimal(_)
| FormKind::Ratio(_)
| FormKind::Char(_)
| FormKind::Str(_)
| FormKind::Regex(_)
| FormKind::Symbolic(_)
| FormKind::Symbol(_)
| FormKind::Keyword(_) => 0,
}
}
fn nodes(f: &Form) -> usize {
let seq = |forms: &[Form]| forms.iter().map(nodes).sum::<usize>();
1 + match &f.kind {
FormKind::List(v)
| FormKind::Vector(v)
| FormKind::Map(v)
| FormKind::Set(v)
| FormKind::AnonFn(v) => seq(v),
FormKind::ReaderCond { clauses, .. } => seq(clauses),
FormKind::Quote(inner)
| FormKind::SyntaxQuote(inner)
| FormKind::Unquote(inner)
| FormKind::UnquoteSplice(inner)
| FormKind::Deref(inner)
| FormKind::Var(inner)
| FormKind::TaggedLiteral(_, inner) => nodes(inner),
FormKind::Meta(meta, inner) => nodes(meta) + nodes(inner),
_ => 0,
}
}
fn run(check: impl Fn(&Form, &Env) -> Result<(), TestCaseError>) {
let globals = cljrs_runtime::Runtime::builder()
.execution_mode(cljrs_runtime::ExecutionMode::TreeWalk)
.eager_clojure_test(true)
.build()
.expect("runtime")
.into_globals();
let env = Env::new(globals, "my.app");
TestRunner::default()
.run(&any_form(), |f| check(&f, &env))
.expect("property failed");
}
#[test]
fn no_auto_identifier_survives_resolution() {
run(|f, env| {
let resolved = resolve_auto_forms(f, env).expect("bare names always resolve");
prop_assert_eq!(unresolved(&resolved), 0);
Ok(())
});
}
#[test]
fn resolution_preserves_the_shape_of_the_tree() {
run(|f, env| {
let resolved = resolve_auto_forms(f, env).expect("bare names always resolve");
prop_assert_eq!(nodes(&resolved), nodes(f));
Ok(())
});
}
#[test]
fn resolution_is_idempotent() {
run(|f, env| {
let once = resolve_auto_forms(f, env).expect("bare names always resolve");
let twice = resolve_auto_forms(&once, env).expect("already resolved");
prop_assert_eq!(twice, once);
Ok(())
});
}
#[test]
fn every_resolved_identifier_carries_the_current_namespace() {
run(|f, env| {
let resolved = resolve_auto_forms(f, env).expect("bare names always resolve");
prop_assert_eq!(qualified_under(&resolved, "my.app/"), auto_count(f));
Ok(())
});
}
fn qualified_under(f: &Form, prefix: &str) -> usize {
let seq = |forms: &[Form]| {
forms
.iter()
.map(|x| qualified_under(x, prefix))
.sum::<usize>()
};
match &f.kind {
FormKind::Keyword(s) | FormKind::Symbol(s) => usize::from(s.starts_with(prefix)),
FormKind::List(v)
| FormKind::Vector(v)
| FormKind::Map(v)
| FormKind::Set(v)
| FormKind::AnonFn(v) => seq(v),
FormKind::ReaderCond { clauses, .. } => seq(clauses),
FormKind::Quote(inner)
| FormKind::SyntaxQuote(inner)
| FormKind::Unquote(inner)
| FormKind::UnquoteSplice(inner)
| FormKind::Deref(inner)
| FormKind::Var(inner)
| FormKind::TaggedLiteral(_, inner) => qualified_under(inner, prefix),
FormKind::Meta(meta, inner) => {
qualified_under(meta, prefix) + qualified_under(inner, prefix)
}
_ => 0,
}
}
fn auto_count(f: &Form) -> usize {
unresolved(f)
}