use std::collections::HashMap;
#[derive(Debug, Clone)]
pub enum EvalResult {
Empty,
Defined {
name: String,
kind: String,
message: String,
},
Expression {
input: String,
value: String,
},
Help(String),
Quit,
Message(String),
TypeInfo(String),
RustCode(String),
WasmInfo {
size_bytes: usize,
functions: usize,
has_memory: bool,
},
SpiritLoaded {
name: String,
declarations: usize,
},
}
#[derive(Debug)]
pub struct ReplEvaluator {
wasm_cache: HashMap<u64, Vec<u8>>,
optimize: bool,
debug_info: bool,
}
impl Default for ReplEvaluator {
fn default() -> Self {
Self::new()
}
}
impl ReplEvaluator {
pub fn new() -> Self {
Self {
wasm_cache: HashMap::new(),
optimize: false,
debug_info: true,
}
}
pub fn optimized() -> Self {
Self {
wasm_cache: HashMap::new(),
optimize: true,
debug_info: false,
}
}
pub fn with_optimization(mut self, enable: bool) -> Self {
self.optimize = enable;
self
}
pub fn with_debug_info(mut self, enable: bool) -> Self {
self.debug_info = enable;
self
}
#[cfg(feature = "wasm-compile")]
pub fn compile_to_wasm(&mut self, source: &str) -> Result<Vec<u8>, EvalError> {
use crate::wasm::WasmCompiler;
let file = crate::parse_dol_file(source).map_err(|e| EvalError::Parse(e.to_string()))?;
let mut compiler = WasmCompiler::new()
.with_optimization(self.optimize)
.with_debug_info(self.debug_info);
compiler
.compile_file(&file)
.map_err(|e| EvalError::Compile(e.message))
}
#[cfg(not(feature = "wasm-compile"))]
pub fn compile_to_wasm(&mut self, _source: &str) -> Result<Vec<u8>, EvalError> {
Err(EvalError::Feature(
"wasm-compile feature not enabled".to_string(),
))
}
#[cfg(feature = "wasm")]
pub fn execute_wasm(
&self,
wasm_bytes: &[u8],
function: &str,
args: &[WasmValue],
) -> Result<Vec<WasmValue>, EvalError> {
use wasmtime::{Engine, Instance, Module, Store, Val};
let engine = Engine::default();
let module = Module::new(&engine, wasm_bytes)
.map_err(|e| EvalError::Runtime(format!("Failed to load WASM: {}", e)))?;
let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &module, &[])
.map_err(|e| EvalError::Runtime(format!("Failed to instantiate: {}", e)))?;
let func = instance
.get_func(&mut store, function)
.ok_or_else(|| EvalError::Runtime(format!("Function '{}' not found", function)))?;
let wasm_args: Vec<Val> = args.iter().map(|a| a.to_wasmtime_val()).collect();
let func_ty = func.ty(&store);
let mut results: Vec<Val> = func_ty.results().map(|_| Val::I64(0)).collect();
func.call(&mut store, &wasm_args, &mut results)
.map_err(|e| EvalError::Runtime(format!("Execution error: {}", e)))?;
Ok(results.iter().map(WasmValue::from_wasmtime_val).collect())
}
#[cfg(not(feature = "wasm"))]
pub fn execute_wasm(
&self,
_wasm_bytes: &[u8],
_function: &str,
_args: &[WasmValue],
) -> Result<Vec<WasmValue>, EvalError> {
Err(EvalError::Feature("wasm feature not enabled".to_string()))
}
pub fn infer_expression_type(&self, expr: &str) -> &'static str {
let expr = expr.trim();
if expr == "true" || expr == "false" {
return "bool";
}
if expr.contains('.') && expr.chars().any(|c| c.is_ascii_digit()) {
let is_float = expr
.chars()
.filter(|c| *c != '.' && *c != '-')
.all(|c| c.is_ascii_digit() || c == 'e' || c == 'E' || c == '+' || c == '_');
if is_float {
return "f64";
}
}
let is_int = expr
.chars()
.filter(|c| *c != '-' && *c != '_')
.all(|c| c.is_ascii_digit());
if is_int && !expr.is_empty() {
return "i64";
}
if expr.contains("f64") || expr.contains("Float64") {
return "f64";
}
for token in expr.split(|c: char| !c.is_ascii_alphanumeric() && c != '.' && c != '_') {
if token.contains('.') {
let is_float = token
.chars()
.filter(|c| *c != '.')
.all(|c| c.is_ascii_digit() || c == 'e' || c == 'E' || c == '_');
if is_float && !token.is_empty() {
return "f64";
}
}
}
"i64"
}
pub fn eval_expression(&mut self, expr: &str, declarations: &str) -> Result<String, EvalError> {
let return_type = self.infer_expression_type(expr);
let source = format!(
r#"{}
pub fun dolReplEval() -> {} {{
{}
}}
"#,
declarations, return_type, expr
);
let wasm = self.compile_to_wasm(&source)?;
let results = self.execute_wasm(&wasm, "dolReplEval", &[])?;
if results.is_empty() {
Ok("()".to_string())
} else {
Ok(results
.iter()
.map(|v| v.to_string())
.collect::<Vec<_>>()
.join(", "))
}
}
pub fn eval_expression_typed(
&mut self,
expr: &str,
declarations: &str,
return_type: &str,
) -> Result<String, EvalError> {
let source = format!(
r#"{}
pub fun dolReplEval() -> {} {{
{}
}}
"#,
declarations, return_type, expr
);
let wasm = self.compile_to_wasm(&source)?;
let results = self.execute_wasm(&wasm, "dolReplEval", &[])?;
if results.is_empty() {
Ok("()".to_string())
} else {
Ok(results
.iter()
.map(|v| v.to_string())
.collect::<Vec<_>>()
.join(", "))
}
}
pub fn clear_cache(&mut self) {
self.wasm_cache.clear();
}
}
#[derive(Debug, Clone)]
pub enum EvalError {
Parse(String),
Compile(String),
Runtime(String),
Feature(String),
}
impl std::fmt::Display for EvalError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EvalError::Parse(msg) => write!(f, "Parse error: {}", msg),
EvalError::Compile(msg) => write!(f, "Compile error: {}", msg),
EvalError::Runtime(msg) => write!(f, "Runtime error: {}", msg),
EvalError::Feature(msg) => write!(f, "Feature error: {}", msg),
}
}
}
impl std::error::Error for EvalError {}
#[derive(Debug, Clone)]
pub enum WasmValue {
I32(i32),
I64(i64),
F32(f32),
F64(f64),
}
impl WasmValue {
#[cfg(feature = "wasm")]
fn to_wasmtime_val(&self) -> wasmtime::Val {
match self {
WasmValue::I32(v) => wasmtime::Val::I32(*v),
WasmValue::I64(v) => wasmtime::Val::I64(*v),
WasmValue::F32(v) => wasmtime::Val::F32(v.to_bits()),
WasmValue::F64(v) => wasmtime::Val::F64(v.to_bits()),
}
}
#[cfg(feature = "wasm")]
fn from_wasmtime_val(val: &wasmtime::Val) -> Self {
match val {
wasmtime::Val::I32(v) => WasmValue::I32(*v),
wasmtime::Val::I64(v) => WasmValue::I64(*v),
wasmtime::Val::F32(bits) => WasmValue::F32(f32::from_bits(*bits)),
wasmtime::Val::F64(bits) => WasmValue::F64(f64::from_bits(*bits)),
_ => WasmValue::I64(0), }
}
}
impl std::fmt::Display for WasmValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
WasmValue::I32(v) => write!(f, "{}", v),
WasmValue::I64(v) => write!(f, "{}", v),
WasmValue::F32(v) => write!(f, "{}", v),
WasmValue::F64(v) => write!(f, "{}", v),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_evaluator_new() {
let eval = ReplEvaluator::new();
assert!(!eval.optimize);
assert!(eval.debug_info);
}
#[test]
fn test_evaluator_optimized() {
let eval = ReplEvaluator::optimized();
assert!(eval.optimize);
assert!(!eval.debug_info);
}
#[test]
fn test_wasm_value_display() {
assert_eq!(format!("{}", WasmValue::I32(42)), "42");
assert_eq!(format!("{}", WasmValue::I64(100)), "100");
assert_eq!(format!("{}", WasmValue::F32(1.5)), "1.5");
assert_eq!(format!("{}", WasmValue::F64(2.5)), "2.5");
}
#[test]
fn test_infer_type_integer_literals() {
let eval = ReplEvaluator::new();
assert_eq!(eval.infer_expression_type("42"), "i64");
assert_eq!(eval.infer_expression_type("0"), "i64");
assert_eq!(eval.infer_expression_type("-1"), "i64");
assert_eq!(eval.infer_expression_type("1_000_000"), "i64");
}
#[test]
fn test_infer_type_float_literals() {
let eval = ReplEvaluator::new();
assert_eq!(eval.infer_expression_type("3.14"), "f64");
assert_eq!(eval.infer_expression_type("0.0"), "f64");
assert_eq!(eval.infer_expression_type("-1.5"), "f64");
assert_eq!(eval.infer_expression_type("1.0e10"), "f64");
assert_eq!(eval.infer_expression_type("2.5E-3"), "f64");
}
#[test]
fn test_infer_type_boolean_literals() {
let eval = ReplEvaluator::new();
assert_eq!(eval.infer_expression_type("true"), "bool");
assert_eq!(eval.infer_expression_type("false"), "bool");
}
#[test]
fn test_infer_type_with_whitespace() {
let eval = ReplEvaluator::new();
assert_eq!(eval.infer_expression_type(" 42 "), "i64");
assert_eq!(eval.infer_expression_type("\t3.14\n"), "f64");
assert_eq!(eval.infer_expression_type(" true "), "bool");
}
#[test]
fn test_infer_type_expressions_with_floats() {
let eval = ReplEvaluator::new();
assert_eq!(eval.infer_expression_type("1.0 + 2.0"), "f64");
assert_eq!(eval.infer_expression_type("x + 3.14"), "f64");
assert_eq!(eval.infer_expression_type("a * 0.5"), "f64");
}
#[test]
fn test_infer_type_expressions_with_type_hints() {
let eval = ReplEvaluator::new();
assert_eq!(eval.infer_expression_type("x as f64"), "f64");
assert_eq!(eval.infer_expression_type("Float64::from(x)"), "f64");
}
#[test]
fn test_infer_type_complex_expressions_default_to_i64() {
let eval = ReplEvaluator::new();
assert_eq!(eval.infer_expression_type("a + b"), "i64");
assert_eq!(eval.infer_expression_type("foo(x, y)"), "i64");
assert_eq!(eval.infer_expression_type("if x { 1 } else { 2 }"), "i64");
}
#[test]
fn test_builder_methods() {
let eval = ReplEvaluator::new()
.with_optimization(true)
.with_debug_info(false);
assert!(eval.optimize);
assert!(!eval.debug_info);
let eval2 = ReplEvaluator::new()
.with_optimization(false)
.with_debug_info(true);
assert!(!eval2.optimize);
assert!(eval2.debug_info);
}
#[test]
fn test_clear_cache() {
let mut eval = ReplEvaluator::new();
eval.clear_cache();
assert!(eval.wasm_cache.is_empty());
}
#[test]
fn test_eval_error_display() {
assert_eq!(
format!("{}", EvalError::Parse("unexpected token".to_string())),
"Parse error: unexpected token"
);
assert_eq!(
format!("{}", EvalError::Compile("type mismatch".to_string())),
"Compile error: type mismatch"
);
assert_eq!(
format!("{}", EvalError::Runtime("division by zero".to_string())),
"Runtime error: division by zero"
);
assert_eq!(
format!("{}", EvalError::Feature("wasm not enabled".to_string())),
"Feature error: wasm not enabled"
);
}
#[test]
fn test_eval_result_variants() {
let empty = EvalResult::Empty;
assert!(matches!(empty, EvalResult::Empty));
let defined = EvalResult::Defined {
name: "foo".to_string(),
kind: "gene".to_string(),
message: "defined".to_string(),
};
assert!(matches!(defined, EvalResult::Defined { .. }));
let expr = EvalResult::Expression {
input: "1 + 2".to_string(),
value: "3".to_string(),
};
assert!(matches!(expr, EvalResult::Expression { .. }));
let help = EvalResult::Help("help text".to_string());
assert!(matches!(help, EvalResult::Help(_)));
let quit = EvalResult::Quit;
assert!(matches!(quit, EvalResult::Quit));
let msg = EvalResult::Message("hello".to_string());
assert!(matches!(msg, EvalResult::Message(_)));
let type_info = EvalResult::TypeInfo("i64".to_string());
assert!(matches!(type_info, EvalResult::TypeInfo(_)));
let rust_code = EvalResult::RustCode("fn main() {}".to_string());
assert!(matches!(rust_code, EvalResult::RustCode(_)));
let wasm_info = EvalResult::WasmInfo {
size_bytes: 100,
functions: 5,
has_memory: true,
};
assert!(matches!(wasm_info, EvalResult::WasmInfo { .. }));
}
}