tatara_lisp_script/stdlib/
string.rs1use std::sync::Arc;
13
14use tatara_lisp_eval::{Arity, EvalError, Interpreter, Value};
15
16use crate::script_ctx::ScriptCtx;
17use crate::stdlib::env::str_arg;
18
19pub fn install(interp: &mut Interpreter<ScriptCtx>) {
20 interp.register_fn(
21 "string-replace",
22 Arity::Exact(3),
23 |args: &[Value], _ctx: &mut ScriptCtx, sp| {
24 let s = str_arg(&args[0], "string-replace", sp)?;
25 let from = str_arg(&args[1], "string-replace", sp)?;
26 let to = str_arg(&args[2], "string-replace", sp)?;
27 Ok(Value::Str(Arc::from(s.replace(&*from, &to))))
28 },
29 );
30
31 interp.register_fn(
32 "string-split",
33 Arity::Exact(2),
34 |args: &[Value], _ctx: &mut ScriptCtx, sp| {
35 let s = str_arg(&args[0], "string-split", sp)?;
36 let sep = str_arg(&args[1], "string-split", sp)?;
37 Ok(Value::list(
38 s.split(&*sep)
39 .map(|p| Value::Str(Arc::from(p)))
40 .collect::<Vec<_>>(),
41 ))
42 },
43 );
44
45 interp.register_fn(
46 "string-contains?",
47 Arity::Exact(2),
48 |args: &[Value], _ctx: &mut ScriptCtx, sp| {
49 let s = str_arg(&args[0], "string-contains?", sp)?;
50 let needle = str_arg(&args[1], "string-contains?", sp)?;
51 Ok(Value::Bool(s.contains(&*needle)))
52 },
53 );
54
55 interp.register_fn(
56 "string-lowercase",
57 Arity::Exact(1),
58 |args: &[Value], _ctx: &mut ScriptCtx, sp| {
59 let s = str_arg(&args[0], "string-lowercase", sp)?;
60 Ok(Value::Str(Arc::from(s.to_lowercase())))
61 },
62 );
63
64 interp.register_fn(
65 "string-trim",
66 Arity::Exact(1),
67 |args: &[Value], _ctx: &mut ScriptCtx, sp| {
68 let s = str_arg(&args[0], "string-trim", sp)?;
69 Ok(Value::Str(Arc::from(s.trim())))
70 },
71 );
72
73 interp.register_fn(
89 "string->number",
90 Arity::Exact(1),
91 |args: &[Value], _ctx: &mut ScriptCtx, sp| {
92 let s = str_arg(&args[0], "string->number", sp)?;
93 let t = s.trim();
94 if let Ok(n) = t.parse::<i64>() {
95 return Ok(Value::Int(n));
96 }
97 if let Ok(f) = t.parse::<f64>() {
98 if f.is_finite() {
99 return Ok(Value::Float(f));
100 }
101 }
102 Err(EvalError::native_fn(
103 "string->number",
104 format!("not a number: {t:?}"),
105 sp,
106 ))
107 },
108 );
109
110 interp.register_fn(
111 "string-format",
112 Arity::AtLeast(1),
113 |args: &[Value], _ctx: &mut ScriptCtx, sp| {
114 let tmpl = str_arg(&args[0], "string-format", sp)?;
115 let mut out = String::with_capacity(tmpl.len());
116 let mut remaining = &*tmpl;
117 let mut idx = 1usize;
118 while let Some(pos) = remaining.find("{}") {
119 out.push_str(&remaining[..pos]);
120 let Some(arg) = args.get(idx) else {
121 return Err(EvalError::native_fn(
122 "string-format",
123 format!("template has more {{}} than args ({idx})"),
124 sp,
125 ));
126 };
127 out.push_str(&display_value(arg));
128 remaining = &remaining[pos + 2..];
129 idx += 1;
130 }
131 out.push_str(remaining);
132 Ok(Value::Str(Arc::from(out)))
133 },
134 );
135}
136
137fn display_value(v: &Value) -> String {
138 match v {
139 Value::Nil => "nil".to_string(),
140 Value::Bool(b) => b.to_string(),
141 Value::Int(n) => n.to_string(),
142 Value::Float(n) => n.to_string(),
143 Value::Str(s) | Value::Symbol(s) | Value::Keyword(s) => s.as_ref().to_string(),
144 other => format!("{other:?}"),
145 }
146}