/*
 * Parser.
 * 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/parser.js,v $
 * $Id: parser.js,v 1.26 1998/10/26 15:25:21 mtr Exp $
 */

/*
 * Global functions.
 */

function JSC$parser_reset ()
{
  JSC$function = null;
  JSC$global_stmts = null;
  JSC$nested_function_declarations = null;
}


function JSC$parser_parse (stream)
{
  JSC$linenum = 1;
  JSC$filename = stream.name;
  JSC$functions = new Array ();
  JSC$global_stmts = new Array ();
  JSC$nested_function_declarations = new Array ();
  JSC$anonymous_function_count = 0;
  JSC$parser_peek_token_valid = false;
  JSC$num_tokens = 0;
  JSC$num_arguments_identifiers = 0;
  JSC$num_missing_semicolons = 0;

  if (JSC$verbose)
    JSC$message ("jsc: parsing");

  while (JSC$parser_peek_token (stream) != JSC$tEOF)
    if (!JSC$parser_parse_source_element (stream))
      JSC$parser_syntax_error ();

  if (JSC$verbose)
    {
      var msg = ("jsc: input stream had " + (JSC$linenum - 1).toString ()
		 + " lines, " + JSC$num_tokens.toString () + " tokens");

      if (JSC$num_missing_semicolons > 0)
	msg += (", " + JSC$num_missing_semicolons.toString ()
		+ " missing semicolons");

      JSC$message (msg);
    }
}


/*
 * General help functions.
 */

function JSC$parser_syntax_error ()
{
  error (JSC$filename + ":" + JSC$linenum.toString () + ": syntax error");
}

/* All warnings are reported through this function. */
function JSC$warning (line)
{
  System.stderr.writeln (line);
}

/* All messages are reported throught this function. */
function JSC$message (line)
{
  System.stderr.writeln (line);
}


function JSC$parser_get_token (stream)
{
  JSC$num_tokens++;

  var token;
  if (JSC$parser_peek_token_valid)
    {
      JSC$parser_peek_token_valid = false;
      JSC$parser_token_value = JSC$parser_peek_token_value;
      JSC$parser_token_linenum = JSC$parser_peek_token_linenum;
      token = JSC$parser_peek_token_token;
    }
  else
    {
      token = JSC$lexer (stream);
      JSC$parser_token_value = JSC$token_value;
      JSC$parser_token_linenum = JSC$token_linenum;
    }

  if (token == JSC$tIDENTIFIER && JSC$parser_token_value == "arguments")
    JSC$num_arguments_identifiers++;

  return token;
}


function JSC$parser_peek_token (stream)
{
  if (JSC$parser_peek_token_valid)
    return JSC$parser_peek_token_token;
  else
    {
      JSC$parser_peek_token_token = JSC$lexer (stream);
      JSC$parser_peek_token_value = JSC$token_value;
      JSC$parser_peek_token_linenum = JSC$token_linenum;
      JSC$parser_peek_token_valid = true;
      return JSC$parser_peek_token_token;
    }
}


function JSC$parser_get_semicolon_asci (stream)
{
  var token = JSC$parser_peek_token (stream);

  if (token == #';')
    {
      /* Everything ok.  It was there. */
      return JSC$parser_get_token (stream);
    }

  /* No semicolon.  Let's see if we can insert it there. */
  if (token == #'}'
      || JSC$parser_token_linenum < JSC$parser_peek_token_linenum
      || token == JSC$tEOF)
    {
      /* Ok, do the automatic semicolon insertion. */
      if (JSC$warn_missing_semicolon)
	JSC$warning (JSC$filename + ":" + JSC$parser_token_linenum.toString ()
		     + ": warning: missing semicolon");
      JSC$num_missing_semicolons++;
      return #';';
    }

  /* Sorry, no can do. */
  JSC$parser_syntax_error ();
}


function JSC$parser_expr_is_left_hand_side (expr)
{
  return (expr.etype == JSC$EXPR_CALL
	  || expr.etype == JSC$EXPR_OBJECT_PROPERTY
	  || expr.etype == JSC$EXPR_OBJECT_ARRAY
	  || expr.etype == JSC$EXPR_NEW
	  || expr.etype == JSC$EXPR_THIS
	  || expr.etype == JSC$EXPR_IDENTIFIER
	  || expr.etype == JSC$EXPR_FLOAT
	  || expr.etype == JSC$EXPR_INTEGER
	  || expr.etype == JSC$EXPR_STRING
	  || expr.etype == JSC$EXPR_REGEXP
	  || expr.etype == JSC$EXPR_ARRAY_INITIALIZER
	  || expr.etype == JSC$EXPR_NULL
	  || expr.etype == JSC$EXPR_TRUE
	  || expr.etype == JSC$EXPR_FALSE);
}


function JSC$parser_parse_source_element (stream)
{
  if (JSC$parser_parse_function_declaration (stream))
    return true;

  var stmt = JSC$parser_parse_stmt (stream);
  if (!stmt)
    return false;

  if (stmt.stype == JSC$STMT_VARIABLE)
    /*
     * This is a variable declaration at the global level.  These
     * are actually global variables.
     */
    stmt.global_level = true;

  JSC$global_stmts.push (stmt);

  return true;
}


function JSC$parser_parse_function_declaration (stream)
{
  var id, args, block;

  if (JSC$parser_peek_token (stream) != JSC$tFUNCTION)
    return false;

  /* Record how many `arguments' identifiers have been seen so far. */
  var num_arguments_identifiers = JSC$num_arguments_identifiers;

  JSC$parser_get_token (stream);
  if (JSC$parser_get_token (stream) != JSC$tIDENTIFIER)
    JSC$parser_syntax_error ();

  id = JSC$parser_token_value;
  var ln = JSC$parser_token_linenum;
  var id_given = id;

  if (JSC$nested_function_declarations.length > 0)
    {
      /* This is a nested function declaration. */
      id = ".F:" + (JSC$anonymous_function_count++).toString ();
    }
  JSC$nested_function_declarations.push (id);

  if (JSC$parser_get_token (stream) != #'(')
    JSC$parser_syntax_error ();

  /* Formal parameter list opt. */
  args = new Array ();
  while (JSC$parser_peek_token (stream) != #')')
    {
      if (JSC$parser_get_token (stream) != JSC$tIDENTIFIER)
	JSC$parser_syntax_error ();
      args.push (JSC$parser_token_value);

      var token = JSC$parser_peek_token (stream);
      if (token == #',')
	{
	  JSC$parser_get_token (stream);
	  if (JSC$parser_peek_token (stream) != JSC$tIDENTIFIER)
	    JSC$parser_syntax_error ();
	}
      else if (token != #')')
	JSC$parser_syntax_error ();
    }

  if (JSC$parser_get_token (stream) != #')')
    JSC$parser_syntax_error ();

  JSC$parser_peek_token (stream);
  var lbrace_ln = JSC$parser_peek_token_linenum;

  block = JSC$parser_parse_block (stream);
  if (typeof block == "boolean")
    JSC$parser_syntax_error ();

  /* Did the function use the `arguments' identifier? */
  var use_arguments = false;
  if (JSC$num_arguments_identifiers > num_arguments_identifiers)
    {
      use_arguments = true;
      if (JSC$warn_deprecated)
	JSC$warning (JSC$filename + ":" + ln.toString ()
		     + ": warning: the `arguments' property of Function "
		     + "instance is deprecated");
    }

  JSC$functions.push (new JSC$function_declaration (ln, lbrace_ln, id,
						    id_given, args,
						    block, use_arguments));

  JSC$nested_function_declarations.pop ();

  return true;
}


function JSC$parser_parse_block (stream)
{
  var block;

  if (JSC$parser_peek_token (stream) != #'{')
    return false;

  JSC$parser_get_token (stream) != #'{';
  var ln = JSC$parser_peek_token_linenum;

  /* Do we have a statement list? */
  if (JSC$parser_peek_token (stream) != #'}')
    /* Yes we have. */
    block = JSC$parser_parse_stmt_list (stream);
  else
    /* Do we don't */
    block = new Array ();

  if (JSC$parser_get_token (stream) != #'}')
    JSC$parser_syntax_error ();

  block.linenum = ln;

  return block;
}


function JSC$parser_parse_stmt_list (stream)
{
  var list, done, item;

  list = new Array ();
  done = false;

  while (!done)
    {
      item = JSC$parser_parse_stmt (stream);
      if (typeof item == "boolean")
	{
	  /* Can't parse more statements.  We'r done. */
	  done = true;
	}
      else
	list.push (item);
    }

  return list;
}


function JSC$parser_parse_stmt (stream)
{
  var item, token;

  if (typeof (item = JSC$parser_parse_block (stream)) != "boolean")
    return new JSC$stmt_block (item.linenum, item);
  else if (JSC$parser_parse_function_declaration (stream))
    {
      /* XXX The function declaration as statement might be incomplete. */

      if (JSC$nested_function_declarations.length == 0)
	/* Function declaration at top-level statements. */
	return new JSC$stmt_empty (JSC$parser_token_linenum);

      /* Function declaration inside another function. */

      var container_id = JSC$nested_function_declarations.pop ();
      JSC$nested_function_declarations.push (container_id);

      var f = JSC$functions[JSC$functions.length - 1];
      var function_id = f.name;
      var given_id = f.name_given;

      return new JSC$stmt_function_declaration (JSC$parser_token_linenum,
						container_id, function_id,
						given_id);
    }
  else if (typeof (item = JSC$parser_parse_variable_stmt (stream))
	   != "boolean")
    return item;
  else if (typeof (item = JSC$parser_parse_if_stmt (stream))
	   != "boolean")
    return item;
  else if (typeof (item = JSC$parser_parse_iteration_stmt (stream))
	   != "boolean")
    return item;
  else if (typeof (item = JSC$parser_parse_expr (stream))
	   != "boolean")
    {
      if (item.etype == JSC$EXPR_IDENTIFIER)
	{
	  /* Possible `Labeled Statement'. */
	  token = JSC$parser_peek_token (stream);
	  if (token == #':' && item.linenum == JSC$parser_peek_token_linenum)
	    {
	      /* Yes it is. */
	      JSC$parser_get_token (stream);
	      var stmt = JSC$parser_parse_stmt (stream);
	      if (!stmt)
		JSC$parser_syntax_error;

	      return new JSC$stmt_labeled_stmt (item.linenum, item.value,
						stmt);
	    }
	  /* FALLTHROUGH */
	}

      JSC$parser_get_semicolon_asci (stream);
      return new JSC$stmt_expr (item);
    }
  else
    {
      token = JSC$parser_peek_token (stream);
      if (token == #';')
	{
	  JSC$parser_get_token (stream);
	  return new JSC$stmt_empty (JSC$parser_token_linenum);
	}
      else if (token == JSC$tCONTINUE)
	{
	  JSC$parser_get_token (stream);

	  /* Check the possible label. */
	  var label = null;
	  token = JSC$parser_peek_token (stream);
	  if (token == JSC$tIDENTIFIER
	      && JSC$parser_token_linenum == JSC$parser_peek_token_linenum)
	    {
	      JSC$parser_get_token (stream);
	      label = JSC$parser_token_value;
	    }

	  item = new JSC$stmt_continue (JSC$parser_token_linenum, label);

	  JSC$parser_get_semicolon_asci (stream);

	  return item;
	}
      else if (token == JSC$tBREAK)
	{
	  JSC$parser_get_token (stream);

	  /* Check the possible label. */
	  var label = null;
	  token = JSC$parser_peek_token (stream);
	  if (token == JSC$tIDENTIFIER
	      && JSC$parser_token_linenum == JSC$parser_peek_token_linenum)
	    {
	      JSC$parser_get_token (stream);
	      label = JSC$parser_token_value;
	    }

	  item = new JSC$stmt_break (JSC$parser_token_linenum, label);

	  JSC$parser_get_semicolon_asci (stream);

	  return item;
	}
      else if (token == JSC$tRETURN)
	{
	  JSC$parser_get_token (stream);
	  var linenum = JSC$parser_token_linenum;

	  if (JSC$parser_peek_token (stream) == #';')
	    {
	      /* Consume the semicolon. */
	      JSC$parser_get_token (stream);
	      item = null;
	    }
	  else
	    {
	      if (JSC$parser_peek_token_linenum > linenum)
		{
		  /*
		   * A line terminator between tRETURN and the next
		   * token that is not a semicolon.  ASCI here.
		   */
		  if (JSC$warn_missing_semicolon)
		    JSC$warning (JSC$filename + ":" + linenum.toString ()
				 + ": warning: missing semicolon");

		  JSC$num_missing_semicolons++;
		  item = null;
		}
	      else
		{
		  item = JSC$parser_parse_expr (stream);
		  if (typeof item == "boolean")
		    JSC$parser_syntax_error ();

		  JSC$parser_get_semicolon_asci (stream);
		}
	    }

	  return new JSC$stmt_return (linenum, item);
	}
      else if (token == JSC$tSWITCH)
	{
	  JSC$parser_get_token (stream);
	  return JSC$parser_parse_switch (stream);
	}
      else if (token == JSC$tWITH)
	{
	  JSC$parser_get_token (stream);
	  var linenum = JSC$parser_token_linenum;

	  if (JSC$parser_get_token (stream) != #'(')
	    JSC$parser_syntax_error ();

	  var expr = JSC$parser_parse_expr (stream);
	  if (typeof expr == "boolean")
	    JSC$parser_syntax_error ();

	  if (JSC$parser_get_token (stream) != #')')
	    JSC$parser_syntax_error ();

	  var stmt = JSC$parser_parse_stmt (stream);
	  if (typeof stmt == "boolean")
	    JSC$parser_syntax_error ();

	  return new JSC$stmt_with (linenum, expr, stmt);
	}
      else if (token == JSC$tTRY)
	{
	  JSC$parser_get_token (stream);
	  return JSC$parser_parse_try (stream);
	}
      else if (token == JSC$tTHROW)
	{
	  JSC$parser_get_token (stream);
	  var linenum = JSC$parser_token_linenum;

	  /*
	   * Get the next token's linenum.  We need it for strict_ecma
	   * warning.
	   */
	  JSC$parser_peek_token (stream);
	  var peek_linenum = JSC$parser_peek_token_linenum;

	  /* The expression to throw. */
	  var expr = JSC$parser_parse_expr (stream);
	  if (typeof expr == "boolean")
	    JSC$parser_syntax_error ();

	  if (JSC$warn_strict_ecma && peek_linenum > linenum)
	    JSC$warning (JSC$filename + ":" + JSC$linenum.toString ()
			 + ": warning: ECMAScript don't allow line terminators"
			 + " between `throw' and expression");

	  JSC$parser_get_semicolon_asci (stream);

	  return new JSC$stmt_throw (linenum, expr);
	}
      else
	/* Can't parse more.  We'r done. */
	return false;
    }
}


function JSC$parser_parse_switch (stream)
{
  var linenum = JSC$parser_token_linenum;

  if (JSC$parser_get_token (stream) != #'(')
    JSC$parser_syntax_error ();

  var expr = JSC$parser_parse_expr (stream);
  if (!expr)
    JSC$parser_syntax_error ();

  if (JSC$parser_get_token (stream) != #')')
    JSC$parser_syntax_error ();

  if (JSC$parser_get_token (stream) != #'{')
    JSC$parser_syntax_error ();

  /* Parse case clauses. */
  var clauses = new Array ();
  while (true)
    {
      var token = JSC$parser_get_token (stream);

      if (token == #'}')
	break;
      else if (token == JSC$tCASE || token == JSC$tDEFAULT)
	{
	  var stmts = new Array ();
	  stmts.expr = null;

	  if (token == JSC$tCASE)
	    {
	      stmts.expr = JSC$parser_parse_expr (stream);
	      if (!stmts.expr)
		JSC$parser_syntax_error ();
	    }
	  if (JSC$parser_get_token (stream) != #':')
	    JSC$parser_syntax_error ();

	  stmts.linenum = JSC$parser_token_linenum;

	  /* Read the statement list. */
	  while (true)
	    {
	      token = JSC$parser_peek_token (stream);
	      if (token == #'}' || token == JSC$tCASE || token == JSC$tDEFAULT)
		/* Done with this branch. */
		break;

	      var stmt = JSC$parser_parse_stmt (stream);
	      if (!stmt)
		JSC$parser_syntax_error ();

	      stmts.push (stmt);
	    }

	  stmts.last_linenum = JSC$parser_token_linenum;

	  /* One clause parsed. */
	  clauses.push (stmts);
	}
      else
	JSC$parser_syntax_error ();
    }

  return new JSC$stmt_switch (linenum, JSC$parser_token_linenum, expr,
			      clauses);
}


function JSC$parser_parse_try (stream)
{
  var linenum = JSC$parser_token_linenum;

  var block = JSC$parser_parse_stmt (stream);
  if (!block)
    JSC$parser_syntax_error ();

  var try_block_last_linenum = JSC$parser_token_linenum;

  /* Now we must see `catch' or `finally'. */
  var token = JSC$parser_peek_token (stream);
  if (token != JSC$tCATCH && token != JSC$tFINALLY)
    JSC$parser_syntax_error ();

  var catch_list = false;
  if (token == JSC$tCATCH)
    {
      /* Parse catch list. */

      catch_list = new Array ();
      catch_list.linenum = JSC$parser_peek_token_linenum;

      while (token == JSC$tCATCH)
	{
	  JSC$parser_get_token (stream);
	  var c = new Object ();
	  c.linenum = JSC$parser_token_linenum;

	  if (JSC$parser_get_token (stream) != #'(')
	    JSC$parser_syntax_error ();

	  if (JSC$parser_get_token (stream) != JSC$tIDENTIFIER)
	    JSC$parser_syntax_error ();
	  c.id = JSC$parser_token_value;

	  c.guard = false;
	  if (JSC$parser_peek_token (stream) == JSC$tIF)
	    {
	      JSC$parser_get_token (stream);
	      c.guard = JSC$parser_parse_expr (stream);
	      if (!c.guard)
		JSC$parser_syntax_error ();
	    }

	  if (JSC$parser_get_token (stream) != #')')
	    JSC$parser_syntax_error ();

	  c.stmt = JSC$parser_parse_stmt (stream);
	  if (!c.stmt)
	    JSC$parser_syntax_error ();

	  catch_list.push (c);

	  token = JSC$parser_peek_token (stream);
	}

      catch_list.last_linenum = JSC$parser_token_linenum;
    }

  var fin = false;
  if (token == JSC$tFINALLY)
    {
      /* Parse the finally. */
      JSC$parser_get_token (stream);

      fin = JSC$parser_parse_stmt (stream);
      if (!fin)
	JSC$parser_syntax_error ();
    }

  return new JSC$stmt_try (linenum, try_block_last_linenum,
			   JSC$parser_token_linenum, block, catch_list,
			   fin);
}


function JSC$parser_parse_variable_stmt (stream)
{
  var list, id, expr, token;

  if (JSC$parser_peek_token (stream) != JSC$tVAR)
    return false;
  JSC$parser_get_token (stream);
  var ln = JSC$parser_token_linenum;

  list = new Array ();

  while (true)
    {
      token = JSC$parser_peek_token (stream);
      if (token == JSC$tIDENTIFIER)
	{
	  JSC$parser_get_token ();
	  id = JSC$parser_token_value;

	  if (JSC$parser_peek_token (stream) == #'=')
	    {
	      JSC$parser_get_token (stream);
	      expr = JSC$parser_parse_assignment_expr (stream);
	      if (typeof expr == "boolean")
		JSC$parser_syntax_error ();
	    }
	  else
	    expr = null;

	  list.push (new JSC$var_declaration (id, expr));

	  /* Check if we have more input. */
	  if (JSC$parser_peek_token (stream) == #',')
	    {
	      /* Yes we have.  */
	      JSC$parser_get_token (stream);

	      /* The next token must be tIDENTIFIER. */
	      if (JSC$parser_peek_token (stream) != JSC$tIDENTIFIER)
		JSC$parser_syntax_error ();
	    }
	  else
	    {
	      /* No, we don't. */
	      JSC$parser_get_semicolon_asci (stream);
	      break;
	    }
	}
      else
	{
	  /* We'r done. */
	  JSC$parser_get_semicolon_asci (stream);
	  break;
	}
    }

  /* There must be at least one variable declaration. */
  if (list.length == 0)
    JSC$parser_syntax_error ();

  return new JSC$stmt_variable (ln, list);
}


function JSC$parser_parse_if_stmt (stream)
{
  var expr, stmt, stmt2;

  if (JSC$parser_peek_token (stream) != JSC$tIF)
    return false;
  JSC$parser_get_token (stream);
  var ln = JSC$parser_token_linenum;

  if (JSC$parser_get_token (stream) != #'(')
    JSC$parser_syntax_error ();

  expr = JSC$parser_parse_expr (stream);
  if (typeof expr == "boolean")
    JSC$parser_syntax_error ();

  if (JSC$parser_get_token (stream) != #')')
    JSC$parser_syntax_error ();

  stmt = JSC$parser_parse_stmt (stream);
  if (typeof stmt == "boolean")
    JSC$parser_syntax_error ();

  if (JSC$parser_peek_token (stream) == JSC$tELSE)
    {
      JSC$parser_get_token (stream);
      stmt2 = JSC$parser_parse_stmt (stream);
      if (typeof stmt2 == "boolean")
	JSC$parser_syntax_error ();
    }
  else
    stmt2 = null;

  return new JSC$stmt_if (ln, expr, stmt, stmt2);
}


function JSC$parser_parse_iteration_stmt (stream)
{
  var token, expr1, expr2, expr3, stmt;

  token = JSC$parser_peek_token (stream);
  if (token == JSC$tDO)
    {
      /* do Statement while (Expression); */
      JSC$parser_get_token (stream);
      var ln = JSC$parser_token_linenum;

      stmt = JSC$parser_parse_stmt (stream);
      if (typeof stmt == "boolean")
	JSC$parser_syntax_error ();

      if (JSC$parser_get_token (stream) != JSC$tWHILE)
	JSC$parser_syntax_error ();

      if (JSC$parser_get_token (stream) != #'(')
	JSC$parser_syntax_error ();

      expr1 = JSC$parser_parse_expr (stream);
      if (typeof expr1 == "boolean")
	JSC$parser_syntax_error ();

      if (JSC$parser_get_token (stream) != #')')
	JSC$parser_syntax_error ();

      JSC$parser_get_semicolon_asci (stream);

      return new JSC$stmt_do_while (ln, expr1, stmt);
    }
  else if (token == JSC$tWHILE)
    {
      /* while (Expression) Statement */
      JSC$parser_get_token (stream);
      var ln = JSC$parser_token_linenum;

      if (JSC$parser_get_token (stream) != #'(')
	JSC$parser_syntax_error ();

      expr1 = JSC$parser_parse_expr (stream);
      if (typeof expr1 == "boolean")
	JSC$parser_syntax_error ();

      if (JSC$parser_get_token (stream) != #')')
	JSC$parser_syntax_error ();

      stmt = JSC$parser_parse_stmt (stream);
      if (typeof stmt == "boolean")
	JSC$parser_syntax_error ();

      return new JSC$stmt_while (ln, expr1, stmt);
    }
  else if (token == JSC$tFOR)
    {
      JSC$parser_get_token (stream);
      var ln = JSC$parser_token_linenum;

      if (JSC$parser_get_token (stream) != #'(')
	JSC$parser_syntax_error ();

      /* Init */

      var vars = null;

      token = JSC$parser_peek_token (stream);
      if (token == JSC$tVAR)
	{
	  JSC$parser_get_token (stream);
	  vars = new Array ();

	  while (true)
	    {
	      /* The identifier. */
	      token = JSC$parser_peek_token (stream);
	      if (token != JSC$tIDENTIFIER)
		break;

	      JSC$parser_get_token (stream);
	      var id = JSC$parser_token_value;

	      /* Possible initializer. */
	      var expr = null;
	      if (JSC$parser_peek_token (stream) == #'=')
		{
		  JSC$parser_get_token (stream);
		  expr = JSC$parser_parse_assignment_expr (stream);
		  if (!expr)
		    JSC$parser_syntax_error ();
		}

	      vars.push (new JSC$var_declaration (id, expr));

	      /* Check if we have more input. */
	      if (JSC$parser_peek_token (stream) == #',')
		{
		  /* Yes we have. */
		  JSC$parser_get_token (stream);

		  /* The next token must be tIDENTIFIER. */
		  if (JSC$parser_peek_token (stream) != JSC$tIDENTIFIER)
		    JSC$parser_syntax_error ();
		}
	      else
		/* No more input. */
		break;
	    }

	  /* Must have at least one variable declaration. */
	  if (vars.length == 0)
	    JSC$parser_syntax_error ();
	}
      else if (token != #';')
	{
	  expr1 = JSC$parser_parse_expr (stream);
	  if (typeof expr1 == "boolean")
	    JSC$parser_syntax_error ();
	}
      else
	expr1 = null;

      token = JSC$parser_get_token (stream);
      var for_in = false;

      if (token == #';')
	{
	  /* Normal for-statement. */

	  /* Check */
	  if (JSC$parser_peek_token (stream) != #';')
	    {
	      expr2 = JSC$parser_parse_expr (stream);
	      if (typeof expr2 == "boolean")
		JSC$parser_syntax_error ();
	    }
	  else
	    expr2 = null;

	  if (JSC$parser_get_token (stream) != #';')
	    JSC$parser_syntax_error ();

	  /* Increment */
	  if (JSC$parser_peek_token (stream) != #')')
	    {
	      expr3 = JSC$parser_parse_expr (stream);
	      if (typeof expr3 == "boolean")
		JSC$parser_syntax_error ();
	    }
	  else
	    expr3 = null;
	}
      else if (token == JSC$tIN)
	{
	  /* The `for (VAR in EXPR)'-statement. */

	  for_in = true;

	  if (expr1)
	    {
	      /* The first expression must be an identifier. */
	      if (expr1.etype != JSC$EXPR_IDENTIFIER)
		JSC$parser_syntax_error ();
	    }
	  else
	    {
	      /* We must have only one variable declaration. */
	      if (vars.length != 1)
		JSC$parser_syntax_error ();
	    }

	  /* The second expressions. */
	  expr2 = JSC$parser_parse_expr (stream);
	  if (typeof expr2 == "boolean")
	    JSC$parser_syntax_error ();
	}
      else
	JSC$parser_syntax_error ();

      if (JSC$parser_get_token (stream) != #')')
	JSC$parser_syntax_error ();

      /* Stmt. */
      stmt = JSC$parser_parse_stmt (stream);
      if (typeof stmt == "boolean")
	JSC$parser_syntax_error ();

      if (for_in)
	return new JSC$stmt_for_in (ln, vars, expr1, expr2, stmt);

      return new JSC$stmt_for (ln, vars, expr1, expr2, expr3, stmt);
    }
  return false;
}


function JSC$parser_parse_expr (stream)
{
  var expr, expr2;

  if (typeof (expr = JSC$parser_parse_assignment_expr (stream))
      == "boolean")
    return false;

  /* Check for the comma expression. */
  while (JSC$parser_peek_token (stream) == #',')
    {
      JSC$parser_get_token (stream);
      var ln = JSC$parser_token_linenum;

      if (typeof (expr2 = JSC$parser_parse_assignment_expr (stream))
	  == "boolean")
	JSC$parser_syntax_error ();
      expr = new JSC$expr_comma (ln, expr, expr2);
    }

  return expr;
}


function JSC$parser_parse_assignment_expr (stream)
{
  var expr, expr2, token;

  if (typeof (expr = JSC$parser_parse_conditional_expr (stream))
      == "boolean")
    return false;

  if (JSC$parser_expr_is_left_hand_side (expr))
    {
      token = JSC$parser_peek_token (stream);
      if (token == #'=' || token == JSC$tMULA
	  || token == JSC$tDIVA || token == JSC$tMODA
	  || token == JSC$tADDA || token == JSC$tSUBA
	  || token == JSC$tLSIA || token == JSC$tRSIA
	  || token == JSC$tRRSA || token == JSC$tANDA
	  || token == JSC$tXORA || token == JSC$tORA)
	{
	  JSC$parser_get_token (stream);
	  var ln = JSC$parser_token_linenum;

	  expr2 = JSC$parser_parse_assignment_expr (stream);
	  if (typeof expr2 == "boolean")
	    JSC$parser_syntax_error ();

	  expr = new JSC$expr_assignment (ln, token, expr, expr2);
	}
    }

  if (JSC$optimize_constant_folding && expr.constant_folding)
    return expr.constant_folding ();

  return expr;
}


function JSC$parser_parse_conditional_expr (stream)
{
  var expr, expr2, expr3, token;

  if (typeof (expr = JSC$parser_parse_logical_or_expr (stream))
      == "boolean")
    return false;

  token = JSC$parser_peek_token (stream);
  if (token == #'?')
    {
      JSC$parser_get_token (stream);
      var ln = JSC$parser_token_linenum;

      expr2 = JSC$parser_parse_assignment_expr (stream);
      if (typeof expr2 == "boolean")
	JSC$parser_syntax_error ();

      if (JSC$parser_get_token (stream) != #':')
	JSC$parser_syntax_error ();
      expr3 = JSC$parser_parse_assignment_expr (stream);
      if (typeof expr3 == "boolean")
	JSC$parser_syntax_error ();

      expr = new JSC$expr_quest_colon (ln, expr, expr2, expr3);
    }

  return expr;
}


function JSC$parser_parse_logical_or_expr (stream)
{
  var expr, expr2;

  if (typeof (expr = JSC$parser_parse_logical_and_expr (stream))
      == "boolean")
    return false;

  while (JSC$parser_peek_token (stream) == JSC$tOR)
    {
      JSC$parser_get_token (stream);
      var ln = JSC$parser_token_linenum;

      expr2 = JSC$parser_parse_logical_and_expr (stream);
      if (typeof expr2 == "boolean")
	JSC$parser_syntax_error ();

      expr = new JSC$expr_logical_or (ln, expr, expr2);
    }

  return expr;
}


function JSC$parser_parse_logical_and_expr (stream)
{
  var expr, expr2;

  if (typeof (expr = JSC$parser_parse_bitwise_or_expr (stream))
      == "boolean")
    return false;

  while (JSC$parser_peek_token (stream) == JSC$tAND)
    {
      JSC$parser_get_token (stream);
      var ln = JSC$parser_token_linenum;

      expr2 = JSC$parser_parse_bitwise_or_expr (stream);
      if (typeof expr2 == "boolean")
	JSC$parser_syntax_error ();

      expr = new JSC$expr_logical_and (ln, expr, expr2);
    }

  return expr;
}


function JSC$parser_parse_bitwise_or_expr (stream)
{
  var expr, expr2;

  if (typeof (expr = JSC$parser_parse_bitwise_xor_expr (stream))
      == "boolean")
    return false;

  while (JSC$parser_peek_token (stream) == #'|')
    {
      JSC$parser_get_token (stream);
      var ln = JSC$parser_token_linenum;

      expr2 = JSC$parser_parse_bitwise_xor_expr (stream);
      if (typeof expr2 == "boolean")
	JSC$parser_syntax_error ();

      expr = new JSC$expr_bitwise_or (ln, expr, expr2);
    }

  return expr;
}


function JSC$parser_parse_bitwise_xor_expr (stream)
{
  var expr, expr2;

  if (typeof (expr = JSC$parser_parse_bitwise_and_expr (stream))
      == "boolean")
    return false;

  while (JSC$parser_peek_token (stream) == #'^')
    {
      JSC$parser_get_token (stream);
      var ln = JSC$parser_token_linenum;

      expr2 = JSC$parser_parse_bitwise_and_expr (stream);
      if (typeof expr2 == "boolean")
	JSC$parser_syntax_error ();

      expr = new JSC$expr_bitwise_xor (ln, expr, expr2);
    }

  return expr;
}


function JSC$parser_parse_bitwise_and_expr (stream)
{
  var expr, expr2;

  if (typeof (expr = JSC$parser_parse_equality_expr (stream))
      == "boolean")
    return false;

  while (JSC$parser_peek_token (stream) == #'&')
    {
      JSC$parser_get_token (stream);
      var ln = JSC$parser_token_linenum;

      expr2 = JSC$parser_parse_equality_expr (stream);
      if (typeof expr2 == "boolean")
	JSC$parser_syntax_error ();

      expr = new JSC$expr_bitwise_and (ln, expr, expr2);
    }

  return expr;
}


function JSC$parser_parse_equality_expr (stream)
{
  var expr, expr2, token;

  if (typeof (expr = JSC$parser_parse_relational_expr (stream))
      == "boolean")
    return false;

  token = JSC$parser_peek_token (stream);
  while (token == JSC$tEQUAL || token == JSC$tNEQUAL
	 || token == JSC$tSEQUAL || token == JSC$tSNEQUAL)
    {
      JSC$parser_get_token (stream);
      var ln = JSC$parser_token_linenum;

      expr2 = JSC$parser_parse_relational_expr (stream);
      if (typeof expr2 == "boolean")
	JSC$parser_syntax_error ();

      expr = new JSC$expr_equality (ln, token, expr, expr2);
      token = JSC$parser_peek_token (stream);
    }

  return expr;
}


function JSC$parser_parse_relational_expr (stream)
{
  var expr, expr2, token;

  if (typeof (expr = JSC$parser_parse_shift_expr (stream))
      == "boolean")
    return false;

  token = JSC$parser_peek_token (stream);
  while (token == #'<' || token == #'>' || token == JSC$tLE
	 || token == JSC$tGE)
    {
      JSC$parser_get_token (stream);
      var ln = JSC$parser_token_linenum;

      expr2 = JSC$parser_parse_shift_expr (stream);
      if (typeof expr2 == "boolean")
	JSC$parser_syntax_error ();

      expr = new JSC$expr_relational (ln, token, expr, expr2);
      token = JSC$parser_peek_token (stream);
    }

  return expr;
}


function JSC$parser_parse_shift_expr (stream)
{
  var expr, expr2, token;

  if (typeof (expr = JSC$parser_parse_additive_expr (stream))
      == "boolean")
    return false;

  token = JSC$parser_peek_token (stream);
  while (token == JSC$tLSHIFT || token == JSC$tRSHIFT || token == JSC$tRRSHIFT)
    {
      JSC$parser_get_token (stream);
      var ln = JSC$parser_token_linenum;

      expr2 = JSC$parser_parse_additive_expr (stream);

      if (typeof expr2 == "boolean")
	JSC$parser_syntax_error ();

      expr = new JSC$expr_shift (ln, token, expr, expr2);
      token = JSC$parser_peek_token (stream);
    }

  return expr;
}


function JSC$parser_parse_additive_expr (stream)
{
  var expr, expr2, token;

  if (typeof (expr = JSC$parser_parse_multiplicative_expr (stream))
      == "boolean")
    return false;

  token = JSC$parser_peek_token (stream);
  while (token == #'+' || token == #'-')
    {
      JSC$parser_get_token (stream);
      var ln = JSC$parser_token_linenum;

      expr2 = JSC$parser_parse_multiplicative_expr (stream);
      if (typeof expr2 == "boolean")
	JSC$parser_syntax_error ();

      expr = new JSC$expr_additive (ln, token, expr, expr2);
      token = JSC$parser_peek_token (stream);
    }

  return expr;
}


function JSC$parser_parse_multiplicative_expr (stream)
{
  var expr, expr2, token;

  if (typeof (expr = JSC$parser_parse_unary_expr (stream)) == "boolean")
    return false;

  token = JSC$parser_peek_token (stream);
  while (token == #'*' || token == #'/' || token == #'%')
    {
      JSC$parser_get_token (stream);
      var ln = JSC$parser_token_linenum;

      expr2 = JSC$parser_parse_unary_expr (stream);
      if (typeof expr2 == "boolean")
	JSC$parser_syntax_error ();

      expr = new JSC$expr_multiplicative (ln, token, expr, expr2);
      token = JSC$parser_peek_token (stream);
    }

  return expr;
}


function JSC$parser_parse_unary_expr (stream)
{
  var expr, token;

  token = JSC$parser_peek_token (stream);
  if (token == JSC$tDELETE
      || token == JSC$tVOID
      || token == JSC$tTYPEOF
      || token == JSC$tPLUSPLUS
      || token == JSC$tMINUSMINUS
      || token == #'+'
      || token == #'-'
      || token == #'~'
      || token == #'!')
    {
      JSC$parser_get_token (stream);
      var ln = JSC$parser_token_linenum;

      expr = JSC$parser_parse_unary_expr (stream);
      if (typeof expr == "boolean")
	JSC$parser_syntax_error ();

      return new JSC$expr_unary (ln, token, expr);
    }

  return JSC$parser_parse_postfix_expr (stream);
}


function JSC$parser_parse_postfix_expr (stream)
{
  var expr, token;

  if (typeof (expr = JSC$parser_parse_left_hand_side_expr (stream))
      == "boolean")
    return false;

  token = JSC$parser_peek_token (stream);
  if (token == JSC$tPLUSPLUS || token == JSC$tMINUSMINUS)
    {
      if (JSC$parser_peek_token_linenum > JSC$parser_token_linenum)
	{
	  if (JSC$warn_missing_semicolon)
	    JSC$warning (JSC$filename + ":"
			 + JSC$parser_token_linenum.toString ()
			 + ": warning: automatic semicolon insertion cuts the expression before ++ or --");
	}
      else
	{
	  JSC$parser_get_token (stream);
	  var ln = JSC$parser_token_linenum;

	  return new JSC$expr_postfix (ln, token, expr);
	}
    }

  return expr;
}


function JSC$parser_parse_left_hand_side_expr (stream)
{
  var expr, args, token, expr2;

  if (typeof (expr = JSC$parser_parse_member_expr (stream))
      == "boolean")
    return false;

  /* Parse the possible first pair of arguments. */
  if (JSC$parser_peek_token (stream) == #'(')
    {
      var ln = JSC$parser_peek_token_linenum;

      args = JSC$parser_parse_arguments (stream);
      if (typeof args == "boolean")
	JSC$parser_syntax_error ();

      expr = new JSC$expr_call (ln, expr, args);
    }
  else
    return expr;

  /* Parse to possibly following arguments and selectors. */
  while ((token = JSC$parser_peek_token (stream)) == #'('
	 || token == #'[' || token == #'.')
    {
      var ln = JSC$parser_peek_token_linenum;

      if (token == #'(')
	{
	  args = JSC$parser_parse_arguments (stream);
	  expr = new JSC$expr_call (ln, expr, args);
	}
      else if (token == #'[')
	{
	  JSC$parser_get_token (stream);

	  expr2 = JSC$parser_parse_expr (stream);
	  if (typeof expr2 == "boolean")
	    JSC$parser_syntax_error ();

	  if (JSC$parser_get_token (stream) != #']')
	    JSC$parser_syntax_error ();

	  expr = new JSC$expr_object_array (ln, expr, expr2);
	}
      else
	{
	  JSC$parser_get_token (stream);
	  if (JSC$parser_get_token (stream) != JSC$tIDENTIFIER)
	    JSC$parser_syntax_error ();

	  expr = new JSC$expr_object_property (ln, expr,
					       JSC$parser_token_value);
	}
    }

  return expr;
}


function JSC$parser_parse_member_expr (stream)
{
  var expr, args, token, expr2;

  if (typeof (expr = JSC$parser_parse_primary_expr (stream))
      == "boolean")
    {
      token = JSC$parser_peek_token (stream);

      if (token == JSC$tNEW)
	{
	  JSC$parser_get_token (stream);
	  var ln = JSC$parser_token_linenum;

	  expr = JSC$parser_parse_member_expr (stream);
	  if (typeof expr == "boolean")
	    JSC$parser_syntax_error ();

	  if (JSC$parser_peek_token (stream) == #'(')
	    {
	      args = JSC$parser_parse_arguments (stream);
	      if (typeof args == "boolean")
		JSC$parser_syntax_error ();
	    }
	  else
	    return new JSC$expr_new (ln, expr, null);

	  expr = new JSC$expr_new (ln, expr, args);
	}
      else
	return false;
    }

  /* Ok, now we have valid starter. */
  token = JSC$parser_peek_token (stream);
  while (token == #'[' || token == #'.')
    {
      JSC$parser_get_token (stream);
      var ln = JSC$parser_token_linenum;

      if (token == #'[')
	{
	  expr2 = JSC$parser_parse_expr (stream);
	  if (typeof expr2 == "boolean")
	    JSC$parser_syntax_error ();

	  if (JSC$parser_get_token (stream) != #']')
	    JSC$parser_syntax_error ();

	  expr = new JSC$expr_object_array (ln, expr, expr2);
	}
      else
	{
	  if (JSC$parser_get_token (stream) != JSC$tIDENTIFIER)
	    JSC$parser_syntax_error ();

	  expr = new JSC$expr_object_property (ln, expr,
					       JSC$parser_token_value);
	}

      token = JSC$parser_peek_token (stream);
    }

  return expr;
}


function JSC$parser_parse_primary_expr (stream)
{
  var token, val;

  token = JSC$parser_peek_token (stream);
  var ln = JSC$parser_peek_token_linenum;

  if (token == JSC$tTHIS)
    val = new JSC$expr_this (ln);
  else if (token == JSC$tIDENTIFIER)
    val = new JSC$expr_identifier (ln, JSC$parser_peek_token_value);
  else if (token == JSC$tFLOAT)
    val = new JSC$expr_float (ln, JSC$parser_peek_token_value);
  else if (token == JSC$tINTEGER)
    val = new JSC$expr_integer (ln, JSC$parser_peek_token_value);
  else if (token == JSC$tSTRING)
    val = new JSC$expr_string (ln, JSC$parser_peek_token_value);
  else if (token == #'/')
    {
      /*
       * Kludge alert!  The regular expression constants (/.../) and
       * div operands are impossible to distinguish, based only on the
       * lexical analysis.  Therefore, we need some syntactical
       * knowledge when the regular expression constants are possible
       * at all.  This is the place where they can appear.  In all
       * other places, the character `/' is interpreted as a div
       * operator.
       */
      JSC$parser_get_token (stream);

      return new JSC$expr_regexp (ln, JSC$lexer_read_regexp_constant (stream));
    }
  else if (token == JSC$tNULL)
    val = new JSC$expr_null (ln);
  else if (token == JSC$tTRUE)
    val = new JSC$expr_true (ln);
  else if (token == JSC$tFALSE)
    val = new JSC$expr_false (ln);
  else if (token == #'[')
    {
      /* Array initializer. */
      /* TODO: SharpVarDefinition_{opt} */

      JSC$parser_get_token (stream);

      var items = new Array ();
      var pos = 0;

      while ((token = JSC$parser_peek_token (stream)) != #']')
	{
	  if (token == #',')
	    {
	      JSC$parser_get_token (stream);
	      items[++pos] = false;
	      continue;
	    }

	  var expr = JSC$parser_parse_assignment_expr (stream);
	  if (!expr)
	    JSC$parser_syntax_error ();

	  items[pos] = expr;

	  /* Got one expression.  It must be followed by ',' or ']'. */
	  token = JSC$parser_peek_token (stream);
	  if (token != #',' && token != #']')
	    JSC$parser_syntax_error ();
	}

      val = new JSC$expr_array_initializer (ln, items);
    }
  else if (token == #'{')
    {
      /* Object literal. */
      /* TODO: SharpVarDefinition_{opt} */

      JSC$parser_get_token (stream);

      var items = new Array ();

      while ((token = JSC$parser_peek_token (stream)) != #'}')
	{
	  var pair = new Object ();

	  token = JSC$parser_get_token (stream);

	  pair.linenum = JSC$linenum;
	  pair.id_type = token;
	  pair.id = JSC$parser_token_value;

	  if (token != JSC$tIDENTIFIER && token != JSC$tSTRING
	      && token != JSC$tINTEGER)
	    JSC$parser_syntax_error ();

	  if (JSC$parser_get_token (stream) != #':')
	    JSC$parser_syntax_error ();

	  pair.expr = JSC$parser_parse_assignment_expr (stream);
	  if (!pair.expr)
	    JSC$parser_syntax_error ();

	  items.push (pair);

	  /*
	   * Got one property, initializer pair.  It must be followed
	   * by ',' or '}'.
	   */
	  token = JSC$parser_peek_token (stream);
	  if (token == #',')
	    {
	      /* Ok, we have more items. */
	      JSC$parser_get_token (stream);

	      token = JSC$parser_peek_token (stream);
	      if (token != JSC$tIDENTIFIER && token != JSC$tSTRING
		  && token != JSC$tINTEGER)
		JSC$parser_syntax_error ();
	    }
	  else if (token != #'}' && token)
	    JSC$parser_syntax_error ();
	}

      val = new JSC$expr_object_initializer (ln, items);
    }
  else if (token == #'(')
    {
      JSC$parser_get_token (stream);

      val = JSC$parser_parse_expr (stream);
      if (typeof val == "boolean"
	  || JSC$parser_peek_token (stream) != #')')
	JSC$parser_syntax_error ();
    }
  else
    return false;

  JSC$parser_get_token (stream);
  return val;
}


function JSC$parser_parse_arguments (stream)
{
  var args, item;

  if (JSC$parser_peek_token (stream) != #'(')
    return false;

  args = new Array ();

  JSC$parser_get_token (stream);
  while (JSC$parser_peek_token (stream) != #')')
    {
      item = JSC$parser_parse_assignment_expr (stream);
      if (typeof item == "boolean")
	JSC$parser_syntax_error ();
      args.push (item);

      var token = JSC$parser_peek_token (stream);
      if (token == #',')
	JSC$parser_get_token (stream);
      else if (token != #')')
	JSC$parser_syntax_error ();
    }
  JSC$parser_get_token (stream);

  return args;
}

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


syntax highlighted by Code2HTML, v. 0.9.1