/* -*- 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); // Banned statement types by language version. const blackLists = { 160: {}, 185: {}, harmony: {} }; blackLists[160][IMPORT] = true; blackLists[160][EXPORT] = true; blackLists[160][LET] = true; blackLists[160][MODULE] = true; blackLists[160][YIELD] = true; blackLists[185][IMPORT] = true; blackLists[185][EXPORT] = true; blackLists[185][MODULE] = true; blackLists.harmony[WITH] = true; /* * 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); } } } function StaticContext(parentScript, parentBlock, inModule, inFunction) { this.parentScript = parentScript; this.parentBlock = parentBlock || parentScript; this.inModule = inModule || false; this.inFunction = inFunction || false; this.inForLoopInit = false; this.topLevel = true; this.allLabels = new Stack(); this.currentLabels = new Stack(); this.labeledTargets = new Stack(); this.defaultLoopTarget = null; this.defaultTarget = null; this.blackList = blackLists[Narcissus.options.version]; 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 isDefaultLoopTarget = target.isLoop; var isDefaultTarget = isDefaultLoopTarget || target.type === SWITCH; if (this.currentLabels.isEmpty()) { if (isDefaultLoopTarget) this.update({ defaultLoopTarget: target }); if (isDefaultTarget) this.update({ defaultTarget: target }); return 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), defaultLoopTarget: isDefaultLoopTarget ? target : this.defaultLoopTarget, defaultTarget: isDefaultTarget ? target : this.defaultTarget }); }, nest: function() { return this.topLevel ? this.update({ topLevel: false }) : this; }, allow: function(type) { switch (type) { case EXPORT: if (!this.inModule || this.inFunction || !this.topLevel) return false; // FALL THROUGH case IMPORT: return !this.inFunction && this.topLevel; case MODULE: return !this.inFunction && this.topLevel; default: return true; } } }; /* * Script :: (tokenizer, boolean, boolean) -> node * * Parses the toplevel and module/function bodies. */ function Script(t, inModule, inFunction) { var n = new Node(t, scriptInit()); Statements(t, new StaticContext(n, n, inModule, inFunction), 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]; } /* * SyntheticNode :: (tokenizer, optional init object) -> node */ function SyntheticNode(t, init) { // print("SYNTHETIC NODE"); // if (init.type === COMMA) { // print("SYNTHETIC COMMA"); // print(init); // } this.tokenizer = t; this.children = []; for (var prop in init) this[prop] = init[prop]; this.synthetic = true; } var Np = Node.prototype = SyntheticNode.prototype = {}; Np.constructor = Node; const TO_SOURCE_SKIP = { type: true, value: true, lineno: true, start: true, end: true, tokenizer: true, assignOp: true }; function unevalableConst(code) { var token = definitions.tokens[code]; var constName = definitions.opTypeNames.hasOwnProperty(token) ? definitions.opTypeNames[token] : token in definitions.keywords ? token.toUpperCase() : token; return { toSource: function() { return constName } }; } Np.toSource = function toSource() { var mock = {}; var self = this; mock.type = unevalableConst(this.type); // avoid infinite recursion in case of back-links if (this.generatingSource) return mock.toSource(); this.generatingSource = true; if ("value" in this) mock.value = this.value; if ("lineno" in this) mock.lineno = this.lineno; if ("start" in this) mock.start = this.start; if ("end" in this) mock.end = this.end; if (this.assignOp) mock.assignOp = unevalableConst(this.assignOp); for (var key in this) { if (this.hasOwnProperty(key) && !(key in TO_SOURCE_SKIP)) mock[key] = this[key]; } try { return mock.toSource(); } finally { delete this.generatingSource; } }; // 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 += ",\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); }; Np.synth = function(init) { var node = new SyntheticNode(this.tokenizer, init); node.filename = this.filename; node.lineno = this.lineno; node.start = this.start; node.end = this.end; return node; }; /* * Helper init objects for common nodes. */ const LOOP_INIT = { isLoop: true }; function blockInit() { return { type: BLOCK, varDecls: [] }; } function scriptInit() { return { type: SCRIPT, funDecls: [], varDecls: [], modDefns: new StringMap(), modAssns: new StringMap(), modDecls: new StringMap(), modLoads: new StringMap(), impDecls: [], expDecls: [], exports: new StringMap(), 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); return n; } const DECLARED_FORM = 0, EXPRESSED_FORM = 1, STATEMENT_FORM = 2; /* * Export :: (binding node, boolean) -> Export * * Static semantic representation of a module export. */ function Export(node, isDefinition) { this.node = node; // the AST node declaring this individual export this.isDefinition = isDefinition; // is the node an 'export'-annotated definition? this.resolved = null; // resolved pointer to the target of this export } /* * registerExport :: (StringMap, EXPORT node) -> void */ function registerExport(exports, decl) { function register(name, exp) { if (exports.has(name)) throw new SyntaxError("multiple exports of " + name); exports.set(name, exp); } switch (decl.type) { case MODULE: case FUNCTION: register(decl.name, new Export(decl, true)); break; case VAR: for (var i = 0; i < decl.children.length; i++) register(decl.children[i].name, new Export(decl.children[i], true)); break; case LET: case CONST: throw new Error("NYI: " + definitions.tokens[decl.type]); case EXPORT: for (var i = 0; i < decl.pathList.length; i++) { var path = decl.pathList[i]; switch (path.type) { case OBJECT_INIT: for (var j = 0; j < path.children.length; j++) { // init :: IDENTIFIER | PROPERTY_INIT var init = path.children[j]; if (init.type === IDENTIFIER) register(init.value, new Export(init, false)); else register(init.children[0].value, new Export(init.children[1], false)); } break; case DOT: register(path.children[1].value, new Export(path, false)); break; case IDENTIFIER: register(path.value, new Export(path, false)); break; default: throw new Error("unexpected export path: " + definitions.tokens[path.type]); } } break; default: throw new Error("unexpected export decl: " + definitions.tokens[exp.type]); } } /* * Module :: (node) -> Module * * Static semantic representation of a module. */ function Module(node) { var exports = node.body.exports; var modDefns = node.body.modDefns; var exportedModules = new StringMap(); exports.forEach(function(name, exp) { var node = exp.node; if (node.type === MODULE) { exportedModules.set(name, node); } else if (!exp.isDefinition && node.type === IDENTIFIER && modDefns.has(node.value)) { var mod = modDefns.get(node.value); exportedModules.set(name, mod); } }); this.node = node; this.exports = exports; this.exportedModules = exportedModules; } /* * 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; var comments = t.blockComments; if (x.blackList[tt]) throw t.newSyntaxError(definitions.tokens[tt] + " statements only allowed in Harmony"); if (!x.allow(tt)) throw t.newSyntaxError(definitions.tokens[tt] + " statement in illegal context"); // Cases for statements ending in a right curly return early, avoiding the // common semicolon insertion magic after this switch. switch (tt) { case IMPORT: n = new Node(t); n.pathList = ImportPathList(t, x); x.parentScript.impDecls.push(n); break; case EXPORT: switch (t.peek()) { case MODULE: case FUNCTION: case LET: case VAR: case CONST: n = Statement(t, x); n.blockComments = comments; n.exported = true; x.parentScript.expDecls.push(n); registerExport(x.parentScript.exports, n); return n; default: n = new Node(t); n.pathList = ExportPathList(t, x); break; } x.parentScript.expDecls.push(n); registerExport(x.parentScript.exports, n); break; case MODULE: n = new Node(t); n.blockComments = comments; t.mustMatch(IDENTIFIER); label = t.token.value; if (t.match(LEFT_CURLY)) { n.name = label; n.body = Script(t, true, false); n.module = new Module(n); t.mustMatch(RIGHT_CURLY); x.parentScript.modDefns.set(n.name, n); return n; } t.unget(); ModuleVariables(t, x, n); return n; case FUNCTION: // DECLARED_FORM extends funDecls of x, STATEMENT_FORM doesn't. return FunctionDefinition(t, x, true, x.topLevel ? DECLARED_FORM : STATEMENT_FORM, comments); case LEFT_CURLY: n = new Node(t, blockInit()); Statements(t, x.update({ parentBlock: n }).pushTarget(n).nest(), n); t.mustMatch(RIGHT_CURLY); return n; case IF: n = new Node(t); n.condition = HeadExpression(t, x); x2 = x.pushTarget(n).nest(); n.thenPart = Statement(t, x2); n.elsePart = t.match(ELSE, true) ? 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(); 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); } return n; case FOR: n = new Node(t, LOOP_INIT); n.blockComments = comments; 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(); x3 = x.update({ inForLoopInit: true }); n2 = null; if ((tt = t.peek(true)) !== 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 { x3.inForLoopInit = false; n.setup = n2; t.mustMatch(SEMICOLON); if (n.isEach) throw t.newSyntaxError("Invalid for each..in loop"); n.condition = (t.peek(true) === SEMICOLON) ? null : Expression(t, x3); t.mustMatch(SEMICOLON); tt2 = t.peek(true); 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); return n; case WHILE: n = new Node(t, { isLoop: true }); n.blockComments = comments; n.condition = HeadExpression(t, x); n.body = Statement(t, x.pushTarget(n).nest()); return n; case DO: n = new Node(t, { isLoop: true }); n.blockComments = comments; n.body = Statement(t, x.pushTarget(n).nest()); t.mustMatch(WHILE); n.condition = HeadExpression(t, x); if (!x.ecmaStrictMode) { //