helix/dna/ops/
math.rs

1//! Calculator DSL Module
2//!
3//! This module provides a complete calculator that can parse and evaluate
4//! a custom DSL with variables, arithmetic operations, and reference-with-modifier syntax.
5
6use crate::dna::hel::error::HlxError;
7use crate::ops::eval::{run_program as eval_run_program, Env};
8// use crate::ops::parse_program as parse_calculator_program;
9// use crate::dna::atp::ops::{Rule, CalcParser};
10use crate::dna::atp::value::Value;
11use anyhow;
12use std::collections::HashMap;
13use async_trait::async_trait;
14
15// AST definitions for the calculator DSL
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub enum Expr {
18    /// Integer literal (e.g., 42)
19    Number(i64),
20    /// Variable reference (e.g., a, b, c)
21    Var(String),
22    /// Multiplication (e.g., a x b)
23    Mul(Box<Expr>, Box<Expr>),
24    /// Addition (e.g., a + b)
25    Add(Box<Expr>, Box<Expr>),
26    /// Subtraction (e.g., a - b)
27    Sub(Box<Expr>, Box<Expr>),
28    /// Reference with optional modifier (e.g., @c or @c #4)
29    Ref {
30        /// Variable name to reference
31        var: String,
32        /// Optional modifier value (number after #)
33        modifier: Option<i64>,
34    },
35}
36
37/// A single assignment statement (e.g., `a = 2`)
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct Assign {
40    /// Variable name being assigned
41    pub name: String,
42    /// Expression being assigned
43    pub value: Expr,
44}
45
46// Note: Env type is imported from the eval module
47
48/// Math operators implementation for the calculator DSL
49pub struct MathOperators {
50    calculator: Calculator,
51}
52
53impl MathOperators {
54    pub async fn new() -> Result<Self, HlxError> {
55        Ok(Self {
56            calculator: Calculator::new(),
57        })
58    }
59
60    pub async fn execute(&self, operator: &str, params: &str) -> Result<Value, HlxError> {
61        self.execute_impl(operator, params).await
62    }
63
64    async fn execute_impl(&self, operator: &str, params: &str) -> Result<Value, HlxError> {
65        match operator {
66            "calc" => {
67                let parsed_params = crate::ops::utils::parse_params(params)?;
68                let source = parsed_params.get("source")
69                    .ok_or_else(|| HlxError::invalid_input("Missing 'source' parameter", "Check the source parameter"))?
70                    .to_string();
71
72                let result = self.calculator.evaluate(&source)
73                    .map_err(|e| HlxError::execution_error(format!("Calculator error: {}", e), "Check calculator syntax"))?;
74
75                // Convert the environment to a Value
76                let mut result_obj = HashMap::new();
77                for (key, value) in result.env {
78                    result_obj.insert(key, Value::Number(value as f64));
79                }
80                Ok(Value::Object(result_obj))
81            }
82            "eval" => {
83                let parsed_params = crate::ops::utils::parse_params(params)?;
84                let expression = parsed_params.get("expression")
85                    .ok_or_else(|| HlxError::invalid_input("Missing 'expression' parameter", "Check the expression parameter"))?
86                    .to_string();
87
88                // Simple expression evaluation
89                let result = self.calculator.evaluate(&format!("reproducibility {{ result = {} }}", expression))
90                    .map_err(|e| HlxError::execution_error(format!("Evaluation error: {}", e), "Check expression syntax"))?;
91
92                if let Some(value) = result.env.get("result") {
93                    Ok(Value::Number(*value as f64))
94                } else {
95                    Ok(Value::Number(0.0))
96                }
97            }
98            _ => Err(HlxError::invalid_input(format!("Unknown math operator: {}", operator), "Check the operator name"))
99        }
100    }
101}
102
103#[async_trait]
104impl crate::ops::OperatorTrait for MathOperators {
105    async fn execute(&self, operator: &str, params: &str) -> Result<Value, HlxError> {
106        self.execute_impl(operator, params).await
107    }
108}
109
110/// Calculator engine that parses and evaluates DSL programs
111pub struct Calculator;
112
113/// Result of evaluating a calculator program
114pub struct CalcResult {
115    /// Final variable environment
116    pub env: Env,
117}
118
119impl Calculator {
120    /// Create a new calculator instance
121    pub fn new() -> Self {
122        Self
123    }
124
125    /// Parse and evaluate a calculator DSL program
126    ///
127    /// # Example
128    /// ```
129    /// use helix::operators::math::Calculator;
130    ///
131    /// let calc = Calculator::new();
132    /// let src = r#"
133    /// reproducibility {
134    ///     a = 2
135    ///     b = 2
136    ///     c = a x b
137    ///     d = @c #4
138    /// }
139    /// "#;
140    ///
141    /// let result = calc.evaluate(src).unwrap();
142    /// assert_eq!(result.env["a"], 2);
143    /// assert_eq!(result.env["b"], 2);
144    /// assert_eq!(result.env["c"], 4); // 2 * 2
145    /// assert_eq!(result.env["d"], 0); // 4 % 4
146    /// ```
147    pub fn evaluate(&self, source: &str) -> anyhow::Result<CalcResult> {
148        // Parse the program
149        let assignments = parse_program(source)?;
150
151        // Evaluate the assignments
152        let env = eval_run_program(&assignments)?;
153
154        Ok(CalcResult { env })
155    }
156
157    /// Parse a program and return the AST without evaluating
158    pub fn parse_only(&self, source: &str) -> anyhow::Result<Vec<Assign>> {
159        parse_program(source)
160    }
161}
162
163/// Parse a calculator DSL program into a list of assignments
164pub fn parse_program(source: &str) -> anyhow::Result<Vec<Assign>> {
165    // Simple manual parser for the calculator DSL
166    let mut assignments = Vec::new();
167
168    for line in source.lines() {
169        let line = line.trim();
170        if line.is_empty() || line.starts_with("//") {
171            continue;
172        }
173
174        if let Some((name, expr_str)) = line.split_once('=') {
175            let name = name.trim().to_string();
176            let expr_str = expr_str.trim();
177
178            let expr = parse_expression(expr_str)?;
179            assignments.push(Assign {
180                name,
181                value: expr,
182            });
183        }
184    }
185
186    Ok(assignments)
187}
188
189/// Parse a mathematical expression
190fn parse_expression(expr_str: &str) -> anyhow::Result<Expr> {
191    parse_add_sub(expr_str)
192}
193
194/// Parse addition and subtraction (lowest precedence)
195fn parse_add_sub(expr_str: &str) -> anyhow::Result<Expr> {
196    let mut result = parse_mul_div(expr_str)?;
197
198    let expr_chars: Vec<char> = expr_str.chars().collect();
199    let mut i = 0;
200
201    while i < expr_chars.len() {
202        match expr_chars[i] {
203            '+' => {
204                i += 1;
205                let right = parse_mul_div(&expr_str[i..])?;
206                result = Expr::Add(Box::new(result), Box::new(right));
207            }
208            '-' => {
209                i += 1;
210                let right = parse_mul_div(&expr_str[i..])?;
211                result = Expr::Sub(Box::new(result), Box::new(right));
212            }
213            _ => break,
214        }
215    }
216
217    Ok(result)
218}
219
220/// Parse multiplication and division (higher precedence)
221fn parse_mul_div(expr_str: &str) -> anyhow::Result<Expr> {
222    let mut result = parse_factor(expr_str)?;
223
224    let expr_chars: Vec<char> = expr_str.chars().collect();
225    let mut i = 0;
226
227    while i < expr_chars.len() {
228        match expr_chars[i] {
229            'x' | '*' => {
230                i += 1;
231                let right = parse_factor(&expr_str[i..])?;
232                result = Expr::Mul(Box::new(result), Box::new(right));
233            }
234            _ => break,
235        }
236    }
237
238    Ok(result)
239}
240
241/// Parse factors (highest precedence)
242fn parse_factor(expr_str: &str) -> anyhow::Result<Expr> {
243    let expr_str = expr_str.trim();
244
245    // Handle parentheses
246    if expr_str.starts_with('(') && expr_str.ends_with(')') {
247        let inner = &expr_str[1..expr_str.len()-1];
248        return parse_add_sub(inner);
249    }
250
251    // Handle references (@variable #modifier)
252    if expr_str.starts_with('@') {
253        if let Some(hash_pos) = expr_str.find('#') {
254            let var = expr_str[1..hash_pos].to_string();
255            let modifier_str = &expr_str[hash_pos+1..];
256            let modifier = modifier_str.parse::<i64>()?;
257            return Ok(Expr::Ref {
258                var,
259                modifier: Some(modifier),
260            });
261        } else {
262            let var = expr_str[1..].to_string();
263            return Ok(Expr::Ref {
264                var,
265                modifier: None,
266            });
267        }
268    }
269
270    // Handle numbers
271    if let Ok(num) = expr_str.parse::<i64>() {
272        return Ok(Expr::Number(num));
273    }
274
275    // Handle variables
276    Ok(Expr::Var(expr_str.to_string()))
277}
278
279
280/// Evaluate an expression in the given environment
281pub fn eval_expr(expr: &Expr, env: &Env) -> i64 {
282    match expr {
283        Expr::Number(n) => *n,
284        Expr::Var(name) => env.get(name).copied().unwrap_or(0),
285        Expr::Add(left, right) => eval_expr(left, env) + eval_expr(right, env),
286        Expr::Sub(left, right) => eval_expr(left, env) - eval_expr(right, env),
287        Expr::Mul(left, right) => eval_expr(left, env) * eval_expr(right, env),
288        Expr::Ref { var, modifier } => {
289            let value = env.get(var).copied().unwrap_or(0);
290            match modifier {
291                Some(mod_val) => value % mod_val,
292                None => value,
293            }
294        }
295    }
296}
297
298/// Run a program (list of assignments) and return the final environment
299pub fn run_program(assignments: &[Assign]) -> anyhow::Result<Env> {
300    let mut env = Env::new();
301    
302    for assignment in assignments {
303        let value = eval_expr(&assignment.value, &env);
304        env.insert(assignment.name.clone(), value);
305    }
306    
307    Ok(env)
308}
309
310// Note: eval_expr and run_program are imported from the eval module
311
312// CalcParser is now defined in atp/ops.rs
313
314#[cfg(test)]
315mod tests {
316    use super::*;
317
318    #[test]
319    fn test_basic_arithmetic() {
320        let calc = Calculator::new();
321        let src = r#"
322            reproducibility {
323                a = 2
324                b = 3
325                c = a x b
326            }
327        "#;
328        
329        let result = calc.evaluate(src).unwrap();
330        assert_eq!(result.env["a"], 2);
331        assert_eq!(result.env["b"], 3);
332        assert_eq!(result.env["c"], 6);
333    }
334
335    #[test]
336    fn test_reference_with_modifier() {
337        let calc = Calculator::new();
338        let src = r#"
339            reproducibility {
340                a = 10
341                b = 3
342                c = a x b
343                d = @c #4
344            }
345        "#;
346        
347        let result = calc.evaluate(src).unwrap();
348        assert_eq!(result.env["a"], 10);
349        assert_eq!(result.env["b"], 3);
350        assert_eq!(result.env["c"], 30);
351        assert_eq!(result.env["d"], 2); // 30 % 4 = 2
352    }
353
354    #[test]
355    fn test_complex_expression() {
356        let calc = Calculator::new();
357        let src = r#"
358            reproducibility {
359                x = 5
360                y = 3
361                z = (x + y) x (x - y)
362            }
363        "#;
364        
365        let result = calc.evaluate(src).unwrap();
366        assert_eq!(result.env["x"], 5);
367        assert_eq!(result.env["y"], 3);
368        assert_eq!(result.env["z"], 16); // (5+3) * (5-3) = 8 * 2 = 16
369    }
370}