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