/*
 * The contents of this file are subject to the Netscape Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/NPL/

 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.

 * The Original Code is Rhino code, released
 * May 6, 1999.

 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1997-1999 Netscape Communications Corporation. All
 * Rights Reserved.

 * Contributor(s): 

 * Alternatively, the contents of this file may be used under the
 * terms of the GNU Public License (the "GPL"), in which case the
 * provisions of the GPL are applicable instead of those above.
 * If you wish to allow use of your version of this file only
 * under the terms of the GPL and not to allow others to use your
 * version of this file under the NPL, indicate your decision by
 * deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL.  If you do not delete
 * the provisions above, a recipient may use your version of this
 * file under either the NPL or the GPL.
 */
/* Header file */
#include <stdio.h>
#include "js-stringbuffer.h"
#include "js-objectadapter.h"
#include "js-functionobject.h"
#include "js-scriptruntime.h"
#include "js-exceptionhandler.h"
#include "js-tokenstream.h"
#include "js-arguments.h"
#include "js-nativearray.h"
#include "js-nativeglobal.h"
#include "js-evaluatorexception.h"
#include "js-utils.h"
#include "js-flattenedobject.h"
#include "js-nativeobject.h"
#include "js-nativefunction.h"

/* Internal data */
const jsString_t	JSNativeFunction::className = "Function";

/* Procedures */
void
JSNativeFunction::finishInit(JSContext *cx, JSScriptable *scope,
			JSFunctionObject *ctor, JSScriptable *proto)
{
    class FunctionAdapter: public JSObjectAdapter {
	public:
	    JS_OA_DefineGetter(length, JSNativeFunction)
	    JS_OA_DefineGetter(arity, JSNativeFunction)
	    JS_OA_DefineGetter(name, JSNativeFunction)
    };
    static jsFunction_t	functions[] = {
#define	Item(name, length)	{ #name,  jsFunction_##name, length }
	Item(toString, -1),	Item(apply, -1),
	Item(call, -1),
#undef	Item
    } ;
    static jsProperty_t	properties[] = {
#define	ItemAdapterG(name, clazz)					\
	{								\
	    #name, JS_PA_ReadOnly | JS_PA_Permanent | JS_PA_DontEnum,	\
	    &clazz::name##_getter, NULL					\
	}
	ItemAdapterG(length, FunctionAdapter),
	ItemAdapterG(arity, FunctionAdapter),
	ItemAdapterG(name, FunctionAdapter),
#undef ItemG
    } ;

    ((JSScriptableObject *) proto)->defineFunctionProperties(cx, className,
				TableSize(functions), functions,
				JS_PA_DontEnum); 
    ((JSScriptableObject *) proto)->defineProperties(cx,
				TableSize(properties), properties); 
    ctor->setPrototype(cx, proto);

    jsString_t *noName = (jsString_t *) JS_NewGCObj(cx, sizeof (*noName));

    noName[0] = JS_NewGCString(cx, "");
    ((JSNativeFunction *) proto)->names = noName;
    ((JSNativeFunction *) proto)->namesLength = 1;
}
jsVal_t
JSNativeFunction::jsConstructor(JSContext *cx, int numArgs,
			jsVal_t *args, JSFunction *ctorObj, jsBool_t inNewExpr)
{
    JSStringBuffer funArgs(cx);
    int i;

    for (i = 0; i < numArgs - 1; i++) {
	if (i > 0) {
	    funArgs.append(",");
	}
	jsString_t string = JSScriptRuntime::toString(cx, args[i]); 

	funArgs.append(string);
	JS_DeleteZoneObj(cx, string);
    }
    jsString_t funBody = numArgs == 0 ?
				(jsString_t) ""
				: JSScriptRuntime::toString(cx, args[i]);

    int length = strlen(funArgs.toString()) + strlen(funBody)
			+ TableSize("function () {}") - 1 + 1;
    jsString_t source = (jsString_t) JS_NewZoneObj(cx, length);

    sprintf(source, "function (%s) {%s}", funArgs.toString(), funBody);

    int linep;
    jsString_t filename = JSContext::getSourcePosition(cx, &linep);

    if (filename == NULL) {
	filename = "<eval'ed string>";
	linep = 1;
    }
    JSScriptable *scope = cx->ctorScope;

    if (scope == NULL) {
        scope = (JSNativeFunction *) ctorObj;
    }
    JSScriptable *global = JSScriptableObject::getTopLevelScope(cx, scope);
    
    // Compile the function with opt level of -1 to force interpreter
    // mode.
    int oldOptLevel = cx->getOptimizationLevel();

    cx->setOptimizationLevel(-1);

    JSNativeFunction *fn
		= (JSNativeFunction *) cx->compileFunction(global, source,
							filename, linep); 
    cx->setOptimizationLevel(oldOptLevel);
    JS_DeleteZoneObj(cx, source);
    if (numArgs != 0) JS_DeleteZoneObj(cx, funBody);

    if (fn->names == NULL) {
        fn->names = (jsString_t *) JS_NewGCObj(cx, sizeof (*fn->names));
    }
    fn->names[0] = JS_NewGCString(cx, "anonymous");
    if (fn->names == NULL) fn->namesLength = 1;
    fn->setPrototype(cx, getFunctionPrototype(cx, global));
    fn->setParentScope(cx, global);

    return JS_OBJECT_TO_VAL(fn);
}
jsVal_t
JSNativeFunction::jsFunction_toString(JSContext *cx, JSScriptable *thisObj,
				int numArgs, jsVal_t *args, JSFunction *funObj)
{
    jsVal_t val = thisObj->getDefaultValue(cx, JS_T_Function);

    if (!JS_VAL_IS_OBJECT(val)
	|| !JS_VAL_TO_OBJECT(val)->isInstanceOf(JSNativeFunction::className)) {
        jsString_t message
			= JSContext::getMessage(cx, "msg.incompat.call %s",
						"toString");
        cx->exceptionHandler->throwEx(
                cx,
		JSNativeGlobal::constructError(cx, "TypeError", message,
					(JSNativeFunction *) funObj));
    }

    int indent = 0;
    if (numArgs > 0) {
        indent = (int) JSScriptRuntime::toNumber(cx, args[0]);
    }

    jsString_t s = ((JSNativeFunction *) JS_VAL_TO_OBJECT(val))
				->decompile(cx, indent, TRUE, FALSE);
    jsVal_t result =JS_STRING_TO_VAL(JS_NewGCString(cx, s));

    JS_DeleteZoneObj(cx, s);

    return result;
}
jsVal_t
JSNativeFunction::jsFunction_apply(JSContext *cx, JSScriptable *thisObj,
				int numArgs, jsVal_t *args, JSFunction *funObj)
{
    if (numArgs != 2) {
        return jsFunction_call(cx, thisObj, numArgs, args, funObj);
    }

    jsVal_t val = thisObj->getDefaultValue(cx, JS_T_Function);

    JSScriptable *newThis = args[0] == JS_VAL_NULL
                         ? getTopLevelScope(cx, thisObj)
                         : JSScriptRuntime::toObject(cx,
					(JSNativeFunction *) funObj, args[0]);
    int numNewArgs;
    jsVal_t *newArgs;

    numNewArgs = 0;
    newArgs = NULL;
    if (numArgs > 1) {
	JSScriptable *scriptable = (JSScriptable *) args[1];

	if (JS_VAL_IS_OBJECT(args[1]) && args[1] != JS_VAL_VOID
	    && (scriptable->isInstanceOf(JSNativeArray::className) 
		|| scriptable->isInstanceOf(JSArguments::className))) {
	    newArgs = cx->getElements(scriptable, &numNewArgs);
        } else {
	    cx->exceptionHandler->throwEx(
                    cx,
                    JSNativeGlobal::constructError(cx, "TypeError",
				JSScriptRuntime::getMessage(cx,
					"msg.arg.isnt.array"),
				thisObj));
	}
    }

    jsVal_t result = JSScriptRuntime::call(cx, val, JS_OBJECT_TO_VAL(newThis),
					numNewArgs, newArgs, newThis);
    if (newArgs != NULL) JS_DeleteZoneObj(cx, newArgs);

    return result;
}
jsVal_t
JSNativeFunction::jsFunction_call(JSContext *cx, JSScriptable *thisObj,
				int numArgs, jsVal_t *args, JSFunction *funObj)
{
    jsVal_t val = thisObj->getDefaultValue(cx, JS_T_Function);

    if (numArgs == 0) {
        JSScriptable *s = JSScriptRuntime::toObject(cx,
				(JSNativeFunction *) funObj, val);
        JSScriptable *scope = s->getParentScope(cx);

        return JSScriptRuntime::call(cx, val, JS_OBJECT_TO_VAL(scope),
					0, NULL, scope);
    } else {
        JSScriptable *newThis
			= args[0] == JS_VAL_NULL ?
			JSScriptableObject::getTopLevelScope(cx, thisObj)
			: JSScriptRuntime::toObject(cx,
				(JSNativeFunction *) funObj, args[0]);
 
        return JSScriptRuntime::call(cx, val, JS_OBJECT_TO_VAL(newThis),
					numArgs - 1, args + 1, newThis);
    }
}
double
JSNativeFunction::longBitsToDouble(int64_t bits)
{
    union {
	double	d;
	int64_t	l;
    } v;

    v.l = bits;
    return v.d;
}
jsBool_t
JSNativeFunction::nextIs(int i, int token)
{
    if (i + 1 < sourceLength) {
        return source[i + 1] == token;
    }
    return FALSE;
}
JSNativeCall *
JSNativeFunction::getActivation(JSContext *cx)
{
    JSNativeCall *activation = cx->currentActivation;

    while (activation != NULL) {
        if (activation->getFunctionObject() == this) {
            return activation;
	}
        activation = activation->caller;
    }
    return NULL;
}
void
JSNativeFunction::defineClass(JSContext *cx, JSScriptable *scope)
{
    JSScriptable *proto = new (cx) JSNativeFunction();

    JSScriptableObject::defineClass(cx, scope, proto, jsConstructor,
				finishInit);
}

jsBool_t
JSNativeFunction::jsGet_length(JSContext *cx, jsVal_t *value)
{
    if (cx->getLanguageVersion() != JSContext::VERSION_1_2) {
        *value = JS_INT_TO_VAL(argCount);
	return TRUE;
    }
    JSNativeCall *activation = getActivation(cx);

    if (activation == NULL) {
	*value = JS_INT_TO_VAL(argCount);
	return TRUE;
    }
    int numArgs;

    (void) activation->getOriginalArguments(&numArgs);

    *value = JS_INT_TO_VAL(numArgs);
    return TRUE;
}
jsBool_t  
JSNativeFunction::jsGet_arity(JSContext *cx, jsVal_t *value)
{
    *value = JS_INT_TO_VAL(argCount);
    return TRUE;
}
jsBool_t  
JSNativeFunction::jsGet_name(JSContext *cx, jsVal_t *value)
{
    if (strcmp(names[0], "anonymous") == 0) {
        if (cx->getLanguageVersion() == JSContext::VERSION_1_2) {
            *value = JS_STRING_TO_VAL(JS_NewGCString(cx, ""));
	    return TRUE;
	}
    }
    *value = JS_STRING_TO_VAL(names[0]);
    return TRUE;
}
jsString_t
JSNativeFunction::decompile(JSContext *cx, int indent, jsBool_t toplevel,
			jsBool_t justbody)
{
     if (source == NULL) {
	jsVal_t name;

	(void) jsGet_name(cx, &name);
	int length = strlen(JS_VAL_TO_STRING(name))
			+ TableSize("function () {\n\t[native code]\n}\n")
			- 1 + 1;
	jsString_t result = (jsString_t) JS_NewZoneObj(cx,
						sizeof (*result) * length);

	sprintf(result, "function %s() {\n\t[native code]\n}\n",
		JS_VAL_TO_STRING(name));
	return result;
    }

    // Spew tokens in source, for debugging.
    // as TYPE number char
    if (printSource) {
	printf("length: %d\n", sourceLength);

        for (int i = 0; i < sourceLength; i++) {
            // Note that tokenToName will fail unless Context.printTrees
            // is true.
            jsString_t tokenname = JSTokenStream::tokenToName(source[i]);

            if (tokenname == NULL) {
                tokenname = "---";
	    }
            jsString_t pad = strlen(tokenname) > 7
						? (jsString_t) "\t"
						: (jsString_t) "\t\t";
 	    jsString_t s = JS_Substring(cx, source, i, i + 1);
            jsString_t escapeString =  JSScriptRuntime::escapeString(cx, s);

	    printf("%s%s%d\t'%s'\n", tokenname, pad, (int) (source[i] & 0xff),
		escapeString);
	    JS_DeleteZoneObj(cx, escapeString);
	    JS_DeleteZoneObj(cx, s);
        }
        printf("\n");
    }

    JSStringBuffer result(cx);

    int i = 0;

    if (sourceLength > 0) {
        /* special-case FUNCTION as the first token; if it is,
         * (and it's not followed by a NAME or LP) then we're
         * decompiling a function (and not the toplevel script.)

         * FUNCTION appearing elsewhere is an escape that means we'll
         * need to call toString of the given function (object).

         * If not at the top level, don't add an initial indent;
         * let the caller do it, so functions as expressions look
         * reasonable.  */

        if (toplevel) {
            // add an initial newline to exactly match js.
            if (!justbody) {
                result.append('\n');
	    }
	    for (int j = 0; j < indent; j++) {
		result.append(' ');
	    }
        }

        if (source[0] == JSTokenStream::FUNCTION
            // make sure it's not a script that begins with a
            // reference to a function definition.
            && sourceLength > 1
            && (source[1] == JSTokenStream::NAME
                || source[1] == JSTokenStream::LP)) {
            if (!justbody) {
                result.append("function ");

                /* version != 1.2 Function constructor behavior - if
                 * there's no function name in the source info, and
                 * the names[0] entry is the empty string, then it must
                 * have been created by the Function constructor;
                 * print 'anonymous' as the function name if the
                 * version (under which the function was compiled) is
                 * less than 1.2... or if it's greater than 1.2, because
                 * we need to be closer to ECMA.  (ToSource, please?)
                 */
                if (nextIs(i, JSTokenStream::LP)
                    && this->version != JSContext::VERSION_1_2
                    && this->names != NULL 
                    && strcmp(this->names[0], "anonymous") == 0) {
                    result.append("anonymous");
		}
                i++;
            } else {
                /* Skip past the entire function header to the next EOL.
                 * Depends on how NAMEs are encoded.
                 */
                while (i < sourceLength
                       && (source[i] != JSTokenStream::EOL
                           // the length char of a NAME sequence
                           // can look like an EOL.
                           || (i > 0
                               && source[i - 1] == JSTokenStream::NAME))) {
                    i++;
                }
                // Skip past the EOL, too.
                i++;
            }
        }
    }

    while (i < sourceLength) {
        int stop;

        switch ((int) (source[i] & 0xff)) {
        case JSTokenStream::NAME:
        case JSTokenStream::OBJECT: { // re-wrapped in '/'s in parser...
            /* NAMEs are encoded as NAME, (char) length, string...
             * Note that lookahead for detecting labels depends on
             * this encoding; change there if this changes.

             * Also change function-header skipping code above,
             * used when decompling under decompileFunctionBody.
             */
            i++;
            stop = i + (int) source[i];
	    jsString_t s = JS_Substring(cx, source, i + 1, stop + 1);

            result.append(s);
	    JS_DeleteZoneObj(cx, s);
            i = stop;
            break;
	}

        case JSTokenStream::NUMBER: {
            i++;
            int64_t lbits = 0;
            switch ((int) (source[i] & 0xff)) {
            case 'S':
                i++;
                result.append((int) source[i]);
                break;

            case 'J':
                i++;
                lbits |= (int64_t) (source[i++] & 0xff) << 56;
                lbits |= (int64_t) (source[i++] & 0xff) << 48;
                lbits |= (int64_t) (source[i++] & 0xff) << 40;
                lbits |= (int64_t) (source[i++] & 0xff) << 32;
                lbits |= (int64_t) (source[i++] & 0xff) << 24;
                lbits |= (int64_t) (source[i++] & 0xff) << 16;
                lbits |= (int64_t) (source[i++] & 0xff) << 8;
                lbits |= (int64_t) (source[i] & 0xff);

                result.append(lbits);
                break;
            case 'D': {
                i++;

                lbits |= (int64_t) (source[i++] & 0xff) << 56;
                lbits |= (int64_t) (source[i++] & 0xff) << 48;
                lbits |= (int64_t) (source[i++] & 0xff) << 40;
                lbits |= (int64_t) (source[i++] & 0xff) << 32;
                lbits |= (int64_t) (source[i++] & 0xff) << 24;
                lbits |= (int64_t) (source[i++] & 0xff) << 16;
                lbits |= (int64_t) (source[i++] & 0xff) << 8;
                lbits |= (int64_t) (source[i] & 0xff);

                double dval = longBitsToDouble(lbits);
		jsString_t s = JSScriptRuntime::numberToString(cx, dval, 10);

                result.append(s);
		JS_DeleteZoneObj(cx, s);
                break;
	    }
            }
            break;
	}

        case JSTokenStream::STRING: {
            i++;
            stop = i + (int) (source[i] & 0xff);
            result.append('"');

	    jsString_t s = JS_Substring(cx, (jsString_t) source, i + 1,
					stop + 1);
	    jsString_t escapeString = JSScriptRuntime::escapeString(cx, s);

            result.append(escapeString);
	    JS_DeleteZoneObj(cx, escapeString);
	    JS_DeleteZoneObj(cx, s);

            result.append('"');
            i = stop;
            break;
 	}

        case JSTokenStream::PRIMARY:
            i++;
            switch ((int) (source[i] & 0xff)) {
            case JSTokenStream::_TRUE:
                result.append("true");
                break;

            case JSTokenStream::_FALSE:
                result.append("false");
                break;

#if defined(CYGWIN)
            case JSTokenStream::__NULL:
#else /* !defined(CYGWIN) */
            case JSTokenStream::_NULL:
#endif /* !defined(CYGWIN) */
                result.append("null");
                break;

            case JSTokenStream::THIS:
                result.append("this");
                break;

            case JSTokenStream::TYPEOF:
                result.append("typeof");
                break;

            case JSTokenStream::VOID:
                result.append("void");
                break;

            case JSTokenStream::UNDEFINED:
                result.append("undefined");
                break;
            }
            break;

        case JSTokenStream::FUNCTION: {
            /* decompile a FUNCTION token as an escape; call
             * toString on the nth enclosed nested function,
             * where n is given by the byte that follows.
             */

            i++;
            int functionNumber = source[i];

            if (nestedFunctions == NULL
                || functionNumber > nestedFunctionsLength) {
                jsString_t message;

                if (names != NULL && namesLength > 0 && strlen(names[0]) > 0) {
                    message = JSContext::getMessage(cx, 
					"msg.no.function.ref.found.in %d %s",
					source[i], names[0]);
                } else {
                    message = JSContext::getMessage (cx,
					"msg.no.function.ref.found %d",
					source[i]);
                }
                cx->exceptionHandler->throwEx(
                        cx,
                        JSContext::reportRuntimeError(cx, message));
            }
	    jsString_t s = nestedFunctions[functionNumber]
				->decompile(cx, indent, FALSE, FALSE);

            result.append(s);
	    JS_DeleteZoneObj(cx, s);
            break;
        }
        case JSTokenStream::COMMA:
            result.append(", ");
            break;

        case JSTokenStream::LC:
            if (nextIs(i, JSTokenStream::EOL)) {
                indent += OFFSET;
	    }
            result.append("{");
            break;

        case JSTokenStream::RC:
            /* don't print the closing RC if it closes the
             * toplevel function and we're called from
             * decompileFunctionBody.
             */
            if (justbody && toplevel && i + 1 == sourceLength) {
                break;
	    }

            if (nextIs(i, JSTokenStream::EOL)) {
                indent -= OFFSET;
	    }
            if (nextIs(i, JSTokenStream::WHILE)
                || nextIs(i, JSTokenStream::ELSE)) {
                indent -= OFFSET;
                result.append("} ");
            } else {
                result.append('}');
	    }
            break;

        case JSTokenStream::LP:
            result.append('(');
            break;

        case JSTokenStream::RP:
            if (nextIs(i, JSTokenStream::LC)) {
                result.append(") ");
            } else {
                result.append(')');
	    }
            break;

        case JSTokenStream::LB:
            result.append('[');
            break;

        case JSTokenStream::RB:
            result.append(']');
            break;

        case JSTokenStream::EOL:
            result.append('\n');

            /* add indent if any tokens remain,
             * less setback if next token is
             * a label, case or default.
             */
            if (i + 1 < sourceLength) {
                int less = 0;

                if (nextIs(i, JSTokenStream::CASE)
                    || nextIs(i, JSTokenStream::DEFAULT)) {
                    less = SETBACK;
                } else if (nextIs(i, JSTokenStream::RC)) {
                    less = OFFSET;

                /* elaborate check against label... skip past a
                 * following inlined NAME and look for a COLON.
                 * Depends on how NAME is encoded.
                 */
		} else if (nextIs(i, JSTokenStream::NAME)) {
                    int skip = source[i + 2];

                    if (source[i + skip + 3] == JSTokenStream::COLON) {
                        less = OFFSET;
		    }
                }

                for (; less < indent; less++) {
                    result.append(' ');
		}
            }
            break;

        case JSTokenStream::DOT:
            result.append('.');
            break;

        case JSTokenStream::NEW:
            result.append("new ");
            break;

        case JSTokenStream::DELPROP:
            result.append("delete ");
            break;

        case JSTokenStream::IF:
            result.append("if ");
            break;

        case JSTokenStream::ELSE:
            result.append("else ");
            break;

        case JSTokenStream::FOR:
            result.append("for ");
            break;

        case JSTokenStream::IN:
            result.append(" in ");
            break;

        case JSTokenStream::WITH:
            result.append("with ");
            break;

        case JSTokenStream::WHILE:
            result.append("while ");
            break;

        case JSTokenStream::DO:
            result.append("do ");
            break;

        case JSTokenStream::TRY:
            result.append("try ");
            break;

        case JSTokenStream::CATCH:
            result.append("catch ");
            break;

        case JSTokenStream::FINALLY:
            result.append("finally ");
            break;

        case JSTokenStream::THROW:
            result.append("throw ");
            break;

        case JSTokenStream::SWITCH:
            result.append("switch ");
            break;

        case JSTokenStream::BREAK:
            if (nextIs(i, JSTokenStream::NAME)) {
                result.append("break ");
            } else {
                result.append("break");
	    }
            break;

        case JSTokenStream::CONTINUE:
            if (nextIs(i, JSTokenStream::NAME)) {
                result.append("continue ");
            } else {
                result.append("continue");
	    }
            break;

        case JSTokenStream::CASE:
            result.append("case ");
            break;

        case JSTokenStream::DEFAULT:
            result.append("default");
            break;

        case JSTokenStream::RETURN:
            if (nextIs(i, JSTokenStream::SEMI)) {
                result.append("return");
            } else {
                result.append("return ");
	    }
            break;

        case JSTokenStream::VAR:
            result.append("var ");
            break;

        case JSTokenStream::SEMI:
            if (nextIs(i, JSTokenStream::EOL)) {
                // statement termination
                result.append(";");
            } else {
                // separators in FOR
                result.append("; ");
	    }
            break;

        case JSTokenStream::ASSIGN:
            i++;
            switch ((int) (source[i] & 0xff)) {
            case JSTokenStream::NOP:
                result.append(" = ");
                break;

            case JSTokenStream::ADD:
                result.append(" += ");
                break;

            case JSTokenStream::SUB:
                result.append(" -= ");
                break;

            case JSTokenStream::MUL:
                result.append(" *= ");
                break;

            case JSTokenStream::DIV:
                result.append(" /= ");
                break;

            case JSTokenStream::MOD:
                result.append(" %= ");
                break;

            case JSTokenStream::BITOR:
                result.append(" |= ");
                break;

            case JSTokenStream::BITXOR:
                result.append(" ^= ");
                break;

            case JSTokenStream::BITAND:
                result.append(" &= ");
                break;

            case JSTokenStream::LSH:
                result.append(" <<= ");
                break;

            case JSTokenStream::RSH:
                result.append(" >>= ");
                break;

            case JSTokenStream::URSH:
                result.append(" >>>= ");
                break;
            }
            break;

        case JSTokenStream::HOOK:
            result.append(" ? ");
            break;

        case JSTokenStream::OBJLIT:
            // pun OBJLIT to mean colon in objlit property initialization.
            // this needs to be distinct from COLON in the general case
            // to distinguish from the colon in a ternary... which needs
            // different spacing.
            result.append(':');
            break;

        case JSTokenStream::COLON:
            if (nextIs(i, JSTokenStream::EOL)) {
                // it's the end of a label
                result.append(":");
            } else {
                // it's the middle part of a ternary
                result.append(" : ");
	    }
            break;

        case JSTokenStream::OR:
            result.append(" || ");
            break;

        case JSTokenStream::AND:
            result.append(" && ");
            break;

        case JSTokenStream::BITOR:
            result.append(" | ");
            break;

        case JSTokenStream::BITXOR:
            result.append(" ^ ");
            break;

        case JSTokenStream::BITAND:
            result.append(" & ");
            break;

        case JSTokenStream::EQOP:
            i++;
            switch ((int) (source[i] & 0xff)) {
            case JSTokenStream::SHEQ:
                /*
                 * Emulate the C engine; if we're under version
                 * 1.2, then the == operator behaves like the ===
                 * operator (and the source is generated by
                 * decompiling a === opcode), so print the ===
                 * operator as ==.
                 */
                result.append(this->version == JSContext::VERSION_1_2 ?
				(jsString_t) " == "
                              : (jsString_t) " === ");
                break;

            case JSTokenStream::SHNE:
                result.append(this->version == JSContext::VERSION_1_2 ?
				(jsString_t) " != "
                              : (jsString_t) " !== ");
                break;

            case JSTokenStream::EQ:
                result.append(" == ");
                break;

            case JSTokenStream::NE:
                result.append(" != ");
                break;
            }
            break;

        case JSTokenStream::RELOP:
            i++;
            switch ((int) (source[i] & 0xff)) {
            case JSTokenStream::LE:
                result.append(" <= ");
                break;

            case JSTokenStream::LT:
                result.append(" < ");
                break;

            case JSTokenStream::GE:
                result.append(" >= ");
                break;

            case JSTokenStream::GT:
                result.append(" > ");
                break;

            case JSTokenStream::INSTANCEOF:
                result.append(" instanceof ");
                break;
            }
            break;

        case JSTokenStream::SHOP:
            i++;
            switch ((int) (source[i] & 0xff)) {
            case JSTokenStream::LSH:
                result.append(" << ");
                break;

            case JSTokenStream::RSH:
                result.append(" >> ");
                break;

            case JSTokenStream::URSH:
                result.append(" >>> ");
                break;
            }
            break;

        case JSTokenStream::UNARYOP:
            i++;
            switch ((int) (source[i] & 0xff)) {
            case JSTokenStream::TYPEOF:
                result.append("typeof ");
                break;

            case JSTokenStream::VOID:
                result.append("void ");
                break;

            case JSTokenStream::NOT:
                result.append('!');
                break;

            case JSTokenStream::BITNOT:
                result.append('~');
                break;

            case JSTokenStream::ADD:
                result.append('+');
                break;

            case JSTokenStream::SUB:
                result.append('-');
                break;
            }
            break;

        case JSTokenStream::INC:
            result.append("++");
            break;

        case JSTokenStream::DEC:
            result.append("--");
            break;

        case JSTokenStream::ADD:
            result.append(" + ");
            break;

        case JSTokenStream::SUB:
            result.append(" - ");
            break;

        case JSTokenStream::MUL:
            result.append(" * ");
            break;

        case JSTokenStream::DIV:
            result.append(" / ");
            break;

        case JSTokenStream::MOD:
            result.append(" % ");
            break;

        default: 
            // If we don't know how to decompile it, raise an exception.
	    jsChar_t buf[32];

	    sprintf(buf, "Unknown token %d", source[i]);
            cx->exceptionHandler->throwEx(cx, new (cx) JSRuntimeException(buf));
        }
        i++;
    }

    // add that trailing newline if it's an outermost function.
    if (toplevel && !justbody) {
        result.append('\n');
    }

    return result.offString();
}
JSScriptable  *
JSNativeFunction::getClassPrototype(JSContext *cx)
{
    jsVal_t protoVal;

    (void) get(cx, "prototype", this, &protoVal);
    if (protoVal == JS_VAL_NULL
        || !JS_VAL_IS_OBJECT(protoVal)
        || (protoVal == JS_VAL_VOID)) {
        return JSScriptableObject::getClassPrototype(cx, this, "Object");
    }

    return JS_VAL_TO_OBJECT(protoVal);
}
jsBool_t
JSNativeFunction::isInstanceOf(const jsString_t className)
{
    if (className == JSNativeFunction::className) return TRUE;
    return JSScriptableObject::isInstanceOf(className);
}
jsString_t
JSNativeFunction::getClassName(JSContext *cx)
{
   return className;
}
jsBool_t
JSNativeFunction::has(JSContext *cx, jsString_t name, JSScriptable *start)
{
    return (
	strcmp(name, "prototype") == 0 || strcmp(name, "arguments") == 0 ||
        JSScriptableObject::has(cx, name, start)
    ) ;
}
jsBool_t
JSNativeFunction::get(JSContext *cx, jsString_t name, JSScriptable *start,
		jsVal_t *value)
{
    if (JSScriptableObject::get(cx, name, start, value)) {
	return TRUE;
    }
    if (strcmp(name, "prototype") == 0) {
        JSNativeObject *obj = new (cx) JSNativeObject();
        const int attr = JS_PA_DontEnum | JS_PA_ReadOnly | JS_PA_Permanent;

        obj->defineProperty(cx, "constructor", JS_OBJECT_TO_VAL(this), attr);

        put(cx, name, this, JS_OBJECT_TO_VAL(obj));

	JSScriptable *proto = getObjectPrototype(cx, this);
        
        if (proto != obj) {
	    // not the one we just made, it must remain grounded
            obj->setPrototype(cx, proto);
	}
	*value = JS_OBJECT_TO_VAL(obj);
        return TRUE;
    }
    if (strcmp(name, "arguments") == 0) {
	JSNativeCall *activation = getActivation(cx);

	if (activation == NULL) {
	    *value = JS_VAL_NULL;
	    return TRUE;
	} else {
	    return activation->get(cx, "arguments", activation, value);
	}
    }
    return FALSE;
}
jsBool_t
JSNativeFunction::hasInstance(JSContext *cx, JSScriptable *instance)
{
    JSFlattenedObject flat(this);
    jsVal_t protoProp;
    double s[2];		// use double to align string

    strcpy((jsChar_t *) s, "prototype");
    protoProp = flat.getProperty(cx, JS_STRING_TO_VAL(s));
    if (JS_VAL_IS_OBJECT(protoProp) && protoProp != JS_VAL_NULL) {
        return JSScriptRuntime::jsDelegatesTo(cx, instance,
					JS_VAL_TO_OBJECT(protoProp));
    }
    cx->exceptionHandler->throwEx(
            cx,
            JSNativeGlobal::constructError(cx, "TypeError",
			JSScriptRuntime::getMessage(cx, 
				"msg.instanceof.bad.prototype %d", names[0]),
				instance));
    /* NOT REACHED */
    return FALSE;
}
jsVal_t
JSNativeFunction::call(JSContext *cx, JSScriptable *scope,
			JSScriptable *thisObj, int numArgs, jsVal_t *args)
{
   return JS_VAL_VOID;
}
JSScriptable *
JSNativeFunction::construct(JSContext *cx, JSScriptable *scope,
			int numArgs, jsVal_t *args)
{
    JSScriptable *newInstance = new (cx) JSNativeObject();
    cx->pushLocalVal(JS_OBJECT_TO_VAL(newInstance)); // gc safe 20061124

    newInstance->setPrototype(cx, getClassPrototype(cx));
    newInstance->setParentScope(cx, getParentScope(cx));

    jsVal_t val = call(cx, scope, newInstance, numArgs, args);

    if (JS_VAL_IS_OBJECT(val) && val != JS_VAL_NULL) {
        cx->popLocalVals(1); // gc safe 20061124
        return JS_VAL_TO_OBJECT(val);
    }
    cx->popLocalVals(1); // gc safe 20061124
    return newInstance;
}
void
JSNativeFunction::mark(JSContext *cx)
{
    JSGCHeap *gcHeap = &cx->runtime->gcHeap;

    if (gcHeap->isMarkObj(this)) return;

    JSScriptableObject::mark(cx);
    if (names != 0) {
	gcHeap->markObj(names);
	for (int i = 0; i < namesLength; ++i) {
	    if (names[i] != NULL) {
	        gcHeap->markObj(names[i]);
	    }
	}
    }
    if (source != NULL) {
	gcHeap->markObj(source);
    }
    if (nestedFunctionsLength > 0) {
	gcHeap->markObj(nestedFunctions);
    }
    for (int i = 0; i < nestedFunctionsLength; ++i) {
	nestedFunctions[i]->mark(cx);
    }
}

JSNativeFunction::JSNativeFunction()
{
    namesLength = 0;
    names = NULL;
    argCount = 0;
    version = 0;
    sourceLength = 0;
    source = NULL;
    nestedFunctionsLength = 0;
    nestedFunctions = NULL;
}

