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