1use std::any::Any;
2use std::collections::HashMap;
3use std::fmt;
4use std::sync::OnceLock;
5
6use super::{Shared, Val};
7
8pub trait HostType: Send + Sync + fmt::Debug + fmt::Display {
12 fn type_name(&self) -> &'static str;
15
16 fn eq_atom(&self, other: &dyn HostType) -> bool;
19
20 fn encode(&self, out: &mut Vec<u8>);
21
22 fn as_any(&self) -> &dyn Any;
23}
24
25#[derive(Clone)]
28pub struct Atom(pub Shared<dyn HostType>);
29
30impl Atom {
31 pub fn new<T: HostType + 'static>(t: T) -> Atom {
32 Atom(Shared::new(t))
33 }
34
35 pub fn downcast<T: 'static>(&self) -> Option<&T> {
36 self.0.as_any().downcast_ref::<T>()
37 }
38
39 pub fn type_name(&self) -> &'static str {
40 self.0.type_name()
41 }
42}
43
44impl PartialEq for Atom {
45 fn eq(&self, other: &Self) -> bool {
46 Shared::ptr_eq(&self.0, &other.0) || self.0.eq_atom(&*other.0)
47 }
48}
49
50impl fmt::Debug for Atom {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 fmt::Debug::fmt(&*self.0, f)
53 }
54}
55
56impl fmt::Display for Atom {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 fmt::Display::fmt(&*self.0, f)
59 }
60}
61
62pub type OpResult = Result<Val, Val>;
65
66pub struct OpDef {
67 pub name: &'static str,
68 pub arity: usize,
69 pub run: fn(&[Val]) -> OpResult,
70}
71
72pub type Decoder = fn(&[u8]) -> Option<Atom>;
73
74#[derive(Default)]
77pub struct Registry {
78 ops: Vec<OpDef>,
79 by_name: HashMap<&'static str, u16>,
80 decoders: HashMap<&'static str, Decoder>,
81}
82
83impl Registry {
84 pub fn new() -> Registry {
85 Registry::default()
86 }
87
88 pub fn op(mut self, def: OpDef) -> Registry {
89 let idx = u16::try_from(self.ops.len()).expect("op table exceeds u16");
90 if self.by_name.insert(def.name, idx).is_some() {
91 panic!("duplicate op {}", def.name);
92 }
93 self.ops.push(def);
94 self
95 }
96
97 pub fn ty(mut self, name: &'static str, decode: Decoder) -> Registry {
98 if self.decoders.insert(name, decode).is_some() {
99 panic!("duplicate host type {name}");
100 }
101 self
102 }
103
104 pub fn lookup(&self, name: &str) -> Option<u16> {
105 self.by_name.get(name).copied()
106 }
107
108 pub fn get(&self, idx: u16) -> &OpDef {
109 &self.ops[idx as usize]
110 }
111
112 pub fn decoder(&self, type_name: &str) -> Option<Decoder> {
113 self.decoders.get(type_name).copied()
114 }
115}
116
117static REGISTRY: OnceLock<Registry> = OnceLock::new();
118
119pub fn install(r: Registry) -> Result<(), Registry> {
122 REGISTRY.set(r)
123}
124
125pub fn registry() -> &'static Registry {
129 REGISTRY.get_or_init(crate::registry)
130}
131
132fn refused(tag: &str, detail: Val) -> Val {
134 Val::Pair(super::rc_val(Val::Sym(tag.into())), super::rc_val(detail))
135}
136
137fn wrong_type(op: &str) -> Val {
138 refused("wrong-type", Val::Sym(op.into()))
139}
140
141fn as_f64(v: &Val) -> Option<f64> {
142 match v {
143 Val::Num(n) => Some(*n as f64),
144 Val::Flo(x) => Some(*x),
145 _ => None,
146 }
147}
148
149macro_rules! flo1 {
152 ($name:literal, $f:expr) => {
153 OpDef {
154 name: $name,
155 arity: 1,
156 run: |a| match as_f64(&a[0]) {
157 Some(x) => Ok(Val::Flo($f(x))),
158 None => Err(wrong_type($name)),
159 },
160 }
161 };
162}
163
164macro_rules! int1 {
165 ($name:literal, $f:expr) => {
166 OpDef {
167 name: $name,
168 arity: 1,
169 run: |a| match &a[0] {
170 Val::Num(n) => Ok(Val::Num(*n)),
171 Val::Flo(x) => Ok(Val::Num($f(*x) as i64)),
172 _ => Err(wrong_type($name)),
173 },
174 }
175 };
176}
177
178pub fn builtins() -> Registry {
179 Registry::new()
180 .ty(Str::NAME, Str::decode)
181 .op(OpDef {
182 name: "str-append",
183 arity: 2,
184 run: |a| match (Str::of(&a[0]), Str::of(&a[1])) {
185 (Some(x), Some(y)) => Ok(Str::val(format!("{x}{y}"))),
186 _ => Err(wrong_type("str-append")),
187 },
188 })
189 .op(OpDef {
190 name: "str-len",
191 arity: 1,
192 run: |a| match Str::of(&a[0]) {
193 Some(s) => Ok(Val::Num(s.chars().count() as i64)),
194 None => Err(wrong_type("str-len")),
195 },
196 })
197 .op(OpDef {
198 name: "sym->str",
199 arity: 1,
200 run: |a| match &a[0] {
201 Val::Sym(s) => Ok(Str::val(s.to_string())),
202 _ => Err(wrong_type("sym->str")),
203 },
204 })
205 .op(OpDef {
206 name: "str->sym",
207 arity: 1,
208 run: |a| match Str::of(&a[0]) {
209 Some(s) => Ok(Val::Sym(s.into())),
210 None => Err(wrong_type("str->sym")),
211 },
212 })
213 .op(OpDef {
216 name: "substr",
217 arity: 3,
218 run: |a| match (Str::of(&a[0]), &a[1], &a[2]) {
219 (Some(s), Val::Num(from), Val::Num(len)) => {
220 let from = (*from).max(0) as usize;
221 let len = (*len).max(0) as usize;
222 Ok(Str::val(s.chars().skip(from).take(len).collect::<String>()))
223 }
224 _ => Err(wrong_type("substr")),
225 },
226 })
227 .op(OpDef {
232 name: "assq",
233 arity: 2,
234 run: |a| {
235 let mut al = &a[1];
236 while let Val::Pair(entry, rest) = al {
237 if let Val::Pair(key, _) = &**entry {
238 if **key == a[0] {
239 return Ok(Val::clone(entry));
240 }
241 }
242 al = rest;
243 }
244 Ok(Val::Num(0))
245 },
246 })
247 .op(OpDef {
250 name: "str->num",
251 arity: 1,
252 run: |a| match Str::of(&a[0]) {
253 Some(s) => {
254 let t = s.trim();
255 if let Ok(n) = t.parse::<i64>() {
256 return Ok(Val::Num(n));
257 }
258 match t.parse::<f64>() {
259 Ok(x) => Ok(Val::Flo(x)),
260 Err(_) => Err(refused("not-a-number", Str::val(s.to_string()))),
261 }
262 }
263 None => Err(wrong_type("str->num")),
264 },
265 })
266 .op(OpDef {
269 name: "str-upper",
270 arity: 1,
271 run: |a| match Str::of(&a[0]) {
272 Some(s) => Ok(Str::val(s.to_uppercase())),
273 None => Err(wrong_type("str-upper")),
274 },
275 })
276 .op(OpDef {
277 name: "str-lower",
278 arity: 1,
279 run: |a| match Str::of(&a[0]) {
280 Some(s) => Ok(Str::val(s.to_lowercase())),
281 None => Err(wrong_type("str-lower")),
282 },
283 })
284 .op(OpDef {
288 name: "ord",
289 arity: 1,
290 run: |a| match Str::of(&a[0]) {
291 Some(s) => match s.chars().next() {
292 Some(c) => Ok(Val::Num(c as i64)),
293 None => Err(refused("empty-string", Val::Sym("ord".into()))),
294 },
295 None => Err(wrong_type("ord")),
296 },
297 })
298 .op(OpDef {
299 name: "chr",
300 arity: 1,
301 run: |a| match &a[0] {
302 Val::Num(n) => match u32::try_from(*n).ok().and_then(char::from_u32) {
303 Some(c) => Ok(Str::val(c.to_string())),
304 None => Err(refused("not-a-character", Val::Num(*n))),
305 },
306 _ => Err(wrong_type("chr")),
307 },
308 })
309 .op(OpDef {
312 name: "show",
313 arity: 1,
314 run: |a| Ok(Str::val(a[0].to_string())),
315 })
316 .op(flo1!("sqrt", f64::sqrt))
319 .op(flo1!("exp", f64::exp))
320 .op(flo1!("log", f64::ln))
321 .op(flo1!("sin", f64::sin))
322 .op(flo1!("cos", f64::cos))
323 .op(flo1!("tan", f64::tan))
324 .op(flo1!("asin", f64::asin))
325 .op(flo1!("acos", f64::acos))
326 .op(flo1!("atan", f64::atan))
327 .op(OpDef {
328 name: "pow",
329 arity: 2,
330 run: |a| match (as_f64(&a[0]), as_f64(&a[1])) {
331 (Some(x), Some(y)) => Ok(Val::Flo(x.powf(y))),
332 _ => Err(wrong_type("pow")),
333 },
334 })
335 .op(OpDef {
338 name: "atan2",
339 arity: 2,
340 run: |a| match (as_f64(&a[0]), as_f64(&a[1])) {
341 (Some(y), Some(x)) => Ok(Val::Flo(y.atan2(x))),
342 _ => Err(wrong_type("atan2")),
343 },
344 })
345 .op(OpDef {
346 name: "read",
347 arity: 1,
348 run: super::read::read_op,
349 })
350 .op(OpDef {
351 name: "trans",
352 arity: 2,
353 run: super::read::trans_op,
354 })
355 .op(int1!("floor", f64::floor))
358 .op(int1!("ceil", f64::ceil))
359 .op(int1!("round", f64::round))
360 .op(int1!("trunc", f64::trunc))
361}
362
363#[derive(Debug, Clone, PartialEq)]
366pub struct Str(pub Shared<str>);
367
368impl Str {
369 pub const NAME: &'static str = "str";
370
371 pub fn val(s: impl Into<Shared<str>>) -> Val {
372 Val::Atom(Atom::new(Str(s.into())))
373 }
374
375 pub fn of(v: &Val) -> Option<&str> {
376 match v {
377 Val::Atom(a) => a.downcast::<Str>().map(|s| &*s.0),
378 _ => None,
379 }
380 }
381
382 fn decode(bytes: &[u8]) -> Option<Atom> {
383 std::str::from_utf8(bytes)
384 .ok()
385 .map(|s| Atom::new(Str(s.into())))
386 }
387}
388
389impl fmt::Display for Str {
390 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
391 write!(f, "{:?}", &*self.0)
392 }
393}
394
395impl HostType for Str {
396 fn type_name(&self) -> &'static str {
397 Str::NAME
398 }
399
400 fn eq_atom(&self, other: &dyn HostType) -> bool {
401 other
402 .as_any()
403 .downcast_ref::<Str>()
404 .is_some_and(|o| self.0 == o.0)
405 }
406
407 fn encode(&self, out: &mut Vec<u8>) {
408 out.extend_from_slice(self.0.as_bytes());
409 }
410
411 fn as_any(&self) -> &dyn Any {
412 self
413 }
414}
415
416#[cfg(test)]
417mod tests {
418 use super::*;
419
420 fn call(name: &str, args: &[Val]) -> OpResult {
421 let r = builtins();
422 let idx = r.lookup(name).expect("op present");
423 let def = r.get(idx);
424 assert_eq!(def.arity, args.len());
425 (def.run)(args)
426 }
427
428 #[test]
429 fn atoms_compare_by_value_within_a_type() {
430 assert_eq!(Str::val("ab"), Str::val("ab"));
431 assert_ne!(Str::val("ab"), Str::val("ba"));
432 assert_ne!(Str::val("ab"), Val::Sym("ab".into()));
433 }
434
435 #[test]
436 fn ops_dispatch_and_throw_narju_values() {
437 assert_eq!(
438 call("str-append", &[Str::val("na"), Str::val("rju")]),
439 Ok(Str::val("narju"))
440 );
441 assert_eq!(call("str-len", &[Str::val("narju")]), Ok(Val::Num(5)));
442 assert!(matches!(
443 call("str-len", &[Val::Num(1)]),
444 Err(Val::Pair(_, _))
445 ));
446 }
447
448 #[test]
449 fn a_substring_is_counted_in_characters_and_clamped() {
450 let s = Str::val("nàrju");
451 assert_eq!(
452 call("substr", &[s.clone(), Val::Num(1), Val::Num(2)]),
453 Ok(Str::val("àr"))
454 );
455 assert_eq!(
456 call("substr", &[s.clone(), Val::Num(3), Val::Num(99)]),
457 Ok(Str::val("ju"))
458 );
459 assert_eq!(
460 call("substr", &[s, Val::Num(9), Val::Num(1)]),
461 Ok(Str::val(""))
462 );
463 }
464
465 #[test]
466 fn a_symbol_names_the_text_it_was_made_from() {
467 assert_eq!(
468 call("str->sym", &[Str::val("narju")]),
469 Ok(Val::Sym("narju".into()))
470 );
471 assert_eq!(
472 call("sym->str", &[Val::Sym("narju".into())]),
473 Ok(Str::val("narju"))
474 );
475 }
476
477 #[test]
478 fn a_host_value_round_trips_the_wire_encoding() {
479 let atom = match Str::val("narju") {
480 Val::Atom(a) => a,
481 _ => unreachable!(),
482 };
483 let mut bytes = Vec::new();
484 atom.0.encode(&mut bytes);
485 let decode = builtins().decoder(atom.type_name()).expect("decoder");
486 assert_eq!(decode(&bytes), Some(atom));
487 }
488}