I am learning to use expression trees/expressions in C#. I have gradually built up a parser, with which I can take a string in "calculator" syntax (like "2 * 3 + 14 * 4 / 7 - 5 * 5") and build and evaluate an abstract syntax tree (AST). It even calculates the correct answer! :-) The AST consists of Expression
nodes: arithmethical binary nodes (Add, Subtract, Multiply, Divide) and unary Constant nodes representing the integer values.
Next step: I want to add parameters to the expression to be parsed, like "2 * 3 + myVar1 * 4 / 7 - 5 * myVar2", and supply the actual values for the parameters at runtime (after the AST has been compiled). I can easily add the ParameterExpressions to the tree - but I cannot find out how to correctly compile my tree and supply the values.
The parser is built using Coco/R and an attributed Bachus-Naur grammar, and looks like this:
using System.Linq.Expressions;
using Ex = System.Linq.Expressions.Expression;
using System;
namespace AtgKalk
{
public class Parser
{
public const int _EOF = 0;
public const int _identifikator = 1;
public const int _tall = 2;
public const int _pluss = 3;
public const int _minus = 4;
public const int _ganger = 5;
public const int _deler = 6;
public const int maxT = 7;
const bool T = true;
const bool x = false;
const int minErrDist = 2;
public Scanner scanner;
public Errors errors;
public Token t; // last recognized token
public Token la; // lookahead token
int errDist = minErrDist;
public Parser(Scanner scanner)
{
this.scanner = scanner;
errors = new Errors();
}
void SynErr(int n)
{
if (errDist >= minErrDist) errors.SynErr(la.line, la.col, n);
errDist = 0;
}
void Get()
{
for (; ; )
{
t = la;
la = scanner.Scan();
if (la.kind <= maxT) { ++errDist; break; }
la = t;
}
}
void Expect(int n)
{
if (la.kind == n) Get(); else { SynErr(n); }
}
void Calculator()
{
Ex n;
CalcExpr(out n);
Console.Write($"AST: {n} = ");
Console.WriteLine(Ex.Lambda<Func<int>>(n).Compile()());
// The above works fine as long as there are no parameter names in the input string
}
void CalcExpr(out Ex n1)
{
Ex n2; Func<Ex, Ex, Ex> f;
Term(out n1);
while (la.kind == 3 || la.kind == 4)
{
AddOp(out f);
Term(out n2);
n1 = f(n1, n2);
}
}
void Term(out Ex n1)
{
n1 = null; Ex n2; Func<Ex, Ex, Ex> f = null;
Fact(out n1);
while (la.kind == 5 || la.kind == 6)
{
MulOp(out f);
Fact(out n2);
n1 = f(n1, n2);
}
}
void AddOp(out Func<Ex, Ex, Ex> f)
{
f = null;
if (la.kind == 3)
{
Get();
f = (l, r) => Ex.Add(l, r);
}
else if (la.kind == 4)
{
Get();
f = (l, r) => Ex.Subtract(l, r);
}
else SynErr(8);
}
void Fact(out Ex n)
{
n = null;
if (la.kind == 2)
{
Number(out n);
}
else if (la.kind == 1)
{
Parameter(out n);
}
else SynErr(9);
}
void MulOp(out Func<Ex, Ex, Ex> f)
{
f = null;
if (la.kind == 5)
{
Get();
f = (l, r) => Ex.Multiply(l, r);
}
else if (la.kind == 6)
{
Get();
f = (l, r) => Ex.Divide(l, r);
}
else SynErr(10);
}
void Number(out Ex n)
{
Expect(2);
n = Ex.Constant(int.Parse(t.val), typeof(int));
}
void Parameter(out Ex n)
{
Expect(1);
n = Ex.Parameter(typeof(int), t.val);
}
public void Parse()
{
la = new Token();
la.val = "";
Get();
Calculator();
Expect(0);
}
static readonly bool[,] set = {
{T,x,x,x, x,x,x,x, x}
};
} // end Parser
public class Errors
{
public int count = 0; // number of errors detected
public System.IO.TextWriter errorStream = Console.Out; // error messages go to this stream
public string errMsgFormat = "-- line {0} col {1}: {2}"; // 0=line, 1=column, 2=text
public virtual void SynErr(int line, int col, int n)
{
string s;
switch (n)
{
case 0: s = "EOF expected"; break;
case 1: s = "identifikator expected"; break;
case 2: s = "tall expected"; break;
case 3: s = "pluss expected"; break;
case 4: s = "minus expected"; break;
case 5: s = "ganger expected"; break;
case 6: s = "deler expected"; break;
case 7: s = "??? expected"; break;
case 8: s = "invalid AddOp"; break;
case 9: s = "invalid Fakt"; break;
case 10: s = "invalid MulOp"; break;
default: s = "error " + n; break;
}
errorStream.WriteLine(errMsgFormat, line, col, s);
count++;
}
} // Errors
public class FatalError : Exception
{
public FatalError(string m) : base(m) { }
}
}
My problem lies in line 63, I think:
Console.WriteLine(Ex.Lambda<Func<int>>(n).Compile()());
Invocation:
Scanner scanner = new Scanner(args[0]); // if args[0] contains the input string :-)
Parser parser = new Parser(scanner);
parser.Parse();
I have now solved my problem. Thanks to kaby76 for valuable tips leading me in the right direction. The example now can handle an arbitrary number of parameters (probably max 16, since this is the maximum number of input arguments for
Func<...>
)The solution to the problem war threefold:
Lambda
Lambda
, letting it infer typesDelegate
The problematic statement then looks like this, for an expression with two parameters: