lpmoo-1.2/etc/
lpmoo-1.2/mudlib/
lpmoo-1.2/mudlib/etc/
lpmoo-1.2/mudlib/include/
lpmoo-1.2/mudlib/include/moo/
lpmoo-1.2/mudlib/lpc/
lpmoo-1.2/mudlib/std/auto/
lpmoo-1.2/mudlib/std/bfuns/
/*
 * NAME:	compiler.c
 * DESCRIPTION:	generate LPC code from a MOO AST
 */

# define DEBUG  0

inherit "/std/string";

# if DEBUG
inherit "/std/vartext";
# endif

# include <objects.h>
# include <moo/tokens.h>
# include <moo/verbinfo.h>
# include <dgd/limits.h>

# define PROTO(elt)		static void c_##elt(mixed *ast)
# define COMPILE(ast, elt)	c_##elt(ast)
# define LINENO(num)		emit("LINENO(" + (string) (num) + ");")

# define VF_DEFINED		0x80000000
# define VAR_DEFINED(ref)	(ref & VF_DEFINED)
# define VAR_ID(ref)		(ref & ~VF_DEFINED)

# define COMMA			", "
# define NL			"\n"

private object optimizer, btable;
private int lineno, max_var, jumpno, sp_overhead;
private int fork_id, fork_cur, nest;
private mapping vars;
private string **forked;

PROTO(program);
PROTO(if);
PROTO(for);
PROTO(while);
PROTO(fork);
PROTO(return);
PROTO(expression);

/*
 * NAME:	create()
 * DESCRIPTION:	initialize object
 */
static
void create(void)
{ optimizer = load_object(OPTIMIZER); }

/*
 * NAME:	main()
 * DESCRIPTION:	compile MOO AST -> LPC directly to a file
 */
void main(mixed *ast, string file)
{
  int i, sz;

  lineno = max_var = jumpno = sp_overhead = fork_cur = fork_id = nest = 0;
  forked = ({ });

  if (! btable)
    btable = load_object(BTABLE);

  vars = ([
	    "args"	: V_ARGS    | VF_DEFINED,
	    "argstr"	: V_ARGSTR  | VF_DEFINED,
	    "buf"	: V_BUF     | VF_DEFINED,
	    "caller"	: V_CALLER  | VF_DEFINED,
	    "dobj"	: V_DOBJ    | VF_DEFINED,
	    "dobjstr"	: V_DOBJSTR | VF_DEFINED,
	    "err"	: V_ERR     | VF_DEFINED,
	    "float"	: V_FLOAT   | VF_DEFINED,
	    "iobj"	: V_IOBJ    | VF_DEFINED,
	    "iobjstr"	: V_IOBJSTR | VF_DEFINED,
	    "list"	: V_LIST    | VF_DEFINED,
	    "num"	: V_NUM     | VF_DEFINED,
	    "obj"	: V_OBJ     | VF_DEFINED,
	    "player"	: V_PLAYER  | VF_DEFINED,
	    "prepstr"	: V_PREPSTR | VF_DEFINED,
	    "str"	: V_STR     | VF_DEFINED,
	    "table"	: V_TABLE   | VF_DEFINED,
	    "this"	: V_THIS    | VF_DEFINED,
	    "verb"	: V_VERB    | VF_DEFINED,
	 ]);

  if (query_editor(this_object()) == "insert")
    editor(".");

  ast = optimizer->main(ast);

  editor("e! " + file + ".c");
  editor("%d");
  editor("0a");

  editor("MOOVAL main(JS_PROTO) { int sp; MOOVAL *vars;" +
	 " JS_BEGIN; ref(); RESET();");
  COMPILE(ast, program);
  editor("return STORE(), del(), NUM(0); JS_END; }");

  for (i = 0, sz = sizeof(forked); i < sz; ++i)
    {
      int j, sz;

      editor("MOOVAL fork" + (string) (i + 1) +
	     "(JS_PROTO) { int sp; MOOVAL *vars;" +
	     " JS_BEGIN; RESET();");

      for (j = 0, sz = sizeof(forked[i]); j < sz; ++j)
	editor(forked[i][j]);

      editor("del(); JS_END; }");
    }
  forked = 0;

  editor(".");

  if (max_var)
    {
      string varmap;

      editor("1a");
      editor("VARS += allocate(" + (string) max_var + "); PUT_VARS();");

      for (varmap = ""; max_var--; varmap += "0");

      editor("info[I_VARDEFS] = \"" + varmap + "\";");
      editor(".");
    }
  vars = 0;

  editor("0a");
  editor("# include <moo/runtime.h>");
  editor(".");

  editor("w!");
}

/*
 * NAME:	var_ref()
 * DESCRIPTION:	return a (new) variable index
 */
private
int var_ref(string varname)
{
  int ref;

  varname = tolower(varname);
  return (ref = vars[varname]) ? ref :
    (vars[varname] = VAR_OFFSET + (max_var++));
}

/*
 * NAME:	var_assigned()
 * DESCRIPTION:	note that a variable has been assigned
 */
private
void var_assigned(string varname)
{
  if (nest == 0)
    vars[tolower(varname)] |= VF_DEFINED;
}

/*
 * NAME:	emit()
 * DESCRIPTION:	handle fork contexts
 */
private
void emit(string line)
{
  string *lines;

  lines = explode(line, "\n");

  if (fork_cur)
    forked[fork_cur - 1] += lines;
  else
    {
      int i, sz;

      for (i = 0, sz = sizeof(lines); i < sz; ++i)
	editor(lines[i]);
    }
}

private string bool_expr(mixed *ast);
private string simple_expr(mixed *ast);

/*
 * NAME:	bfun_args()
 * DESCRIPTION:	compile simple builtin function arguments
 */
private
string bfun_args(string func, mixed *ast)
{
  string argstr;
  int i, sz;

  argstr = "";

  for (i = 1, sz = sizeof(ast); i < sz; ++i)
    {
      if (TAG(ast[i]) == TOK_SPLICE)
	return "*";

      argstr += COMMA + simple_expr(ast[i]);
    }

  --sz;
  if (sz < btable->minargs(func) ||
      ((i = btable->maxargs(func)) >= 0 && sz > i))
    return 0;

  return argstr;
}

/*
 * NAME:	simple_expr()
 * DESCRIPTION:	return code for a simple expression
 */
private
string simple_expr(mixed *ast)
{
  int ref;

  switch (TAG(ast))
    {
    case TOK_LIT_NUM:
      if (ast[1] == INT_MIN)
	return "NUM(" + (string) (INT_MIN + 1) + " - 1)";
      else
	return "NUM(" + ast[1] + ")";

    case TOK_LIT_STR:
      return "STR(\"" + ast[1] + "\")";

    case TOK_LIT_OBJ:
      if (ast[1] == INT_MIN)
	return "OBJ(" + (string) (INT_MIN + 1) + " - 1)";
      else
	return "OBJ(" + ast[1] + ")";

    case TOK_LIT_ERR:
      return "ERR(" + ast[1] + ")";

    case TOK_LIT_FLT:
      return "FLT(" + ast[1] + ")";

    case TOK_IDENTIFIER:
      ref = var_ref(ast[1]);
      if (VAR_DEFINED(ref))
	return "VARS[" + (string) VAR_ID(ref) + "]";
      else
	return "VAR(" + (string) VAR_ID(ref) + ")";

    case TOK_PLUS:
      return "KFUN2(plus, " +
	simple_expr(ast[1]) + COMMA + NL +
	simple_expr(ast[2]) + ")";

    case TOK_MINUS:
      return "KFUN2(minus, " +
	simple_expr(ast[1]) + COMMA + NL +
	simple_expr(ast[2]) + ")";

    case TOK_TIMES:
      return "KFUN2(times, " +
	simple_expr(ast[1]) + COMMA + NL +
	simple_expr(ast[2]) + ")";

    case TOK_DIVIDE:
      return "KFUN2(divide, " +
	simple_expr(ast[1]) + COMMA + NL +
	simple_expr(ast[2]) + ")";

    case TOK_PERCENT:
      return "KFUN2(modulus, " +
	simple_expr(ast[1]) + COMMA + NL +
	simple_expr(ast[2]) + ")";

    case TOK_DOT:
      return "KFUN2(getprop, " +
	simple_expr(ast[1]) + COMMA + NL +
	simple_expr(ast[2]) + ")";

    case TOK_EQUAL:
      return "(EQUALP(" +
	simple_expr(ast[1]) + COMMA + NL +
	simple_expr(ast[2]) + ") ? NUM(1) : NUM(0))";

    case TOK_NEQUAL:
      return "(EQUALP(" +
	simple_expr(ast[1]) + COMMA + NL +
	simple_expr(ast[2]) + ") ? NUM(0) : NUM(1))";

    case TOK_LESS:
      return "KFUN2(less, " +
	simple_expr(ast[1]) + COMMA + NL +
	simple_expr(ast[2]) + ")";

    case TOK_LSEQUAL:
      return "KFUN2(lsequal, " +
	simple_expr(ast[1]) + COMMA + NL +
	simple_expr(ast[2]) + ")";

    case TOK_GREATER:
      return "KFUN2(greater, " +
	simple_expr(ast[1]) + COMMA + NL +
	simple_expr(ast[2]) + ")";

    case TOK_GREQUAL:
      return "KFUN2(grequal, " +
	simple_expr(ast[1]) + COMMA + NL +
	simple_expr(ast[2]) + ")";

    case TOK_IN:
      return "KFUN2(in, " +
	simple_expr(ast[1]) + COMMA + NL +
	simple_expr(ast[2]) + ")";

# if 0
    case TOK_ASSOC:
      return "KFUN2(bound, " +
	simple_expr(ast[1]) + COMMA + NL +
	simple_expr(ast[2]) + ")";
# endif

    case TOK_LBRACKET:
      if (TAG(ast[2]) == TOK_RANGE)
	return "KFUN3(range, " +
	  simple_expr(ast[1]) + COMMA + NL +
	  simple_expr(ast[2][1]) + COMMA + NL +
	  simple_expr(ast[2][2]) + ")";
      else
	return "KFUN2(index, " +
	  simple_expr(ast[1]) + COMMA + NL +
	  simple_expr(ast[2]) + ")";

    case TOK_BANG:
      return "(" + bool_expr(ast[1]) + " ? NUM(0) : NUM(1))";

    case TOK_U_MINUS:
      return "KFUN1(negate, " + simple_expr(ast[1]) + ")";

    case TOK_QUESTION:
      return "(" + bool_expr(ast[1]) + "\n? " +
	simple_expr(ast[2]) + "\n: " + simple_expr(ast[3]) + ")";

    case TOK_AND:
      return "(TRUTHOF(RET = " + simple_expr(ast[1]) + ")\n? " +
	simple_expr(ast[2]) + " : RET)";

    case TOK_OR:
      return "(TRUTHOF(RET = " + simple_expr(ast[1]) + ")\n? " +
	"RET : " + simple_expr(ast[2]) + ")";

    case TOK_LPAREN:
      {
	string func, args;

	func = ast[1];

	switch (args = bfun_args(func, ast[2]))
	  {
	  case 0:  /* incorrect # args */
	    return "(" + simple_expr(ast[2]) + ", RAISE(E_ARGS))";
	  case "*":  /* spliced arguments */
	    return "(argcheck(RET = " + simple_expr(ast[2]) + COMMA +
	      (string) btable->minargs(func) + COMMA +
	      (string) btable->maxargs(func) + ")\n? " +
	      btable->invokation(func, 0) + COMMA +
	      "RET...) : RAISE(E_ARGS))";
	  default:
	    return btable->invokation(func, 0) + args + ")";
	  }
      }

    case TOK_LIST:
      {
	string text, splices;
	int i, sz;

	sz = sizeof(ast);

	if (sz == 1)
	  return "LST(LNEW())";

	for (text = splices = "", i = 1; i < sz; ++i)
	  {
	    if (TAG(ast[i]) == TOK_SPLICE)
	      {
		text    += (i > 1 ? COMMA + NL : "") + simple_expr(ast[i][1]);
		splices += (string) (i - 1) + COMMA;
	      }
	    else
	      text += (i > 1 ? COMMA + NL : "") + simple_expr(ast[i]);
	  }

	if (strlen(splices))
	  return "slist(info, ({ " + splices + "}), " + text + ")";
	else
	  return "LST( ({ " + text + " }) )";
      }

    case TOK_TABLE:
      {
	string text, splices;
	int i, sz, spcount;

	if (sizeof(ast) == 1)
	  return "TBL(TNEW())";

	spcount = 0;
	for (text = splices = "", i = 1, sz = sizeof(ast); i < sz; ++i)
	  {
	    if (TAG(ast[i]) == TOK_SPLICE)
	      {
		text    += COMMA + NL + simple_expr(ast[i][1]) + COMMA + "0";
		splices += "@", ++spcount;
	      }
	    else
	      {
		text    += COMMA + NL + simple_expr(ast[i][1]) + COMMA +
		                        simple_expr(ast[i][2]);
		splices += ".";
	      }
	  }

	return "table(info, " +
	  (spcount ? "\"" + splices + "\"" : "0") + text + ")";
      }

    case TOK_AMBAGGR:
      {
	string text;
	int i, sz;

	for (text = "", i = 1, sz = sizeof(ast); i < sz; ++i)
	  text += COMMA + NL + simple_expr(ast[i][1]);

	return "ambaggr(info" + text + ")";
      }

    case TOK_BUFFER:
      {
	string text, splices;
	int i, sz, spcount;

	sz = sizeof(ast);

	if (sz == 1)
	  return "BUF(\"\")";

	spcount = 0;
	for (text = splices = "", i = 1; i < sz; ++i)
	  {
	    if (TAG(ast[i]) == TOK_SPLICE)
	      {
		text    += COMMA + NL + simple_expr(ast[i][1]);
		splices += "@", ++spcount;
	      }
	    else
	      {
		text    += COMMA + NL + simple_expr(ast[i]);
		splices += ".";
	      }
	  }

	return "buffer(info, " +
	  (spcount ? "\"" + splices + "\"" : "0") + text + ")";
      }

    case TOK_ASSIGN:
      {
	mixed *frame;
	int icount, range;
	string rvalue, indices;

	rvalue = simple_expr(ast[2]);

	frame = ast[1];
	icount = range = 0;

	if (TAG(frame) == TOK_LBRACKET && TAG(frame[2]) == TOK_RANGE)
	  {
	    indices = simple_expr(frame[2][1]) + COMMA +
	              simple_expr(frame[2][2]);
	    range = 1, icount = 2;
	    frame = frame[1];
	  }
	else
	  indices = "";

	while (TAG(frame) == TOK_LBRACKET)
	  {
	    indices = simple_expr(frame[2]) +
	      (strlen(indices) ? COMMA : "") + indices;
	    ++icount;
	    frame = frame[1];
	  }

	indices = "LST( ({ " + indices + " }) )";

	if (TAG(frame) == TOK_IDENTIFIER)
	  {
	    int var;

	    var = VAR_ID(var_ref(frame[1]));
	    var_assigned(frame[1]);
	    if (icount)
	      return "avari(info, " + (string) var + COMMA + (string) range +
		COMMA + indices + COMMA + rvalue + ")";
	    else
	      return "avar(info, " + (string) var + COMMA + rvalue + ")";
	  }
	else	/* TOK_DOT */
	  {
	    string obj, prop;

	    obj  = simple_expr(frame[1]);
	    prop = simple_expr(frame[2]);

	    if (icount)
	      return "apropi(info, " + obj + COMMA + prop + COMMA +
		(string) range + COMMA + indices + COMMA + rvalue + ")";
	    else
	      return "aprop(info, " + obj + COMMA + prop + COMMA +
		rvalue + ")";
	  }
      }

    default:
# if DEBUG
      error("Unknown token: " + var2str(ast));
# else
      error("Unknown token: " + TAG(ast));
# endif
    }
}

/*
 * NAME:	bool_expr()
 * DESCRIPTION:	compile a boolean expression (evaluates to LPC true/false)
 */
private
string bool_expr(mixed *ast)
{
  switch (TAG(ast))
    {
    case TOK_LIT_NUM:
      return ast[1] ? "1" : "0";

    case TOK_LIT_STR:
      return strlen(ast[1]) ? "1" : "0";

    case TOK_LIT_OBJ:
    case TOK_LIT_ERR:
      return "0";

    case TOK_LIT_FLT:
      return (float) ast[1] != 0.0 ? "1" : "0";

    case TOK_EQUAL:
      return "EQUALP(" + simple_expr(ast[1]) + COMMA + NL +
	                 simple_expr(ast[2]) + ")";

    case TOK_NEQUAL:
      return "(! EQUALP(" + simple_expr(ast[1]) + COMMA + NL +
	                    simple_expr(ast[2]) + "))";

    case TOK_BANG:
      return "(! " + bool_expr(ast[1]) + ")";

    case TOK_QUESTION:
      return "(" + bool_expr(ast[1]) + " ? " +
	bool_expr(ast[2]) + " : " + bool_expr(ast[3]) + ")";

    case TOK_AND:
      return "(" + bool_expr(ast[1]) + " ? " + bool_expr(ast[2]) + " : 0)";

    case TOK_OR:
      return "(" + bool_expr(ast[1]) + " ? 1 : " + bool_expr(ast[2]) + ")";

    default:
      return "TRUTHOF(" + simple_expr(ast) + ")";
    }
}

/*
 * NAME:	stack_kfun2()
 * DESCRIPTION:	compile a 2-arg kfun (at least 1 arg is non-simple)
 */
private
void stack_kfun2(string kfun, mixed *ast1, mixed *ast2)
{
  string arg1, arg2;
  int offset;

  COMPILE(ast1, expression), offset = 1, arg1 = "stack[SP]";

  if (SIMPLE(ast2))
    arg2 = simple_expr(ast2);
  else
    COMPILE(ast2, expression),
    arg2 = "stack[SP + " + (string) (offset++) + "]";

  emit("stack[SP -= " + (string) (offset - 1) + "] = KFUN2(" + kfun +
       COMMA + arg1 + COMMA + arg2 + ");");
}

/*
 * NAME:	stack_kfun3()
 * DESCRIPTION:	compile a 3-arg kfun (at least 1 arg is non-simple)
 */
private
void stack_kfun3(string kfun, mixed *ast1, mixed *ast2, mixed *ast3)
{
  string arg1, arg2, arg3;
  int offset;

  COMPILE(ast1, expression), offset = 1, arg1 = "stack[SP]";

  if (SIMPLE(ast2) && SIMPLE(ast3))
    arg2 = simple_expr(ast2);
  else
    COMPILE(ast2, expression),
    arg2 = "stack[SP + " + (string) (offset++) + "]";

  if (SIMPLE(ast3))
    arg3 = simple_expr(ast3);
  else
    COMPILE(ast3, expression),
    arg3 = "stack[SP + " + (string) (offset++) + "]";

  emit("stack[SP -= " + (string) (offset - 1) + "] = KFUN3(" + kfun +
       COMMA + arg1 + COMMA + arg2 + COMMA + arg3 + ");");
}

PROTO(program)
{
  int i, sz, tag;

  if (DEBUG)
    emit("/* program */");

  /* program == ({ statement, statement, ... }) */

  for (i = 0, sz = sizeof(ast); i < sz; ++i)
    {
      switch (tag = TAG(ast[i]))
	{
	case TOK_IF:
	  COMPILE(ast[i], if);
	  break;
	case TOK_FOR:
	  COMPILE(ast[i], for);
	  break;
	case TOK_WHILE:
	  COMPILE(ast[i], while);
	  break;
	case TOK_FORK:
	  COMPILE(ast[i], fork);
	  break;
	case TOK_RETURN:
	  COMPILE(ast[i], return);
	  break;

	case TOK_NOP:
	case TOK_LIT_NUM:  /* ignore expressions with no side-effects */
	case TOK_LIT_STR:
	case TOK_LIT_ERR:
	case TOK_LIT_OBJ:
	case TOK_LIT_FLT:
	  ++lineno;
	  break;

	default:  /* expression */
	  LINENO(++lineno);
	  if (SIMPLE(ast[i]))
	    emit(simple_expr(ast[i]) + ";");
	  else
	    {
	      COMPILE(ast[i], expression);
	      emit("--SP;");
	    }
	}
    }
}

PROTO(expression)
{
  if (DEBUG)
    emit("/* expression (" + TAG(ast) + ") */");

  if (SIMPLE(ast))
    {
      emit("PUSH(" + simple_expr(ast) + ");");
      return;
    }

  switch (TAG(ast))
    {
    case TOK_LIST:
      {
	string text, splices;
	int i, sz;

	sz = sizeof(ast);

	if (sz == 2 && TAG(ast[1]) != TOK_SPLICE)
	  {
	    COMPILE(ast[1], expression);
	    emit("stack[SP] = LST( ({ stack[SP] }) );");
	    return;
	  }

	for (splices = "", i = 1; i < sz; ++i)
	  {
	    if (TAG(ast[i]) == TOK_SPLICE)
	      {
		COMPILE(ast[i][1], expression);
		splices += (string) (i - 1) + COMMA;
	      }
	    else
	      COMPILE(ast[i], expression);
	  }

	sz  -= 2;
	text = "stack[SP .. SP + " + (string) sz + "]";

	if (strlen(splices))
	  text = "slist(info, ({ " + splices + "}), " + text + "...)";
	else
	  text = "LST(" + text + ")";

	emit("SP -= " + (string) sz + "; stack[SP] = " + text + ";");
	return;
      }

    case TOK_TABLE:
      {
	string splices;
	int i, sz, spcount;

	spcount = 0;
	for (splices = "", i = 1, sz = sizeof(ast); i < sz; ++i)
	  {
	    if (TAG(ast[i]) == TOK_SPLICE)
	      {
		COMPILE(ast[i][1], expression);
		emit("PUSH(NUM(0));");
		splices += "@", ++spcount;
	      }
	    else
	      {
		COMPILE(ast[i][1], expression);
		COMPILE(ast[i][2], expression);
		splices += ".";
	      }
	  }

	sz = (sz - 1) * 2 - 1;
	emit("SP -= " + (string) sz + "; stack[SP] = table(info, " +
	     (spcount ? "\"" + splices + "\"" : "0") +
	     ", stack[SP .. SP + " + (string) sz + "]...);");
	return;
      }

    case TOK_AMBAGGR:
      {
	int i, sz;

	for (i = 1, sz = sizeof(ast); i < sz; ++i)
	  COMPILE(ast[i][1], expression);

	sz -= 2;
	emit("SP -= " + (string) sz +
	     "; stack[SP] = ambaggr(info, stack[SP .. SP + " + (string) sz +
	     "]...);");
	return;
      }

    case TOK_BUFFER:
      {
	string splices;
	int i, sz, spcount;

	sz = sizeof(ast);

	spcount = 0;
	for (splices = "", i = 1; i < sz; ++i)
	  {
	    if (TAG(ast[i]) == TOK_SPLICE)
	      {
		COMPILE(ast[i][1], expression);
		splices += "@", ++spcount;
	      }
	    else
	      {
		COMPILE(ast[i], expression);
		splices += ".";
	      }
	  }

	sz  -= 2;

	emit("SP -= " + (string) sz + "; stack[SP] = buffer(info, " +
	     (spcount ? "\"" + splices + "\"" : "0") +
	     ", stack[SP .. SP + " + (string) sz + "]...);");
	return;
      }

    case TOK_PLUS:
      stack_kfun2("plus", ast[1], ast[2]);
      return;

    case TOK_MINUS:
      stack_kfun2("minus", ast[1], ast[2]);
      return;

    case TOK_TIMES:
      stack_kfun2("times", ast[1], ast[2]);
      return;

    case TOK_DIVIDE:
      stack_kfun2("divide", ast[1], ast[2]);
      return;

    case TOK_PERCENT:
      stack_kfun2("modulus", ast[1], ast[2]);
      return;

    case TOK_DOT:
      stack_kfun2("getprop", ast[1], ast[2]);
      return;

    case TOK_ASSIGN:
      {
	mixed *frame, *list;
	int icount, range;

	list = ({ ast[2] });  /* rvalue */

	frame  = ast[1];
	icount = range = 0;

	if (TAG(frame) == TOK_LBRACKET && TAG(frame[2]) == TOK_RANGE)
	  {
	    list += ({ frame[2][2], frame[2][1] });
	    range = 1, icount = 2;
	    frame = frame[1];
	  }

	while (TAG(frame) == TOK_LBRACKET)
	  {
	    list += ({ frame[2] });
	    ++icount;
	    frame = frame[1];
	  }

	if (TAG(frame) == TOK_IDENTIFIER)
	  {
	    int i, var;

	    for (i = sizeof(list); i--; )
	      COMPILE(list[i], expression);

	    var = VAR_ID(var_ref(frame[1]));
	    if (icount)
	      emit("SP -= " + (string) icount + "; " +
		   "stack[SP] = avari(info" + COMMA +
		   (string) var + COMMA + (string) range + COMMA +
		   "stack[SP .. SP + " + (string) (icount - 1) + "]" + COMMA +
		   "stack[SP + " + (string) icount + "]);");
	    else
	      emit("avar(info" + COMMA + (string) var + COMMA + "stack[SP]);");

	    var_assigned(frame[1]);
	  }
	else	/* TOK_DOT */
	  {
	    int i;

	    COMPILE(frame[1], expression);  /* obj */
	    COMPILE(frame[2], expression);  /* prop */

	    for (i = sizeof(list); i--; )
	      COMPILE(list[i], expression);

	    if (icount)
	      emit("SP -= " + (string) (icount + 2) + "; " +
		   "stack[SP] = apropi(info, stack[SP], stack[SP + 1], " +
		   (string) range + COMMA + "stack[SP + 2 .. SP + " +
		   (string) (icount - 1 + 2) + "]" + COMMA + "stack[SP + " +
		   (string) (icount + 2) + "]);");
	    else
	      emit("SP -= 2; " +
		   "stack[SP] = aprop(info" + COMMA +
		   "stack[SP]" + COMMA +
		   "stack[SP + 1]" + COMMA +
		   "stack[SP + 2]);");
	  }
	return;
      }

    case TOK_COLON:
      {
	mixed *expr;
	string obj, verb, args;
	int offset;

	if (SIMPLE(expr = ast[1]) && SIMPLE(ast[2][0]) && SIMPLE(ast[2][1]))
	  obj = simple_expr(expr);
	else
	  COMPILE(expr, expression),
	  obj = "stack[SP + 1]", offset = 1;

	if (SIMPLE(expr = ast[2][0]) && SIMPLE(ast[2][1]))
	  verb = simple_expr(expr);
	else
	  COMPILE(expr, expression),
	  verb = "stack[SP + " + (string) (++offset) + "]";

	if (SIMPLE(expr = ast[2][1]))
	  args = simple_expr(expr);
	else
	  COMPILE(expr, expression),
	  args = "stack[SP + " + (string) (++offset) + "]";

	emit("SP -= " + (string) offset + "; JS_PREP(" + (string) (++jumpno) +
	     "); RET = verbcall(JS_DATA(" + (string) jumpno + ")" + COMMA +
	     obj + COMMA + verb + COMMA + NL + args + "); JS_END; PUSH(RET);");
	return;
      }

    case TOK_LPAREN:
      {
	string func, args;

	func = ast[1];

	if (SIMPLE(ast[2]))
	  {
	    switch (args = bfun_args(func, ast[2]))
	      {
	      case 0:  /* incorrect # args */
		emit(simple_expr(ast[2]) + ", PUSH(RAISE(E_ARGS));");
		return;
	      case "*":  /* spliced args */
		emit("if (! argcheck(RET = " + simple_expr(ast[2]) + COMMA +
		     (string) btable->minargs(func) + COMMA +
		     (string) btable->maxargs(func) + "))");
		emit("PUSH(RAISE(E_ARGS)); else");
		args = COMMA + "RET...";
		break;
	      }
	  }
	else
	  {
	    COMPILE(ast[2], expression);
	    emit("if (! argcheck(RET = POP(), " +
		 (string) btable->minargs(func) + COMMA +
		 (string) btable->maxargs(func) + "))");
	    emit("PUSH(RAISE(E_ARGS)); else");
	    args = COMMA + "RET...";
	  }

	emit("{ JS_PREP(" + (string) (++jumpno) +
	     "); RET = " + btable->invokation(func, jumpno) + args +
	     "); JS_END; PUSH(RET); }");
	return;
      }

    case TOK_LBRACKET:
      if (TAG(ast[2]) == TOK_RANGE)
	stack_kfun3("range", ast[1], ast[2][1], ast[2][2]);
      else
	stack_kfun2("index", ast[1], ast[2]);
      return;

    case TOK_BANG:
      COMPILE(ast[1], expression);
      emit("stack[SP] = TRUTHOF(stack[SP]) ? NUM(0) : NUM(1);");
      return;

    case TOK_U_MINUS:
      COMPILE(ast[1], expression);
      emit("stack[SP] = KFUN1(negate, stack[SP]);");
      return;

    case TOK_EQUAL:
      stack_kfun2("equal", ast[1], ast[2]);
      return;

    case TOK_NEQUAL:
      stack_kfun2("nequal", ast[1], ast[2]);
      return;

    case TOK_LESS:
      stack_kfun2("less", ast[1], ast[2]);
      return;

    case TOK_LSEQUAL:
      stack_kfun2("lsequal", ast[1], ast[2]);
      return;

    case TOK_GREATER:
      stack_kfun2("greater", ast[1], ast[2]);
      return;

    case TOK_GREQUAL:
      stack_kfun2("grequal", ast[1], ast[2]);
      return;

    case TOK_IN:
      stack_kfun2("in", ast[1], ast[2]);
      return;

# if 0
    case TOK_ASSOC:
      stack_kfun2("bound", ast[1], ast[2]);
      return;
# endif

    case TOK_QUESTION:
      if (SIMPLE(ast[1]))
	{
	  emit("if (" + bool_expr(ast[1]) + ") {");
	}
      else
	{
	  COMPILE(ast[1], expression);
	  emit("if (TRUTHOF(POP())) {");
	}
      COMPILE(ast[2], expression);
      emit("} else {");
      COMPILE(ast[3], expression);
      emit("}");
      return;

    case TOK_AND:
      COMPILE(ast[1], expression);
      emit("if (TRUTHOF(stack[SP])) {");
      if (SIMPLE(ast[2]))
	{
	  emit("stack[SP] = " + simple_expr(ast[2]) + ";");
	}
      else
	{
	  emit("--SP;");
	  COMPILE(ast[2], expression);
	}
      emit("}");
      return;

    case TOK_OR:
      COMPILE(ast[1], expression);
      emit("if (! TRUTHOF(stack[SP])) {");
      if (SIMPLE(ast[2]))
	{
	  emit("stack[SP] = " + simple_expr(ast[2]) + ";");
	}
      else
	{
	  emit("--SP;");
	  COMPILE(ast[2], expression);
	}
      emit("}");
      return;

    default:
# if DEBUG
      error("Unknown token: " + var2str(ast));
# else
      error("Unknown token: " + TAG(ast));
# endif
    }
}

PROTO(if)
{
  int i, sz, close;
  string closers;

  if (DEBUG)
    emit("/* if */");

  LINENO(++lineno);
  if (SIMPLE(ast[1]))
    emit("if (" + bool_expr(ast[1]) + ") {");
  else
    {
      COMPILE(ast[1], expression);
      emit("if (TRUTHOF(POP())) {");
    }
  ++nest, COMPILE(ast[2], program), --nest;

  i = 3; sz = sizeof(ast);
  while (i < sz && ast[i] == TOK_ELSEIF)
    {
      emit("} else {");
      LINENO(++lineno);
      if (SIMPLE(ast[i + 1]))
	emit("if (" + bool_expr(ast[i + 1]) + ") {");
      else
	{
	  COMPILE(ast[i + 1], expression);
	  emit("if (TRUTHOF(POP())) {");
	}
      ++nest, COMPILE(ast[i + 2], program), --nest;
      i += 3, ++close;
    }

  if (i < sz && ast[i] == TOK_ELSE)
    {
      emit("} else {");
      ++lineno;
      ++nest, COMPILE(ast[i + 1], program), --nest;
    }

  for (closers = "}"; close--; closers += " }");
  emit(closers);

  ++lineno;
}

PROTO(for)
{
  string key, value;
  int key_var, value_var, table_iterp;

  if (DEBUG)
    emit("/* for */");

  if (arrayp(ast[1]))
    {
      table_iterp = 1;

      key   = ast[1][0];
      value = ast[1][1];

      key_var   = VAR_ID(var_ref(key));
      value_var = VAR_ID(var_ref(value));
    }
  else
    {
      key     = ast[1];
      key_var = VAR_ID(var_ref(key));
    }

  sp_overhead += 3;

  if (TAG(ast[2]) == TOK_RANGE)
    {
      LINENO(++lineno);

      COMPILE(ast[2][1], expression);
      COMPILE(ast[2][2], expression);

      emit("if (! NUMP(stack[SP - 1]) || ! NUMP(stack[SP]))" +
	   " { SP -= 2; RAISE(E_TYPE); } else {" +
	   " ++SP; stack[SP] = stack[SP - 2];");
      emit("while (stack[SP] <= stack[SP - 1]) {" +
	   " avar(info" + COMMA + (string) key_var + COMMA + "stack[SP]);");
      var_assigned(key);
      ++nest, COMPILE(ast[3], program), --nest;
      emit("++stack[SP]; } SP -= 3; }");
    }
  else
    {
      LINENO(++lineno);
      COMPILE(ast[2][1], expression);

      if (table_iterp)
	{
	  emit("if (! TBLP(stack[SP]))" +
	       " { --SP; RAISE(E_TYPE); } else {" +
	       " stack[SP] = map_values(TBLVAL(stack[SP])); PUSH(0);");
	  emit("while (stack[SP] < sizeof(stack[SP - 1])) { ++SP;");
	  emit("stack[SP] = stack[SP - 2][stack[SP - 1]][0];" +
	       " avar(info" + COMMA + (string) key_var + COMMA +
	       "stack[SP]);" +
	       " stack[SP] = stack[SP - 2][stack[SP - 1]][1];" +
	       " avar(info" + COMMA + (string) value_var + COMMA +
	       "stack[SP]);");
	  var_assigned(key);
	  var_assigned(value);
	}
      else
	{
	  emit("if (! LSTP(stack[SP]))" +
	       " { --SP; RAISE(E_TYPE); } else {" +
	       " stack[SP] = LSTVAL(stack[SP]); PUSH(0);");
	  emit("while (stack[SP] < sizeof(stack[SP - 1])) { ++SP;" +
	       " stack[SP] = stack[SP - 2][stack[SP - 1]];" +
	       " avar(info" + COMMA + key_var + COMMA + "stack[SP]);");
	  var_assigned(key);
	}

      ++nest, COMPILE(ast[3], program), --nest;
# if DEBUG
      emit("debug(\"LOOP, sp == \" + sp + \", counter == \" + stack[sp - 1] + \", max == \" + var2str(stack[sp - 2]));");
# endif

      emit("++stack[--SP]; } SP -= 2; }");
    }

  sp_overhead -= 3;

  ++lineno;
}

PROTO(while)
{
  if (DEBUG)
    emit("/* while */");

  emit("while (1) {");
  LINENO(++lineno);
  if (SIMPLE(ast[1]))
    emit("if (" + bool_expr(ast[1]) + ") {");
  else
    {
      COMPILE(ast[1], expression);
      emit("if (TRUTHOF(POP())) {");
    }
  ++nest, COMPILE(ast[2], program), --nest;
  emit("} else break; }");

  ++lineno;
}

PROTO(fork)
{
  string var;
  int var_id, save_fork;

  if (DEBUG)
    emit("/* fork */");

  if (var = ast[1])
    var_id = VAR_ID(var_ref(var));

  LINENO(++lineno);
  COMPILE(ast[2], expression);
  emit("if (! NUMP(stack[SP])) { --SP; RAISE(E_TYPE); } else {" +
       " PUSH(NUM(global->take_task()));");
  if (var)
    {
      emit("avar(info" + COMMA + (string) var_id + COMMA + "stack[SP]);");
      var_assigned(var);
    }

  emit("STORE(); PREP_FORK(" + (string) (++fork_id) + "); RESET(); }");

  save_fork = fork_cur;
  fork_cur  = fork_id;

  forked += ({ ({ "global->task_started(info[I_TASKID]);" }) });
  ++nest, COMPILE(ast[3], program), --nest;

  fork_cur = save_fork;

  ++lineno;
}

PROTO(return)
{
  string value;

  if (DEBUG)
    emit("/* return */");

  if (sp_overhead)
    emit("SP -= " + sp_overhead + ";");
  LINENO(++lineno);

  if (sizeof(ast) == 1)
    emit("return STORE(), del(), NUM(0);");
  else
    {
      if (SIMPLE(ast[1]))
	value = simple_expr(ast[1]);
      else
	COMPILE(ast[1], expression), value = "POP()";

      emit("return RET = " + value + COMMA + "STORE(), del(), RET;");
    }
}