Skip to main content

cljrs_reader/
namespaced_map.rs

1//! Key qualification for namespaced map literals (`#:ns{…}`, `#::{…}`,
2//! `#::alias{…}`).
3//!
4//! Pure: the whole feature is a source-level rewrite of the keys of an
5//! already-parsed map, so it is expressed as functions over `Form` with no
6//! reader state. Auto-resolved spellings are NOT resolved here - they lower to
7//! `AutoKeyword` / `AutoSymbol`, which the evaluator resolves against the
8//! current namespace and its aliases.
9
10use crate::chars::is_symbol_start;
11use crate::form::{Form, FormKind};
12
13/// The namespace prefix of a namespaced map literal, after validation.
14///
15/// The JVM reader reads the prefix as an unqualified `Symbol`, so `#:foo/bar`,
16/// `#:1` and `#:nil` are read errors. Parsing into this type performs that
17/// check once, and leaves `(namespace, auto)` combinations that cannot occur -
18/// such as a bare `#:{…}` - unrepresentable.
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub enum MapNs {
21    /// `#:ns{…}` - the namespace is written out.
22    Literal(String),
23    /// `#::{…}` - the namespace the form is read in.
24    CurrentNs,
25    /// `#::alias{…}` - a `:require :as` alias, resolved by the evaluator.
26    Alias(String),
27}
28
29impl MapNs {
30    /// Validate the prefix text following `#:` (or `#::` when `auto`).
31    ///
32    /// `Err` carries a reader-facing message.
33    pub fn parse(text: &str, auto: bool) -> Result<Self, String> {
34        if text.is_empty() {
35            return if auto {
36                Ok(MapNs::CurrentNs)
37            } else {
38                Err("namespaced map literal requires a namespace".to_string())
39            };
40        }
41        validate_unqualified_symbol(text)?;
42        Ok(if auto {
43            MapNs::Alias(text.to_string())
44        } else {
45            MapNs::Literal(text.to_string())
46        })
47    }
48}
49
50/// A namespaced map's prefix must read as an unqualified symbol.
51fn validate_unqualified_symbol(text: &str) -> Result<(), String> {
52    let first = text.chars().next().expect("caller checked non-empty");
53    if !is_symbol_start(first) {
54        return Err(format!(
55            "namespaced map prefix must be an unqualified symbol, got '{text}'"
56        ));
57    }
58    if text.contains('/') {
59        return Err(format!(
60            "namespaced map prefix must be an unqualified symbol, got '{text}' \
61             (a prefix carries no namespace of its own)"
62        ));
63    }
64    if matches!(text, "nil" | "true" | "false") {
65        return Err(format!(
66            "namespaced map prefix must be an unqualified symbol, got '{text}'"
67        ));
68    }
69    Ok(())
70}
71
72/// Qualify the keys of a namespaced map literal's body.
73///
74/// `forms` is the flat key/value vector of the map; only even indices are
75/// touched.
76pub fn qualify_keys(ns: &MapNs, forms: Vec<Form>) -> Vec<Form> {
77    forms
78        .into_iter()
79        .enumerate()
80        .map(|(i, form)| {
81            if i.is_multiple_of(2) {
82                qualify_key(ns, form)
83            } else {
84                form
85            }
86        })
87        .collect()
88}
89
90/// Qualify one key. Keys that already carry a namespace, and key forms that are
91/// neither a keyword nor a symbol, pass through untouched - matching the JVM
92/// reader.
93fn qualify_key(ns: &MapNs, key: Form) -> Form {
94    let span = key.span.clone();
95    let kind = match key.kind {
96        FormKind::Keyword(name) => match qualified(ns, &name) {
97            Qualified::Unchanged => FormKind::Keyword(name),
98            Qualified::Literal(full) => FormKind::Keyword(full),
99            Qualified::Auto(full) => FormKind::AutoKeyword(full),
100        },
101        FormKind::Symbol(name) => match qualified(ns, &name) {
102            Qualified::Unchanged => FormKind::Symbol(name),
103            Qualified::Literal(full) => FormKind::Symbol(full),
104            Qualified::Auto(full) => FormKind::AutoSymbol(full),
105        },
106        other => other,
107    };
108    Form::new(kind, span)
109}
110
111/// What qualifying a bare key name yields.
112enum Qualified {
113    /// Already namespaced - leave the key alone.
114    Unchanged,
115    /// A fully-spelled `ns/name`.
116    Literal(String),
117    /// An auto-resolved `alias/name` (or bare `name` for `#::`).
118    Auto(String),
119}
120
121fn qualified(ns: &MapNs, name: &str) -> Qualified {
122    // `/` is a name, not a separator: `#:foo{/ 1}` has the key `foo//`.
123    let already_namespaced = match name.split_once('/') {
124        // `:_/k` explicitly opts the key OUT of the map's namespace.
125        Some(("_", bare)) => return Qualified::Literal(bare.to_string()),
126        Some(_) if name != "/" => true,
127        _ => false,
128    };
129    if already_namespaced {
130        return Qualified::Unchanged;
131    }
132    match ns {
133        MapNs::CurrentNs => Qualified::Auto(name.to_string()),
134        MapNs::Alias(alias) => Qualified::Auto(format!("{alias}/{name}")),
135        MapNs::Literal(literal) => Qualified::Literal(format!("{literal}/{name}")),
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142    use cljrs_types::span::Span;
143    use std::sync::Arc;
144
145    fn f(kind: FormKind) -> Form {
146        Form::new(kind, Span::new(Arc::new("<test>".to_string()), 0, 0, 1, 1))
147    }
148
149    fn kw(s: &str) -> Form {
150        f(FormKind::Keyword(s.to_string()))
151    }
152
153    fn sym(s: &str) -> Form {
154        f(FormKind::Symbol(s.to_string()))
155    }
156
157    fn keys(ns: MapNs, forms: Vec<Form>) -> Vec<FormKind> {
158        qualify_keys(&ns, forms)
159            .into_iter()
160            .map(|x| x.kind)
161            .collect()
162    }
163
164    fn lit(ns: &str) -> MapNs {
165        MapNs::Literal(ns.to_string())
166    }
167
168    // ── prefix validation ───────────────────────────────────────────────────
169
170    #[test]
171    fn a_bare_prefix_is_only_legal_when_auto_resolved() {
172        assert_eq!(MapNs::parse("", true).unwrap(), MapNs::CurrentNs);
173        assert!(
174            MapNs::parse("", false)
175                .unwrap_err()
176                .contains("requires a namespace")
177        );
178    }
179
180    #[test]
181    fn prefix_must_be_an_unqualified_symbol() {
182        for (text, auto) in [
183            ("foo/bar", false),
184            ("foo/bar", true),
185            ("1", false),
186            ("nil", false),
187            ("true", false),
188            ("false", false),
189            (":kw", false),
190        ] {
191            let err = match MapNs::parse(text, auto) {
192                Ok(ns) => panic!("{text} was accepted as a prefix: {ns:?}"),
193                Err(e) => e,
194            };
195            assert!(err.contains("unqualified symbol"), "{text}: {err}");
196        }
197    }
198
199    #[test]
200    fn ordinary_prefixes_parse() {
201        assert_eq!(MapNs::parse("adt", false).unwrap(), lit("adt"));
202        assert_eq!(
203            MapNs::parse("my.ns", false).unwrap(),
204            MapNs::Literal("my.ns".to_string())
205        );
206        assert_eq!(
207            MapNs::parse("al", true).unwrap(),
208            MapNs::Alias("al".to_string())
209        );
210    }
211
212    // ── key qualification ───────────────────────────────────────────────────
213
214    #[test]
215    fn literal_ns_qualifies_bare_keyword_keys() {
216        let got = keys(lit("adt"), vec![kw("a"), f(FormKind::Int(1))]);
217        assert_eq!(got[0], FormKind::Keyword("adt/a".to_string()));
218        assert_eq!(got[1], FormKind::Int(1));
219    }
220
221    #[test]
222    fn values_are_never_touched() {
223        // A value that LOOKS like a bare key must survive unqualified.
224        let got = keys(lit("adt"), vec![kw("a"), kw("b")]);
225        assert_eq!(got[1], FormKind::Keyword("b".to_string()));
226    }
227
228    #[test]
229    fn explicit_namespace_on_a_key_wins() {
230        let got = keys(lit("adt"), vec![kw("other/a"), f(FormKind::Int(1))]);
231        assert_eq!(got[0], FormKind::Keyword("other/a".to_string()));
232    }
233
234    #[test]
235    fn underscore_namespace_unqualifies() {
236        let got = keys(lit("adt"), vec![kw("_/a"), f(FormKind::Int(1))]);
237        assert_eq!(got[0], FormKind::Keyword("a".to_string()));
238    }
239
240    #[test]
241    fn bare_auto_lowers_to_auto_keyword() {
242        let got = keys(MapNs::CurrentNs, vec![kw("a"), f(FormKind::Int(1))]);
243        assert_eq!(got[0], FormKind::AutoKeyword("a".to_string()));
244    }
245
246    #[test]
247    fn aliased_auto_lowers_to_auto_keyword_with_the_alias() {
248        let got = keys(
249            MapNs::Alias("al".to_string()),
250            vec![kw("a"), f(FormKind::Int(1))],
251        );
252        assert_eq!(got[0], FormKind::AutoKeyword("al/a".to_string()));
253    }
254
255    #[test]
256    fn symbol_keys_qualify_under_a_literal_namespace() {
257        let got = keys(lit("adt"), vec![sym("a"), f(FormKind::Int(1))]);
258        assert_eq!(got[0], FormKind::Symbol("adt/a".to_string()));
259    }
260
261    #[test]
262    fn symbol_keys_lower_to_auto_symbol_under_an_auto_resolved_namespace() {
263        let got = keys(MapNs::CurrentNs, vec![sym("a"), f(FormKind::Int(1))]);
264        assert_eq!(got[0], FormKind::AutoSymbol("a".to_string()));
265
266        let got = keys(
267            MapNs::Alias("al".to_string()),
268            vec![sym("a"), f(FormKind::Int(1))],
269        );
270        assert_eq!(got[0], FormKind::AutoSymbol("al/a".to_string()));
271    }
272
273    #[test]
274    fn slash_is_a_name_and_takes_the_map_namespace() {
275        // `#:foo{/ 1}` has the symbol key `foo//`, `#:foo{:/ 1}` the keyword
276        // key `:foo//` - `/` is a name, not a namespace separator.
277        let got = keys(lit("foo"), vec![sym("/"), f(FormKind::Int(1))]);
278        assert_eq!(got[0], FormKind::Symbol("foo//".to_string()));
279
280        let got = keys(lit("foo"), vec![kw("/"), f(FormKind::Int(1))]);
281        assert_eq!(got[0], FormKind::Keyword("foo//".to_string()));
282    }
283
284    #[test]
285    fn non_identifier_keys_pass_through() {
286        let got = keys(lit("adt"), vec![f(FormKind::Int(7)), f(FormKind::Int(1))]);
287        assert_eq!(got[0], FormKind::Int(7));
288    }
289}