/*
 * Grammar components.
 * Copyright (c) 1998 New Generation Software (NGS) Oy
 *
 * Author: Markku Rossi <mtr@ngs.fi>
 */

/*
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA
 */

/*
 * $Source: /usr/local/cvsroot/ngs/js/jsc/gram.js,v $
 * $Id: gram.js,v 1.22 1998/10/26 15:25:21 mtr Exp $
 */

/* General helpers. */

function JSC$gram_reset ()
{
  JSC$label_count = 1;
  JSC$cont_break = new JSC$ContBreak ();
}


function JSC$alloc_label (num_labels)
{
  JSC$label_count += num_labels;

  return JSC$label_count - num_labels;
}


function JSC$format_label (num)
{
  return ".L" + num.toString ();
}


function JSC$count_locals_from_stmt_list (list)
{
  var i;

  /* First, count how many variables we need at the toplevel. */
  var lcount = 0;
  for (i = 0; i < list.length; i++)
    lcount += list[i].count_locals (false);

  /* Second, count the maximum amount needed by the nested blocks. */
  var rmax = 0;
  for (i = 0; i < list.length; i++)
    {
      var rc = list[i].count_locals (true);
      if (rc > rmax)
	rmax = rc;
    }

  return lcount + rmax;
}

/*
 * The handling of the `continue' and `break' labels for looping
 * constructs.  The variable `JSC$cont_break' holds an instance of
 * JSC$ContBreak class.  The instance contains a valid chain of
 * looping constructs and the currently active with and try testing
 * levels.  The actual `continue', `break', and `return' statements
 * investigate the chain and generate appropriate `with_pop' and
 * `try_pop' operands.
 *
 * If the instance variable `inswitch' is true, the continue statement
 * is inside a switch statement.  In this case, the continue statement
 * must pop one item from the stack.  That item is the value of the
 * case expression.
 */

function JSC$ContBreakFrame (loop_break, loop_continue, inswitch, label, next)
{
  this.loop_break = loop_break;
  this.loop_continue = loop_continue;
  this.inswitch = inswitch;
  this.label = label;
  this.next = next;

  this.with_nesting = 0;
  this.try_nesting = 0;
}


function JSC$ContBreak ()
{
  this.top = new JSC$ContBreakFrame (null, null, false, null);
}

new JSC$ContBreak ();

function JSC$ContBreak$push (loop_break, loop_continue, inswitch, label)
{
  this.top = new JSC$ContBreakFrame (loop_break, loop_continue, inswitch,
				     label, this.top);
}
JSC$ContBreak.prototype.push = JSC$ContBreak$push;

function JSC$ContBreak$pop ()
{
  if (this.top == null)
    error ("jsc: internal error: continue-break stack underflow");

  this.top = this.top.next;
}
JSC$ContBreak.prototype.pop = JSC$ContBreak$pop;

/*
 * Count the currently active `try' nesting that should be removed on
 * `return' statement.
 */
function JSC$ContBreak$try_return_nesting ()
{
  var f;
  var count = 0;

  for (f = this.top; f; f = f.next)
    count += f.try_nesting;

  return count;
}
JSC$ContBreak.prototype.try_return_nesting = JSC$ContBreak$try_return_nesting;

/*
 * Count currently active `with' nesting that should be removed on
 * `continue' or `break' statement.
 */
function JSC$ContBreak$count_with_nesting (label)
{
  var f;
  var count = 0;

  for (f = this.top; f; f = f.next)
    {
      count += f.with_nesting;
      if (label)
	{
	  if (f.label == label)
	    break;
	}
      else
	if (f.loop_continue)
	  break;
    }
  return count;
}
JSC$ContBreak.prototype.count_with_nesting = JSC$ContBreak$count_with_nesting;

/*
 * Count the currently active `try' nesting that should be removed on
 * `continue' or `break' statement.
 */
function JSC$ContBreak$count_try_nesting (label)
{
  var f;
  var count = 0;

  for (f = this.top; f; f = f.next)
    {
      count += f.try_nesting;
      if (label)
	{
	  if (f.label == label)
	    break;
	}
      else
	if (f.loop_continue)
	  break;
    }
  return count;
}
JSC$ContBreak.prototype.count_try_nesting = JSC$ContBreak$count_try_nesting;

function JSC$ContBreak$count_switch_nesting (label)
{
  var f;
  var count = 0;

  for (f = this.top; f; f = f.next)
    {
      if (f.inswitch)
	count++;
      if (label)
	{
	  if (f.label == label)
	    break;
	  }
      else
	if (f.loop_continue)
	  break;
    }
  return count;
}
JSC$ContBreak.prototype.count_switch_nesting
  = JSC$ContBreak$count_switch_nesting;

function JSC$ContBreak$get_continue (label)
{
  var f;

  for (f = this.top; f; f = f.next)
    if (label)
      {
	if (f.label == label)
	  return f.loop_continue;
      }
    else
      if (f.loop_continue)
	return f.loop_continue;

  return null;
}
JSC$ContBreak.prototype.get_continue = JSC$ContBreak$get_continue;

function JSC$ContBreak$get_break (label)
{
  var f;

  for (f = this.top; f; f = f.next)
    if (label)
      {
	if (f.label == label)
	  return f.loop_break;
      }
    else
      if (f.loop_break)
	return f.loop_break;

  return null;
}
JSC$ContBreak.prototype.get_break = JSC$ContBreak$get_break;

function JSC$ContBreak$is_unique_label (label)
{
  var f;

  for (f = this.top; f; f = f.next)
    if (f.label == label)
      return false;

  return true;
}
JSC$ContBreak.prototype.is_unique_label = JSC$ContBreak$is_unique_label;

JSC$cont_break = null;


/* Function declaration. */

function JSC$function_declaration (ln, lbrace_ln, name, name_given, args,
				   block, use_arguments_prop)
{
  this.linenum = ln;
  this.lbrace_linenum = lbrace_ln;
  this.name = name;
  this.name_given = name_given;
  this.args = args;
  this.block = block;
  this.use_arguments_prop = use_arguments_prop;
  this.asm = JSC$function_declaration_asm;
}

function JSC$function_declaration_asm ()
{
  var i, a;

  /* Define arguments. */
  JSC$ns.push_frame ();

  for (i = 0, a = 2; i < this.args.length; i++, a++)
    JSC$ns.define_symbol (this.args[i], JSC$SCOPE_ARG, a, this.linenum);

  /* Define the function name to be a global symbol. */
  new JSC$ASM_symbol (this.linenum, this.name).link ();

  /* Check that function gets the required amount of arguments. */
  new JSC$ASM_load_arg (this.lbrace_linenum, 1).link ();
  new JSC$ASM_add_2_i (this.lbrace_linenum).link ();
  new JSC$ASM_min_args (this.lbrace_linenum, this.args.length + 2).link ();

  /* Count how many local variables we need. */
  var num_locals = JSC$count_locals_from_stmt_list (this.block);

  /* Is the `arguments' property of function instance used? */
  if (this.use_arguments_prop)
    num_locals++;

  if (num_locals > 0)
    {
      new JSC$ASM_locals (this.lbrace_linenum, num_locals).link ();
      if (this.use_arguments_prop)
	{
	  /*
	   * Create an array for the arguments array and store it to
	   * the first local variable.
	   */
	  var ln = this.lbrace_linenum;
	  var locn = JSC$ns.alloc_local ();
	  JSC$ns.define_symbol ("arguments", JSC$SCOPE_LOCAL, locn, ln);

	  new JSC$ASM_const_i0 (ln).link ();
	  new JSC$ASM_load_global (ln, "Array").link ();
	  new JSC$ASM_new (ln).link ();
	  new JSC$ASM_swap (ln).link ();
	  new JSC$ASM_apop (ln, 2).link ();
	  new JSC$ASM_store_local (ln, locn).link ();

	  /* Push individual argumens to the array. */

	  /* Init the loop counter. */
	  new JSC$ASM_const_i0 (ln).link ();

	  var l_loop = new JSC$ASM_label ();
	  var l_out = new JSC$ASM_label ();

	  l_loop.link ();

	  /* Check if we'r done. */
	  new JSC$ASM_dup (ln).link ();
	  new JSC$ASM_load_arg (ln, 1).link ();
	  new JSC$ASM_cmp_ge (ln).link ();
	  new JSC$ASM_iftrue_b (ln, l_out).link ();

	  /* Load the nth argument to the top of the stack. */
	  new JSC$ASM_dup (ln).link ();
	  new JSC$ASM_add_2_i (ln).link ();
	  new JSC$ASM_load_nth_arg (ln).link ();

	  /* Push it to the array. */
	  new JSC$ASM_const_i1 (ln).link ();
	  new JSC$ASM_load_local (ln, locn).link ();
	  new JSC$ASM_call_method (ln, "push").link ();
	  new JSC$ASM_pop_n (ln, 4).link ();

	  /* Increment loop counter and continue. */
	  new JSC$ASM_add_1_i (ln).link ();
	  new JSC$ASM_jmp (ln, l_loop).link ();

	  /* We'r done. */
	  l_out.link ();

	  /* Pop the loop counter. */
	  new JSC$ASM_pop (ln).link ();
	}
    }

  /* Assembler for our body. */
  for (i = 0; i < this.block.length; i++)
    this.block[i].asm ();

  /*
   * Every function must return something.  We could check if all
   * control flows in this function ends to a return, but that would
   * bee too hard...  Just append a return const_undefined.  The optimizer
   * will remove it if it is not needed.
   */
  var ln;
  if (this.block.length > 0)
    ln = this.block[this.block.length - 1].linenum;
  else
    ln = this.linenum;

  new JSC$ASM_const_undefined (ln).link ();
  new JSC$ASM_return (ln).link ();

  /* Pop our namespace. */
  JSC$ns.pop_frame ();
}


function JSC$zero_function ()
{
  return 0;
}


/*
 * Statements.
 */

/* Block. */

function JSC$stmt_block (ln, list)
{
  this.stype = JSC$STMT_BLOCK;
  this.linenum = ln;
  this.stmts = list;
  this.asm = JSC$stmt_block_asm;
  this.count_locals = JSC$stmt_block_count_locals;
}

function JSC$stmt_block_asm ()
{
  JSC$ns.push_frame ();

  /* Assembler for our stmts. */
  var i;
  for (i = 0; i < this.stmts.length; i++)
    this.stmts[i].asm ();

  JSC$ns.pop_frame ();
}


function JSC$stmt_block_count_locals (recursive)
{
  if (!recursive)
    return 0;

  return JSC$count_locals_from_stmt_list (this.stmts);
}

/* Function declaration. */

function JSC$stmt_function_declaration (ln, container_id, function_id,
					given_id)
{
  this.stype = JSC$STMT_FUNCTION_DECLARATION;
  this.linenum = ln;
  this.container_id = container_id;
  this.function_id = function_id;
  this.given_id = given_id;
  this.asm = JSC$stmt_function_declaration_asm;
  this.count_locals = JSC$zero_function;
}

function JSC$stmt_function_declaration_asm ()
{
  new JSC$ASM_load_global (this.linenum, this.function_id).link ();
  new JSC$ASM_load_global (this.linenum, this.container_id).link ();
  new JSC$ASM_store_property (this.linenum, this.given_id).link ();
}

/* Empty */

function JSC$stmt_empty (ln)
{
  this.stype = JSC$STMT_EMPTY;
  this.linenum = ln;
  this.asm = JSC$stmt_empty_asm;
  this.count_locals = JSC$zero_function;
}

function JSC$stmt_empty_asm ()
{
  /* Nothing here. */
}


/* Continue. */

function JSC$stmt_continue (ln, label)
{
  this.stype = JSC$STMT_CONTINUE;
  this.linenum = ln;
  this.label = label;
  this.asm = JSC$stmt_continue_asm;
  this.count_locals = JSC$zero_function;
}

function JSC$stmt_continue_asm ()
{
  var l_cont = JSC$cont_break.get_continue (this.label);

  if (l_cont == null)
    {
      if (this.label)
	error (JSC$filename + ":" + this.linenum.toString ()
	       + ": label `" + this.label
	       + "' not found for continue statement");
      else
	error (JSC$filename + ":" + this.linenum.toString ()
	       + ": continue statement not within a loop");
    }

  var nesting = JSC$cont_break.count_with_nesting (this.label);
  if (nesting > 0)
    new JSC$ASM_with_pop (this.linenum, nesting).link ();

  nesting = JSC$cont_break.count_try_nesting (this.label);
  if (nesting > 0)
    new JSC$ASM_try_pop (this.linenum, nesting).link ();

  nesting = JSC$cont_break.count_switch_nesting (this.label);
  if (nesting > 0)
    {
      /* Pop the value of the switch expression. */
      if (nesting == 1)
	new JSC$ASM_pop (this.linenum).link ();
      else
	new JSC$ASM_pop_n (this.linenum, nesting).link ();
    }

  new JSC$ASM_jmp (this.linenum, l_cont).link ();
}


/* Break. */

function JSC$stmt_break (ln, label)
{
  this.stype = JSC$STMT_BREAK;
  this.linenum = ln;
  this.label = label;
  this.asm = JSC$stmt_break_asm;
  this.count_locals = JSC$zero_function;
}

function JSC$stmt_break_asm ()
{
  var l_break = JSC$cont_break.get_break (this.label);

  if (l_break == null)
    {
      if (this.label)
	error (JSC$filename + ":" + this.linenum.toString ()
	       + ": label `" + this.label
	       + "' not found for break statement");
      else
	error (JSC$filename + ":" + this.linenum.toString()
	       + ": break statement not within a loop or switch");
    }

  var nesting = JSC$cont_break.count_with_nesting (this.label);
  if (nesting > 0)
    new JSC$ASM_with_pop (this.linenum, nesting).link ();

  nesting = JSC$cont_break.count_try_nesting (this.label);
  if (nesting > 0)
    new JSC$ASM_try_pop (this.linenum, nesting).link ();

  /*
   * For non-labeled breaks, the switch nesting is handled in the
   * stmt_switch().  The code after the label, returned by the
   * get_break(), will handle the switch nesting in these cases.
   * For the labeled breaks, we must pop the switch nesting here.
   */
  if (this.label)
    {
      nesting = JSC$cont_break.count_switch_nesting (this.label);
      if (nesting > 0)
	{
	  if (nesting == 1)
	    new JSC$ASM_pop (this.linenum).link ();
	  else
	    new JSC$ASM_pop_n (this.linenum, nesting).link ();
	}
    }

  new JSC$ASM_jmp (this.linenum, l_break).link ();
}


/* Return. */

function JSC$stmt_return (ln, expr)
{
  this.stype = JSC$STMT_RETURN;
  this.linenum = ln;
  this.expr = expr;
  this.asm = JSC$stmt_return_asm;
  this.count_locals = JSC$zero_function;
}

function JSC$stmt_return_asm ()
{
  var nesting = JSC$cont_break.try_return_nesting ();
  if (nesting > 0)
    new JSC$ASM_try_pop (this.linenum, nesting).link ();

  if (this.expr != null)
    this.expr.asm ();
  else
    new JSC$ASM_const_undefined (this.linenum).link ();

  new JSC$ASM_return (this.linenum).link ();
}


/* Switch. */

function JSC$stmt_switch (ln, last_ln, expr, clauses)
{
  this.stype = JSC$STMT_SWITCH;
  this.linenum = ln;
  this.last_linenum = last_ln;
  this.expr = expr;
  this.clauses = clauses;
  this.asm = JSC$stmt_switch_asm;
  this.count_locals = JSC$stmt_switch_count_locals;
}

function JSC$stmt_switch_asm ()
{
  /* Evaluate the switch expression to the top of the stack. */
  this.expr.asm ();

  /* The switch statement define a break label. */
  var l_break = new JSC$ASM_label ();
  JSC$cont_break.push (l_break, null, true, null);

  /* For each clause (except the first), insert check and body labels. */
  var i;
  for (i = 1; i < this.clauses.length; i++)
    {
      this.clauses[i].l_check = new JSC$ASM_label ();
      this.clauses[i].l_body = new JSC$ASM_label ();
    }

  /* Generate code for each clause. */
  for (i = 0; i < this.clauses.length; i++)
    {
      /* Is this the last clause? */
      var last = i + 1 >= this.clauses.length;
      var c = this.clauses[i];

      var next_check, next_body;
      if (last)
	next_check = next_body = l_break;
      else
	{
	  next_check = this.clauses[i + 1].l_check;
	  next_body = this.clauses[i + 1].l_body;
	}

      if (c.expr)
	{
	  /*
	   * Must check if this clause matches the expression.  If c.expr
	   * is null, this is the default clause that matches always.
	   */

	  if (i > 0)
	    c.l_check.link ();

	  new JSC$ASM_dup (c.linenum).link ();
	  c.expr.asm ();
	  new JSC$ASM_cmp_eq (c.linenum).link ();
	  new JSC$ASM_iffalse_b (c.linenum, next_check).link ();
	}
      else
	{
	  if (i > 0)
	    /* The check label for the default case. */
	    c.l_check.link ();
	}

      /* Generate assembler for the body. */
      if (i > 0)
	c.l_body.link ();

      var j;
      for (j = 0; j < c.length; j++)
	c[j].asm ();

      /* And finally, jump to the next body. (this is the fallthrough case). */
      new JSC$ASM_jmp (c.last_linenum, next_body).link ();
    }

  JSC$cont_break.pop ();

  /* The break label. */
  l_break.link ();

  /* Pop the value of the switch expression. */
  new JSC$ASM_pop (this.last_linenum).link ();
}

function JSC$stmt_switch_count_locals (recursive)
{
  var locals = 0;
  var i, j;

  if (recursive)
    {
      /* For the recursive cases, we need the maximum of our clause stmts. */
      for (i = 0; i < this.clauses.length; i++)
	{
	  var c = this.clauses[i];
	  for (j = 0; j < c.length; j++)
	    {
	      var l = c[j].count_locals (true);
	      if (l > locals)
		locals = l;
	    }
	}
    }
  else
    {
      /*
       * The case clauses are not blocks.  Therefore, we need the amount,
       * needed by the clauses at the top-level.
       */

      for (i = 0; i < this.clauses.length; i++)
	{
	  var c = this.clauses[i];
	  for (j = 0; j < c.length; j++)
	    locals += c[j].count_locals (false);
	}
    }

  return locals;
}


/* With. */

function JSC$stmt_with (ln, expr, stmt)
{
  this.stype = JSC$STMT_WITH;
  this.linenum = ln;
  this.expr = expr;
  this.stmt = stmt;
  this.asm = JSC$stmt_with_asm;
  this.count_locals = JSC$stmt_with_count_locals;
}

function JSC$stmt_with_asm ()
{
  this.expr.asm ();

  new JSC$ASM_with_push (this.linenum).link ();
  JSC$cont_break.top.with_nesting++;

  this.stmt.asm ();

  JSC$cont_break.top.with_nesting--;
  new JSC$ASM_with_pop (this.linenum, 1).link ();
}

function JSC$stmt_with_count_locals (recursive)
{
  if (!recursive)
    {
      if (this.stmt.stype == JSC$STMT_VARIABLE)
	return this.stmt.list.length;

      return 0;
    }
  else
    return this.stmt.count_locals (true);
}


/* Try. */

function JSC$stmt_try (ln, try_block_last_ln, try_last_ln, block, catch_list,
		       fin)
{
  this.stype = JSC$STMT_TRY;
  this.linenum = ln;
  this.try_block_last_linenum = try_block_last_ln;
  this.try_last_linenum = try_last_ln;
  this.block = block;
  this.catch_list = catch_list;
  this.fin = fin;
  this.asm = JSC$stmt_try_asm;
  this.count_locals = JSC$stmt_try_count_locals;
}

function JSC$stmt_try_asm ()
{
  var l_finally = new JSC$ASM_label ();

  /* Protect and execute the try-block. */

  var l_try_error = new JSC$ASM_label ();
  new JSC$ASM_try_push (this.linenum, l_try_error).link ();
  JSC$cont_break.top.try_nesting++;

  this.block.asm ();

  JSC$cont_break.top.try_nesting--;
  new JSC$ASM_try_pop (this.try_block_last_linenum, 1).link ();

  /*
   * All ok so far.  Push a `false' to indicate no error and jump to
   * the finally block (or out if we have no finally block).
   */
  new JSC$ASM_const_false (this.try_block_last_linenum).link ();
  new JSC$ASM_jmp (this.try_block_last_linenum, l_finally).link ();

  /*
   * Handle try block failures.  The thrown value is on the top of the
   * stack.
   */

  l_try_error.link ();

  if (this.catch_list)
    {
      /*
       * We keep one boolean variable below the thrown value.  Its default
       * value is false.  When one of our catch blocks are entered, it is
       * set to true to indicate that we shouldn't throw the error
       * anymore.
       */
      new JSC$ASM_const_false (this.catch_list.linenum).link ();
      new JSC$ASM_swap (this.catch_list.linenum).link ();

      /* Protect and execute the catch list. */

      var l_catch_list_error = new JSC$ASM_label ();
      new JSC$ASM_try_push (this.catch_list.linenum,
			    l_catch_list_error).link ();
      JSC$cont_break.top.try_nesting++;

      /* Insert start and body labels for each catch list item. */
      var i;
      for (i = 0; i < this.catch_list.length; i++)
	{
	  this.catch_list[i].l_start = new JSC$ASM_label ();
	  this.catch_list[i].l_body = new JSC$ASM_label ();
	}

      /* A label for the catch list end. */
      var l_catch_list_end = new JSC$ASM_label ();

      /* Process the individual catches. */
      for (i = 0; i < this.catch_list.length; i++)
	{
	  var c = this.catch_list[i];

	  /* This is the starting point of this catch frame. */
	  c.l_start.link ();

	  /*
	   * Create a new namespace frame and bind the catch's
	   * identifier to the thrown exception.
	   */

	  JSC$ns.push_frame ();
	  JSC$ns.define_symbol (c.id, JSC$SCOPE_LOCAL, JSC$ns.alloc_local (),
				c.linenum);

	  new JSC$ASM_dup (c.linenum).link ();
	  new JSC$ASM_store_local (c.linenum,
				   JSC$ns.lookup_symbol (c.id).value).link ();

	  /* Check the possible guard.  We must protect its calculation. */
	  if (c.guard)
	    {
	      var l_guard_error = new JSC$ASM_label ();
	      new JSC$ASM_try_push (c.linenum, l_guard_error).link ();
	      JSC$cont_break.top.try_nesting++;

	      /* Calculate the guard. */
	      c.guard.asm ();

	      JSC$cont_break.top.try_nesting--;
	      new JSC$ASM_try_pop (c.linenum, 1).link ();

	      /*
	       * Wow!  We managed to do it.  Now, let's check if we
	       * accept this catch case.
	       */

	      var next;
	      if (i + 1 >= this.catch_list.length)
		next = l_catch_list_end;
	      else
		next = this.catch_list[i + 1].l_start;

	      if (c.guard.lang_type == JSC$JS_BOOLEAN)
		new JSC$ASM_iffalse_b (c.linenum, next).link ();
	      else
		new JSC$ASM_iffalse (c.linenum, next).link ();

	      /* Yes, we do accept it.  Just jump to do the stuffs. */
	      new JSC$ASM_jmp (c.linenum, c.l_body).link ();

	      /*
	       * The evaluation of the guard failed.  Do the cleanup
	       * and jump to the next case.
	       */

	      l_guard_error.link ();

	      /* Pop the exception. */
	      new JSC$ASM_pop (c.linenum).link ();

	      /* Check the next case. */
	      new JSC$ASM_jmp (c.linenum, next).link ();
	    }

	  /*
	   * We did enter the catch body.  Let's update our boolean
	   * status variable to reflect this fact.
	   */
	  c.l_body.link ();

	  new JSC$ASM_swap (c.linenum).link ();
	  new JSC$ASM_pop (c.linenum).link ();
	  new JSC$ASM_const_true (c.linenum).link ();
	  new JSC$ASM_swap (c.linenum).link ();

	  /* Code for the catch body. */
	  c.stmt.asm ();

	  /* We'r done with the namespace frame. */
	  JSC$ns.pop_frame ();

	  /*
	   * The next catch tag, or the l_catch_list_end follows us,
	   * so we don't need a jumps here.
	   */
	}

      /*
       * The catch list was evaluated without errors.
       */

      l_catch_list_end.link ();
      JSC$cont_break.top.try_nesting--;
      new JSC$ASM_try_pop (this.catch_list.last_linenum, 1).link ();

      /* Did we enter any of our catch lists? */

      var l_we_did_enter = new JSC$ASM_label ();
      new JSC$ASM_swap (this.catch_list.last_linenum).link ();
      new JSC$ASM_iftrue_b (this.catch_list.last_linenum,
			    l_we_did_enter).link ();

      /* No we didn't. */

      /*
       * Push `true' to indicate an exception and jump to the finally
       * block.  The exception is now on the top of the stack.
       */
      new JSC$ASM_const_true (this.catch_list.last_linenum).link ();
      new JSC$ASM_jmp (this.catch_list.last_linenum, l_finally).link ();

      /* Yes, we did enter one (or many) of our catch lists. */

      l_we_did_enter.link ();

      /* Pop the try-block's exception */
      new JSC$ASM_pop (this.catch_list.last_linenum).link ();

      /*
       * Push a `false' to indicate "no errors" and jump to the
       * finally block.
       */
      new JSC$ASM_const_false (this.catch_list.last_linenum).link ();
      new JSC$ASM_jmp (this.catch_list.last_linenum, l_finally).link ();


      /*
       * Handle catch list failures.  The thrown value is on the top of the
       * stack.
       */

      l_catch_list_error.link ();

      /*
       * Pop the try-block's exception and our boolean `did we enter a
       * catch block' variable.  They are below our new exception.
       */
      new JSC$ASM_apop (this.catch_list.last_linenum, 2).link ();

      /*
       * Push `true' to indicate an exception.  We will fallthrough to
       * the finally part, so no jump is needed here.
       */
      new JSC$ASM_const_true (this.catch_list.last_linenum).link ();
    }
  else
    {
      /* No catch list. */
      new JSC$ASM_const_true (this.try_block_last_linenum).link ();
    }

  /* The possible finally block. */

  l_finally.link ();

  if (this.fin)
    /* Execute it without protection. */
    this.fin.asm ();

  /* We'r almost there.  Let's see if we have to raise a new exception. */

  var l_out = new JSC$ASM_label ();
  new JSC$ASM_iffalse_b (this.try_last_linenum, l_out).link ();

  /* Do raise it. */
  new JSC$ASM_throw (this.try_last_linenum).link ();

  /* The possible exception is handled.  Please, continue. */
  l_out.link ();
}

function JSC$stmt_try_count_locals (recursive)
{
  var count = 0;
  var c;

  if (recursive)
    {
      c = this.block.count_locals (true);
      if (c > count)
	count = c;

      if (this.catch_list)
	{
	  var i;
	  for (i = 0; i < this.catch_list.length; i++)
	    {
	      c = this.catch_list[i].stmt.count_locals (true);
	      if (c > count)
		count = c;
	    }
	}
      if (this.fin)
	{
	  c = this.fin.count_locals (true);
	  if (c > count)
	    count = c;
	}
    }
  else
    {
      if (this.block.stype == JSC$STMT_VARIABLE)
	count += this.block.list.length;

      if (this.catch_list)
	{
	  /* One for the call variable. */
	  count++;

	  var i;
	  for (i = 0; i < this.catch_list.length; i++)
	    if (this.catch_list[i].stmt.stype == JSC$STMT_VARIABLE)
	      count += this.catch_list[i].stmt.list.length;
	}

      if (this.fin)
	if (this.fin.stype == JSC$STMT_VARIABLE)
	  count += this.fin.list.length;
    }

  return count;
}


/* Throw. */

function JSC$stmt_throw (ln, expr)
{
  this.stype = JSC$STMT_THROW;
  this.linenum = ln;
  this.expr = expr;
  this.asm = JSC$stmt_throw_asm;
  this.count_locals = JSC$zero_function;
}

function JSC$stmt_throw_asm ()
{
  this.expr.asm ();
  new JSC$ASM_throw (this.linenum).link ();
}


/* Labeled statement. */
function JSC$stmt_labeled_stmt (ln, id, stmt)
{
  this.stype = JSC$STMT_LABELED_STMT;
  this.linenum = ln;
  this.id = id;
  this.stmt = stmt;
  this.asm = JSC$stmt_labeled_stmt_asm;
  this.count_locals = JSC$stmt_labeled_stmt_count_locals;
}

function JSC$stmt_labeled_stmt_asm ()
{
  var l_continue = new JSC$ASM_label ();
  var l_break = new JSC$ASM_label ();

  /*
   * It is an error if we already have a labeled statement with the
   * same id.
   */
  if (!JSC$cont_break.is_unique_label (this.id))
    error (JSC$filename + ":" + this.linenum.toString ()
	   + ": labeled statement is enclosed by another labeled statement "
	   + "with the same label");

  /* Push the break and continue labels. */
  JSC$cont_break.push (l_break, l_continue, false, this.id);

  /* Dump the assembler. */
  l_continue.link ();
  this.stmt.asm ();
  l_break.link ();

  /* And we'r done with out label scope. */
  JSC$cont_break.pop ();
}

function JSC$stmt_labeled_stmt_count_locals (recursive)
{
  return this.stmt.count_locals (recursive);
}


/* Expression. */

function JSC$stmt_expr (expr)
{
  this.stype = JSC$STMT_EXPR;
  this.linenum = expr.linenum;
  this.expr = expr;
  this.asm = JSC$stmt_expr_asm;
  this.count_locals = JSC$zero_function;
}

function JSC$stmt_expr_asm ()
{
  this.expr.asm ();
  new JSC$ASM_pop (this.linenum).link ();
}


/* If. */

function JSC$stmt_if (ln, expr, stmt1, stmt2)
{
  this.stype = JSC$STMT_IF;
  this.linenum = ln;
  this.expr = expr;
  this.stmt1 = stmt1;
  this.stmt2 = stmt2;
  this.asm = JSC$stmt_if_asm;
  this.count_locals = JSC$stmt_if_count_locals;
}

function JSC$stmt_if_asm ()
{
  this.expr.asm ();

  var l1 = new JSC$ASM_label ();
  var l2 = new JSC$ASM_label ();

  if (JSC$optimize_type && this.expr.lang_type
      && this.expr.lang_type == JSC$JS_BOOLEAN)
    new JSC$ASM_iffalse_b (this.linenum, l1).link ();
  else
    new JSC$ASM_iffalse (this.linenum, l1).link ();

  /* Code for the then branch. */
  this.stmt1.asm ();
  new JSC$ASM_jmp (this.linenum, l2).link ();

  /* Code for the else branch. */
  l1.link ();
  if (this.stmt2 != null)
    this.stmt2.asm ();

  /* Done label. */
  l2.link ();
}


function JSC$stmt_if_count_locals (recursive)
{
  var lcount;

  if (!recursive)
    {
      lcount = 0;
      if (this.stmt1.stype == JSC$STMT_VARIABLE)
	lcount += this.stmt1.list.length;

      if (this.stmt2 != null && this.stmt2.stype == JSC$STMT_VARIABLE)
	lcount += this.stmt2.list.length;
    }
  else
    {
      lcount = this.stmt1.count_locals (true);

      if (this.stmt2)
	{
	  var c = this.stmt2.count_locals (true);
	  if (c > lcount)
	    lcount = c;
	}
    }

  return lcount;
}


/* Do...While. */

function JSC$stmt_do_while (ln, expr, stmt)
{
  this.stype = JSC$STMT_DO_WHILE;
  this.linenum = ln;
  this.expr = expr;
  this.stmt = stmt;
  this.asm = JSC$stmt_do_while_asm;
  this.count_locals = JSC$stmt_do_while_count_locals;
}

function JSC$stmt_do_while_asm ()
{
  var l1 = new JSC$ASM_label ();
  var l2 = new JSC$ASM_label ();
  var l3 = new JSC$ASM_label ();

  /* Loop label. */
  l1.link ();

  /* Body. */
  JSC$cont_break.push (l3, l2, false, null);
  this.stmt.asm ();
  JSC$cont_break.pop ();

  /* Condition & continue. */
  l2.link ();
  this.expr.asm ();
  if (JSC$optimize_type && this.expr.lang_type
      && this.expr.lang_type == JSC$JS_BOOLEAN)
    new JSC$ASM_iftrue_b (this.linenum, l1).link ();
  else
    new JSC$ASM_iftrue (this.linenum, l1).link ();

  /* Break label. */
  l3.link ();
}

function JSC$stmt_do_while_count_locals (recursive)
{
  if (!recursive)
    {
      if (this.stmt.stype == JSC$STMT_VARIABLE)
	return this.stmt.list.length;

      return 0;
    }
  else
    return this.stmt.count_locals (true);
}

/* While. */

function JSC$stmt_while (ln, expr, stmt)
{
  this.stype = JSC$STMT_WHILE;
  this.linenum = ln;
  this.expr = expr;
  this.stmt = stmt;
  this.asm = JSC$stmt_while_asm;
  this.count_locals = JSC$stmt_while_count_locals;
}

function JSC$stmt_while_asm ()
{
  var l1 = new JSC$ASM_label ();
  var l2 = new JSC$ASM_label ();

  /* Loop label. */
  l1.link ();

  /* Condition. */
  this.expr.asm ();
  if (JSC$optimize_type && this.expr.lang_type
      && this.expr.lang_type == JSC$JS_BOOLEAN)
    new JSC$ASM_iffalse_b (this.linenum, l2).link ();
  else
    new JSC$ASM_iffalse (this.linenum, l2).link ();

  /* Body. */
  JSC$cont_break.push (l2, l1, false, null);
  this.stmt.asm ();
  JSC$cont_break.pop ();

  /* Goto loop. */
  new JSC$ASM_jmp (this.linenum, l1).link ();

  /* Break label. */
  l2.link ();
}


function JSC$stmt_while_count_locals (recursive)
{
  if (!recursive)
    {
      if (this.stmt.stype == JSC$STMT_VARIABLE)
	return this.stmt.list.length;

      return 0;
    }
  else
    return this.stmt.count_locals (true);
}


/* For. */

function JSC$stmt_for (ln, vars, e1, e2, e3, stmt)
{
  this.stype = JSC$STMT_FOR;
  this.linenum = ln;
  this.vars = vars;
  this.expr1 = e1;
  this.expr2 = e2;
  this.expr3 = e3;
  this.stmt = stmt;
  this.asm = JSC$stmt_for_asm;
  this.count_locals = JSC$stmt_for_count_locals;
}

function JSC$stmt_for_asm ()
{
  /* Code for the init. */
  if (this.vars)
    {
      /* We have our own variable scope. */
      JSC$ns.push_frame ();

      var i;
      for (i = 0; i < this.vars.length; i++)
	{
	  var decl = this.vars[i];

	  JSC$ns.define_symbol (decl.id, JSC$SCOPE_LOCAL,
				JSC$ns.alloc_local (), this.linenum);

	  /* Possible init. */
	  if (decl.expr)
	    {
	      decl.expr.asm ();

	      var r = JSC$ns.lookup_symbol (decl.id);
	      if (r == null || r.scope != JSC$SCOPE_LOCAL)
		error (JSC$filename + ":" + this.liennum.toString ()
		       + ": internal compiler error in local variable "
		       + "declaration in for statement");

	      new JSC$ASM_store_local (this.linenum, r.value).link ();
	    }
	}
    }
  else if (this.expr1 != null)
    {
      this.expr1.asm ();
      new JSC$ASM_pop (this.linenum).link ();
    }

  var l1 = new JSC$ASM_label ();
  var l2 = new JSC$ASM_label ();
  var l3 = new JSC$ASM_label ();

  /* Loop label. */
  l1.link ();

  /* Condition. */
  var type_op = false;
  if (this.expr2 != null)
    {
      this.expr2.asm ();
      if (JSC$optimize_type && this.expr2.lang_type
	  && this.expr2.lang_type == JSC$JS_BOOLEAN)
	type_op = true;
    }
  else
    {
      new JSC$ASM_const_true (this.linenum).link ();
      type_op = JSC$optimize_type;
    }
  if (type_op)
    new JSC$ASM_iffalse_b (this.linenum, l3).link ();
  else
    new JSC$ASM_iffalse (this.linenum, l3).link ();

  JSC$cont_break.push (l3, l2, false, null);
  /* Body. */
  this.stmt.asm ();
  JSC$cont_break.pop ();

  /* Continue label. */
  l2.link ();

  /* Increment. */
  if (this.expr3 != null)
    {
      this.expr3.asm ();
      new JSC$ASM_pop (this.linenum).link ();
    }

  /* Goto loop. */
  new JSC$ASM_jmp (this.linenum, l1).link ();

  /* Break label. */
  l3.link ();

  if (this.vars)
    /* Pop the local variable scope. */
    JSC$ns.pop_frame ();
}


function JSC$stmt_for_count_locals (recursive)
{
  var count = 0;

  if (recursive)
    {
      if (this.vars)
	count += this.vars.length;

      count += this.stmt.count_locals (true);
    }
  else
    {
      if (this.stmt.stype == JSC$STMT_VARIABLE)
	count += this.stmt.list.length;
    }

  return count;
}


/* For...in. */

function JSC$stmt_for_in (ln, vars, e1, e2, stmt)
{
  this.stype = JSC$STMT_FOR_IN;
  this.linenum = ln;
  this.vars = vars;
  this.expr1 = e1;
  this.expr2 = e2;
  this.stmt = stmt;
  this.asm = JSC$stmt_for_in_asm;
  this.count_locals = JSC$stmt_for_in_count_locals;
}

function JSC$stmt_for_in_asm ()
{
  var local_id;

  if (this.vars)
    {
      /* We need our own variable scope here. */
      JSC$ns.push_frame ();

      var decl = this.vars[0];
      local_id = JSC$ns.alloc_local ();
      JSC$ns.define_symbol (decl.id, JSC$SCOPE_LOCAL, local_id, this.linenum);

      /* Possible init. */
      if (decl.expr)
	{
	  decl.expr.asm ();
	  new JSC$ASM_store_local (this.linenum, local_id).link ();
	}
    }

  /* Init the world. */
  this.expr2.asm ();
  new JSC$ASM_dup (this.linenum).link ();
  new JSC$ASM_const_i0 (this.linenum).link ();
  new JSC$ASM_swap (this.linenum).link ();
  new JSC$ASM_const_i0 (this.linenum).link ();

  var l_loop = new JSC$ASM_label ();
  var l_cont = new JSC$ASM_label ();
  var l_iffalse_b = new JSC$ASM_label ();
  var l_break = new JSC$ASM_label ();

  /* Loop label. */
  l_loop.link ();

  /* Fetch nth. */
  new JSC$ASM_nth (this.linenum).link ();
  new JSC$ASM_iffalse_b (this.linenum, l_iffalse_b).link ();

  /* Store value to variable. */
  if (this.vars)
    new JSC$ASM_store_local (this.linenum, local_id).link ();
  else
    JSC$asm_expr_lvalue_store_asm (this.expr1);

  /* Body. */
  JSC$cont_break.push (l_break, l_cont, false, null);
  this.stmt.asm ();
  JSC$cont_break.pop ();

  /* Continue label. */
  l_cont.link ();

  /* Increment. */
  new JSC$ASM_const_i1 (this.linenum).link ();
  new JSC$ASM_add (this.linenum).link ();
  new JSC$ASM_dup (this.linenum).link ();
  new JSC$ASM_roll (this.linenum, -3).link ();
  new JSC$ASM_dup (this.linenum).link ();
  new JSC$ASM_roll (this.linenum, 4).link ();
  new JSC$ASM_swap (this.linenum).link ();

  /* Goto loop. */
  new JSC$ASM_jmp (this.linenum, l_loop).link ();

  /* Out label. */
  l_iffalse_b.link ();

  new JSC$ASM_pop (this.linenum).link ();

  /* Break label. */
  l_break.link ();
  new JSC$ASM_pop_n (this.linenum, 2).link ();

  if (this.vars)
    /* Pop the variable scope. */
    JSC$ns.pop_frame ();
}

function JSC$stmt_for_in_count_locals (recursive)
{
  var count = 0;

  if (recursive)
    {
      if (this.vars)
	count++;

      count += this.stmt.count_locals (true);
    }
  else
    {
      if (this.stmt.stype == JSC$STMT_VARIABLE)
	count += this.stmt.list.length;

    }

  return count;
}


/* Variable. */

function JSC$stmt_variable (ln, list)
{
  this.stype = JSC$STMT_VARIABLE;
  this.linenum = ln;
  this.global_level = false;
  this.list = list;
  this.asm = JSC$stmt_variable_asm;
  this.count_locals = JSC$stmt_variable_count_locals;
}

function JSC$stmt_variable_asm ()
{
  var j, r;

  /* Define all local variables to our namespace. */
  for (j = 0; j < this.list.length; j++)
    {
      var i = this.list[j];

      if (!this.global_level)
	JSC$ns.define_symbol (i.id, JSC$SCOPE_LOCAL,
			      JSC$ns.alloc_local (), this.linenum);
      if (i.expr)
	{
	  i.expr.asm ();

	  if (this.global_level)
	    new JSC$ASM_store_global (this.linenum, i.id).link ();
	  else
	    {
	      r = JSC$ns.lookup_symbol (i.id);
	      if (r == null || r.scope != JSC$SCOPE_LOCAL)
		error (JSC$filename + ":" + this.linenum.toString()
		       + ": internal compiler error in local variable declaration");

	      new JSC$ASM_store_local (this.linenum, r.value).link ();
	    }
	}
    }
}

function JSC$stmt_variable_count_locals (recursive)
{
  if (!recursive)
    {
      if (this.global_level)
	/* We define these as global variables. */
	return 0;

      return this.list.length;
    }

  return 0;
}

function JSC$var_declaration (id, expr)
{
  this.id = id;
  this.expr = expr;
}


/*
 * Expressions.
 */

/* This. */

function JSC$expr_this (ln)
{
  this.etype = JSC$EXPR_THIS;
  this.linenum = ln;
  this.asm = JSC$expr_this_asm;
}

function JSC$expr_this_asm ()
{
  new JSC$ASM_load_arg (this.linenum, 0).link ();
}

/* Identifier. */

function JSC$expr_identifier (ln, value)
{
  this.etype = JSC$EXPR_IDENTIFIER;
  this.linenum = ln;
  this.value = value;
  this.asm = JSC$expr_identifier_asm;
}

function JSC$expr_identifier_asm ()
{
  JSC$asm_expr_lvalue_load_asm (this);
}

/* Float. */

function JSC$expr_float (ln, value)
{
  this.etype = JSC$EXPR_FLOAT;
  this.lang_type = JSC$JS_FLOAT;
  this.linenum = ln;
  this.value = value;
  this.asm = JSC$expr_float_asm;
}

function JSC$expr_float_asm ()
{
  new JSC$ASM_const (this.linenum, this.value).link ();
}

/* Integer. */

function JSC$expr_integer (ln, value)
{
  this.etype = JSC$EXPR_INTEGER;
  this.lang_type = JSC$JS_INTEGER;
  this.linenum = ln;
  this.value = value;
  this.asm = JSC$expr_integer_asm;
}

function JSC$expr_integer_asm ()
{
  if (this.value == 0)
    new JSC$ASM_const_i0 (this.linenum).link ();
  else if (this.value == 1)
    new JSC$ASM_const_i1 (this.linenum).link ();
  else if (this.value == 2)
    new JSC$ASM_const_i2 (this.linenum).link ();
  else if (this.value == 3)
    new JSC$ASM_const_i3 (this.linenum).link ();
  else
    new JSC$ASM_const_i (this.linenum, this.value).link ();
}

/* String. */

function JSC$expr_string (ln, value)
{
  this.etype = JSC$EXPR_STRING;
  this.lang_type = JSC$JS_STRING;
  this.linenum = ln;
  this.value = value;
  this.asm = JSC$expr_string_asm;
}

function JSC$expr_string_asm ()
{
  new JSC$ASM_const (this.linenum, this.value).link ();
}

/* Regexp. */

function JSC$expr_regexp (ln, value)
{
  this.etype = JSC$EXPR_REGEXP;
  this.lang_type = JSC$JS_BUILTIN;
  this.linenum = ln;
  this.value = value;
  this.asm = JSC$expr_regexp_asm;
}

function JSC$expr_regexp_asm ()
{
  new JSC$ASM_const (this.linenum, this.value).link ();
}

/* Array initializer. */

function JSC$expr_array_initializer (ln, items)
{
  this.etype = JSC$EXPR_ARRAY_INITIALIZER;
  this.lang_type = JSC$JS_ARRAY;
  this.linenum = ln;
  this.items = items;
  this.asm = JSC$expr_array_initializer_asm;
}

function JSC$expr_array_initializer_asm ()
{
  /* Generate assembler for the individual items. */

  var i;
  for (i = this.items.length - 1; i >= 0; i--)
    {
      if (this.items[i])
	this.items[i].asm ();
      else
	new JSC$ASM_const_undefined (this.linenum).link ();
    }

  /*
   * The number of items as a negative integer.  The Array object's
   * constructor knows that if the number of arguments is negative, it
   * is called from the array initializer.  Why?  Because the code:
   *
   *   new Array (5);
   *
   * creates an array of length of 5, but code:
   *
   *   [5]
   *
   * creates an array with one item: integer number five.  These cases
   * must be separatable from the code and that's why the argument
   * counts for the array initializers are negative.
   */
  new JSC$ASM_const (this.linenum, -this.items.length).link ();

  /* Call the constructor. */
  new JSC$ASM_load_global (this.linenum, "Array").link ();
  new JSC$ASM_new (this.linenum).link ();
  new JSC$ASM_swap (this.linenum).link ();
  new JSC$ASM_apop (this.linenum, this.items.length + 2).link ();
}

/* Object initializer. */

function JSC$expr_object_initializer (ln, items)
{
  this.etype = JSC$EXPR_OBJECT_INITIALIZER;
  this.lang_type = JSC$JS_OBJECT;
  this.linenum = ln;
  this.items = items;
  this.asm = JSC$expr_object_initializer_asm;
}

function JSC$expr_object_initializer_asm ()
{
  /* Create a new object. */
  new JSC$ASM_const_i0 (this.linenum).link ();
  new JSC$ASM_load_global (this.linenum, "Object").link ();
  new JSC$ASM_new (this.linenum).link ();
  new JSC$ASM_swap (this.linenum).link ();
  new JSC$ASM_apop (this.linenum, 2).link ();

  /* Insert the items. */
  for (var i = 0; i < this.items.length; i++)
    {
      var item = this.items[i];

      new JSC$ASM_dup (item.linenum).link ();
      item.expr.asm ();
      new JSC$ASM_swap (item.linenum).link ();

      switch (item.id_type)
	{
	case JSC$tIDENTIFIER:
	  new JSC$ASM_store_property (item.linenum, item.id).link ();
	  break;

	case JSC$tSTRING:
	  new JSC$ASM_const (item.linenum, item.id).link ();
	  new JSC$ASM_store_array (item.linenum).link ();
	  break;

	case JSC$tINTEGER:
	  switch (item.id)
	    {
	    case 0:
	      new JSC$ASM_const_i0 (item.linenum).link ();
	      break;

	    case 1:
	      new JSC$ASM_const_i1 (item.linenum).link ();
	      break;

	    case 2:
	      new JSC$ASM_const_i2 (item.linenum).link ();
	      break;

	    case 3:
	      new JSC$ASM_const_i3 (item.linenum).link ();
	      break;

	    default:
	      new JSC$ASM_const_i (item.linenum, item.id).link ();
	      break;
	    }
	  new JSC$ASM_store_array (item.linenum).link ();
	  break;
	}
    }
}


/* Null. */

function JSC$expr_null (ln)
{
  this.etype = JSC$EXPR_NULL;
  this.lang_type = JSC$JS_NULL;
  this.linenum = ln;
  this.asm = JSC$expr_null_asm;
}

function JSC$expr_null_asm ()
{
  new JSC$ASM_const_null (this.linenum).link ();
}

/* True. */

function JSC$expr_true (ln)
{
  this.etype = JSC$EXPR_TRUE;
  this.lang_type = JSC$JS_BOOLEAN;
  this.linenum = ln;
  this.asm = JSC$expr_true_asm;
}

function JSC$expr_true_asm ()
{
  new JSC$ASM_const_true (this.linenum).link ();
}

/* False. */

function JSC$expr_false (ln)
{
  this.etype = JSC$EXPR_FALSE;
  this.lang_type = JSC$JS_BOOLEAN;
  this.linenum = ln;
  this.asm = JSC$expr_false_asm;
}

function JSC$expr_false_asm ()
{
  new JSC$ASM_const_false (this.linenum).link ();
}


/* Multiplicative expr. */

function JSC$expr_multiplicative (ln, type, e1, e2)
{
  this.etype = JSC$EXPR_MULTIPLICATIVE;
  this.linenum = ln;
  this.type = type;
  this.e1 = e1;
  this.e2 = e2;
  this.asm = JSC$expr_multiplicative_asm;
}

function JSC$expr_multiplicative_asm ()
{
  this.e1.asm ();
  this.e2.asm ();
  if (this.type == #'*')
    new JSC$ASM_mul (this.linenum).link ();
  else if (this.type == #'/')
    new JSC$ASM_div (this.linenum).link ();
  else
    new JSC$ASM_mod (this.linenum).link ();
}


/* Additive expr. */

function JSC$expr_additive (ln, type, e1, e2)
{
  this.etype = JSC$EXPR_ADDITIVE;
  this.linenum = ln;
  this.type = type;
  this.e1 = e1;
  this.e2 = e2;
  this.asm = JSC$expr_additive_asm;
  this.constant_folding = JSC$expr_additive_constant_folding;
}

function JSC$expr_additive_asm ()
{
  this.e1.asm ();
  this.e2.asm ();
  if (this.type == #'+')
    new JSC$ASM_add (this.linenum).link ();
  else
    new JSC$ASM_sub (this.linenum).link ();
}

function JSC$expr_additive_constant_folding ()
{
  if (this.e1.constant_folding)
    this.e1 = this.e1.constant_folding ();
  if (this.e2.constant_folding)
    this.e2 = this.e2.constant_folding ();

  /* This could be smarter. */
  if (this.e1.lang_type && this.e2.lang_type
      && this.e1.lang_type == this.e2.lang_type)
    {
      switch (this.e1.lang_type)
	{
	case JSC$JS_INTEGER:
	  return new JSC$expr_integer (this.linenum,
				       this.type == #'+'
				       ? this.e1.value + this.e2.value
				       : this.e1.value - this.e2.value);
	  break;

	case JSC$JS_FLOAT:
	  return new JSC$expr_float (this.linenum,
				     this.type == #'+'
				     ? this.e1.value + this.e2.value
				     : this.e1.value - this.e2.value);
	  break;

	case JSC$JS_STRING:
	  if (this.type == #'+')
	    /* Only the addition is available for the strings. */
	    return new JSC$expr_string (this.linenum,
					this.e1.value + this.e2.value);
	  break;

	default:
	  /* FALLTHROUGH */
	  break;
	}
    }

  return this;
}

/* Shift expr. */

function JSC$expr_shift (ln, type, e1, e2)
{
  this.etype = JSC$EXPR_SHIFT;
  this.linenum = ln;
  this.type = type;
  this.e1 = e1;
  this.e2 = e2;
  this.asm = JSC$expr_shift_asm;
}

function JSC$expr_shift_asm ()
{
  this.e1.asm ();
  this.e2.asm ();

  if (this.type == JSC$tLSHIFT)
    new JSC$ASM_shift_left (this.linenum).link ();
  else if (this.type == JSC$tRSHIFT)
    new JSC$ASM_shift_right (this.linenum).link ();
  else
    new JSC$ASM_shift_rright (this.linenum).link ();
}


/* Relational expr. */

function JSC$expr_relational (ln, type, e1, e2)
{
  this.etype = JSC$EXPR_RELATIONAL;
  this.lang_type = JSC$JS_BOOLEAN;
  this.linenum = ln;
  this.type = type;
  this.e1 = e1;
  this.e2 = e2;
  this.asm = JSC$expr_relational_asm;
}

function JSC$expr_relational_asm ()
{
  this.e1.asm ();
  this.e2.asm ();

  if (this.type == #'<')
    new JSC$ASM_cmp_lt (this.linenum).link ();
  else if (this.type == #'>')
    new JSC$ASM_cmp_gt (this.linenum).link ();
  else if (this.type == JSC$tLE)
    new JSC$ASM_cmp_le (this.linenum).link ();
  else
    new JSC$ASM_cmp_ge (this.linenum).link ();
}


/* Equality expr. */

function JSC$expr_equality (ln, type, e1, e2)
{
  this.etype = JSC$EXPR_EQUALITY;
  this.lang_type = JSC$JS_BOOLEAN;
  this.linenum = ln;
  this.type = type;
  this.e1 = e1;
  this.e2 = e2;
  this.asm = JSC$expr_equality_asm;
}

function JSC$expr_equality_asm ()
{
  this.e1.asm ();
  this.e2.asm ();

  switch (this.type)
    {
    case JSC$tEQUAL:
      new JSC$ASM_cmp_eq (this.linenum).link ();
      break;

    case JSC$tNEQUAL:
      new JSC$ASM_cmp_ne (this.linenum).link ();
      break;

    case JSC$tSEQUAL:
      new JSC$ASM_cmp_seq (this.linenum).link ();
      break;

    case JSC$tSNEQUAL:
      new JSC$ASM_cmp_sne (this.linenum).link ();
      break;

    default:
      error ("jsc: expr_equality: internal compiler error");
      break;
    }
}


/* Bitwise and expr. */

function JSC$expr_bitwise_and (ln, e1, e2)
{
  this.etype = JSC$EXPR_BITWISE;
  this.linenum = ln;
  this.e1 = e1;
  this.e2 = e2;
  this.asm = JSC$expr_bitwise_and_asm;
}

function JSC$expr_bitwise_and_asm ()
{
  this.e1.asm ();
  this.e2.asm ();

  new JSC$ASM_and (this.linenum).link ();
}


/* Bitwise or expr. */

function JSC$expr_bitwise_or (ln, e1, e2)
{
  this.etype = JSC$EXPR_BITWISE;
  this.linenum = ln;
  this.e1 = e1;
  this.e2 = e2;
  this.asm = JSC$expr_bitwise_or_asm;
}

function JSC$expr_bitwise_or_asm ()
{
  this.e1.asm ();
  this.e2.asm ();

  new JSC$ASM_or (this.linenum).link ();
}


/* Bitwise xor expr.  */

function JSC$expr_bitwise_xor (ln, e1, e2)
{
  this.etype = JSC$EXPR_BITWISE;
  this.linenum = ln;
  this.e1 = e1;
  this.e2 = e2;
  this.asm = JSC$expr_bitwise_xor_asm;
}

function JSC$expr_bitwise_xor_asm ()
{
  this.e1.asm ();
  this.e2.asm ();

  new JSC$ASM_xor (this.linenum).link ();
}


/* Logical and expr.  */

function JSC$expr_logical_and (ln, e1, e2)
{
  this.etype = JSC$EXPR_LOGICAL;

  if (e1.lang_type && e2.lang_type
      && e1.lang_type == JSC$JS_BOOLEAN && e2.lang_type == JSC$JS_BOOLEAN)
    this.lang_type = JSC$JS_BOOLEAN;

  this.linenum = ln;
  this.e1 = e1;
  this.e2 = e2;
  this.asm = JSC$expr_logical_and_asm;
}

function JSC$expr_logical_and_asm ()
{
  this.e1.asm ();

  var l = new JSC$ASM_label ();
  new JSC$ASM_dup (this.linenum).link ();

  if (JSC$optimize_type && this.e1.lang_type
      && this.e1.lang_type == JSC$JS_BOOLEAN)
    new JSC$ASM_iffalse_b (this.linenum, l).link ();
  else
    new JSC$ASM_iffalse (this.linenum, l).link ();

  new JSC$ASM_pop (this.linenum).link ();

  this.e2.asm ();

  /* Done label. */
  l.link ();
}


/* Logical or expr.  */

function JSC$expr_logical_or (ln, e1, e2)
{
  this.etype = JSC$EXPR_LOGICAL;

  if (e1.lang_type && e2.lang_type
      && e1.lang_type == JSC$JS_BOOLEAN && e2.lang_type == JSC$JS_BOOLEAN)
    this.lang_type = JSC$JS_BOOLEAN;

  this.linenum = ln;
  this.e1 = e1;
  this.e2 = e2;
  this.asm = JSC$expr_logical_or_asm;
}

function JSC$expr_logical_or_asm ()
{
  this.e1.asm ();

  var l = new JSC$ASM_label ();
  new JSC$ASM_dup (this.linenum).link ();

  if (JSC$optimize_type && this.e1.lang_type
      && this.e1.lang_type == JSC$JS_BOOLEAN)
    new JSC$ASM_iftrue_b (this.linenum, l).link ();
  else
    new JSC$ASM_iftrue (this.linenum, l).link ();

  new JSC$ASM_pop (this.linenum).link ();

  this.e2.asm ();

  /* Done label. */
  l.link ();
}


/* New expr. */

function JSC$expr_new (ln, expr, args)
{
  this.etype = JSC$EXPR_NEW;
  this.linenum = ln;
  this.expr = expr;
  this.args = args;
  this.asm = JSC$expr_new_asm;
}

function JSC$expr_new_asm ()
{
  var i;

  if (this.args)
    {
      /* Code for the arguments. */
      for (i = this.args.length - 1; i >= 0; i--)
	this.args[i].asm ();

      if (this.args.length == 0)
	new JSC$ASM_const_i0 (this.linenum).link ();
      else if (this.args.length == 1)
	new JSC$ASM_const_i1 (this.linenum).link ();
      else if (this.args.length == 2)
	new JSC$ASM_const_i2 (this.linenum).link ();
      else if (this.args.length == 3)
	new JSC$ASM_const_i3 (this.linenum).link ();
      else
	new JSC$ASM_const (this.linenum, this.args.length).link ();
    }
  else
    {
      /* A `new Foo' call.  This is identical to `new Foo ()'. */
      new JSC$ASM_const_i0 (this.linenum).link ();
    }

  /* Object. */
  this.expr.asm ();

  /* Call new. */
  new JSC$ASM_new (this.linenum).link ();

  /* Replace the constructor's return value with the object. */
  new JSC$ASM_swap (this.linenum).link ();

  /* Remove the arguments and return the new object. */
  new JSC$ASM_apop (this.linenum,
		    (this.args ? this.args.length : 0) + 2).link ();
}


/* Object property expr. */

function JSC$expr_object_property (ln, expr, id)
{
  this.etype = JSC$EXPR_OBJECT_PROPERTY;
  this.linenum = ln;
  this.expr = expr;
  this.id = id;
  this.asm = JSC$expr_object_property_asm;
}

function JSC$expr_object_property_asm ()
{
  JSC$asm_expr_lvalue_load_asm (this);
}


/* Object array expr. */

function JSC$expr_object_array (ln, expr1, expr2)
{
  this.etype = JSC$EXPR_OBJECT_ARRAY;
  this.linenum = ln;
  this.expr1 = expr1;
  this.expr2 = expr2;
  this.asm = JSC$expr_object_array_asm;
}

function JSC$expr_object_array_asm ()
{
  JSC$asm_expr_lvalue_load_asm (this);
}


/* Call. */

function JSC$expr_call (ln, expr, args)
{
  this.etype = JSC$EXPR_CALL;
  this.linenum = ln;
  this.expr = expr;
  this.args = args;
  this.asm = JSC$expr_call_asm;
}

function JSC$expr_call_asm ()
{
  var i;

  /* Code for the arguments. */
  for (i = this.args.length - 1; i >= 0; i--)
    this.args[i].asm ();

  if (this.args.length == 0)
    new JSC$ASM_const_i0 (this.linenum).link ();
  else if (this.args.length == 1)
    new JSC$ASM_const_i1 (this.linenum).link ();
  else if (this.args.length == 2)
    new JSC$ASM_const_i2 (this.linenum).link ();
  else if (this.args.length == 3)
    new JSC$ASM_const_i3 (this.linenum).link ();
  else
    new JSC$ASM_const (this.linenum, this.args.length).link ();

  /* Check the function type. */
  if (this.expr.etype == JSC$EXPR_IDENTIFIER)
    {
      /* The simple subroutine or global object method invocation. */

      var saved_with_nesting = JSC$cont_break.top.with_nesting;
      JSC$cont_break.top.with_nesting = 0;

      JSC$asm_expr_lvalue_load_asm (this.expr);

      JSC$cont_break.top.with_nesting = saved_with_nesting;

      if (JSC$cont_break.top.with_nesting > 0)
	new JSC$ASM_jsr_w (this.linenum, this.expr.value).link ();
      else
	new JSC$ASM_jsr (this.linenum).link ();

      new JSC$ASM_apop (this.linenum, this.args.length + 2).link ();
    }
  else if (this.expr.etype == JSC$EXPR_OBJECT_PROPERTY)
    {
      /* Method invocation. */
      this.expr.expr.asm ();
      new JSC$ASM_call_method (this.linenum, this.expr.id).link ();
      new JSC$ASM_apop (this.linenum, this.args.length + 2).link ();
    }
  else
    {
      /* Something like a function pointer invocation. */
      JSC$asm_expr_lvalue_load_asm (this.expr);
      new JSC$ASM_jsr (this.linenum).link ();
      new JSC$ASM_apop (this.linenum, this.args.length + 2).link ();
    }
}


/* Assignment. */

function JSC$expr_assignment (ln, type, expr1, expr2)
{
  this.etype = JSC$EXPR_ASSIGNMENT;
  this.linenum = ln;
  this.type = type;
  this.expr1 = expr1;
  this.expr2 = expr2;
  this.asm = JSC$expr_assignment_asm;
}

function JSC$expr_assignment_asm ()
{
  if (this.type != #'=')
    JSC$asm_expr_lvalue_load_asm (this.expr1);

  /* Count the rvalue. */
  this.expr2.asm ();

  if (this.type == #'=')
    /* Nothing here. */
    ;
  else if (this.type == JSC$tMULA)
    new JSC$ASM_mul (this.linenum).link ();
  else if (this.type == JSC$tDIVA)
    new JSC$ASM_div (this.linenum).link ();
  else if (this.type == JSC$tMODA)
    new JSC$ASM_mod (this.linenum).link ();
  else if (this.type == JSC$tADDA)
    new JSC$ASM_add (this.linenum).link ();
  else if (this.type == JSC$tSUBA)
    new JSC$ASM_sub (this.linenum).link ();
  else if (this.type == JSC$tLSIA)
    new JSC$ASM_shift_left (this.linenum).link ();
  else if (this.type == JSC$tRSIA)
    new JSC$ASM_shift_right (this.linenum).link ();
  else if (this.type == JSC$tRRSA)
    new JSC$ASM_shift_rright (this.linenum).link ();
  else if (this.type == JSC$tANDA)
    new JSC$ASM_and (this.linenum).link ();
  else if (this.type == JSC$tXORA)
    new JSC$ASM_xor (this.linenum).link ();
  else if (this.type == JSC$tORA)
    new JSC$ASM_or (this.linenum).link ();
  else
    error (JSC$filename + ":" + this.linenum.toString ()
	   + ": internal compiler error in assignment expression");

  /* Duplicate the value. */
  new JSC$ASM_dup (this.linenum).link ();

  /* Store it to the lvalue. */
  JSC$asm_expr_lvalue_store_asm (this.expr1);
}

function JSC$asm_expr_lvalue_load_asm (expr)
{
  var i;

  if (expr.etype == JSC$EXPR_IDENTIFIER)
    {
      /* Must check global / local / argument. */
      i = JSC$ns.lookup_symbol (expr.value);
      if (i == null)
	{
	  if (JSC$cont_break.top.with_nesting > 0)
	    new JSC$ASM_load_global_w (expr.linenum, expr.value).link ();
	  else
	    new JSC$ASM_load_global (expr.linenum, expr.value).link ();
	}
      else if (i.scope == JSC$SCOPE_ARG)
	{
	  if (JSC$cont_break.top.with_nesting > 0 && JSC$warn_with_clobber)
	    JSC$warning (JSC$filename + ":" + expr.linenum.toString ()
			 + ": warning: the with-lookup of symbol `" + i.symbol
			 + "' is clobbered by the argument definition");

	  new JSC$ASM_load_arg (expr.linenum, i.value).link ();
	}
      else
	{
	  if (JSC$cont_break.top.with_nesting > 0 && JSC$warn_with_clobber)
	    JSC$warning (JSC$filename + ":" + expr.linenum.toString ()
			 + ": warning: the with-lookup of symbol `" + i.symbol
			 + "' is clobbered by the local variable definition");

	  new JSC$ASM_load_local (expr.linenum, i.value).link ();
	}
    }
  else if (expr.etype == JSC$EXPR_OBJECT_PROPERTY)
    {
      expr.expr.asm ();
      new JSC$ASM_load_property (expr.linenum, expr.id).link ();
    }
  else if (expr.etype == JSC$EXPR_OBJECT_ARRAY)
    {
      expr.expr1.asm ();
      expr.expr2.asm ();
      new JSC$ASM_load_array (expr.linenum).link ();
    }
  else
    error (JSC$filename + ":" + expr.linenum.toString () + ": syntax error");
}

function JSC$asm_expr_lvalue_store_asm (expr)
{
  var i;

  if (expr.etype == JSC$EXPR_IDENTIFIER)
    {
      i = JSC$ns.lookup_symbol (expr.value);
      if (i == null)
	new JSC$ASM_store_global (expr.linenum, expr.value).link ();
      else if (i.scope == JSC$SCOPE_ARG)
	new JSC$ASM_store_arg (expr.linenum, i.value).link ();
      else
	new JSC$ASM_store_local (expr.linenum, i.value).link ();
    }
  else if (expr.etype == JSC$EXPR_OBJECT_PROPERTY)
    {
      expr.expr.asm ();
      new JSC$ASM_store_property (expr.linenum, expr.id).link ();
    }
  else if (expr.etype == JSC$EXPR_OBJECT_ARRAY)
    {
      expr.expr1.asm ();
      expr.expr2.asm ();
      new JSC$ASM_store_array (expr.linenum).link ();
    }
  else
    error (JSC$filename + ":" + expr.linenum.toString () + ": syntax error");
}


/* Quest colon. */

function JSC$expr_quest_colon (ln, e1, e2, e3)
{
  this.etype = JSC$EXPR_QUEST_COLON;
  this.linenum = ln;
  this.e1 = e1;
  this.e2 = e2;
  this.e3 = e3;
  this.asm = JSC$expr_quest_colon_asm;
}

function JSC$expr_quest_colon_asm()
{
  /* Code for the condition. */
  this.e1.asm ();

  var l1 = new JSC$ASM_label ();
  var l2 = new JSC$ASM_label ();

  if (JSC$optimize_type && this.e1.lang_type
      && this.e1.lang_type == JSC$JS_BOOLEAN)
    new JSC$ASM_iffalse_b (this.linenum, l1).link ();
  else
    new JSC$ASM_iffalse (this.linenum, l1).link ();

  /* Code for the true branch. */
  this.e2.asm ();
  new JSC$ASM_jmp (this.linenum, l2).link ();

  /* Code for the false branch. */
  l1.link ();
  this.e3.asm ();

  /* Done label. */
  l2.link ();
}


/* Unary. */

function JSC$expr_unary (ln, type, expr)
{
  this.etype = JSC$EXPR_UNARY;
  this.linenum = ln;
  this.type = type;
  this.expr = expr;
  this.asm = JSC$expr_unary_asm;
}

function JSC$expr_unary_asm ()
{
  if (this.type == #'!')
    {
      this.expr.asm ();
      new JSC$ASM_not (this.linenum).link ();
    }
  else if (this.type == #'+')
    {
      this.expr.asm ();
      /* Nothing here. */
    }
  else if (this.type == #'~')
    {
      this.expr.asm ();
      new JSC$ASM_const (this.linenum, -1).link ();
      new JSC$ASM_xor (this.linenum).link ();
    }
  else if (this.type == #'-')
    {
      this.expr.asm ();
      new JSC$ASM_neg (this.linenum).link ();
    }
  else if (this.type == JSC$tDELETE)
    {
      if (this.expr.etype == JSC$EXPR_OBJECT_PROPERTY)
	{
	  this.expr.expr.asm ();
	  new JSC$ASM_delete_property (this.linenum, this.expr.id).link ();
	}
      else if (this.expr.etype == JSC$EXPR_OBJECT_ARRAY)
	{
	  this.expr.expr1.asm ();
	  this.expr.expr2.asm ();
	  new JSC$ASM_delete_array (this.linenum).link ();
	}
      else if (this.expr.etype == JSC$EXPR_IDENTIFIER)
	{
	  if (JSC$cont_break.top.with_nesting == 0)
	    error (JSC$filename + ":" + this.linenum.toString ()
		   + ": `delete property' called outside of a with-block");

	  new JSC$ASM_const_null (this.linenum).link ();
	  new JSC$ASM_delete_property (this.linenum, this.expr.value).link ();
	}
      else
	error (JSC$filename + ":" + this.linenum.toString ()
	       + ": illegal target for the delete operand");
    }
  else if (this.type == JSC$tVOID)
    {
      this.expr.asm ();
      new JSC$ASM_pop (this.linenum).link ();
      new JSC$ASM_const_undefined (this.linenum).link ();
    }
  else if (this.type == JSC$tTYPEOF)
    {
      this.expr.asm ();
      new JSC$ASM_typeof (this.linenum).link ();
    }
  else if (this.type == JSC$tPLUSPLUS
	   || this.type == JSC$tMINUSMINUS)
    {
      /* Fetch the old value. */
      JSC$asm_expr_lvalue_load_asm (this.expr);

      /* Do the operation. */
      new JSC$ASM_const_i1 (this.linenum).link ();
      if (this.type == JSC$tPLUSPLUS)
	new JSC$ASM_add (this.linenum).link ();
      else
	new JSC$ASM_sub (this.linenum).link ();

      /* Duplicate the value and store one copy pack to lvalue. */
      new JSC$ASM_dup (this.linenum).link ();
      JSC$asm_expr_lvalue_store_asm (this.expr);
    }
  else
    {
      error ("jsc: internal error: unary expr's type is "
	     + this.type.toString ());
    }
}


/* Postfix. */

function JSC$expr_postfix (ln, type, expr)
{
  this.etype = JSC$EXPR_POSTFIX;
  this.linenum = ln;
  this.type = type;
  this.expr = expr;
  this.asm = JSC$expr_postfix_asm;
}

function JSC$expr_postfix_asm ()
{
  /* Fetch the old value. */
  JSC$asm_expr_lvalue_load_asm (this.expr);

  /* Duplicate the value since it is the expression's value. */
  new JSC$ASM_dup (this.linenum).link ();

  /* Do the operation. */
  new JSC$ASM_const_i1 (this.linenum).link ();
  if (this.type == JSC$tPLUSPLUS)
    new JSC$ASM_add (this.linenum).link ();
  else
    new JSC$ASM_sub (this.linenum).link ();

  /* And finally, store it back. */
  JSC$asm_expr_lvalue_store_asm (this.expr);
}


/* Postfix. */

function JSC$expr_comma (ln, expr1, expr2)
{
  this.etype = JSC$EXPR_COMMA;
  this.linenum = ln;
  this.expr1 = expr1;
  this.expr2 = expr2;
  this.asm = JSC$expr_comma_asm;
}

function JSC$expr_comma_asm ()
{
  this.expr1.asm ();
  new JSC$ASM_pop (this.linenum).link ();
  this.expr2.asm ();
}


/*
Local variables:
mode: c
End:
*/


syntax highlighted by Code2HTML, v. 0.9.1