
YantraJS is JavaScript built on top of Brand new Expression Compiler which is based on System.Linq.Expressions (referred further as SLE) but with different implementation.
var a = YExpression.Parameter(typeof(int));
var b = YExpression.Parameter(typeof(int));
var exp = YExpression.Lambda<Func<int,int,int>>("add",
YExpression.Binary(a, YOperator.Add, b),
new YParameterExpression[] { a, b });
var fx = exp.Compile();
Assert.AreEqual(1, fx(1, 0));
Assert.AreEqual(3, fx(1, 2));
Methods
There are five different methods to generate and execute the code. They are all available in .NET Standard so they are even available for .NET Core.
Compile
This method will compile expression to IL in memory, it will not analyze nested lambdas, this is by design to generate IL faster. For nested lambda, please use CompileWithNestedLambdas method. It will return DynamicMethod delegate.
CompileWithNestedLambdas
When you expression tree contains nested lambda with closures, use this method to generate the IL. It will return DynamicMethod delegate.
CompileInAssembly
This is similar to CompileToMethod, this method generate an empty assembly and it will return generated method from MethodBuilder. This method will compile nested lambdas correctly.
CompileToStaticMethod
This is same as CompileToMethod of Linq. This method will compile nested lambdas correctly.
CompileToInstanceMethod
You can compile lambda with this parameter to an instance method. This method will compile nested lambdas correctly.
Examples
Read Array
var args = YExpression.Parameters(typeof(int[]), typeof(int));
var exp = YExpression.Lambda<Func<int[],int,int>>("load", YExpression.ArrayIndex(
args[0],
args[1]
), args);
var fx = exp.Compile();
var n = new int[]{1,2,3 };
Assert.AreEqual(1, fx(n, 0));
Nested Closures - Curry
var a = YExpression.Parameters(typeof(int));
var b = YExpression.Parameters(typeof(int));
var c = YExpression.Parameters(typeof(int));
var lamba = YExpression.Lambda<Func<int, Func<int, Func<int, int>>>>("c",
YExpression.Lambda<Func<int, Func<int, int>>>("b",
YExpression.Lambda<Func<int, int>>("inner",
a[0] + b[0] + c[0],
c
), b
),
a
);
var fx1 = lamba.CompileInAssembly();
var fx2 = fx1(1);
var fx3 = fx2(2);
var r = fx3(3);
Assert.AreEqual(6, r);
Recursive Fibonacci series
var a = YExpression.Parameters(typeof(int));
var fibs = YExpression.Parameters(typeof(Func<int,int>));
var fib = fibs[0];
var fibp = YExpression.Parameters(typeof(int));
var p1 = fibp[0];
var f = YExpression.Lambda<Func<Func<int,int>>>("fib_outer",
YExpression.Block(
fibs.AsSequence(),
YExpression.Assign(fib, YExpression.Lambda<Func<int, int>>("fib",
YExpression.Block(
YExpression.Conditional(
p1 <= 1,
YExpression.Constant(0),
YExpression.Conditional(
YExpression.Equal(p1, YExpression.Constant(2)),
YExpression.Constant(1),
YExpression.Invoke(fib, p1 - 1)
+
YExpression.Invoke(fib, p1 - 2)
))
)
,
fibp) )
)
, a); ;
var outer = f.CompileInAssembly();
var fx = outer();
Assert.AreEqual(3, fx(5));
Expression Visitors
YantraJS Expression Compiler comes with YExpressionVisitor<T>
and YExpressionMapVisitor
. YExpressionVisitor<T>
is very basic, you may never need it unless you are translating expressions to something else. YExpressionMapVisitor
provides visitor which analyzes entire tree and it will replace child nodes as required. Each node and children will be compared to see if any node is modified and it will modify entire tree. All expressions are immutable.
Closure Rewriter
SLE generates new types and fields for closures. In YantraJS Expression Compiler we are generating Box<T>
inside array field of the root class Closure
. Again we chose this approach to reduce compile time as AssemblyBuiler
becomes slower for larger code base. We are still working constantly to improve the compilation speed.
No Validation
API is similar to that of SLE, but implementation is faster as it does not perform any checks whether the expression is correct or not. There are no null checks either. Since we needed to speed up the code generation, we had to skip various checks.
So in order to use the library, you must validate your expressions with SLE and then you can easily replace it with Yantra Expressions.
Following validations are not performed
- Label must be within correct block, SLE checks if jumps are possible in blocks. Jumps out of try/catch are not permitted etc.
- Return/Jump not permitted in finally block.
- Argument types mismatch, SLE checks each method's input output type with expressions.
There may be more validations, and we do not plan to add them.
Reason being, we are using this to generate JavaScript IL code, however all these validations are already performed and translated correctly by JavaScript parser and compiler. And there are thousands of unit test of JavaScript which tells us that Expression Compiler is generating code correctly.
Different Node Types
In SLE, ConstantExpression
is a single expression holding type object
of any type. This is a problem while generating code as everything a full list of type check occurs to generate the code. Where else in YantraJS we have classes like YInt32Constant
, StringConstant
etc, which can avoid all type checks and generate code faster. For native value types (int etc), there is no boxing for ConstantExpression
leading to faster execution.
YArrayIndexExpression
, YIndexExpression
(for indexed property) and YPropertyExpression
, YFieldExpression
are various extra node types which simplifies the code generation.
Assign and Binary expressions are different expressions, again to simplify code generation.
Sequence of T
We also realized that List<T>
causes too many re allocations while expression tree is being built, for smaller expressions it does not matter but for extremely large tree, building tree becomes exponentially slower as each node is moved from smaller array to larger array. To solve this issue, we replaced List<T>
with Sequence<T>
, which is fast for sequential access and appending items. In Expressions, most List<T>
are only built once and only read once. In this case, smaller linked arrays inside Sequence
reduces GC overhead and memory usage.
![]() | ![]() | ![]() | ![]() |
Like | Comment | Save | Share |