Una vez más, no muy seguro de si esto es exactamente lo que está buscando, pero desde el punto de partida de querer crear una especie de árbol de expresión usando sintaxis de C#, yo he llegado con ...
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();
}
}
Ahora, obviamente, ninguna de estas clases hace nada (lo que probablemente quiere un método Compile()
allí, entre otros) y no todos los operadores se implementan, y es obvio que puede acortar los nombres de las clases para que sea más conciso, etc. ... pero te permite hacer cosas como:
var result = 100 * new NumericReferenceExpression("Test") + 50;
Después de lo cual, result
habrá:
NumericAddExpression
- LHS = NumericMultiplyExpression
- LHS = NumericConstantExpression(100)
- RHS = NumericReferenceExpression(Test)
- RHS = NumericConstantExpression(50)
No es del todo perfecto - si utiliza las conversiones implícitas de valores numéricos a NumericConstantExpression
(en vez de forma explícita a presión/construirlas), a continuación, en función de la ordenación de las sus términos, algunos de los cálculos pueden ser realizados por los operadores integrados, y solo obtendrá el número resultante (¡podría simplemente llamar a esto una "optimización en tiempo de compilación"!)
Para mostrar lo que quiero decir, si se va a ejecutar en su lugar esto:
var result = 25 * 4 * new NumericReferenceExpression("Test") + 50;
en este caso, el 25 * 4
se evalúa mediante operadores enteros incorporadas, por lo que el resultado es realmente idéntica a la anterior , en lugar de construir un NumericMultiplyExpression
adicional con dos NumericConstantExpression
s (25 y 4) en el LHS y el RHS.
Estas expresiones se pueden imprimir utilizando ToString()
y evaluados, si se proporciona una tabla de símbolos (en este caso un 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));
Resultados en:
Pretty printed: 100 * Ref(Test) + 50
Evaluated: 3050
Esperamos que a pesar de la desventaja (s) mencionado, esto es algo parecido a lo que buscabas (¡o acabo de malgastar la última media hora!)
slighlty actualizado; Esperaré un poco de comentarios antes de expandir en direcciones innecesarias – sehe
Gracias por tomarse el tiempo para escribir una publicación tan extensa. Creo que su solución va en la dirección correcta, pero algunos puntos clave no están claros o faltan: 1) Quiero evaluar símbolos (su 'EvalAddress()') más tarde, cuando evalúo toda la función. Esto no debería ser difícil, simplemente pasando el 'Estado' a la función. 2) Quiero conservar el árbol para poder recorrerlo con un visitante del árbol de expresiones e imprimir mi propia representación de cadenas de cada una de las clases personalizadas. Pruebe esto, por ejemplo: '10 + ref1 + 20 + ref2', ¿cómo funcionaría? – Virtlink
@Virtlink: Respuestas rápidas 1) de hecho, siempre tendrá el estado del programa durante la etapa de emisión del código de operación; Opté por pasarlo (KISS) pero podría usar 'singleton' y/o IoC. 2) El código ya lo hace (solo compilando los árboles para evaluarlos). Simplemente manténgase con los árboles 'Expression <>' el mayor tiempo posible. En la impresión: mi código ya lo hace (ver ToString). Si desea realizar transformaciones más avanzadas/flexibles (optimización, anotación, etc.) debe ir a un patrón de visitante (use Visitor + Builder para realizar transformaciones de AST a AST, por ejemplo) – sehe