/* -*- 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): * Tom Austin * Brendan Eich * Shu-Yu Guo * Dave Herman * Dimitris Vardoulakis * Patrick Walton * * 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. */ Narcissus.parser = (function() { var lexer = Narcissus.lexer; var definitions = Narcissus.definitions; const StringMap = definitions.StringMap; const Stack = definitions.Stack; // Set constants in the local scope. eval(definitions.consts); /* * pushDestructuringVarDecls :: (node, hoisting node) -> void * * Recursively add all destructured declarations to varDecls. */ function pushDestructuringVarDecls(n, s) { for (var i in n) { var sub = n[i]; if (sub.type === IDENTIFIER) { s.varDecls.push(sub); } else { pushDestructuringVarDecls(sub, s); } } } // NESTING_TOP: top-level // NESTING_SHALLOW: nested within static forms such as { ... } or labeled statement // NESTING_DEEP: nested within dynamic forms such as if, loops, etc. const NESTING_TOP = 0, NESTING_SHALLOW = 1, NESTING_DEEP = 2; function StaticContext(parentScript, parentBlock, inFunction, inForLoopInit, nesting) { this.parentScript = parentScript; this.parentBlock = parentBlock; this.inFunction = inFunction; this.inForLoopInit = inForLoopInit; this.nesting = nesting; this.allLabels = new Stack(); this.currentLabels = new Stack(); this.labeledTargets = new Stack(); this.defaultTarget = null; Narcissus.options.ecma3OnlyMode && (this.ecma3OnlyMode = true); Narcissus.options.parenFreeMode && (this.parenFreeMode = true); } StaticContext.prototype = { ecma3OnlyMode: false, parenFreeMode: false, // non-destructive update via prototype extension update: function(ext) { var desc = {}; for (var key in ext) { desc[key] = { value: ext[key], writable: true, enumerable: true, configurable: true } } return Object.create(this, desc); }, pushLabel: function(label) { return this.update({ currentLabels: this.currentLabels.push(label), allLabels: this.allLabels.push(label) }); }, pushTarget: function(target) { var isDefaultTarget = target.isLoop || target.type === SWITCH; if (isDefaultTarget) target.target = this.defaultTarget; if (this.currentLabels.isEmpty()) { return isDefaultTarget ? this.update({ defaultTarget: target }) : this; } target.labels = new StringMap(); this.currentLabels.forEach(function(label) { target.labels.set(label, true); }); return this.update({ currentLabels: new Stack(), labeledTargets: this.labeledTargets.push(target), defaultTarget: isDefaultTarget ? target : this.defaultTarget }); }, nest: function(atLeast) { var nesting = Math.max(this.nesting, atLeast); return (nesting !== this.nesting) ? this.update({ nesting: nesting }) : this; } }; /* * Script :: (tokenizer, boolean) -> node * * Parses the toplevel and function bodies. */ function Script(t, inFunction) { var n = new Node(t, scriptInit()); var x = new StaticContext(n, n, inFunction, false, NESTING_TOP); Statements(t, x, n); return n; } // We extend Array slightly with a top-of-stack method. definitions.defineProperty(Array.prototype, "top", function() { return this.length && this[this.length-1]; }, false, false, true); /* * Node :: (tokenizer, optional init object) -> node */ function Node(t, init) { var token = t.token; if (token) { // If init.type exists it will override token.type. this.type = token.type; this.value = token.value; this.lineno = token.lineno; // Start and end are file positions for error handling. this.start = token.start; this.end = token.end; } else { this.lineno = t.lineno; } // Node uses a tokenizer for debugging (getSource, filename getter). this.tokenizer = t; this.children = []; for (var prop in init) this[prop] = init[prop]; } var Np = Node.prototype = {}; 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 this.children.push(kid); } Node.indentLevel = 0; function tokenString(tt) { var t = definitions.tokens[tt]; return /^\W/.test(t) ? definitions.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: " + tokenString(this.type); for (i = 0; i < a.length; i++) s += ", " + a[i].id + ": " + a[i].value; //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); }; /* * Helper init objects for common nodes. */ const LOOP_INIT = { isLoop: true }; function blockInit() { return { type: BLOCK, varDecls: [] }; } function scriptInit() { return { type: SCRIPT, funDecls: [], varDecls: [], modDecls: [], impDecls: [], expDecls: [], loadDeps: [], hasEmptyReturn: false, hasReturnWithValue: false, isGenerator: false }; } definitions.defineGetter(Np, "filename", function() { return this.tokenizer.filename; }); definitions.defineGetter(Np, "length", function() { throw new Error("Node.prototype.length is gone; " + "use n.children.length instead"); }); definitions.defineProperty(String.prototype, "repeat", function(n) { var s = "", t = this + s; while (--n >= 0) s += t; return s; }, false, false, true); function MaybeLeftParen(t, x) { if (x.parenFreeMode) return t.match(LEFT_PAREN) ? LEFT_PAREN : END; return t.mustMatch(LEFT_PAREN).type; } function MaybeRightParen(t, p) { if (p === LEFT_PAREN) t.mustMatch(RIGHT_PAREN); } /* * Statements :: (tokenizer, compiler context, node) -> void * * Parses a sequence of Statements. */ function Statements(t, x, n) { try { while (!t.done && t.peek(true) !== RIGHT_CURLY) n.push(Statement(t, x)); } catch (e) { if (t.done) t.unexpectedEOF = true; throw e; } } function Block(t, x) { t.mustMatch(LEFT_CURLY); var n = new Node(t, blockInit()); Statements(t, x.update({ parentBlock: n }).pushTarget(n), n); t.mustMatch(RIGHT_CURLY); n.end = t.token.end; 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, p, c, ss, tt = t.get(true), tt2, x2, x3; // 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.nesting !== NESTING_TOP) ? STATEMENT_FORM : DECLARED_FORM); case LEFT_CURLY: n = new Node(t, blockInit()); Statements(t, x.update({ parentBlock: n }).pushTarget(n).nest(NESTING_SHALLOW), n); t.mustMatch(RIGHT_CURLY); n.end = t.token.end; return n; case IF: n = new Node(t); n.condition = HeadExpression(t, x); x2 = x.pushTarget(n).nest(NESTING_DEEP); n.thenPart = Statement(t, x2); n.elsePart = t.match(ELSE) ? Statement(t, x2) : null; return n; case SWITCH: // This allows CASEs after a DEFAULT, which is in the standard. n = new Node(t, { cases: [], defaultIndex: -1 }); n.discriminant = HeadExpression(t, x); x2 = x.pushTarget(n).nest(NESTING_DEEP); 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"); // FALL THROUGH case CASE: n2 = new Node(t); if (tt === DEFAULT) n.defaultIndex = n.cases.length; else n2.caseLabel = Expression(t, x2, COLON); break; default: throw t.newSyntaxError("Invalid switch case"); } t.mustMatch(COLON); n2.statements = new Node(t, blockInit()); while ((tt=t.peek(true)) !== CASE && tt !== DEFAULT && tt !== RIGHT_CURLY) n2.statements.push(Statement(t, x2)); n.cases.push(n2); } n.end = t.token.end; return n; case FOR: n = new Node(t, LOOP_INIT); if (t.match(IDENTIFIER)) { if (t.token.value === "each") n.isEach = true; else t.unget(); } if (!x.parenFreeMode) t.mustMatch(LEFT_PAREN); x2 = x.pushTarget(n).nest(NESTING_DEEP); x3 = x.update({ inForLoopInit: true }); if ((tt = t.peek()) !== SEMICOLON) { if (tt === VAR || tt === CONST) { t.get(); n2 = Variables(t, x3); } else if (tt === LET) { t.get(); if (t.peek() === LEFT_PAREN) { n2 = LetBlock(t, x3, false); } else { // Let in for head, we need to add an implicit block // around the rest of the for. x3.parentBlock = n; n.varDecls = []; n2 = Variables(t, x3); } } else { n2 = Expression(t, x3); } } if (n2 && t.match(IN)) { n.type = FOR_IN; n.object = Expression(t, x3); if (n2.type === VAR || n2.type === LET) { c = n2.children; // Destructuring turns one decl into multiples, so either // there must be only one destructuring or only one // decl. if (c.length !== 1 && n2.destructurings.length !== 1) { throw new SyntaxError("Invalid for..in left-hand side", t.filename, n2.lineno); } if (n2.destructurings.length > 0) { n.iterator = n2.destructurings[0]; } else { n.iterator = c[0]; } n.varDecl = n2; } else { if (n2.type === ARRAY_INIT || n2.type === OBJECT_INIT) { n2.destructuredNames = checkDestructuring(t, x3, n2); } n.iterator = n2; } } else { n.setup = n2; t.mustMatch(SEMICOLON); if (n.isEach) throw t.newSyntaxError("Invalid for each..in loop"); n.condition = (t.peek() === SEMICOLON) ? null : Expression(t, x3); t.mustMatch(SEMICOLON); tt2 = t.peek(); n.update = (x.parenFreeMode ? tt2 === LEFT_CURLY || definitions.isStatementStartCode[tt2] : tt2 === RIGHT_PAREN) ? null : Expression(t, x3); } if (!x.parenFreeMode) t.mustMatch(RIGHT_PAREN); n.body = Statement(t, x2); n.end = t.token.end; return n; case WHILE: n = new Node(t, { isLoop: true }); n.condition = HeadExpression(t, x); n.body = Statement(t, x.pushTarget(n).nest(NESTING_DEEP)); n.end = t.token.end; return n; case DO: n = new Node(t, { isLoop: true }); n.body = Statement(t, x.pushTarget(n).nest(NESTING_DEEP)); t.mustMatch(WHILE); n.condition = HeadExpression(t, x); if (!x.ecmaStrictMode) { //