1use tan::{
2 context::Context,
3 error::Error,
4 expr::{format_value, Expr},
5 util::{args::unpack_stringable_arg, module_util::require_module},
6};
7
8pub fn string_new(args: &[Expr]) -> Result<Expr, Error> {
19 let output = args.iter().fold(String::new(), |mut str, x| {
20 str.push_str(&format_value(x));
21 str
22 });
23
24 Ok(Expr::String(output))
25}
26
27pub fn string_get_length(args: &[Expr]) -> Result<Expr, Error> {
30 let [this] = args else {
31 return Err(Error::invalid_arguments(
32 "`chars` requires `this` argument",
33 None,
34 ));
35 };
36
37 let Expr::String(s) = this.unpack() else {
38 return Err(Error::invalid_arguments(
39 "`this` argument should be a String",
40 this.range(),
41 ));
42 };
43
44 Ok(Expr::Int(s.len() as i64))
45}
46
47pub fn string_is_empty(args: &[Expr]) -> Result<Expr, Error> {
49 let s = unpack_stringable_arg(args, 0, "s")?;
50 Ok(Expr::Bool(s.is_empty()))
51}
52
53pub fn string_trim(args: &[Expr]) -> Result<Expr, Error> {
57 let [this] = args else {
58 return Err(Error::invalid_arguments("requires `this` argument", None));
59 };
60
61 let Expr::String(s) = this.unpack() else {
62 return Err(Error::invalid_arguments(
63 "`this` argument should be a String",
64 this.range(),
65 ));
66 };
67
68 Ok(Expr::string(s.trim()))
69}
70
71pub fn string_pop(_args: &[Expr]) -> Result<Expr, Error> {
75 todo!()
79}
80
81pub fn string_slice(args: &[Expr]) -> Result<Expr, Error> {
91 let [this, start, ..] = args else {
92 return Err(Error::invalid_arguments(
93 "`slice` requires `this` and start arguments",
94 None,
95 ));
96 };
97
98 let Some(s) = this.as_stringable() else {
99 return Err(Error::invalid_arguments(
100 "`this` argument should be a String",
101 this.range(),
102 ));
103 };
104
105 let Expr::Int(start) = start.unpack() else {
106 return Err(Error::invalid_arguments(
107 "`start` argument should be an Int",
108 this.range(),
109 ));
110 };
111
112 let start = *start;
113
114 let end = if let Some(end) = args.get(2) {
115 let Expr::Int(end) = end.unpack() else {
116 return Err(Error::invalid_arguments(
117 "`end` argument should be an Int",
118 this.range(),
119 ));
120 };
121 *end
122 } else {
123 s.len() as i64
124 };
125
126 let start = start as usize;
127 let end = if end < 0 {
128 (s.len() as i64 + end) as usize
132 } else {
133 end as usize
134 };
135
136 let string_slice = &s[start..end];
137
138 Ok(Expr::string(string_slice))
139}
140
141pub fn string_slice_range(args: &[Expr]) -> Result<Expr, Error> {
145 let [this, start, ..] = args else {
146 return Err(Error::invalid_arguments(
147 "`slice` requires `this` and range arguments",
148 None,
149 ));
150 };
151
152 let Expr::String(s) = this.unpack() else {
153 return Err(Error::invalid_arguments(
154 "`this` argument should be a String",
155 this.range(),
156 ));
157 };
158
159 let Expr::IntRange(start, end, ..) = start.unpack() else {
160 return Err(Error::invalid_arguments(
161 "`range` argument should be a Range",
162 this.range(),
163 ));
164 };
165
166 let start = *start;
170 let end = *end;
171
172 let start = start as usize;
173 let end = if end < 0 {
174 (s.len() as i64 + end) as usize
178 } else {
179 end as usize
180 };
181
182 let string_slice = &s[start..end];
183
184 Ok(Expr::string(string_slice))
185}
186
187pub fn string_chars(args: &[Expr]) -> Result<Expr, Error> {
189 let [this] = args else {
190 return Err(Error::invalid_arguments(
191 "`chars` requires `this` argument",
192 None,
193 ));
194 };
195
196 let Some(this) = this.as_string() else {
197 return Err(Error::invalid_arguments(
198 "`this` argument should be a String",
199 this.range(),
200 ));
201 };
202
203 let mut exprs: Vec<Expr> = Vec::new();
204
205 for char in this.chars() {
206 exprs.push(Expr::Char(char));
207 }
208
209 Ok(Expr::array(exprs))
210}
211
212pub fn string_constructor_from_chars(args: &[Expr]) -> Result<Expr, Error> {
213 let [chars] = args else {
214 return Err(Error::invalid_arguments("requires `chars` argument", None));
215 };
216
217 let Some(exprs) = chars.as_array() else {
218 return Err(Error::invalid_arguments(
219 "`chars` argument should be a (Array Char)",
220 chars.range(),
221 ));
222 };
223
224 let mut chars: Vec<char> = Vec::new();
227
228 for expr in exprs.iter() {
229 if let Some(c) = expr.as_char() {
230 chars.push(c);
231 }
232 }
233
234 let string = String::from_iter(chars);
235
236 Ok(Expr::String(string))
237}
238
239pub fn char_to_upper_case(args: &[Expr]) -> Result<Expr, Error> {
242 let [this] = args else {
243 return Err(Error::invalid_arguments(
244 "`to-upper-case` requires `this` argument",
245 None,
246 ));
247 };
248
249 let Expr::Char(this) = this.unpack() else {
250 return Err(Error::invalid_arguments(
251 "`this` argument should be a Char",
252 this.range(),
253 ));
254 };
255
256 let uppercased = this.to_uppercase().next().unwrap();
258
259 Ok(Expr::Char(uppercased))
260}
261
262pub fn string_to_lower_case(args: &[Expr]) -> Result<Expr, Error> {
267 let [this] = args else {
268 return Err(Error::invalid_arguments("requires `this` argument", None));
269 };
270
271 let Some(this) = this.as_string() else {
273 return Err(Error::invalid_arguments(
274 "`this` argument should be a String",
275 this.range(),
276 ));
277 };
278
279 let lowercased = this.to_lowercase();
280
281 Ok(Expr::String(lowercased))
282}
283
284pub fn string_format(args: &[Expr]) -> Result<Expr, Error> {
291 let output = args.iter().fold(String::new(), |mut str, x| {
292 str.push_str(&format_value(x));
293 str
294 });
295
296 Ok(Expr::String(output))
297}
298
299pub fn string_split(args: &[Expr]) -> Result<Expr, Error> {
305 let string = unpack_stringable_arg(args, 0, "this")?;
308 let separator = unpack_stringable_arg(args, 1, "separator")?;
309
310 let parts: Vec<Expr> = string.split(separator).map(Expr::string).collect();
313
314 Ok(Expr::array(parts))
315}
316
317pub fn string_contains(args: &[Expr]) -> Result<Expr, Error> {
320 let [this, string] = args else {
321 return Err(Error::invalid_arguments(
322 "`contains` requires `this` and `string` arguments",
323 None,
324 ));
325 };
326
327 let Some(this) = this.as_string() else {
328 return Err(Error::invalid_arguments(
329 "`this` argument should be a String",
330 this.range(),
331 ));
332 };
333
334 let Some(string) = string.as_string() else {
335 return Err(Error::invalid_arguments(
336 "`string` argument should be a String",
337 string.range(),
338 ));
339 };
340
341 Ok(Expr::Bool(this.contains(string)))
342}
343
344pub fn string_starts_with(args: &[Expr]) -> Result<Expr, Error> {
345 let [this, prefix] = args else {
346 return Err(Error::invalid_arguments(
347 "`starts-with` requires `this` and `prefix` arguments",
348 None,
349 ));
350 };
351
352 let Some(this) = this.as_string() else {
353 return Err(Error::invalid_arguments(
354 "`this` argument should be a String",
355 this.range(),
356 ));
357 };
358
359 let Some(prefix) = prefix.as_string() else {
360 return Err(Error::invalid_arguments(
361 "`prefix` argument should be a String",
362 prefix.range(),
363 ));
364 };
365
366 Ok(Expr::Bool(this.starts_with(prefix)))
367}
368
369pub fn string_ends_with(args: &[Expr]) -> Result<Expr, Error> {
370 let [this, postfix] = args else {
372 return Err(Error::invalid_arguments(
373 "`ends-with` requires `this` and `postfix` arguments",
374 None,
375 ));
376 };
377
378 let Some(this) = this.as_string() else {
379 return Err(Error::invalid_arguments(
380 "`this` argument should be a String",
381 this.range(),
382 ));
383 };
384
385 let Some(postfix) = postfix.as_string() else {
386 return Err(Error::invalid_arguments(
387 "`postfix` argument should be a String",
388 postfix.range(),
389 ));
390 };
391
392 Ok(Expr::Bool(this.ends_with(postfix)))
393}
394
395pub fn string_replace(args: &[Expr]) -> Result<Expr, Error> {
404 let [this, _from, _to, ..] = args else {
406 return Err(Error::invalid_arguments(
407 "`replace` requires `this`, `from`, and `to` arguments",
408 None,
409 ));
410 };
411
412 let Some(this) = this.as_string() else {
413 return Err(Error::invalid_arguments(
414 "`this` argument should be a String",
415 this.range(),
416 ));
417 };
418
419 let mut output: String = this.to_string();
420
421 let mut i = 1;
422 while i < args.len() {
423 let from = &args[i];
424 let Some(from) = from.as_string() else {
425 return Err(Error::invalid_arguments(
426 "`from` argument should be a String",
427 from.range(),
428 ));
429 };
430
431 let to = &args[i + 1];
432 let Some(to) = to.as_string() else {
433 return Err(Error::invalid_arguments(
434 "`to` argument should be a String",
435 to.range(),
436 ));
437 };
438
439 output = output.replace(from, to);
440
441 i += 2;
442 }
443
444 Ok(Expr::String(output))
445
446 }
462
463pub fn string_compare(args: &[Expr]) -> Result<Expr, Error> {
467 let [a, b] = args else {
469 return Err(Error::invalid_arguments(
470 "requires at least two arguments",
471 None,
472 ));
473 };
474
475 let Some(a) = a.as_stringable() else {
479 return Err(Error::invalid_arguments(
480 &format!("{a} is not a String"),
481 a.range(),
482 ));
483 };
484
485 let Some(b) = b.as_stringable() else {
486 return Err(Error::invalid_arguments(
487 &format!("{b} is not a String"),
488 b.range(),
489 ));
490 };
491
492 let ordering = match a.cmp(b) {
494 std::cmp::Ordering::Less => -1,
495 std::cmp::Ordering::Equal => 0,
496 std::cmp::Ordering::Greater => 1,
497 };
498
499 Ok(Expr::Int(ordering))
500}
501
502pub fn setup_lib_string(context: &mut Context) {
503 let module = require_module("prelude", context);
504
505 module.insert_invocable("String", Expr::foreign_func(&string_new));
506
507 module.insert_invocable(
511 "String/from-chars",
512 Expr::foreign_func(&string_constructor_from_chars),
513 );
514 module.insert_invocable("chars", Expr::foreign_func(&string_chars));
517 module.insert_invocable("chars$$String", Expr::foreign_func(&string_chars));
518
519 module.insert_invocable("is-empty?", Expr::foreign_func(&string_is_empty));
520 module.insert_invocable("is-empty?$$String", Expr::foreign_func(&string_is_empty));
521
522 module.insert_invocable("to-upper-case", Expr::foreign_func(&char_to_upper_case));
524 module.insert_invocable(
525 "to-upper-case$$Char",
526 Expr::foreign_func(&char_to_upper_case),
527 );
528
529 module.insert_invocable("to-lower-case", Expr::foreign_func(&string_to_lower_case));
530 module.insert_invocable(
531 "to-lower-case$$String",
532 Expr::foreign_func(&string_to_lower_case),
533 );
534
535 module.insert_invocable("format-string", Expr::foreign_func(&string_format));
536
537 module.insert_invocable("split", Expr::foreign_func(&string_split));
538
539 module.insert_invocable("replace", Expr::foreign_func(&string_replace));
540
541 module.insert_invocable("slice", Expr::foreign_func(&string_slice));
544 module.insert_invocable("slice$$String$$Int$$Int", Expr::foreign_func(&string_slice));
545 module.insert_invocable(
546 "slice$$String$$(Range Int)",
547 Expr::foreign_func(&string_slice_range),
548 );
549
550 module.insert_invocable("get-length", Expr::foreign_func(&string_get_length));
554 module.insert_invocable("get-length$$String", Expr::foreign_func(&string_get_length));
555
556 module.insert_invocable("trim", Expr::foreign_func(&string_trim));
558
559 module.insert_invocable(
561 "contains?$$String$$String",
562 Expr::foreign_func(&string_contains),
563 );
564
565 module.insert_invocable("starts-with?", Expr::foreign_func(&string_starts_with));
566
567 module.insert_invocable("ends-with?", Expr::foreign_func(&string_ends_with));
576}