Fast Expressions is an expression parser and evaluation library.
Version: 0.1.7
Fast Expressions is an expression parser and evaluation library.
High performance when parsing parser expressions is achieved by using a very fast parser.
Parsed expressions are wrapped into function calls.
High performance when evaluating expressions is achieved by not using any classes and using expression function execution directly.
import 'dart:math';
import 'package:fast_expressions/fast_expressions.dart';
void main(List<String> args) {
{
const e = '1.isEven ? "Yes, 1 is even" : "No, 1 is odd"';
final r = parseExpression(
e,
resolve: _resolve,
);
print(r());
}
{
const e = '1 + 2 * 3';
final r = parseExpression(e);
print(r());
}
{
const e = '1 + 2 * x';
final r = parseExpression(
e,
context: {
'x': 3,
},
);
print(r());
}
{
const e = '1 + 2 * x[y]';
final r = parseExpression(
e,
context: {
'x': [1, 2, 3],
'y': 2,
},
);
print(r());
}
{
const e = '1 + 2 * add(1, 2)';
final r = parseExpression(
e,
context: {
'add': (num x, num y) => x + y,
},
);
print(r());
}
{
const e = '1 + 2 * sub(x: 7, y: 4)';
final r = parseExpression(
e,
context: {
'sub': ({required num x, required num y}) => x - y,
},
);
print(r());
}
{
const e = '1 + 2 * foo.add(1, 2)';
final r = parseExpression(
e,
context: {
'foo': Foo(),
},
resolve: _resolve,
);
print(r());
}
{
const e = '1 + 2 * foo.list()[foo.add(1, 1)]';
final r = parseExpression(
e,
context: {
'foo': Foo(),
},
resolve: _resolve,
);
print(r());
}
{
const e = '''
"Hello, " + friends[random()].name
''';
final friends = [
Person('Jack'),
Person('Jerry'),
Person('John'),
];
final r = parseExpression(
e,
context: {
'friends': friends,
'random': () => Random().nextInt(friends.length - 1),
},
resolve: _resolve,
);
print(r());
}
}
dynamic _resolve(dynamic object, String member) {
Never error() {
throw StateError("Invalid member '$member', object is $object");
}
if (object is Foo) {
switch (member) {
case 'add':
return object.add;
case 'list':
return object.list;
}
}
if (object is Person) {
switch (member) {
case 'name':
return object.name;
}
}
if (object is int) {
switch (member) {
case 'isEven':
return object.isEven;
}
}
error();
}
class Foo {
num add(num x, num y) => x + y;
List<num> list() => [1, 2, 3];
}
class Person {
final String name;
Person(this.name);
}
Parser are generated from PEG grammar.
Software used to generate parsers
Below is the source code of the grammar.
%{
typedef Expression = dynamic Function();
String _escape(int charCode) {
switch (charCode) {
case 0x22:
return '"';
case 0x2f:
return '/';
case 0x5c:
return '\\';
case 0x62:
return '\b';
case 0x66:
return '\f';
case 0x6e:
return '\n';
case 0x72:
return '\r';
case 0x74:
return '\t';
default:
throw StateError('Unable to escape charCode: $charCode');
}
}
}%
%%
final Map<String, dynamic> context;
final dynamic Function(dynamic object, String member)? resolve;
ExpressionParser({
this.context = const {},
this.resolve,
});
Expression _binary(Expression? left, ({String op, Expression expr}) next) {
final op = next.op;
final right = next.expr;
final l = left!;
switch (op) {
case '+':
return () => l() + right();
case '-':
return () => l() - right();
case '/':
return () => l() / right();
case '*':
return () => l() * right();
case '%':
return () => l() % right();
case '~/':
return () => l() ~/ right();
case '<<':
return () => l() << right();
case '>>':
return () => l() >> right();
case '>>>':
return () => l() >>> right();
case '&':
return () => l() & right();
case '^':
return () => l() ^ right();
case '|':
return () => l() | right();
case '>':
return () => l() > right();
case '>=':
return () => l() >= right();
case '<':
return () => l() < right();
case '<=':
return () => l() <= right();
case '||':
return () => l() as bool || right() as bool;
case '&&':
return () => l() as bool && right() as bool;
case '??':
return () => l() ?? right();
case '==':
return () => l() == right();
case '!=':
return () => l() != right();
default:
throw StateError('Unknown operator: $op');
}
}
Expression _prefix(String? operator, Expression operand) {
if (operator == null) {
return operand;
}
switch (operator) {
case '-':
return () => -operand();
case '!':
return () => !(operand() as bool);
case '~':
return () => ~operand();
default:
throw StateError('Unknown operator: $operator');
}
}
Expression _postfix(
Expression object, List<({String kind, dynamic arguments})> selectors) {
for (final selector in selectors) {
final kind = selector.kind;
final arguments = selector.arguments;
switch (kind) {
case '.':
final member = arguments as String;
final object2 = object;
object = () {
final object3 = object2();
if (resolve case final resolve?) {
return resolve(object3, member);
} else {
throw StateError(
"Unable to resolve member '$member' for $object3");
}
};
break;
case '[':
final index = arguments as dynamic Function();
final object2 = object;
object = () {
final object3 = object2();
final index2 = index();
return object3[index2];
};
break;
case '(':
final object2 = object;
final arguments2 =
arguments as List<({String name, Expression expr})>;
object = () {
final object3 = object2() as Function;
final positionalArguments = <dynamic>[];
final namedArguments = <Symbol, dynamic>{};
for (final element in arguments2) {
final name = element.name;
final expr = element.expr;
if (name.isEmpty) {
positionalArguments.add(expr());
} else {
if (namedArguments.containsKey(name)) {
throw StateError('Duplicate named argument: $name');
}
final key = Symbol(name);
namedArguments[key] = expr();
}
}
return Function.apply(object3, positionalArguments, namedArguments);
};
break;
default:
throw StateError('Unknown selector: $kind');
}
}
return object;
}
%%
Start = Spaces v:Expression Eof ;
Expression = Conditional ;
Expression
Conditional =
e1:IfNull Question e2:Expression Colon e3:Expression { $$ = () => e1() as bool ? e2() : e3(); }
/ IfNull ;
Expression
IfNull = h:LogicalOr t:(op:IfNullOp ↑ expr:LogicalOr)* { $$ = t.isEmpty ? h : t.fold(h, _binary); } ;
IfNullOp = v:'??' Spaces ;
Expression
LogicalOr = h:LogicalAnd t:(op:LogicalOrOp ↑ expr:LogicalAnd)* { $$ = t.isEmpty ? h : t.fold(h, _binary); } ;
LogicalOrOp = v:'||' Spaces ;
Expression
LogicalAnd = h:Equality t:(op:LogicalAndOp ↑ expr:Equality)* { $$ = t.isEmpty ? h : t.fold(h, _binary); } ;
LogicalAndOp = v:'&&' Spaces ;
Expression
Equality = h:Relational t:(op:EqualityOp ↑ expr:Relational)* { $$ = t.isEmpty ? h : t.fold(h, _binary); } ;
EqualityOp = v:('==' / '!=') Spaces ;
Expression
Relational = h:BitwiseOr t:(op:RelationalOp ↑ expr:BitwiseOr)* { $$ = t.isEmpty ? h : t.fold(h, _binary); } ;
RelationalOp = v:('>=' / '>' / '<=' / '<') Spaces ;
Expression
BitwiseOr = h:BitwiseXor t:(op:BitwiseOrOp ↑ expr:BitwiseXor)* { $$ = t.isEmpty ? h : t.fold(h, _binary); } ;
BitwiseOrOp = !'||' v:'|' Spaces ;
Expression
BitwiseXor = h:BitwiseAnd t:(op:BitwiseXorOp ↑ expr:BitwiseAnd)* { $$ = t.isEmpty ? h : t.fold(h, _binary); } ;
BitwiseXorOp = v:'^' Spaces ;
Expression
BitwiseAnd = h:Shift t:(op:BitwiseAndOp ↑ expr:Shift)* { $$ = t.isEmpty ? h : t.fold(h, _binary); } ;
BitwiseAndOp = !'&&' v:'&' Spaces ;
Expression
Shift = h:Additive t:(op:ShiftOp ↑ expr:Additive)* { $$ = t.isEmpty ? h : t.fold(h, _binary); } ;
ShiftOp = v:('<<' / '>>>' / '>>') Spaces ;
Expression
Additive = h:Multiplicative t:(op:AdditiveOp ↑ expr:Multiplicative)* { $$ = t.isEmpty ? h : t.fold(h, _binary); } ;
AdditiveOp = v:('-' / '+') Spaces ;
Expression
Multiplicative = h:UnaryPrefix t:(op:MultiplicativeOp ↑ expr:UnaryPrefix)* { $$ = t.isEmpty ? h : t.fold(h, _binary); } ;
MultiplicativeOp = v:('/' / '*' / '%' / '~/') Spaces ;
UnaryPrefix = @expected('expression', UnaryPrefix_) ;
@inline
Expression
UnaryPrefix_ = op:UnaryPrefixOp? expr:UnaryPostfix { $$ = _prefix(op, expr); } ;
UnaryPrefixOp = v:('-' / '!' / '~') Spaces ;
Expression
UnaryPostfix = object:Primary selectors:Selector* { $$ = _postfix(object, selectors); } ;
({String kind, dynamic arguments})
Selector =
kind:Dot arguments:Identifier_
/ kind:OpenBracket arguments:Expression CloseBracket
/ kind:OpenParenthesis arguments:Arguments CloseParenthesis ;
Arguments = @list(NamedArgument / PositionalArgument, Comma ↑ v:(NamedArgument / PositionalArgument)) ;
NamedArgument = name:Identifier_ Colon expr:Expression ;
PositionalArgument = name:'' expr:Expression ;
Primary =
Number
/ Boolean
/ String
/ Null
/ Identifier
/ OpenParenthesis v:Expression CloseParenthesis ;
Expression
Null = 'null' Spaces { $$ = () => null; } ;
Expression
Boolean =
'true' Spaces { $$ = () => true; }
/ 'false' Spaces { $$ = () => false; } ;
Expression
Number = v:$(
[-]?
([0] / [1-9][0-9]*)
([.] [0-9]+)?
([eE] ↑ [-+]? [0-9]+)?
) Spaces {
final n = num.parse(v);
$$ = () => n;
} ;
@inline
String
EscapeChar = c:["/bfnrt\\] { $$ = _escape(c); } ;
@inline
String
EscapeHex = 'u' v:HexNumber { $$ = String.fromCharCode(v); } ;
HexNumber = @indicate('Expected 4 digit hex number', HexNumber_) ;
@inline
int
HexNumber_ = v:$([0-9A-Za-z]{4}) { $$ = int.parse(v, radix: 16); } ;
String
StringChars =
$[\u{20}-\u{21}\u{23}-\u{5b}\u{5d}-\u{10ffff}]+
/ '\\' v:(EscapeChar / EscapeHex) ;
String
StringRaw = '"' v:StringChars* DoubleQuote { $$ = v.join(); } ;
Expression
String = v:StringRaw { $$ = () => v; } ;
@inline
String
Identifier_ = v:@expected('identifier', $([a-zA-Z_$] [a-zA-Z_$0-9]*)) Spaces ;
Expression
Identifier = v:Identifier_ {
$$ = () {
if (!context.containsKey(v)) {
throw StateError("Variable '$v' not found");
}
return context[v];
};
} ;
Eof = !. ;
CloseBracket = v:']' Spaces ;
CloseParenthesis = v:')' Spaces ;
Colon = v:':' Spaces ;
Comma = v:',' Spaces ;
Dot = v:'.' Spaces ;
DoubleQuote = v:'"' Spaces ;
OpenBracket = v:'[' Spaces ;
OpenParenthesis = v:'(' Spaces ;
Question = v:'?' Spaces ;
Spaces = [ \n\r\t]* ;