Milán Major

netscape-revival

1 branch
Code

mocha/tests/mo_shell.c

/*
** Mocha shell.
**
** Brendan Eich, 6/20/95
*/
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "prarena.h"
#include "prlog.h"
#include "prprf.h"
#include "mo_atom.h"
#include "mo_cntxt.h"
#include "mo_emit.h"
#include "mo_parse.h"
#include "mo_scan.h"
#include "mo_scope.h"
#include "mocha.h"
#include "mochaapi.h"
#include "mochalib.h"

#ifndef MOCHAFILE
# error "MOCHAFILE must be defined for this module to work."
#endif

extern MochaAtomState mocha_AtomState;

static void
Process(MochaContext *mc, MochaObject *obj, char *filename)
{
    MochaTokenStream *ts;
    MochaScript script;		/* XXX we know all about this struct */
    CodeGenerator cg;
    MochaDatum result;
    MochaAtom *atom;

    script.filename = filename;
    if (filename && strcmp(filename, "-") != 0) {
	ts = mocha_NewFileTokenStream(mc, filename);
    } else {
	ts = mocha_NewBufferTokenStream(mc, 0, 0);
	if (ts) ts->file = stdin;
    }
    if (!ts) goto out;
    if (isatty(fileno(ts->file)))
	ts->flags |= (TSF_INTERACTIVE | TSF_COMMAND);

    mocha_InitCodeGenerator(mc, &cg, &mc->codePool);
    script.notes = 0;
    while (!(ts->flags & TSF_EOF)) {
	script.lineno = ts->lineno;
	if (ts->flags & TSF_INTERACTIVE)
	    printf("mocha> ");

	CG_RESET(&cg);
	if (!mocha_Parse(mc, obj, ts, &cg) || CG_OFFSET(&cg) == 0)
	    continue;

	script.code = cg.base;
	script.length = CG_OFFSET(&cg);
	script.depth = cg.maxStackDepth;
	if (mocha_InitAtomMap(mc, &script.atomMap, &cg)) {
	    script.notes = mocha_FinishTakingSourceNotes(mc, &cg);
	    if (script.notes) {
		if (MOCHA_ExecuteScript(mc, obj, &script, &result)) {
		    if ((ts->flags & TSF_INTERACTIVE) &&
			result.tag != MOCHA_UNDEF &&
			MOCHA_DatumToString(mc, result, &atom)) {
			printf("%s\n", atom_name(atom));
			mocha_DropAtom(mc, atom);
		    }
		    mocha_DropRef(mc, &result);
		}
		free(script.notes);
	    }
	    mocha_FreeAtomMap(mc, &script.atomMap);
	}
    }

    mocha_CloseTokenStream(ts);
out:
    PR_FreeArenaPool(&mc->codePool);
    PR_FreeArenaPool(&mc->tempPool);
}

static MochaBoolean
Load(MochaContext *mc, MochaObject *obj,
     unsigned argc, MochaDatum *argv, MochaDatum *rval)
{
    unsigned i;
    MochaAtom *atom;
    const char *filename;
    MochaScript *script;
    MochaDatum result;

    for (i = 0; i < argc; i++) {
	if (!MOCHA_DatumToString(mc, argv[i], &atom))
	    return MOCHA_FALSE;
	filename = atom_name(atom);
	errno = 0;
	script = MOCHA_CompileFile(mc, obj, filename);
	mocha_DropAtom(mc, atom);
	if (!script) {
	    fprintf(stderr, "Mocha: cannot load %s", filename);
	    if (errno)
		fprintf(stderr, ": %s", strerror(errno));
	    putc('\n', stderr);
	    continue;
	}
	if (MOCHA_ExecuteScript(mc, obj, script, &result))
	    mocha_DropRef(mc, &result);
	MOCHA_DestroyScript(mc, script);
    }
    return MOCHA_TRUE;
}

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

    for (i = n = 0; i < argc; i++) {
	if (!MOCHA_DatumToString(mc, argv[i], &atom))
	    return MOCHA_FALSE;
	printf("%s%s", i ? " " : "", atom_name(atom));
	mocha_DropAtom(mc, atom);
	n++;
    }
    if (n)
	putchar('\n');
    return MOCHA_TRUE;
}

#ifdef DEBUG

static char *sourceNoteName[] = {
    "null", "newline", "setline", "if", "if-else", "while", "for", "continue",
    "var", "comma", "assignop", "cond", "paren", "hidden"
};

static MochaBoolean
Disassemble(MochaContext *mc, MochaObject *obj,
	    unsigned argc, MochaDatum *argv, MochaDatum *rval)
{
    unsigned i, offset, delta;
    MochaAtom *atom;
    MochaFunction *fun;
    SourceNote *notes, *sn;
    SourceNoteType type;

    for (i = 0; i < argc; i++) {
	if (argv[i].tag != MOCHA_FUNCTION) {
	    if (!MOCHA_DatumToString(mc, argv[i], &atom))
		return MOCHA_FALSE;
	    fprintf(stderr, "Mocha: cannot disassemble %s\n", atom_name(atom));
	    mocha_DropAtom(mc, atom);
	    continue;
	}

	fun = argv[i].u.fun;
	mocha_Disassemble(mc, fun->script, stdout);
	notes = fun->script->notes;
	if (notes) {
	    printf("\nSource notes:\n");
	    offset = 0;
	    for (sn = notes; !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) {
		delta = SN_DELTA(sn);
		offset += delta;
		printf("%3u: %5u [%4u] %-8s",
		       sn - notes, offset, delta, sourceNoteName[SN_TYPE(sn)]);
		type = SN_TYPE(sn);
		if (type == SRC_SETLINE) {
		    printf(" lineno %u", SN_OFFSET(&sn[1]));
		} else if (type == SRC_FOR) {
		    printf(" cond %u update %u tail %u",
			   SN_OFFSET(&sn[1]), SN_OFFSET(&sn[2]),
			   SN_OFFSET(&sn[3]));
		}
		putchar('\n');
	    }
	}
    }
    return MOCHA_TRUE;
}

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

    if (argc == 0) {
	MOCHA_INIT_DATUM(mc, rval, MOCHA_BOOLEAN, u.bval, mc->tracefp != 0);
	return MOCHA_TRUE;
    }

    switch (argv[0].tag) {
      case MOCHA_NUMBER:
	bval = argv[0].u.fval != 0;
	break;
      case MOCHA_BOOLEAN:
	bval = argv[0].u.bval;
	break;
      default:
	if (!MOCHA_DatumToString(mc, argv[0], &atom))
	    return MOCHA_FALSE;
	fprintf(stderr, "tracing: illegal argument %s\n", atom_name(atom));
	mocha_DropAtom(mc, atom);
	return MOCHA_TRUE;
    }
    mc->tracefp = bval ? stdout : 0;
    return MOCHA_TRUE;
}

static char *symbolTypeName[] = {
    "undef", "argument", "variable", "property", "constructor"
};

int
DumpAtom(PRHashEntry *he, int i, void *arg)
{
    FILE *fp = arg;
    MochaAtom *atom = (MochaAtom *)he;

    fprintf(fp, "%3d %08x %5u %5ld %.16g \"%s\"\n",
	    i, (unsigned)he->keyHash, atom->index, atom->number, atom->fval,
	    atom_name(atom));
    return HT_ENUMERATE_NEXT;
}

int
DumpSymbol(PRHashEntry *he, int i, void *arg)
{
    FILE *fp = arg;
    MochaSymbol *sym = (MochaSymbol *)he;
    MochaAtom *atom = sym_atom(sym);

    fprintf(fp, "%3d %08x %-8s \"%s\"\n",
	    i, (unsigned)he->keyHash, symbolTypeName[sym->type],
	    atom_name(atom));
    return HT_ENUMERATE_NEXT;
}

void
DumpScope(MochaScope *scope, PRHashEnumerator dump, FILE *fp)
{
    fprintf(fp, "\n%s scope contents:\n", scope->object->clazz->name);
    PR_HashTableDump(scope->table, dump, fp);
}

/* These are callable from gdb. */
void Dsym(MochaSymbol *sym) { if (sym) DumpSymbol(&sym->entry, 0, stderr); }
void Datom(MochaAtom *atom) { if (atom) DumpAtom(&atom->entry, 0, stderr); }

static MochaBoolean
DumpStats(MochaContext *mc, MochaObject *obj,
	  unsigned argc, MochaDatum *argv, MochaDatum *rval)
{
    unsigned i;
    MochaAtom *atom, *atom2;
    const char *which;
    MochaScope *scope;
    MochaPair pair;
    MochaObject *obj2;

    for (i = 0; i < argc; i++) {
	if (!MOCHA_DatumToString(mc, argv[i], &atom))
	    return MOCHA_FALSE;
	which = atom_name(atom);
	if (strcmp(which, "arena") == 0) {
#ifdef ARENAMETER
	    PR_DumpArenaStats(stdout);
#endif
	} else if (strcmp(which, "atom") == 0) {
	    printf("\natom table contents:\n");
	    PR_HashTableDump(mocha_AtomState.table, DumpAtom, stdout);
	} else if (strcmp(which, "global") == 0) {
	    for (scope = mc->staticLink->scope; scope->object->parent;
		 scope = scope->object->parent->scope)
		;
	    DumpScope(scope, DumpSymbol, stdout);
	} else {
	    atom2 = mocha_Atomize(mc, which, ATOM_STRING);
	    if (atom2) {
		if (!mocha_SearchScopes(mc, atom2, MLF_GET, &pair))
		    pair.sym = 0;
		mocha_DropAtom(mc, atom2);
		if (!pair.sym ||
		    pair.sym->type != SYM_PROPERTY ||
		    sym_datum(pair.sym)->tag != MOCHA_OBJECT) {
		    fprintf(stderr, "Mocha: invalid stats argument %s\n",
			    which);
		} else {
		    obj2 = sym_datum(pair.sym)->u.obj;
		    DumpScope(obj2->scope, DumpSymbol, stdout);
		}
	    }
	}
	mocha_DropAtom(mc, atom);
    }
    return MOCHA_TRUE;
}

#endif /* DEBUG */

static MochaBoolean
Help(MochaContext *mc, MochaObject *obj,
     unsigned argc, MochaDatum *argv, MochaDatum *rval);

static MochaBoolean
Quit(MochaContext *mc, MochaObject *obj,
     unsigned argc, MochaDatum *argv, MochaDatum *rval)
{
    exit(0);
}

static MochaFunctionSpec shell_functions[] = {
    {"load",            Load,           1},
    {"print",           Print,          0},
    {"help",            Help,           0},
    {"quit",            Quit,           0},
#ifdef DEBUG
    {"dis",             Disassemble,    1},
    {"tracing",         Tracing,        0},
    {"stats",           DumpStats,      1},
#endif
    {0}
};

/* *** NOTE: These must be kept in sync with the above***  */

static char *shell_help_messages[] = {
    "Load files named by string arguments",
    "Evaluate and print expressions",
    "Display usage and help messages",
    "Quit mocha",
#ifdef DEBUG
    "Disassemble functions into bytecodes",
    "Turn tracing on or off",
    "Dump 'arena', 'atom', or 'global' stats",
#endif
    0
};

static void
ShowHelpHeader(void)
{
    printf("%-9.9s %s\n", "Command", "description");
    printf("%-9.9s %s\n", "=======", "===========");
}

static void
ShowHelpForCommand(unsigned n)
{
    printf("%-9.9s %s\n", shell_functions[n].name, shell_help_messages[n]);
}

static MochaBoolean
Help(MochaContext *mc, MochaObject *obj,
     unsigned argc, MochaDatum *argv, MochaDatum *rval)
{
    unsigned i, j;
    int did_header, did_something;
    MochaAtom *atom;

    if (argc == 0) {
	ShowHelpHeader();
	for (i = 0; shell_functions[i].name; i++)
	    ShowHelpForCommand(i);
    } else {
	did_header = 0;
	for (i = 0; i < argc; i++) {
	    did_something = 0;
	    if (argv[i].tag == MOCHA_FUNCTION) {
		atom = mocha_HoldAtom(mc, argv[i].u.fun->atom);
	    } else if (argv[i].tag == MOCHA_STRING) {
		atom = mocha_HoldAtom(mc, argv[i].u.atom);
	    } else {
		atom = 0;
	    }
	    if (atom) {
		for (j = 0; shell_functions[j].name; j++) {
		    if (!strcmp(atom_name(atom), shell_functions[j].name)) {
			if (!did_header) {
			    did_header = 1;
			    ShowHelpHeader();
			}
			did_something = 1;
			ShowHelpForCommand(j);
			break;
		    }
		}
		mocha_DropAtom(mc, atom);
	    }
	    if (!did_something) {
		if (!MOCHA_DatumToString(mc, argv[i], &atom))
		    return MOCHA_FALSE;
		fprintf(stderr, "Sorry, no help for %s\n", atom_name(atom));
		mocha_DropAtom(mc, atom);
	    }
	}
    }
    return MOCHA_TRUE;
}

/*
** Define a Mocha object called "it".
*/
enum its_slot {
    ITS_COLOR, ITS_HEIGHT, ITS_WIDTH, ITS_FUNNY, ITS_ARRAY, ITS_MAX
};

static MochaPropertySpec its_props[] = {
    {"color",           ITS_COLOR},
    {"height",          ITS_HEIGHT},
    {"width",           ITS_WIDTH},
    {"funny",           ITS_FUNNY},
    {"array",           ITS_ARRAY},
    {0}
};

static MochaClass its_class = {
    "It",
    MOCHA_PropertyStub, MOCHA_PropertyStub, MOCHA_ListPropStub,
    MOCHA_ResolveStub,  MOCHA_ConvertStub, MOCHA_FinalizeStub
};

static void
my_ErrorReporter(MochaContext *mc, const char *message,
		 MochaErrorReport *report)
{
    int i, j, k, n;

    fputs("Mocha: ", stderr);
    if (!report) {
	fprintf(stderr, "%s\n", message);
	return;
    }

    if (report->filename)
	fprintf(stderr, "%s, ", report->filename);
    if (report->lineno)
	fprintf(stderr, "line %u: ", report->lineno);
    fputs(message, stderr);
    if (!report->linebuf) {
	putc('\n', stderr);
	return;
    }

    fprintf(stderr, ":\n%s\n", report->linebuf);
    n = report->tokenptr - report->linebuf;
    for (i = j = 0; i < n; i++) {
	if (report->linebuf[i] == '\t') {
	    for (k = (j + 8) & ~7; j < k; j++)
		putc('.', stderr);
	    continue;
	}
	putc('.', stderr);
	j++;
    }
    fputs("^\n", stderr);
}

static MochaClass global_class = {
    "global",
    MOCHA_PropertyStub, MOCHA_PropertyStub, MOCHA_ListPropStub,
    MOCHA_ResolveStub,  MOCHA_ConvertStub,  MOCHA_FinalizeStub
};

/* Stub to avoid linking with half the known universe. */
MochaBoolean
mocha_InitJava(MochaContext *mc, MochaObject *obj)
{
    return MOCHA_TRUE;
}

/* Another stub to avoid linking with half the known universe. */
void
mocha_DestroyJavaContext(MochaContext *mc)
{
}

int
main(int argc, char **argv)
{
    MochaContext *mc;
    MochaObject *glob;
    int i;

    mc = MOCHA_NewContext(8192);
    if (!mc) return 1;

    glob = MOCHA_NewObject(mc, &global_class, 0, 0, 0, 0, 0);
    if (!glob) return 1;
    MOCHA_HoldObject(mc, glob);
    MOCHA_SetGlobalObject(mc, glob);

    MOCHA_SetErrorReporter(mc, my_ErrorReporter);
    MOCHA_DefineFunctions(mc, glob, shell_functions);
    MOCHA_DefineNewObject(mc, glob, "it", &its_class, 0, 0, 0, its_props, 0);

    if (argc > 1) {
	for (i = 1; i < argc; i++)
	    Process(mc, glob, argv[i]);
    } else {
	Process(mc, glob, 0);
    }

    MOCHA_DropObject(mc, glob);
    MOCHA_DestroyContext(mc);
    return 0;
}