Skip to main content

gridline_engine/engine/
eval.rs

1//! Rhai engine creation and formula evaluation.
2//!
3//! Creates the Rhai scripting engine with all spreadsheet built-in functions
4//! registered (SUM, AVERAGE, cell accessors, etc.). Also handles evaluation
5//! of formulas with optional user-defined custom functions from external files.
6
7use rhai::Engine;
8
9use super::{AST, Dynamic, Grid, ValueCache};
10
11/// Create a Rhai engine with built-ins registered.
12pub fn create_engine(grid: Grid) -> Engine {
13    let value_cache = ValueCache::default();
14    create_engine_with_cache(grid, value_cache)
15}
16
17/// Create a Rhai engine with built-ins registered and shared value cache.
18pub fn create_engine_with_cache(grid: Grid, value_cache: ValueCache) -> Engine {
19    let mut engine = Engine::new();
20    crate::builtins::register_builtins(&mut engine, grid, value_cache);
21    engine
22}
23
24/// Create a Rhai engine with built-ins registered.
25/// Optionally compiles custom functions from the provided script.
26/// Returns the engine, compiled AST (if any), and any error message.
27pub fn create_engine_with_functions(
28    grid: Grid,
29    custom_script: Option<&str>,
30) -> (Engine, Option<AST>, Option<String>) {
31    let value_cache = ValueCache::default();
32    create_engine_with_functions_and_cache(grid, value_cache, custom_script)
33}
34
35/// Create a Rhai engine with built-ins, custom functions, and shared value cache.
36/// Returns the engine, compiled AST (if any), and any error message.
37pub fn create_engine_with_functions_and_cache(
38    grid: Grid,
39    value_cache: ValueCache,
40    custom_script: Option<&str>,
41) -> (Engine, Option<AST>, Option<String>) {
42    let engine = create_engine_with_cache(grid, value_cache);
43
44    let (ast, error) = if let Some(script) = custom_script {
45        match engine.compile(script) {
46            Ok(ast) => (Some(ast), None),
47            Err(e) => (None, Some(format!("Error in custom functions: {}", e))),
48        }
49    } else {
50        (None, None)
51    };
52
53    (engine, ast, error)
54}
55
56/// Evaluate a formula, optionally with custom functions AST.
57///
58/// When custom functions are present, we need both the AST and the original script
59/// to properly evaluate closures. Closures need access to registered functions,
60/// which works better when evaluating as a script rather than merged ASTs.
61pub fn eval_with_functions(
62    engine: &Engine,
63    formula: &str,
64    custom_ast: Option<&AST>,
65) -> Result<Dynamic, String> {
66    if custom_ast.is_some() {
67        // For now, use simple AST merging
68        // TODO: This has issues with closures not accessing registered functions
69        let formula_ast = engine.compile(formula).map_err(|e| e.to_string())?;
70        let merged = custom_ast.unwrap().clone().merge(&formula_ast);
71        engine.eval_ast(&merged).map_err(|e| e.to_string())
72    } else {
73        engine.eval(formula).map_err(|e| e.to_string())
74    }
75}
76
77/// Evaluate a formula with custom functions provided as script text.
78/// This version concatenates the scripts and evaluates them together,
79/// which properly handles closures accessing registered functions.
80pub fn eval_with_functions_script(
81    engine: &Engine,
82    formula: &str,
83    custom_script: Option<&str>,
84) -> Result<Dynamic, String> {
85    if let Some(script) = custom_script {
86        // Concatenate custom functions with formula and evaluate as one script
87        // This ensures closures can access both custom and registered functions
88        let combined = format!("{}\n{}", script, formula);
89        engine.eval(&combined).map_err(|e| e.to_string())
90    } else {
91        engine.eval(formula).map_err(|e| e.to_string())
92    }
93}
94
95// Backward compatibility aliases (deprecated)
96#[doc(hidden)]
97#[allow(dead_code)]
98pub fn create_engine_with_spill(
99    grid: Grid,
100    value_cache: ValueCache,
101    _deprecated: ValueCache,
102) -> Engine {
103    create_engine_with_cache(grid, value_cache)
104}
105
106#[doc(hidden)]
107#[allow(dead_code)]
108pub fn create_engine_with_functions_and_spill(
109    grid: Grid,
110    value_cache: ValueCache,
111    custom_script: Option<&str>,
112) -> (Engine, Option<AST>, Option<String>) {
113    create_engine_with_functions_and_cache(grid, value_cache, custom_script)
114}