1use tatara_lisp_eval::ffi::Arity;
36use tatara_lisp_eval::{EvalError, Interpreter, Value};
37
38fn as_str(v: &Value, span: tatara_lisp::Span) -> Result<String, EvalError> {
39 match v {
40 Value::Str(s) => Ok(s.to_string()),
41 Value::Symbol(s) | Value::Keyword(s) => Ok(s.to_string()),
44 other => Err(EvalError::type_mismatch(
45 "a string",
46 other.type_name(),
47 span,
48 )),
49 }
50}
51
52pub fn blue_equal(a: &Value, b: &Value) -> bool {
65 match (a, b) {
66 (Value::Nil, Value::Nil) => true,
67 (Value::Bool(x), Value::Bool(y)) => x == y,
68 (Value::Int(x), Value::Int(y)) => x == y,
69 (Value::Float(x), Value::Float(y)) => x == y,
70 (Value::Int(x), Value::Float(y)) | (Value::Float(y), Value::Int(x)) => (*x as f64) == *y,
71 (Value::Str(x), Value::Str(y))
72 | (Value::Symbol(x), Value::Symbol(y))
73 | (Value::Keyword(x), Value::Keyword(y)) => x == y,
74 (Value::List(x), Value::List(y)) => {
75 x.len() == y.len() && x.iter().zip(y.iter()).all(|(p, q)| blue_equal(p, q))
76 }
77 (Value::Map(x), Value::Map(y)) => {
78 x.len() == y.len()
79 && x.iter().all(|(k, v)| y.get(k).is_some_and(|w| blue_equal(v, w)))
80 }
81 (Value::Closure(x), Value::Closure(y)) => std::sync::Arc::ptr_eq(x, y),
82 (Value::NativeFn(x), Value::NativeFn(y)) => x.name == y.name,
83 (Value::Sexp(x, _), Value::Sexp(y, _)) => x == y,
84 _ => false,
85 }
86}
87
88fn render(v: &Value) -> String {
93 match v {
94 Value::Nil => String::new(),
95 Value::Str(s) | Value::Symbol(s) | Value::Keyword(s) => s.to_string(),
96 other => literal(other),
97 }
98}
99
100fn literal(v: &Value) -> String {
104 match v {
105 Value::Nil => "nil".into(),
106 Value::Bool(b) => b.to_string(),
107 Value::Int(n) => n.to_string(),
108 Value::Float(x) => float_text(*x),
109 Value::Str(s) => quoted(s),
110 Value::Symbol(s) => s.to_string(),
111 Value::Keyword(s) => format!(":{s}"),
112 Value::List(items) => {
113 let parts: Vec<String> = items.iter().map(literal).collect();
114 format!("[{}]", parts.join(", "))
115 }
116 Value::Map(m) => {
117 let mut entries: Vec<String> = m
118 .iter()
119 .map(|(k, v)| format!("{} {}", key_text(k), literal(v)))
120 .collect();
121 entries.sort();
122 format!("{{{}}}", entries.join(", "))
123 }
124 other => other.type_name().to_string(),
125 }
126}
127
128fn key_text(k: &tatara_lisp_eval::value::MapKey) -> String {
131 use tatara_lisp_eval::value::MapKey;
132 match k {
133 MapKey::Keyword(s) | MapKey::Symbol(s) => format!("{s}:"),
134 MapKey::Str(s) => format!("{} =>", quoted(s)),
135 MapKey::Int(n) => format!("{n} =>"),
136 MapKey::Float(bits) => format!("{} =>", float_text(f64::from_bits(*bits))),
137 MapKey::Bool(b) => format!("{b} =>"),
138 MapKey::Nil => "nil =>".into(),
139 }
140}
141
142fn float_text(x: f64) -> String {
144 if x.is_finite() && x.fract() == 0.0 && x.abs() < 1e16 {
145 format!("{x:.1}")
146 } else {
147 format!("{x}")
148 }
149}
150
151fn quoted(s: &str) -> String {
152 let mut out = String::with_capacity(s.len() + 2);
153 out.push('"');
154 for c in s.chars() {
155 match c {
156 '"' => out.push_str("\\\""),
157 '\\' => out.push_str("\\\\"),
158 '\n' => out.push_str("\\n"),
159 '\t' => out.push_str("\\t"),
160 c => out.push(c),
161 }
162 }
163 out.push('"');
164 out
165}
166
167fn list(items: Vec<Value>) -> Value {
168 Value::List(std::sync::Arc::new(items))
169}
170
171pub fn install_blue_stdlib<H: 'static>(interp: &mut Interpreter<H>) {
173 interp.register_fn(
178 "length",
179 Arity::Exact(1),
180 |a: &[Value], _h: &mut H, span| match &a[0] {
181 Value::List(items) => Ok(Value::Int(items.len() as i64)),
182 other => Ok(Value::Int(as_str(other, span)?.chars().count() as i64)),
183 },
184 );
185
186 interp.register_fn("to_s", Arity::Exact(1), |a: &[Value], _h: &mut H, _s| {
187 Ok(Value::Str(render(&a[0]).into()))
188 });
189
190 interp.register_fn("upcase", Arity::Exact(1), |a: &[Value], _h: &mut H, s| {
191 Ok(Value::Str(as_str(&a[0], s)?.to_uppercase().into()))
192 });
193
194 interp.register_fn("downcase", Arity::Exact(1), |a: &[Value], _h: &mut H, s| {
195 Ok(Value::Str(as_str(&a[0], s)?.to_lowercase().into()))
196 });
197
198 interp.register_fn("trim", Arity::Exact(1), |a: &[Value], _h: &mut H, s| {
199 Ok(Value::Str(as_str(&a[0], s)?.trim().into()))
200 });
201
202 interp.register_fn("concat", Arity::AtLeast(1), |a: &[Value], _h: &mut H, _s| {
209 Ok(Value::Str(a.iter().map(render).collect::<String>().into()))
210 });
211
212 interp.register_fn("equal?", Arity::Exact(2), |a: &[Value], _h: &mut H, _s| {
217 Ok(Value::Bool(blue_equal(&a[0], &a[1])))
218 });
219 interp.register_fn("not=", Arity::Exact(2), |a: &[Value], _h: &mut H, _s| {
220 Ok(Value::Bool(!blue_equal(&a[0], &a[1])))
221 });
222
223 interp.register_fn("nil?", Arity::Exact(1), |a: &[Value], _h: &mut H, _s| {
229 Ok(Value::Bool(matches!(a[0], Value::Nil)))
230 });
231 interp.register_fn("list?", Arity::Exact(1), |a: &[Value], _h: &mut H, _s| {
232 Ok(Value::Bool(matches!(a[0], Value::List(_))))
233 });
234 interp.register_fn("map?", Arity::Exact(1), |a: &[Value], _h: &mut H, _s| {
235 Ok(Value::Bool(matches!(a[0], Value::Map(_))))
236 });
237 interp.register_fn("float?", Arity::Exact(1), |a: &[Value], _h: &mut H, _s| {
238 Ok(Value::Bool(matches!(a[0], Value::Float(_))))
239 });
240 interp.register_fn("bool?", Arity::Exact(1), |a: &[Value], _h: &mut H, _s| {
241 Ok(Value::Bool(matches!(a[0], Value::Bool(_))))
242 });
243
244 interp.register_fn("cdr", Arity::Exact(1), |a: &[Value], _h: &mut H, span| {
249 match &a[0] {
250 Value::List(xs) if !xs.is_empty() => Ok(list(xs[1..].to_vec())),
251 Value::Nil | Value::List(_) => Err(EvalError::native_fn(
252 std::sync::Arc::<str>::from("cdr"),
253 "cdr of empty list",
254 span,
255 )),
256 other => Err(EvalError::type_mismatch("pair", other.type_name(), span)),
257 }
258 });
259 interp.register_fn("append", Arity::Any, |a: &[Value], _h: &mut H, span| {
260 let mut out = Vec::new();
261 for v in a {
262 match v {
263 Value::Nil => {}
264 Value::List(xs) => out.extend(xs.iter().cloned()),
265 other => return Err(EvalError::type_mismatch("list", other.type_name(), span)),
266 }
267 }
268 Ok(list(out))
269 });
270
271 interp.register_fn("split", Arity::Exact(2), |a: &[Value], _h: &mut H, s| {
272 let text = as_str(&a[0], s)?;
273 let sep = as_str(&a[1], s)?;
274 let parts: Vec<Value> = if sep.is_empty() {
283 text.chars()
284 .map(|c| Value::Str(c.to_string().into()))
285 .collect()
286 } else {
287 text.split(sep.as_str())
288 .map(|p| Value::Str(p.into()))
289 .collect()
290 };
291 Ok(list(parts))
292 });
293
294 interp.register_fn("join", Arity::Exact(2), |a: &[Value], _h: &mut H, s| {
295 let sep = as_str(&a[1], s)?;
296 match &a[0] {
297 Value::List(items) => Ok(Value::Str(
298 items
299 .iter()
300 .map(render)
301 .collect::<Vec<_>>()
302 .join(&sep)
303 .into(),
304 )),
305 other => Err(EvalError::type_mismatch("a list", other.type_name(), s).into()),
306 }
307 });
308
309 interp.register_fn(
310 "contains?",
311 Arity::Exact(2),
312 |a: &[Value], _h: &mut H, s| {
313 Ok(Value::Bool(as_str(&a[0], s)?.contains(&as_str(&a[1], s)?)))
314 },
315 );
316
317 interp.register_fn(
318 "starts_with?",
319 Arity::Exact(2),
320 |a: &[Value], _h: &mut H, s| {
321 Ok(Value::Bool(
322 as_str(&a[0], s)?.starts_with(&as_str(&a[1], s)?),
323 ))
324 },
325 );
326
327 interp.register_fn(
328 "ends_with?",
329 Arity::Exact(2),
330 |a: &[Value], _h: &mut H, s| {
331 Ok(Value::Bool(as_str(&a[0], s)?.ends_with(&as_str(&a[1], s)?)))
332 },
333 );
334
335 interp.register_fn("replace", Arity::Exact(3), |a: &[Value], _h: &mut H, s| {
336 Ok(Value::Str(
337 as_str(&a[0], s)?
338 .replace(&as_str(&a[1], s)?, &as_str(&a[2], s)?)
339 .into(),
340 ))
341 });
342
343 interp.register_fn("reverse", Arity::Exact(1), |a: &[Value], _h: &mut H, s| {
344 match &a[0] {
345 Value::List(items) => {
346 let mut v = items.as_ref().clone();
347 v.reverse();
348 Ok(list(v))
349 }
350 other => Ok(Value::Str(
353 as_str(other, s)?.chars().rev().collect::<String>().into(),
354 )),
355 }
356 });
357
358 interp.register_fn("chars", Arity::Exact(1), |a: &[Value], _h: &mut H, s| {
359 Ok(list(
360 as_str(&a[0], s)?
361 .chars()
362 .map(|c| Value::Str(c.to_string().into()))
363 .collect(),
364 ))
365 });
366
367 interp.register_fn(
374 "to_int",
375 Arity::Exact(1),
376 |a: &[Value], _h: &mut H, s| match &a[0] {
377 Value::Int(n) => Ok(Value::Int(*n)),
378 Value::Float(x) => Ok(Value::Int(*x as i64)),
379 other => Ok(as_str(other, s)?
380 .trim()
381 .parse::<i64>()
382 .map_or(Value::Nil, Value::Int)),
383 },
384 );
385
386 interp.register_fn(
387 "to_int!",
388 Arity::Exact(1),
389 |a: &[Value], _h: &mut H, s| match &a[0] {
390 Value::Int(n) => Ok(Value::Int(*n)),
391 Value::Float(x) => Ok(Value::Int(*x as i64)),
392 other => {
393 let text = as_str(other, s)?;
394 text.trim().parse::<i64>().map(Value::Int).map_err(|_| {
395 EvalError::native_fn(
396 "to_int!",
397 "`".to_string() + &text + "` is not an integer",
398 s,
399 )
400 .into()
401 })
402 }
403 },
404 );
405
406 interp.register_fn(
407 "to_float",
408 Arity::Exact(1),
409 |a: &[Value], _h: &mut H, s| match &a[0] {
410 Value::Float(x) => Ok(Value::Float(*x)),
411 Value::Int(n) => Ok(Value::Float(*n as f64)),
412 other => Ok(as_str(other, s)?
413 .trim()
414 .parse::<f64>()
415 .map_or(Value::Nil, Value::Float)),
416 },
417 );
418
419 interp.register_fn(
420 "abs",
421 Arity::Exact(1),
422 |a: &[Value], _h: &mut H, s| match &a[0] {
423 Value::Int(n) => Ok(Value::Int(n.abs())),
424 Value::Float(x) => Ok(Value::Float(x.abs())),
425 other => Err(EvalError::type_mismatch("a number", other.type_name(), s).into()),
426 },
427 );
428
429 interp.register_fn(
450 "range",
451 Arity::Range(1, 3),
452 |a: &[Value], _h: &mut H, s| {
453 let (start, end, step) = match a.len() {
454 1 => (Value::Int(0), a[0].clone(), Value::Int(1)),
455 2 => (a[0].clone(), a[1].clone(), Value::Int(1)),
456 _ => (a[0].clone(), a[1].clone(), a[2].clone()),
457 };
458 range_list(start, &end, &step, s)
459 },
460 );
461
462 interp.register_fn("write_stdout", Arity::Exact(1), |a: &[Value], _h: &mut H, s| {
469 use std::io::Write;
470 let text = as_str(&a[0], s)?;
471 let mut out = std::io::stdout().lock();
472 out.write_all(text.as_bytes())
473 .and_then(|()| out.flush())
474 .map_err(|e| EvalError::native_fn("write_stdout", e.to_string(), s))?;
475 Ok(Value::Nil)
476 });
477 interp.register_fn("write_stderr", Arity::Exact(1), |a: &[Value], _h: &mut H, s| {
478 use std::io::Write;
479 let text = as_str(&a[0], s)?;
480 let mut err = std::io::stderr().lock();
481 err.write_all(text.as_bytes())
482 .and_then(|()| err.flush())
483 .map_err(|e| EvalError::native_fn("write_stderr", e.to_string(), s))?;
484 Ok(Value::Nil)
485 });
486
487 interp.register_higher_order_fn(
494 "sort_keyed",
495 Arity::Exact(2),
496 |a: &[Value], host: &mut H, caller: &tatara_lisp_eval::ffi::Caller<H>, s| {
497 let xs = match &a[1] {
498 Value::List(xs) => xs.as_ref().clone(),
499 Value::Nil => Vec::new(),
500 other => {
501 return Err(EvalError::type_mismatch("a list", other.type_name(), s));
502 }
503 };
504 let mut keyed = Vec::with_capacity(xs.len());
505 for x in xs {
506 keyed.push((caller.apply_value(&a[0], vec![x.clone()], host, s)?, x));
507 }
508 Ok(list(tatara_lisp_eval::hof::sort_keyed_values(keyed, s)?))
509 },
510 );
511}
512
513fn range_num(v: &Value, s: tatara_lisp::Span) -> Result<f64, EvalError> {
515 match v {
516 Value::Int(n) => Ok(*n as f64),
517 Value::Float(x) => Ok(*x),
518 other => Err(EvalError::native_fn(
519 "range",
520 "expected a number, got ".to_string() + other.type_name(),
521 s,
522 )),
523 }
524}
525
526fn range_add(a: &Value, b: &Value, s: tatara_lisp::Span) -> Result<Value, EvalError> {
529 match (a, b) {
530 (Value::Int(x), Value::Int(y)) => x.checked_add(*y).map(Value::Int).ok_or_else(|| {
531 EvalError::native_fn("range", "an element overflowed a 64-bit integer", s)
532 }),
533 _ => Ok(Value::Float(range_num(a, s)? + range_num(b, s)?)),
534 }
535}
536
537fn range_list(
538 start: Value,
539 end: &Value,
540 step: &Value,
541 s: tatara_lisp::Span,
542) -> Result<Value, EvalError> {
543 let end_f = range_num(end, s)?;
544 let step_f = range_num(step, s)?;
545 range_num(&start, s)?;
546 if step_f == 0.0 {
547 return Err(EvalError::native_fn(
548 "range",
549 "a step of zero never reaches the end",
550 s,
551 ));
552 }
553 let mut out = Vec::new();
554 let mut cur = start;
555 loop {
556 let c = range_num(&cur, s)?;
557 if (step_f > 0.0 && c >= end_f) || (step_f < 0.0 && c <= end_f) {
558 break;
559 }
560 let next = range_add(&cur, step, s)?;
561 out.push(cur);
562 cur = next;
563 }
564 Ok(list(out))
565}
566
567#[cfg(test)]
568mod tests {
569 use super::*;
570
571 #[test]
572 fn sort_keyed_is_stable_keyed_and_deep() {
573 assert_eq!(
575 ints("map(fn(p) last(p) end, sort_keyed(fn(p) first(p) end, [[2, 1], [1, 2], [2, 3], [1, 4]]))"),
576 vec![2, 4, 1, 3]
577 );
578 assert_eq!(
580 i("first(sort_keyed(fn(x) x end, reverse(range(0, 20000))))"),
581 0
582 );
583 assert_eq!(i("last(sort_keyed(fn(x) 0 - x end, range(0, 20000)))"), 0);
584 assert_eq!(ints("sort_keyed(fn(x) x end, [])"), Vec::<i64>::new());
586 assert!(crate::run("sort_keyed(fn(x) x end, [1, \"a\"])").is_err());
587 }
588
589 fn eval(src: &str) -> Value {
590 crate::run(src)
591 .unwrap_or_else(|e| panic!("{src:?}: {e}"))
592 .value
593 }
594
595 fn s(src: &str) -> String {
596 match eval(src) {
597 Value::Str(v) => v.to_string(),
598 other => panic!("{src:?} produced {other:?}"),
599 }
600 }
601
602 fn i(src: &str) -> i64 {
603 match eval(src) {
604 Value::Int(v) => v,
605 other => panic!("{src:?} produced {other:?}"),
606 }
607 }
608
609 fn ints(src: &str) -> Vec<i64> {
610 match eval(src) {
611 Value::List(xs) => xs
612 .iter()
613 .map(|v| match v {
614 Value::Int(n) => *n,
615 other => panic!("{src:?} produced a non-Int element {other:?}"),
616 })
617 .collect(),
618 other => panic!("{src:?} produced {other:?}"),
619 }
620 }
621
622 #[test]
625 fn range_answers_as_the_lisp_definition_did() {
626 assert_eq!(ints("range(5)"), vec![0, 1, 2, 3, 4]);
627 assert_eq!(ints("range(2, 6)"), vec![2, 3, 4, 5]);
628 assert_eq!(ints("range(0, 10, 2)"), vec![0, 2, 4, 6, 8]);
629 assert_eq!(ints("range(10, 0, 0 - 2)"), vec![10, 8, 6, 4, 2]);
630 assert!(ints("range(5, 5)").is_empty());
631 assert!(ints("range(5, 2)").is_empty());
632 assert!(ints("range(0)").is_empty());
633 match eval("range(0, 1, 0.5)") {
635 Value::List(xs) => {
636 assert!(matches!(xs[0], Value::Int(0)), "{xs:?}");
637 assert!(matches!(xs[1], Value::Float(x) if x == 0.5), "{xs:?}");
638 assert_eq!(xs.len(), 2);
639 }
640 other => panic!("{other:?}"),
641 }
642 }
643
644 #[test]
650 fn range_builds_long_lists_without_recursing() {
651 assert_eq!(i("length(range(0, 200000))"), 200_000);
652 assert_eq!(i("nth(199999, range(0, 200000))"), 199_999);
653 }
654
655 #[test]
658 fn range_refuses_a_zero_step_and_a_non_number() {
659 assert!(crate::run("range(0, 5, 0)").is_err());
660 assert!(crate::run("range(0, \"x\")").is_err());
661 }
662
663 #[test]
667 fn length_counts_characters_not_bytes() {
668 assert_eq!(i("length(\"hello\")"), 5);
669 assert_eq!(i("length(\"héllo\")"), 5, "must not be 6");
671 assert_eq!(i("length(\"😀\")"), 1, "must not be 4");
673 }
674
675 #[test]
678 fn a_combining_sequence_counts_scalars_not_graphemes() {
679 assert_eq!(
681 i("length(\"e\\u{301}\")"),
682 2,
683 "blue counts scalar values; Elixir's String.length would say 1"
684 );
685 }
686
687 #[test]
688 fn length_also_works_on_a_list() {
689 assert_eq!(i("length([1, 2, 3])"), 3);
690 }
691
692 #[test]
693 fn case_and_trim() {
694 assert_eq!(s("upcase(\"abc\")"), "ABC");
695 assert_eq!(s("downcase(\"ABC\")"), "abc");
696 assert_eq!(s("trim(\" hi \")"), "hi");
697 assert_eq!(s("upcase(\"é\")"), "É");
699 }
700
701 #[test]
702 fn concat_and_to_s() {
703 assert_eq!(s("concat(\"a\", \"b\")"), "ab");
704 assert_eq!(s("concat(\"n=\", 42)"), "n=42");
705 assert_eq!(s("to_s(42)"), "42");
706 assert_eq!(s("to_s(true)"), "true");
707 }
708
709 #[test]
713 fn plus_is_not_string_concatenation() {
714 assert!(
715 crate::run("\"a\" + \"b\"").is_err(),
716 "`+` must not silently concatenate — use concat"
717 );
718 }
719
720 #[test]
721 fn split_and_join() {
722 assert_eq!(i("length(split(\"a,b,c\", \",\"))"), 3);
723 assert_eq!(s("join(split(\"a,b,c\", \",\"), \"-\")"), "a-b-c");
724 assert_eq!(i("length(split(\"abc\", \"\"))"), 3);
726 }
727
728 #[test]
729 fn predicates() {
730 assert!(matches!(
731 eval("contains?(\"hello\", \"ell\")"),
732 Value::Bool(true)
733 ));
734 assert!(matches!(
735 eval("contains?(\"hello\", \"xyz\")"),
736 Value::Bool(false)
737 ));
738 assert!(matches!(
739 eval("starts_with?(\"hello\", \"he\")"),
740 Value::Bool(true)
741 ));
742 assert!(matches!(
743 eval("ends_with?(\"hello\", \"lo\")"),
744 Value::Bool(true)
745 ));
746 }
747
748 #[test]
749 fn replace_and_chars() {
750 assert_eq!(s("replace(\"a-b-c\", \"-\", \"+\")"), "a+b+c");
751 assert_eq!(i("length(chars(\"abc\"))"), 3);
752 }
753
754 #[test]
757 fn reverse_is_character_wise() {
758 assert_eq!(s("reverse(\"abc\")"), "cba");
759 assert_eq!(s("reverse(\"héllo\")"), "olléh", "must not corrupt the é");
760 }
761
762 #[test]
763 fn reverse_also_works_on_a_list() {
764 assert_eq!(s("join(reverse([1, 2, 3]), \",\")"), "3,2,1");
765 }
766
767 #[test]
772 fn to_int_is_nil_on_garbage_rather_than_zero() {
773 assert_eq!(i("to_int(\"42\")"), 42);
774 assert!(
775 matches!(eval("to_int(\"banana\")"), Value::Nil),
776 "Ruby would say 0 here; a falsy nil cannot be mistaken for a result"
777 );
778 assert!(
779 matches!(eval("to_int(\"0\")"), Value::Int(0)),
780 "and a real 0 is still a real 0 — the two must stay distinguishable"
781 );
782 }
783
784 #[test]
785 fn to_int_bang_raises_on_garbage() {
786 assert_eq!(i("to_int!(\"42\")"), 42);
787 let err = crate::run("to_int!(\"banana\")").expect_err("must raise");
788 assert!(err.to_string().contains("banana"), "must name it: {err}");
789 }
790
791 #[test]
792 fn numeric_conversions_and_abs() {
793 assert_eq!(i("to_int(3.9)"), 3);
794 assert_eq!(i("abs(0 - 5)"), 5);
795 assert!(matches!(eval("to_float(\"1.5\")"), Value::Float(_)));
796 assert!(matches!(eval("to_float(\"nope\")"), Value::Nil));
797 }
798
799 #[test]
801 fn a_non_string_argument_is_a_type_error() {
802 assert!(crate::run("upcase([1, 2])").is_err());
803 assert!(crate::run("join(\"not a list\", \",\")").is_err());
804 }
805}