Milán Major

netscape-revival

1 branch
Code

mocha/src/mo_parse.c

/*
** Mocha parser.
**
** This is a recursive descent parser for the extended BNF grammar in
** "The Mocha Language Report" (XXX need wnj JavaScript spec cite here).
** It generates Mocha bytecode and builds symbol table entries for functions
** as it parses.
**
** The parser attempts no error recovery yet -- you have to fix each error
** as you find it.
**
** Brendan Eich, 6/14/95
*/
#include <stdlib.h>
#include <string.h>
#include "prarena.h"
#include "prlog.h"
#include "mo_atom.h"
#include "mo_bcode.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 "mochalib.h"	/* XXX only for mocha_GetMutableScope() */

/*
** Mocha parsers, from lowest to highest precedence.
**
** Each parser takes a context, a static link, a token stream, and generates
** bytecode using the code generator argument.
*/
typedef MochaBoolean
MochaParser(MochaContext *mc, MochaTokenStream *ts, CodeGenerator *cg);

static MochaParser FunctionDefinition;
static MochaParser Statements;
static MochaParser Statement;
static MochaParser Variables;
static MochaParser StmtExpr;
static MochaParser Expr;
static MochaParser AssignExpr;
static MochaParser CondExpr;
static MochaParser OrExpr;
static MochaParser AndExpr;
static MochaParser BitOrExpr;
static MochaParser BitXorExpr;
static MochaParser BitAndExpr;
static MochaParser EqExpr;
static MochaParser RelExpr;
static MochaParser ShiftExpr;
static MochaParser AddExpr;
static MochaParser MulExpr;
static MochaParser UnaryExpr;
static MochaParser MemberExpr;
static MochaParser PrimaryExpr;
static MochaParser NameExpr;

/* NB: this macro uses mc, ts, and cg from its lexical environment. */
#define MUST_MATCH_TOKEN(tt, err) {                                           \
    if (mocha_GetToken(mc, ts, cg) != tt) {                                   \
	mocha_ReportSyntaxError(mc, ts, err);                                 \
	return MOCHA_FALSE;                                                   \
    }                                                                         \
}

/*
** Parse a top-level Mocha script.
*/
MochaBoolean
mocha_Parse(MochaContext *mc, MochaObject *slink, MochaTokenStream *ts,
	    CodeGenerator *cg)
{
    MochaObject *oldslink;
    MochaTokenType stop, tt;
    MochaBoolean ok;

    oldslink = mc->staticLink;
    mc->staticLink = slink;

    if (ts->flags & TSF_INTERACTIVE) {
	SCAN_NEWLINES(ts);
	stop = TOK_EOL;
    } else {
	stop = TOK_EOF;
    }

    ok = MOCHA_TRUE;
    while (ok && (tt = mocha_GetToken(mc, ts, cg)) != stop && tt != TOK_EOF) {
	switch (tt) {
	  case TOK_FUNCTION:
	    if (!FunctionDefinition(mc, ts, cg))
		ok = MOCHA_FALSE;
	    break;
	  default:
	    mocha_UngetToken(ts);
	    if (!Statement(mc, ts, cg))
		ok = MOCHA_FALSE;
	}
    }

    mc->staticLink = oldslink;
    if (!ok) {
	CLEAR_PUSHBACK(ts);
	mocha_DropUnmappedAtoms(mc, cg);
    }
    return ok;
}

/*
** Parse a Mocha function body, which might appear as the value of an event
** handler attribute in a HTML <INPUT> tag.
*/
MochaBoolean
mocha_ParseFunctionBody(MochaContext *mc, MochaTokenStream *ts,
			MochaFunction *fun)
{
    unsigned lineno;
    CodeGenerator funcg;
    MochaObject *oldslink;
    MochaBoolean ok;

    if (ts->flags & TSF_FUNCTION) {
	mocha_ReportSyntaxError(mc, ts, "function defined inside a function");
	return MOCHA_FALSE;
    }

    lineno = ts->lineno - 1;
    if (!mocha_InitCodeGenerator(mc, &funcg, &mc->codePool))
	return MOCHA_FALSE;

    oldslink = mc->staticLink;
    mc->staticLink = &fun->object;
    ts->flags |= TSF_FUNCTION;
    ok = Statements(mc, ts, &funcg);
    ts->flags &= ~TSF_FUNCTION;
    mc->staticLink = oldslink;

    /* Check for falling off the end of a function that returns a value. */
    if (ok &&
	(ts->flags & TSF_RETURN_EXPR) &&
	funcg.lastOpcode != MOP_RETURN &&
	funcg.lastOpcode != MOP_LEAVE) {
	mocha_ReportSyntaxError(mc, ts,
				"function does not always return a value");
	ok = MOCHA_FALSE;
    }
    ts->flags &= ~(TSF_RETURN_EXPR | TSF_RETURN_VOID);

    if (!ok) {
	CLEAR_PUSHBACK(ts);
	mocha_DropUnmappedAtoms(mc, &funcg);
    } else {
	fun->script = mocha_NewScript(mc, &funcg, ts->filename, lineno);
	if (!fun->script)
	    ok = MOCHA_FALSE;
    }
    return ok;
}

static MochaBoolean
FunctionDefinition(MochaContext *mc, MochaTokenStream *ts, CodeGenerator *cg)
{
    MochaAtom *atom;
    char *cp;
    unsigned nargs;
    MochaAtomMap map;
    MochaFunction *fun;
    MochaBoolean ok;
    MochaSymbol *arg, *args, **argp;
    void *mark;
    unsigned i;
    int snindex;

    /* Save atoms indexed but not mapped for the top-level script. */
    if (!mocha_InitAtomMap(mc, &map, cg))
	return MOCHA_FALSE;

    MUST_MATCH_TOKEN(TOK_NAME, "missing function name");
    atom = ts->token.u.atom;

    /* Estimate number of arguments for initial function scope size. */
    nargs = 1;
    for (cp = ts->token.ptr; *cp != '\0'; cp++) {
	if (*cp == ',')
	    nargs++;
    }

    fun = mocha_DefineFunction(mc, mc->staticLink, atom, 0, nargs, 0);
    if (!fun || !mocha_GetMutableScope(mc, &fun->object)) {
	ok = MOCHA_FALSE;
	goto out;
    }
    PR_ASSERT(fun->object.parent == mc->staticLink);

    /* Now parse formal argument list and compute the final fun->nargs. */
    nargs = 0;
    args = 0;
    MUST_MATCH_TOKEN(TOK_LP, "missing ( before formal parameters");
    /* balance) */
    if (!mocha_MatchToken(mc, ts, cg, TOK_RP)) {
	argp = &args;
	do {
	    MUST_MATCH_TOKEN(TOK_NAME, "missing formal parameter");
	    arg = mocha_DefineSymbol(mc, fun->object.scope, ts->token.u.atom,
				     SYM_ARGUMENT, 0);
	    if (!arg) {
		ok = MOCHA_FALSE;
		goto out;
	    }
	    arg->slot = nargs++;
	    *argp = arg;
	    argp = &arg->next;
	} while (mocha_MatchToken(mc, ts, cg, TOK_COMMA));

	/* (balance: */
	MUST_MATCH_TOKEN(TOK_RP, "missing ) after formal parameters");
    }
    fun->nargs = nargs;

    MUST_MATCH_TOKEN(TOK_LC, "missing { before function body");
    mark = PR_ARENA_MARK(&mc->codePool);
    ok = mocha_ParseFunctionBody(mc, ts, fun);
    if (ok) {
	MUST_MATCH_TOKEN(TOK_RC, "missing } after function body");
	fun->script->depth += fun->object.scope->freeslot;
	fun->script->args = args;

	/* Generate a setline note for script that follows this function. */
	snindex = mocha_NewSourceNote(mc, cg, SRC_SETLINE);
	if (snindex >= 0)
	    SN_SET_OFFSET(&cg->notes[snindex + 1], ts->lineno - 1);
	else
	    ok = MOCHA_FALSE;
    }
    PR_ARENA_RELEASE(&mc->codePool, mark);

out:
    if (!ok)
	mocha_RemoveSymbol(mc, mc->staticLink->scope, atom);
    for (i = 0; i < map.length; i++) {
	mocha_IndexAtom(mc, map.vector[i], cg);
	PR_ASSERT(map.vector[i]->index == i);
    }
    mocha_FreeAtomMap(mc, &map);
    return ok;
}

static MochaBoolean
Statements(MochaContext *mc, MochaTokenStream *ts, CodeGenerator *cg)
{
    int newlines;
    MochaBoolean ok;
    MochaTokenType tt;

    newlines = ts->flags & TSF_NEWLINES;
    if (newlines) HIDE_NEWLINES(ts);

    ok = MOCHA_TRUE;
    while ((tt = mocha_PeekToken(mc, ts, cg)) != TOK_EOF && tt != TOK_RC) {
	if (!Statement(mc, ts, cg)) {
	    ok = MOCHA_FALSE;
	    break;
	}
    }
    if (newlines) SCAN_NEWLINES(ts);
    return ok;
}

static MochaBoolean
Condition(MochaContext *mc, MochaTokenStream *ts, CodeGenerator *cg)
{
    MUST_MATCH_TOKEN(TOK_LP, "missing ( before condition");
    if (!Expr(mc, ts, cg))
	return MOCHA_FALSE;
    MUST_MATCH_TOKEN(TOK_RP, "missing ) after condition");

    /* Check for an AssignExpr (see below) and "correct" it to an EqExpr */
    if (cg->lastOpcode == MOP_ASSIGN &&
	(cg->noteCount == 0 ||
	 SN_TYPE(&cg->notes[cg->noteCount-1]) != SRC_ASSIGNOP ||
	 cg->lastOffset < CG_OFFSET(cg) - 2)) {
	mocha_ReportSyntaxError(mc, ts,
	    "test for equality (==) mistyped as assignment (=)?\n"
	    "Assuming equality test");
	cg->ptr[-1] = MOP_EQ;
    }
    return MOCHA_TRUE;
}

static MochaBoolean
Statement(MochaContext *mc, MochaTokenStream *ts, CodeGenerator *cg)
{
    int snindex, upindex, newlines;
    SourceNote *topsn;
    ptrdiff_t top, beq, jmp;
    LoopInfo loopInfo;
    unsigned topNoteCount;
    CodeGenerator updater;
    MochaTokenType tt;
    MochaBoolean forIn, ok;

    switch (mocha_GetToken(mc, ts, cg)) {
      case TOK_IF:
	if (!Condition(mc, ts, cg))
	    return MOCHA_FALSE;
	snindex = mocha_NewSourceNote(mc, cg, SRC_IF);
	if (snindex < 0)
	    return MOCHA_FALSE;
	beq = mocha_Emit3(mc, cg, MOP_IFEQ, 0, 0);
	if (beq < 0)
	    return MOCHA_FALSE;
	if (!Statement(mc, ts, cg))
	    return MOCHA_FALSE;
	if (mocha_MatchToken(mc, ts, cg, TOK_ELSE)) {
	    SN_SET_TYPE(&cg->notes[snindex], SRC_IF_ELSE);
	    jmp = mocha_Emit3(mc, cg, MOP_GOTO, 0, 0);
	    if (jmp < 0)
		return MOCHA_FALSE;
	    SET_JUMP_OFFSET(CG_CODE(cg, beq), CG_OFFSET(cg) - beq);
	    if (!Statement(mc, ts, cg))
		return MOCHA_FALSE;
	    SET_JUMP_OFFSET(CG_CODE(cg, jmp), CG_OFFSET(cg) - jmp);
	} else {
	    SET_JUMP_OFFSET(CG_CODE(cg, beq), CG_OFFSET(cg) - beq);
	}
	break;

      case TOK_WHILE:
	top = CG_OFFSET(cg);
	mocha_PushLoopInfo(cg, &loopInfo, top);
	if (!Condition(mc, ts, cg))
	    return MOCHA_FALSE;
	snindex = mocha_NewSourceNote(mc, cg, SRC_WHILE);
	if (snindex < 0)
	    return MOCHA_FALSE;
	beq = mocha_Emit3(mc, cg, MOP_IFEQ, 0, 0);
	if (beq < 0)
	    return MOCHA_FALSE;
	if (!Statement(mc, ts, cg))
	    return MOCHA_FALSE;
	jmp = mocha_Emit3(mc, cg, MOP_GOTO, 0, 0);
	if (jmp < 0)
	    return MOCHA_FALSE;
	SET_JUMP_OFFSET(CG_CODE(cg, jmp), top - jmp);
	SET_JUMP_OFFSET(CG_CODE(cg, beq), CG_OFFSET(cg) - beq);
	mocha_PopLoopInfo(cg);
	break;

      case TOK_FOR:
	top = CG_OFFSET(cg);
	mocha_PushLoopInfo(cg, &loopInfo, top);
	snindex = -1;
	topNoteCount = cg->noteCount;
	updater.base = 0;

	MUST_MATCH_TOKEN(TOK_LP, "missing ( after for");	/* balance) */
	tt = mocha_PeekToken(mc, ts, cg);
	if (tt == TOK_SEMI) {
	    /* No initializer -- emit an annotated nop for the decompiler. */
	    snindex = mocha_NewSourceNote(mc, cg, SRC_FOR);
	    if (snindex < 0 || mocha_Emit1(mc, cg, MOP_NOP) < 0)
		return MOCHA_FALSE;
	} else {
	    if (tt == TOK_VAR) {
		(void) mocha_GetToken(mc, ts, cg);
		if (!Variables(mc, ts, cg))
		    return MOCHA_FALSE;
	    } else {
		if (!Expr(mc, ts, cg))
		    return MOCHA_FALSE;
	    }
	    if (mocha_PeekToken(mc, ts, cg) != TOK_IN) {
		snindex = mocha_NewSourceNote(mc, cg, SRC_FOR);
		if (snindex < 0 || mocha_Emit1(mc, cg, MOP_POP) < 0)
		    return MOCHA_FALSE;
	    }
	}

	forIn = mocha_MatchToken(mc, ts, cg, TOK_IN);
	if (forIn) {
	    /* Insert an MOP_PUSH to allocate an iterator before this loop. */
	    if (!mocha_SetStatementDepthType(mc, cg, MOCHA_FALSE))
		return MOCHA_FALSE;
	    cg->withDepth++;
	    mocha_MoveCode(mc, cg, top, cg, top + 1);
	    *CG_CODE(cg, top) = MOP_PUSH;
	    mocha_UpdateDepth(mc, cg, top);
	    top++;
	    SET_LOOPINFO_TOP(&loopInfo, top);

	    /* If the iterator had source notes, bump the first one's delta. */
	    if (cg->noteCount > topNoteCount) {
		topsn = &cg->notes[topNoteCount];
		SN_SET_DELTA(topsn, SN_DELTA(topsn) + 1);
		cg->lastOffset++;
	    }

	    /* Now compile the object expression over which we're iterating. */
	    if (!Expr(mc, ts, cg))
		return MOCHA_FALSE;
	    if (mocha_Emit1(mc, cg, MOP_IN) < 0)
		return MOCHA_FALSE;
	    beq = mocha_Emit3(mc, cg, MOP_IFEQ, 0, 0);
	    if (beq < 0)
		return MOCHA_FALSE;
	} else {
	    MUST_MATCH_TOKEN(TOK_SEMI, "missing ; after for-loop initializer");
	    top = CG_OFFSET(cg);
	    SET_LOOPINFO_TOP(&loopInfo, top);
	    if (mocha_PeekToken(mc, ts, cg) == TOK_SEMI) {
		/* No loop condition -- flag this fact in the source note. */
		if (snindex >= 0)
		    SN_SET_OFFSET(&cg->notes[snindex + 1], 0);
		beq = 0;
	    } else {
		if (!Expr(mc, ts, cg))
		    return MOCHA_FALSE;
		if (snindex >= 0)
		    SN_SET_OFFSET(&cg->notes[snindex + 1], CG_OFFSET(cg) - top);
		beq = mocha_Emit3(mc, cg, MOP_IFEQ, 0, 0);
		if (beq < 0)
		    return MOCHA_FALSE;
	    }

	    MUST_MATCH_TOKEN(TOK_SEMI, "missing ; after for-loop condition");
	    if (mocha_PeekToken(mc, ts, cg) != TOK_RP) {
		if (!mocha_InitCodeGenerator(mc, &updater, &mc->tempPool))
		    return MOCHA_FALSE;
		upindex = mocha_NewSourceNote(mc, &updater, SRC_SETLINE);
		if (upindex < 0)
		    return MOCHA_FALSE;
		SN_SET_OFFSET(&updater.notes[upindex + 1], ts->lineno);
/* XXX egad */
updater.atomList = cg->atomList;
updater.atomCount = cg->atomCount;
		if (!Expr(mc, ts, &updater))
		    return MOCHA_FALSE;
/* XXX egad */
cg->atomList = updater.atomList;
cg->atomCount = updater.atomCount;
		if (mocha_Emit1(mc, &updater, MOP_POP) < 0)
		    return MOCHA_FALSE;
	    }
	}

	/* (balance: */
	MUST_MATCH_TOKEN(TOK_RP, "missing ) after for-loop control");
	if (!Statement(mc, ts, cg))
	    return MOCHA_FALSE;

	if (snindex != -1)
	    SN_SET_OFFSET(&cg->notes[snindex + 2], CG_OFFSET(cg) - top);

	if (updater.base) {
	    /* Set our loopInfo's "update code" offset, for continue. */
	    loopInfo.update = CG_OFFSET(cg);

	    /* Copy the update code from its temporary pool into cg's pool. */
	    mocha_MoveSourceNotes(mc, &updater, cg);
	    mocha_MoveCode(mc, &updater, 0, cg, loopInfo.update);

	    /* Restore the absolute line number for source note readers. */
	    upindex = mocha_NewSourceNote(mc, cg, SRC_SETLINE);
	    if (upindex < 0)
		return MOCHA_FALSE;
	    SN_SET_OFFSET(&cg->notes[upindex + 1], ts->lineno);
	}

	if (snindex != -1)
	    SN_SET_OFFSET(&cg->notes[snindex + 3], CG_OFFSET(cg) - top);

	jmp = mocha_Emit3(mc, cg, MOP_GOTO, 0, 0);
	if (jmp < 0)
	    return MOCHA_FALSE;
	SET_JUMP_OFFSET(CG_CODE(cg, jmp), top - jmp);
	if (beq > 0)
	    SET_JUMP_OFFSET(CG_CODE(cg, beq), CG_OFFSET(cg) - beq);
	mocha_PopLoopInfo(cg);
	if (forIn) {
	    if (mocha_Emit1(mc, cg, MOP_POP) < 0)
		return MOCHA_FALSE;
	    cg->withDepth--;
	}
	break;

      case TOK_BREAK:
	if (!cg->loopInfo) {
	    mocha_ReportSyntaxError(mc, ts, "break used outside a loop");
	    return MOCHA_FALSE;
	}
	if (mocha_EmitBreak(mc, cg) < 0)
	    return MOCHA_FALSE;
	break;

      case TOK_CONTINUE:
	if (!cg->loopInfo) {
	    mocha_ReportSyntaxError(mc, ts, "continue used outside a loop");
	    return MOCHA_FALSE;
	}
	if (mocha_EmitContinue(mc, cg) < 0)
	    return MOCHA_FALSE;
	break;

      case TOK_WITH:
	MUST_MATCH_TOKEN(TOK_LP, "missing ( before formal parameters");
	/* balance) */
	if (!Expr(mc, ts, cg))
	    return MOCHA_FALSE;
	/* (balance: */
	MUST_MATCH_TOKEN(TOK_RP, "missing ) after formal parameters");

	if (mocha_Emit1(mc, cg, MOP_ENTER) < 0)
	    return MOCHA_FALSE;
	if (!mocha_SetStatementDepthType(mc, cg, MOCHA_TRUE))
	    return MOCHA_FALSE;
	cg->withDepth++;
	if (cg->loopInfo)
	    cg->loopInfo->withDepth++;
	ok = Statement(mc, ts, cg);
	cg->withDepth--;
	if (cg->loopInfo)
	    cg->loopInfo->withDepth--;
	if (!ok || mocha_Emit1(mc, cg, MOP_LEAVE) < 0)
	    return MOCHA_FALSE;
	break;

      case TOK_VAR:
	if (!Variables(mc, ts, cg) || mocha_Emit1(mc, cg, MOP_POP) < 0)
	    return MOCHA_FALSE;
	break;

      case TOK_RETURN:
	if (!(ts->flags & TSF_FUNCTION)) {
	    mocha_ReportSyntaxError(mc, ts, "return used outside a function");
	    return MOCHA_FALSE;
	}

	/* This is ugly, but we don't want to require a semicolon. */
	newlines = ts->flags & TSF_NEWLINES;
	if (!newlines) SCAN_NEWLINES(ts);
	tt = mocha_PeekToken(mc, ts, cg);
	if (!newlines) HIDE_NEWLINES(ts);

	/* Arrange to leave all current with and for-in statements. */
	while (cg->withDepth > 0) {
	    if (mocha_NewSourceNote(mc, cg, SRC_HIDDEN) < 0)
		return MOCHA_FALSE;
	    cg->withDepth--;
	    if ((mocha_IsWithStatementDepth(cg, cg->withDepth)
		? mocha_Emit1(mc, cg, MOP_LEAVE)
		: mocha_Emit1(mc, cg, MOP_POP)) < 0) {
		return MOCHA_FALSE;
	    }
	}

	if (tt != TOK_EOF && tt != TOK_EOL && tt != TOK_SEMI && tt != TOK_RC) {
	    if (!StmtExpr(mc, ts, cg))
		return MOCHA_FALSE;
	    ts->flags |= TSF_RETURN_EXPR;
	} else {
	    if (mocha_Emit1(mc, cg, MOP_PUSH) < 0)
		return MOCHA_FALSE;
	    ts->flags |= TSF_RETURN_VOID;
	}
	if ((ts->flags & (TSF_RETURN_EXPR | TSF_RETURN_VOID)) ==
	    (TSF_RETURN_EXPR | TSF_RETURN_VOID)) {
	    mocha_ReportSyntaxError(mc, ts,
				    "function does not always return a value");
	    return MOCHA_FALSE;
	}
	if (mocha_Emit1(mc, cg, MOP_RETURN) < 0)
	    return MOCHA_FALSE;
	break;

      case TOK_LC:
	if (!Statements(mc, ts, cg))
	    return MOCHA_FALSE;

	/* {balance: */
	MUST_MATCH_TOKEN(TOK_RC, "missing } in compound statement");
	break;

      case TOK_EOL:
      case TOK_SEMI:
	return MOCHA_TRUE;

      default:
	mocha_UngetToken(ts);
	if (!StmtExpr(mc, ts, cg) || mocha_Emit1(mc, cg, MOP_POP) < 0)
	    return MOCHA_FALSE;
	break;
    }

    (void) mocha_MatchToken(mc, ts, cg, TOK_SEMI);
    return MOCHA_TRUE;
}

/*
** Emit a bytecode and its 2-byte constant (atom) index immediate operand.
** We use mc and cg from the caller's lexical scope, and return MOCHA_FALSE
** on error.
*/
#define EMIT_CONST_ATOM_OP(op, atomIndex) {                                   \
    if (mocha_Emit3(mc, cg, op, (MochaCode)((atomIndex) >> 8),                \
				(MochaCode)(atomIndex)) < 0) {                \
	return MOCHA_FALSE;                                                   \
    }                                                                         \
}

static MochaBoolean
Variables(MochaContext *mc, MochaTokenStream *ts, CodeGenerator *cg)
{
    MochaBoolean ok;
    MochaAtom *atom;
    MochaAtomNumber atomIndex;
    MochaScope *scope;
    MochaSymbol *var;

    if (mocha_NewSourceNote(mc, cg, SRC_VAR) < 0)
	return MOCHA_FALSE;
    ok = MOCHA_TRUE;
    for (;;) {
	MUST_MATCH_TOKEN(TOK_NAME, "missing variable name");
	atom = ts->token.u.atom;
	atomIndex = mocha_IndexAtom(mc, atom, cg);
	EMIT_CONST_ATOM_OP(MOP_NAME, atomIndex);

	scope = mc->staticLink->scope;
	var = mocha_DefineSymbol(mc, scope, atom, SYM_VARIABLE, 0);
	if (!var)
	    return MOCHA_FALSE;
	var->slot = scope->freeslot++;

	if (mocha_MatchToken(mc, ts, cg, TOK_ASSIGN)) {
	    if (ts->token.u.op != MOP_NOP) {
		mocha_ReportSyntaxError(mc, ts,
					"illegal variable initialization");
		ok = MOCHA_FALSE;
	    }
	    if (!AssignExpr(mc, ts, cg) || mocha_Emit1(mc, cg, MOP_ASSIGN) < 0)
		return MOCHA_FALSE;
	}
	if (!mocha_MatchToken(mc, ts, cg, TOK_COMMA))
	    break;
	if (mocha_NewSourceNote(mc, cg, SRC_COMMA) < 0 ||
	    mocha_Emit1(mc, cg, MOP_POP) < 0) {
	    return MOCHA_FALSE;
	}
    }
    return ok;
}

/* Parse an expression followed by a statement separator or terminator. */
static MochaBoolean
StmtExpr(MochaContext *mc, MochaTokenStream *ts, CodeGenerator *cg)
{
    unsigned lineno;
    MochaTokenType tt;

    lineno = ts->lineno;
    if (!Expr(mc, ts, cg))
	return MOCHA_FALSE;
    if (ts->lineno == lineno) {
	tt = ts->pushback.type;
	if (tt != TOK_EOF && tt != TOK_EOL && tt != TOK_SEMI && tt != TOK_RC) {
	    mocha_ReportSyntaxError(mc, ts,
				    (tt == TOK_LP || IS_PRIMARY_TOKEN(tt))
				    ? "missing operator in expression"
				    : "missing semicolon before statement");
	    return MOCHA_FALSE;
	}
    }
    return MOCHA_TRUE;
}

static MochaBoolean
Expr(MochaContext *mc, MochaTokenStream *ts, CodeGenerator *cg)
{
    for (;;) {
	if (!AssignExpr(mc, ts, cg))
	    return MOCHA_FALSE;
	if (!mocha_MatchToken(mc, ts, cg, TOK_COMMA))
	    break;
	if (mocha_NewSourceNote(mc, cg, SRC_COMMA) < 0 ||
	    mocha_Emit1(mc, cg, MOP_POP) < 0) {
	    return MOCHA_FALSE;
	}
    }
    return MOCHA_TRUE;
}

static MochaBoolean
AssignExpr(MochaContext *mc, MochaTokenStream *ts, CodeGenerator *cg)
{
    MochaOp op;

    if (!CondExpr(mc, ts, cg))
	return MOCHA_FALSE;
    if (mocha_MatchToken(mc, ts, cg, TOK_ASSIGN)) {
	op = ts->token.u.op;
	if (op != MOP_NOP && mocha_Emit1(mc, cg, MOP_DUP) < 0)
	    return MOCHA_FALSE;
	if (!AssignExpr(mc, ts, cg))
	    return MOCHA_FALSE;
	if (op != MOP_NOP) {
	    if (mocha_NewSourceNote(mc, cg, SRC_ASSIGNOP) < 0 ||
		mocha_Emit1(mc, cg, op) < 0) {
		return MOCHA_FALSE;
	    }
	}
	if (mocha_Emit1(mc, cg, MOP_ASSIGN) < 0)
	    return MOCHA_FALSE;
    }
    return MOCHA_TRUE;
}

static MochaBoolean
CondExpr(MochaContext *mc, MochaTokenStream *ts, CodeGenerator *cg)
{
    ptrdiff_t beq, jmp;

    if (!OrExpr(mc, ts, cg))
	return MOCHA_FALSE;
    if (mocha_MatchToken(mc, ts, cg, TOK_HOOK)) {
	if (mocha_NewSourceNote(mc, cg, SRC_COND) < 0)
	    return MOCHA_FALSE;
	beq = mocha_Emit3(mc, cg, MOP_IFEQ, 0, 0);
	if (beq < 0 || !AssignExpr(mc, ts, cg))
	    return MOCHA_FALSE;
	MUST_MATCH_TOKEN(TOK_COLON, "missing : in conditional expression");
	jmp = mocha_Emit3(mc, cg, MOP_GOTO, 0, 0);
	if (jmp < 0)
	    return MOCHA_FALSE;
	SET_JUMP_OFFSET(CG_CODE(cg, beq), CG_OFFSET(cg) - beq);
	if (!AssignExpr(mc, ts, cg))
	    return MOCHA_FALSE;
	SET_JUMP_OFFSET(CG_CODE(cg, jmp), CG_OFFSET(cg) - jmp);
    }
    return MOCHA_TRUE;
}

static MochaBoolean
OrExpr(MochaContext *mc, MochaTokenStream *ts, CodeGenerator *cg)
{
    ptrdiff_t beq, tmp, jmp;

    if (!AndExpr(mc, ts, cg))
	return MOCHA_FALSE;
    if (mocha_MatchToken(mc, ts, cg, TOK_OR)) {
	beq = mocha_Emit3(mc, cg, MOP_IFEQ, 0, 0);
	tmp = mocha_Emit1(mc, cg, MOP_TRUE);
	jmp = mocha_Emit3(mc, cg, MOP_GOTO, 0, 0);
	if (beq < 0 || tmp < 0 || jmp < 0)
	    return MOCHA_FALSE;
	SET_JUMP_OFFSET(CG_CODE(cg, beq), CG_OFFSET(cg) - beq);
	if (!OrExpr(mc, ts, cg))
	    return MOCHA_FALSE;
	SET_JUMP_OFFSET(CG_CODE(cg, jmp), CG_OFFSET(cg) - jmp);
    }
    return MOCHA_TRUE;
}

static MochaBoolean
AndExpr(MochaContext *mc, MochaTokenStream *ts, CodeGenerator *cg)
{
    ptrdiff_t bne, tmp, jmp;

    if (!BitOrExpr(mc, ts, cg))
	return MOCHA_FALSE;
    if (mocha_MatchToken(mc, ts, cg, TOK_AND)) {
	bne = mocha_Emit3(mc, cg, MOP_IFNE, 0, 0);
	tmp = mocha_Emit1(mc, cg, MOP_FALSE);
	jmp = mocha_Emit3(mc, cg, MOP_GOTO, 0, 0);
	if (bne < 0 || tmp < 0 || jmp < 0)
	    return MOCHA_FALSE;
	SET_JUMP_OFFSET(CG_CODE(cg, bne), CG_OFFSET(cg) - bne);
	if (!AndExpr(mc, ts, cg))
	    return MOCHA_FALSE;
	SET_JUMP_OFFSET(CG_CODE(cg, jmp), CG_OFFSET(cg) - jmp);
    }
    return MOCHA_TRUE;
}

static MochaBoolean
BitOrExpr(MochaContext *mc, MochaTokenStream *ts, CodeGenerator *cg)
{
    if (!BitXorExpr(mc, ts, cg))
	return MOCHA_FALSE;
    while (mocha_MatchToken(mc, ts, cg, TOK_BITOR)) {
	if (!BitXorExpr(mc, ts, cg) ||
	    mocha_Emit1(mc, cg, MOP_BITOR) < 0) {
	    return MOCHA_FALSE;
	}
    }
    return MOCHA_TRUE;
}

static MochaBoolean
BitXorExpr(MochaContext *mc, MochaTokenStream *ts, CodeGenerator *cg)
{
    if (!BitAndExpr(mc, ts, cg))
	return MOCHA_FALSE;
    while (mocha_MatchToken(mc, ts, cg, TOK_BITXOR)) {
	if (!BitAndExpr(mc, ts, cg) ||
	    mocha_Emit1(mc, cg, MOP_BITXOR) < 0) {
	    return MOCHA_FALSE;
	}
    }
    return MOCHA_TRUE;
}

static MochaBoolean
BitAndExpr(MochaContext *mc, MochaTokenStream *ts, CodeGenerator *cg)
{
    if (!EqExpr(mc, ts, cg))
	return MOCHA_FALSE;
    while (mocha_MatchToken(mc, ts, cg, TOK_BITAND)) {
	if (!EqExpr(mc, ts, cg) || mocha_Emit1(mc, cg, MOP_BITAND) < 0)
	    return MOCHA_FALSE;
    }
    return MOCHA_TRUE;
}

static MochaBoolean
EqExpr(MochaContext *mc, MochaTokenStream *ts, CodeGenerator *cg)
{
    MochaOp op;

    if (!RelExpr(mc, ts, cg))
	return MOCHA_FALSE;
    while (mocha_MatchToken(mc, ts, cg, TOK_EQOP)) {
	op = ts->token.u.op;
	if (!RelExpr(mc, ts, cg) || mocha_Emit1(mc, cg, op) < 0)
	    return MOCHA_FALSE;
    }
    return MOCHA_TRUE;
}

static MochaBoolean
RelExpr(MochaContext *mc, MochaTokenStream *ts, CodeGenerator *cg)
{
    MochaOp op;

    if (!ShiftExpr(mc, ts, cg))
	return MOCHA_FALSE;
    while (mocha_MatchToken(mc, ts, cg, TOK_RELOP)) {
	op = ts->token.u.op;
	if (!ShiftExpr(mc, ts, cg) || mocha_Emit1(mc, cg, op) < 0)
	    return MOCHA_FALSE;
    }
    return MOCHA_TRUE;
}

static MochaBoolean
ShiftExpr(MochaContext *mc, MochaTokenStream *ts, CodeGenerator *cg)
{
    MochaOp op;

    if (!AddExpr(mc, ts, cg))
	return MOCHA_FALSE;
    while (mocha_MatchToken(mc, ts, cg, TOK_SHOP)) {
	op = ts->token.u.op;
	if (!AddExpr(mc, ts, cg) || mocha_Emit1(mc, cg, op) < 0)
	    return MOCHA_FALSE;
    }
    return MOCHA_TRUE;
}

static MochaBoolean
AddExpr(MochaContext *mc, MochaTokenStream *ts, CodeGenerator *cg)
{
    MochaTokenType tt;

    if (!MulExpr(mc, ts, cg))
	return MOCHA_FALSE;
    while ((tt = mocha_GetToken(mc, ts, cg)) == TOK_PLUS || tt == TOK_MINUS) {
	if (!MulExpr(mc, ts, cg) ||
	    mocha_Emit1(mc, cg, (tt == TOK_PLUS) ? MOP_ADD : MOP_SUB) < 0) {
	    return MOCHA_FALSE;
	}
    }
    mocha_UngetToken(ts);
    return MOCHA_TRUE;
}

static MochaBoolean
MulExpr(MochaContext *mc, MochaTokenStream *ts, CodeGenerator *cg)
{
    MochaOp op;

    if (!UnaryExpr(mc, ts, cg))
	return MOCHA_FALSE;
    while (mocha_MatchToken(mc, ts, cg, TOK_MULOP)) {
	op = ts->token.u.op;
	if (!UnaryExpr(mc, ts, cg) || mocha_Emit1(mc, cg, op) < 0)
	    return MOCHA_FALSE;
    }
    return MOCHA_TRUE;
}

static MochaBoolean
UnaryExpr(MochaContext *mc, MochaTokenStream *ts, CodeGenerator *cg)
{
    MochaTokenType tt;
    MochaOp op;
    int argc;
    unsigned lineno;

    tt = mocha_GetToken(mc, ts, cg);
    switch (tt) {
      case TOK_UNARYOP:
      case TOK_MINUS:
	op = ts->token.u.op;
	if (!UnaryExpr(mc, ts, cg) || mocha_Emit1(mc, cg, op) < 0)
	    return MOCHA_FALSE;
	break;
      case TOK_INCOP:
	op = ts->token.u.op;
	if (!MemberExpr(mc, ts, cg) || mocha_Emit2(mc, cg, op, 0) < 0)
	    return MOCHA_FALSE;
	break;
      case TOK_NEW:
	if (!NameExpr(mc, ts, cg))
	    return MOCHA_FALSE;
	while ((tt = mocha_GetToken(mc, ts, cg)) == TOK_DOT) {
	    if (!NameExpr(mc, ts, cg) || mocha_Emit1(mc, cg, MOP_MEMBER) < 0)
		return MOCHA_FALSE;
	}
	argc = 0;
	if (tt != TOK_LP) {
	    mocha_UngetToken(ts);
	} else if (!mocha_MatchToken(mc, ts, cg, TOK_RP)) {
	    do {
		if (!AssignExpr(mc, ts, cg))
		    return MOCHA_FALSE;
		argc++;
	    } while (mocha_MatchToken(mc, ts, cg, TOK_COMMA));

	    /* (balance: */
	    MUST_MATCH_TOKEN(TOK_RP, "missing ) after constructor argument list");
	}
	if (mocha_Emit2(mc, cg, MOP_NEW, (MochaCode)argc) < 0)
	    return MOCHA_FALSE;
	break;

      default:
	mocha_UngetToken(ts);
	lineno = ts->lineno;
	if (!MemberExpr(mc, ts, cg))
	    return MOCHA_FALSE;

	/* Don't look across a newline boundary looking for a postfix incop. */
	if (ts->lineno == lineno && mocha_MatchToken(mc, ts, cg, TOK_INCOP)) {
	    op = ts->token.u.op;
	    if (mocha_Emit2(mc, cg, op, 1) < 0)
		return MOCHA_FALSE;
	}
    }
    return MOCHA_TRUE;
}

static MochaBoolean
MemberExpr(MochaContext *mc, MochaTokenStream *ts, CodeGenerator *cg)
{
    MochaToken token;
    MochaTokenType tt;
    int argc;
    MochaSymbol *sym;

    if (!PrimaryExpr(mc, ts, cg))
	return MOCHA_FALSE;
    token = ts->token;
    while ((tt = mocha_GetToken(mc, ts, cg)) != TOK_EOF) {
	if (tt == TOK_DOT) {
	    if (!NameExpr(mc, ts, cg))
		return MOCHA_FALSE;
	    if (mocha_Emit1(mc, cg, (mocha_PeekToken(mc,ts,cg) == TOK_ASSIGN)
				    ? MOP_LMEMBER : MOP_MEMBER) < 0) {
		return MOCHA_FALSE;
	    }
	} else if (tt == TOK_LB) {
	    if (!Expr(mc, ts, cg))
		return MOCHA_FALSE;
	    /* [balance: */
	    MUST_MATCH_TOKEN(TOK_RB, "missing ] in index expression");
	    if (mocha_Emit1(mc, cg, (mocha_PeekToken(mc,ts,cg) == TOK_ASSIGN)
				    ? MOP_LINDEX : MOP_INDEX) < 0) {
		return MOCHA_FALSE;
	    }
	} else if (tt == TOK_LP) {
	    argc = 0;
	    if (!mocha_MatchToken(mc, ts, cg, TOK_RP)) {
		do {
		    if (!AssignExpr(mc, ts, cg))
			return MOCHA_FALSE;
		    argc++;
		} while (mocha_MatchToken(mc, ts, cg, TOK_COMMA));

		/* (balance: */
		MUST_MATCH_TOKEN(TOK_RP, "missing ) after argument list");
	    }
	    if (mocha_Emit2(mc, cg, MOP_CALL, (MochaCode)argc) < 0)
		return MOCHA_FALSE;
	} else {
	    mocha_UngetToken(ts);

	    /*
	    ** Look for a name, number, or string immediately after a name.
	    ** Such a token juxtaposition is likely to be a "command-style"
	    ** function call.
	    */
	    if ((ts->flags & TSF_COMMAND) && token.type == TOK_NAME &&
		(IS_PRIMARY_TOKEN(tt) || tt == TOK_EOL)) {
		if (!mocha_LookupSymbol(mc, mc->staticLink->scope, token.u.atom,
					MLF_GET, &sym)) {
		    return MOCHA_FALSE;
		}
		if (sym &&
		    sym->type == SYM_PROPERTY &&
		    sym_datum(sym)->tag == MOCHA_FUNCTION &&
		    (tt != TOK_EOL ||
		     sym_datum(sym)->u.fun->nargs == 0)) {
		    argc = 0;

		    while (tt != TOK_EOL && tt != TOK_SEMI && tt != TOK_RP &&
			   tt != TOK_RC) {
			if (!AssignExpr(mc, ts, cg))
			    return MOCHA_FALSE;
			argc++;
			mocha_MatchToken(mc, ts, cg, TOK_COMMA);
			tt = mocha_PeekToken(mc, ts, cg);
		    }
		    if (mocha_Emit2(mc, cg, MOP_CALL, (MochaCode)argc) < 0)
			return MOCHA_FALSE;
		}
	    }
	    break;
	}
	token = ts->token;
    }
    return MOCHA_TRUE;
}

static MochaBoolean
PrimaryExpr(MochaContext *mc, MochaTokenStream *ts, CodeGenerator *cg)
{
    MochaTokenType tt;
    MochaAtomNumber atomIndex;

    tt = mocha_GetToken(mc, ts, cg);
    switch (tt) {
      case TOK_LP:
	if (!Expr(mc, ts, cg))
	    return MOCHA_FALSE;
	/* (balance: */
	MUST_MATCH_TOKEN(TOK_RP, "missing ) in parenthetical");

	/* XXX optimize to annotate expr's last instruction and avoid a NOP */
	if (mocha_NewSourceNote(mc, cg, SRC_PAREN) < 0 ||
	    mocha_Emit1(mc, cg, MOP_NOP) < 0) {
	    return MOCHA_FALSE;
	}
	break;

      case TOK_NAME:
	mocha_UngetToken(ts);
	return NameExpr(mc, ts, cg);

      case TOK_NUMBER:
	if (ts->token.u.atom->fval == 0) {
	    if (mocha_Emit1(mc, cg, MOP_ZERO) < 0)
		return MOCHA_FALSE;
	} else if (ts->token.u.atom->fval == 1) {
	    if (mocha_Emit1(mc, cg, MOP_ONE) < 0)
		return MOCHA_FALSE;
	} else {
	    atomIndex = mocha_IndexAtom(mc, ts->token.u.atom, cg);
	    EMIT_CONST_ATOM_OP(MOP_NUMBER, atomIndex);
	}
	break;

      case TOK_STRING:
	atomIndex = mocha_IndexAtom(mc, ts->token.u.atom, cg);
	EMIT_CONST_ATOM_OP(MOP_STRING, atomIndex);
	break;

      case TOK_PRIMARY:
	if (mocha_Emit1(mc, cg, ts->token.u.op) < 0)
	    return MOCHA_FALSE;
	break;

      case TOK_RESERVED:
	mocha_ReportSyntaxError(mc, ts, "identifier is a reserved word");
	return MOCHA_FALSE;

      default:
	mocha_ReportSyntaxError(mc, ts, IS_PRIMARY_TOKEN(tt)
					? "missing operand in expression"
					: "syntax error");
	return MOCHA_FALSE;
    }
    return MOCHA_TRUE;
}

static MochaBoolean
NameExpr(MochaContext *mc, MochaTokenStream *ts, CodeGenerator *cg)
{
    MochaTokenType tt;
    MochaAtomNumber atomIndex;

    tt = mocha_GetToken(mc, ts, cg);
    switch (tt) {
      case TOK_NAME:
	atomIndex = mocha_IndexAtom(mc, ts->token.u.atom, cg);
	EMIT_CONST_ATOM_OP(MOP_NAME, atomIndex);
	break;
      case TOK_PRIMARY:
	if (ts->token.u.op == MOP_THIS) {
	    if (mocha_Emit1(mc, cg, MOP_THIS) < 0)
		return MOCHA_FALSE;
	    break;
	}
	/* FALL THROUGH */
      default:
	mocha_ReportSyntaxError(mc, ts, "missing name in expression");
	return MOCHA_FALSE;
    }
    return MOCHA_TRUE;
}