SimpleScript (2) El Lexer

Published on Author lopez

Anterior Post
Siguiente Post

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