1use tatara_lisp_eval::ffi::Arity;
36use tatara_lisp_eval::{EvalError, Interpreter, Value};
37
38const TRIM_OPTIONS: &[&str] = &["side", "chars"];
39
40fn trim_with(text: &str, opts: &Value, s: tatara_lisp::Span) -> Result<Value, EvalError> {
41 let bad = |message: String| crate::errors::raise_kind("bad_option", message, s);
42 let mut side = "both".to_string();
43 let mut chars: Option<Vec<char>> = None;
44 match opts {
45 Value::Nil => {}
46 Value::Map(m) => {
47 for (k, v) in m.iter() {
48 let key = match k {
49 tatara_lisp_eval::MapKey::Keyword(n)
50 | tatara_lisp_eval::MapKey::Symbol(n)
51 | tatara_lisp_eval::MapKey::Str(n) => n.to_string(),
52 _ => String::new(),
53 };
54 match (key.as_str(), v) {
55 ("side", Value::Keyword(k)) if matches!(&**k, "both" | "start" | "end") => {
56 side = k.to_string();
57 }
58 ("side", _) => return Err(bad("side must be :both, :start or :end".into())),
59 ("chars", Value::Str(c)) => chars = Some(c.chars().collect()),
60 ("chars", _) => return Err(bad("chars must be text".into())),
61 _ => {
62 return Err(bad(format!(
63 "unknown option {key}; this verb takes {}",
64 TRIM_OPTIONS.join(", ")
65 )))
66 }
67 }
68 }
69 }
70 other => {
71 return Err(bad(format!(
72 "options must be a map, got {}",
73 other.type_name()
74 )))
75 }
76 }
77 let out = match (&chars, side.as_str()) {
78 (None, "both") => text.trim(),
79 (None, "start") => text.trim_start(),
80 (None, _) => text.trim_end(),
81 (Some(set), "both") => text.trim_matches(|c| set.contains(&c)),
82 (Some(set), "start") => text.trim_start_matches(|c| set.contains(&c)),
83 (Some(set), _) => text.trim_end_matches(|c| set.contains(&c)),
84 };
85 Ok(Value::Str(out.into()))
86}
87
88fn as_str(v: &Value, span: tatara_lisp::Span) -> Result<String, EvalError> {
89 match v {
90 Value::Str(s) => Ok(s.to_string()),
91 Value::Symbol(s) | Value::Keyword(s) => Ok(s.to_string()),
94 other => Err(EvalError::type_mismatch(
95 "a string",
96 other.type_name(),
97 span,
98 )),
99 }
100}
101
102pub fn blue_equal(a: &Value, b: &Value) -> bool {
115 match (a, b) {
116 (Value::Nil, Value::Nil) => true,
117 (Value::Bool(x), Value::Bool(y)) => x == y,
118 (Value::Int(x), Value::Int(y)) => x == y,
119 (Value::Float(x), Value::Float(y)) => x == y,
120 (Value::Int(x), Value::Float(y)) | (Value::Float(y), Value::Int(x)) => (*x as f64) == *y,
121 (Value::Str(x), Value::Str(y))
122 | (Value::Symbol(x), Value::Symbol(y))
123 | (Value::Keyword(x), Value::Keyword(y)) => x == y,
124 (Value::List(x), Value::List(y)) => {
125 x.len() == y.len() && x.iter().zip(y.iter()).all(|(p, q)| blue_equal(p, q))
126 }
127 (Value::Map(x), Value::Map(y)) => {
128 x.len() == y.len()
129 && x.iter()
130 .all(|(k, v)| y.get(k).is_some_and(|w| blue_equal(v, w)))
131 }
132 (Value::Closure(x), Value::Closure(y)) => std::sync::Arc::ptr_eq(x, y),
133 (Value::NativeFn(x), Value::NativeFn(y)) => x.name == y.name,
134 (Value::Sexp(x, _), Value::Sexp(y, _)) => x == y,
135 _ => false,
136 }
137}
138
139#[derive(PartialEq, Eq, Hash)]
140enum EqualKey<'a> {
141 Nil,
142 Bool(bool),
143 Number(u64),
144 Str(&'a str),
145 Symbol(&'a str),
146 Keyword(&'a str),
147}
148
149fn number_key(n: f64) -> Option<EqualKey<'static>> {
150 (!n.is_nan()).then(|| EqualKey::Number(if n == 0.0 { 0 } else { n.to_bits() }))
151}
152
153fn equal_key(v: &Value) -> Option<EqualKey<'_>> {
154 match v {
155 Value::Nil => Some(EqualKey::Nil),
156 Value::Bool(b) => Some(EqualKey::Bool(*b)),
157 Value::Int(i) => number_key(*i as f64),
158 Value::Float(f) => number_key(*f),
159 Value::Str(s) => Some(EqualKey::Str(s)),
160 Value::Symbol(s) => Some(EqualKey::Symbol(s)),
161 Value::Keyword(s) => Some(EqualKey::Keyword(s)),
162 _ => None,
163 }
164}
165
166pub fn frequencies<'a>(
174 xs: impl Iterator<Item = &'a Value>,
175 span: tatara_lisp::Span,
176) -> Result<Value, EvalError> {
177 let mut counts = tatara_lisp_eval::Map::new();
178 for x in xs {
179 let key = tatara_lisp_eval::value::MapKey::from_value(x).ok_or_else(|| {
180 EvalError::native_fn(
181 "hash-map",
182 format!("non-hashable key kind: {}", x.type_name()),
183 span,
184 )
185 })?;
186 let n = match counts.get(&key) {
187 Some(Value::Int(n)) => n + 1,
188 _ => 1,
189 };
190 counts.insert(key, Value::Int(n));
191 }
192 Ok(Value::Map(std::sync::Arc::new(counts)))
193}
194
195pub fn distinct<'a>(xs: impl Iterator<Item = &'a Value>) -> Vec<Value> {
202 let mut kept: Vec<Value> = Vec::new();
203 let mut buckets: std::collections::HashMap<EqualKey<'a>, Vec<usize>> =
204 std::collections::HashMap::new();
205 let mut keyless: Vec<usize> = Vec::new();
206 for x in xs {
207 let seen = match equal_key(x) {
208 Some(k) => buckets.entry(k).or_default(),
209 None => &mut keyless,
210 };
211 if !seen.iter().any(|&i| blue_equal(&kept[i], x)) {
212 seen.push(kept.len());
213 kept.push(x.clone());
214 }
215 }
216 kept
217}
218
219fn chain_rules_value() -> Value {
224 use blue_lang_check::chains::{cases, Case, Kind, RULES};
225 let sym = |s: &str| Value::Symbol(std::sync::Arc::from(s));
226 let text = |s: &str| Value::Str(std::sync::Arc::from(s));
227 let hole = |n: usize| (0..n).map(|_| sym("x")).collect::<Vec<_>>();
228 let lambda = |body: Value| Value::list([sym("lambda"), Value::list([sym("x")]), body]);
229 let call = |name: &str, args: Vec<Value>| Value::list(std::iter::once(sym(name)).chain(args));
230 Value::list(RULES.iter().map(|rule| {
231 let rows = cases(rule).into_iter().map(|case| match case {
232 Case::Call { inner, argc } => {
233 let inner_call = call(inner, hole(argc));
234 Value::list([
235 text(&format!("{}({}(x))", rule.outer, inner)),
236 Value::Nil,
237 lambda(call(rule.outer, vec![inner_call.clone()])),
238 lambda(inner_call),
239 ])
240 }
241 Case::Literal(kind) => Value::list([
242 text(&format!("{}(<literal>)", rule.outer)),
243 Value::Keyword(std::sync::Arc::from(match kind {
244 Kind::Text => "text",
245 Kind::Int => "int",
246 Kind::Float => "float",
247 })),
248 lambda(call(rule.outer, vec![sym("x")])),
249 lambda(sym("x")),
250 ]),
251 });
252 Value::list([
253 text(rule.id),
254 text(rule.slug),
255 text(rule.outer),
256 text(rule.soundness),
257 Value::list(rows),
258 ])
259 }))
260}
261
262fn render(v: &Value) -> String {
263 match v {
264 Value::Nil => String::new(),
265 Value::Str(s) | Value::Symbol(s) | Value::Keyword(s) => s.to_string(),
266 Value::Error(e) => crate::errors::describe(e),
267 other => literal(other),
268 }
269}
270
271fn literal(v: &Value) -> String {
275 match v {
276 Value::Nil => "nil".into(),
277 Value::Bool(b) => b.to_string(),
278 Value::Int(n) => n.to_string(),
279 Value::Float(x) => float_text(*x),
280 Value::Str(s) => quoted(s),
281 Value::Symbol(s) => s.to_string(),
282 Value::Keyword(s) => format!(":{s}"),
283 Value::List(items) => {
284 let parts: Vec<String> = items.iter().map(literal).collect();
285 format!("[{}]", parts.join(", "))
286 }
287 Value::Map(m) => {
288 let mut entries: Vec<String> = m
289 .iter()
290 .map(|(k, v)| format!("{} {}", key_text(k), literal(v)))
291 .collect();
292 entries.sort();
293 format!("{{{}}}", entries.join(", "))
294 }
295 other => other.type_name().to_string(),
296 }
297}
298
299fn key_text(k: &tatara_lisp_eval::value::MapKey) -> String {
302 use tatara_lisp_eval::value::MapKey;
303 match k {
304 MapKey::Keyword(s) | MapKey::Symbol(s) => format!("{s}:"),
305 MapKey::Str(s) => format!("{} =>", quoted(s)),
306 MapKey::Int(n) => format!("{n} =>"),
307 MapKey::Float(bits) => format!("{} =>", float_text(f64::from_bits(*bits))),
308 MapKey::Bool(b) => format!("{b} =>"),
309 MapKey::Nil => "nil =>".into(),
310 }
311}
312
313fn float_text(x: f64) -> String {
315 if x.is_finite() && x.fract() == 0.0 && x.abs() < 1e16 {
316 format!("{x:.1}")
317 } else {
318 format!("{x}")
319 }
320}
321
322fn quoted(s: &str) -> String {
323 let mut out = String::with_capacity(s.len() + 2);
324 out.push('"');
325 for c in s.chars() {
326 match c {
327 '"' => out.push_str("\\\""),
328 '\\' => out.push_str("\\\\"),
329 '\n' => out.push_str("\\n"),
330 '\t' => out.push_str("\\t"),
331 c => out.push(c),
332 }
333 }
334 out.push('"');
335 out
336}
337
338fn list(items: Vec<Value>) -> Value {
339 Value::list(items)
340}
341
342pub fn install_blue_stdlib<H: 'static>(interp: &mut Interpreter<H>) {
344 crate::errors::install(interp);
345 crate::paths::install(interp);
346 crate::spread::install(interp);
347 crate::collection::install(interp);
348 interp.register_fn(
353 "length",
354 Arity::Exact(1),
355 |a: &[Value], _h: &mut H, span| match &a[0] {
356 Value::List(items) => Ok(Value::Int(items.len() as i64)),
357 other => Ok(Value::Int(as_str(other, span)?.chars().count() as i64)),
358 },
359 );
360
361 interp.register_fn(
364 "builtin_names",
365 Arity::Exact(0),
366 |_a: &[Value], _h: &mut H, _s| {
367 Ok(Value::list(
368 crate::docs::NAMES
369 .iter()
370 .map(|n| Value::Str(std::sync::Arc::from(n.name))),
371 ))
372 },
373 );
374 interp.register_fn(
375 "chain_rules",
376 Arity::Exact(0),
377 |_a: &[Value], _h: &mut H, _s| Ok(chain_rules_value()),
378 );
379 interp.register_fn(
380 "volatile",
381 Arity::Exact(1),
382 |a: &[Value], _h: &mut H, s| match &a[0] {
383 Value::Closure(_) | Value::NativeFn(_) | Value::Foreign(_) => Ok(Value::Nil),
384 other => Err(EvalError::type_mismatch("function", other.type_name(), s)),
385 },
386 );
387 interp.register_fn("to_s", Arity::Exact(1), |a: &[Value], _h: &mut H, _s| {
388 Ok(Value::Str(render(&a[0]).into()))
389 });
390
391 interp.register_fn("upcase", Arity::Exact(1), |a: &[Value], _h: &mut H, s| {
392 Ok(Value::Str(as_str(&a[0], s)?.to_uppercase().into()))
393 });
394
395 interp.register_fn("downcase", Arity::Exact(1), |a: &[Value], _h: &mut H, s| {
396 Ok(Value::Str(as_str(&a[0], s)?.to_lowercase().into()))
397 });
398
399 interp.register_fn("trim", Arity::Range(1, 2), |a: &[Value], _h: &mut H, s| {
400 let text = as_str(&a[0], s)?;
401 trim_with(&text, a.get(1).unwrap_or(&Value::Nil), s)
402 });
403
404 interp.register_fn(
405 blue_lang_syntax::LOWERED_OPTIONAL_ARITY,
406 Arity::Exact(4),
407 |a: &[Value], _h: &mut H, s| {
408 let name = as_str(&a[0], s)?;
409 let (Value::List(rest), Value::Int(required), Value::Int(most)) = (&a[1], &a[2], &a[3])
410 else {
411 return Err(EvalError::native_fn(
412 blue_lang_syntax::LOWERED_OPTIONAL_ARITY,
413 "expects a name, the rest list and two counts",
414 s,
415 ));
416 };
417 if rest.len() as i64 > *most {
418 let want = Arity::Range(*required as usize, (*required + *most) as usize);
419 return Err(EvalError::native_fn(
420 name.as_str(),
421 format!(
422 "takes {}, got {}",
423 crate::messages::arguments(&want),
424 *required as usize + rest.len()
425 ),
426 s,
427 ));
428 }
429 Ok(Value::Nil)
430 },
431 );
432
433 interp.register_fn(
434 blue_lang_syntax::LOWERED_OPTIONAL_ARG,
435 Arity::Exact(2),
436 |a: &[Value], _h: &mut H, s| match (&a[0], &a[1]) {
437 (Value::List(rest), Value::Int(i)) => {
438 Ok(rest.get(*i as usize).cloned().unwrap_or(Value::Nil))
439 }
440 _ => Err(EvalError::native_fn(
441 blue_lang_syntax::LOWERED_OPTIONAL_ARG,
442 "expects the rest list and an index",
443 s,
444 )),
445 },
446 );
447
448 interp.register_fn(
455 "concat",
456 Arity::AtLeast(1),
457 |a: &[Value], _h: &mut H, _s| {
458 Ok(Value::Str(a.iter().map(render).collect::<String>().into()))
459 },
460 );
461
462 interp.register_fn("equal?", Arity::Exact(2), |a: &[Value], _h: &mut H, _s| {
467 Ok(Value::Bool(blue_equal(&a[0], &a[1])))
468 });
469 interp.register_fn("not=", Arity::Exact(2), |a: &[Value], _h: &mut H, _s| {
470 Ok(Value::Bool(!blue_equal(&a[0], &a[1])))
471 });
472 interp.register_fn(
473 "distinct",
474 Arity::Exact(1),
475 |a: &[Value], _h: &mut H, span| match &a[0] {
476 Value::Nil => Ok(Value::list(std::iter::empty())),
477 Value::List(xs) => Ok(Value::list(distinct(xs.iter()))),
478 other => Err(EvalError::type_mismatch("a list", other.type_name(), span)),
479 },
480 );
481 interp.register_fn(
482 "frequencies",
483 Arity::Exact(1),
484 |a: &[Value], _h: &mut H, span| match &a[0] {
485 Value::Nil => Ok(frequencies(std::iter::empty(), span)?),
486 Value::List(xs) => Ok(frequencies(xs.iter(), span)?),
487 other => Err(EvalError::type_mismatch("a list", other.type_name(), span)),
488 },
489 );
490
491 interp.register_fn("nil?", Arity::Exact(1), |a: &[Value], _h: &mut H, _s| {
497 Ok(Value::Bool(matches!(a[0], Value::Nil)))
498 });
499 interp.register_fn("list?", Arity::Exact(1), |a: &[Value], _h: &mut H, _s| {
500 Ok(Value::Bool(matches!(a[0], Value::List(_))))
501 });
502 interp.register_fn("map?", Arity::Exact(1), |a: &[Value], _h: &mut H, _s| {
503 Ok(Value::Bool(matches!(a[0], Value::Map(_))))
504 });
505 interp.register_fn("float?", Arity::Exact(1), |a: &[Value], _h: &mut H, _s| {
506 Ok(Value::Bool(matches!(a[0], Value::Float(_))))
507 });
508 interp.register_fn("bool?", Arity::Exact(1), |a: &[Value], _h: &mut H, _s| {
509 Ok(Value::Bool(matches!(a[0], Value::Bool(_))))
510 });
511
512 interp.register_fn("cdr", Arity::Exact(1), |a: &[Value], _h: &mut H, span| {
517 match &a[0] {
518 Value::List(xs) if !xs.is_empty() => Ok(Value::List(std::sync::Arc::new(xs.skip(1)))),
520 Value::Nil | Value::List(_) => Err(EvalError::native_fn(
521 std::sync::Arc::<str>::from("cdr"),
522 "cdr of empty list",
523 span,
524 )),
525 other => Err(EvalError::type_mismatch("pair", other.type_name(), span)),
526 }
527 });
528 interp.register_owned_fn("append", Arity::Any, |a: Vec<Value>, _h: &mut H, span| {
529 let mut lists = Vec::with_capacity(a.len());
536 for v in a {
537 match v {
538 Value::Nil => {}
539 Value::List(xs) => lists.push(xs),
540 other => return Err(EvalError::type_mismatch("list", other.type_name(), span)),
541 }
542 }
543 let mut lists = lists.into_iter();
544 let Some(mut out) = lists.next() else {
545 return Ok(Value::List(std::sync::Arc::new(
546 tatara_lisp_eval::List::new(),
547 )));
548 };
549 let acc = std::sync::Arc::make_mut(&mut out);
550 for xs in lists {
551 acc.append(
552 std::sync::Arc::try_unwrap(xs)
553 .unwrap_or_else(|shared| tatara_lisp_eval::List::clone(&shared)),
554 );
555 }
556 Ok(Value::List(out))
557 });
558
559 interp.register_fn("split", Arity::Exact(2), |a: &[Value], _h: &mut H, s| {
560 let text = as_str(&a[0], s)?;
561 let sep = as_str(&a[1], s)?;
562 let parts: Vec<Value> = if sep.is_empty() {
571 text.chars()
572 .map(|c| Value::Str(c.to_string().into()))
573 .collect()
574 } else {
575 text.split(sep.as_str())
576 .map(|p| Value::Str(p.into()))
577 .collect()
578 };
579 Ok(list(parts))
580 });
581
582 interp.register_fn("join", Arity::Exact(2), |a: &[Value], _h: &mut H, s| {
583 let sep = as_str(&a[1], s)?;
584 match &a[0] {
585 Value::List(items) => Ok(Value::Str(
586 items
587 .iter()
588 .map(render)
589 .collect::<Vec<_>>()
590 .join(&sep)
591 .into(),
592 )),
593 other => Err(EvalError::type_mismatch("a list", other.type_name(), s).into()),
594 }
595 });
596
597 interp.register_fn(
598 "join_lines",
599 Arity::Exact(1),
600 |a: &[Value], _h: &mut H, s| match &a[0] {
601 Value::List(items) => Ok(Value::Str(
602 items
603 .iter()
604 .map(render)
605 .collect::<Vec<_>>()
606 .join("\n")
607 .into(),
608 )),
609 other => Err(EvalError::type_mismatch("a list", other.type_name(), s).into()),
610 },
611 );
612
613 interp.register_fn(
614 "unlines",
615 Arity::Exact(1),
616 |a: &[Value], _h: &mut H, s| match &a[0] {
617 Value::List(items) => Ok(Value::Str(
618 items
619 .iter()
620 .map(|v| render(v) + "\n")
621 .collect::<String>()
622 .into(),
623 )),
624 other => Err(EvalError::type_mismatch("a list", other.type_name(), s).into()),
625 },
626 );
627
628 interp.register_fn(
629 "line_break",
630 Arity::Exact(0),
631 |_a: &[Value], _h: &mut H, _s| Ok(Value::Str("\n".into())),
632 );
633
634 interp.register_fn(
635 "contains?",
636 Arity::Exact(2),
637 |a: &[Value], _h: &mut H, s| {
638 Ok(Value::Bool(as_str(&a[0], s)?.contains(&as_str(&a[1], s)?)))
639 },
640 );
641
642 interp.register_fn(
643 "starts_with?",
644 Arity::Exact(2),
645 |a: &[Value], _h: &mut H, s| {
646 Ok(Value::Bool(
647 as_str(&a[0], s)?.starts_with(&as_str(&a[1], s)?),
648 ))
649 },
650 );
651
652 interp.register_fn(
653 "ends_with?",
654 Arity::Exact(2),
655 |a: &[Value], _h: &mut H, s| {
656 Ok(Value::Bool(as_str(&a[0], s)?.ends_with(&as_str(&a[1], s)?)))
657 },
658 );
659
660 interp.register_fn(
661 "text_index",
662 Arity::Exact(3),
663 |a: &[Value], _h: &mut H, s| {
664 Ok(Value::Int(text_index(
665 &as_str(&a[0], s)?,
666 &as_str(&a[1], s)?,
667 as_index(&a[2], s)?,
668 )))
669 },
670 );
671
672 interp.register_fn(
673 "text_last_index",
674 Arity::Exact(3),
675 |a: &[Value], _h: &mut H, s| {
676 Ok(Value::Int(text_last_index(
677 &as_str(&a[0], s)?,
678 &as_str(&a[1], s)?,
679 as_index(&a[2], s)?,
680 )))
681 },
682 );
683
684 interp.register_fn("replace", Arity::Exact(3), |a: &[Value], _h: &mut H, s| {
685 Ok(Value::Str(
686 as_str(&a[0], s)?
687 .replace(&as_str(&a[1], s)?, &as_str(&a[2], s)?)
688 .into(),
689 ))
690 });
691
692 interp.register_fn("reverse", Arity::Exact(1), |a: &[Value], _h: &mut H, s| {
693 match &a[0] {
694 Value::List(items) => Ok(Value::list(items.iter().rev().cloned())),
695 other => Ok(Value::Str(
698 as_str(other, s)?.chars().rev().collect::<String>().into(),
699 )),
700 }
701 });
702
703 interp.register_fn("chars", Arity::Exact(1), |a: &[Value], _h: &mut H, s| {
704 Ok(list(
705 as_str(&a[0], s)?
706 .chars()
707 .map(|c| Value::Str(c.to_string().into()))
708 .collect(),
709 ))
710 });
711
712 interp.register_fn(
713 "text_bytes",
714 Arity::Exact(1),
715 |a: &[Value], _h: &mut H, s| {
716 Ok(list(
717 as_str(&a[0], s)?
718 .as_bytes()
719 .iter()
720 .map(|b| Value::Int(i64::from(*b)))
721 .collect(),
722 ))
723 },
724 );
725
726 interp.register_fn(
727 "bytes_text",
728 Arity::Exact(1),
729 |a: &[Value], _h: &mut H, s| {
730 let bytes = crate::crypto::message_bytes(&a[0])
731 .map_err(|e| EvalError::native_fn("bytes_text", e.to_string(), s))?;
732 Ok(match String::from_utf8(bytes) {
733 Ok(text) => Value::Str(text.into()),
734 Err(_) => Value::Nil,
735 })
736 },
737 );
738
739 interp.register_fn(
746 "to_int",
747 Arity::Exact(1),
748 |a: &[Value], _h: &mut H, s| match &a[0] {
749 Value::Int(n) => Ok(Value::Int(*n)),
750 Value::Float(x) => Ok(float_to_int(*x).map_or(Value::Nil, Value::Int)),
754 other => Ok(as_str(other, s)?
755 .trim()
756 .parse::<i64>()
757 .map_or(Value::Nil, Value::Int)),
758 },
759 );
760
761 interp.register_fn(
762 "to_int!",
763 Arity::Exact(1),
764 |a: &[Value], _h: &mut H, s| match &a[0] {
765 Value::Int(n) => Ok(Value::Int(*n)),
766 Value::Float(x) => float_to_int(*x)
767 .map(Value::Int)
768 .ok_or(EvalError::IntegerOverflow {
769 op: "to_int!",
770 at: s,
771 }),
772 other => {
773 let text = as_str(other, s)?;
774 text.trim().parse::<i64>().map(Value::Int).map_err(|_| {
775 EvalError::native_fn(
776 "to_int!",
777 "`".to_string() + &text + "` is not an integer",
778 s,
779 )
780 .into()
781 })
782 }
783 },
784 );
785
786 interp.register_fn(
787 "to_float",
788 Arity::Exact(1),
789 |a: &[Value], _h: &mut H, s| match &a[0] {
790 Value::Float(x) => Ok(Value::Float(*x)),
791 Value::Int(n) => Ok(Value::Float(*n as f64)),
792 other => Ok(as_str(other, s)?
793 .trim()
794 .parse::<f64>()
795 .map_or(Value::Nil, Value::Float)),
796 },
797 );
798
799 interp.register_fn(
800 "abs",
801 Arity::Exact(1),
802 |a: &[Value], _h: &mut H, s| match &a[0] {
803 Value::Int(n) => n
806 .checked_abs()
807 .map(Value::Int)
808 .ok_or(EvalError::IntegerOverflow { op: "abs", at: s }),
809 Value::Float(x) => Ok(Value::Float(x.abs())),
810 other => Err(EvalError::type_mismatch("a number", other.type_name(), s).into()),
811 },
812 );
813
814 interp.register_fn("range", Arity::Range(1, 3), |a: &[Value], _h: &mut H, s| {
835 let (start, end, step) = match a.len() {
836 1 => (Value::Int(0), a[0].clone(), Value::Int(1)),
837 2 => (a[0].clone(), a[1].clone(), Value::Int(1)),
838 _ => (a[0].clone(), a[1].clone(), a[2].clone()),
839 };
840 range_list(start, &end, &step, s)
841 });
842
843 interp.register_fn(
850 "write_stdout",
851 Arity::Exact(1),
852 |a: &[Value], _h: &mut H, s| {
853 use std::io::Write;
854 let text = as_str(&a[0], s)?;
855 let mut out = std::io::stdout().lock();
856 out.write_all(text.as_bytes())
857 .and_then(|()| out.flush())
858 .map_err(|e| EvalError::native_fn("write_stdout", e.to_string(), s))?;
859 Ok(Value::Nil)
860 },
861 );
862 interp.register_fn(
863 "write_stderr",
864 Arity::Exact(1),
865 |a: &[Value], _h: &mut H, s| {
866 use std::io::Write;
867 let text = as_str(&a[0], s)?;
868 let mut err = std::io::stderr().lock();
869 err.write_all(text.as_bytes())
870 .and_then(|()| err.flush())
871 .map_err(|e| EvalError::native_fn("write_stderr", e.to_string(), s))?;
872 Ok(Value::Nil)
873 },
874 );
875
876 interp.register_higher_order_fn(
883 "sort_keyed",
884 Arity::Exact(2),
885 |a: &[Value], host: &mut H, caller: &tatara_lisp_eval::ffi::Caller<H>, s| {
886 let xs = match &a[1] {
887 Value::List(xs) => xs.to_vec(),
888 Value::Nil => Vec::new(),
889 other => {
890 return Err(EvalError::type_mismatch("a list", other.type_name(), s));
891 }
892 };
893 let mut keyed = Vec::with_capacity(xs.len());
894 for x in xs {
895 keyed.push((caller.apply_value(&a[0], vec![x.clone()], host, s)?, x));
896 }
897 Ok(list(tatara_lisp_eval::hof::sort_keyed_values(keyed, s)?))
898 },
899 );
900
901 interp.register_fn(
902 "error_message",
903 Arity::Exact(1),
904 |a: &[Value], _h: &mut H, s| match &a[0] {
905 Value::Error(e) => Ok(Value::Str(e.message.clone())),
906 other => Err(EvalError::type_mismatch("an error", other.type_name(), s)),
907 },
908 );
909
910 interp.register_fn(
911 "error_kind",
912 Arity::Exact(1),
913 |a: &[Value], _h: &mut H, s| match &a[0] {
914 Value::Error(e) => Ok(Value::Keyword(e.tag.clone())),
915 other => Err(EvalError::type_mismatch("an error", other.type_name(), s)),
916 },
917 );
918
919 interp.register_fn(
920 "map_entries",
921 Arity::Exact(1),
922 |a: &[Value], _h: &mut H, s| match &a[0] {
923 Value::Map(m) => {
924 let keyed = m
925 .iter()
926 .map(|(k, v)| (k.to_value(), list(vec![k.to_value(), v.clone()])))
927 .collect();
928 Ok(list(tatara_lisp_eval::hof::sort_keyed_values(keyed, s)?))
929 }
930 Value::Nil => Ok(list(Vec::new())),
931 other => Err(EvalError::type_mismatch("a map", other.type_name(), s)),
932 },
933 );
934}
935
936fn as_index(v: &Value, span: tatara_lisp::Span) -> Result<i64, EvalError> {
937 match v {
938 Value::Int(n) => Ok(*n),
939 other => Err(EvalError::type_mismatch(
940 "an integer",
941 other.type_name(),
942 span,
943 )),
944 }
945}
946
947fn text_index(text: &str, sub: &str, from: i64) -> i64 {
948 let from = usize::try_from(from.max(0)).unwrap_or(usize::MAX);
949 let start = match text.char_indices().nth(from) {
950 Some((b, _)) => b,
951 None if text.chars().count() == from => text.len(),
952 None => return -1,
953 };
954 text[start..].find(sub).map_or(-1, |b| {
955 i64::try_from(from + text[start..start + b].chars().count()).unwrap_or(i64::MAX)
956 })
957}
958
959fn text_last_index(text: &str, sub: &str, from: i64) -> i64 {
960 let Ok(from) = usize::try_from(from) else {
961 return -1;
962 };
963 let total = text.chars().count();
964 let width = sub.chars().count();
965 if width > total {
966 return -1;
967 }
968 let limit = from.min(total - width);
969 let end = text
970 .char_indices()
971 .nth(limit + width)
972 .map_or(text.len(), |(b, _)| b);
973 text[..end].rfind(sub).map_or(-1, |b| {
974 i64::try_from(text[..b].chars().count()).unwrap_or(i64::MAX)
975 })
976}
977
978fn range_num(v: &Value, s: tatara_lisp::Span) -> Result<f64, EvalError> {
980 match v {
981 Value::Int(n) => Ok(*n as f64),
982 Value::Float(x) => Ok(*x),
983 other => Err(EvalError::native_fn(
984 "range",
985 "expected a number, got ".to_string() + other.type_name(),
986 s,
987 )),
988 }
989}
990
991fn float_to_int(x: f64) -> Option<i64> {
994 const LIMIT: f64 = 9_223_372_036_854_775_808.0;
995 let t = x.trunc();
996 #[allow(clippy::cast_possible_truncation)]
997 (t.is_finite() && (-LIMIT..LIMIT).contains(&t)).then(|| t as i64)
998}
999
1000fn range_add(a: &Value, b: &Value, s: tatara_lisp::Span) -> Result<Value, EvalError> {
1003 match (a, b) {
1004 (Value::Int(x), Value::Int(y)) => x.checked_add(*y).map(Value::Int).ok_or_else(|| {
1005 EvalError::native_fn("range", "an element overflowed a 64-bit integer", s)
1006 }),
1007 _ => Ok(Value::Float(range_num(a, s)? + range_num(b, s)?)),
1008 }
1009}
1010
1011fn range_list(
1012 start: Value,
1013 end: &Value,
1014 step: &Value,
1015 s: tatara_lisp::Span,
1016) -> Result<Value, EvalError> {
1017 let end_f = range_num(end, s)?;
1018 let step_f = range_num(step, s)?;
1019 range_num(&start, s)?;
1020 if step_f == 0.0 {
1021 return Err(EvalError::native_fn(
1022 "range",
1023 "a step of zero never reaches the end",
1024 s,
1025 ));
1026 }
1027 let mut out = Vec::new();
1028 let mut cur = start;
1029 loop {
1030 let c = range_num(&cur, s)?;
1031 if (step_f > 0.0 && c >= end_f) || (step_f < 0.0 && c <= end_f) {
1032 break;
1033 }
1034 let next = range_add(&cur, step, s)?;
1035 out.push(cur);
1036 cur = next;
1037 }
1038 Ok(list(out))
1039}
1040
1041#[cfg(test)]
1042mod tests {
1043 use super::*;
1044
1045 #[test]
1046 fn sort_keyed_is_stable_keyed_and_deep() {
1047 assert_eq!(
1049 ints("map(fn(p) last(p) end, sort_keyed(fn(p) first(p) end, [[2, 1], [1, 2], [2, 3], [1, 4]]))"),
1050 vec![2, 4, 1, 3]
1051 );
1052 assert_eq!(
1054 i("first(sort_keyed(fn(x) x end, reverse(range(0, 20000))))"),
1055 0
1056 );
1057 assert_eq!(i("last(sort_keyed(fn(x) 0 - x end, range(0, 20000)))"), 0);
1058 assert_eq!(ints("sort_keyed(fn(x) x end, [])"), Vec::<i64>::new());
1060 assert!(crate::run("sort_keyed(fn(x) x end, [1, \"a\"])").is_err());
1061 }
1062
1063 #[test]
1064 fn distinct_is_native_and_keeps_tataras_meaning() {
1065 assert!(matches!(
1066 crate::interpreter_hostless().lookup_global("distinct"),
1067 Some(Value::NativeFn(_))
1068 ));
1069 let corpus = eval(
1070 "[1, 1.0, 0, -0.0, 0.0, 9007199254740993, 9007199254740992, 9007199254740992.0, \
1071 \"a\", :a, \"a\", nil, nil, true, false, true, [1, 2], [1, 2.0], [2], \
1072 {a: 1}, {a: 1.0}, 2.5, 2.5, -1, -1.0, \"\", \"\"]",
1073 );
1074 let Value::List(xs) = &corpus else {
1075 panic!("{corpus:?}")
1076 };
1077 let mut naive: Vec<Value> = Vec::new();
1078 for x in xs.iter() {
1079 if !naive.iter().any(|k| blue_equal(k, x)) {
1080 naive.push(x.clone());
1081 }
1082 }
1083 let fast = distinct(xs.iter());
1084 assert_eq!(fast.len(), naive.len());
1085 assert!(fast.iter().zip(&naive).all(|(a, b)| blue_equal(a, b)));
1086 assert_eq!(
1087 i("length(distinct(map((x) -> x % 997, range(300000))))"),
1088 997
1089 );
1090 assert_eq!(i("length(distinct(nil))"), 0);
1091 }
1092
1093 #[test]
1094 fn frequencies_is_native_and_counts_as_tatara_does() {
1095 assert!(matches!(
1096 crate::interpreter_hostless().lookup_global("frequencies"),
1097 Some(Value::NativeFn(_))
1098 ));
1099 let corpus = "(list 1 1.0 1 \"a\" 'a :a \"a\" #t #f #t 2.5 2.5 -0.0 0.0 0)";
1100 let mut upstream = tatara_lisp_eval::Interpreter::<()>::new();
1101 tatara_lisp_eval::install_full_stdlib_with(&mut upstream, &mut ());
1102 let forms = tatara_lisp::read_spanned(&format!("(frequencies {corpus})")).expect("reads");
1103 let expected = upstream
1104 .eval_program(&forms, &mut ())
1105 .expect("upstream counts");
1106 let Value::List(xs) = upstream
1107 .eval_program(&tatara_lisp::read_spanned(corpus).expect("reads"), &mut ())
1108 .expect("corpus")
1109 else {
1110 panic!("a list")
1111 };
1112 let ours = frequencies(xs.iter(), tatara_lisp::Span::synthetic()).expect("counts");
1113 assert!(blue_equal(&ours, &expected), "{ours:?} vs {expected:?}");
1114 assert!(crate::run("frequencies([[1]])").is_err());
1115 assert_eq!(
1116 i("get(frequencies(map((x) -> x % 7, range(70000))), 3)"),
1117 10000
1118 );
1119 }
1120
1121 fn eval(src: &str) -> Value {
1122 crate::run(src)
1123 .unwrap_or_else(|e| panic!("{src:?}: {e}"))
1124 .value
1125 }
1126
1127 fn s(src: &str) -> String {
1128 match eval(src) {
1129 Value::Str(v) => v.to_string(),
1130 other => panic!("{src:?} produced {other:?}"),
1131 }
1132 }
1133
1134 fn i(src: &str) -> i64 {
1135 match eval(src) {
1136 Value::Int(v) => v,
1137 other => panic!("{src:?} produced {other:?}"),
1138 }
1139 }
1140
1141 fn ints(src: &str) -> Vec<i64> {
1142 match eval(src) {
1143 Value::List(xs) => xs
1144 .iter()
1145 .map(|v| match v {
1146 Value::Int(n) => *n,
1147 other => panic!("{src:?} produced a non-Int element {other:?}"),
1148 })
1149 .collect(),
1150 other => panic!("{src:?} produced {other:?}"),
1151 }
1152 }
1153
1154 #[test]
1157 fn range_answers_as_the_lisp_definition_did() {
1158 assert_eq!(ints("range(5)"), vec![0, 1, 2, 3, 4]);
1159 assert_eq!(ints("range(2, 6)"), vec![2, 3, 4, 5]);
1160 assert_eq!(ints("range(0, 10, 2)"), vec![0, 2, 4, 6, 8]);
1161 assert_eq!(ints("range(10, 0, 0 - 2)"), vec![10, 8, 6, 4, 2]);
1162 assert!(ints("range(5, 5)").is_empty());
1163 assert!(ints("range(5, 2)").is_empty());
1164 assert!(ints("range(0)").is_empty());
1165 match eval("range(0, 1, 0.5)") {
1167 Value::List(xs) => {
1168 assert!(matches!(xs[0], Value::Int(0)), "{xs:?}");
1169 assert!(matches!(xs[1], Value::Float(x) if x == 0.5), "{xs:?}");
1170 assert_eq!(xs.len(), 2);
1171 }
1172 other => panic!("{other:?}"),
1173 }
1174 }
1175
1176 #[test]
1182 fn range_builds_long_lists_without_recursing() {
1183 assert_eq!(i("length(range(0, 200000))"), 200_000);
1184 assert_eq!(i("nth(199999, range(0, 200000))"), 199_999);
1185 }
1186
1187 #[test]
1190 fn range_refuses_a_zero_step_and_a_non_number() {
1191 assert!(crate::run("range(0, 5, 0)").is_err());
1192 assert!(crate::run("range(0, \"x\")").is_err());
1193 }
1194
1195 #[test]
1199 fn length_counts_characters_not_bytes() {
1200 assert_eq!(i("length(\"hello\")"), 5);
1201 assert_eq!(i("length(\"héllo\")"), 5, "must not be 6");
1203 assert_eq!(i("length(\"😀\")"), 1, "must not be 4");
1205 }
1206
1207 #[test]
1210 fn a_combining_sequence_counts_scalars_not_graphemes() {
1211 assert_eq!(
1213 i("length(\"e\\u{301}\")"),
1214 2,
1215 "blue counts scalar values; Elixir's String.length would say 1"
1216 );
1217 }
1218
1219 #[test]
1220 fn length_also_works_on_a_list() {
1221 assert_eq!(i("length([1, 2, 3])"), 3);
1222 }
1223
1224 #[test]
1225 fn case_and_trim() {
1226 assert_eq!(s("upcase(\"abc\")"), "ABC");
1227 assert_eq!(s("downcase(\"ABC\")"), "abc");
1228 assert_eq!(s("trim(\" hi \")"), "hi");
1229 assert_eq!(s("upcase(\"é\")"), "É");
1231 }
1232
1233 #[test]
1234 fn concat_and_to_s() {
1235 assert_eq!(s("concat(\"a\", \"b\")"), "ab");
1236 assert_eq!(s("concat(\"n=\", 42)"), "n=42");
1237 assert_eq!(s("to_s(42)"), "42");
1238 assert_eq!(s("to_s(true)"), "true");
1239 }
1240
1241 #[test]
1245 fn plus_is_not_string_concatenation() {
1246 assert!(
1247 crate::run("\"a\" + \"b\"").is_err(),
1248 "`+` must not silently concatenate — use concat"
1249 );
1250 }
1251
1252 #[test]
1253 fn split_and_join() {
1254 assert_eq!(i("length(split(\"a,b,c\", \",\"))"), 3);
1255 assert_eq!(s("join(split(\"a,b,c\", \",\"), \"-\")"), "a-b-c");
1256 assert_eq!(i("length(split(\"abc\", \"\"))"), 3);
1258 }
1259
1260 #[test]
1261 fn predicates() {
1262 assert!(matches!(
1263 eval("contains?(\"hello\", \"ell\")"),
1264 Value::Bool(true)
1265 ));
1266 assert!(matches!(
1267 eval("contains?(\"hello\", \"xyz\")"),
1268 Value::Bool(false)
1269 ));
1270 assert!(matches!(
1271 eval("starts_with?(\"hello\", \"he\")"),
1272 Value::Bool(true)
1273 ));
1274 assert!(matches!(
1275 eval("ends_with?(\"hello\", \"lo\")"),
1276 Value::Bool(true)
1277 ));
1278 }
1279
1280 fn refused(src: &str) -> String {
1281 match crate::run(src) {
1282 Ok(v) => panic!("{src:?} should fail, produced {:?}", v.value),
1283 Err(e) => e.to_string(),
1284 }
1285 }
1286
1287 #[test]
1288 fn text_bytes_and_bytes_text_are_utf8_both_ways() {
1289 assert_eq!(ints("text_bytes(\"abc\")"), vec![97, 98, 99]);
1290 assert_eq!(ints("text_bytes(\"é\")"), vec![195, 169]);
1291 assert!(ints("text_bytes(\"\")").is_empty());
1292 assert_eq!(s("bytes_text([104, 195, 169])"), "hé");
1293 assert_eq!(s("bytes_text(text_bytes(\"€ x\"))"), "€ x");
1294 assert_eq!(s("bytes_text([])"), "");
1295 assert!(matches!(eval("bytes_text([195])"), Value::Nil));
1296 assert!(matches!(eval("bytes_text([255, 97])"), Value::Nil));
1297 assert!(refused("bytes_text([256])").contains("outside 0..=255"));
1298 assert!(refused("bytes_text(nil)").contains("string or a list of bytes"));
1299 assert!(refused("text_bytes(5)").contains("string"));
1300 }
1301
1302 #[test]
1303 fn trim_without_options_cuts_whitespace_at_both_ends() {
1304 assert_eq!(s("trim(\" a b \\n\")"), "a b");
1305 assert_eq!(s("trim(\" a \", nil)"), "a");
1306 assert_eq!(s("trim(\" a \", {})"), "a");
1307 assert_eq!(s("trim(\"\")"), "");
1308 assert_eq!(s("trim(\" \")"), "");
1309 assert_eq!(s("trim(\"\", {side: :start})"), "");
1310 }
1311
1312 #[test]
1313 fn trim_side_keeps_the_other_end() {
1314 assert_eq!(s("trim(\" a \", {side: :both})"), "a");
1315 assert_eq!(s("trim(\" a \", {side: :start})"), "a ");
1316 assert_eq!(s("trim(\" a \", {side: :end})"), " a");
1317 }
1318
1319 #[test]
1320 fn trim_chars_replaces_whitespace_as_the_set_to_cut() {
1321 assert_eq!(s("trim(\"--a-b--\", {chars: \"-\"})"), "a-b");
1322 assert_eq!(s("trim(\"-+a+-\", {chars: \"-+\", side: :start})"), "a+-");
1323 assert_eq!(s("trim(\"-+a+-\", {chars: \"-+\", side: :end})"), "-+a");
1324 assert_eq!(s("trim(\" a \", {chars: \"-\"})"), " a ");
1325 assert_eq!(s("trim(\"aaa\", {chars: \"a\"})"), "");
1326 assert_eq!(s("trim(\"héé\", {chars: \"é\", side: :end})"), "h");
1327 assert_eq!(s("trim(\" a \", {chars: \"\"})"), " a ");
1328 }
1329
1330 #[test]
1331 fn trim_refuses_what_it_does_not_take() {
1332 assert!(refused("trim(\"a\", {sid: :end})").contains("bad_option"));
1333 assert!(refused("trim(\"a\", {sid: :end})").contains("side, chars"));
1334 assert!(refused("trim(\"a\", {side: :middle})").contains("side must be"));
1335 assert!(refused("trim(\"a\", {side: \"end\"})").contains("bad_option"));
1336 assert!(refused("trim(\"a\", {chars: 1})").contains("chars must be text"));
1337 assert!(refused("trim(\"a\", 5)").contains("options must be a map"));
1338 assert!(refused("trim(\"a\", {}, {})").contains("1 to 2"));
1339 assert!(refused("trim()").contains("1 to 2"));
1340 assert!(refused("trim(5)").contains("string"));
1341 }
1342
1343 #[test]
1344 fn trim_is_still_a_value_other_functions_take() {
1345 assert_eq!(s("join(map(trim, [\" a\", \"b \"]), \",\")"), "a,b");
1346 }
1347
1348 #[test]
1349 fn text_index_answers_character_positions() {
1350 assert_eq!(i("text_index(\"héllo héllo\", \"llo\", 0)"), 2);
1351 assert_eq!(i("text_index(\"héllo héllo\", \"llo\", 3)"), 8);
1352 assert_eq!(i("text_index(\"héllo\", \"x\", 0)"), -1);
1353 assert_eq!(i("text_index(\"abc\", \"\", 3)"), 3);
1354 assert_eq!(i("text_index(\"abc\", \"\", 4)"), -1);
1355 assert_eq!(i("text_index(\"abc\", \"a\", -5)"), 0);
1356 assert_eq!(i("text_index(\"\", \"a\", 0)"), -1);
1357 }
1358
1359 #[test]
1360 fn text_last_index_finds_the_last_start_at_or_before_from() {
1361 assert_eq!(i("text_last_index(\"héllo héllo\", \"llo\", 99)"), 8);
1362 assert_eq!(i("text_last_index(\"héllo héllo\", \"llo\", 7)"), 2);
1363 assert_eq!(i("text_last_index(\"aaa\", \"aa\", 1)"), 1);
1364 assert_eq!(i("text_last_index(\"aaa\", \"aa\", 0)"), 0);
1365 assert_eq!(i("text_last_index(\"abc\", \"x\", 2)"), -1);
1366 assert_eq!(i("text_last_index(\"abc\", \"a\", -1)"), -1);
1367 assert_eq!(i("text_last_index(\"ab\", \"abc\", 0)"), -1);
1368 assert_eq!(i("text_last_index(\"abc\", \"\", 9)"), 3);
1369 }
1370
1371 #[test]
1372 fn replace_and_chars() {
1373 assert_eq!(s("replace(\"a-b-c\", \"-\", \"+\")"), "a+b+c");
1374 assert_eq!(i("length(chars(\"abc\"))"), 3);
1375 }
1376
1377 #[test]
1380 fn reverse_is_character_wise() {
1381 assert_eq!(s("reverse(\"abc\")"), "cba");
1382 assert_eq!(s("reverse(\"héllo\")"), "olléh", "must not corrupt the é");
1383 }
1384
1385 #[test]
1386 fn reverse_also_works_on_a_list() {
1387 assert_eq!(s("join(reverse([1, 2, 3]), \",\")"), "3,2,1");
1388 }
1389
1390 #[test]
1395 fn to_int_is_nil_on_garbage_rather_than_zero() {
1396 assert_eq!(i("to_int(\"42\")"), 42);
1397 assert!(
1398 matches!(eval("to_int(\"banana\")"), Value::Nil),
1399 "Ruby would say 0 here; a falsy nil cannot be mistaken for a result"
1400 );
1401 assert!(
1402 matches!(eval("to_int(\"0\")"), Value::Int(0)),
1403 "and a real 0 is still a real 0 — the two must stay distinguishable"
1404 );
1405 }
1406
1407 #[test]
1408 fn to_int_bang_raises_on_garbage() {
1409 assert_eq!(i("to_int!(\"42\")"), 42);
1410 let err = crate::run("to_int!(\"banana\")").expect_err("must raise");
1411 assert!(err.to_string().contains("banana"), "must name it: {err}");
1412 }
1413
1414 #[test]
1415 fn numeric_conversions_and_abs() {
1416 assert_eq!(i("to_int(3.9)"), 3);
1417 assert_eq!(i("abs(0 - 5)"), 5);
1418 assert!(matches!(eval("to_float(\"1.5\")"), Value::Float(_)));
1419 assert!(matches!(eval("to_float(\"nope\")"), Value::Nil));
1420 }
1421
1422 #[test]
1424 fn a_non_string_argument_is_a_type_error() {
1425 assert!(crate::run("upcase([1, 2])").is_err());
1426 assert!(crate::run("join(\"not a list\", \",\")").is_err());
1427 }
1428}