1use crate::chars::is_symbol_start;
11use crate::form::{Form, FormKind};
12
13#[derive(Debug, Clone, PartialEq, Eq)]
20pub enum MapNs {
21 Literal(String),
23 CurrentNs,
25 Alias(String),
27}
28
29impl MapNs {
30 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
50fn 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
72pub 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
90fn 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
111enum Qualified {
113 Unchanged,
115 Literal(String),
117 Auto(String),
119}
120
121fn qualified(ns: &MapNs, name: &str) -> Qualified {
122 let already_namespaced = match name.split_once('/') {
124 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 #[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 #[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 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 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}