use cljrs_reader::form::{Form, FormKind};
use cljrs_reader::namespaced_map::{MapNs, qualify_keys};
use cljrs_types::span::Span;
use proptest::prelude::*;
use std::sync::Arc;
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,7}"
}
fn ns_name() -> impl Strategy<Value = String> {
prop::collection::vec(bare_name(), 1..3).prop_map(|parts| parts.join("."))
}
fn key_form() -> impl Strategy<Value = Form> {
prop_oneof![
bare_name().prop_map(|n| form(FormKind::Keyword(n))),
bare_name().prop_map(|n| form(FormKind::Symbol(n))),
(ns_name(), bare_name()).prop_map(|(ns, n)| form(FormKind::Keyword(format!("{ns}/{n}")))),
bare_name().prop_map(|n| form(FormKind::Keyword(format!("_/{n}")))),
Just(form(FormKind::Symbol("/".to_string()))),
any::<i64>().prop_map(|i| form(FormKind::Int(i))),
]
}
fn body() -> impl Strategy<Value = Vec<Form>> {
prop::collection::vec((key_form(), any::<i64>()), 0..8).prop_map(|pairs| {
pairs
.into_iter()
.flat_map(|(k, v)| [k, form(FormKind::Int(v))])
.collect()
})
}
fn keyword_body() -> impl Strategy<Value = Vec<Form>> {
prop::collection::vec((key_form(), any::<i64>()), 0..8).prop_map(|pairs| {
pairs
.into_iter()
.filter(|(k, _)| match &k.kind {
FormKind::Symbol(_) => false,
FormKind::Keyword(n) => !n.starts_with("_/"),
_ => true,
})
.flat_map(|(k, v)| [k, form(FormKind::Int(v))])
.collect()
})
}
fn kinds(forms: Vec<Form>) -> Vec<FormKind> {
forms.into_iter().map(|f| f.kind).collect()
}
proptest! {
#[test]
fn length_is_preserved(ns in ns_name(), b in body()) {
let n = b.len();
{
let out = qualify_keys(&MapNs::Literal(ns), b);
prop_assert_eq!(out.len(), n);
}
}
#[test]
fn odd_indices_are_never_rewritten(ns in ns_name(), b in body()) {
let before = kinds(b.clone());
{
let out = qualify_keys(&MapNs::Literal(ns), b);
let after = kinds(out);
for i in (1..before.len()).step_by(2) {
prop_assert_eq!(&before[i], &after[i]);
}
}
}
#[test]
fn qualification_is_idempotent(ns in ns_name(), b in keyword_body()) {
let once = qualify_keys(&MapNs::Literal(ns.clone()), b);
let twice = qualify_keys(&MapNs::Literal(ns), once.clone());
prop_assert_eq!(kinds(once), kinds(twice));
}
#[test]
fn underscore_optout_is_one_shot_by_design(ns in ns_name(), name in bare_name()) {
let once = qualify_keys(
&MapNs::Literal(ns.clone()),
vec![form(FormKind::Keyword(format!("_/{name}"))), form(FormKind::Int(0))],
);
prop_assert_eq!(&once[0].kind, &FormKind::Keyword(name.clone()));
let twice = qualify_keys(&MapNs::Literal(ns.clone()), once);
prop_assert_eq!(&twice[0].kind, &FormKind::Keyword(format!("{ns}/{name}")));
}
#[test]
fn bare_keyword_keys_gain_the_literal_namespace(ns in ns_name(), name in bare_name()) {
let out = qualify_keys(
&MapNs::Literal(ns.clone()),
vec![form(FormKind::Keyword(name.clone())), form(FormKind::Int(0))],
);
prop_assert_eq!(&out[0].kind, &FormKind::Keyword(format!("{ns}/{name}")));
}
#[test]
fn underscore_always_unqualifies(ns in ns_name(), name in bare_name()) {
let out = qualify_keys(
&MapNs::Literal(ns),
vec![form(FormKind::Keyword(format!("_/{name}"))), form(FormKind::Int(0))],
);
prop_assert_eq!(&out[0].kind, &FormKind::Keyword(name));
}
#[test]
fn explicit_namespace_always_wins(
map_ns in ns_name(),
key_ns in ns_name(),
name in bare_name(),
) {
prop_assume!(key_ns != "_");
let spelled = format!("{key_ns}/{name}");
let out = qualify_keys(
&MapNs::Literal(map_ns),
vec![form(FormKind::Keyword(spelled.clone())), form(FormKind::Int(0))],
);
prop_assert_eq!(&out[0].kind, &FormKind::Keyword(spelled));
}
#[test]
fn literal_namespace_never_defers_resolution(ns in ns_name(), b in body()) {
{
let out = qualify_keys(&MapNs::Literal(ns), b);
for f in out {
prop_assert!(!matches!(f.kind, FormKind::AutoKeyword(_)));
}
}
}
#[test]
fn auto_defers_exactly_the_bare_keyword_keys(alias in ns_name(), name in bare_name()) {
let out = qualify_keys(
&MapNs::Alias(alias.clone()),
vec![form(FormKind::Keyword(name.clone())), form(FormKind::Int(0))],
);
prop_assert_eq!(&out[0].kind, &FormKind::AutoKeyword(format!("{alias}/{name}")));
}
#[test]
fn auto_defers_symbol_keys_to_auto_symbol(alias in ns_name(), name in bare_name()) {
let out = qualify_keys(
&MapNs::CurrentNs,
vec![form(FormKind::Symbol(name.clone())), form(FormKind::Int(0))],
);
prop_assert_eq!(&out[0].kind, &FormKind::AutoSymbol(name.clone()));
let out = qualify_keys(
&MapNs::Alias(alias.clone()),
vec![form(FormKind::Symbol(name.clone())), form(FormKind::Int(0))],
);
prop_assert_eq!(&out[0].kind, &FormKind::AutoSymbol(format!("{alias}/{name}")));
}
#[test]
fn slash_takes_the_map_namespace(ns in ns_name(), name in bare_name()) {
let out = qualify_keys(
&MapNs::Literal(ns.clone()),
vec![form(FormKind::Symbol("/".to_string())), form(FormKind::Int(0))],
);
prop_assert_eq!(&out[0].kind, &FormKind::Symbol(format!("{ns}//")));
let out = qualify_keys(
&MapNs::Literal(ns.clone()),
vec![form(FormKind::Keyword("/".to_string())), form(FormKind::Int(0))],
);
prop_assert_eq!(&out[0].kind, &FormKind::Keyword(format!("{ns}//")));
let out = qualify_keys(
&MapNs::Alias(name.clone()),
vec![form(FormKind::Keyword("/".to_string())), form(FormKind::Int(0))],
);
prop_assert_eq!(&out[0].kind, &FormKind::AutoKeyword(format!("{name}//")));
}
#[test]
fn prefix_parses_exactly_the_unqualified_symbols(
ns in ns_name(),
name in bare_name(),
auto in any::<bool>(),
) {
let qualified = format!("{ns}/{name}");
let leading_digit = format!("1{ns}");
prop_assert!(MapNs::parse(&ns, auto).is_ok(), "{} rejected", ns);
prop_assert!(MapNs::parse(&qualified, auto).is_err(), "{} accepted", qualified);
prop_assert!(MapNs::parse(&leading_digit, auto).is_err(), "{} accepted", leading_digit);
}
}