/*
 * The contents of this file are subject to the Netscape 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/NPL/

 * 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 Netscape are
 * Copyright (C) 1997-1999 Netscape Communications Corporation. All
 * Rights Reserved.

 * Contributor(s): 

 * Alternatively, the contents of this file may be used under the
 * terms of the GNU Public License (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 NPL, indicate your decision by
 * deleting the provisions above and replace 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 NPL or the GPL.
 */
/* Header file */
#include <limits.h>
#include "js-scriptruntime.h"
#include "js-nativeglobal.h"
#include "js-exceptionhandler.h"
#include "js-functionnode.h"
#include "js-utils.h"
#include "js-irfactory.h"

/* Procedures */
JSNode *
JSIRFactory::createConvert(JSContext *cx, jsType_t toType, JSNode *expr)
{
    if (toType == JS_T_Null) {
        return expr;
    }
    JSNode *result = new (cx) JSNode(JSTokenStream::CONVERT, expr);

    result->putProp(cx, JSNode::TYPE_PROP, JS_T_Number);
    return result;
}
JSNode *
JSIRFactory::createSetName(JSContext *cx, int nodeOp, JSNode *left,
			JSNode *right, jsType_t convert, jsBool_t postfix)
{
    if (nodeOp == JSTokenStream::NOP) {
        left->setType(JSTokenStream::BINDNAME);
        return new (cx) JSNode(JSTokenStream::SETNAME, left, right);
    }

    jsString_t s = left->getString();

    if (strcmp(s, "__proto__") == 0 || strcmp(s, "__parent__") == 0) {
        JSNode *result = new (cx) JSNode(JSTokenStream::SETPROP, left, right);

        result->putProp(cx, JSNode::SPECIAL_PROP_PROP, s);
        return result;
    }
    JSNode *opLeft = new (cx) JSNode(cx, JSTokenStream::NAME, s);

    if (convert != JS_T_Null) {
        opLeft = createConvert(cx, convert, opLeft);
    }
    if (postfix) {
        opLeft = createNewTemp(cx, opLeft);
    }
    JSNode *op = new (cx) JSNode(nodeOp, opLeft, right);

    JSNode *lvalueLeft = new (cx) JSNode(cx, JSTokenStream::BINDNAME, s);
    JSNode *result = new (cx) JSNode(JSTokenStream::SETNAME, lvalueLeft, op);
    if (postfix) {
        result = new (cx) JSNode(JSTokenStream::COMMA, result,
				createUseTemp(cx, opLeft));
    }
    left->release( cx );
    return result;
}
JSNode *
JSIRFactory::createSetProp(JSContext *cx, int nodeType, int nodeOp,
			JSNode *obj, JSNode *id, JSNode *expr,
			jsType_t convert, jsBool_t postfix)
{
    int type = nodeType == JSTokenStream::GETPROP
			? (int) JSTokenStream::SETPROP
			: (int) JSTokenStream::SETELEM;

    jsString_t s;
    jsBool_t ok = id->getDatum(&s);

    if (type == JSTokenStream::SETPROP && ok) {
        if (strcmp(s, "__proto__") == 0 || strcmp(s, "__parent__") == 0) {
            JSNode *result = new (cx) JSNode(type, obj, expr);

            result->putProp(cx, JSNode::SPECIAL_PROP_PROP, s);
            return result;
        }
    }

    if (nodeOp == JSTokenStream::NOP) {
        return new (cx) JSNode(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. Ditto, if the id
*expression has side-effects.
*
*XXX If the hasSideEffects tests take too long, we
*   could make this an optimizer-only transform
*   and always do the temp assignment otherwise.
*
*/
    JSNode *tmp1, *tmp2, *opLeft;

    if (hasSideEffects(expr)
            || hasSideEffects(id)
            || (obj->getType() != JSTokenStream::NAME))  {
        tmp1 = createNewTemp(cx, obj);
        JSNode *useTmp1 = createUseTemp(cx, tmp1);

        tmp2 = createNewTemp(cx, id);
        JSNode *useTmp2 = createUseTemp(cx, tmp2);

        opLeft = new (cx) JSNode(nodeType, useTmp1, useTmp2);
    } else {
        tmp1 = obj->cloneNode(cx);
        tmp2 = id->cloneNode(cx);
        opLeft = new (cx) JSNode(nodeType, obj, id);
    }

    if (convert != JS_T_Null) {
        opLeft = createConvert(cx, convert, opLeft);
    }
    if (postfix) {
        opLeft = createNewTemp(cx, opLeft);
    }
    JSNode *op = new (cx) JSNode(nodeOp, opLeft, expr);

    JSNode *result = new (cx) JSNode(type, tmp1, tmp2, op);
    if (postfix) {
        result = new (cx) JSNode(JSTokenStream::COMMA, result,
				createUseTemp(cx, opLeft));
    }

    return result;
}
void
JSIRFactory::reportError(JSContext *cx, jsString_t msgResource)
{
    if (scope != NULL) {
        cx->exceptionHandler->throwEx(
                cx,
		JSNativeGlobal::constructError(cx, "SyntaxError",
			JSScriptRuntime::getMessage(cx, msgResource),
			scope));
    } else {
        jsString_t message = JSContext::getMessage(cx, msgResource);

        JSContext::reportError(cx, message, ts->getSourceName(),
			ts->getLineno(), ts->getLine(cx), ts->getOffset());
    }
}

jsBool_t
JSIRFactory::hasSideEffects(JSNode *exprTree)
{
    switch (exprTree->getType()) {
    case JSTokenStream::INC:
    case JSTokenStream::DEC:
    case JSTokenStream::SETPROP:
    case JSTokenStream::SETELEM:
    case JSTokenStream::SETNAME:
    case JSTokenStream::CALL:
    case JSTokenStream::NEW:
        return TRUE;
    default:
        JSNode *child = exprTree->getFirstChild();

        while (child != NULL) {
            if (hasSideEffects(child)) {
                return TRUE;
            } else {
                child = child->getNextSibling();
	    }
        }
        break;
    }
    return FALSE;
}

JSNode *
JSIRFactory::createScript(JSContext *cx, JSNode *body, jsString_t sourceName, 
		int baseLineno, int endLineno, jsString_t source,
		int sourceLength)
{
    JSNode *result = new (cx) JSNode(cx, JSTokenStream::SCRIPT, sourceName);
    JSNode *children = ((JSNode *) body)->getFirstChild();

    if (children != NULL) {
	result->addChildrenToBack(children);
    }
    result->putProp(cx, JSNode::SOURCENAME_PROP, sourceName);
    result->putProp(cx, JSNode::BASE_LINENO_PROP, baseLineno);
    result->putProp(cx, JSNode::END_LINENO_PROP, endLineno);
    if (source != NULL) {
	result->putProp(cx, JSNode::SOURCE_PROP, source, sourceLength);
    }
    return result;
}
/**
 * Leaf
 */
JSNode *
JSIRFactory::createLeaf(JSContext *cx, int nodeType)
{
    return new (cx) JSNode(nodeType);
}
JSNode *
JSIRFactory::createLeaf(JSContext *cx, int nodeType, jsString_t id)
{
    return new (cx) JSNode(cx, nodeType, id);
}
JSNode *
JSIRFactory::createLeaf(JSContext *cx, int nodeType, int nodeOp)
{
    return new (cx) JSNode(cx, nodeType, nodeOp);
}
/**
 * Statement leaf nodes.
 */
JSNode *
JSIRFactory::createSwitch(JSContext *cx, int lineno)
{
    return new (cx) JSNode(cx, JSTokenStream::SWITCH, lineno);
}
JSNode *
JSIRFactory::createVariables(JSContext *cx, int lineno)
{
    return new (cx) JSNode(cx, JSTokenStream::VAR, lineno);
}
JSNode *
JSIRFactory::createExprStatement(JSContext *cx, JSNode *expr, int lineno)
{
    return new (cx) JSNode(cx, JSTokenStream::EXPRSTMT, expr, lineno);
}
/**
 * Name
 */
JSNode *
JSIRFactory::createName(JSContext *cx, jsString_t name)
{
    return new (cx) JSNode(cx, JSTokenStream::NAME, name);
}
/**
 * String (for literals)
 */
JSNode *
JSIRFactory::createString(JSContext *cx, jsString_t string)
{
    return new (cx) JSNode(cx, JSTokenStream::STRING, string);
}
/**
 * Number (for literals)
 */

JSNode *
JSIRFactory::createNumber(JSContext *cx, double number)
{
    return new (cx) JSNode(cx, JSTokenStream::NUMBER, number);
}
/**
 * Catch clause of try/catch/finally
 */
JSNode *
JSIRFactory::createCatch(JSContext *cx, jsString_t varName, JSNode *catchCond,
			JSNode *stmts, int lineno)
{
    if (catchCond == NULL) {
        catchCond = new (cx) JSNode(cx, JSTokenStream::PRIMARY,
					JSTokenStream::_TRUE);
    }
    JSNode *result = new (cx) JSNode(JSTokenStream::CATCH,
					createName(cx, varName), 
					catchCond, stmts);
    result->setDatum(cx, lineno);
    return result;
}
/**
 * Throw
 */
JSNode *
JSIRFactory::createThrow(JSContext *cx, JSNode *expr, int lineno)
{
    return new (cx) JSNode(cx, JSTokenStream::THROW, expr, lineno);
}
/**
 * Return
 */
JSNode *
JSIRFactory::createReturn(JSContext *cx, JSNode *expr, int lineno)
{
    return expr == NULL
        ? new (cx) JSNode(cx, JSTokenStream::RETURN, lineno)
        : new (cx) JSNode(cx, JSTokenStream::RETURN, expr, lineno);
}
/**
 * Label
 */
JSNode *
JSIRFactory::createLabel(JSContext *cx, jsString_t label, int lineno)
{
    JSNode *result = new (cx) JSNode(cx, JSTokenStream::LABEL, lineno);
    JSNode *name = new (cx) JSNode(cx, JSTokenStream::NAME, label);

    result->addChildToBack(name);
    return result;
}

/**
 * Break (possibly labeled)
 */

JSNode *
JSIRFactory::createBreak(JSContext *cx, jsString_t label, int lineno)
{
    JSNode *result = new (cx) JSNode(cx, JSTokenStream::BREAK, lineno);

    if (label == NULL) {
        return result;
    } else {
        JSNode *name = new (cx) JSNode(cx, JSTokenStream::NAME, label);

        result->addChildToBack(name);
        return result;
    }
}
/**
 * Continue (possibly labeled)
 */
JSNode *
JSIRFactory::createContinue(JSContext *cx, jsString_t label, int lineno)
{
    JSNode *result = new (cx) JSNode(cx, JSTokenStream::CONTINUE, lineno);
    if (label == NULL) {
        return result;
    } else {
        JSNode *name = new (cx) JSNode(cx, JSTokenStream::NAME, label);
        result->addChildToBack(name);

        return result;
    }
}
/**
 * Statement block
 * Creates the empty statement block
 * Must make subsequent calls to add statements to the node
 */
JSNode *
JSIRFactory::createBlock(JSContext *cx, int lineno)
{
    return new (cx) JSNode(cx, JSTokenStream::BLOCK, lineno);
}
JSNode *
JSIRFactory::createFunctionNode(JSContext *cx, jsString_t name, JSNode *args, 
                                 JSNode *statements) 
{
    if (name == NULL) {
        name = "";
    }
    return new (cx) JSFunctionNode(cx, name, args, statements);
}
JSNode *
JSIRFactory::createFunction(JSContext *cx, jsString_t name, JSNode *args,
			JSNode *statements, jsString_t sourceName,
			int baseLineno, int endLineno, jsString_t source,
			int sourceLength, jsBool_t isExpr)
{
    JSFunctionNode *f = (JSFunctionNode *) createFunctionNode(cx, name, args, 
                                                       statements);

    if (isExpr) {
	f->setFunctionType(JSFunctionNode::FUNCTION_EXPRESSION);
    } else {
	f->setFunctionType(JSFunctionNode::FUNCTION_STATEMENT);
    }
    f->putProp(cx, JSNode::SOURCENAME_PROP, sourceName);
    f->putProp(cx, JSNode::BASE_LINENO_PROP, baseLineno);
    f->putProp(cx, JSNode::END_LINENO_PROP, endLineno);
    if (source != NULL) {
	f->putProp(cx, JSNode::SOURCE_PROP, source, sourceLength);
    }
    JSNode *result = new (cx) JSNode(cx, JSTokenStream::FUNCTION, name);

    result->putProp(cx, JSNode::FUNCTION_PROP, f);
    f->release(cx);	// decrement reference count
    return result;
}

void
JSIRFactory::setFunctionExpressionStatement(JSContext *cx, JSNode *o)
{
    JSNode *n = o;
    JSFunctionNode *f;
    JSNode* _f; /* avoid warnign errors */

    (void) n->getProp(JSNode::FUNCTION_PROP, &_f);
    f = ( JSFunctionNode* ) _f;

    f->setFunctionType(JSFunctionNode::FUNCTION_EXPRESSION_STATEMENT);
}
/**
 * Add a child to the back of the given node.  This function
 * breaks the Factory abstraction, but it removes a requirement
 * from implementors of JSNode::
 */
void
JSIRFactory::addChildToBack(JSNode *parent, JSNode *child)
{
    parent->addChildToBack(child);
}
/**
 * While
 */
JSNode *
JSIRFactory::createWhile(JSContext *cx, JSNode *cond, JSNode *body, int lineno)
{
    // Just add a GOTO to the condition in the do..while
    JSNode *result = createDoWhile(cx, body, cond, lineno);
    JSNode *condTarget;

    (void) result->getProp(JSNode::CONTINUE_PROP, &condTarget);

    JSNode *GOTO = new (cx) JSNode(JSTokenStream::GOTO);

    GOTO->putProp(cx, JSNode::TARGET_PROP, condTarget);
    result->addChildToFront(GOTO);
    return result;
}
/**
 * DoWhile
 */
JSNode *
JSIRFactory::createDoWhile(JSContext *cx, JSNode *body, JSNode *cond,
			int lineno)
{
    JSNode *result = new (cx) JSNode(cx, JSTokenStream::LOOP, lineno);
    JSNode *bodyTarget = new (cx) JSNode(JSTokenStream::TARGET);
    JSNode *condTarget = new (cx) JSNode(JSTokenStream::TARGET);
    JSNode *IFEQ = new (cx) JSNode(JSTokenStream::IFEQ, cond);

    IFEQ->putProp(cx, JSNode::TARGET_PROP, bodyTarget);

    JSNode *breakTarget = new (cx) JSNode(JSTokenStream::TARGET);

    result->addChildToBack(bodyTarget);
    result->addChildrenToBack(body);
    result->addChildToBack(condTarget);
    result->addChildToBack(IFEQ);
    result->addChildToBack(breakTarget);

    result->putProp(cx, JSNode::BREAK_PROP, breakTarget);
    result->putProp(cx, JSNode::CONTINUE_PROP, condTarget);

    return result;
}
/**
 * For
 */
JSNode *
JSIRFactory::createFor(JSContext *cx, JSNode *init, JSNode *test, JSNode *incr,
		JSNode *body, int lineno)
{
    if (test->getType() == JSTokenStream::VOID) {
        test = new (cx) JSNode(cx, JSTokenStream::PRIMARY,
				JSTokenStream::_TRUE);
    }
    JSNode *result = createWhile(cx, test, body, lineno);
    JSNode *initNode = init;

    if (initNode->getType() != JSTokenStream::VOID) {
        if (initNode->getType() != JSTokenStream::VAR) {
            initNode = new (cx) JSNode(JSTokenStream::POP, initNode);
	}
        result->addChildToFront(initNode);
    } else {
        init->release( cx );
    }
    JSNode *condTarget;

    (void) result->getProp(JSNode::CONTINUE_PROP, &condTarget);

    JSNode *incrTarget = new (cx) JSNode(JSTokenStream::TARGET);

    result->addChildBefore(incrTarget, condTarget);
    if (incr->getType() != JSTokenStream::VOID) {
        incr = createUnary(cx, JSTokenStream::POP, incr);
        result->addChildAfter(incr, incrTarget);
    } else {
        incr->release( cx );
    }
    result->putProp(cx, JSNode::CONTINUE_PROP, incrTarget);
    return result;
}
/**
 * For .. In
 *
 */
JSNode *
JSIRFactory::createForIn(JSContext *cx, JSNode *lhs, JSNode *obj,
			JSNode *body, int lineno)
{
    JSNode *lhsNode = lhs;
    JSNode *objNode = obj;
    int type = lhsNode->getType();
    JSNode *lvalue = lhsNode;

    switch (type) {

      case JSTokenStream::NAME:
      case JSTokenStream::GETPROP:
      case JSTokenStream::GETELEM:
        break;

      case JSTokenStream::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.
         */
        JSNode *lastChild = lhsNode->getLastChild();

        if (lhsNode->getFirstChild() != lastChild) {
	    reportError(cx, "msg.mult.index");
        }
        lvalue = new (cx) JSNode(cx, JSTokenStream::NAME,
				lastChild->getString());
        break;

    }
    default:
        reportError(cx, "msg.bad.for.in.lhs");
        return objNode;
    }

    JSNode *init = new (cx) JSNode(JSTokenStream::ENUMINIT, objNode);
    JSNode *next = new (cx) JSNode(JSTokenStream::ENUMNEXT);

    next->putProp(cx, JSNode::ENUM_PROP, init);

    JSNode *temp = createNewTemp(cx, next);
    JSNode *cond = new (cx) JSNode(cx, JSTokenStream::EQOP, JSTokenStream::NE);

    cond->addChildToBack(temp);
#if defined(CYGWIN)
    cond->addChildToBack(new (cx) JSNode(cx, JSTokenStream::PRIMARY,
					JSTokenStream::__NULL));
#else /* !defined(CYGWIN) */
    cond->addChildToBack(new (cx) JSNode(cx, JSTokenStream::PRIMARY,
					JSTokenStream::_NULL));
#endif /* !defined(CYGWIN) */
    JSNode *newBody = new (cx) JSNode(JSTokenStream::BLOCK);
    JSNode *assign = createAssignment(cx, JSTokenStream::NOP, lvalue,
				createUseTemp(cx, temp), JS_T_Null, FALSE);
    newBody->addChildToBack(new (cx) JSNode(JSTokenStream::POP, assign));
    newBody->addChildToBack(body);
    JSNode *result = createWhile(cx, cond, newBody, lineno);

    result->addChildToFront(init);
    if (type == JSTokenStream::VAR) {
        result->addChildToFront(lhsNode);
    }

    JSNode *done = new (cx) JSNode(JSTokenStream::ENUMDONE);
    done->putProp(cx, JSNode::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.
 */
JSNode *
JSIRFactory::createTryCatchFinally(JSContext *cx, JSNode *tryblock,
				JSNode *catchblocks, JSNode *finallyblock,
				int lineno)
{
    JSNode *trynode = tryblock;

    // short circuit
    if (trynode->getType() == JSTokenStream::BLOCK && !trynode->hasChildren()) {
        return trynode;
    }

    JSNode *pn = new (cx) JSNode(cx, JSTokenStream::TRY, trynode, lineno);
    JSNode *catchNodes = catchblocks;
    jsBool_t hasCatch = catchNodes->hasChildren();
    jsBool_t hasFinally = FALSE;
    JSNode *finallyNode = NULL;
    JSNode *finallyTarget = NULL;

    if (finallyblock != NULL) {
        finallyNode = finallyblock;
        hasFinally = (finallyNode->getType() != JSTokenStream::BLOCK
                      || finallyNode->hasChildren());

        if (hasFinally) {
            // make a TARGET for the finally that the tcf node knows about
            finallyTarget = new (cx) JSNode(JSTokenStream::TARGET);

            pn->putProp(cx, JSNode::FINALLY_PROP, finallyTarget);

            // add jsr finally to the try block
            JSNode *jsrFinally = new (cx) JSNode(JSTokenStream::JSR);
            jsrFinally->putProp(cx, JSNode::TARGET_PROP, finallyTarget);
            pn->addChildToBack(jsrFinally);
        }
    }

    // short circuit
    if (!hasFinally && !hasCatch) { // bc finally might be an empty block...
        return trynode;
    }

    JSNode *endTarget = new (cx) JSNode(JSTokenStream::TARGET);
    JSNode *GOTOToEnd = new (cx) JSNode(JSTokenStream::GOTO);
    GOTOToEnd->putProp(cx, JSNode::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
        JSNode *catchTarget = new (cx) JSNode(JSTokenStream::TARGET);

        pn->putProp(cx, JSNode::TARGET_PROP, catchTarget);
        // mark it
        pn->addChildToBack(catchTarget);
        
        // get the exception object and store it in a temp
        JSNode *exn = createNewLocal(cx, new (cx) JSNode(JSTokenStream::VOID));

        pn->addChildToBack(new (cx) JSNode(JSTokenStream::POP, exn));
        
        JSNode *endCatch = new (cx) JSNode(JSTokenStream::TARGET);

        // add [jsr finally?] goto end to each catch block
        // expects catchNode children to be (cond block) pairs.
        JSNode *cb = catchNodes->getFirstChild();

        while (cb != NULL) {
            JSNode *catchStmt = new (cx) JSNode(JSTokenStream::BLOCK);
            int catchLineNo;
            
	    cb->getDatum(&catchLineNo);

            JSNode *name = cb->getFirstChild();
            JSNode *cond = name->getNextSibling();
            JSNode *catchBlock = cond->getNextSibling();
            cb->removeChild(name);
            cb->removeChild(cond);
            cb->removeChild(catchBlock);
            
            JSNode *newScope
			= createNewLocal(cx,
				new (cx) JSNode(JSTokenStream::NEWSCOPE));
            JSNode *initScope
		= new (cx) JSNode(JSTokenStream::SETPROP,
				newScope, 
				new (cx) JSNode(cx, JSTokenStream::STRING, 
						name->getString()), 
				createUseLocal(cx, exn));
	    name->release(cx);
            catchStmt->addChildToBack(
			new (cx) JSNode(JSTokenStream::POP, initScope));
            
            catchBlock->addChildToBack(
				new (cx) JSNode(JSTokenStream::LEAVEWITH));

            JSNode *GOTOToEndCatch = new (cx) JSNode(JSTokenStream::GOTO);
            GOTOToEndCatch->putProp(cx, JSNode::TARGET_PROP, endCatch);
            catchBlock->addChildToBack(GOTOToEndCatch);
            
            JSNode *ifStmt
		= (JSNode *) createIf(cx, 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.
            JSNode *withStmt
		= (JSNode *) createWith(cx, createUseLocal(cx, 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
        JSNode *rethrow
		= new (cx) JSNode(JSTokenStream::THROW,
				createUseLocal(cx, exn));
        pn->addChildToBack(rethrow);

        pn->addChildToBack(endCatch);
        // add a JSR finally if needed
        if (hasFinally) {
            JSNode *jsrFinally = new (cx) JSNode(JSTokenStream::JSR);

            jsrFinally->putProp(cx, JSNode::TARGET_PROP, finallyTarget);
            pn->addChildToBack(jsrFinally);

            JSNode *GOTO = new (cx) JSNode(JSTokenStream::GOTO);

            GOTO->putProp(cx, JSNode::TARGET_PROP, endTarget);
            pn->addChildToBack(GOTO);
        }
    }

    if (hasFinally) {
        pn->addChildToBack(finallyTarget);
        JSNode *returnTemp
		= createNewLocal(cx, new (cx) JSNode(JSTokenStream::VOID));
        JSNode *popAndMake
		= new (cx) JSNode(JSTokenStream::POP, returnTemp);

        pn->addChildToBack(popAndMake);
        pn->addChildToBack(finallyNode);

        JSNode *ret = createUseLocal(cx, returnTemp);

        // add the magic prop that makes it output a RET
        ret->putProp(cx, JSNode::TARGET_PROP, TRUE);
        pn->addChildToBack(ret);
    }
    pn->addChildToBack(endTarget);

    return pn;
}
/**
 * Throw, Return, Label, Break and Continue are defined in ASTFactory.
 */
/**
 * With
 */
JSNode *
JSIRFactory::createWith(JSContext *cx, JSNode *obj, JSNode *body, int lineno)
{
    JSNode *result = new (cx) JSNode(cx, JSTokenStream::BLOCK, lineno);

    result->addChildToBack(new (cx) JSNode(JSTokenStream::ENTERWITH, obj));

    JSNode *bodyNode = new (cx) JSNode(cx, JSTokenStream::WITH, body,
					lineno);

    result->addChildrenToBack(bodyNode);
    result->addChildToBack(new (cx) JSNode(JSTokenStream::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.
 */
JSNode *
JSIRFactory::createArrayLiteral(JSContext *cx, JSNode *obj)
{
    JSNode *array;
    JSNode *result;

    array = result = new (cx) JSNode(JSTokenStream::NEW,
				new (cx) JSNode(cx, JSTokenStream::NAME,
						"Array"));
    JSNode *temp = createNewTemp(cx, result);

    result = temp;

    JSShallowNodeIterator children = obj->getChildIterator();

    JSNode *elem = NULL;
    int i = 0;

    while (children.hasMoreElements()) {
        elem = children.nextElement();
        if (elem->getType() == JSTokenStream::PRIMARY &&
            elem->getInt() == JSTokenStream::UNDEFINED) {
            i++;
            continue;
        }
        JSNode *addelem
		= new (cx) JSNode(JSTokenStream::SETELEM,
				createUseTemp(cx, temp),
				new (cx) JSNode(cx, JSTokenStream::NUMBER, i),
				elem);
        i++;
        result = new (cx) JSNode(JSTokenStream::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 (cx->getLanguageVersion() == JSContext::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() == JSTokenStream::PRIMARY &&
            elem->getInt() == JSTokenStream::UNDEFINED) {
            JSNode *setlength
			= new (cx) JSNode(JSTokenStream::SETPROP,
					createUseTemp(cx, temp),
					new (cx) JSNode(cx,
							JSTokenStream::STRING,
							"length"),
					new (cx) JSNode(cx,
							JSTokenStream::NUMBER,
							i));
            result = new (cx) JSNode(JSTokenStream::COMMA, result, setlength);
        }
    } else {
        array->addChildToBack(new (cx) JSNode(cx, JSTokenStream::NUMBER, i));
    }
#ifdef REUSE_LOCAL
    result = new (cx) JSNode(JSTokenStream::COMMA, result,
			createUseTemp(cx, temp));
    return createDelTemp(cx, result, temp);
#else
    return new (cx) JSNode(JSTokenStream::COMMA, result,
			createUseTemp(cx, temp));
#endif
}
/**
 * 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.
 */
JSNode *
JSIRFactory::createObjectLiteral(JSContext *cx, JSNode *obj)
{
    JSNode *result = new (cx) JSNode(JSTokenStream::NEW,
				new (cx) JSNode(cx, JSTokenStream::NAME,
						"Object"));
    JSNode *temp = createNewTemp(cx, result);

    result = temp;

    JSShallowNodeIterator children = obj->getChildIterator();

    while (children.hasMoreElements()) {
        JSNode *elem = children.nextElement();

        int op = (elem->getType() == JSTokenStream::NAME)
               ? (int) JSTokenStream::SETPROP
               : (int) JSTokenStream::SETELEM;
        JSNode *addelem = new (cx) JSNode(op, createUseTemp(cx, temp),
					elem, children.nextElement());
        result = new (cx) JSNode(JSTokenStream::COMMA, result, addelem);
    }
#ifdef REUSE_LOCAL
    result = new (cx) JSNode(JSTokenStream::COMMA, result,
			createUseTemp(cx, temp));
    return createDelTemp(cx, result, temp);
#else
    return new (cx) JSNode(JSTokenStream::COMMA, result,
			createUseTemp(cx, temp));
#endif
}
/**
 * Regular expressions
 */
JSNode *
JSIRFactory::createRegExp(JSContext *cx, jsString_t string, jsString_t flags)
{
    return strlen(flags) == 0
           ? new (cx) JSNode(JSTokenStream::OBJECT,
                      new (cx) JSNode(cx, JSTokenStream::STRING, string))
           : new (cx) JSNode(JSTokenStream::OBJECT,
                      new (cx) JSNode(cx, JSTokenStream::STRING, string),
                      new (cx) JSNode(cx, JSTokenStream::STRING, flags));
}
/**
 * If statement
 */
JSNode *
JSIRFactory::createIf(JSContext *cx, JSNode *cond, JSNode *ifTrue,
		JSNode *ifFalse, int lineno)
{
    JSNode *result = new (cx) JSNode(cx, JSTokenStream::BLOCK, lineno);
    JSNode *ifNotTarget = new (cx) JSNode(JSTokenStream::TARGET);
    JSNode *IFNE = new (cx) JSNode(JSTokenStream::IFNE, cond);

    IFNE->putProp(cx, JSNode::TARGET_PROP, ifNotTarget);

    result->addChildToBack(IFNE);
    result->addChildrenToBack(ifTrue);

    if (ifFalse != NULL) {
        JSNode *GOTOToEnd = new (cx) JSNode(JSTokenStream::GOTO);
        JSNode *endTarget = new (cx) JSNode(JSTokenStream::TARGET);

        GOTOToEnd->putProp(cx, JSNode::TARGET_PROP, endTarget);
        result->addChildToBack(GOTOToEnd);
        result->addChildToBack(ifNotTarget);
        result->addChildrenToBack(ifFalse);
        result->addChildToBack(endTarget);
    } else {
        result->addChildToBack(ifNotTarget);
    }
    return result;
}
JSNode *
JSIRFactory::createTernary(JSContext *cx, JSNode *cond, JSNode *ifTrue,
			JSNode *ifFalse)
{
    return createIf(cx, cond, ifTrue, ifFalse, -1);
}
/**
 * Unary
 */
JSNode *
JSIRFactory::createUnary(JSContext *cx, int nodeType, JSNode *child)
{
    JSNode *childNode = child;

    if (nodeType == JSTokenStream::DELPROP) {
        int childType = childNode->getType();
        JSNode *left;
        JSNode *right;

        if (childType == JSTokenStream::NAME) {
            // Transform Delete(Name "a")
            //  to Delete(Bind("a"), String("a"))
            childNode->setType(JSTokenStream::BINDNAME);
            left = childNode;
            right = childNode->cloneNode(cx);
            right->setType(JSTokenStream::STRING);
        } else if (childType == JSTokenStream::GETPROP ||
                   childType == JSTokenStream::GETELEM) {
            left = childNode->getFirstChild();
            right = childNode->getLastChild();
            childNode->removeChild(left);
            childNode->removeChild(right);
        } else {
            return new (cx) JSNode(cx, JSTokenStream::PRIMARY,
				JSTokenStream::_TRUE);
        }
        return new (cx) JSNode( nodeType, left, right);
    }
    return new (cx) JSNode(nodeType, childNode);
}
JSNode *
JSIRFactory::createUnary(JSContext *cx, int nodeType, int nodeOp,
			JSNode *child)
{
    JSNode *childNode = child;
    int childType = childNode->getType();

    if (nodeOp == JSTokenStream::TYPEOF && childType == JSTokenStream::NAME) {
        childNode->setType(JSTokenStream::TYPEOF);
        return childNode;
    }

    if (nodeType == JSTokenStream::INC || nodeType == JSTokenStream::DEC) {

        if (!hasSideEffects(childNode)
            && (nodeOp == JSTokenStream::POST)
            && (childType == JSTokenStream::NAME 
                || childType == JSTokenStream::GETPROP
                || childType == JSTokenStream::GETELEM)) {
            // if it's not a LHS type, createAssignment (below) will throw
            // an exception.
            return new (cx) JSNode(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.
        JSNode *rhs = createNumber(cx, 1.0);

        return createAssignment(cx, nodeType == JSTokenStream::INC
					? (int) JSTokenStream::ADD
					: (int) JSTokenStream::SUB,
				childNode,
				rhs,
				JS_T_Number,
				nodeOp == JSTokenStream::POST);
    }

    JSNode *result = new (cx) JSNode(cx, nodeType, nodeOp);

    result->addChildToBack(child);
    return result;
}
/**
 * Binary
 */
JSNode *
JSIRFactory::createBinary(JSContext *cx, int nodeType, JSNode *left,
			JSNode *right)
{
    switch (nodeType) {

    case JSTokenStream::DOT: {
        JSNode *idNode = right;
        jsString_t id = idNode->getString();

        nodeType = JSTokenStream::GETPROP;
        idNode->setType(JSTokenStream::STRING);
        if (strcmp(id, "__proto__") == 0 || strcmp(id, "__parent__") == 0) {
            JSNode *result = new (cx) JSNode(nodeType, left);

            result->putProp(cx, JSNode::SPECIAL_PROP_PROP, id);
            return result;
        }
        break;

    }
    case JSTokenStream::LB:
        // OPT: could optimize to GETPROP iff string can't be a number
        nodeType = JSTokenStream::GETELEM;
        break;
/*
    case JSTokenStream::AND:
        temp = createNewTemp(cx, left);
        return createTernary(cx, temp, right, createUseTemp(temp));

    case JSTokenStream::OR:
        temp = createNewTemp(cx, left);
        return createTernary(cx, temp, createUseTemp(temp), right);
*/        
    }
    return new (cx) JSNode(nodeType, left, right);
}
JSNode *
JSIRFactory::createBinary(JSContext *cx, int nodeType, int nodeOp,
			JSNode *left, JSNode *right)
{
    if (nodeType == JSTokenStream::ASSIGN) {
        return createAssignment(cx, nodeOp, left, right, JS_T_Null, FALSE);
    }
    return new (cx) JSNode(cx, nodeType, left, right, nodeOp);
}
JSNode *
JSIRFactory::createAssignment(JSContext *cx, int nodeOp, JSNode *left,
			JSNode *right, jsType_t convert, jsBool_t postfix)
{
    int nodeType = left->getType();
    jsString_t idString;
    JSNode *id = NULL;
    jsBool_t ok;

    switch (nodeType) {
    case JSTokenStream::NAME:
        return createSetName(cx, nodeOp, left, right, convert, postfix);

    case JSTokenStream::GETPROP:
	ok = left->getProp(JSNode::SPECIAL_PROP_PROP, &idString);
        if (ok) {
            id = new (cx) JSNode(cx, JSTokenStream::STRING, idString);
	}
        /* fall through */
    case JSTokenStream::GETELEM:
    {
        if (id == NULL) {
            id = left->getLastChild();
	}
        JSNode* result
                = createSetProp(cx, nodeType, nodeOp, left->getFirstChild(),
                             id, right, convert, postfix);

        left->removeChildren();
        left->release( cx );
        return result;
    }
    default:
        // TODO: This should be a ReferenceError--but that's a runtime 
        //  exception. Should we compile an exception into the code?
        reportError(cx, "msg.bad.lhs.assign");
        return left;
    }
}

JSNode *
JSIRFactory::createNewTemp(JSContext *cx, JSNode *n)
{
    int type = n->getType();

    if (type == JSTokenStream::STRING || type == JSTokenStream::NUMBER) {
        // Optimization: clone these values rather than storing
        // and loading from a temp
        return n;
    }
    JSNode *result = new (cx) JSNode(JSTokenStream::NEWTEMP, n);
    return result;
}
JSNode *
JSIRFactory::createUseTemp(JSContext *cx, JSNode *newTemp)
{
    int type = newTemp->getType();

    if (type == JSTokenStream::NEWTEMP) {
        JSNode *result = new (cx) JSNode(JSTokenStream::USETEMP);

        result->putProp(cx, JSNode::TEMP_PROP, newTemp);

        int n;
  	jsBool_t ok = newTemp->getProp(JSNode::USES_PROP, &n);

        if (!ok) {
            n = 1;
        } else {
	    if (n < INT_MAX) {
		n += 1;
	    }
        }
        newTemp->putProp(cx, JSNode::USES_PROP, n);
        return result;
    }
    return newTemp->cloneNode(cx);
}

JSNode *
JSIRFactory::createNewLocal(JSContext *cx, JSNode *n)
{
    JSNode *result = new (cx) JSNode(JSTokenStream::NEWLOCAL, n);

    return result;
}
JSNode *
JSIRFactory::createUseLocal(JSContext *cx, JSNode *newLocal)
{
    int type = newLocal->getType();

    if (type == JSTokenStream::NEWLOCAL) {
        JSNode *result = new (cx) JSNode(JSTokenStream::USELOCAL);

        result->putProp(cx, JSNode::LOCAL_PROP, newLocal);
        return result;
    }
    return newLocal->cloneNode(cx);    // what's this path for ?
}
#ifdef REUSE_LOCAL
JSNode *
JSIRFactory::createDelTemp(JSContext *cx, JSNode *n, JSNode *newTemp)
{
    int type = newTemp->getType();

    if (type == JSTokenStream::NEWTEMP) {
    	JSNode *result = new (cx) JSNode(JSTokenStream::DELTEMP, n);

        result->putProp(cx, JSNode::TEMP_PROP, newTemp);
        return result;
    }
    return n;
}
#endif /* REUSE_LOCAL */

JSIRFactory::JSIRFactory(JSTokenStream *ts, JSScriptable *scope)
{
    this->ts = ts;
    this->scope = scope;
}
