mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
2307 lines
66 KiB
JavaScript
2307 lines
66 KiB
JavaScript
/* -*- 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 <brendan@mozilla.org>.
|
|
* 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.
|
|
*/
|
|
|
|
Narcissus.parser = (function() {
|
|
|
|
var lexer = Narcissus.lexer;
|
|
var definitions = Narcissus.definitions;
|
|
|
|
// Set constants in the local scope.
|
|
eval(definitions.consts);
|
|
|
|
/*
|
|
* The vanilla AST builder.
|
|
*/
|
|
|
|
VanillaBuilder = 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);
|
|
},
|
|
|
|
ARRAY_COMP$setExpression: function(n, e) {
|
|
n.expression = e
|
|
},
|
|
|
|
ARRAY_COMP$setTail: function(n, n2) {
|
|
n.tail = n2;
|
|
},
|
|
|
|
ARRAY_COMP$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 StaticContext(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 = [];
|
|
}
|
|
|
|
StaticContext.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.
|
|
definitions.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 = 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: " + 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);
|
|
};
|
|
|
|
definitions.defineGetter(Np, "filename",
|
|
function() {
|
|
return this.tokenizer.filename;
|
|
});
|
|
|
|
definitions.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) {
|
|
/*
|
|
* Blocks are uniquely numbered by a blockId within a function that is
|
|
* at the top level of the program. blockId starts from 0.
|
|
*
|
|
* This is done to aid hoisting for parse-time analyses done in custom
|
|
* builders.
|
|
*
|
|
* For more details in its interaction with hoisting, see comments in
|
|
* FunctionDefinition.
|
|
*/
|
|
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);
|
|
/*
|
|
* If a block needs hoisting, we need to propagate this flag up to
|
|
* the CompilerContext.
|
|
*/
|
|
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) {
|
|
// <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 = tt === BREAK ? b.BREAK$build(t) : b.CONTINUE$build(t);
|
|
|
|
if (t.peekOnSameLine() === IDENTIFIER) {
|
|
t.get();
|
|
if (tt === BREAK)
|
|
b.BREAK$setLabel(n, t.token.value);
|
|
else
|
|
b.CONTINUE$setLabel(n, 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);
|
|
|
|
/*
|
|
* Both break and continue to label need to be handled specially
|
|
* within a labeled loop, so that they target that loop. If not in
|
|
* a loop, then break targets its labeled statement. Labels can be
|
|
* nested so we skip all labels immediately enclosing the nearest
|
|
* non-label statement.
|
|
*/
|
|
while (i < ss.length - 1 && ss[i+1].type === LABEL)
|
|
i++;
|
|
if (i < ss.length - 1 && ss[i+1].isLoop)
|
|
i++;
|
|
else if (tt === CONTINUE)
|
|
throw t.newSyntaxError("Invalid continue");
|
|
} else {
|
|
do {
|
|
if (--i < 0) {
|
|
throw t.newSyntaxError("Invalid " + ((tt === BREAK)
|
|
? "break"
|
|
: "continue"));
|
|
}
|
|
} while (!ss[i].isLoop && !(tt === BREAK && ss[i].type === SWITCH));
|
|
}
|
|
if (tt === BREAK) {
|
|
b.BREAK$setTarget(n, ss[i]);
|
|
b.BREAK$finish(n);
|
|
} else {
|
|
b.CONTINUE$setTarget(n, ss[i]);
|
|
b.CONTINUE$finish(n);
|
|
}
|
|
break;
|
|
|
|
case TRY:
|
|
n = b.TRY$build(t);
|
|
b.TRY$setTryBlock(n, Block(t, x));
|
|
while (t.match(CATCH)) {
|
|
n2 = b.CATCH$build(t);
|
|
t.mustMatch(LEFT_PAREN);
|
|
switch (t.get()) {
|
|
case LEFT_BRACKET:
|
|
case LEFT_CURLY:
|
|
// Destructured catch identifiers.
|
|
t.unget();
|
|
b.CATCH$setVarName(n2, DestructuringExpression(t, x, true));
|
|
case IDENTIFIER:
|
|
b.CATCH$setVarName(n2, t.token.value);
|
|
break;
|
|
default:
|
|
throw t.newSyntaxError("missing identifier in catch");
|
|
break;
|
|
}
|
|
if (t.match(IF)) {
|
|
if (x.ecma3OnlyMode)
|
|
throw t.newSyntaxError("Illegal catch guard");
|
|
if (n.catchClauses.length && !n.catchClauses.top().guard)
|
|
throw t.newSyntaxError("Guarded catch after unguarded");
|
|
b.CATCH$setGuard(n2, Expression(t, x));
|
|
} else {
|
|
b.CATCH$setGuard(n2, null);
|
|
}
|
|
t.mustMatch(RIGHT_PAREN);
|
|
b.CATCH$setBlock(n2, Block(t, x));
|
|
b.CATCH$finish(n2);
|
|
b.TRY$addCatch(n, n2);
|
|
}
|
|
b.TRY$finishCatches(n);
|
|
if (t.match(FINALLY))
|
|
b.TRY$setFinallyBlock(n, Block(t, x));
|
|
if (!n.catchClauses.length && !n.finallyBlock)
|
|
throw t.newSyntaxError("Invalid try statement");
|
|
b.TRY$finish(n);
|
|
return n;
|
|
|
|
case CATCH:
|
|
case FINALLY:
|
|
throw t.newSyntaxError(definitions.tokens[tt] + " without preceding try");
|
|
|
|
case THROW:
|
|
n = b.THROW$build(t);
|
|
b.THROW$setException(n, Expression(t, x));
|
|
b.THROW$finish(n);
|
|
break;
|
|
|
|
case RETURN:
|
|
n = returnOrYield(t, x);
|
|
break;
|
|
|
|
case WITH:
|
|
n = b.WITH$build(t);
|
|
b.WITH$setObject(n, ParenExpression(t, x));
|
|
b.WITH$setBody(n, nest(t, x, n, Statement));
|
|
b.WITH$finish(n);
|
|
return n;
|
|
|
|
case VAR:
|
|
case CONST:
|
|
n = Variables(t, x);
|
|
break;
|
|
|
|
case LET:
|
|
if (t.peek() === LEFT_PAREN)
|
|
n = LetBlock(t, x, true);
|
|
else
|
|
n = Variables(t, x);
|
|
break;
|
|
|
|
case DEBUGGER:
|
|
n = b.DEBUGGER$build(t);
|
|
break;
|
|
|
|
case NEWLINE:
|
|
case SEMICOLON:
|
|
n = b.SEMICOLON$build(t);
|
|
b.SEMICOLON$setExpression(n, null);
|
|
b.SEMICOLON$finish(t);
|
|
return n;
|
|
|
|
default:
|
|
if (tt === IDENTIFIER) {
|
|
tt = t.peek();
|
|
// Labeled statement.
|
|
if (tt === 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 = b.LABEL$build(t);
|
|
b.LABEL$setLabel(n, label)
|
|
b.LABEL$setStatement(n, nest(t, x, n, Statement));
|
|
b.LABEL$finish(n);
|
|
return n;
|
|
}
|
|
}
|
|
|
|
// Expression statement.
|
|
// We unget the current token to parse the expression as a whole.
|
|
n = b.SEMICOLON$build(t);
|
|
t.unget();
|
|
b.SEMICOLON$setExpression(n, Expression(t, x));
|
|
n.end = n.expression.end;
|
|
b.SEMICOLON$finish(n);
|
|
break;
|
|
}
|
|
|
|
MagicalSemicolon(t);
|
|
return n;
|
|
}
|
|
|
|
function MagicalSemicolon(t) {
|
|
var tt;
|
|
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);
|
|
}
|
|
|
|
function returnOrYield(t, x) {
|
|
var n, b = x.builder, tt = t.token.type, tt2;
|
|
|
|
if (tt === RETURN) {
|
|
if (!x.inFunction)
|
|
throw t.newSyntaxError("Return not in function");
|
|
n = b.RETURN$build(t);
|
|
} else /* (tt === YIELD) */ {
|
|
if (!x.inFunction)
|
|
throw t.newSyntaxError("Yield not in function");
|
|
x.isGenerator = true;
|
|
n = b.YIELD$build(t);
|
|
}
|
|
|
|
tt2 = t.peek(true);
|
|
if (tt2 !== END && tt2 !== NEWLINE && tt2 !== SEMICOLON && tt2 !== RIGHT_CURLY
|
|
&& (tt !== YIELD ||
|
|
(tt2 !== tt && tt2 !== RIGHT_BRACKET && tt2 !== RIGHT_PAREN &&
|
|
tt2 !== COLON && tt2 !== COMMA))) {
|
|
if (tt === RETURN) {
|
|
b.RETURN$setValue(n, Expression(t, x));
|
|
x.hasReturnWithValue = true;
|
|
} else {
|
|
b.YIELD$setValue(n, AssignExpression(t, x));
|
|
}
|
|
} else if (tt === RETURN) {
|
|
x.hasEmptyReturn = true;
|
|
}
|
|
|
|
// Disallow return v; in generator.
|
|
if (x.hasReturnWithValue && x.isGenerator)
|
|
throw t.newSyntaxError("Generator returns a value");
|
|
|
|
if (tt === RETURN)
|
|
b.RETURN$finish(n);
|
|
else
|
|
b.YIELD$finish(n);
|
|
|
|
return n;
|
|
}
|
|
|
|
/*
|
|
* FunctionDefinition :: (tokenizer, compiler context, boolean,
|
|
* DECLARED_FORM or EXPRESSED_FORM or STATEMENT_FORM)
|
|
* -> node
|
|
*/
|
|
function FunctionDefinition(t, x, requireName, functionForm) {
|
|
var b = x.builder;
|
|
var f = b.FUNCTION$build(t);
|
|
if (t.match(IDENTIFIER))
|
|
b.FUNCTION$setName(f, t.token.value);
|
|
else if (requireName)
|
|
throw t.newSyntaxError("missing function identifier");
|
|
|
|
t.mustMatch(LEFT_PAREN);
|
|
if (!t.match(RIGHT_PAREN)) {
|
|
do {
|
|
switch (t.get()) {
|
|
case LEFT_BRACKET:
|
|
case LEFT_CURLY:
|
|
// Destructured formal parameters.
|
|
t.unget();
|
|
b.FUNCTION$addParam(f, DestructuringExpression(t, x));
|
|
break;
|
|
case IDENTIFIER:
|
|
b.FUNCTION$addParam(f, t.token.value);
|
|
break;
|
|
default:
|
|
throw t.newSyntaxError("missing formal parameter");
|
|
break;
|
|
}
|
|
} while (t.match(COMMA));
|
|
t.mustMatch(RIGHT_PAREN);
|
|
}
|
|
|
|
// Do we have an expression closure or a normal body?
|
|
var tt = t.get();
|
|
if (tt !== LEFT_CURLY)
|
|
t.unget();
|
|
|
|
var x2 = new StaticContext(true, b);
|
|
var rp = t.save();
|
|
if (x.inFunction) {
|
|
/*
|
|
* Inner functions don't reset block numbering, only functions at
|
|
* the top level of the program do.
|
|
*/
|
|
x2.blockId = x.blockId;
|
|
}
|
|
|
|
if (tt !== LEFT_CURLY) {
|
|
b.FUNCTION$setBody(f, AssignExpression(t, x));
|
|
if (x.isGenerator)
|
|
throw t.newSyntaxError("Generator returns a value");
|
|
} else {
|
|
b.FUNCTION$hoistVars(x2.blockId);
|
|
b.FUNCTION$setBody(f, Script(t, x2));
|
|
}
|
|
|
|
/*
|
|
* Hoisting makes parse-time binding analysis tricky. A taxonomy of hoists:
|
|
*
|
|
* 1. vars hoist to the top of their function:
|
|
*
|
|
* var x = 'global';
|
|
* function f() {
|
|
* x = 'f';
|
|
* if (false)
|
|
* var x;
|
|
* }
|
|
* f();
|
|
* print(x); // "global"
|
|
*
|
|
* 2. lets hoist to the top of their block:
|
|
*
|
|
* function f() { // id: 0
|
|
* var x = 'f';
|
|
* {
|
|
* {
|
|
* print(x); // "undefined"
|
|
* }
|
|
* let x;
|
|
* }
|
|
* }
|
|
* f();
|
|
*
|
|
* 3. inner functions at function top-level hoist to the beginning
|
|
* of the function.
|
|
*
|
|
* If the builder used is doing parse-time analyses, hoisting may
|
|
* invalidate earlier conclusions it makes about variable scope.
|
|
*
|
|
* The builder can opt to set the needsHoisting flag in a
|
|
* CompilerContext (in the case of var and function hoisting) or in a
|
|
* node of type BLOCK (in the case of let hoisting). This signals for
|
|
* the parser to reparse sections of code.
|
|
*
|
|
* To avoid exponential blowup, if a function at the program top-level
|
|
* has any hoists in its child blocks or inner functions, we reparse
|
|
* the entire toplevel function. Each toplevel function is parsed at
|
|
* most twice.
|
|
*
|
|
* The list of declarations can be tied to block ids to aid talking
|
|
* about declarations of blocks that have not yet been fully parsed.
|
|
*
|
|
* Blocks are already uniquely numbered; see the comment in
|
|
* Statements.
|
|
*/
|
|
if (x2.needsHoisting) {
|
|
|
|
/*
|
|
* Order is important here! Builders expect funDecls to come after
|
|
* varDecls!
|
|
*/
|
|
b.setHoists(f.body.id, x2.varDecls.concat(x2.funDecls));
|
|
|
|
if (x.inFunction) {
|
|
/*
|
|
* If an inner function needs hoisting, we need to propagate
|
|
* this flag up to the parent function.
|
|
*/
|
|
x.needsHoisting = true;
|
|
} else {
|
|
// Only re-parse functions at the top level of the program.
|
|
x2 = new StaticContext(true, b);
|
|
t.rewind(rp);
|
|
/*
|
|
* Set a flag in case the builder wants to have different behavior
|
|
* on the second pass.
|
|
*/
|
|
b.secondPass = true;
|
|
b.FUNCTION$hoistVars(f.body.id, true);
|
|
b.FUNCTION$setBody(f, Script(t, x2));
|
|
b.secondPass = false;
|
|
}
|
|
}
|
|
|
|
if (tt === LEFT_CURLY)
|
|
t.mustMatch(RIGHT_CURLY);
|
|
|
|
f.end = t.token.end;
|
|
f.functionForm = functionForm;
|
|
if (functionForm === DECLARED_FORM)
|
|
x.funDecls.push(f);
|
|
b.FUNCTION$finish(f, x);
|
|
return f;
|
|
}
|
|
|
|
/*
|
|
* Variables :: (tokenizer, compiler context) -> node
|
|
*
|
|
* Parses a comma-separated list of var declarations (and maybe
|
|
* initializations).
|
|
*/
|
|
function Variables(t, x, letBlock) {
|
|
var b = x.builder;
|
|
var n, ss, i, s;
|
|
var build, addDecl, finish;
|
|
switch (t.token.type) {
|
|
case VAR:
|
|
build = b.VAR$build;
|
|
addDecl = b.VAR$addDecl;
|
|
finish = b.VAR$finish;
|
|
s = x;
|
|
break;
|
|
case CONST:
|
|
build = b.CONST$build;
|
|
addDecl = b.CONST$addDecl;
|
|
finish = b.CONST$finish;
|
|
s = x;
|
|
break;
|
|
case LET:
|
|
case LEFT_PAREN:
|
|
build = b.LET$build;
|
|
addDecl = b.LET$addDecl;
|
|
finish = b.LET$finish;
|
|
if (!letBlock) {
|
|
ss = x.stmtStack;
|
|
i = ss.length;
|
|
while (ss[--i].type !== BLOCK) ; // a BLOCK *must* be found.
|
|
/*
|
|
* Lets at the function toplevel are just vars, at least in
|
|
* SpiderMonkey.
|
|
*/
|
|
if (i === 0) {
|
|
build = b.VAR$build;
|
|
addDecl = b.VAR$addDecl;
|
|
finish = b.VAR$finish;
|
|
s = x;
|
|
} else {
|
|
s = ss[i];
|
|
}
|
|
} else {
|
|
s = letBlock;
|
|
}
|
|
break;
|
|
}
|
|
n = build.call(b, t);
|
|
initializers = [];
|
|
do {
|
|
var tt = t.get();
|
|
/*
|
|
* FIXME Should have a special DECLARATION node instead of overloading
|
|
* IDENTIFIER to mean both identifier declarations and destructured
|
|
* declarations.
|
|
*/
|
|
var n2 = b.DECL$build(t);
|
|
if (tt === LEFT_BRACKET || tt === LEFT_CURLY) {
|
|
// Pass in s if we need to add each pattern matched into
|
|
// its varDecls, else pass in x.
|
|
var data = null;
|
|
// Need to unget to parse the full destructured expression.
|
|
t.unget();
|
|
b.DECL$setName(n2, DestructuringExpression(t, x, true, s));
|
|
if (x.inForLoopInit && t.peek() === IN) {
|
|
addDecl.call(b, n, n2, s);
|
|
continue;
|
|
}
|
|
|
|
t.mustMatch(ASSIGN);
|
|
if (t.token.assignOp)
|
|
throw t.newSyntaxError("Invalid variable initialization");
|
|
|
|
// Parse the init as a normal assignment.
|
|
var n3 = b.ASSIGN$build(t);
|
|
b.ASSIGN$addOperand(n3, n2.name);
|
|
b.ASSIGN$addOperand(n3, AssignExpression(t, x));
|
|
b.ASSIGN$finish(n3);
|
|
|
|
// But only add the rhs as the initializer.
|
|
b.DECL$setInitializer(n2, n3[1]);
|
|
b.DECL$finish(n2);
|
|
addDecl.call(b, n, n2, s);
|
|
continue;
|
|
}
|
|
|
|
if (tt !== IDENTIFIER)
|
|
throw t.newSyntaxError("missing variable name");
|
|
|
|
b.DECL$setName(n2, t.token.value);
|
|
b.DECL$setReadOnly(n2, n.type === CONST);
|
|
addDecl.call(b, n, n2, s);
|
|
|
|
if (t.match(ASSIGN)) {
|
|
if (t.token.assignOp)
|
|
throw t.newSyntaxError("Invalid variable initialization");
|
|
|
|
// Parse the init as a normal assignment with a fake lhs.
|
|
var id = new Node(n2.tokenizer, IDENTIFIER);
|
|
var n3 = b.ASSIGN$build(t);
|
|
id.name = id.value = n2.name;
|
|
b.ASSIGN$addOperand(n3, id);
|
|
b.ASSIGN$addOperand(n3, AssignExpression(t, x));
|
|
b.ASSIGN$finish(n3);
|
|
initializers.push(n3);
|
|
|
|
// But only add the rhs as the initializer.
|
|
b.DECL$setInitializer(n2, n3[1]);
|
|
}
|
|
|
|
b.DECL$finish(n2);
|
|
s.varDecls.push(n2);
|
|
} while (t.match(COMMA));
|
|
finish.call(b, n);
|
|
return n;
|
|
}
|
|
|
|
/*
|
|
* LetBlock :: (tokenizer, compiler context, boolean) -> node
|
|
*
|
|
* Does not handle let inside of for loop init.
|
|
*/
|
|
function LetBlock(t, x, isStatement) {
|
|
var n, n2, binds;
|
|
var b = x.builder;
|
|
|
|
// t.token.type must be LET
|
|
n = b.LET_BLOCK$build(t);
|
|
t.mustMatch(LEFT_PAREN);
|
|
b.LET_BLOCK$setVariables(n, Variables(t, x, n));
|
|
t.mustMatch(RIGHT_PAREN);
|
|
|
|
if (isStatement && t.peek() !== LEFT_CURLY) {
|
|
/*
|
|
* If this is really an expression in let statement guise, then we
|
|
* need to wrap the LET_BLOCK node in a SEMICOLON node so that we pop
|
|
* the return value of the expression.
|
|
*/
|
|
n2 = b.SEMICOLON$build(t);
|
|
b.SEMICOLON$setExpression(n2, n);
|
|
b.SEMICOLON$finish(n2);
|
|
isStatement = false;
|
|
}
|
|
|
|
if (isStatement) {
|
|
n2 = Block(t, x);
|
|
b.LET_BLOCK$setBlock(n, n2);
|
|
} else {
|
|
n2 = AssignExpression(t, x);
|
|
b.LET_BLOCK$setExpression(n, n2);
|
|
}
|
|
|
|
b.LET_BLOCK$finish(n);
|
|
|
|
return n;
|
|
}
|
|
|
|
function checkDestructuring(t, x, n, simpleNamesOnly, data) {
|
|
if (n.type === ARRAY_COMP)
|
|
throw t.newSyntaxError("Invalid array comprehension left-hand side");
|
|
if (n.type !== ARRAY_INIT && n.type !== OBJECT_INIT)
|
|
return;
|
|
|
|
var b = x.builder;
|
|
|
|
for (var i = 0, j = n.length; i < j; i++) {
|
|
var nn = n[i], lhs, rhs;
|
|
if (!nn)
|
|
continue;
|
|
if (nn.type === PROPERTY_INIT)
|
|
lhs = nn[0], rhs = nn[1];
|
|
else
|
|
lhs = null, rhs = null;
|
|
if (rhs && (rhs.type === ARRAY_INIT || rhs.type === OBJECT_INIT))
|
|
checkDestructuring(t, x, rhs, simpleNamesOnly, data);
|
|
if (lhs && simpleNamesOnly) {
|
|
// In declarations, lhs must be simple names
|
|
if (lhs.type !== IDENTIFIER) {
|
|
throw t.newSyntaxError("missing name in pattern");
|
|
} else if (data) {
|
|
var n2 = b.DECL$build(t);
|
|
b.DECL$setName(n2, lhs.value);
|
|
// Don't need to set initializer because it's just for
|
|
// hoisting anyways.
|
|
b.DECL$finish(n2);
|
|
// Each pattern needs to be added to varDecls.
|
|
data.varDecls.push(n2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function DestructuringExpression(t, x, simpleNamesOnly, data) {
|
|
var n = PrimaryExpression(t, x);
|
|
checkDestructuring(t, x, n, simpleNamesOnly, data);
|
|
return n;
|
|
}
|
|
|
|
function GeneratorExpression(t, x, e) {
|
|
var n;
|
|
|
|
n = b.GENERATOR$build(t);
|
|
b.GENERATOR$setExpression(n, e);
|
|
b.GENERATOR$setTail(n, comprehensionTail(t, x));
|
|
b.GENERATOR$finish(n);
|
|
|
|
return n;
|
|
}
|
|
|
|
function comprehensionTail(t, x) {
|
|
var body, n;
|
|
var b = x.builder;
|
|
// t.token.type must be FOR
|
|
body = b.COMP_TAIL$build(t);
|
|
|
|
do {
|
|
n = b.FOR$build(t);
|
|
// Comprehension tails are always for..in loops.
|
|
b.FOR$rebuildForIn(n);
|
|
if (t.match(IDENTIFIER)) {
|
|
// But sometimes they're for each..in.
|
|
if (t.token.value === "each")
|
|
b.FOR$rebuildForEach(n);
|
|
else
|
|
t.unget();
|
|
}
|
|
t.mustMatch(LEFT_PAREN);
|
|
switch(t.get()) {
|
|
case LEFT_BRACKET:
|
|
case LEFT_CURLY:
|
|
t.unget();
|
|
// Destructured left side of for in comprehension tails.
|
|
b.FOR$setIterator(n, DestructuringExpression(t, x), null);
|
|
break;
|
|
|
|
case IDENTIFIER:
|
|
var n3 = b.DECL$build(t);
|
|
b.DECL$setName(n3, n3.value);
|
|
b.DECL$finish(n3);
|
|
var n2 = b.VAR$build(t);
|
|
b.VAR$addDecl(n2, n3);
|
|
b.VAR$finish(n2);
|
|
b.FOR$setIterator(n, n3, n2);
|
|
/*
|
|
* Don't add to varDecls since the semantics of comprehensions is
|
|
* such that the variables are in their own function when
|
|
* desugared.
|
|
*/
|
|
break;
|
|
|
|
default:
|
|
throw t.newSyntaxError("missing identifier");
|
|
}
|
|
t.mustMatch(IN);
|
|
b.FOR$setObject(n, Expression(t, x));
|
|
t.mustMatch(RIGHT_PAREN);
|
|
b.COMP_TAIL$addFor(body, n);
|
|
} while (t.match(FOR));
|
|
|
|
// Optional guard.
|
|
if (t.match(IF))
|
|
b.COMP_TAIL$setGuard(body, ParenExpression(t, x));
|
|
|
|
b.COMP_TAIL$finish(body);
|
|
return body;
|
|
}
|
|
|
|
function ParenExpression(t, x) {
|
|
t.mustMatch(LEFT_PAREN);
|
|
|
|
/*
|
|
* Always accept the 'in' operator in a parenthesized expression,
|
|
* where it's unambiguous, even if we might be parsing the init of a
|
|
* for statement.
|
|
*/
|
|
var oldLoopInit = x.inForLoopInit;
|
|
x.inForLoopInit = false;
|
|
var n = Expression(t, x);
|
|
x.inForLoopInit = oldLoopInit;
|
|
|
|
var err = "expression must be parenthesized";
|
|
if (t.match(FOR)) {
|
|
if (n.type === YIELD && !n.parenthesized)
|
|
throw t.newSyntaxError("Yield " + err);
|
|
if (n.type === COMMA && !n.parenthesized)
|
|
throw t.newSyntaxError("Generator " + err);
|
|
n = GeneratorExpression(t, x, n);
|
|
}
|
|
|
|
t.mustMatch(RIGHT_PAREN);
|
|
|
|
return n;
|
|
}
|
|
|
|
/*
|
|
* Expression: (tokenizer, compiler context) -> node
|
|
*
|
|
* Top-down expression parser matched against SpiderMonkey.
|
|
*/
|
|
function Expression(t, x) {
|
|
var n, n2;
|
|
var b = x.builder;
|
|
|
|
n = AssignExpression(t, x);
|
|
if (t.match(COMMA)) {
|
|
n2 = b.COMMA$build(t);
|
|
b.COMMA$addOperand(n2, n);
|
|
n = n2;
|
|
do {
|
|
n2 = n[n.length-1];
|
|
if (n2.type === YIELD && !n2.parenthesized)
|
|
throw t.newSyntaxError("Yield expression must be parenthesized");
|
|
b.COMMA$addOperand(n, AssignExpression(t, x));
|
|
} while (t.match(COMMA));
|
|
b.COMMA$finish(n);
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
function AssignExpression(t, x) {
|
|
var n, lhs;
|
|
var b = x.builder;
|
|
|
|
// Have to treat yield like an operand because it could be the leftmost
|
|
// operand of the expression.
|
|
if (t.match(YIELD, true))
|
|
return returnOrYield(t, x);
|
|
|
|
n = b.ASSIGN$build(t);
|
|
lhs = ConditionalExpression(t, x);
|
|
|
|
if (!t.match(ASSIGN)) {
|
|
b.ASSIGN$finish(n);
|
|
return lhs;
|
|
}
|
|
|
|
switch (lhs.type) {
|
|
case OBJECT_INIT:
|
|
case ARRAY_INIT:
|
|
checkDestructuring(t, x, lhs);
|
|
// FALL THROUGH
|
|
case IDENTIFIER: case DOT: case INDEX: case CALL:
|
|
break;
|
|
default:
|
|
throw t.newSyntaxError("Bad left-hand side of assignment");
|
|
break;
|
|
}
|
|
|
|
b.ASSIGN$setAssignOp(n, t.token.assignOp);
|
|
b.ASSIGN$addOperand(n, lhs);
|
|
b.ASSIGN$addOperand(n, AssignExpression(t, x));
|
|
b.ASSIGN$finish(n);
|
|
|
|
return n;
|
|
}
|
|
|
|
function ConditionalExpression(t, x) {
|
|
var n, n2;
|
|
var b = x.builder;
|
|
|
|
n = OrExpression(t, x);
|
|
if (t.match(HOOK)) {
|
|
n2 = n;
|
|
n = b.HOOK$build(t);
|
|
b.HOOK$setCondition(n, n2);
|
|
/*
|
|
* Always accept the 'in' operator in the middle clause of a ternary,
|
|
* where it's unambiguous, even if we might be parsing the init of a
|
|
* for statement.
|
|
*/
|
|
var oldLoopInit = x.inForLoopInit;
|
|
x.inForLoopInit = false;
|
|
b.HOOK$setThenPart(n, AssignExpression(t, x));
|
|
x.inForLoopInit = oldLoopInit;
|
|
if (!t.match(COLON))
|
|
throw t.newSyntaxError("missing : after ?");
|
|
b.HOOK$setElsePart(n, AssignExpression(t, x));
|
|
b.HOOK$finish(n);
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
function OrExpression(t, x) {
|
|
var n, n2;
|
|
var b = x.builder;
|
|
|
|
n = AndExpression(t, x);
|
|
while (t.match(OR)) {
|
|
n2 = b.OR$build(t);
|
|
b.OR$addOperand(n2, n);
|
|
b.OR$addOperand(n2, AndExpression(t, x));
|
|
b.OR$finish(n2);
|
|
n = n2;
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
function AndExpression(t, x) {
|
|
var n, n2;
|
|
var b = x.builder;
|
|
|
|
n = BitwiseOrExpression(t, x);
|
|
while (t.match(AND)) {
|
|
n2 = b.AND$build(t);
|
|
b.AND$addOperand(n2, n);
|
|
b.AND$addOperand(n2, BitwiseOrExpression(t, x));
|
|
b.AND$finish(n2);
|
|
n = n2;
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
function BitwiseOrExpression(t, x) {
|
|
var n, n2;
|
|
var b = x.builder;
|
|
|
|
n = BitwiseXorExpression(t, x);
|
|
while (t.match(BITWISE_OR)) {
|
|
n2 = b.BITWISE_OR$build(t);
|
|
b.BITWISE_OR$addOperand(n2, n);
|
|
b.BITWISE_OR$addOperand(n2, BitwiseXorExpression(t, x));
|
|
b.BITWISE_OR$finish(n2);
|
|
n = n2;
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
function BitwiseXorExpression(t, x) {
|
|
var n, n2;
|
|
var b = x.builder;
|
|
|
|
n = BitwiseAndExpression(t, x);
|
|
while (t.match(BITWISE_XOR)) {
|
|
n2 = b.BITWISE_XOR$build(t);
|
|
b.BITWISE_XOR$addOperand(n2, n);
|
|
b.BITWISE_XOR$addOperand(n2, BitwiseAndExpression(t, x));
|
|
b.BITWISE_XOR$finish(n2);
|
|
n = n2;
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
function BitwiseAndExpression(t, x) {
|
|
var n, n2;
|
|
var b = x.builder;
|
|
|
|
n = EqualityExpression(t, x);
|
|
while (t.match(BITWISE_AND)) {
|
|
n2 = b.BITWISE_AND$build(t);
|
|
b.BITWISE_AND$addOperand(n2, n);
|
|
b.BITWISE_AND$addOperand(n2, EqualityExpression(t, x));
|
|
b.BITWISE_AND$finish(n2);
|
|
n = n2;
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
function EqualityExpression(t, x) {
|
|
var n, n2;
|
|
var b = x.builder;
|
|
|
|
n = RelationalExpression(t, x);
|
|
while (t.match(EQ) || t.match(NE) ||
|
|
t.match(STRICT_EQ) || t.match(STRICT_NE)) {
|
|
n2 = b.EQUALITY$build(t);
|
|
b.EQUALITY$addOperand(n2, n);
|
|
b.EQUALITY$addOperand(n2, RelationalExpression(t, x));
|
|
b.EQUALITY$finish(n2);
|
|
n = n2;
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
function RelationalExpression(t, x) {
|
|
var n, n2;
|
|
var b = x.builder;
|
|
var oldLoopInit = x.inForLoopInit;
|
|
|
|
/*
|
|
* Uses of the in operator in shiftExprs are always unambiguous,
|
|
* so unset the flag that prohibits recognizing it.
|
|
*/
|
|
x.inForLoopInit = false;
|
|
n = ShiftExpression(t, x);
|
|
while ((t.match(LT) || t.match(LE) || t.match(GE) || t.match(GT) ||
|
|
(oldLoopInit === false && t.match(IN)) ||
|
|
t.match(INSTANCEOF))) {
|
|
n2 = b.RELATIONAL$build(t);
|
|
b.RELATIONAL$addOperand(n2, n);
|
|
b.RELATIONAL$addOperand(n2, ShiftExpression(t, x));
|
|
b.RELATIONAL$finish(n2);
|
|
n = n2;
|
|
}
|
|
x.inForLoopInit = oldLoopInit;
|
|
|
|
return n;
|
|
}
|
|
|
|
function ShiftExpression(t, x) {
|
|
var n, n2;
|
|
var b = x.builder;
|
|
|
|
n = AddExpression(t, x);
|
|
while (t.match(LSH) || t.match(RSH) || t.match(URSH)) {
|
|
n2 = b.SHIFT$build(t);
|
|
b.SHIFT$addOperand(n2, n);
|
|
b.SHIFT$addOperand(n2, AddExpression(t, x));
|
|
b.SHIFT$finish(n2);
|
|
n = n2;
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
function AddExpression(t, x) {
|
|
var n, n2;
|
|
var b = x.builder;
|
|
|
|
n = MultiplyExpression(t, x);
|
|
while (t.match(PLUS) || t.match(MINUS)) {
|
|
n2 = b.ADD$build(t);
|
|
b.ADD$addOperand(n2, n);
|
|
b.ADD$addOperand(n2, MultiplyExpression(t, x));
|
|
b.ADD$finish(n2);
|
|
n = n2;
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
function MultiplyExpression(t, x) {
|
|
var n, n2;
|
|
var b = x.builder;
|
|
|
|
n = UnaryExpression(t, x);
|
|
while (t.match(MUL) || t.match(DIV) || t.match(MOD)) {
|
|
n2 = b.MULTIPLY$build(t);
|
|
b.MULTIPLY$addOperand(n2, n);
|
|
b.MULTIPLY$addOperand(n2, UnaryExpression(t, x));
|
|
b.MULTIPLY$finish(n2);
|
|
n = n2;
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
function UnaryExpression(t, x) {
|
|
var n, n2, tt;
|
|
var b = x.builder;
|
|
|
|
switch (tt = t.get(true)) {
|
|
case DELETE: case VOID: case TYPEOF:
|
|
case NOT: case BITWISE_NOT: case PLUS: case MINUS:
|
|
n = b.UNARY$build(t);
|
|
b.UNARY$addOperand(n, UnaryExpression(t, x));
|
|
break;
|
|
|
|
case INCREMENT:
|
|
case DECREMENT:
|
|
// Prefix increment/decrement.
|
|
n = b.UNARY$build(t)
|
|
b.UNARY$addOperand(n, MemberExpression(t, x, true));
|
|
break;
|
|
|
|
default:
|
|
t.unget();
|
|
n = MemberExpression(t, x, true);
|
|
|
|
// Don't look across a newline boundary for a postfix {in,de}crement.
|
|
if (t.tokens[(t.tokenIndex + t.lookahead - 1) & 3].lineno ===
|
|
t.lineno) {
|
|
if (t.match(INCREMENT) || t.match(DECREMENT)) {
|
|
n2 = b.UNARY$build(t);
|
|
b.UNARY$setPostfix(n2);
|
|
b.UNARY$finish(n);
|
|
b.UNARY$addOperand(n2, n);
|
|
n = n2;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
b.UNARY$finish(n);
|
|
return n;
|
|
}
|
|
|
|
function MemberExpression(t, x, allowCallSyntax) {
|
|
var n, n2, tt;
|
|
var b = x.builder;
|
|
|
|
if (t.match(NEW)) {
|
|
n = b.MEMBER$build(t);
|
|
b.MEMBER$addOperand(n, MemberExpression(t, x, false));
|
|
if (t.match(LEFT_PAREN)) {
|
|
b.MEMBER$rebuildNewWithArgs(n);
|
|
b.MEMBER$addOperand(n, ArgumentList(t, x));
|
|
}
|
|
b.MEMBER$finish(n);
|
|
} else {
|
|
n = PrimaryExpression(t, x);
|
|
}
|
|
|
|
while ((tt = t.get()) !== END) {
|
|
switch (tt) {
|
|
case DOT:
|
|
n2 = b.MEMBER$build(t);
|
|
b.MEMBER$addOperand(n2, n);
|
|
t.mustMatch(IDENTIFIER);
|
|
b.MEMBER$addOperand(n2, b.MEMBER$build(t));
|
|
break;
|
|
|
|
case LEFT_BRACKET:
|
|
n2 = b.MEMBER$build(t, INDEX);
|
|
b.MEMBER$addOperand(n2, n);
|
|
b.MEMBER$addOperand(n2, Expression(t, x));
|
|
t.mustMatch(RIGHT_BRACKET);
|
|
break;
|
|
|
|
case LEFT_PAREN:
|
|
if (allowCallSyntax) {
|
|
n2 = b.MEMBER$build(t, CALL);
|
|
b.MEMBER$addOperand(n2, n);
|
|
b.MEMBER$addOperand(n2, ArgumentList(t, x));
|
|
break;
|
|
}
|
|
|
|
// FALL THROUGH
|
|
default:
|
|
t.unget();
|
|
return n;
|
|
}
|
|
|
|
b.MEMBER$finish(n2);
|
|
n = n2;
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
function ArgumentList(t, x) {
|
|
var n, n2;
|
|
var b = x.builder;
|
|
var err = "expression must be parenthesized";
|
|
|
|
n = b.LIST$build(t);
|
|
if (t.match(RIGHT_PAREN, true))
|
|
return n;
|
|
do {
|
|
n2 = AssignExpression(t, x);
|
|
if (n2.type === YIELD && !n2.parenthesized && t.peek() === COMMA)
|
|
throw t.newSyntaxError("Yield " + err);
|
|
if (t.match(FOR)) {
|
|
n2 = GeneratorExpression(t, x, n2);
|
|
if (n.length > 1 || t.peek(true) === COMMA)
|
|
throw t.newSyntaxError("Generator " + err);
|
|
}
|
|
b.LIST$addOperand(n, n2);
|
|
} while (t.match(COMMA));
|
|
t.mustMatch(RIGHT_PAREN);
|
|
b.LIST$finish(n);
|
|
|
|
return n;
|
|
}
|
|
|
|
function PrimaryExpression(t, x) {
|
|
var n, n2, n3, tt = t.get(true);
|
|
var b = x.builder;
|
|
|
|
switch (tt) {
|
|
case FUNCTION:
|
|
n = FunctionDefinition(t, x, false, EXPRESSED_FORM);
|
|
break;
|
|
|
|
case LEFT_BRACKET:
|
|
n = b.ARRAY_INIT$build(t);
|
|
while ((tt = t.peek()) !== RIGHT_BRACKET) {
|
|
if (tt === COMMA) {
|
|
t.get();
|
|
b.ARRAY_INIT$addElement(n, null);
|
|
continue;
|
|
}
|
|
b.ARRAY_INIT$addElement(n, AssignExpression(t, x));
|
|
if (tt !== COMMA && !t.match(COMMA))
|
|
break;
|
|
}
|
|
|
|
// If we matched exactly one element and got a FOR, we have an
|
|
// array comprehension.
|
|
if (n.length === 1 && t.match(FOR)) {
|
|
n2 = b.ARRAY_COMP$build(t);
|
|
b.ARRAY_COMP$setExpression(n2, n[0]);
|
|
b.ARRAY_COMP$setTail(n2, comprehensionTail(t, x));
|
|
n = n2;
|
|
}
|
|
t.mustMatch(RIGHT_BRACKET);
|
|
b.PRIMARY$finish(n);
|
|
break;
|
|
|
|
case LEFT_CURLY:
|
|
var id;
|
|
n = b.OBJECT_INIT$build(t);
|
|
|
|
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.ecma3OnlyMode)
|
|
throw t.newSyntaxError("Illegal property accessor");
|
|
var fd = FunctionDefinition(t, x, true, EXPRESSED_FORM);
|
|
b.OBJECT_INIT$addProperty(n, fd);
|
|
} else {
|
|
switch (tt) {
|
|
case IDENTIFIER: case NUMBER: case STRING:
|
|
id = b.PRIMARY$build(t, IDENTIFIER);
|
|
b.PRIMARY$finish(id);
|
|
break;
|
|
case RIGHT_CURLY:
|
|
if (x.ecma3OnlyMode)
|
|
throw t.newSyntaxError("Illegal trailing ,");
|
|
break object_init;
|
|
default:
|
|
if (t.token.value in definitions.keywords) {
|
|
id = b.PRIMARY$build(t, IDENTIFIER);
|
|
b.PRIMARY$finish(id);
|
|
break;
|
|
}
|
|
throw t.newSyntaxError("Invalid property name");
|
|
}
|
|
if (t.match(COLON)) {
|
|
n2 = b.PROPERTY_INIT$build(t);
|
|
b.PROPERTY_INIT$addOperand(n2, id);
|
|
b.PROPERTY_INIT$addOperand(n2, AssignExpression(t, x));
|
|
b.PROPERTY_INIT$finish(n2);
|
|
b.OBJECT_INIT$addProperty(n, n2);
|
|
} else {
|
|
// Support, e.g., |var {x, y} = o| as destructuring shorthand
|
|
// for |var {x: x, y: y} = o|, per proposed JS2/ES4 for JS1.8.
|
|
if (t.peek() !== COMMA && t.peek() !== RIGHT_CURLY)
|
|
throw t.newSyntaxError("missing : after property");
|
|
b.OBJECT_INIT$addProperty(n, id);
|
|
}
|
|
}
|
|
} while (t.match(COMMA));
|
|
t.mustMatch(RIGHT_CURLY);
|
|
}
|
|
b.OBJECT_INIT$finish(n);
|
|
break;
|
|
|
|
case LEFT_PAREN:
|
|
// ParenExpression does its own matching on parentheses, so we need to
|
|
// unget.
|
|
t.unget();
|
|
n = ParenExpression(t, x);
|
|
n.parenthesized = true;
|
|
break;
|
|
|
|
case LET:
|
|
n = LetBlock(t, x, false);
|
|
break;
|
|
|
|
case NULL: case THIS: case TRUE: case FALSE:
|
|
case IDENTIFIER: case NUMBER: case STRING: case REGEXP:
|
|
n = b.PRIMARY$build(t);
|
|
b.PRIMARY$finish(n);
|
|
break;
|
|
|
|
default:
|
|
throw t.newSyntaxError("missing operand");
|
|
break;
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
/*
|
|
* parse :: (builder, file ptr, path, line number) -> node
|
|
*/
|
|
function parse(b, s, f, l) {
|
|
var t = new lexer.Tokenizer(s, f, l);
|
|
var x = new StaticContext(false, b);
|
|
var n = Script(t, x);
|
|
if (!t.done)
|
|
throw t.newSyntaxError("Syntax error");
|
|
|
|
return n;
|
|
}
|
|
|
|
return {
|
|
parse: parse,
|
|
VanillaBuilder: VanillaBuilder,
|
|
DECLARED_FORM: DECLARED_FORM,
|
|
EXPRESSED_FORM: EXPRESSED_FORM,
|
|
STATEMENT_FORM: STATEMENT_FORM,
|
|
Tokenizer: lexer.Tokenizer,
|
|
FunctionDefinition: FunctionDefinition
|
|
};
|
|
|
|
}());
|