Skip to main content

blue_lang_runtime/
dispatch.rs

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(&reg, &plain), None);
319        reg.kinds.insert("map".into(), "jisho".into());
320        assert_eq!(space_of(&reg, &plain).as_deref(), Some("jisho"));
321        assert_eq!(
322            space_of(&reg, &tag("gitto", Value::Nil)).as_deref(),
323            Some("gitto")
324        );
325        assert_eq!(space_of(&reg, &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}