org.mozilla.javascript.IRFactory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of js Show documentation
Show all versions of js Show documentation
Rhino is an open-source implementation of JavaScript written entirely in Java. It is typically embedded into Java applications to provide scripting to end users.
The newest version!
/* -*- 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;
}