Skip to main content

aver/types/
string.rs

1/// String namespace — text manipulation helpers.
2///
3/// Methods:
4///   String.len(s)               → Int            — char count (code points)
5///   String.byteLength(s)        → Int            — byte count (UTF-8)
6///   String.startsWith(s, pre)   → Bool
7///   String.endsWith(s, suf)     → Bool
8///   String.contains(s, sub)     → Bool
9///   String.slice(s, from, to)   → String         — code-point based substring
10///   String.trim(s)              → String
11///   String.split(s, delim)      → List<String>
12///   String.replace(s, old, new) → String
13///   String.join(list, sep)      → String
14///   String.charAt(s, index)     → Option<String>  — O(1)-ish char at code-point index
15///   String.chars(s)             → List<String>   — each char as 1-char string
16///   String.fromInt(n)           → String
17///   String.fromFloat(f)         → String
18///   String.fromBool(b)          → String
19///   String.toLower(s)           → String         — lowercase (Unicode-aware)
20///   String.toUpper(s)           → String         — uppercase (Unicode-aware)
21///
22/// No effects required.
23use std::collections::HashMap;
24use std::rc::Rc;
25
26use crate::nan_value::{Arena, NanString, NanValue};
27use crate::value::{RuntimeError, Value, list_from_vec, list_view};
28
29pub fn register(global: &mut HashMap<String, Value>) {
30    let mut members = HashMap::new();
31    for method in &[
32        "len",
33        "byteLength",
34        "startsWith",
35        "endsWith",
36        "contains",
37        "slice",
38        "trim",
39        "split",
40        "replace",
41        "join",
42        "charAt",
43        "chars",
44        "fromInt",
45        "fromFloat",
46        "fromBool",
47        "toLower",
48        "toUpper",
49    ] {
50        members.insert(
51            method.to_string(),
52            Value::Builtin(format!("String.{}", method)),
53        );
54    }
55    global.insert(
56        "String".to_string(),
57        Value::Namespace {
58            name: "String".to_string(),
59            members,
60        },
61    );
62}
63
64pub fn effects(_name: &str) -> &'static [&'static str] {
65    &[]
66}
67
68/// Returns `Some(result)` when `name` is owned by this namespace, `None` otherwise.
69pub fn call(name: &str, args: &[Value]) -> Option<Result<Value, RuntimeError>> {
70    match name {
71        "String.len" => Some(length(args)),
72        "String.byteLength" => Some(byte_length(args)),
73        "String.startsWith" => Some(starts_with(args)),
74        "String.endsWith" => Some(ends_with(args)),
75        "String.contains" => Some(contains(args)),
76        "String.slice" => Some(slice(args)),
77        "String.trim" => Some(trim(args)),
78        "String.split" => Some(split(args)),
79        "String.replace" => Some(replace(args)),
80        "String.join" => Some(join(args)),
81        "String.charAt" => Some(char_at(args)),
82        "String.chars" => Some(chars(args)),
83        "String.fromInt" => Some(from_int(args)),
84        "String.fromFloat" => Some(from_float(args)),
85        "String.fromBool" => Some(from_bool(args)),
86        "String.toLower" => Some(to_lower(args)),
87        "String.toUpper" => Some(to_upper(args)),
88        _ => None,
89    }
90}
91
92// ─── Implementations ────────────────────────────────────────────────────────
93
94fn length(args: &[Value]) -> Result<Value, RuntimeError> {
95    let [val] = one_arg("String.len", args)?;
96    let Value::Str(s) = val else {
97        return Err(RuntimeError::Error(
98            "String.len: argument must be a String".to_string(),
99        ));
100    };
101    Ok(Value::Int(s.chars().count() as i64))
102}
103
104fn byte_length(args: &[Value]) -> Result<Value, RuntimeError> {
105    let [val] = one_arg("String.byteLength", args)?;
106    let Value::Str(s) = val else {
107        return Err(RuntimeError::Error(
108            "String.byteLength: argument must be a String".to_string(),
109        ));
110    };
111    Ok(Value::Int(s.len() as i64))
112}
113
114fn starts_with(args: &[Value]) -> Result<Value, RuntimeError> {
115    let [a, b] = two_args("String.startsWith", args)?;
116    let (Value::Str(s), Value::Str(prefix)) = (a, b) else {
117        return Err(RuntimeError::Error(
118            "String.startsWith: both arguments must be String".to_string(),
119        ));
120    };
121    Ok(Value::Bool(s.starts_with(prefix.as_str())))
122}
123
124fn ends_with(args: &[Value]) -> Result<Value, RuntimeError> {
125    let [a, b] = two_args("String.endsWith", args)?;
126    let (Value::Str(s), Value::Str(suffix)) = (a, b) else {
127        return Err(RuntimeError::Error(
128            "String.endsWith: both arguments must be String".to_string(),
129        ));
130    };
131    Ok(Value::Bool(s.ends_with(suffix.as_str())))
132}
133
134fn contains(args: &[Value]) -> Result<Value, RuntimeError> {
135    let [a, b] = two_args("String.contains", args)?;
136    let (Value::Str(s), Value::Str(sub)) = (a, b) else {
137        return Err(RuntimeError::Error(
138            "String.contains: both arguments must be String".to_string(),
139        ));
140    };
141    Ok(Value::Bool(s.contains(sub.as_str())))
142}
143
144fn slice(args: &[Value]) -> Result<Value, RuntimeError> {
145    if args.len() != 3 {
146        return Err(RuntimeError::Error(format!(
147            "String.slice() takes 3 arguments (s, from, to), got {}",
148            args.len()
149        )));
150    }
151    let Value::Str(s) = &args[0] else {
152        return Err(RuntimeError::Error(
153            "String.slice: first argument must be a String".to_string(),
154        ));
155    };
156    let Value::Int(from) = &args[1] else {
157        return Err(RuntimeError::Error(
158            "String.slice: second argument must be an Int".to_string(),
159        ));
160    };
161    let Value::Int(to) = &args[2] else {
162        return Err(RuntimeError::Error(
163            "String.slice: third argument must be an Int".to_string(),
164        ));
165    };
166    Ok(Value::Str(aver_rt::string_slice(s, *from, *to)))
167}
168
169fn trim(args: &[Value]) -> Result<Value, RuntimeError> {
170    let [val] = one_arg("String.trim", args)?;
171    let Value::Str(s) = val else {
172        return Err(RuntimeError::Error(
173            "String.trim: argument must be a String".to_string(),
174        ));
175    };
176    Ok(Value::Str(s.trim().to_string()))
177}
178
179fn split(args: &[Value]) -> Result<Value, RuntimeError> {
180    let [a, b] = two_args("String.split", args)?;
181    let (Value::Str(s), Value::Str(delim)) = (a, b) else {
182        return Err(RuntimeError::Error(
183            "String.split: both arguments must be String".to_string(),
184        ));
185    };
186    let parts: Vec<Value> = s
187        .split(delim.as_str())
188        .map(|p| Value::Str(p.to_string()))
189        .collect();
190    Ok(list_from_vec(parts))
191}
192
193fn replace(args: &[Value]) -> Result<Value, RuntimeError> {
194    if args.len() != 3 {
195        return Err(RuntimeError::Error(format!(
196            "String.replace() takes 3 arguments (s, old, new), got {}",
197            args.len()
198        )));
199    }
200    let (Value::Str(s), Value::Str(old), Value::Str(new)) = (&args[0], &args[1], &args[2]) else {
201        return Err(RuntimeError::Error(
202            "String.replace: all arguments must be String".to_string(),
203        ));
204    };
205    Ok(Value::Str(s.replace(old.as_str(), new.as_str())))
206}
207
208fn join(args: &[Value]) -> Result<Value, RuntimeError> {
209    let [a, b] = two_args("String.join", args)?;
210    let items = list_view(a).ok_or_else(|| {
211        RuntimeError::Error("String.join: first argument must be a List".to_string())
212    })?;
213    let Value::Str(sep) = b else {
214        return Err(RuntimeError::Error(
215            "String.join: second argument must be a String".to_string(),
216        ));
217    };
218    let strs: Result<Vec<String>, RuntimeError> = items
219        .iter()
220        .map(|v| match v {
221            Value::Str(s) => Ok(s.clone()),
222            _ => Err(RuntimeError::Error(
223                "String.join: list elements must be String".to_string(),
224            )),
225        })
226        .collect();
227    Ok(Value::Str(strs?.join(sep.as_str())))
228}
229
230fn char_at(args: &[Value]) -> Result<Value, RuntimeError> {
231    let [a, b] = two_args("String.charAt", args)?;
232    let Value::Str(s) = a else {
233        return Err(RuntimeError::Error(
234            "String.charAt: first argument must be a String".to_string(),
235        ));
236    };
237    let Value::Int(idx) = b else {
238        return Err(RuntimeError::Error(
239            "String.charAt: second argument must be an Int".to_string(),
240        ));
241    };
242    let idx = *idx as usize;
243    match s.chars().nth(idx) {
244        Some(c) => Ok(Value::Some(Box::new(Value::Str(c.to_string())))),
245        None => Ok(Value::None),
246    }
247}
248
249fn chars(args: &[Value]) -> Result<Value, RuntimeError> {
250    let [val] = one_arg("String.chars", args)?;
251    let Value::Str(s) = val else {
252        return Err(RuntimeError::Error(
253            "String.chars: argument must be a String".to_string(),
254        ));
255    };
256    let result: Vec<Value> = s.chars().map(|c| Value::Str(c.to_string())).collect();
257    Ok(list_from_vec(result))
258}
259
260fn from_int(args: &[Value]) -> Result<Value, RuntimeError> {
261    let [val] = one_arg("String.fromInt", args)?;
262    let Value::Int(n) = val else {
263        return Err(RuntimeError::Error(
264            "String.fromInt: argument must be an Int".to_string(),
265        ));
266    };
267    Ok(Value::Str(format!("{}", n)))
268}
269
270fn from_float(args: &[Value]) -> Result<Value, RuntimeError> {
271    let [val] = one_arg("String.fromFloat", args)?;
272    let Value::Float(f) = val else {
273        return Err(RuntimeError::Error(
274            "String.fromFloat: argument must be a Float".to_string(),
275        ));
276    };
277    Ok(Value::Str(format!("{}", f)))
278}
279
280fn from_bool(args: &[Value]) -> Result<Value, RuntimeError> {
281    let [val] = one_arg("String.fromBool", args)?;
282    let Value::Bool(b) = val else {
283        return Err(RuntimeError::Error(
284            "String.fromBool: argument must be a Bool".to_string(),
285        ));
286    };
287    Ok(Value::Str(if *b { "true" } else { "false" }.to_string()))
288}
289
290fn to_lower(args: &[Value]) -> Result<Value, RuntimeError> {
291    let [val] = one_arg("String.toLower", args)?;
292    let Value::Str(s) = val else {
293        return Err(RuntimeError::Error(
294            "String.toLower: argument must be a String".to_string(),
295        ));
296    };
297    Ok(Value::Str(s.to_lowercase()))
298}
299
300fn to_upper(args: &[Value]) -> Result<Value, RuntimeError> {
301    let [val] = one_arg("String.toUpper", args)?;
302    let Value::Str(s) = val else {
303        return Err(RuntimeError::Error(
304            "String.toUpper: argument must be a String".to_string(),
305        ));
306    };
307    Ok(Value::Str(s.to_uppercase()))
308}
309
310// ─── Helpers ────────────────────────────────────────────────────────────────
311
312fn one_arg<'a>(name: &str, args: &'a [Value]) -> Result<[&'a Value; 1], RuntimeError> {
313    if args.len() != 1 {
314        return Err(RuntimeError::Error(format!(
315            "{}() takes 1 argument, got {}",
316            name,
317            args.len()
318        )));
319    }
320    Ok([&args[0]])
321}
322
323fn two_args<'a>(name: &str, args: &'a [Value]) -> Result<[&'a Value; 2], RuntimeError> {
324    if args.len() != 2 {
325        return Err(RuntimeError::Error(format!(
326            "{}() takes 2 arguments, got {}",
327            name,
328            args.len()
329        )));
330    }
331    Ok([&args[0], &args[1]])
332}
333
334// ─── NanValue-native API ─────────────────────────────────────────────────────
335
336pub fn register_nv(global: &mut HashMap<String, NanValue>, arena: &mut Arena) {
337    let methods = &[
338        "len",
339        "byteLength",
340        "startsWith",
341        "endsWith",
342        "contains",
343        "slice",
344        "trim",
345        "split",
346        "replace",
347        "join",
348        "charAt",
349        "chars",
350        "fromInt",
351        "fromFloat",
352        "fromBool",
353        "toLower",
354        "toUpper",
355    ];
356    let mut members: Vec<(Rc<str>, NanValue)> = Vec::with_capacity(methods.len());
357    for method in methods {
358        let idx = arena.push_builtin(&format!("String.{}", method));
359        members.push((Rc::from(*method), NanValue::new_builtin(idx)));
360    }
361    let ns_idx = arena.push(crate::nan_value::ArenaEntry::Namespace {
362        name: Rc::from("String"),
363        members,
364    });
365    global.insert("String".to_string(), NanValue::new_namespace(ns_idx));
366}
367
368pub fn call_nv(
369    name: &str,
370    args: &[NanValue],
371    arena: &mut Arena,
372) -> Option<Result<NanValue, RuntimeError>> {
373    match name {
374        "String.len" => Some(length_nv(args, arena)),
375        "String.byteLength" => Some(byte_length_nv(args, arena)),
376        "String.startsWith" => Some(starts_with_nv(args, arena)),
377        "String.endsWith" => Some(ends_with_nv(args, arena)),
378        "String.contains" => Some(contains_nv(args, arena)),
379        "String.slice" => Some(slice_nv(args, arena)),
380        "String.trim" => Some(trim_nv(args, arena)),
381        "String.split" => Some(split_nv(args, arena)),
382        "String.replace" => Some(replace_nv(args, arena)),
383        "String.join" => Some(join_nv(args, arena)),
384        "String.charAt" => Some(char_at_nv(args, arena)),
385        "String.chars" => Some(chars_nv(args, arena)),
386        "String.fromInt" => Some(from_int_nv(args, arena)),
387        "String.fromFloat" => Some(from_float_nv(args, arena)),
388        "String.fromBool" => Some(from_bool_nv(args, arena)),
389        "String.toLower" => Some(to_lower_nv(args, arena)),
390        "String.toUpper" => Some(to_upper_nv(args, arena)),
391        _ => None,
392    }
393}
394
395fn nv_str(v: NanValue, arena: &Arena) -> Option<NanString<'_>> {
396    if v.is_string() {
397        Some(arena.get_string_value(v))
398    } else {
399        None
400    }
401}
402
403fn length_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
404    if args.len() != 1 {
405        return Err(RuntimeError::Error(format!(
406            "String.len() takes 1 argument, got {}",
407            args.len()
408        )));
409    }
410    let s = nv_str(args[0], arena)
411        .ok_or_else(|| RuntimeError::Error("String.len: argument must be a String".to_string()))?;
412    Ok(NanValue::new_int(s.chars().count() as i64, arena))
413}
414
415fn byte_length_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
416    if args.len() != 1 {
417        return Err(RuntimeError::Error(format!(
418            "String.byteLength() takes 1 argument, got {}",
419            args.len()
420        )));
421    }
422    let s = nv_str(args[0], arena).ok_or_else(|| {
423        RuntimeError::Error("String.byteLength: argument must be a String".to_string())
424    })?;
425    Ok(NanValue::new_int(s.len() as i64, arena))
426}
427
428fn starts_with_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
429    if args.len() != 2 {
430        return Err(RuntimeError::Error(format!(
431            "String.startsWith() takes 2 arguments, got {}",
432            args.len()
433        )));
434    }
435    if !args[0].is_string() || !args[1].is_string() {
436        return Err(RuntimeError::Error(
437            "String.startsWith: both arguments must be String".to_string(),
438        ));
439    }
440    let s = arena.get_string_value(args[0]);
441    let prefix = arena.get_string_value(args[1]);
442    let result = s.starts_with(prefix.as_str());
443    Ok(NanValue::new_bool(result))
444}
445
446fn ends_with_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
447    if args.len() != 2 {
448        return Err(RuntimeError::Error(format!(
449            "String.endsWith() takes 2 arguments, got {}",
450            args.len()
451        )));
452    }
453    if !args[0].is_string() || !args[1].is_string() {
454        return Err(RuntimeError::Error(
455            "String.endsWith: both arguments must be String".to_string(),
456        ));
457    }
458    let s = arena.get_string_value(args[0]);
459    let suffix = arena.get_string_value(args[1]);
460    let result = s.ends_with(suffix.as_str());
461    Ok(NanValue::new_bool(result))
462}
463
464fn contains_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
465    if args.len() != 2 {
466        return Err(RuntimeError::Error(format!(
467            "String.contains() takes 2 arguments, got {}",
468            args.len()
469        )));
470    }
471    if !args[0].is_string() || !args[1].is_string() {
472        return Err(RuntimeError::Error(
473            "String.contains: both arguments must be String".to_string(),
474        ));
475    }
476    let s = arena.get_string_value(args[0]);
477    let sub = arena.get_string_value(args[1]);
478    let result = s.contains(sub.as_str());
479    Ok(NanValue::new_bool(result))
480}
481
482fn slice_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
483    if args.len() != 3 {
484        return Err(RuntimeError::Error(format!(
485            "String.slice() takes 3 arguments (s, from, to), got {}",
486            args.len()
487        )));
488    }
489    if !args[0].is_string() {
490        return Err(RuntimeError::Error(
491            "String.slice: first argument must be a String".to_string(),
492        ));
493    }
494    if !args[1].is_int() {
495        return Err(RuntimeError::Error(
496            "String.slice: second argument must be an Int".to_string(),
497        ));
498    }
499    if !args[2].is_int() {
500        return Err(RuntimeError::Error(
501            "String.slice: third argument must be an Int".to_string(),
502        ));
503    }
504    let s = arena.get_string_value(args[0]).to_string();
505    let from = args[1].as_int(arena);
506    let to = args[2].as_int(arena);
507    let result = aver_rt::string_slice(&s, from, to);
508    Ok(NanValue::new_string_value(&result, arena))
509}
510
511fn trim_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
512    if args.len() != 1 {
513        return Err(RuntimeError::Error(format!(
514            "String.trim() takes 1 argument, got {}",
515            args.len()
516        )));
517    }
518    let s = nv_str(args[0], arena)
519        .ok_or_else(|| RuntimeError::Error("String.trim: argument must be a String".to_string()))?;
520    let result = s.trim().to_string();
521    Ok(NanValue::new_string_value(&result, arena))
522}
523
524fn split_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
525    if args.len() != 2 {
526        return Err(RuntimeError::Error(format!(
527            "String.split() takes 2 arguments, got {}",
528            args.len()
529        )));
530    }
531    if !args[0].is_string() || !args[1].is_string() {
532        return Err(RuntimeError::Error(
533            "String.split: both arguments must be String".to_string(),
534        ));
535    }
536    let s = arena.get_string_value(args[0]).to_string();
537    let delim = arena.get_string_value(args[1]).to_string();
538    let parts: Vec<NanValue> = s
539        .split(&*delim)
540        .map(|p| NanValue::new_string_value(p, arena))
541        .collect();
542    let list_idx = arena.push_list(parts);
543    Ok(NanValue::new_list(list_idx))
544}
545
546fn replace_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
547    if args.len() != 3 {
548        return Err(RuntimeError::Error(format!(
549            "String.replace() takes 3 arguments (s, old, new), got {}",
550            args.len()
551        )));
552    }
553    if !args[0].is_string() || !args[1].is_string() || !args[2].is_string() {
554        return Err(RuntimeError::Error(
555            "String.replace: all arguments must be String".to_string(),
556        ));
557    }
558    let s = arena.get_string_value(args[0]).to_string();
559    let old = arena.get_string_value(args[1]).to_string();
560    let new = arena.get_string_value(args[2]).to_string();
561    let result = s.replace(&*old, &new);
562    Ok(NanValue::new_string_value(&result, arena))
563}
564
565fn join_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
566    if args.len() != 2 {
567        return Err(RuntimeError::Error(format!(
568            "String.join() takes 2 arguments, got {}",
569            args.len()
570        )));
571    }
572    if !args[0].is_list() {
573        return Err(RuntimeError::Error(
574            "String.join: first argument must be a List".to_string(),
575        ));
576    }
577    if !args[1].is_string() {
578        return Err(RuntimeError::Error(
579            "String.join: second argument must be a String".to_string(),
580        ));
581    }
582    let items = arena.list_to_vec_value(args[0]);
583    let sep = arena.get_string_value(args[1]).to_string();
584    let mut strs: Vec<String> = Vec::with_capacity(items.len());
585    for item in &items {
586        if !item.is_string() {
587            return Err(RuntimeError::Error(
588                "String.join: list elements must be String".to_string(),
589            ));
590        }
591        strs.push(arena.get_string_value(*item).to_string());
592    }
593    let result = strs.join(&sep);
594    Ok(NanValue::new_string_value(&result, arena))
595}
596
597fn char_at_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
598    if args.len() != 2 {
599        return Err(RuntimeError::Error(format!(
600            "String.charAt() takes 2 arguments, got {}",
601            args.len()
602        )));
603    }
604    if !args[0].is_string() {
605        return Err(RuntimeError::Error(
606            "String.charAt: first argument must be a String".to_string(),
607        ));
608    }
609    if !args[1].is_int() {
610        return Err(RuntimeError::Error(
611            "String.charAt: second argument must be an Int".to_string(),
612        ));
613    }
614    let s = arena.get_string_value(args[0]);
615    let idx_val = args[1].as_int(arena) as usize;
616    match s.chars().nth(idx_val) {
617        Some(c) => {
618            let cs = c.to_string();
619            let inner = NanValue::new_string_value(&cs, arena);
620            Ok(NanValue::new_some_value(inner, arena))
621        }
622        None => Ok(NanValue::NONE),
623    }
624}
625
626fn chars_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
627    if args.len() != 1 {
628        return Err(RuntimeError::Error(format!(
629            "String.chars() takes 1 argument, got {}",
630            args.len()
631        )));
632    }
633    if !args[0].is_string() {
634        return Err(RuntimeError::Error(
635            "String.chars: argument must be a String".to_string(),
636        ));
637    }
638    let s = arena.get_string_value(args[0]).to_string();
639    let items: Vec<NanValue> = s
640        .chars()
641        .map(|c| NanValue::new_string_value(&c.to_string(), arena))
642        .collect();
643    let list_idx = arena.push_list(items);
644    Ok(NanValue::new_list(list_idx))
645}
646
647fn from_int_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
648    if args.len() != 1 {
649        return Err(RuntimeError::Error(format!(
650            "String.fromInt() takes 1 argument, got {}",
651            args.len()
652        )));
653    }
654    if !args[0].is_int() {
655        return Err(RuntimeError::Error(
656            "String.fromInt: argument must be an Int".to_string(),
657        ));
658    }
659    let s = format!("{}", args[0].as_int(arena));
660    Ok(NanValue::new_string_value(&s, arena))
661}
662
663fn from_float_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
664    if args.len() != 1 {
665        return Err(RuntimeError::Error(format!(
666            "String.fromFloat() takes 1 argument, got {}",
667            args.len()
668        )));
669    }
670    if !args[0].is_float() {
671        return Err(RuntimeError::Error(
672            "String.fromFloat: argument must be a Float".to_string(),
673        ));
674    }
675    let s = format!("{}", args[0].as_float());
676    Ok(NanValue::new_string_value(&s, arena))
677}
678
679fn from_bool_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
680    if args.len() != 1 {
681        return Err(RuntimeError::Error(format!(
682            "String.fromBool() takes 1 argument, got {}",
683            args.len()
684        )));
685    }
686    if !args[0].is_bool() {
687        return Err(RuntimeError::Error(
688            "String.fromBool: argument must be a Bool".to_string(),
689        ));
690    }
691    let s = if args[0].as_bool() { "true" } else { "false" };
692    Ok(NanValue::new_string_value(s, arena))
693}
694
695fn to_lower_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
696    if args.len() != 1 {
697        return Err(RuntimeError::Error(format!(
698            "String.toLower() takes 1 argument, got {}",
699            args.len()
700        )));
701    }
702    if !args[0].is_string() {
703        return Err(RuntimeError::Error(
704            "String.toLower: argument must be a String".to_string(),
705        ));
706    }
707    let s = arena.get_string_value(args[0]).to_lowercase();
708    Ok(NanValue::new_string_value(&s, arena))
709}
710
711fn to_upper_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
712    if args.len() != 1 {
713        return Err(RuntimeError::Error(format!(
714            "String.toUpper() takes 1 argument, got {}",
715            args.len()
716        )));
717    }
718    if !args[0].is_string() {
719        return Err(RuntimeError::Error(
720            "String.toUpper: argument must be a String".to_string(),
721        ));
722    }
723    let s = arena.get_string_value(args[0]).to_uppercase();
724    Ok(NanValue::new_string_value(&s, arena))
725}