Skip to main content

tatara_lisp_script/stdlib/
string_ext.rs

1//! Extended string operators beyond tatara-lisp-eval primitives.
2//!
3//!   (string-starts-with? STR PREFIX) → bool
4//!   (string-ends-with?   STR SUFFIX) → bool
5//!   (string-repeat STR N)            → STR repeated N times
6//!   (string-reverse STR)             → reversed (by chars, not bytes)
7//!   (string-join SEP XS)             → concatenate XS with SEP between
8//!   (string-chars STR)               → list of 1-char strings
9//!   (string-bytes STR)               → list of integers 0..=255
10//!   (string-uppercase STR)           → uppercase (rounds out -lowercase)
11
12use std::sync::Arc;
13
14use tatara_lisp_eval::{Arity, EvalError, Interpreter, Value};
15
16use crate::script_ctx::ScriptCtx;
17use crate::stdlib::env::{int_arg, str_arg};
18
19pub fn install(interp: &mut Interpreter<ScriptCtx>) {
20    interp.register_fn(
21        "string-starts-with?",
22        Arity::Exact(2),
23        |args: &[Value], _ctx: &mut ScriptCtx, sp| {
24            let s = str_arg(&args[0], "string-starts-with?", sp)?;
25            let pfx = str_arg(&args[1], "string-starts-with?", sp)?;
26            Ok(Value::Bool(s.starts_with(&*pfx)))
27        },
28    );
29
30    interp.register_fn(
31        "string-ends-with?",
32        Arity::Exact(2),
33        |args: &[Value], _ctx: &mut ScriptCtx, sp| {
34            let s = str_arg(&args[0], "string-ends-with?", sp)?;
35            let sfx = str_arg(&args[1], "string-ends-with?", sp)?;
36            Ok(Value::Bool(s.ends_with(&*sfx)))
37        },
38    );
39
40    interp.register_fn(
41        "string-repeat",
42        Arity::Exact(2),
43        |args: &[Value], _ctx: &mut ScriptCtx, sp| {
44            let s = str_arg(&args[0], "string-repeat", sp)?;
45            let n = int_arg(&args[1], "string-repeat", sp)?.max(0) as usize;
46            Ok(Value::Str(Arc::from(s.repeat(n))))
47        },
48    );
49
50    interp.register_fn(
51        "string-reverse",
52        Arity::Exact(1),
53        |args: &[Value], _ctx: &mut ScriptCtx, sp| {
54            let s = str_arg(&args[0], "string-reverse", sp)?;
55            Ok(Value::Str(Arc::from(s.chars().rev().collect::<String>())))
56        },
57    );
58
59    interp.register_fn(
60        "string-join",
61        Arity::Exact(2),
62        |args: &[Value], _ctx: &mut ScriptCtx, sp| {
63            let sep = str_arg(&args[0], "string-join", sp)?;
64            let xs = match &args[1] {
65                Value::List(xs) => xs.clone(),
66                Value::Nil => Arc::new(Vec::new()),
67                other => {
68                    return Err(EvalError::native_fn(
69                        "string-join",
70                        format!("expected list, got {}", other.type_name()),
71                        sp,
72                    ))
73                }
74            };
75            let parts: Vec<String> = xs
76                .iter()
77                .map(|v| match v {
78                    Value::Str(s) => s.as_ref().to_owned(),
79                    Value::Int(n) => n.to_string(),
80                    Value::Float(n) => n.to_string(),
81                    Value::Bool(b) => b.to_string(),
82                    Value::Nil => String::new(),
83                    Value::Symbol(s) | Value::Keyword(s) => s.as_ref().to_owned(),
84                    other => format!("{other:?}"),
85                })
86                .collect();
87            Ok(Value::Str(Arc::from(parts.join(&sep))))
88        },
89    );
90
91    interp.register_fn(
92        "string-chars",
93        Arity::Exact(1),
94        |args: &[Value], _ctx: &mut ScriptCtx, sp| {
95            let s = str_arg(&args[0], "string-chars", sp)?;
96            Ok(Value::list(
97                s.chars()
98                    .map(|c| Value::Str(Arc::from(c.to_string())))
99                    .collect::<Vec<_>>(),
100            ))
101        },
102    );
103
104    interp.register_fn(
105        "string-bytes",
106        Arity::Exact(1),
107        |args: &[Value], _ctx: &mut ScriptCtx, sp| {
108            let s = str_arg(&args[0], "string-bytes", sp)?;
109            Ok(Value::list(
110                s.bytes().map(|b| Value::Int(b as i64)).collect::<Vec<_>>(),
111            ))
112        },
113    );
114
115    interp.register_fn(
116        "string-uppercase",
117        Arity::Exact(1),
118        |args: &[Value], _ctx: &mut ScriptCtx, sp| {
119            let s = str_arg(&args[0], "string-uppercase", sp)?;
120            Ok(Value::Str(Arc::from(s.to_uppercase())))
121        },
122    );
123}