use std::str::FromStr as _;
use bigdecimal::BigDecimal;
use crate::{
error::{EvalError, EvalResult, InterpreterError},
value::Value,
};
pub fn has_function(name: &str) -> bool {
matches!(name, "Decimal")
}
pub fn call(func: &str, args: &[Value]) -> EvalResult {
match func {
"Decimal" => construct_decimal(args.first()),
_ => Err(InterpreterError::AttributeError(format!(
"module 'decimal' has no attribute '{func}'"
))
.into()),
}
}
pub(crate) fn construct_decimal(arg: Option<&Value>) -> EvalResult {
let Some(arg) = arg else {
return Err(InterpreterError::TypeError(
"Decimal() requires a value (int, str, or Decimal)".into(),
)
.into());
};
let big = match arg {
Value::Int(i) => BigDecimal::from(*i),
Value::Bool(b) => BigDecimal::from(i64::from(*b)),
Value::String(s) => BigDecimal::from_str(s.trim()).map_err(|e| {
EvalError::from(InterpreterError::ValueError(format!(
"invalid Decimal literal: {s:?} ({e})"
)))
})?,
Value::Decimal(d) => (**d).clone(),
Value::Float(_) => {
return Err(InterpreterError::TypeError(
"Decimal() does not accept float — use a string instead (see \
CONFORMANCE.md#decimal-float-rejection)"
.into(),
)
.into());
}
other => {
return Err(InterpreterError::TypeError(format!(
"Decimal() expects int / str / Decimal, got '{}'",
other.type_name()
))
.into());
}
};
Ok(Value::Decimal(Box::new(big)))
}
pub struct DecimalModule;
#[async_trait::async_trait]
impl crate::eval::modules::Module for DecimalModule {
fn name(&self) -> &'static str {
"decimal"
}
fn has_function(&self, name: &str) -> bool {
has_function(name)
}
async fn call(
&self,
_state: &mut crate::state::InterpreterState,
func: &str,
args: &[Value],
_kwargs: &indexmap::IndexMap<String, Value>,
_tools: &crate::tools::Tools,
) -> EvalResult {
call(func, args)
}
}