Again, not quite sure if this is exactly what you're looking for, but from the starting point of wanting to create some kind of expression tree using C# syntax, I've come up with...
public abstract class BaseExpression
{
// Maybe a Compile() method here?
}
public class NumericExpression : BaseExpression
{
public static NumericExpression operator +(NumericExpression lhs, NumericExpression rhs)
{
return new NumericAddExpression(lhs, rhs);
}
public static NumericExpression operator -(NumericExpression lhs, NumericExpression rhs)
{
return new NumericSubtractExpression(lhs, rhs);
}
public static NumericExpression operator *(NumericExpression lhs, NumericExpression rhs)
{
return new NumericMultiplyExpression(lhs, rhs);
}
public static NumericExpression operator /(NumericExpression lhs, NumericExpression rhs)
{
return new NumericDivideExpression(lhs, rhs);
}
public static implicit operator NumericExpression(int value)
{
return new NumericConstantExpression(value);
}
public abstract int Evaluate(Dictionary<string,int> symbolTable);
public abstract override string ToString();
}
public abstract class NumericBinaryExpression : NumericExpression
{
protected NumericExpression LHS { get; private set; }
protected NumericExpression RHS { get; private set; }
protected NumericBinaryExpression(NumericExpression lhs, NumericExpression rhs)
{
LHS = lhs;
RHS = rhs;
}
public override string ToString()
{
return string.Format("{0} {1} {2}", LHS, Operator, RHS);
}
}
public class NumericAddExpression : NumericBinaryExpression
{
protected override string Operator { get { return "+"; } }
public NumericAddExpression(NumericExpression lhs, NumericExpression rhs)
: base(lhs, rhs)
{
}
public override int Evaluate(Dictionary<string,int> symbolTable)
{
return LHS.Evaluate(symbolTable) + RHS.Evaluate(symbolTable);
}
}
public class NumericSubtractExpression : NumericBinaryExpression
{
protected override string Operator { get { return "-"; } }
public NumericSubtractExpression(NumericExpression lhs, NumericExpression rhs)
: base(lhs, rhs)
{
}
public override int Evaluate(Dictionary<string, int> symbolTable)
{
return LHS.Evaluate(symbolTable) - RHS.Evaluate(symbolTable);
}
}
public class NumericMultiplyExpression : NumericBinaryExpression
{
protected override string Operator { get { return "*"; } }
public NumericMultiplyExpression(NumericExpression lhs, NumericExpression rhs)
: base(lhs, rhs)
{
}
public override int Evaluate(Dictionary<string, int> symbolTable)
{
return LHS.Evaluate(symbolTable) * RHS.Evaluate(symbolTable);
}
}
public class NumericDivideExpression : NumericBinaryExpression
{
protected override string Operator { get { return "/"; } }
public NumericDivideExpression(NumericExpression lhs, NumericExpression rhs)
: base(lhs, rhs)
{
}
public override int Evaluate(Dictionary<string, int> symbolTable)
{
return LHS.Evaluate(symbolTable) / RHS.Evaluate(symbolTable);
}
}
public class NumericReferenceExpression : NumericExpression
{
public string Symbol { get; private set; }
public NumericReferenceExpression(string symbol)
{
Symbol = symbol;
}
public override int Evaluate(Dictionary<string, int> symbolTable)
{
return symbolTable[Symbol];
}
public override string ToString()
{
return string.Format("Ref({0})", Symbol);
}
}
public class StringConstantExpression : BaseExpression
{
public string Value { get; private set; }
public StringConstantExpression(string value)
{
Value = value;
}
public static implicit operator StringConstantExpression(string value)
{
return new StringConstantExpression(value);
}
}
public class NumericConstantExpression : NumericExpression
{
public int Value { get; private set; }
public NumericConstantExpression(int value)
{
Value = value;
}
public override int Evaluate(Dictionary<string, int> symbolTable)
{
return Value;
}
public override string ToString()
{
return Value.ToString();
}
}
Now, obviously none of these classes actually do anything (you'd probably want a Compile() method on there amongst others) and not all the operators are implemented, and you can obviously shorten the class names to make it more concise etc... but it does allow you to do things like:
var result = 100 * new NumericReferenceExpression("Test") + 50;
After which, result will be:
NumericAddExpression
- LHS = NumericMultiplyExpression
- LHS = NumericConstantExpression(100)
- RHS = NumericReferenceExpression(Test)
- RHS = NumericConstantExpression(50)
It's not quite perfect - if you use the implicit conversions of numeric values to NumericConstantExpression (instead of explicitly casting/constructing them), then depending on the ordering of your terms, some of the calculations may be performed by the built-in operators, and you'll only get the resulting number (you could just call this a "compile-time optimization"!)
To show what I mean, if you were to instead run this:
var result = 25 * 4 * new NumericReferenceExpression("Test") + 50;
in this case, the 25 * 4 is evaluated using built-in integer operators, so the result is actually identical to the above, rather than building an additional NumericMultiplyExpression with two NumericConstantExpressions (25 and 4) on the LHS and RHS.
These expressions can be printed using ToString() and evaluated, if you provide a symbol table (here just a simple Dictionary<string, int>):
var result = 100 * new NumericReferenceExpression("Test") + 50;
var symbolTable = new Dictionary<string, int>
{
{ "Test", 30 }
};
Console.WriteLine("Pretty printed: {0}", result);
Console.WriteLine("Evaluated: {0}", result.Evaluate(symbolTable));
Results in:
Pretty printed: 100 * Ref(Test) + 50
Evaluated: 3050
Hopefully despite the drawback(s) mentioned, this is something approaching what you were looking fo (or I've just wasted the last half hour!)