Hace un tiempo que escribo de este proyecto, que trata de implementar Erlang como intérprete sobre C#. Veamos hoy una primera aproximación a expresiones. Una expresión es algo que se evalúa dentro de un contexto. Por ejemplo, la variable X, es una expresión que se evalúa dentro de un contexto que diga cuánto vale la variable X, a qué valor está ligada, o que diga que X todavía es una variable sin ligar.
Todas expresiones se escribieron usando el flujo de trabajo de TDD (Test-Driven Development). Quedó, con el tiempo, definida esta interfaz, que al final implementan todas las expresiones:
public interface IExpression { object Evaluate(Context context, bool withvars = false); bool HasVariable(); }
El gran método es el evaluar, que puede admitir o no que queden variables sin ligar. También hay un método hasVariable() que indica si una expresión tiene o no una variable. Veamos la expresión más simple, una constante:
public class ConstantExpression : IExpression { private object value; public ConstantExpression(object value) { this.value = value; } public object Value { get { return this.value; } } public object Evaluate(Context context, bool withvars = false) { return this.value; } public bool HasVariable() { return false; } }
El método Evaluate no usa el Context provisto, sino que simplemente devuelve el objeto constante que se proveyó en el constructor al armar esta expresión (todavía no lo vimos, pero las expresiones se agrupan en un árbol de expresiones, que se evalúa recorriéndo sus ramas y hojas).
Una implementación menos trivial es la expresión que evalúa una variable, dado su nombre:
public class VariableExpression : IExpression { private Variable variable; public VariableExpression(Variable variable) { this.variable = variable; } public Variable Variable { get { return this.variable; } } public object Evaluate(Context context, bool withvars = false) { if (!context.HasValue(this.variable.Name)) if (!withvars) throw new Exception(string.Format("variable '{0}' is unbound", this.variable.Name)); else return this.variable; return context.GetValue(this.variable.Name); } public bool HasVariable() { return true; } }
Esta vez se usa el contexto. Si la variable no está todavía ligada a un valor, pueden pasar dos cosas: que el Evaluate acepte que alguna variable quede sin ligar, y se devuelve como resultado de la evaluación la variable, o que se dispare una excepción. Vamos a ver que hay ocasiones donde Erlang espera y necesita que una expresión esté completamente evaluada, en el sentido de no contener variables sin ligar.
Les comentaba que todo este código fue implementado siguiendo el flujo de trabajo de TDD. Listo algunos tests abajo para dar constancia. Para expresión constante:
[TestMethod] public void CreateSimpleConstantExpression() { ConstantExpression expr = new ConstantExpression(10); Assert.AreEqual(10, expr.Value); } [TestMethod] public void EvaluateSimpleConstantExpression() { ConstantExpression expr = new ConstantExpression(10); Assert.AreEqual(10, expr.Evaluate(null)); }
Para expresión de variable:
[TestMethod] public void CreateSimpleVariableExpression() { Variable variable = new Variable("X"); VariableExpression expression = new VariableExpression(variable); Assert.AreEqual(variable, expression.Variable); } [TestMethod] public void EvaluateVariableExpression() { Variable variable = new Variable("X"); Context context = new Context(); context.SetValue("X", 1); VariableExpression expression = new VariableExpression(variable); Assert.AreEqual(1, expression.Evaluate(context)); } [TestMethod] public void EvaluateUndefinedVariableExpression() { Variable variable = new Variable("X"); Context context = new Context(); VariableExpression expression = new VariableExpression(variable); Assert.AreEqual(variable, expression.Evaluate(context, true)); } [TestMethod] public void RaiseIfEvaluateUndefinedVariableExpression() { Variable variable = new Variable("X"); Context context = new Context(); VariableExpression expression = new VariableExpression(variable); try { expression.Evaluate(context, false); Assert.Fail(); } catch (Exception ex) { Assert.AreEqual("variable 'X' is unbound", ex.Message); } }
Próximos posts: explorar otras implementaciones de expresiones, implementaciones de estructuras básicas del lenguaje, como variables y mapas, etc…
Nos leemos!
Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez