diff --git a/pom.xml b/pom.xml
index fd5b1bb69..64198c718 100644
--- a/pom.xml
+++ b/pom.xml
@@ -328,6 +328,17 @@
org/camunda/feel/syntaxtree/Range
8001
+
+
+ org/camunda/feel/syntaxtree/**
+ 7002
+ scala.Function1 andThen(scala.Function1)
+
+
+ org/camunda/feel/syntaxtree/**
+ 7002
+ scala.Function1 compose(scala.Function1)
+
diff --git a/src/main/scala/org/camunda/feel/FeelEngine.scala b/src/main/scala/org/camunda/feel/FeelEngine.scala
index 8568a47ec..736ab641b 100644
--- a/src/main/scala/org/camunda/feel/FeelEngine.scala
+++ b/src/main/scala/org/camunda/feel/FeelEngine.scala
@@ -26,7 +26,7 @@ import org.camunda.feel.FeelEngine.{
import org.camunda.feel.context.{Context, FunctionProvider, VariableProvider}
import org.camunda.feel.impl.interpreter.{BuiltinFunctions, EvalContext, FeelInterpreter}
import org.camunda.feel.impl.parser.{ExpressionValidator, FeelParser}
-import org.camunda.feel.syntaxtree.{Exp, ParsedExpression, ValError}
+import org.camunda.feel.syntaxtree.{Exp, ParsedExpression, ValError, ValFatalError}
import org.camunda.feel.valuemapper.ValueMapper.CompositeValueMapper
import org.camunda.feel.valuemapper.{CustomValueMapper, ValueMapper}
@@ -205,9 +205,11 @@ class FeelEngine(
private def eval(exp: ParsedExpression, context: EvalContext): EvalExpressionResult = {
interpreter.eval(exp.expression)(context) match {
- case ValError(cause) =>
+ case ValError(cause) =>
Left(Failure(s"failed to evaluate expression '${exp.text}': $cause"))
- case value => Right(valueMapper.unpackVal(value))
+ case ValFatalError(cause) =>
+ Left(Failure(s"failed to evaluate expression '${exp.text}': $cause"))
+ case value => Right(valueMapper.unpackVal(value))
}
}
diff --git a/src/main/scala/org/camunda/feel/impl/DefaultValueMapper.scala b/src/main/scala/org/camunda/feel/impl/DefaultValueMapper.scala
index 2598adc19..6b98e579b 100644
--- a/src/main/scala/org/camunda/feel/impl/DefaultValueMapper.scala
+++ b/src/main/scala/org/camunda/feel/impl/DefaultValueMapper.scala
@@ -35,6 +35,7 @@ import org.camunda.feel.syntaxtree.{
ValDateTime,
ValDayTimeDuration,
ValError,
+ ValFatalError,
ValFunction,
ValList,
ValLocalDateTime,
@@ -170,8 +171,9 @@ class DefaultValueMapper extends CustomValueMapper {
}.toMap
)
- case f: ValFunction => Some(f)
- case e: ValError => Some(e)
+ case f: ValFunction => Some(f)
+ case e: ValError => Some(e)
+ case fatalError: ValFatalError => Some(fatalError)
case _ => None
}
diff --git a/src/main/scala/org/camunda/feel/impl/builtin/BuiltinFunction.scala b/src/main/scala/org/camunda/feel/impl/builtin/BuiltinFunction.scala
index 6a68b2dc2..3a6da6dfa 100644
--- a/src/main/scala/org/camunda/feel/impl/builtin/BuiltinFunction.scala
+++ b/src/main/scala/org/camunda/feel/impl/builtin/BuiltinFunction.scala
@@ -17,7 +17,7 @@
package org.camunda.feel.impl.builtin
import org.camunda.feel.logger
-import org.camunda.feel.syntaxtree.{Val, ValError, ValFunction, ValNull}
+import org.camunda.feel.syntaxtree.{Val, ValError, ValFatalError, ValFunction, ValNull}
object BuiltinFunction {
@@ -34,12 +34,13 @@ object BuiltinFunction {
}
private def error: PartialFunction[List[Val], Any] = {
- case vars if (vars.exists(_.isInstanceOf[ValError])) =>
- vars.filter(_.isInstanceOf[ValError]).head.asInstanceOf[ValError]
- case e => {
- logger.warn(s"Suppressed failure: illegal arguments: $e")
+ case args if args.exists(_.isInstanceOf[ValFatalError]) =>
+ args.find(_.isInstanceOf[ValFatalError])
+ case args if args.exists(_.isInstanceOf[ValError]) => args.find(_.isInstanceOf[ValError])
+ case args =>
+ val argumentList = args.map("'" + _ + "'").mkString(", ")
+ logger.warn(s"Suppressed failure: Illegal arguments: $argumentList")
ValNull
- }
}
}
diff --git a/src/main/scala/org/camunda/feel/impl/interpreter/FeelInterpreter.scala b/src/main/scala/org/camunda/feel/impl/interpreter/FeelInterpreter.scala
index b8f9d0571..5b8edfb65 100644
--- a/src/main/scala/org/camunda/feel/impl/interpreter/FeelInterpreter.scala
+++ b/src/main/scala/org/camunda/feel/impl/interpreter/FeelInterpreter.scala
@@ -16,103 +16,15 @@
*/
package org.camunda.feel.impl.interpreter
-import java.time.{Duration, Period}
import org.camunda.feel.FeelEngine.UnaryTests
import org.camunda.feel.context.Context
+import org.camunda.feel.impl.interpreter.FeelInterpreter.INPUT_VALUE_SYMBOL
+import org.camunda.feel.syntaxtree._
import org.camunda.feel.valuemapper.ValueMapper
-import org.camunda.feel.syntaxtree.{
- Addition,
- ArithmeticNegation,
- AtLeastOne,
- ClosedConstRangeBoundary,
- ClosedRangeBoundary,
- Comparison,
- Conjunction,
- ConstBool,
- ConstContext,
- ConstDate,
- ConstDateTime,
- ConstDayTimeDuration,
- ConstInputValue,
- ConstList,
- ConstLocalDateTime,
- ConstLocalTime,
- ConstNull,
- ConstNumber,
- ConstRange,
- ConstRangeBoundary,
- ConstString,
- ConstTime,
- ConstYearMonthDuration,
- Disjunction,
- Division,
- Equal,
- EveryItem,
- Exp,
- Exponentiation,
- Filter,
- For,
- FunctionDefinition,
- FunctionInvocation,
- FunctionParameters,
- GreaterOrEqual,
- GreaterThan,
- If,
- In,
- InputEqualTo,
- InputGreaterOrEqual,
- InputGreaterThan,
- InputInRange,
- InputLessOrEqual,
- InputLessThan,
- InstanceOf,
- IterationContext,
- JavaFunctionInvocation,
- LessOrEqual,
- LessThan,
- Multiplication,
- NamedFunctionParameters,
- Not,
- OpenConstRangeBoundary,
- OpenRangeBoundary,
- PathExpression,
- PositionalFunctionParameters,
- QualifiedFunctionInvocation,
- RangeBoundary,
- Ref,
- SomeItem,
- Subtraction,
- UnaryTestExpression,
- Val,
- ValBoolean,
- ValContext,
- ValDate,
- ValDateTime,
- ValDayTimeDuration,
- ValError,
- ValFunction,
- ValList,
- ValLocalDateTime,
- ValLocalTime,
- ValNull,
- ValNumber,
- ValRange,
- ValString,
- ValTime,
- ValYearMonthDuration,
- ZonedTime
-}
-import org.camunda.feel.{
- Date,
- DateTime,
- DayTimeDuration,
- LocalDateTime,
- LocalTime,
- Number,
- Time,
- YearMonthDuration,
- logger
-}
+import org.camunda.feel.{Number, logger}
+
+import java.time.{Duration, Period}
+import scala.reflect.ClassTag
/** @author
* Philipp Ossler
@@ -124,7 +36,7 @@ class FeelInterpreter {
// literals
case ConstNull => ValNull
- case ConstInputValue => input
+ case ConstInputValue => getInputValueBySymbol
case ConstNumber(x) => ValNumber(x)
case ConstBool(b) => ValBoolean(b)
case ConstString(s) => ValString(s)
@@ -153,19 +65,19 @@ class FeelInterpreter {
// simple unary tests
case InputEqualTo(x) =>
- withVal(input, i => checkEquality(i, eval(x), _ == _, ValBoolean))
+ withVal(getImplicitInputValue, i => checkEquality(i, eval(x), _ == _, ValBoolean))
case InputLessThan(x) =>
- withVal(input, i => dualOp(i, eval(x), _ < _, ValBoolean))
+ withVal(getImplicitInputValue, i => dualOp(i, eval(x), _ < _, ValBoolean))
case InputLessOrEqual(x) =>
- withVal(input, i => dualOp(i, eval(x), _ <= _, ValBoolean))
+ withVal(getImplicitInputValue, i => dualOp(i, eval(x), _ <= _, ValBoolean))
case InputGreaterThan(x) =>
- withVal(input, i => dualOp(i, eval(x), _ > _, ValBoolean))
+ withVal(getImplicitInputValue, i => dualOp(i, eval(x), _ > _, ValBoolean))
case InputGreaterOrEqual(x) =>
- withVal(input, i => dualOp(i, eval(x), _ >= _, ValBoolean))
+ withVal(getImplicitInputValue, i => dualOp(i, eval(x), _ >= _, ValBoolean))
case InputInRange(range @ ConstRange(start, end)) =>
unaryOpDual(eval(start.value), eval(end.value), isInRange(range), ValBoolean)
- case UnaryTestExpression(x) => withVal(eval(x), unaryTestExpression)
+ case UnaryTestExpression(x) => unaryTestExpression(x)
// arithmetic operations
case Addition(x, y) => withValOrNull(addOp(eval(x), eval(y)))
@@ -174,16 +86,17 @@ class FeelInterpreter {
case Division(x, y) => withValOrNull(divOp(eval(x), eval(y)))
case Exponentiation(x, y) =>
withValOrNull(
- dualNumericOp(
+ withNumbers(
eval(x),
eval(y),
- (x, y) =>
- if (y.isWhole) {
+ (x, y) => {
+ val result: Number = if (y.isWhole) {
x.pow(y.toInt)
} else {
math.pow(x.toDouble, y.toDouble)
- },
- ValNumber
+ }
+ ValNumber(result)
+ }
)
)
case ArithmeticNegation(x) =>
@@ -214,19 +127,19 @@ class FeelInterpreter {
}
)
case In(x, test) =>
- withVal(eval(x), x => eval(test)(context.add(inputKey -> x)))
+ withVal(eval(x), x => eval(test)(context.add(getInputVariableName -> x)))
case InstanceOf(x, typeName) =>
withVal(
eval(x),
x => {
+ val valueType = getTypeName(x.getClass)
+
typeName match {
- case "Any" if x != ValNull => ValBoolean(true)
- case "years and months duration" =>
- withType(x, t => ValBoolean(t == "year-month-duration"))
- case "days and time duration" =>
- withType(x, t => ValBoolean(t == "day-time-duration"))
- case "date and time" => withType(x, t => ValBoolean(t == "date time"))
- case _ => withType(x, t => ValBoolean(t == typeName))
+ case "Any" => ValBoolean(x != ValNull)
+ case "date time" => ValBoolean("date and time" == valueType)
+ case "year-month-duration" => ValBoolean("years and months duration" == valueType)
+ case "day-time-duration" => ValBoolean("days and time duration" == valueType)
+ case _ => ValBoolean(typeName == valueType)
}
}
)
@@ -239,12 +152,13 @@ class FeelInterpreter {
case SomeItem(iterators, condition) =>
withCartesianProduct(
iterators,
- p => atLeastOne(p.map(vars => () => eval(condition)(context.addAll(vars))), ValBoolean)
+ p =>
+ atLeastOneValue(p.map(vars => () => eval(condition)(context.addAll(vars))), ValBoolean)
)
case EveryItem(iterators, condition) =>
withCartesianProduct(
iterators,
- p => all(p.map(vars => () => eval(condition)(context.addAll(vars))), ValBoolean)
+ p => allValues(p.map(vars => () => eval(condition)(context.addAll(vars))), ValBoolean)
)
case For(iterators, exp) =>
withCartesianProduct(
@@ -323,10 +237,12 @@ class FeelInterpreter {
)
// unsupported expression
- case exp => ValError(s"unsupported expression '$exp'")
+ case exp => ValError(s"Unsupported expression '$exp'")
}
+ // ======== helpers ====================
+
private def mapEither[T, R](
it: Iterable[T],
f: T => Either[ValError, R],
@@ -360,221 +276,143 @@ class FeelInterpreter {
}
}
+ // =========================================
+
private def error(x: Val, message: String) = x match {
case e: ValError => e
case _ => ValError(message)
}
+ private def withVal(x: Val, f: Val => Val): Val = x match {
+ case fatalError: ValFatalError => fatalError
+ case error: ValError => error
+ case value => f(value)
+ }
+
private def withValOrNull(x: Val): Val = x match {
- case ValError(e) => {
+ case fatalError: ValFatalError => fatalError
+ case ValError(e) => {
logger.warn(s"Suppressed failure: $e")
ValNull
}
- case _ => x
+ case _ => x
}
- private def unaryOpDual(x: Val, y: Val, c: (Val, Val, Val) => Boolean, f: Boolean => Val)(implicit
- context: EvalContext
- ): Val =
- withVal(
- input,
- _ match {
- case ValNull => f(false)
- case _ if (x == ValNull || y == ValNull) => f(false)
- case i if (!i.isComparable) => ValError(s"$i is not comparable")
- case _ if (!x.isComparable) => ValError(s"$x is not comparable")
- case _ if (!y.isComparable) => ValError(s"$y is not comparable")
- case i if (i.getClass != x.getClass) =>
- ValError(s"$i can not be compared to $x")
- case i if (i.getClass != y.getClass) =>
- ValError(s"$i can not be compared to $y")
- case i => f(c(i, x, y))
- }
- )
+ private def withValues(x: Val, y: Val, f: (Val, Val) => Val) =
+ withVal(x, valueX => withVal(y, valueY => f(valueX, valueY)))
- private def withNumbers(x: Val, y: Val, f: (Number, Number) => Val): Val =
- withNumber(
- x,
- x => {
- withNumber(
- y,
- y => {
- f(x, y)
- }
+ private def withValueType[T <: Val](value: Val, f: T => Val)(implicit
+ context: EvalContext,
+ tag: ClassTag[T]
+ ): Val = {
+ value match {
+ case fatalError: ValFatalError => fatalError
+ case error: ValError => error
+ case v: T if tag.runtimeClass.isInstance(value) => f(v)
+ case other =>
+ error(
+ value,
+ s"Expected ${getTypeName(tag.runtimeClass)} but found '$other'"
)
- }
- )
-
- private def withNumber(x: Val, f: Number => Val): Val = x match {
- case ValNumber(x) => f(x)
- case _ => error(x, s"expected Number but found '$x'")
+ }
}
- private def withBoolean(x: Val, f: Boolean => Val): Val = x match {
- case ValBoolean(x) => f(x)
- case _ => error(x, s"expected Boolean but found '$x'")
+ private def getTypeName(valueType: Class[_]): String = valueType match {
+ case _ if valueType == ValNull.getClass => "null"
+ case _ if valueType == classOf[ValNumber] => "number"
+ case _ if valueType == classOf[ValBoolean] => "boolean"
+ case _ if valueType == classOf[ValString] => "string"
+ case _ if valueType == classOf[ValDate] => "date"
+ case _ if valueType == classOf[ValTime] => "time"
+ case _ if valueType == classOf[ValLocalTime] => "time"
+ case _ if valueType == classOf[ValDateTime] => "date and time"
+ case _ if valueType == classOf[ValLocalDateTime] => "date and time"
+ case _ if valueType == classOf[ValYearMonthDuration] => "years and months duration"
+ case _ if valueType == classOf[ValDayTimeDuration] => "days and time duration"
+ case _ if valueType == classOf[ValList] => "list"
+ case _ if valueType == classOf[ValContext] => "context"
+ case _ if valueType == classOf[ValFunction] => "function"
+ case _ if valueType == classOf[ValRange] => "range"
+ case _ if valueType == classOf[ValError] => "error"
+ case _ if valueType == classOf[ValFatalError] => "fatal error"
+ case other => other.getSimpleName
}
- private def withBooleanOrNull(x: Val, f: Boolean => Val): Val = x match {
- case ValBoolean(x) => f(x)
- case _ => ValNull
- }
+ private def isComparable(values: Val*): Boolean = values.forall(_.isComparable)
- private def withBooleanOrFalse(x: Val, f: Boolean => Val): Val = x match {
- case ValBoolean(x) => f(x)
- case _ => {
- logger.warn(s"Suppressed failure: expected Boolean but found '$x'")
- f(false)
- }
- }
+ private def hasSameType(values: Val*): Boolean = values.map(_.getClass).distinct.size == 1
- private def withString(x: Val, f: String => Val): Val = x match {
- case ValString(x) => f(x)
- case _ => error(x, s"expected String but found '$x'")
- }
+ // ======== type checks ====================
- private def withDates(x: Val, y: Val, f: (Date, Date) => Val): Val =
- withDate(
- x,
- x => {
- withDate(
- y,
- y => {
- f(x, y)
- }
- )
- }
- )
+ private def withNumber(x: Val, f: Number => Val)(implicit context: EvalContext): Val =
+ withValueType[ValNumber](x, number => f(number.value))
- private def withDate(x: Val, f: Date => Val): Val = x match {
- case ValDate(x) => f(x)
- case _ => error(x, s"expected Date but found '$x'")
- }
+ private def withNumbers(x: Val, y: Val, f: (Number, Number) => Val)(implicit
+ context: EvalContext
+ ): Val =
+ withNumber(x, x => withNumber(y, y => f(x, y)))
- private def withTimes(x: Val, y: Val, f: (Time, Time) => Val): Val =
- withTime(
- x,
- x => {
- withTime(
- y,
- y => {
- f(x, y)
- }
- )
- }
- )
+ private def withBoolean(x: Val, f: Boolean => Val)(implicit context: EvalContext): Val =
+ withValueType[ValBoolean](x, boolean => f(boolean.value))
- private def withLocalTimes(x: Val, y: Val, f: (LocalTime, LocalTime) => Val): Val =
- withLocalTime(
- x,
- x => {
- withLocalTime(
- y,
- y => {
- f(x, y)
- }
- )
- }
- )
+ private def withBooleanOrNull(x: Val, f: Boolean => Val)(implicit context: EvalContext): Val =
+ withBoolean(x, f) match {
+ case _: ValError => ValNull
+ case value => value
+ }
- private def withLocalTime(x: Val, f: LocalTime => Val): Val = x match {
- case ValLocalTime(x) => f(x)
- case _ => error(x, s"expect Local Time but found '$x'")
- }
+ private def withBooleanOrFalse(x: Val, f: Boolean => Val)(implicit context: EvalContext): Val =
+ withBoolean(x, f) match {
+ case _: ValError =>
+ logger.warn(s"Suppressed failure: Expected boolean but found '$x'")
+ f(false)
+ case value => value
+ }
- private def withTime(x: Val, f: Time => Val): Val = x match {
- case ValTime(x) => f(x)
- case _ => error(x, s"expect Time but found '$x'")
- }
+ private def withFunction(x: Val, f: ValFunction => Val)(implicit context: EvalContext): Val =
+ withValueType[ValFunction](x, f)
- private def withDateTimes(x: Val, y: Val, f: (DateTime, DateTime) => Val): Val =
- withDateTime(
- x,
- x => {
- withDateTime(
- y,
- y => {
- f(x, y)
- }
- )
- }
- )
+ private def withList(x: Val, f: ValList => Val)(implicit context: EvalContext): Val =
+ withValueType[ValList](x, f)
- private def withLocalDateTimes(x: Val, y: Val, f: (LocalDateTime, LocalDateTime) => Val): Val =
- withLocalDateTime(
- x,
- x => {
- withLocalDateTime(
- y,
- y => {
- f(x, y)
- }
- )
- }
- )
+ private def withContext(x: Val, f: ValContext => Val)(implicit context: EvalContext): Val =
+ withValueType[ValContext](x, f)
- private def withDateTime(x: Val, f: DateTime => Val): Val = x match {
- case ValDateTime(x) => f(x)
- case _ => error(x, s"expect Date Time but found '$x'")
- }
+ // =========================================
- private def withLocalDateTime(x: Val, f: LocalDateTime => Val): Val =
- x match {
- case ValLocalDateTime(x) => f(x)
- case _ => error(x, s"expect Local Date Time but found '$x'")
+ private def getInputVariableName(implicit context: EvalContext): String = {
+ context.variable(UnaryTests.inputVariable) match {
+ case ValString(inputVariableName) => inputVariableName
+ case _ => UnaryTests.defaultInputVariable
}
+ }
- private def withYearMonthDurations(
- x: Val,
- y: Val,
- f: (YearMonthDuration, YearMonthDuration) => Val
- ): Val =
- withYearMonthDuration(
- x,
- x => {
- withYearMonthDuration(
- y,
- y => {
- f(x, y)
- }
- )
- }
- )
+ private def getImplicitInputValue(implicit context: EvalContext): Val = {
+ context.variable(getInputVariableName)
+ }
+
+ private def getInputValueBySymbol(implicit context: EvalContext): Val = {
+ context.variable(INPUT_VALUE_SYMBOL).toOption.getOrElse {
+ ValFatalError(
+ s"No input value available. '$INPUT_VALUE_SYMBOL' can only be used inside an unary-test expression."
+ )
+ }
+ }
- private def withDayTimeDurations(
- x: Val,
- y: Val,
- f: (DayTimeDuration, DayTimeDuration) => Val
+ private def unaryOpDual(x: Val, y: Val, c: (Val, Val, Val) => Boolean, f: Boolean => Val)(implicit
+ context: EvalContext
): Val =
- withDayTimeDuration(
- x,
- x => {
- withDayTimeDuration(
- y,
- y => {
- f(x, y)
- }
- )
+ withVal(
+ getImplicitInputValue,
+ {
+ case ValNull => f(false)
+ case _ if (x == ValNull || y == ValNull) => f(false)
+ case i if !isComparable(i, x, y) || !hasSameType(i, x, y) =>
+ ValError(s"Can't compare '$i' with '$x' and '$y'")
+ case i => f(c(i, x, y))
}
)
- private def withYearMonthDuration(x: Val, f: YearMonthDuration => Val): Val =
- x match {
- case ValYearMonthDuration(x) => f(x)
- case _ => error(x, s"expect Year-Month-Duration but found '$x'")
- }
-
- private def withDayTimeDuration(x: Val, f: DayTimeDuration => Val): Val =
- x match {
- case ValDayTimeDuration(x) => f(x)
- case _ => error(x, s"expect Day-Time-Duration but found '$x'")
- }
-
- private def withVal(x: Val, f: Val => Val): Val = x match {
- case e: ValError => e
- case _ => f(x)
- }
-
private def isInRange(range: ConstRange): (Val, Val, Val) => Boolean =
(i, x, y) => {
val inStart: Boolean = range.start match {
@@ -589,292 +427,290 @@ class FeelInterpreter {
}
private def atLeastOne(xs: List[Exp], f: Boolean => Val)(implicit context: EvalContext): Val =
- atLeastOne(xs map (x => () => eval(x)), f)
+ atLeastOneValue(xs map (x => () => eval(x)), f)
- private def atLeastOne(items: List[() => Val], f: Boolean => Val): Val = {
+ private def atLeastOneValue(items: List[() => Val], f: Boolean => Val)(implicit
+ context: EvalContext
+ ): Val = {
items.foldLeft(f(false)) {
- case (ValBoolean(true), _) => f(true)
- case (ValNull, item) =>
+ case (ValBoolean(true), _) => f(true)
+ case (fatalError: ValFatalError, _) => fatalError
+ case (ValNull, item) =>
item() match {
- case ValBoolean(true) => f(true)
- case _ => ValNull
+ case ValBoolean(true) => f(true)
+ case fatalError: ValFatalError => fatalError
+ case _ => ValNull
}
- case (_, item) => withBooleanOrNull(item(), f)
+ case (_, item) => withBooleanOrNull(item(), f)
}
}
private def all(xs: List[Exp], f: Boolean => Val)(implicit context: EvalContext): Val =
- all(xs map (x => () => eval(x)), f)
+ allValues(xs map (x => () => eval(x)), f)
- private def all(items: List[() => Val], f: Boolean => Val): Val = {
+ private def allValues(items: List[() => Val], f: Boolean => Val)(implicit
+ context: EvalContext
+ ): Val = {
items.foldLeft(f(true)) {
- case (ValBoolean(false), _) => f(false)
- case (ValNull, item) =>
+ case (ValBoolean(false), _) => f(false)
+ case (fatalError: ValFatalError, _) => fatalError
+ case (ValNull, item) =>
item() match {
- case ValBoolean(false) => f(false)
- case _ => ValNull
+ case ValBoolean(false) => f(false)
+ case fatalError: ValFatalError => fatalError
+ case _ => ValNull
}
- case (_, item) => withBooleanOrNull(item(), f)
+ case (_, item) => withBooleanOrNull(item(), f)
}
}
- private def inputKey(implicit context: EvalContext): String =
- context.variable(UnaryTests.inputVariable) match {
- case ValString(inputVariableName) => inputVariableName
- case _ => UnaryTests.defaultInputVariable
- }
-
- private def input(implicit context: EvalContext): Val =
- context.variable(inputKey)
-
- private def dualNumericOp(x: Val, y: Val, op: (Number, Number) => Number, f: Number => Val)(
- implicit context: EvalContext
- ): Val =
- x match {
- case ValNumber(x) => withNumber(y, y => f(op(x, y)))
- case _ => error(x, s"expected Number but found '$x'")
- }
-
private def checkEquality(x: Val, y: Val, c: (Any, Any) => Boolean, f: Boolean => Val)(implicit
context: EvalContext
- ): Val =
- x match {
- case ValNull => f(c(ValNull, y.toOption.getOrElse(ValNull)))
- case x if (y == ValNull) => f(c(x.toOption.getOrElse(ValNull), ValNull))
- case ValNumber(x) => withNumber(y, y => f(c(x, y)))
- case ValBoolean(x) => withBoolean(y, y => f(c(x, y)))
- case ValString(x) => withString(y, y => f(c(x, y)))
- case ValDate(x) => withDate(y, y => f(c(x, y)))
- case ValLocalTime(x) => withLocalTime(y, y => f(c(x, y)))
- case ValTime(x) => withTime(y, y => f(c(x, y)))
- case ValLocalDateTime(x) => withLocalDateTime(y, y => f(c(x, y)))
- case ValDateTime(x) => withDateTime(y, y => f(c(x, y)))
- case ValYearMonthDuration(x) => withYearMonthDuration(y, y => f(c(x, y)))
- case ValDayTimeDuration(x) => withDayTimeDuration(y, y => f(c(x, y)))
- case ValList(x) =>
- withList(
+ ): Val = {
+ (x, y) match {
+ case (fatalError: ValFatalError, _) => fatalError
+ case (_, fatalError: ValFatalError) => fatalError
+ case (ValNull, _) => f(c(ValNull, y.toOption.getOrElse(ValNull)))
+ case (_, ValNull) => f(c(x.toOption.getOrElse(ValNull), ValNull))
+ case _ =>
+ withValues(
+ x,
y,
- y => {
- if (x.size != y.items.size) {
- f(false)
+ {
+ case (ValNull, _) => f(c(ValNull, y.toOption.getOrElse(ValNull)))
+ case (_, ValNull) => f(c(x.toOption.getOrElse(ValNull), ValNull))
+ case (ValNumber(x), ValNumber(y)) => f(c(x, y))
+ case (ValBoolean(x), ValBoolean(y)) => f(c(x, y))
+ case (ValString(x), ValString(y)) => f(c(x, y))
+ case (ValDate(x), ValDate(y)) => f(c(x, y))
+ case (ValLocalTime(x), ValLocalTime(y)) => f(c(x, y))
+ case (ValTime(x), ValTime(y)) => f(c(x, y))
+ case (ValLocalDateTime(x), ValLocalDateTime(y)) => f(c(x, y))
+ case (ValDateTime(x), ValDateTime(y)) => f(c(x, y))
+ case (ValYearMonthDuration(x), ValYearMonthDuration(y)) => f(c(x, y))
+ case (ValDayTimeDuration(x), ValDayTimeDuration(y)) => f(c(x, y))
+ case (ValList(x), ValList(y)) =>
+ if (x.size != y.size) {
+ f(false)
- } else {
- val isEqual = x.zip(y.items).foldRight(true) { case ((x, y), listIsEqual) =>
- listIsEqual && {
- checkEquality(x, y, c, f) match {
- case ValBoolean(itemIsEqual) => itemIsEqual
- case _ => false
+ } else {
+ val isEqual = x.zip(y).foldRight(true) { case ((x, y), listIsEqual) =>
+ listIsEqual && {
+ checkEquality(x, y, c, f) match {
+ case ValBoolean(itemIsEqual) => itemIsEqual
+ case _ => false
+ }
}
}
+ f(isEqual)
}
- f(isEqual)
- }
- }
- )
- case ValContext(x) =>
- withContext(
- y,
- y => {
- val xVars = x.variableProvider.getVariables
- val yVars = y.context.variableProvider.getVariables
+ case (ValContext(x), ValContext(y)) =>
+ val xVars = x.variableProvider.getVariables
+ val yVars = y.variableProvider.getVariables
- if (xVars.keys != yVars.keys) {
- f(false)
+ if (xVars.keys != yVars.keys) {
+ f(false)
- } else {
- val isEqual = xVars.keys.foldRight(true) { case (key, contextIsEqual) =>
- contextIsEqual && {
- val xVal = context.valueMapper.toVal(xVars(key))
- val yVal = context.valueMapper.toVal(yVars(key))
-
- checkEquality(xVal, yVal, c, f) match {
- case ValBoolean(entryIsEqual) => entryIsEqual
- case _ => false
+ } else {
+ val isEqual = xVars.keys.foldRight(true) { case (key, contextIsEqual) =>
+ contextIsEqual && {
+ val xVal = context.valueMapper.toVal(xVars(key))
+ val yVal = context.valueMapper.toVal(yVars(key))
+
+ checkEquality(xVal, yVal, c, f) match {
+ case ValBoolean(entryIsEqual) => entryIsEqual
+ case _ => false
+ }
}
}
+ f(isEqual)
}
- f(isEqual)
- }
+ case _ =>
+ error(x, s"Can't compare '$x' with '$y'")
}
)
- case _ =>
- error(
- x,
- s"expected Number, Boolean, String, Date, Time, Duration, List or Context but found '$x'"
- )
}
+ }
private def dualOp(x: Val, y: Val, c: (Val, Val) => Boolean, f: Boolean => Val)(implicit
context: EvalContext
- ): Val =
- x match {
- case ValNull => withVal(y, y => ValBoolean(false))
- case _ if (y == ValNull) => withVal(x, x => ValBoolean(false))
- case _ if (!x.isComparable) => ValError(s"$x is not comparable")
- case _ if (!y.isComparable) => ValError(s"$y is not comparable")
- case _ if (x.getClass != y.getClass) =>
- ValError(s"$x can not be compared to $y")
- case _ => f(c(x, y))
+ ): Val = {
+ (x, y) match {
+ case (ValNull, _) => withVal(y, y => ValBoolean(false))
+ case (_, ValNull) => withVal(x, x => ValBoolean(false))
+ case _ =>
+ withValues(
+ x,
+ y,
+ {
+ case _ if !isComparable(x, y) || !hasSameType(x, y) =>
+ error(x, s"Can't compare '$x' with '$y'")
+ case _ => f(c(x, y))
+ }
+ )
}
+ }
- private def addOp(x: Val, y: Val): Val = x match {
- case ValNumber(x) => withNumber(y, y => ValNumber(x + y))
- case ValString(x) => withString(y, y => ValString(x + y))
- case ValLocalTime(x) => withDayTimeDuration(y, y => ValLocalTime(x.plus(y)))
- case ValTime(x) => withDayTimeDuration(y, y => ValTime(x.plus(y)))
- case ValLocalDateTime(x) =>
- y match {
- case ValYearMonthDuration(y) => ValLocalDateTime(x.plus(y))
- case ValDayTimeDuration(y) => ValLocalDateTime(x.plus(y))
- case _ =>
- error(y, s"expect Year-Month-/Day-Time-Duration but found '$x'")
- }
- case ValDateTime(x) =>
- y match {
- case ValYearMonthDuration(y) => ValDateTime(x.plus(y))
- case ValDayTimeDuration(y) => ValDateTime(x.plus(y))
- case _ =>
- error(y, s"expect Year-Month-/Day-Time-Duration but found '$x'")
- }
- case ValYearMonthDuration(x) =>
- y match {
- case ValYearMonthDuration(y) =>
+ private def addOp(x: Val, y: Val)(implicit context: EvalContext): Val =
+ withValues(
+ x,
+ y,
+ {
+ case (ValNumber(x), ValNumber(y)) => ValNumber(x + y)
+ case (ValString(x), ValString(y)) => ValString(x + y)
+
+ case (ValLocalTime(x), ValDayTimeDuration(y)) => ValLocalTime(x.plus(y))
+ case (ValTime(x), ValDayTimeDuration(y)) => ValTime(x.plus(y))
+
+ case (ValLocalDateTime(x), ValYearMonthDuration(y)) => ValLocalDateTime(x.plus(y))
+ case (ValLocalDateTime(x), ValDayTimeDuration(y)) => ValLocalDateTime(x.plus(y))
+ case (ValDateTime(x), ValYearMonthDuration(y)) => ValDateTime(x.plus(y))
+ case (ValDateTime(x), ValDayTimeDuration(y)) => ValDateTime(x.plus(y))
+
+ case (ValYearMonthDuration(x), ValYearMonthDuration(y)) =>
ValYearMonthDuration(x.plus(y).normalized)
- case ValLocalDateTime(y) => ValLocalDateTime(y.plus(x))
- case ValDateTime(y) => ValDateTime(y.plus(x))
- case ValDate(y) => ValDate(y.plus(x))
- case _ =>
- error(y, s"expect Date-Time, Date, or Year-Month-Duration but found '$x'")
- }
- case ValDayTimeDuration(x) =>
- y match {
- case ValDayTimeDuration(y) => ValDayTimeDuration(x.plus(y))
- case ValLocalDateTime(y) => ValLocalDateTime(y.plus(x))
- case ValDateTime(y) => ValDateTime(y.plus(x))
- case ValLocalTime(y) => ValLocalTime(y.plus(x))
- case ValTime(y) => ValTime(y.plus(x))
- case ValDate(y) => ValDate(y.atStartOfDay().plus(x).toLocalDate())
- case _ =>
- error(y, s"expect Date-Time, Date, Time, or Day-Time-Duration but found '$x'")
- }
- case ValDate(x) =>
- y match {
- case ValDayTimeDuration(y) =>
- ValDate(x.atStartOfDay().plus(y).toLocalDate())
- case ValYearMonthDuration(y) => ValDate(x.plus(y))
- case _ =>
- error(y, s"expect Year-Month-/Day-Time-Duration but found '$x'")
- }
- case _ =>
- error(x, s"expected Number, String, Date, Time or Duration but found '$x'")
- }
+ case (ValYearMonthDuration(x), ValLocalDateTime(y)) => ValLocalDateTime(y.plus(x))
+ case (ValYearMonthDuration(x), ValDateTime(y)) => ValDateTime(y.plus(x))
+ case (ValYearMonthDuration(x), ValDate(y)) => ValDate(y.plus(x))
- private def subOp(x: Val, y: Val): Val = x match {
- case ValNumber(x) => withNumber(y, y => ValNumber(x - y))
- case ValLocalTime(x) =>
- y match {
- case ValLocalTime(y) => ValDayTimeDuration(Duration.between(y, x))
- case ValDayTimeDuration(y) => ValLocalTime(x.minus(y))
- case _ => error(y, s"expect Time, or Day-Time-Duration but found '$x'")
- }
- case ValTime(x) =>
- y match {
- case ValTime(y) => ValDayTimeDuration(ZonedTime.between(x, y))
- case ValDayTimeDuration(y) => ValTime(x.minus(y))
- case _ => error(y, s"expect Time, or Day-Time-Duration but found '$x'")
- }
- case ValLocalDateTime(x) =>
- y match {
- case ValLocalDateTime(y) => ValDayTimeDuration(Duration.between(y, x))
- case ValYearMonthDuration(y) => ValLocalDateTime(x.minus(y))
- case ValDayTimeDuration(y) => ValLocalDateTime(x.minus(y))
- case _ =>
- error(y, s"expect Time, or Year-Month-/Day-Time-Duration but found '$x'")
- }
- case ValDateTime(x) =>
- y match {
- case ValDateTime(y) => ValDayTimeDuration(Duration.between(y, x))
- case ValYearMonthDuration(y) => ValDateTime(x.minus(y))
- case ValDayTimeDuration(y) => ValDateTime(x.minus(y))
- case _ =>
- error(y, s"expect Time, or Year-Month-/Day-Time-Duration but found '$x'")
+ case (ValDayTimeDuration(x), ValDayTimeDuration(y)) => ValDayTimeDuration(x.plus(y))
+ case (ValDayTimeDuration(x), ValLocalDateTime(y)) => ValLocalDateTime(y.plus(x))
+ case (ValDayTimeDuration(x), ValDateTime(y)) => ValDateTime(y.plus(x))
+ case (ValDayTimeDuration(x), ValLocalTime(y)) => ValLocalTime(y.plus(x))
+ case (ValDayTimeDuration(x), ValTime(y)) => ValTime(y.plus(x))
+ case (ValDayTimeDuration(x), ValDate(y)) => ValDate(y.atStartOfDay().plus(x).toLocalDate)
+
+ case (ValDate(x), ValDayTimeDuration(y)) => ValDate(x.atStartOfDay().plus(y).toLocalDate)
+ case (ValDate(x), ValYearMonthDuration(y)) => ValDate(x.plus(y))
+
+ case _ => error(x, s"Can't add '$y' to '$x'")
}
- case ValDate(x) =>
- y match {
- case ValDate(y) =>
+ )
+
+ private def subOp(x: Val, y: Val)(implicit context: EvalContext): Val =
+ withValues(
+ x,
+ y,
+ {
+ case (ValNumber(x), ValNumber(y)) => ValNumber(x - y)
+
+ case (ValLocalTime(x), ValLocalTime(y)) => ValDayTimeDuration(Duration.between(y, x))
+ case (ValLocalTime(x), ValDayTimeDuration(y)) => ValLocalTime(x.minus(y))
+
+ case (ValTime(x), ValTime(y)) => ValDayTimeDuration(ZonedTime.between(x, y))
+ case (ValTime(x), ValDayTimeDuration(y)) => ValTime(x.minus(y))
+
+ case (ValLocalDateTime(x), ValLocalDateTime(y)) =>
+ ValDayTimeDuration(Duration.between(y, x))
+ case (ValLocalDateTime(x), ValYearMonthDuration(y)) => ValLocalDateTime(x.minus(y))
+ case (ValLocalDateTime(x), ValDayTimeDuration(y)) => ValLocalDateTime(x.minus(y))
+
+ case (ValDateTime(x), ValDateTime(y)) => ValDayTimeDuration(Duration.between(y, x))
+ case (ValDateTime(x), ValYearMonthDuration(y)) => ValDateTime(x.minus(y))
+ case (ValDateTime(x), ValDayTimeDuration(y)) => ValDateTime(x.minus(y))
+
+ case (ValDate(x), ValDate(y)) =>
ValDayTimeDuration(Duration.between(y.atStartOfDay, x.atStartOfDay))
- case ValYearMonthDuration(y) => ValDate(x.minus(y))
- case ValDayTimeDuration(y) =>
- ValDate(x.atStartOfDay.minus(y).toLocalDate())
- case _ =>
- error(y, s"expect Date, or Year-Month-/Day-Time-Duration but found '$x'")
+ case (ValDate(x), ValYearMonthDuration(y)) => ValDate(x.minus(y))
+ case (ValDate(x), ValDayTimeDuration(y)) => ValDate(x.atStartOfDay.minus(y).toLocalDate)
+
+ case (ValYearMonthDuration(x), ValYearMonthDuration(y)) =>
+ ValYearMonthDuration(x.minus(y).normalized)
+ case (ValDayTimeDuration(x), ValDayTimeDuration(y)) => ValDayTimeDuration(x.minus(y))
+
+ case _ => error(x, s"Can't subtract '$y' from '$x'")
}
- case ValYearMonthDuration(x) =>
- withYearMonthDuration(y, y => ValYearMonthDuration(x.minus(y).normalized))
- case ValDayTimeDuration(x) =>
- withDayTimeDuration(y, y => ValDayTimeDuration(x.minus(y)))
- case _ =>
- error(x, s"expected Number, Date, Time or Duration but found '$x'")
- }
+ )
- private def mulOp(x: Val, y: Val): Val = x match {
- case ValNumber(x) =>
- y match {
- case ValNumber(y) => ValNumber(x * y)
- case ValYearMonthDuration(y) =>
+ private def mulOp(x: Val, y: Val)(implicit context: EvalContext): Val =
+ withValues(
+ x,
+ y,
+ {
+ case (ValNumber(x), ValNumber(y)) => ValNumber(x * y)
+ case (ValNumber(x), ValYearMonthDuration(y)) =>
ValYearMonthDuration(y.multipliedBy(x.intValue).normalized)
- case ValDayTimeDuration(y) =>
- ValDayTimeDuration(y.multipliedBy(x.intValue))
- case _ =>
- error(y, s"expect Number, or Year-Month-/Day-Time-Duration but found '$x'")
- }
- case ValYearMonthDuration(x) =>
- withNumber(y, y => ValYearMonthDuration(x.multipliedBy(y.intValue).normalized))
- case ValDayTimeDuration(x) =>
- withNumber(y, y => ValDayTimeDuration(x.multipliedBy(y.intValue)))
- case _ => error(x, s"expected Number, or Duration but found '$x'")
- }
+ case (ValNumber(x), ValDayTimeDuration(y)) => ValDayTimeDuration(y.multipliedBy(x.intValue))
- private def divOp(x: Val, y: Val): Val = y match {
- case ValNumber(y) if (y != 0) =>
- x match {
- case ValNumber(x) => ValNumber(x / y)
- case ValYearMonthDuration(x) =>
- ValYearMonthDuration(Period.ofMonths((x.toTotalMonths() / y).intValue).normalized)
- case ValDayTimeDuration(x) =>
- ValDayTimeDuration(Duration.ofMillis((x.toMillis() / y).intValue))
- case _ => error(x, s"expected Number, or Duration but found '$x'")
+ case (ValYearMonthDuration(x), ValNumber(y)) =>
+ ValYearMonthDuration(x.multipliedBy(y.intValue).normalized)
+ case (ValDayTimeDuration(x), ValNumber(y)) => ValDayTimeDuration(x.multipliedBy(y.intValue))
+
+ case _ => error(x, s"Can't multiply '$x' by '$y'")
}
+ )
- case ValYearMonthDuration(y) if (!y.isZero) =>
- withYearMonthDuration(x, x => ValNumber(x.toTotalMonths / y.toTotalMonths))
- case ValDayTimeDuration(y) if (!y.isZero) =>
- withDayTimeDuration(x, x => ValNumber(x.toMillis / y.toMillis))
+ private def divOp(x: Val, y: Val)(implicit context: EvalContext): Val =
+ withValues(
+ x,
+ y,
+ {
+ case (ValNumber(x), ValNumber(y)) if (y != 0) => ValNumber(x / y)
- case _ => ValError(s"'$x / $y' is not allowed")
- }
+ case (ValYearMonthDuration(x), ValNumber(y)) if (y != 0) =>
+ ValYearMonthDuration(Period.ofMonths((x.toTotalMonths / y).intValue).normalized)
+ case (ValYearMonthDuration(x), ValYearMonthDuration(y)) if (!y.isZero) =>
+ ValNumber(x.toTotalMonths / y.toTotalMonths)
- private def unaryTestExpression(x: Val)(implicit context: EvalContext): Val =
- withVal(
- input,
- i =>
- if (x == ValBoolean(true)) {
- ValBoolean(true)
+ case (ValDayTimeDuration(x), ValDayTimeDuration(y)) if (!y.isZero) =>
+ ValNumber(x.toMillis / y.toMillis)
+ case (ValDayTimeDuration(x), ValNumber(y)) if (y != 0) =>
+ ValDayTimeDuration(Duration.ofMillis((x.toMillis / y).intValue))
- } else if (checkEquality(i, x, _ == _, ValBoolean) == ValBoolean(true)) {
- ValBoolean(true)
+ case _ => error(x, s"Can't divide '$x' by '$y'")
+ }
+ )
- } else {
- x match {
- case ValList(ys) => ValBoolean(ys.contains(i))
- case _ => ValBoolean(false)
- }
+ private def unaryTestExpression(expression: Exp)(implicit context: EvalContext): Val = {
+ withVal(
+ getImplicitInputValue,
+ inputValue =>
+ eval(expression) match {
+ case _: ValFatalError =>
+ eval(expression)(context.add(INPUT_VALUE_SYMBOL -> inputValue)) match {
+ case ValBoolean(true) => ValBoolean(true)
+ case ValBoolean(false) => ValBoolean(false)
+ case other =>
+ error(
+ other,
+ s"The unary-test should return a boolean value when the input value is applied but was '$other'."
+ )
+ ValNull
+ }
+ case error: ValError => error
+ case ValBoolean(true) => ValBoolean(true)
+ case ValList(ys) if ys.contains(inputValue) =>
+ // the expression contains the input value
+ ValBoolean(true)
+ case x =>
+ checkEquality(inputValue, x, _ == _, ValBoolean) match {
+ case ValBoolean(true) =>
+ // the expression is the input value
+ ValBoolean(true)
+ case _ if x.isInstanceOf[ValList] =>
+ // the expression is a list but doesn't contain the input value
+ ValBoolean(false)
+ case ValNull => ValNull
+ case _ =>
+ // the expression is not the input value
+ ValBoolean(false)
+ }
}
)
+ }
+
+ private def findFunction(ctx: EvalContext, name: String, params: FunctionParameters)(implicit
+ context: EvalContext
+ ): Val = {
+ val function = params match {
+ case PositionalFunctionParameters(params) => ctx.function(name, params.size)
+ case NamedFunctionParameters(params) => ctx.function(name, params.keySet)
+ }
- private def withFunction(x: Val, f: ValFunction => Val): Val = x match {
- case x: ValFunction => f(x)
- case _ => error(x, s"expect Function but found '$x'")
+ function match {
+ case ValError(failure) => error(function, failure)
+ case _ => function
+ }
}
private def invokeFunction(function: ValFunction, params: FunctionParameters)(implicit
@@ -908,63 +744,36 @@ class FeelInterpreter {
}
function.invoke(paramList) match {
- case ValError(failure) => {
+ case fatalError: ValFatalError => fatalError
+ case ValError(failure) => {
// TODO (saig0): customize error handling (#260)
logger.warn(s"Failed to invoke function: $failure")
ValNull
}
- case result => context.valueMapper.toVal(result)
+ case result => context.valueMapper.toVal(result)
}
}
- private def findFunction(ctx: EvalContext, name: String, params: FunctionParameters): Val =
- params match {
- case PositionalFunctionParameters(params) => ctx.function(name, params.size)
- case NamedFunctionParameters(params) => ctx.function(name, params.keySet)
- }
-
- private def withType(x: Val, f: String => ValBoolean): Val = x match {
- case ValNumber(_) => f("number")
- case ValBoolean(_) => f("boolean")
- case ValString(_) => f("string")
- case ValDate(_) => f("date")
- case ValLocalTime(_) => f("time")
- case ValTime(_) => f("time")
- case ValLocalDateTime(_) => f("date time")
- case ValDateTime(_) => f("date time")
- case ValYearMonthDuration(_) => f("year-month-duration")
- case ValDayTimeDuration(_) => f("day-time-duration")
- case ValNull => f("null")
- case ValList(_) => f("list")
- case ValContext(_) => f("context")
- case ValFunction(_, _, _) => f("function")
- case _ => error(x, s"unexpected type '${x.getClass.getName}'")
- }
-
- private def withList(x: Val, f: ValList => Val): Val = x match {
- case x: ValList => f(x)
- case _ => error(x, s"expect List but found '$x'")
- }
-
private def withLists(lists: List[(String, Val)], f: List[(String, ValList)] => Val)(implicit
context: EvalContext
): Val = {
lists
.map { case (name, it) => name -> withList(it, list => list) }
- .find(_._2.isInstanceOf[ValError]) match {
- case Some(Tuple2(_, e: Val)) => e
- case None => f(lists.asInstanceOf[List[(String, ValList)]])
+ .find { case (_, value) => !value.isInstanceOf[ValList] } match {
+ case Some(Tuple2(_, error: Val)) => error
+ case None => f(lists.asInstanceOf[List[(String, ValList)]])
}
}
private def withCartesianProduct(
iterators: List[(String, Exp)],
f: List[Map[String, Val]] => Val
- )(implicit context: EvalContext): Val =
+ )(implicit context: EvalContext): Val = {
withLists(
iterators.map { case (name, it) => name -> eval(it) },
lists => f(flattenAndZipLists(lists))
)
+ }
private def flattenAndZipLists(lists: List[(String, ValList)]): List[Map[String, Val]] =
lists match {
@@ -976,7 +785,9 @@ class FeelInterpreter {
} yield values + (name -> v) // zip
}
- private def filterList(list: List[Val], filter: Val => Val): Val = {
+ private def filterList(list: List[Val], filter: Val => Val)(implicit
+ context: EvalContext
+ ): Val = {
val conditionNotFulfilled = ValString("_")
val withBooleanFilter = (list: List[Val]) =>
@@ -1039,11 +850,6 @@ class FeelInterpreter {
}
}
- private def withContext(x: Val, f: ValContext => Val): Val = x match {
- case x: ValContext => f(x)
- case _ => error(x, s"expect Context but found '$x'")
- }
-
private def filterContext(x: Val)(implicit context: EvalContext): EvalContext =
x match {
case ValContext(ctx: Context) => context.add("item" -> x).merge(ctx)
@@ -1065,14 +871,25 @@ class FeelInterpreter {
case ctx: ValContext =>
EvalContext.wrap(ctx.context, context.valueMapper).variable(key) match {
case _: ValError =>
- ValError(s"context contains no entry with key '$key'")
+ val detailedMessage = ctx.context.variableProvider.keys match {
+ case Nil => "The context is empty"
+ case keys => s"Available keys: ${keys.map("'" + _ + "'").mkString(", ")}"
+ }
+ error(
+ v,
+ s"No context entry found with key '$key'. $detailedMessage"
+ )
case x: Val => x
- case _ => ValError(s"context contains no entry with key '$key'")
}
case ValList(list) => ValList(list map (item => path(item, key)))
+ case ValNull =>
+ error(
+ v,
+ s"No context entry found with key '$key'. The context is null"
+ )
case value =>
value.property(key).getOrElse {
- val propertyNames: String = value.propertyNames().mkString(",")
+ val propertyNames: String = value.propertyNames().map("'" + _ + "'").mkString(", ")
error(
value,
s"No property found with name '$key' of value '$value'. Available properties: $propertyNames"
@@ -1080,16 +897,13 @@ class FeelInterpreter {
}
}
- private def evalContextEntry(key: String, exp: Exp)(implicit context: EvalContext): Val =
- withVal(eval(exp), value => value)
-
private def invokeJavaFunction(
className: String,
methodName: String,
arguments: List[String],
paramValues: List[Val],
valueMapper: ValueMapper
- ): Val = {
+ )(implicit context: EvalContext): Val = {
try {
val clazz = JavaClassMapper.loadClass(className)
@@ -1121,21 +935,19 @@ class FeelInterpreter {
}
private def toRange(range: ConstRange)(implicit context: EvalContext): Val = {
- withVal(
+ withValues(
eval(range.start.value),
- startValue =>
- withVal(
- eval(range.end.value),
- endValue =>
- if (isValidRange(startValue, endValue)) {
- ValRange(
- start = toRangeBoundary(range.start, startValue),
- end = toRangeBoundary(range.end, endValue)
- )
- } else {
- ValError(s"invalid range definition '$range'")
- }
- )
+ eval(range.end.value),
+ (startValue, endValue) => {
+ if (isValidRange(startValue, endValue)) {
+ ValRange(
+ start = toRangeBoundary(range.start, startValue),
+ end = toRangeBoundary(range.end, endValue)
+ )
+ } else {
+ error(startValue, s"Invalid range definition '$range'")
+ }
+ }
)
}
@@ -1160,3 +972,9 @@ class FeelInterpreter {
}
}
+
+object FeelInterpreter {
+
+ val INPUT_VALUE_SYMBOL: String = "?"
+
+}
diff --git a/src/main/scala/org/camunda/feel/syntaxtree/Val.scala b/src/main/scala/org/camunda/feel/syntaxtree/Val.scala
index 3b12fb6d6..0846d5f80 100644
--- a/src/main/scala/org/camunda/feel/syntaxtree/Val.scala
+++ b/src/main/scala/org/camunda/feel/syntaxtree/Val.scala
@@ -25,11 +25,14 @@ import org.camunda.feel.{
LocalTime,
Number,
Time,
- YearMonthDuration
+ YearMonthDuration,
+ dateTimeFormatter,
+ localDateTimeFormatter,
+ localTimeFormatter
}
-import java.math.BigInteger
import java.time.Duration
+import java.util.regex.Pattern
/** FEEL supports the following datatypes: number string boolean days and time duration years and
* months duration time date and time Duration and date/time datatypes have no literal syntax. They
@@ -91,22 +94,30 @@ sealed trait Val extends Ordered[Val] {
}
def toEither: Either[ValError, Val] = this match {
- case e: ValError => Left(e)
- case v => Right(v)
+ case e: ValError => Left(e)
+ case e: ValFatalError => Left(ValError(e.toString))
+ case v => Right(v)
}
def toOption: Option[Val] = this match {
- case e: ValError => None
- case v => Some(v)
+ case _: ValError => None
+ case fatalError: ValFatalError => Some(fatalError)
+ case v => Some(v)
}
}
-case class ValNumber(value: Number) extends Val
+case class ValNumber(value: Number) extends Val {
+ override def toString: String = value.toString()
+}
-case class ValBoolean(value: Boolean) extends Val
+case class ValBoolean(value: Boolean) extends Val {
+ override def toString: String = value.toString
+}
-case class ValString(value: String) extends Val
+case class ValString(value: String) extends Val {
+ override def toString: String = s"\"$value\""
+}
case class ValDate(value: Date) extends Val {
override protected val properties: Map[String, Val] = Map(
@@ -115,6 +126,8 @@ case class ValDate(value: Date) extends Val {
"day" -> ValNumber(value.getDayOfMonth),
"weekday" -> ValNumber(value.getDayOfWeek.getValue)
)
+
+ override def toString: String = value.toString
}
case class ValLocalTime(value: LocalTime) extends Val {
@@ -125,6 +138,8 @@ case class ValLocalTime(value: LocalTime) extends Val {
"time offset" -> ValNull,
"timezone" -> ValNull
)
+
+ override def toString: String = value.format(localTimeFormatter)
}
case class ValTime(value: Time) extends Val {
@@ -136,6 +151,8 @@ case class ValTime(value: Time) extends Val {
ValDayTimeDuration(Duration.ofSeconds(value.getOffsetInTotalSeconds)),
"timezone" -> value.getZoneId.map(ValString).getOrElse(ValNull)
)
+
+ override def toString: String = value.format
}
case class ValLocalDateTime(value: LocalDateTime) extends Val {
@@ -150,6 +167,8 @@ case class ValLocalDateTime(value: LocalDateTime) extends Val {
"time offset" -> ValNull,
"timezone" -> ValNull
)
+
+ override def toString: String = value.format(localDateTimeFormatter)
}
case class ValDateTime(value: DateTime) extends Val {
@@ -169,54 +188,73 @@ case class ValDateTime(value: DateTime) extends Val {
)
private def hasTimeZone = !value.getOffset.equals(value.getZone)
+
+ override def toString: String = ValDateTime.format(value)
+}
+
+object ValDateTime {
+
+ private val dateTimeOffsetZoneIdPattern = Pattern.compile("(.*)([+-]\\d{2}:\\d{2}|Z)(@.*)")
+
+ def format(value: DateTime): String = {
+ val formattedDateTime = value.format(dateTimeFormatter)
+ // remove offset-id if zone-id is present
+ dateTimeOffsetZoneIdPattern
+ .matcher(formattedDateTime)
+ .replaceAll("$1$3")
+ }
+
}
case class ValYearMonthDuration(value: YearMonthDuration) extends Val {
- override def toString: String = {
- def makeString(sign: String, year: Long, month: Long): String = {
- val y = Option(year).filterNot(_ == 0).map(_ + "Y").getOrElse("")
- val m = Option(month).filterNot(_ == 0).map(_ + "M").getOrElse("")
+ override def toString: String = ValYearMonthDuration.format(value)
- val stringBuilder = new StringBuilder("")
- stringBuilder.append(sign).append("P").append(y).append(m)
- stringBuilder.toString()
- }
+ override val properties: Map[String, Val] = Map(
+ "years" -> ValNumber(value.getYears),
+ "months" -> ValNumber(value.getMonths)
+ )
+}
+
+object ValYearMonthDuration {
+ def format(value: YearMonthDuration): String = {
val year = value.getYears
val month = value.getMonths % 12
if (year == 0 && month == 0)
"P0Y"
else if (year <= 0 && month <= 0)
- makeString("-", -year, -month)
+ "-" + mkString(-year, -month)
else
- makeString("", year, month)
+ mkString(year, month)
}
+ private def mkString(year: Long, month: Long): String = {
+ val y = Option(year).filterNot(_ == 0).map(_ + "Y").getOrElse("")
+ val m = Option(month).filterNot(_ == 0).map(_ + "M").getOrElse("")
+
+ val stringValue = new StringBuilder("P")
+ stringValue.append(y).append(m)
+ stringValue.toString()
+ }
+
+}
+
+case class ValDayTimeDuration(value: DayTimeDuration) extends Val {
+ override def toString: String = ValDayTimeDuration.format(value)
+
override val properties: Map[String, Val] = Map(
- "years" -> ValNumber(value.getYears),
- "months" -> ValNumber(value.getMonths)
+ "days" -> ValNumber(value.toDays),
+ "hours" -> ValNumber(value.toHours % 24),
+ "minutes" -> ValNumber(value.toMinutes % 60),
+ "seconds" -> ValNumber(value.getSeconds % 60)
)
}
-case class ValDayTimeDuration(value: DayTimeDuration) extends Val {
- override def toString: String = {
- def makeString(sign: String, day: Long, hour: Long, minute: Long, second: Long): String = {
- val d = Option(day).filterNot(_ == 0).map(_ + "D").getOrElse("")
- val h = Option(hour).filterNot(_ == 0).map(_ + "H").getOrElse("")
- val m = Option(minute).filterNot(_ == 0).map(_ + "M").getOrElse("")
- val s = Option(second).filterNot(_ == 0).map(_ + "S").getOrElse("")
-
- val stringBuilder = new StringBuilder("")
- stringBuilder.append(sign).append("P").append(d)
- if (h.nonEmpty || m.nonEmpty || s.nonEmpty) {
- stringBuilder.append("T")
- stringBuilder.append(h).append(m).append(s)
- }
- stringBuilder.toString()
- }
+object ValDayTimeDuration {
+ def format(value: DayTimeDuration): String = {
val day = value.toDays
val hour = value.toHours % 24
val minute = value.toMinutes % 60
@@ -225,30 +263,63 @@ case class ValDayTimeDuration(value: DayTimeDuration) extends Val {
if (day == 0 && hour == 0 && minute == 0 && second == 0)
"P0D"
else if (day <= 0 && hour <= 0 && minute <= 0 && second <= 0)
- makeString("-", -day, -hour, -minute, -second)
+ "-" + mkString(-day, -hour, -minute, -second)
else
- makeString("", day, hour, minute, second)
+ mkString(day, hour, minute, second)
}
- override val properties: Map[String, Val] = Map(
- "days" -> ValNumber(value.toDays),
- "hours" -> ValNumber(value.toHours % 24),
- "minutes" -> ValNumber(value.toMinutes % 60),
- "seconds" -> ValNumber(value.getSeconds % 60)
- )
+
+ private def mkString(day: Long, hour: Long, minute: Long, second: Long): String = {
+ val d = Option(day).filterNot(_ == 0).map(_ + "D").getOrElse("")
+ val h = Option(hour).filterNot(_ == 0).map(_ + "H").getOrElse("")
+ val m = Option(minute).filterNot(_ == 0).map(_ + "M").getOrElse("")
+ val s = Option(second).filterNot(_ == 0).map(_ + "S").getOrElse("")
+
+ val stringValue = new StringBuilder("P")
+ stringValue.append(d)
+ if (h.nonEmpty || m.nonEmpty || s.nonEmpty) {
+ stringValue.append("T")
+ stringValue.append(h).append(m).append(s)
+ }
+ stringValue.toString()
+ }
+
}
-case class ValError(error: String) extends Val
+case class ValError(error: String) extends Val {
+ override def toString: String = s"error(\"$error\")"
+}
-case object ValNull extends Val
+case class ValFatalError(error: String) extends Val {
+ override def toString: String = s"fatal error(\"$error\")"
+}
+
+case object ValNull extends Val {
+ override def toString: String = "null"
+}
case class ValFunction(params: List[String], invoke: List[Val] => Any, hasVarArgs: Boolean = false)
extends Val {
val paramSet: Set[String] = params.toSet
+
+ override def toString: String = s"function(${params.mkString(", ")})"
}
-case class ValContext(context: Context) extends Val
+case class ValContext(context: Context) extends Val {
+ override def toString: String = context.variableProvider.getVariables
+ .map { case (key, value) => s"$key:$value" }
+ .mkString(start = "{", sep = ", ", end = "}")
+}
-case class ValList(items: List[Val]) extends Val
+case class ValList(items: List[Val]) extends Val {
+ override def toString: String = items.mkString(start = "[", sep = ", ", end = "]")
+}
-case class ValRange(start: RangeBoundary, end: RangeBoundary) extends Val
+case class ValRange(start: RangeBoundary, end: RangeBoundary) extends Val {
+ override def toString: String = {
+ val startSymbol = if (start.isClosed) "[" else "("
+ val endSymbol = if (end.isClosed) "]" else ")"
+
+ s"$startSymbol${start.value}..${end.value}$endSymbol"
+ }
+}
diff --git a/src/test/scala/org/camunda/feel/api/FeelEngineTest.scala b/src/test/scala/org/camunda/feel/api/FeelEngineTest.scala
index 80e6cd629..372a931b1 100644
--- a/src/test/scala/org/camunda/feel/api/FeelEngineTest.scala
+++ b/src/test/scala/org/camunda/feel/api/FeelEngineTest.scala
@@ -66,7 +66,7 @@ class FeelEngineTest extends AnyFlatSpec with Matchers with EitherValues {
engine.evalUnaryTests("< 3", variables = Map(UnaryTests.defaultInputVariable -> "2")) should be(
Left(
Failure(
- "failed to evaluate expression '< 3': ValString(2) can not be compared to ValNumber(3)"
+ """failed to evaluate expression '< 3': Can't compare '"2"' with '3'"""
)
)
)
diff --git a/src/test/scala/org/camunda/feel/impl/FeelEngineTest.scala b/src/test/scala/org/camunda/feel/impl/FeelEngineTest.scala
index 95c8db41e..76d8349e3 100644
--- a/src/test/scala/org/camunda/feel/impl/FeelEngineTest.scala
+++ b/src/test/scala/org/camunda/feel/impl/FeelEngineTest.scala
@@ -16,10 +16,21 @@
*/
package org.camunda.feel.impl
+import org.camunda.feel.{
+ Date,
+ DateTime,
+ DayTimeDuration,
+ LocalDateTime,
+ LocalTime,
+ Time,
+ YearMonthDuration
+}
import org.camunda.feel.FeelEngine
import org.camunda.feel.FeelEngine.{EvalExpressionResult, EvalUnaryTestsResult}
import org.camunda.feel.context.Context
-import org.camunda.feel.syntaxtree.ValFunction
+import org.camunda.feel.syntaxtree.{ValFunction, ZonedTime}
+
+import java.time.{Duration, LocalDate, LocalDateTime, LocalTime, Period, ZonedDateTime}
trait FeelEngineTest {
@@ -88,4 +99,18 @@ trait FeelEngineTest {
}
}
+ def date(x: String): Date = LocalDate.parse(x)
+
+ def localTime(x: String): LocalTime = LocalTime.parse(x)
+
+ def time(x: String): Time = ZonedTime.parse(x)
+
+ def dateTime(x: String): DateTime = ZonedDateTime.parse(x)
+
+ def localDateTime(x: String): LocalDateTime = LocalDateTime.parse(x)
+
+ def yearMonthDuration(x: String): YearMonthDuration = Period.parse(x)
+
+ def dayTimeDuration(x: String): DayTimeDuration = Duration.parse(x)
+
}
diff --git a/src/test/scala/org/camunda/feel/impl/interpreter/DateTimeDurationPropertiesTest.scala b/src/test/scala/org/camunda/feel/impl/interpreter/DateTimeDurationPropertiesTest.scala
index 13af0592b..f7a450e79 100644
--- a/src/test/scala/org/camunda/feel/impl/interpreter/DateTimeDurationPropertiesTest.scala
+++ b/src/test/scala/org/camunda/feel/impl/interpreter/DateTimeDurationPropertiesTest.scala
@@ -54,7 +54,7 @@ class DateTimeDurationPropertiesTest extends AnyFlatSpec with Matchers with Feel
result shouldBe a[ValError]
result.asInstanceOf[ValError].error should startWith(
- "No property found with name 'x' of value 'ValDate(2020-09-30)'. Available properties:"
+ "No property found with name 'x' of value '2020-09-30'. Available properties: 'year', 'month', 'day', 'weekday'"
)
}
@@ -97,7 +97,7 @@ class DateTimeDurationPropertiesTest extends AnyFlatSpec with Matchers with Feel
result shouldBe a[ValError]
result.asInstanceOf[ValError].error should startWith(
- "No property found with name 'x' of value 'ValTime(ZonedTime(11:45:30,+02:00,None))'. Available properties:"
+ "No property found with name 'x' of value '11:45:30+02:00'. Available properties: 'timezone', 'second', 'time offset', 'minute', 'hour'"
)
}
@@ -139,7 +139,7 @@ class DateTimeDurationPropertiesTest extends AnyFlatSpec with Matchers with Feel
result shouldBe a[ValError]
result.asInstanceOf[ValError].error should startWith(
- "No property found with name 'x' of value 'ValLocalTime(11:45:30)'. Available properties:"
+ "No property found with name 'x' of value '11:45:30'. Available properties: 'timezone', 'second', 'time offset', 'minute', 'hour'"
)
}
@@ -208,7 +208,7 @@ class DateTimeDurationPropertiesTest extends AnyFlatSpec with Matchers with Feel
result shouldBe a[ValError]
result.asInstanceOf[ValError].error should startWith(
- "No property found with name 'x' of value 'ValDateTime(2020-09-30T22:50:30+02:00)'. Available properties:"
+ "No property found with name 'x' of value '2020-09-30T22:50:30+02:00'. Available properties: 'timezone', 'year', 'second', 'month', 'day', 'time offset', 'weekday', 'minute', 'hour'"
)
}
@@ -269,7 +269,7 @@ class DateTimeDurationPropertiesTest extends AnyFlatSpec with Matchers with Feel
result shouldBe a[ValError]
result.asInstanceOf[ValError].error should startWith(
- "No property found with name 'x' of value 'ValLocalDateTime(2020-09-30T22:50:30)'. Available properties:"
+ "No property found with name 'x' of value '2020-09-30T22:50:30'. Available properties: 'timezone', 'year', 'second', 'month', 'day', 'time offset', 'weekday', 'minute', 'hour'"
)
}
diff --git a/src/test/scala/org/camunda/feel/impl/interpreter/InterpreterBeanExpressionTest.scala b/src/test/scala/org/camunda/feel/impl/interpreter/InterpreterBeanExpressionTest.scala
index 358a8deae..421af2100 100644
--- a/src/test/scala/org/camunda/feel/impl/interpreter/InterpreterBeanExpressionTest.scala
+++ b/src/test/scala/org/camunda/feel/impl/interpreter/InterpreterBeanExpressionTest.scala
@@ -51,7 +51,7 @@ class InterpreterBeanExpressionTest extends AnyFlatSpec with Matchers with FeelI
}
eval("a.result", Map("a" -> new A(2))) should be(
- ValError("context contains no entry with key 'result'")
+ ValError("No context entry found with key 'result'. Available keys: ")
)
}
@@ -62,7 +62,7 @@ class InterpreterBeanExpressionTest extends AnyFlatSpec with Matchers with FeelI
}
eval("a.plus", Map("a" -> new A(2))) should be(
- ValError("context contains no entry with key 'plus'")
+ ValError("No context entry found with key 'plus'. Available keys: ")
)
}
@@ -70,7 +70,7 @@ class InterpreterBeanExpressionTest extends AnyFlatSpec with Matchers with FeelI
class A(private val x: Int)
eval("a.x", Map("a" -> new A(2))) should be(
- ValError("context contains no entry with key 'x'")
+ ValError("No context entry found with key 'x'. Available keys: ")
)
}
@@ -80,7 +80,7 @@ class InterpreterBeanExpressionTest extends AnyFlatSpec with Matchers with FeelI
}
eval("a.result", Map("a" -> new A(2))) should be(
- ValError("context contains no entry with key 'result'")
+ ValError("No context entry found with key 'result'. Available keys: 'x'")
)
}
diff --git a/src/test/scala/org/camunda/feel/impl/interpreter/InterpreterContextExpressionTest.scala b/src/test/scala/org/camunda/feel/impl/interpreter/InterpreterContextExpressionTest.scala
index e2ba82e27..31c08141b 100644
--- a/src/test/scala/org/camunda/feel/impl/interpreter/InterpreterContextExpressionTest.scala
+++ b/src/test/scala/org/camunda/feel/impl/interpreter/InterpreterContextExpressionTest.scala
@@ -96,7 +96,7 @@ class InterpreterContextExpressionTest
}
it should "fail if compare to not a context" in {
- evaluateExpression("{} = 1") should failWith("expect Context but found 'ValNumber(1)'")
+ evaluateExpression("{} = 1") should failWith("Can't compare '{}' with '1'")
}
it should "fail when special symbols violate context syntax" in {
@@ -135,26 +135,34 @@ class InterpreterContextExpressionTest
}
it should "fail if the context is empty" in {
- evaluateExpression("{}.x") should failWith("context contains no entry with key 'x'")
+ evaluateExpression("{}.x") should failWith(
+ "No context entry found with key 'x'. The context is empty"
+ )
}
it should "fail if no entry exists with the key" in {
- evaluateExpression("{x:1, y:2}.z") should failWith("context contains no entry with key 'z'")
+ evaluateExpression("{x:1, y:2}.z") should failWith(
+ "No context entry found with key 'z'. Available keys: 'x', 'y'"
+ )
}
- it should "return fail if the context is null" in {
+ it should "fail if the context is null" in {
evaluateExpression(
expression = "a.b",
variables = Map("a" -> null)
- ) should failWith("No property found with name 'b' of value 'ValNull'")
+ ) should failWith("No context entry found with key 'b'. The context is null")
}
it should "fail if the chained context is null" in {
- evaluateExpression("{a:1}.b.c") should failWith("context contains no entry with key 'b'")
+ evaluateExpression("{a:1}.b.c") should failWith(
+ "No context entry found with key 'b'. Available keys: 'a'"
+ )
}
it should "fail if the context is empty (inside a context)" in {
- evaluateExpression("{x:1, y:{}.z}") should failWith("context contains no entry with key 'z'")
+ evaluateExpression("{x:1, y:{}.z}") should failWith(
+ "No context entry found with key 'z'. The context is empty"
+ )
}
it should "return the value of a key with whitespaces" in {
diff --git a/src/test/scala/org/camunda/feel/impl/interpreter/InterpreterExpressionTest.scala b/src/test/scala/org/camunda/feel/impl/interpreter/InterpreterExpressionTest.scala
index e602305e3..5eab431b1 100644
--- a/src/test/scala/org/camunda/feel/impl/interpreter/InterpreterExpressionTest.scala
+++ b/src/test/scala/org/camunda/feel/impl/interpreter/InterpreterExpressionTest.scala
@@ -17,263 +17,284 @@
package org.camunda.feel.impl.interpreter
import org.camunda.feel.FeelEngine.UnaryTests
-import org.camunda.feel.impl.FeelIntegrationTest
+import org.camunda.feel.impl.{EvaluationResultMatchers, FeelEngineTest}
import org.camunda.feel.syntaxtree._
-import org.scalatest.matchers.should.Matchers
import org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
/** @author
* Philipp Ossler
*/
-class InterpreterExpressionTest extends AnyFlatSpec with Matchers with FeelIntegrationTest {
+class InterpreterExpressionTest
+ extends AnyFlatSpec
+ with Matchers
+ with FeelEngineTest
+ with EvaluationResultMatchers {
"An expression" should "be an if-then-else (with parentheses)" in {
val exp = """ if (x < 5) then "low" else "high" """
- eval(exp, Map("x" -> 2)) should be(ValString("low"))
- eval(exp, Map("x" -> 7)) should be(ValString("high"))
+ evaluateExpression(exp, Map("x" -> 2)) should returnResult("low")
+ evaluateExpression(exp, Map("x" -> 7)) should returnResult("high")
- eval(exp, Map("x" -> "foo")) should be(ValString("high"))
+ evaluateExpression(exp, Map("x" -> "foo")) should returnResult("high")
}
it should "be an if-then-else (without parentheses)" in {
- eval("if x < 5 then 1 else 2", Map("x" -> 2)) should be(ValNumber(1))
+ evaluateExpression("if x < 5 then 1 else 2", Map("x" -> 2)) should returnResult(1)
}
it should "be an if-then-else (with literal)" in {
- eval("if true then 1 else 2") should be(ValNumber(1))
+ evaluateExpression("if true then 1 else 2") should returnResult(1)
}
it should "be an if-then-else (with path)" in {
- eval("if {a: true}.a then 1 else 2") should be(ValNumber(1))
+ evaluateExpression("if {a: true}.a then 1 else 2") should returnResult(1)
}
it should "be an if-then-else (with filter)" in {
- eval("if [true][1] then 1 else 2") should be(ValNumber(1))
+ evaluateExpression("if [true][1] then 1 else 2") should returnResult(1)
}
it should "be an if-then-else (with conjunction)" in {
- eval("if true and true then 1 else 2") should be(ValNumber(1))
+ evaluateExpression("if true and true then 1 else 2") should returnResult(1)
}
it should "be an if-then-else (with disjunction)" in {
- eval("if false or true then 1 else 2") should be(ValNumber(1))
+ evaluateExpression("if false or true then 1 else 2") should returnResult(1)
}
it should "be an if-then-else (with in-test)" in {
- eval("if 1 in < 5 then 1 else 2") should be(ValNumber(1))
+ evaluateExpression("if 1 in < 5 then 1 else 2") should returnResult(1)
}
it should "be an if-then-else (with instance of)" in {
- eval("if 1 instance of number then 1 else 2") should be(ValNumber(1))
+ evaluateExpression("if 1 instance of number then 1 else 2") should returnResult(1)
}
it should "be an if-then-else (with variable and function call -> then)" in {
- eval("if 7 > var then flatten(xs) else []", Map("xs" -> List(1, 2), "var" -> 3)) should be(
- ValList(List(ValNumber(1), ValNumber(2)))
- )
+ evaluateExpression(
+ "if 7 > var then flatten(xs) else []",
+ Map("xs" -> List(1, 2), "var" -> 3)
+ ) should returnResult(List(1, 2))
}
it should "be an if-then-else (with variable and function call -> else)" in {
- eval("if false then var else flatten(xs)", Map("xs" -> List(1, 2), "var" -> 3)) should be(
- ValList(List(ValNumber(1), ValNumber(2)))
- )
+ evaluateExpression(
+ "if false then var else flatten(xs)",
+ Map("xs" -> List(1, 2), "var" -> 3)
+ ) should returnResult(List(1, 2))
}
it should "be a simple positive unary test" in {
- eval("< 3", Map(UnaryTests.defaultInputVariable -> 2)) should be(ValBoolean(true))
+ evaluateExpression("< 3", Map(UnaryTests.defaultInputVariable -> 2)) should returnResult(true)
- eval("(2 .. 4)", Map(UnaryTests.defaultInputVariable -> 5)) should be(ValBoolean(false))
+ evaluateExpression("(2 .. 4)", Map(UnaryTests.defaultInputVariable -> 5)) should returnResult(
+ false
+ )
}
it should "be an instance of (literal)" in {
- eval("x instance of number", Map("x" -> 1)) should be(ValBoolean(true))
- eval("x instance of number", Map("x" -> "NaN")) should be(ValBoolean(false))
+ evaluateExpression("x instance of number", Map("x" -> 1)) should returnResult(true)
+ evaluateExpression("x instance of number", Map("x" -> "NaN")) should returnResult(false)
- eval("x instance of boolean", Map("x" -> true)) should be(ValBoolean(true))
- eval("x instance of boolean", Map("x" -> 0)) should be(ValBoolean(false))
+ evaluateExpression("x instance of boolean", Map("x" -> true)) should returnResult(true)
+ evaluateExpression("x instance of boolean", Map("x" -> 0)) should returnResult(false)
- eval("x instance of string", Map("x" -> "yes")) should be(ValBoolean(true))
- eval("x instance of string", Map("x" -> 0)) should be(ValBoolean(false))
+ evaluateExpression("x instance of string", Map("x" -> "yes")) should returnResult(true)
+ evaluateExpression("x instance of string", Map("x" -> 0)) should returnResult(false)
}
it should "be an instance of (duration)" in {
- eval("""duration("P3M") instance of years and months duration""") should be(ValBoolean(true))
- eval("""duration("PT4H") instance of days and time duration""") should be(ValBoolean(true))
- eval("""null instance of years and months duration""") should be(ValBoolean(false))
- eval("""null instance of days and time duration""") should be(ValBoolean(false))
+ evaluateExpression(
+ """duration("P3M") instance of years and months duration"""
+ ) should returnResult(true)
+ evaluateExpression(
+ """duration("PT4H") instance of days and time duration"""
+ ) should returnResult(true)
+ evaluateExpression("""null instance of years and months duration""") should returnResult(false)
+ evaluateExpression("""null instance of days and time duration""") should returnResult(false)
}
it should "be an instance of (date)" in {
- eval("""date("2023-03-07") instance of date""") should be(ValBoolean(true))
- eval(""" @"2023-03-07" instance of date""") should be(ValBoolean(true))
- eval("1 instance of date") should be(ValBoolean(false))
+ evaluateExpression("""date("2023-03-07") instance of date""") should returnResult(true)
+ evaluateExpression(""" @"2023-03-07" instance of date""") should returnResult(true)
+ evaluateExpression("1 instance of date") should returnResult(false)
}
it should "be an instance of (time)" in {
- eval("""time("11:27:00") instance of time""") should be(ValBoolean(true))
- eval(""" @"11:27:00" instance of time""") should be(ValBoolean(true))
- eval("1 instance of time") should be(ValBoolean(false))
+ evaluateExpression("""time("11:27:00") instance of time""") should returnResult(true)
+ evaluateExpression(""" @"11:27:00" instance of time""") should returnResult(true)
+ evaluateExpression("1 instance of time") should returnResult(false)
}
it should "be an instance of (date and time)" in {
- eval("""date and time("2023-03-07T11:27:00") instance of date and time""") should be(
- ValBoolean(true)
+ evaluateExpression(
+ """date and time("2023-03-07T11:27:00") instance of date and time"""
+ ) should returnResult(true)
+
+ evaluateExpression(""" @"2023-03-07T11:27:00" instance of date and time""") should returnResult(
+ true
)
- eval(""" @"2023-03-07T11:27:00" instance of date and time""") should be(ValBoolean(true))
- eval("1 instance of date and time") should be(ValBoolean(false))
+ evaluateExpression("1 instance of date and time") should returnResult(false)
}
it should "be an instance of (list)" in {
- eval("[1,2,3] instance of list") should be(ValBoolean(true))
- eval("[] instance of list") should be(ValBoolean(true))
- eval("1 instance of list") should be(ValBoolean(false))
+ evaluateExpression("[1,2,3] instance of list") should returnResult(true)
+ evaluateExpression("[] instance of list") should returnResult(true)
+ evaluateExpression("1 instance of list") should returnResult(false)
}
it should "be an instance of (context)" in {
- eval("{x:1} instance of context") should be(ValBoolean(true))
- eval("{} instance of context") should be(ValBoolean(true))
- eval("1 instance of context") should be(ValBoolean(false))
+ evaluateExpression("{x:1} instance of context") should returnResult(true)
+ evaluateExpression("{} instance of context") should returnResult(true)
+ evaluateExpression("1 instance of context") should returnResult(false)
}
it should "be an instance of (multiplication)" in {
- eval("2 * 3 instance of number") should be(ValBoolean(true))
+ evaluateExpression("2 * 3 instance of number") should returnResult(true)
}
it should "be an instance of (function definition)" in {
- eval(""" (function() "foo") instance of function """) should be(ValBoolean(true))
- eval("""1 instance of function""") should be(ValBoolean(false))
+ evaluateExpression(""" (function() "foo") instance of function """) should returnResult(true)
+ evaluateExpression("""1 instance of function""") should returnResult(false)
}
it should "be a instance of Any should always pass" in {
- eval("x instance of Any", Map("x" -> "yes")) should be(ValBoolean(true))
- eval("x instance of Any", Map("x" -> 1)) should be(ValBoolean(true))
- eval("x instance of Any", Map("x" -> true)) should be(ValBoolean(true))
- eval("x instance of Any", Map("x" -> null)) should be(ValBoolean(false))
+ evaluateExpression("x instance of Any", Map("x" -> "yes")) should returnResult(true)
+ evaluateExpression("x instance of Any", Map("x" -> 1)) should returnResult(true)
+ evaluateExpression("x instance of Any", Map("x" -> true)) should returnResult(true)
+ evaluateExpression("x instance of Any", Map("x" -> null)) should returnResult(false)
}
it should "be an escaped identifier" in {
// regular identifier
- eval(" `x` ", Map("x" -> "foo")) should be(ValString("foo"))
+ evaluateExpression(" `x` ", Map("x" -> "foo")) should returnResult("foo")
// with whitespace
- eval(" `a b` ", Map("a b" -> "foo")) should be(ValString("foo"))
+ evaluateExpression(" `a b` ", Map("a b" -> "foo")) should returnResult("foo")
// with operator
- eval(" `a-b` ", Map("a-b" -> 3)) should be(ValNumber(3))
+ evaluateExpression(" `a-b` ", Map("a-b" -> 3)) should returnResult(3)
}
it should "contains parentheses" in {
- eval("(1 + 2)") should be(ValNumber(3))
- eval("(1 + 2) + 3") should be(ValNumber(6))
- eval("1 + (2 + 3)") should be(ValNumber(6))
+ evaluateExpression("(1 + 2)") should returnResult(3)
+ evaluateExpression("(1 + 2) + 3") should returnResult(6)
+ evaluateExpression("1 + (2 + 3)") should returnResult(6)
- eval("([1,2,3])[1]") should be(ValNumber(1))
- eval("({x:1}).x") should be(ValNumber(1))
- eval("{x:(1)}.x") should be(ValNumber(1))
+ evaluateExpression("([1,2,3])[1]") should returnResult(1)
+ evaluateExpression("({x:1}).x") should returnResult(1)
+ evaluateExpression("{x:(1)}.x") should returnResult(1)
- eval("[1,2,3,4][(1)]") should be(ValNumber(1))
+ evaluateExpression("[1,2,3,4][(1)]") should returnResult(1)
}
it should "contain parentheses in a context literal" in {
val context = Map("xs" -> List(1, 2, 3))
- eval("{x:(xs[1])}.x", context) should be(ValNumber(1))
- eval("{x:(xs)[1]}.x", context) should be(ValNumber(1))
- eval("{x:(xs)}.x", context) should be(ValList(List(ValNumber(1), ValNumber(2), ValNumber(3))))
+ evaluateExpression("{x:(xs[1])}.x", context) should returnResult(1)
+ evaluateExpression("{x:(xs)[1]}.x", context) should returnResult(1)
+ evaluateExpression("{x:(xs)}.x", context) should returnResult(List(1, 2, 3))
}
it should "contains nested filter expressions" in {
- eval("[1,2,3,4][item > 2][1]") should be(ValNumber(3))
- eval("([1,2,3,4])[item > 2][1]") should be(ValNumber(3))
- eval("([1,2,3,4][item > 2])[1]") should be(ValNumber(3))
+ evaluateExpression("[1,2,3,4][item > 2][1]") should returnResult(3)
+ evaluateExpression("([1,2,3,4])[item > 2][1]") should returnResult(3)
+ evaluateExpression("([1,2,3,4][item > 2])[1]") should returnResult(3)
}
it should "contains nested path expressions" in {
- eval("{x:{y:1}}.x.y") should be(ValNumber(1))
- eval("{x:{y:{z:1}}}.x.y.z") should be(ValNumber(1))
+ evaluateExpression("{x:{y:1}}.x.y") should returnResult(1)
+ evaluateExpression("{x:{y:{z:1}}}.x.y.z") should returnResult(1)
- eval("({x:{y:{z:1}}}).x.y.z") should be(ValNumber(1))
- eval("({x:{y:{z:1}}}.x).y.z") should be(ValNumber(1))
- eval("({x:{y:{z:1}}}.x.y).z") should be(ValNumber(1))
+ evaluateExpression("({x:{y:{z:1}}}).x.y.z") should returnResult(1)
+ evaluateExpression("({x:{y:{z:1}}}.x).y.z") should returnResult(1)
+ evaluateExpression("({x:{y:{z:1}}}.x.y).z") should returnResult(1)
}
it should "contains nested filter and path expressions" in {
- eval("[{x:{y:1}},{x:{y:2}},{x:{y:3}}].x.y[2]") should be(ValNumber(2))
- eval("([{x:{y:1}},{x:{y:2}},{x:{y:3}}]).x.y[2]") should be(ValNumber(2))
- eval("([{x:{y:1}},{x:{y:2}},{x:{y:3}}].x).y[2]") should be(ValNumber(2))
- eval("([{x:{y:1}},{x:{y:2}},{x:{y:3}}].x.y)[2]") should be(ValNumber(2))
+ evaluateExpression("[{x:{y:1}},{x:{y:2}},{x:{y:3}}].x.y[2]") should returnResult(2)
+ evaluateExpression("([{x:{y:1}},{x:{y:2}},{x:{y:3}}]).x.y[2]") should returnResult(2)
+ evaluateExpression("([{x:{y:1}},{x:{y:2}},{x:{y:3}}].x).y[2]") should returnResult(2)
+ evaluateExpression("([{x:{y:1}},{x:{y:2}},{x:{y:3}}].x.y)[2]") should returnResult(2)
- eval("([{x:{y:1}},{x:{y:2}},{x:{y:3}}]).x[2].y") should be(ValNumber(2))
- eval("([{x:{y:1}},{x:{y:2}},{x:{y:3}}])[2].x.y") should be(ValNumber(2))
+ evaluateExpression("([{x:{y:1}},{x:{y:2}},{x:{y:3}}]).x[2].y") should returnResult(2)
+ evaluateExpression("([{x:{y:1}},{x:{y:2}},{x:{y:3}}])[2].x.y") should returnResult(2)
- eval("[{x:[1,2]},{x:[3,4]},{x:[5,6]}][2].x[1]") should be(ValNumber(3))
+ evaluateExpression("[{x:[1,2]},{x:[3,4]},{x:[5,6]}][2].x[1]") should returnResult(3)
- eval("([{x:[1,2]},{x:[3,4]},{x:[5,6]}]).x[2][1]") should be(ValNumber(3))
- eval("([{x:[1,2]},{x:[3,4]},{x:[5,6]}].x)[2][1]") should be(ValNumber(3))
- eval("([{x:[1,2]},{x:[3,4]},{x:[5,6]}].x[2])[1]") should be(ValNumber(3))
+ evaluateExpression("([{x:[1,2]},{x:[3,4]},{x:[5,6]}]).x[2][1]") should returnResult(3)
+ evaluateExpression("([{x:[1,2]},{x:[3,4]},{x:[5,6]}].x)[2][1]") should returnResult(3)
+ evaluateExpression("([{x:[1,2]},{x:[3,4]},{x:[5,6]}].x[2])[1]") should returnResult(3)
}
"Null" should "compare to null" in {
- eval("null = null") should be(ValBoolean(true))
- eval("null != null") should be(ValBoolean(false))
+ evaluateExpression("null = null") should returnResult(true)
+ evaluateExpression("null != null") should returnResult(false)
}
it should "compare to nullable variable" in {
- eval("null = x", Map("x" -> ValNull)) should be(ValBoolean(true))
- eval("null = x", Map("x" -> 1)) should be(ValBoolean(false))
+ evaluateExpression("null = x", Map("x" -> ValNull)) should returnResult(true)
+ evaluateExpression("null = x", Map("x" -> 1)) should returnResult(false)
- eval("null != x", Map("x" -> ValNull)) should be(ValBoolean(false))
- eval("null != x", Map("x" -> 1)) should be(ValBoolean(true))
+ evaluateExpression("null != x", Map("x" -> ValNull)) should returnResult(false)
+ evaluateExpression("null != x", Map("x" -> 1)) should returnResult(true)
}
it should "compare to nullable context entry" in {
- eval("null = {x: null}.x") should be(ValBoolean(true))
- eval("null = {x: 1}.x") should be(ValBoolean(false))
+ evaluateExpression("null = {x: null}.x") should returnResult(true)
+ evaluateExpression("null = {x: 1}.x") should returnResult(false)
- eval("null != {x: null}.x") should be(ValBoolean(false))
- eval("null != {x: 1}.x") should be(ValBoolean(true))
+ evaluateExpression("null != {x: null}.x") should returnResult(false)
+ evaluateExpression("null != {x: 1}.x") should returnResult(true)
}
it should "compare to not existing variable" in {
- eval("null = x") should be(ValBoolean(true))
- eval("null = x.y") should be(ValBoolean(true))
+ evaluateExpression("null = x") should returnResult(true)
+ evaluateExpression("null = x.y") should returnResult(true)
- eval("x = null") should be(ValBoolean(true))
- eval("x.y = null") should be(ValBoolean(true))
+ evaluateExpression("x = null") should returnResult(true)
+ evaluateExpression("x.y = null") should returnResult(true)
}
it should "compare to not existing context entry" in {
- eval("null = {}.x") should be(ValBoolean(true))
- eval("null = {x: null}.x.y") should be(ValBoolean(true))
+ evaluateExpression("null = {}.x") should returnResult(true)
+ evaluateExpression("null = {x: null}.x.y") should returnResult(true)
- eval("{}.x = null") should be(ValBoolean(true))
- eval("{x: null}.x.y = null") should be(ValBoolean(true))
+ evaluateExpression("{}.x = null") should returnResult(true)
+ evaluateExpression("{x: null}.x.y = null") should returnResult(true)
}
"A variable name" should "not be a key-word" in {
-
- eval("some = true") shouldBe a[ValError]
- eval("every = true") shouldBe a[ValError]
- eval("if = true") shouldBe a[ValError]
- eval("then = true") shouldBe a[ValError]
- eval("else = true") shouldBe a[ValError]
- eval("function = true") shouldBe a[ValError]
- eval("for = true") shouldBe a[ValError]
- eval("between = true") shouldBe a[ValError]
- eval("instance = true") shouldBe a[ValError]
- eval("of = true") shouldBe a[ValError]
- eval("not = true") shouldBe a[ValError]
- eval("in = true") shouldBe a[ValError]
- eval("satisfies = true") shouldBe a[ValError]
- eval("and = true") shouldBe a[ValError]
- eval("or = true") shouldBe a[ValError]
- eval("return = true") shouldBe a[ValError]
+ evaluateExpression("{ null: 1 }.null") should failToParse()
+ evaluateExpression("{ true: 1}.true") should failToParse()
+ evaluateExpression("{ false: 1}.false") should failToParse()
+ evaluateExpression("function") should failToParse()
+ evaluateExpression("in") should failToParse()
+ evaluateExpression("return") should failToParse()
+ evaluateExpression("then") should failToParse()
+ evaluateExpression("else") should failToParse()
+ evaluateExpression("satisfies") should failToParse()
+ evaluateExpression("and") should failToParse()
+ evaluateExpression("or") should failToParse()
+ }
+
+// Ignored as these keywords are not listed as reserved keywords yet
+ ignore should "not be a key-word (ignored)" in {
+ evaluateExpression("some") should failToParse()
+ evaluateExpression("every") should failToParse()
+ evaluateExpression("if") should failToParse()
+ evaluateExpression("for") should failToParse()
+ evaluateExpression("between") should failToParse()
+ evaluateExpression("instance") should failToParse()
+ evaluateExpression("of") should failToParse()
+ evaluateExpression("not") should failToParse()
}
List(
@@ -300,32 +321,47 @@ class InterpreterExpressionTest extends AnyFlatSpec with Matchers with FeelInteg
).foreach { variableName =>
it should s"contain a key-word ($variableName)" in {
- eval(s"$variableName = true", Map(variableName -> true)) should be(ValBoolean(true))
+ evaluateExpression(s"$variableName = true", Map(variableName -> true)) should returnResult(
+ true
+ )
}
}
"A comment" should "be written as end of line comments //" in {
- eval(""" [1,2,3][1] // the first item """) should be(ValNumber(1))
+ evaluateExpression(""" [1,2,3][1] // the first item """) should returnResult(1)
}
it should "be written as trailing comments /* .. */" in {
- eval(""" [1,2,3][1] /* the first item */ """) should be(ValNumber(1))
+ evaluateExpression(""" [1,2,3][1] /* the first item */ """) should returnResult(1)
}
it should "be written as single line comments /* .. */" in {
- eval("""
+ evaluateExpression("""
/* the first item */
[1,2,3][1]
- """) should be(ValNumber(1))
+ """) should returnResult(1)
}
it should "be written as block comments /* .. */" in {
- eval("""
+ evaluateExpression("""
/*
* the first item
*/
[1,2,3][1]
- """) should be(ValNumber(1))
+ """) should returnResult(1)
+ }
+
+ "The special variable '?' (input value)" should "be available in an unary-test" in {
+
+ evaluateExpression("5 in ? < 10") should returnResult(true)
+ evaluateExpression("5 in ? < 3") should returnResult(false)
+ }
+
+ it should "not be available outside an unary-test" in {
+
+ evaluateExpression("? < 10") should failWith(
+ """failed to evaluate expression '? < 10': No input value available. '?' can only be used inside an unary-test expression."""
+ )
}
}
diff --git a/src/test/scala/org/camunda/feel/impl/interpreter/InterpreterListExpressionTest.scala b/src/test/scala/org/camunda/feel/impl/interpreter/InterpreterListExpressionTest.scala
index 7f3c0613f..5d184e778 100644
--- a/src/test/scala/org/camunda/feel/impl/interpreter/InterpreterListExpressionTest.scala
+++ b/src/test/scala/org/camunda/feel/impl/interpreter/InterpreterListExpressionTest.scala
@@ -150,7 +150,7 @@ class InterpreterListExpressionTest
}
it should "fail if compare to not a list" in {
- evaluateExpression("[] = 1") should failWith("expect List but found 'ValNumber(1)'")
+ evaluateExpression("[] = 1") should failWith("Can't compare '[]' with '1'")
}
"A for-expression" should "iterate over a range" in {
@@ -390,7 +390,7 @@ class InterpreterListExpressionTest
it should "fail if the filter doesn't return a boolean or a number" in {
evaluateExpression(""" [1,2,3,4]["not a valid filter"] """) should
- failWith("Expected boolean filter or number but found 'ValString(not a valid filter)'")
+ failWith("""Expected boolean filter or number but found '"not a valid filter"'""")
}
it should "access an item property if the context contains a variable with the same name" in {
diff --git a/src/test/scala/org/camunda/feel/impl/interpreter/InterpreterUnaryTest.scala b/src/test/scala/org/camunda/feel/impl/interpreter/InterpreterUnaryTest.scala
index 4d099b837..b87949382 100644
--- a/src/test/scala/org/camunda/feel/impl/interpreter/InterpreterUnaryTest.scala
+++ b/src/test/scala/org/camunda/feel/impl/interpreter/InterpreterUnaryTest.scala
@@ -16,525 +16,636 @@
*/
package org.camunda.feel.impl.interpreter
-import org.camunda.feel.impl.FeelIntegrationTest
-import org.camunda.feel.syntaxtree._
-import org.scalatest.matchers.should.Matchers
+import org.camunda.feel.impl.{EvaluationResultMatchers, FeelEngineTest}
import org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
/** @author
* Philipp Ossler
*/
-class InterpreterUnaryTest extends AnyFlatSpec with Matchers with FeelIntegrationTest {
+class InterpreterUnaryTest
+ extends AnyFlatSpec
+ with Matchers
+ with FeelEngineTest
+ with EvaluationResultMatchers {
"A number" should "compare with '<'" in {
- evalUnaryTests(2, "< 3") should be(ValBoolean(true))
- evalUnaryTests(3, "< 3") should be(ValBoolean(false))
- evalUnaryTests(4, "< 3") should be(ValBoolean(false))
+ evaluateUnaryTests("< 3", 2) should returnResult(true)
+ evaluateUnaryTests("< 3", 3) should returnResult(false)
+ evaluateUnaryTests("< 3", 4) should returnResult(false)
}
it should "compare with '<='" in {
- evalUnaryTests(2, "<= 3") should be(ValBoolean(true))
- evalUnaryTests(3, "<= 3") should be(ValBoolean(true))
- evalUnaryTests(4, "<= 3") should be(ValBoolean(false))
+ evaluateUnaryTests("<= 3", 2) should returnResult(true)
+ evaluateUnaryTests("<= 3", 3) should returnResult(true)
+ evaluateUnaryTests("<= 3", 4) should returnResult(false)
}
it should "compare with '>'" in {
- evalUnaryTests(2, "> 3") should be(ValBoolean(false))
- evalUnaryTests(3, "> 3") should be(ValBoolean(false))
- evalUnaryTests(4, "> 3") should be(ValBoolean(true))
+ evaluateUnaryTests("> 3", 2) should returnResult(false)
+ evaluateUnaryTests("> 3", 3) should returnResult(false)
+ evaluateUnaryTests("> 3", 4) should returnResult(true)
}
it should "compare with '>='" in {
- evalUnaryTests(2, ">= 3") should be(ValBoolean(false))
- evalUnaryTests(3, ">= 3") should be(ValBoolean(true))
- evalUnaryTests(4, ">= 3") should be(ValBoolean(true))
+ evaluateUnaryTests(">= 3", 2) should returnResult(false)
+ evaluateUnaryTests(">= 3", 3) should returnResult(true)
+ evaluateUnaryTests(">= 3", 4) should returnResult(true)
}
it should "be equal to another number" in {
- evalUnaryTests(2, "3") should be(ValBoolean(false))
- evalUnaryTests(3, "3") should be(ValBoolean(true))
+ evaluateUnaryTests("3", 2) should returnResult(false)
+ evaluateUnaryTests("3", 3) should returnResult(true)
- evalUnaryTests(-1, "-1") should be(ValBoolean(true))
- evalUnaryTests(0, "-1") should be(ValBoolean(false))
+ evaluateUnaryTests("-1", -1) should returnResult(true)
+ evaluateUnaryTests("-1", 0) should returnResult(false)
}
it should "be in interval '(2..4)'" in {
- evalUnaryTests(2, "(2..4)") should be(ValBoolean(false))
- evalUnaryTests(3, "(2..4)") should be(ValBoolean(true))
- evalUnaryTests(4, "(2..4)") should be(ValBoolean(false))
+ evaluateUnaryTests("(2..4)", 2) should returnResult(false)
+ evaluateUnaryTests("(2..4)", 3) should returnResult(true)
+ evaluateUnaryTests("(2..4)", 4) should returnResult(false)
}
it should "be in interval '[2..4]'" in {
- evalUnaryTests(2, "[2..4]") should be(ValBoolean(true))
- evalUnaryTests(3, "[2..4]") should be(ValBoolean(true))
- evalUnaryTests(4, "[2..4]") should be(ValBoolean(true))
+ evaluateUnaryTests("[2..4]", 2) should returnResult(true)
+ evaluateUnaryTests("[2..4]", 3) should returnResult(true)
+ evaluateUnaryTests("[2..4]", 4) should returnResult(true)
}
it should "be in one of two intervals (disjunction)" in {
- evalUnaryTests(3, "[1..5], [6..10]") should be(ValBoolean(true))
- evalUnaryTests(6, "[1..5], [6..10]") should be(ValBoolean(true))
- evalUnaryTests(11, "[1..5], [6..10]") should be(ValBoolean(false))
+ evaluateUnaryTests("[1..5], [6..10]", 3) should returnResult(true)
+ evaluateUnaryTests("[1..5], [6..10]", 6) should returnResult(true)
+ evaluateUnaryTests("[1..5], [6..10]", 11) should returnResult(false)
}
it should "be in '2,3'" in {
- evalUnaryTests(2, "2,3") should be(ValBoolean(true))
- evalUnaryTests(3, "2,3") should be(ValBoolean(true))
- evalUnaryTests(4, "2,3") should be(ValBoolean(false))
+ evaluateUnaryTests("2,3", 2) should returnResult(true)
+ evaluateUnaryTests("2,3", 3) should returnResult(true)
+ evaluateUnaryTests("2,3", 4) should returnResult(false)
}
it should "be not equal 'not(3)'" in {
- evalUnaryTests(2, "not(3)") should be(ValBoolean(true))
- evalUnaryTests(3, "not(3)") should be(ValBoolean(false))
- evalUnaryTests(4, "not(3)") should be(ValBoolean(true))
+ evaluateUnaryTests("not(3)", 2) should returnResult(true)
+ evaluateUnaryTests("not(3)", 3) should returnResult(false)
+ evaluateUnaryTests("not(3)", 4) should returnResult(true)
}
it should "be not in 'not(2,3)'" in {
- evalUnaryTests(2, "not(2,3)") should be(ValBoolean(false))
- evalUnaryTests(3, "not(2,3)") should be(ValBoolean(false))
- evalUnaryTests(4, "not(2,3)") should be(ValBoolean(true))
+ evaluateUnaryTests("not(2,3)", 2) should returnResult(false)
+ evaluateUnaryTests("not(2,3)", 3) should returnResult(false)
+ evaluateUnaryTests("not(2,3)", 4) should returnResult(true)
}
it should "compare to a variable (qualified name)" in {
- evalUnaryTests(2, "var", Map("var" -> 3)) should be(ValBoolean(false))
- evalUnaryTests(3, "var", Map("var" -> 3)) should be(ValBoolean(true))
+ evaluateUnaryTests("var", 2, Map("var" -> 3)) should returnResult(false)
+ evaluateUnaryTests("var", 3, Map("var" -> 3)) should returnResult(true)
- evalUnaryTests(2, "< var", Map("var" -> 3)) should be(ValBoolean(true))
- evalUnaryTests(3, "< var", Map("var" -> 3)) should be(ValBoolean(false))
+ evaluateUnaryTests("< var", 2, Map("var" -> 3)) should returnResult(true)
+ evaluateUnaryTests("< var", 3, Map("var" -> 3)) should returnResult(false)
}
it should "compare to a field of a bean" in {
class A(val b: Int)
- evalUnaryTests(3, "a.b", Map("a" -> new A(3))) should be(ValBoolean(true))
- evalUnaryTests(3, "a.b", Map("a" -> new A(4))) should be(ValBoolean(false))
+ evaluateUnaryTests("a.b", 3, Map("a" -> new A(3))) should returnResult(true)
+ evaluateUnaryTests("a.b", 3, Map("a" -> new A(4))) should returnResult(false)
- evalUnaryTests(3, "< a.b", Map("a" -> new A(4))) should be(ValBoolean(true))
- evalUnaryTests(3, "< a.b", Map("a" -> new A(2))) should be(ValBoolean(false))
+ evaluateUnaryTests("< a.b", 3, Map("a" -> new A(4))) should returnResult(true)
+ evaluateUnaryTests("< a.b", 3, Map("a" -> new A(2))) should returnResult(false)
}
it should "compare to null" in {
+ evaluateUnaryTests("3", inputValue = null) should returnResult(false)
+ }
+
+ it should "compare null with less/greater than" in {
+ evaluateUnaryTests("< 3", inputValue = null) should returnResult(false)
+ evaluateUnaryTests("<= 3", inputValue = null) should returnResult(false)
+ evaluateUnaryTests("> 3", inputValue = null) should returnResult(false)
+ evaluateUnaryTests(">= 3", inputValue = null) should returnResult(false)
+ }
- evalUnaryTests(null, "3") should be(ValBoolean(false))
- evalUnaryTests(null, "< 3") should be(ValBoolean(false))
- evalUnaryTests(null, "> 3") should be(ValBoolean(false))
- evalUnaryTests(null, "(0..10)") should be(ValBoolean(false))
+ it should "compare null with interval" in {
+ evaluateUnaryTests("(0..10)", inputValue = null) should returnResult(false)
}
"A string" should "be equal to another string" in {
- evalUnaryTests("a", """ "b" """) should be(ValBoolean(false))
- evalUnaryTests("b", """ "b" """) should be(ValBoolean(true))
+ evaluateUnaryTests(""" "b" """, "a") should returnResult(false)
+ evaluateUnaryTests(""" "b" """, "b") should returnResult(true)
}
it should "compare to null" in {
- evalUnaryTests(null, """ "a" """) should be(ValBoolean(false))
+ evaluateUnaryTests(""" "a" """, inputValue = null) should returnResult(false)
}
it should """be in '"a","b"' """ in {
- evalUnaryTests("a", """ "a","b" """) should be(ValBoolean(true))
- evalUnaryTests("b", """ "a","b" """) should be(ValBoolean(true))
- evalUnaryTests("c", """ "a","b" """) should be(ValBoolean(false))
+ evaluateUnaryTests(""" "a","b" """, "a") should returnResult(true)
+ evaluateUnaryTests(""" "a","b" """, "b") should returnResult(true)
+ evaluateUnaryTests(""" "a","b" """, "c") should returnResult(false)
}
"A boolean" should "be equal to another boolean" in {
- evalUnaryTests(false, "true") should be(ValBoolean(false))
- evalUnaryTests(true, "false") should be(ValBoolean(false))
+ evaluateUnaryTests("true", false) should returnResult(false)
+ evaluateUnaryTests("false", true) should returnResult(false)
- evalUnaryTests(false, "false") should be(ValBoolean(true))
- evalUnaryTests(true, "true") should be(ValBoolean(true))
+ evaluateUnaryTests("false", false) should returnResult(true)
+ evaluateUnaryTests("true", true) should returnResult(true)
}
it should "compare to null" in {
- evalUnaryTests(null, "true") should be(ValBoolean(false))
- evalUnaryTests(null, "false") should be(ValBoolean(false))
+ evaluateUnaryTests("true", inputValue = null) should returnResult(false)
+ evaluateUnaryTests("false", inputValue = null) should returnResult(false)
}
it should "compare to a boolean comparison (numeric)" in {
- evalUnaryTests(true, "1 < 2") should be(ValBoolean(true))
- evalUnaryTests(true, "2 < 1") should be(ValBoolean(false))
+ evaluateUnaryTests("1 < 2", true) should returnResult(true)
+ evaluateUnaryTests("2 < 1", true) should returnResult(false)
}
it should "compare to a boolean comparison (string)" in {
- evalUnaryTests(true, """ "a" = "a" """) should be(ValBoolean(true))
- evalUnaryTests(true, """ "a" = "b" """) should be(ValBoolean(false))
+ evaluateUnaryTests(""" "a" = "a" """, true) should returnResult(true)
+ evaluateUnaryTests(""" "a" = "b" """, true) should returnResult(false)
}
it should "compare to a conjunction (and)" in {
// it is uncommon to use a conjunction in a unary-tests but the engine should be able to parse
- evalUnaryTests(true, "true and true") shouldBe ValBoolean(true)
- evalUnaryTests(true, "false and true") shouldBe ValBoolean(false)
+ evaluateUnaryTests("true and true", true) should returnResult(true)
+ evaluateUnaryTests("false and true", true) should returnResult(false)
- evalUnaryTests(true, "true and null") shouldBe ValBoolean(false)
- evalUnaryTests(true, "false and null") shouldBe ValBoolean(false)
+ evaluateUnaryTests("true and null", true) should returnResult(false)
+ evaluateUnaryTests("false and null", true) should returnResult(false)
- evalUnaryTests(true, """true and "otherwise" """) shouldBe ValBoolean(false)
- evalUnaryTests(true, """false and "otherwise" """) shouldBe ValBoolean(false)
+ evaluateUnaryTests("""true and "otherwise" """, true) should returnResult(false)
+ evaluateUnaryTests("""false and "otherwise" """, true) should returnResult(false)
}
it should "compare to a disjunction (or)" in {
// it is uncommon to use a disjunction in a unary-tests but the engine should be able to parse
- evalUnaryTests(true, "true or true") shouldBe ValBoolean(true)
- evalUnaryTests(true, "false or true") shouldBe ValBoolean(true)
- evalUnaryTests(true, "false or false") shouldBe ValBoolean(false)
+ evaluateUnaryTests("true or true", true) should returnResult(true)
+ evaluateUnaryTests("false or true", true) should returnResult(true)
+ evaluateUnaryTests("false or false", true) should returnResult(false)
- evalUnaryTests(true, "true or null") shouldBe ValBoolean(true)
- evalUnaryTests(true, "false or null") shouldBe ValBoolean(false)
+ evaluateUnaryTests("true or null", true) should returnResult(true)
+ evaluateUnaryTests("false or null", true) should returnResult(false)
- evalUnaryTests(true, """true or "otherwise" """) shouldBe ValBoolean(true)
- evalUnaryTests(true, """false or "otherwise" """) shouldBe ValBoolean(false)
+ evaluateUnaryTests("""true or "otherwise" """, true) should returnResult(true)
+ evaluateUnaryTests("""false or "otherwise" """, true) should returnResult(false)
}
"A date" should "compare with '<'" in {
- evalUnaryTests(date("2015-09-17"), """< date("2015-09-18")""") should be(ValBoolean(true))
- evalUnaryTests(date("2015-09-18"), """< date("2015-09-18")""") should be(ValBoolean(false))
- evalUnaryTests(date("2015-09-19"), """< date("2015-09-18")""") should be(ValBoolean(false))
+ evaluateUnaryTests("""< date("2015-09-18")""", date("2015-09-17")) should returnResult(true)
+ evaluateUnaryTests("""< date("2015-09-18")""", date("2015-09-18")) should returnResult(false)
+ evaluateUnaryTests("""< date("2015-09-18")""", date("2015-09-19")) should returnResult(false)
}
it should "compare with '<='" in {
- evalUnaryTests(date("2015-09-17"), """<= date("2015-09-18")""") should be(ValBoolean(true))
- evalUnaryTests(date("2015-09-18"), """<= date("2015-09-18")""") should be(ValBoolean(true))
- evalUnaryTests(date("2015-09-19"), """<= date("2015-09-18")""") should be(ValBoolean(false))
+ evaluateUnaryTests("""<= date("2015-09-18")""", date("2015-09-17")) should returnResult(true)
+ evaluateUnaryTests("""<= date("2015-09-18")""", date("2015-09-18")) should returnResult(true)
+ evaluateUnaryTests("""<= date("2015-09-18")""", date("2015-09-19")) should returnResult(false)
}
it should "compare with '>'" in {
- evalUnaryTests(date("2015-09-17"), """> date("2015-09-18")""") should be(ValBoolean(false))
- evalUnaryTests(date("2015-09-18"), """> date("2015-09-18")""") should be(ValBoolean(false))
- evalUnaryTests(date("2015-09-19"), """> date("2015-09-18")""") should be(ValBoolean(true))
+ evaluateUnaryTests("""> date("2015-09-18")""", date("2015-09-17")) should returnResult(false)
+ evaluateUnaryTests("""> date("2015-09-18")""", date("2015-09-18")) should returnResult(false)
+ evaluateUnaryTests("""> date("2015-09-18")""", date("2015-09-19")) should returnResult(true)
}
it should "compare with '>='" in {
- evalUnaryTests(date("2015-09-17"), """>= date("2015-09-18")""") should be(ValBoolean(false))
- evalUnaryTests(date("2015-09-18"), """>= date("2015-09-18")""") should be(ValBoolean(true))
- evalUnaryTests(date("2015-09-19"), """>= date("2015-09-18")""") should be(ValBoolean(true))
+ evaluateUnaryTests(""">= date("2015-09-18")""", date("2015-09-17")) should returnResult(false)
+ evaluateUnaryTests(""">= date("2015-09-18")""", date("2015-09-18")) should returnResult(true)
+ evaluateUnaryTests(""">= date("2015-09-18")""", date("2015-09-19")) should returnResult(true)
}
it should "be equal to another date" in {
- evalUnaryTests(date("2015-09-17"), """date("2015-09-18")""") should be(ValBoolean(false))
- evalUnaryTests(date("2015-09-18"), """date("2015-09-18")""") should be(ValBoolean(true))
+ evaluateUnaryTests("""date("2015-09-18")""", date("2015-09-17")) should returnResult(false)
+ evaluateUnaryTests("""date("2015-09-18")""", date("2015-09-18")) should returnResult(true)
}
it should """be in interval '(date("2015-09-17")..date("2015-09-19")]'""" in {
- evalUnaryTests(date("2015-09-17"), """(date("2015-09-17")..date("2015-09-19"))""") should be(
- ValBoolean(false)
- )
- evalUnaryTests(date("2015-09-18"), """(date("2015-09-17")..date("2015-09-19"))""") should be(
- ValBoolean(true)
- )
- evalUnaryTests(date("2015-09-19"), """(date("2015-09-17")..date("2015-09-19"))""") should be(
- ValBoolean(false)
- )
+ evaluateUnaryTests(
+ """(date("2015-09-17")..date("2015-09-19"))""",
+ date("2015-09-17")
+ ) should returnResult(false)
+ evaluateUnaryTests(
+ """(date("2015-09-17")..date("2015-09-19"))""",
+ date("2015-09-18")
+ ) should returnResult(true)
+ evaluateUnaryTests(
+ """(date("2015-09-17")..date("2015-09-19"))""",
+ date("2015-09-19")
+ ) should returnResult(false)
}
it should """be in interval '[date("2015-09-17")..date("2015-09-19")]'""" in {
- evalUnaryTests(date("2015-09-17"), """[date("2015-09-17")..date("2015-09-19")]""") should be(
- ValBoolean(true)
- )
- evalUnaryTests(date("2015-09-18"), """[date("2015-09-17")..date("2015-09-19")]""") should be(
- ValBoolean(true)
- )
- evalUnaryTests(date("2015-09-19"), """[date("2015-09-17")..date("2015-09-19")]""") should be(
- ValBoolean(true)
- )
+ evaluateUnaryTests(
+ """[date("2015-09-17")..date("2015-09-19")]""",
+ date("2015-09-17")
+ ) should returnResult(true)
+ evaluateUnaryTests(
+ """[date("2015-09-17")..date("2015-09-19")]""",
+ date("2015-09-18")
+ ) should returnResult(true)
+ evaluateUnaryTests(
+ """[date("2015-09-17")..date("2015-09-19")]""",
+ date("2015-09-19")
+ ) should returnResult(true)
}
"A time" should "compare with '<'" in {
- evalUnaryTests(localTime("08:31:14"), """< time("10:00:00")""") should be(ValBoolean(true))
- evalUnaryTests(localTime("10:10:00"), """< time("10:00:00")""") should be(ValBoolean(false))
- evalUnaryTests(localTime("11:31:14"), """< time("10:00:00")""") should be(ValBoolean(false))
+ evaluateUnaryTests("""< time("10:00:00")""", localTime("08:31:14")) should returnResult(true)
+ evaluateUnaryTests("""< time("10:00:00")""", localTime("10:10:00")) should returnResult(false)
+ evaluateUnaryTests("""< time("10:00:00")""", localTime("11:31:14")) should returnResult(false)
- evalUnaryTests(time("10:00:00+01:00"), """< time("11:00:00+01:00")""") should be(
- ValBoolean(true)
+ evaluateUnaryTests("""< time("11:00:00+01:00")""", time("10:00:00+01:00")) should returnResult(
+ true
)
- evalUnaryTests(time("10:00:00+01:00"), """< time("10:00:00+01:00")""") should be(
- ValBoolean(false)
+ evaluateUnaryTests("""< time("10:00:00+01:00")""", time("10:00:00+01:00")) should returnResult(
+ false
)
}
it should "be equal to another time" in {
- evalUnaryTests(localTime("08:31:14"), """time("10:00:00")""") should be(ValBoolean(false))
- evalUnaryTests(localTime("08:31:14"), """time("08:31:14")""") should be(ValBoolean(true))
+ evaluateUnaryTests("""time("10:00:00")""", localTime("08:31:14")) should returnResult(false)
+ evaluateUnaryTests("""time("08:31:14")""", localTime("08:31:14")) should returnResult(true)
- evalUnaryTests(time("10:00:00+01:00"), """time("10:00:00+02:00")""") should be(
- ValBoolean(false)
+ evaluateUnaryTests("""time("10:00:00+02:00")""", time("10:00:00+01:00")) should returnResult(
+ false
+ )
+ evaluateUnaryTests("""time("11:00:00+02:00")""", time("10:00:00+01:00")) should returnResult(
+ false
)
- evalUnaryTests(time("10:00:00+01:00"), """time("11:00:00+02:00")""") should be(
- ValBoolean(false)
+ evaluateUnaryTests("""time("10:00:00+01:00")""", time("10:00:00+01:00")) should returnResult(
+ true
)
- evalUnaryTests(time("10:00:00+01:00"), """time("10:00:00+01:00")""") should be(ValBoolean(true))
}
it should """be in interval '[time("08:00:00")..time("10:00:00")]'""" in {
- evalUnaryTests(localTime("07:45:10"), """[time("08:00:00")..time("10:00:00")]""") should be(
- ValBoolean(false)
- )
- evalUnaryTests(localTime("09:15:20"), """[time("08:00:00")..time("10:00:00")]""") should be(
- ValBoolean(true)
- )
- evalUnaryTests(localTime("11:30:30"), """[time("08:00:00")..time("10:00:00")]""") should be(
- ValBoolean(false)
- )
-
- evalUnaryTests(
- time("11:30:00+01:00"),
- """[time("08:00:00+01:00")..time("10:00:00+01:00")]"""
- ) should be(ValBoolean(false))
- evalUnaryTests(
- time("09:30:00+01:00"),
- """[time("08:00:00+01:00")..time("10:00:00+01:00")]"""
- ) should be(ValBoolean(true))
+ evaluateUnaryTests(
+ """[time("08:00:00")..time("10:00:00")]""",
+ localTime("07:45:10")
+ ) should returnResult(false)
+ evaluateUnaryTests(
+ """[time("08:00:00")..time("10:00:00")]""",
+ localTime("09:15:20")
+ ) should returnResult(true)
+ evaluateUnaryTests(
+ """[time("08:00:00")..time("10:00:00")]""",
+ localTime("11:30:30")
+ ) should returnResult(false)
+
+ evaluateUnaryTests(
+ """[time("08:00:00+01:00")..time("10:00:00+01:00")]""",
+ time("11:30:00+01:00")
+ ) should returnResult(false)
+ evaluateUnaryTests(
+ """[time("08:00:00+01:00")..time("10:00:00+01:00")]""",
+ time("09:30:00+01:00")
+ ) should returnResult(true)
}
"A date-time" should "compare with '<'" in {
- evalUnaryTests(
- localDateTime("2015-09-17T08:31:14"),
- """< date and time("2015-09-17T10:00:00")"""
- ) should be(ValBoolean(true))
- evalUnaryTests(
- localDateTime("2015-09-17T10:10:00"),
- """< date and time("2015-09-17T10:00:00")"""
- ) should be(ValBoolean(false))
- evalUnaryTests(
- localDateTime("2015-09-17T11:31:14"),
- """< date and time("2015-09-17T10:00:00")"""
- ) should be(ValBoolean(false))
-
- evalUnaryTests(
- dateTime("2015-09-17T10:00:00+01:00"),
- """< date and time("2015-09-17T12:00:00+01:00")"""
- ) should be(ValBoolean(true))
- evalUnaryTests(
- dateTime("2015-09-17T10:00:00+01:00"),
- """< date and time("2015-09-17T09:00:00+01:00")"""
- ) should be(ValBoolean(false))
+ evaluateUnaryTests(
+ """< date and time("2015-09-17T10:00:00")""",
+ localDateTime("2015-09-17T08:31:14")
+ ) should returnResult(true)
+ evaluateUnaryTests(
+ """< date and time("2015-09-17T10:00:00")""",
+ localDateTime("2015-09-17T10:10:00")
+ ) should returnResult(false)
+ evaluateUnaryTests(
+ """< date and time("2015-09-17T10:00:00")""",
+ localDateTime("2015-09-17T11:31:14")
+ ) should returnResult(false)
+
+ evaluateUnaryTests(
+ """< date and time("2015-09-17T12:00:00+01:00")""",
+ dateTime("2015-09-17T10:00:00+01:00")
+ ) should returnResult(true)
+ evaluateUnaryTests(
+ """< date and time("2015-09-17T09:00:00+01:00")""",
+ dateTime("2015-09-17T10:00:00+01:00")
+ ) should returnResult(false)
}
it should "be equal to another date-time" in {
- evalUnaryTests(
- localDateTime("2015-09-17T08:31:14"),
- """date and time("2015-09-17T10:00:00")"""
- ) should be(ValBoolean(false))
- evalUnaryTests(
- localDateTime("2015-09-17T08:31:14"),
- """date and time("2015-09-17T08:31:14")"""
- ) should be(ValBoolean(true))
-
- evalUnaryTests(
- dateTime("2015-09-17T08:30:00+01:00"),
- """date and time("2015-09-17T09:30:00+01:00")"""
- ) should be(ValBoolean(false))
- evalUnaryTests(
- dateTime("2015-09-17T08:30:00+01:00"),
- """date and time("2015-09-17T08:30:00+02:00")"""
- ) should be(ValBoolean(false))
- evalUnaryTests(
- dateTime("2015-09-17T08:30:00+01:00"),
- """date and time("2015-09-17T08:30:00+01:00")"""
- ) should be(ValBoolean(true))
+ evaluateUnaryTests(
+ """date and time("2015-09-17T10:00:00")""",
+ localDateTime("2015-09-17T08:31:14")
+ ) should returnResult(false)
+ evaluateUnaryTests(
+ """date and time("2015-09-17T08:31:14")""",
+ localDateTime("2015-09-17T08:31:14")
+ ) should returnResult(true)
+
+ evaluateUnaryTests(
+ """date and time("2015-09-17T09:30:00+01:00")""",
+ dateTime("2015-09-17T08:30:00+01:00")
+ ) should returnResult(false)
+ evaluateUnaryTests(
+ """date and time("2015-09-17T08:30:00+02:00")""",
+ dateTime("2015-09-17T08:30:00+01:00")
+ ) should returnResult(false)
+ evaluateUnaryTests(
+ """date and time("2015-09-17T08:30:00+01:00")""",
+ dateTime("2015-09-17T08:30:00+01:00")
+ ) should returnResult(true)
}
it should """be in interval '[dante and time("2015-09-17T08:00:00")..date and time("2015-09-17T10:00:00")]'""" in {
- evalUnaryTests(
- localDateTime("2015-09-17T07:45:10"),
- """[date and time("2015-09-17T08:00:00")..date and time("2015-09-17T10:00:00")]"""
- ) should be(ValBoolean(false))
- evalUnaryTests(
- localDateTime("2015-09-17T09:15:20"),
- """[date and time("2015-09-17T08:00:00")..date and time("2015-09-17T10:00:00")]"""
- ) should be(ValBoolean(true))
- evalUnaryTests(
- localDateTime("2015-09-17T11:30:30"),
- """[date and time("2015-09-17T08:00:00")..date and time("2015-09-17T10:00:00")]"""
- ) should be(ValBoolean(false))
-
- evalUnaryTests(
- dateTime("2015-09-17T08:30:00+01:00"),
- """[date and time("2015-09-17T09:00:00+01:00")..date and time("2015-09-17T10:00:00+01:00")]"""
- ) should be(ValBoolean(false))
- evalUnaryTests(
- dateTime("2015-09-17T08:30:00+01:00"),
- """[date and time("2015-09-17T08:00:00+01:00")..date and time("2015-09-17T10:00:00+01:00")]"""
- ) should be(ValBoolean(true))
+ evaluateUnaryTests(
+ """[date and time("2015-09-17T08:00:00")..date and time("2015-09-17T10:00:00")]""",
+ localDateTime("2015-09-17T07:45:10")
+ ) should returnResult(false)
+ evaluateUnaryTests(
+ """[date and time("2015-09-17T08:00:00")..date and time("2015-09-17T10:00:00")]""",
+ localDateTime("2015-09-17T09:15:20")
+ ) should returnResult(true)
+ evaluateUnaryTests(
+ """[date and time("2015-09-17T08:00:00")..date and time("2015-09-17T10:00:00")]""",
+ localDateTime("2015-09-17T11:30:30")
+ ) should returnResult(false)
+
+ evaluateUnaryTests(
+ """[date and time("2015-09-17T09:00:00+01:00")..date and time("2015-09-17T10:00:00+01:00")]""",
+ dateTime("2015-09-17T08:30:00+01:00")
+ ) should returnResult(false)
+ evaluateUnaryTests(
+ """[date and time("2015-09-17T08:00:00+01:00")..date and time("2015-09-17T10:00:00+01:00")]""",
+ dateTime("2015-09-17T08:30:00+01:00")
+ ) should returnResult(true)
}
"A year-month-duration" should "compare with '<'" in {
- evalUnaryTests(yearMonthDuration("P1Y"), """< duration("P2Y")""") should be(ValBoolean(true))
- evalUnaryTests(yearMonthDuration("P1Y"), """< duration("P1Y")""") should be(ValBoolean(false))
- evalUnaryTests(yearMonthDuration("P1Y2M"), """< duration("P1Y")""") should be(ValBoolean(false))
+ evaluateUnaryTests("""< duration("P2Y")""", yearMonthDuration("P1Y")) should returnResult(true)
+ evaluateUnaryTests("""< duration("P1Y")""", yearMonthDuration("P1Y")) should returnResult(false)
+ evaluateUnaryTests("""< duration("P1Y")""", yearMonthDuration("P1Y2M")) should returnResult(
+ false
+ )
}
it should "be equal to another duration" in {
- evalUnaryTests(yearMonthDuration("P1Y4M"), """duration("P1Y3M")""") should be(ValBoolean(false))
- evalUnaryTests(yearMonthDuration("P1Y4M"), """duration("P1Y4M")""") should be(ValBoolean(true))
+ evaluateUnaryTests("""duration("P1Y3M")""", yearMonthDuration("P1Y4M")) should returnResult(
+ false
+ )
+ evaluateUnaryTests("""duration("P1Y4M")""", yearMonthDuration("P1Y4M")) should returnResult(
+ true
+ )
}
it should """be in interval '[duration("P1Y")..duration("P2Y")]'""" in {
- evalUnaryTests(yearMonthDuration("P6M"), """[duration("P1Y")..duration("P2Y")]""") should be(
- ValBoolean(false)
- )
- evalUnaryTests(yearMonthDuration("P1Y8M"), """[duration("P1Y")..duration("P2Y")]""") should be(
- ValBoolean(true)
- )
- evalUnaryTests(yearMonthDuration("P2Y1M"), """[duration("P1Y")..duration("P2Y")]""") should be(
- ValBoolean(false)
- )
+ evaluateUnaryTests(
+ """[duration("P1Y")..duration("P2Y")]""",
+ yearMonthDuration("P6M")
+ ) should returnResult(false)
+ evaluateUnaryTests(
+ """[duration("P1Y")..duration("P2Y")]""",
+ yearMonthDuration("P1Y8M")
+ ) should returnResult(true)
+ evaluateUnaryTests(
+ """[duration("P1Y")..duration("P2Y")]""",
+ yearMonthDuration("P2Y1M")
+ ) should returnResult(false)
}
"A day-time-duration" should "compare with '<'" in {
- evalUnaryTests(dayTimeDuration("P1DT4H"), """< duration("P2DT4H")""") should be(
- ValBoolean(true)
+ evaluateUnaryTests("""< duration("P2DT4H")""", dayTimeDuration("P1DT4H")) should returnResult(
+ true
)
- evalUnaryTests(dayTimeDuration("P2DT4H"), """< duration("P2DT4H")""") should be(
- ValBoolean(false)
+ evaluateUnaryTests("""< duration("P2DT4H")""", dayTimeDuration("P2DT4H")) should returnResult(
+ false
)
- evalUnaryTests(dayTimeDuration("P2DT8H"), """< duration("P2DT4H")""") should be(
- ValBoolean(false)
+ evaluateUnaryTests("""< duration("P2DT4H")""", dayTimeDuration("P2DT8H")) should returnResult(
+ false
)
}
it should "be equal to another duration" in {
- evalUnaryTests(dayTimeDuration("P1DT4H"), """duration("P2DT4H")""") should be(ValBoolean(false))
- evalUnaryTests(dayTimeDuration("P2DT4H"), """duration("P2DT4H")""") should be(ValBoolean(true))
+ evaluateUnaryTests("""duration("P2DT4H")""", dayTimeDuration("P1DT4H")) should returnResult(
+ false
+ )
+ evaluateUnaryTests("""duration("P2DT4H")""", dayTimeDuration("P2DT4H")) should returnResult(
+ true
+ )
}
it should """be in interval '[duration("P1D")..duration("P2D")]'""" in {
- evalUnaryTests(dayTimeDuration("PT4H"), """[duration("P1D")..duration("P2D")]""") should be(
- ValBoolean(false)
- )
- evalUnaryTests(dayTimeDuration("P1DT4H"), """[duration("P1D")..duration("P2D")]""") should be(
- ValBoolean(true)
- )
- evalUnaryTests(dayTimeDuration("P2DT4H"), """[duration("P1D")..duration("P2D")]""") should be(
- ValBoolean(false)
- )
+ evaluateUnaryTests(
+ """[duration("P1D")..duration("P2D")]""",
+ dayTimeDuration("PT4H")
+ ) should returnResult(false)
+ evaluateUnaryTests(
+ """[duration("P1D")..duration("P2D")]""",
+ dayTimeDuration("P1DT4H")
+ ) should returnResult(true)
+ evaluateUnaryTests(
+ """[duration("P1D")..duration("P2D")]""",
+ dayTimeDuration("P2DT4H")
+ ) should returnResult(false)
}
"A list" should "be equal to another list" in {
- evalUnaryTests(List.empty, "[]") should be(ValBoolean(true))
- evalUnaryTests(List(1, 2), "[1,2]") should be(ValBoolean(true))
+ evaluateUnaryTests("[]", List.empty) should returnResult(true)
+ evaluateUnaryTests("[1,2]", List(1, 2)) should returnResult(true)
- evalUnaryTests(List(1, 2), "[]") should be(ValBoolean(false))
- evalUnaryTests(List(1, 2), "[1]") should be(ValBoolean(false))
- evalUnaryTests(List(1, 2), "[2,1]") should be(ValBoolean(false))
- evalUnaryTests(List(1, 2), "[1,2,3]") should be(ValBoolean(false))
+ evaluateUnaryTests("[]", List(1, 2)) should returnResult(false)
+ evaluateUnaryTests("[1]", List(1, 2)) should returnResult(false)
+ evaluateUnaryTests("[2,1]", List(1, 2)) should returnResult(false)
+ evaluateUnaryTests("[1,2,3]", List(1, 2)) should returnResult(false)
}
it should "be checked in an every expression" in {
- evalUnaryTests(List(1, 2, 3), "every x in ? satisfies x > 3") should be(ValBoolean(false))
- evalUnaryTests(List(4, 5, 6), "every x in ? satisfies x > 3") should be(ValBoolean(true))
+ evaluateUnaryTests("every x in ? satisfies x > 3", List(1, 2, 3)) should returnResult(false)
+ evaluateUnaryTests("every x in ? satisfies x > 3", List(4, 5, 6)) should returnResult(true)
}
it should "be checked in a some expression" in {
- evalUnaryTests(List(1, 2, 3), "some x in ? satisfies x > 4") should be(ValBoolean(false))
- evalUnaryTests(List(4, 5, 6), "some x in ? satisfies x > 4") should be(ValBoolean(true))
+ evaluateUnaryTests("some x in ? satisfies x > 4", List(1, 2, 3)) should returnResult(false)
+ evaluateUnaryTests("some x in ? satisfies x > 4", List(4, 5, 6)) should returnResult(true)
}
"A context" should "be equal to another context" in {
- evalUnaryTests(Map.empty, "{}") should be(ValBoolean(true))
- evalUnaryTests(Map("x" -> 1), "{x:1}") should be(ValBoolean(true))
+ evaluateUnaryTests("{}", Map.empty) should returnResult(true)
+ evaluateUnaryTests("{x:1}", Map("x" -> 1)) should returnResult(true)
- evalUnaryTests(Map("x" -> 1), "{}") should be(ValBoolean(false))
- evalUnaryTests(Map("x" -> 1), "{x:2}") should be(ValBoolean(false))
- evalUnaryTests(Map("x" -> 1), "{y:1}") should be(ValBoolean(false))
- evalUnaryTests(Map("x" -> 1), "{x:1,y:2}") should be(ValBoolean(false))
+ evaluateUnaryTests("{}", Map("x" -> 1)) should returnResult(false)
+ evaluateUnaryTests("{x:2}", Map("x" -> 1)) should returnResult(false)
+ evaluateUnaryTests("{y:1}", Map("x" -> 1)) should returnResult(false)
+ evaluateUnaryTests("{x:1,y:2}", Map("x" -> 1)) should returnResult(false)
}
"An empty expression ('-')" should "be always true" in {
- evalUnaryTests(None, "-") should be(ValBoolean(true))
+ evaluateUnaryTests("-", None) should returnResult(true)
}
"A null expression" should "compare to null" in {
- evalUnaryTests(1, "null") should be(ValBoolean(false))
- evalUnaryTests(true, "null") should be(ValBoolean(false))
- evalUnaryTests("a", "null") should be(ValBoolean(false))
+ evaluateUnaryTests("null", 1) should returnResult(false)
+ evaluateUnaryTests("null", true) should returnResult(false)
+ evaluateUnaryTests("null", "a") should returnResult(false)
- evalUnaryTests(null, "null") should be(ValBoolean(true))
+ evaluateUnaryTests("null", inputValue = null) should returnResult(true)
}
"A function" should "be invoked with ? (input value)" in {
- evalUnaryTests("foo", """ starts with(?, "f") """) should be(ValBoolean(true))
- evalUnaryTests("foo", """ starts with(?, "b") """) should be(ValBoolean(false))
+ evaluateUnaryTests(""" starts with(?, "f") """, "foo") should returnResult(true)
+ evaluateUnaryTests(""" starts with(?, "b") """, "foo") should returnResult(false)
}
it should "be invoked as endpoint" in {
- evalUnaryTests(2, "< max(1,2,3)") should be(ValBoolean(true))
- evalUnaryTests(2, "< min(1,2,3)") should be(ValBoolean(false))
+ evaluateUnaryTests("< max(1,2,3)", 2) should returnResult(true)
+ evaluateUnaryTests("< min(1,2,3)", 2) should returnResult(false)
}
- "An expression" should "be compared with equals" in {
+ "A unary-tests expression" should "return true if it evaluates to a value that is equal to the implicit value" in {
- evalUnaryTests(2, """number("2")""") should be(ValBoolean(true))
+ evaluateUnaryTests("5", 5) should returnResult(true)
+ evaluateUnaryTests("2 + 3", 5) should returnResult(true)
+ evaluateUnaryTests("x", 5, Map("x" -> 5)) should returnResult(true)
}
- it should "be compared with a boolean" in {
+ it should "return false if it evaluates to a value that is not equal to the implicit value" in {
- evalUnaryTests(false, """(5 < 4)""") should be(ValBoolean(true))
- evalUnaryTests(true, """(5 < 4)""") should be(ValBoolean(false))
+ evaluateUnaryTests("3", 5) should returnResult(false)
+ evaluateUnaryTests("1 + 2", 5) should returnResult(false)
+ evaluateUnaryTests("x", 5, Map("x" -> 3)) should returnResult(false)
}
- it should "be compared to literal" in {
+ it should "return false if it evaluates to a value that has a different type than the implicit value" in {
- evalUnaryTests(date("2019-08-12"), """ date(now) """, Map("now" -> "2019-08-12")) should be(
- ValBoolean(true)
- )
+ evaluateUnaryTests(""" @"2024-08-19" """, 5) should returnResult(false)
+ }
+
+ it should "return true if it evaluates to a list that contains the implicit value" in {
- evalUnaryTests(date("2019-08-12"), """ date(now) """, Map("now" -> "2019-08-13")) should be(
- ValBoolean(false)
+ evaluateUnaryTests("[4,5,6]", 5) should returnResult(true)
+ evaluateUnaryTests("concatenate([1,2,3], [4,5,6])", 5) should returnResult(true)
+ evaluateUnaryTests("x", 5, Map("x" -> List(4, 5, 6))) should returnResult(true)
+ }
+
+ it should "return false if it evaluates to a list that doesn't contain the implicit value" in {
+
+ evaluateUnaryTests("[1,2,3]", 5) should returnResult(false)
+ evaluateUnaryTests("concatenate([1,2], [3])", 5) should returnResult(false)
+ evaluateUnaryTests("x", 5, Map("x" -> List(1, 2, 3))) should returnResult(false)
+ }
+
+ it should "return true if it evaluates to true when the implicit value is applied to it" in {
+
+ evaluateUnaryTests("< 10", 5) should returnResult(true)
+ evaluateUnaryTests("[1..10]", 5) should returnResult(true)
+ evaluateUnaryTests("> x", 5, Map("x" -> 3)) should returnResult(true)
+ }
+
+ it should "return false if it evaluates to false when the implicit value is applied to it" in {
+
+ evaluateUnaryTests("< 3", 5) should returnResult(false)
+ evaluateUnaryTests("[1..3]", 5) should returnResult(false)
+ evaluateUnaryTests("> x", 5, Map("x" -> 10)) should returnResult(false)
+ }
+
+ it should "fail if the implicit value has a different type" in {
+
+ evaluateUnaryTests(""" < @"2024-08-19" """, 5) should failWith(
+ "Can't compare '5' with '2024-08-19'"
)
}
- it should "be compared with a list value" in {
+ it should "return true if it evaluates to true when the implicit value is assigned to the special variable '?'" in {
+
+ evaluateUnaryTests("odd(?)", 5) should returnResult(true)
+ evaluateUnaryTests("abs(?) < 10", 5) should returnResult(true)
+ evaluateUnaryTests("? > x", 5, Map("x" -> 3)) should returnResult(true)
+ }
+
+ it should "return false if it evaluates to false when the implicit value is assigned to the special variable '?'" in {
+
+ evaluateUnaryTests("even(?)", 5) should returnResult(false)
+ evaluateUnaryTests("abs(?) < 3", 5) should returnResult(false)
+ evaluateUnaryTests("? > x", 5, Map("x" -> 10)) should returnResult(false)
+ }
+
+ it should "return false if it evaluates to a value that is not a boolean when the implicit value is assigned to the special variable '?'" in {
+
+ evaluateUnaryTests("abs(?)", 5) should returnResult(false)
+ evaluateUnaryTests("?", 5) should returnResult(false)
+ evaluateUnaryTests("? + not_existing", 5) should returnResult(false)
+ }
+
+ it should "return true if it evaluates to null and the implicit value is null" in {
+
+ evaluateUnaryTests("null", inputValue = null) should returnResult(true)
+ evaluateUnaryTests("2 + not_existing", inputValue = null) should returnResult(true)
+ }
+
+ it should "return false if it evaluates to null and the implicit value is not null" in {
- evalUnaryTests(2, """[1,2,3]""") should be(ValBoolean(true))
- evalUnaryTests(4, """[1,2,3]""") should be(ValBoolean(false))
+ evaluateUnaryTests("null", 5) should returnResult(false)
+ evaluateUnaryTests("2 + not_existing", 5) should returnResult(false)
}
- it should "be compared with a list variable" in {
- evalUnaryTests(2, "x", Map("x" -> List(1, 2, 3))) should be(ValBoolean(true))
- evalUnaryTests(4, "x", Map("x" -> List(1, 2, 3))) should be(ValBoolean(false))
+ it should "return true if it evaluates to true when null is assigned to the special variable '?'" in {
+
+ evaluateUnaryTests("? = null", inputValue = null) should returnResult(true)
+ evaluateUnaryTests("odd(?) or ? = null", inputValue = null) should returnResult(true)
+ }
+
+ it should "return false if it evaluates to false when null is assigned to the special variable '?'" in {
+
+ evaluateUnaryTests("? != null", inputValue = null) should returnResult(false)
+ evaluateUnaryTests("odd(?) and ? != null", inputValue = null) should returnResult(false)
+ }
+
+ it should "return false if it evaluates to null when null is assigned to the special variable '?'" in {
+
+ evaluateUnaryTests("? < 10", inputValue = null) should returnResult(false)
+ evaluateUnaryTests("odd(?)", inputValue = null) should returnResult(false)
+ evaluateUnaryTests("5 < ? and ? < 10", inputValue = null) should returnResult(false)
+ evaluateUnaryTests("5 < ? or ? < 10", inputValue = null) should returnResult(false)
}
}