Skip to main content

aver/types/
int.rs

1/// Int namespace — numeric helpers for integer values.
2///
3/// Methods:
4///   Int.fromString(s)   → Result<Int, String>  — parse string to int
5///   Int.fromFloat(f)    → Int                  — truncate float to int
6///   Int.toString(n)     → String               — format int as string
7///   Int.abs(n)          → Int                  — absolute value
8///   Int.min(a, b)       → Int                  — minimum of two ints
9///   Int.max(a, b)       → Int                  — maximum of two ints
10///   Int.mod(a, b)       → Result<Int, String>  — modulo (error on b=0)
11///   Int.toFloat(n)      → Float                — widen int to float
12///
13/// No effects required.
14use std::collections::HashMap;
15
16use crate::value::{RuntimeError, Value};
17
18pub fn register(global: &mut HashMap<String, Value>) {
19    let mut members = HashMap::new();
20    for method in &[
21        "fromString",
22        "fromFloat",
23        "toString",
24        "abs",
25        "min",
26        "max",
27        "mod",
28        "toFloat",
29    ] {
30        members.insert(
31            method.to_string(),
32            Value::Builtin(format!("Int.{}", method)),
33        );
34    }
35    global.insert(
36        "Int".to_string(),
37        Value::Namespace {
38            name: "Int".to_string(),
39            members,
40        },
41    );
42}
43
44pub fn effects(_name: &str) -> &'static [&'static str] {
45    &[]
46}
47
48/// Returns `Some(result)` when `name` is owned by this namespace, `None` otherwise.
49pub fn call(name: &str, args: &[Value]) -> Option<Result<Value, RuntimeError>> {
50    match name {
51        "Int.fromString" => Some(from_string(&args)),
52        "Int.fromFloat" => Some(from_float(&args)),
53        "Int.toString" => Some(to_string(&args)),
54        "Int.abs" => Some(abs(&args)),
55        "Int.min" => Some(min(&args)),
56        "Int.max" => Some(max(&args)),
57        "Int.mod" => Some(modulo(&args)),
58        "Int.toFloat" => Some(to_float(&args)),
59        _ => None,
60    }
61}
62
63// ─── Implementations ────────────────────────────────────────────────────────
64
65fn from_string(args: &[Value]) -> Result<Value, RuntimeError> {
66    let [val] = one_arg("Int.fromString", args)?;
67    let Value::Str(s) = val else {
68        return Err(RuntimeError::Error(
69            "Int.fromString: argument must be a String".to_string(),
70        ));
71    };
72    match s.parse::<i64>() {
73        Ok(n) => Ok(Value::Ok(Box::new(Value::Int(n)))),
74        Err(_) => Ok(Value::Err(Box::new(Value::Str(format!(
75            "Cannot parse '{}' as Int",
76            s
77        ))))),
78    }
79}
80
81fn from_float(args: &[Value]) -> Result<Value, RuntimeError> {
82    let [val] = one_arg("Int.fromFloat", args)?;
83    let Value::Float(f) = val else {
84        return Err(RuntimeError::Error(
85            "Int.fromFloat: argument must be a Float".to_string(),
86        ));
87    };
88    Ok(Value::Int(*f as i64))
89}
90
91fn to_string(args: &[Value]) -> Result<Value, RuntimeError> {
92    let [val] = one_arg("Int.toString", args)?;
93    let Value::Int(n) = val else {
94        return Err(RuntimeError::Error(
95            "Int.toString: argument must be an Int".to_string(),
96        ));
97    };
98    Ok(Value::Str(format!("{}", n)))
99}
100
101fn abs(args: &[Value]) -> Result<Value, RuntimeError> {
102    let [val] = one_arg("Int.abs", args)?;
103    let Value::Int(n) = val else {
104        return Err(RuntimeError::Error(
105            "Int.abs: argument must be an Int".to_string(),
106        ));
107    };
108    Ok(Value::Int(n.abs()))
109}
110
111fn min(args: &[Value]) -> Result<Value, RuntimeError> {
112    let [a, b] = two_args("Int.min", args)?;
113    let (Value::Int(x), Value::Int(y)) = (a, b) else {
114        return Err(RuntimeError::Error(
115            "Int.min: both arguments must be Int".to_string(),
116        ));
117    };
118    Ok(Value::Int(std::cmp::min(*x, *y)))
119}
120
121fn max(args: &[Value]) -> Result<Value, RuntimeError> {
122    let [a, b] = two_args("Int.max", args)?;
123    let (Value::Int(x), Value::Int(y)) = (a, b) else {
124        return Err(RuntimeError::Error(
125            "Int.max: both arguments must be Int".to_string(),
126        ));
127    };
128    Ok(Value::Int(std::cmp::max(*x, *y)))
129}
130
131fn modulo(args: &[Value]) -> Result<Value, RuntimeError> {
132    let [a, b] = two_args("Int.mod", args)?;
133    let (Value::Int(x), Value::Int(y)) = (a, b) else {
134        return Err(RuntimeError::Error(
135            "Int.mod: both arguments must be Int".to_string(),
136        ));
137    };
138    if *y == 0 {
139        Ok(Value::Err(Box::new(Value::Str(
140            "division by zero".to_string(),
141        ))))
142    } else {
143        Ok(Value::Ok(Box::new(Value::Int(x % y))))
144    }
145}
146
147fn to_float(args: &[Value]) -> Result<Value, RuntimeError> {
148    let [val] = one_arg("Int.toFloat", args)?;
149    let Value::Int(n) = val else {
150        return Err(RuntimeError::Error(
151            "Int.toFloat: argument must be an Int".to_string(),
152        ));
153    };
154    Ok(Value::Float(*n as f64))
155}
156
157// ─── Helpers ────────────────────────────────────────────────────────────────
158
159fn one_arg<'a>(name: &str, args: &'a [Value]) -> Result<[&'a Value; 1], RuntimeError> {
160    if args.len() != 1 {
161        return Err(RuntimeError::Error(format!(
162            "{}() takes 1 argument, got {}",
163            name,
164            args.len()
165        )));
166    }
167    Ok([&args[0]])
168}
169
170fn two_args<'a>(name: &str, args: &'a [Value]) -> Result<[&'a Value; 2], RuntimeError> {
171    if args.len() != 2 {
172        return Err(RuntimeError::Error(format!(
173            "{}() takes 2 arguments, got {}",
174            name,
175            args.len()
176        )));
177    }
178    Ok([&args[0], &args[1]])
179}