280 lines
8.5 KiB
Rust
280 lines
8.5 KiB
Rust
// src/lib.rs
|
|
//! # Steel Decimal
|
|
//!
|
|
//! A Rust library that provides decimal arithmetic support for the Steel programming language.
|
|
//! This crate transforms Steel scripts to use high-precision decimal operations and provides
|
|
//! function registration utilities for Steel VMs.
|
|
//!
|
|
//! ## Quick Start
|
|
//!
|
|
//! ### Basic Usage
|
|
//! ```rust
|
|
//! use steel_decimal::SteelDecimal;
|
|
//! use steel::steel_vm::engine::Engine;
|
|
//!
|
|
//! // Create a new Steel Decimal engine
|
|
//! let steel_decimal = SteelDecimal::new();
|
|
//!
|
|
//! // Transform a script
|
|
//! let script = "(+ 1.5 2.3)";
|
|
//! let transformed = steel_decimal.transform(script);
|
|
//! // Result: "(decimal-add \"1.5\" \"2.3\")"
|
|
//!
|
|
//! // Register functions with Steel VM
|
|
//! let mut vm = Engine::new();
|
|
//! steel_decimal.register_functions(&mut vm);
|
|
//!
|
|
//! // Now you can execute the transformed script
|
|
//! let result = vm.compile_and_run_raw_program(transformed);
|
|
//! ```
|
|
//!
|
|
//! ### With Variables
|
|
//! ```rust
|
|
//! use steel_decimal::SteelDecimal;
|
|
//! use steel::steel_vm::engine::Engine;
|
|
//! use std::collections::HashMap;
|
|
//!
|
|
//! let mut variables = HashMap::new();
|
|
//! variables.insert("x".to_string(), "10.5".to_string());
|
|
//! variables.insert("y".to_string(), "20.3".to_string());
|
|
//!
|
|
//! let steel_decimal = SteelDecimal::with_variables(variables);
|
|
//!
|
|
//! let script = "(+ $x $y)";
|
|
//! let transformed = steel_decimal.transform(script);
|
|
//!
|
|
//! let mut vm = Engine::new();
|
|
//! steel_decimal.register_functions(&mut vm);
|
|
//! ```
|
|
//!
|
|
//! ### Selective Function Registration
|
|
//! ```rust
|
|
//! use steel_decimal::FunctionRegistryBuilder;
|
|
//! use steel::steel_vm::engine::Engine;
|
|
//!
|
|
//! let mut vm = Engine::new();
|
|
//! FunctionRegistryBuilder::new()
|
|
//! .basic_arithmetic(true)
|
|
//! .advanced_math(false)
|
|
//! .trigonometric(false)
|
|
//! .register(&mut vm);
|
|
//! ```
|
|
|
|
pub mod functions;
|
|
pub mod parser;
|
|
pub mod registry;
|
|
pub mod utils;
|
|
|
|
pub use functions::*;
|
|
pub use parser::ScriptParser;
|
|
pub use registry::{FunctionRegistry, FunctionRegistryBuilder};
|
|
pub use utils::{TypeConverter, ScriptAnalyzer, DecimalPrecision, ConversionError};
|
|
|
|
use std::collections::HashMap;
|
|
use steel::steel_vm::engine::Engine;
|
|
|
|
/// Main entry point for the Steel Decimal library
|
|
pub struct SteelDecimal {
|
|
parser: ScriptParser,
|
|
variables: HashMap<String, String>,
|
|
}
|
|
|
|
impl SteelDecimal {
|
|
/// Create a new SteelDecimal instance
|
|
pub fn new() -> Self {
|
|
Self {
|
|
parser: ScriptParser::new(),
|
|
variables: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
/// Create a new SteelDecimal instance with variables
|
|
pub fn with_variables(variables: HashMap<String, String>) -> Self {
|
|
Self {
|
|
parser: ScriptParser::new(),
|
|
variables,
|
|
}
|
|
}
|
|
|
|
/// Transform a script by converting math operations to decimal functions
|
|
pub fn transform(&self, script: &str) -> String {
|
|
self.parser.transform(script)
|
|
}
|
|
|
|
/// Register all decimal functions with a Steel VM
|
|
pub fn register_functions(&self, vm: &mut Engine) {
|
|
FunctionRegistry::register_all(vm);
|
|
|
|
if !self.variables.is_empty() {
|
|
FunctionRegistry::register_variables(vm, self.variables.clone());
|
|
}
|
|
}
|
|
|
|
/// Register functions selectively using a builder
|
|
pub fn register_functions_with_builder(&self, vm: &mut Engine, builder: FunctionRegistryBuilder) {
|
|
let builder = if !self.variables.is_empty() {
|
|
builder.with_variables(self.variables.clone())
|
|
} else {
|
|
builder
|
|
};
|
|
|
|
builder.register(vm);
|
|
}
|
|
|
|
/// Add a variable to the context
|
|
pub fn add_variable(&mut self, name: String, value: String) {
|
|
self.variables.insert(name, value);
|
|
}
|
|
|
|
/// Get all variables
|
|
pub fn get_variables(&self) -> &HashMap<String, String> {
|
|
&self.variables
|
|
}
|
|
|
|
/// Extract dependencies from a script
|
|
pub fn extract_dependencies(&self, script: &str) -> std::collections::HashSet<String> {
|
|
self.parser.extract_dependencies(script)
|
|
}
|
|
|
|
/// Parse and execute a script in one step (convenience method)
|
|
pub fn parse_and_execute(&self, script: &str) -> Result<Vec<steel::rvals::SteelVal>, String> {
|
|
let mut vm = Engine::new();
|
|
self.register_functions(&mut vm);
|
|
|
|
let transformed = self.transform(script);
|
|
|
|
vm.compile_and_run_raw_program(transformed)
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
|
|
/// Validate that a script can be transformed without errors
|
|
pub fn validate_script(&self, script: &str) -> Result<(), String> {
|
|
// Basic validation - check if transformation succeeds
|
|
let transformed = self.transform(script);
|
|
|
|
// Check if the script contains balanced parentheses
|
|
let open_count = transformed.chars().filter(|c| *c == '(').count();
|
|
let close_count = transformed.chars().filter(|c| *c == ')').count();
|
|
|
|
if open_count != close_count {
|
|
return Err("Unbalanced parentheses in script".to_string());
|
|
}
|
|
|
|
// Check if all variables are defined
|
|
let dependencies = self.extract_dependencies(script);
|
|
for dep in dependencies {
|
|
if !self.variables.contains_key(&dep) {
|
|
return Err(format!("Undefined variable: {}", dep));
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Default for SteelDecimal {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
/// Convenience functions for quick operations
|
|
pub mod prelude {
|
|
pub use crate::{SteelDecimal, FunctionRegistry, FunctionRegistryBuilder};
|
|
pub use crate::functions::*;
|
|
pub use crate::utils::{TypeConverter, ScriptAnalyzer, DecimalPrecision};
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_basic_transformation() {
|
|
let steel_decimal = SteelDecimal::new();
|
|
let script = "(+ 1.5 2.3)";
|
|
let transformed = steel_decimal.transform(script);
|
|
assert_eq!(transformed, "(decimal-add \"1.5\" \"2.3\")");
|
|
}
|
|
|
|
#[test]
|
|
fn test_with_variables() {
|
|
let mut variables = HashMap::new();
|
|
variables.insert("x".to_string(), "10.5".to_string());
|
|
variables.insert("y".to_string(), "20.3".to_string());
|
|
|
|
let steel_decimal = SteelDecimal::with_variables(variables);
|
|
let script = "(+ $x $y)";
|
|
let transformed = steel_decimal.transform(script);
|
|
assert_eq!(transformed, "(decimal-add (get-var \"x\") (get-var \"y\"))");
|
|
}
|
|
|
|
#[test]
|
|
fn test_function_registration() {
|
|
let steel_decimal = SteelDecimal::new();
|
|
let mut vm = Engine::new();
|
|
|
|
steel_decimal.register_functions(&mut vm);
|
|
|
|
let script = "(decimal-add \"1.5\" \"2.3\")";
|
|
let result = vm.compile_and_run_raw_program(script.to_string());
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_and_execute() {
|
|
let steel_decimal = SteelDecimal::new();
|
|
let script = "(+ 1.5 2.3)";
|
|
|
|
let result = steel_decimal.parse_and_execute(script);
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_script_validation() {
|
|
let steel_decimal = SteelDecimal::new();
|
|
|
|
// Valid script
|
|
assert!(steel_decimal.validate_script("(+ 1.5 2.3)").is_ok());
|
|
|
|
// Invalid script - unbalanced parentheses
|
|
assert!(steel_decimal.validate_script("(+ 1.5 2.3").is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_variable_validation() {
|
|
let steel_decimal = SteelDecimal::new();
|
|
|
|
// Script with undefined variable
|
|
assert!(steel_decimal.validate_script("(+ $x 2.3)").is_err());
|
|
|
|
// Script with defined variable
|
|
let mut variables = HashMap::new();
|
|
variables.insert("x".to_string(), "10.5".to_string());
|
|
let steel_decimal = SteelDecimal::with_variables(variables);
|
|
assert!(steel_decimal.validate_script("(+ $x 2.3)").is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_complex_expressions() {
|
|
let steel_decimal = SteelDecimal::new();
|
|
|
|
let script = "(+ (* 2.5 3.0) (/ 15.0 3.0))";
|
|
let transformed = steel_decimal.transform(script);
|
|
let expected = "(decimal-add (decimal-mul \"2.5\" \"3.0\") (decimal-div \"15.0\" \"3.0\"))";
|
|
assert_eq!(transformed, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn test_dependency_extraction() {
|
|
let steel_decimal = SteelDecimal::new();
|
|
let script = "(+ $x $y $z)";
|
|
|
|
let dependencies = steel_decimal.extract_dependencies(script);
|
|
assert_eq!(dependencies.len(), 3);
|
|
assert!(dependencies.contains("x"));
|
|
assert!(dependencies.contains("y"));
|
|
assert!(dependencies.contains("z"));
|
|
}
|
|
}
|