mod coercion;
mod eval;
mod prelude;
mod value_handler;
mod document;
pub mod value;
use std::collections::HashMap;
use thiserror::Error;
use crate::{
expression::{Expression, Symbol},
runtime::value::Value,
Location, Reference,
};
pub use value_handler::ValueHandler;
#[derive(Error, Debug)]
#[error("{kind}")]
pub struct RuntimeError {
location: Location,
kind: RuntimeErrorKind,
}
impl RuntimeError {
pub fn new(location: Location, kind: RuntimeErrorKind) -> Self {
Self { location, kind }
}
pub fn kind(&self) -> &RuntimeErrorKind {
&self.kind
}
pub fn location(&self) -> Location {
self.location
}
}
#[derive(Error, Debug, Clone, Eq, PartialEq)]
pub enum RuntimeErrorKind {
#[error("Unknown reference")]
UnknownReference,
#[error("Unknown symbol `{0}`")]
UnknownSymbol(String),
#[error("Type mismatch")]
TypeMismatch,
#[error("Not enough arguments")]
NotEnoughArguments,
#[error("Too many arguments")]
TooManyArguments,
#[error("Unsupported selection")]
UnsupportedSelection,
#[error("Unavailable random generator")]
UnavailableRandomGenerator,
#[error("Unavailable size")]
UnavailableSize,
#[error("Uncomparable types")]
UncomparableTypes,
}
pub enum Binding {
Available(Value),
Pending,
Unknown,
}
pub trait Context {
fn resolve(&self, symbol: &Symbol) -> Binding;
fn value_handler(&self, reference: Reference) -> Option<&dyn ValueHandler>;
}
type Prelude = HashMap<&'static str, Value>;
impl Context for Prelude {
fn resolve(&self, symbol: &Symbol) -> Binding {
self.get(symbol.as_str())
.cloned()
.map(Binding::Available)
.unwrap_or(Binding::Unknown)
}
fn value_handler(&self, _reference: Reference) -> Option<&dyn ValueHandler> {
None
}
}
struct MergedContext<'a> {
root: &'a dyn Context,
current: &'a dyn Context,
}
struct EmptyContext;
impl Context for EmptyContext {
fn resolve(&self, _: &Symbol) -> Binding {
Binding::Unknown
}
fn value_handler(&self, _reference: Reference) -> Option<&dyn ValueHandler> {
None
}
}
impl<'a> MergedContext<'a> {
fn new(root: &'a dyn Context, current: &'a dyn Context) -> Self {
Self { root, current }
}
}
impl Context for MergedContext<'_> {
fn resolve(&self, symbol: &Symbol) -> Binding {
match self.current.resolve(symbol) {
Binding::Unknown => self.root.resolve(symbol),
b => b,
}
}
fn value_handler(&self, reference: Reference) -> Option<&dyn ValueHandler> {
self.current
.value_handler(reference)
.or_else(|| self.root.value_handler(reference))
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum Evaluation {
Complete(Location, Value),
Partial(Expression),
}
impl Evaluation {
pub fn map<F: FnOnce(Value) -> Value>(self, op: F) -> Self {
match self {
Self::Complete(l, v) => Self::Complete(l, op(v)),
e => e,
}
}
pub fn map_unsafe<F: FnOnce(Value) -> Result<Value, RuntimeError>>(
self,
op: F,
) -> Result<Self, RuntimeError> {
match self {
Self::Complete(l, v) => Ok(Self::Complete(l, op(v)?)),
e => Ok(e),
}
}
pub fn map_partial<F: FnOnce(Expression) -> Expression>(self, op: F) -> Self {
match self {
Self::Partial(e) => Self::Partial(op(e)),
e => e,
}
}
pub fn complete(self) -> Option<Value> {
match self {
Self::Complete(_, v) => Some(v),
_ => None,
}
}
pub fn partial(self) -> Option<Expression> {
match self {
Self::Partial(expression) => Some(expression),
_ => None,
}
}
pub fn is_complete(&self) -> bool {
matches!(self, Self::Complete(_, _))
}
pub fn into_expression(self) -> Expression {
match self {
Self::Complete(location, value) => Expression::new(location, value),
Self::Partial(expression) => expression,
}
}
}
pub trait Eval {
fn eval(&self, context: &dyn Context) -> Result<Evaluation, RuntimeError>;
}
pub struct Runtime<P = Prelude> {
prelude: P,
}
impl Runtime {
pub fn new() -> Self {
Self::with_prelude(prelude::prelude())
}
}
impl Default for Runtime {
fn default() -> Self {
Self::new()
}
}
impl<P: Context> Runtime<P> {
#[doc(hidden)]
pub fn with_prelude(prelude: P) -> Self {
Self { prelude }
}
}
impl Runtime {
pub fn eval_with_context(
&self,
e: &dyn Eval,
context: &dyn Context,
) -> Result<Evaluation, RuntimeError> {
let context = MergedContext::new(&self.prelude, context);
e.eval(&context).map(|ev| {
ev.map(|v| {
v.to_value_handler(&context)
.and_then(|vh| vh.detach())
.unwrap_or_else(Value::null)
})
})
}
pub fn eval(&self, e: &dyn Eval) -> Result<Evaluation, RuntimeError> {
self.eval_with_context(e, &EmptyContext)
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use std::rc::Rc;
use super::value::{Object, QualifiedName, Value};
use crate::{
expression::Symbol,
parser::Parser,
runtime::{Binding, Context, RuntimeErrorKind, ValueHandler},
ContextId, Reference,
};
use super::{Prelude, Runtime, RuntimeError};
use crate::runtime::document::DocumentBuilder;
use test_case::test_case;
#[test]
fn inline_array() {
let json = r#"[":array", "0-17", [":str", "1-6", "one"], [":nbr", "8-9", "2"], [":bool", "11-16", "false"]]"#;
let expression = Parser::new().parse_str(json).unwrap();
let actual = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
let expected = &[
Value::string("one".to_string()),
Value::number(2.0),
Value::bool(false),
];
assert_eq!(actual.as_slice().unwrap(), expected);
}
#[test]
fn unknown_symbol_fail() {
let pel = r#"[":ref", "0-7", "unknown"]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let error = Runtime::new().eval(&expression).unwrap_err();
assert_eq!(
error.kind(),
&RuntimeErrorKind::UnknownSymbol("unknown".to_string())
);
}
#[test]
fn null_value() {
let pel = r#"[":null", "0-4"]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert!(result.is_null());
}
#[test]
fn apply() {
let json = r#"[":apply", "5-7", [":ref", "5-7", "++"], [":str", "0-4", "hi"], [":str", "8-12", "by"]]"#;
let expression = Parser::new().parse_str(json).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!("hiby", result.as_str().unwrap());
}
#[test]
fn if_else() {
let json = r#"[":if", "0-23", [":bool", "4-9", "false"], [":str", "11-14", "a"], [":str", "20-23", "b"]]"#;
let expression = Parser::new().parse_str(json).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!("b", result.as_str().unwrap());
}
#[test]
fn default_left_null() {
let pel = r#"
[":default", "0-6",
[":null", "0-1"],
[":str", "5-6", "right"]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_str().unwrap(), "right");
}
#[test]
fn default_left_not_null() {
let pel = r#"
[":default", "0-6",
[":nbr", "0-1", "700"],
[":bool", "5-6", "true"]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_f64().unwrap(), 700.0);
}
#[test]
fn array_positive_index() {
let pel = r#"
[".", "0-45",
[":array", "0-41",
[":str", "1-9", "accept"],
[":str", "10-27", "accept-encoding"],
[":str", "28-40", "user-agent"]
], [":nbr", "42-44", "2"]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!("user-agent", result.as_str().unwrap());
}
#[test]
fn array_negative_index() {
let pel = r#"
[".", "0-45",
[":array", "0-41",
[":str", "1-9", "accept"],
[":str", "10-27", "accept-encoding"],
[":str", "28-40", "user-agent"]
], [":nbr", "42-44", "-2"]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!("accept-encoding", result.as_str().unwrap());
}
#[test]
fn array_out_of_bounds_index() {
let pel = r#"
[".", "0-45",
[":array", "0-41",
[":str", "1-9", "accept"],
[":str", "10-27", "accept-encoding"],
[":str", "28-40", "user-agent"]
], [":nbr", "42-44", "5"]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert!(result.is_null());
}
#[test]
fn string_positive_index() {
let pel = r#"
[".", "0-45",
[":str", "0-41", "peregrine expression language"],
[":nbr", "42-44", "10"]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!("e", result.as_str().unwrap());
}
#[test]
fn string_negative_index() {
let pel = r#"
[".", "0-45",
[":str", "0-41", "peregrine expression language"],
[":nbr", "42-44", "-5"]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!("g", result.as_str().unwrap());
}
#[test]
fn string_out_of_bounds_index() {
let pel = r#"
[".", "0-45",
[":str", "0-41", "peregrine expression language"],
[":nbr", "42-44", "100"]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert!(result.is_null());
}
#[test]
fn selection_by_key() {
let mut b_object = Object::new();
b_object.insert("c".to_string(), Value::string("foo".to_string()));
let mut a_object = Object::new();
a_object.insert("b".to_string(), Value::object(b_object));
let a_object = Value::object(a_object);
let mut context = HashMap::new();
context.insert("a", a_object);
let json = r#"[".", "0-5", [".", "0-3", [":ref", "0-1", "a"], [":str", "2-3", "b"]], [":str", "4-5", "c"]]"#;
let expression = Parser::new().parse_str(json).unwrap();
let result = Runtime::new()
.eval_with_context(&expression, &context)
.unwrap()
.complete()
.unwrap();
assert_eq!("foo", result.as_str().unwrap());
}
#[test]
fn selection_by_key_in_array() {
let mut b_object = Object::new();
b_object.insert("c".to_string(), Value::string("foo".to_string()));
let mut a_object = Object::new();
a_object.insert("b".to_string(), Value::object(b_object));
let a_object = Value::object(a_object);
let array = Value::array(vec![a_object]);
let mut context = HashMap::new();
context.insert("array", array);
let pel = r#"
[".", "0-3",
[".", "0-5",
[":ref", "0-5", "array"],
[":str", "0-1", "b"]
],
[":str", "2-3", "c"]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval_with_context(&expression, &context)
.unwrap()
.complete()
.unwrap();
assert_eq!(&[Value::string("foo")], result.as_slice().unwrap());
}
#[test]
fn select_by_key_inexistent_in_object() {
let b_object = Object::new();
let mut a_object = Object::new();
a_object.insert("b".to_string(), Value::object(b_object));
let a_object = Value::object(a_object);
let mut context = HashMap::new();
context.insert("a", a_object);
let pel = r#"
[".", "0-5",
[".", "0-3",
[":ref", "0-1", "a"],
[":str", "2-3", "b"]
],
[":str", "4-5", "inexistent"]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval_with_context(&expression, &context)
.unwrap()
.complete()
.unwrap();
assert!(result.is_null());
}
#[test]
fn select_by_key_inexistent_in_reference() {
struct TestContext;
struct TestValueHandler;
const CONTEXT_ID: ContextId = ContextId::new("TestContext");
const REFERENCE: Reference = CONTEXT_ID.first_reference();
impl ValueHandler for TestValueHandler {
fn detach(&self) -> Option<Value> {
unimplemented!()
}
fn select_by_key(&self, _key: &str) -> Option<Value> {
None
}
}
impl Context for TestContext {
fn resolve(&self, symbol: &Symbol) -> Binding {
match symbol.as_str() {
"a" => Binding::Available(Value::reference(REFERENCE)),
_ => Binding::Unknown,
}
}
fn value_handler(&self, reference: Reference) -> Option<&dyn ValueHandler> {
match reference {
REFERENCE => Some(&TestValueHandler),
_ => None,
}
}
}
let pel = r#"
[".", "0-5",
[":ref", "0-1", "a"],
[":str", "2-3", "inexistent"]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval_with_context(&expression, &TestContext)
.unwrap()
.complete()
.unwrap();
assert!(result.is_null());
}
#[test]
fn operation_eq() {
let json = r#"["==", "0-6", [":nbr", "0-1", "10"], [":nbr", "5-6", "10.0"]]"#;
let expression = Parser::new().parse_str(json).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert!(result.as_bool().unwrap());
}
#[test]
fn operation_eq_mismatch() {
let json = r#"["==", "0-6", [":nbr", "0-1", "10"], [":str", "5-6", "10.0"]]"#;
let expression = Parser::new().parse_str(json).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert!(!result.as_bool().unwrap());
}
#[test]
fn operation_neq() {
let json = r#"["!=", "0-6", [":nbr", "0-1", "10"], [":nbr", "5-6", "10.0"]]"#;
let expression = Parser::new().parse_str(json).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert!(!result.as_bool().unwrap());
}
#[test]
fn operation_neq_mismatch() {
let json = r#"["!=", "0-6", [":nbr", "0-1", "10"], [":str", "5-6", "10"]]"#;
let expression = Parser::new().parse_str(json).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert!(result.as_bool().unwrap());
}
#[test]
fn operation_gt() {
let json = r#"[">", "0-6", [":nbr", "0-1", "10"], [":nbr", "5-6", "100.0"]]"#;
let expression = Parser::new().parse_str(json).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert!(!result.as_bool().unwrap());
}
#[test]
fn operation_get() {
let json = r#"[">=", "0-6", [":str", "0-1", "left"], [":str", "5-6", "left"]]"#;
let expression = Parser::new().parse_str(json).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert!(result.as_bool().unwrap());
}
#[test]
fn operation_lt() {
let json = r#"["<", "0-6", [":str", "0-1", "2.0"], [":nbr", "5-6", "10"]]"#;
let expression = Parser::new().parse_str(json).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert!(result.as_bool().unwrap());
}
#[test]
fn operation_let() {
let json = r#"["<=", "0-6", [":bool", "0-1", "true"], [":str", "5-6", "true"]]"#;
let expression = Parser::new().parse_str(json).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert!(result.as_bool().unwrap());
}
#[test]
fn operation_type_mismatch_fail() {
let json = r#"["<=", "0-14", [":str", "0-6", "11.0"], [":bool", "9-13", "true"]]"#;
let expression = Parser::new().parse_str(json).unwrap();
let error = Runtime::new().eval(&expression).err().unwrap();
assert_eq!(error.kind(), &RuntimeErrorKind::TypeMismatch);
assert_eq!(error.location().start, 0);
assert_eq!(error.location().end, 14);
}
#[test]
fn operation_and() {
let json = r#"["&&", "0-6", [":bool", "0-1", "false"], [":bool", "5-6", "true"]]"#;
let expression = Parser::new().parse_str(json).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert!(!result.as_bool().unwrap());
}
#[test]
fn operation_or() {
let json = r#"["||", "0-6", [":bool", "0-1", "false"], [":bool", "5-6", "true"]]"#;
let expression = Parser::new().parse_str(json).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert!(result.as_bool().unwrap());
}
#[test]
fn operation_not() {
let json = r#"["!", "0-6", [":bool", "0-1", "false"]]"#;
let expression = Parser::new().parse_str(json).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert!(result.as_bool().unwrap());
}
#[test]
fn lower_null() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "lower"],
[":null", "0-4"]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert!(result.is_null());
}
#[test]
fn lower_string() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "lower"],
[":str", "0-4", "Peregrine Expression Language"]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!("peregrine expression language", result.as_str().unwrap());
}
#[test]
fn lower_boolean() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "lower"],
[":bool", "0-4", "true"]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!("true", result.as_str().unwrap());
}
#[test]
fn lower_number() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "lower"],
[":nbr", "0-4", "1944.07"]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!("1944.07", result.as_str().unwrap());
}
#[test]
fn lower_array_fail() {
let pel = r#"
[":apply", "0-50",
[":ref", "0-5", "lower"],
[":array", "6-49",
[":str", "7-15", "accept"],
[":str", "17-34", "accept-encoding"],
[":str", "36-48", "user-agent"]
]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let error = Runtime::new().eval(&expression).unwrap_err();
assert_eq!(error.kind(), &RuntimeErrorKind::TypeMismatch);
assert_eq!(error.location().start, 0);
assert_eq!(error.location().end, 50);
}
#[test]
fn selection_by_key_in_lookup_context() {
struct LookupValueHandler(fn(&str) -> Option<Value>);
struct LookupContext;
const CONTEXT_ID: ContextId = ContextId::new("lookup_context");
const A_REFERENCE: Reference = CONTEXT_ID.first_reference();
const B_REFERENCE: Reference = A_REFERENCE.next();
static A_VALUE_HANDLER: LookupValueHandler =
LookupValueHandler(|key| (key == "d").then(|| Value::reference(B_REFERENCE)));
static B_VALUE_HANDLER: LookupValueHandler =
LookupValueHandler(|key| (key == "c").then(|| Value::number(111.0)));
impl ValueHandler for LookupValueHandler {
fn select_by_key(&self, key: &str) -> Option<Value> {
self.0(key)
}
fn detach(&self) -> Option<Value> {
None
}
}
impl Context for LookupContext {
fn resolve(&self, symbol: &Symbol) -> Binding {
match symbol.as_str() {
"a" => Binding::Available(Value::reference(A_REFERENCE)),
_ => Binding::Unknown,
}
}
fn value_handler(
&self,
reference: Reference,
) -> Option<&dyn crate::runtime::ValueHandler> {
match reference {
A_REFERENCE => Some(&A_VALUE_HANDLER),
B_REFERENCE => Some(&B_VALUE_HANDLER),
_ => None,
}
}
}
let json = r#"[".", "0-5", [".", "0-3", [":ref", "0-1", "a"], [":str", "2-3", "d"]], [":str", "4-5", "c"]]"#;
let expression = Parser::new().parse_str(json).unwrap();
let result = Runtime::new()
.eval_with_context(&expression, &LookupContext)
.unwrap()
.complete()
.unwrap();
assert_eq!(111, result.as_f64().unwrap() as i32);
}
#[test]
fn contains_string_string_is_true() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-8", "contains"],
[":str", "5-7", "Peregrine expression language"],
[":str", "0-4", "lang"]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert!(result.as_bool().unwrap());
}
#[test]
fn contains_string_number_is_true() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-8", "contains"],
[":str", "5-7", "Peregrine expression language 1.0"],
[":nbr", "0-4", "1.0"]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert!(result.as_bool().unwrap());
}
#[test]
fn contains_string_string_is_false() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-8", "contains"],
[":str", "5-7", "Peregrine expression language"],
[":str", "0-4", "PEREGRINE"]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert!(!result.as_bool().unwrap());
}
#[test]
fn contains_boolean_string_is_true() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-8", "contains"],
[":bool", "5-7", "true"],
[":str", "0-4", "ru"]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert!(result.as_bool().unwrap());
}
#[test]
fn contains_boolean_string_is_false() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-8", "contains"],
[":bool", "5-7", "true"],
[":str", "0-4", "fal"]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert!(!result.as_bool().unwrap());
}
#[test]
fn contains_number_string_is_true() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-8", "contains"],
[":nbr", "5-7", "3.141516"],
[":str", "0-4", ".141"]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert!(result.as_bool().unwrap());
}
#[test]
fn contains_number_number_is_false() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-8", "contains"],
[":nbr", "5-7", "3.141516"],
[":nbr", "0-4", "9"]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert!(!result.as_bool().unwrap());
}
#[test]
fn contains_array_is_true() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-8", "contains"],
[":array", "5-7",
[":str", "5-7", "a"],
[":nbr", "5-7", "10"]
],
[":nbr", "0-4", "10"]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert!(result.as_bool().unwrap());
}
#[test]
fn contains_array_is_false() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-8", "contains"],
[":array", "5-7",
[":str", "5-7", "a"],
[":bool", "5-7", "false"]
],
[":nbr", "0-4", "10"]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert!(!result.as_bool().unwrap());
}
#[test]
fn trim_array() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "trim"],
[":array", "0-17",
[":str", "1-6", "one"],
[":nbr", "8-9", "2"],
[":bool", "11-16", "false"]
]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let error = Runtime::new().eval(&expression).unwrap_err();
assert_eq!(error.kind, RuntimeErrorKind::TypeMismatch);
}
#[test]
fn trim_boolean() {
let json = r#"
[":apply", "5-7",
[":ref", "5-7", "trim"],
[":bool", "0-4", "true"]
]
"#;
let expression = Parser::new().parse_str(json).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!("true", result.as_str().unwrap());
}
#[test]
fn trim_number() {
let json = r#"
[":apply", "5-7",
[":ref", "5-7", "trim"],
[":nbr", "0-4", "1000.0"]
]
"#;
let expression = Parser::new().parse_str(json).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!("1000.0", result.as_str().unwrap());
}
#[test]
fn trim_string() {
let json = r#"
[":apply", "5-7",
[":ref", "5-7", "trim"],
[":str", "0-4", " hello world "]
]
"#;
let expression = Parser::new().parse_str(json).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!("hello world", result.as_str().unwrap());
}
#[test]
fn generate_uuid() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "uuid"]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
let uuid = result.as_str().unwrap();
assert!(uuid::Uuid::parse_str(uuid).is_ok());
}
#[test]
fn size_of_string() {
let pel = r#"
[":apply", "0-45",
[":ref", "0-10", "sizeOf"],
[":str", "0-41", "hello world"]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!(11, result.as_f64().unwrap() as usize);
}
#[test]
fn size_of_array() {
let pel = r#"
[":apply", "0-45",
[":ref", "0-10", "sizeOf"],
[":array", "0-41",
[":str", "1-9", "accept"],
[":str", "10-27", "accept-encoding"],
[":str", "28-40", "user-agent"]
]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!(3, result.as_f64().unwrap() as usize);
}
#[test]
fn size_of_object() {
let object = Value::object(Object::from([("foo".to_string(), Value::bool(true))]));
let context = HashMap::from([("object", object)]);
let pel = r#"
[":apply", "0-45",
[":ref", "0-10", "sizeOf"],
[":ref", "0-41", "object"]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval_with_context(&expression, &context)
.unwrap()
.complete()
.unwrap();
assert_eq!(1, result.as_f64().unwrap() as usize);
}
#[test_case(r#"[":apply", "0-45", [":ref", "0-10", "isEmpty"],[":str", "0-41", ""]]"#,
true;
r#"DW isEmpty('') should be true 1"#
)]
#[test_case(r#"[":apply", "0-45", [":ref", "0-10", "isEmpty"],[":str", "0-41", "peregrine"]]"#,
false;
r#"DW isEmpty('peregrine') should be false"#
)]
#[test_case(r#"[":apply", "0-45", [":ref", "0-10", "isEmpty"],[":array", "0-2"]]"#,
true;
r#"DW isEmpty([]) should be true 2"#
)]
#[test_case(r#"[":apply", "0-45", [":ref", "0-10", "isEmpty"],[":array", "0-41", [":str", "1-9", "accept"]]]"#,
false;
r#"DW isEmpty(['accept']) should be false"#
)]
#[test_case(r#"[":apply", "0-45", [":ref", "0-10", "isEmpty"],[":ref", "0-41", "empty_object"]]"#,
true;
r#"DW isEmpty({}) should be true 3"#
)]
#[test_case(r#"[":apply", "0-45", [":ref", "0-10", "isEmpty"],[":ref", "0-41", "object"]]"#,
false;
r#"DW isEmpty({'foo': true}) should be false"#
)]
fn is_empty(pel: &str, expected: bool) {
let object = Value::object(Object::from([("foo".to_string(), Value::bool(true))]));
let empty_object = Value::object(Object::from([]));
let context = HashMap::from([("object", object), ("empty_object", empty_object)]);
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval_with_context(&expression, &context)
.unwrap()
.complete()
.unwrap();
assert_eq!(expected, result.as_bool().unwrap());
}
#[test_case(r#"[":apply", "0-78", [":ref", "0-29", "toString"], [":apply", "30-69", [":ref", "30-60", "fromBase64"], [":str", "61-68", "SGk=="]], [":str", "70-77", "UTF-8"]]"#;
r#"Double padded expression should return 'Hi'"#
)]
#[test_case(r#"[":apply", "0-77", [":ref", "0-29", "toString"], [":apply", "30-68", [":ref", "30-60", "fromBase64"], [":str", "61-67", "SGk="]], [":str", "69-76", "UTF-8"]]"#;
r#"Padded expression should return 'Hi'"#
)]
#[test_case(r#"[":apply", "0-76", [":ref", "0-29", "toString"], [":apply", "30-68", [":ref", "30-60", "fromBase64"], [":str", "61-66", "SGk"]], [":str", "68-75", "UTF-8"]]"#;
r#"Unpadded expression should return 'Hi'"#
)]
fn base64_is_decoded_padded_or_unpadded(pel: &str) {
let headers = Value::object(HashMap::new());
let context: Prelude = HashMap::from([("payload", headers)]);
let value = Runtime::new()
.eval_with_context(&Parser::new().parse_str(pel).unwrap(), &context)
.unwrap()
.complete()
.unwrap();
assert_eq!(value.as_str().unwrap(), "Hi");
}
#[test]
fn split_by_string_string() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "splitBy"],
[":str", "0-4", "Peregrine Expression Language"],
[":str", "8-12", "re"]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
let expected = [
Value::string("Pe".to_string()),
Value::string("grine Exp".to_string()),
Value::string("ssion Language".to_string()),
];
assert_eq!(&expected, result.as_slice().unwrap());
}
#[test]
fn split_by_string_empty_string() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "splitBy"],
[":str", "0-4", "2022"],
[":str", "8-12", ""]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
let expected = [
Value::string("2".to_string()),
Value::string("0".to_string()),
Value::string("2".to_string()),
Value::string("2".to_string()),
];
assert_eq!(&expected, result.as_slice().unwrap());
}
#[ignore = "bug W-11098754"]
#[test]
fn split_by_string_string_2() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "splitBy"],
[":str", "0-4", "2022"],
[":str", "8-12", "2"]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
let expected = [
Value::string("".to_string()),
Value::string("0".to_string()),
];
assert_eq!(&expected, result.as_slice().unwrap());
}
#[test]
fn split_by_number_string() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "splitBy"],
[":nbr", "0-4", "1946.03"],
[":str", "8-12", "."]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
let expected = [
Value::string("1946".to_string()),
Value::string("03".to_string()),
];
assert_eq!(&expected, result.as_slice().unwrap());
}
#[test]
fn split_by_boolean_string() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "splitBy"],
[":bool", "0-4", "true"],
[":str", "8-12", "r"]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
let expected = [
Value::string("t".to_string()),
Value::string("ue".to_string()),
];
assert_eq!(&expected, result.as_slice().unwrap());
}
#[test]
fn split_by_null_string() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "splitBy"],
[":null", "0-41"],
[":str", "8-12", "r"]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert!(result.is_null());
}
#[test]
fn split_by_string_null_faiil() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "splitBy"],
[":str", "0-41", "Peregrine"],
[":null", "8-12"]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let error = Runtime::new().eval(&expression).unwrap_err();
assert_eq!(error.kind, RuntimeErrorKind::TypeMismatch);
}
#[test]
fn split_by_array_fail() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "splitBy"],
[":array", "0-41",
[":str", "1-9", "accept"],
[":str", "10-27", "accept-encoding"],
[":str", "28-40", "user-agent"]
],
[":str", "8-12", "r"]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let error = Runtime::new().eval(&expression).unwrap_err();
assert_eq!(error.kind, RuntimeErrorKind::TypeMismatch);
}
#[test]
fn substring_after_null_string() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "substringAfter"],
[":null", "0-4"],
[":str", "8-12", "peregrine"]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert!(result.is_null());
}
#[test]
fn substring_after_string_string() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "substringAfter"],
[":str", "0-4", "peregrine"],
[":str", "8-12", "gr"]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_str().unwrap(), "ine");
}
#[test]
fn substring_after_string_null_fail() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "substringAfter"],
[":str", "0-4", "peregrine"],
[":null", "8-12"]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let error = Runtime::new().eval(&expression).unwrap_err();
assert_eq!(error.kind, RuntimeErrorKind::TypeMismatch);
}
#[test]
fn substring_after_boolean_string() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "substringAfter"],
[":bool", "0-4", "true"],
[":str", "8-12", "r"]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_str().unwrap(), "ue");
}
#[test]
fn substring_after_number_number() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "substringAfter"],
[":nbr", "0-4", "1234.567"],
[":nbr", "8-12", "4.5"]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_str().unwrap(), "67");
}
#[test]
fn substring_after_array_fail() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "substringAfter"],
[":array", "0-41",
[":str", "1-9", "accept"],
[":str", "10-27", "accept-encoding"],
[":str", "28-40", "user-agent"]
],
[":str", "1-10", "re"]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new().eval(&expression).unwrap_err();
assert_eq!(result.kind, RuntimeErrorKind::TypeMismatch);
}
#[test]
fn substring_after_not_found() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "substringAfter"],
[":str", "0-4", "peregrine"],
[":str", "8-12", "XX"]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_str().unwrap(), "");
}
#[test]
fn substring_after_empty_string() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "substringAfter"],
[":str", "0-4", "peregrine"],
[":str", "8-10", ""]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_str().unwrap(), "peregrine");
}
#[test]
fn substring_after_last_null_string() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "substringAfterLast"],
[":null", "0-4"],
[":str", "8-12", "peregrine"]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert!(result.is_null());
}
#[test]
fn substring_after_last_string_string() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "substringAfterLast"],
[":str", "0-4", "Peregrine Expression Language"],
[":str", "8-12", "re"]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_str().unwrap(), "ssion Language");
}
#[test]
fn substring_after_last_string_null_fail() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "substringAfterLast"],
[":str", "0-4", "peregrine"],
[":null", "8-12"]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let error = Runtime::new().eval(&expression).unwrap_err();
assert_eq!(error.kind, RuntimeErrorKind::TypeMismatch);
}
#[test]
fn substring_after_last_boolean_string() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "substringAfterLast"],
[":bool", "0-4", "true"],
[":str", "8-12", "r"]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_str().unwrap(), "ue");
}
#[test]
fn substring_after_last_number_number() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "substringAfterLast"],
[":nbr", "0-4", "12123512.3512"],
[":nbr", "8-12", "35"]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_str().unwrap(), "12");
}
#[test]
fn substring_after_last_array_fail() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "substringAfterLast"],
[":array", "0-41",
[":str", "1-9", "accept"],
[":str", "10-27", "accept-encoding"],
[":str", "28-40", "user-agent"]
],
[":str", "1-10", "re"]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new().eval(&expression).unwrap_err();
assert_eq!(result.kind, RuntimeErrorKind::TypeMismatch);
}
#[test]
fn substring_after_last_not_found() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "substringAfterLast"],
[":str", "0-4", "peregrine"],
[":str", "8-12", "XX"]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_str().unwrap(), "");
}
#[test]
fn substring_after_last_empty_string() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "substringAfterLast"],
[":str", "0-4", "peregrine"],
[":str", "8-10", ""]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_str().unwrap(), "");
}
#[test]
fn substring_before_null_string() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "substringBefore"],
[":null", "0-4"],
[":str", "8-12", "peregrine"]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert!(result.is_null());
}
#[test]
fn substring_before_string_string() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "substringBefore"],
[":str", "0-4", "peregrine"],
[":str", "8-12", "gr"]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_str().unwrap(), "pere");
}
#[test]
fn substring_before_string_null_fail() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "substringBefore"],
[":str", "0-4", "peregrine"],
[":null", "8-12"]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new().eval(&expression).unwrap_err();
assert_eq!(result.kind, RuntimeErrorKind::TypeMismatch);
}
#[test]
fn substring_before_boolean_string() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "substringBefore"],
[":bool", "0-4", "true"],
[":str", "8-12", "ue"]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_str().unwrap(), "tr");
}
#[test]
fn substring_before_number_number() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "substringBefore"],
[":nbr", "0-4", "1234.56"],
[":nbr", "8-12", "4.5"]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_str().unwrap(), "123");
}
#[test]
fn substring_before_array_fail() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "substringBefore"],
[":array", "0-41",
[":str", "1-9", "accept"],
[":str", "10-27", "accept-encoding"],
[":str", "28-40", "user-agent"]
],
[":str", "1-10", "re"]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new().eval(&expression).unwrap_err();
assert_eq!(result.kind, RuntimeErrorKind::TypeMismatch);
}
#[test]
fn substring_before_not_found() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "substringBefore"],
[":str", "0-4", "peregrine"],
[":str", "8-12", "XX"]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_str().unwrap(), "");
}
#[test]
fn substring_before_empty_string() {
let pel = r#"
[":apply", "0-55",
[":ref", "0-38", "substringBefore"],
[":str", "39-50", "peregrine"],
[":str", "52-54", ""]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_str().unwrap(), "");
}
#[test]
fn substring_before_last_null_string() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "substringBeforeLast"],
[":null", "0-4"],
[":str", "8-12", "peregrine"]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert!(result.is_null());
}
#[test]
fn substring_before_last_string_string() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "substringBeforeLast"],
[":str", "0-4", "Peregrine Expression Language"],
[":str", "8-12", "re"]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_str().unwrap(), "Peregrine Exp");
}
#[test]
fn substring_before_last_string_null_fail() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "substringBeforeLast"],
[":str", "0-4", "peregrine"],
[":null", "8-12"]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new().eval(&expression).unwrap_err();
assert_eq!(result.kind, RuntimeErrorKind::TypeMismatch);
}
#[test]
fn substring_before_last_boolean_string() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "substringBeforeLast"],
[":bool", "0-4", "true"],
[":str", "8-12", "ue"]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_str().unwrap(), "tr");
}
#[test]
fn substring_before_last_number_number() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "substringBeforeLast"],
[":nbr", "0-4", "121235.123512"],
[":nbr", "8-12", "12"]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_str().unwrap(), "121235.1235");
}
#[test]
fn substring_before_last_array_fail() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "substringBeforeLast"],
[":array", "0-41",
[":str", "1-9", "accept"],
[":str", "10-27", "accept-encoding"],
[":str", "28-40", "user-agent"]
],
[":str", "1-10", "re"]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new().eval(&expression).unwrap_err();
assert_eq!(result.kind, RuntimeErrorKind::TypeMismatch);
}
#[test]
fn substring_before_last_not_found() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "substringBeforeLast"],
[":str", "0-4", "peregrine"],
[":str", "8-12", "XX"]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_str().unwrap(), "");
}
#[test]
fn substring_before_last_empty_string() {
let pel = r#"
[":apply", "0-55",
[":ref", "0-38", "substringBeforeLast"],
[":str", "39-50", "peregrine"],
[":str", "52-54", ""]
]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_str().unwrap(), "peregrin");
}
#[test]
fn upper_null() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "upper"],
[":null", "0-4"]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert!(result.is_null());
}
#[test]
fn upper_string() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "upper"],
[":str", "0-4", "Peregrine Expression Language"]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!("PEREGRINE EXPRESSION LANGUAGE", result.as_str().unwrap());
}
#[test]
fn upper_boolean() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "upper"],
[":bool", "0-4", "true"]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!("TRUE", result.as_str().unwrap());
}
#[test]
fn upper_number() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "upper"],
[":nbr", "0-4", "123.4"]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!("123.4", result.as_str().unwrap());
}
#[test]
fn upper_array_fail() {
let pel = r#"
[":apply", "5-7",
[":ref", "5-7", "upper"],
[":array", "0-41",
[":str", "1-9", "accept"],
[":str", "10-27", "accept-encoding"],
[":str", "28-40", "user-agent"]
]
]
"#;
let expression = Parser::new().parse_str(pel).unwrap();
let error = Runtime::new().eval(&expression).unwrap_err();
assert_eq!(error.kind, RuntimeErrorKind::TypeMismatch);
}
#[test]
fn to_binary() {
let pel = r#"[":apply", "0-46", [":ref", "0-29", "toBinary"], [":str", "30-36", "some"], [":str", "38-45", "UTF-8"]]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_binary().unwrap(), "some".as_bytes());
}
#[test]
fn to_binary_not_enough_args() {
let pel = r#"[":apply", "0-46", [":ref", "0-29", "toBinary"], [":str", "30-36", "some"]]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new().eval(&expression).unwrap_err();
assert_eq!(result.kind, RuntimeErrorKind::NotEnoughArguments);
}
#[test]
fn to_binary_to_many_args() {
let pel = r#"[":apply", "0-46", [":ref", "0-29", "toBinary"], [":str", "30-36", "some"], [":str", "38-45", "UTF-8"], [":str", "38-45", "UTF-8"]]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new().eval(&expression).unwrap_err();
assert_eq!(result.kind, RuntimeErrorKind::TooManyArguments);
}
#[test]
fn to_binary_invalid_argument() {
let pel = r#"[":apply", "0-46", [":ref", "0-29", "toBinary"], [":str", "30-36", "some"], [":str", "38-45", "UTF-32"]]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new().eval(&expression).unwrap_err();
assert_eq!(result.kind, RuntimeErrorKind::TypeMismatch);
}
#[test]
fn from_binary() {
let pel = r#"[":apply", "0-86", [":ref", "0-29", "toString"], [":apply", "30-76", [":ref", "30-59", "toBinary"], [":str", "60-66", "some"], [":str", "68-75", "UTF-8"]], [":str", "78-85", "UTF-8"]]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_str(), Some("some"));
}
#[test]
fn from_binary_not_enough_args() {
let pel = r#"[":apply", "0-86", [":ref", "0-29", "toString"], [":apply", "30-76", [":ref", "30-59", "toBinary"], [":str", "60-66", "some"], [":str", "68-75", "UTF-8"]]]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new().eval(&expression).unwrap_err();
assert_eq!(result.kind, RuntimeErrorKind::NotEnoughArguments);
}
#[test]
fn from_binary_to_many_args() {
let pel = r#"[":apply", "0-86", [":ref", "0-29", "toString"], [":apply", "30-76", [":ref", "30-59", "toBinary"], [":str", "60-66", "some"], [":str", "68-75", "UTF-8"]], [":str", "78-85", "UTF-8"], [":str", "78-85", "UTF-8"]]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new().eval(&expression).unwrap_err();
assert_eq!(result.kind, RuntimeErrorKind::TooManyArguments);
}
#[test]
fn from_binary_invalid_argument() {
let pel = r#"[":apply", "0-86", [":ref", "0-29", "toString"], [":apply", "30-76", [":ref", "30-59", "toBinary"], [":str", "60-66", "some"], [":str", "68-75", "UTF-8"]], [":str", "78-85", "UTF-32"]]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new().eval(&expression).unwrap_err();
assert_eq!(result.kind, RuntimeErrorKind::TypeMismatch);
}
#[test]
fn from_base64() {
let pel =
r#"[":apply", "0-46", [":ref", "0-29", "fromBase64"], [":str", "30-36", "c29tZQ=="]]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_binary().unwrap(), "some".as_bytes());
}
#[test]
fn from_base64_not_enough_args() {
let pel = r#"[":apply", "0-46", [":ref", "0-29", "fromBase64"]]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new().eval(&expression).unwrap_err();
assert_eq!(result.kind, RuntimeErrorKind::NotEnoughArguments);
}
#[test]
fn from_base64_to_many_args() {
let pel = r#"[":apply", "0-46", [":ref", "0-29", "fromBase64"], [":str", "30-36", "AA"], [":str", "38-45", "UTF-8"]]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new().eval(&expression).unwrap_err();
assert_eq!(result.kind, RuntimeErrorKind::TooManyArguments);
}
#[test]
fn from_base64_invalid_argument() {
let pel = r#"[":apply", "0-46", [":ref", "0-29", "fromBase64"], [":str", "30-36", "0"]]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new().eval(&expression).unwrap_err();
assert_eq!(result.kind, RuntimeErrorKind::TypeMismatch);
}
#[test]
fn to_base64() {
let pel = r#"[":apply", "0-86", [":ref", "0-29", "toBase64"], [":apply", "30-76", [":ref", "30-59", "fromBase64"], [":str", "60-66", "AAAA"]]]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_str(), Some("AAAA"));
}
#[test]
fn to_base64_not_enough_args() {
let pel = r#"[":apply", "0-86", [":ref", "0-29", "toBase64"]]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new().eval(&expression).unwrap_err();
assert_eq!(result.kind, RuntimeErrorKind::NotEnoughArguments);
}
#[test]
fn to_base64_to_many_args() {
let pel = r#"[":apply", "0-86", [":ref", "0-29", "toBase64"], [":apply", "30-76", [":ref", "30-59", "fromBase64"], [":str", "60-66", "AAAA"]], [":str", "78-85", "UTF-8"]]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new().eval(&expression).unwrap_err();
assert_eq!(result.kind, RuntimeErrorKind::TooManyArguments);
}
#[test]
fn decode_basic_auth() {
let pel = r#"[":apply", "0-143", [":ref", "0-29", "toString"], [":apply", "30-133", [":ref", "30-60", "fromBase64"], [":apply", "61-132", [":ref", "61-94", "substringAfter"], [":str", "95-120", "bearer c29tZTpvdGhlcg=="], [":str", "122-131", "bearer "]]], [":str", "135-142", "UTF-8"]]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_str().unwrap(), "some:other");
}
#[test]
fn decode_basic_auth_auto_coerce() {
let pel = r#"[":apply", "0-90", [":ref", "0-33", "substringAfter"], [":apply", "34-84", [":ref", "34-64", "fromBase64"], [":str", "65-83", "c29tZTpvdGhlcg=="]], [":str", "86-89", ":"]]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_str().unwrap(), "other");
}
#[test]
fn encode_basic_auth() {
let pel = r#"[":apply", "0-82", [":ref", "0-28", "toBase64"], [":apply", "29-81", [":ref", "29-58", "toBinary"], [":str", "59-71", "some:other"], [":str", "73-80", "utf-8"]]]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_str().unwrap(), "c29tZTpvdGhlcg==");
}
#[test]
fn encode_basic_auth_auto_coerce() {
let pel =
r#"[":apply", "0-0", [":ref", "0-28", "toBase64"], [":str", "0-0", "some:other"]]"#;
let expression = Parser::new().parse_str(pel).unwrap();
let result = Runtime::new()
.eval(&expression)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_str().unwrap(), "c29tZTpvdGhlcg==");
}
#[test]
fn multimap_return_full() {
let origin = "<envelope><some>b</some></envelope>";
let value = DocumentBuilder::root(
QualifiedName::new("", "envelope"),
Rc::new(origin.to_string()),
0..origin.len(),
)
.with_element(QualifiedName::new("", "some"), 10..24)
.with_text("b")
.finish_element()
.finish_element()
.build();
let pel = r#"[":ref", "0-0", "payload"]"#;
assert_eq!(
evaluate(pel, value).as_doc_node().unwrap().content(),
Some(origin.to_string())
)
}
#[test]
fn multimap_return_obj() {
let origin = "<envelope><some>b</some></envelope>";
let value = DocumentBuilder::root(
QualifiedName::new("", "envelope"),
Rc::new(origin.to_string()),
0..origin.len(),
)
.with_element(QualifiedName::new("", "some"), 10..24)
.with_text("b")
.finish_element()
.finish_element()
.build();
let pel = r#"
[".", "0-0",
[":ref", "0-0", "payload"],
[":str", "0-0", "envelope"]
]
"#;
assert_eq!(
evaluate(pel, value).as_doc_node().unwrap().content(),
Some("<?xml version='1.0' encoding='UTF-8'?>\n<some>\n b\n</some>".to_string())
)
}
#[test]
fn access_multimap_by_name() {
let origin = "<envelope>b</envelope>";
let value = DocumentBuilder::root(
QualifiedName::new("", "envelope"),
Rc::new(origin.to_string()),
0..origin.len(),
)
.with_text("b")
.finish_element()
.build();
let pel = r#"
["==", "0-0",
[":str", "0-0", "b"],
[".", "0-0",
[":ref", "0-0", "payload"],
[":str", "0-0", "envelope"]
]
]
"#;
evaluate_and_assert(pel, value, Value::bool(true))
}
#[test]
fn multimap_content() {
let value = DocumentBuilder::root(
QualifiedName::new("", "envelope"),
Rc::new(Default::default()),
0..0,
)
.with_element(QualifiedName::prefixed("s", "some-uri", "some"), 0..0)
.with_attribute(
QualifiedName::prefixed("s", "some-uri", "qualified"),
"qualified",
)
.with_attribute(
QualifiedName::prefixed("", "some-uri", "unqualified"),
"unqualified",
)
.with_element(QualifiedName::prefixed("s", "some-uri", "first"), 0..0)
.finish_element()
.with_element(QualifiedName::prefixed("s", "some-uri", "second"), 0..0)
.with_text("b")
.finish_element()
.finish_element()
.finish_element()
.build();
let pel = r#"
[".", "0-0",
[":ref", "0-0", "payload"],
[":str", "0-0", "envelope"]
]
"#;
assert_eq!(
evaluate(pel, value.clone())
.as_doc_node()
.unwrap()
.content(),
Some(
r#"<?xml version='1.0' encoding='UTF-8'?>
<s:some xmlns:s="some-uri" s:qualified="qualified" xmlns="some-uri" unqualified="unqualified">
<s:first/>
<s:second>
b
</s:second>
</s:some>"#
.to_string()
)
);
let pel = r#"
[".", "0-0",
[".", "0-0",
[":ref", "0-0", "payload"],
[":str", "0-0", "envelope"]
],
[":str", "0-0", "some"]
]
"#;
assert_eq!(evaluate(pel, value).as_doc_node().unwrap().content(), None)
}
#[test]
fn access_multimap_by_qname() {
let value = DocumentBuilder::root(
QualifiedName::new("http://test.com", "envelope"),
Rc::new("".to_string()),
0..0,
)
.with_text("b")
.finish_element()
.build();
let pel = r#"
["==", "0-0",
[":str", "0-0", "b"],
[".", "0-0", [":ref", "0-0", "payload"], [":name", "0-0", [":ns", "0-0", "http://test.com"], [":str", "0-0", "envelope"]]]
]
"#;
evaluate_and_assert(pel, value, Value::bool(true))
}
#[test]
fn access_multimap_attribute_by_name() {
let value = DocumentBuilder::root(
QualifiedName::new("http://test.com", "envelope"),
Rc::new("".to_string()),
0..0,
)
.with_attribute(QualifiedName::new("http://another.com", "attr"), "another")
.with_attribute(QualifiedName::new("http://test.com", "attr"), "JJ")
.with_text("b")
.finish_element()
.build();
let pel = r#"
["==", "0-0",
[":str", "0-0", "another"],
["@", "0-0", [".", "0-0", [":ref", "0-0", "payload"], [":str", "0-0", "envelope"]], [":str", "0-0", "attr"]]
]
"#;
evaluate_and_assert(pel, value, Value::bool(true))
}
#[test]
fn access_multimap_attribute_by_qname() {
let value = DocumentBuilder::root(
QualifiedName::new("http://test.com", "envelope"),
Rc::new("".to_string()),
0..0,
)
.with_attribute(QualifiedName::new("http://another.com", "attr"), "another")
.with_attribute(QualifiedName::new("http://test.com", "attr"), "JJ")
.with_text("b")
.finish_element()
.build();
let pel = r#"
["==", "0-0",
[":str", "0-0", "JJ"],
["@", "0-0", [".", "0-0", [":ref", "0-0", "payload"], [":str", "0-0", "envelope"]], [":name", "0-0", [":ns", "0-0", "http://test.com"], [":str", "0-0", "attr"]]]
]
"#;
evaluate_and_assert(pel, value, Value::bool(true))
}
#[test]
fn access_multimap_by_non_existing_key() {
let value = DocumentBuilder::root(
QualifiedName::new("", "envelope"),
Rc::new("".to_string()),
0..0,
)
.with_text("b")
.finish_element()
.build();
let pel = r#"
["==", "0-0",
[".", "0-0",
[":ref", "0-0", "payload"],
[":str", "0-0", "env"]
],
[":null", "0-0"]
]
"#;
evaluate_and_assert(pel, value, Value::bool(true))
}
#[test]
fn access_multimap_by_repeated_key() {
let value = DocumentBuilder::root(
QualifiedName::new("", "envelope"),
Rc::new("".to_string()),
0..0,
)
.with_element(QualifiedName::new("", "a"), 0..0)
.with_text("a1")
.finish_element()
.with_element(QualifiedName::new("", "a"), 0..0)
.with_text("a2")
.finish_element()
.finish_element()
.build();
let pel = r#"
["==", "0-0",
[".", "0-0",
[".", "0-0",
[":ref", "0-0", "payload"],
[":str", "0-0", "envelope"]
],
[":str", "0-0", "a"]
],
[":str", "0-0", "a1"]
]
"#;
evaluate_and_assert(pel, value, Value::bool(true))
}
#[test]
fn compare_multimaps() {
let value = DocumentBuilder::root(
QualifiedName::new("", "envelope"),
Rc::new("".to_string()),
0..0,
)
.with_element(QualifiedName::new("", "a1"), 0..0)
.with_text("a")
.with_element(QualifiedName::new("", "b"), 0..0)
.finish_element()
.finish_element()
.with_element(QualifiedName::new("", "a2"), 0..0)
.with_text("a")
.with_element(QualifiedName::new("", "b"), 0..0)
.finish_element()
.finish_element()
.finish_element()
.build();
let pel = r#"
["==", "0-0",
[".", "0-0",
[".", "0-0",
[":ref", "0-0", "payload"],
[":str", "0-0", "envelope"]
],
[":str", "0-0", "a1"]
],
[".", "0-0",
[".", "0-0",
[":ref", "0-0", "payload"],
[":str", "0-0", "envelope"]
],
[":str", "0-0", "a2"]
]
]
"#;
evaluate_and_assert(pel, value, Value::bool(true))
}
#[test]
fn access_multimap_by_index() {
let value = DocumentBuilder::root(
QualifiedName::new("", "envelope"),
Rc::new("".to_string()),
0..0,
)
.with_text("b")
.finish_element()
.build();
let pel = r#"
["==", "0-0",
[".", "0-0",
[":ref", "0-0", "payload"],
[":nbr", "0-0", "0"]
],
[":str", "0-0", "b"]
]
"#;
evaluate_and_assert(pel, value, Value::bool(true))
}
#[test]
fn access_multimap_by_non_existing_index() {
let value = DocumentBuilder::root(
QualifiedName::new("", "envelope"),
Rc::new("".to_string()),
0..0,
)
.with_text("b")
.finish_element()
.build();
let pel = r#"
["==", "0-0",
[".", "0-0",
[":ref", "0-0", "payload"],
[":nbr", "0-0", "1"]
],
[":null", "0-0"]
]
"#;
evaluate_and_assert(pel, value, Value::bool(true))
}
#[test]
fn access_multimap_multivalue_by_name() {
let value = DocumentBuilder::root(
QualifiedName::new("http://test.com", "envelope"),
Rc::new("".to_string()),
0..0,
)
.with_element(QualifiedName::new("http://test.com", "b"), 0..0)
.with_text("1")
.finish_element()
.with_element(QualifiedName::new("http://test.com", "b"), 0..0)
.with_text("2")
.finish_element()
.finish_element()
.build();
let pel = r#"
["*", "0-0", [".", "0-0", [":ref", "0-0", "payload"], [":str", "0-0", "envelope"]], [":str", "0-0", "b"]]
"#;
let expected = Value::array(vec![Value::string("1"), Value::string("2")]);
evaluate_and_assert(pel, value, expected)
}
#[test]
fn access_multimap_multivalue_by_qname() {
let value = DocumentBuilder::root(
QualifiedName::new("http://test.com", "envelope"),
Rc::new("".to_string()),
0..0,
)
.with_element(QualifiedName::new("http://test.com", "b"), 0..0)
.with_text("1")
.finish_element()
.with_element(QualifiedName::new("http://other.com", "b"), 0..0)
.with_text("2")
.finish_element()
.with_element(QualifiedName::new("http://test.com", "b"), 0..0)
.with_text("3")
.finish_element()
.finish_element()
.build();
let pel = r#"
["*", "0-0", [".", "0-0", [":ref", "0-0", "payload"], [":str", "0-0", "envelope"]], [":name", "0-0", [":ns", "0-0", "http://test.com"], [":str", "0-0", "b"]]]
"#;
let expected = Value::array(vec![Value::string("1"), Value::string("3")]);
evaluate_and_assert(pel, value, expected)
}
#[cfg(feature = "experimental_coerced_type")]
#[test]
fn multimap_root_coerced() {
let origin = "<envelope><some>b</some></envelope>";
let value = DocumentBuilder::root(
QualifiedName::new("", "envelope"),
Rc::new(origin.to_string()),
0..origin.len(),
)
.with_element(QualifiedName::new("", "some"), 10..24)
.with_text("b")
.finish_element()
.finish_element()
.build();
let pel = r#"[":apply", "0-0", [":ref", "0-0", "++"], [":ref", "0-0", "payload"], [":str", "0-0", "envelope"]]"#;
assert_eq!(
evaluate(pel, value.clone()).as_str().unwrap(),
origin.to_string() + "envelope"
);
let pel = r#"[":apply", "0-0", [":ref", "0-0", "++"], [":str", "0-0", "envelope"], [":ref", "0-0", "payload"]]"#;
assert_eq!(
evaluate(pel, value.clone()).as_str().unwrap(),
"envelope".to_string() + origin
);
let pel = r#"
["==", "0-0",
[":ref", "0-0", "payload"],
[":str", "0-0", "<envelope><some>b</some></envelope>"]
]
"#;
assert!(evaluate(pel, value).as_bool().unwrap());
}
#[test]
fn multimap_not_coerced() {
let origin = "<envelope><some>b</some></envelope>";
let value = DocumentBuilder::root(
QualifiedName::new("", "envelope"),
Rc::new(origin.to_string()),
0..origin.len(),
)
.with_element(QualifiedName::new("", "some"), 10..24)
.with_text("b")
.finish_element()
.finish_element()
.build();
let pel = r#"[":apply", "0-0",
[":ref", "0-0", "++"],
[".", "0-0",
[":ref", "0-0", "payload"],
[":str", "0-0", "envelope"]
],
[":str", "0-0", "envelope"]
]"#;
assert_eq!(
&RuntimeErrorKind::TypeMismatch,
evaluate_error(pel, value.clone()).kind(),
);
let pel = r#"
["==", "0-0",
[".", "0-0",
[":ref", "0-0", "payload"],
[":str", "0-0", "envelope"]
],
[":str", "0-0", "<some>b</some>"]
]
"#;
assert!(!evaluate(pel, value).as_bool().unwrap());
}
#[cfg(feature = "experimental_coerced_type")]
#[test]
fn object_root_coerced() {
let origin = r#"{"envelope":{"some":"value"}}"#;
let value = Value::coerced_object(
HashMap::from([(
"envelope".to_string(),
Value::object(HashMap::from([(
"some".to_string(),
Value::string("value"),
)])),
)]),
Rc::new(origin.to_string()),
);
let pel = r#"[":apply", "0-0", [":ref", "0-0", "++"], [":ref", "0-0", "payload"], [":str", "0-0", "envelope"]]"#;
assert_eq!(
evaluate(pel, value.clone()).as_str().unwrap(),
origin.to_string() + "envelope"
);
let pel = r#"[":apply", "0-0", [":ref", "0-0", "++"], [":str", "0-0", "envelope"], [":ref", "0-0", "payload"]]"#;
assert_eq!(
evaluate(pel, value.clone()).as_str().unwrap(),
"envelope".to_string() + origin
);
let pel = r#"
["==", "0-0",
[":ref", "0-0", "payload"],
[":str", "0-0", "{\"envelope\":{\"some\":\"value\"}}"]
]
"#;
assert!(evaluate(pel, value).as_bool().unwrap());
}
#[test]
fn object_not_coerced() {
#[cfg(feature = "experimental_coerced_type")]
let origin = r#"{"envelope":{"some":"value"}}"#;
let map = HashMap::from([(
"envelope".to_string(),
Value::object(HashMap::from([(
"some".to_string(),
Value::string("value"),
)])),
)]);
#[cfg(feature = "experimental_coerced_type")]
let value = Value::coerced_object(map, Rc::new(origin.to_string()));
#[cfg(not(feature = "experimental_coerced_type"))]
let value = Value::object(map);
let pel = r#"[":apply", "0-0",
[":ref", "0-0", "++"],
[".", "0-0",
[":ref", "0-0", "payload"],
[":str", "0-0", "envelope"]
],
[":str", "0-0", "envelope"]
]"#;
assert_eq!(
&RuntimeErrorKind::TypeMismatch,
evaluate_error(pel, value.clone()).kind(),
);
let pel = r#"
["==", "0-0",
[".", "0-0",
[":ref", "0-0", "payload"],
[":str", "0-0", "envelope"]
],
[":str", "0-0", "{\"some\":\"value\"}"]
]
"#;
assert!(!evaluate(pel, value).as_bool().unwrap());
}
fn evaluate_error(pel: &str, payload: Value) -> RuntimeError {
let context: Prelude = HashMap::from([("payload", payload)]);
let expression = Parser::new().parse_str(pel).unwrap();
Runtime::new()
.eval_with_context(&expression, &context)
.err()
.unwrap()
}
fn evaluate(pel: &str, payload: Value) -> Value {
let context: Prelude = HashMap::from([("payload", payload)]);
let expression = Parser::new().parse_str(pel).unwrap();
Runtime::new()
.eval_with_context(&expression, &context)
.unwrap()
.complete()
.unwrap()
}
fn evaluate_and_assert(pel: &str, payload: Value, expected: Value) {
assert_eq!(evaluate(pel, payload), expected)
}
mod partial_evaluation {
use std::collections::HashMap;
use crate::{
expression::Symbol,
parser::Parser,
runtime::{value::Value, Binding, Context, Runtime, ValueHandler},
Reference,
};
struct TestContextChain {
contexts: Vec<HashMap<String, Value>>,
}
impl TestContextChain {
fn new<const N: usize>(entries: [(&str, Value); N]) -> Self {
Self { contexts: vec![] }.then(entries)
}
fn then<const N: usize>(mut self, entries: [(&str, Value); N]) -> Self {
self.contexts
.push(entries.map(|(k, v)| (k.to_string(), v)).into());
self
}
fn next(mut self) -> Self {
self.contexts.remove(0);
self
}
}
impl Context for TestContextChain {
fn resolve(&self, symbol: &Symbol) -> Binding {
let s = symbol.as_str();
match self.contexts.first().expect("Empty context").get(s) {
Some(value) => Binding::Available(value.clone()),
None => {
if self.contexts.iter().any(|c| c.contains_key(s)) {
Binding::Pending
} else {
Binding::Unknown
}
}
}
}
fn value_handler(&self, _reference: Reference) -> Option<&dyn ValueHandler> {
None
}
}
#[test]
fn if_else_pending_condition() {
let runtime = Runtime::new();
let parser = Parser::new();
let context_1 = TestContextChain::new([("a", Value::string("ctx1".to_string()))])
.then([("condition", Value::bool(true))])
.then([("b", Value::string("ctx3".to_string()))]);
let pel_1 = r#"[":if", "0-23", [":ref", "4-9", "condition"], [":ref", "11-14", "a"], [":ref", "20-23", "b"]]"#;
let expression_1 = parser.parse_str(pel_1).unwrap();
let expression_2 = runtime
.eval_with_context(&expression_1, &context_1)
.unwrap()
.partial()
.unwrap();
let pel_2 = r#"[":if", "0-23", [":ref", "4-9", "condition"], [":str", "11-14", "ctx1"], [":ref", "20-23", "b"]]"#;
assert_eq!(expression_2, parser.parse_str(pel_2).unwrap());
let context_2 = context_1.next();
let result = runtime
.eval_with_context(&expression_2, &context_2)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_str().unwrap(), "ctx1");
}
#[test]
fn if_else_pending_true_branch() {
let runtime = Runtime::new();
let parser = Parser::new();
let context_1 = TestContextChain::new([("a", Value::string("ctx1".to_string()))])
.then([("b", Value::string("ctx2".to_string()))]);
let pel_1 = r#"[":if", "0-23", [":bool", "4-9", "true"], [":ref", "11-14", "b"], [":ref", "20-23", "a"]]"#;
let expression_1 = parser.parse_str(pel_1).unwrap();
let expression_2 = runtime
.eval_with_context(&expression_1, &context_1)
.unwrap()
.partial()
.unwrap();
let pel_2 = r#"[":ref", "11-14", "b"]"#;
assert_eq!(expression_2, parser.parse_str(pel_2).unwrap());
let context_2 = context_1.next();
let result = runtime
.eval_with_context(&expression_2, &context_2)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_str().unwrap(), "ctx2");
}
#[test]
fn if_else_pending_false_branch() {
let runtime = Runtime::new();
let parser = Parser::new();
let context_1 = TestContextChain::new([("a", Value::string("ctx1".to_string()))])
.then([("b", Value::string("ctx2".to_string()))]);
let pel_1 = r#"[":if", "0-23", [":bool", "4-9", "false"], [":ref", "11-14", "a"], [":ref", "20-23", "b"]]"#;
let expression_1 = parser.parse_str(pel_1).unwrap();
let expression_2 = runtime
.eval_with_context(&expression_1, &context_1)
.unwrap()
.partial()
.unwrap();
let pel_2 = r#"[":ref", "20-23", "b"]"#;
assert_eq!(expression_2, parser.parse_str(pel_2).unwrap());
let context_2 = context_1.next();
let result = runtime
.eval_with_context(&expression_2, &context_2)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_str().unwrap(), "ctx2");
}
#[test]
fn default_left_unavailable() {
let runtime = Runtime::new();
let parser = Parser::new();
let context_1 = TestContextChain::new([("right", Value::string("ctx1".to_string()))])
.then([("left", Value::null())]);
let pel_1 = r#"
[":default", "0-6",
[":ref", "0-1", "left"],
[":ref", "5-6", "right"]
]
"#;
let expression_1 = parser.parse_str(pel_1).unwrap();
let expression_2 = runtime
.eval_with_context(&expression_1, &context_1)
.unwrap()
.partial()
.unwrap();
let pel_2 = r#"
[":default", "0-6",
[":ref", "0-1", "left"],
[":str", "5-6", "ctx1"]
]
"#;
assert_eq!(expression_2, parser.parse_str(pel_2).unwrap());
let context_2 = context_1.next();
let result = runtime
.eval_with_context(&expression_2, &context_2)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_str().unwrap(), "ctx1");
}
#[test]
fn default_right_unavailable() {
let runtime = Runtime::new();
let parser = Parser::new();
let context_1 = TestContextChain::new([("left", Value::null())])
.then([("right", Value::string("ctx1".to_string()))]);
let pel_1 = r#"
[":default", "0-6",
[":ref", "0-1", "left"],
[":ref", "5-6", "right"]
]
"#;
let expression_1 = parser.parse_str(pel_1).unwrap();
let expression_2 = runtime
.eval_with_context(&expression_1, &context_1)
.unwrap()
.partial()
.unwrap();
let pel_2 = r#"
[":ref", "5-6", "right"]
"#;
assert_eq!(expression_2, parser.parse_str(pel_2).unwrap());
let context_2 = context_1.next();
let result = runtime
.eval_with_context(&expression_2, &context_2)
.unwrap()
.complete()
.unwrap();
assert_eq!(result.as_str().unwrap(), "ctx1");
}
}
}