1use std::collections::BTreeMap;
2use std::sync::{Arc, Mutex};
3
4use tatara_lisp_eval::ffi::{Arity, Caller};
5use tatara_lisp_eval::value::MapKey;
6use tatara_lisp_eval::vm::run::CompiledClosure;
7use tatara_lisp_eval::{EvalError, Interpreter, Value};
8
9use crate::errors::raise_kind;
10
11pub use blue_lang_syntax::MASTER_VERBS;
12
13pub const KINDS: &[&str] = &[
14 "map", "list", "string", "int", "float", "bool", "keyword", "function",
15];
16
17const SPACE_KEY: &str = "space";
18const OF_KEY: &str = "of";
19const USAGE: &str = "usage";
20const STATE: &str = "blue-dispatch-state";
21
22#[derive(Default, Clone)]
23struct Registry {
24 handlers: BTreeMap<(String, String), Value>,
25 kinds: BTreeMap<String, String>,
26}
27
28type Shared = Arc<Mutex<Registry>>;
29
30fn key(name: &str) -> MapKey {
31 MapKey::Keyword(Arc::from(name))
32}
33
34#[must_use]
35pub fn kind_of(v: &Value) -> Option<&'static str> {
36 match v {
37 Value::Map(_) => Some("map"),
38 Value::List(_) => Some("list"),
39 Value::Str(_) => Some("string"),
40 Value::Int(_) => Some("int"),
41 Value::Float(_) => Some("float"),
42 Value::Bool(_) => Some("bool"),
43 Value::Keyword(_) => Some("keyword"),
44 other if procedure(other) => Some("function"),
45 _ => None,
46 }
47}
48
49fn tagged(v: &Value) -> Option<(&str, &Value)> {
50 let Value::Map(m) = v else { return None };
51 if m.len() != 2 {
52 return None;
53 }
54 match (m.get(&key(SPACE_KEY)), m.get(&key(OF_KEY))) {
55 (Some(Value::Str(space)), Some(of)) => Some((space, of)),
56 _ => None,
57 }
58}
59
60#[must_use]
61pub fn tag(space: &str, value: Value) -> Value {
62 Value::map([
63 (key(SPACE_KEY), Value::Str(Arc::from(space))),
64 (key(OF_KEY), value),
65 ])
66}
67
68fn usage(message: String, sp: tatara_lisp::Span) -> EvalError {
69 raise_kind(USAGE, message, sp)
70}
71
72fn text<'a>(
73 who: &str,
74 what: &str,
75 v: &'a Value,
76 sp: tatara_lisp::Span,
77) -> Result<&'a str, EvalError> {
78 match v {
79 Value::Str(s) if !s.is_empty() => Ok(s),
80 other => Err(usage(
81 format!(
82 "{who}: {what} is a non-empty string, got {}",
83 other.type_name()
84 ),
85 sp,
86 )),
87 }
88}
89
90fn space_of(reg: &Registry, subject: &Value) -> Option<String> {
91 if let Some((space, _)) = tagged(subject) {
92 return Some(space.to_string());
93 }
94 kind_of(subject).and_then(|k| reg.kinds.get(k).cloned())
95}
96
97fn dispatch<H: 'static>(
98 reg: &Shared,
99 verb: &str,
100 args: &[Value],
101 host: &mut H,
102 caller: &Caller<H>,
103 sp: tatara_lisp::Span,
104) -> Result<Value, EvalError> {
105 let subject = &args[0];
106 let handler = {
107 let guard = reg.lock().unwrap_or_else(|p| p.into_inner());
108 let Some(space) = space_of(&guard, subject) else {
109 return Err(usage(
110 format!(
111 "{verb}: no space owns a {} subject; name one with in_space(\"<space>\", subject)",
112 kind_of(subject).unwrap_or_else(|| subject.type_name())
113 ),
114 sp,
115 ));
116 };
117 let Some(handler) = guard
118 .handlers
119 .get(&(verb.to_string(), space.clone()))
120 .cloned()
121 else {
122 let owners: Vec<&str> = guard
123 .handlers
124 .keys()
125 .filter(|(v, _)| v == verb)
126 .map(|(_, s)| s.as_str())
127 .collect();
128 let have = if owners.is_empty() {
129 "no space has loaded one".to_string()
130 } else {
131 format!("loaded: {}", owners.join(", "))
132 };
133 return Err(usage(
134 format!(
135 "{verb}: space {space} has no handler ({have}); load it with use(\"{space}\")"
136 ),
137 sp,
138 ));
139 };
140 handler
141 };
142 let mut call = Vec::with_capacity(args.len());
143 call.push(tagged(subject).map_or_else(|| subject.clone(), |(_, of)| of.clone()));
144 call.extend(args[1..].iter().cloned());
145 caller.apply_value(&handler, call, host, sp)
146}
147
148fn required(verb: &str) -> usize {
149 match verb {
150 "put" | "update" | "create" => 3,
151 "need" | "has?" | "delete" | "diff" => 2,
152 _ => 1,
153 }
154}
155
156fn procedure(v: &Value) -> bool {
157 match v {
158 Value::Closure(_) | Value::NativeFn(_) => true,
159 Value::Foreign(any) => any.is::<CompiledClosure>(),
160 _ => false,
161 }
162}
163
164pub fn install<H: 'static>(interp: &mut Interpreter<H>) {
165 let inherited = match interp.lookup_global(STATE) {
166 Some(Value::Foreign(f)) => f
167 .downcast::<Mutex<Registry>>()
168 .ok()
169 .map(|r| r.lock().unwrap_or_else(|p| p.into_inner()).clone()),
170 _ => None,
171 };
172 let reg: Shared = Arc::new(Mutex::new(inherited.unwrap_or_default()));
173 interp.define_global(STATE, Value::Foreign(reg.clone()));
174
175 let r = reg.clone();
176 interp.register_fn(
177 "verb_for",
178 Arity::Exact(3),
179 move |a: &[Value], _h: &mut H, sp| {
180 let space = text("verb_for", "the space", &a[0], sp)?;
181 let verb = text("verb_for", "the verb", &a[1], sp)?;
182 if !MASTER_VERBS.contains(&verb) {
183 return Err(usage(
184 format!(
185 "verb_for: {verb} is not a dispatched master verb; they are {}",
186 MASTER_VERBS.join(", ")
187 ),
188 sp,
189 ));
190 }
191 if !procedure(&a[2]) {
192 return Err(usage(
193 format!(
194 "verb_for: the handler is a function, got {}",
195 a[2].type_name()
196 ),
197 sp,
198 ));
199 }
200 r.lock()
201 .unwrap_or_else(|p| p.into_inner())
202 .handlers
203 .insert((verb.to_string(), space.to_string()), a[2].clone());
204 Ok(Value::Nil)
205 },
206 );
207
208 let r = reg.clone();
209 interp.register_fn(
210 "space_for",
211 Arity::Exact(2),
212 move |a: &[Value], _h: &mut H, sp| {
213 let kind = match &a[0] {
214 Value::Keyword(k) if KINDS.contains(&&**k) => k.to_string(),
215 other => {
216 return Err(usage(
217 format!(
218 "space_for: the kind is one of {}, got {}",
219 KINDS.join(", "),
220 crate::literal::render_value(other)
221 ),
222 sp,
223 ))
224 }
225 };
226 let space = text("space_for", "the space", &a[1], sp)?.to_string();
227 let mut guard = r.lock().unwrap_or_else(|p| p.into_inner());
228 match guard.kinds.get(&kind) {
229 Some(owner) if *owner != space => Err(usage(
230 format!("space_for: kind {kind} already belongs to space {owner}"),
231 sp,
232 )),
233 _ => {
234 guard.kinds.insert(kind, space);
235 Ok(Value::Nil)
236 }
237 }
238 },
239 );
240
241 interp.register_fn(
242 "in_space",
243 Arity::Exact(2),
244 |a: &[Value], _h: &mut H, sp| {
245 let space = text("in_space", "the space", &a[0], sp)?;
246 match tagged(&a[1]) {
247 Some((owner, _)) if owner == space => Ok(a[1].clone()),
248 Some((owner, _)) => Err(usage(
249 format!("in_space: the subject is already in space {owner}, not {space}"),
250 sp,
251 )),
252 None => Ok(tag(space, a[1].clone())),
253 }
254 },
255 );
256
257 let r = reg.clone();
258 interp.register_fn(
259 "space_of",
260 Arity::Exact(1),
261 move |a: &[Value], _h: &mut H, _sp| {
262 let guard = r.lock().unwrap_or_else(|p| p.into_inner());
263 Ok(space_of(&guard, &a[0]).map_or(Value::Nil, |s| Value::Str(Arc::from(s))))
264 },
265 );
266
267 for verb in MASTER_VERBS {
268 let r = reg.clone();
269 let name: &'static str = verb;
270 interp.register_higher_order_fn(
271 name,
272 Arity::AtLeast(required(name)),
273 move |a: &[Value], host: &mut H, caller: &Caller<H>, sp| {
274 dispatch(&r, name, a, host, caller, sp)
275 },
276 );
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283
284 #[test]
285 fn tag_is_read_back_as_its_space_and_subject() {
286 let v = tag("gitto", Value::Str(Arc::from(".")));
287 let (space, of) = tagged(&v).expect("tagged");
288 assert_eq!(space, "gitto");
289 assert!(matches!(of, Value::Str(s) if &**s == "."));
290 }
291
292 #[test]
293 fn a_map_with_other_keys_is_not_a_tag() {
294 let three = Value::map([
295 (key(SPACE_KEY), Value::Str(Arc::from("x"))),
296 (key(OF_KEY), Value::Nil),
297 (key("extra"), Value::Nil),
298 ]);
299 assert!(tagged(&three).is_none());
300 let wrong_type = Value::map([(key(SPACE_KEY), Value::Int(1)), (key(OF_KEY), Value::Nil)]);
301 assert!(tagged(&wrong_type).is_none());
302 assert!(tagged(&Value::map([])).is_none());
303 assert!(tagged(&Value::Nil).is_none());
304 }
305
306 #[test]
307 fn kinds_name_every_dispatchable_value() {
308 assert_eq!(kind_of(&Value::map([])), Some("map"));
309 assert_eq!(kind_of(&Value::Str(Arc::from("p"))), Some("string"));
310 assert_eq!(kind_of(&Value::Int(1)), Some("int"));
311 assert_eq!(kind_of(&Value::Nil), None);
312 }
313
314 #[test]
315 fn a_kind_resolves_only_when_registered_and_a_tag_always_wins() {
316 let mut reg = Registry::default();
317 let plain = Value::map([(key("a"), Value::Int(1))]);
318 assert_eq!(space_of(®, &plain), None);
319 reg.kinds.insert("map".into(), "jisho".into());
320 assert_eq!(space_of(®, &plain).as_deref(), Some("jisho"));
321 assert_eq!(
322 space_of(®, &tag("gitto", Value::Nil)).as_deref(),
323 Some("gitto")
324 );
325 assert_eq!(space_of(®, &Value::Nil), None);
326 }
327
328 #[test]
329 fn a_verb_requires_the_positionals_its_contract_names() {
330 let wants: Vec<(&str, usize)> = MASTER_VERBS.iter().map(|v| (*v, required(v))).collect();
331 assert_eq!(
332 wants,
333 vec![
334 ("read", 1),
335 ("need", 2),
336 ("has?", 2),
337 ("put", 3),
338 ("update", 3),
339 ("delete", 2),
340 ("create", 3),
341 ("diff", 2),
342 ("info", 1),
343 ("exists?", 1),
344 ]
345 );
346 }
347
348 #[test]
349 fn every_master_verb_is_unique() {
350 let mut seen = std::collections::BTreeSet::new();
351 for v in MASTER_VERBS {
352 assert!(seen.insert(*v), "{v} listed twice");
353 }
354 }
355}