use std::collections::HashMap;
use std::str::FromStr;
use std::sync::Arc as Rc;
use aver_rt::AverInt;
use crate::nan_value::{Arena, NanIntExt, NanValue};
use crate::value::{RuntimeError, Value};
pub fn register(global: &mut HashMap<String, Value>) {
let mut members = HashMap::new();
for method in &["fromString", "fromFloat", "abs", "min", "max", "mod", "div"] {
members.insert(
method.to_string(),
Value::Builtin(format!("Int.{}", method)),
);
}
global.insert(
"Int".to_string(),
Value::Namespace {
name: "Int".to_string(),
members,
},
);
}
pub fn effects(_name: &str) -> &'static [&'static str] {
&[]
}
pub fn call(name: &str, args: &[Value]) -> Option<Result<Value, RuntimeError>> {
match name {
"Int.fromString" => Some(from_string(args)),
"Int.fromFloat" => Some(from_float(args)),
"Int.abs" => Some(abs(args)),
"Int.min" => Some(min(args)),
"Int.max" => Some(max(args)),
"Int.mod" => Some(modulo(args)),
"Int.div" => Some(divide(args)),
_ => None,
}
}
fn from_string(args: &[Value]) -> Result<Value, RuntimeError> {
let [val] = one_arg("Int.fromString", args)?;
let Value::Str(s) = val else {
return Err(RuntimeError::Error(
"Int.fromString: argument must be a String".to_string(),
));
};
match AverInt::from_str(s) {
Ok(n) => Ok(Value::Ok(Box::new(Value::Int(n)))),
Err(_) => Ok(Value::Err(Box::new(Value::Str(format!(
"Cannot parse '{}' as Int",
s
))))),
}
}
fn from_float(args: &[Value]) -> Result<Value, RuntimeError> {
let [val] = one_arg("Int.fromFloat", args)?;
let Value::Float(f) = val else {
return Err(RuntimeError::Error(
"Int.fromFloat: argument must be a Float".to_string(),
));
};
Ok(Value::Int(float_to_aver_int(*f)))
}
fn abs(args: &[Value]) -> Result<Value, RuntimeError> {
let [val] = one_arg("Int.abs", args)?;
let Value::Int(n) = val else {
return Err(RuntimeError::Error(
"Int.abs: argument must be an Int".to_string(),
));
};
Ok(Value::Int(n.abs()))
}
fn min(args: &[Value]) -> Result<Value, RuntimeError> {
let [a, b] = two_args("Int.min", args)?;
let (Value::Int(x), Value::Int(y)) = (a, b) else {
return Err(RuntimeError::Error(
"Int.min: both arguments must be Int".to_string(),
));
};
Ok(Value::Int(x.min_ref(y)))
}
fn max(args: &[Value]) -> Result<Value, RuntimeError> {
let [a, b] = two_args("Int.max", args)?;
let (Value::Int(x), Value::Int(y)) = (a, b) else {
return Err(RuntimeError::Error(
"Int.max: both arguments must be Int".to_string(),
));
};
Ok(Value::Int(x.max_ref(y)))
}
fn modulo(args: &[Value]) -> Result<Value, RuntimeError> {
let [a, b] = two_args("Int.mod", args)?;
let (Value::Int(x), Value::Int(y)) = (a, b) else {
return Err(RuntimeError::Error(
"Int.mod: both arguments must be Int".to_string(),
));
};
match x.rem_euclid(y) {
Some(r) => Ok(Value::Ok(Box::new(Value::Int(r)))),
None => Ok(Value::Err(Box::new(Value::Str(
"division by zero".to_string(),
)))),
}
}
fn divide(args: &[Value]) -> Result<Value, RuntimeError> {
let [a, b] = two_args("Int.div", args)?;
let (Value::Int(x), Value::Int(y)) = (a, b) else {
return Err(RuntimeError::Error(
"Int.div: both arguments must be Int".to_string(),
));
};
match x.div_euclid(y) {
Some(q) => Ok(Value::Ok(Box::new(Value::Int(q)))),
None => Ok(Value::Err(Box::new(Value::Str(
"division by zero".to_string(),
)))),
}
}
pub(crate) fn float_to_aver_int(f: f64) -> AverInt {
use num_bigint::BigInt;
use num_traits::FromPrimitive;
use num_traits::cast::ToPrimitive;
if !f.is_finite() {
return AverInt::zero();
}
let truncated = f.trunc();
if let Some(n) = truncated.to_i64() {
AverInt::from_i64(n)
} else {
match BigInt::from_f64(truncated) {
Some(b) => AverInt::from_str(&b.to_string()).unwrap_or_else(|_| AverInt::zero()),
None => AverInt::zero(),
}
}
}
fn one_arg<'a>(name: &str, args: &'a [Value]) -> Result<[&'a Value; 1], RuntimeError> {
if args.len() != 1 {
return Err(RuntimeError::Error(format!(
"{}() takes 1 argument, got {}",
name,
args.len()
)));
}
Ok([&args[0]])
}
fn two_args<'a>(name: &str, args: &'a [Value]) -> Result<[&'a Value; 2], RuntimeError> {
if args.len() != 2 {
return Err(RuntimeError::Error(format!(
"{}() takes 2 arguments, got {}",
name,
args.len()
)));
}
Ok([&args[0], &args[1]])
}
pub fn register_nv(global: &mut HashMap<String, NanValue>, arena: &mut Arena) {
let methods = &["fromString", "fromFloat", "abs", "min", "max", "mod", "div"];
let mut members: Vec<(Rc<str>, NanValue)> = Vec::with_capacity(methods.len());
for method in methods {
let idx = arena.push_builtin(&format!("Int.{}", method));
members.push((Rc::from(*method), NanValue::new_builtin(idx)));
}
let ns_idx = arena.push(crate::nan_value::ArenaEntry::Namespace {
name: Rc::from("Int"),
members,
});
global.insert("Int".to_string(), NanValue::new_namespace(ns_idx));
}
pub fn call_nv(
name: &str,
args: &[NanValue],
arena: &mut Arena,
) -> Option<Result<NanValue, RuntimeError>> {
match name {
"Int.fromString" => Some(from_string_nv(args, arena)),
"Int.fromFloat" => Some(from_float_nv(args, arena)),
"Int.abs" => Some(abs_nv(args, arena)),
"Int.min" => Some(min_nv(args, arena)),
"Int.max" => Some(max_nv(args, arena)),
"Int.mod" => Some(modulo_nv(args, arena)),
"Int.div" => Some(divide_nv(args, arena)),
_ => None,
}
}
fn nv_check1(name: &str, args: &[NanValue]) -> Result<NanValue, RuntimeError> {
if args.len() != 1 {
return Err(RuntimeError::Error(format!(
"{}() takes 1 argument, got {}",
name,
args.len()
)));
}
Ok(args[0])
}
fn nv_check2(name: &str, args: &[NanValue]) -> Result<(NanValue, NanValue), RuntimeError> {
if args.len() != 2 {
return Err(RuntimeError::Error(format!(
"{}() takes 2 arguments, got {}",
name,
args.len()
)));
}
Ok((args[0], args[1]))
}
fn from_string_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
let v = nv_check1("Int.fromString", args)?;
if !v.is_string() {
return Err(RuntimeError::Error(
"Int.fromString: argument must be a String".to_string(),
));
}
let parsed = AverInt::from_str(&arena.get_string_value(v));
match parsed {
Ok(n) => {
let inner = NanValue::from_aver_int(n, arena);
Ok(NanValue::new_ok_value(inner, arena))
}
Err(_) => {
let msg = format!("Cannot parse '{}' as Int", arena.get_string_value(v));
let inner = NanValue::new_string_value(&msg, arena);
Ok(NanValue::new_err_value(inner, arena))
}
}
}
fn from_float_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
let v = nv_check1("Int.fromFloat", args)?;
if !v.is_float() {
return Err(RuntimeError::Error(
"Int.fromFloat: argument must be a Float".to_string(),
));
}
Ok(NanValue::from_aver_int(
float_to_aver_int(v.as_float()),
arena,
))
}
fn abs_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
let v = nv_check1("Int.abs", args)?;
if !v.is_int() {
return Err(RuntimeError::Error(
"Int.abs: argument must be an Int".to_string(),
));
}
let r = v.as_aver_int(arena).abs();
Ok(NanValue::from_aver_int(r, arena))
}
fn min_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
let (a, b) = nv_check2("Int.min", args)?;
if !a.is_int() || !b.is_int() {
return Err(RuntimeError::Error(
"Int.min: both arguments must be Int".to_string(),
));
}
let r = a.as_aver_int(arena).min_ref(&b.as_aver_int(arena));
Ok(NanValue::from_aver_int(r, arena))
}
fn max_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
let (a, b) = nv_check2("Int.max", args)?;
if !a.is_int() || !b.is_int() {
return Err(RuntimeError::Error(
"Int.max: both arguments must be Int".to_string(),
));
}
let r = a.as_aver_int(arena).max_ref(&b.as_aver_int(arena));
Ok(NanValue::from_aver_int(r, arena))
}
fn modulo_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
let (a, b) = nv_check2("Int.mod", args)?;
if !a.is_int() || !b.is_int() {
return Err(RuntimeError::Error(
"Int.mod: both arguments must be Int".to_string(),
));
}
let x = a.as_aver_int(arena);
let y = b.as_aver_int(arena);
match x.rem_euclid(&y) {
Some(r) => {
let inner = NanValue::from_aver_int(r, arena);
Ok(NanValue::new_ok_value(inner, arena))
}
None => {
let inner = NanValue::new_string_value("division by zero", arena);
Ok(NanValue::new_err_value(inner, arena))
}
}
}
fn divide_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
let (a, b) = nv_check2("Int.div", args)?;
if !a.is_int() || !b.is_int() {
return Err(RuntimeError::Error(
"Int.div: both arguments must be Int".to_string(),
));
}
let x = a.as_aver_int(arena);
let y = b.as_aver_int(arena);
match x.div_euclid(&y) {
Some(q) => {
let inner = NanValue::from_aver_int(q, arena);
Ok(NanValue::new_ok_value(inner, arena))
}
None => {
let inner = NanValue::new_string_value("division by zero", arena);
Ok(NanValue::new_err_value(inner, arena))
}
}
}