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

En este post, agrego un analizador léxico, un lexer, para procesar texto y separar el código en tokens, las “palabras” de nuestra entrada.

La nueva solución:

Pueden bajar el código de InterpreterStep04.zip. Hice refactor de la versión anterior: ahora la class library Interpreter tiene tres directorios y namespaces: Commands, Expressions, Compiler.

El nuevo código es:

El TokenType es una enumeración:

    public enum TokenType
    {
        Name,
        String,
        Integer,
        Operator,
        Separator
    }


Token representa una “palabra” de nuestro lenguaje:



    public class Token
    {
        public TokenType TokenType { get; private set;  }
        public object Value { get; private set;  }
        public Token(TokenType type, object value)
        {
            this.TokenType = type;
            this.Value = value;
        }
    }


Value contiene el token detectado: su valor entero si era un entero, o su texto, si era un nombre o separador u operador.



Lexer está encargado de detectar el próximo token desde un TextReader o desde un string:



       public Lexer(TextReader reader)
        {
            this.reader = reader;
        }
        public Lexer(string text)
            : this(new StringReader(text))
        {
        }
        public Token NextToken()
        {
            int ch;
            for (ch = this.NextChar(); ch != -1 && char.IsWhiteSpace((char)ch); ch = this.NextChar())
                ;
            if (ch == -1)
                return null;
//...
        }


Lexer fue escrito en “baby-steps” (pasos de bebé), siguiendo la evolución de los test (recuerden, estoy usando TDD). Escribí un tests, y modifiqué el código para que pasara a verde, y así. Tests como:



        [TestMethod]
        public void ProcessNameWithWhitespaces()
        {
            Lexer lexer = new Lexer("  one  ");
            Token token = lexer.NextToken();
            Assert.IsNotNull(token);
            Assert.AreEqual(TokenType.Name, token.TokenType);
            Assert.AreEqual("one", token.Value);
            Assert.IsNull(lexer.NextToken());
        }
        [TestMethod]
        public void ProcessTwoNames()
        {
            Lexer lexer = new Lexer("one two");
            Token token = lexer.NextToken();
            Assert.IsNotNull(token);
            Assert.AreEqual(TokenType.Name, token.TokenType);
            Assert.AreEqual("one", token.Value);
            token = lexer.NextToken();
            Assert.IsNotNull(token);
            Assert.AreEqual(TokenType.Name, token.TokenType);
            Assert.AreEqual("two", token.Value);
            Assert.IsNull(lexer.NextToken());
        }
        [TestMethod]
        public void ProcessNameAndSeparator()
        {
            Lexer lexer = new Lexer("one;");
            Token token = lexer.NextToken();
            Assert.IsNotNull(token);
            Assert.AreEqual(TokenType.Name, token.TokenType);
            Assert.AreEqual("one", token.Value);
            token = lexer.NextToken();
            Assert.IsNotNull(token);
            Assert.AreEqual(TokenType.Separator, token.TokenType);
            Assert.AreEqual(";", token.Value);
            Assert.IsNull(lexer.NextToken());
        }


La versión actual (en este paso) del lexer solo reconoce nombres, algunos separadores como “;” y algún operador como “=”. No procesa strings delimitados, números reales u otros separadores y operadores. Mi idea es ir agregando más capacidades a medida que escriba más tests.



Todos los tests de la solución en verde:





Buen code coverage:





Próximos pasos: escribir un parser que reconozca expresiones, luego que procese comandos, y agregar comandos como if y for, así como definición de funciones. Esto es un intérprete, pero en una nueve serie de posts, podría tratar de compilarlo a código .NET nativo, usando Reflection.Emit or CodeDom.



Nos leemos!



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>