diff --git a/README.md b/README.md index 363e55a..349e04d 100644 --- a/README.md +++ b/README.md @@ -361,60 +361,60 @@ Not all contexts support enabling or disabling builtin functions. Specifically the `EmptyContext` has builtin functions disabled by default, and they cannot be enabled. Symmetrically, the `EmptyContextWithBuiltinFunctions` has builtin functions enabled by default, and they cannot be disabled. -| Identifier | Argument Amount | Argument Types | Description | -|----------------------|-----------------|-------------------------------|-------------| -| `min` | >= 1 | Numeric | Returns the minimum of the arguments | -| `max` | >= 1 | Numeric | Returns the maximum of the arguments | -| `len` | 1 | String/Tuple | Returns the character length of a string, or the amount of elements in a tuple (not recursively) | -| `floor` | 1 | Numeric | Returns the largest integer less than or equal to a number | -| `round` | 1 | Numeric | Returns the nearest integer to a number. Rounds half-way cases away from 0.0 | -| `ceil` | 1 | Numeric | Returns the smallest integer greater than or equal to a number | -| `if` | 3 | Boolean, Any, Any | If the first argument is true, returns the second argument, otherwise, returns the third | -| `contains` | 2 | Tuple, any non-tuple | Returns true if second argument exists in first tuple argument. | -| `contains_any` | 2 | Tuple, Tuple of any non-tuple | Returns true if one of the values in the second tuple argument exists in first tuple argument. | -| `typeof` | 1 | Any | returns "string", "float", "int", "boolean", "tuple", or "empty" depending on the type of the argument | -| `math::is_nan` | 1 | Numeric | Returns true if the argument is the floating-point value NaN, false if it is another floating-point value, and throws an error if it is not a number | -| `math::is_finite` | 1 | Numeric | Returns true if the argument is a finite floating-point number, false otherwise | -| `math::is_infinite` | 1 | Numeric | Returns true if the argument is an infinite floating-point number, false otherwise | -| `math::is_normal` | 1 | Numeric | Returns true if the argument is a floating-point number that is neither zero, infinite, [subnormal](https://en.wikipedia.org/wiki/Subnormal_number), or NaN, false otherwise | -| `math::ln` | 1 | Numeric | Returns the natural logarithm of the number | -| `math::log` | 2 | Numeric, Numeric | Returns the logarithm of the number with respect to an arbitrary base | -| `math::log2` | 1 | Numeric | Returns the base 2 logarithm of the number | -| `math::log10` | 1 | Numeric | Returns the base 10 logarithm of the number | -| `math::exp` | 1 | Numeric | Returns `e^(number)`, (the exponential function) | -| `math::exp2` | 1 | Numeric | Returns `2^(number)` | -| `math::pow` | 2 | Numeric, Numeric | Raises a number to the power of the other number | -| `math::cos` | 1 | Numeric | Computes the cosine of a number (in radians) | -| `math::acos` | 1 | Numeric | Computes the arccosine of a number. The return value is in radians in the range [0, pi] or NaN if the number is outside the range [-1, 1] | -| `math::cosh` | 1 | Numeric | Hyperbolic cosine function | -| `math::acosh` | 1 | Numeric | Inverse hyperbolic cosine function | -| `math::sin` | 1 | Numeric | Computes the sine of a number (in radians) | -| `math::asin` | 1 | Numeric | Computes the arcsine of a number. The return value is in radians in the range [-pi/2, pi/2] or NaN if the number is outside the range [-1, 1] | -| `math::sinh` | 1 | Numeric | Hyperbolic sine function | -| `math::asinh` | 1 | Numeric | Inverse hyperbolic sine function | -| `math::tan` | 1 | Numeric | Computes the tangent of a number (in radians) | -| `math::atan` | 1 | Numeric | Computes the arctangent of a number. The return value is in radians in the range [-pi/2, pi/2] | -| `math::atan2` | 2 | Numeric, Numeric | Computes the four quadrant arctangent in radians | -| `math::tanh` | 1 | Numeric | Hyperbolic tangent function | -| `math::atanh` | 1 | Numeric | Inverse hyperbolic tangent function. | -| `math::sqrt` | 1 | Numeric | Returns the square root of a number. Returns NaN for a negative number | -| `math::cbrt` | 1 | Numeric | Returns the cube root of a number | -| `math::hypot` | 2 | Numeric | Calculates the length of the hypotenuse of a right-angle triangle given legs of length given by the two arguments | -| `math::abs` | 1 | Numeric | Returns the absolute value of a number, returning an integer if the argument was an integer, and a float otherwise | -| `str::regex_matches` | 2 | String, String | Returns true if the first argument matches the regex in the second argument (Requires `regex_support` feature flag) | -| `str::regex_replace` | 3 | String, String, String | Returns the first argument with all matches of the regex in the second argument replaced by the third argument (Requires `regex_support` feature flag) | -| `str::to_lowercase` | 1 | String | Returns the lower-case version of the string | -| `str::to_uppercase` | 1 | String | Returns the upper-case version of the string | -| `str::trim` | 1 | String | Strips whitespace from the start and the end of the string | -| `str::from` | >= 0 | Any | Returns passed value as string | -| `str::substring` | 3 | String, Int, Int | Returns a substring of the first argument, starting at the second argument and ending at the third argument. If the last argument is omitted, the substring extends to the end of the string | -| `bitand` | 2 | Int | Computes the bitwise and of the given integers | -| `bitor` | 2 | Int | Computes the bitwise or of the given integers | -| `bitxor` | 2 | Int | Computes the bitwise xor of the given integers | -| `bitnot` | 1 | Int | Computes the bitwise not of the given integer | -| `shl` | 2 | Int | Computes the given integer bitwise shifted left by the other given integer | -| `shr` | 2 | Int | Computes the given integer bitwise shifted right by the other given integer | -| `random` | 0 | Empty | Return a random float between 0 and 1. Requires the `rand` feature flag. | +| Identifier | Argument Amount | Argument Types | Description | +|----------------------|-----------------|-------------------------------------------------|-------------| +| `min` | >= 1 | Numeric | Returns the minimum of the arguments | +| `max` | >= 1 | Numeric | Returns the maximum of the arguments | +| `len` | 1 | String/Tuple/Array | Returns the character length of a string, or the amount of elements in a tuple (not recursively) | +| `floor` | 1 | Numeric | Returns the largest integer less than or equal to a number | +| `round` | 1 | Numeric | Returns the nearest integer to a number. Rounds half-way cases away from 0.0 | +| `ceil` | 1 | Numeric | Returns the smallest integer greater than or equal to a number | +| `if` | 3 | Boolean, Any, Any | If the first argument is true, returns the second argument, otherwise, returns the third | +| `contains` | 2 | Tuple/Array, any non-tuple/array | Returns true if second argument exists in first tuple or array argument. | +| `contains_any` | 2 | Tuple/Array, Tuple/Array of any non-tuple/array | Returns true if one of the values in the second tuple or array argument exists in first tuple or array argument. | +| `typeof` | 1 | Any | returns "string", "float", "int", "boolean", "tuple", or "empty" depending on the type of the argument | +| `math::is_nan` | 1 | Numeric | Returns true if the argument is the floating-point value NaN, false if it is another floating-point value, and throws an error if it is not a number | +| `math::is_finite` | 1 | Numeric | Returns true if the argument is a finite floating-point number, false otherwise | +| `math::is_infinite` | 1 | Numeric | Returns true if the argument is an infinite floating-point number, false otherwise | +| `math::is_normal` | 1 | Numeric | Returns true if the argument is a floating-point number that is neither zero, infinite, [subnormal](https://en.wikipedia.org/wiki/Subnormal_number), or NaN, false otherwise | +| `math::ln` | 1 | Numeric | Returns the natural logarithm of the number | +| `math::log` | 2 | Numeric, Numeric | Returns the logarithm of the number with respect to an arbitrary base | +| `math::log2` | 1 | Numeric | Returns the base 2 logarithm of the number | +| `math::log10` | 1 | Numeric | Returns the base 10 logarithm of the number | +| `math::exp` | 1 | Numeric | Returns `e^(number)`, (the exponential function) | +| `math::exp2` | 1 | Numeric | Returns `2^(number)` | +| `math::pow` | 2 | Numeric, Numeric | Raises a number to the power of the other number | +| `math::cos` | 1 | Numeric | Computes the cosine of a number (in radians) | +| `math::acos` | 1 | Numeric | Computes the arccosine of a number. The return value is in radians in the range [0, pi] or NaN if the number is outside the range [-1, 1] | +| `math::cosh` | 1 | Numeric | Hyperbolic cosine function | +| `math::acosh` | 1 | Numeric | Inverse hyperbolic cosine function | +| `math::sin` | 1 | Numeric | Computes the sine of a number (in radians) | +| `math::asin` | 1 | Numeric | Computes the arcsine of a number. The return value is in radians in the range [-pi/2, pi/2] or NaN if the number is outside the range [-1, 1] | +| `math::sinh` | 1 | Numeric | Hyperbolic sine function | +| `math::asinh` | 1 | Numeric | Inverse hyperbolic sine function | +| `math::tan` | 1 | Numeric | Computes the tangent of a number (in radians) | +| `math::atan` | 1 | Numeric | Computes the arctangent of a number. The return value is in radians in the range [-pi/2, pi/2] | +| `math::atan2` | 2 | Numeric, Numeric | Computes the four quadrant arctangent in radians | +| `math::tanh` | 1 | Numeric | Hyperbolic tangent function | +| `math::atanh` | 1 | Numeric | Inverse hyperbolic tangent function. | +| `math::sqrt` | 1 | Numeric | Returns the square root of a number. Returns NaN for a negative number | +| `math::cbrt` | 1 | Numeric | Returns the cube root of a number | +| `math::hypot` | 2 | Numeric | Calculates the length of the hypotenuse of a right-angle triangle given legs of length given by the two arguments | +| `math::abs` | 1 | Numeric | Returns the absolute value of a number, returning an integer if the argument was an integer, and a float otherwise | +| `str::regex_matches` | 2 | String, String | Returns true if the first argument matches the regex in the second argument (Requires `regex_support` feature flag) | +| `str::regex_replace` | 3 | String, String, String | Returns the first argument with all matches of the regex in the second argument replaced by the third argument (Requires `regex_support` feature flag) | +| `str::to_lowercase` | 1 | String | Returns the lower-case version of the string | +| `str::to_uppercase` | 1 | String | Returns the upper-case version of the string | +| `str::trim` | 1 | String | Strips whitespace from the start and the end of the string | +| `str::from` | >= 0 | Any | Returns passed value as string | +| `str::substring` | 3 | String, Int, Int | Returns a substring of the first argument, starting at the second argument and ending at the third argument. If the last argument is omitted, the substring extends to the end of the string | +| `bitand` | 2 | Int | Computes the bitwise and of the given integers | +| `bitor` | 2 | Int | Computes the bitwise or of the given integers | +| `bitxor` | 2 | Int | Computes the bitwise xor of the given integers | +| `bitnot` | 1 | Int | Computes the bitwise not of the given integer | +| `shl` | 2 | Int | Computes the given integer bitwise shifted left by the other given integer | +| `shr` | 2 | Int | Computes the given integer bitwise shifted right by the other given integer | +| `random` | 0 | Empty | Return a random float between 0 and 1. Requires the `rand` feature flag. | The `min` and `max` functions can deal with a mixture of integer and floating point arguments. If the maximum or minimum is an integer, then an integer is returned. @@ -435,6 +435,7 @@ Values are denoted as displayed in the following table. | `Value::Int` | `3`, `-9`, `0`, `135412`, `0xfe02`, `-0x1e` | | `Value::Float` | `3.`, `.35`, `1.00`, `0.5`, `123.554`, `23e4`, `-2e-3`, `3.54e+2` | | `Value::Tuple` | `(3, 55.0, false, ())`, `(1, 2)` | +| `Value::Array` | `{3, 55.0, false, ()}`, `{1}`, `{}` | | `Value::Empty` | `()` | Integers are internally represented as `i64`, and floating point numbers are represented as `f64`. diff --git a/src/error/display.rs b/src/error/display.rs index faa218a..33de572 100644 --- a/src/error/display.rs +++ b/src/error/display.rs @@ -41,6 +41,7 @@ impl fmt::Display for EvalexprError { ExpectedBoolean { actual } => { write!(f, "Expected a Value::Boolean, but got {:?}.", actual) }, + ExpectedArray { actual } => write!(f, "Expected a Value::Array, but got {:?}.", actual), ExpectedTuple { actual } => write!(f, "Expected a Value::Tuple, but got {:?}.", actual), ExpectedFixedLengthTuple { expected_length, @@ -60,6 +61,11 @@ impl fmt::Display for EvalexprError { expected_length.end(), actual ), + ExpectedVec { actual } => write!( + f, + "Expected a Value which can be interpreted as vec, but got {:?}.", + actual + ), ExpectedEmpty { actual } => write!(f, "Expected a Value::Empty, but got {:?}.", actual), AppendedToLeafNode => write!(f, "Tried to append a node to a leaf node."), PrecedenceViolation => write!( @@ -86,6 +92,8 @@ impl fmt::Display for EvalexprError { ), UnmatchedLBrace => write!(f, "Found an unmatched opening parenthesis '('."), UnmatchedRBrace => write!(f, "Found an unmatched closing parenthesis ')'."), + UnmatchedLCurlyBrace => write!(f, "Found an unmatched opening curly brace '{{'."), + UnmatchedRCurlyBrace => write!(f, "Found an unmatched closing curly brace '}}'."), UnmatchedDoubleQuote => write!(f, "Found an unmatched double quote '\"'"), MissingOperatorOutsideOfBrace { .. } => write!( f, diff --git a/src/error/mod.rs b/src/error/mod.rs index 7de0651..f5faa88 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -79,6 +79,12 @@ pub enum EvalexprError { actual: Value, }, + /// A array value was expected. + ExpectedArray { + /// The actual value. + actual: Value, + }, + /// A tuple value of a certain length was expected. ExpectedFixedLengthTuple { /// The expected length. @@ -95,6 +101,12 @@ pub enum EvalexprError { actual: Value, }, + /// A value which can be interpreted as vec was expected + ExpectedVec { + /// The actual value. + actual: Value, + }, + /// An empty value was expected. ExpectedEmpty { /// The actual value. @@ -139,6 +151,12 @@ pub enum EvalexprError { /// A closing brace without a matching opening brace was found. UnmatchedRBrace, + /// An opening curly brace without a matching closing brace was found. + UnmatchedLCurlyBrace, + + /// A closing curly brace without a matching opening brace was found. + UnmatchedRCurlyBrace, + /// A double quote without a matching second double quote was found. UnmatchedDoubleQuote, @@ -296,6 +314,11 @@ impl EvalexprError { EvalexprError::ExpectedTuple { actual } } + /// Constructs `EvalexprError::ExpectedArray{actual}`. + pub fn expected_array(actual: Value) -> Self { + EvalexprError::ExpectedArray { actual } + } + /// Constructs `EvalexprError::ExpectedFixedLenTuple{expected_len, actual}`. pub fn expected_fixed_len_tuple(expected_len: usize, actual: Value) -> Self { EvalexprError::ExpectedFixedLengthTuple { @@ -312,6 +335,11 @@ impl EvalexprError { } } + /// Constructs `EvalexprError::ExpectedVec{actual}`. + pub fn expected_vec(actual: Value) -> Self { + EvalexprError::ExpectedVec { actual } + } + /// Constructs `EvalexprError::ExpectedEmpty{actual}`. pub fn expected_empty(actual: Value) -> Self { EvalexprError::ExpectedEmpty { actual } @@ -325,6 +353,7 @@ impl EvalexprError { ValueType::Float => Self::expected_float(actual), ValueType::Boolean => Self::expected_boolean(actual), ValueType::Tuple => Self::expected_tuple(actual), + ValueType::Array => Self::expected_array(actual), ValueType::Empty => Self::expected_empty(actual), } } @@ -436,6 +465,10 @@ mod tests { EvalexprError::expected_type(&Value::Tuple(vec![]), Value::Empty), EvalexprError::expected_tuple(Value::Empty) ); + assert_eq!( + EvalexprError::expected_type(&Value::Array(vec![]), Value::Empty), + EvalexprError::expected_array(Value::Empty) + ); assert_eq!( EvalexprError::expected_type(&Value::Empty, Value::String("abc".to_string())), EvalexprError::expected_empty(Value::String("abc".to_string())) diff --git a/src/function/builtin.rs b/src/function/builtin.rs index be55e93..14d754a 100644 --- a/src/function/builtin.rs +++ b/src/function/builtin.rs @@ -104,6 +104,7 @@ pub fn builtin_function(identifier: &str) -> Option { Value::Int(_) => "int", Value::Boolean(_) => "boolean", Value::Tuple(_) => "tuple", + Value::Array(_) => "array", Value::Empty => "empty", } .into()) @@ -159,67 +160,55 @@ pub fn builtin_function(identifier: &str) -> Option { })), "contains" => Some(Function::new(move |argument| { let arguments = argument.as_fixed_len_tuple(2)?; - if let (Value::Tuple(a), b) = (&arguments[0].clone(), &arguments[1].clone()) { - if let Value::String(_) | Value::Int(_) | Value::Float(_) | Value::Boolean(_) = b { - Ok(a.contains(b).into()) - } else { - Err(EvalexprError::type_error( - b.clone(), - vec![ - ValueType::String, - ValueType::Int, - ValueType::Float, - ValueType::Boolean, - ], - )) - } + let (a, b) = (arguments[0].as_slice()?, &arguments[1].clone()); + if let Value::String(_) | Value::Int(_) | Value::Float(_) | Value::Boolean(_) = b { + Ok(a.contains(b).into()) } else { - Err(EvalexprError::expected_tuple(arguments[0].clone())) + Err(EvalexprError::type_error( + b.clone(), + vec![ + ValueType::String, + ValueType::Int, + ValueType::Float, + ValueType::Boolean, + ], + )) } })), "contains_any" => Some(Function::new(move |argument| { let arguments = argument.as_fixed_len_tuple(2)?; - if let (Value::Tuple(a), b) = (&arguments[0].clone(), &arguments[1].clone()) { - if let Value::Tuple(b) = b { - let mut contains = false; - for value in b { - if let Value::String(_) - | Value::Int(_) - | Value::Float(_) - | Value::Boolean(_) = value - { - if a.contains(value) { - contains = true; - } - } else { - return Err(EvalexprError::type_error( - value.clone(), - vec![ - ValueType::String, - ValueType::Int, - ValueType::Float, - ValueType::Boolean, - ], - )); - } + let (a, b) = (arguments[0].as_slice()?, arguments[1].as_slice()?); + let mut contains = false; + for value in b { + if let Value::String(_) | Value::Int(_) | Value::Float(_) | Value::Boolean(_) = + value + { + if a.contains(value) { + contains = true; } - Ok(contains.into()) } else { - Err(EvalexprError::expected_tuple(b.clone())) + return Err(EvalexprError::type_error( + value.clone(), + vec![ + ValueType::String, + ValueType::Int, + ValueType::Float, + ValueType::Boolean, + ], + )); } - } else { - Err(EvalexprError::expected_tuple(arguments[0].clone())) } + Ok(contains.into()) })), "len" => Some(Function::new(|argument| { if let Ok(subject) = argument.as_string() { Ok(Value::from(subject.len() as IntType)) - } else if let Ok(subject) = argument.as_tuple() { + } else if let Ok(subject) = argument.as_slice() { Ok(Value::from(subject.len() as IntType)) } else { Err(EvalexprError::type_error( argument.clone(), - vec![ValueType::String, ValueType::Tuple], + vec![ValueType::String, ValueType::Tuple, ValueType::Array], )) } })), diff --git a/src/interface/mod.rs b/src/interface/mod.rs index 332b704..bbbea15 100644 --- a/src/interface/mod.rs +++ b/src/interface/mod.rs @@ -1,6 +1,8 @@ use crate::{ - token, tree, value::TupleType, Context, ContextWithMutableVariables, EmptyType, EvalexprError, - EvalexprResult, FloatType, HashMapContext, IntType, Node, Value, EMPTY_VALUE, + token, tree, + value::{ArrayType, TupleType}, + Context, ContextWithMutableVariables, EmptyType, EvalexprError, EvalexprResult, FloatType, + HashMapContext, IntType, Node, Value, EMPTY_VALUE, }; /// Evaluate the given expression string. @@ -130,6 +132,13 @@ pub fn eval_tuple(string: &str) -> EvalexprResult { eval_tuple_with_context_mut(string, &mut HashMapContext::new()) } +/// Evaluate the given expression string into a array. +/// +/// *See the [crate doc](index.html) for more examples and explanations of the expression format.* +pub fn eval_array(string: &str) -> EvalexprResult { + eval_array_with_context_mut(string, &mut HashMapContext::new()) +} + /// Evaluate the given expression string into an empty value. /// /// *See the [crate doc](index.html) for more examples and explanations of the expression format.* @@ -208,6 +217,17 @@ pub fn eval_tuple_with_context(string: &str, context: &C) -> Evalexp } } +/// Evaluate the given expression string into an array with the given context. +/// +/// *See the [crate doc](index.html) for more examples and explanations of the expression format.* +pub fn eval_array_with_context(string: &str, context: &C) -> EvalexprResult { + match eval_with_context(string, context) { + Ok(Value::Array(array)) => Ok(array), + Ok(value) => Err(EvalexprError::expected_array(value)), + Err(error) => Err(error), + } +} + /// Evaluate the given expression string into an empty value with the given context. /// /// *See the [crate doc](index.html) for more examples and explanations of the expression format.* @@ -305,6 +325,20 @@ pub fn eval_tuple_with_context_mut( } } +/// Evaluate the given expression string into a array with the given mutable context. +/// +/// *See the [crate doc](index.html) for more examples and explanations of the expression format.* +pub fn eval_array_with_context_mut( + string: &str, + context: &mut C, +) -> EvalexprResult { + match eval_with_context_mut(string, context) { + Ok(Value::Array(array)) => Ok(array), + Ok(value) => Err(EvalexprError::expected_array(value)), + Err(error) => Err(error), + } +} + /// Evaluate the given expression string into an empty value with the given mutable context. /// /// *See the [crate doc](index.html) for more examples and explanations of the expression format.* diff --git a/src/lib.rs b/src/lib.rs index 763e94b..456b957 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -342,60 +342,60 @@ //! Specifically the `EmptyContext` has builtin functions disabled by default, and they cannot be enabled. //! Symmetrically, the `EmptyContextWithBuiltinFunctions` has builtin functions enabled by default, and they cannot be disabled. //! -//! | Identifier | Argument Amount | Argument Types | Description | -//! |----------------------|-----------------|-------------------------------|-------------| -//! | `min` | >= 1 | Numeric | Returns the minimum of the arguments | -//! | `max` | >= 1 | Numeric | Returns the maximum of the arguments | -//! | `len` | 1 | String/Tuple | Returns the character length of a string, or the amount of elements in a tuple (not recursively) | -//! | `floor` | 1 | Numeric | Returns the largest integer less than or equal to a number | -//! | `round` | 1 | Numeric | Returns the nearest integer to a number. Rounds half-way cases away from 0.0 | -//! | `ceil` | 1 | Numeric | Returns the smallest integer greater than or equal to a number | -//! | `if` | 3 | Boolean, Any, Any | If the first argument is true, returns the second argument, otherwise, returns the third | -//! | `contains` | 2 | Tuple, any non-tuple | Returns true if second argument exists in first tuple argument. | -//! | `contains_any` | 2 | Tuple, Tuple of any non-tuple | Returns true if one of the values in the second tuple argument exists in first tuple argument. | -//! | `typeof` | 1 | Any | returns "string", "float", "int", "boolean", "tuple", or "empty" depending on the type of the argument | -//! | `math::is_nan` | 1 | Numeric | Returns true if the argument is the floating-point value NaN, false if it is another floating-point value, and throws an error if it is not a number | -//! | `math::is_finite` | 1 | Numeric | Returns true if the argument is a finite floating-point number, false otherwise | -//! | `math::is_infinite` | 1 | Numeric | Returns true if the argument is an infinite floating-point number, false otherwise | -//! | `math::is_normal` | 1 | Numeric | Returns true if the argument is a floating-point number that is neither zero, infinite, [subnormal](https://en.wikipedia.org/wiki/Subnormal_number), or NaN, false otherwise | -//! | `math::ln` | 1 | Numeric | Returns the natural logarithm of the number | -//! | `math::log` | 2 | Numeric, Numeric | Returns the logarithm of the number with respect to an arbitrary base | -//! | `math::log2` | 1 | Numeric | Returns the base 2 logarithm of the number | -//! | `math::log10` | 1 | Numeric | Returns the base 10 logarithm of the number | -//! | `math::exp` | 1 | Numeric | Returns `e^(number)`, (the exponential function) | -//! | `math::exp2` | 1 | Numeric | Returns `2^(number)` | -//! | `math::pow` | 2 | Numeric, Numeric | Raises a number to the power of the other number | -//! | `math::cos` | 1 | Numeric | Computes the cosine of a number (in radians) | -//! | `math::acos` | 1 | Numeric | Computes the arccosine of a number. The return value is in radians in the range [0, pi] or NaN if the number is outside the range [-1, 1] | -//! | `math::cosh` | 1 | Numeric | Hyperbolic cosine function | -//! | `math::acosh` | 1 | Numeric | Inverse hyperbolic cosine function | -//! | `math::sin` | 1 | Numeric | Computes the sine of a number (in radians) | -//! | `math::asin` | 1 | Numeric | Computes the arcsine of a number. The return value is in radians in the range [-pi/2, pi/2] or NaN if the number is outside the range [-1, 1] | -//! | `math::sinh` | 1 | Numeric | Hyperbolic sine function | -//! | `math::asinh` | 1 | Numeric | Inverse hyperbolic sine function | -//! | `math::tan` | 1 | Numeric | Computes the tangent of a number (in radians) | -//! | `math::atan` | 1 | Numeric | Computes the arctangent of a number. The return value is in radians in the range [-pi/2, pi/2] | -//! | `math::atan2` | 2 | Numeric, Numeric | Computes the four quadrant arctangent in radians | -//! | `math::tanh` | 1 | Numeric | Hyperbolic tangent function | -//! | `math::atanh` | 1 | Numeric | Inverse hyperbolic tangent function. | -//! | `math::sqrt` | 1 | Numeric | Returns the square root of a number. Returns NaN for a negative number | -//! | `math::cbrt` | 1 | Numeric | Returns the cube root of a number | -//! | `math::hypot` | 2 | Numeric | Calculates the length of the hypotenuse of a right-angle triangle given legs of length given by the two arguments | -//! | `math::abs` | 1 | Numeric | Returns the absolute value of a number, returning an integer if the argument was an integer, and a float otherwise | -//! | `str::regex_matches` | 2 | String, String | Returns true if the first argument matches the regex in the second argument (Requires `regex_support` feature flag) | -//! | `str::regex_replace` | 3 | String, String, String | Returns the first argument with all matches of the regex in the second argument replaced by the third argument (Requires `regex_support` feature flag) | -//! | `str::to_lowercase` | 1 | String | Returns the lower-case version of the string | -//! | `str::to_uppercase` | 1 | String | Returns the upper-case version of the string | -//! | `str::trim` | 1 | String | Strips whitespace from the start and the end of the string | -//! | `str::from` | >= 0 | Any | Returns passed value as string | -//! | `str::substring` | 3 | String, Int, Int | Returns a substring of the first argument, starting at the second argument and ending at the third argument. If the last argument is omitted, the substring extends to the end of the string | -//! | `bitand` | 2 | Int | Computes the bitwise and of the given integers | -//! | `bitor` | 2 | Int | Computes the bitwise or of the given integers | -//! | `bitxor` | 2 | Int | Computes the bitwise xor of the given integers | -//! | `bitnot` | 1 | Int | Computes the bitwise not of the given integer | -//! | `shl` | 2 | Int | Computes the given integer bitwise shifted left by the other given integer | -//! | `shr` | 2 | Int | Computes the given integer bitwise shifted right by the other given integer | -//! | `random` | 0 | Empty | Return a random float between 0 and 1. Requires the `rand` feature flag. | +//! | Identifier | Argument Amount | Argument Types | Description | +//! |----------------------|-----------------|-------------------------------------------------|-------------| +//! | `min` | >= 1 | Numeric | Returns the minimum of the arguments | +//! | `max` | >= 1 | Numeric | Returns the maximum of the arguments | +//! | `len` | 1 | String/Tuple/Array | Returns the character length of a string, or the amount of elements in a tuple (not recursively) | +//! | `floor` | 1 | Numeric | Returns the largest integer less than or equal to a number | +//! | `round` | 1 | Numeric | Returns the nearest integer to a number. Rounds half-way cases away from 0.0 | +//! | `ceil` | 1 | Numeric | Returns the smallest integer greater than or equal to a number | +//! | `if` | 3 | Boolean, Any, Any | If the first argument is true, returns the second argument, otherwise, returns the third | +//! | `contains` | 2 | Tuple/Array, any non-tuple/array | Returns true if second argument exists in first tuple or array argument. | +//! | `contains_any` | 2 | Tuple/Array, Tuple/Array of any non-tuple/array | Returns true if one of the values in the second tuple or array argument exists in first tuple or array argument. | +//! | `typeof` | 1 | Any | returns "string", "float", "int", "boolean", "tuple", or "empty" depending on the type of the argument | +//! | `math::is_nan` | 1 | Numeric | Returns true if the argument is the floating-point value NaN, false if it is another floating-point value, and throws an error if it is not a number | +//! | `math::is_finite` | 1 | Numeric | Returns true if the argument is a finite floating-point number, false otherwise | +//! | `math::is_infinite` | 1 | Numeric | Returns true if the argument is an infinite floating-point number, false otherwise | +//! | `math::is_normal` | 1 | Numeric | Returns true if the argument is a floating-point number that is neither zero, infinite, [subnormal](https://en.wikipedia.org/wiki/Subnormal_number), or NaN, false otherwise | +//! | `math::ln` | 1 | Numeric | Returns the natural logarithm of the number | +//! | `math::log` | 2 | Numeric, Numeric | Returns the logarithm of the number with respect to an arbitrary base | +//! | `math::log2` | 1 | Numeric | Returns the base 2 logarithm of the number | +//! | `math::log10` | 1 | Numeric | Returns the base 10 logarithm of the number | +//! | `math::exp` | 1 | Numeric | Returns `e^(number)`, (the exponential function) | +//! | `math::exp2` | 1 | Numeric | Returns `2^(number)` | +//! | `math::pow` | 2 | Numeric, Numeric | Raises a number to the power of the other number | +//! | `math::cos` | 1 | Numeric | Computes the cosine of a number (in radians) | +//! | `math::acos` | 1 | Numeric | Computes the arccosine of a number. The return value is in radians in the range [0, pi] or NaN if the number is outside the range [-1, 1] | +//! | `math::cosh` | 1 | Numeric | Hyperbolic cosine function | +//! | `math::acosh` | 1 | Numeric | Inverse hyperbolic cosine function | +//! | `math::sin` | 1 | Numeric | Computes the sine of a number (in radians) | +//! | `math::asin` | 1 | Numeric | Computes the arcsine of a number. The return value is in radians in the range [-pi/2, pi/2] or NaN if the number is outside the range [-1, 1] | +//! | `math::sinh` | 1 | Numeric | Hyperbolic sine function | +//! | `math::asinh` | 1 | Numeric | Inverse hyperbolic sine function | +//! | `math::tan` | 1 | Numeric | Computes the tangent of a number (in radians) | +//! | `math::atan` | 1 | Numeric | Computes the arctangent of a number. The return value is in radians in the range [-pi/2, pi/2] | +//! | `math::atan2` | 2 | Numeric, Numeric | Computes the four quadrant arctangent in radians | +//! | `math::tanh` | 1 | Numeric | Hyperbolic tangent function | +//! | `math::atanh` | 1 | Numeric | Inverse hyperbolic tangent function. | +//! | `math::sqrt` | 1 | Numeric | Returns the square root of a number. Returns NaN for a negative number | +//! | `math::cbrt` | 1 | Numeric | Returns the cube root of a number | +//! | `math::hypot` | 2 | Numeric | Calculates the length of the hypotenuse of a right-angle triangle given legs of length given by the two arguments | +//! | `math::abs` | 1 | Numeric | Returns the absolute value of a number, returning an integer if the argument was an integer, and a float otherwise | +//! | `str::regex_matches` | 2 | String, String | Returns true if the first argument matches the regex in the second argument (Requires `regex_support` feature flag) | +//! | `str::regex_replace` | 3 | String, String, String | Returns the first argument with all matches of the regex in the second argument replaced by the third argument (Requires `regex_support` feature flag) | +//! | `str::to_lowercase` | 1 | String | Returns the lower-case version of the string | +//! | `str::to_uppercase` | 1 | String | Returns the upper-case version of the string | +//! | `str::trim` | 1 | String | Strips whitespace from the start and the end of the string | +//! | `str::from` | >= 0 | Any | Returns passed value as string | +//! | `str::substring` | 3 | String, Int, Int | Returns a substring of the first argument, starting at the second argument and ending at the third argument. If the last argument is omitted, the substring extends to the end of the string | +//! | `bitand` | 2 | Int | Computes the bitwise and of the given integers | +//! | `bitor` | 2 | Int | Computes the bitwise or of the given integers | +//! | `bitxor` | 2 | Int | Computes the bitwise xor of the given integers | +//! | `bitnot` | 1 | Int | Computes the bitwise not of the given integer | +//! | `shl` | 2 | Int | Computes the given integer bitwise shifted left by the other given integer | +//! | `shr` | 2 | Int | Computes the given integer bitwise shifted right by the other given integer | +//! | `random` | 0 | Empty | Return a random float between 0 and 1. Requires the `rand` feature flag. | //! //! The `min` and `max` functions can deal with a mixture of integer and floating point arguments. //! If the maximum or minimum is an integer, then an integer is returned. @@ -416,6 +416,7 @@ //! | `Value::Int` | `3`, `-9`, `0`, `135412`, `0xfe02`, `-0x1e` | //! | `Value::Float` | `3.`, `.35`, `1.00`, `0.5`, `123.554`, `23e4`, `-2e-3`, `3.54e+2` | //! | `Value::Tuple` | `(3, 55.0, false, ())`, `(1, 2)` | +//! | `Value::Array` | `{3, 55.0, false, ()}`, `{1}`, `{}` | //! | `Value::Empty` | `()` | //! //! Integers are internally represented as `i64`, and floating point numbers are represented as `f64`. diff --git a/src/operator/display.rs b/src/operator/display.rs index 12351dd..6f55e49 100644 --- a/src/operator/display.rs +++ b/src/operator/display.rs @@ -38,6 +38,7 @@ impl Display for Operator { OrAssign => write!(f, " ||= "), Tuple => write!(f, ", "), + Array => write!(f, "{{}} "), Chain => write!(f, "; "), Const { value } => write!(f, "{}", value), diff --git a/src/operator/mod.rs b/src/operator/mod.rs index 386357a..b5ea642 100644 --- a/src/operator/mod.rs +++ b/src/operator/mod.rs @@ -66,6 +66,8 @@ pub enum Operator { /// An n-ary tuple constructor. Tuple, + /// An n-ary array. + Array, /// An n-ary subexpression chain. Chain, @@ -129,6 +131,7 @@ impl Operator { | AndAssign | OrAssign => 50, Tuple => 40, + Array => 200, Chain => 0, Const { .. } => 200, @@ -164,7 +167,7 @@ impl Operator { Add | Sub | Mul | Div | Mod | Exp | Eq | Neq | Gt | Lt | Geq | Leq | And | Or | Assign | AddAssign | SubAssign | MulAssign | DivAssign | ModAssign | ExpAssign | AndAssign | OrAssign => Some(2), - Tuple | Chain => None, + Tuple | Chain | Array => None, Not | Neg | RootNode => Some(1), Const { .. } => Some(0), VariableIdentifierWrite { .. } | VariableIdentifierRead { .. } => Some(0), @@ -426,6 +429,7 @@ impl Operator { Assign | AddAssign | SubAssign | MulAssign | DivAssign | ModAssign | ExpAssign | AndAssign | OrAssign => Err(EvalexprError::ContextNotMutable), Tuple => Ok(Value::Tuple(arguments.into())), + Array => Ok(Value::Array(arguments.into())), Chain => { if arguments.is_empty() { return Err(EvalexprError::wrong_operator_argument_amount(0, 1)); diff --git a/src/token/display.rs b/src/token/display.rs index 35f2087..b0e2177 100644 --- a/src/token/display.rs +++ b/src/token/display.rs @@ -42,6 +42,8 @@ impl fmt::Display for Token { // Special Comma => write!(f, ","), Semicolon => write!(f, ";"), + LCurlyBrace => write!(f, "{{"), + RCurlyBrace => write!(f, "}}"), // Values => write!(f, ""), Variables and Functions Identifier(identifier) => identifier.fmt(f), diff --git a/src/token/mod.rs b/src/token/mod.rs index ea22890..0e24f5f 100644 --- a/src/token/mod.rs +++ b/src/token/mod.rs @@ -44,6 +44,8 @@ pub enum Token { // Special Comma, Semicolon, + LCurlyBrace, + RCurlyBrace, // Values, Variables and Functions Identifier(String), @@ -103,6 +105,8 @@ fn char_to_partial_token(c: char) -> PartialToken { ',' => PartialToken::Token(Token::Comma), ';' => PartialToken::Token(Token::Semicolon), + '{' => PartialToken::Token(Token::LCurlyBrace), + '}' => PartialToken::Token(Token::RCurlyBrace), '=' => PartialToken::Eq, '!' => PartialToken::ExclamationMark, @@ -144,6 +148,8 @@ impl Token { Token::LBrace => true, Token::RBrace => false, + Token::LCurlyBrace => false, + Token::RCurlyBrace => false, Token::Comma => false, Token::Semicolon => false, @@ -191,6 +197,8 @@ impl Token { Token::Comma => false, Token::Semicolon => false, + Token::LCurlyBrace => false, + Token::RCurlyBrace => false, Token::Assign => false, Token::PlusAssign => false, @@ -509,7 +517,7 @@ mod tests { #[test] fn test_token_display() { let token_string = - "+ - * / % ^ == != > < >= <= && || ! ( ) = += -= *= /= %= ^= &&= ||= , ; "; + "+ - * / % ^ == != > < >= <= && || ! ( ) = += -= *= /= %= ^= &&= ||= , ; { } "; let tokens = tokenize(token_string).unwrap(); let mut result_string = String::new(); diff --git a/src/tree/mod.rs b/src/tree/mod.rs index e835fd3..bb41192 100644 --- a/src/tree/mod.rs +++ b/src/tree/mod.rs @@ -1,6 +1,6 @@ use crate::{ token::Token, - value::{TupleType, EMPTY_VALUE}, + value::{ArrayType, TupleType, EMPTY_VALUE}, Context, ContextWithMutableVariables, EmptyType, FloatType, HashMapContext, IntType, }; @@ -417,6 +417,17 @@ impl Node { } } + /// Evaluates the operator tree rooted at this node into a array with an the given context. + /// + /// Fails, if one of the operators in the expression tree fails. + pub fn eval_array_with_context(&self, context: &C) -> EvalexprResult { + match self.eval_with_context(context) { + Ok(Value::Array(array)) => Ok(array), + Ok(value) => Err(EvalexprError::expected_array(value)), + Err(error) => Err(error), + } + } + /// Evaluates the operator tree rooted at this node into an empty value with an the given context. /// /// Fails, if one of the operators in the expression tree fails. @@ -514,6 +525,20 @@ impl Node { } } + /// Evaluates the operator tree rooted at this node into a array with an the given mutable context. + /// + /// Fails, if one of the operators in the expression tree fails. + pub fn eval_array_with_context_mut( + &self, + context: &mut C, + ) -> EvalexprResult { + match self.eval_with_context_mut(context) { + Ok(Value::Array(array)) => Ok(array), + Ok(value) => Err(EvalexprError::expected_array(value)), + Err(error) => Err(error), + } + } + /// Evaluates the operator tree rooted at this node into an empty value with an the given mutable context. /// /// Fails, if one of the operators in the expression tree fails. @@ -571,6 +596,13 @@ impl Node { self.eval_tuple_with_context_mut(&mut HashMapContext::new()) } + /// Evaluates the operator tree rooted at this node into an array. + /// + /// Fails, if one of the operators in the expression tree fails. + pub fn eval_array(&self) -> EvalexprResult { + self.eval_array_with_context_mut(&mut HashMapContext::new()) + } + /// Evaluates the operator tree rooted at this node into an empty value. /// /// Fails, if one of the operators in the expression tree fails. @@ -765,10 +797,17 @@ fn collapse_all_sequences(root_stack: &mut Vec) -> EvalexprResult<()> { Ok(()) } +#[derive(PartialEq, Eq, Debug, Clone, Copy)] +enum ParsingContext { + InsideBraces, + InsideArray, +} + pub(crate) fn tokens_to_operator_tree(tokens: Vec) -> EvalexprResult { let mut root_stack = vec![Node::root_node()]; let mut last_token_is_rightsided_value = false; let mut token_iter = tokens.iter().peekable(); + let mut parsing_context_stack: Vec = vec![]; while let Some(token) = token_iter.next().cloned() { let next = token_iter.peek().cloned(); @@ -799,14 +838,53 @@ pub(crate) fn tokens_to_operator_tree(tokens: Vec) -> EvalexprResult { root_stack.push(Node::root_node()); + parsing_context_stack.push(ParsingContext::InsideBraces); None }, Token::RBrace => { - if root_stack.len() <= 1 { + if let Some(parsing_context) = parsing_context_stack.pop() { + match parsing_context { + ParsingContext::InsideBraces => { + collapse_all_sequences(&mut root_stack)?; + root_stack.pop() + }, + ParsingContext::InsideArray => { + return Err(EvalexprError::UnmatchedLCurlyBrace) + }, + } + } else { return Err(EvalexprError::UnmatchedRBrace); + } + }, + Token::LCurlyBrace => { + root_stack.push(Node::root_node()); + parsing_context_stack.push(ParsingContext::InsideArray); + None + }, + Token::RCurlyBrace => { + if let Some(parsing_context) = parsing_context_stack.pop() { + match parsing_context { + ParsingContext::InsideBraces => { + return Err(EvalexprError::UnmatchedLBrace); + }, + ParsingContext::InsideArray => { + collapse_all_sequences(&mut root_stack)?; + let mut arr_node = Node::new(Operator::Array); + if let Some(mut root) = root_stack.pop() { + if let Some(real_root) = root.children.get(0) { + if real_root.operator().is_sequence() { + let real_node = root.children.remove(0); + arr_node.children = real_node.children; + } else { + arr_node.children = root.children; + } + } + } + Some(arr_node) + }, + } } else { - collapse_all_sequences(&mut root_stack)?; - root_stack.pop() + return Err(EvalexprError::UnmatchedRCurlyBrace); } }, @@ -820,7 +898,18 @@ pub(crate) fn tokens_to_operator_tree(tokens: Vec) -> EvalexprResult Some(Node::new(Operator::AndAssign)), Token::OrAssign => Some(Node::new(Operator::OrAssign)), - Token::Comma => Some(Node::new(Operator::Tuple)), + Token::Comma => { + // This if-statement is here to ignore trailing comma at the end of array. + if matches!( + parsing_context_stack.last(), + Some(ParsingContext::InsideArray) + ) && matches!(next, Some(Token::RCurlyBrace)) + { + None + } else { + Some(Node::new(Operator::Tuple)) + } + }, Token::Semicolon => Some(Node::new(Operator::Chain)), Token::Identifier(identifier) => { @@ -897,7 +986,16 @@ pub(crate) fn tokens_to_operator_tree(tokens: Vec) -> EvalexprResult) -> EvalexprResult 1 { - Err(EvalexprError::UnmatchedLBrace) + Err( + if matches!( + parsing_context_stack.pop(), + Some(ParsingContext::InsideArray) + ) { + EvalexprError::UnmatchedLCurlyBrace + } else { + EvalexprError::UnmatchedLBrace + }, + ) } else if let Some(root) = root_stack.pop() { Ok(root) } else { - Err(EvalexprError::UnmatchedRBrace) + Err( + if matches!( + parsing_context_stack.pop(), + Some(ParsingContext::InsideArray) + ) { + EvalexprError::UnmatchedRCurlyBrace + } else { + EvalexprError::UnmatchedRBrace + }, + ) } } diff --git a/src/value/display.rs b/src/value/display.rs index 599baa4..af5ef63 100644 --- a/src/value/display.rs +++ b/src/value/display.rs @@ -22,6 +22,19 @@ impl Display for Value { } write!(f, ")") }, + Value::Array(array) => { + write!(f, "{{")?; + let mut once = false; + for value in array { + if once { + write!(f, ", ")?; + } else { + once = true; + } + value.fmt(f)?; + } + write!(f, "}}") + }, Value::Empty => write!(f, "()"), } } diff --git a/src/value/mod.rs b/src/value/mod.rs index 6cd3371..705c1f1 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -13,6 +13,9 @@ pub type FloatType = f64; /// The type used to represent tuples in `Value::Tuple`. pub type TupleType = Vec; +/// The type used to represent tuples in `Value::Array`. +pub type ArrayType = Vec; + /// The type used to represent empty values in `Value::Empty`. pub type EmptyType = (); @@ -34,6 +37,8 @@ pub enum Value { Boolean(bool), /// A tuple value. Tuple(TupleType), + /// An array value. + Array(ArrayType), /// An empty value. Empty, } @@ -68,6 +73,11 @@ impl Value { matches!(self, Value::Tuple(_)) } + /// Returns true if `self` is a `Value::Array`. + pub fn is_array(&self) -> bool { + matches!(self, Value::Array(_)) + } + /// Returns true if `self` is a `Value::Empty`. pub fn is_empty(&self) -> bool { matches!(self, Value::Empty) @@ -154,6 +164,26 @@ impl Value { } } + /// Clones the value stored in `self` as `Vec`, or returns `Err` if `self` is not a + /// `Value::Tuple` or `Value::Array` + pub fn as_vec(&self) -> EvalexprResult> { + match self { + Value::Tuple(tuple) => Ok(tuple.clone()), + Value::Array(array) => Ok(array.clone()), + value => Err(EvalexprError::expected_vec(value.clone())), + } + } + + /// Returns the value stored in `self` as `&[Value]`, or returns `Err` if `self` is not a + /// `Value::Tuple` or `Value::Array` + pub fn as_slice(&self) -> EvalexprResult<&[Value]> { + match self { + Value::Tuple(tuple) => Ok(tuple.as_slice()), + Value::Array(array) => Ok(array.as_slice()), + value => Err(EvalexprError::expected_vec(value.clone())), + } + } + /// Returns `()`, or returns`Err` if `self` is not a `Value::Tuple`. pub fn as_empty(&self) -> EvalexprResult<()> { match self { @@ -285,7 +315,10 @@ impl TryFrom for () { #[cfg(test)] mod tests { - use crate::value::{TupleType, Value}; + use crate::{ + value::{ArrayType, TupleType, Value}, + EvalexprError, + }; #[test] fn test_value_conversions() { @@ -300,6 +333,20 @@ mod tests { Value::from(TupleType::new()).as_tuple(), Ok(TupleType::new()) ); + assert_eq!( + Value::from(2).as_vec(), + Err(EvalexprError::expected_vec(Value::from(2))) + ); + assert_eq!(Value::from(TupleType::new()).as_vec(), Ok(vec![])); + assert_eq!(Value::Array(ArrayType::new()).as_vec(), Ok(vec![])); + assert_eq!( + Value::from(TupleType::new()).as_slice(), + Ok(vec![].as_slice()) + ); + assert_eq!( + Value::Array(ArrayType::new()).as_slice(), + Ok(vec![].as_slice()) + ); } #[test] @@ -309,5 +356,6 @@ mod tests { assert!(Value::from(3.3).is_float()); assert!(Value::from(true).is_boolean()); assert!(Value::from(TupleType::new()).is_tuple()); + assert!(Value::Array(ArrayType::new()).is_array()); } } diff --git a/src/value/value_type.rs b/src/value/value_type.rs index 4ea7395..9430043 100644 --- a/src/value/value_type.rs +++ b/src/value/value_type.rs @@ -13,6 +13,8 @@ pub enum ValueType { Boolean, /// The `Value::Tuple` type. Tuple, + /// The `Value::Array` type. + Array, /// The `Value::Empty` type. Empty, } @@ -25,6 +27,7 @@ impl From<&Value> for ValueType { Value::Int(_) => ValueType::Int, Value::Boolean(_) => ValueType::Boolean, Value::Tuple(_) => ValueType::Tuple, + Value::Array(_) => ValueType::Array, Value::Empty => ValueType::Empty, } } diff --git a/tests/integration.rs b/tests/integration.rs index 695056b..0a19ac9 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -79,6 +79,21 @@ fn test_braced_examples() { assert_eq!(eval("7/(7/(7/(7/(7/(7)))))"), Ok(Value::Int(1))); } +#[test] +fn test_array_examples() { + assert_eq!( + eval("{3, 55.0, false, ()}"), + Ok(Value::Array(vec![ + Value::from(3), + Value::from(55.0), + Value::from(false), + Value::from(()) + ])) + ); + assert_eq!(eval("{1}"), Ok(Value::Array(vec![Value::from(1),]))); + assert_eq!(eval("{}"), Ok(Value::Array(vec![]))); +} + #[test] fn test_mod_examples() { assert_eq!(eval("1 % 4"), Ok(Value::Int(1))); @@ -391,6 +406,7 @@ fn test_builtin_functions() { assert_eq!(eval("math::abs(-15)"), Ok(Value::Int(15))); // Other assert_eq!(eval("typeof(4.0, 3)"), Ok(Value::String("tuple".into()))); + assert_eq!(eval("typeof({4.0, 3})"), Ok(Value::String("array".into()))); assert_eq!(eval("typeof(4.0)"), Ok(Value::String("float".into()))); assert_eq!(eval("typeof(4)"), Ok(Value::String("int".into()))); assert_eq!(eval("typeof(\"\")"), Ok(Value::String("string".into()))); @@ -400,6 +416,14 @@ fn test_builtin_functions() { assert_eq!(eval("max(4.0, 3)"), Ok(Value::Float(4.0))); assert_eq!(eval("len(\"foobar\")"), Ok(Value::Int(6))); assert_eq!(eval("len(\"a\", \"b\")"), Ok(Value::Int(2))); + assert_eq!(eval("len({1, 2, 3})"), Ok(Value::Int(3))); + assert_eq!( + eval("len(3)"), + Err(EvalexprError::type_error( + Value::Int(3), + vec![ValueType::String, ValueType::Tuple, ValueType::Array], + )) + ); //Contians assert_eq!( eval("contains(1, 2, 3)"), @@ -418,7 +442,7 @@ fn test_builtin_functions() { ); assert_eq!( eval("contains(\"foo\", \"bar\")"), - Err(EvalexprError::expected_tuple(Value::String("foo".into()))) + Err(EvalexprError::expected_vec(Value::String("foo".into()))) ); assert_eq!( eval("contains((\"foo\", \"bar\", 123), 123)"), @@ -469,11 +493,11 @@ fn test_builtin_functions() { ); assert_eq!( eval("contains_any(\"foo\", \"bar\")"), - Err(EvalexprError::expected_tuple(Value::String("foo".into()))) + Err(EvalexprError::expected_vec(Value::String("foo".into()))) ); assert_eq!( eval("contains_any((\"foo\", \"bar\"), \"buzz\")"), - Err(EvalexprError::expected_tuple(Value::String("buzz".into()))) + Err(EvalexprError::expected_vec(Value::String("buzz".into()))) ); assert_eq!( eval("contains_any((\"foo\", \"bar\"), (\"buzz\", (1, 2, 3)))"), @@ -514,6 +538,10 @@ fn test_builtin_functions() { eval("str::from(1, 2, 3)"), Ok(Value::String(String::from("(1, 2, 3)"))) ); + assert_eq!( + eval("str::from({1, 2, 3})"), + Ok(Value::String(String::from("{1, 2, 3}"))) + ); assert_eq!(eval("str::from()"), Ok(Value::String(String::from("()")))); assert_eq!( eval("str::substring(\"foobar\", 3)"), @@ -852,6 +880,46 @@ fn test_shortcut_functions() { Err(EvalexprError::VariableIdentifierNotFound("3a3".to_owned())) ); + assert_eq!(eval_array("{3,3}"), Ok(vec![Value::Int(3), Value::Int(3)])); + assert_eq!( + eval_array("33"), + Err(EvalexprError::ExpectedArray { + actual: Value::Int(33) + }) + ); + assert_eq!( + eval_array("3a3"), + Err(EvalexprError::VariableIdentifierNotFound("3a3".to_owned())) + ); + assert_eq!( + eval_array_with_context("{3,3}", &context), + Ok(vec![Value::Int(3), Value::Int(3)]) + ); + assert_eq!( + eval_array_with_context("33", &context), + Err(EvalexprError::ExpectedArray { + actual: Value::Int(33) + }) + ); + assert_eq!( + eval_array_with_context("3a3", &context), + Err(EvalexprError::VariableIdentifierNotFound("3a3".to_owned())) + ); + assert_eq!( + eval_array_with_context_mut("{3,3}", &mut context), + Ok(vec![Value::Int(3), Value::Int(3)]) + ); + assert_eq!( + eval_array_with_context_mut("33", &mut context), + Err(EvalexprError::ExpectedArray { + actual: Value::Int(33) + }) + ); + assert_eq!( + eval_array_with_context_mut("3a3", &mut context), + Err(EvalexprError::VariableIdentifierNotFound("3a3".to_owned())) + ); + assert_eq!(eval_empty(""), Ok(EMPTY_VALUE)); assert_eq!(eval_empty("()"), Ok(EMPTY_VALUE)); assert_eq!( @@ -1224,6 +1292,61 @@ fn test_shortcut_functions() { Err(EvalexprError::VariableIdentifierNotFound("3a3".to_owned())) ); + assert_eq!( + build_operator_tree("{3,3}").unwrap().eval_array(), + Ok(vec![Value::Int(3), Value::Int(3)]) + ); + assert_eq!( + build_operator_tree("33").unwrap().eval_array(), + Err(EvalexprError::ExpectedArray { + actual: Value::Int(33) + }) + ); + assert_eq!( + build_operator_tree("3a3").unwrap().eval_array(), + Err(EvalexprError::VariableIdentifierNotFound("3a3".to_owned())) + ); + assert_eq!( + build_operator_tree("{3,3}") + .unwrap() + .eval_array_with_context(&context), + Ok(vec![Value::Int(3), Value::Int(3)]) + ); + assert_eq!( + build_operator_tree("33") + .unwrap() + .eval_array_with_context(&context), + Err(EvalexprError::ExpectedArray { + actual: Value::Int(33) + }) + ); + assert_eq!( + build_operator_tree("3a3") + .unwrap() + .eval_array_with_context(&context), + Err(EvalexprError::VariableIdentifierNotFound("3a3".to_owned())) + ); + assert_eq!( + build_operator_tree("{3,3}") + .unwrap() + .eval_array_with_context_mut(&mut context), + Ok(vec![Value::Int(3), Value::Int(3)]) + ); + assert_eq!( + build_operator_tree("33") + .unwrap() + .eval_array_with_context_mut(&mut context), + Err(EvalexprError::ExpectedArray { + actual: Value::Int(33) + }) + ); + assert_eq!( + build_operator_tree("3a3") + .unwrap() + .eval_array_with_context_mut(&mut context), + Err(EvalexprError::VariableIdentifierNotFound("3a3".to_owned())) + ); + assert_eq!( build_operator_tree("").unwrap().eval_empty(), Ok(EMPTY_VALUE) @@ -1383,6 +1506,88 @@ fn test_string_escaping() { ); } +#[test] +fn test_array_and_braces() { + assert_eq!(eval("("), Err(EvalexprError::UnmatchedLBrace)); + assert_eq!(eval("{"), Err(EvalexprError::UnmatchedLCurlyBrace)); + assert_eq!(eval(")"), Err(EvalexprError::UnmatchedRBrace)); + assert_eq!(eval("}"), Err(EvalexprError::UnmatchedRCurlyBrace)); + assert_eq!(eval("{)}"), Err(EvalexprError::UnmatchedLCurlyBrace)); + assert_eq!(eval("(})"), Err(EvalexprError::UnmatchedLBrace)); + assert_eq!(eval("{(}"), Err(EvalexprError::UnmatchedLBrace)); + assert_eq!(eval("({)"), Err(EvalexprError::UnmatchedLCurlyBrace)); +} + +#[test] +fn test_array_definitions() { + assert_eq!(eval_array("{}"), Ok(vec![])); + assert_eq!(eval_array("{()}"), Ok(vec![Value::Empty])); + + assert_eq!(eval_array("{(),()}"), Ok(vec![Value::Empty, Value::Empty])); + + assert_eq!(eval_array("{1}"), Ok(vec![Value::Int(1)])); + + assert_eq!(eval_array("{1, }"), Ok(vec![Value::Int(1)])); + + assert_eq!(eval_array("{1,, }"), Ok(vec![Value::Int(1), Value::Empty])); + assert_eq!( + eval_array("{1,,2}"), + Ok(vec![Value::Int(1), Value::Empty, Value::Int(2)]) + ); + + assert_eq!( + eval_tuple("({1, 2}, {3, 4})"), + Ok(vec![ + Value::Array(vec![Value::Int(1), Value::Int(2)]), + Value::Array(vec![Value::Int(3), Value::Int(4)]) + ]) + ); + assert_eq!( + eval_array("{(1, 2), (3, 4)}"), + Ok(vec![ + Value::Tuple(vec![Value::Int(1), Value::Int(2)]), + Value::Tuple(vec![Value::Int(3), Value::Int(4)]) + ]) + ); + assert_eq!( + eval_array("{{1, 2}, {3, 4}}"), + Ok(vec![ + Value::Array(vec![Value::Int(1), Value::Int(2)]), + Value::Array(vec![Value::Int(3), Value::Int(4)]) + ]) + ); + assert_eq!( + eval_array("{{1, 1 + 1}, {(1 - 2) * -3, 2*2}}"), + Ok(vec![ + Value::Array(vec![Value::Int(1), Value::Int(2)]), + Value::Array(vec![Value::Int(3), Value::Int(4)]) + ]) + ); +} + +#[test] +fn test_array_invalid_computation() { + assert_eq!( + eval("{}+"), + Err(EvalexprError::WrongOperatorArgumentAmount { + expected: 2, + actual: 1 + }) + ); + assert_eq!( + eval("1+{}"), + Err(EvalexprError::ExpectedNumberOrString { + actual: Value::Array(vec![]) + }) + ); + assert_eq!( + eval("{1, 2, 3}+2"), + Err(EvalexprError::ExpectedNumberOrString { + actual: Value::Array(vec![1.into(), 2.into(), 3.into()]) + }) + ); +} + #[test] fn test_tuple_definitions() { assert_eq!(eval_empty("()"), Ok(())); @@ -1731,6 +1936,12 @@ fn test_error_constructors() { actual: Value::Int(4) }) ); + assert_eq!( + eval_array("4"), + Err(EvalexprError::ExpectedArray { + actual: Value::Int(4) + }) + ); assert_eq!( Value::Tuple(vec![Value::Int(4), Value::Int(5)]).as_fixed_len_tuple(3), Err(EvalexprError::ExpectedFixedLengthTuple { @@ -1840,6 +2051,7 @@ fn test_value_type() { assert_eq!(ValueType::from(&Value::Int(0)), ValueType::Int); assert_eq!(ValueType::from(&Value::Boolean(true)), ValueType::Boolean); assert_eq!(ValueType::from(&Value::Tuple(Vec::new())), ValueType::Tuple); + assert_eq!(ValueType::from(&Value::Array(Vec::new())), ValueType::Array); assert_eq!(ValueType::from(&Value::Empty), ValueType::Empty); assert_eq!( @@ -1856,6 +2068,10 @@ fn test_value_type() { ValueType::from(&mut Value::Tuple(Vec::new())), ValueType::Tuple ); + assert_eq!( + ValueType::from(&mut Value::Array(Vec::new())), + ValueType::Array + ); assert_eq!(ValueType::from(&mut Value::Empty), ValueType::Empty); assert!(!Value::String(String::new()).is_number()); @@ -1863,6 +2079,7 @@ fn test_value_type() { assert!(Value::Int(0).is_number()); assert!(!Value::Boolean(true).is_number()); assert!(!Value::Tuple(Vec::new()).is_number()); + assert!(!Value::Array(Vec::new()).is_number()); assert!(!Value::Empty.is_number()); assert!(!Value::String(String::new()).is_empty()); @@ -1870,6 +2087,7 @@ fn test_value_type() { assert!(!Value::Int(0).is_empty()); assert!(!Value::Boolean(true).is_empty()); assert!(!Value::Tuple(Vec::new()).is_empty()); + assert!(!Value::Array(Vec::new()).is_empty()); assert!(Value::Empty.is_empty()); assert_eq!( @@ -1897,6 +2115,12 @@ fn test_value_type() { actual: Value::Tuple(Vec::new()) }) ); + assert_eq!( + Value::Array(Vec::new()).as_float(), + Err(EvalexprError::ExpectedFloat { + actual: Value::Array(Vec::new()) + }) + ); assert_eq!( Value::Empty.as_float(), Err(EvalexprError::ExpectedFloat { @@ -2001,6 +2225,12 @@ fn test_value_type() { actual: Value::Tuple(Vec::new()) }) ); + assert_eq!( + Value::Array(Vec::new()).as_empty(), + Err(EvalexprError::ExpectedEmpty { + actual: Value::Array(Vec::new()) + }) + ); assert_eq!(Value::Empty.as_empty(), Ok(())); assert_eq!(