Skip to main content

oxide_eval/
lib.rs

1mod bin_op;
2pub mod context;
3mod error;
4#[cfg(feature = "math")]
5mod math;
6#[cfg(any(feature = "string", feature = "array"))]
7mod method;
8#[cfg(feature = "semver-support")]
9mod semver_wrapper;
10mod unary;
11mod util;
12
13use anyhow::{anyhow, bail, Result};
14use bin_op::{
15    addition, bitwise_operation, compare, division, equality, exponential, multiplication,
16    remainder, subtraction, unsigned_right_shift,
17};
18use context::ContextEntry;
19use oxc::{
20    allocator::Allocator,
21    ast::ast::{
22        ArrayExpression, BinaryExpression, BinaryOperator, CallExpression, ChainElement,
23        ChainExpression, ConditionalExpression, Expression, LogicalExpression, LogicalOperator,
24        ObjectExpression, ObjectPropertyKind, Statement, StaticMemberExpression, UnaryExpression,
25        UnaryOperator,
26    },
27    parser::Parser,
28    span::SourceType,
29};
30use serde_json::{to_string, Map, Value};
31use std::collections::HashMap;
32use unary::{unary_bitwise_not, unary_negation, unary_plus};
33use util::number_from_f64;
34
35use crate::error::EvaluatorError;
36
37pub struct Evaluator {
38    context: HashMap<String, ContextEntry>,
39}
40
41impl Evaluator {
42    pub fn new(context: HashMap<String, ContextEntry>) -> Self {
43        Self { context }
44    }
45
46    pub fn evaluate(&self, expression: &str) -> Result<Value> {
47        let allocator = Allocator::default();
48        let parser = Parser::new(&allocator, expression, SourceType::cjs());
49        let parsed = parser.parse();
50        if parsed.errors.len() > 0 {
51            let errors = parsed
52                .errors
53                .iter()
54                .map(|d| d.message.to_string())
55                .collect::<String>();
56            bail!("Parsing error: {:?}", errors)
57        }
58        let program = parsed.program;
59        let stmt = &program.body.first();
60        match stmt {
61            Some(Statement::ExpressionStatement(expr)) => self.evaluate_expr(&expr.expression),
62            Some(stmt) => bail!("Unsupported statement: {:?}", stmt),
63            None => {
64                let directive = &program.directives.first();
65                if let Some(directive) = directive {
66                    return Ok(Value::String(directive.directive.into_string()));
67                }
68                bail!("No statements found")
69            }
70        }
71    }
72
73    fn evaluate_expr(&self, expr: &Expression) -> Result<Value> {
74        match expr {
75            Expression::BooleanLiteral(expr) => Ok(Value::Bool(expr.value)),
76            Expression::NullLiteral(_) => Ok(Value::Null),
77            Expression::NumericLiteral(expr) => Ok(Value::Number(number_from_f64(expr.value)?)),
78            // Expression::BigIntLiteral()
79            // Expression::RegExpLiteral()
80            Expression::StringLiteral(expr) => Ok(Value::String(expr.value.into_string())),
81            // Expression::TemplateLiteral(expr) => Ok(Value::String(expr.quasis)),
82            Expression::Identifier(expr) => self.evaluate_by_name(&expr.name),
83            // Expression::MetaProperty()
84            // Expression::Super()
85            Expression::ArrayExpression(expr) => self.evaluate_array(expr),
86            // Expression::ArrowFunctionExpression()
87            // Expression::AssignmentExpression()
88            // Expression::AwaitExpression()
89            Expression::BinaryExpression(expr) => self.evaluate_binary(expr),
90            Expression::CallExpression(expr) => self.evaluate_call(expr),
91            Expression::ChainExpression(expr) => self.evaluate_chain(expr),
92            // Expression::ClassExpression()
93            Expression::ConditionalExpression(expr) => self.evaluate_conditional(expr),
94            // Expression::FunctionExpression()
95            // Expression::ImportExpression()
96            Expression::LogicalExpression(expr) => self.evaluate_logical(expr),
97            // Expression::NewExpression()
98            Expression::ObjectExpression(expr) => self.evaluate_object(expr),
99            Expression::ParenthesizedExpression(expr) => self.evaluate_expr(&expr.expression),
100            // Expression::SequenceExpression()
101            Expression::StaticMemberExpression(expr) => self.evaluate_static_member(expr),
102            // Expression::TaggedTemplateExpression()
103            // Expression::ThisExpression()
104            Expression::UnaryExpression(expr) => self.evaluate_unary(expr),
105            // Expression::UpdateExpression()
106            // Expression::YieldExpression()
107            // Expression::PrivateInExpression()
108            _ => bail!("Unsupported expression: {:?}", expr),
109        }
110    }
111
112    fn evaluate_array(&self, expr: &ArrayExpression) -> Result<Value> {
113        let result = expr
114            .elements
115            .iter()
116            .map(|f| self.evaluate_expr(f.to_expression()).unwrap())
117            .collect::<Vec<Value>>();
118        Ok(Value::Array(result))
119    }
120    fn evaluate_binary(&self, expr: &BinaryExpression) -> Result<Value> {
121        let left = self.evaluate_expr(&expr.left)?;
122        let right = self.evaluate_expr(&expr.right)?;
123
124        #[cfg(feature = "semver-support")]
125        {
126            use semver_wrapper::SemverWrapper;
127            use serde::Deserialize;
128            if let (Ok(left), Ok(right)) = (
129                SemverWrapper::deserialize(&left),
130                SemverWrapper::deserialize(&right),
131            ) {
132                let cmp = left.version.cmp_precedence(&right.version);
133                let result = match expr.operator {
134                    BinaryOperator::Equality | BinaryOperator::StrictEquality => cmp.is_eq(),
135                    BinaryOperator::Inequality | BinaryOperator::StrictInequality => !cmp.is_eq(),
136                    BinaryOperator::LessThan => cmp.is_lt(),
137                    BinaryOperator::LessEqualThan => cmp.is_le(),
138                    BinaryOperator::GreaterThan => cmp.is_gt(),
139                    BinaryOperator::GreaterEqualThan => cmp.is_gt(),
140                    _ => bail!(
141                        "Unsupported binary operator for semver: {:?}",
142                        expr.operator
143                    ),
144                };
145                return Ok(Value::Bool(result));
146            }
147        }
148
149        match expr.operator {
150            BinaryOperator::Equality => Ok(Value::Bool(equality(&left, &right, false))),
151            BinaryOperator::Inequality => Ok(Value::Bool(!equality(&left, &right, false))),
152            BinaryOperator::StrictEquality => Ok(Value::Bool(equality(&left, &right, true))),
153            BinaryOperator::StrictInequality => Ok(Value::Bool(!equality(&left, &right, true))),
154            BinaryOperator::LessThan => Ok(Value::Bool(compare(&left, &right, |l, r| l < r))),
155            BinaryOperator::LessEqualThan => Ok(Value::Bool(compare(&left, &right, |l, r| l <= r))),
156            BinaryOperator::GreaterThan => Ok(Value::Bool(compare(&left, &right, |l, r| l > r))),
157            BinaryOperator::GreaterEqualThan => {
158                Ok(Value::Bool(compare(&left, &right, |l, r| l >= r)))
159            }
160            BinaryOperator::Addition => addition(left, right),
161            BinaryOperator::Subtraction => subtraction(left, right),
162            BinaryOperator::Multiplication => multiplication(left, right),
163            BinaryOperator::Division => division(left, right),
164            BinaryOperator::Remainder => remainder(left, right),
165            BinaryOperator::Exponential => exponential(left, right),
166            BinaryOperator::ShiftLeft => bitwise_operation(left, right, |l, r| l << (r & 0x1F)),
167            BinaryOperator::ShiftRight => bitwise_operation(left, right, |l, r| l >> (r & 0x1F)),
168            BinaryOperator::ShiftRightZeroFill => unsigned_right_shift(left, right),
169            BinaryOperator::BitwiseOR => bitwise_operation(left, right, |l, r| l | r),
170            BinaryOperator::BitwiseXOR => bitwise_operation(left, right, |l, r| l ^ r),
171            BinaryOperator::BitwiseAnd => bitwise_operation(left, right, |l, r| l & r),
172            _ => bail!("Unsupported binary operator: {:?}", expr.operator),
173        }
174    }
175    fn evaluate_call(&self, expr: &CallExpression) -> Result<Value> {
176        let args = expr
177            .arguments
178            .iter()
179            .map(|f| self.evaluate_expr(f.to_expression()).unwrap())
180            .collect::<Vec<Value>>();
181        match &expr.callee {
182            Expression::Identifier(expr) => {
183                let callee_name = expr.name.to_string();
184
185                #[cfg(feature = "semver-support")]
186                {
187                    use semver_wrapper::SemverWrapper;
188                    if callee_name == "semver" {
189                        return Ok(serde_json::json!(SemverWrapper::from_values(args)?));
190                    }
191                }
192
193                match self.context.get(&callee_name) {
194                    Some(ContextEntry::Function(f)) => Ok(f(args)),
195                    _ => {
196                        #[cfg(feature = "string")]
197                        {
198                            if let Some(Value::String(callee)) = args.get(0) {
199                                return match Evaluator::evaluate_str_method(
200                                    callee,
201                                    &callee_name,
202                                    args[1..].to_vec(),
203                                ) {
204                                    Ok(value) => Ok(value),
205                                    _ => {
206                                        #[cfg(feature = "math")]
207                                        {
208                                            if let Ok(result) = Evaluator::evaluate_math_function(
209                                                &callee_name,
210                                                args,
211                                            ) {
212                                                return Ok(result);
213                                            }
214                                        }
215                                        bail!("{:?} not found in function context", callee_name)
216                                    }
217                                };
218                            }
219                        }
220                        #[cfg(feature = "array")]
221                        {
222                            if let Some(Value::Array(callee)) = args.get(0) {
223                                return match Evaluator::evaluate_array_method(
224                                    callee,
225                                    &callee_name,
226                                    args[1..].to_vec(),
227                                ) {
228                                    Ok(value) => Ok(value),
229                                    _ => bail!("{:?} not found in function context", callee_name),
230                                };
231                            }
232                        }
233                        #[cfg(feature = "math")]
234                        {
235                            if let Some(Value::Number(_)) = args.get(0) {
236                                return Evaluator::evaluate_math_function(&callee_name, args);
237                            }
238                        }
239                        bail!("{:?} not found in function context", callee_name)
240                    }
241                }
242            }
243            _ => {
244                let callee = self.evaluate_expr(&expr.callee)?;
245
246                if let Value::String(callee) = &callee {
247                    #[cfg(feature = "string")]
248                    {
249                        let callee_name = expr.callee_name().unwrap_or_default();
250                        return Evaluator::evaluate_str_method(callee, callee_name, args);
251                    }
252                    #[cfg(not(feature = "string"))]
253                    bail!("'string' feature is not enabled. callee: {:?}", callee)
254                } else if let Value::Array(callee) = &callee {
255                    #[cfg(feature = "array")]
256                    {
257                        let callee_name = expr.callee_name().unwrap_or_default();
258                        return Evaluator::evaluate_array_method(callee, callee_name, args);
259                    }
260                    #[cfg(not(feature = "array"))]
261                    bail!("'array' feature is not enabled. callee: {:?}", callee)
262                }
263
264                bail!("Unsupported method for {:?}", callee);
265            }
266        }
267    }
268    fn evaluate_chain(&self, expr: &ChainExpression) -> Result<Value> {
269        let ex = &expr.expression;
270        match ex {
271            ChainElement::CallExpression(expr) => self.evaluate_call(expr),
272            ChainElement::StaticMemberExpression(expr) => self.evaluate_static_member(expr),
273            _ => bail!("Unsupported ChainExpression: {:?}", ex),
274        }
275    }
276    fn evaluate_conditional(&self, expr: &ConditionalExpression) -> Result<Value> {
277        let test = self.evaluate_value(&self.evaluate_expr(&expr.test)?);
278        let expr = match test {
279            true => &expr.consequent,
280            false => &expr.alternate,
281        };
282        self.evaluate_expr(expr)
283    }
284    fn evaluate_logical(&self, expr: &LogicalExpression) -> Result<Value> {
285        let operator = expr.operator;
286        let left = &self.evaluate_expr(&expr.left)?;
287        let right = &self.evaluate_expr(&expr.right)?;
288        match operator {
289            LogicalOperator::And => Ok(Value::Bool(
290                self.evaluate_value(left) && self.evaluate_value(right),
291            )),
292            LogicalOperator::Coalesce => match left {
293                Value::Null => Ok(right.clone()),
294                _ => Ok(left.clone()),
295            },
296            LogicalOperator::Or => match self.evaluate_value(left) {
297                true => Ok(left.clone()),
298                false => Ok(right.clone()),
299            },
300        }
301    }
302    fn evaluate_object(&self, expr: &ObjectExpression) -> Result<Value> {
303        let properties = &expr.properties;
304        let mut map = Map::new();
305        for property in properties {
306            match property {
307                ObjectPropertyKind::ObjectProperty(object_property) => {
308                    let key = match object_property.key.as_expression() {
309                        Some(v) => v,
310                        _ => bail!("Object key is not an expression"),
311                    };
312                    let key = self.evaluate_expr(key)?;
313                    let key = to_string(&key)?;
314                    let value = self.evaluate_expr(&object_property.value)?;
315                    map.insert(key, value);
316                }
317                _ => bail!("Unsupported ObjectPropertyKind: {:?}", property),
318            }
319        }
320        Ok(Value::Object(map))
321    }
322    fn evaluate_static_member(&self, expr: &StaticMemberExpression) -> Result<Value> {
323        let obj = if expr.optional {
324            match self.evaluate_expr(&expr.object) {
325                Ok(value) => value,
326                Err(e) => {
327                    if e.downcast_ref::<EvaluatorError>().map_or(false, |err| {
328                        matches!(err, EvaluatorError::VariableNotFound(_))
329                    }) {
330                        return Ok(Value::Null);
331                    }
332                    return Err(e);
333                }
334            }
335        } else {
336            self.evaluate_expr(&expr.object)?
337        };
338        let property = expr.property.name.to_string();
339        match &obj {
340            Value::Object(map) => {
341                let value = map.get(&property);
342                if let Some(value) = value {
343                    return Ok(value.clone());
344                }
345                if expr.optional {
346                    return Ok(Value::Null);
347                }
348                Err(anyhow!(EvaluatorError::PropertyNotFound(
349                    obj.to_string(),
350                    property.clone(),
351                )))
352            }
353            _ => Ok(obj),
354        }
355    }
356    fn evaluate_unary(&self, expr: &UnaryExpression) -> Result<Value> {
357        let operator = expr.operator;
358        let value = self.evaluate_expr(&expr.argument)?;
359        match operator {
360            UnaryOperator::UnaryPlus => unary_plus(value),
361            UnaryOperator::UnaryNegation => unary_negation(value),
362            UnaryOperator::BitwiseNot => unary_bitwise_not(value),
363            UnaryOperator::LogicalNot => Ok(Value::Bool(!self.evaluate_value(&value))),
364            _ => {
365                bail!("Unsupported UnaryOperator {:?}", operator)
366            }
367        }
368    }
369
370    fn evaluate_by_name(&self, name: &str) -> Result<Value> {
371        match self.context.get(name) {
372            Some(ContextEntry::Variable(value)) => Ok(value.clone()),
373            _ => Err(anyhow!(EvaluatorError::VariableNotFound(name.to_string()))),
374        }
375    }
376    fn evaluate_value(&self, value: &Value) -> bool {
377        match value {
378            Value::Array(arr) => !arr.is_empty(),
379            Value::Bool(bool) => *bool,
380            Value::Null => false,
381            Value::Number(number) => number.as_f64().is_some_and(|value| value != 0.0),
382            Value::Object(_) => true,
383            Value::String(str) => !str.is_empty(),
384        }
385    }
386    #[cfg(feature = "string")]
387    fn evaluate_str_method(callee: &str, callee_name: &str, args: Vec<Value>) -> Result<Value> {
388        use method::string::StringMethod;
389
390        let str_method = StringMethod::new(args);
391        match callee_name {
392            "replace" => str_method.replace(callee),
393            "contains" => str_method.contains(callee),
394            "split" => str_method.split(callee),
395            "indexOf" => str_method.index_of(callee),
396            "lastIndexOf" => str_method.last_index_of(callee),
397            "toUpperCase" => str_method.to_upper_case(callee),
398            "toLowerCase" => str_method.to_lower_case(callee),
399            "substring" => str_method.substring(callee),
400            "startsWith" => str_method.starts_with(callee),
401            "endsWith" => str_method.ends_with(callee),
402            "regexReplace" => str_method.regex_replace(callee),
403            "length" => str_method.length(callee),
404            "trim" => str_method.trim(callee),
405            _ => {
406                bail!("Unknown string method: {}", callee_name);
407            }
408        }
409    }
410    #[cfg(feature = "array")]
411    fn evaluate_array_method(
412        callee: &Vec<Value>,
413        callee_name: &str,
414        args: Vec<Value>,
415    ) -> Result<Value> {
416        use method::array::ArrayMethod;
417
418        let array_method = ArrayMethod::new(args);
419        match callee_name {
420            "join" => array_method.join(callee),
421            _ => bail!("Unknown array method: {}", callee_name),
422        }
423    }
424    #[cfg(feature = "math")]
425    fn evaluate_math_function(callee_name: &str, mut args: Vec<Value>) -> Result<Value> {
426        let value = args.remove(0).take();
427        match args.len() {
428            1 => match callee_name {
429                "floor" => math::unary_function(value, f64::floor),
430                "ceil" => math::unary_function(value, f64::ceil),
431                "round" => math::unary_function(value, f64::round),
432                "sin" => math::unary_function(value, f64::sin),
433                "cos" => math::unary_function(value, f64::cos),
434                "tan" => math::unary_function(value, f64::tan),
435                "asin" => math::unary_function(value, f64::asin),
436                "acos" => math::unary_function(value, f64::acos),
437                "atan" => math::unary_function(value, f64::atan),
438                "sqrt" => math::unary_function(value, f64::sqrt),
439                "abs" => math::unary_function(value, f64::abs),
440                "clamp" => math::unary_function(value, |x| x.clamp(0.0, 1.0)),
441                "bitwiseNot" => unary_bitwise_not(value),
442                _ => bail!("{:?} not found in function context", callee_name),
443            },
444            2 => {
445                let second = args.remove(0).take();
446                match callee_name {
447                    "atan2" => math::binary_function(value, second, f64::atan2),
448                    "min" => math::binary_function(value, second, f64::min),
449                    "max" => math::binary_function(value, second, f64::max),
450                    "mod" => remainder(value, second),
451                    "pow" => math::binary_function(value, second, f64::powf),
452                    "bitwiseAnd" => bitwise_operation(value, second, |l, r| l & r),
453                    "bitwiseOr" => bitwise_operation(value, second, |l, r| l | r),
454                    "bitwiseLeft" => bitwise_operation(value, second, |l, r| l << (r & 0x1F)),
455                    "bitwiseRight" => bitwise_operation(value, second, |l, r| l >> (r & 0x1F)),
456                    _ => bail!("{:?} not found in function context", callee_name),
457                }
458            }
459            _ => bail!("{:?} not found in function context", callee_name),
460        }
461    }
462}