gecko-dev/js/rhino/org/mozilla/javascript/IRFactory.java
1999-04-19 20:43:53 +00:00

1010 lines
36 KiB
Java

/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* The contents of this file are subject to the Netscape Public License
* Version 1.0 (the "NPL"); you may not use this file except in
* compliance with the NPL. You may obtain a copy of the NPL at
* http://www.mozilla.org/NPL/
*
* Software distributed under the NPL is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
* for the specific language governing rights and limitations under the
* NPL.
*
* The Initial Developer of this code under the NPL is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1997-1999 Netscape Communications Corporation. All Rights
* Reserved.
*/
package org.mozilla.javascript;
/**
* This class allows the creation of nodes, and follows the Factory pattern.
*
* @see Node
* @see NodeFactory
* @see ASTFactory
* @author Mike McCabe
* @author Norris Boyd
*/
public class IRFactory {
public IRFactory(TokenStream ts) {
this.ts = ts;
}
/**
* Script (for associating file/url names with toplevel scripts.)
*/
public Object createScript(Object body, String sourceName,
int baseLineno, int endLineno, Object source)
{
Node result = new Node(TokenStream.SCRIPT, sourceName);
Node children = ((Node) body).getFirstChild();
if (children != null)
result.addChildrenToBack(children);
result.putProp(Node.SOURCENAME_PROP, sourceName);
result.putProp(Node.BASE_LINENO_PROP, new Integer(baseLineno));
result.putProp(Node.END_LINENO_PROP, new Integer(endLineno));
if (source != null)
result.putProp(Node.SOURCE_PROP, source);
return result;
}
/**
* Leaf
*/
public Object createLeaf(int nodeType) {
return new Node(nodeType);
}
public Object createLeaf(int nodeType, String id) {
return new Node(nodeType, id);
}
public Object createLeaf(int nodeType, int nodeOp) {
return new Node(nodeType, new Integer(nodeOp));
}
/**
* Statement leaf nodes.
*/
public Object createSwitch(int lineno) {
return new Node(TokenStream.SWITCH, new Integer(lineno));
}
public Object createVariables(int lineno) {
return new Node(TokenStream.VAR, new Integer(lineno));
}
public Object createExprStatement(Object expr, int lineno) {
return new Node(TokenStream.EXPRSTMT, (Node) expr, new Integer(lineno));
}
/**
* Name
*/
public Object createName(String name) {
return new Node(TokenStream.NAME, name);
}
/**
* String (for literals)
*/
public Object createString(String string) {
return new Node(TokenStream.STRING, string);
}
/**
* Number (for literals)
*/
public Object createNumber(Number number) {
return new Node(TokenStream.NUMBER, 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
*/
public Object createCatch(String varName, Object catchCond, Object stmts,
int lineno)
{
if (catchCond == null)
catchCond = new Node(TokenStream.PRIMARY,
new Integer(TokenStream.TRUE));
Node result = new Node(TokenStream.CATCH, (Node)createName(varName),
(Node)catchCond, (Node)stmts);
result.setDatum(new Integer(lineno));
return result;
}
/**
* Throw
*/
public Object createThrow(Object expr, int lineno) {
return new Node(TokenStream.THROW, (Node)expr, new Integer(lineno));
}
/**
* Return
*/
public Object createReturn(Object expr, int lineno) {
return expr == null
? new Node(TokenStream.RETURN, new Integer(lineno))
: new Node(TokenStream.RETURN, (Node)expr, new Integer(lineno));
}
/**
* Label
*/
public Object createLabel(String label, int lineno) {
Node result = new Node(TokenStream.LABEL, new Integer(lineno));
Node name = new Node(TokenStream.NAME, label);
result.addChildToBack(name);
return result;
}
/**
* Break (possibly labeled)
*/
public Object createBreak(String label, int lineno) {
Node result = new Node(TokenStream.BREAK, new Integer(lineno));
if (label == null) {
return result;
} else {
Node name = new Node(TokenStream.NAME, label);
result.addChildToBack(name);
return result;
}
}
/**
* Continue (possibly labeled)
*/
public Object createContinue(String label, int lineno) {
Node result = new Node(TokenStream.CONTINUE, new Integer(lineno));
if (label == null) {
return result;
} else {
Node name = new Node(TokenStream.NAME, label);
result.addChildToBack(name);
return result;
}
}
/**
* Statement block
* Creates the empty statement block
* Must make subsequent calls to add statements to the node
*/
public Object createBlock(int lineno) {
return new Node(TokenStream.BLOCK, new Integer(lineno));
}
public Object createFunctionNode(String name, Object args,
Object statements)
{
if (name == null)
name = "";
return new FunctionNode(name, (Node) args, (Node) statements);
}
public Object createFunction(String name, Object args, Object statements,
String sourceName, int baseLineno,
int endLineno, Object source)
{
Node f = (Node) createFunctionNode(name, args, statements);
f.putProp(Node.SOURCENAME_PROP, sourceName);
f.putProp(Node.BASE_LINENO_PROP, new Integer(baseLineno));
f.putProp(Node.END_LINENO_PROP, new Integer(endLineno));
if (source != null)
f.putProp(Node.SOURCE_PROP, source);
Node result = new Node(TokenStream.FUNCTION, name);
result.putProp(Node.FUNCTION_PROP, f);
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.
*/
public void addChildToBack(Object parent, Object child) {
((Node)parent).addChildToBack((Node)child);
}
/**
* While
*/
public Object createWhile(Object cond, Object body, int lineno) {
// Just add a GOTO to the condition in the do..while
Node result = (Node) createDoWhile(body, cond, lineno);
Node condTarget = (Node) result.getProp(Node.CONTINUE_PROP);
Node GOTO = new Node(TokenStream.GOTO);
GOTO.putProp(Node.TARGET_PROP, condTarget);
result.addChildToFront(GOTO);
return result;
}
/**
* DoWhile
*/
public Object createDoWhile(Object body, Object cond, int lineno) {
Node result = new Node(TokenStream.LOOP, new Integer(lineno));
Node bodyTarget = new Node(TokenStream.TARGET);
Node condTarget = new Node(TokenStream.TARGET);
Node IFEQ = new Node(TokenStream.IFEQ, (Node)cond);
IFEQ.putProp(Node.TARGET_PROP, bodyTarget);
Node breakTarget = new Node(TokenStream.TARGET);
result.addChildToBack(bodyTarget);
result.addChildrenToBack((Node)body);
result.addChildToBack(condTarget);
result.addChildToBack(IFEQ);
result.addChildToBack(breakTarget);
result.putProp(Node.BREAK_PROP, breakTarget);
result.putProp(Node.CONTINUE_PROP, condTarget);
return result;
}
/**
* For
*/
public Object createFor(Object init, Object test, Object incr,
Object body, int lineno)
{
if (((Node) test).getType() == TokenStream.VOID) {
test = new Node(TokenStream.PRIMARY,
new Integer(TokenStream.TRUE));
}
Node result = (Node)createWhile(test, body, lineno);
Node initNode = (Node) init;
if (initNode.getType() != TokenStream.VOID) {
if (initNode.getType() != TokenStream.VAR)
initNode = new Node(TokenStream.POP, initNode);
result.addChildToFront(initNode);
}
Node condTarget = (Node)result.getProp(Node.CONTINUE_PROP);
Node incrTarget = new Node(TokenStream.TARGET);
result.addChildBefore(incrTarget, condTarget);
if (((Node) incr).getType() != TokenStream.VOID) {
incr = createUnary(TokenStream.POP, incr);
result.addChildAfter((Node)incr, incrTarget);
}
result.putProp(Node.CONTINUE_PROP, incrTarget);
return result;
}
/**
* For .. In
*
*/
public Object createForIn(Object lhs, Object obj, Object body, int lineno) {
String name;
Node lhsNode = (Node) lhs;
Node objNode = (Node) obj;
int type = lhsNode.getType();
Node lvalue = lhsNode;
switch (type) {
case TokenStream.NAME:
case TokenStream.GETPROP:
case TokenStream.GETELEM:
break;
case TokenStream.VAR:
/*
* 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.
*/
Node lastChild = lhsNode.getLastChild();
if (lhsNode.getFirstChild() != lastChild) {
reportError("msg.mult.index");
}
lvalue = new Node(TokenStream.NAME, lastChild.getString());
break;
default:
reportError("msg.bad.for.in.lhs");
return objNode;
}
Node init = new Node(TokenStream.ENUMINIT, objNode);
Node next = new Node(TokenStream.ENUMNEXT);
next.putProp(Node.ENUM_PROP, init);
Node temp = createNewTemp(next);
Node cond = new Node(TokenStream.EQOP, new Integer(TokenStream.NE));
cond.addChildToBack(temp);
cond.addChildToBack(new Node(TokenStream.PRIMARY,
new Integer(TokenStream.NULL)));
Node newBody = new Node(TokenStream.BLOCK);
Node assign = (Node) createAssignment(TokenStream.NOP, lvalue,
createUseTemp(temp), null,
false);
newBody.addChildToBack(new Node(TokenStream.POP, assign));
newBody.addChildToBack((Node) body);
Node result = (Node) createWhile(cond, newBody, lineno);
result.addChildToFront(init);
if (type == TokenStream.VAR)
result.addChildToFront(lhsNode);
Node done = new Node(TokenStream.ENUMDONE);
done.putProp(Node.ENUM_PROP, init);
result.addChildToBack(done);
return result;
}
/**
* 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 -
* TARGET_PROP in the try node.
* - a finally handler that catches any exception, stores it to a
* temporary, and JSRs to the finally target - FINALLY_PROP in the
* try node - before re-throwing the exception.
* ... and a goto to GOTO around these handlers.
*/
public Object createTryCatchFinally(Object tryblock, Object catchblocks,
Object finallyblock, int lineno)
{
Node trynode = (Node)tryblock;
// short circuit
if (trynode.getType() == TokenStream.BLOCK && !trynode.hasChildren())
return trynode;
Node pn = new Node(TokenStream.TRY, trynode, new Integer(lineno));
Node catchNodes = (Node)catchblocks;
boolean hasCatch = catchNodes.hasChildren();
boolean hasFinally = false;
Node finallyNode = null;
Node finallyTarget = null;
if (finallyblock != null) {
finallyNode = (Node)finallyblock;
hasFinally = (finallyNode.getType() != TokenStream.BLOCK
|| finallyNode.hasChildren());
if (hasFinally) {
// make a TARGET for the finally that the tcf node knows about
finallyTarget = new Node(TokenStream.TARGET);
pn.putProp(Node.FINALLY_PROP, finallyTarget);
// add jsr finally to the try block
Node jsrFinally = new Node(TokenStream.JSR);
jsrFinally.putProp(Node.TARGET_PROP, finallyTarget);
pn.addChildToBack(jsrFinally);
}
}
// short circuit
if (!hasFinally && !hasCatch) // bc finally might be an empty block...
return trynode;
Node endTarget = new Node(TokenStream.TARGET);
Node GOTOToEnd = new Node(TokenStream.GOTO);
GOTOToEnd.putProp(Node.TARGET_PROP, endTarget);
pn.addChildToBack(GOTOToEnd);
if (hasCatch) {
/*
*
Given
try {
throw 3;
} catch (e: e instanceof Object) {
print("object");
} catch (e2) {
print(e2);
}
rewrite as
try {
throw 3;
} catch (x) {
o = newScope();
o.e = x;
with (o) {
if (e instanceof Object) {
print("object");
}
}
o2 = newScope();
o2.e2 = x;
with (o2) {
if (true) {
print(e2);
}
}
}
*/
// make a TARGET for the catch that the tcf node knows about
Node catchTarget = new Node(TokenStream.TARGET);
pn.putProp(Node.TARGET_PROP, catchTarget);
// mark it
pn.addChildToBack(catchTarget);
// get the exception object and store it in a temp
Node exn = createNewLocal(new Node(TokenStream.VOID));
pn.addChildToBack(new Node(TokenStream.POP, exn));
Node endCatch = new Node(TokenStream.TARGET);
// add [jsr finally?] goto end to each catch block
// expects catchNode children to be (cond block) pairs.
Node cb = catchNodes.getFirstChild();
while (cb != null) {
Node catchStmt = new Node(TokenStream.BLOCK);
int catchLineNo = ((Integer)cb.getDatum()).intValue();
Node name = cb.getFirstChild();
Node cond = name.getNextSibling();
Node catchBlock = cond.getNextSibling();
cb.removeChild(name);
cb.removeChild(cond);
cb.removeChild(catchBlock);
Node newScope = createNewLocal(new Node(TokenStream.NEWSCOPE));
Node initScope = new Node(TokenStream.SETPROP, newScope,
new Node(TokenStream.STRING,
name.getString()),
createUseLocal(exn));
catchStmt.addChildToBack(new Node(TokenStream.POP, initScope));
catchBlock.addChildToBack(new Node(TokenStream.LEAVEWITH));
Node GOTOToEndCatch = new Node(TokenStream.GOTO);
GOTOToEndCatch.putProp(Node.TARGET_PROP, endCatch);
catchBlock.addChildToBack(GOTOToEndCatch);
Node ifStmt = (Node) createIf(cond, catchBlock, null, catchLineNo);
// Try..catch produces "with" code in order to limit
// the scope of the exception object.
// OPT: We should be able to figure out the correct
// scoping at compile-time and avoid the
// runtime overhead.
Node withStmt = (Node) createWith(createUseLocal(newScope),
ifStmt, catchLineNo);
catchStmt.addChildToBack(withStmt);
pn.addChildToBack(catchStmt);
// move to next cb
cb = cb.getNextSibling();
}
// Generate code to rethrow if no catch clause was executed
Node rethrow = new Node(TokenStream.THROW, createUseLocal(exn));
pn.addChildToBack(rethrow);
pn.addChildToBack(endCatch);
// add a JSR finally if needed
if (hasFinally) {
Node jsrFinally = new Node(TokenStream.JSR);
jsrFinally.putProp(Node.TARGET_PROP, finallyTarget);
pn.addChildToBack(jsrFinally);
Node GOTO = new Node(TokenStream.GOTO);
GOTO.putProp(Node.TARGET_PROP, endTarget);
pn.addChildToBack(GOTO);
}
}
if (hasFinally) {
pn.addChildToBack(finallyTarget);
Node returnTemp = createNewLocal(new Node(TokenStream.VOID));
Node popAndMake = new Node(TokenStream.POP, returnTemp);
pn.addChildToBack(popAndMake);
pn.addChildToBack(finallyNode);
Node ret = createUseLocal(returnTemp);
// add the magic prop that makes it output a RET
ret.putProp(Node.TARGET_PROP, Boolean.TRUE);
pn.addChildToBack(ret);
}
pn.addChildToBack(endTarget);
return pn;
}
/**
* Throw, Return, Label, Break and Continue are defined in ASTFactory.
*/
/**
* With
*/
public Object createWith(Object obj, Object body, int lineno) {
Node result = new Node(TokenStream.BLOCK, new Integer(lineno));
result.addChildToBack(new Node(TokenStream.ENTERWITH, (Node)obj));
Node bodyNode = new Node(TokenStream.WITH, (Node) body,
new Integer(lineno));
result.addChildrenToBack(bodyNode);
result.addChildToBack(new Node(TokenStream.LEAVEWITH));
return result;
}
/**
* Array Literal
* <BR>createArrayLiteral rewrites its argument as array creation
* plus a series of array element entries, so later compiler
* stages don't need to know about array literals.
*/
public Object createArrayLiteral(Object obj) {
Node array;
Node result;
array = result = new Node(TokenStream.NEW,
new Node(TokenStream.NAME, "Array"));
Node temp = createNewTemp(result);
result = temp;
java.util.Enumeration children = ((Node) obj).getChildIterator();
Node elem = null;
int i = 0;
while (children.hasMoreElements()) {
elem = (Node) children.nextElement();
if (elem.getType() == TokenStream.PRIMARY &&
elem.getInt() == TokenStream.UNDEFINED)
{
i++;
continue;
}
Node addelem = new Node(TokenStream.SETELEM, createUseTemp(temp),
new Node(TokenStream.NUMBER,
new Integer(i)),
elem);
i++;
result = new Node(TokenStream.COMMA, result, addelem);
}
/*
* If the version is 120, then new Array(4) means create a new
* array with 4 as the first element. In this case, we might
* need to explicitly check against trailing undefined
* elements in the array literal, and set the length manually
* if these occur. Otherwise, we can add an argument to the
* node specifying new Array() to provide the array length.
* (Which will make Array optimizations involving allocating a
* Java array to back the javascript array work better.)
*/
if (Context.getContext().getLanguageVersion() == Context.VERSION_1_2) {
/* When last array element is empty, we need to set the
* length explicitly, because we can't depend on SETELEM
* to do it for us - because empty [,,] array elements
* never set anything at all. */
if (elem != null &&
elem.getType() == TokenStream.PRIMARY &&
elem.getInt() == TokenStream.UNDEFINED)
{
Node setlength = new Node(TokenStream.SETPROP,
createUseTemp(temp),
new Node(TokenStream.STRING,
"length"),
new Node(TokenStream.NUMBER,
new Integer(i)));
result = new Node(TokenStream.COMMA, result, setlength);
}
} else {
array.addChildToBack(new Node(TokenStream.NUMBER,
new Integer(i)));
}
return new Node(TokenStream.COMMA, result, createUseTemp(temp));
}
/**
* 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.
*/
public Object createObjectLiteral(Object obj) {
Node result = new Node(TokenStream.NEW, new Node(TokenStream.NAME,
"Object"));
Node temp = createNewTemp(result);
result = temp;
java.util.Enumeration children = ((Node) obj).getChildIterator();
while (children.hasMoreElements()) {
Node elem = (Node)children.nextElement();
int op = (elem.getType() == TokenStream.NAME)
? TokenStream.SETPROP
: TokenStream.SETELEM;
Node addelem = new Node(op, createUseTemp(temp),
elem, (Node)children.nextElement());
result = new Node(TokenStream.COMMA, result, addelem);
}
return new Node(TokenStream.COMMA, result, createUseTemp(temp));
}
/**
* Regular expressions
*/
public Object createRegExp(String string, String flags) {
return flags.length() == 0
? new Node(TokenStream.OBJECT,
new Node(TokenStream.STRING, string))
: new Node(TokenStream.OBJECT,
new Node(TokenStream.STRING, string),
new Node(TokenStream.STRING, flags));
}
/**
* If statement
*/
public Object createIf(Object cond, Object ifTrue, Object ifFalse,
int lineno)
{
Node result = new Node(TokenStream.BLOCK, new Integer(lineno));
Node ifNotTarget = new Node(TokenStream.TARGET);
Node IFNE = new Node(TokenStream.IFNE, (Node) cond);
IFNE.putProp(Node.TARGET_PROP, ifNotTarget);
result.addChildToBack(IFNE);
result.addChildrenToBack((Node)ifTrue);
if (ifFalse != null) {
Node GOTOToEnd = new Node(TokenStream.GOTO);
Node endTarget = new Node(TokenStream.TARGET);
GOTOToEnd.putProp(Node.TARGET_PROP, endTarget);
result.addChildToBack(GOTOToEnd);
result.addChildToBack(ifNotTarget);
result.addChildrenToBack((Node)ifFalse);
result.addChildToBack(endTarget);
} else {
result.addChildToBack(ifNotTarget);
}
return result;
}
public Object createTernary(Object cond, Object ifTrue, Object ifFalse) {
return createIf(cond, ifTrue, ifFalse, -1);
}
/**
* Unary
*/
public Object createUnary(int nodeType, Object child) {
Node childNode = (Node) child;
if (nodeType == TokenStream.DELPROP) {
int childType = childNode.getType();
Node left;
Node right;
if (childType == TokenStream.NAME) {
// Transform Delete(Name "a")
// to Delete(Bind("a"), String("a"))
childNode.setType(TokenStream.BINDNAME);
left = childNode;
right = childNode.cloneNode();
right.setType(TokenStream.STRING);
} else if (childType == TokenStream.GETPROP ||
childType == TokenStream.GETELEM)
{
left = childNode.getFirstChild();
right = childNode.getLastChild();
childNode.removeChild(left);
childNode.removeChild(right);
} else {
reportError("msg.del.nonref");
return new Node(TokenStream.PRIMARY,
new Integer(TokenStream.TRUE));
}
return new Node(nodeType, left, right);
}
return new Node(nodeType, childNode);
}
public Object createUnary(int nodeType, int nodeOp, Object child) {
Node childNode = (Node) child;
int childType = childNode.getType();
if (nodeOp == TokenStream.TYPEOF &&
childType == TokenStream.NAME)
{
childNode.setType(TokenStream.TYPEOF);
return childNode;
}
if (nodeType == TokenStream.INC || nodeType == TokenStream.DEC) {
if (!hasSideEffects(childNode)
&& (nodeOp == TokenStream.POST)
&& (childType == TokenStream.NAME
|| childType == TokenStream.GETPROP
|| childType == TokenStream.GETELEM))
{
// if it's not a LHS type, createAssignment (below) will throw
// an exception.
return new Node(nodeType, childNode);
}
/*
* Transform INC/DEC ops to +=1, -=1,
* expecting later optimization of all +/-=1 cases to INC, DEC.
*/
// we have to use Double for now, because
// 0.0 and 1.0 are stored as dconst_[01],
// and using a Float creates a stack mismatch.
Node rhs = (Node) createNumber(new Double(1.0));
return createAssignment(nodeType == TokenStream.INC
? TokenStream.ADD
: TokenStream.SUB,
childNode,
rhs,
ScriptRuntime.NumberClass,
nodeOp == TokenStream.POST);
}
Node result = new Node(nodeType, new Integer(nodeOp));
result.addChildToBack((Node)child);
return result;
}
/**
* Binary
*/
public Object createBinary(int nodeType, Object left, Object right) {
Node temp;
switch (nodeType) {
case TokenStream.DOT:
nodeType = TokenStream.GETPROP;
Node idNode = (Node) right;
idNode.setType(TokenStream.STRING);
String id = idNode.getString();
if (id.equals("__proto__") || id.equals("__parent__")) {
Node result = new Node(nodeType, (Node) left);
result.putProp(Node.SPECIAL_PROP_PROP, id);
return result;
}
break;
case TokenStream.LB:
// OPT: could optimize to GETPROP iff string can't be a number
nodeType = TokenStream.GETELEM;
break;
/*
case TokenStream.AND:
temp = createNewTemp((Node) left);
return createTernary(temp, right, createUseTemp(temp));
case TokenStream.OR:
temp = createNewTemp((Node) left);
return createTernary(temp, createUseTemp(temp), right);
*/
}
return new Node(nodeType, (Node)left, (Node)right);
}
public Object createBinary(int nodeType, int nodeOp, Object left,
Object right)
{
if (nodeType == TokenStream.ASSIGN) {
return createAssignment(nodeOp, (Node) left, (Node) right,
null, false);
}
return new Node(nodeType, (Node) left, (Node) right,
new Integer(nodeOp));
}
public Object createAssignment(int nodeOp, Node left, Node right,
Class convert, boolean postfix)
{
int nodeType = left.getType();
String idString;
Node id = null;
switch (nodeType) {
case TokenStream.NAME:
return createSetName(nodeOp, left, right, convert, postfix);
case TokenStream.GETPROP:
idString = (String) left.getProp(Node.SPECIAL_PROP_PROP);
if (idString != null)
id = new Node(TokenStream.STRING, idString);
/* fall through */
case TokenStream.GETELEM:
if (id == null)
id = left.getLastChild();
return createSetProp(nodeType, nodeOp, left.getFirstChild(),
id, right, convert, postfix);
default:
reportError("msg.bad.lhs.assign");
return left;
}
}
private Node createConvert(Class toType, Node expr) {
if (toType == null)
return expr;
Node result = new Node(TokenStream.CONVERT, expr);
result.putProp(Node.TYPE_PROP, ScriptRuntime.NumberClass);
return result;
}
private Object createSetName(int nodeOp, Node left, Node right,
Class convert, boolean postfix)
{
if (nodeOp == TokenStream.NOP) {
left.setType(TokenStream.BINDNAME);
return new Node(TokenStream.SETNAME, left, right);
}
String s = left.getString();
if (s.equals("__proto__") || s.equals("__parent__")) {
Node result = new Node(TokenStream.SETPROP, left, right);
result.putProp(Node.SPECIAL_PROP_PROP, s);
return result;
}
Node opLeft = new Node(TokenStream.NAME, s);
if (convert != null)
opLeft = createConvert(convert, opLeft);
if (postfix)
opLeft = createNewTemp(opLeft);
Node op = new Node(nodeOp, opLeft, right);
Node lvalueLeft = new Node(TokenStream.BINDNAME, s);
Node result = new Node(TokenStream.SETNAME, lvalueLeft, op);
if (postfix) {
result = new Node(TokenStream.COMMA, result,
createUseTemp(opLeft));
}
return result;
}
public Node createNewTemp(Node n) {
int type = n.getType();
if (type == TokenStream.STRING || type == TokenStream.NUMBER) {
// Optimization: clone these values rather than storing
// and loading from a temp
return n;
}
Node result = new Node(TokenStream.NEWTEMP, n);
return result;
}
public Node createUseTemp(Node newTemp) {
int type = newTemp.getType();
if (type == TokenStream.NEWTEMP) {
Node result = new Node(TokenStream.USETEMP);
result.putProp(Node.TEMP_PROP, newTemp);
Integer n = (Integer) newTemp.getProp(Node.USES_PROP);
if (n == null) {
n = new Integer(1);
} else {
if (n.intValue() < Integer.MAX_VALUE)
n = new Integer(n.intValue() + 1);
}
newTemp.putProp(Node.USES_PROP, n);
return result;
}
return newTemp.cloneNode();
}
public Node createNewLocal(Node n) {
Node result = new Node(TokenStream.NEWLOCAL, n);
return result;
}
public Node createUseLocal(Node newLocal) {
int type = newLocal.getType();
if (type == TokenStream.NEWLOCAL) {
Node result = new Node(TokenStream.USELOCAL);
result.putProp(Node.LOCAL_PROP, newLocal);
return result;
}
return newLocal.cloneNode(); // what's this path for ?
}
public static boolean hasSideEffects(Node exprTree) {
switch (exprTree.getType()) {
case TokenStream.SETPROP:
case TokenStream.SETELEM:
case TokenStream.CALL:
case TokenStream.NEW:
return true;
default:
Node child = exprTree.getFirstChild();
while (child != null) {
if (hasSideEffects(child))
return true;
else
child = child.getNextSibling();
}
break;
}
return false;
}
private Node createSetProp(int nodeType, int nodeOp, Node obj, Node id,
Node expr, Class convert, boolean postfix)
{
int type = nodeType == TokenStream.GETPROP
? TokenStream.SETPROP
: TokenStream.SETELEM;
Object datum = id.getDatum();
if (type == TokenStream.SETPROP && datum != null &&
datum instanceof String)
{
String s = (String) datum;
if (s.equals("__proto__") || s.equals("__parent__")) {
Node result = new Node(type, obj, expr);
result.putProp(Node.SPECIAL_PROP_PROP, s);
return result;
}
}
if (nodeOp == TokenStream.NOP)
return new Node(type, obj, id, expr);
/*
if the RHS expression could modify the LHS we have
to construct a temporary to hold the LHS context
prior to running the expression
*/
Node tmp1, tmp2, opLeft;
if (hasSideEffects(expr) || (obj.getType() != TokenStream.NAME)) {
tmp1 = createNewTemp(obj);
Node useTmp1 = createUseTemp(tmp1);
tmp2 = createNewTemp(id);
Node useTmp2 = createUseTemp(tmp2);
opLeft = new Node(nodeType, useTmp1, useTmp2);
} else {
tmp1 = obj.cloneNode();
tmp2 = id.cloneNode();
opLeft = new Node(nodeType, obj, id);
}
if (convert != null)
opLeft = createConvert(convert, opLeft);
if (postfix)
opLeft = createNewTemp(opLeft);
Node op = new Node(nodeOp, opLeft, expr);
Node result = new Node(type, tmp1, tmp2, op);
if (postfix) {
result = new Node(TokenStream.COMMA, result,
createUseTemp(opLeft));
}
return result;
}
private void reportError(String msgResource) {
String message = Context.getMessage(msgResource, null);
Context.reportError(message, ts.getSourceName(), ts.getLineno(),
ts.getLine(), ts.getOffset());
}
// Only needed to get file/line information. Could create an interface
// that TokenStream implements if we want to make the connection less
// direct.
private TokenStream ts;
}