Skip to main content

aver/types/
char.rs

1/// Char namespace — Unicode scalar value operations on strings.
2///
3/// Char is NOT a type — these are functions operating on String (first character)
4/// and Int (code point). Same pattern as Byte: namespace of operations on existing types.
5///
6/// Methods:
7///   Char.toCode(s: String)    → Int             — Unicode scalar value of first char
8///   Char.fromCode(n: Int)     → Option<String>  — code point to 1-char string
9///
10/// No effects required.
11use std::collections::HashMap;
12
13use crate::value::{RuntimeError, Value};
14
15pub fn register(global: &mut HashMap<String, Value>) {
16    let mut members = HashMap::new();
17    for method in &["toCode", "fromCode"] {
18        members.insert(
19            method.to_string(),
20            Value::Builtin(format!("Char.{}", method)),
21        );
22    }
23    global.insert(
24        "Char".to_string(),
25        Value::Namespace {
26            name: "Char".to_string(),
27            members,
28        },
29    );
30}
31
32pub fn effects(_name: &str) -> &'static [&'static str] {
33    &[]
34}
35
36pub fn call(name: &str, args: &[Value]) -> Option<Result<Value, RuntimeError>> {
37    match name {
38        "Char.toCode" => Some(to_code(args)),
39        "Char.fromCode" => Some(from_code(args)),
40        _ => None,
41    }
42}
43
44// ─── Implementations ────────────────────────────────────────────────────────
45
46fn to_code(args: &[Value]) -> Result<Value, RuntimeError> {
47    if args.len() != 1 {
48        return Err(RuntimeError::Error(format!(
49            "Char.toCode() takes 1 argument, got {}",
50            args.len()
51        )));
52    }
53    let Value::Str(s) = &args[0] else {
54        return Err(RuntimeError::Error(
55            "Char.toCode: argument must be a String".to_string(),
56        ));
57    };
58    match s.chars().next() {
59        Some(c) => Ok(Value::Int(c as i64)),
60        None => Err(RuntimeError::Error(
61            "Char.toCode: string is empty".to_string(),
62        )),
63    }
64}
65
66fn from_code(args: &[Value]) -> Result<Value, RuntimeError> {
67    if args.len() != 1 {
68        return Err(RuntimeError::Error(format!(
69            "Char.fromCode() takes 1 argument, got {}",
70            args.len()
71        )));
72    }
73    let Value::Int(n) = &args[0] else {
74        return Err(RuntimeError::Error(
75            "Char.fromCode: argument must be an Int".to_string(),
76        ));
77    };
78    let n = *n;
79    if n < 0 {
80        return Ok(Value::None);
81    }
82    let Ok(code) = u32::try_from(n) else {
83        return Ok(Value::None);
84    };
85    match char::from_u32(code) {
86        Some(c) => Ok(Value::Some(Box::new(Value::Str(c.to_string())))),
87        None => Ok(Value::None),
88    }
89}