diff options
author | xue <> | 2006-09-30 18:40:40 +0000 |
---|---|---|
committer | xue <> | 2006-09-30 18:40:40 +0000 |
commit | 1c32172efb18e8d08ea483e2460813670ebfe1a5 (patch) | |
tree | 8420f9e53eaba35d7b4822fac823197254f0d131 /tests/test_tools/selenium/core/scripts/narcissus-parse.js | |
parent | 6b1d87352911e43672b46b7a65a3c90dd8e5b8b1 (diff) |
merge from 3.0 branch till 1451.
Diffstat (limited to 'tests/test_tools/selenium/core/scripts/narcissus-parse.js')
-rw-r--r-- | tests/test_tools/selenium/core/scripts/narcissus-parse.js | 1003 |
1 files changed, 1003 insertions, 0 deletions
diff --git a/tests/test_tools/selenium/core/scripts/narcissus-parse.js b/tests/test_tools/selenium/core/scripts/narcissus-parse.js new file mode 100644 index 00000000..d6acb836 --- /dev/null +++ b/tests/test_tools/selenium/core/scripts/narcissus-parse.js @@ -0,0 +1,1003 @@ +/* ***** 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 <brendan@mozilla.org>. + * Portions created by the Initial Developer are Copyright (C) 2004 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): Richard Hundt <www.plextk.org> + * + * 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. + * + * Lexical scanner and parser. + */ + +// jrh +//module('JS.Parse'); + +// Build a regexp that recognizes operators and punctuators (except newline). +var opRegExp = +/^;|^,|^\?|^:|^\|\||^\&\&|^\||^\^|^\&|^===|^==|^=|^!==|^!=|^<<|^<=|^<|^>>>|^>>|^>=|^>|^\+\+|^\-\-|^\+|^\-|^\*|^\/|^%|^!|^~|^\.|^\[|^\]|^\{|^\}|^\(|^\)/; + +// A regexp to match floating point literals (but not integer literals). +var fpRegExp = /^\d+\.\d*(?:[eE][-+]?\d+)?|^\d+(?:\.\d*)?[eE][-+]?\d+|^\.\d+(?:[eE][-+]?\d+)?/; + +function Tokenizer(s, f, l) { + this.cursor = 0; + this.source = String(s); + this.tokens = []; + this.tokenIndex = 0; + this.lookahead = 0; + this.scanNewlines = false; + this.scanOperand = true; + this.filename = f || ""; + this.lineno = l || 1; +} + +Tokenizer.prototype = { + input : function() { + return this.source.substring(this.cursor); + }, + + done : function() { + return this.peek() == END; + }, + + token : function() { + return this.tokens[this.tokenIndex]; + }, + + match: function (tt) { + return this.get() == tt || this.unget(); + }, + + mustMatch: function (tt) { + if (!this.match(tt)) + throw this.newSyntaxError("Missing " + this.tokens[tt].toLowerCase()); + return this.token(); + }, + + peek: function () { + var tt; + if (this.lookahead) { + tt = this.tokens[(this.tokenIndex + this.lookahead) & 3].type; + } else { + tt = this.get(); + this.unget(); + } + return tt; + }, + + peekOnSameLine: function () { + this.scanNewlines = true; + var tt = this.peek(); + this.scanNewlines = false; + return tt; + }, + + get: function () { + var token; + while (this.lookahead) { + --this.lookahead; + this.tokenIndex = (this.tokenIndex + 1) & 3; + token = this.tokens[this.tokenIndex]; + if (token.type != NEWLINE || this.scanNewlines) + return token.type; + } + + for (;;) { + var input = this.input(); + var rx = this.scanNewlines ? /^[ \t]+/ : /^\s+/; + var match = input.match(rx); + if (match) { + var spaces = match[0]; + this.cursor += spaces.length; + var newlines = spaces.match(/\n/g); + if (newlines) + this.lineno += newlines.length; + input = this.input(); + } + + if (!(match = input.match(/^\/(?:\*(?:.|\n)*?\*\/|\/.*)/))) + break; + var comment = match[0]; + this.cursor += comment.length; + newlines = comment.match(/\n/g); + if (newlines) + this.lineno += newlines.length + } + + this.tokenIndex = (this.tokenIndex + 1) & 3; + token = this.tokens[this.tokenIndex]; + if (!token) + this.tokens[this.tokenIndex] = token = {}; + if (!input) + return token.type = END; + if ((match = input.match(fpRegExp))) { + token.type = NUMBER; + token.value = parseFloat(match[0]); + } else if ((match = input.match(/^0[xX][\da-fA-F]+|^0[0-7]*|^\d+/))) { + token.type = NUMBER; + token.value = parseInt(match[0]); + } else if ((match = input.match(/^((\$\w*)|(\w+))/))) { + var id = match[0]; + token.type = keywords[id] || IDENTIFIER; + token.value = id; + } else if ((match = input.match(/^"(?:\\.|[^"])*"|^'(?:[^']|\\.)*'/))) { + token.type = STRING; + token.value = eval(match[0]); + } else if (this.scanOperand && + (match = input.match(/^\/((?:\\.|[^\/])+)\/([gi]*)/))) { + token.type = REGEXP; + token.value = new RegExp(match[1], match[2]); + } else if ((match = input.match(opRegExp))) { + var op = match[0]; + if (assignOps[op] && input[op.length] == '=') { + token.type = ASSIGN; + token.assignOp = GLOBAL[opTypeNames[op]]; + match[0] += '='; + } else { + token.type = GLOBAL[opTypeNames[op]]; + if (this.scanOperand && + (token.type == PLUS || token.type == MINUS)) { + token.type += UNARY_PLUS - PLUS; + } + token.assignOp = null; + } + //debug('token.value => '+op+', token.type => '+token.type); + token.value = op; + } else { + throw this.newSyntaxError("Illegal token"); + } + + token.start = this.cursor; + this.cursor += match[0].length; + token.end = this.cursor; + token.lineno = this.lineno; + return token.type; + }, + + unget: function () { + if (++this.lookahead == 4) throw "PANIC: too much lookahead!"; + this.tokenIndex = (this.tokenIndex - 1) & 3; + }, + + newSyntaxError: function (m) { + var e = new SyntaxError(m, this.filename, this.lineno); + e.source = this.source; + e.cursor = this.cursor; + return e; + } +}; + +function CompilerContext(inFunction) { + this.inFunction = inFunction; + this.stmtStack = []; + this.funDecls = []; + this.varDecls = []; +} + +var CCp = CompilerContext.prototype; +CCp.bracketLevel = CCp.curlyLevel = CCp.parenLevel = CCp.hookLevel = 0; +CCp.ecmaStrictMode = CCp.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.top = function() { + return this.length && this[this.length-1]; +} + +function NarcNode(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 = NarcNode.prototype = new Array(); +Np.constructor = NarcNode; +Np.$length = 0; +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; + //debug('length before => '+this.$length); + this[this.$length] = kid; + this.$length++; + //debug('length after => '+this.$length); +} + +NarcNode.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') + a.push({id: i, value: this[i]}); + } + a.sort(function (a,b) { return (a.id < b.id) ? -1 : 1; }); + INDENTATION = " "; + var n = ++NarcNode.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 = --NarcNode.indentLevel; + s += "\n" + INDENTATION.repeat(n) + "}"; + return s; +} + +Np.getSource = function () { + return this.tokenizer.source.slice(this.start, this.end); +}; + +Np.filename = function () { return this.tokenizer.filename; }; + +String.prototype.repeat = function (n) { + var s = "", t = this + s; + while (--n >= 0) + s += t; + return s; +} + +// 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 NarcNode(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; +} + +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 NarcNode(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 NarcNode(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 NarcNode(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 NarcNode(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 NarcNode(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 NarcNode(t); + n.isLoop = true; + n.condition = ParenExpression(t, x); + n.body = nest(t, x, n, Statement); + return n; + + case DO: + n = new NarcNode(t); + n.isLoop = true; + n.body = nest(t, x, n, Statement, WHILE); + n.condition = ParenExpression(t, x); + if (!x.ecmaStrictMode) { + // <script language="JavaScript"> (without version hints) may need + // automatic semicolon insertion without a newline after do-while. + // See http://bugzilla.mozilla.org/show_bug.cgi?id=238945. + t.match(SEMICOLON); + return n; + } + break; + + case BREAK: + case CONTINUE: + n = new NarcNode(t); + if (t.peekOnSameLine() == IDENTIFIER) { + t.get(); + n.label = t.token().value; + } + ss = x.stmtStack; + i = ss.length; + label = n.label; + if (label) { + do { + if (--i < 0) + throw t.newSyntaxError("Label not found"); + } while (ss[i].label != label); + } else { + do { + if (--i < 0) { + throw t.newSyntaxError("Invalid " + ((tt == BREAK) + ? "break" + : "continue")); + } + } while (!ss[i].isLoop && (tt != BREAK || ss[i].type != SWITCH)); + } + n.target = ss[i]; + break; + + case TRY: + n = new NarcNode(t); + n.tryBlock = Block(t, x); + n.catchClauses = []; + while (t.match(CATCH)) { + n2 = new NarcNode(t); + t.mustMatch(LEFT_PAREN); + n2.varName = t.mustMatch(IDENTIFIER).value; + if (t.match(IF)) { + if (x.ecmaStrictMode) + throw t.newSyntaxError("Illegal catch guard"); + if (n.catchClauses.length && !n.catchClauses.top().guard) + throw t.newSyntaxError("Guarded catch after unguarded"); + n2.guard = Expression(t, x); + } else { + n2.guard = null; + } + t.mustMatch(RIGHT_PAREN); + n2.block = Block(t, x); + n.catchClauses.push(n2); + } + if (t.match(FINALLY)) + n.finallyBlock = Block(t, x); + if (!n.catchClauses.length && !n.finallyBlock) + throw t.newSyntaxError("Invalid try statement"); + return n; + + case CATCH: + case FINALLY: + throw t.newSyntaxError(tokens[tt] + " without preceding try"); + + case THROW: + n = new NarcNode(t); + n.exception = Expression(t, x); + break; + + case RETURN: + if (!x.inFunction) + throw t.newSyntaxError("Invalid return"); + n = new NarcNode(t); + tt = t.peekOnSameLine(); + if (tt != END && tt != NEWLINE && tt != SEMICOLON && tt != RIGHT_CURLY) + n.value = Expression(t, x); + break; + + case WITH: + n = new NarcNode(t); + n.object = ParenExpression(t, x); + n.body = nest(t, x, n, Statement); + return n; + + case VAR: + case CONST: + n = Variables(t, x); + break; + + case DEBUGGER: + n = new NarcNode(t); + break; + + case REQUIRE: + n = new NarcNode(t); + n.classPath = ParenExpression(t, x); + break; + + case NEWLINE: + case SEMICOLON: + n = new NarcNode(t, SEMICOLON); + n.expression = null; + return n; + + default: + if (tt == IDENTIFIER && t.peek() == COLON) { + label = t.token().value; + ss = x.stmtStack; + for (i = ss.length-1; i >= 0; --i) { + if (ss[i].label == label) + throw t.newSyntaxError("Duplicate label"); + } + t.get(); + n = new NarcNode(t, LABEL); + n.label = label; + n.statement = nest(t, x, n, Statement); + return n; + } + + n = new NarcNode(t, SEMICOLON); + t.unget(); + n.expression = Expression(t, x); + n.end = n.expression.end; + break; + } + + if (t.lineno == t.token().lineno) { + tt = t.peekOnSameLine(); + if (tt != END && tt != NEWLINE && tt != SEMICOLON && tt != RIGHT_CURLY) + throw t.newSyntaxError("Missing ; before statement"); + } + t.match(SEMICOLON); + return n; +} + +function FunctionDefinition(t, x, requireName, functionForm) { + var f = new NarcNode(t); + if (f.type != FUNCTION) + f.type = (f.value == "get") ? GETTER : SETTER; + if (t.match(IDENTIFIER)) { + f.name = t.token().value; + } + else if (requireName) + throw t.newSyntaxError("Missing function identifier"); + + t.mustMatch(LEFT_PAREN); + f.params = []; + var tt; + while ((tt = t.get()) != RIGHT_PAREN) { + if (tt != IDENTIFIER) + throw t.newSyntaxError("Missing formal parameter"); + f.params.push(t.token().value); + if (t.peek() != RIGHT_PAREN) + t.mustMatch(COMMA); + } + + t.mustMatch(LEFT_CURLY); + var x2 = new CompilerContext(true); + f.body = Script(t, x2); + t.mustMatch(RIGHT_CURLY); + f.end = t.token().end; + + f.functionForm = functionForm; + if (functionForm == DECLARED_FORM) { + x.funDecls.push(f); + } + + return f; +} + +function Variables(t, x) { + var n = new NarcNode(t); + do { + t.mustMatch(IDENTIFIER); + var n2 = new NarcNode(t); + n2.name = n2.value; + if (t.match(ASSIGN)) { + if (t.token().assignOp) + throw t.newSyntaxError("Invalid variable initialization"); + n2.initializer = Expression(t, x, COMMA); + } + n2.readOnly = (n.type == CONST); + n.push(n2); + x.varDecls.push(n2); + } while (t.match(COMMA)); + return n; +} + +function ParenExpression(t, x) { + t.mustMatch(LEFT_PAREN); + var n = Expression(t, x); + t.mustMatch(RIGHT_PAREN); + return n; +} + +var opPrecedence = { + SEMICOLON: 0, + COMMA: 1, + ASSIGN: 2, HOOK: 2, COLON: 2, CONDITIONAL: 2, + // The above all have to have the same precedence, see bug 330975. + OR: 4, + AND: 5, + BITWISE_OR: 6, + BITWISE_XOR: 7, + BITWISE_AND: 8, + EQ: 9, NE: 9, STRICT_EQ: 9, STRICT_NE: 9, + LT: 10, LE: 10, GE: 10, GT: 10, IN: 10, INSTANCEOF: 10, + LSH: 11, RSH: 11, URSH: 11, + PLUS: 12, MINUS: 12, + MUL: 13, DIV: 13, MOD: 13, + DELETE: 14, VOID: 14, TYPEOF: 14, // PRE_INCREMENT: 14, PRE_DECREMENT: 14, + NOT: 14, BITWISE_NOT: 14, UNARY_PLUS: 14, UNARY_MINUS: 14, + INCREMENT: 15, DECREMENT: 15, // postfix + NEW: 16, + DOT: 17 +}; + +// Map operator type code to precedence. +for (i in opPrecedence) + opPrecedence[GLOBAL[i]] = opPrecedence[i]; + +var opArity = { + COMMA: -2, + ASSIGN: 2, + CONDITIONAL: 3, + OR: 2, + AND: 2, + BITWISE_OR: 2, + BITWISE_XOR: 2, + BITWISE_AND: 2, + EQ: 2, NE: 2, STRICT_EQ: 2, STRICT_NE: 2, + LT: 2, LE: 2, GE: 2, GT: 2, IN: 2, INSTANCEOF: 2, + LSH: 2, RSH: 2, URSH: 2, + PLUS: 2, MINUS: 2, + MUL: 2, DIV: 2, MOD: 2, + DELETE: 1, VOID: 1, TYPEOF: 1, // PRE_INCREMENT: 1, PRE_DECREMENT: 1, + NOT: 1, BITWISE_NOT: 1, UNARY_PLUS: 1, UNARY_MINUS: 1, + INCREMENT: 1, DECREMENT: 1, // postfix + NEW: 1, NEW_WITH_ARGS: 2, DOT: 2, INDEX: 2, CALL: 2, + ARRAY_INIT: 1, OBJECT_INIT: 1, GROUP: 1 +}; + +// Map operator type code to arity. +for (i in opArity) + opArity[GLOBAL[i]] = opArity[i]; + +function Expression(t, x, stop) { + var n, id, tt, operators = [], operands = []; + var bl = x.bracketLevel, cl = x.curlyLevel, pl = x.parenLevel, + hl = x.hookLevel; + + function reduce() { + //debug('OPERATORS => '+operators); + var n = operators.pop(); + var op = n.type; + var arity = opArity[op]; + if (arity == -2) { + // Flatten left-associative trees. + var left = operands.length >= 2 && operands[operands.length-2]; + if (left.type == op) { + var right = operands.pop(); + left.push(right); + return left; + } + arity = 2; + } + + // Always use push to add operands to n, to update start and end. + var a = operands.splice(operands.length - arity, operands.length); + for (var i = 0; i < arity; i++) { + n.push(a[i]); + } + + // Include closing bracket or postfix operator in [start,end). + if (n.end < t.token().end) + n.end = t.token().end; + + operands.push(n); + return n; + } + +loop: + while ((tt = t.get()) != END) { + //debug('TT => '+tokens[tt]); + if (tt == stop && + x.bracketLevel == bl && x.curlyLevel == cl && x.parenLevel == pl && + x.hookLevel == hl) { + // Stop only if tt matches the optional stop parameter, and that + // token is not quoted by some kind of bracket. + break; + } + switch (tt) { + case SEMICOLON: + // NB: cannot be empty, Statement handled that. + break loop; + + case ASSIGN: + case HOOK: + case COLON: + if (t.scanOperand) + break loop; + // Use >, not >=, for right-associative ASSIGN and HOOK/COLON. + while (operators.length && opPrecedence[operators.top().type] > opPrecedence[tt] || + (tt == COLON && operators.top().type == ASSIGN)) { + reduce(); + } + if (tt == COLON) { + n = operators.top(); + if (n.type != HOOK) + throw t.newSyntaxError("Invalid label"); + n.type = CONDITIONAL; + --x.hookLevel; + } else { + operators.push(new NarcNode(t)); + if (tt == ASSIGN) + operands.top().assignOp = t.token().assignOp; + else + ++x.hookLevel; // tt == HOOK + } + t.scanOperand = true; + break; + + case IN: + // An in operator should not be parsed if we're parsing the head of + // a for (...) loop, unless it is in the then part of a conditional + // expression, or parenthesized somehow. + if (x.inForLoopInit && !x.hookLevel && + !x.bracketLevel && !x.curlyLevel && !x.parenLevel) { + break loop; + } + // FALL THROUGH + case COMMA: + // Treat comma as left-associative so reduce can fold left-heavy + // COMMA trees into a single array. + // FALL THROUGH + case OR: + case AND: + case BITWISE_OR: + case BITWISE_XOR: + case BITWISE_AND: + case EQ: case NE: case STRICT_EQ: case STRICT_NE: + case LT: case LE: case GE: case GT: + case INSTANCEOF: + case LSH: case RSH: case URSH: + case PLUS: case MINUS: + case MUL: case DIV: case MOD: + case DOT: + if (t.scanOperand) + break loop; + while (operators.length && opPrecedence[operators.top().type] >= opPrecedence[tt]) + reduce(); + if (tt == DOT) { + t.mustMatch(IDENTIFIER); + operands.push(new NarcNode(t, DOT, operands.pop(), new NarcNode(t))); + } else { + operators.push(new NarcNode(t)); + t.scanOperand = true; + } + break; + + case DELETE: case VOID: case TYPEOF: + case NOT: case BITWISE_NOT: case UNARY_PLUS: case UNARY_MINUS: + case NEW: + if (!t.scanOperand) + break loop; + operators.push(new NarcNode(t)); + break; + + case INCREMENT: case DECREMENT: + if (t.scanOperand) { + operators.push(new NarcNode(t)); // prefix increment or decrement + } else { + // Use >, not >=, so postfix has higher precedence than prefix. + while (operators.length && opPrecedence[operators.top().type] > opPrecedence[tt]) + reduce(); + n = new NarcNode(t, tt, operands.pop()); + n.postfix = true; + operands.push(n); + } + break; + + case FUNCTION: + if (!t.scanOperand) + break loop; + operands.push(FunctionDefinition(t, x, false, EXPRESSED_FORM)); + t.scanOperand = false; + break; + + case NULL: case THIS: case TRUE: case FALSE: + case IDENTIFIER: case NUMBER: case STRING: case REGEXP: + if (!t.scanOperand) + break loop; + operands.push(new NarcNode(t)); + t.scanOperand = false; + break; + + case LEFT_BRACKET: + if (t.scanOperand) { + // Array initialiser. Parse using recursive descent, as the + // sub-grammar here is not an operator grammar. + n = new NarcNode(t, ARRAY_INIT); + while ((tt = t.peek()) != RIGHT_BRACKET) { + if (tt == COMMA) { + t.get(); + n.push(null); + continue; + } + n.push(Expression(t, x, COMMA)); + if (!t.match(COMMA)) + break; + } + t.mustMatch(RIGHT_BRACKET); + operands.push(n); + t.scanOperand = false; + } else { + // Property indexing operator. + operators.push(new NarcNode(t, INDEX)); + t.scanOperand = true; + ++x.bracketLevel; + } + break; + + case RIGHT_BRACKET: + if (t.scanOperand || x.bracketLevel == bl) + break loop; + while (reduce().type != INDEX) + continue; + --x.bracketLevel; + break; + + case LEFT_CURLY: + if (!t.scanOperand) + break loop; + // Object initialiser. As for array initialisers (see above), + // parse using recursive descent. + ++x.curlyLevel; + n = new NarcNode(t, OBJECT_INIT); + object_init: + if (!t.match(RIGHT_CURLY)) { + do { + tt = t.get(); + if ((t.token().value == "get" || t.token().value == "set") && + t.peek() == IDENTIFIER) { + if (x.ecmaStrictMode) + throw t.newSyntaxError("Illegal property accessor"); + n.push(FunctionDefinition(t, x, true, EXPRESSED_FORM)); + } else { + switch (tt) { + case IDENTIFIER: + case NUMBER: + case STRING: + id = new NarcNode(t); + break; + case RIGHT_CURLY: + if (x.ecmaStrictMode) + throw t.newSyntaxError("Illegal trailing ,"); + break object_init; + default: + throw t.newSyntaxError("Invalid property name"); + } + t.mustMatch(COLON); + n.push(new NarcNode(t, PROPERTY_INIT, id, + Expression(t, x, COMMA))); + } + } while (t.match(COMMA)); + t.mustMatch(RIGHT_CURLY); + } + operands.push(n); + t.scanOperand = false; + --x.curlyLevel; + break; + + case RIGHT_CURLY: + if (!t.scanOperand && x.curlyLevel != cl) + throw "PANIC: right curly botch"; + break loop; + + case LEFT_PAREN: + if (t.scanOperand) { + operators.push(new NarcNode(t, GROUP)); + } else { + while (operators.length && opPrecedence[operators.top().type] > opPrecedence[NEW]) + reduce(); + + // Handle () now, to regularize the n-ary case for n > 0. + // We must set scanOperand in case there are arguments and + // the first one is a regexp or unary+/-. + n = operators.top(); + t.scanOperand = true; + if (t.match(RIGHT_PAREN)) { + if (n.type == NEW) { + --operators.length; + n.push(operands.pop()); + } else { + n = new NarcNode(t, CALL, operands.pop(), + new NarcNode(t, LIST)); + } + operands.push(n); + t.scanOperand = false; + break; + } + if (n.type == NEW) + n.type = NEW_WITH_ARGS; + else + operators.push(new NarcNode(t, CALL)); + } + ++x.parenLevel; + break; + + case RIGHT_PAREN: + if (t.scanOperand || x.parenLevel == pl) + break loop; + while ((tt = reduce().type) != GROUP && tt != CALL && + tt != NEW_WITH_ARGS) { + continue; + } + if (tt != GROUP) { + n = operands.top(); + if (n[1].type != COMMA) + n[1] = new NarcNode(t, LIST, n[1]); + else + n[1].type = LIST; + } + --x.parenLevel; + break; + + // Automatic semicolon insertion means we may scan across a newline + // and into the beginning of another statement. If so, break out of + // the while loop and let the t.scanOperand logic handle errors. + default: + break loop; + } + } + if (x.hookLevel != hl) + throw t.newSyntaxError("Missing : after ?"); + if (x.parenLevel != pl) + throw t.newSyntaxError("Missing ) in parenthetical"); + if (x.bracketLevel != bl) + throw t.newSyntaxError("Missing ] in index expression"); + if (t.scanOperand) + throw t.newSyntaxError("Missing operand"); + + // Resume default mode, scanning for operands, not operators. + t.scanOperand = true; + t.unget(); + + while (operators.length) + reduce(); + return operands.pop(); +} + +function parse(s, f, l) { + var t = new Tokenizer(s, f, l); + var x = new CompilerContext(false); + var n = Script(t, x); + if (!t.done()) + throw t.newSyntaxError("Syntax error"); + return n; +} + +debug = function(msg) { + document.body.appendChild(document.createTextNode(msg)); + document.body.appendChild(document.createElement('br')); +} + |