steel decimal crate implemented

This commit is contained in:
filipriec
2025-07-02 16:31:15 +02:00
parent d1ebe4732f
commit 93c67ffa14
7 changed files with 485 additions and 5 deletions

207
steel_decimal/src/lib.rs Normal file
View File

@@ -0,0 +1,207 @@
// src/lib.rs
use steel::steel_vm::engine::Engine;
use steel::steel_vm::register_fn::RegisterFn;
use steel::rvals::SteelVal;
use std::collections::HashMap;
use thiserror::Error;
mod decimal_math;
mod syntax_parser;
pub use decimal_math::*;
use syntax_parser::*;
#[derive(Debug, Error)]
pub enum SteelDecimalError {
#[error("Script parsing failed: {0}")]
ParseError(String),
#[error("Script execution failed: {0}")]
RuntimeError(String),
#[error("Type conversion error: {0}")]
TypeConversionError(String),
}
#[derive(Clone, Debug)]
pub struct SteelContext {
pub variables: HashMap<String, String>,
pub current_table: String,
}
impl SteelContext {
pub fn new(current_table: String) -> Self {
Self {
variables: HashMap::new(),
current_table,
}
}
pub fn with_variables(current_table: String, variables: HashMap<String, String>) -> Self {
Self {
variables,
current_table,
}
}
pub fn add_variable(&mut self, key: String, value: String) {
self.variables.insert(key, value);
}
}
#[derive(Debug, Clone)]
pub struct SteelResult {
pub result: String,
pub warnings: Vec<String>,
}
pub struct SteelDecimalEngine {
parser: SyntaxParser,
}
impl SteelDecimalEngine {
pub fn new() -> Self {
Self {
parser: SyntaxParser::new(),
}
}
pub fn parse_script(&self, script: &str, context: &SteelContext) -> Result<String, SteelDecimalError> {
Ok(self.parser.parse(script, &context.current_table))
}
pub fn execute_script(&self, script: &str, context: &SteelContext) -> Result<SteelResult, SteelDecimalError> {
let transformed_script = self.parser.parse(script, &context.current_table);
let mut vm = Engine::new();
self.register_decimal_functions(&mut vm);
self.register_context_functions(&mut vm, context);
let results = vm.compile_and_run_raw_program(transformed_script)
.map_err(|e| SteelDecimalError::RuntimeError(e.to_string()))?;
let result_string = self.convert_result_to_string(results)?;
Ok(SteelResult {
result: result_string,
warnings: Vec::new(),
})
}
fn register_decimal_functions(&self, vm: &mut Engine) {
vm.register_fn("decimal-add", decimal_add);
vm.register_fn("decimal-sub", decimal_sub);
vm.register_fn("decimal-mul", decimal_mul);
vm.register_fn("decimal-div", decimal_div);
vm.register_fn("decimal-pow", decimal_pow);
vm.register_fn("decimal-sqrt", decimal_sqrt);
vm.register_fn("decimal-ln", decimal_ln);
vm.register_fn("decimal-log10", decimal_log10);
vm.register_fn("decimal-exp", decimal_exp);
vm.register_fn("decimal-sin", decimal_sin);
vm.register_fn("decimal-cos", decimal_cos);
vm.register_fn("decimal-tan", decimal_tan);
vm.register_fn("decimal-gt", decimal_gt);
vm.register_fn("decimal-gte", decimal_gte);
vm.register_fn("decimal-lt", decimal_lt);
vm.register_fn("decimal-lte", decimal_lte);
vm.register_fn("decimal-eq", decimal_eq);
vm.register_fn("decimal-abs", decimal_abs);
vm.register_fn("decimal-round", decimal_round);
vm.register_fn("decimal-min", decimal_min);
vm.register_fn("decimal-max", decimal_max);
vm.register_fn("decimal-zero", || "0".to_string());
vm.register_fn("decimal-one", || "1".to_string());
vm.register_fn("decimal-pi", || "3.1415926535897932384626433833".to_string());
vm.register_fn("decimal-e", || "2.7182818284590452353602874714".to_string());
vm.register_fn("decimal-percentage", decimal_percentage);
vm.register_fn("decimal-compound", decimal_compound);
}
fn register_context_functions(&self, vm: &mut Engine, context: &SteelContext) {
let variables = context.variables.clone();
vm.register_fn("get-var", move |var_name: String| -> Result<String, String> {
variables.get(&var_name)
.cloned()
.ok_or_else(|| format!("Variable '{}' not found", var_name))
});
let variables_check = context.variables.clone();
vm.register_fn("has-var?", move |var_name: String| -> bool {
variables_check.contains_key(&var_name)
});
}
fn convert_result_to_string(&self, results: Vec<SteelVal>) -> Result<String, SteelDecimalError> {
if results.is_empty() {
return Ok(String::new());
}
let last_result = &results[results.len() - 1];
match last_result {
SteelVal::StringV(s) => Ok(s.to_string()),
SteelVal::NumV(n) => Ok(n.to_string()),
SteelVal::IntV(i) => Ok(i.to_string()),
SteelVal::BoolV(b) => Ok(b.to_string()),
SteelVal::VectorV(v) => {
let string_values: Result<Vec<String>, _> = v.iter()
.map(|val| match val {
SteelVal::StringV(s) => Ok(s.to_string()),
SteelVal::NumV(n) => Ok(n.to_string()),
SteelVal::IntV(i) => Ok(i.to_string()),
SteelVal::BoolV(b) => Ok(b.to_string()),
_ => Err(SteelDecimalError::TypeConversionError(
format!("Cannot convert vector element to string: {:?}", val)
))
})
.collect();
match string_values {
Ok(strings) => Ok(strings.join(",")),
Err(e) => Err(e),
}
}
_ => Err(SteelDecimalError::TypeConversionError(
format!("Cannot convert result to string: {:?}", last_result)
))
}
}
}
impl Default for SteelDecimalEngine {
fn default() -> Self {
Self::new()
}
}
fn decimal_percentage(amount: String, percentage: String) -> Result<String, String> {
use rust_decimal::prelude::*;
use std::str::FromStr;
let amount_dec = Decimal::from_str(&amount)
.map_err(|e| format!("Invalid amount: {}", e))?;
let percentage_dec = Decimal::from_str(&percentage)
.map_err(|e| format!("Invalid percentage: {}", e))?;
let hundred = Decimal::from(100);
Ok((amount_dec * percentage_dec / hundred).to_string())
}
fn decimal_compound(principal: String, rate: String, time: String) -> Result<String, String> {
use rust_decimal::prelude::*;
use rust_decimal::MathematicalOps;
use std::str::FromStr;
let principal_dec = Decimal::from_str(&principal)
.map_err(|e| format!("Invalid principal: {}", e))?;
let rate_dec = Decimal::from_str(&rate)
.map_err(|e| format!("Invalid rate: {}", e))?;
let time_dec = Decimal::from_str(&time)
.map_err(|e| format!("Invalid time: {}", e))?;
let one = Decimal::ONE;
let compound_factor = (one + rate_dec).checked_powd(time_dec)
.ok_or("Compound calculation overflow")?;
Ok((principal_dec * compound_factor).to_string())
}