expression_language

pub package

Dart library for parsing and evaluating expressions.

Main goal

Main goal of this library is to be able to parse and evaluate expressions like:

4 * 2.5 + 8.5 + 1.5 / 3.0
3* @control1.numberProperty1 < (length(@control2.stringProperty1 + "test string") - 42)
(!@control1.boolProperty1 && length(@control2.stringProperty1) == 21) ? "string1" : "string2"

Features

Currently there are multiple supported data types and operations.

Data types

  • String -> maps directly to the Dart String
  • bool -> maps directly to the Dart bool
  • Integer -> wrapper around the Dart int
  • Decimal -> Custom type
  • DateTime -> maps directly to the Dart DateTime
  • Duration -> maps directly to the Dart Duration

All the data types above are non-nullable. Since veresion 1.0 we also support nullable types that are mapped to the Dart nullable types. To cast away nullability you can use postfix exclamation mark operator ! in an expression.

Note: To be able to easily work with financial data and not to lose precision we decided to use Decimal data type taken from dart-decimal instead of double. To keep our expression definitions strongly typed and to have a common way to work with all number data types we introduced base Number data type class which is simmilar to Dart num class. Since we can't modify definition of the Dart int we have also introduced Integer data type which is a simple wrapper around the int and which also extends Number. There is a conversion expression from Integer to int and from Decimal to double so higher layers can hide those data types as an implementation detail. To learn more about DateTime data type in expressions see this merge request.

Operations

There are most of the standard operations working on the data types above. For example you can use most of the arithmetic operators like +,-, *, / , ~/, % or the logical operators like &&, ||, !, <, >, <=, >=, ==.

To be able to reference another expression from the expression itself we use a construct @element.propertyName. The element can map to any type extending ExpressionProviderElement. Both element and propertyName must consist only from alpha numeric characters or an underscore and can't start with a number.

There are also special functions like length which returns length of the string. Each function paramter can also come from an expression. Here is the complete list:

FunctionDescriptionSample
bool contains(String value, String searchValue)Returns true if value constains searchValuecontains("abcd", "bc")
String toString<T>(<T> value)Returns .toString of the valuetoString(5)
int durationInDays(Duration value)Returns duration in days of a given duration valuedurationInDays(duration("P5D1H"))
int durationInHours(Duration value)Returns duration in hours of a given duration valuedurationInHours(duration("P5D1H"))
int durationInMinutes(Duration value)Returns duration in minutes of a given duration valuedurationInMinutes(duration("P5D1H"))
int durationInSeconds(Duration value)Returns duration in seconds of a given duration valuedurationInSeconds(duration("P5D1H"))
bool startsWith(String value, String searchValue)Returns true if value starts with searchValuestartsWith("Hello", "He")
bool endsWith(String value, String searchValue)Returns true if value ends with searchValuestartsWith("Hello", "lo")
bool isEmpty(String value)Returns true if value is empty StringisEmpty("")
bool isNull(String value)Returns true if value is nullisNull(someNullExpression)
bool isNullOrEmpty(String value)Returns true if value is null or empty StringisNullOrEmpty("")
bool matches(String value, String regex)Returns true if value fully matches regex expressionmatches("test@email.com","^a-zA-Z0-9_.+-+@a-zA-Z0-9-+.a-zA-Z0-9-+$")
int length(String value)length of the stringlength("Hi")
int length(String value)length of the stringlength("Hi")
int count<T>(List<T> value)length of the stringcount(@element.array)
DateTime dateTime(String value)Try to parse value into DateTime, throws InvalidParameterException if it failsdateTime("1978-03-20 00:00:00.000")
DateTime now()Returns DateTime.now()now()
DateTime nowInUtc()Returns DateTime.now().toUtc()nowInUtc()
Duration diffDateTime(DateTime left, DateTime right)Returns difference between two dates - value is always positivediff(dateTime("1978-03-20"), dateTime("1976-03-20"))
Duration duration(String value)Returns duration from Iso8601 String, thows InvalidParameterException if it failsduration("P5D1H")
num round(num value, int precision, int roundingMode)Rounds the value with given precision and rounding mode as an int (described below)round(1.5, 2, 0)
num round(num value, int precision, String roundingMode)Rounds the value with given precision and rounding mode as a String (described below)round(13.5, 0, "nearestEven")

Table of the roundingModes used in the round function: | Name | Integer representation | Description | --- | --- | --- | | nearestEven | 0 | Rounds to the nearest value; if the number falls midway, it is rounded to the nearest value with an even least significant digit | nearestOdd | 1 | Rounds to the nearest value; if the number falls midway, it is rounded to the nearest value with an odd least significant digit | nearestFromZero| 2 | Rounds to the nearest value; if the number falls midway, it is rounded to the value which is the farthest from zero | nearestToZero | 3 | Rounds to the nearest value; if the number falls midway, it is rounded to the value which is the closest to zero | nearestDownward |4 | Rounds to the nearest value; if the number falls midway, it rounds down | nearestUpward | 5 | Rounds to the nearest value; if the number falls midway, it rounds down | towardsZero | 6 | Directed rounding towards zero | fromZero | 7 | Directed rounding from zero | up | 8 | Directed rounding towards positive infinity | down | 9 | Directed rounding towards negative infinity |

Usage

//Create expression parser and pass a map of the types extending ExpressionProviderElement which can hold other expressions.
var expressionGrammarDefinition =
    ExpressionGrammarParser({"element": TestFormElement()});
var parser = expressionGrammarDefinition.build();

//Parse the expression.
var result = parser
    .parse("(1 + @element.value < 3*5) && false || (2 + 3*(4 + 21)) >= 15");

//The expression now contains strongly typed expression tree representing the expression above.
var expression = result.value as Expression<bool>;

//Evaluate the expression.
bool value = expression.evaluate();

Writing custom expressions

You can write your custom expressions simmilar to the ones in the list above. First you need to extend the Expression<T> class, where T is the return value of the Expression. Here is the example of String concatenation expression (in case you don't want to use already implemented + operator):

import 'package:expression_language/expression_language.dart';

class StringConcatenationExpression extends Expression<String> {
  final Expression<String> left;
  final Expression<String> right;

  StringConcatenationExpression(this.left, this.right);

  @override
  String evaluate() {
    return left.evaluate() + right.evaluate();
  }

  @override
  Expression<String> clone(Map<String, ExpressionProviderElement> elementMap) {
    return StringConcatenationExpression(left.clone(elementMap), right.clone(elementMap));
  }

  @override
  List<Expression> getChildren() {
    return [left, right];
  }
}

Now you just need to tell the parser how to create this expression. To do this you need to pass subclass of FunctionExpressionFactory<T> to the ExpressionGrammarParser constructor - there is a customFunctionExpressionFactories parameter which takes List<FunctionExpressionFactory>. In case of a simple expressions you can use already existing subclass ExplicitFunctionExpressionFactory<T> which takes all the parameters in the constructor so you can avoid subclassing.

This is the registration code for StringConcatenationExpression:

var expressionGrammarDefinition =
    ExpressionGrammarParser({}, 
        customFunctionExpressionFactories: [
            ExplicitFunctionExpressionFactory(
                name: 'concat',
                createFunctionExpression: (parameters) =>
                    StringConcatenationExpression(parameters[0], parameters[1]),
                parametersLength: 2),
        ],
    );

Libraries

expression_language