/* -*- 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
*
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 destructuringNames = new ArrayList();
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;
}