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, 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<&str> {
396    if v.is_string() {
397        Some(arena.get_string(v.arena_index()))
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(args[0].arena_index());
441    let prefix = arena.get_string(args[1].arena_index());
442    let result = s.starts_with(prefix);
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(args[0].arena_index());
459    let suffix = arena.get_string(args[1].arena_index());
460    let result = s.ends_with(suffix);
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(args[0].arena_index());
477    let sub = arena.get_string(args[1].arena_index());
478    let result = s.contains(sub);
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(args[0].arena_index()).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    let idx = arena.push_string(&result);
509    Ok(NanValue::new_string(idx))
510}
511
512fn trim_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
513    if args.len() != 1 {
514        return Err(RuntimeError::Error(format!(
515            "String.trim() takes 1 argument, got {}",
516            args.len()
517        )));
518    }
519    let s = nv_str(args[0], arena)
520        .ok_or_else(|| RuntimeError::Error("String.trim: argument must be a String".to_string()))?;
521    let result = s.trim().to_string();
522    let idx = arena.push_string(&result);
523    Ok(NanValue::new_string(idx))
524}
525
526fn split_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
527    if args.len() != 2 {
528        return Err(RuntimeError::Error(format!(
529            "String.split() takes 2 arguments, got {}",
530            args.len()
531        )));
532    }
533    if !args[0].is_string() || !args[1].is_string() {
534        return Err(RuntimeError::Error(
535            "String.split: both arguments must be String".to_string(),
536        ));
537    }
538    let s = arena.get_string(args[0].arena_index()).to_string();
539    let delim = arena.get_string(args[1].arena_index()).to_string();
540    let parts: Vec<NanValue> = s
541        .split(&*delim)
542        .map(|p| {
543            let idx = arena.push_string(p);
544            NanValue::new_string(idx)
545        })
546        .collect();
547    let list_idx = arena.push_list(parts);
548    Ok(NanValue::new_list(list_idx))
549}
550
551fn replace_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
552    if args.len() != 3 {
553        return Err(RuntimeError::Error(format!(
554            "String.replace() takes 3 arguments (s, old, new), got {}",
555            args.len()
556        )));
557    }
558    if !args[0].is_string() || !args[1].is_string() || !args[2].is_string() {
559        return Err(RuntimeError::Error(
560            "String.replace: all arguments must be String".to_string(),
561        ));
562    }
563    let s = arena.get_string(args[0].arena_index()).to_string();
564    let old = arena.get_string(args[1].arena_index()).to_string();
565    let new = arena.get_string(args[2].arena_index()).to_string();
566    let result = s.replace(&*old, &new);
567    let idx = arena.push_string(&result);
568    Ok(NanValue::new_string(idx))
569}
570
571fn join_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
572    if args.len() != 2 {
573        return Err(RuntimeError::Error(format!(
574            "String.join() takes 2 arguments, got {}",
575            args.len()
576        )));
577    }
578    if !args[0].is_list() {
579        return Err(RuntimeError::Error(
580            "String.join: first argument must be a List".to_string(),
581        ));
582    }
583    if !args[1].is_string() {
584        return Err(RuntimeError::Error(
585            "String.join: second argument must be a String".to_string(),
586        ));
587    }
588    let items = arena.list_to_vec(args[0].arena_index());
589    let sep = arena.get_string(args[1].arena_index()).to_string();
590    let mut strs: Vec<String> = Vec::with_capacity(items.len());
591    for item in &items {
592        if !item.is_string() {
593            return Err(RuntimeError::Error(
594                "String.join: list elements must be String".to_string(),
595            ));
596        }
597        strs.push(arena.get_string(item.arena_index()).to_string());
598    }
599    let result = strs.join(&sep);
600    let idx = arena.push_string(&result);
601    Ok(NanValue::new_string(idx))
602}
603
604fn char_at_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
605    if args.len() != 2 {
606        return Err(RuntimeError::Error(format!(
607            "String.charAt() takes 2 arguments, got {}",
608            args.len()
609        )));
610    }
611    if !args[0].is_string() {
612        return Err(RuntimeError::Error(
613            "String.charAt: first argument must be a String".to_string(),
614        ));
615    }
616    if !args[1].is_int() {
617        return Err(RuntimeError::Error(
618            "String.charAt: second argument must be an Int".to_string(),
619        ));
620    }
621    let s = arena.get_string(args[0].arena_index());
622    let idx_val = args[1].as_int(arena) as usize;
623    match s.chars().nth(idx_val) {
624        Some(c) => {
625            let cs = c.to_string();
626            let s_idx = arena.push_string(&cs);
627            let inner = NanValue::new_string(s_idx);
628            let box_idx = arena.push_boxed(inner);
629            Ok(NanValue::new_some(box_idx))
630        }
631        None => Ok(NanValue::NONE),
632    }
633}
634
635fn chars_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
636    if args.len() != 1 {
637        return Err(RuntimeError::Error(format!(
638            "String.chars() takes 1 argument, got {}",
639            args.len()
640        )));
641    }
642    if !args[0].is_string() {
643        return Err(RuntimeError::Error(
644            "String.chars: argument must be a String".to_string(),
645        ));
646    }
647    let s = arena.get_string(args[0].arena_index()).to_string();
648    let items: Vec<NanValue> = s
649        .chars()
650        .map(|c| {
651            let cs = c.to_string();
652            let idx = arena.push_string(&cs);
653            NanValue::new_string(idx)
654        })
655        .collect();
656    let list_idx = arena.push_list(items);
657    Ok(NanValue::new_list(list_idx))
658}
659
660fn from_int_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
661    if args.len() != 1 {
662        return Err(RuntimeError::Error(format!(
663            "String.fromInt() takes 1 argument, got {}",
664            args.len()
665        )));
666    }
667    if !args[0].is_int() {
668        return Err(RuntimeError::Error(
669            "String.fromInt: argument must be an Int".to_string(),
670        ));
671    }
672    let s = format!("{}", args[0].as_int(arena));
673    let idx = arena.push_string(&s);
674    Ok(NanValue::new_string(idx))
675}
676
677fn from_float_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
678    if args.len() != 1 {
679        return Err(RuntimeError::Error(format!(
680            "String.fromFloat() takes 1 argument, got {}",
681            args.len()
682        )));
683    }
684    if !args[0].is_float() {
685        return Err(RuntimeError::Error(
686            "String.fromFloat: argument must be a Float".to_string(),
687        ));
688    }
689    let s = format!("{}", args[0].as_float());
690    let idx = arena.push_string(&s);
691    Ok(NanValue::new_string(idx))
692}
693
694fn from_bool_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
695    if args.len() != 1 {
696        return Err(RuntimeError::Error(format!(
697            "String.fromBool() takes 1 argument, got {}",
698            args.len()
699        )));
700    }
701    if !args[0].is_bool() {
702        return Err(RuntimeError::Error(
703            "String.fromBool: argument must be a Bool".to_string(),
704        ));
705    }
706    let s = if args[0].as_bool() { "true" } else { "false" };
707    let idx = arena.push_string(s);
708    Ok(NanValue::new_string(idx))
709}
710
711fn to_lower_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
712    if args.len() != 1 {
713        return Err(RuntimeError::Error(format!(
714            "String.toLower() takes 1 argument, got {}",
715            args.len()
716        )));
717    }
718    if !args[0].is_string() {
719        return Err(RuntimeError::Error(
720            "String.toLower: argument must be a String".to_string(),
721        ));
722    }
723    let s = arena.get_string(args[0].arena_index()).to_lowercase();
724    let idx = arena.push_string(&s);
725    Ok(NanValue::new_string(idx))
726}
727
728fn to_upper_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
729    if args.len() != 1 {
730        return Err(RuntimeError::Error(format!(
731            "String.toUpper() takes 1 argument, got {}",
732            args.len()
733        )));
734    }
735    if !args[0].is_string() {
736        return Err(RuntimeError::Error(
737            "String.toUpper: argument must be a String".to_string(),
738        ));
739    }
740    let s = arena.get_string(args[0].arena_index()).to_uppercase();
741    let idx = arena.push_string(&s);
742    Ok(NanValue::new_string(idx))
743}