XPress: Simple Truth Expression Language
Background
How many times you needed a quick, simple method to create expressions in a Json
or XML
element value. and you wished there were an easy wy to create dynamic expressions at compile time as a literal string
to be compiled and evaluated at runtime using the same powerful CLR
. Well, I have!
The need for string expressions that can be simply expressed at compile time by engineers or non-engineering business or support team, has been a recurring theme through out my career. Whether its is business rules, business teams needs to modify it and change it frequently at runtime. or operation rules that devops team would like to control at runtime without the need to make a new deployment.
Goals
So, I rolled up my sleeves and created XPress
. It came handy in many situations and i used it in different product and solution. I had a few goals in mind when I designed XPress
:
- Simple, yet powerful.
- Contained as possible, as little dependencies as possible.
- few to zero configurations.
- Purely written in C# and .net.
- Fast, and efficient.
- Generates an optimized cacheable .net expressions .
- Support all truth operators (
Binary
,Unary
,Relational
, andConditional
). - Support basic data types
int32
,string
,boolean
,float
. - Support variable binding at runtime.
- Ability to check on
null
-ability.
How it works?
PM> Install-Package XPress
XPress
is simple, all you have to do after installing XPress package using nuget
manager, is instantiating XpressCompiler
, author XPress
expressions, compile it, and execute it. here is a simple XPress
sample code:
XpressCompiler _compiler = XpressCompiler.Default;
// Create and Compile Expression
var expression = "x gt y*2";
var compilationResult = _compiler.Compile(code);
// Create XpressRuntimeContext object with variable names and values
XpressRuntimeContext runtimeCtx = new XpressRuntimeContext() { { "x", "10" }, { "y", "9" } };
// Call compiled Func with runtime context
var result = compilationResult.Code(runtimeCtx);
As you can see, XPress
language doesn’t need much. very little configuration to start and you’re good to go. If you notice I used XpressCompiler.Default
a singleton compiler instance can be used quickly to compile expressions. But a compiler instance can be always instantiated through constructor.
XpressCompiler _compiler = new XpressCompiler();
The compiler class offers a single method Compile(string)
taking an xpress expression and return a compilation result. The XpressCompilationResult
object offers a boolean flag Compiled
to indicate the success or failure of the compilation process, and a Log
object of type ILogReader
which can be used to get status on compilation process and debug the result. a particularly useful pattern after compilation process is to check the HasErrors
fla, if true GetErrorMessages()
and print it or log it.
if (compilationResult.Log.HasErrors)
{
var errors = compilationResult.Log.GetErrorMessages();
foreach(var error in errors)
{
Console.WriteLine(error);
}
}
Finally, the compilation result object, will have a Code
predicate of type Func<XpressRuntimeContext, bool>
that is the optimized compiled .net expression tree for your string expression. it accepts XpressRuntimeContext
which is simply a strongly typed dictionary of <string,string>
to bind expression’s variables to runtime values. use the XpressRuntimeContext
object to pass values to your variables. Code
predicate jsut like any other .net Func
can be cached and executed multiple time. the result of execution is a boolean
.
Language Features
Variables
Variables literals can be used anywhere within an expression
The variable name follows same C# variable literals naming constraints. It Must start with a letter
, or _
, and it may contain Unicode letter characters, decimal digit characters, Unicode connecting characters, Unicode combining characters, or Unicode formatting characters.
var e = "y eq x+2*2-4/2";
Operands
XPress supports boolean literals of true
and false
values, numerical operands of int32
and float
decimal values, and string
operands. notice how the operand values are defined in the next sample code:
var e1 = "true";
var e2 = "false";
var e3 = "9 gt 10";
var e4 = " 'Ahmed' eq 'ahmed'";
a null
operands can also be expressed to compare a string with nothing
var e = " 'Ahmed' ne null";
Binary Expressions
+
, -
, *
, /
, %
is supported on numerical operands
var e = "x eq (1+2)";
If +
or -
operators applied to a string
operands it will act as String.concat()
var e = "x eq ('ahmed '+'elmalt')";
Note: Binary operators can’t be applied to mixed types of operands.
Unary Expressions
At the moment only unary logical operators are supported. not
can onyl be applied to boolean
operands or expressions.
// boolean operands
var e1 = "not false";
// boolean variables, the value of x must be set to a boolean value at runtime or runtime error
var e2 = "not x";
// boolean expression
var e3 = "not ('a' eq 'b' and 9 gt 10)";
Notice i applied not
to variable x
in the previous example. which is acceptable by XPress
compiler. it will assume that value of x
will be boolean at runtime otherwise it will throw a runtime exception.
Relational Expressions
The following relational expressions are supported:
eq
Equalne
Not Equalgt
Greater Thanlt
Less Thange
Greater Than or Equalle
Less Than or Equal
var e = "1 eq 4";
Conditional Expressions
The real power of XPress
is composition, you can compose powerful expressions using conditional logical and
and or
or
Logical ORand
Logical AND
var e = "false or x lt 2";
Operator Precedence
- Parentheses:
()
- Unary Logial Operators:
not
- Binary Multiplicative Operators:
*
,/
,%
- Binary Additive Operators:
+
,-
- Equality Operators:
eq
,ne
- Logical Operators:
gt
,lt
,ge
,le
-
Conditional Operators:
and
,or
XPress
is very powerful and can be used to compose sophisticated boolean expressions as strings.
var e = "(x ne null and x+1 gt 10) or (y ne null and (y*(5+1)-2) lt 5)";
What about performance?
XPress
is built to be fast and optimized to generated very efficient .net lambda expressions. the most expensive part of the process as you can expect is the compilation of expressions, but this phase needs to occur once, and the result can be cached for the lifetime of the application. on average Xpress
compilation phase takes about ~120ms.
Find more information about XPress
on project github repository. I will be blogging more in the future about the internals implementation of the language and its compiler.
XPress is distributed under MIT license. What are you waiting, give it a try and let me know what you think!
Happy Coding!
Leave a Comment