1use std::collections::HashMap;
24
25use crate::value::{RuntimeError, Value, list_from_vec, list_slice};
26
27pub fn register(global: &mut HashMap<String, Value>) {
28 let mut members = HashMap::new();
29 for method in &[
30 "len",
31 "byteLength",
32 "startsWith",
33 "endsWith",
34 "contains",
35 "slice",
36 "trim",
37 "split",
38 "replace",
39 "join",
40 "charAt",
41 "chars",
42 "fromInt",
43 "fromFloat",
44 "fromBool",
45 "toLower",
46 "toUpper",
47 ] {
48 members.insert(
49 method.to_string(),
50 Value::Builtin(format!("String.{}", method)),
51 );
52 }
53 global.insert(
54 "String".to_string(),
55 Value::Namespace {
56 name: "String".to_string(),
57 members,
58 },
59 );
60}
61
62pub fn effects(_name: &str) -> &'static [&'static str] {
63 &[]
64}
65
66pub fn call(name: &str, args: &[Value]) -> Option<Result<Value, RuntimeError>> {
68 match name {
69 "String.len" => Some(length(args)),
70 "String.byteLength" => Some(byte_length(args)),
71 "String.startsWith" => Some(starts_with(args)),
72 "String.endsWith" => Some(ends_with(args)),
73 "String.contains" => Some(contains(args)),
74 "String.slice" => Some(slice(args)),
75 "String.trim" => Some(trim(args)),
76 "String.split" => Some(split(args)),
77 "String.replace" => Some(replace(args)),
78 "String.join" => Some(join(args)),
79 "String.charAt" => Some(char_at(args)),
80 "String.chars" => Some(chars(args)),
81 "String.fromInt" => Some(from_int(args)),
82 "String.fromFloat" => Some(from_float(args)),
83 "String.fromBool" => Some(from_bool(args)),
84 "String.toLower" => Some(to_lower(args)),
85 "String.toUpper" => Some(to_upper(args)),
86 _ => None,
87 }
88}
89
90fn length(args: &[Value]) -> Result<Value, RuntimeError> {
93 let [val] = one_arg("String.len", args)?;
94 let Value::Str(s) = val else {
95 return Err(RuntimeError::Error(
96 "String.len: argument must be a String".to_string(),
97 ));
98 };
99 Ok(Value::Int(s.chars().count() as i64))
100}
101
102fn byte_length(args: &[Value]) -> Result<Value, RuntimeError> {
103 let [val] = one_arg("String.byteLength", args)?;
104 let Value::Str(s) = val else {
105 return Err(RuntimeError::Error(
106 "String.byteLength: argument must be a String".to_string(),
107 ));
108 };
109 Ok(Value::Int(s.len() as i64))
110}
111
112fn starts_with(args: &[Value]) -> Result<Value, RuntimeError> {
113 let [a, b] = two_args("String.startsWith", args)?;
114 let (Value::Str(s), Value::Str(prefix)) = (a, b) else {
115 return Err(RuntimeError::Error(
116 "String.startsWith: both arguments must be String".to_string(),
117 ));
118 };
119 Ok(Value::Bool(s.starts_with(prefix.as_str())))
120}
121
122fn ends_with(args: &[Value]) -> Result<Value, RuntimeError> {
123 let [a, b] = two_args("String.endsWith", args)?;
124 let (Value::Str(s), Value::Str(suffix)) = (a, b) else {
125 return Err(RuntimeError::Error(
126 "String.endsWith: both arguments must be String".to_string(),
127 ));
128 };
129 Ok(Value::Bool(s.ends_with(suffix.as_str())))
130}
131
132fn contains(args: &[Value]) -> Result<Value, RuntimeError> {
133 let [a, b] = two_args("String.contains", args)?;
134 let (Value::Str(s), Value::Str(sub)) = (a, b) else {
135 return Err(RuntimeError::Error(
136 "String.contains: both arguments must be String".to_string(),
137 ));
138 };
139 Ok(Value::Bool(s.contains(sub.as_str())))
140}
141
142fn slice(args: &[Value]) -> Result<Value, RuntimeError> {
143 if args.len() != 3 {
144 return Err(RuntimeError::Error(format!(
145 "String.slice() takes 3 arguments (s, from, to), got {}",
146 args.len()
147 )));
148 }
149 let Value::Str(s) = &args[0] else {
150 return Err(RuntimeError::Error(
151 "String.slice: first argument must be a String".to_string(),
152 ));
153 };
154 let Value::Int(from) = &args[1] else {
155 return Err(RuntimeError::Error(
156 "String.slice: second argument must be an Int".to_string(),
157 ));
158 };
159 let Value::Int(to) = &args[2] else {
160 return Err(RuntimeError::Error(
161 "String.slice: third argument must be an Int".to_string(),
162 ));
163 };
164 let from = *from as usize;
165 let to = *to as usize;
166 let result: String = s.chars().skip(from).take(to.saturating_sub(from)).collect();
167 Ok(Value::Str(result))
168}
169
170fn trim(args: &[Value]) -> Result<Value, RuntimeError> {
171 let [val] = one_arg("String.trim", args)?;
172 let Value::Str(s) = val else {
173 return Err(RuntimeError::Error(
174 "String.trim: argument must be a String".to_string(),
175 ));
176 };
177 Ok(Value::Str(s.trim().to_string()))
178}
179
180fn split(args: &[Value]) -> Result<Value, RuntimeError> {
181 let [a, b] = two_args("String.split", args)?;
182 let (Value::Str(s), Value::Str(delim)) = (a, b) else {
183 return Err(RuntimeError::Error(
184 "String.split: both arguments must be String".to_string(),
185 ));
186 };
187 let parts: Vec<Value> = s
188 .split(delim.as_str())
189 .map(|p| Value::Str(p.to_string()))
190 .collect();
191 Ok(list_from_vec(parts))
192}
193
194fn replace(args: &[Value]) -> Result<Value, RuntimeError> {
195 if args.len() != 3 {
196 return Err(RuntimeError::Error(format!(
197 "String.replace() takes 3 arguments (s, old, new), got {}",
198 args.len()
199 )));
200 }
201 let (Value::Str(s), Value::Str(old), Value::Str(new)) = (&args[0], &args[1], &args[2]) else {
202 return Err(RuntimeError::Error(
203 "String.replace: all arguments must be String".to_string(),
204 ));
205 };
206 Ok(Value::Str(s.replace(old.as_str(), new.as_str())))
207}
208
209fn join(args: &[Value]) -> Result<Value, RuntimeError> {
210 let [a, b] = two_args("String.join", args)?;
211 let items = list_slice(a).ok_or_else(|| {
212 RuntimeError::Error("String.join: first argument must be a List".to_string())
213 })?;
214 let Value::Str(sep) = b else {
215 return Err(RuntimeError::Error(
216 "String.join: second argument must be a String".to_string(),
217 ));
218 };
219 let strs: Result<Vec<String>, RuntimeError> = items
220 .iter()
221 .map(|v| match v {
222 Value::Str(s) => Ok(s.clone()),
223 _ => Err(RuntimeError::Error(
224 "String.join: list elements must be String".to_string(),
225 )),
226 })
227 .collect();
228 Ok(Value::Str(strs?.join(sep.as_str())))
229}
230
231fn char_at(args: &[Value]) -> Result<Value, RuntimeError> {
232 let [a, b] = two_args("String.charAt", args)?;
233 let Value::Str(s) = a else {
234 return Err(RuntimeError::Error(
235 "String.charAt: first argument must be a String".to_string(),
236 ));
237 };
238 let Value::Int(idx) = b else {
239 return Err(RuntimeError::Error(
240 "String.charAt: second argument must be an Int".to_string(),
241 ));
242 };
243 let idx = *idx as usize;
244 match s.chars().nth(idx) {
245 Some(c) => Ok(Value::Some(Box::new(Value::Str(c.to_string())))),
246 None => Ok(Value::None),
247 }
248}
249
250fn chars(args: &[Value]) -> Result<Value, RuntimeError> {
251 let [val] = one_arg("String.chars", args)?;
252 let Value::Str(s) = val else {
253 return Err(RuntimeError::Error(
254 "String.chars: argument must be a String".to_string(),
255 ));
256 };
257 let result: Vec<Value> = s.chars().map(|c| Value::Str(c.to_string())).collect();
258 Ok(list_from_vec(result))
259}
260
261fn from_int(args: &[Value]) -> Result<Value, RuntimeError> {
262 let [val] = one_arg("String.fromInt", args)?;
263 let Value::Int(n) = val else {
264 return Err(RuntimeError::Error(
265 "String.fromInt: argument must be an Int".to_string(),
266 ));
267 };
268 Ok(Value::Str(format!("{}", n)))
269}
270
271fn from_float(args: &[Value]) -> Result<Value, RuntimeError> {
272 let [val] = one_arg("String.fromFloat", args)?;
273 let Value::Float(f) = val else {
274 return Err(RuntimeError::Error(
275 "String.fromFloat: argument must be a Float".to_string(),
276 ));
277 };
278 Ok(Value::Str(format!("{}", f)))
279}
280
281fn from_bool(args: &[Value]) -> Result<Value, RuntimeError> {
282 let [val] = one_arg("String.fromBool", args)?;
283 let Value::Bool(b) = val else {
284 return Err(RuntimeError::Error(
285 "String.fromBool: argument must be a Bool".to_string(),
286 ));
287 };
288 Ok(Value::Str(if *b { "true" } else { "false" }.to_string()))
289}
290
291fn to_lower(args: &[Value]) -> Result<Value, RuntimeError> {
292 let [val] = one_arg("String.toLower", args)?;
293 let Value::Str(s) = val else {
294 return Err(RuntimeError::Error(
295 "String.toLower: argument must be a String".to_string(),
296 ));
297 };
298 Ok(Value::Str(s.to_lowercase()))
299}
300
301fn to_upper(args: &[Value]) -> Result<Value, RuntimeError> {
302 let [val] = one_arg("String.toUpper", args)?;
303 let Value::Str(s) = val else {
304 return Err(RuntimeError::Error(
305 "String.toUpper: argument must be a String".to_string(),
306 ));
307 };
308 Ok(Value::Str(s.to_uppercase()))
309}
310
311fn one_arg<'a>(name: &str, args: &'a [Value]) -> Result<[&'a Value; 1], RuntimeError> {
314 if args.len() != 1 {
315 return Err(RuntimeError::Error(format!(
316 "{}() takes 1 argument, got {}",
317 name,
318 args.len()
319 )));
320 }
321 Ok([&args[0]])
322}
323
324fn two_args<'a>(name: &str, args: &'a [Value]) -> Result<[&'a Value; 2], RuntimeError> {
325 if args.len() != 2 {
326 return Err(RuntimeError::Error(format!(
327 "{}() takes 2 arguments, got {}",
328 name,
329 args.len()
330 )));
331 }
332 Ok([&args[0], &args[1]])
333}