Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/third_party/rust/jexl-eval/src/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 30 kB image not shown  

Quelle  lib.rs

  Sprache: Rust
 

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


//! A JEXL evaluator written in Rust
//! This crate depends on a JEXL parser crate that handles all the parsing
//! and is a part of the same workspace.
//! JEXL is an expression language used by Mozilla, you can find more information here: https://github.com/mozilla/mozjexl
//!
//! # How to use
//! The access point for this crate is the `eval` functions of the Evaluator Struct
//! You can use the `eval` function directly to evaluate standalone statements
//!
//! For example:
//! ```rust
//! use jexl_eval::Evaluator;
//! use serde_json::json as value;
//! let evaluator = Evaluator::new();
//! assert_eq!(evaluator.eval("'Hello ' + 'World'").unwrap(), value!("Hello World"));
//! ```
//!
//! You can also run the statements against a context using the `eval_in_context` function
//! The context can be any type that implements the `serde::Serializable` trait
//! and the function will return errors if the statement doesn't match the context
//!
//! For example:
//! ```rust
//! use jexl_eval::Evaluator;
//! use serde_json::json as value;
//! let context = value!({"a": {"b": 2.0}});
//! let evaluator = Evaluator::new();
//! assert_eq!(evaluator.eval_in_context("a.b", context).unwrap(), value!(2.0));
//! ```
//!

use jexl_parser::{
    ast::{Expression, OpCode},
    Parser,
};
use serde_json::{json as value, Value};

pub mod error;
use error::*;
use std::collections::HashMap;

const EPSILON: f64 = 0.000001f64;

trait Truthy {
    fn is_truthy(&self) -> bool;
}

impl Truthy for Value {
    fn is_truthy(&self) -> bool {
        match self {
            Value::Bool(b) => *b,
            Value::Null => false,
            Value::Number(f) => f.as_f64().unwrap() != 0.0,
            Value::String(s) => !s.is_empty(),
            // It would be better if these depended on the contents of the
            // object (empty array/object is falsey, non-empty is truthy, like
            // in Python) but this matches JS semantics. Is it worth changing?
            Value::Array(_) => true,
            Value::Object(_) => true,
        }
    }
}

impl<'b> Truthy for Result<'b, Value> {
    fn is_truthy(&self) -> bool {
        match self {
            Ok(v) => v.is_truthy(),
            _ => false,
        }
    }
}

type Context = Value;

/// TransformFn represents an arbitrary transform function
/// Transform functions take an arbitrary number of `serde_json::Value`to represent their arguments
/// and return a `serde_json::Value`.
/// the transform function itself is responsible for checking if the format and number of
/// the arguments is correct
///
/// Returns a Result with an `anyhow::Error`. This allows consumers to return their own custom errors
/// in the closure, and use `.into` to convert it into an `anyhow::Error`. The error message will be perserved
pub type TransformFn<'a> = Box<dyn Fn(&[Value]) -> Result<Value, anyhow::Error> + Send + Sync + 'a>;

#[derive(Default)]
pub struct Evaluator<'a> {
    transforms: HashMap<String, TransformFn<'a>>,
}

impl<'a> Evaluator<'a> {
    pub fn new() -> Self {
        Self::default()
    }

    /// Adds a custom transform function
    /// This is meant as a way to allow consumers to add their own custom functionality
    /// to the expression language.
    /// Note that the name added here has to match with
    /// the name that the transform will have when it's a part of the expression statement
    ///
    /// # Arguments:
    /// - `name`: The name of the transfrom
    /// - `transform`: The actual function. A closure the implements Fn(&[serde_json::Value]) -> Result<Value, anyhow::Error>
    ///
    /// # Example:
    ///
    /// ```rust
    /// use jexl_eval::Evaluator;
    /// use serde_json::{json as value, Value};
    ///
    /// let mut evaluator = Evaluator::new().with_transform("lower", |v: &[Value]| {
    ///    let s = v
    ///            .first()
    ///            .expect("Should have 1 argument!")
    ///            .as_str()
    ///            .expect("Should be a string!");
    ///       Ok(value!(s.to_lowercase()))
    ///  });
    ///
    /// assert_eq!(evaluator.eval("'JOHN DOe'|lower").unwrap(), value!("john doe"))
    /// ```
    pub fn with_transform<F>(mut self, name: &str, transform: F) -> Self
    where
        F: Fn(&[Value]) -> Result<Value, anyhow::Error> + Send + Sync + 'a,
    {
        self.transforms
            .insert(name.to_string(), Box::new(transform));
        self
    }

    pub fn eval<'b>(&self, input: &'b str) -> Result<'b, Value> {
        let context = value!({});
        self.eval_in_context(input, &context)
    }

    pub fn eval_in_context<'b, T: serde::Serialize>(
        &self,
        input: &'b str,
        context: T,
    ) -> Result<'b, Value> {
        let tree = Parser::parse(input)?;
        let context = serde_json::to_value(context)?;
        if !context.is_object() {
            return Err(EvaluationError::InvalidContext);
        }
        self.eval_ast(tree, &context)
    }

    fn eval_ast<'b>(&self, ast: Expression, context: &Context) -> Result<'b, Value> {
        match ast {
            Expression::Number(n) => Ok(value!(n)),
            Expression::Boolean(b) => Ok(value!(b)),
            Expression::String(s) => Ok(value!(s)),
            Expression::Array(xs) => xs.into_iter().map(|x| self.eval_ast(*x, context)).collect(),
            Expression::Null => Ok(Value::Null),

            Expression::Object(items) => {
                let mut map = serde_json::Map::with_capacity(items.len());
                for (key, expr) in items.into_iter() {
                    if map.contains_key(&key) {
                        return Err(EvaluationError::DuplicateObjectKey(key));
                    }
                    let value = self.eval_ast(*expr, context)?;
                    map.insert(key, value);
                }
                Ok(Value::Object(map))
            }

            Expression::Identifier(inner) => match context.get(&inner) {
                Some(v) => Ok(v.clone()),
                _ => Err(EvaluationError::UndefinedIdentifier(inner.clone())),
            },

            Expression::DotOperation { subject, ident } => {
                let subject = self.eval_ast(*subject, context)?;
                Ok(subject.get(&ident).unwrap_or(&value!(null)).clone())
            }

            Expression::IndexOperation { subject, index } => {
                let subject = self.eval_ast(*subject, context)?;
                if let Expression::Filter { ident, op, right } = *index {
                    let subject_arr = subject.as_array().ok_or(EvaluationError::InvalidFilter)?;
                    let right = self.eval_ast(*right, context)?;
                    let filtered = subject_arr
                        .iter()
                        .filter(|e| {
                            let left = e.get(&ident).unwrap_or(&value!(null));
                            // returns false if any members fail the op, could happen if array members are missing the identifier
                            Self::apply_op(op, left.clone(), right.clone())
                                .unwrap_or(value!(false))
                                .is_truthy()
                        })
                        .collect::<Vec<_>>();
                    return Ok(value!(filtered));
                }

                let index = self.eval_ast(*index, context)?;
                match index {
                    Value::String(inner) => {
                        Ok(subject.get(&inner).unwrap_or(&value!(null)).clone())
                    }
                    Value::Number(inner) => Ok(subject
                        .get(inner.as_f64().unwrap().floor() as usize)
                        .unwrap_or(&value!(null))
                        .clone()),
                    _ => Err(EvaluationError::InvalidIndexType),
                }
            }

            Expression::BinaryOperation {
                left,
                right,
                operation,
            } => self.eval_op(operation, left, right, context),
            Expression::Transform {
                name,
                subject,
                args,
            } => {
                let subject = self.eval_ast(*subject, context)?;
                let mut args_arr = Vec::new();
                args_arr.push(subject);
                if let Some(args) = args {
                    for arg in args {
                        args_arr.push(self.eval_ast(*arg, context)?);
                    }
                }
                let f = self
                    .transforms
                    .get(&name)
                    .ok_or(EvaluationError::UnknownTransform(name))?;
                f(&args_arr).map_err(|e| e.into())
            }

            Expression::Conditional {
                left,
                truthy,
                falsy,
            } => {
                if self.eval_ast(*left, context).is_truthy() {
                    self.eval_ast(*truthy, context)
                } else {
                    self.eval_ast(*falsy, context)
                }
            }

            Expression::Filter {
                ident: _,
                op: _,
                right: _,
            } => {
                // Filters shouldn't be evaluated individually
                // instead, they are evaluated as a part of an IndexOperation
                return Err(EvaluationError::InvalidFilter);
            }
        }
    }

    fn eval_op<'b>(
        &self,
        operation: OpCode,
        left: Box<Expression>,
        right: Box<Expression>,
        context: &Context,
    ) -> Result<'b, Value> {
        let left = self.eval_ast(*left, context);

        // We want to delay evaluating the right hand side in the cases of AND and OR.
        let eval_right = || self.eval_ast(*right, context);
        Ok(match operation {
            OpCode::Or => {
                if left.is_truthy() {
                    left?
                } else {
                    eval_right()?
                }
            }
            OpCode::And => {
                if left.is_truthy() {
                    eval_right()?
                } else {
                    left?
                }
            }
            _ => Self::apply_op(operation, left?, eval_right()?)?,
        })
    }

    fn apply_op<'b>(operation: OpCode, left: Value, right: Value) -> Result<'b, Value> {
        match (operation, left, right) {
            (OpCode::NotEqual, a, b) => {
                // Implement NotEquals as the inverse of Equals.
                let value = Self::apply_op(OpCode::Equal, a, b)?;
                let equality = value
                    .as_bool()
                    .unwrap_or_else(|| unreachable!("Equality always returns a bool"));
                Ok(value!(!equality))
            }

            (OpCode::And, a, b) => Ok(if a.is_truthy() { b } else { a }),
            (OpCode::Or, a, b) => Ok(if a.is_truthy() { a } else { b }),

            (op, Value::Number(a), Value::Number(b)) => {
                let left = a.as_f64().unwrap();
                let right = b.as_f64().unwrap();
                Ok(match op {
                    OpCode::Add => value!(left + right),
                    OpCode::Subtract => value!(left - right),
                    OpCode::Multiply => value!(left * right),
                    OpCode::Divide => value!(left / right),
                    OpCode::FloorDivide => value!((left / right).floor()),
                    OpCode::Modulus => value!(left % right),
                    OpCode::Exponent => value!(left.powf(right)),
                    OpCode::Less => value!(left < right),
                    OpCode::Greater => value!(left > right),
                    OpCode::LessEqual => value!(left <= right),
                    OpCode::GreaterEqual => value!(left >= right),
                    OpCode::Equal => value!((left - right).abs() < EPSILON),
                    OpCode::NotEqual => value!((left - right).abs() >= EPSILON),
                    OpCode::In => value!(false),
                    OpCode::And | OpCode::Or => {
                        unreachable!("Covered by previous case in parent match")
                    }
                })
            }

            (op, Value::String(a), Value::String(b)) => match op {
                OpCode::Equal => Ok(value!(a == b)),

                OpCode::Add => Ok(value!(format!("{}{}", a, b))),
                OpCode::In => Ok(value!(b.contains(&a))),

                OpCode::Less => Ok(value!(a < b)),
                OpCode::Greater => Ok(value!(a > b)),
                OpCode::LessEqual => Ok(value!(a <= b)),
                OpCode::GreaterEqual => Ok(value!(a >= b)),

                _ => Err(EvaluationError::InvalidBinaryOp {
                    operation,
                    left: value!(a),
                    right: value!(b),
                }),
            },

            (OpCode::In, left, Value::Array(v)) => Ok(value!(v.contains(&left))),
            (OpCode::In, Value::String(left), Value::Object(v)) => {
                Ok(value!(v.contains_key(&left)))
            }

            (OpCode::Equal, a, b) => match (a, b) {
                // Number == Number is handled above
                // String == String is handled above
                (Value::Bool(a), Value::Bool(b)) => Ok(value!(a == b)),
                (Value::Null, Value::Null) => Ok(value!(true)),
                (Value::Array(a), Value::Array(b)) => Ok(value!(a == b)),
                (Value::Object(a), Value::Object(b)) => Ok(value!(a == b)),
                // If the types don't match, it's always false
                _ => Ok(value!(false)),
            },

            (operation, left, right) => Err(EvaluationError::InvalidBinaryOp {
                operation,
                left,
                right,
            }),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json as value;

    #[test]
    fn test_literal() {
        assert_eq!(Evaluator::new().eval("1").unwrap(), value!(1.0));
    }

    #[test]
    fn test_binary_expression_addition() {
        assert_eq!(Evaluator::new().eval("1 + 2").unwrap(), value!(3.0));
    }

    #[test]
    fn test_binary_expression_multiplication() {
        assert_eq!(Evaluator::new().eval("2 * 3").unwrap(), value!(6.0));
    }

    #[test]
    fn test_precedence() {
        assert_eq!(Evaluator::new().eval("2 + 3 * 4").unwrap(), value!(14.0));
    }

    #[test]
    fn test_parenthesis() {
        assert_eq!(Evaluator::new().eval("(2 + 3) * 4").unwrap(), value!(20.0));
    }

    #[test]
    fn test_string_concat() {
        assert_eq!(
            Evaluator::new().eval("'Hello ' + 'World'").unwrap(),
            value!("Hello World")
        );
    }

    #[test]
    fn test_true_comparison() {
        assert_eq!(Evaluator::new().eval("2 > 1").unwrap(), value!(true));
    }

    #[test]
    fn test_false_comparison() {
        assert_eq!(Evaluator::new().eval("2 <= 1").unwrap(), value!(false));
    }

    #[test]
    fn test_boolean_logic() {
        assert_eq!(
            Evaluator::new()
                .eval("'foo' && 6 >= 6 && 0 + 1 && true")
                .unwrap(),
            value!(true)
        );
    }

    #[test]
    fn test_identifier() {
        let context = value!({"a"1.0});
        assert_eq!(
            Evaluator::new().eval_in_context("a", context).unwrap(),
            value!(1.0)
        );
    }

    #[test]
    fn test_identifier_chain() {
        let context = value!({"a": {"b"2.0}});
        assert_eq!(
            Evaluator::new().eval_in_context("a.b", context).unwrap(),
            value!(2.0)
        );
    }

    #[test]
    fn test_context_filter_arrays() {
        let context = value!({
            "foo": {
                "bar": [
                    {"tek""hello"},
                    {"tek""baz"},
                    {"tok""baz"},
                ]
            }
        });
        assert_eq!(
            Evaluator::new()
                .eval_in_context("foo.bar[.tek == 'baz']", &context)
                .unwrap(),
            value!([{"tek""baz"}])
        );
    }

    #[test]
    fn test_context_array_index() {
        let context = value!({
            "foo": {
                "bar": [
                    {"tek""hello"},
                    {"tek""baz"},
                    {"tok""baz"},
                ]
            }
        });
        assert_eq!(
            Evaluator::new()
                .eval_in_context("foo.bar[1].tek", context)
                .unwrap(),
            value!("baz")
        );
    }

    #[test]
    fn test_object_expression_properties() {
        let context = value!({"foo": {"baz": {"bar""tek"}}});
        assert_eq!(
            Evaluator::new()
                .eval_in_context("foo['ba' + 'z'].bar", &context)
                .unwrap(),
            value!("tek")
        );
    }

    #[test]
    fn test_divfloor() {
        assert_eq!(Evaluator::new().eval("7 // 2").unwrap(), value!(3.0));
    }

    #[test]
    fn test_empty_object_literal() {
        assert_eq!(Evaluator::new().eval("{}").unwrap(), value!({}));
    }

    #[test]
    fn test_object_literal_strings() {
        assert_eq!(
            Evaluator::new().eval("{'foo': {'bar': 'tek'}}").unwrap(),
            value!({"foo": {"bar""tek"}})
        );
    }

    #[test]
    fn test_object_literal_identifiers() {
        assert_eq!(
            Evaluator::new().eval("{foo: {bar: 'tek'}}").unwrap(),
            value!({"foo": {"bar""tek"}})
        );
    }

    #[test]
    fn test_object_literal_properties() {
        assert_eq!(
            Evaluator::new().eval("{foo: 'bar'}.foo").unwrap(),
            value!("bar")
        );
    }

    #[test]
    fn test_array_literal() {
        assert_eq!(
            Evaluator::new().eval("['foo', 1+2]").unwrap(),
            value!(["foo"3.0])
        );
    }

    #[test]
    fn test_array_literal_indexing() {
        assert_eq!(Evaluator::new().eval("[1, 2, 3][1]").unwrap(), value!(2.0));
    }

    #[test]
    fn test_in_operator_string() {
        assert_eq!(
            Evaluator::new().eval("'bar' in 'foobartek'").unwrap(),
            value!(true)
        );
        assert_eq!(
            Evaluator::new().eval("'baz' in 'foobartek'").unwrap(),
            value!(false)
        );
    }

    #[test]
    fn test_in_operator_array() {
        assert_eq!(
            Evaluator::new()
                .eval("'bar' in ['foo', 'bar', 'tek']")
                .unwrap(),
            value!(true)
        );
        assert_eq!(
            Evaluator::new()
                .eval("'baz' in ['foo', 'bar', 'tek']")
                .unwrap(),
            value!(false)
        );
    }

    #[test]
    fn test_in_operator_object() {
        assert_eq!(
            Evaluator::new()
                .eval("'bar' in {foo: 1, bar: 2, tek: 3}")
                .unwrap(),
            value!(true)
        );
        assert_eq!(
            Evaluator::new()
                .eval("'baz' in {foo: 1, bar: 2, tek: 3}")
                .unwrap(),
            value!(false)
        );
    }

    #[test]
    fn test_conditional_expression() {
        assert_eq!(
            Evaluator::new().eval("'foo' ? 1 : 2").unwrap(),
            value!(1f64)
        );
        assert_eq!(Evaluator::new().eval("'' ? 1 : 2").unwrap(), value!(2f64));
    }

    #[test]
    fn test_arbitrary_whitespace() {
        assert_eq!(
            Evaluator::new().eval("(\t2\n+\n3) *\n4\n\r\n").unwrap(),
            value!(20.0)
        );
    }

    #[test]
    fn test_non_integer() {
        assert_eq!(Evaluator::new().eval("1.5 * 3.0").unwrap(), value!(4.5));
    }

    #[test]
    fn test_string_literal() {
        assert_eq!(
            Evaluator::new().eval("'hello world'").unwrap(),
            value!("hello world")
        );
        assert_eq!(
            Evaluator::new().eval("\"hello world\"").unwrap(),
            value!("hello world")
        );
    }

    #[test]
    fn test_string_escapes() {
        assert_eq!(Evaluator::new().eval("'a\\'b'").unwrap(), value!("a'b"));
        assert_eq!(Evaluator::new().eval("\"a\\\"b\"").unwrap(), value!("a\"b"));
    }

    #[test]
    // Test a very simple transform that applies to_lowercase to a string
    fn test_simple_transform() {
        let evaluator = Evaluator::new().with_transform("lower", |v: &[Value]| {
            let s = v
                .get(0)
                .expect("There should be one argument!")
                .as_str()
                .expect("Should be a string!");
            Ok(value!(s.to_lowercase()))
        });
        assert_eq!(evaluator.eval("'T_T'|lower").unwrap(), value!("t_t"));
    }

    #[test]
    // Test returning an UnknownTransform error if a transform is unknown
    fn test_missing_transform() {
        let err = Evaluator::new().eval("'hello'|world").unwrap_err();
        if let EvaluationError::UnknownTransform(transform) = err {
            assert_eq!(transform, "world")
        } else {
            panic!("Should have thrown an unknown transform error")
        }
    }

    #[test]
    // Test returning an UndefinedIdentifier error if an identifier is unknown
    fn test_undefined_identifier() {
        let err = Evaluator::new().eval("not_defined").unwrap_err();
        if let EvaluationError::UndefinedIdentifier(id) = err {
            assert_eq!(id, "not_defined")
        } else {
            panic!("Should have thrown an undefined identifier error")
        }
    }

    #[test]
    // Test returning an UndefinedIdentifier error if an identifier is unknown
    fn test_undefined_identifier_truthy_ops() {
        let err = Evaluator::new().eval("not_defined").unwrap_err();
        if let EvaluationError::UndefinedIdentifier(id) = err {
            assert_eq!(id, "not_defined")
        } else {
            panic!("Should have thrown an undefined identifier error")
        }

        let evaluator = Evaluator::new();
        let context = value!({
            "NULL": null,
            "DEFINED""string",
        });

        let test = |expr: &str, is_ok: bool, exp: Value| {
            let obs = evaluator.eval_in_context(&expr, context.clone());
            if !is_ok {
                assert!(obs.is_err());
                assert!(matches!(
                    obs.unwrap_err(),
                    EvaluationError::UndefinedIdentifier(_)
                ));
            } else {
                assert_eq!(obs.unwrap(), exp,);
            }
        };

        test("UNDEFINED"false, value!(null));
        test("UNDEFINED == 'string'"false, value!(null));
        test("'string' == UNDEFINED"false, value!(null));

        test("UNDEFINED ? 'WRONG' : 'RIGHT'"true, value!("RIGHT"));
        test("DEFINED ? UNDEFINED : 'WRONG'"false, value!(null));

        test("UNDEFINED || 'RIGHT'"true, value!("RIGHT"));
        test("'RIGHT' || UNDEFINED"true, value!("RIGHT"));

        test("'WRONG' && UNDEFINED"false, value!(null));
        test("UNDEFINED && 'WRONG'"false, value!(null));

        test("UNDEFINED && 'WRONG'"false, value!(null));

        test(
            "(UNDEFINED && UNDEFINED == 'string') || (DEFINED && DEFINED == 'string')",
            true,
            value!(true),
        );
    }

    #[test]
    fn test_add_multiple_transforms() {
        let evaluator = Evaluator::new()
            .with_transform("sqrt", |v: &[Value]| {
                let num = v
                    .first()
                    .expect("There should be one argument!")
                    .as_f64()
                    .expect("Should be a valid number!");
                Ok(value!(num.sqrt() as u64))
            })
            .with_transform("square", |v: &[Value]| {
                let num = v
                    .first()
                    .expect("There should be one argument!")
                    .as_f64()
                    .expect("Should be a valid number!");
                Ok(value!((num as u64).pow(2)))
            });

        assert_eq!(evaluator.eval("4|square").unwrap(), value!(16));
        assert_eq!(evaluator.eval("4|sqrt").unwrap(), value!(2));
        assert_eq!(evaluator.eval("4|square|sqrt").unwrap(), value!(4));
    }

    #[test]
    fn test_transform_with_argument() {
        let evaluator = Evaluator::new().with_transform("split", |args: &[Value]| {
            let s = args
                .first()
                .expect("Should be a first argument!")
                .as_str()
                .expect("Should be a string!");
            let c = args
                .get(1)
                .expect("There should be a second argument!")
                .as_str()
                .expect("Should be a string");
            let res: Vec<&str> = s.split_terminator(c).collect();
            Ok(value!(res))
        });

        assert_eq!(
            evaluator.eval("'John Doe'|split(' ')").unwrap(),
            value!(vec!["John""Doe"])
        );
    }

    #[derive(Debug, thiserror::Error)]
    enum CustomError {
        #[error("Invalid argument in transform!")]
        InvalidArgument,
    }

    #[test]
    fn test_custom_error_message() {
        let evaluator = Evaluator::new().with_transform("error", |_: &[Value]| {
            Err(CustomError::InvalidArgument.into())
        });
        let res = evaluator.eval("1234|error");
        assert!(res.is_err());
        if let EvaluationError::CustomError(e) = res.unwrap_err() {
            assert_eq!(e.to_string(), "Invalid argument in transform!")
        } else {
            panic!("Should have returned a Custom error!")
        }
    }

    #[test]
    fn test_filter_collections_many_returned() {
        let evaluator = Evaluator::new();
        let context = value!({
            "foo": [
                {"bobo"50"fofo"100},
                {"bobo"60"baz"90},
                {"bobo"10"bar"83},
                {"bobo"20"yam"12},
            ]
        });
        let exp = "foo[.bobo >= 50]";
        assert_eq!(
            evaluator.eval_in_context(exp, context).unwrap(),
            value!([{"bobo"50"fofo"100}, {"bobo"60"baz"90}])
        );
    }

    #[test]
    fn test_binary_op_eq_ne() {
        let evaluator = Evaluator::new();
        let context = value!({
            "NULL": null,
            "STRING""string",
            "BOOLEAN"true,
            "NUMBER"42,
            "OBJECT": { "x"1"y"2 },
            "ARRAY": [ "string" ]
        });

        let test = |l: &str, r: &str, exp: bool| {
            let expr = format!("{} == {}", l, r);
            assert_eq!(
                evaluator.eval_in_context(&expr, context.clone()).unwrap(),
                value!(exp)
            );

            let expr = format!("{} != {}", l, r);
            assert_eq!(
                evaluator.eval_in_context(&expr, context.clone()).unwrap(),
                value!(!exp)
            );
        };

        test("STRING""'string'"true);
        test("NUMBER""42"true);
        test("BOOLEAN""true"true);
        test("OBJECT""OBJECT"true);
        test("ARRAY""[ 'string' ]"true);
        test("NULL""null"true);

        test("OBJECT""{ 'x': 1, 'y': 2 }"false);

        test("STRING""NULL"false);
        test("NUMBER""NULL"false);
        test("BOOLEAN""NULL"false);
        // test("NULL", "NULL", false);
        test("OBJECT""NULL"false);
        test("ARRAY""NULL"false);

        // test("STRING", "STRING", false);
        test("NUMBER""STRING"false);
        test("BOOLEAN""STRING"false);
        test("NULL""STRING"false);
        test("OBJECT""STRING"false);
        test("ARRAY""STRING"false);

        test("STRING""NUMBER"false);
        // test("NUMBER", "NUMBER", false);
        test("BOOLEAN""NUMBER"false);
        test("NULL""NUMBER"false);
        test("OBJECT""NUMBER"false);
        test("ARRAY""NUMBER"false);

        test("STRING""BOOLEAN"false);
        test("NUMBER""BOOLEAN"false);
        // test("BOOLEAN", "BOOLEAN", false);
        test("NULL""BOOLEAN"false);
        test("OBJECT""BOOLEAN"false);
        test("ARRAY""BOOLEAN"false);

        test("STRING""OBJECT"false);
        test("NUMBER""OBJECT"false);
        test("BOOLEAN""OBJECT"false);
        test("NULL""OBJECT"false);
        // test("OBJECT", "OBJECT", false);
        test("ARRAY""OBJECT"false);

        test("STRING""ARRAY"false);
        test("NUMBER""ARRAY"false);
        test("BOOLEAN""ARRAY"false);
        test("NULL""ARRAY"false);
        test("OBJECT""ARRAY"false);
        // test("ARRAY", "ARRAY", false);
    }

    #[test]
    fn test_binary_op_string_gt_lt_gte_lte() {
        let evaluator = Evaluator::new();
        let context = value!({
            "A""A string",
            "B""B string",
        });

        let test = |l: &str, r: &str, is_gt: bool| {
            let expr = format!("{} > {}", l, r);
            assert_eq!(
                evaluator.eval_in_context(&expr, context.clone()).unwrap(),
                value!(is_gt)
            );

            let expr = format!("{} <= {}", l, r);
            assert_eq!(
                evaluator.eval_in_context(&expr, context.clone()).unwrap(),
                value!(!is_gt)
            );

            // we test equality in another test
            let expr = format!("{} == {}", l, r);
            let is_eq = evaluator
                .eval_in_context(&expr, context.clone())
                .unwrap()
                .as_bool()
                .unwrap();

            if is_eq {
                let expr = format!("{} >= {}", l, r);
                assert_eq!(
                    evaluator.eval_in_context(&expr, context.clone()).unwrap(),
                    value!(true)
                );
            } else {
                let expr = format!("{} < {}", l, r);
                assert_eq!(
                    evaluator.eval_in_context(&expr, context.clone()).unwrap(),
                    value!(!is_gt)
                );
            }
        };

        test("A""B"false);
        test("B""A"true);
        test("A""A"false);
    }

    #[test]
    fn test_lazy_eval_binary_op_and_or() {
        let evaluator = Evaluator::new();
        // error is a missing transform
        let res = evaluator.eval("42 || 0|error");
        assert!(res.is_ok());
        assert_eq!(res.unwrap(), value!(42.0));

        let res = evaluator.eval("false || 0|error");
        assert!(res.is_err());

        let res = evaluator.eval("42 && 0|error");
        assert!(res.is_err());

        let res = evaluator.eval("false && 0|error");
        assert!(res.is_ok());
        assert_eq!(res.unwrap(), value!(false));
    }

    #[test]
    fn test_lazy_eval_trinary_op() {
        let evaluator = Evaluator::new();
        // error is a missing transform
        let res = evaluator.eval("true ? 42 : 0|error");
        assert!(res.is_ok());
        assert_eq!(res.unwrap(), value!(42.0));

        let res = evaluator.eval("true ? 0|error : 42");
        assert!(res.is_err());

        let res = evaluator.eval("true ? 0|error : 42");
        assert!(res.is_err());

        let res = evaluator.eval("false ? 0|error : 42");
        assert!(res.is_ok());
        assert_eq!(res.unwrap(), value!(42.0));
    }
}

Messung V0.5 in Prozent
C=92 H=96 G=93

¤ Dauer der Verarbeitung: 0.15 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.