mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-30 13:45:27 +00:00
1010 lines
36 KiB
Java
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;
|
|
}
|
|
|