Aug 21, 2014 at 12:23 AM
Edited Aug 21, 2014 at 2:19 AM
The goal of this library is to provide runtime compilation of C# code. The feature you are suggesting isn't part of the C# specification so I don't think it should be part of the library itself. It is important that this library stay as close to C# specification
as possible so that anyone can build on top of it easily.
That said, it is an interesting use case and I believe it can be implemented on top of this library rather easily.
I think the key thing here is to separate the atom name from the expression itself. The expressions should be an actual assignment to a scope variable, and then the atom name should just correlate to the left hand part of the expression. When a string expression
is compiled, a LINQ Expression tree is created that encapsulates the expression as a code structure, an abstract syntax tree. We can analyze this tree as a dependency tree. For each node in the tree we would have to execute the associated assignment expression
so that the corresponding scope variable is updated.
In your example:
Expression: "A = 10"
LINQ Expression Tree: $scope.A = 10
We would then add this to a dictionary whose key is the atom and whose value is an aggregate of the string expression, the LINQ tree and the compiled function.
Expression: "B = A+10"
LINQ Expression Tree: $scope.B = $scope.A + 10
Expression: "C = A+B"
LINQ Expression Tree: $scope.C = $scope.A + $scope.B
Now your extended expression parser intercepts the expression, for example, "C", looks it up in the dictionary, recompiles it if it has changed, then takes the LINQ expression and traverses the right hand part. If it encounters a scope variable (PropertyExpression
with $scope as the parent), it will look up that property name in the dictionary, and repeat until it reaches the leaves. It executes the compiled function at each node, ensuring that the corresponding assignment expression is executed so that the scope variable
So in your example, setting A = 20 will flag Atom A as being dirty and requiring recompilation. When "C" is evaluated, it will lookup the dictionary, get the expression, traverse the tree up to the assignment of A and recompile the expression "A
= 20" and then execute it. This will ensure that the scope variable A is updated. Then atom B is evaluated. As the expression hasn't changed, you don't have to recompile (saves time) and just execute the function. Now Scope.B is updated. Finally it will
execute the entire expression of assignment of C, the sum of scope.A and scope.B, and return the value.
The only problem I see is that you should not set values for the scope variables outside your expression parser, i.e. in normal code. The Scope object is simply a container for values. Any values set outside would be overwritten as the engine would execute
the assignment expressions every run. You would also have to check for circular dependencies in your expressions.
I can probably come up with a working proof of concept today.
Does this method work for you?