Milán Major

netscape-revival

1 branch
Code

mocha/src/mo_scope.c

/*
** Mocha symbol tables.
**
** Brendan Eich, 6/20/95
*/
#include <memory.h>
#include <stdio.h>
#include <string.h>
#include "prhash.h"
#include "prlog.h"
#include "prmem.h"
#include "prprf.h"
#include "mo_atom.h"
#include "mo_cntxt.h"
#include "mo_scope.h"
#include "mocha.h"
#include "mochaapi.h"

/*
** MochaScope hash allocator ops.
*/
PR_STATIC_CALLBACK(void *)
AllocScopeSpace(void *pool, size_t size)
{
    return MOCHA_malloc(pool, size);
}

PR_STATIC_CALLBACK(void)
FreeScopeSpace(void *pool, void *item)
{
    MOCHA_free(pool, item);
}

PR_STATIC_CALLBACK(PRHashEntry *)
AllocSymbol(void *pool)
{
    MochaSymbol *sym;

    sym = MOCHA_malloc(pool, sizeof *sym);
    if (!sym)
	return 0;
    return &sym->entry;
}

PR_STATIC_CALLBACK(void)
FreeSymbol(void *pool, PRHashEntry *he, int flag)
{
    MochaContext *mc;
    MochaSymbol *sym, *lastsym, **sp;
    MochaDatum *vp;
    MochaProperty *prop;
    MochaScope *scope;
    MochaSlot slot;

    mc = pool;
    sym = (MochaSymbol *)he;
    vp = sym->entry.value;

    /* Robustify reference counting by using a signed type and <= 0. */
    if (vp) {
	PR_ASSERT(vp->nrefs > 0);
	if (vp->nrefs <= 0) return;
	if (--vp->nrefs == 0) {
	    switch (sym->type) {
	      case SYM_VARIABLE:
		mocha_DropRef(mc, vp);
		MOCHA_free(mc, vp);
		break;

	      case SYM_PROPERTY:
		prop = (MochaProperty *)vp;
		scope = sym->scope;
		slot = prop->slot;
		mocha_DropRef(mc, &prop->datum);
		PROP_UNLINK(scope, prop);
		MOCHA_free(mc, prop);

		/* Depending on slot's sign, reset freeslot or minslot. */
		if (slot >= 0) {
		    if (slot + 1 == scope->freeslot)
			scope->freeslot = slot;
		} else {
		    if (slot == scope->minslot)
			scope->minslot = slot + 1;
		}
		break;

	      default:;
	    }
	    vp = 0;
	}
	sym->entry.value = 0;
    }

    if (flag == HT_FREE_ENTRY) {
	mocha_DropAtom(mc, sym_atom(sym));
	if (vp && sym->type == SYM_PROPERTY) {
	    prop = (MochaProperty *)vp;
	    lastsym = 0;
	    for (sp = &prop->lastsym; *sp; sp = &(*sp)->next) {
		if (*sp == sym) {
		    *sp = sym->next;
		    if (!*sp) break;
		}
		lastsym = *sp;
	    }
	    prop->lastsym = lastsym;
	}
	MOCHA_free(mc, he);
    }
}

static PRHashAllocOps scopeHashAllocOps = {
    AllocScopeSpace, FreeScopeSpace,
    AllocSymbol, FreeSymbol
};

PR_STATIC_CALLBACK(PRHashNumber)
HashAtom(const void *key)
{
    const MochaAtom *atom = key;

    return atom->number;
}

PR_STATIC_CALLBACK(int)
ComparePointers(const void *ptr1, const void *ptr2)
{
    return ptr1 == ptr2;
}

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

    scope = PR_NEW(MochaScope);
    if (!scope) {
	MOCHA_ReportOutOfMemory(mc);
	return 0;
    }
    scope->nrefs = 0;
    scope->object = obj;
    scope->table = 0;
    scope->list = 0;
    scope->freeslot = scope->minslot = 0;
    scope->props = 0;
    scope->proptail = &scope->props;
    return scope;
}

void
mocha_DestroyScope(MochaContext *mc, MochaScope *scope)
{
    mocha_ClearScope(mc, scope);
    PR_DELETE(scope);
}

MochaScope *
mocha_HoldScope(MochaContext *mc, MochaScope *scope)
{
    PR_ASSERT(scope->nrefs >= 0);
    scope->nrefs++;
    return scope;
}

MochaScope *
mocha_DropScope(MochaContext *mc, MochaScope *scope)
{
    PR_ASSERT(scope->nrefs > 0);
    if (--scope->nrefs == 0) {
	mocha_DestroyScope(mc, scope);
	return 0;
    }
    return scope;
}

static MochaSymbol *
CopySymbol(MochaContext *mc, MochaSymbol *sym, MochaScope *to)
{
    MochaProperty *prop, *copy;

    if (sym->type == SYM_PROPERTY) {
	/* XXX avoid recursion through SetProperty/RawLookupSymbol */
	(void) mocha_DefineSymbol(mc, to, sym_atom(sym), SYM_UNDEF, 0);

	/* XXX kludge around incomplete parameterization */
	prop = sym_property(sym);
	sym = mocha_SetProperty(mc, to, sym_atom(sym), prop->slot, prop->datum);
	if (sym) {
	    copy = sym_property(sym);
	    copy->getter = prop->getter;
	    copy->setter = prop->setter;
	}
    } else {
	sym = mocha_DefineSymbol(mc, to, sym_atom(sym), sym->type,
				 sym->entry.value);
    }
    return sym;
}

#ifdef NOTUSED
typedef struct CopyArgs {
    MochaContext *context;
    MochaScope   *scope;
    MochaBoolean status;
} CopyArgs;

PR_STATIC_CALLBACK(int)
CopyHashEntry(PRHashEntry *he, int i, void *arg)
{
    MochaSymbol *sym = (MochaSymbol *)he;
    CopyArgs *ca = arg;
    MochaContext *mc = ca->context;
    MochaScope *to = ca->scope;

    if (!CopySymbol(mc, sym, to)) {
	ca->status = MOCHA_FALSE;
	return HT_ENUMERATE_STOP;
    }
    return HT_ENUMERATE_NEXT;
}

MochaBoolean
mocha_CopyScope(MochaContext *mc, MochaScope *from, MochaScope *to)
{
    CopyArgs ca;
    MochaSymbol *sym;

    ca.context = mc;
    ca.scope = to;
    ca.status = MOCHA_TRUE;
    if (from->table) {
	PR_HashTableEnumerateEntries(from->table, CopyHashEntry, &ca);
    } else {
	for (sym = from->list; sym; sym = (MochaSymbol *)sym->entry.next) {
	    if (CopyHashEntry(&sym->entry, 0, &ca) == HT_ENUMERATE_STOP)
		break;
	}
    }
    return ca.status;
}
#endif

void
mocha_ClearScope(MochaContext *mc, MochaScope *scope)
{
    MochaSymbol *sym, **sp;

    if (scope->table) {
	scope->table->allocPool = mc;
	PR_HashTableDestroy(scope->table);
	scope->table = 0;
    } else {
	sp = &scope->list;
	while ((sym = *sp) != 0) {
	    *sp = (MochaSymbol *)sym->entry.next;
	    FreeSymbol(mc, &sym->entry, HT_FREE_ENTRY);
	}
    }
}

MochaBoolean
RawLookupSymbol(MochaContext *mc, MochaScope *scope, PRHashNumber hash,
		const MochaAtom *atom, MochaLookupFlag flag,
		MochaSymbol **symp)
{
    MochaObject *obj;
    MochaScope *first;
    PRHashEntry **hep;
    MochaSymbol *sym, **sp;

    first = scope;
    obj = scope->object;
    for (;;) {
	if (scope->table) {
	    hep = PR_HashTableRawLookup(scope->table, hash, atom);
	    sym = (MochaSymbol *) *hep;
	    if (sym)
		goto out;
	} else {
	    for (sp = &scope->list; (sym = *sp) != 0;
		 sp = (MochaSymbol **)&sym->entry.next) {
		if (sym_atom(sym) == atom) {
		    /* Move sym to the front for shorter searches. */
		    *sp = (MochaSymbol *)sym->entry.next;
		    sym->entry.next = (PRHashEntry *)scope->list;
		    scope->list = sym;
		    goto out;
		}
	    }
	}
	obj = scope->object->prototype;
	if (!obj || obj->scope == scope)
	    break;
	scope = obj->scope;
    }

out:
    if (flag == MLF_SET && sym && sym->scope != first) {
	sym = CopySymbol(mc, sym, first);
	if (!sym)
	    return MOCHA_FALSE;
    }
    *symp = sym;
    return MOCHA_TRUE;
}

MochaBoolean
mocha_LookupSymbol(MochaContext *mc, MochaScope *scope, const MochaAtom *atom,
                   MochaLookupFlag flag, MochaSymbol **symp)
{
    PRHashNumber hash;

    hash = HashAtom(atom);
    return RawLookupSymbol(mc, scope, hash, atom, flag, symp);
}

MochaBoolean
mocha_SearchScopes(MochaContext *mc, const MochaAtom *atom,
		   MochaLookupFlag flag, MochaPair *pair)
{
    PRHashNumber hash;
    MochaObjectStack *stack;
    MochaScope *scope;
    MochaObject *slink;

    /* Try dynamic scopes nested by "with" statements at run time. */
    hash = HashAtom(atom);
    for (stack = mc->objectStack; stack; stack = stack->down) {
	scope = stack->object->scope;
	if (!RawLookupSymbol(mc, scope, hash, atom, flag, &pair->sym))
	    return MOCHA_FALSE;
	if (pair->sym) {
	    pair->obj = stack->object;
	    return MOCHA_TRUE;
	}
    }

    /* No luck -- try statically linked scopes. */
    for (slink = mc->staticLink; slink; slink = slink->parent) {
	scope = slink->scope;
	if (!RawLookupSymbol(mc, scope, hash, atom, flag, &pair->sym))
	    return MOCHA_FALSE;
	if (pair->sym) {
	    pair->obj = slink;
	    return MOCHA_TRUE;
	}
    }
    pair->obj = 0;
    pair->sym = 0;
    return MOCHA_TRUE;
}

#define HASH_THRESHOLD	10

MochaSymbol *
mocha_DefineSymbol(MochaContext *mc, MochaScope *scope, MochaAtom *atom,
		   MochaSymbolType type, void *value)
{
    MochaSlot nsyms;
    MochaSymbol *sym, *next;
    PRHashEntry **hep;

    if (!scope->table) {
	for (nsyms = 0, sym = scope->list; sym;
	     sym = (MochaSymbol *)sym->entry.next) {
	    if (sym_atom(sym) == atom)
		break;
	    nsyms++;
	}
	if (nsyms >= HASH_THRESHOLD) {
	    scope->table = PR_NewHashTable(nsyms, HashAtom,
					   ComparePointers, ComparePointers,
					   &scopeHashAllocOps, mc);
	    if (scope->table) {
		for (sym = scope->list; sym; sym = next) {
		    next = (MochaSymbol *)sym->entry.next;
		    sym->entry.keyHash = HashAtom(sym->entry.key);
		    sym->entry.next = 0;
		    hep = PR_HashTableRawLookup(scope->table,
						sym->entry.keyHash,
						sym->entry.key);
		    *hep = &sym->entry;
		}
		scope->list = 0;
	    }
	}
    }

    if (scope->table) {
	scope->table->allocPool = mc;
	sym = (MochaSymbol *)PR_HashTableAdd(scope->table, atom, value);
	if (!sym)
	    return 0;
	mocha_HoldAtom(mc, atom);
    } else {
	if (sym) {
	    FreeSymbol(mc, &sym->entry, HT_FREE_VALUE);
	} else {
	    sym = (MochaSymbol *)AllocSymbol(mc);
	    if (!sym)
		return 0;
	    /* Don't set sym->entry.keyHash until we know we need it. */
	    sym->entry.key = mocha_HoldAtom(mc, atom);
	    sym->entry.next = (PRHashEntry *)scope->list;
	    scope->list = sym;
	}
	sym->entry.value = value;
    }

    sym->scope = scope;
    sym->type = type;
    sym->slot = 0;
    sym->next = 0;

    if (value)
	(*(MochaRefCount *)value)++;
    return sym;
}

void
mocha_RemoveSymbol(MochaContext *mc, MochaScope *scope, MochaAtom *atom)
{
    MochaSymbol **sp, *sym;

    if (scope->table) {
	scope->table->allocPool = mc;
	PR_HashTableRemove(scope->table, atom);
    } else {
	for (sp = &scope->list; (sym = *sp) != 0;
	     sp = (MochaSymbol **)&sym->entry.next) {
	    if (sym_atom(sym) == atom) {
		*sp = (MochaSymbol *)sym->entry.next;
		FreeSymbol(mc, &sym->entry, HT_FREE_ENTRY);
		return;
	    }
	}
    }
}

MochaSymbol *
mocha_SetProperty(MochaContext *mc, MochaScope *scope, MochaAtom *atom,
		  MochaSlot slot, MochaDatum datum)
{
    MochaObject *obj;
    MochaAtom *slotAtom;
    char buf[16];
    PRHashNumber hash;
    MochaSymbol *sym, *sym2;
    MochaDatum oldDatum;
    MochaProperty *prop;

    obj = scope->object;
    if (scope->table) scope->table->allocPool = mc;

    if (slot < 0) {
	/* Negative slots have no index atom, so bail if name atom is null. */
	PR_ASSERT(atom);
	if (!atom) return 0;
	slotAtom = atom;
    } else {
	/* Create canonical index name. */
	PR_snprintf(buf, sizeof buf, "%ld", (long)slot);
	slotAtom = mocha_Atomize(mc, buf, ATOM_NUMBER);
	if (!slotAtom)
	    return 0;
	slotAtom->fval = slot;
    }

    /* Look it up in scope to find a pre-existing slot datum. */
    hash = HashAtom(slotAtom);
    if (!RawLookupSymbol(mc, scope, hash, slotAtom, MLF_SET, &sym))
	return 0;
    if (sym && sym->type == SYM_PROPERTY) {
	sym->slot = slot;
	prop = sym_property(sym);
	PR_ASSERT(prop);
	oldDatum = prop->datum;
	prop->datum = datum;
	prop->datum.nrefs = oldDatum.nrefs;
	mocha_HoldRef(mc, &prop->datum);
	mocha_DropRef(mc, &oldDatum);
    } else {
	/* No such slot -- allocate a new property for this slot. */
	prop = MOCHA_malloc(mc, sizeof *prop);
	if (!prop)
	    return 0;
	prop->datum = datum;
	prop->datum.nrefs = 0;
	mocha_HoldRef(mc, &prop->datum);
	prop->slot = slot;
	prop->getter = obj->clazz->getProperty;
	prop->setter = obj->clazz->setProperty;

	/* Install a symbol for the new property if there isn't one already. */
	if (sym) {
	    FreeSymbol(mc, &sym->entry, HT_FREE_VALUE);
	    sym->type = SYM_PROPERTY;
	    sym->entry.value = prop;
	    prop->datum.nrefs = 1;
	} else {
	    sym = mocha_DefineSymbol(mc, scope, slotAtom, SYM_PROPERTY, prop);
	    if (!sym) {
		MOCHA_free(mc, prop);
		return 0;
	    }
	}
	sym->slot = slot;
	sym->next = 0;
	prop->lastsym = sym;

	/* Append the new property at the end of obj's property list. */
	PROP_APPEND(scope, prop);
    }

    /* Now that we have slot's property, define a symbol named atom for it. */
    if (atom && atom != slotAtom) {
	sym = mocha_DefineSymbol(mc, scope, atom, SYM_PROPERTY, prop);
	if (!sym)
	    return 0;	/* XXX undo above side effects? */
	sym->slot = slot;
	for (sym2 = prop->lastsym; sym2 != sym; sym2 = sym2->next) {
	    if (!sym2) {
		sym->next = prop->lastsym;
		prop->lastsym = sym;
		break;
	    }
	}
    }

    /* Now that we've succeeded, update freeslot and minslot. */
    if (slot >= scope->freeslot)
	scope->freeslot = slot + 1;
    if (slot < scope->minslot)
	scope->minslot = slot;
    return sym;
}

void
mocha_RemoveProperty(MochaContext *mc, MochaScope *scope, MochaAtom *atom)
{
    /* XXX properties should be keyed by datum, not atom */
    mocha_RemoveSymbol(mc, scope, atom);
}