Milán Major

netscape-revival

1 branch
Code

mocha/src/mo_obj.c

/*
** Mocha user object operations.
**
** Brendan Eich, 11/15/95
*/
#include <string.h>
#include "prlog.h"
#include "prprf.h"
#include "alloca.h"
#include "mo_atom.h"
#include "mo_cntxt.h"
#include "mo_emit.h"
#include "mo_scope.h"
#include "mocha.h"
#include "mochaapi.h"
#include "mochalib.h"

MochaBoolean
mocha_DatumToObject(MochaContext *mc, MochaDatum d, MochaObject **objp)
{
    MochaDatum aval;
    MochaObject *obj;
    MochaAtom *atom;

    aval = d;
    if (!mocha_ResolveValue(mc, &d))
	return MOCHA_FALSE;
    switch (d.tag) {
      case MOCHA_UNDEF:
	obj = 0;	/* XXX backwards compat for undef property tests */
	break;
      case MOCHA_FUNCTION:
	obj = mocha_FunctionToObject(mc, d.u.fun);
	break;
      case MOCHA_NUMBER:
	obj = mocha_NumberToObject(mc, d.u.fval);
	break;
      case MOCHA_BOOLEAN:
	obj = mocha_BooleanToObject(mc, d.u.bval);
	break;
      case MOCHA_STRING:
	obj = mocha_StringToObject(mc, d.u.atom);
	break;
      case MOCHA_OBJECT:
	obj = d.u.obj;
	if (!obj)
	    break;
	if (!OBJ_CONVERT(mc, obj, MOCHA_OBJECT, &d))
	    return MOCHA_FALSE;
	if (d.tag == MOCHA_OBJECT) {
	    obj = d.u.obj;
	    MOCHA_MIX_TAINT(mc, mc->taintInfo->accum, d.taint);
	    break;
	}
	/* FALL THROUGH */
      default:
	if (mocha_RawDatumToString(mc, aval, &atom)) {
	    MOCHA_ReportError(mc, "%s is not an object", atom_name(atom));
	    mocha_DropAtom(mc, atom);
	}
	return MOCHA_FALSE;
    }
    *objp = MOCHA_HoldObject(mc, obj);
    return MOCHA_TRUE;
}

MochaBoolean
mocha_RawObjectToString(MochaContext *mc, MochaObject *obj, MochaAtom **atomp)
{
    const char *name;
    char *str;
    size_t size;
    MochaAtom *atom;
    
    name = obj->clazz->name;
    size = strlen(name) + 10;
    str = (char *)alloca(size);
    PR_snprintf(str, size, "[object %s]", name);
    atom = mocha_Atomize(mc, str, ATOM_HELD | ATOM_STRING);
    if (!atom)
	return MOCHA_FALSE;
    *atomp = atom;
    return MOCHA_TRUE;
}

MochaBoolean
mocha_ObjectToString(MochaContext *mc, MochaObject *obj, MochaAtom **atomp)
{
    MochaDatum rval;

    if (!obj) {
	*atomp = mocha_HoldAtom(mc, mocha_nullAtom);
	return MOCHA_TRUE;
    }

    rval = MOCHA_void;
    if (!OBJ_CONVERT(mc, obj, MOCHA_STRING, &rval))
	return MOCHA_FALSE;
    if (rval.tag == MOCHA_STRING) {
	*atomp = mocha_HoldAtom(mc, rval.u.atom);
	return MOCHA_TRUE;
    }

    /* Try the toString method, if it's defined. */
    if (!mocha_TryMethod(mc, obj, mocha_toStringAtom, 0, 0, &rval))
	return MOCHA_FALSE;
    if (rval.tag != MOCHA_STRING) {
	/* Try valueOf method, see if that returns a string. */
	mocha_DropRef(mc, &rval);
	if (!mocha_TryMethod(mc, obj, mocha_valueOfAtom, 0, 0, &rval))
	    return MOCHA_FALSE;
    }
    if (rval.tag == MOCHA_STRING) {
	/* XXX don't steal rval's atom ref -- it may hold taint. */
	*atomp = mocha_HoldAtom(mc, rval.u.atom);
	MOCHA_MIX_TAINT(mc, mc->taintInfo->accum, rval.taint);
	mocha_DropRef(mc, &rval);
	return MOCHA_TRUE;
    }

    mocha_DropRef(mc, &rval);
    return mocha_RawObjectToString(mc, obj, atomp);
}

MochaBoolean
mocha_TryMethod(MochaContext *mc, MochaObject *obj, MochaAtom *atom,
		unsigned argc, MochaDatum *argv, MochaDatum *rval)
{
    MochaPair pair;
    MochaDatum fd;
    MochaErrorReporter older;

    if (!mocha_LookupSymbol(mc, obj->scope, atom, MLF_GET, &pair.sym))
	return MOCHA_FALSE;
    if (!pair.sym) {
	*rval = MOCHA_void;
	return MOCHA_TRUE;
    }
    pair.obj = obj;
    MOCHA_INIT_FULL_DATUM(mc, &fd, MOCHA_SYMBOL, 0, MOCHA_TAINT_IDENTITY,
			  u.pair, pair);
    older = MOCHA_SetErrorReporter(mc, 0);
    if (!mocha_Call(mc, fd, argc, argv, rval))
	*rval = MOCHA_void;
    MOCHA_SetErrorReporter(mc, older);
    return MOCHA_TRUE;
}

MochaClass mocha_ObjectClass = {
    "Object",
    MOCHA_PropertyStub, MOCHA_PropertyStub, MOCHA_ListPropStub,
    MOCHA_ResolveStub, MOCHA_ConvertStub, MOCHA_FinalizeStub
};

static MochaBoolean
obj_to_string(MochaContext *mc, MochaObject *obj,
	      unsigned argc, MochaDatum *argv, MochaDatum *rval)
{
    MochaAtom *atom;

    if (!mocha_RawObjectToString(mc, obj, &atom))
	return MOCHA_FALSE;
    MOCHA_INIT_DATUM(mc, rval, MOCHA_STRING, u.atom, atom);
    MOCHA_WeakenRef(mc, rval);
    return MOCHA_TRUE;
}

static MochaBoolean
obj_value_of(MochaContext *mc, MochaObject *obj,
	     unsigned argc, MochaDatum *argv, MochaDatum *rval)
{
    MOCHA_INIT_DATUM(mc, rval, MOCHA_OBJECT, u.obj, obj);
    return MOCHA_TRUE;
}

static MochaBoolean
obj_eval(MochaContext *mc, MochaObject *obj,
	 unsigned argc, MochaDatum *argv, MochaDatum *rval)
{
    MochaBoolean no_parent;
    MochaAtom *atom;
    char *filename;
    unsigned lineno;
    MochaBoolean ok;

    if (argv[0].tag != MOCHA_STRING) {
	*rval = argv[0];
	return MOCHA_TRUE;
    }
    no_parent = (obj->parent == 0 && mc->staticLink != obj);
    if (no_parent)
	obj->parent = mc->staticLink;
    atom = argv[0].u.atom;
    filename = mc->script->filename;
    lineno = mocha_PCtoLineNumber(mc->script, mc->pc);
    ok = MOCHA_EvaluateBuffer(mc, obj, atom_name(atom), atom->length,
			      filename, lineno, rval);
    if (ok)
	MOCHA_WeakenRef(mc, rval);
    if (no_parent)
	obj->parent = 0;
    return ok;
}

static MochaFunctionSpec object_methods[] = {
    {"toString",	obj_to_string,		0},
    {"valueOf",		obj_value_of,		0},
    {"eval",            obj_eval,               1},
    {0}
};

static MochaBoolean
Object(MochaContext *mc, MochaObject *obj,
       unsigned argc, MochaDatum *argv, MochaDatum *rval)
{
    if (argc != 0) {
	if (!mocha_DatumToObject(mc, argv[0], &obj))
	    return MOCHA_FALSE;
	if (!obj)
	    return MOCHA_TRUE;
	obj->nrefs--;
    }
    MOCHA_INIT_DATUM(mc, rval, MOCHA_OBJECT, u.obj, obj);
    return MOCHA_TRUE;
}

MochaObject *
mocha_InitObjectClass(MochaContext *mc, MochaObject *obj)
{
    return MOCHA_InitClass(mc, obj, &mocha_ObjectClass, 0, Object, 0,
			   0, object_methods, 0, 0);
}

MochaBoolean
mocha_GetPrototype(MochaContext *mc, MochaClass *clazz, MochaObject **objp)
{
    MochaAtom *atom;
    MochaObject *prototype;
    MochaSymbol *sym;
    MochaBoolean ok;
    MochaFunction *fun;
    MochaDatum *dp;

    atom = mocha_Atomize(mc, clazz->name, ATOM_HELD | ATOM_NAME);
    if (!atom)
	return MOCHA_FALSE;

    /* XXX mc->globalObject vs. top level scope found from mc->staticLink. */
    prototype = 0;
    ok = mocha_LookupSymbol(mc, mc->globalObject->scope, atom, MLF_GET, &sym);
    if (ok && sym &&
	sym->type == SYM_PROPERTY &&
	sym_property(sym)->datum.tag == MOCHA_FUNCTION) {
	fun = sym_property(sym)->datum.u.fun;
	ok = mocha_LookupSymbol(mc, fun->object.scope, mocha_prototypeAtom,
				MLF_GET, &sym);
	if (ok && sym && sym->type == SYM_PROPERTY) {
	    dp = &sym_property(sym)->datum;
	    if (dp->tag == MOCHA_OBJECT)
		prototype = dp->u.obj;
	}
    }

    mocha_DropAtom(mc, atom);
    *objp = prototype;
    return ok;
}

MochaBoolean
mocha_SetPrototype(MochaContext *mc, MochaFunction *fun, MochaObject *obj)
{
    MochaDatum d;

    /* Define a property for the prototype in the function's scope. */
    if (!mocha_GetMutableScope(mc, &fun->object))
	return MOCHA_FALSE;
    MOCHA_INIT_FULL_DATUM(mc, &d, MOCHA_OBJECT, 0, MOCHA_TAINT_IDENTITY,
			  u.obj, obj);
    if (!mocha_SetProperty(mc, fun->object.scope, mocha_prototypeAtom,
			   fun->object.scope->minslot-1, d)) {
	return MOCHA_FALSE;
    }

    /* Define a property for the constructor in the prototype's scope. */
    if (!mocha_GetMutableScope(mc, obj))
	return MOCHA_FALSE;
    MOCHA_INIT_FULL_DATUM(mc, &d, MOCHA_FUNCTION,
			  MDF_BACKEDGE | MDF_READONLY, MOCHA_TAINT_IDENTITY,
			  u.fun, fun);
    if (!mocha_SetProperty(mc, obj->scope, mocha_constructorAtom,
			   obj->scope->minslot-1, d)) {
	return MOCHA_FALSE;
    }
    return MOCHA_TRUE;
}

MochaBoolean
mocha_GetMutableScope(MochaContext *mc, MochaObject *obj)
{
    MochaScope *scope, *newscope;

    scope = obj->scope;
    if (scope->object == obj)
	return MOCHA_TRUE;
    newscope = mocha_NewScope(mc, obj);
    if (!newscope)
	return MOCHA_FALSE;
    newscope->minslot = scope->minslot;
    newscope->freeslot = scope->freeslot;
    obj->scope = mocha_HoldScope(mc, newscope);
    mocha_DropScope(mc, scope);
    return MOCHA_TRUE;
}

MochaBoolean
mocha_InitObject(MochaContext *mc, MochaObject *obj, MochaClass *clazz,
		 void *data, MochaObject *prototype, MochaObject *parent)
{
    MochaScope *scope;

    /* Get clazz's prototype from its constructor.prototype property. */
    if (!prototype && mc->globalObject) {
	if (!mocha_GetPrototype(mc, clazz, &prototype))
	    return MOCHA_FALSE;
	if (!prototype &&
	    !mocha_GetPrototype(mc, &mocha_ObjectClass, &prototype)) {
	    return MOCHA_FALSE;
	}
    }

    /* Share the given prototype's scope (create it if necessary). */
    if (prototype) {
	scope = prototype->scope;
    } else {
	scope = mocha_NewScope(mc, obj);
	if (!scope)
	    return MOCHA_FALSE;
    }

    obj->nrefs = 0;
    obj->clazz = clazz;
    obj->data = data;
    obj->scope = mocha_HoldScope(mc, scope);
    obj->prototype = MOCHA_HoldObject(mc, prototype);
    obj->parent = parent;
    return MOCHA_TRUE;
}

void
mocha_FreeObject(MochaContext *mc, MochaObject *obj)
{
    MochaScope *scope;

    /* Set obj->nrefs to a magic value that can't be incremented. */
    PR_ASSERT(obj->nrefs == 0);
    obj->nrefs = MOCHA_FINALIZING;

    /* Drop obj->scope first, in case kid finalizers use this obj->data. */
    scope = obj->scope;
    if (scope->object == obj)
	scope->object = 0;
    obj->scope = 0;
    mocha_DropScope(mc, scope);

    /* Finalize obj, which must be unreferenced, and clear obj->data. */
    OBJ_FINALIZE(mc, obj);
    obj->data = 0;

    /* Drop our prototype object reference and clear parent. */
    MOCHA_DropObject(mc, obj->prototype);
    obj->prototype = 0;
    obj->parent = 0;

    /* Sanity: clear obj->nrefs (XXX mark it MOCHA_FINALIZED instead?). */
    PR_ASSERT(obj->nrefs == MOCHA_FINALIZING);
    obj->nrefs = 0;
}

MochaObject *
mocha_NewObject(MochaContext *mc, MochaClass *clazz, void *data,
		MochaObject *prototype, MochaObject *parent)
{
    MochaObject *obj;

    obj = MOCHA_malloc(mc, sizeof *obj);
    if (!obj)
	return 0;
    if (!mocha_InitObject(mc, obj, clazz, data, prototype, parent)) {
	MOCHA_free(mc, obj);
	return 0;
    }
    return obj;
}

void
mocha_DestroyObject(MochaContext *mc, MochaObject *obj)
{
    mocha_FreeObject(mc, obj);
    MOCHA_free(mc, obj);
}

MochaObject *
mocha_NewObjectByClass(MochaContext *mc, MochaClass *clazz)
{
    return mocha_NewObject(mc, clazz, 0, 0, 0);
}

MochaObject *
mocha_NewObjectByPrototype(MochaContext *mc, MochaObject *prototype)
{
    return mocha_NewObject(mc, prototype->clazz, 0, prototype, 0);
}