Escribiendo un intérprete en .NET (Parte 6)

Ahora, en este paso, he agregado: procesamiento de string, expresiones binarias, expresiones aritméticas, reconocimiento en el parser de esas expresiones, usando precedencia y paréntesis.

El código puede bajarse desde InterpreterStep06.zip.

Procesamiento de Strings

El lexer ahora puede procesar strings, delimitados con dobles comillas, y reconoce el caracter de escape. Soporta strings multilineas. Para eso, fui escribiendo tests, al comienzo en rojo, y luego fui implementando la funcionalidad en el código. Algunos tests (hay más en código):

        [TestMethod]
        public void ProcessSimpleString()
        {
            Lexer lexer = new Lexer("\"foo\"");
            Token token = lexer.NextToken();
            Assert.IsNotNull(token);
            Assert.AreEqual(TokenType.String, token.TokenType);
            Assert.AreEqual("foo", token.Value);
            Assert.IsNull(lexer.NextToken());
        }
        [TestMethod]
        [ExpectedException(typeof(LexerException), "Unclosed string")]
        public void RaiseIfStringIsUnclosed()
        {
            Lexer lexer = new Lexer("\"foo");
            Token token = lexer.NextToken();
        }


Expresiones aritméticas y binarias



Agregué una nueva clase abstracta BinaryExpr ession (derivada luego de tener los tests sobre una concreta, BinaryArithmeticExpr ession):





BinaryExpr ession evalua dos expresiones, izquierda y derecha, y aplica la operación abstracta:



    public abstract class BinaryExpr ession : IExpr ession
    {
        IExpr ession leftExpr ession;
        IExpr ession rightExpr ession;
        public BinaryExpr ession(IExpr ession leftExpr ession, IExpr ession rightExpr ession)
        {
            this.leftExpr ession = leftExpr ession;
            this.rightExpr ession = rightExpr ession;
        }
        public IExpr ession LeftExpr ession { get { return this.leftExpr ession; } }
        public IExpr ession RightExpr ession { get { return this.rightExpr ession; } }
        public object Evaluate(BindingEnvironment environment)
        {
            object leftValue = this.leftExpr ession.Evaluate(environment);
            object rightValue = this.rightExpr ession.Evaluate(environment);
            return Apply(leftValue, rightValue);
        }
        public abstract object Apply(object leftValue, object rightValue);
    }


Definí operadores aritméticos, que tengo que extender en el futuro:



    public enum ArithmeticOperator
    {
        Add,
        Subtract,
        Multiply,
        Divide
    }


BinaryArithmeticExpr ession usa Microsoft.VisualBasic.CompilerServices.Operators. Estos métodos ayudantes, que agregué referenciando a la librería de VB.NET que viene con el framework, pueden sumar, restar, multiplicar, dividir dos objetos, detectando si son números:



    public class BinaryArithmeticExpr ession : BinaryExpr ession
    {
        private ArithmeticOperator @operator;
        public ArithmeticOperator Operator { get { return this.@operator; } }
        public BinaryArithmeticExpr ession(IExpr ession leftExpr ession, IExpr ession rightExpr ession, ArithmeticOperator @operator)
            : base(leftExpr ession, rightExpr ession)
        {
            this.@operator = @operator;
        }
        public override object Apply(object leftValue, object rightValue)
        {
            switch (this.@operator)
            {
                case ArithmeticOperator.Add:
                    return Operators.AddObject(leftValue, rightValue);
                case ArithmeticOperator.Subtract:
                    return Operators.SubtractObject(leftValue, rightValue);
                case ArithmeticOperator.Multiply:
                    return Operators.MultiplyObject(leftValue, rightValue);
                case ArithmeticOperator.Divide:
                    return Operators.DivideObject(leftValue, rightValue);
            }
            throw new InvalidOperationException(string.Format("Unknow operator '{0}'", this.@operator));
        }
    }


Algunos tess (más en código):



        [TestMethod]
        public void EvaluateAddExpr ession()
        {
            ConstantExpr ession leftExpr ession = new ConstantExpr ession(1);
            ConstantExpr ession rightExpr ession = new ConstantExpr ession(2);
            BinaryArithmeticExpr ession Expr ession = new BinaryArithmeticExpr ession(leftExpr ession, rightExpr ession, ArithmeticOperator.Add);
            Assert.AreEqual(3, Expr ession.Evaluate(null));
        }
        [TestMethod]
        public void EvaluateSubtractExpr ession()
        {
            ConstantExpr ession leftExpr ession = new ConstantExpr ession(1);
            ConstantExpr ession rightExpr ession = new ConstantExpr ession(2);
            BinaryArithmeticExpr ession Expr ession = new BinaryArithmeticExpr ession(leftExpr ession, rightExpr ession, ArithmeticOperator.Subtract);
            Assert.AreEqual(-1, Expr ession.Evaluate(null));
        }


Mejoras en el Parser



El Parser tiene nuevos métodos privados ParseSimpleExpr ession, ParseFactorExpr ession:





Son las implementaciones que surgieron al satisfacer los tests (más tests en el código):



        [TestMethod]
        public void ParseAndEvaluateAddAndMultiplyExpr ession()
        {
            Parser parser = new Parser("1+2*3");
            IExpr ession Expr ession = parser.ParseExpr ession();
            Assert.IsNotNull(Expr ession);
            Assert.IsInstanceOfType(Expr ession, typeof(BinaryArithmeticExpr ession));
            Assert.AreEqual(7, Expr ession.Evaluate(null));
            Assert.IsNull(parser.ParseExpr ession());
        }
        [TestMethod]
        public void ParseAndEvaluateAddAndMultiplyExpr essionWithParenthesis()
        {
            Parser parser = new Parser("(1+2)*3");
            IExpr ession Expr ession = parser.ParseExpr ession();
            Assert.IsNotNull(Expr ession);
            Assert.IsInstanceOfType(Expr ession, typeof(BinaryArithmeticExpr ession));
            Assert.AreEqual(9, Expr ession.Evaluate(null));
            Assert.IsNull(parser.ParseExpr ession());
        }


De esta manera, puedo reconocer operadores binarios, procesarlos según su precedencia, y usar paréntesis para alterar el orden de evaluación.



Todos los tests en verde:





Buen code coverage:





Próximos pasos



Me gustaría agregar proceso de números reales, definiciones de funciones, más operadores, comandos if, for.



Keep tuned!



Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

This entry was posted in 11699, 1389, 8870. Bookmark the permalink.

Leave a Reply

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


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>