Escribiendo un Intérprete in .NET (Part 8)

Published on Author lopez1 Comment

Vuelvo a escribir sobre el tema: programar un intérprete, usando C# y algunas ideas de TDD. En este paso, agrego un nuevo ICommand, el WhileCommand:

Pueden bajarse la versión de este paso desde InterpreterStep08.zip. Si quieren ver todos los pasos, sigo manteniendo el códicon en trunk/Interpreter en mi Google Code Project AjCodeKatas.

El código de WhileCommand es simple:

    public class WhileCommand : ICommand
    {
        private IExpression condition;
        private ICommand command;
        public WhileCommand(IExpression condition, ICommand command)
        {
            this.condition = condition;
            this.command = command;
        }
        public IExpression Condition { get { return this.condition; } }
        public ICommand Command { get { return this.command; } }
        public void Execute(BindingEnvironment environment)
        {
            while (BooleanPredicates.IsTrue(this.condition.Evaluate(environment)))
                this.command.Execute(environment);
        }
    }

Noten que tengo ahora un nuevo BooleanPredicates. Nació de haber refctorizado el código anterior que usé en la implementación de IfCommand. Escribí en el anterior post sobre mis intenciones de hacer ese refactor. Usando TDD, escribí los tests de la nueva clase, ejemplo:

        [TestMethod]
        public void IsFalse()
        {
            Assert.IsTrue(BooleanPredicates.IsFalse(null));
            Assert.IsTrue(BooleanPredicates.IsFalse(string.Empty));
            Assert.IsTrue(BooleanPredicates.IsFalse(0));
            Assert.IsTrue(BooleanPredicates.IsFalse((short)0));
            Assert.IsTrue(BooleanPredicates.IsFalse((long)0));
            Assert.IsTrue(BooleanPredicates.IsFalse(0.0));
            Assert.IsTrue(BooleanPredicates.IsFalse((float)0.0));
            Assert.IsFalse(BooleanPredicates.IsFalse(new object()));
            Assert.IsFalse(BooleanPredicates.IsFalse("foo"));
            Assert.IsFalse(BooleanPredicates.IsFalse(1));
            Assert.IsFalse(BooleanPredicates.IsFalse((short)2));
            Assert.IsFalse(BooleanPredicates.IsFalse((long)3));
            Assert.IsFalse(BooleanPredicates.IsFalse(4.0));
            Assert.IsFalse(BooleanPredicates.IsFalse((float)5.0));
        }

Vean: el test es una especie de especificación escrita en código, que contesta: ¿Qué es lo que considero como false en mi intérprete? Luego de tener el test en verde, pude agregar su llamada en el IfCommand (que YA estaba escrito) y ver de correr los tests de ese comando, para ver que todo siguiera funcionando.

El nuevo comando WhileCommand nació de haberlo testeado, ejemplo:

        [TestMethod]
        public void EvaluateWhileCommandUsingDecrement()
        {
            BindingEnvironment environment = new BindingEnvironment();
            environment.SetValue("a", 2);
            IExpression condition = new VariableExpression("a");
            ICommand command = new SetCommand("a", new BinaryArithmeticExpression(new VariableExpression("a"), new ConstantExpression(1), ArithmeticOperator.Subtract));
            WhileCommand wcommand = new WhileCommand(condition, command);
            Assert.AreEqual(command, wcommand.Command);
            Assert.AreEqual(condition, wcommand.Condition);
            wcommand.Execute(environment);
            Assert.AreEqual(0, environment.GetValue("a"));
        }

Una vez que estuvo el comando listo, agregué el soporte de reconocer y procesar un while en el Parser:

        private ICommand ParseWhileCommand()
        {
            IExpression condition;
            ICommand cmd;
            this.ParseToken(TokenType.Separator, "(");
            condition = this.ParseExpression();
            this.ParseToken(TokenType.Separator, ")");
            cmd = this.ParseCommand();
            return new WhileCommand(condition, cmd);
        }

Claro, tiene que responder a tests 😉 :

        [TestMethod]
        public void ParseAndEvaluateSimpleWhileCommand()
        {
            Parser parser = new Parser("while (a) a = a-1;");
            ICommand command = parser.ParseCommand();
            Assert.IsNotNull(command);
            Assert.IsInstanceOfType(command, typeof(WhileCommand));
            BindingEnvironment environment = new BindingEnvironment();
            environment.SetValue("a", 2);
            command.Execute(environment);
            Assert.AreEqual(0, environment.GetValue("a"));
        }

Podría haber agregado más tests, como qué pasa si al WhileCommand le entrego una condición null, pero, por ahora, el nuevo comando goza de buena salud. Todos los tess siguen en verde:

Buen code coverage:

Próximos pasos: comandos composite, comandos for/foreach, declaración de funciones…

Nos leemos!

Angel “Java” Lopez

http://www.ajlopez.com

http://twitter.com/ajlopez

Leave a Reply

Your email address will not be published. Required fields are marked *