/* -*- Mode: JS; tab-width: 4; indent-tabs-mode: nil; -*- * vim: set sw=4 ts=4 et tw=78: * ***** BEGIN LICENSE BLOCK ***** * * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the Narcissus JavaScript engine. * * The Initial Developer of the Original Code is * Brendan Eich . * Portions created by the Initial Developer are Copyright (C) 2004 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* * Narcissus - JS implemented in JS. * * Parser. */ /* * The vanilla AST builder. */ function VanillaBuilder() { } VanillaBuilder.prototype = { IF$build: function(t) { return new Node(t, IF); }, IF$setCondition: function(n, e) { n.condition = e; }, IF$setThenPart: function(n, s) { n.thenPart = s; }, IF$setElsePart: function(n, s) { n.elsePart = s; }, IF$finish: function(n) { }, SWITCH$build: function(t) { var n = new Node(t, SWITCH); n.cases = []; n.defaultIndex = -1; return n; }, SWITCH$setDiscriminant: function(n, e) { n.discriminant = e; }, SWITCH$setDefaultIndex: function(n, i) { n.defaultIndex = i; }, SWITCH$addCase: function(n, n2) { n.cases.push(n2); }, SWITCH$finish: function(n) { }, CASE$build: function(t) { return new Node(t, CASE); }, CASE$setLabel: function(n, e) { n.caseLabel = e; }, CASE$initializeStatements: function(n, t) { n.statements = new Node(t, BLOCK); }, CASE$addStatement: function(n, s) { n.statements.push(s); }, CASE$finish: function(n) { }, DEFAULT$build: function(t, p) { return new Node(t, DEFAULT); }, DEFAULT$initializeStatements: function(n, t) { n.statements = new Node(t, BLOCK); }, DEFAULT$addStatement: function(n, s) { n.statements.push(s); }, DEFAULT$finish: function(n) { }, FOR$build: function(t) { var n = new Node(t, FOR); n.isLoop = true; n.isEach = false; return n; }, FOR$rebuildForEach: function(n) { n.isEach = true; }, // NB. This function is called after rebuildForEach, if that's called // at all. FOR$rebuildForIn: function(n) { n.type = FOR_IN; }, FOR$setCondition: function(n, e) { n.condition = e; }, FOR$setSetup: function(n, e) { n.setup = e || null; }, FOR$setUpdate: function(n, e) { n.update = e; }, FOR$setObject: function(n, e) { n.object = e; }, FOR$setIterator: function(n, e, e2) { n.iterator = e; n.varDecl = e2; }, FOR$setBody: function(n, s) { n.body = s; }, FOR$finish: function(n) { }, WHILE$build: function(t) { var n = new Node(t, WHILE); n.isLoop = true; return n; }, WHILE$setCondition: function(n, e) { n.condition = e; }, WHILE$setBody: function(n, s) { n.body = s; }, WHILE$finish: function(n) { }, DO$build: function(t) { var n = new Node(t, DO); n.isLoop = true; return n; }, DO$setCondition: function(n, e) { n.condition = e; }, DO$setBody: function(n, s) { n.body = s; }, DO$finish: function(n) { }, BREAK$build: function(t) { return new Node(t, BREAK); }, BREAK$setLabel: function(n, v) { n.label = v; }, BREAK$setTarget: function(n, n2) { n.target = n2; }, BREAK$finish: function(n) { }, CONTINUE$build: function(t) { return new Node(t, CONTINUE); }, CONTINUE$setLabel: function(n, v) { n.label = v; }, CONTINUE$setTarget: function(n, n2) { n.target = n2; }, CONTINUE$finish: function(n) { }, TRY$build: function(t) { var n = new Node(t, TRY); n.catchClauses = []; return n; }, TRY$setTryBlock: function(n, s) { n.tryBlock = s; }, TRY$addCatch: function(n, n2) { n.catchClauses.push(n2); }, TRY$finishCatches: function(n) { }, TRY$setFinallyBlock: function(n, s) { n.finallyBlock = s; }, TRY$finish: function(n) { }, CATCH$build: function(t) { var n = new Node(t, CATCH); n.guard = null; return n; }, CATCH$setVarName: function(n, v) { n.varName = v; }, CATCH$setGuard: function(n, e) { n.guard = e; }, CATCH$setBlock: function(n, s) { n.block = s; }, CATCH$finish: function(n) { }, THROW$build: function(t) { return new Node(t, THROW); }, THROW$setException: function(n, e) { n.exception = e; }, THROW$finish: function(n) { }, RETURN$build: function(t) { return new Node(t, RETURN); }, RETURN$setValue: function(n, e) { n.value = e; }, RETURN$finish: function(n) { }, YIELD$build: function(t) { return new Node(t, YIELD); }, YIELD$setValue: function(n, e) { n.value = e; }, YIELD$finish: function(n) { }, GENERATOR$build: function(t) { return new Node(t, GENERATOR); }, GENERATOR$setExpression: function(n, e) { n.expression = e; }, GENERATOR$setTail: function(n, n2) { n.tail = n2; }, GENERATOR$finish: function(n) { }, WITH$build: function(t) { return new Node(t, WITH); }, WITH$setObject: function(n, e) { n.object = e; }, WITH$setBody: function(n, s) { n.body = s; }, WITH$finish: function(n) { }, DEBUGGER$build: function(t) { return new Node(t, DEBUGGER); }, SEMICOLON$build: function(t) { return new Node(t, SEMICOLON); }, SEMICOLON$setExpression: function(n, e) { n.expression = e; }, SEMICOLON$finish: function(n) { }, LABEL$build: function(t) { return new Node(t, LABEL); }, LABEL$setLabel: function(n, e) { n.label = e; }, LABEL$setStatement: function(n, s) { n.statement = s; }, LABEL$finish: function(n) { }, FUNCTION$build: function(t) { var n = new Node(t); if (n.type != FUNCTION) n.type = (n.value == "get") ? GETTER : SETTER; n.params = []; return n; }, FUNCTION$setName: function(n, v) { n.name = v; }, FUNCTION$addParam: function(n, v) { n.params.push(v); }, FUNCTION$setBody: function(n, s) { n.body = s; }, FUNCTION$hoistVars: function(x) { }, FUNCTION$finish: function(n, x) { }, VAR$build: function(t) { return new Node(t, VAR); }, VAR$addDecl: function(n, n2, x) { n.push(n2); }, VAR$finish: function(n) { }, CONST$build: function(t) { return new Node(t, VAR); }, CONST$addDecl: function(n, n2, x) { n.push(n2); }, CONST$finish: function(n) { }, LET$build: function(t) { return new Node(t, LET); }, LET$addDecl: function(n, n2, x) { n.push(n2); }, LET$finish: function(n) { }, DECL$build: function(t) { return new Node(t, IDENTIFIER); }, DECL$setName: function(n, v) { n.name = v; }, DECL$setInitializer: function(n, e) { n.initializer = e; }, DECL$setReadOnly: function(n, b) { n.readOnly = b; }, DECL$finish: function(n) { }, LET_BLOCK$build: function(t) { var n = Node(t, LET_BLOCK); n.varDecls = []; return n; }, LET_BLOCK$setVariables: function(n, n2) { n.variables = n2; }, LET_BLOCK$setExpression: function(n, e) { n.expression = e; }, LET_BLOCK$setBlock: function(n, s) { n.block = s; }, LET_BLOCK$finish: function(n) { }, BLOCK$build: function(t, id) { var n = new Node(t, BLOCK); n.varDecls = []; n.id = id; return n; }, BLOCK$hoistLets: function(n) { }, BLOCK$addStatement: function(n, n2) { n.push(n2); }, BLOCK$finish: function(n) { }, EXPRESSION$build: function(t, tt) { return new Node(t, tt); }, EXPRESSION$addOperand: function(n, n2) { n.push(n2); }, EXPRESSION$finish: function(n) { }, ASSIGN$build: function(t) { return new Node(t, ASSIGN); }, ASSIGN$addOperand: function(n, n2) { n.push(n2); }, ASSIGN$setAssignOp: function(n, o) { n.assignOp = o; }, ASSIGN$finish: function(n) { }, HOOK$build: function(t) { return new Node(t, HOOK); }, HOOK$setCondition: function(n, e) { n[0] = e; }, HOOK$setThenPart: function(n, n2) { n[1] = n2; }, HOOK$setElsePart: function(n, n2) { n[2] = n2; }, HOOK$finish: function(n) { }, OR$build: function(t) { return new Node(t, OR); }, OR$addOperand: function(n, n2) { n.push(n2); }, OR$finish: function(n) { }, AND$build: function(t) { return new Node(t, AND); }, AND$addOperand: function(n, n2) { n.push(n2); }, AND$finish: function(n) { }, BITWISE_OR$build: function(t) { return new Node(t, BITWISE_OR); }, BITWISE_OR$addOperand: function(n, n2) { n.push(n2); }, BITWISE_OR$finish: function(n) { }, BITWISE_XOR$build: function(t) { return new Node(t, BITWISE_XOR); }, BITWISE_XOR$addOperand: function(n, n2) { n.push(n2); }, BITWISE_XOR$finish: function(n) { }, BITWISE_AND$build: function(t) { return new Node(t, BITWISE_AND); }, BITWISE_AND$addOperand: function(n, n2) { n.push(n2); }, BITWISE_AND$finish: function(n) { }, EQUALITY$build: function(t) { // NB t.token.type must be EQ, NE, STRICT_EQ, or STRICT_NE. return new Node(t); }, EQUALITY$addOperand: function(n, n2) { n.push(n2); }, EQUALITY$finish: function(n) { }, RELATIONAL$build: function(t) { // NB t.token.type must be LT, LE, GE, or GT. return new Node(t); }, RELATIONAL$addOperand: function(n, n2) { n.push(n2); }, RELATIONAL$finish: function(n) { }, SHIFT$build: function(t) { // NB t.token.type must be LSH, RSH, or URSH. return new Node(t); }, SHIFT$addOperand: function(n, n2) { n.push(n2); }, SHIFT$finish: function(n) { }, ADD$build: function(t) { // NB t.token.type must be PLUS or MINUS. return new Node(t); }, ADD$addOperand: function(n, n2) { n.push(n2); }, ADD$finish: function(n) { }, MULTIPLY$build: function(t) { // NB t.token.type must be MUL, DIV, or MOD. return new Node(t); }, MULTIPLY$addOperand: function(n, n2) { n.push(n2); }, MULTIPLY$finish: function(n) { }, UNARY$build: function(t) { // NB t.token.type must be DELETE, VOID, TYPEOF, NOT, BITWISE_NOT, // UNARY_PLUS, UNARY_MINUS, INCREMENT, or DECREMENT. if (t.token.type == PLUS) t.token.type = UNARY_PLUS; else if (t.token.type == MINUS) t.token.type = UNARY_MINUS; return new Node(t); }, UNARY$addOperand: function(n, n2) { n.push(n2); }, UNARY$setPostfix: function(n) { n.postfix = true; }, UNARY$finish: function(n) { }, MEMBER$build: function(t, tt) { // NB t.token.type must be NEW, DOT, or INDEX. return new Node(t, tt); }, MEMBER$rebuildNewWithArgs: function(n) { n.type = NEW_WITH_ARGS; }, MEMBER$addOperand: function(n, n2) { n.push(n2); }, MEMBER$finish: function(n) { }, PRIMARY$build: function(t, tt) { // NB t.token.type must be NULL, THIS, TRUIE, FALSE, IDENTIFIER, // NUMBER, STRING, or REGEXP. return new Node(t, tt); }, PRIMARY$finish: function(n) { }, ARRAY_INIT$build: function(t) { return new Node(t, ARRAY_INIT); }, ARRAY_INIT$addElement: function(n, n2) { n.push(n2); }, ARRAY_INIT$finish: function(n) { }, ARRAY_COMP: { build: function(t) { return new Node(t, ARRAY_COMP); }, setExpression: function(n, e) { n.expression = e }, setTail: function(n, n2) { n.tail = n2; }, finish: function(n) { } }, COMP_TAIL$build: function(t) { return new Node(t, COMP_TAIL); }, COMP_TAIL$setGuard: function(n, e) { n.guard = e; }, COMP_TAIL$addFor: function(n, n2) { n.push(n2); }, COMP_TAIL$finish: function(n) { }, OBJECT_INIT$build: function(t) { return new Node(t, OBJECT_INIT); }, OBJECT_INIT$addProperty: function(n, n2) { n.push(n2); }, OBJECT_INIT$finish: function(n) { }, PROPERTY_INIT$build: function(t) { return new Node(t, PROPERTY_INIT); }, PROPERTY_INIT$addOperand: function(n, n2) { n.push(n2); }, PROPERTY_INIT$finish: function(n) { }, COMMA$build: function(t) { return new Node(t, COMMA); }, COMMA$addOperand: function(n, n2) { n.push(n2); }, COMMA$finish: function(n) { }, LIST$build: function(t) { return new Node(t, LIST); }, LIST$addOperand: function(n, n2) { n.push(n2); }, LIST$finish: function(n) { }, setHoists: function(id, vds) { } }; function CompilerContext(inFunction, builder) { this.inFunction = inFunction; this.hasEmptyReturn = false; this.hasReturnWithValue = false; this.isGenerator = false; this.blockId = 0; this.builder = builder; this.stmtStack = []; this.funDecls = []; this.varDecls = []; } CompilerContext.prototype = { bracketLevel: 0, curlyLevel: 0, parenLevel: 0, hookLevel: 0, ecma3OnlyMode: false, inForLoopInit: false, }; /* * Script :: (tokenizer, compiler context) -> node * * Parses the toplevel and function bodies. */ function Script(t, x) { var n = Statements(t, x); n.type = SCRIPT; n.funDecls = x.funDecls; n.varDecls = x.varDecls; return n; } // Node extends Array, which we extend slightly with a top-of-stack method. defineProperty(Array.prototype, "top", function() { return this.length && this[this.length-1]; }, false, false, true); /* * Node :: (tokenizer, optional type) -> node */ function Node(t, type) { var token = t.token; if (token) { this.type = type || token.type; this.value = token.value; this.lineno = token.lineno; // Start & end are file positions for error handling. this.start = token.start; this.end = token.end; } else { this.type = type; this.lineno = t.lineno; } // Nodes use a tokenizer for debugging (getSource, filename getter). this.tokenizer = t; for (var i = 2; i < arguments.length; i++) this.push(arguments[i]); } var Np = Node.prototype = new Array; Np.constructor = Node; Np.toSource = Object.prototype.toSource; // Always use push to add operands to an expression, to update start and end. Np.push = function (kid) { // kid can be null e.g. [1, , 2]. if (kid !== null) { if (kid.start < this.start) this.start = kid.start; if (this.end < kid.end) this.end = kid.end; } return Array.prototype.push.call(this, kid); } Node.indentLevel = 0; function tokenstr(tt) { var t = tokens[tt]; return /^\W/.test(t) ? opTypeNames[t] : t.toUpperCase(); } Np.toString = function () { var a = []; for (var i in this) { if (this.hasOwnProperty(i) && i != 'type' && i != 'target') a.push({id: i, value: this[i]}); } a.sort(function (a,b) { return (a.id < b.id) ? -1 : 1; }); const INDENTATION = " "; var n = ++Node.indentLevel; var s = "{\n" + INDENTATION.repeat(n) + "type: " + tokenstr(this.type); for (i = 0; i < a.length; i++) s += ",\n" + INDENTATION.repeat(n) + a[i].id + ": " + a[i].value; n = --Node.indentLevel; s += "\n" + INDENTATION.repeat(n) + "}"; return s; } Np.getSource = function () { return this.tokenizer.source.slice(this.start, this.end); }; defineGetter(Np, "filename", function() { return this.tokenizer.filename; }); defineProperty(String.prototype, "repeat", function(n) { var s = "", t = this + s; while (--n >= 0) s += t; return s; }, false, false, true); // Statement stack and nested statement handler. function nest(t, x, node, func, end) { x.stmtStack.push(node); var n = func(t, x); x.stmtStack.pop(); end && t.mustMatch(end); return n; } /* * Statements :: (tokenizer, compiler context) -> node * * Parses a list of Statements. */ function Statements(t, x) { var b = x.builder; var n = b.BLOCK$build(t, x.blockId++); b.BLOCK$hoistLets(n); x.stmtStack.push(n); while (!t.done && t.peek(true) != RIGHT_CURLY) b.BLOCK$addStatement(n, Statement(t, x)); x.stmtStack.pop(); b.BLOCK$finish(n); if (n.needsHoisting) { b.setHoists(n.id, n.varDecls); // Propagate up to the function. x.needsHoisting = true; } return n; } function Block(t, x) { t.mustMatch(LEFT_CURLY); var n = Statements(t, x); t.mustMatch(RIGHT_CURLY); return n; } const DECLARED_FORM = 0, EXPRESSED_FORM = 1, STATEMENT_FORM = 2; /* * Statement :: (tokenizer, compiler context) -> node * * Parses a Statement. */ function Statement(t, x) { var i, label, n, n2, ss, tt = t.get(true); var b = x.builder; // Cases for statements ending in a right curly return early, avoiding the // common semicolon insertion magic after this switch. switch (tt) { case FUNCTION: // DECLARED_FORM extends funDecls of x, STATEMENT_FORM doesn't. return FunctionDefinition(t, x, true, (x.stmtStack.length > 1) ? STATEMENT_FORM : DECLARED_FORM); case LEFT_CURLY: n = Statements(t, x); t.mustMatch(RIGHT_CURLY); return n; case IF: n = b.IF$build(t); b.IF$setCondition(n, ParenExpression(t, x)); x.stmtStack.push(n); b.IF$setThenPart(n, Statement(t, x)); if (t.match(ELSE)) b.IF$setElsePart(n, Statement(t, x)); x.stmtStack.pop(); b.IF$finish(n); return n; case SWITCH: // This allows CASEs after a DEFAULT, which is in the standard. n = b.SWITCH$build(t); b.SWITCH$setDiscriminant(n, ParenExpression(t, x)); x.stmtStack.push(n); t.mustMatch(LEFT_CURLY); while ((tt = t.get()) != RIGHT_CURLY) { switch (tt) { case DEFAULT: if (n.defaultIndex >= 0) throw t.newSyntaxError("More than one switch default"); n2 = b.DEFAULT$build(t); b.SWITCH$setDefaultIndex(n, n.cases.length); t.mustMatch(COLON); b.DEFAULT$initializeStatements(n2, t); while ((tt=t.peek(true)) != CASE && tt != DEFAULT && tt != RIGHT_CURLY) b.DEFAULT$addStatement(n2, Statement(t, x)); b.DEFAULT$finish(n2); break; case CASE: n2 = b.CASE$build(t); b.CASE$setLabel(n2, Expression(t, x, COLON)); t.mustMatch(COLON); b.CASE$initializeStatements(n2, t); while ((tt=t.peek(true)) != CASE && tt != DEFAULT && tt != RIGHT_CURLY) b.CASE$addStatement(n2, Statement(t, x)); b.CASE$finish(n2); break; default: throw t.newSyntaxError("Invalid switch case"); } b.SWITCH$addCase(n, n2); } x.stmtStack.pop(); b.SWITCH$finish(n); return n; case FOR: n = b.FOR$build(t); if (t.match(IDENTIFIER) && t.token.value == "each") b.FOR$rebuildForEach(n); t.mustMatch(LEFT_PAREN); if ((tt = t.peek()) != SEMICOLON) { x.inForLoopInit = true; if (tt == VAR || tt == CONST) { t.get(); n2 = Variables(t, x); } else if (tt == LET) { t.get(); if (t.peek() == LEFT_PAREN) { n2 = LetBlock(t, x, false); } else { /* * Let in for head, we need to add an implicit block * around the rest of the for. */ var forBlock = b.BLOCK$build(t, x.blockId++); x.stmtStack.push(forBlock); n2 = Variables(t, x, forBlock); } } else { n2 = Expression(t, x); } x.inForLoopInit = false; } if (n2 && t.match(IN)) { b.FOR$rebuildForIn(n); b.FOR$setObject(n, Expression(t, x), forBlock); if (n2.type == VAR || n2.type == LET) { if (n2.length != 1) { throw new SyntaxError("Invalid for..in left-hand side", t.filename, n2.lineno); } b.FOR$setIterator(n, n2[0], n2, forBlock); } else { b.FOR$setIterator(n, n2, null, forBlock); } } else { b.FOR$setSetup(n, n2); t.mustMatch(SEMICOLON); if (n.isEach) throw t.newSyntaxError("Invalid for each..in loop"); b.FOR$setCondition(n, (t.peek() == SEMICOLON) ? null : Expression(t, x)); t.mustMatch(SEMICOLON); b.FOR$setUpdate(n, (t.peek() == RIGHT_PAREN) ? null : Expression(t, x)); } t.mustMatch(RIGHT_PAREN); b.FOR$setBody(n, nest(t, x, n, Statement)); if (forBlock) { b.BLOCK$finish(forBlock); x.stmtStack.pop(); } b.FOR$finish(n); return n; case WHILE: n = b.WHILE$build(t); b.WHILE$setCondition(n, ParenExpression(t, x)); b.WHILE$setBody(n, nest(t, x, n, Statement)); b.WHILE$finish(n); return n; case DO: n = b.DO$build(t); b.DO$setBody(n, nest(t, x, n, Statement, WHILE)); b.DO$setCondition(n, ParenExpression(t, x)); b.DO$finish(n); if (!x.ecmaStrictMode) { //