/*
 * 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 "js-runtimeexception.h"
#include "js-exceptionhandler.h"
#include "js-nativeglobal.h"
#include "js-variabletable.h"
#include "js-functionnode.h"
#include "js-nodetransformerp.h"

/* Procedures */
/**
 * Return true if the node is a call to a function that requires 
 * access to the enclosing activation object.
 */
jsBool_t
JSNodeTransformer::isSpecialCallName(JSNode *tree, JSNode *node)
{
    JSNode *left = node->getFirstChild();
    jsBool_t isSpecial = FALSE;

    if (left->getType() == JSTokenStream::NAME) {
	jsString_t name = left->getString();

	isSpecial = strcmp(name, "eval") == 0 || strcmp(name, "With") == 0;
    } else {
	if (left->getType() == JSTokenStream::GETPROP) {
	    jsString_t name = left->getLastChild()->getString();

	    isSpecial = strcmp(name, "exec") == 0;
	}
    }
    if (isSpecial) {
	// Calls to these functions require activation objects.
	if (inFunction) {
	    ((JSFunctionNode *) tree)->setRequiresActivation(TRUE);
	}
	return TRUE;
    }
    return FALSE;
}

/**
 * Return new instance of this class. So that derived classes
 * can override methods of the transformer.
 */
JSNodeTransformer *
JSNodeTransformer::newInstance(JSContext *cx)
{
    return new (cx) JSNodeTransformer(cx);
}
JSIRFactory *
JSNodeTransformer::createIRFactory(JSContext *cx, JSTokenStream *ts,
				JSScriptable *scope)
{
    return new (cx) JSIRFactory(ts, scope);
}
JSNode *
JSNodeTransformer::transform(JSContext *cx, JSNode *tree, JSNode *enclosing,
			JSTokenStream *ts, JSScriptable *scope)
{
    inFunction = tree->getType() == JSTokenStream::FUNCTION;
    if (!inFunction) {
	addVariables(cx, tree, getVariableTable(cx, tree));
    }
    irFactory = createIRFactory(cx, ts, scope);

    // to save against upchecks if no finally blocks are used.
    jsBool_t hasFinally = FALSE;

    JSPreorderNodeIterator iterator = tree->getPreorderIterator(cx);
    JSNode *node;

    while ((node = iterator.nextNode()) != NULL) {
	int type = node->getType();

typeswitch:
	switch (type) {

	case JSTokenStream::FUNCTION:
	    if (node == tree) {
		// Add the variables to variable table, the
		// parameters were added earlier.
		JSVariableTable *vars = getVariableTable(cx, tree);
		addVariables(cx, tree, vars);

		// Add return to end if needed.
		JSNode *stmts = node->getLastChild();
		JSNode *lastStmt = stmts->getLastChild();
		if (lastStmt == NULL ||
		    lastStmt->getType() != JSTokenStream::RETURN) {
		    stmts->addChildToBack(
				new (cx) JSNode(JSTokenStream::RETURN));
		}
	    } else {
		if (inFunction) {
		    // Nested functions require activation  objects.
		    ((JSFunctionNode *) tree)->setRequiresActivation(TRUE);
		}
		JSFunctionNode *fnNode;
                JSNode* _fnNode;        /* avoid warning errors */

		(void) node->getProp(JSNode::FUNCTION_PROP,
				&_fnNode);
                fnNode = ( JSFunctionNode* ) _fnNode;
		addParameters(cx, fnNode);

		JSNodeTransformer *inner = newInstance(cx);

		fnNode = (JSFunctionNode *) 
				inner->transform(cx, fnNode, tree, ts, scope);
		node->putProp(cx, JSNode::FUNCTION_PROP, fnNode);
		inner->destroy( cx );

		JSNode::Vector *fns;
		jsBool_t ok = tree->getProp(JSNode::FUNCTION_PROP, &fns);

		if (!ok) {
		    fns = new (cx) JSNode::Vector(cx, 7);
		    tree->putProp(cx, JSNode::FUNCTION_PROP, fns);
		}
		fns->addElement(fnNode);
	    }
	    break;

	case JSTokenStream::LABEL: {
	    JSNode *child = node->getFirstChild();

	    node->removeChild(child);

	    jsString_t id = child->getString();

	    // check against duplicate labels...
	    for (int i = loops.size() - 1; i >= 0; i--) {
		JSNode *n = (JSNode*) loops.elementAt(i);
		if (n->getType() == JSTokenStream::LABEL) {
		    jsString_t otherId;

		    (void) n->getProp(JSNode::LABEL_PROP, &otherId);
		    if (strcmp(id, otherId) == 0) {
			jsString_t message
				= JSContext::getMessage(cx, "msg.dup.label %s",
							id);
			reportMessage(cx, message, node, tree, TRUE, scope);
			JS_DeleteZoneObj(cx, message);
			goto typeswitch;
		    }
		}
	    }

	    node->putProp(cx, JSNode::LABEL_PROP, id);
	    child->release(cx);

	    /* Make a target and put it _after_ the following
	     * node.  And in the LABEL node, so breaks get the
	     * right target.
	     */
	    JSNode *breakTarget = new (cx) JSNode(JSTokenStream::TARGET);
	    JSNode *parent = iterator.getCurrentParent();
	    JSNode *next = node->getNextSibling();

	    while (next != NULL &&
		   (next->getType() == JSTokenStream::LABEL ||
		    next->getType() == JSTokenStream::TARGET)) {
		next = next->getNextSibling();
	    }
	    if (next == NULL) {
		break;
	    }
	    parent->addChildAfter(breakTarget, next);
	    node->putProp(cx, JSNode::BREAK_PROP, breakTarget);
	    
	    if (next->getType() == JSTokenStream::LOOP) {
		node->putProp(cx, JSNode::CONTINUE_PROP, 
			     next->getProp(JSNode::CONTINUE_PROP));
	    }   

	    loops.push(node);
	    loopEnds.push(breakTarget);

	    break;
	  }

	case JSTokenStream::SWITCH: {
	    JSNode *breakTarget = new (cx) JSNode(JSTokenStream::TARGET);
	    JSNode *parent = iterator.getCurrentParent();
	    parent->addChildAfter(breakTarget, node);

	    // make all children siblings except for selector
	    JSNode *sib = node;
	    JSNode *child = node->getFirstChild()->getNext();

	    while (child != NULL) {
		JSNode *next = child->getNext();
		node->removeChild(child);
		parent->addChildAfter(child, sib);
		sib = child;
		child = next;
	    }

	    node->putProp(cx, JSNode::BREAK_PROP, breakTarget);
	    loops.push(node);
	    loopEnds.push(breakTarget);
	    node->putProp(cx, JSNode::CASES_PROP,
			new (cx) JSNode::Vector(cx, 13));
	    break;
	  }

	case JSTokenStream::DEFAULT:
	case JSTokenStream::CASE: {
	    JSNode *sw = (JSNode *) loops.peek();
	    if (type == JSTokenStream::CASE) {
		JSNode::Vector *cases;

		(void) sw->getProp(JSNode::CASES_PROP, &cases);
		cases->addElement(node);
	    } else {
		sw->putProp(cx, JSNode::DEFAULT_PROP, node);
	    }
	    break;
	  }

	case JSTokenStream::NEWLOCAL : {
	    int localCount;
	    jsBool_t ok = tree->getProp(JSNode::LOCALCOUNT_PROP, &localCount);

	    if (!ok) {
		tree->putProp(cx, JSNode::LOCALCOUNT_PROP, 1);
	    } else {
		tree->putProp(cx, JSNode::LOCALCOUNT_PROP, localCount + 1);
	    }
	     break;
	 }

	case JSTokenStream::LOOP:
	    loops.push(node);
	    JSNode *breakTarget;
	
	    (void) node->getProp(JSNode::BREAK_PROP, &breakTarget);
	    loopEnds.push(breakTarget);
	    break;

	case JSTokenStream::WITH: {
	    if (inFunction) {
		// With statements require an activation object.
		((JSFunctionNode *) tree)->setRequiresActivation(TRUE);
	    }
	    loops.push(node);

	    JSNode *leave = node->getNextSibling();

	    if (leave->getType() != JSTokenStream::LEAVEWITH) {
		cx->exceptionHandler->throwEx(
                        cx,
                        new (cx) JSRuntimeException("Unexpected tree"));
	    }
	    loopEnds.push(leave);
	    break;
	  }

	case JSTokenStream::TRY: {
	    JSNode *finallytarget;
	    jsBool_t ok = node->getProp(JSNode::FINALLY_PROP, &finallytarget);

	    if (ok) {
		hasFinally = TRUE;
		loops.push(node);
		loopEnds.push(finallytarget);
	    }
	    int localCount;

	    ok = tree->getProp(JSNode::LOCALCOUNT_PROP, &localCount);
	    if (!ok) {
		tree->putProp(cx, JSNode::LOCALCOUNT_PROP, 1);
	    } else {
		tree->putProp(cx, JSNode::LOCALCOUNT_PROP, localCount + 1);
	    }
	    break;
	  }

	case JSTokenStream::TARGET:
	case JSTokenStream::LEAVEWITH:
	    if (!loopEnds.empty() && loopEnds.peek() == node) {
		loopEnds.pop();
		loops.pop();
	    }
	    break;

	case JSTokenStream::RETURN: {
	    /* If we didn't support try/finally, it wouldn't be
	     * necessary to put LEAVEWITH nodes here... but as
	     * we do need a series of JSR FINALLY nodes before
	     * each RETURN, we need to ensure that each finally
	     * block gets the correct scope... which could mean
	     * that some LEAVEWITH nodes are necessary.
	     */
	    if (!hasFinally) {
		break;     // skip the whole mess.
	    }
	    JSNode *parent = iterator.getCurrentParent();

	    for (int i = loops.size() - 1; i >= 0; i--) {
		JSNode *n = (JSNode *) loops.elementAt(i);
		int elemtype = n->getType();

		if (elemtype == JSTokenStream::TRY) {
		    JSNode *jsrnode = new (cx) JSNode(JSTokenStream::JSR);
		    JSNode *jsrtarget;

		    (void) n->getProp(JSNode::FINALLY_PROP, &jsrtarget);
		    jsrnode->putProp(cx, JSNode::TARGET_PROP, jsrtarget);
		    parent->addChildBefore(jsrnode, node);
		} else if (elemtype == JSTokenStream::WITH) {
		    parent->addChildBefore(
				new (cx) JSNode(JSTokenStream::LEAVEWITH),
				node);
		}
	    }
	    break;
	  }

	case JSTokenStream::BREAK:
	case JSTokenStream::CONTINUE: {
	    JSNode *loop = NULL;
	    jsBool_t labelled = node->hasChildren();
	    jsString_t id = NULL;

	    if (labelled) {
		/* get the label */
		JSNode *child = node->getFirstChild();
		id = JS_NewZoneString(cx, child->getString());
		node->removeChild(child);
		child->release(cx);
	    }
	    int i;
	    JSNode *parent = iterator.getCurrentParent();

	    for (i = loops.size() - 1; i >= 0; i--) {
		JSNode *n = (JSNode *) loops.elementAt(i);
		int elemtype = n->getType();

		if (elemtype == JSTokenStream::WITH) {
		    parent->addChildBefore(
			new (cx) JSNode(JSTokenStream::LEAVEWITH), node);
		} else if (elemtype == JSTokenStream::TRY) {
		    JSNode *jsrFinally = new (cx) JSNode(JSTokenStream::JSR);
		    JSNode *jsrTarget;

		    (void) n->getProp(JSNode::FINALLY_PROP, &jsrTarget);

		    jsrFinally->putProp(cx, JSNode::TARGET_PROP, jsrTarget);
		    parent->addChildBefore(jsrFinally, node);
		} else if (!labelled &&
			   (elemtype == JSTokenStream::LOOP ||
			    (elemtype == JSTokenStream::SWITCH &&
			     type == JSTokenStream::BREAK))) {
		    /* if it's a simple break/continue, break from the
		     * nearest enclosing loop or switch
		     */
		    loop = n;
		    break;
		} else if (labelled && elemtype == JSTokenStream::LABEL) {
		    jsString_t label;

		    (void) n->getProp(JSNode::LABEL_PROP, &label);
		    if (strcmp(id, label) == 0) {
			loop = n;
			break;
		    }
		}
	    }
	    int propType = type == JSTokenStream::BREAK
				? (int) JSNode::BREAK_PROP
				: (int) JSNode::CONTINUE_PROP;
	    JSNode *target;

	    if (loop == NULL) {
		target = NULL;
	    } else {
		jsBool_t ok = loop->getProp(propType, &target);

		if (!ok) target = NULL;
	    }
	    if (loop == NULL || target == NULL) {
		jsString_t message;

		if (!labelled) {
		    // didn't find an appropriate target
		    if (type == JSTokenStream::CONTINUE) {
			message = JSContext::getMessage(cx,
						"msg.continue.outside");
		    } else {
			message = JSContext::getMessage(cx, "msg.bad.break");
		    }
		} else if (loop != NULL) {
		    message = JSContext::getMessage(cx, "msg.continue.nonloop");
		} else {
		    message = JSContext::getMessage(cx,
						"msg.undef.label %s", id);
		}
		reportMessage(cx, message, node, tree, true, scope);
		JS_DeleteZoneObj(cx, message);
		node->setType(JSTokenStream::NOP);
		if (labelled) JS_DeleteZoneObj(cx, id);
		break;
	    }
	    node->setType(JSTokenStream::GOTO);
	    node->putProp(cx, JSNode::TARGET_PROP, target);
	    if (labelled) JS_DeleteZoneObj(cx, id);
	    break;
	}

	case JSTokenStream::CALL:
	    if (isSpecialCallName(tree, node)) {
		node->putProp(cx, JSNode::SPECIALCALL_PROP, TRUE);
	    }
	    visitCall(cx, node, tree);
	    break;

	case JSTokenStream::NEW:
	    if (isSpecialCallName(tree, node)) {
		node->putProp(cx, JSNode::SPECIALCALL_PROP, TRUE);
	    }
	    visitNew(cx, node, tree);
	    break;

	case JSTokenStream::DOT: {
	    JSNode *right = node->getLastChild();
	    right->setType(JSTokenStream::STRING);
	    break;
	}

	case JSTokenStream::EXPRSTMT:
	    node->setType(inFunction ? (int) JSTokenStream::POP
					: (int) JSTokenStream::POPV);
	    break;

	case JSTokenStream::OBJECT: {
	    JSNode::Vector *regexps;
	    jsBool_t ok = tree->getProp(JSNode::REGEXP_PROP, &regexps);

	    if (!ok) {
		regexps = new (cx) JSNode::Vector(cx, 3);
		tree->putProp(cx, JSNode::REGEXP_PROP, regexps);
	    }
	    regexps->addElement(node);

	    JSNode *n = new (cx) JSNode(JSTokenStream::OBJECT);

	    iterator.replaceCurrent(n);
	    n->putProp(cx, JSNode::REGEXP_PROP, node);
	    break;
	}

	case JSTokenStream::VAR: {
	    JSShallowNodeIterator i = node->getChildIterator();
	    JSNode *result = new (cx) JSNode(JSTokenStream::BLOCK);

	    while (i.hasMoreElements()) {
		JSNode *n = i.nextNode();

		if (!n->hasChildren()) {
                    n->release(cx);     // this is safe.
                                        // check JSShallowNodeIterator
		    continue;
		}
		JSNode *init = n->getFirstChild();

		n->removeChild(init);
		JSNode *asn = (JSNode *) irFactory->createAssignment(cx,
						JSTokenStream::NOP, n, init,
						JS_T_Null, FALSE);
		JSNode *pop = new (cx) JSNode(cx, JSTokenStream::POP, asn,
						node->getDatum());

		result->addChildToBack(pop);
	    }
	    iterator.replaceCurrent(result);
            node->removeChildren();
            node->release( cx );
	    break;
	}

	case JSTokenStream::DELPROP:
	case JSTokenStream::SETNAME: {
	    if (!inFunction || inWithStatement()) {
		break;
	    }
	    JSNode *bind = node->getFirstChild();

	    if (bind == NULL || bind->getType() != JSTokenStream::BINDNAME) {
		break;
	    }
	    jsString_t name = bind->getString();

	    if (strcmp(name, "arguments") == 0) {
		// use of "arguments" requires an activation object.
		((JSFunctionNode *) tree)->setRequiresActivation(TRUE);
	    }
	    JSVariableTable *vars = getVariableTable(cx, tree);

	    if (vars->get(name) != NULL) {
		if (type == JSTokenStream::SETNAME) {
		    node->setType(JSTokenStream::SETVAR);
		    bind->setType(JSTokenStream::STRING);
		} else {
		    // Local variables are by definition permanent
		    JSNode *n = new (cx) JSNode(cx, JSTokenStream::PRIMARY,
			 		     JSTokenStream::_FALSE);

		    iterator.replaceCurrent(n);
                    node->release( cx );
		}
	    }
	    break;
	}
	  
	case JSTokenStream::GETPROP:
	    if (inFunction) {
		JSNode *n = node->getFirstChild()->getNextSibling();
		jsString_t name = n == NULL ? (jsString_t) "" : n->getString();

		if (strcmp(name, "arguments")  == 0
		    || (strcmp(name, "length") == 0
			&& cx->getLanguageVersion()
				== JSContext::VERSION_1_2)) {
		    // Use of "arguments" or "length" in 1.2 requires 
		    // an activation object.
		    ((JSFunctionNode *) tree)->setRequiresActivation(TRUE);
		}
	    }
	    break;

	case JSTokenStream::NAME: {
	    if (!inFunction || inWithStatement()) {
		break;
	    }
	    jsString_t name = node->getString();

	    if (strcmp(name, "arguments") == 0) {
		// Use of "arguments" requires an activation object.
		((JSFunctionNode *) tree)->setRequiresActivation(TRUE);
	    }
	    JSVariableTable *vars = getVariableTable(cx, tree);

	    if (vars->get(name) != NULL) {
		node->setType(JSTokenStream::GETVAR);
	    }
	    break;
	  }
	}
    }

    return tree;
}
void
JSNodeTransformer::addVariables(JSContext *cx, JSNode *tree,
				JSVariableTable *vars)
{
    // OPT: a whole pass to collect variables seems expensive.
    // Could special case to go into statements only.
    jsBool_t inFunction = tree->getType() == JSTokenStream::FUNCTION;
    JSPreorderNodeIterator iterator = tree->getPreorderIterator(cx);
    JSVariableName ht(cx);
    JSNode *node;

    while ((node = iterator.nextNode()) != NULL) {
	int nodeType = node->getType();

	if (inFunction && nodeType == JSTokenStream::FUNCTION && node != tree) {
	    // In a function with both "var x" and "function x",
	    // disregard the var statement, independent of order.
	    jsString_t name = node->getString();

	    if (name == NULL) {
		continue;
	    }
	    vars->removeLocal(cx, name);
	    ht.put(name, TRUE);
	}
	if (nodeType != JSTokenStream::VAR) {
	    continue;
	}
	JSShallowNodeIterator i = node->getChildIterator();
	while (i.hasMoreElements()) {
	    JSNode *n = i.nextNode();

	    if (ht.get(n->getString()) != TRUE) {
		vars->addLocal(cx, n->getString());
	    }
	}
    }
}
void
JSNodeTransformer::addParameters(JSContext *cx, JSFunctionNode *fnNode)
{
    JSVariableTable *vars = fnNode->getVariableTable();
    JSNode *args = fnNode->getFirstChild();

    if (args->getType() == JSTokenStream::LP
	&& vars->getParameterCount() == 0) {
	// Add parameters
	JSShallowNodeIterator i = args->getChildIterator();

	while (i.hasMoreElements()) {
	    JSNode *n = i.nextNode();
	    jsString_t arg = n->getString();

	    vars->addParameter(cx, arg);
	}
    }
}
void
JSNodeTransformer::visitNew(JSContext *cx, JSNode *node, JSNode *tree)
{
}
void
JSNodeTransformer::visitCall(JSContext *cx, JSNode *node, JSNode *tree)
{
    /*
     * For
     *      Call(GetProp(a, b), c, d)   // or GetElem...
     * we wish to evaluate as
     *      Call(GetProp(tmp=a, b), tmp, c, d)
     *
     * for
     *      Call(Name("a"), b, c)
     * we wish to evaluate as
     *      Call(GetProp(tmp=GetBase("a"), "a"), tmp, b, c)
     *
     * and for
     *      Call(a, b, c);
     * we wish to evaluate as
     *      Call(tmp=a, Parent(tmp), c, d)
     */
    JSNode *left = node->getFirstChild();
    // count the arguments
    int argCount = 0;
    JSNode *arg = left->getNextSibling();

    while (arg != NULL) {
	arg = arg->getNextSibling();
	argCount++;
    }
    jsBool_t addGetThis = FALSE;

    if (left->getType() == JSTokenStream::NAME) {
	JSVariableTable *vars = getVariableTable(cx, tree);
	jsString_t name = left->getString();

	if (inFunction && vars->get(name) != NULL && !inWithStatement()) {
	    // call to a var. Transform to Call(GetVar("a"), b, c)
	    left->setType(JSTokenStream::GETVAR);
	    // fall through to code to add GetParent
	} else {
	    // transform to Call(GetProp(GetBase("a"), "a"), b, c)
	    node->removeChild(left);
	    left->setType(JSTokenStream::GETBASE);
	    JSNode *str = left->cloneNode(cx);

	    str->setType(JSTokenStream::STRING);
	    JSNode *getProp
			= new (cx) JSNode(JSTokenStream::GETPROP, left, str);

	    node->addChildToFront(getProp);
	    left = getProp;

	    // Conditionally set a flag to add a GETTHIS node.
	    // The getThis entry in the runtime will take a
	    // Scriptable object intended to be used as a 'this'
	    // and make sure that it is neither a With object or
	    // an activation object.
	    // Executing getThis requires at least two instanceof
	    // tests, so we only include it if we are currently
	    // inside a 'with' statement, or if we are executing
	    // a script (to protect against an eval inside a with).
	    addGetThis = inWithStatement() || !inFunction;
	    // fall through to GETPROP code
	}
    }
    if (left->getType() != JSTokenStream::GETPROP &&
	left->getType() != JSTokenStream::GETELEM) {
	node->removeChild(left);

	JSNode *tmp = irFactory->createNewTemp(cx, left);
	JSNode *use = irFactory->createUseTemp(cx, tmp);

	use->putProp(cx, JSNode::TEMP_PROP, tmp);

	JSNode *parent = new (cx) JSNode(JSTokenStream::PARENT, use);

	node->addChildToFront(parent);
	node->addChildToFront(tmp);
	return;
    }
    JSNode *leftLeft = left->getFirstChild();

    left->removeChild(leftLeft);

    JSNode *tmp = irFactory->createNewTemp(cx, leftLeft);

    left->addChildToFront(tmp);

    JSNode *use = irFactory->createUseTemp(cx, tmp);

    use->putProp(cx, JSNode::TEMP_PROP, tmp);
    if (addGetThis) {
	use = new (cx) JSNode(JSTokenStream::GETTHIS, use);
    }
    node->addChildAfter(use, left);
}
jsBool_t
JSNodeTransformer::inWithStatement()
{
    for (int i = loops.size() - 1; i >= 0; i--) {
	JSNode *n = (JSNode *) loops.elementAt(i);
	if (n->getType() == JSTokenStream::WITH) {
	    return TRUE;
	}
    }
    return FALSE;
}
JSVariableTable *
JSNodeTransformer::createVariableTable(JSContext *cx)
{
    return new (cx) JSVariableTable(cx);
}

JSVariableTable *
JSNodeTransformer::getVariableTable(JSContext *cx, JSNode *tree)
{
    if (inFunction) {
	return ((JSFunctionNode *) tree)->getVariableTable();
    }
    JSVariableTable *result;
    jsBool_t ok = tree->getProp(JSNode::VARS_PROP, &result);

    if (!ok) {
	result = createVariableTable(cx);
	cx->addVariableTable(result);
	tree->putProp(cx, JSNode::VARS_PROP, result);
    }
    return result;
}
void
JSNodeTransformer::reportMessage(JSContext *cx, jsString_t msg, JSNode *stmt, 
				JSNode *tree, jsBool_t isError,
				JSScriptable *scope)
{
    int lineno;
    jsBool_t ok = stmt->getDatum(&lineno);
    
    if (!ok) lineno = 0;

    jsString_t prop;

    ok = tree->getProp(JSNode::SOURCENAME_PROP, &prop);
    if (!ok) prop = NULL;

    if (isError) {
	if (scope != NULL) {
	    cx->exceptionHandler->throwEx(
                    cx,
                    JSNativeGlobal::constructError(cx, "SyntaxError",
					msg, scope, prop, lineno, 0, NULL));
	} else {
	    JSContext::reportError(cx, msg, prop, lineno, NULL, 0);
	}
    } else {
	JSContext::reportWarning(cx, msg, prop, lineno, NULL, 0);	
    }
}
void
JSNodeTransformer::destroy(JSContext *cx)
{
    irFactory->destroy(cx);
    loops.~NodeStack();
    loopEnds.~NodeStack();
    JSZoneNew::destroy(cx);
}

JSNodeTransformer::JSNodeTransformer(JSContext *cx)
		: loops(&cx->allocator), loopEnds(&cx->allocator)
{
}
