use crate::chars::is_symbol_start;
use crate::form::{Form, FormKind};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MapNs {
Literal(String),
CurrentNs,
Alias(String),
}
impl MapNs {
pub fn parse(text: &str, auto: bool) -> Result<Self, String> {
if text.is_empty() {
return if auto {
Ok(MapNs::CurrentNs)
} else {
Err("namespaced map literal requires a namespace".to_string())
};
}
validate_unqualified_symbol(text)?;
Ok(if auto {
MapNs::Alias(text.to_string())
} else {
MapNs::Literal(text.to_string())
})
}
}
fn validate_unqualified_symbol(text: &str) -> Result<(), String> {
let first = text.chars().next().expect("caller checked non-empty");
if !is_symbol_start(first) {
return Err(format!(
"namespaced map prefix must be an unqualified symbol, got '{text}'"
));
}
if text.contains('/') {
return Err(format!(
"namespaced map prefix must be an unqualified symbol, got '{text}' \
(a prefix carries no namespace of its own)"
));
}
if matches!(text, "nil" | "true" | "false") {
return Err(format!(
"namespaced map prefix must be an unqualified symbol, got '{text}'"
));
}
Ok(())
}
pub fn qualify_keys(ns: &MapNs, forms: Vec<Form>) -> Vec<Form> {
forms
.into_iter()
.enumerate()
.map(|(i, form)| {
if i.is_multiple_of(2) {
qualify_key(ns, form)
} else {
form
}
})
.collect()
}
fn qualify_key(ns: &MapNs, key: Form) -> Form {
let span = key.span.clone();
let kind = match key.kind {
FormKind::Keyword(name) => match qualified(ns, &name) {
Qualified::Unchanged => FormKind::Keyword(name),
Qualified::Literal(full) => FormKind::Keyword(full),
Qualified::Auto(full) => FormKind::AutoKeyword(full),
},
FormKind::Symbol(name) => match qualified(ns, &name) {
Qualified::Unchanged => FormKind::Symbol(name),
Qualified::Literal(full) => FormKind::Symbol(full),
Qualified::Auto(full) => FormKind::AutoSymbol(full),
},
other => other,
};
Form::new(kind, span)
}
enum Qualified {
Unchanged,
Literal(String),
Auto(String),
}
fn qualified(ns: &MapNs, name: &str) -> Qualified {
let already_namespaced = match name.split_once('/') {
Some(("_", bare)) => return Qualified::Literal(bare.to_string()),
Some(_) if name != "/" => true,
_ => false,
};
if already_namespaced {
return Qualified::Unchanged;
}
match ns {
MapNs::CurrentNs => Qualified::Auto(name.to_string()),
MapNs::Alias(alias) => Qualified::Auto(format!("{alias}/{name}")),
MapNs::Literal(literal) => Qualified::Literal(format!("{literal}/{name}")),
}
}
#[cfg(test)]
mod tests {
use super::*;
use cljrs_types::span::Span;
use std::sync::Arc;
fn f(kind: FormKind) -> Form {
Form::new(kind, Span::new(Arc::new("<test>".to_string()), 0, 0, 1, 1))
}
fn kw(s: &str) -> Form {
f(FormKind::Keyword(s.to_string()))
}
fn sym(s: &str) -> Form {
f(FormKind::Symbol(s.to_string()))
}
fn keys(ns: MapNs, forms: Vec<Form>) -> Vec<FormKind> {
qualify_keys(&ns, forms)
.into_iter()
.map(|x| x.kind)
.collect()
}
fn lit(ns: &str) -> MapNs {
MapNs::Literal(ns.to_string())
}
#[test]
fn a_bare_prefix_is_only_legal_when_auto_resolved() {
assert_eq!(MapNs::parse("", true).unwrap(), MapNs::CurrentNs);
assert!(
MapNs::parse("", false)
.unwrap_err()
.contains("requires a namespace")
);
}
#[test]
fn prefix_must_be_an_unqualified_symbol() {
for (text, auto) in [
("foo/bar", false),
("foo/bar", true),
("1", false),
("nil", false),
("true", false),
("false", false),
(":kw", false),
] {
let err = match MapNs::parse(text, auto) {
Ok(ns) => panic!("{text} was accepted as a prefix: {ns:?}"),
Err(e) => e,
};
assert!(err.contains("unqualified symbol"), "{text}: {err}");
}
}
#[test]
fn ordinary_prefixes_parse() {
assert_eq!(MapNs::parse("adt", false).unwrap(), lit("adt"));
assert_eq!(
MapNs::parse("my.ns", false).unwrap(),
MapNs::Literal("my.ns".to_string())
);
assert_eq!(
MapNs::parse("al", true).unwrap(),
MapNs::Alias("al".to_string())
);
}
#[test]
fn literal_ns_qualifies_bare_keyword_keys() {
let got = keys(lit("adt"), vec![kw("a"), f(FormKind::Int(1))]);
assert_eq!(got[0], FormKind::Keyword("adt/a".to_string()));
assert_eq!(got[1], FormKind::Int(1));
}
#[test]
fn values_are_never_touched() {
let got = keys(lit("adt"), vec![kw("a"), kw("b")]);
assert_eq!(got[1], FormKind::Keyword("b".to_string()));
}
#[test]
fn explicit_namespace_on_a_key_wins() {
let got = keys(lit("adt"), vec![kw("other/a"), f(FormKind::Int(1))]);
assert_eq!(got[0], FormKind::Keyword("other/a".to_string()));
}
#[test]
fn underscore_namespace_unqualifies() {
let got = keys(lit("adt"), vec![kw("_/a"), f(FormKind::Int(1))]);
assert_eq!(got[0], FormKind::Keyword("a".to_string()));
}
#[test]
fn bare_auto_lowers_to_auto_keyword() {
let got = keys(MapNs::CurrentNs, vec![kw("a"), f(FormKind::Int(1))]);
assert_eq!(got[0], FormKind::AutoKeyword("a".to_string()));
}
#[test]
fn aliased_auto_lowers_to_auto_keyword_with_the_alias() {
let got = keys(
MapNs::Alias("al".to_string()),
vec![kw("a"), f(FormKind::Int(1))],
);
assert_eq!(got[0], FormKind::AutoKeyword("al/a".to_string()));
}
#[test]
fn symbol_keys_qualify_under_a_literal_namespace() {
let got = keys(lit("adt"), vec![sym("a"), f(FormKind::Int(1))]);
assert_eq!(got[0], FormKind::Symbol("adt/a".to_string()));
}
#[test]
fn symbol_keys_lower_to_auto_symbol_under_an_auto_resolved_namespace() {
let got = keys(MapNs::CurrentNs, vec![sym("a"), f(FormKind::Int(1))]);
assert_eq!(got[0], FormKind::AutoSymbol("a".to_string()));
let got = keys(
MapNs::Alias("al".to_string()),
vec![sym("a"), f(FormKind::Int(1))],
);
assert_eq!(got[0], FormKind::AutoSymbol("al/a".to_string()));
}
#[test]
fn slash_is_a_name_and_takes_the_map_namespace() {
let got = keys(lit("foo"), vec![sym("/"), f(FormKind::Int(1))]);
assert_eq!(got[0], FormKind::Symbol("foo//".to_string()));
let got = keys(lit("foo"), vec![kw("/"), f(FormKind::Int(1))]);
assert_eq!(got[0], FormKind::Keyword("foo//".to_string()));
}
#[test]
fn non_identifier_keys_pass_through() {
let got = keys(lit("adt"), vec![f(FormKind::Int(7)), f(FormKind::Int(1))]);
assert_eq!(got[0], FormKind::Int(7));
}
}