use std::collections::HashMap;
use crate::eval::functions::Registry;
use crate::eval::{evaluate_expr, Context, EvalCtx, Resolver};
use crate::parser::{parse_formula, Expr};
use crate::types::{ErrorKind, ParseError, Value};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
pub enum EngineFlavor {
Sheets,
Excel,
}
pub struct Engine {
flavor: EngineFlavor,
registry: Registry,
}
impl Engine {
pub fn sheets() -> Self {
Self { flavor: EngineFlavor::Sheets, registry: Registry::new() }
}
pub fn excel() -> Self {
Self { flavor: EngineFlavor::Excel, registry: Registry::new() }
}
#[deprecated(note = "use Engine::sheets() — engine flavor is required; see ADR 2026-04-27")]
pub fn google_sheets() -> Self {
Self::sheets()
}
pub fn flavor(&self) -> EngineFlavor {
self.flavor
}
pub fn parse(&self, formula: &str) -> Result<Expr, ParseError> {
parse_formula(formula)
}
pub fn validate(&self, formula: &str) -> Result<(), ParseError> {
self.parse(formula).map(|_| ())
}
pub fn evaluate(&self, formula: &str, variables: &HashMap<String, Value>) -> Value {
self.evaluate_inner(formula, variables, None)
}
pub fn evaluate_at(
&self,
formula: &str,
variables: &HashMap<String, Value>,
now_serial: f64,
) -> Value {
if !now_serial.is_finite() {
return Value::Error(ErrorKind::Num);
}
self.evaluate_inner(formula, variables, Some(now_serial))
}
pub fn evaluate_with_resolver(&self, formula: &str, resolver: &mut impl Resolver) -> Value {
self.evaluate_with_resolver_at(formula, resolver, None)
}
pub fn evaluate_with_resolver_at(
&self,
formula: &str,
resolver: &mut impl Resolver,
now_serial: Option<f64>,
) -> Value {
if let Some(n) = now_serial {
if !n.is_finite() {
return Value::Error(ErrorKind::Num);
}
}
if self.flavor == EngineFlavor::Excel {
return Value::Error(ErrorKind::NA);
}
match parse_formula(formula) {
Err(_) => Value::Error(ErrorKind::Value),
Ok(expr) => {
let mut ctx = Context::empty();
ctx.now_serial = now_serial;
let mut eval_ctx = EvalCtx::with_resolver(ctx, &self.registry, resolver);
evaluate_expr(&expr, &mut eval_ctx)
}
}
}
fn evaluate_inner(
&self,
formula: &str,
variables: &HashMap<String, Value>,
now_serial: Option<f64>,
) -> Value {
if self.flavor == EngineFlavor::Excel {
return Value::Error(ErrorKind::NA);
}
match parse_formula(formula) {
Err(_) => Value::Error(ErrorKind::Value),
Ok(expr) => {
let mut ctx = Context::new(variables.clone());
ctx.now_serial = now_serial;
let mut eval_ctx = EvalCtx::new(ctx, &self.registry);
evaluate_expr(&expr, &mut eval_ctx)
}
}
}
}
#[cfg(test)]
mod tests;