exprimo/
lib.rs

1use rslint_parser::{
2    ast::{BinExpr, BinOp, CondExpr, DotExpr, Expr, Name, NameRef, UnaryExpr, UnaryOp},
3    parse_text, AstNode, SyntaxKind, SyntaxNode,
4};
5
6use anyhow::Result;
7use thiserror::Error;
8
9use serde_json::Value;
10
11use std::collections::HashMap;
12
13#[derive(Error, Debug)]
14#[error("Evaluation error")]
15pub struct EvaluationError {
16    #[from]
17    source: NodeError,
18}
19
20#[derive(Error, Debug)]
21#[error("Node error {message}, node: {node:?}")]
22pub struct NodeError {
23    message: String,
24    node: Option<SyntaxNode>,
25}
26
27#[cfg(feature = "logging")]
28use scribe_rust::Logger;
29#[cfg(feature = "logging")]
30use std::sync::Arc;
31
32pub struct Evaluator {
33    context: HashMap<String, serde_json::Value>,
34    #[cfg(feature = "logging")]
35    logger: Arc<Logger>,
36}
37
38impl Evaluator {
39    pub fn new(
40        context: HashMap<String, serde_json::Value>,
41        #[cfg(feature = "logging")] logger: Arc<Logger>,
42    ) -> Self {
43        Evaluator {
44            context,
45            #[cfg(feature = "logging")]
46            logger,
47        }
48    }
49
50    pub fn evaluate(&self, expression: &str) -> Result<Value> {
51        let ast = parse_text(expression, 0).syntax();
52        let untyped_expr_node = match ast.first_child() {
53            Some(node) => node,
54            None => {
55                return Err(NodeError {
56                    message: "Empty expression".to_string(),
57                    node: None,
58                }
59                .into())
60            }
61        };
62
63        #[cfg(feature = "logging")]
64        self.logger.trace(&format!(
65            "Expression AST:\n\n{:#?}\n-----------------",
66            untyped_expr_node
67        ));
68
69        let result = self.evaluate_node(&untyped_expr_node)?;
70
71        #[cfg(feature = "logging")]
72        self.logger.trace(&format!("Result: {}", result));
73
74        Ok(result)
75    }
76
77    fn evaluate_node(&self, node: &SyntaxNode) -> Result<Value, NodeError> {
78        #[cfg(feature = "logging")]
79        self.logger.trace(&format!(
80            "Evaluating NodeKind: {:#?}, {:?}",
81            node.kind(),
82            node.to_string()
83        ));
84
85        let res = match node.kind() {
86            SyntaxKind::EXPR_STMT => {
87                let expr = node.first_child().ok_or_else(|| NodeError {
88                    message: "[Empty expression]".to_string(),
89                    node: None,
90                })?;
91                self.evaluate_node(&expr)
92            }
93            SyntaxKind::DOT_EXPR => self.evaluate_dot_expr(&DotExpr::cast(node.clone()).unwrap()),
94            SyntaxKind::NAME_REF => self.evaluate_name_ref(&NameRef::cast(node.clone()).unwrap()),
95            SyntaxKind::NAME => self.evaluate_name(&Name::cast(node.clone()).unwrap()),
96            SyntaxKind::BIN_EXPR => self.evaluate_bin_expr(&BinExpr::cast(node.clone()).unwrap()),
97            SyntaxKind::LITERAL => self.evaluate_literal(&Expr::cast(node.clone()).unwrap()),
98            SyntaxKind::COND_EXPR => {
99                self.evaluate_cond_expr(&CondExpr::cast(node.clone()).unwrap())
100            }
101            SyntaxKind::IDENT => self.evaluate_identifier(&Expr::cast(node.clone()).unwrap()),
102            SyntaxKind::UNARY_EXPR => {
103                self.evaluate_prefix_expr(&UnaryExpr::cast(node.clone()).unwrap())
104            }
105            _ => Err(NodeError {
106                message: format!("Unsupported syntax kind: {:?}", node.kind()),
107                node: Some(node.clone()),
108            }),
109        };
110
111        #[cfg(feature = "logging")]
112        self.logger.trace(&format!(
113            "NodeKind: {:?} => {:#?}",
114            node.kind(),
115            res.as_ref()
116        ));
117
118        res
119    }
120
121    fn evaluate_bin_expr(&self, bin_expr: &BinExpr) -> Result<Value, NodeError> {
122        #[cfg(feature = "logging")]
123        self.logger.trace(&format!(
124            "Evaluating Binary Expression: {:#?}",
125            bin_expr.to_string()
126        ));
127
128        let left = bin_expr.lhs().ok_or_else(|| NodeError {
129            message: "[Empty BinExpr Left Expression]".to_string(),
130            node: Some(bin_expr.syntax().clone()),
131        })?;
132        let right = bin_expr.rhs().ok_or_else(|| NodeError {
133            message: "[Empty BinExpr Right Expression]".to_string(),
134            node: Some(bin_expr.syntax().clone()),
135        })?;
136
137        let left_value = self.evaluate_node(left.syntax())?;
138        let right_value = self.evaluate_node(right.syntax())?;
139
140        let op = bin_expr.op_details();
141
142        #[cfg(feature = "logging")]
143        self.logger
144            .trace(&format!("BinaryOp left_value {:?}", left_value));
145
146        #[cfg(feature = "logging")]
147        self.logger
148            .trace(&format!("BinaryOp right_value {:?}", right_value));
149
150        #[cfg(feature = "logging")]
151        self.logger.trace(&format!("BinaryOp op_details {:?}", op));
152
153        let result = match op {
154            Some((_, BinOp::Plus)) => self.add_values(left_value, right_value),
155            Some((_, BinOp::Minus)) => self.subtract_values(left_value, right_value),
156            Some((_, BinOp::Times)) => self.multiply_values(left_value, right_value),
157            Some((_, BinOp::Divide)) => self.divide_values(left_value, right_value),
158            Some((_, BinOp::Remainder)) => self.modulo_values(left_value, right_value),
159            Some((_, BinOp::LogicalAnd)) => Ok(Value::Bool(
160                self.to_boolean(&left_value)? && self.to_boolean(&right_value)?,
161            )),
162            Some((_, BinOp::LogicalOr)) => Ok(Value::Bool(
163                self.to_boolean(&left_value)? || self.to_boolean(&right_value)?,
164            )),
165            Some((_, BinOp::Equality)) | Some((_, BinOp::StrictEquality)) => Ok(Value::Bool(
166                self.abstract_equality(&left_value, &right_value),
167            )),
168            Some((_, BinOp::Inequality)) | Some((_, BinOp::StrictInequality)) => Ok(Value::Bool(
169                !self.abstract_equality(&left_value, &right_value),
170            )),
171            Some((_, BinOp::GreaterThan)) => {
172                self.compare_values(&left_value, &right_value, |a, b| a > b)
173            }
174            Some((_, BinOp::LessThan)) => {
175                self.compare_values(&left_value, &right_value, |a, b| a < b)
176            }
177            Some((_, BinOp::GreaterThanOrEqual)) => {
178                self.compare_values(&left_value, &right_value, |a, b| a >= b)
179            }
180            Some((_, BinOp::LessThanOrEqual)) => {
181                self.compare_values(&left_value, &right_value, |a, b| a <= b)
182            }
183            _ => Err(NodeError {
184                message: "Unsupported binary operator".to_string(),
185                node: Some(bin_expr.syntax().clone()),
186            }),
187        }?;
188
189        #[cfg(feature = "logging")]
190        self.logger.trace(&format!("Binary Result: {:?}", result));
191
192        Ok(result)
193    }
194
195    fn add_values(&self, left: Value, right: Value) -> Result<Value, NodeError> {
196        match (left.clone(), right.clone()) {
197            (Value::Number(l), Value::Number(r)) => {
198                let sum = l.as_f64().unwrap() + r.as_f64().unwrap();
199                Ok(Value::Number(serde_json::Number::from_f64(sum).unwrap()))
200            }
201            (Value::String(l), Value::String(r)) => Ok(Value::String(l + &r)),
202            (Value::String(l), r) => Ok(Value::String(l + &self.value_to_string(&r))),
203            (l, Value::String(r)) => Ok(Value::String(self.value_to_string(&l) + &r)),
204            _ => {
205                // Type coercion similar to JavaScript
206                let l_str = self.value_to_string(&left);
207                let r_str = self.value_to_string(&right);
208                Ok(Value::String(l_str + &r_str))
209            }
210        }
211    }
212
213    fn subtract_values(&self, left: Value, right: Value) -> Result<Value, NodeError> {
214        let l_num = self.to_number(&left)?;
215        let r_num = self.to_number(&right)?;
216        Ok(Value::Number(
217            serde_json::Number::from_f64(l_num - r_num).unwrap(),
218        ))
219    }
220
221    fn multiply_values(&self, left: Value, right: Value) -> Result<Value, NodeError> {
222        let l_num = self.to_number(&left)?;
223        let r_num = self.to_number(&right)?;
224        Ok(Value::Number(
225            serde_json::Number::from_f64(l_num * r_num).unwrap(),
226        ))
227    }
228
229    fn divide_values(&self, left: Value, right: Value) -> Result<Value, NodeError> {
230        let l_num = self.to_number(&left)?;
231        let r_num = self.to_number(&right)?;
232        if r_num == 0.0 {
233            return Err(NodeError {
234                message: "Division by zero".to_string(),
235                node: None,
236            });
237        }
238        Ok(Value::Number(
239            serde_json::Number::from_f64(l_num / r_num).unwrap(),
240        ))
241    }
242
243    fn modulo_values(&self, left: Value, right: Value) -> Result<Value, NodeError> {
244        let l_num = self.to_number(&left)?;
245        let r_num = self.to_number(&right)?;
246        Ok(Value::Number(
247            serde_json::Number::from_f64(l_num % r_num).unwrap(),
248        ))
249    }
250
251    fn compare_values<F>(&self, left: &Value, right: &Value, cmp: F) -> Result<Value, NodeError>
252    where
253        F: Fn(f64, f64) -> bool,
254    {
255        let l_num = self.to_number(left)?;
256        let r_num = self.to_number(right)?;
257        Ok(Value::Bool(cmp(l_num, r_num)))
258    }
259
260    fn evaluate_prefix_expr(&self, prefix_expr: &UnaryExpr) -> Result<Value, NodeError> {
261        #[cfg(feature = "logging")]
262        self.logger.trace(&format!(
263            "Evaluating Prefix Expression: {:#?}",
264            prefix_expr.to_string()
265        ));
266        let expr = prefix_expr.expr().ok_or_else(|| NodeError {
267            message: "[Empty PrefixExpr Expression]".to_string(),
268            node: Some(prefix_expr.syntax().clone()),
269        })?;
270        let expr_value = self.evaluate_node(expr.syntax())?;
271
272        let op = prefix_expr.op_details();
273
274        let result = match op {
275            Some((_, UnaryOp::LogicalNot)) => Value::Bool(!self.to_boolean(&expr_value)?),
276            Some((_, UnaryOp::Minus)) => {
277                let num = self.to_number(&expr_value)?;
278                Value::Number(serde_json::Number::from_f64(-num).unwrap())
279            }
280            Some((_, UnaryOp::Plus)) => {
281                let num = self.to_number(&expr_value)?;
282                Value::Number(serde_json::Number::from_f64(num).unwrap())
283            }
284            _ => {
285                return Err(NodeError {
286                    message: "Unsupported unary operator".to_string(),
287                    node: Some(prefix_expr.syntax().clone()),
288                })
289            }
290        };
291
292        #[cfg(feature = "logging")]
293        self.logger.trace(&format!("Prefix Result: {:?}", result));
294
295        Ok(result)
296    }
297
298    fn evaluate_cond_expr(&self, cond_expr: &CondExpr) -> Result<Value, NodeError> {
299        #[cfg(feature = "logging")]
300        self.logger.trace(&format!(
301            "Evaluating Conditional Expression: {:#?}",
302            cond_expr.to_string()
303        ));
304        let cond = cond_expr.test().ok_or_else(|| NodeError {
305            message: "[Empty CondExpr Test Expression]".to_string(),
306            node: Some(cond_expr.syntax().clone()),
307        })?;
308        let true_expr = cond_expr.cons().ok_or_else(|| NodeError {
309            message: "[Empty CondExpr Consequent Expression]".to_string(),
310            node: Some(cond_expr.syntax().clone()),
311        })?;
312        let false_expr = cond_expr.alt().ok_or_else(|| NodeError {
313            message: "[Empty CondExpr Alternate Expression]".to_string(),
314            node: Some(cond_expr.syntax().clone()),
315        })?;
316
317        let cond_value = self.evaluate_node(cond.syntax())?;
318        let cond_bool = self.to_boolean(&cond_value)?;
319
320        let result = if cond_bool {
321            self.evaluate_node(true_expr.syntax())?
322        } else {
323            self.evaluate_node(false_expr.syntax())?
324        };
325
326        #[cfg(feature = "logging")]
327        self.logger
328            .trace(&format!("Conditional Result: {:?}", result));
329
330        Ok(result)
331    }
332
333    fn evaluate_dot_expr(&self, dot_expr: &DotExpr) -> Result<Value, NodeError> {
334        #[cfg(feature = "logging")]
335        self.logger
336            .trace(&format!("Evaluating Dot Expression: {:#?}", dot_expr));
337
338        // Start with the leftmost expression
339        let mut current_expr = dot_expr.clone();
340        let mut property_chain = Vec::new();
341
342        // Collect all identifiers in the dot expression
343        loop {
344            let prop = current_expr.prop();
345            let obj = current_expr.object();
346
347            if let Some(prop) = prop {
348                let prop_name = prop.syntax().text().to_string();
349                property_chain.push(prop_name);
350            } else {
351                return Err(NodeError {
352                    message: "Missing property in dot expression".to_string(),
353                    node: Some(current_expr.syntax().clone()),
354                });
355            }
356
357            if let Some(obj_expr) = obj {
358                let obj_syntax = obj_expr.syntax().clone();
359                if let Some(prev_dot_expr) = DotExpr::cast(obj_syntax.clone()) {
360                    current_expr = prev_dot_expr;
361                } else if let Some(name_ref) = NameRef::cast(obj_syntax.clone()) {
362                    let obj_name = name_ref.syntax().text().to_string();
363                    property_chain.push(obj_name);
364                    break;
365                } else if let Some(name) = Name::cast(obj_syntax.clone()) {
366                    let obj_name = name.syntax().text().to_string();
367                    property_chain.push(obj_name);
368                    break;
369                } else {
370                    return Err(NodeError {
371                        message: "Unsupported object type in dot expression".to_string(),
372                        node: Some(obj_expr.syntax().clone()),
373                    });
374                }
375            } else {
376                break;
377            }
378        }
379
380        // Reverse the property chain to get the correct order
381        property_chain.reverse();
382
383        #[cfg(feature = "logging")]
384        self.logger
385            .trace(&format!("Property Chain: {:?}", property_chain));
386
387        // Start from the top-level context
388        let mut value = self
389            .context
390            .get(&property_chain[0])
391            .cloned()
392            .unwrap_or(Value::Null);
393
394        // Navigate through the nested properties
395        for prop in &property_chain[1..] {
396            match &value {
397                Value::Object(map) => {
398                    value = map.get(prop).cloned().unwrap_or(Value::Null);
399                }
400                _ => {
401                    // Return Null when the value is not an object or property is missing
402                    value = Value::Null;
403                    break;
404                }
405            }
406        }
407
408        Ok(value)
409    }
410
411    // Implement abstract equality similar to JavaScript
412    fn abstract_equality(&self, left: &Value, right: &Value) -> bool {
413        match (left, right) {
414            (Value::Null, Value::Null) => true,
415            (Value::Number(l), Value::Number(r)) => l.as_f64() == r.as_f64(),
416            (Value::String(l), Value::String(r)) => l == r,
417            (Value::Bool(l), Value::Bool(r)) => l == r,
418            _ => false,
419        }
420    }
421
422    fn evaluate_by_name(&self, identifier_name: String) -> Result<Value, NodeError> {
423        let identifier_value = self.context.get(&identifier_name);
424
425        #[cfg(feature = "logging")]
426        self.logger
427            .trace(&format!("Identifier Value: {:#?}", identifier_value));
428
429        match identifier_value {
430            Some(value) => Ok(value.clone()),
431            None => Err(NodeError {
432                message: format!("Identifier '{}' not found in context.", identifier_name),
433                node: None,
434            }),
435        }
436    }
437
438    fn evaluate_name(&self, name: &Name) -> Result<Value, NodeError> {
439        #[cfg(feature = "logging")]
440        self.logger
441            .trace(&format!("Evaluating Name: {:#?}", name.to_string()));
442        let identifier_name = name
443            .ident_token()
444            .ok_or_else(|| NodeError {
445                message: "[Empty Name]".to_string(),
446                node: Some(name.syntax().clone()),
447            })?
448            .to_string();
449
450        self.evaluate_by_name(identifier_name)
451    }
452
453    fn evaluate_name_ref(&self, name_ref: &NameRef) -> Result<Value, NodeError> {
454        #[cfg(feature = "logging")]
455        self.logger.trace(&format!(
456            "Evaluating Name Reference: {:#?}",
457            name_ref.to_string()
458        ));
459        let identifier_name = name_ref
460            .ident_token()
461            .ok_or_else(|| NodeError {
462                message: "[Empty NameRef]".to_string(),
463                node: Some(name_ref.syntax().clone()),
464            })?
465            .to_string();
466
467        self.evaluate_by_name(identifier_name)
468    }
469
470    fn evaluate_identifier(&self, identifier: &Expr) -> Result<Value, NodeError> {
471        #[cfg(feature = "logging")]
472        self.logger.trace(&format!(
473            "Evaluating Identifier: {:#?}",
474            identifier.to_string()
475        ));
476        let identifier_name = identifier.to_string();
477
478        self.evaluate_by_name(identifier_name)
479    }
480
481    fn evaluate_literal(&self, literal: &Expr) -> Result<Value, NodeError> {
482        #[cfg(feature = "logging")]
483        self.logger
484            .trace(&format!("Evaluating Literal: {:#?}", literal.to_string()));
485
486        let literal_str = literal.to_string();
487
488        // Handle numeric literals
489        if let Ok(number) = literal_str.parse::<f64>() {
490            return Ok(Value::Number(serde_json::Number::from_f64(number).unwrap()));
491        }
492
493        // Handle string literals
494        if literal_str.starts_with('"') || literal_str.starts_with('\'') {
495            let unquoted = literal_str
496                .trim_matches(|c| c == '"' || c == '\'')
497                .to_string();
498            return Ok(Value::String(unquoted));
499        }
500
501        // Handle boolean literals
502        match literal_str.as_str() {
503            "true" => return Ok(Value::Bool(true)),
504            "false" => return Ok(Value::Bool(false)),
505            "null" => return Ok(Value::Null),
506            _ => {}
507        }
508
509        Err(NodeError {
510            message: format!("Unknown literal type: {}", literal_str),
511            node: Some(literal.syntax().clone()),
512        })
513    }
514
515    fn to_number(&self, value: &Value) -> Result<f64, NodeError> {
516        match value {
517            Value::Number(n) => Ok(n.as_f64().unwrap()),
518            Value::String(s) => s.parse::<f64>().map_err(|_| NodeError {
519                message: format!("Cannot convert string '{}' to number", s),
520                node: None,
521            }),
522            Value::Bool(b) => Ok(if *b { 1.0 } else { 0.0 }),
523            Value::Null => Ok(0.0),
524            _ => Err(NodeError {
525                message: "Cannot convert value to number".to_string(),
526                node: None,
527            }),
528        }
529    }
530
531    fn to_boolean(&self, value: &Value) -> Result<bool, NodeError> {
532        let result = match value {
533            Value::Bool(b) => *b,
534            Value::Null => false,
535            Value::Number(n) => {
536                let num = n.as_f64().unwrap();
537                num != 0.0 && !num.is_nan()
538            }
539            Value::String(s) => !s.is_empty(),
540            Value::Array(a) => !a.is_empty(),
541            Value::Object(o) => !o.is_empty(),
542        };
543        Ok(result)
544    }
545
546    fn value_to_string(&self, value: &Value) -> String {
547        match value {
548            Value::String(s) => s.clone(),
549            Value::Number(n) => n.to_string(),
550            Value::Bool(b) => b.to_string(),
551            Value::Null => "null".to_string(),
552            Value::Array(_) => "[Array]".to_string(),
553            Value::Object(_) => "[Object]".to_string(),
554        }
555    }
556}