/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * 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 the JavaScript 2 Prototype.
 *
 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 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.
 */


#ifdef _WIN32
 // Turn off warnings about identifiers too long in browser information
#pragma warning(disable: 4786)
#pragma warning(disable: 4711)
#pragma warning(disable: 4710)
#endif

#include <stdio.h>
#include <string.h>

#include <algorithm>

#include "parser.h"
#include "numerics.h"
#include "js2runtime.h"
#include "bytecodegen.h"

#include "jsstring.h"
#include "jsarray.h"
#include "jsmath.h"
#include "hash.h"

#include "fdlibm_ns.h"


namespace JavaScript {    
namespace JS2Runtime {


inline char narrow(char16 ch) { return char(ch); }

JSValue Context::readEvalString(const String &str, const String& fileName, ScopeChain *scopeChain, const JSValue& thisValue)
{
    JSValue result = kUndefinedValue;
    NamespaceList *useOnceNamespace = NULL;

    Arena a;
    Parser p(mWorld, a, mFlags, str, fileName);
    Reader *oldReader = mReader;
    setReader(&p.lexer.reader);
    try {
        StmtNode *parsedStatements = p.parseProgram();
        ASSERT(p.lexer.peek(true).hasKind(Token::end));
        if (mDebugFlag)
        {
            PrettyPrinter f(stdOut, 30);
            {
                PrettyPrinter::Block b(f, 2);
                f << "Program =";
                f.linearBreak(1);
                StmtNode::printStatements(f, parsedStatements);
            }
            f.end();
            stdOut << '\n';
        }
        buildRuntime(parsedStatements);
        JS2Runtime::ByteCodeModule* bcm = genCode(parsedStatements, fileName);
        if (bcm) {
            setReader(NULL);
            bcm->setSource(str, fileName);
            result = interpret(bcm, 0, scopeChain, thisValue, NULL, 0);
            delete bcm;
        }
    }
    catch (Exception &x) {
        setReader(oldReader);
        throw x;
    }
    setReader(oldReader);
    return result;
}

JSValue Context::readEvalFile(const String& fileName)
{
    String buffer;
    int ch;

    JSValue result = kUndefinedValue;

    std::string str(fileName.length(), char());
    std::transform(fileName.begin(), fileName.end(), str.begin(), narrow);
    FILE* f = fopen(str.c_str(), "r");
    if (f) {
        while ((ch = getc(f)) != EOF)
            buffer += static_cast<char>(ch);
        fclose(f);
        result = readEvalString(buffer, fileName, NULL, JSValue(getGlobalObject()));
    }
    return result;
}

// Given an operator op, and two operand types - dispatch to the
// appropriate operator. The operands are still on the execution stack.
// Return result indicates whether interpreter loop has to begin
// execution of new function.
bool Context::executeOperator(Operator op, JSType *t1, JSType *t2)
{
    // look in the operator table for applicable operators
    OperatorList applicableOperators;

    for (OperatorList::iterator oi = mBinaryOperatorTable[op].begin(),
                end = mBinaryOperatorTable[op].end();
                    (oi != end); oi++) 
    {
        if ((*oi)->isApplicable(t1, t2)) {
            applicableOperators.push_back(*oi);
        }
    }
    if (applicableOperators.size() == 0)
        reportError(Exception::runtimeError, "No applicable operators found");

    OperatorList::iterator candidate = applicableOperators.begin();
    for (OperatorList::iterator aoi = applicableOperators.begin() + 1,
                aend = applicableOperators.end();
                    (aoi != aend); aoi++) 
    {
        if ((*aoi)->mType1->derivesFrom((*candidate)->mType1)
                || ((*aoi)->mType2->derivesFrom((*candidate)->mType2)))
            candidate = aoi;
    }
    // XXX how to complain if there is more than one best fit?

    JSFunction *target = (*candidate)->mImp;

    JSValue newThis = kNullValue;
    if (target->isNative()) {
        JSValue result = target->getNativeCode()(this, newThis, getBase(stackSize() - 2), 2);
        resizeStack(stackSize() - 2);
        pushValue(result);
        return false;
    }
    else {
        mActivationStack.push(new Activation(mLocals, mStack, mStackTop - 2, mScopeChain,
                                                mArgumentBase, mThis, mPC, mCurModule, mNamespaceList));
        mThis = newThis;
        mCurModule = target->getByteCode();
        mArgumentBase = getBase(stackSize() - 2);
        mScopeChain = target->getScopeChain();
        return true;
    }
}


// Invokes either the native or bytecode implementation. Causes another interpreter loop
// to begin execution, and does nothing to clean up the incoming arguments (which need
// not even be on the execution stack).
JSValue Context::invokeFunction(JSFunction *target, const JSValue& thisValue, JSValue *argv, uint32 argc)
{
    if (target->isNative())
        return target->getNativeCode()(this, thisValue, argv, argc);
    else
        return interpret(target->getByteCode(), 0, target->getScopeChain(), thisValue, argv, argc);
}

JSValue Context::interpret(JS2Runtime::ByteCodeModule *bcm, int offset, ScopeChain *scopeChain, const JSValue& thisValue, JSValue *argv, uint32 /*argc*/)
{ 
    Activation *prev = new Activation(mLocals, mStack, mStackTop, mScopeChain,
                                            mArgumentBase, mThis, NULL, mCurModule, mNamespaceList); // use NULL pc value to force interpret loop to exit
    uint32 activationHeight = mActivationStack.size();
    mActivationStack.push(prev);   
    mThis = thisValue;
    if (scopeChain)
        mScopeChain = scopeChain;
    else {
        mScopeChain = new ScopeChain(this, mWorld);
        mScopeChain->addScope(getGlobalObject());
    }
    
//    if (mThis.isObject())
//        mScopeChain->addScope(mThis.object);
//    mScopeChain->addScope(mActivationStack.top());

    mCurModule = bcm;
    uint8 *pc = mCurModule->mCodeBase + offset;
    uint8 *endPC = mCurModule->mCodeBase + mCurModule->mLength;
    mArgumentBase = argv;
    mLocals = new JSValue[mCurModule->mLocalsCount];
    mStack = new JSValue[mCurModule->mStackDepth];
    mStackMax = mCurModule->mStackDepth;
    mStackTop = 0;

    JSValue result;
    try {
        result = interpret(pc, endPC);
    }
    catch (Exception &jsx) {
        while (mActivationStack.size() != activationHeight)
            mActivationStack.pop();
         
        // the following (delete's) are a bit iffy - depends on whether
        // a closure capturing the contents has come along...
//        if (mThis.isObject())
//          mScopeChain->popScope();
        JSValue x;
        if (jsx.hasKind(Exception::userException)) 
            x = popValue();

        delete[] mStack;
        delete[] mLocals;
        if (scopeChain == NULL)
            delete mScopeChain;

        mNamespaceList = prev->mNamespaceList;
        mCurModule = prev->mModule;
        mStack = prev->mStack;
        mStackTop = 0;      // we're processing an exception, no need to preserve the stack
        

        if (mCurModule) {
            mStackMax = mCurModule->mStackDepth;
            if (jsx.hasKind(Exception::userException)) 
                pushValue(x);
        }
        mLocals = prev->mLocals;
        mArgumentBase = prev->mArgumentBase;
        mThis = prev->mThis;
        mScopeChain = prev->mScopeChain;
        delete prev;
        throw jsx;
    }
    ASSERT(prev == mActivationStack.top());
    mActivationStack.pop();

    // the following (delete's) are a bit iffy - depends on whether
    // a closure capturing the contents has come along...
//    if (mThis.isObject())
//        mScopeChain->popScope();
    delete[] mStack;
    delete[] mLocals;
    if (scopeChain == NULL)
        delete mScopeChain;

    mNamespaceList = prev->mNamespaceList;
    mCurModule = prev->mModule;
    mStack = prev->mStack;
    mStackTop = prev->mStackTop;
    if (mCurModule)
        mStackMax = mCurModule->mStackDepth;
    mLocals = prev->mLocals;
    mArgumentBase = prev->mArgumentBase;
    mThis = prev->mThis;
    mScopeChain = prev->mScopeChain;
    delete prev;
    return result;
}

// Assumes arguments are on the top of stack. argCount is the number that were pushed by user code
JSValue *Context::buildArgumentBlock(JSFunction *target, uint32 &argCount)
{
    JSValue *argBase;
    uint32 maxExpectedParameterCount = target->getRequiredParameterCount() + target->getOptionalParameterCount() + target->getNamedParameterCount();
    if (target->isChecked()) {
        if (argCount < target->getRequiredParameterCount())
            reportError(Exception::referenceError, "Insufficient quantity of arguments");
    
        if ((argCount > maxExpectedParameterCount) && !target->hasRestParameter())
            reportError(Exception::referenceError, "Oversufficient quantity of arguments");
    }

    uint32 i;
    uint32 argBlockSize = max(argCount, maxExpectedParameterCount) + (target->hasRestParameter() ? 1 : 0);
                                                    // room for all required,optional & named arguments
                                                    // plus the rest parameter if it exists.
    argBase = new JSValue[argBlockSize];     

/*
* If the first parameter is required and no positional argument has been supplied for it, then raise an error unless the function 
*   is unchecked, in which case let undefined be the first parameter’s value.
*
* If the first parameter is optional and there is a positional argument remaining, use the value of the positional argument 
*    and raise an error if there is also a named argument with a matching argument name. If there are no remaining positional 
*    arguments, then if there is a named argument with a matching argument name, use the value of that named argument. Otherwise, 
*   evaluate the first parameter’s AssignmentExpression and let it be the first parameter’s value.
* 
* Implicitly coerce the argument (or default) value to type t and bind the parameter’s Identifier to the result.
*/    
    uint32 inArgIndex = 0;
    uint32 argStart = stackSize() - argCount;
    bool *argUsed = new bool[argCount];
    for (i = 0; i < argCount; i++)
        argUsed[i] = false;    
    uint32 restParameterIndex;

    uint32 pCount = maxExpectedParameterCount + (target->hasRestParameter() ? 1 : 0);
    for (uint32 pIndex = 0; pIndex < pCount; pIndex++) {        
        bool needDefault = true;
        if (target->parameterIsRequired(pIndex)) {
            if (inArgIndex < argCount) {
                JSValue v = getValue(inArgIndex + argStart);
                bool isPositionalArg = !v.isNamedArg();
                // if the next argument is named, then we've run out of positional args
                if (!isPositionalArg) {
                    if (!target->isChecked())
                        needDefault = false; // the undefined value is already assigned in the arg block
                }
                else {
                    argBase[pIndex] = getValue(inArgIndex + argStart);
                    argUsed[inArgIndex++] = true;
                    needDefault = false;
                }
            }
            if (needDefault && target->isChecked())
                reportError(Exception::referenceError, "missing required argument");
        }
        else {
            if (target->parameterIsOptional(pIndex)) {
                bool tookPositionalArg = false;
                if (inArgIndex < argCount) {
                    JSValue v = getValue(inArgIndex + argStart);
                    if (!v.isNamedArg()) {
                        argBase[pIndex] = v;
                        argUsed[inArgIndex++] = true;
                        needDefault = false;
                    }
                }
            }
            else {
                if (target->parameterIsNamed(pIndex)) {
                    const String *parameterName = target->getParameterName(pIndex);
                    for (uint32 i = inArgIndex; i < argCount; i++) {
                        if (!argUsed[i]) {
                            JSValue v = getValue(i + argStart);
                            if (v.isNamedArg()) {
                                if (v.namedArg->mName == parameterName) {
                                    argBase[pIndex] = v.namedArg->mValue;
                                    argUsed[i] = true;
                                    needDefault = false;
                                    break;
                                }
                            }
                        }
                    }
                }
                else
                    restParameterIndex = pIndex;
            }
        }
        if (needDefault)
            if (target->parameterHasInitializer(pIndex))
                argBase[pIndex] = target->runParameterInitializer(this, pIndex, mThis, argBase, maxExpectedParameterCount);
    }

    // now find a place for any left-overs:
/*
* If there is a RestParameter with a type that does not allow the dynamic addition of named properties and one or more of the
*    remaining arguments is named, raise an error.
*
* If there is a RestParameter with an Identifier, bind that Identifier to an array of the remaining positional and/or named
*    arguments. The remaining positional arguments are assigned indices starting from 0.
*
* If there is no RestParameter and any arguments remain, raise an error unless the function is unchecked.
*/

    JSValue restArgument;
    bool haveRestArg = false;
    
    if (target->hasRestParameter() && target->getParameterName(restParameterIndex)) {
        restArgument = target->getParameterType(restParameterIndex)->newInstance(this);
        argBase[restParameterIndex] = restArgument;
        haveRestArg = true;
    }
    inArgIndex = 0;    // re-number the non-named arguments that end up in the rest arg
    for (i = 0; i < argCount; i++) {
        if (!argUsed[i]) {
            JSValue v = getValue(i + argStart);
            bool isNamedArg = v.isNamedArg();
            if (isNamedArg) {
                NamedArgument *arg = v.namedArg;
                if (haveRestArg)
                    restArgument.object->setProperty(this, *arg->mName, (NamespaceList *)(NULL), arg->mValue);
                else
                    reportError(Exception::referenceError, "Unused named argument, no rest argument");                                
            }
            else {
                if (haveRestArg) {
                    const String *id = numberToString(inArgIndex++);
                    restArgument.object->setProperty(this, *id, (NamespaceList *)(NULL), v);
                }
                else {
                    if (target->isChecked())
                       reportError(Exception::referenceError, "Extra argument, no rest argument"); 
                    else {
                        if (isNamedArg)
                            argBase[i] = v.namedArg->mValue;
                        else
                            argBase[i] = v;
                    }
                }
            }
        }
    }
    
    argCount = argBlockSize;
    return argBase;
}

JSValue Context::interpret(uint8 *pc, uint8 *endPC)
{
    bool useOnceNamespace = false;
    JSValue result = kUndefinedValue;
    while (pc != endPC) {
        mPC = pc;
        try {
            if (mDebugFlag) {
                FunctionName *fnName;
                uint32 x = mScopeChain->mScopeStack.size();
                if (mCurModule->mFunction && (fnName = mCurModule->mFunction->getFunctionName())) {
                    StringFormatter s;
                    PrettyPrinter pp(s);
                    fnName->print(pp);
                    const String &fnStr = s.getString();
                    std::string str(fnStr.length(), char());
                    std::transform(fnStr.begin(), fnStr.end(), str.begin(), narrow);
                    uint32 len = strlen(str.c_str());
                    printFormat(stdOut, "%.30s+%.4d%*c%d %d        ", str.c_str(), (pc - mCurModule->mCodeBase), (len > 30) ? 0 : (len - 30), ' ', stackSize(), x);
                }
                else
                    printFormat(stdOut, "+%.4d%*c%d %d        ", (pc - mCurModule->mCodeBase), 30, ' ', stackSize(), x);
                printInstruction(stdOut, toUInt32(pc - mCurModule->mCodeBase), *mCurModule);
            }
            switch ((ByteCodeOp)(*pc++)) {
            case PopOp:
                {
                    result = popValue();
                }
                break;
            case VoidPopOp:
                {
                    popValue(); // XXX just decrement top
                }
                break;
            case PopNOp:
                {
                    uint16 count = *((uint16 *)pc);
                    pc += sizeof(uint16);
                    resizeStack(mStackTop - count);
                }
                break;
            case DupOp:
                {
                    JSValue v = topValue();
                    pushValue(v);
                }
                break;
            case DupInsertOp:   // XXX something more efficient than pop/push?
                {
                    JSValue v1 = popValue();
                    JSValue v2 = popValue();
                    pushValue(v1);
                    pushValue(v2);
                    pushValue(v1);
                }
                break;
            case DupNOp:
                {
                    uint16 count = *((uint16 *)pc);
                    pc += sizeof(uint16);
                    JSValue *vp = getBase(stackSize() - count);
                    while (count--)
                        pushValue(*vp++);
                }
                break;
            // <N things> <object2> --> <object2> <N things> <object2>
            case DupInsertNOp:
                {
                    JSValue v2 = topValue();
                    uint16 count = *((uint16 *)pc);
                    pc += sizeof(uint16);
                    insertValue(v2, mStackTop - (count + 1));
                }
                break;
            case SwapOp:   // XXX something more efficient than pop/push?
                {
                    JSValue v1 = popValue();
                    JSValue v2 = popValue();
                    pushValue(v1);
                    pushValue(v2);
                }
                break;
            case LogicalXorOp:
                {
                    JSValue v2 = popValue();
                    ASSERT(v2.isBool());
                    JSValue v1 = popValue();
                    ASSERT(v1.isBool());

                    if (v1.boolean) {
                        if (v2.boolean) {
                            popValue();
                            popValue();
                            pushValue(kFalseValue);
                        }
                        else
                            popValue();
                    }
                    else {
                        if (v1.boolean) {
                            popValue();
                            popValue();
                            pushValue(kFalseValue);
                        }
                        else {
                            JSValue t = topValue();
                            popValue();
                            popValue();
                            pushValue(t);
                        }
                    }
                }
                break;
            case LogicalNotOp:
                {
                    JSValue v = popValue();
                    ASSERT(v.isBool());
                    pushValue(JSValue(!v.boolean));
                }
                break;
            case JumpOp:
                {
                    uint32 offset = *((uint32 *)pc);
                    pc += offset;
                }
                break;
            case ToBooleanOp:
                {
                    JSValue v = popValue();
                    pushValue(v.toBoolean(this));
                }
                break;
            case JumpFalseOp:
                {
                    JSValue v = popValue();
                    ASSERT(v.isBool());
                    if (!v.boolean) {
                        uint32 offset = *((uint32 *)pc);
                        pc += offset;
                    }
                    else
                        pc += sizeof(uint32);
                }
                break;
            case JumpTrueOp:
                {
                    JSValue v = popValue();
                    ASSERT(v.isBool());
                    if (v.boolean) {
                        uint32 offset = *((uint32 *)pc);
                        pc += offset;
                    }
                    else
                        pc += sizeof(uint32);
                }
                break;
            case NamedArgOp:
                {
                    JSValue name = popValue();
                    if (!name.isString())
                        reportError(Exception::typeError, "String needed for argument name");
                    JSValue value = popValue();
                    pushValue(JSValue(new NamedArgument(value, name.string)));
                }
                break;
            case InvokeOp:
                {
                    uint32 argCount = *((uint32 *)pc); 
                    uint32 cleanUp = argCount;
                    pc += sizeof(uint32);
                    CallFlag callFlags = (CallFlag)(*pc++);
                    mPC = pc;

                    JSValue *targetValue = getBase(stackSize() - (argCount + 1));
                    JSFunction *target;
                    JSValue oldThis = mThis;
                    switch (callFlags & ThisFlags) {
                    case NoThis:
                        mThis = kNullValue; 
                        break;
                    case Explicit:
                        mThis = getValue(stackSize() - (argCount + 2));
                        cleanUp++;
                        break;
                    default:
                        NOT_REACHED("bad bytecode");
                    }

                    if (!targetValue->isFunction()) {
                        if (targetValue->isType()) {
                            // how to distinguish between a cast and an invocation of
                            // the superclass constructor from a constructor????
                            // XXX help
                            if ((callFlags & SuperInvoke) == SuperInvoke) {
                                // in this case, calling the constructor requires passing the 'this' value
                                // through.
                                target = targetValue->type->getDefaultConstructor();
                                mThis = oldThis;
                            }
                            else {
                                // "  Type()  "
                                // - it's a cast expression, we call the
                                // default constructor, overriding the supplied 'this'.
                                //
                            
                                // XXX Note that this is different behaviour from JS1.5, where
                                // (e.g.) Array(2) is an invocation of the constructor.
                                
                                target = targetValue->type->getTypeCastFunction();
                                if (target == NULL) {
                                    if ((argCount > 1) || ((callFlags & ThisFlags) != NoThis))
                                        reportError(Exception::referenceError, "Type cast can only take one argument");
                                    JSValue v;
                                    if (argCount > 0)
                                        v = popValue();
                                    popValue();             // don't need the target anymore
                                    pushValue(mapValueToType(v, targetValue->type));
                                    mThis = oldThis;
                                    break;      // all done
                                }
                            }
                        }
                        else
                            reportError(Exception::referenceError, "Not a function");
                    }
                    else {
                        target = targetValue->function;

                        // an invocation of the super constructor has to pass thru the existing 'this'
                        if (target->isConstructor() && (callFlags & SuperInvoke))
                            mThis = oldThis;
                        else
                            if (target->hasBoundThis())   // then we use it instead of the expressed version
                                mThis = target->getThisValue();
                    }
                    if (target->isPrototype() && mThis.isNull())
                        mThis = JSValue(getGlobalObject());
                                        
                    if (!target->isNative()) {
                        JSValue *argBase = buildArgumentBlock(target, argCount);
                        // XXX Optimize the more typical case in which there are no named arguments, no optional arguments
                        // and the appropriate number of arguments have been supplied.

                        mActivationStack.push(new Activation(mLocals, mStack, mStackTop - (cleanUp + 1),
                                                                    mScopeChain,
                                                                    mArgumentBase, oldThis,
                                                                    pc, mCurModule, mNamespaceList));
                        mScopeChain = target->getScopeChain();
                        mScopeChain->addScope(target->getParameterBarrel());
                        mScopeChain->addScope(target->getActivation());

                        if (!target->isChecked()) {
                            JSValue args = Array_Type->newInstance(this);
                            for (uint32 i = 0; i < argCount; i++) 
                                args.instance->setProperty(this, *numberToString(i), NULL, argBase[i]);
                            target->getActivation()->setProperty(this, Arguments_StringAtom, NULL, args);
                        }

                        mCurModule = target->getByteCode();
                        pc = mCurModule->mCodeBase;
                        endPC = mCurModule->mCodeBase + mCurModule->mLength;
                        mArgumentBase = argBase;
                        mLocals = new JSValue[mCurModule->mLocalsCount];
                        mStack = new JSValue[mCurModule->mStackDepth];
                        mStackMax = mCurModule->mStackDepth;
                        mStackTop = 0;
                    }
                    else {
                        JSValue *argBase = getBase(stackSize() - argCount);
                        // native functions may still need access to information
                        // about the currently executing function.
                        mActivationStack.push(new Activation(mLocals, mStack, mStackTop - (cleanUp + 1),
                                                                    mScopeChain,
                                                                    mArgumentBase, oldThis,
                                                                    pc, mCurModule, mNamespaceList));
                        JSValue result = (target->getNativeCode())(this, mThis, argBase, argCount);
                        Activation *prev = mActivationStack.top();
                        delete prev;
                        mActivationStack.pop();

                        mThis = oldThis;
                        resizeStack(stackSize() - (cleanUp + 1));
                        pushValue(result);
                    }

                }
                break;
            case ReturnVoidOp:
                {                    
                    if (mActivationStack.empty())
                        return result;
                    Activation *prev = mActivationStack.top();
                    if (prev->mPC == NULL) {   // NULL is used to indicate that we want the loop to exit
                                               // (even though there is more activation stack to go
                        return result;         // - used to implement Xetters from XProperty ops. e.g.)
                                        
                    }
                    mActivationStack.pop();
                    delete[] mLocals;
                    delete[] mStack;

                    mScopeChain->popScope();
                    mScopeChain->popScope();

                    mNamespaceList = prev->mNamespaceList;
                    mCurModule = prev->mModule;
                    pc = prev->mPC;
                    endPC = mCurModule->mCodeBase + mCurModule->mLength;
                    mStack = prev->mStack;
                    mStackTop = prev->mStackTop;
                    mStackMax = mCurModule->mStackDepth;
                    mLocals = prev->mLocals;
                    mArgumentBase = prev->mArgumentBase;
                    mThis = prev->mThis;
                    mScopeChain = prev->mScopeChain;       
                    pushValue(kUndefinedValue);
                    delete prev;
                }
                break;
            case ReturnOp:
                {
                    JSValue result = popValue();                    
                    if (mActivationStack.empty())
                        return result;                    
                    Activation *prev = mActivationStack.top();
                    if (prev->mPC == NULL) {
                        return result;
                    }
                    mActivationStack.pop();
                    delete[] mLocals;
                    delete[] mStack;                    

                    mScopeChain->popScope();
                    mScopeChain->popScope();

                    mNamespaceList = prev->mNamespaceList;
                    mCurModule = prev->mModule;
                    pc = prev->mPC;
                    endPC = mCurModule->mCodeBase + mCurModule->mLength;
                    mStack = prev->mStack;
                    mStackTop = prev->mStackTop;
                    mStackMax = mCurModule->mStackDepth;
                    mLocals = prev->mLocals;
                    mArgumentBase = prev->mArgumentBase;
                    mThis = prev->mThis;
                    mScopeChain = prev->mScopeChain;
                    pushValue(result);
                    delete prev;
                }
                break;
            case LoadTypeOp:
                {
                    JSType *t = *((JSType **)pc);
                    pc += sizeof(JSType *);
                    pushValue(JSValue(t));
                }
                break;
            case LoadFunctionOp:
                {
                    JSFunction *f = *((JSFunction **)pc);
                    pc += sizeof(JSFunction *);
                    pushValue(JSValue(f));
                }
                break;
            case LoadConstantStringOp:
                {
                    uint32 index = *((uint32 *)pc);
                    pc += sizeof(uint32);
                    pushValue(JSValue(mCurModule->getString(index)));
                }
                break;
            case LoadConstantNumberOp:
                {
                    pushValue(JSValue(mCurModule->getNumber(pc)));
                    pc += sizeof(float64);
                }
                break;
            case LoadConstantUndefinedOp:
                pushValue(kUndefinedValue);
                break;
            case LoadConstantTrueOp:
                pushValue(kTrueValue);
                break;
            case LoadConstantFalseOp:
                pushValue(kFalseValue);
                break;
            case LoadConstantNullOp:
                pushValue(kNullValue);
                break;
            case LoadConstantZeroOp:
                pushValue(kPositiveZero);
                break;
            case LoadConstantRegExpOp:
                {
                    JSInstance *t = *((JSInstance **)pc);
                    pc += sizeof(JSInstance *);
                    pushValue(JSValue(t));
                }
                break;
            case DeleteNameOp:
                {
                    uint32 index = *((uint32 *)pc);
                    pc += sizeof(uint32);
                    const StringAtom &name = *mCurModule->getString(index);
                    if (mScopeChain->deleteName(this, name, mNamespaceList))
                        pushValue(kTrueValue);
                    else
                        pushValue(kFalseValue);
                }
                break;
            case DeleteOp:
                {
                    JSValue base = popValue();
                    JSObject *obj = base.toObjectValue(this);
                    uint32 index = *((uint32 *)pc);
                    pc += sizeof(uint32);
                    const StringAtom &name = *mCurModule->getString(index);
                    PropertyIterator it;
                    if (obj->hasOwnProperty(this, name, mNamespaceList, Read, &it))
                        if (obj->deleteProperty(this, name, mNamespaceList))
                            pushValue(kTrueValue);
                        else
                            pushValue(kFalseValue);
                    else
                        pushValue(kTrueValue);
                }
                break;
            case TypeOfOp:
                {
                    JSValue v = popValue();
                    if (v.isUndefined())
                        pushValue(JSValue(&Undefined_StringAtom));
                    else
                    if (v.isNull())
                        pushValue(JSValue(&Object_StringAtom));
                    else
                    if (v.isBool())
                        pushValue(JSValue(&Boolean_StringAtom));
                    else
                    if (v.isNumber())
                        pushValue(JSValue(&Number_StringAtom));
                    else
                    if (v.isString())
                        pushValue(JSValue(&String_StringAtom));
                    else
                    if (v.isFunction())
                        pushValue(JSValue(&Function_StringAtom));
                    else
                        pushValue(JSValue(&Object_StringAtom));
                }
                break;
            case AsOp:
                {
                    JSValue t = popValue();
                    JSValue v = popValue();
                    if (t.isType()) {
                        if (v.isInstance() 
                                && (v.instance->getType() == t.type))
                            pushValue(v);
                        else
                            pushValue(kNullValue);   // XXX or throw an exception if 
                                                            // NULL is not a member of type t
                    }
                    else
                        reportError(Exception::typeError, "As needs type");
                }
                break;
            case IsOp:
                {
                    JSValue t = popValue();
                    JSValue v = popValue();
                    if (t.isType()) {
                        if (v.isNull())
                            if (t.type == Object_Type)
                                pushValue(kTrueValue);
                            else
                                pushValue(kFalseValue);
                        else
                            if (v.isInstance() 
                                    && ((v.instance->getType() == t.type)
                                        || (v.instance->getType()->derivesFrom(t.type))))
                                pushValue(kTrueValue);
                            else {
                                if (v.getType() == t.type)
                                    pushValue(kTrueValue);
                                else
                                    pushValue(kFalseValue);
                            }
                    }
                    else {  // behave like instanceof
                        if (t.isObject() && t.isFunction()) {                            
                            // XXX prove that t->function["prototype"] is on t.object->mPrototype chain
                            pushValue(kTrueValue);
                        }
                        else
                            reportError(Exception::typeError, "InstanceOf needs object");
                    }
                }
                break;
            case InstanceOfOp:  
                {
                    JSValue t = popValue();
                    JSValue v = popValue();
                    if (t.isFunction()) {
                        JSFunction *obj = t.function;                        
                        PropertyIterator i;
                        JSFunction *target = NULL;
                        if (obj->hasProperty(this, HasInstance_StringAtom, mNamespaceList, Read, &i)) {
                            JSValue hi = obj->getPropertyValue(i);
                            if (hi.isFunction())
                                target = hi.function;
                        }
                        if (target)
                            pushValue(invokeFunction(target, t, &v, 1));
                        else
                            reportError(Exception::typeError, "InstanceOf couldn't find [[hasInstance]]");
                    }
                    else {
                        // XXX hack for <= ECMA 3 compatibility
                        if (t.isType()) {
                            if ((v.getType() == t.type)
                                    || v.getType()->derivesFrom(t.type))
                                pushValue(kTrueValue);
                            else
                                pushValue(kFalseValue);
                        }
                        else
                            reportError(Exception::typeError, "InstanceOf needs function object");
                    }
                }
                break;
            case GetNameOp:
                {
                    uint32 index = *((uint32 *)pc);
                    pc += sizeof(uint32);
                    const StringAtom &name = *mCurModule->getString(index);
                    JSObject *parent = mScopeChain->getNameValue(this, name, mNamespaceList);
                    JSValue result = topValue();
                    if (result.isFunction() && !result.function->hasBoundThis()) {
                        popValue();
                        if (result.function->isConstructor())
                            // A constructor has to be called with a NULL 'this' in order to prompt it
                            // to construct the instance object.
                            pushValue(JSValue((JSFunction *)(new JSBoundFunction(this, result.function, NULL))));
                        else
                            pushValue(JSValue((JSFunction *)(new JSBoundFunction(this, result.function, parent))));
                    }
                    if (useOnceNamespace) {
                        NamespaceList *t = mNamespaceList->mNext;
                        delete mNamespaceList;
                        useOnceNamespace = false;
                        mNamespaceList = t;
                    }                        
                }
                break;
            case SetNameOp:
                {
                    uint32 index = *((uint32 *)pc);
                    pc += sizeof(uint32);
                    const StringAtom &name = *mCurModule->getString(index);
                    JSValue v = topValue();
                    if (v.isFunction() && v.function->hasBoundThis() && !v.function->isMethod()) {
                        popValue();
                        pushValue(JSValue(v.function->getFunction()));
                    }
                    mScopeChain->setNameValue(this, name, mNamespaceList);
                    if (useOnceNamespace) {
                        NamespaceList *t = mNamespaceList->mNext;
                        delete mNamespaceList;
                        useOnceNamespace = false;
                        mNamespaceList = t;
                    }                        
                }
                break;
            case GetTypeOfNameOp:
                {
                    uint32 index = *((uint32 *)pc);
                    pc += sizeof(uint32);
                    const StringAtom &name = *mCurModule->getString(index);
                    if (mScopeChain->hasNameValue(this, name, mNamespaceList)) {
                        mScopeChain->getNameValue(this, name, mNamespaceList);
                    }
                    else
                        pushValue(kUndefinedValue);
                    if (useOnceNamespace) {
                        NamespaceList *t = mNamespaceList->mNext;
                        delete mNamespaceList;
                        useOnceNamespace = false;
                        mNamespaceList = t;
                    }                        
                }
                break;
            case GetElementOp:
                {
                    uint32 dimCount = *((uint16 *)pc);
                    pc += sizeof(uint16);
                    mPC = pc;

                    JSValue *base = getBase(stackSize() - (dimCount + 1));

                    // Use the type of the base to dispatch on...
                    JSObject *obj = base->toObjectValue(this);
                    JSFunction *target = getUnaryOperator(base->getType(), Index);

                    if (target) {
                        JSValue result;
                        if (target->isNative()) {
                            JSValue *argBase = new JSValue[dimCount + 1];
                            for (uint32 i = 0; i < (dimCount + 1); i++)
                                argBase[i] = base[i];
                            resizeStack(stackSize() - (dimCount + 1));
                            result = target->getNativeCode()(this, argBase[0], argBase, dimCount + 1);
                            delete[] argBase;
                        }
                        else {
                            uint32 argCount = dimCount + 1;
                            JSValue *argBase = buildArgumentBlock(target, argCount);
                            resizeStack(stackSize() - (dimCount + 1));
                            try {
                                result = interpret(target->getByteCode(), 0, target->getScopeChain(), argBase[0], argBase, argCount);
                            }
                            catch (Exception &x) {
                                delete[] argBase;
                                throw x;
                            }
                        }
                        pushValue(result);
                    }
                    else {  // XXX or should this be implemented in Object_Type as operator "[]" ?
                        if (dimCount != 1)
                            reportError(Exception::typeError, "too many indices");
                        JSValue index = popValue();
                        popValue();     // discard base
                        const String *name = index.toString(this).string;
                        obj->getProperty(this, *name, mNamespaceList);
                    }
                    // if the result is a method of some kind, bind
                    // the base object to it
                    JSValue result = topValue();
                    if (result.isFunction()) {
                        popValue();
                        if (result.function->isConstructor())
                            // A constructor has to be called with a NULL 'this' in order to prompt it
                            // to construct the instance object.
                            pushValue(JSValue((JSFunction *)(new JSBoundFunction(this, result.function, NULL))));
                        else
                            pushValue(JSValue((JSFunction *)(new JSBoundFunction(this, result.function, obj))));
                    }
                }
                break;
            case SetElementOp:
                {
                    uint32 dimCount = *((uint16 *)pc);
                    pc += sizeof(uint16);
                    mPC = pc;

                    JSValue *base = getBase(stackSize() - (dimCount + 2));     // +1 for assigned value

                    // Use the type of the base to dispatch on...
                    JSObject *obj = base->toObjectValue(this);
                    JSFunction *target = getUnaryOperator(base->getType(), IndexEqual);

                    if (target) {
                        JSValue v = popValue();     // need to have this sitting right above the base value
                        if (v.isFunction() && v.function->hasBoundThis() && !v.function->isMethod()) {
                            v = JSValue(v.function->getFunction());
                        }
                        insertValue(v, mStackTop - dimCount);

                        JSValue result;
                        if (target->isNative()) {
                            JSValue *argBase = new JSValue[dimCount + 2];
                            for (uint32 i = 0; i < (dimCount + 2); i++)
                                argBase[i] = base[i];
                            resizeStack(stackSize() - (dimCount + 2));
                            result = target->getNativeCode()(this, *base, base, (dimCount + 2));
                            delete[] argBase;
                        }
                        else {
                            uint32 argCount = dimCount + 2;
                            JSValue *argBase = buildArgumentBlock(target, argCount);
                            resizeStack(stackSize() - (dimCount + 2));
                            try {
                                result = interpret(target->getByteCode(), 0, target->getScopeChain(), *base, argBase, argCount);
                            }
                            catch (Exception &x) {
                                delete[] argBase;
                                throw x;
                            }
                        }
                        pushValue(result);
                    }
                    else {  // XXX or should this be implemented in Object_Type as operator "[]=" ?
                        if (dimCount != 1)
                            reportError(Exception::typeError, "too many indices");
                        JSValue v = popValue();
                        if (v.isFunction() && v.function->hasBoundThis() && !v.function->isMethod()) {
                            v = JSValue(v.function->getFunction());
                        }
                        JSValue index = popValue();
                        popValue();     // discard base
                        const String *name = index.toString(this).string;
                        obj->setProperty(this, *name, mNamespaceList, v);
                        pushValue(v);                
                    }
                }
                break;
            case DeleteElementOp:
                {
                    uint32 dimCount = *((uint16 *)pc);
                    pc += sizeof(uint16);
                    mPC = pc;

                    JSValue *base = getBase(stackSize() - (dimCount + 1));

                    // Use the type of the base to dispatch on...
                    JSObject *obj = base->toObjectValue(this);
                    JSFunction *target = getUnaryOperator(base->getType(), DeleteIndex);

                    if (target) {
                        JSValue result;
                        if (target->isNative()) {
                            JSValue *argBase = new JSValue[dimCount + 1];
                            for (uint32 i = 0; i < (dimCount + 1); i++)
                                argBase[i] = base[i];
                            resizeStack(stackSize() - (dimCount + 1));
                            result = target->getNativeCode()(this, argBase[0], argBase, dimCount + 1);
                            delete[] argBase;
                        }
                        else {
                            uint32 argCount = dimCount + 1;
                            JSValue *argBase = buildArgumentBlock(target, argCount);
                            resizeStack(stackSize() - (dimCount + 1));
                            try {
                                result = interpret(target->getByteCode(), 0, target->getScopeChain(), kNullValue, argBase, argCount);
                            }
                            catch (Exception &x) {
                                delete[] argBase;
                                throw x;
                            }
                        }
                        pushValue(result);
                    }
                    else {  // XXX or should this be implemented in Object_Type as operator "delete[]" ?
                        if (dimCount != 1)
                            reportError(Exception::typeError, "too many indices");
                        JSValue index = popValue();
                        popValue();     // discard base
                        const String *name = index.toString(this).string;
                        PropertyIterator it;
                        if (obj->hasOwnProperty(this, *name, mNamespaceList, Read, &it))
                            obj->deleteProperty(this, *name, mNamespaceList);
                        pushValue(kTrueValue);
                    }
                }
                break;
            case GetPropertyOp:
                {
                    JSValue base = popValue();
                    JSObject *obj = base.toObjectValue(this);
                    uint32 index = *((uint32 *)pc);
                    pc += sizeof(uint32);
                    mPC = pc;
                    const StringAtom &name = *mCurModule->getString(index);
                    obj->getProperty(this, name, mNamespaceList);
                    // if the result is a method of some kind, bind
                    // the base object to it
                    JSValue result = topValue();
                    if (result.isFunction()) {
                        popValue();
                        if (result.function->isConstructor())
                            // A constructor has to be called with a NULL 'this' in order to prompt it
                            // to construct the instance object.
                            pushValue(JSValue((JSFunction *)(new JSBoundFunction(this, result.function, NULL))));
                        else
                            pushValue(JSValue((JSFunction *)(new JSBoundFunction(this, result.function, obj))));
                    }
                    if (useOnceNamespace) {
                        NamespaceList *t = mNamespaceList->mNext;
                        delete mNamespaceList;
                        useOnceNamespace = false;
                        mNamespaceList = t;
                    }                        
                }
                break;
            case GetInvokePropertyOp:
                {
                    JSValue base = topValue();
                    JSValue baseObject = base.toObject(this);
                    // XXX really only need to pop/push if the base got modified
                    popValue();
                    pushValue(baseObject); // want the "toObject'd" version of base

                    uint32 index = *((uint32 *)pc);
                    pc += sizeof(uint32);
                    mPC = pc;
                    
                    const StringAtom &name = *mCurModule->getString(index);
                    baseObject.getObjectValue()->getProperty(this, name, mNamespaceList);
                    if (useOnceNamespace) {
                        NamespaceList *t = mNamespaceList->mNext;
                        delete mNamespaceList;
                        useOnceNamespace = false;
                        mNamespaceList = t;
                    }                        
                }
                break;
            case SetPropertyOp:
                {
                    JSValue v = popValue();
                    if (v.isFunction() && v.function->hasBoundThis() && !v.function->isMethod()) {
                        v = JSValue(v.function->getFunction());
                    }
                    JSValue base = popValue();
                    JSObject *obj = base.toObjectValue(this);
                    uint32 index = *((uint32 *)pc);
                    pc += sizeof(uint32);
                    mPC = pc;
                    const StringAtom &name = *mCurModule->getString(index);
                    obj->setProperty(this, name, mNamespaceList, v);
                    pushValue(v);
                    if (useOnceNamespace) {
                        NamespaceList *t = mNamespaceList->mNext;
                        delete mNamespaceList;
                        useOnceNamespace = false;
                        mNamespaceList = t;
                    }                        
                }
                break;
            case DoUnaryOp:
                {
                    Operator op = (Operator)(*pc++);
                    mPC = pc;
                    JSValue v = topValue();
                    JSFunction *target = getUnaryOperator(v.getType(), op);
                    if (target) {                    
                        uint32 argBase = stackSize() - 1;
                        JSValue newThis = kNullValue;
                        if (!target->isNative()) {
                            // lie about argCount to the activation since it
                            // would normally expect to clean the function pointer
                            // off the stack as well.
                            mActivationStack.push(new Activation(mLocals, mStack, mStackTop - 1, 
                                                                    mScopeChain,
                                                                    mArgumentBase, mThis,
                                                                    pc, mCurModule, mNamespaceList));
                            mThis = newThis;
                            mCurModule = target->getByteCode();
                            pc = mCurModule->mCodeBase;
                            endPC = mCurModule->mCodeBase + mCurModule->mLength;
                            mArgumentBase = getBase(argBase);
                            mLocals = new JSValue[mCurModule->mLocalsCount];
                            mStack = new JSValue[mCurModule->mStackDepth];
                            mStackMax = mCurModule->mStackDepth;
                            mStackTop = 0;
                        }
                        else {
                            JSValue result = (target->getNativeCode())(this, newThis, getBase(argBase), 0);
                            resizeStack(stackSize() -  1);
                            pushValue(result);
                        }
                        break;
                    }                    

                    switch (op) {
                    default:
                        NOT_REACHED("bad unary op");
                    case Negate:
                        {
                            popValue();
                            JSValue n = v.toNumber(this);
                            if (n.isNaN())
                                pushValue(n);
                            else
                                pushValue(JSValue(-n.f64));
                        }
                        break;
                    case Posate:
                        {
                            popValue();
                            JSValue n = v.toNumber(this);
                            pushValue(n);
                        }
                        break;
                    case Complement:
                        {
                            popValue();
                            JSValue n = v.toInt32(this);
                            pushValue(JSValue((float64)(~(int32)(n.f64))));
                        }
                        break;
                    }
                }
                break;
            case DoOperatorOp:
                {
                    Operator op = (Operator)(*pc++);
                    mPC = pc;
                    JSValue v1 = getValue(stackSize() - 2);
                    JSValue v2 = getValue(stackSize() - 1);
                    if (executeOperator(op, v1.getType(), v2.getType())) {
                        // need to invoke
                        pc = mCurModule->mCodeBase;
                        endPC = mCurModule->mCodeBase + mCurModule->mLength;
                        mLocals = new JSValue[mCurModule->mLocalsCount];
                        mStack = new JSValue[mCurModule->mStackDepth];
                        mStackMax = mCurModule->mStackDepth;
                        mStackTop = 0;
                    }
                }
                break;
            case GetConstructorOp:
                {
                    JSValue v = popValue();
                    ASSERT(v.isType());
                    pushValue(JSValue(v.type->getDefaultConstructor()));
                }
                break;
            case NewInstanceOp:
                {
                    uint32 argCount = *((uint32 *)pc); 
                    pc += sizeof(uint32);
                    uint32 cleanUp = argCount;
                    JSValue *argBase = getBase(stackSize() - argCount);
                    bool isPrototypeFunctionCall = false;

                    JSFunction *target = NULL;
                    JSValue newThis = kNullValue;
                    JSValue *typeValue = getBase(stackSize() - (argCount + 1));
                    if (!typeValue->isType()) {                        
                        if (typeValue->isFunction() && typeValue->function->isPrototype()) {
                            isPrototypeFunctionCall = true;
                            target = typeValue->function;
                            newThis = Object_Type->newInstance(this);
                            PropertyIterator i;
                            if (target->hasProperty(this, Prototype_StringAtom, mNamespaceList, Read, &i)) {
                                JSValue v = target->getPropertyValue(i);
                                newThis.object->mPrototype = v.toObjectValue(this);
                                newThis.object->setProperty(this, UnderbarPrototype_StringAtom, (NamespaceList *)NULL, JSValue(newThis.object->mPrototype));
                            }
                        }
                        else            
                            reportError(Exception::referenceError, "Not a type or a prototype function");
                    }
                    else {
                        // if the type has an operator "new" use that, 
                        // otherwise use the default constructor (and pass NULL
                        // for the this value)
                        target = getUnaryOperator(typeValue->type, New);
                        if (target)
                            newThis = typeValue->type->newInstance(this);
                        else {
                            newThis = kNullValue;
                            target = typeValue->type->getDefaultConstructor();
                        }
                    }                    
                    ASSERT(target);

                    JSValue result;
                    if (target->isNative()) {
                        JSValue *tArgBase = new JSValue[argCount];
                        for (uint32 i = 0; i < argCount; i++)
                            tArgBase[i] = argBase[i];
                        resizeStack(stackSize() - cleanUp);
                        result = target->getNativeCode()(this, newThis, tArgBase, argCount);
                    }
                    else {
                        argBase = buildArgumentBlock(target, argCount);
                        resizeStack(stackSize() - cleanUp);
                        if (!target->isChecked()) {
                            JSValue args = Array_Type->newInstance(this);
                            for (uint32 i = 0; i < argCount; i++) 
                                args.instance->setProperty(this, *numberToString(i), NULL, argBase[i]);
                            target->getActivation()->setProperty(this, Arguments_StringAtom, NULL, args);
                        }
                        try {
                            result = interpret(target->getByteCode(), 0, target->getScopeChain(), newThis, argBase, argCount);
                        }
                        catch (Exception &x) {
                            delete[] argBase;
                            throw x;
                        }
                    }
                    
                    if (isPrototypeFunctionCall) {
                        // If it's a prototype function, the return value is only
                        // interesting if it's not a primitive, in which case it
                        // overrides the newly constructed object. Weird, huh.
                        if (!result.isPrimitive())
                            newThis = result;
                    }
                    else
                        // otherwise, constructor has potentially made the 'this', so retain it
                        newThis = result;

                    popValue();             // don't need the type anymore
                    pushValue(newThis);
                 }
                break;
            case NewThisOp:
                {
                    JSValue v = popValue();
                    if (mThis.isNull()) {
                        ASSERT(v.isType());
                        mThis = v.type->newInstance(this);
                    }
                }
                break;
            case NewObjectOp:
                {
                    pushValue(Object_Type->newInstance(this));
                }
                break;
            case GetLocalVarOp:
                {
                    uint32 index = *((uint32 *)pc);
                    pc += sizeof(uint32);
                    pushValue(mLocals[index]);
                }
                break;
            case SetLocalVarOp:
                {
                    uint32 index = *((uint32 *)pc);
                    pc += sizeof(uint32);
                    JSValue v = topValue();
                    if (v.isFunction() && v.function->hasBoundThis() && !v.function->isMethod()) {
                        v = JSValue(v.function->getFunction());
                    }
                    mLocals[index] = v;
                }
                break;
            case GetClosureVarOp:
                {
                    uint32 depth = *((uint32 *)pc);
                    pc += sizeof(uint32);
                    uint32 index = *((uint32 *)pc);
                    pc += sizeof(uint32);
//                    pushValue(mScopeChain->getClosureVar(depth, index));                    
                }
                break;
            case SetClosureVarOp:
                {
                    JSValue v = topValue();
                    if (v.isFunction() && v.function->hasBoundThis() && !v.function->isMethod()) {
                        v = JSValue(v.function->getFunction());
                    }
                    uint32 depth = *((uint32 *)pc);
                    pc += sizeof(uint32);
                    uint32 index = *((uint32 *)pc);
                    pc += sizeof(uint32);
//                    mScopeChain->setClosureVar(depth, index, topValue()));
                }
                break;
            case NewClosureOp:
                {
                }
                break;
            case LoadThisOp:
                {
                    pushValue(mThis);
                }
                break;
            case GetArgOp:
                {
                    uint32 index = *((uint32 *)pc);
                    pc += sizeof(uint32);
                    pushValue(mArgumentBase[index]);
                }
                break;
            case SetArgOp:
                {
                    JSValue v = topValue();
                    if (v.isFunction() && v.function->hasBoundThis() && !v.function->isMethod()) {
                        v = JSValue(v.function->getFunction());
                    }
                    uint32 index = *((uint32 *)pc);
                    pc += sizeof(uint32);
                    mArgumentBase[index] = v;
                }
                break;
            case GetMethodOp:
                {
                    JSValue base = topValue();
                    ASSERT(base.isInstance());
                    uint32 index = *((uint32 *)pc);
                    pc += sizeof(uint32);
                    pushValue(JSValue(base.instance->mType->mMethods[index]));
                }
                break;
            case GetMethodRefOp:
                {
                    JSValue base = popValue();
                    if (!base.isInstance())
                        reportError(Exception::semanticError, "Illegal method reference");
                    uint32 index = *((uint32 *)pc);
                    pc += sizeof(uint32);
                    pushValue(JSValue(new JSBoundFunction(this, base.instance->mType->mMethods[index], base.object)));
                }
                break;
            case GetFieldOp:
                {
                    JSValue base = popValue();
                    ASSERT(base.isInstance());
                    uint32 index = *((uint32 *)pc);
                    pc += sizeof(uint32);
                    pushValue(base.instance->mInstanceValues[index]);
                }
                break;
            case SetFieldOp:
                {
                    JSValue v = popValue();
                    if (v.isFunction() && v.function->hasBoundThis() && !v.function->isMethod()) {
                        v = JSValue(v.function->getFunction());
                    }
                    JSValue base = popValue();
                    ASSERT(base.isInstance());
                    uint32 index = *((uint32 *)pc);
                    pc += sizeof(uint32);
                    base.instance->mInstanceValues[index] = v;
                    pushValue(v);
                }
                break;
            case WithinOp:
                {
                    JSValue base = popValue();
                    mScopeChain->addScope(base.toObjectValue(this));
                }
                break;
            case WithoutOp:
                {
                    mScopeChain->popScope();
                }
                break;
            case PushScopeOp:
                {
                    JSObject *obj = *((JSObject **)pc);
                    mScopeChain->addScope(obj);
                    pc += sizeof(JSObject *);
                }
                break;
            case PopScopeOp:
                {
                    mScopeChain->popScope();
                }
                break;
            case LoadGlobalObjectOp:
                {
                    pushValue(JSValue(getGlobalObject()));
                }
                break;
            case JsrOp:
                {
                    uint32 offset = *((uint32 *)pc);
                    mSubStack.push(pc + sizeof(uint32));
                    pc += offset;
                }
                break;
            case RtsOp:
                {
                    pc = mSubStack.top();
                    mSubStack.pop();
                }
                break;

            case TryOp:
                {
                    Activation *curAct = (mActivationStack.size() > 0) ? mActivationStack.top() : NULL;
                    uint32 handler = *((uint32 *)pc);
                    if (handler != toUInt32(-1))
                        mTryStack.push(new HandlerData(pc + handler, stackSize(), curAct));
                    pc += sizeof(uint32);
                    handler = *((uint32 *)pc);
                    if (handler != toUInt32(-1))
                        mTryStack.push(new HandlerData(pc + handler, stackSize(), curAct));
                    pc += sizeof(uint32);
                }
                break;
            case HandlerOp:
                {
                    HandlerData *hndlr = (HandlerData *)mTryStack.top();
                    mTryStack.pop();
                    delete hndlr;
                }
                break;
            case ThrowOp:
                {   
                    throw Exception(Exception::userException, "");
                }
                break;
            case JuxtaposeOp:
                {
                    JSValue v2 = popValue();
                    JSValue v1 = popValue();

                    ASSERT(v1.isAttribute());
                    ASSERT(v2.isAttribute());

                    Attribute *a1 = v1.attribute;
                    Attribute *a2 = v2.attribute;

                    if ((a1->mTrueFlags & a2->mFalseFlags) != 0)
                        reportError(Exception::semanticError, "Mismatched attributes"); // XXX could supply more detail
                    if ((a1->mFalseFlags & a2->mTrueFlags) != 0)
                        reportError(Exception::semanticError, "Mismatched attributes");

                    
                    // Now build the result attribute and set it's values
                    Attribute *x = new Attribute(a1->mTrueFlags | a2->mTrueFlags, a1->mFalseFlags | a2->mFalseFlags);

                    NamespaceList *t = a1->mNamespaceList;
                    while (t) {
                        x->mNamespaceList = new NamespaceList(t->mName, x->mNamespaceList);
                        t = t->mNext;
                    }
                    t = a2->mNamespaceList;
                    while (t) {
                        x->mNamespaceList = new NamespaceList(t->mName, x->mNamespaceList);
                        t = t->mNext;
                    }
                    if (a1->mExtendArgument)
                        x->mExtendArgument = a1->mExtendArgument;
                    else
                        if (a2->mExtendArgument)
                            x->mExtendArgument = a2->mExtendArgument;
                    
                    pushValue(JSValue(x));
                }
                break;
            case CastOp:
                {
                    JSValue t = popValue();
                    ASSERT(t.isType());
                    JSType *toType = t.type;
                    JSValue v = popValue();

                    pushValue(mapValueToType(v, toType));
                }
                break;
            case UseOnceOp:
                {
                    uint32 index = *((uint32 *)pc);
                    pc += sizeof(uint32);
                    const StringAtom &nameSpace = *mCurModule->getString(index);
                    ASSERT(useOnceNamespace == false);
                    useOnceNamespace = true;
                    mNamespaceList = new NamespaceList(nameSpace, mNamespaceList);
                }
                break;
            case UseOp:
                {
                    uint32 index = *((uint32 *)pc);
                    pc += sizeof(uint32);
                    const StringAtom &nameSpace = *mCurModule->getString(index);
                    mNamespaceList = new NamespaceList(nameSpace, mNamespaceList);
                }
                break;

            default:
                reportError(Exception::internalError, "Bad Opcode");
            }
        }
        catch (Exception &jsx) {
            if (mTryStack.size() > 0) {
                HandlerData *hndlr = (HandlerData *)mTryStack.top();
                Activation *curAct = (mActivationStack.size() > 0) ? mActivationStack.top() : NULL;
                
                JSValue x;
                if (curAct != hndlr->mActivation) {
                    ASSERT(mActivationStack.size() > 0);
                    Activation *prev;// = mActivationStack.top();
                    do {
                        prev = curAct;
                        if (prev->mPC == NULL) {
                            // Yikes! the exception is getting thrown across a re-invocation
                            // of the interpreter loop.
                            throw jsx;
                        }
                        mActivationStack.pop();
                        curAct = mActivationStack.top();                            
                    } while (hndlr->mActivation != curAct);
                    if (jsx.hasKind(Exception::userException))  // snatch the exception before the stack gets clobbered
                        x = popValue();
                    mNamespaceList = prev->mNamespaceList;
                    mCurModule = prev->mModule;
                    endPC = mCurModule->mCodeBase + mCurModule->mLength;
                    mLocals = prev->mLocals;
                    mStack = prev->mStack;
                    mStackMax = mCurModule->mStackDepth;
                    mArgumentBase = prev->mArgumentBase;
                    mThis = prev->mThis;
                }
                else {
                    if (jsx.hasKind(Exception::userException))
                        x = popValue();
                }

                // make sure there's a JS object for the catch clause to work with
                if (!jsx.hasKind(Exception::userException)) {
                    JSValue argv[1];
                    argv[0] = JSValue(new String(jsx.fullMessage()));
                    switch (jsx.kind) {
                    case Exception::syntaxError:
                        x = SyntaxError_Constructor(this, kNullValue, argv, 1);
                        break;
                    case Exception::referenceError:
                        x = ReferenceError_Constructor(this, kNullValue, argv, 1);
                        break;
                    case Exception::typeError:
                        x = TypeError_Constructor(this, kNullValue, argv, 1);
                        break;
                    case Exception::rangeError:
                        x = RangeError_Constructor(this, kNullValue, argv, 1);
                        break;
                    default:
                        x = Error_Constructor(this, kNullValue, argv, 1);
                        break;
                    }
                }
                
                resizeStack(hndlr->mStackSize);
                pc = hndlr->mPC;
                pushValue(x);
            }
            else
                throw jsx; //reportError(Exception::uncaughtError, "No handler for throw");
        }
    }
    return result;
}

// called on a JSValue to return a base object. The value may be a type, function, instance
// or generic object
JSObject *JSValue::getObjectValue() const
{
    switch (tag) {
    case object_tag:
        return object;
    case instance_tag:
        return instance;
    case function_tag:
        return function;
    case type_tag:
        return type;
    case package_tag:
        return package;
    default:
        NOT_REACHED("Bad tag");
        return NULL;
    }
}

// called on a JSValue with type == Number_Type, which could be either a tagged
// value or a Number object (actually an instance). Return the raw value in either case.
float64 JSValue::getNumberValue() const
{
    if (isNumber())
        return f64;
    ASSERT(isInstance() && (getType() == Number_Type));
    JSNumberInstance *numInst = checked_cast<JSNumberInstance *>(instance);
    return numInst->mValue;
}

// called on a JSValue with type == String_Type, which could be either a tagged
// value or a String object (actually an instance). Return the raw value in either case.
const String *JSValue::getStringValue() const
{
    if (isString())
        return string;
    ASSERT(isInstance() && (getType() == String_Type));
    JSStringInstance *strInst = checked_cast<JSStringInstance *>(instance);
    return strInst->mValue;
}

// called on a JSValue with type == Boolean_Type, which could be either a tagged
// value or a Boolean object (actually an instance). Return the raw value in either case.
bool JSValue::getBoolValue() const
{
    if (isBool())
        return boolean;
    ASSERT(isInstance() && (getType() == Boolean_Type));
    JSBooleanInstance *boolInst = checked_cast<JSBooleanInstance *>(instance);
    return boolInst->mValue;
}

// Implementation of '+' for two number types
static JSValue numberPlus(Context *, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
    return JSValue(argv[0].getNumberValue() + argv[1].getNumberValue());
}

// Implementation of '+' for two integer types
static JSValue integerPlus(Context * /*cx*/, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
    return JSValue(argv[0].getNumberValue() + argv[1].getNumberValue());
}

// Implementation of '+' for two 'generic' objects
static JSValue objectPlus(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
    JSValue &r1 = argv[0];
    JSValue &r2 = argv[1];
    JSType *t1 = r1.getType();
    JSType *t2 = r2.getType();

    if ((t1 == Number_Type) && (t2 == Number_Type)) {        
        return JSValue(r1.getNumberValue() + r2.getNumberValue());
    }

    if (t1 == String_Type) {
        if (t2 == String_Type)
            return JSValue(new String(*r1.getStringValue() + *r2.getStringValue()));
        else
            return JSValue(new String(*r1.getStringValue() + *r2.toString(cx).string));
    }
    else {
        if (t2 == String_Type) 
            return JSValue(new String(*r1.toString(cx).string + *r2.getStringValue()));
        else {
            JSValue r1p = r1.toPrimitive(cx);
            JSValue r2p = r2.toPrimitive(cx);
            // gar-on-teed tagged values now
            if (r1p.isString())
                if (r2p.isString())
                    return JSValue(new String(*r1p.string + *r2p.string));
                else
                    return JSValue(new String(*r1p.string + *r2p.toString(cx).string));
            else
                if (r2p.isString())
                    return JSValue(new String(*r1p.toString(cx).string + *r2p.string));
                else {
                    JSValue num1(r1.toNumber(cx));
                    JSValue num2(r2.toNumber(cx));
                    return JSValue(num1.f64 + num2.f64);
                }
        }
    }
}



// Implementation of '-' for two integer types
static JSValue integerMinus(Context * /*cx*/, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
    return JSValue(argv[0].getNumberValue() - argv[1].getNumberValue());
}

// Implementation of '-' for two number types
static JSValue numberMinus(Context *, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
    return JSValue(argv[0].getNumberValue() - argv[1].getNumberValue());
}

// Implementation of '-' for two 'generic' objects
static JSValue objectMinus(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
    JSValue &r1 = argv[0];
    JSValue &r2 = argv[1];
    return JSValue(r1.toNumber(cx).f64 - r2.toNumber(cx).f64);
}



// Implementation of '*' for two integer types
static JSValue integerMultiply(Context * /*cx*/, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
    return JSValue(argv[0].getNumberValue() * argv[1].getNumberValue());
}

// Implementation of '*' for two 'generic' objects
static JSValue objectMultiply(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
    JSValue &r1 = argv[0];
    JSValue &r2 = argv[1];
    return JSValue(r1.toNumber(cx).f64 * r2.toNumber(cx).f64);
}


// Implementation of '/' for two integer types
static JSValue integerDivide(Context * /*cx*/, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
    float64 f1 = argv[0].getNumberValue();
    float64 f2 = argv[1].getNumberValue();
    float64 d = f1 / f2;
    bool neg = (d < 0);
    d = fd::floor(neg ? -d : d);
    d = neg ? -d : d;
    return JSValue(d);
}

// Implementation of '/' for two 'generic' objects
static JSValue objectDivide(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
    JSValue &r1 = argv[0];
    JSValue &r2 = argv[1];
    return JSValue(r1.toNumber(cx).f64 / r2.toNumber(cx).f64);
}


// Implementation of '%' for two integer types
static JSValue integerRemainder(Context * /*cx*/, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
    float64 f1 = argv[0].getNumberValue();
    float64 f2 = argv[1].getNumberValue();
    float64 d = fd::fmod(f1, f2);
    bool neg = (d < 0);
    d = fd::floor(neg ? -d : d);
    d = neg ? -d : d;
    return JSValue(d);
}

// Implementation of '%' for two 'generic' objects
static JSValue objectRemainder(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
    JSValue &r1 = argv[0];
    JSValue &r2 = argv[1];

    float64 f1 = r1.toNumber(cx).f64;
    float64 f2 = r2.toNumber(cx).f64;

#ifdef XP_PC
    /* Workaround MS fmod bug where 42 % (1/0) => NaN, not 42. */
    if (JSDOUBLE_IS_FINITE(f1) && JSDOUBLE_IS_INFINITE(f2))
        return JSValue(f1);
#endif

    return JSValue(fd::fmod(f1, f2));
}


// Implementation of '<<' for two integer types
static JSValue integerShiftLeft(Context * /*cx*/, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
    float64 f1 = argv[0].getNumberValue();
    float64 f2 = argv[1].getNumberValue();
    return JSValue((float64)( (int32)(f1) << ( (uint32)(f2) & 0x1F)) );
}

// Implementation of '<<' for two 'generic' objects
static JSValue objectShiftLeft(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
    JSValue &r1 = argv[0];
    JSValue &r2 = argv[1];
    return JSValue((float64)( (int32)(r1.toInt32(cx).f64) << ( (uint32)(r2.toUInt32(cx).f64) & 0x1F)) );
}



// Implementation of '>>' for two integer types
static JSValue integerShiftRight(Context * /*cx*/, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
    float64 f1 = argv[0].getNumberValue();
    float64 f2 = argv[1].getNumberValue();
    return JSValue((float64) ( (int32)(f1) >> ( (uint32)(f2) & 0x1F)) );
}

// Implementation of '>>' for two 'generic' objects
static JSValue objectShiftRight(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
    JSValue &r1 = argv[0];
    JSValue &r2 = argv[1];
    return JSValue((float64) ( (int32)(r1.toInt32(cx).f64) >> ( (uint32)(r2.toUInt32(cx).f64) & 0x1F)) );
}



// Implementation of '>>>' for two integer types
static JSValue integerUShiftRight(Context * /*cx*/, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
    float64 f1 = argv[0].getNumberValue();
    float64 f2 = argv[1].getNumberValue();
    return JSValue((float64) ( (uint32)(f1) >> ( (uint32)(f2) & 0x1F)) );
}

// Implementation of '>>>' for two 'generic' objects
static JSValue objectUShiftRight(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
    JSValue &r1 = argv[0];
    JSValue &r2 = argv[1];
    return JSValue((float64) ( (uint32)(r1.toUInt32(cx).f64) >> ( (uint32)(r2.toUInt32(cx).f64) & 0x1F)) );
}


// Implementation of '&' for two integer types
static JSValue integerBitAnd(Context * /*cx*/, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
    float64 f1 = argv[0].getNumberValue();
    float64 f2 = argv[1].getNumberValue();
    return JSValue((float64)( (int32)(f1) & (int32)(f2) ));
}

// Implementation of '&' for two 'generic' objects
static JSValue objectBitAnd(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
    JSValue &r1 = argv[0];
    JSValue &r2 = argv[1];
    return JSValue((float64)( (int32)(r1.toInt32(cx).f64) & (int32)(r2.toInt32(cx).f64) ));
}



// Implementation of '^' for two integer types
static JSValue integerBitXor(Context * /*cx*/, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
    float64 f1 = argv[0].getNumberValue();
    float64 f2 = argv[1].getNumberValue();
    return JSValue((float64)( (int32)(f1) ^ (int32)(f2) ));
}

// Implementation of '^' for two 'generic' objects
static JSValue objectBitXor(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
    JSValue &r1 = argv[0];
    JSValue &r2 = argv[1];
    return JSValue((float64)( (int32)(r1.toInt32(cx).f64) ^ (int32)(r2.toInt32(cx).f64) ));
}



// Implementation of '|' for two integer types
static JSValue integerBitOr(Context * /*cx*/, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
    float64 f1 = argv[0].getNumberValue();
    float64 f2 = argv[1].getNumberValue();
    return JSValue((float64)( (int32)(f1) | (int32)(f2) ));
}

// Implementation of '|' for two 'generic' objects
static JSValue objectBitOr(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
    JSValue &r1 = argv[0];
    JSValue &r2 = argv[1];
    return JSValue((float64)( (int32)(r1.toInt32(cx).f64) | (int32)(r2.toInt32(cx).f64) ));
}



//
// implements r1 < r2, returning true or false or undefined
//
static JSValue objectCompare(Context *cx, JSValue &r1, JSValue &r2)
{
    JSValue r1p = r1.toPrimitive(cx, JSValue::NumberHint);
    JSValue r2p = r2.toPrimitive(cx, JSValue::NumberHint);

    if (r1p.isString() && r2p.isString())
        return JSValue(bool(r1p.string->compare(*r2p.string) < 0));
    else {
        JSValue r1n = r1p.toNumber(cx);
        JSValue r2n = r2p.toNumber(cx);
        if (r1n.isNaN() || r2n.isNaN())
            return kUndefinedValue;
        else
            return JSValue(r1n.f64 < r2n.f64);
    }

}

static JSValue objectLess(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
    JSValue &r1 = argv[0];
    JSValue &r2 = argv[1];
    JSValue result = objectCompare(cx, r1, r2);
    if (result.isUndefined())
        return kFalseValue;
    else
        return result;
}

static JSValue objectLessEqual(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
    JSValue &r1 = argv[0];
    JSValue &r2 = argv[1];
    JSValue result = objectCompare(cx, r2, r1);
    if (result.isUndefined() || result.isTrue())
        return kFalseValue;
    else
        return kTrueValue;
}

// Compare two values for '=='
static JSValue compareEqual(Context *cx, JSValue r1, JSValue r2)
{
    JSType *t1 = r1.getType();
    JSType *t2 = r2.getType();

    if (t1 != t2) {
        if (r1.isNull() && r2.isUndefined())
            return kTrueValue;
        if (r1.isUndefined() && r2.isNull())
            return kTrueValue;
        if ((t1 == Number_Type) && (t2 == String_Type))
            return compareEqual(cx, r1, r2.toNumber(cx));
        if ((t1 == String_Type) && (t2 == Number_Type))
            return compareEqual(cx, r1.toNumber(cx), r2.toString(cx));
        if (t1 == Boolean_Type)
            return compareEqual(cx, r1.toNumber(cx), r2);
        if (t2 == Boolean_Type)
            return compareEqual(cx, r1, r2.toNumber(cx));
        if ( ((t1 == String_Type) || (t1 == Number_Type)) && r2.isObject() )
            return compareEqual(cx, r1, r2.toPrimitive(cx));
        if ( (r1.isObject()) && ((t2 == String_Type) || (t2 == Number_Type)) )
            return compareEqual(cx, r1.toPrimitive(cx), r2);
        return kFalseValue;
    }
    else {
        if (r1.isUndefined())
            return kTrueValue;
        if (r1.isNull())
            return kTrueValue;
        if (r1.isInstance() && r2.isInstance()) // because new Boolean()->getType() == Boolean_Type
            return JSValue(r1.instance == r2.instance);
        if (r1.isObject() && r2.isObject())
            return JSValue(r1.object == r2.object);
        if (r1.isPackage() && r2.isPackage())
            return JSValue(r1.package == r2.package);
        if (r1.isType())
            return JSValue(r1.type == r2.type);
        if (r1.isFunction())
            return JSValue(r1.function->isEqual(r2.function));
        if (t1 == Number_Type) {
            
            float64 f1 = r1.getNumberValue();
            float64 f2 = r2.getNumberValue();

            if (JSDOUBLE_IS_NaN(f1))
                return kFalseValue;
            if (JSDOUBLE_IS_NaN(f2))
                return kFalseValue;

            return JSValue(r1.f64 == r2.f64);
        }
        else {
            if (t1 == String_Type)
                return JSValue(bool(r1.getStringValue()->compare(*r2.getStringValue()) == 0));
            if (t1 == Boolean_Type)
                return JSValue(r1.getBoolValue() == r2.getBoolValue());
            NOT_REACHED("unhandled type");
            return kFalseValue;
        }
    }
}

static JSValue objectEqual(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
    JSValue r1 = argv[0];
    JSValue r2 = argv[1];
    
    return compareEqual(cx, r1, r2);
}

static JSValue objectSpittingImage(Context * /*cx*/, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
    JSValue r1 = argv[0];
    JSValue r2 = argv[1];
    
    JSType *t1 = r1.getType();
    JSType *t2 = r2.getType();

    if ((t1 != t2) || (r1.isObject() != r2.isObject())) {
        return kFalseValue;
    }
    else {
        if (r1.isUndefined())
            return kTrueValue;
        if (r1.isNull())
            return kTrueValue;
        if (r1.isObject() && r2.isObject()) // because new Boolean()->getType() == Boolean_Type
            return JSValue(r1.object == r2.object);
        if (r1.isType())
            return JSValue(r1.type == r2.type);
        if (r1.isFunction())
            return JSValue(r1.function->isEqual(r2.function));
        if (t1 == Number_Type) {
            
            float64 f1 = r1.getNumberValue();
            float64 f2 = r2.getNumberValue();

            if (JSDOUBLE_IS_NaN(f1))
                return kFalseValue;
            if (JSDOUBLE_IS_NaN(f2))
                return kFalseValue;

            return JSValue(r1.f64 == r2.f64);
        }
        else {
            if (t1 == String_Type)
                return JSValue(bool(r1.getStringValue()->compare(*r2.getStringValue()) == 0));
            if (r1.isBool() && r2.isBool())
                return JSValue(r1.getBoolValue() == r2.getBoolValue());
            return kFalseValue;
        }
    }
}


// Build the default state of the binary operator table
void Context::initOperators()
{
    struct OpTableEntry {
        Operator which;
        JSType *op1;
        JSType *op2;
        JSFunction::NativeCode *imp;
        JSType *resType;
    } OpTable[] = {
        { Plus,  Object_Type, Object_Type, objectPlus,  Object_Type },
        { Plus,  Number_Type, Number_Type, numberPlus,  Number_Type },
        { Plus,  Integer_Type, Integer_Type, integerPlus,  Integer_Type },

        { Minus, Object_Type, Object_Type, objectMinus, Number_Type },
        { Minus, Number_Type, Number_Type, numberMinus, Number_Type },
        { Minus,  Integer_Type, Integer_Type, integerMinus,  Integer_Type },

        { ShiftLeft, Integer_Type, Integer_Type, integerShiftLeft, Integer_Type },
        { ShiftLeft, Object_Type, Object_Type, objectShiftLeft, Number_Type },

        { ShiftRight, Integer_Type, Integer_Type, integerShiftRight, Integer_Type },
        { ShiftRight, Object_Type, Object_Type, objectShiftRight, Number_Type },

        { UShiftRight, Integer_Type, Integer_Type, integerUShiftRight, Integer_Type },
        { UShiftRight, Object_Type, Object_Type, objectUShiftRight, Number_Type },
        
        { BitAnd, Integer_Type, Integer_Type, integerBitAnd, Integer_Type },
        { BitAnd, Object_Type, Object_Type, objectBitAnd, Number_Type },
        
        { BitXor, Integer_Type, Integer_Type, integerBitXor, Integer_Type },
        { BitXor, Object_Type, Object_Type, objectBitXor, Number_Type },

        { BitOr, Integer_Type, Integer_Type, integerBitOr, Integer_Type },
        { BitOr, Object_Type, Object_Type, objectBitOr, Number_Type },

        { Multiply, Integer_Type, Integer_Type, integerMultiply, Integer_Type },
        { Multiply, Object_Type, Object_Type, objectMultiply, Number_Type },
        
        { Divide, Integer_Type, Integer_Type, integerDivide, Integer_Type },
        { Divide, Object_Type, Object_Type, objectDivide, Number_Type },

        { Remainder, Integer_Type, Integer_Type, integerRemainder, Integer_Type },
        { Remainder, Object_Type, Object_Type, objectRemainder, Number_Type },

        { Less, Object_Type, Object_Type, objectLess, Boolean_Type },
        { LessEqual, Object_Type, Object_Type, objectLessEqual, Boolean_Type },

        { Equal, Object_Type, Object_Type, objectEqual, Boolean_Type },

        { SpittingImage, Object_Type, Object_Type, objectSpittingImage, Boolean_Type }

    };

    for (uint32 i = 0; i < sizeof(OpTable) / sizeof(OpTableEntry); i++) {
        JSFunction *f = new JSFunction(this, OpTable[i].imp, OpTable[i].resType);
        OperatorDefinition *op = new OperatorDefinition(OpTable[i].op1, OpTable[i].op2, f);
        mBinaryOperatorTable[OpTable[i].which].push_back(op);
    }
}

// Convert a primitive value into an object value, this is a no-op for
// values that are already object-like.

// XXX for Waldemar - is it possible to modify behaviour for Number, Boolean etc
// so that the behaviour here of constructing a new instance of those types would
// be different?

JSValue JSValue::valueToObject(Context *cx, const JSValue& value)
{
    switch (value.tag) {
    case f64_tag:
        {
            JSValue result = Number_Type->newInstance(cx);
            ((JSNumberInstance *)result.instance)->mValue = value.f64;
            return JSValue(result);
        }
    case boolean_tag:
        {
            JSValue result = Boolean_Type->newInstance(cx);
            ((JSBooleanInstance *)result.instance)->mValue = value.boolean;
            return JSValue(result);
        }
    case string_tag: 
        {
            JSValue result = String_Type->newInstance(cx);
            ((JSStringInstance *)result.instance)->mValue = value.string;
            return JSValue(result);
        }
    case instance_tag:
    case package_tag:
    case type_tag:
    case object_tag:
    case function_tag:
        return value;
    case null_tag:
        cx->reportError(Exception::typeError, "converting null to object");
    case undefined_tag:
        cx->reportError(Exception::typeError, "converting undefined to object");
    default:
        NOT_REACHED("Bad tag");
        return kUndefinedValue;
    }
}

// Convert a value to an object, without constructing a new value
// unless absolutely necessary, which would be the case for using
// valueToObject().getObjectValue() instead.

// XXX for Waldemar - is it possible to modify behaviour for Number, Boolean etc
// so that the behaviour here of constructing a new instance of those types would
// be different?


JSObject *JSValue::toObjectValue(Context *cx) const
{
    switch (tag) {
    case f64_tag:
        {
            JSValue result = Number_Type->newInstance(cx);
            ((JSNumberInstance *)result.instance)->mValue = f64;
            return result.instance;
        }
    case boolean_tag:
        {
            JSValue result = Boolean_Type->newInstance(cx);
            ((JSBooleanInstance *)result.instance)->mValue = boolean;
            return result.instance;
        }
    case string_tag: 
        {
            JSValue result = String_Type->newInstance(cx);
            ((JSStringInstance *)result.instance)->mValue = string;
            return result.instance;
        }
    case type_tag:
        return type;
    case object_tag:
        return object;
    case function_tag:
        return function;
    case instance_tag:
        return instance;
    case package_tag:
        return package;
    case null_tag:
        cx->reportError(Exception::typeError, "converting null to object");
    case undefined_tag:
        cx->reportError(Exception::typeError, "converting undefined to object");
    default:
        NOT_REACHED("Bad tag");
        return NULL;
    }
}

// convert string to double - a wrapper around the numerics routine
// to handle hex literals as well
float64 stringToNumber(const String *string)
{
    const char16 *numEnd;
    const char16 *sBegin = string->begin();
    if (sBegin)
        if ((sBegin[0] == '0') && ((sBegin[1] & ~0x20) == 'X'))
            return stringToInteger(sBegin, string->end(), numEnd, 16);
        else {
            float64 result = stringToDouble(sBegin, string->end(), numEnd);
            if (numEnd != string->end()) {
                const char16 *sEnd = string->end();
                while (numEnd != sEnd) {
                    if (!isSpace(*numEnd++))
                        return nan;
                }
                return result;
            }
            else
                return result;
        }
    else
        return 0.0;
}

// Convert a JSValue to number-type
JSValue JSValue::valueToNumber(Context *cx, const JSValue& value)
{
    switch (value.tag) {
    case f64_tag:
        return value;
    case string_tag: 
        return JSValue(stringToNumber(value.string));
    case object_tag:
    case instance_tag:
    case package_tag:
    case type_tag:
    case function_tag:
        return value.toPrimitive(cx, NumberHint).toNumber(cx);
    case boolean_tag:
        return JSValue((value.boolean) ? 1.0 : 0.0);
    case undefined_tag:
        return kNaNValue;
    case null_tag:
        return kPositiveZero;
    default:
        NOT_REACHED("Bad tag");
        return kUndefinedValue;
    }
}

// Convert a double to a string representation (base-10)
const String *numberToString(float64 number)
{
    char buf[dtosStandardBufferSize];
    const char *chrp = doubleToStr(buf, dtosStandardBufferSize, number, dtosStandard, 0);
    return new JavaScript::String(widenCString(chrp));
}

// Convert a JSValue to a string (ECMA rules)
JSValue JSValue::valueToString(Context *cx, const JSValue& value)
{
    const String *strp = NULL;
    JSObject *obj = NULL;
    switch (value.tag) {
    case f64_tag:
        return JSValue(numberToString(value.f64));
    case object_tag:
        obj = value.object;
        break;
    case function_tag:
        obj = value.function;
        break;
    case string_tag:
        return value;
    case boolean_tag:
        strp = (value.boolean) ? &cx->True_StringAtom : &cx->False_StringAtom;
        break;
    case type_tag:
        strp = value.type->mClassName;
        break;
    case undefined_tag:
        strp = &cx->Undefined_StringAtom;
        break;
    case null_tag:
        strp = &cx->Null_StringAtom;
        break;
    case instance_tag:
        obj = value.instance;
        break;
    case package_tag:
        obj = value.package;
        break;
    default:
        NOT_REACHED("Bad tag");
    }
    if (obj) {
        JSFunction *target = NULL;
        PropertyIterator i;
        if (obj->hasProperty(cx, cx->ToString_StringAtom, NULL, Read, &i)) {
            JSValue v = obj->getPropertyValue(i);
            if (v.isFunction())
                target = v.function;
        }
        if (target == NULL) {
            if (obj->hasProperty(cx, cx->ValueOf_StringAtom, NULL, Read, &i)) {
                JSValue v = obj->getPropertyValue(i);
                if (v.isFunction())
                    target = v.function;
            }
        }
        if (target)
            return cx->invokeFunction(target, value, NULL, 0);
        cx->reportError(Exception::runtimeError, "toString");    // XXX
        return kUndefinedValue; // keep compilers happy
    }
    else
        return JSValue(strp);

}

// Convert a JSValue to a primitive type
JSValue JSValue::toPrimitive(Context *cx, Hint hint) const
{
    JSObject *obj;
    switch (tag) {
    case f64_tag:
    case string_tag:
    case boolean_tag:
    case undefined_tag:
        return *this;

    case instance_tag:
        obj = instance;
        if ((hint == NoHint) && (getType() == Date_Type))
            hint = StringHint;
        break;
    case object_tag:
        obj = object;
        break;
    case function_tag:
        obj = function;
        break;

    default:
        NOT_REACHED("Bad tag");
        return kUndefinedValue;
    }

    // The following is [[DefaultValue]]
    //
    ASSERT(obj);
    JSFunction *target = NULL;
    JSValue result;
    PropertyIterator i;

    StringAtom *first = &cx->ValueOf_StringAtom;
    StringAtom *second = &cx->ToString_StringAtom;

    if (hint == StringHint) {
        first = &cx->ToString_StringAtom;
        second = &cx->ValueOf_StringAtom;
    }


    if (obj->hasProperty(cx, *first, NULL, Read, &i)) {
        JSValue v = obj->getPropertyValue(i);
        if (v.isFunction()) {
            target = v.function;
            if (target) {
                result = cx->invokeFunction(target, *this, NULL, 0);
                if (result.isPrimitive())
                    return result;
            }
        }
    }
    if (obj->hasProperty(cx, *second, NULL, Read, &i)) {
        JSValue v = obj->getPropertyValue(i);
        if (v.isFunction()) {
            target = v.function;
            if (target) {
                result = cx->invokeFunction(target, *this, NULL, 0);
                if (result.isPrimitive())
                    return result;
            }
        }
    }
    cx->reportError(Exception::runtimeError, "toPrimitive");    // XXX
    return kUndefinedValue;
}

int JSValue::operator==(const JSValue& value) const
{
    if (this->tag == value.tag) {
#       define CASE(T) case T##_tag: return (this->T == value.T)
        switch (tag) {
        CASE(f64);
        CASE(object);
        CASE(boolean);
        #undef CASE
        // question:  are all undefined values equal to one another?
        case undefined_tag: return 1;
        default:
            NOT_REACHED("Broken compiler?");            
        }
    }
    return 0;
}

float64 JSValue::float64ToInteger(float64 d)
{
    if (JSDOUBLE_IS_NaN(d))
        return 0.0;
    else
        return (d >= 0.0) ? fd::floor(d) : -fd::floor(-d);
}

JSValue JSValue::valueToInteger(Context *cx, const JSValue& value)
{
    JSValue v = valueToNumber(cx, value);
    v.f64 = float64ToInteger(v.f64);
    return v;
}

int32 JSValue::float64ToInt32(float64 d)
{
    d = fd::fmod(d, two32);
    d = (d >= 0) ? d : d + two32;
    if (d >= two31)
        return (int32)(d - two32);
    else
        return (int32)(d);    
}

uint32 JSValue::float64ToUInt32(float64 d)
{
    bool neg = (d < 0);
    d = fd::floor(neg ? -d : d);
    d = neg ? -d : d;
    d = fd::fmod(d, two32);
    d = (d >= 0) ? d : d + two32;
    return (uint32)d;
}

JSValue JSValue::valueToInt32(Context *, const JSValue& value)
{
    float64 d;
    switch (value.tag) {
    case f64_tag:
        d = value.f64;
        break;
    case string_tag: 
        {
            const char16 *numEnd;
            d = stringToDouble(value.string->begin(), value.string->end(), numEnd);
        }
        break;
    case boolean_tag:
        return JSValue((float64)((value.boolean) ? 1 : 0));
    case object_tag:
    case undefined_tag:
        // toNumber(toPrimitive(hint Number))
        return kUndefinedValue;
    default:
        NOT_REACHED("Bad tag");
        return kUndefinedValue;
    }
    if ((d == 0.0) || !JSDOUBLE_IS_FINITE(d) )
        return JSValue((float64)0);
    return JSValue((float64)float64ToInt32(d));
}

JSValue JSValue::valueToUInt32(Context *, const JSValue& value)
{
    float64 d;
    switch (value.tag) {
    case f64_tag:
        d = value.f64;
        break;
    case string_tag: 
        {
            const char16 *numEnd;
            d = stringToDouble(value.string->begin(), value.string->end(), numEnd);
        }
        break;
    case boolean_tag:
        return JSValue((float64)((value.boolean) ? 1 : 0));
    case object_tag:
    case undefined_tag:
        // toNumber(toPrimitive(hint Number))
        return kUndefinedValue;
    default:
        NOT_REACHED("Bad tag");
        return kUndefinedValue;
    }
    if ((d == 0.0) || !JSDOUBLE_IS_FINITE(d))
        return JSValue((float64)0);
    return JSValue((float64)float64ToUInt32(d));
}

JSValue JSValue::valueToUInt16(Context *, const JSValue& value)
{
    float64 d;
    switch (value.tag) {
    case f64_tag:
        d = value.f64;
        break;
    case string_tag: 
        {
            const char16 *numEnd;
            d = stringToDouble(value.string->begin(), value.string->end(), numEnd);
        }
        break;
    case boolean_tag:
        return JSValue((float64)((value.boolean) ? 1 : 0));
    case object_tag:
    case undefined_tag:
        // toNumber(toPrimitive(hint Number))
        return kUndefinedValue;
    default:
        NOT_REACHED("Bad tag");
        return kUndefinedValue;
    }
    if ((d == 0.0) || !JSDOUBLE_IS_FINITE(d))
        return JSValue((float64)0);
    bool neg = (d < 0);
    d = fd::floor(neg ? -d : d);
    d = neg ? -d : d;
    d = fd::fmod(d, two16);
    d = (d >= 0) ? d : d + two16;
    return JSValue((float64)d);
}

JSValue JSValue::valueToBoolean(Context * /*cx*/, const JSValue& value)
{
    switch (value.tag) {
    case f64_tag:
        if (JSDOUBLE_IS_NaN(value.f64))
            return kFalseValue;
        if (value.f64 == 0.0)
            return kFalseValue;
        return kTrueValue;
    case string_tag: 
        return JSValue(value.string->length() != 0);
    case boolean_tag:
        return value;
    case type_tag:
    case instance_tag:
    case object_tag:
    case function_tag:
        return kTrueValue;
        break;
    case null_tag:
    case undefined_tag:
        return kFalseValue;
    default:
        NOT_REACHED("Bad tag");
        return kUndefinedValue;
    }
}


// See if 'v' can be represented as a 't' - works for
// the built-ins by falling back to the ECMA3 behaviour
JSValue Context::mapValueToType(JSValue v, JSType *t)
{
    // user conversions?
    if (v.getType() == t)
        return v;
    
    if (t == Number_Type) {
        return v.toNumber(this);
    }
    else
    if (t == Integer_Type) {
        float64 d = v.toNumber(this).f64;
        bool neg = (d < 0);
        d = fd::floor(neg ? -d : d);
        d = neg ? -d : d;
        return JSValue(d);
    }
    else
    if (t == String_Type) {
        return v.toString(this);
    }
    else
    if (t == Boolean_Type) {
        return v.toBoolean(this);
    }

    if (v.isUndefined())
        return v;

    if (v.getType()->derivesFrom(t))
        return v;

    reportError(Exception::typeError, "Invalid type cast");
    return kUndefinedValue;
}

JSType *JSValue::getType() const {
    switch (tag) {
    case f64_tag:       return Number_Type;
    case boolean_tag:   return Boolean_Type;
    case string_tag:    return (JSType *)String_Type;
    case object_tag:    return Object_Type;
    case instance_tag:  return instance->getType();
    case undefined_tag: return Void_Type;
    case null_tag:      return Null_Type;
    case function_tag:  return Function_Type;
    case type_tag:      return Type_Type;
    default: 
        NOT_REACHED("bad type"); 
        return NULL;
    }
}


JSFunction *Context::getUnaryOperator(JSType *dispatchType, Operator which)
{
    // look in the operator table for applicable operators
    OperatorList applicableOperators;

    for (OperatorList::iterator oi = mUnaryOperatorTable[which].begin(),
                end = mUnaryOperatorTable[which].end(); 
                    (oi != end); oi++) {        
        if ((*oi)->isApplicable(dispatchType)) {
            applicableOperators.push_back(*oi);
        }
    }
    if (applicableOperators.size() == 0)
        return NULL;   
        
    OperatorList::iterator candidate = applicableOperators.begin();
    for (OperatorList::iterator aoi = applicableOperators.begin() + 1,
                aend = applicableOperators.end();
                    (aoi != aend); aoi++) 
    {
        if ((*aoi)->mType1->derivesFrom((*candidate)->mType1) )
            candidate = aoi;
    }
    
    return (*candidate)->mImp;
}

    
}
}

