SimpleScript (3) El Parser, Expresiones y Comandos

Published on Author lopez

Anterior Post

Veamos hoy parte del Parser, que es un módulo separado. Comienza con una declaración sencilla:

'use strict';

var lexer;

if (typeof lexer == 'undefined')
    lexer = require('./lexer');

var parser = (function () {
    var TokenType = lexer.TokenType;
    var binoperators = [ "+", "-", "*", "/", "==", "!=", "<", ">", "<=", ">=" ];

Usa a lexer, y lo requiere. Luego hay definidos expresiones y comandos. Por ejemplo, esta es la expresión para un simple nombre (por ejemplo, foo):

function NameExpression(name) {
    this.isLeftValue = true;

    this.compile = function () {
        return name;
    };

    this.getName = function () {
        return name;
    }

    this.collectContext = function (context) {
        context.declare(name);
    }
}

Veremos en el próximo post cómo se detecta y construye esta expresión. Una expresión cualquiera tiene que implementar por lo menos dos métodos: compile, que devuelve la expresión compilada a un string de código JavaScript, y collectContext, que permite descubrir las variables declaradas en un programa. En este caso, la NameExpression declara a su nombre (por ejemplo, “foo”), como una variable declarada en el programa (esto es necesario para tener implementado el “hoisting” como en JavaScript).

Veamos una IndexedExpression: compuesta de una expresión y otra expresión para el índice (representaría expresiones como foo[42+1]):

function IndexedExpression(expr, indexpr) {
    this.isLeftValue = true;

    this.compile = function () {
        return expr.compile() + '[' + indexpr.compile() + ']';
    };

    this.collectContext = function (context) {
        expr.collectContext(context);
    }
}

Vean como ahora el collectContext se toma el trabajo de visitar la expresión interna (no vi que fuera necesario visitar y descubrir variables en la expresión de índice, pero puede que lo agregue; esa expresión en general es simple, pero bien podría tener usada una variable).

Pero también hay comandos. Puedo poner como ejemplo el IfCommand:

function IfCommand(cond, thencmd, elsecmd) {
    this.compile = function () {
        var code = 'if (' + cond.compile() + ') { ' + thencmd.compile() + ' }';
        if (elsecmd)
            code += ' else { ' + elsecmd.compile() + ' }';
        return code;
    };

    this.collectContext = function (context) {
        cond.collectContext(context);
        thencmd.collectContext(context);
        if (elsecmd)
            elsecmd.collectContext(context);
    }
}

La distinción entre comandos y expresiones es por ahora puramente formal. Vean que este comando tiene que implementar cómo se compila a JavaScript un if, dado la cond (expresión de la condición), el thencmd (el comando de la rama then), y el elsecmd (el comando de la rama else, que es opcional). Y el collectContext deriva a esas subpartes.

Como siempre, desarrollo paso a paso, usando el flujo de trabajo de TDD (Test-Driven Development). Ejemplo parcial:

exports['Compile string without quotes inside'] = function (test) {
    test.equal(compileExpression("'foo'", test), "'foo'");
    test.equal(compileExpression('"foo"', test), "'foo'");
}

exports['Compile name'] = function (test) {
    test.equal(compileExpression("foo", test), "foo");
}

exports['Qualified name'] = function (test) {
    test.equal(compileExpression("foo.bar", test), "foo.bar");
}

exports['Indexed term'] = function (test) {
    test.equal(compileExpression("foo[bar]", test), "foo[bar]");
}

Ya saben 😉 Sin TDD no hay paraíso!

Próximos temas: cómo se reconocen las expresiones y comandos.

Nos leemos!

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