Sigo en estos días mejorando mi compilador de SimpleScript a JavaScript. Hoy quería comentar por encima la existencia e implementación de un Lexer. Ver el repo en
http://github.com/ajlopez/SimpleScript
El Lexer está ahora separado en un archivo lib/lexer.js, que establece un módulo, consumible desde Node.js y desde el browser cuando alguna vez lo necesite. Comienza definiendo los tipos de token que va a ir entregando:
var lexer = (function () { var TokenType = { Name: 1, Integer: 2, Real: 3, String: 4, NewLine: 5, Separator: 6, Assignment: 7 };
Luego define algunos operadores, separadores y lo que sería un Token, con dos elementos: tipo y valor.
var separators = ".,()[]"; var assignments = ["=", "+=", "-=", "*=", "/="]; var operators = ["+", "-", "*", "/", "==", "!=", "<", ">", "<=", ">="]; function Token(value, type) { this.value = value; this.type = type; }
Y luego el gran trabajo lo realiza una “clase” interna Lexer, cuyo principal método es nextToken():
function Lexer(text) { var length = text ? text.length : 0; var position = 0; var next = []; this.nextToken = function () { if (next.length > 0) return next.pop(); skipSpaces(); var ch = nextChar(); if (ch === null) return null; if (ch === '"' || ch === "'") return nextString(ch); if (ch === '\n') return new Token(ch, TokenType.NewLine); if (ch === '\r') { var ch2 = nextChar(); if (ch2 === '\n') return new Token(ch + ch2, TokenType.NewLine); if (ch2) pushChar(ch2); return new Token(ch, TokenType.NewLine); } if (isAssignment(ch)) return new Token(ch, TokenType.Assignment); if (isOperator(ch)) return nextOperator(ch); if (isSeparator(ch)) return new Token(ch, TokenType.Separator); if (isFirstCharOfName(ch)) return nextName(ch); if (isDigit(ch)) return nextInteger(ch); }
Finalmente el módulo expone hacia afuera una factoría de lexers, y la enumeración de los tipos de token:
return { lexer: function (text) { return new Lexer(text); }, TokenType: TokenType }
Finalmente, esto no estaría completo sin notar que hay un test/lexer.js que fue armado de pasos, junto con lib/lexer.js, siguiendo Test-Driven Development (con algunos refactors, hubo un tiempo que lib/lexer.js no estaba como archivo separado), fragmento:
function getToken(text, value, type, test) { var lexer = sslexer.lexer(text); var token = lexer.nextToken(); test.ok(token); test.equal(token.value, value); test.equal(token.type, type); test.equal(lexer.nextToken(), null); }; exports['Get names'] = function (test) { getToken('foo', 'foo', TokenType.Name, test); getToken('foo123', 'foo123', TokenType.Name, test); getToken('foo_123', 'foo_123', TokenType.Name, test); getToken('_foo', '_foo', TokenType.Name, test); } exports['Get integer'] = function (test) { getToken('123', '123', TokenType.Integer, test); getToken('1234567890', '1234567890', TokenType.Integer, test); }
Recuerden: sin TDD no hay paraíso 😉
Próximos temas: el parser, comandos, expresiones, compilación a JavaScript.
Nos leemos!
Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez