1608 lines
53 KiB
Java
1608 lines
53 KiB
Java
|
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||
|
*
|
||
|
* ***** BEGIN LICENSE BLOCK *****
|
||
|
* Version: MPL 1.1/GPL 2.0
|
||
|
*
|
||
|
* 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 Rhino code, released
|
||
|
* May 6, 1999.
|
||
|
*
|
||
|
* The Initial Developer of the Original Code is
|
||
|
* Netscape Communications Corporation.
|
||
|
* Portions created by the Initial Developer are Copyright (C) 1997-1999
|
||
|
* the Initial Developer. All Rights Reserved.
|
||
|
*
|
||
|
* Contributor(s):
|
||
|
* Norris Boyd
|
||
|
* Igor Bukanov
|
||
|
* Ethan Hugg
|
||
|
* Bob Jervis
|
||
|
* Terry Lucas
|
||
|
* Milen Nankov
|
||
|
*
|
||
|
* Alternatively, the contents of this file may be used under the terms of
|
||
|
* the GNU General Public License Version 2 or later (the "GPL"), in which
|
||
|
* case the provisions of the GPL are applicable instead of those above. If
|
||
|
* you wish to allow use of your version of this file only under the terms of
|
||
|
* the GPL and not to allow others to use your version of this file under the
|
||
|
* MPL, indicate your decision by deleting the provisions above and replacing
|
||
|
* them with the notice and other provisions required by the GPL. If you do
|
||
|
* not delete the provisions above, a recipient may use your version of this
|
||
|
* file under either the MPL or the GPL.
|
||
|
*
|
||
|
* ***** END LICENSE BLOCK ***** */
|
||
|
|
||
|
package org.mozilla.javascript;
|
||
|
|
||
|
import java.util.List;
|
||
|
import java.util.ArrayList;
|
||
|
|
||
|
/**
|
||
|
* This class allows the creation of nodes, and follows the Factory pattern.
|
||
|
*
|
||
|
* @see Node
|
||
|
* @author Mike McCabe
|
||
|
* @author Norris Boyd
|
||
|
*/
|
||
|
final class IRFactory
|
||
|
{
|
||
|
IRFactory(Parser parser)
|
||
|
{
|
||
|
this.parser = parser;
|
||
|
}
|
||
|
|
||
|
ScriptOrFnNode createScript()
|
||
|
{
|
||
|
return new ScriptOrFnNode(Token.SCRIPT);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Script (for associating file/url names with toplevel scripts.)
|
||
|
*/
|
||
|
void initScript(ScriptOrFnNode scriptNode, Node body)
|
||
|
{
|
||
|
Node children = body.getFirstChild();
|
||
|
if (children != null) { scriptNode.addChildrenToBack(children); }
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Leaf
|
||
|
*/
|
||
|
Node createLeaf(int nodeType)
|
||
|
{
|
||
|
return new Node(nodeType);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Statement leaf nodes.
|
||
|
*/
|
||
|
|
||
|
Node createSwitch(Node expr, int lineno)
|
||
|
{
|
||
|
//
|
||
|
// The switch will be rewritten from:
|
||
|
//
|
||
|
// switch (expr) {
|
||
|
// case test1: statements1;
|
||
|
// ...
|
||
|
// default: statementsDefault;
|
||
|
// ...
|
||
|
// case testN: statementsN;
|
||
|
// }
|
||
|
//
|
||
|
// to:
|
||
|
//
|
||
|
// {
|
||
|
// switch (expr) {
|
||
|
// case test1: goto label1;
|
||
|
// ...
|
||
|
// case testN: goto labelN;
|
||
|
// }
|
||
|
// goto labelDefault;
|
||
|
// label1:
|
||
|
// statements1;
|
||
|
// ...
|
||
|
// labelDefault:
|
||
|
// statementsDefault;
|
||
|
// ...
|
||
|
// labelN:
|
||
|
// statementsN;
|
||
|
// breakLabel:
|
||
|
// }
|
||
|
//
|
||
|
// where inside switch each "break;" without label will be replaced
|
||
|
// by "goto breakLabel".
|
||
|
//
|
||
|
// If the original switch does not have the default label, then
|
||
|
// the transformed code would contain after the switch instead of
|
||
|
// goto labelDefault;
|
||
|
// the following goto:
|
||
|
// goto breakLabel;
|
||
|
//
|
||
|
|
||
|
Node.Jump switchNode = new Node.Jump(Token.SWITCH, expr, lineno);
|
||
|
Node block = new Node(Token.BLOCK, switchNode);
|
||
|
return block;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* If caseExpression argument is null it indicate default label.
|
||
|
*/
|
||
|
void addSwitchCase(Node switchBlock, Node caseExpression, Node statements)
|
||
|
{
|
||
|
if (switchBlock.getType() != Token.BLOCK) throw Kit.codeBug();
|
||
|
Node.Jump switchNode = (Node.Jump)switchBlock.getFirstChild();
|
||
|
if (switchNode.getType() != Token.SWITCH) throw Kit.codeBug();
|
||
|
|
||
|
Node gotoTarget = Node.newTarget();
|
||
|
if (caseExpression != null) {
|
||
|
Node.Jump caseNode = new Node.Jump(Token.CASE, caseExpression);
|
||
|
caseNode.target = gotoTarget;
|
||
|
switchNode.addChildToBack(caseNode);
|
||
|
} else {
|
||
|
switchNode.setDefault(gotoTarget);
|
||
|
}
|
||
|
switchBlock.addChildToBack(gotoTarget);
|
||
|
switchBlock.addChildToBack(statements);
|
||
|
}
|
||
|
|
||
|
void closeSwitch(Node switchBlock)
|
||
|
{
|
||
|
if (switchBlock.getType() != Token.BLOCK) throw Kit.codeBug();
|
||
|
Node.Jump switchNode = (Node.Jump)switchBlock.getFirstChild();
|
||
|
if (switchNode.getType() != Token.SWITCH) throw Kit.codeBug();
|
||
|
|
||
|
Node switchBreakTarget = Node.newTarget();
|
||
|
// switchNode.target is only used by NodeTransformer
|
||
|
// to detect switch end
|
||
|
switchNode.target = switchBreakTarget;
|
||
|
|
||
|
Node defaultTarget = switchNode.getDefault();
|
||
|
if (defaultTarget == null) {
|
||
|
defaultTarget = switchBreakTarget;
|
||
|
}
|
||
|
|
||
|
switchBlock.addChildAfter(makeJump(Token.GOTO, defaultTarget),
|
||
|
switchNode);
|
||
|
switchBlock.addChildToBack(switchBreakTarget);
|
||
|
}
|
||
|
|
||
|
Node createVariables(int token, int lineno)
|
||
|
{
|
||
|
return new Node(token, lineno);
|
||
|
}
|
||
|
|
||
|
Node createExprStatement(Node expr, int lineno)
|
||
|
{
|
||
|
int type;
|
||
|
if (parser.insideFunction()) {
|
||
|
type = Token.EXPR_VOID;
|
||
|
} else {
|
||
|
type = Token.EXPR_RESULT;
|
||
|
}
|
||
|
return new Node(type, expr, lineno);
|
||
|
}
|
||
|
|
||
|
Node createExprStatementNoReturn(Node expr, int lineno)
|
||
|
{
|
||
|
return new Node(Token.EXPR_VOID, expr, lineno);
|
||
|
}
|
||
|
|
||
|
Node createDefaultNamespace(Node expr, int lineno)
|
||
|
{
|
||
|
// default xml namespace requires activation
|
||
|
setRequiresActivation();
|
||
|
Node n = createUnary(Token.DEFAULTNAMESPACE, expr);
|
||
|
Node result = createExprStatement(n, lineno);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Name
|
||
|
*/
|
||
|
Node createName(String name)
|
||
|
{
|
||
|
checkActivationName(name, Token.NAME);
|
||
|
return Node.newString(Token.NAME, name);
|
||
|
}
|
||
|
|
||
|
private Node createName(int type, String name, Node child)
|
||
|
{
|
||
|
Node result = createName(name);
|
||
|
result.setType(type);
|
||
|
if (child != null)
|
||
|
result.addChildToBack(child);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* String (for literals)
|
||
|
*/
|
||
|
Node createString(String string)
|
||
|
{
|
||
|
return Node.newString(string);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Number (for literals)
|
||
|
*/
|
||
|
Node createNumber(double number)
|
||
|
{
|
||
|
return Node.newNumber(number);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Catch clause of try/catch/finally
|
||
|
* @param varName the name of the variable to bind to the exception
|
||
|
* @param catchCond the condition under which to catch the exception.
|
||
|
* May be null if no condition is given.
|
||
|
* @param stmts the statements in the catch clause
|
||
|
* @param lineno the starting line number of the catch clause
|
||
|
*/
|
||
|
Node createCatch(String varName, Node catchCond, Node stmts, int lineno)
|
||
|
{
|
||
|
if (catchCond == null) {
|
||
|
catchCond = new Node(Token.EMPTY);
|
||
|
}
|
||
|
return new Node(Token.CATCH, createName(varName),
|
||
|
catchCond, stmts, lineno);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Throw
|
||
|
*/
|
||
|
Node createThrow(Node expr, int lineno)
|
||
|
{
|
||
|
return new Node(Token.THROW, expr, lineno);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return
|
||
|
*/
|
||
|
Node createReturn(Node expr, int lineno)
|
||
|
{
|
||
|
return expr == null
|
||
|
? new Node(Token.RETURN, lineno)
|
||
|
: new Node(Token.RETURN, expr, lineno);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Debugger
|
||
|
*/
|
||
|
Node createDebugger(int lineno)
|
||
|
{
|
||
|
return new Node(Token.DEBUGGER, lineno);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Label
|
||
|
*/
|
||
|
Node createLabel(int lineno)
|
||
|
{
|
||
|
return new Node.Jump(Token.LABEL, lineno);
|
||
|
}
|
||
|
|
||
|
Node getLabelLoop(Node label)
|
||
|
{
|
||
|
return ((Node.Jump)label).getLoop();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Label
|
||
|
*/
|
||
|
Node createLabeledStatement(Node labelArg, Node statement)
|
||
|
{
|
||
|
Node.Jump label = (Node.Jump)labelArg;
|
||
|
|
||
|
// Make a target and put it _after_ the statement
|
||
|
// node. And in the LABEL node, so breaks get the
|
||
|
// right target.
|
||
|
|
||
|
Node breakTarget = Node.newTarget();
|
||
|
Node block = new Node(Token.BLOCK, label, statement, breakTarget);
|
||
|
label.target = breakTarget;
|
||
|
|
||
|
return block;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Break (possibly labeled)
|
||
|
*/
|
||
|
Node createBreak(Node breakStatement, int lineno)
|
||
|
{
|
||
|
Node.Jump n = new Node.Jump(Token.BREAK, lineno);
|
||
|
Node.Jump jumpStatement;
|
||
|
int t = breakStatement.getType();
|
||
|
if (t == Token.LOOP || t == Token.LABEL) {
|
||
|
jumpStatement = (Node.Jump)breakStatement;
|
||
|
} else if (t == Token.BLOCK
|
||
|
&& breakStatement.getFirstChild().getType() == Token.SWITCH)
|
||
|
{
|
||
|
jumpStatement = (Node.Jump)breakStatement.getFirstChild();
|
||
|
} else {
|
||
|
throw Kit.codeBug();
|
||
|
}
|
||
|
n.setJumpStatement(jumpStatement);
|
||
|
return n;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Continue (possibly labeled)
|
||
|
*/
|
||
|
Node createContinue(Node loop, int lineno)
|
||
|
{
|
||
|
if (loop.getType() != Token.LOOP) Kit.codeBug();
|
||
|
Node.Jump n = new Node.Jump(Token.CONTINUE, lineno);
|
||
|
n.setJumpStatement((Node.Jump)loop);
|
||
|
return n;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Statement block
|
||
|
* Creates the empty statement block
|
||
|
* Must make subsequent calls to add statements to the node
|
||
|
*/
|
||
|
Node createBlock(int lineno)
|
||
|
{
|
||
|
return new Node(Token.BLOCK, lineno);
|
||
|
}
|
||
|
|
||
|
FunctionNode createFunction(String name)
|
||
|
{
|
||
|
return new FunctionNode(name);
|
||
|
}
|
||
|
|
||
|
Node initFunction(FunctionNode fnNode, int functionIndex,
|
||
|
Node statements, int functionType)
|
||
|
{
|
||
|
fnNode.itsFunctionType = functionType;
|
||
|
fnNode.addChildToBack(statements);
|
||
|
|
||
|
int functionCount = fnNode.getFunctionCount();
|
||
|
if (functionCount != 0) {
|
||
|
// Functions containing other functions require activation objects
|
||
|
fnNode.itsNeedsActivation = true;
|
||
|
}
|
||
|
|
||
|
if (functionType == FunctionNode.FUNCTION_EXPRESSION) {
|
||
|
String name = fnNode.getFunctionName();
|
||
|
if (name != null && name.length() != 0) {
|
||
|
// A function expression needs to have its name as a
|
||
|
// variable (if it isn't already allocated as a variable).
|
||
|
// See ECMA Ch. 13. We add code to the beginning of the
|
||
|
// function to initialize a local variable of the
|
||
|
// function's name to the function value.
|
||
|
Node setFn = new Node(Token.EXPR_VOID,
|
||
|
new Node(Token.SETNAME,
|
||
|
Node.newString(Token.BINDNAME, name),
|
||
|
new Node(Token.THISFN)));
|
||
|
statements.addChildrenToFront(setFn);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Add return to end if needed.
|
||
|
Node lastStmt = statements.getLastChild();
|
||
|
if (lastStmt == null || lastStmt.getType() != Token.RETURN) {
|
||
|
statements.addChildToBack(new Node(Token.RETURN));
|
||
|
}
|
||
|
|
||
|
Node result = Node.newString(Token.FUNCTION,
|
||
|
fnNode.getFunctionName());
|
||
|
result.putIntProp(Node.FUNCTION_PROP, functionIndex);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Add a child to the back of the given node. This function
|
||
|
* breaks the Factory abstraction, but it removes a requirement
|
||
|
* from implementors of Node.
|
||
|
*/
|
||
|
void addChildToBack(Node parent, Node child)
|
||
|
{
|
||
|
parent.addChildToBack(child);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Create a node that can be used to hold lexically scoped variable
|
||
|
* definitions (via let declarations).
|
||
|
*
|
||
|
* @param token the token of the node to create
|
||
|
* @param lineno line number of source
|
||
|
* @return the created node
|
||
|
*/
|
||
|
Node createScopeNode(int token, int lineno) {
|
||
|
return new Node.Scope(token, lineno);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Create loop node. The parser will later call
|
||
|
* createWhile|createDoWhile|createFor|createForIn
|
||
|
* to finish loop generation.
|
||
|
*/
|
||
|
Node createLoopNode(Node loopLabel, int lineno)
|
||
|
{
|
||
|
Node.Jump result = new Node.Scope(Token.LOOP, lineno);
|
||
|
if (loopLabel != null) {
|
||
|
((Node.Jump)loopLabel).setLoop(result);
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* While
|
||
|
*/
|
||
|
Node createWhile(Node loop, Node cond, Node body)
|
||
|
{
|
||
|
return createLoop((Node.Jump)loop, LOOP_WHILE, body, cond,
|
||
|
null, null);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* DoWhile
|
||
|
*/
|
||
|
Node createDoWhile(Node loop, Node body, Node cond)
|
||
|
{
|
||
|
return createLoop((Node.Jump)loop, LOOP_DO_WHILE, body, cond,
|
||
|
null, null);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* For
|
||
|
*/
|
||
|
Node createFor(Node loop, Node init, Node test, Node incr, Node body)
|
||
|
{
|
||
|
if (init.getType() == Token.LET) {
|
||
|
// rewrite "for (let i=s; i < N; i++)..." as
|
||
|
// "let (i=s) { for (; i < N; i++)..." so that "s" is evaluated
|
||
|
// outside the scope of the for.
|
||
|
Node.Scope let = Node.Scope.splitScope((Node.Scope)loop);
|
||
|
let.setType(Token.LET);
|
||
|
let.addChildrenToBack(init);
|
||
|
let.addChildToBack(createLoop((Node.Jump)loop, LOOP_FOR, body, test,
|
||
|
new Node(Token.EMPTY), incr));
|
||
|
return let;
|
||
|
}
|
||
|
return createLoop((Node.Jump)loop, LOOP_FOR, body, test, init, incr);
|
||
|
}
|
||
|
|
||
|
private Node createLoop(Node.Jump loop, int loopType, Node body, Node cond,
|
||
|
Node init, Node incr)
|
||
|
{
|
||
|
Node bodyTarget = Node.newTarget();
|
||
|
Node condTarget = Node.newTarget();
|
||
|
if (loopType == LOOP_FOR && cond.getType() == Token.EMPTY) {
|
||
|
cond = new Node(Token.TRUE);
|
||
|
}
|
||
|
Node.Jump IFEQ = new Node.Jump(Token.IFEQ, cond);
|
||
|
IFEQ.target = bodyTarget;
|
||
|
Node breakTarget = Node.newTarget();
|
||
|
|
||
|
loop.addChildToBack(bodyTarget);
|
||
|
loop.addChildrenToBack(body);
|
||
|
if (loopType == LOOP_WHILE || loopType == LOOP_FOR) {
|
||
|
// propagate lineno to condition
|
||
|
loop.addChildrenToBack(new Node(Token.EMPTY, loop.getLineno()));
|
||
|
}
|
||
|
loop.addChildToBack(condTarget);
|
||
|
loop.addChildToBack(IFEQ);
|
||
|
loop.addChildToBack(breakTarget);
|
||
|
|
||
|
loop.target = breakTarget;
|
||
|
Node continueTarget = condTarget;
|
||
|
|
||
|
if (loopType == LOOP_WHILE || loopType == LOOP_FOR) {
|
||
|
// Just add a GOTO to the condition in the do..while
|
||
|
loop.addChildToFront(makeJump(Token.GOTO, condTarget));
|
||
|
|
||
|
if (loopType == LOOP_FOR) {
|
||
|
int initType = init.getType();
|
||
|
if (initType != Token.EMPTY) {
|
||
|
if (initType != Token.VAR && initType != Token.LET) {
|
||
|
init = new Node(Token.EXPR_VOID, init);
|
||
|
}
|
||
|
loop.addChildToFront(init);
|
||
|
}
|
||
|
Node incrTarget = Node.newTarget();
|
||
|
loop.addChildAfter(incrTarget, body);
|
||
|
if (incr.getType() != Token.EMPTY) {
|
||
|
incr = new Node(Token.EXPR_VOID, incr);
|
||
|
loop.addChildAfter(incr, incrTarget);
|
||
|
}
|
||
|
continueTarget = incrTarget;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
loop.setContinue(continueTarget);
|
||
|
|
||
|
return loop;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* For .. In
|
||
|
*
|
||
|
*/
|
||
|
Node createForIn(int declType, Node loop, Node lhs, Node obj, Node body,
|
||
|
boolean isForEach)
|
||
|
{
|
||
|
int destructuring = -1;
|
||
|
int destructuringLen = 0;
|
||
|
Node lvalue;
|
||
|
int type = lhs.getType();
|
||
|
if (type == Token.VAR || type == Token.LET) {
|
||
|
Node lastChild = lhs.getLastChild();
|
||
|
if (lhs.getFirstChild() != lastChild) {
|
||
|
/*
|
||
|
* check that there was only one variable given.
|
||
|
* we can't do this in the parser, because then the
|
||
|
* parser would have to know something about the
|
||
|
* 'init' node of the for-in loop.
|
||
|
*/
|
||
|
parser.reportError("msg.mult.index");
|
||
|
}
|
||
|
if (lastChild.getType() == Token.ARRAYLIT ||
|
||
|
lastChild.getType() == Token.OBJECTLIT)
|
||
|
{
|
||
|
type = destructuring = lastChild.getType();
|
||
|
lvalue = lastChild;
|
||
|
destructuringLen = lastChild.getIntProp(
|
||
|
Node.DESTRUCTURING_ARRAY_LENGTH, 0);
|
||
|
} else if (lastChild.getType() == Token.NAME) {
|
||
|
lvalue = Node.newString(Token.NAME, lastChild.getString());
|
||
|
} else {
|
||
|
parser.reportError("msg.bad.for.in.lhs");
|
||
|
return obj;
|
||
|
}
|
||
|
} else if (type == Token.ARRAYLIT || type == Token.OBJECTLIT) {
|
||
|
destructuring = type;
|
||
|
lvalue = lhs;
|
||
|
destructuringLen = lhs.getIntProp(Node.DESTRUCTURING_ARRAY_LENGTH, 0);
|
||
|
} else {
|
||
|
lvalue = makeReference(lhs);
|
||
|
if (lvalue == null) {
|
||
|
parser.reportError("msg.bad.for.in.lhs");
|
||
|
return obj;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Node localBlock = new Node(Token.LOCAL_BLOCK);
|
||
|
int initType = (isForEach) ? Token.ENUM_INIT_VALUES :
|
||
|
(destructuring != -1) ? Token.ENUM_INIT_ARRAY :
|
||
|
Token.ENUM_INIT_KEYS;
|
||
|
Node init = new Node(initType, obj);
|
||
|
init.putProp(Node.LOCAL_BLOCK_PROP, localBlock);
|
||
|
Node cond = new Node(Token.ENUM_NEXT);
|
||
|
cond.putProp(Node.LOCAL_BLOCK_PROP, localBlock);
|
||
|
Node id = new Node(Token.ENUM_ID);
|
||
|
id.putProp(Node.LOCAL_BLOCK_PROP, localBlock);
|
||
|
|
||
|
Node newBody = new Node(Token.BLOCK);
|
||
|
Node assign;
|
||
|
if (destructuring != -1) {
|
||
|
assign = createDestructuringAssignment(declType, lvalue, id);
|
||
|
if (!isForEach && (destructuring == Token.OBJECTLIT ||
|
||
|
destructuringLen != 2))
|
||
|
{
|
||
|
// destructuring assignment is only allowed in for..each or
|
||
|
// with an array type of length 2 (to hold key and value)
|
||
|
parser.reportError("msg.bad.for.in.destruct");
|
||
|
}
|
||
|
} else {
|
||
|
assign = simpleAssignment(lvalue, id);
|
||
|
}
|
||
|
newBody.addChildToBack(new Node(Token.EXPR_VOID, assign));
|
||
|
newBody.addChildToBack(body);
|
||
|
|
||
|
loop = createWhile(loop, cond, newBody);
|
||
|
loop.addChildToFront(init);
|
||
|
if (type == Token.VAR || type == Token.LET)
|
||
|
loop.addChildToFront(lhs);
|
||
|
localBlock.addChildToBack(loop);
|
||
|
|
||
|
return localBlock;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Try/Catch/Finally
|
||
|
*
|
||
|
* The IRFactory tries to express as much as possible in the tree;
|
||
|
* the responsibilities remaining for Codegen are to add the Java
|
||
|
* handlers: (Either (but not both) of TARGET and FINALLY might not
|
||
|
* be defined)
|
||
|
|
||
|
* - a catch handler for javascript exceptions that unwraps the
|
||
|
* exception onto the stack and GOTOes to the catch target
|
||
|
|
||
|
* - a finally handler
|
||
|
|
||
|
* ... and a goto to GOTO around these handlers.
|
||
|
*/
|
||
|
Node createTryCatchFinally(Node tryBlock, Node catchBlocks,
|
||
|
Node finallyBlock, int lineno)
|
||
|
{
|
||
|
boolean hasFinally = (finallyBlock != null)
|
||
|
&& (finallyBlock.getType() != Token.BLOCK
|
||
|
|| finallyBlock.hasChildren());
|
||
|
|
||
|
// short circuit
|
||
|
if (tryBlock.getType() == Token.BLOCK && !tryBlock.hasChildren()
|
||
|
&& !hasFinally)
|
||
|
{
|
||
|
return tryBlock;
|
||
|
}
|
||
|
|
||
|
boolean hasCatch = catchBlocks.hasChildren();
|
||
|
|
||
|
// short circuit
|
||
|
if (!hasFinally && !hasCatch) {
|
||
|
// bc finally might be an empty block...
|
||
|
return tryBlock;
|
||
|
}
|
||
|
|
||
|
|
||
|
Node handlerBlock = new Node(Token.LOCAL_BLOCK);
|
||
|
Node.Jump pn = new Node.Jump(Token.TRY, tryBlock, lineno);
|
||
|
pn.putProp(Node.LOCAL_BLOCK_PROP, handlerBlock);
|
||
|
|
||
|
if (hasCatch) {
|
||
|
// jump around catch code
|
||
|
Node endCatch = Node.newTarget();
|
||
|
pn.addChildToBack(makeJump(Token.GOTO, endCatch));
|
||
|
|
||
|
// make a TARGET for the catch that the tcf node knows about
|
||
|
Node catchTarget = Node.newTarget();
|
||
|
pn.target = catchTarget;
|
||
|
// mark it
|
||
|
pn.addChildToBack(catchTarget);
|
||
|
|
||
|
//
|
||
|
// Given
|
||
|
//
|
||
|
// try {
|
||
|
// tryBlock;
|
||
|
// } catch (e if condition1) {
|
||
|
// something1;
|
||
|
// ...
|
||
|
//
|
||
|
// } catch (e if conditionN) {
|
||
|
// somethingN;
|
||
|
// } catch (e) {
|
||
|
// somethingDefault;
|
||
|
// }
|
||
|
//
|
||
|
// rewrite as
|
||
|
//
|
||
|
// try {
|
||
|
// tryBlock;
|
||
|
// goto after_catch:
|
||
|
// } catch (x) {
|
||
|
// with (newCatchScope(e, x)) {
|
||
|
// if (condition1) {
|
||
|
// something1;
|
||
|
// goto after_catch;
|
||
|
// }
|
||
|
// }
|
||
|
// ...
|
||
|
// with (newCatchScope(e, x)) {
|
||
|
// if (conditionN) {
|
||
|
// somethingN;
|
||
|
// goto after_catch;
|
||
|
// }
|
||
|
// }
|
||
|
// with (newCatchScope(e, x)) {
|
||
|
// somethingDefault;
|
||
|
// goto after_catch;
|
||
|
// }
|
||
|
// }
|
||
|
// after_catch:
|
||
|
//
|
||
|
// If there is no default catch, then the last with block
|
||
|
// arround "somethingDefault;" is replaced by "rethrow;"
|
||
|
|
||
|
// It is assumed that catch handler generation will store
|
||
|
// exeception object in handlerBlock register
|
||
|
|
||
|
// Block with local for exception scope objects
|
||
|
Node catchScopeBlock = new Node(Token.LOCAL_BLOCK);
|
||
|
|
||
|
// expects catchblocks children to be (cond block) pairs.
|
||
|
Node cb = catchBlocks.getFirstChild();
|
||
|
boolean hasDefault = false;
|
||
|
int scopeIndex = 0;
|
||
|
while (cb != null) {
|
||
|
int catchLineNo = cb.getLineno();
|
||
|
|
||
|
Node name = cb.getFirstChild();
|
||
|
Node cond = name.getNext();
|
||
|
Node catchStatement = cond.getNext();
|
||
|
cb.removeChild(name);
|
||
|
cb.removeChild(cond);
|
||
|
cb.removeChild(catchStatement);
|
||
|
|
||
|
// Add goto to the catch statement to jump out of catch
|
||
|
// but prefix it with LEAVEWITH since try..catch produces
|
||
|
// "with"code in order to limit the scope of the exception
|
||
|
// object.
|
||
|
catchStatement.addChildToBack(new Node(Token.LEAVEWITH));
|
||
|
catchStatement.addChildToBack(makeJump(Token.GOTO, endCatch));
|
||
|
|
||
|
// Create condition "if" when present
|
||
|
Node condStmt;
|
||
|
if (cond.getType() == Token.EMPTY) {
|
||
|
condStmt = catchStatement;
|
||
|
hasDefault = true;
|
||
|
} else {
|
||
|
condStmt = createIf(cond, catchStatement, null,
|
||
|
catchLineNo);
|
||
|
}
|
||
|
|
||
|
// Generate code to create the scope object and store
|
||
|
// it in catchScopeBlock register
|
||
|
Node catchScope = new Node(Token.CATCH_SCOPE, name,
|
||
|
createUseLocal(handlerBlock));
|
||
|
catchScope.putProp(Node.LOCAL_BLOCK_PROP, catchScopeBlock);
|
||
|
catchScope.putIntProp(Node.CATCH_SCOPE_PROP, scopeIndex);
|
||
|
catchScopeBlock.addChildToBack(catchScope);
|
||
|
|
||
|
// Add with statement based on catch scope object
|
||
|
catchScopeBlock.addChildToBack(
|
||
|
createWith(createUseLocal(catchScopeBlock), condStmt,
|
||
|
catchLineNo));
|
||
|
|
||
|
// move to next cb
|
||
|
cb = cb.getNext();
|
||
|
++scopeIndex;
|
||
|
}
|
||
|
pn.addChildToBack(catchScopeBlock);
|
||
|
if (!hasDefault) {
|
||
|
// Generate code to rethrow if no catch clause was executed
|
||
|
Node rethrow = new Node(Token.RETHROW);
|
||
|
rethrow.putProp(Node.LOCAL_BLOCK_PROP, handlerBlock);
|
||
|
pn.addChildToBack(rethrow);
|
||
|
}
|
||
|
|
||
|
pn.addChildToBack(endCatch);
|
||
|
}
|
||
|
|
||
|
if (hasFinally) {
|
||
|
Node finallyTarget = Node.newTarget();
|
||
|
pn.setFinally(finallyTarget);
|
||
|
|
||
|
// add jsr finally to the try block
|
||
|
pn.addChildToBack(makeJump(Token.JSR, finallyTarget));
|
||
|
|
||
|
// jump around finally code
|
||
|
Node finallyEnd = Node.newTarget();
|
||
|
pn.addChildToBack(makeJump(Token.GOTO, finallyEnd));
|
||
|
|
||
|
pn.addChildToBack(finallyTarget);
|
||
|
Node fBlock = new Node(Token.FINALLY, finallyBlock);
|
||
|
fBlock.putProp(Node.LOCAL_BLOCK_PROP, handlerBlock);
|
||
|
pn.addChildToBack(fBlock);
|
||
|
|
||
|
pn.addChildToBack(finallyEnd);
|
||
|
}
|
||
|
handlerBlock.addChildToBack(pn);
|
||
|
return handlerBlock;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Throw, Return, Label, Break and Continue are defined in ASTFactory.
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* With
|
||
|
*/
|
||
|
Node createWith(Node obj, Node body, int lineno)
|
||
|
{
|
||
|
setRequiresActivation();
|
||
|
Node result = new Node(Token.BLOCK, lineno);
|
||
|
result.addChildToBack(new Node(Token.ENTERWITH, obj));
|
||
|
Node bodyNode = new Node(Token.WITH, body, lineno);
|
||
|
result.addChildrenToBack(bodyNode);
|
||
|
result.addChildToBack(new Node(Token.LEAVEWITH));
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* DOTQUERY
|
||
|
*/
|
||
|
public Node createDotQuery (Node obj, Node body, int lineno)
|
||
|
{
|
||
|
setRequiresActivation();
|
||
|
Node result = new Node(Token.DOTQUERY, obj, body, lineno);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
Node createArrayLiteral(ObjArray elems, int skipCount, int destructuringLen)
|
||
|
{
|
||
|
int length = elems.size();
|
||
|
int[] skipIndexes = null;
|
||
|
if (skipCount != 0) {
|
||
|
skipIndexes = new int[skipCount];
|
||
|
}
|
||
|
Node array = new Node(Token.ARRAYLIT);
|
||
|
for (int i = 0, j = 0; i != length; ++i) {
|
||
|
Node elem = (Node)elems.get(i);
|
||
|
if (elem != null) {
|
||
|
array.addChildToBack(elem);
|
||
|
} else {
|
||
|
skipIndexes[j] = i;
|
||
|
++j;
|
||
|
}
|
||
|
}
|
||
|
if (skipCount != 0) {
|
||
|
array.putProp(Node.SKIP_INDEXES_PROP, skipIndexes);
|
||
|
}
|
||
|
array.putIntProp(Node.DESTRUCTURING_ARRAY_LENGTH, destructuringLen);
|
||
|
return array;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Object Literals
|
||
|
* <BR> createObjectLiteral rewrites its argument as object
|
||
|
* creation plus object property entries, so later compiler
|
||
|
* stages don't need to know about object literals.
|
||
|
*/
|
||
|
Node createObjectLiteral(ObjArray elems)
|
||
|
{
|
||
|
int size = elems.size() / 2;
|
||
|
Node object = new Node(Token.OBJECTLIT);
|
||
|
Object[] properties;
|
||
|
if (size == 0) {
|
||
|
properties = ScriptRuntime.emptyArgs;
|
||
|
} else {
|
||
|
properties = new Object[size];
|
||
|
for (int i = 0; i != size; ++i) {
|
||
|
properties[i] = elems.get(2 * i);
|
||
|
Node value = (Node)elems.get(2 * i + 1);
|
||
|
object.addChildToBack(value);
|
||
|
}
|
||
|
}
|
||
|
object.putProp(Node.OBJECT_IDS_PROP, properties);
|
||
|
return object;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Regular expressions
|
||
|
*/
|
||
|
Node createRegExp(int regexpIndex)
|
||
|
{
|
||
|
Node n = new Node(Token.REGEXP);
|
||
|
n.putIntProp(Node.REGEXP_PROP, regexpIndex);
|
||
|
return n;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* If statement
|
||
|
*/
|
||
|
Node createIf(Node cond, Node ifTrue, Node ifFalse, int lineno)
|
||
|
{
|
||
|
int condStatus = isAlwaysDefinedBoolean(cond);
|
||
|
if (condStatus == ALWAYS_TRUE_BOOLEAN) {
|
||
|
return ifTrue;
|
||
|
} else if (condStatus == ALWAYS_FALSE_BOOLEAN) {
|
||
|
if (ifFalse != null) {
|
||
|
return ifFalse;
|
||
|
}
|
||
|
// Replace if (false) xxx by empty block
|
||
|
return new Node(Token.BLOCK, lineno);
|
||
|
}
|
||
|
|
||
|
Node result = new Node(Token.BLOCK, lineno);
|
||
|
Node ifNotTarget = Node.newTarget();
|
||
|
Node.Jump IFNE = new Node.Jump(Token.IFNE, cond);
|
||
|
IFNE.target = ifNotTarget;
|
||
|
|
||
|
result.addChildToBack(IFNE);
|
||
|
result.addChildrenToBack(ifTrue);
|
||
|
|
||
|
if (ifFalse != null) {
|
||
|
Node endTarget = Node.newTarget();
|
||
|
result.addChildToBack(makeJump(Token.GOTO, endTarget));
|
||
|
result.addChildToBack(ifNotTarget);
|
||
|
result.addChildrenToBack(ifFalse);
|
||
|
result.addChildToBack(endTarget);
|
||
|
} else {
|
||
|
result.addChildToBack(ifNotTarget);
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
Node createCondExpr(Node cond, Node ifTrue, Node ifFalse)
|
||
|
{
|
||
|
int condStatus = isAlwaysDefinedBoolean(cond);
|
||
|
if (condStatus == ALWAYS_TRUE_BOOLEAN) {
|
||
|
return ifTrue;
|
||
|
} else if (condStatus == ALWAYS_FALSE_BOOLEAN) {
|
||
|
return ifFalse;
|
||
|
}
|
||
|
return new Node(Token.HOOK, cond, ifTrue, ifFalse);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Unary
|
||
|
*/
|
||
|
Node createUnary(int nodeType, Node child)
|
||
|
{
|
||
|
int childType = child.getType();
|
||
|
switch (nodeType) {
|
||
|
case Token.DELPROP: {
|
||
|
Node n;
|
||
|
if (childType == Token.NAME) {
|
||
|
// Transform Delete(Name "a")
|
||
|
// to Delete(Bind("a"), String("a"))
|
||
|
child.setType(Token.BINDNAME);
|
||
|
Node left = child;
|
||
|
Node right = Node.newString(child.getString());
|
||
|
n = new Node(nodeType, left, right);
|
||
|
} else if (childType == Token.GETPROP ||
|
||
|
childType == Token.GETELEM)
|
||
|
{
|
||
|
Node left = child.getFirstChild();
|
||
|
Node right = child.getLastChild();
|
||
|
child.removeChild(left);
|
||
|
child.removeChild(right);
|
||
|
n = new Node(nodeType, left, right);
|
||
|
} else if (childType == Token.GET_REF) {
|
||
|
Node ref = child.getFirstChild();
|
||
|
child.removeChild(ref);
|
||
|
n = new Node(Token.DEL_REF, ref);
|
||
|
} else {
|
||
|
n = new Node(Token.TRUE);
|
||
|
}
|
||
|
return n;
|
||
|
}
|
||
|
case Token.TYPEOF:
|
||
|
if (childType == Token.NAME) {
|
||
|
child.setType(Token.TYPEOFNAME);
|
||
|
return child;
|
||
|
}
|
||
|
break;
|
||
|
case Token.BITNOT:
|
||
|
if (childType == Token.NUMBER) {
|
||
|
int value = ScriptRuntime.toInt32(child.getDouble());
|
||
|
child.setDouble(~value);
|
||
|
return child;
|
||
|
}
|
||
|
break;
|
||
|
case Token.NEG:
|
||
|
if (childType == Token.NUMBER) {
|
||
|
child.setDouble(-child.getDouble());
|
||
|
return child;
|
||
|
}
|
||
|
break;
|
||
|
case Token.NOT: {
|
||
|
int status = isAlwaysDefinedBoolean(child);
|
||
|
if (status != 0) {
|
||
|
int type;
|
||
|
if (status == ALWAYS_TRUE_BOOLEAN) {
|
||
|
type = Token.FALSE;
|
||
|
} else {
|
||
|
type = Token.TRUE;
|
||
|
}
|
||
|
if (childType == Token.TRUE || childType == Token.FALSE) {
|
||
|
child.setType(type);
|
||
|
return child;
|
||
|
}
|
||
|
return new Node(type);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return new Node(nodeType, child);
|
||
|
}
|
||
|
|
||
|
Node createYield(Node child, int lineno)
|
||
|
{
|
||
|
if (!parser.insideFunction()) {
|
||
|
parser.reportError("msg.bad.yield");
|
||
|
}
|
||
|
setRequiresActivation();
|
||
|
setIsGenerator();
|
||
|
if (child != null)
|
||
|
return new Node(Token.YIELD, child, lineno);
|
||
|
else
|
||
|
return new Node(Token.YIELD, lineno);
|
||
|
}
|
||
|
|
||
|
Node createCallOrNew(int nodeType, Node child)
|
||
|
{
|
||
|
int type = Node.NON_SPECIALCALL;
|
||
|
if (child.getType() == Token.NAME) {
|
||
|
String name = child.getString();
|
||
|
if (name.equals("eval")) {
|
||
|
type = Node.SPECIALCALL_EVAL;
|
||
|
} else if (name.equals("With")) {
|
||
|
type = Node.SPECIALCALL_WITH;
|
||
|
}
|
||
|
} else if (child.getType() == Token.GETPROP) {
|
||
|
String name = child.getLastChild().getString();
|
||
|
if (name.equals("eval")) {
|
||
|
type = Node.SPECIALCALL_EVAL;
|
||
|
}
|
||
|
}
|
||
|
Node node = new Node(nodeType, child);
|
||
|
if (type != Node.NON_SPECIALCALL) {
|
||
|
// Calls to these functions require activation objects.
|
||
|
setRequiresActivation();
|
||
|
node.putIntProp(Node.SPECIALCALL_PROP, type);
|
||
|
}
|
||
|
return node;
|
||
|
}
|
||
|
|
||
|
Node createIncDec(int nodeType, boolean post, Node child)
|
||
|
{
|
||
|
child = makeReference(child);
|
||
|
if (child == null) {
|
||
|
String msg;
|
||
|
if (nodeType == Token.DEC) {
|
||
|
msg = "msg.bad.decr";
|
||
|
} else {
|
||
|
msg = "msg.bad.incr";
|
||
|
}
|
||
|
parser.reportError(msg);
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
int childType = child.getType();
|
||
|
|
||
|
switch (childType) {
|
||
|
case Token.NAME:
|
||
|
case Token.GETPROP:
|
||
|
case Token.GETELEM:
|
||
|
case Token.GET_REF: {
|
||
|
Node n = new Node(nodeType, child);
|
||
|
int incrDecrMask = 0;
|
||
|
if (nodeType == Token.DEC) {
|
||
|
incrDecrMask |= Node.DECR_FLAG;
|
||
|
}
|
||
|
if (post) {
|
||
|
incrDecrMask |= Node.POST_FLAG;
|
||
|
}
|
||
|
n.putIntProp(Node.INCRDECR_PROP, incrDecrMask);
|
||
|
return n;
|
||
|
}
|
||
|
}
|
||
|
throw Kit.codeBug();
|
||
|
}
|
||
|
|
||
|
Node createPropertyGet(Node target, String namespace, String name,
|
||
|
int memberTypeFlags)
|
||
|
{
|
||
|
if (namespace == null && memberTypeFlags == 0) {
|
||
|
if (target == null) {
|
||
|
return createName(name);
|
||
|
}
|
||
|
checkActivationName(name, Token.GETPROP);
|
||
|
if (ScriptRuntime.isSpecialProperty(name)) {
|
||
|
Node ref = new Node(Token.REF_SPECIAL, target);
|
||
|
ref.putProp(Node.NAME_PROP, name);
|
||
|
return new Node(Token.GET_REF, ref);
|
||
|
}
|
||
|
return new Node(Token.GETPROP, target, createString(name));
|
||
|
}
|
||
|
Node elem = createString(name);
|
||
|
memberTypeFlags |= Node.PROPERTY_FLAG;
|
||
|
return createMemberRefGet(target, namespace, elem, memberTypeFlags);
|
||
|
}
|
||
|
|
||
|
Node createElementGet(Node target, String namespace, Node elem,
|
||
|
int memberTypeFlags)
|
||
|
{
|
||
|
// OPT: could optimize to createPropertyGet
|
||
|
// iff elem is string that can not be number
|
||
|
if (namespace == null && memberTypeFlags == 0) {
|
||
|
// stand-alone [aaa] as primary expression is array literal
|
||
|
// declaration and should not come here!
|
||
|
if (target == null) throw Kit.codeBug();
|
||
|
return new Node(Token.GETELEM, target, elem);
|
||
|
}
|
||
|
return createMemberRefGet(target, namespace, elem, memberTypeFlags);
|
||
|
}
|
||
|
|
||
|
private Node createMemberRefGet(Node target, String namespace, Node elem,
|
||
|
int memberTypeFlags)
|
||
|
{
|
||
|
Node nsNode = null;
|
||
|
if (namespace != null) {
|
||
|
// See 11.1.2 in ECMA 357
|
||
|
if (namespace.equals("*")) {
|
||
|
nsNode = new Node(Token.NULL);
|
||
|
} else {
|
||
|
nsNode = createName(namespace);
|
||
|
}
|
||
|
}
|
||
|
Node ref;
|
||
|
if (target == null) {
|
||
|
if (namespace == null) {
|
||
|
ref = new Node(Token.REF_NAME, elem);
|
||
|
} else {
|
||
|
ref = new Node(Token.REF_NS_NAME, nsNode, elem);
|
||
|
}
|
||
|
} else {
|
||
|
if (namespace == null) {
|
||
|
ref = new Node(Token.REF_MEMBER, target, elem);
|
||
|
} else {
|
||
|
ref = new Node(Token.REF_NS_MEMBER, target, nsNode, elem);
|
||
|
}
|
||
|
}
|
||
|
if (memberTypeFlags != 0) {
|
||
|
ref.putIntProp(Node.MEMBER_TYPE_PROP, memberTypeFlags);
|
||
|
}
|
||
|
return new Node(Token.GET_REF, ref);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Binary
|
||
|
*/
|
||
|
Node createBinary(int nodeType, Node left, Node right)
|
||
|
{
|
||
|
switch (nodeType) {
|
||
|
|
||
|
case Token.ADD:
|
||
|
// numerical addition and string concatenation
|
||
|
if (left.type == Token.STRING) {
|
||
|
String s2;
|
||
|
if (right.type == Token.STRING) {
|
||
|
s2 = right.getString();
|
||
|
} else if (right.type == Token.NUMBER) {
|
||
|
s2 = ScriptRuntime.numberToString(right.getDouble(), 10);
|
||
|
} else {
|
||
|
break;
|
||
|
}
|
||
|
String s1 = left.getString();
|
||
|
left.setString(s1.concat(s2));
|
||
|
return left;
|
||
|
} else if (left.type == Token.NUMBER) {
|
||
|
if (right.type == Token.NUMBER) {
|
||
|
left.setDouble(left.getDouble() + right.getDouble());
|
||
|
return left;
|
||
|
} else if (right.type == Token.STRING) {
|
||
|
String s1, s2;
|
||
|
s1 = ScriptRuntime.numberToString(left.getDouble(), 10);
|
||
|
s2 = right.getString();
|
||
|
right.setString(s1.concat(s2));
|
||
|
return right;
|
||
|
}
|
||
|
}
|
||
|
// can't do anything if we don't know both types - since
|
||
|
// 0 + object is supposed to call toString on the object and do
|
||
|
// string concantenation rather than addition
|
||
|
break;
|
||
|
|
||
|
case Token.SUB:
|
||
|
// numerical subtraction
|
||
|
if (left.type == Token.NUMBER) {
|
||
|
double ld = left.getDouble();
|
||
|
if (right.type == Token.NUMBER) {
|
||
|
//both numbers
|
||
|
left.setDouble(ld - right.getDouble());
|
||
|
return left;
|
||
|
} else if (ld == 0.0) {
|
||
|
// first 0: 0-x -> -x
|
||
|
return new Node(Token.NEG, right);
|
||
|
}
|
||
|
} else if (right.type == Token.NUMBER) {
|
||
|
if (right.getDouble() == 0.0) {
|
||
|
//second 0: x - 0 -> +x
|
||
|
// can not make simply x because x - 0 must be number
|
||
|
return new Node(Token.POS, left);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case Token.MUL:
|
||
|
// numerical multiplication
|
||
|
if (left.type == Token.NUMBER) {
|
||
|
double ld = left.getDouble();
|
||
|
if (right.type == Token.NUMBER) {
|
||
|
//both numbers
|
||
|
left.setDouble(ld * right.getDouble());
|
||
|
return left;
|
||
|
} else if (ld == 1.0) {
|
||
|
// first 1: 1 * x -> +x
|
||
|
return new Node(Token.POS, right);
|
||
|
}
|
||
|
} else if (right.type == Token.NUMBER) {
|
||
|
if (right.getDouble() == 1.0) {
|
||
|
//second 1: x * 1 -> +x
|
||
|
// can not make simply x because x - 0 must be number
|
||
|
return new Node(Token.POS, left);
|
||
|
}
|
||
|
}
|
||
|
// can't do x*0: Infinity * 0 gives NaN, not 0
|
||
|
break;
|
||
|
|
||
|
case Token.DIV:
|
||
|
// number division
|
||
|
if (right.type == Token.NUMBER) {
|
||
|
double rd = right.getDouble();
|
||
|
if (left.type == Token.NUMBER) {
|
||
|
// both constants -- just divide, trust Java to handle x/0
|
||
|
left.setDouble(left.getDouble() / rd);
|
||
|
return left;
|
||
|
} else if (rd == 1.0) {
|
||
|
// second 1: x/1 -> +x
|
||
|
// not simply x to force number convertion
|
||
|
return new Node(Token.POS, left);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case Token.AND: {
|
||
|
// Since x && y gives x, not false, when Boolean(x) is false,
|
||
|
// and y, not Boolean(y), when Boolean(x) is true, x && y
|
||
|
// can only be simplified if x is defined. See bug 309957.
|
||
|
|
||
|
int leftStatus = isAlwaysDefinedBoolean(left);
|
||
|
if (leftStatus == ALWAYS_FALSE_BOOLEAN) {
|
||
|
// if the first one is false, just return it
|
||
|
return left;
|
||
|
} else if (leftStatus == ALWAYS_TRUE_BOOLEAN) {
|
||
|
// if first is true, set to second
|
||
|
return right;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case Token.OR: {
|
||
|
// Since x || y gives x, not true, when Boolean(x) is true,
|
||
|
// and y, not Boolean(y), when Boolean(x) is false, x || y
|
||
|
// can only be simplified if x is defined. See bug 309957.
|
||
|
|
||
|
int leftStatus = isAlwaysDefinedBoolean(left);
|
||
|
if (leftStatus == ALWAYS_TRUE_BOOLEAN) {
|
||
|
// if the first one is true, just return it
|
||
|
return left;
|
||
|
} else if (leftStatus == ALWAYS_FALSE_BOOLEAN) {
|
||
|
// if first is false, set to second
|
||
|
return right;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return new Node(nodeType, left, right);
|
||
|
}
|
||
|
|
||
|
private Node simpleAssignment(Node left, Node right)
|
||
|
{
|
||
|
int nodeType = left.getType();
|
||
|
switch (nodeType) {
|
||
|
case Token.NAME:
|
||
|
left.setType(Token.BINDNAME);
|
||
|
return new Node(Token.SETNAME, left, right);
|
||
|
|
||
|
case Token.GETPROP:
|
||
|
case Token.GETELEM: {
|
||
|
Node obj = left.getFirstChild();
|
||
|
Node id = left.getLastChild();
|
||
|
int type;
|
||
|
if (nodeType == Token.GETPROP) {
|
||
|
type = Token.SETPROP;
|
||
|
} else {
|
||
|
type = Token.SETELEM;
|
||
|
}
|
||
|
return new Node(type, obj, id, right);
|
||
|
}
|
||
|
case Token.GET_REF: {
|
||
|
Node ref = left.getFirstChild();
|
||
|
checkMutableReference(ref);
|
||
|
return new Node(Token.SET_REF, ref, right);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
throw Kit.codeBug();
|
||
|
}
|
||
|
|
||
|
private void checkMutableReference(Node n)
|
||
|
{
|
||
|
int memberTypeFlags = n.getIntProp(Node.MEMBER_TYPE_PROP, 0);
|
||
|
if ((memberTypeFlags & Node.DESCENDANTS_FLAG) != 0) {
|
||
|
parser.reportError("msg.bad.assign.left");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Node createAssignment(int assignType, Node left, Node right)
|
||
|
{
|
||
|
Node ref = makeReference(left);
|
||
|
if (ref == null) {
|
||
|
if (left.getType() == Token.ARRAYLIT ||
|
||
|
left.getType() == Token.OBJECTLIT)
|
||
|
{
|
||
|
if (assignType != Token.ASSIGN) {
|
||
|
parser.reportError("msg.bad.destruct.op");
|
||
|
return right;
|
||
|
}
|
||
|
return createDestructuringAssignment(-1, left, right);
|
||
|
}
|
||
|
parser.reportError("msg.bad.assign.left");
|
||
|
return right;
|
||
|
}
|
||
|
left = ref;
|
||
|
|
||
|
int assignOp;
|
||
|
switch (assignType) {
|
||
|
case Token.ASSIGN:
|
||
|
return simpleAssignment(left, right);
|
||
|
case Token.ASSIGN_BITOR: assignOp = Token.BITOR; break;
|
||
|
case Token.ASSIGN_BITXOR: assignOp = Token.BITXOR; break;
|
||
|
case Token.ASSIGN_BITAND: assignOp = Token.BITAND; break;
|
||
|
case Token.ASSIGN_LSH: assignOp = Token.LSH; break;
|
||
|
case Token.ASSIGN_RSH: assignOp = Token.RSH; break;
|
||
|
case Token.ASSIGN_URSH: assignOp = Token.URSH; break;
|
||
|
case Token.ASSIGN_ADD: assignOp = Token.ADD; break;
|
||
|
case Token.ASSIGN_SUB: assignOp = Token.SUB; break;
|
||
|
case Token.ASSIGN_MUL: assignOp = Token.MUL; break;
|
||
|
case Token.ASSIGN_DIV: assignOp = Token.DIV; break;
|
||
|
case Token.ASSIGN_MOD: assignOp = Token.MOD; break;
|
||
|
default: throw Kit.codeBug();
|
||
|
}
|
||
|
|
||
|
int nodeType = left.getType();
|
||
|
switch (nodeType) {
|
||
|
case Token.NAME: {
|
||
|
Node op = new Node(assignOp, left, right);
|
||
|
Node lvalueLeft = Node.newString(Token.BINDNAME, left.getString());
|
||
|
return new Node(Token.SETNAME, lvalueLeft, op);
|
||
|
}
|
||
|
case Token.GETPROP:
|
||
|
case Token.GETELEM: {
|
||
|
Node obj = left.getFirstChild();
|
||
|
Node id = left.getLastChild();
|
||
|
|
||
|
int type = nodeType == Token.GETPROP
|
||
|
? Token.SETPROP_OP
|
||
|
: Token.SETELEM_OP;
|
||
|
|
||
|
Node opLeft = new Node(Token.USE_STACK);
|
||
|
Node op = new Node(assignOp, opLeft, right);
|
||
|
return new Node(type, obj, id, op);
|
||
|
}
|
||
|
case Token.GET_REF: {
|
||
|
ref = left.getFirstChild();
|
||
|
checkMutableReference(ref);
|
||
|
Node opLeft = new Node(Token.USE_STACK);
|
||
|
Node op = new Node(assignOp, opLeft, right);
|
||
|
return new Node(Token.SET_REF_OP, ref, op);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
throw Kit.codeBug();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Given a destructuring assignment with a left hand side parsed
|
||
|
* as an array or object literal and a right hand side expression,
|
||
|
* rewrite as a series of assignments to the variables defined in
|
||
|
* left from property accesses to the expression on the right.
|
||
|
* @param type declaration type: Token.VAR or Token.LET or -1
|
||
|
* @param left array or object literal containing NAME nodes for
|
||
|
* variables to assign
|
||
|
* @param right expression to assign from
|
||
|
* @return expression that performs a series of assignments to
|
||
|
* the variables defined in left
|
||
|
*/
|
||
|
Node createDestructuringAssignment(int type, Node left, Node right)
|
||
|
{
|
||
|
String tempName = parser.currentScriptOrFn.getNextTempName();
|
||
|
Node result = destructuringAssignmentHelper(type, left, right,
|
||
|
tempName);
|
||
|
Node comma = result.getLastChild();
|
||
|
comma.addChildToBack(createName(tempName));
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
private Node destructuringAssignmentHelper(int variableType, Node left,
|
||
|
Node right, String tempName)
|
||
|
{
|
||
|
Node result = createScopeNode(Token.LETEXPR,
|
||
|
parser.getCurrentLineNumber());
|
||
|
result.addChildToFront(new Node(Token.LET,
|
||
|
createName(Token.NAME, tempName, right)));
|
||
|
try {
|
||
|
parser.pushScope(result);
|
||
|
parser.defineSymbol(Token.LET, true, tempName);
|
||
|
} finally {
|
||
|
parser.popScope();
|
||
|
}
|
||
|
Node comma = new Node(Token.COMMA);
|
||
|
result.addChildToBack(comma);
|
||
|
final int setOp = variableType == Token.CONST ? Token.SETCONST
|
||
|
: Token.SETNAME;
|
||
|
List<String> destructuringNames = new ArrayList<String>();
|
||
|
boolean empty = true;
|
||
|
int type = left.getType();
|
||
|
if (type == Token.ARRAYLIT) {
|
||
|
int index = 0;
|
||
|
int[] skipIndices = (int[])left.getProp(Node.SKIP_INDEXES_PROP);
|
||
|
int skip = 0;
|
||
|
Node n = left.getFirstChild();
|
||
|
for (;;) {
|
||
|
if (skipIndices != null) {
|
||
|
while (skip < skipIndices.length &&
|
||
|
skipIndices[skip] == index) {
|
||
|
skip++;
|
||
|
index++;
|
||
|
}
|
||
|
}
|
||
|
if (n == null)
|
||
|
break;
|
||
|
Node rightElem = new Node(Token.GETELEM,
|
||
|
createName(tempName),
|
||
|
createNumber(index));
|
||
|
if (n.getType() == Token.NAME) {
|
||
|
String name = n.getString();
|
||
|
comma.addChildToBack(new Node(setOp,
|
||
|
createName(Token.BINDNAME, name, null),
|
||
|
rightElem));
|
||
|
if (variableType != -1) {
|
||
|
parser.defineSymbol(variableType, true, name);
|
||
|
destructuringNames.add(name);
|
||
|
}
|
||
|
} else {
|
||
|
comma.addChildToBack(
|
||
|
destructuringAssignmentHelper(variableType, n,
|
||
|
rightElem,
|
||
|
parser.currentScriptOrFn.getNextTempName()));
|
||
|
}
|
||
|
index++;
|
||
|
empty = false;
|
||
|
n = n.getNext();
|
||
|
}
|
||
|
} else if (type == Token.OBJECTLIT) {
|
||
|
int index = 0;
|
||
|
Object[] propertyIds = (Object[])
|
||
|
left.getProp(Node.OBJECT_IDS_PROP);
|
||
|
for (Node n = left.getFirstChild(); n != null; n = n.getNext())
|
||
|
{
|
||
|
Object id = propertyIds[index];
|
||
|
Node rightElem = id instanceof String
|
||
|
? new Node(Token.GETPROP,
|
||
|
createName(tempName),
|
||
|
createString((String)id))
|
||
|
: new Node(Token.GETELEM,
|
||
|
createName(tempName),
|
||
|
createNumber(((Number)id).intValue()));
|
||
|
if (n.getType() == Token.NAME) {
|
||
|
String name = n.getString();
|
||
|
comma.addChildToBack(new Node(setOp,
|
||
|
createName(Token.BINDNAME, name, null),
|
||
|
rightElem));
|
||
|
if (variableType != -1) {
|
||
|
parser.defineSymbol(variableType, true, name);
|
||
|
destructuringNames.add(name);
|
||
|
}
|
||
|
} else {
|
||
|
comma.addChildToBack(
|
||
|
destructuringAssignmentHelper(variableType, n,
|
||
|
rightElem,
|
||
|
parser.currentScriptOrFn.getNextTempName()));
|
||
|
}
|
||
|
index++;
|
||
|
empty = false;
|
||
|
}
|
||
|
} else if (type == Token.GETPROP || type == Token.GETELEM) {
|
||
|
comma.addChildToBack(simpleAssignment(left, createName(tempName)));
|
||
|
} else {
|
||
|
parser.reportError("msg.bad.assign.left");
|
||
|
}
|
||
|
if (empty) {
|
||
|
// Don't want a COMMA node with no children. Just add a zero.
|
||
|
comma.addChildToBack(createNumber(0));
|
||
|
}
|
||
|
result.putProp(Node.DESTRUCTURING_NAMES, destructuringNames);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
Node createUseLocal(Node localBlock)
|
||
|
{
|
||
|
if (Token.LOCAL_BLOCK != localBlock.getType()) throw Kit.codeBug();
|
||
|
Node result = new Node(Token.LOCAL_LOAD);
|
||
|
result.putProp(Node.LOCAL_BLOCK_PROP, localBlock);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
private Node.Jump makeJump(int type, Node target)
|
||
|
{
|
||
|
Node.Jump n = new Node.Jump(type);
|
||
|
n.target = target;
|
||
|
return n;
|
||
|
}
|
||
|
|
||
|
private Node makeReference(Node node)
|
||
|
{
|
||
|
int type = node.getType();
|
||
|
switch (type) {
|
||
|
case Token.NAME:
|
||
|
case Token.GETPROP:
|
||
|
case Token.GETELEM:
|
||
|
case Token.GET_REF:
|
||
|
return node;
|
||
|
case Token.CALL:
|
||
|
node.setType(Token.REF_CALL);
|
||
|
return new Node(Token.GET_REF, node);
|
||
|
}
|
||
|
// Signal caller to report error
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
// Check if Node always mean true or false in boolean context
|
||
|
private static int isAlwaysDefinedBoolean(Node node)
|
||
|
{
|
||
|
switch (node.getType()) {
|
||
|
case Token.FALSE:
|
||
|
case Token.NULL:
|
||
|
return ALWAYS_FALSE_BOOLEAN;
|
||
|
case Token.TRUE:
|
||
|
return ALWAYS_TRUE_BOOLEAN;
|
||
|
case Token.NUMBER: {
|
||
|
double num = node.getDouble();
|
||
|
if (num == num && num != 0.0) {
|
||
|
return ALWAYS_TRUE_BOOLEAN;
|
||
|
} else {
|
||
|
return ALWAYS_FALSE_BOOLEAN;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
private void checkActivationName(String name, int token)
|
||
|
{
|
||
|
if (parser.insideFunction()) {
|
||
|
boolean activation = false;
|
||
|
if ("arguments".equals(name)
|
||
|
|| (parser.compilerEnv.activationNames != null
|
||
|
&& parser.compilerEnv.activationNames.contains(name)))
|
||
|
{
|
||
|
activation = true;
|
||
|
} else if ("length".equals(name)) {
|
||
|
if (token == Token.GETPROP
|
||
|
&& parser.compilerEnv.getLanguageVersion()
|
||
|
== Context.VERSION_1_2)
|
||
|
{
|
||
|
// Use of "length" in 1.2 requires an activation object.
|
||
|
activation = true;
|
||
|
}
|
||
|
}
|
||
|
if (activation) {
|
||
|
setRequiresActivation();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void setRequiresActivation()
|
||
|
{
|
||
|
if (parser.insideFunction()) {
|
||
|
((FunctionNode)parser.currentScriptOrFn).itsNeedsActivation = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void setIsGenerator()
|
||
|
{
|
||
|
if (parser.insideFunction()) {
|
||
|
((FunctionNode)parser.currentScriptOrFn).itsIsGenerator = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private Parser parser;
|
||
|
|
||
|
private static final int LOOP_DO_WHILE = 0;
|
||
|
private static final int LOOP_WHILE = 1;
|
||
|
private static final int LOOP_FOR = 2;
|
||
|
|
||
|
private static final int ALWAYS_TRUE_BOOLEAN = 1;
|
||
|
private static final int ALWAYS_FALSE_BOOLEAN = -1;
|
||
|
}
|