/* vim: set sw=4 ts=8 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. */ function CompilerContext(inFunction) { this.inFunction = inFunction; this.stmtStack = []; this.funDecls = []; this.varDecls = []; } CompilerContext.prototype = { bracketLevel: 0, curlyLevel: 0, parenLevel: 0, hookLevel: 0, ecma3OnlyMode: false, inForLoopInit: false, }; 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. Array.prototype.__defineProperty__( 'top', function () { return this.length && this[this.length-1]; }, false, false, true ); function Node(t, type) { var token = t.token; if (token) { this.type = type || token.type; this.value = token.value; this.lineno = token.lineno; this.start = token.start; this.end = token.end; } else { this.type = type; this.lineno = t.lineno; } 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) { 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); }; Np.__defineGetter__('filename', function () { return this.tokenizer.filename; }); String.prototype.__defineProperty__( '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; } function Statements(t, x) { var n = new Node(t, BLOCK); x.stmtStack.push(n); while (!t.done && t.peek() != RIGHT_CURLY) n.push(Statement(t, x)); x.stmtStack.pop(); 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; function Statement(t, x) { var i, label, n, n2, ss, tt = t.get(); // Cases for statements ending in a right curly return early, avoiding the // common semicolon insertion magic after this switch. switch (tt) { case FUNCTION: 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 = new Node(t); n.condition = ParenExpression(t, x); x.stmtStack.push(n); n.thenPart = Statement(t, x); n.elsePart = t.match(ELSE) ? Statement(t, x) : null; x.stmtStack.pop(); return n; case SWITCH: n = new Node(t); t.mustMatch(LEFT_PAREN); n.discriminant = Expression(t, x); t.mustMatch(RIGHT_PAREN); n.cases = []; n.defaultIndex = -1; 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"); // FALL THROUGH case CASE: n2 = new Node(t); if (tt == DEFAULT) n.defaultIndex = n.cases.length; else n2.caseLabel = Expression(t, x, COLON); break; default: throw t.newSyntaxError("Invalid switch case"); } t.mustMatch(COLON); n2.statements = new Node(t, BLOCK); while ((tt=t.peek()) != CASE && tt != DEFAULT && tt != RIGHT_CURLY) n2.statements.push(Statement(t, x)); n.cases.push(n2); } x.stmtStack.pop(); return n; case FOR: n = new Node(t); n.isLoop = true; t.mustMatch(LEFT_PAREN); if ((tt = t.peek()) != SEMICOLON) { x.inForLoopInit = true; if (tt == VAR || tt == CONST) { t.get(); n2 = Variables(t, x); } else { n2 = Expression(t, x); } x.inForLoopInit = false; } if (n2 && t.match(IN)) { n.type = FOR_IN; if (n2.type == VAR) { if (n2.length != 1) { throw new SyntaxError("Invalid for..in left-hand side", t.filename, n2.lineno); } // NB: n2[0].type == IDENTIFIER and n2[0].value == n2[0].name. n.iterator = n2[0]; n.varDecl = n2; } else { n.iterator = n2; n.varDecl = null; } n.object = Expression(t, x); } else { n.setup = n2 || null; t.mustMatch(SEMICOLON); n.condition = (t.peek() == SEMICOLON) ? null : Expression(t, x); t.mustMatch(SEMICOLON); n.update = (t.peek() == RIGHT_PAREN) ? null : Expression(t, x); } t.mustMatch(RIGHT_PAREN); n.body = nest(t, x, n, Statement); return n; case WHILE: n = new Node(t); n.isLoop = true; n.condition = ParenExpression(t, x); n.body = nest(t, x, n, Statement); return n; case DO: n = new Node(t); n.isLoop = true; n.body = nest(t, x, n, Statement, WHILE); n.condition = ParenExpression(t, x); if (!x.ecmaStrictMode) { //