/*
 * Copyright (c) 1998-2006 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source 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, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.quercus.parser;

import java.io.StringReader;
import java.io.Reader;
import java.io.IOException;

import java.lang.reflect.Modifier;

import java.util.ArrayList;

import com.caucho.quercus.QuercusRuntimeException;

import com.caucho.quercus.expr.*;

import com.caucho.quercus.program.*;

import com.caucho.quercus.Quercus;
import com.caucho.quercus.Location;

import com.caucho.quercus.env.Env;
import com.caucho.quercus.env.Value;
import com.caucho.quercus.env.BooleanValue;
import com.caucho.quercus.env.LongValue;
import com.caucho.quercus.env.DoubleValue;
import com.caucho.quercus.env.CallbackFunction;

import com.caucho.util.IntMap;
import com.caucho.util.L10N;
import com.caucho.util.CharBuffer;
import com.caucho.vfs.IOExceptionWrapper;

import com.caucho.vfs.Vfs;
import com.caucho.vfs.Path;
import com.caucho.vfs.StringPath;
import com.caucho.vfs.ReadStream;

/**
 * Parses a PHP program.
 */
public class QuercusParser {
  private final static L10N L = new L10N(QuercusParser.class);

  private final static int M_STATIC = 0x1;
  private final static int M_PUBLIC = 0x2;
  private final static int M_PROTECTED = 0x4;
  private final static int M_PRIVATE = 0x8;
  private final static int M_FINAL = 0x10;
  private final static int M_ABSTRACT = 0x20;
  private final static int M_INTERFACE = 0x40;
  
  private final static int IDENTIFIER = 256;
  private final static int STRING = 257;
  private final static int LONG = 258;
  private final static int DOUBLE = 259;
  private final static int LSHIFT = 260;
  private final static int RSHIFT = 261;
  private final static int PHP_END = 262;
  private final static int EQ = 263;
  private final static int DEREF = 264;
  private final static int LEQ = 268;
  private final static int GEQ = 269;
  private final static int NEQ = 270;
  private final static int EQUALS = 271;
  private final static int NEQUALS = 272;
  private final static int C_AND = 273;
  private final static int C_OR = 274;
  
  private final static int PLUS_ASSIGN = 278;
  private final static int MINUS_ASSIGN = 279;
  private final static int APPEND_ASSIGN = 280;
  private final static int MUL_ASSIGN = 281;
  private final static int DIV_ASSIGN = 282;
  private final static int MOD_ASSIGN = 283;
  private final static int AND_ASSIGN = 284;
  private final static int OR_ASSIGN = 285;
  private final static int XOR_ASSIGN = 286;
  private final static int LSHIFT_ASSIGN = 287;
  private final static int RSHIFT_ASSIGN = 288;
  
  private final static int INCR = 289;
  private final static int DECR = 290;
  
  private final static int SCOPE = 291;
  private final static int ESCAPED_STRING = 292;
  private final static int HEREDOC = 293;
  private final static int ARRAY_RIGHT = 294;
  private final static int SIMPLE_STRING_ESCAPE = 295;
  private final static int COMPLEX_STRING_ESCAPE = 296;
  
  private final static int FIRST_IDENTIFIER_LEXEME = 512;
  private final static int ECHO = 512;
  private final static int NULL = 513;
  private final static int IF = 514;
  private final static int WHILE = 515;
  private final static int FUNCTION = 516;
  private final static int CLASS = 517;
  private final static int NEW = 518;
  private final static int RETURN = 519;
  private final static int VAR = 520;
  private final static int PRIVATE = 521;
  private final static int PROTECTED = 522;
  private final static int PUBLIC = 523;
  private final static int FOR = 524;
  private final static int DO = 525;
  private final static int BREAK = 526;
  private final static int CONTINUE = 527;
  private final static int ELSE = 528;
  private final static int EXTENDS = 529;
  private final static int STATIC = 530;
  private final static int INCLUDE = 531;
  private final static int REQUIRE = 532;
  private final static int INCLUDE_ONCE = 533;
  private final static int REQUIRE_ONCE = 534;
  private final static int UNSET = 535;
  private final static int FOREACH = 536;
  private final static int AS = 537;
  private final static int TEXT = 538;
  private final static int ISSET = 539;
  private final static int SWITCH = 540;
  private final static int CASE = 541;
  private final static int DEFAULT = 542;
  private final static int EXIT = 543;
  private final static int GLOBAL = 544;
  private final static int ELSEIF = 545;
  private final static int PRINT = 546;
  private final static int SYSTEM_STRING = 547;
  private final static int SIMPLE_SYSTEM_STRING = 548;
  private final static int COMPLEX_SYSTEM_STRING = 549;
  private final static int TEXT_ECHO = 550;
  private final static int ENDIF = 551;
  private final static int ENDWHILE = 552;
  private final static int ENDFOR = 553;
  private final static int ENDFOREACH = 554;
  private final static int ENDSWITCH = 555;
  
  private final static int XOR_RES = 556;
  private final static int AND_RES = 557;
  private final static int OR_RES = 558;
  private final static int LIST = 559;
  
  private final static int THIS = 560;
  private final static int TRUE = 561;
  private final static int FALSE = 562;
  private final static int CLONE = 563;
  private final static int INSTANCEOF = 564;
  private final static int CONST = 565;
  private final static int ABSTRACT = 566;
  private final static int FINAL = 567;
  private final static int DIE = 568;
  private final static int THROW = 569;
  private final static int TRY = 570;
  private final static int CATCH = 571;
  private final static int INTERFACE = 572;
  private final static int IMPLEMENTS = 573;
  
  private final static int LAST_IDENTIFIER_LEXEME = 1024;

  private final static IntMap _insensitiveReserved = new IntMap();
  private final static IntMap _reserved = new IntMap();

  private Quercus _quercus;

  private Path _sourceFile;

  private ParserLocation _parserLocation = new ParserLocation();

  private boolean _hasCr;

  private int _peek = -1;
  private Reader _is;

  private CharBuffer _sb = new CharBuffer();

  private int _peekToken = -1;
  private String _lexeme = "";
  private String _heredocEnd = null;

  private GlobalScope _globalScope = new GlobalScope();

  private boolean _returnsReference = false;

  private Scope _scope = _globalScope;
  private InterpretedClassDef _quercusClass;

  private FunctionInfo _function;
  
  private boolean _isTop;

  QuercusParser(Quercus quercus)
  {
    _quercus = quercus;
  }

  public QuercusParser(Quercus quercus, Path sourceFile, Reader is)
  {
    _quercus = quercus;

    init(sourceFile, is);
  }

  private void init(Path sourceFile)
    throws IOException
  {
    init(sourceFile, sourceFile.openRead().getReader());
  }

  private void init(Path sourceFile, Reader is)
  {
    _sourceFile = sourceFile;
    _is = is;

    if (sourceFile != null)
      _parserLocation.setFileName(sourceFile.getPath());
    else
      _parserLocation.setFileName("eval:");

    _parserLocation.setLineNumber(1);

    _peek = -1;
    _peekToken = -1;
  }

  public void setLocation(String fileName, int line)
  {
    _parserLocation.setFileName(fileName);
    _parserLocation.setLineNumber(line);
  }
  
  public static QuercusProgram parse(Quercus quercus,
				     Path path,
				     String encoding)
    throws IOException
  {
    ReadStream is = path.openRead();

    try {
      is.setEncoding(encoding);
      
      QuercusParser parser;
      parser = new QuercusParser(quercus, path, is.getReader());

      return parser.parse();
    } finally {
      is.close();
    }
  }
  
  public static QuercusProgram parse(Quercus quercus,
				     Path path,
				     String encoding,
				     String fileName,
				     int line)
    throws IOException
  {
    ReadStream is = path.openRead();

    try {
      is.setEncoding(encoding);
      
      QuercusParser parser;
      parser = new QuercusParser(quercus, path, is.getReader());

      if (fileName != null && line >= 0)
	parser.setLocation(fileName, line);

      return parser.parse();
    } finally {
      is.close();
    }
  }
  
  public static QuercusProgram parse(Quercus quercus,
				     ReadStream is)
    throws IOException
  {
    QuercusParser parser;
    parser = new QuercusParser(quercus, is.getPath(), is.getReader());

    return parser.parse();
  }
  
  public static QuercusProgram parse(Quercus quercus,
				     Path path, Reader is)
    throws IOException
  {
    return new QuercusParser(quercus, path, is).parse();
  }
  
  public static QuercusProgram parseEval(Quercus quercus, String str)
    throws IOException
  {
    Path path = new StringPath(str);

    QuercusParser parser = new QuercusParser(quercus, path, path.openRead().getReader());

    return parser.parseCode();
  }
  
  public static QuercusProgram parseEvalExpr(Quercus quercus, String str)
    throws IOException
  {
    Path path = new StringPath(str);

    QuercusParser parser = new QuercusParser(quercus, path, path.openRead().getReader());

    return parser.parseCode().createExprReturn();
  }
  
  public static Value parseFunction(Quercus quercus, String args, String code)
    throws IOException
  {
    Path argPath = new StringPath(args);
    Path codePath = new StringPath(code);

    QuercusParser parser = new QuercusParser(quercus);

    Function fun = parser.parseFunction(argPath, codePath);

    return new CallbackFunction(fun);
  }
  
  public static Expr parse(Quercus quercus, String str)
    throws IOException
  {
    Path path = Vfs.lookup("string:");
    
    return new QuercusParser(quercus, path, new StringReader(str)).parseExpr();
  }
  
  public static Expr parseDefault(String str)
  {
    try {
      Path path = Vfs.lookup("string:");
    
      return new QuercusParser(null, path, new StringReader(str)).parseExpr();
    } catch (IOException e) {
      e.printStackTrace();
      
      throw new QuercusRuntimeException(e);
    }
  }

  /**
   * Returns the current filename.
   */
  public String getFileName()
  {
    if (_sourceFile == null)
      return null;
    else
      return _sourceFile.getPath();
  }

  /**
   * Returns the current line
   */
  public int getLine()
  {
    return _parserLocation.getLineNumber();
  }

  public QuercusProgram parse()
    throws IOException
  {
    _function = new FunctionInfo(_quercus, "main");
    _function.setPageMain(true);

    // quercus/0b0d
    _function.setVariableVar(true);
    _function.setUsesSymbolTable(true);
    
    Statement stmt = parseTop();

    QuercusProgram program = new QuercusProgram(_quercus, _sourceFile,
						_globalScope.getFunctionMap(),
						_globalScope.getClassMap(),
						_function,
						stmt);
    return program;

    /*
    com.caucho.vfs.WriteStream out = com.caucho.vfs.Vfs.lookup("stdout:").openWrite();
    out.setFlushOnNewline(true);
    stmt.debug(new JavaWriter(out));
    */
  }

  QuercusProgram parseCode()
    throws IOException
  {
    _function = new FunctionInfo(_quercus, "eval");
    // XXX: need param or better function name for non-global?
    _function.setGlobal(false);

    Location location = getLocation();

    ArrayList<Statement> stmtList = parseStatementList();
    
    return new QuercusProgram(_quercus, _sourceFile,
			      _globalScope.getFunctionMap(),
			      _globalScope.getClassMap(),
			      _function,
			      BlockStatement.create(location, stmtList));
  }

  Function parseFunction(Path argPath, Path codePath)
    throws IOException
  {
    _function = new FunctionInfo(_quercus, "anonymous");
    // XXX: need param or better function name for non-global?
    _function.setGlobal(false);
    _function.setPageMain(true);

    init(argPath);

    ArrayList<Arg> args = parseFunctionArgDefinition();
      
    init(codePath);
      
    ArrayList<Statement> statementList;

    statementList = parseStatementList();

    return new Function(Location.UNKNOWN, "anonymous", _function, args, statementList);
  }

  /**
   * Parses the top page.
   */
  Statement parseTop()
    throws IOException
  {
    _isTop = true;
    
    ArrayList<Statement> statements = new ArrayList<Statement>();

    Location location = getLocation();

    int token = parsePhpText();

    if (_lexeme.length() > 0)
      statements.add(new TextStatement(location, _lexeme));

    if (token == TEXT_ECHO) {
      parseEcho(statements);
    }
    
    statements.addAll(parseStatementList());

    return BlockStatement.create(location, statements);
  }

  /**
   * Parses a statement list.
   */
  private ArrayList<Statement> parseStatementList()
    throws IOException
  {
    ArrayList<Statement> statements = new ArrayList<Statement>();

    while (true) {
      Location location = getLocation();

      int token = parseToken();

      switch (token) {
      case -1:
	return statements;

      case ';':
	break;

      case ECHO:
	parseEcho(statements);
	break;

      case PRINT:
	statements.add(parsePrint());
	break;

      case UNSET:
	parseUnset(statements);
	break;

      case ABSTRACT:
      case FINAL:
	{
	  _peekToken = token;

	  int modifiers = 0;
	  do {
	    token = parseToken();

	    switch (token) {
	    case ABSTRACT:
	      modifiers |= M_ABSTRACT;
	      break;
	    case FINAL:
	      modifiers |= M_FINAL;
	      break;
	    case CLASS:
	      parseClassDefinition(modifiers);
	      break;
	    default:
	      throw error(L.l("expected 'class' at {0}",
			      tokenName(token)));
	    }
	  } while (token != CLASS);
	}
	break;

      case FUNCTION:
	{
          Location functionLocation = getLocation();

          Function fun = parseFunctionDefinition(M_STATIC);

	  if (! _isTop) {
	    statements.add(new FunctionDefStatement(functionLocation, fun));
	  }
	}
	break;

      case CLASS:
	parseClassDefinition(0);
	// statements.add(new ClassDefStatement(parseClassDefinition()));
	break;

      case INTERFACE:
	parseClassDefinition(M_INTERFACE);
	// statements.add(new ClassDefStatement(parseClassDefinition()));
	break;

      case IF:
	statements.add(parseIf());
	break;

      case SWITCH:
	statements.add(parseSwitch());
	break;

      case WHILE:
	statements.add(parseWhile());
	break;

      case DO:
	statements.add(parseDo());
	break;

      case FOR:
	statements.add(parseFor());
	break;

      case FOREACH:
	statements.add(parseForeach());
	break;

      case PHP_END:
	return statements;

      case RETURN:
	statements.add(parseReturn());
	break;

      case THROW:
	statements.add(parseThrow());
	break;

      case BREAK:
	statements.add(BreakStatement.BREAK);
	break;

      case CONTINUE:
	statements.add(ContinueStatement.CONTINUE);
	break;

      case GLOBAL:
	statements.add(parseGlobal());
	break;
	
      case STATIC:
	statements.add(parseStatic());
	break;
	
      case TRY:
	statements.add(parseTry());
	break;

      case '{':
	{
	  ArrayList<Statement> statementList = parseStatementList();

	  expect('}');

	  statements.addAll(statementList);
	}
	break;

      case '}':
      case CASE:
      case DEFAULT:
      case ELSE:
      case ELSEIF:
      case ENDIF:
      case ENDWHILE:
      case ENDFOR:
      case ENDFOREACH:
      case ENDSWITCH:
	_peekToken = token;
	return statements;

      case TEXT:
	if (_lexeme.length() > 0) {
	  statements.add(new TextStatement(location, _lexeme));
	}
	break;
	
      case TEXT_ECHO:
	if (_lexeme.length() > 0)
	  statements.add(new TextStatement(location, _lexeme));

	parseEcho(statements);

	break;

      default:
	_peekToken = token;
	
	statements.add(parseExprStatement());
	break;
      }
    }
  }

  private Statement parseStatement()
    throws IOException
  {
    Location location = getLocation();

    int token = parseToken();

    switch (token) {
    case ';':
      return NullStatement.NULL;

    case '{':
      location = getLocation();

      ArrayList<Statement> statementList = parseStatementList();

      expect('}');

      return BlockStatement.create(location, statementList);

    case IF:
      return parseIf();

    case SWITCH:
      return parseSwitch();

    case WHILE:
      return parseWhile();

    case DO:
      return parseDo();

    case FOR:
      return parseFor();

    case FOREACH:
      return parseForeach();

    case TRY:
      return parseTry();

    case TEXT:
      if (_lexeme.length() > 0) {
	return new TextStatement(location, _lexeme);
      }
      else
	return parseStatement();
      
    default:
      Statement stmt = parseStatementImpl(token);

      token  = parseToken();
      if (token != ';')
	_peekToken = token;

      return stmt;
    }
  }

  /**
   * Parses statements that expect to be terminated by ';'.
   */
  private Statement parseStatementImpl(int token)
    throws IOException
  {
    switch (token) {
    case ECHO:
      {
        Location location = getLocation();

        ArrayList<Statement> statementList = new ArrayList<Statement>();
	parseEcho(statementList);

	return BlockStatement.create(location, statementList);
      }

    case PRINT:
      return parsePrint();

    case UNSET:
      return parseUnset();

    case GLOBAL:
      return parseGlobal();

    case STATIC:
      return parseStatic();

    case BREAK:
      return BreakStatement.BREAK;

    case CONTINUE:
      return ContinueStatement.CONTINUE;

    case RETURN:
      return parseReturn();

    case THROW:
      return parseThrow();

    case TRY:
      return parseTry();

    default:
      _peekToken = token;
      return parseExprStatement();

      /*
    default:
      throw error(L.l("unexpected token {0}.", tokenName(token)));
      */
    }
  }

  /**
   * Parses the echo statement.
   */
  private void parseEcho(ArrayList<Statement> statements)
    throws IOException
  {
    Location location = getLocation();

    while (true) {
      Expr expr = parseTopExpr();

      createEchoStatements(location, statements, expr);

      int token = parseToken();

      if (token != ',') {
	_peekToken = token;
	return;
      }
    }
  }

  /**
   * Creates echo statements from an expression.
   */
  private void createEchoStatements(Location location,
                                    ArrayList<Statement> statements,
				    Expr expr)
  {
    if (expr == null) {
      // since AppendExpr.getNext() can be null.
    }
    else if (expr instanceof AppendExpr) {
      AppendExpr append = (AppendExpr) expr;

      // XXX: children of append print differently?

      createEchoStatements(location, statements, append.getValue());
      createEchoStatements(location, statements, append.getNext());
    }
    else if (expr instanceof StringLiteralExpr) {
      StringLiteralExpr string = (StringLiteralExpr) expr;
      
      Statement statement = new TextStatement(location, string.evalConstant().toString());

      statements.add(statement);
    }
    else {
      Statement statement = new EchoStatement(location, expr);

      statements.add(statement);
    }
  }

  /**
   * Parses the print statement.
   */
  private Statement parsePrint()
    throws IOException
  {
    return new ExprStatement(getLocation(), parsePrintExpr());
  }

  /**
   * Parses the print statement.
   */
  private Expr parsePrintExpr()
    throws IOException
  {
    ArrayList<Expr> args = new ArrayList<Expr>();
    args.add(parseTopExpr());

    return new FunctionExpr(getLocation(), "print", args);
  }

  /**
   * Parses the global statement.
   */
  private Statement parseGlobal()
    throws IOException
  {
    ArrayList<Statement> statementList = new ArrayList<Statement>();

    Location location = getLocation();

    while (true) {
      Expr expr = parseTopExpr();

      if (expr instanceof VarExpr) {
	VarExpr var = (VarExpr) expr;

	// php/3a6g, php/3a58
	var.getVarInfo().setReference();

	statementList.add(new GlobalStatement(location, var));
      }
      else if (expr instanceof VarVarExpr) {
	VarVarExpr var = (VarVarExpr) expr;

	statementList.add(new VarGlobalStatement(location, var));
      }
      else
	throw error(L.l("unknown expr {0} to global", expr));

      // statementList.add(new ExprStatement(expr));

      int token = parseToken();

      if (token != ',') {
	_peekToken = token;
	return BlockStatement.create(location, statementList);
      }
    }
  }

  /**
   * Parses the static statement.
   */
  private Statement parseStatic()
    throws IOException
  {
    ArrayList<Statement> statementList = new ArrayList<Statement>();

    Location location = getLocation();

    while (true) {
      expect('$');

      String name = parseIdentifier();

      VarExpr var = new VarExpr(getLocation(), _function.createVar(name));

      Expr init = null;

      int token = parseToken();

      if (token == '=') {
	init = parseExpr();
	token = parseToken();
      }

      var.getVarInfo().setReference();
      statementList.add(new StaticStatement(location, var, init));
      
      if (token != ',') {
	_peekToken = token;
	return BlockStatement.create(location, statementList);
      }
    }
  }

  /**
   * Parses the unset statement.
   */
  private Statement parseUnset()
    throws IOException
  {
    Location location = getLocation();

    ArrayList<Statement> statementList = new ArrayList<Statement>();
    parseUnset(statementList);

    return BlockStatement.create(location, statementList);
  }

  /**
   * Parses the unset statement.
   */
  private void parseUnset(ArrayList<Statement> statementList)
    throws IOException
  {
    Location location = getLocation();

    int token = parseToken();

    if (token != '(') {
      _peekToken = token;

      statementList.add(parseTopExpr().createUnset(location));

      return;
    }

    do {
      statementList.add(parseTopExpr().createUnset(getLocation()));
    } while ((token = parseToken()) == ',');

    _peekToken = token;
    expect(')');
  }

  /**
   * Parses the if statement
   */
  private Statement parseIf()
    throws IOException
  {
    boolean oldTop = _isTop;
    _isTop = false;

    try {
      Location location = getLocation();

      expect('(');

      Expr test = parseExpr();

      expect(')');

      int token = parseToken();

      if (token == ':')
	return parseAlternateIf(test, location);
      else
	_peekToken = token;

      Statement trueBlock = parseStatement();
      Statement falseBlock = null;

      token = parseToken();

      if (token == ELSEIF) {
	falseBlock = parseIf();
      }
      else if (token == ELSE) {
	falseBlock = parseStatement();
      }
      else
	_peekToken = token;

      return new IfStatement(location, test, trueBlock, falseBlock);

    } finally {
      _isTop = oldTop;
    }
  }

  /**
   * Parses the if statement
   */
  private Statement parseAlternateIf(Expr test, Location location)
    throws IOException
  {
    Statement trueBlock = BlockStatement.create(location, parseStatementList());

    Statement falseBlock = null;

    int token = parseToken();

    if (token == ELSEIF) {
      Location subLocation = getLocation();

      Expr subTest = parseExpr();
      expect(':');
      
      falseBlock = parseAlternateIf(subTest, subLocation);
    }
    else if (token == ELSE) {
      expect(':');
      
      falseBlock = BlockStatement.create(getLocation(), parseStatementList());

      expect(ENDIF);
    }
    else {
      _peekToken = token;
      expect(ENDIF);
    }

    return new IfStatement(location, test, trueBlock, falseBlock);
  }

  /**
   * Parses the switch statement
   */
  private Statement parseSwitch()
    throws IOException
  {
    Location location = getLocation();

    boolean oldTop = _isTop;
    _isTop = false;

    try {
      expect('(');

      Expr test = parseExpr();

      expect(')');

      boolean isAlternate = false;

      int token = parseToken();

      if (token == ':')
	isAlternate = true;
      else if (token == '{')
	isAlternate = false;
      else {
	_peekToken = token;

	expect('{');
      }

      ArrayList<Expr[]> caseList = new ArrayList<Expr[]>();
      ArrayList<BlockStatement> blockList = new ArrayList<BlockStatement>();

      ArrayList<Integer> fallThroughList = new ArrayList<Integer>();
      BlockStatement defaultBlock = null;

      while ((token = parseToken()) == CASE || token == DEFAULT) {
        Location caseLocation = getLocation();

	ArrayList<Expr> valueList = new ArrayList<Expr>();
	boolean isDefault = false;
      
	while (token == CASE || token == DEFAULT) {
          if (token == CASE) {
	    Expr value = parseExpr();

	    valueList.add(value);
	  }
	  else
	    isDefault = true;

	  token = parseToken();
	  if (token == ':') {
	  }
	  else if (token == ';') {
	    // XXX: warning?
	  }
	  else
	    throw error("expected ':' at " + tokenName(token));

	  token = parseToken();
	}

	_peekToken = token;

	Expr []values = new Expr[valueList.size()];
	valueList.toArray(values);

	ArrayList<Statement> newBlockList = parseStatementList();

	for (int fallThrough : fallThroughList) {
	  BlockStatement block = blockList.get(fallThrough);

	  boolean isDefaultBlock = block == defaultBlock;

	  block = block.append(newBlockList);

	  blockList.set(fallThrough, block);

	  if (isDefaultBlock)
	    defaultBlock = block;
	}
      
	BlockStatement block = new BlockStatement(caseLocation, newBlockList);

	if (values.length > 0) {
	  caseList.add(values);

	  blockList.add(block);
	}

	if (isDefault)
	  defaultBlock = block;

	if (blockList.size() > 0 &&
	    ! fallThroughList.contains(blockList.size() - 1)) {
	  fallThroughList.add(blockList.size() - 1);
	}

	  
	if (block.fallThrough() != Statement.FALL_THROUGH)
	  fallThroughList.clear();
      }

      _peekToken = token;

      if (isAlternate)
	expect(ENDSWITCH);
      else
	expect('}');

      return new SwitchStatement(location, test, caseList, blockList, defaultBlock);
    } finally {
      _isTop = oldTop;
    }
  }

  /**
   * Parses the 'while' statement
   */
  private Statement parseWhile()
    throws IOException
  {
    boolean oldTop = _isTop;
    _isTop = false;

    try {
      Location location = getLocation();

      expect('(');

      Expr test = parseExpr();

      expect(')');

      Statement block;

      int token = parseToken();

      if (token == ':') {
	block = BlockStatement.create(getLocation(), parseStatementList());

	expect(ENDWHILE);
      }
      else {
	_peekToken = token;
    
	block = parseStatement();
      }

      return new WhileStatement(location, test, block);
    } finally {
      _isTop = oldTop;
    }
  }

  /**
   * Parses the 'do' statement
   */
  private Statement parseDo()
    throws IOException
  {
    boolean oldTop = _isTop;
    _isTop = false;

    try {
      Location location = getLocation();

      Statement block = parseStatement();

      expect(WHILE);
      expect('(');

      Expr test = parseExpr();

      expect(')');
    
      return new DoStatement(location, test, block);
    } finally {
      _isTop = oldTop;
    }
  }

  /**
   * Parses the 'for' statement
   */
  private Statement parseFor()
    throws IOException
  {
    boolean oldTop = _isTop;
    _isTop = false;

    try {
      Location location = getLocation();

      expect('(');

      Expr init = null;

      int token = parseToken();
      if (token != ';') {
	_peekToken = token;
	init = parseTopCommaExpr();
	expect(';');
      }

      Expr test = null;

      token = parseToken();
      if (token != ';') {
	_peekToken = token;
	test = parseTopExpr();
	expect(';');
      }

      Expr incr = null;

      token = parseToken();
      if (token != ')') {
	_peekToken = token;
	incr = parseTopCommaExpr();
	expect(')');
      }

      Statement block;

      token = parseToken();

      if (token == ':') {
	block = BlockStatement.create(getLocation(), parseStatementList());

	expect(ENDFOR);
      }
      else {
	_peekToken = token;
	
	block = parseStatement();
      }

      return new ForStatement(location, init, test, incr, block);

    } finally {
      _isTop = oldTop;
    }
  }

  /**
   * Parses the 'foreach' statement
   */
  private Statement parseForeach()
    throws IOException
  {
    boolean oldTop = _isTop;
    _isTop = false;

    try {
      Location location = getLocation();

      expect('(');

      Expr objExpr = parseTopExpr();

      expect(AS);

      boolean isRef = false;

      int token = parseToken();
      if (token == '&')
	isRef = true;
      else
	_peekToken = token;

      AbstractVarExpr valueExpr = (AbstractVarExpr) parseLeftHandSide();

      AbstractVarExpr keyVar = null;
      AbstractVarExpr valueVar;

      token = parseToken();

      if (token == ARRAY_RIGHT) {
	if (isRef)
	  throw error(L.l("key reference is forbidden in foreach"));
	
	keyVar = valueExpr;

	token = parseToken();

	if (token == '&')
	  isRef = true;
	else
	  _peekToken = token;

	valueVar = (AbstractVarExpr) parseLeftHandSide();

	token = parseToken();
      }
      else
	valueVar = valueExpr;
      

      if (token != ')')
	throw error(L.l("expected ')' in foreach"));
    
      Statement block;

      token = parseToken();

      if (token == ':') {
	block = BlockStatement.create(getLocation(), parseStatementList());

	expect(ENDFOREACH);
      }
      else {
	_peekToken = token;

	block = parseStatement();
      }

      return new ForeachStatement(location, objExpr, keyVar, valueVar, isRef, block);
    } finally {
      _isTop = oldTop;
    }
  }

  /**
   * Parses the try statement
   */
  private Statement parseTry()
    throws IOException
  {
    boolean oldTop = _isTop;
    _isTop = false;

    try {
      Location location = getLocation();

      Statement block = parseStatement();

      TryStatement stmt = new TryStatement(location, block);
      
      int token = parseToken();

      while (token == CATCH) {
	expect('(');
	
	String id = parseIdentifier();

	AbstractVarExpr lhs = parseLeftHandSide();
	
	expect(')');

	block = parseStatement();

	stmt.addCatch(id, lhs, block);

	token = parseToken();
      }

      _peekToken = token;

      return stmt;
    } finally {
      _isTop = oldTop;
    }
  }

  /**
   * Parses a function definition
   */
  private Function parseFunctionDefinition(int modifiers)
    throws IOException
  {
    boolean oldTop = _isTop;
    _isTop = false;
    
    boolean oldReturnsReference = _returnsReference;
    FunctionInfo oldFunction = _function;

    boolean isAbstract = (modifiers & M_ABSTRACT) != 0;

    if (_quercusClass != null && _quercusClass.isInterface())
      isAbstract = true;

    try {
      _returnsReference = false;

      int token = parseToken();

      if (token == '&')
	_returnsReference = true;
      else
	_peekToken = token;

      String name = parseIdentifier();

      if (isAbstract && ! _scope.isAbstract()) {
	if (_quercusClass != null)
	  throw error(L.l("'{0}' may not be abstract because class {1} is not abstract.",
			  name, _quercusClass.getName()));
	else
	  throw error(L.l("'{0}' may not be abstract.  Abstract functions are only allowed in abstract classes.",
			  name));
      }

      _function = new FunctionInfo(_quercus, name);
      _function.setDeclaringClass(_quercusClass);
      _function.setPageStatic(oldTop);
      
      _function.setReturnsReference(_returnsReference);

      Location location = getLocation();

      expect('(');

      ArrayList<Arg> args = parseFunctionArgDefinition();
      
      expect(')');
      
      Function function;
      
      if (isAbstract) {
	expect(';');

	function = new MethodDeclaration(location,
					 _quercusClass, name,
					 _function, args);
      }
      else {
	expect('{');
      
	ArrayList<Statement> statementList;

	statementList = parseStatementList();
    
	expect('}');
	
	if (_quercusClass != null)
	  function = new ObjectMethod(location, _quercusClass,
				      name, _function,
				      args, statementList);
	else
	  function = new Function(location, name,
				  _function, args,
				  statementList);
      }

      function.setGlobal(oldTop);
      function.setStatic((modifiers & M_STATIC) != 0);

      _scope.addFunction(name, function);

      /*
    com.caucho.vfs.WriteStream out = com.caucho.vfs.Vfs.lookup("stdout:").openWrite();
    out.setFlushOnNewline(true);
    function.debug(new JavaWriter(out));
      */

      return function;
    } finally {
      _returnsReference = oldReturnsReference;
      _function = oldFunction;
      _isTop = oldTop;
    }
  }

  private ArrayList<Arg> parseFunctionArgDefinition()
    throws IOException
  {
    ArrayList<Arg> args = new ArrayList<Arg>();

    while (true) {
      int token = parseToken();
      boolean isReference = false;

      if (token == '&') {
	isReference = true;
	token = parseToken();
      }
	
      if (token != '$') {
	_peekToken = token;
	break;
      }
      
      String argName = parseIdentifier();
      Expr defaultExpr = RequiredExpr.REQUIRED;

      token = parseToken();
      if (token == '=') {
	// XXX: actually needs to be primitive
	defaultExpr = parseTerm();
	
	token = parseToken();
      }

      Arg arg = new Arg(argName, defaultExpr, isReference);

      args.add(arg);

      VarInfo var = _function.createVar(argName);
      var.setArgument(true);
      var.setArgumentIndex(args.size() - 1);

      if (isReference)
	var.setRefArgument();
      
      if (token != ',') {
	_peekToken = token;
	break;
      }
    }

    return args;
  }

  /**
   * Parses the 'return' statement
   */
  private Statement parseReturn()
    throws IOException
  {
    Location location = getLocation();

    int token = parseToken();

    switch (token) {
    case ';':
      _peekToken = token;

      return new ReturnStatement(location, NullLiteralExpr.NULL);
      
    default:
      _peekToken = token;

      Expr expr = parseTopExpr();

      /*
      if (_returnsReference)
	expr = expr.createRef();
      else
	expr = expr.createCopy();
      */

      if (_returnsReference)
	return new ReturnRefStatement(location, expr);
      else
	return new ReturnStatement(location, expr);
    }
  }

  /**
   * Parses the 'throw' statement
   */
  private Statement parseThrow()
    throws IOException
  {
    Location location = getLocation();
    
    Expr expr = parseExpr();

    return new ThrowStatement(location, expr);
  }

  /**
   * Parses a class definition
   */
  private Statement parseClassDefinition(int modifiers)
    throws IOException
  {
    String name = parseIdentifier();

    String parentName = null;

    int token = parseToken();
    if (token == EXTENDS) {
      parentName = parseIdentifier();
      token = parseToken();
    }

    ArrayList<String> ifaceList = new ArrayList<String>();

    if (token == IMPLEMENTS) {
      do {
	ifaceList.add(parseIdentifier());

	token = parseToken();
      } while (token == ',');
    }

    _peekToken = token;

    InterpretedClassDef oldClass = _quercusClass;
    Scope oldScope = _scope;

    try {
      _quercusClass = oldScope.addClass(name, parentName, ifaceList);

      if ((modifiers & M_ABSTRACT) != 0)
	_quercusClass.setAbstract(true);
      if ((modifiers & M_INTERFACE) != 0)
	_quercusClass.setInterface(true);
    
      _scope = new ClassScope(_quercusClass);

      expect('{');

      parseClassContents();

      expect('}');

      return new ClassDefStatement(getLocation(), _quercusClass);
    } finally {
      _quercusClass = oldClass;
      _scope = oldScope;
    }
  }

  /**
   * Parses a statement list.
   */
  private void parseClassContents()
    throws IOException
  {
    while (true) {
      int token = parseToken();

      switch (token) {
      case ';':
	break;

      case FUNCTION:
	{
          Function fun = parseFunctionDefinition(0);
	  fun.setStatic(false);
	  break;
	}

      case CLASS:
	parseClassDefinition(0);
	break;

	/* quercus/0260
      case VAR:
	parseClassVarDefinition(false);
	break;
	*/

      case CONST:
	parseClassConstDefinition();
	break;

      case PUBLIC:
      case PRIVATE:
      case PROTECTED:
      case STATIC:
      case FINAL:
      case ABSTRACT:
	{
	  _peekToken = token;

	  int modifiers = parseModifiers();
	  
	  int token2 = parseToken();

	  if (token2 == FUNCTION) {
	    Function fun = parseFunctionDefinition(modifiers);
	  }
	  else {
	    _peekToken = token2;
	    
	    parseClassVarDefinition((modifiers & M_STATIC) != 0);
	  }
	}
	break;

      case IDENTIFIER:
	if (_lexeme.equals("var")) {
	  parseClassVarDefinition(false);
	}
	else {
	  _peekToken = token;
	  return;
	}
	break;

      case -1:
      case '}':
      default:
	_peekToken = token;
	return;
      }
    }
  }

  /**
   * Parses a function definition
   */
  private void parseClassVarDefinition(boolean isStatic)
    throws IOException
  {
    int token;
    
    do {
      expect('$');
      String name = parseIdentifier();

      token = parseToken();

      Expr expr = null;

      if (token == '=') {
	expr = parseExpr();
      }
      else {
	_peekToken = token;
	expr = NullLiteralExpr.NULL;
      }

      if (isStatic)
	((ClassScope) _scope).addStaticVar(name, expr);
      else
	((ClassScope) _scope).addVar(name, expr);

      token = parseToken();
    } while (token == ',');

    _peekToken = token;
  }

  /**
   * Parses a const definition
   */
  private void parseClassConstDefinition()
    throws IOException
  {
    int token;
    
    do {
      String name = parseIdentifier();
      
      expect('=');

      Expr expr = parseExpr();

      ((ClassScope) _scope).addConstant(name, expr);

      token = parseToken();
    } while (token == ',');

    _peekToken = token;
  }

  private int parseModifiers()
    throws IOException
  {
    int token;
    int modifiers = 0;

    while (true) {
      token = parseToken();

      switch (token) {
      case PUBLIC:
	modifiers |= M_PUBLIC;
	break;

      case PRIVATE:
	modifiers |= M_PRIVATE;
	break;

      case PROTECTED:
	modifiers |= M_PROTECTED;
	break;

      case FINAL:
	modifiers |= M_FINAL;
	break;

      case STATIC:
	modifiers |= M_STATIC;
	break;

      case ABSTRACT:
	modifiers |= M_ABSTRACT;
	break;

      default:
	_peekToken = token;
	return modifiers;
      }
    }
  }

  /**
   * Parses an expression statement.
   */
  private Statement parseExprStatement()
    throws IOException
  {
    Location location = getLocation();

    Expr expr = parseTopExpr();

    Statement statement = new ExprStatement(location, expr);

    int token = parseToken();
    _peekToken = token;

    switch (token) {
    case -1:
    case ';':
    case '}':
    case PHP_END:
    case TEXT:
    case TEXT_ECHO:
      break;

    default:
      expect(';');
      break;
    }
    
    return statement;
  }

  /**
   * Parses a top expression.
   */
  private Expr parseTopExpr()
    throws IOException
  {
    return parseExpr();
  }

  /**
   * Parses a top expression.
   */
  private Expr parseTopCommaExpr()
    throws IOException
  {
    return parseCommaExpr();
  }

  /**
   * Parses a comma expression.
   */
  private Expr parseCommaExpr()
    throws IOException
  {
    Expr expr = parseExpr();

    while (true) {
      int token = parseToken();

      switch (token) {
      case ',':
	expr = new CommaExpr(getLocation(), expr, parseExpr());
	break;
      default:
	_peekToken = token;
	return expr;
      }
    }
  }

  /**
   * Parses an expression with optional '&'.
   */
  private Expr parseRefExpr()
    throws IOException
  {
    int token = parseToken();

    boolean isRef = token == '&';

    if (! isRef)
      _peekToken = token;
    
    Expr expr = parseExpr();

    if (isRef)
      expr = new RefExpr(getLocation(), expr);

    return expr;
  }

  /**
   * Parses an expression.
   */
  private Expr parseExpr()
    throws IOException
  {
    return parseWeakOrExpr();
  }

  /**
   * Parses a logical xor expression.
   */
  private Expr parseWeakOrExpr()
    throws IOException
  {
    Expr expr = parseWeakXorExpr();

    while (true) {
      int token = parseToken();

      switch (token) {
      case OR_RES:
	expr = new OrExpr(getLocation(), expr, parseWeakXorExpr());
	break;
      default:
	_peekToken = token;
	return expr;
      }
    }
  }

  /**
   * Parses a logical xor expression.
   */
  private Expr parseWeakXorExpr()
    throws IOException
  {
    Expr expr = parseWeakAndExpr();

    while (true) {
      int token = parseToken();

      switch (token) {
      case XOR_RES:
	expr = new XorExpr(getLocation(), expr, parseWeakAndExpr());
	break;
      default:
	_peekToken = token;
	return expr;
      }
    }
  }

  /**
   * Parses a logical and expression.
   */
  private Expr parseWeakAndExpr()
    throws IOException
  {
    Expr expr = parseConditionalExpr();

    while (true) {
      int token = parseToken();

      switch (token) {
      case AND_RES:
	expr = new AndExpr(getLocation(), expr, parseConditionalExpr());
	break;
      default:
	_peekToken = token;
	return expr;
      }
    }
  }

  /**
   * Parses a conditional expression.
   */
  private Expr parseConditionalExpr()
    throws IOException
  {
    Expr expr = parseOrExpr();

    while (true) {
      int token = parseToken();

      switch (token) {
      case '?':
	Expr trueExpr = parseExpr();
	expect(':');
	// php/33c1
	expr = new ConditionalExpr(getLocation(), expr, trueExpr, parseOrExpr());
	break;
      default:
	_peekToken = token;
	return expr;
      }
    }
  }

  /**
   * Parses a logical or expression.
   */
  private Expr parseOrExpr()
    throws IOException
  {
    Expr expr = parseAndExpr();

    while (true) {
      int token = parseToken();

      switch (token) {
      case C_OR:
	expr = new OrExpr(getLocation(), expr, parseAndExpr());
	break;
      default:
	_peekToken = token;
	return expr;
      }
    }
  }

  /**
   * Parses a logical and expression.
   */
  private Expr parseAndExpr()
    throws IOException
  {
    Expr expr = parseBitOrExpr();

    while (true) {
      int token = parseToken();

      switch (token) {
      case C_AND:
	expr = new AndExpr(getLocation(), expr, parseBitOrExpr());
	break;
      default:
	_peekToken = token;
	return expr;
      }
    }
  }

  /**
   * Parses a bit or expression.
   */
  private Expr parseBitOrExpr()
    throws IOException
  {
    Expr expr = parseBitXorExpr();

    while (true) {
      int token = parseToken();

      switch (token) {
      case '|':
	expr = new BitOrExpr(getLocation(), expr, parseBitXorExpr());
	break;
      default:
	_peekToken = token;
	return expr;
      }
    }
  }

  /**
   * Parses a bit xor expression.
   */
  private Expr parseBitXorExpr()
    throws IOException
  {
    Expr expr = parseBitAndExpr();

    while (true) {
      int token = parseToken();

      switch (token) {
      case '^':
	expr = new BitXorExpr(getLocation(), expr, parseBitAndExpr());
	break;
      default:
	_peekToken = token;
	return expr;
      }
    }
  }

  /**
   * Parses a bit and expression.
   */
  private Expr parseBitAndExpr()
    throws IOException
  {
    Expr expr = parseEqExpr();

    while (true) {
      int token = parseToken();

      switch (token) {
      case '&':
	expr = new BitAndExpr(getLocation(), expr, parseEqExpr());
	break;
      default:
	_peekToken = token;
	return expr;
      }
    }
  }

  /**
   * Parses a comparison expression.
   */
  private Expr parseEqExpr()
    throws IOException
  {
    Expr expr = parseCmpExpr();

    int token = parseToken();

    switch (token) {
    case EQ:
      return new EqExpr(getLocation(), expr, parseCmpExpr());
      
    case NEQ:
      return new NeqExpr(getLocation(), expr, parseCmpExpr());
      
    case EQUALS:
      return new EqualsExpr(getLocation(), expr, parseCmpExpr());
      
    case NEQUALS:
      return new NotExpr(getLocation(), new EqualsExpr(getLocation(), expr, parseCmpExpr()));
      
    default:
      _peekToken = token;
      return expr;
    }
  }

  /**
   * Parses a comparison expression.
   */
  private Expr parseCmpExpr()
    throws IOException
  {
    Expr expr = parseShiftExpr();

    int token = parseToken();

    switch (token) {
    case '<':
      return new LtExpr(getLocation(), expr, parseShiftExpr());

    case '>':
      return new GtExpr(getLocation(), expr, parseShiftExpr());

    case LEQ:
      return new LeqExpr(getLocation(), expr, parseShiftExpr());

    case GEQ:
      return new GeqExpr(getLocation(), expr, parseShiftExpr());

    case INSTANCEOF:
      return new InstanceOfExpr(getLocation(), expr, parseIdentifier());
      
    default:
      _peekToken = token;
      return expr;
    }
  }

  /**
   * Parses a left/right shift expression.
   */
  private Expr parseShiftExpr()
    throws IOException
  {
    Expr expr = parseAddExpr();

    while (true) {
      int token = parseToken();

      switch (token) {
      case LSHIFT:
	expr = new LeftShiftExpr(getLocation(), expr, parseAddExpr());
	break;
      case RSHIFT:
	expr = new RightShiftExpr(getLocation(), expr, parseAddExpr());
	break;
      default:
	_peekToken = token;
	return expr;
      }
    }
  }

  /**
   * Parses an add/substract expression.
   */
  private Expr parseAddExpr()
    throws IOException
  {
    Expr expr = parseMulExpr();

    while (true) {
      int token = parseToken();

      switch (token) {
      case '+':
	expr = new AddExpr(getLocation(), expr, parseMulExpr());
	break;
      case '-':
	expr = new SubExpr(getLocation(), expr, parseMulExpr());
	break;
      case '.':
	expr = AppendExpr.create(expr, parseMulExpr());
	break;
      default:
	_peekToken = token;
	return expr;
      }
    }
  }

  /**
   * Parses a multiplication/division expression.
   */
  private Expr parseMulExpr()
    throws IOException
  {
    Expr expr = parseAssignExpr();

    while (true) {
      int token = parseToken();

      switch (token) {
      case '*':
	expr = new MulExpr(getLocation(), expr, parseAssignExpr());
	break;
      case '/':
	expr = new DivExpr(getLocation(), expr, parseAssignExpr());
	break;
      case '%':
	expr = new ModExpr(getLocation(), expr, parseAssignExpr());
	break;
      default:
	_peekToken = token;
	return expr;
      }
    }
  }

  /**
   * Parses an assignment expression.
   */
  private Expr parseAssignExpr()
    throws IOException
  {
    Expr expr = parseTerm();

    while (true) {
      int token = parseToken();

      switch (token) {
      case '=':
	token = parseToken();
	
	try {
	  if (token == '&') {
	    // php/03d6
	    expr = expr.createAssignRef(this,
                                        parseBitOrExpr());
	  }
	  else {
	    _peekToken = token;
	    expr = expr.createAssign(this, parseConditionalExpr());
	  }
	} catch (QuercusParseException e) {
	  throw e;
	} catch (IOException e) {
	  throw error(e.getMessage());
	}
	break;
	
      case PLUS_ASSIGN:
	if (expr.canRead())
	  expr = expr.createAssign(this,
                                   new AddExpr(getLocation(), expr.copy(
                                     getLocation()),
                                               parseConditionalExpr()));
	else // php/03d4
	  expr = expr.createAssign(this, parseConditionalExpr());
	break;
	
      case MINUS_ASSIGN:
	if (expr.canRead())
	  expr = expr.createAssign(this,
                                   new SubExpr(getLocation(), expr.copy(
                                     getLocation()),
                                               parseConditionalExpr()));
	else
	  expr = expr.createAssign(this, parseConditionalExpr());
	break;
	
      case APPEND_ASSIGN:
	if (expr.canRead())
	  expr = expr.createAssign(this, AppendExpr.create(expr.copy(getLocation()),
                                                           parseConditionalExpr()));
	else
	  expr = expr.createAssign(this, parseConditionalExpr());
	break;
	
      case MUL_ASSIGN:
	if (expr.canRead())
	  expr = expr.createAssign(this,
                                   new MulExpr(getLocation(), expr.copy(
                                     getLocation()),
                                               parseConditionalExpr()));
	else
	  expr = expr.createAssign(this, parseConditionalExpr());
	break;
	
      case DIV_ASSIGN:
	if (expr.canRead())
	  expr = expr.createAssign(this,
                                   new DivExpr(getLocation(), expr.copy(
                                     getLocation()),
                                               parseConditionalExpr()));
	else
	  expr = expr.createAssign(this, parseConditionalExpr());
	break;
	
      case MOD_ASSIGN:
	if (expr.canRead())
	  expr = expr.createAssign(this,
                                   new ModExpr(getLocation(), expr.copy(
                                     getLocation()),
                                               parseConditionalExpr()));
	else
	  expr = expr.createAssign(this, parseConditionalExpr());
	break;
	
      case LSHIFT_ASSIGN:
	if (expr.canRead())
	  expr = expr.createAssign(this,
                                   new LeftShiftExpr(getLocation(), expr.copy(
                                     getLocation()),
                                                     parseConditionalExpr()));
	else
	  expr = expr.createAssign(this, parseConditionalExpr());
	break;
	
      case RSHIFT_ASSIGN:
	if (expr.canRead())
	  expr = expr.createAssign(this,
                                   new RightShiftExpr(getLocation(), expr.copy(
                                     getLocation()),
                                                      parseConditionalExpr()));
	else
	  expr = expr.createAssign(this, parseConditionalExpr());
	break;
	
      case AND_ASSIGN:
	if (expr.canRead())
	  expr = expr.createAssign(this,
                                   new BitAndExpr(getLocation(), expr.copy(
                                     getLocation()),
                                                  parseConditionalExpr()));
	else
	  expr = expr.createAssign(this, parseConditionalExpr());
	break;
	
      case OR_ASSIGN:
	if (expr.canRead())
	  expr = expr.createAssign(this,
                                   new BitOrExpr(getLocation(), expr.copy(
                                     getLocation()),
                                                 parseConditionalExpr()));
	else
	  expr = expr.createAssign(this, parseConditionalExpr());
	break;
	
      case XOR_ASSIGN:
	if (expr.canRead())
	  expr = expr.createAssign(this,
                                   new BitXorExpr(getLocation(), expr.copy(
                                     getLocation()),
                                                  parseConditionalExpr()));
	else
	  expr = expr.createAssign(this, parseConditionalExpr());
	break;
	
      default:
	_peekToken = token;
	return expr;
      }
    }
  }

  /**
   * Parses a basic term.
   *
   * <pre>
   * term ::= termBase
   *      ::= term '[' index ']'
   *      ::= term '{' index '}'
   *      ::= term '(' index ')'
   * </pre>
   */
  private Expr parseTerm()
    throws IOException
  {
    Expr term = parseTermBase();

    while (true) {
      int token = parseToken();

      switch (token) {
      case '[':
        {
	  token = parseToken();
	  
	  if (token == ']') {
	    term = new ArrayTailExpr(getLocation(), term);
	  }
	  else {
	    _peekToken = token;
	    Expr index = parseExpr();
	    token = parseToken();

	    term = new ArrayGetExpr(getLocation(), term, index);
	  }

          if (token != ']')
            throw expect("']'", token);
        }
        break;
	
      case '{':
        {
          Expr index = parseExpr();

	  expect('}');

          term = new CharAtExpr(getLocation(), term, index);
        }
        break;

      case INCR:
	term = new PostIncrementExpr(getLocation(), term, 1);
	break;

      case DECR:
	term = new PostIncrementExpr(getLocation(), term, -1);
	break;

      case DEREF:
	term = parseDeref(term);
        break;

      case '(':
	_peek = token;
	term = parseFunction(term);
        break;

      default:
        _peekToken = token;
        return term;
      }
    }
  }

  /**
   * Parses a basic term.
   *
   * <pre>
   * term ::= termBase
   *      ::= term '[' index ']'
   *      ::= term '{' index '}'
   *      ::= term '->' field;
   * </pre>
   *
   * No function, though.
   */
  private Expr parseTermDeref()
    throws IOException
  {
    Expr term = parseTermBase();

    while (true) {
      int token = parseToken();

      switch (token) {
      case '[':
        {
	  token = parseToken();
	  
	  if (token == ']') {
	    term = new ArrayTailExpr(getLocation(), term);
	  }
	  else {
	    _peekToken = token;
	    Expr index = parseExpr();
	    token = parseToken();

	    term = new ArrayGetExpr(getLocation(), term, index);
	  }

          if (token != ']')
            throw expect("']'", token);
        }
        break;
	
      case '{':
        {
          Expr index = parseExpr();

	  expect('}');

          term = new CharAtExpr(getLocation(), term, index);
        }
        break;

      case INCR:
	term = new PostIncrementExpr(getLocation(), term, 1);
	break;

      case DECR:
	term = new PostIncrementExpr(getLocation(), term, -1);
	break;

      case DEREF:
	term = parseDeref(term);
        break;

      default:
        _peekToken = token;
        return term;
      }
    }
  }
  
  /**
   * Parses a deref
   *
   * <pre>
   * deref ::= term -> IDENTIFIER
   *       ::= term -> IDENTIFIER '(' args ')'
   * </pre>
   */
  private Expr parseDeref(Expr term)
    throws IOException
  {
    String name = null;
    Expr nameExpr = null;

    int token = parseToken();
    
    if (token == '$') {
      _peekToken = token;
      nameExpr = parseTermBase();

      // php/09e0
      token = parseToken();
      switch (token) {
      case '[':
	Expr index = parseExpr();

	token = parseToken();
	if (token != ']')
	  throw expect("']'", token);

	nameExpr = new ArrayGetExpr(getLocation(), nameExpr, index);
	break;
	
      default:
	_peekToken = token;
	break;
      }
    }
    else if (token == '{') {
      nameExpr = parseExpr();
      expect('}');
    }
    else {
      _peekToken = token;
      name = parseIdentifier();
    }

    token = parseToken();
    if (token == '(') {
      ArrayList<Expr> args = new ArrayList<Expr>();

      parseFunctionArgs(args);

      if (nameExpr != null)
	return new VarMethodCallExpr(getLocation(), term, nameExpr, args);
      else if (term instanceof ThisExpr)
	return new ThisMethodCallExpr(getLocation(), term, name, args);
      else
	return new MethodCallExpr(getLocation(), term, name, args);
    }
    else if (nameExpr != null) {
      _peekToken = token;

      return term.createFieldGet(getLocation(), nameExpr);
    }
    else {
      _peekToken = token;

      return term.createFieldGet(getLocation(), name);
    }
  }
  
  /**
   * Parses a basic term.
   *
   * <pre>
   * term ::= STRING
   *      ::= LONG
   *      ::= DOUBLE
   * </pre>
   */
  private Expr parseTermBase()
    throws IOException
  {
    int token = parseToken();

    switch (token) {
    case STRING:
      return new StringLiteralExpr(getLocation(), _lexeme);
      
    case SYSTEM_STRING:
      {
	ArrayList<Expr> args = new ArrayList<Expr>();
	args.add(new StringLiteralExpr(getLocation(), _lexeme));
	return new FunctionExpr(getLocation(), "shell_exec", args);
      }
      
    case SIMPLE_SYSTEM_STRING:
      {
	ArrayList<Expr> args = new ArrayList<Expr>();
	args.add(parseEscapedString(_lexeme, SIMPLE_STRING_ESCAPE, true));
	return new FunctionExpr(getLocation(), "shell_exec", args);
      }

    case COMPLEX_SYSTEM_STRING:
      {
	ArrayList<Expr> args = new ArrayList<Expr>();
	args.add(parseEscapedString(_lexeme, COMPLEX_STRING_ESCAPE, true));
	return new FunctionExpr(getLocation(), "shell_exec", args);
      }
      
    case SIMPLE_STRING_ESCAPE:
    case COMPLEX_STRING_ESCAPE:
      return parseEscapedString(_lexeme, token, false);
      
    case LONG:
      return new LiteralExpr(getLocation(), LongValue.create(Long.parseLong(_lexeme)));
      
    case DOUBLE:
      return new LiteralExpr(getLocation(), new DoubleValue(Double.parseDouble(_lexeme)));

    case NULL:
      return NullLiteralExpr.NULL;

    case TRUE:
      return new LiteralExpr(getLocation(), BooleanValue.TRUE);

    case FALSE:
      return new LiteralExpr(getLocation(), BooleanValue.FALSE);

    case '$':
      return parseVariable();

      /* quercus/0211
    case '&':
      {
	Expr expr = parseTerm();
	
	return expr.createRef();
      }
      */

    case '-':
      {
        Expr expr = parseTerm();

        return new MinusExpr(getLocation(), expr);
      }

    case '+':
      {
        Expr expr = parseTerm();

        return new PlusExpr(getLocation(), expr);
      }

    case '!':
      {
	// XXX: quercus/03i3 vs quercus/03i4
	
        Expr expr = parseTerm();

	token = parseToken();

	if (token == '=') {
	  return new NotExpr(getLocation(), expr.createAssign(this,
                                                              parseConditionalExpr()));
	}
	else {
	  _peekToken = token;
	  
	  return new NotExpr(getLocation(), expr);
	}
      }

    case '~':
      {
        Expr expr = parseTerm();

        return new BitNotExpr(getLocation(), expr);
      }

    case '@':
      {
        Expr expr = parseTerm();

	return new SuppressErrorExpr(getLocation(), expr);
      }

    case CLONE:
      {
        Expr expr = parseTerm();

	return new CloneExpr(getLocation(), expr);
      }

    case INCR:
      {
        Expr expr = parseTerm();

        return new PreIncrementExpr(getLocation(), expr, 1);
      }

    case DECR:
      {
        Expr expr = parseTerm();

        return new PreIncrementExpr(getLocation(), expr, -1);
      }

    case NEW:
      return parseNew();

    case INCLUDE:
      return new IncludeExpr(getLocation(), _sourceFile, parseExpr());
    case REQUIRE:
      return new IncludeExpr(getLocation(), _sourceFile, parseExpr(), true);
    case INCLUDE_ONCE:
      return new IncludeOnceExpr(getLocation(), _sourceFile, parseExpr());
    case REQUIRE_ONCE:
      return new IncludeOnceExpr(getLocation(), _sourceFile, parseExpr(), true);

    case LIST:
      return parseList();

    case PRINT:
      return parsePrintExpr();
      
    case EXIT:
      return parseExit();
      
    case DIE:
      return parseDie();

    case IDENTIFIER:
      {
	if (_lexeme.equals("new"))
	  return parseNew();

	String className = null;
	String name = _lexeme;
	    
        token = parseToken();
        _peekToken = token;

	if (token == SCOPE) {
	  _peekToken = -1;
	  
	  className = name;

	  token = parseToken();
	  if (token == '$')
	    return parseStaticClassField(className);
	  else
	    _peekToken = token;
	  
	  name = parseIdentifier();

	  token = parseToken();
	  _peekToken = token;
	}
	  
        if (token == '(')
          return parseFunction(className, name);
        else
          return parseConstant(className, name);
      }

    case '(':
      {
	Expr expr = parseExpr();

	expect(')');

	if (expr instanceof ConstExpr) {
	  String type = ((ConstExpr) expr).getVar();
	  
	  if ("bool".equals(type) || "boolean".equals(type))
	    return new ToBooleanExpr(getLocation(), parseTerm());
	  else if ("int".equals(type) || "integer".equals(type))
	    return new ToLongExpr(getLocation(), parseTerm());
	  else if ("float".equals(type)
		   || "double".equals(type)
		   || "real".equals(type))
	    return new ToDoubleExpr(getLocation(), parseTerm());
	  else if ("string".equals(type))
	    return new ToStringExpr(getLocation(), parseTerm());
	  else if ("object".equals(type))
	    return new ToObjectExpr(getLocation(), parseTerm());
	  else if ("array".equalsIgnoreCase(type))
	    return new ToArrayExpr(getLocation(), parseTerm());
	}

	return expr;
      }

    default:
      throw error(L.l("{0} is an unexpected token, expected an expression.",
		      tokenName(token)));
    }
  }
  
  /**
   * Parses a basic term.
   *
   * <pre>
   * lhs ::= VARIABLE
   *     ::= lhs '[' expr ']'
   *     ::= lhs -> FIELD
   * </pre>
   */
  private AbstractVarExpr parseLeftHandSide()
    throws IOException
  {
    int token = parseToken();
    AbstractVarExpr lhs = null;

    if (token == '$')
      lhs = parseVariable();
    else
      throw error(L.l("expected variable at {0} as left-hand-side",
		      tokenName(token)));

    while (true) {
      token = parseToken();

      switch (token) {
      case '[':
        {
	  token = parseToken();
	  
	  if (token == ']') {
	    lhs = new ArrayTailExpr(getLocation(), lhs);
	  }
	  else {
	    _peekToken = token;
	    Expr index = parseExpr();
	    token = parseToken();

	    lhs = new ArrayGetExpr(getLocation(), lhs, index);
	  }

          if (token != ']')
            throw expect("']'", token);
        }
        break;
	
      case '{':
        {
          Expr index = parseExpr();

	  expect('}');

          lhs = new CharAtExpr(getLocation(), lhs, index);
        }
        break;

      case DEREF:
	lhs = (AbstractVarExpr) parseDeref(lhs);
        break;
	
      default:
	_peekToken = token;
	return lhs;
      }
    }
    
      
  }

  /**
   * Parses the next variable
   */
  private AbstractVarExpr parseVariable()
    throws IOException
  {
    int token = parseToken();

    if (token == THIS) {
      return new ThisExpr(getLocation(), _quercusClass);
    }
    else if (token == '$') {
      _peekToken = token;

      // php/0d6c
      return new VarVarExpr(getLocation(), parseTerm());
    }
    else if (token == '{') {
      AbstractVarExpr expr = new VarVarExpr(getLocation(), parseExpr());

      expect('}');

      return expr;
    }
    else if (_lexeme == null)
      throw error(L.l("Expected identifier at '{0}'", tokenName(token)));

    return new VarExpr(getLocation(), _function.createVar(_lexeme));
  }

  /**
   * Parses the next function
   */
  private Expr parseFunction(String className, String name)
    throws IOException
  {
    if (name.equalsIgnoreCase("array"))
      return parseArrayFunction();

    int token = parseToken();

    if (token != '(')
      throw error(L.l("Expected '('"));

    ArrayList<Expr> args = new ArrayList<Expr>();

    parseFunctionArgs(args);

    if (className == null) {
      if (name.equals("each")) {
	if (args.size() != 1)
	  throw error(L.l("each requires a single expression"));
      
	return new EachExpr(getLocation(), args.get(0));
      }

      return new FunctionExpr(getLocation(), name, args);
      
    }
    else if (className.equals("parent")) {
      className = _quercusClass.getParentName();
      
      return new ClassMethodExpr(getLocation(), className, name, args);
    }
    else if (className.equals("self")) {
      className = _quercusClass.getName();
      
      return new ClassMethodExpr(getLocation(), className, name, args);
    }
    else {
      return new StaticMethodExpr(getLocation(), className, name, args);
    }
  }

  /**
   * Parses the next constant
   */
  private Expr parseConstant(String className, String name)
  {
    if (className != null)
      return new ClassConstExpr(getLocation(), className, name);
    else if (name.equals("__FILE__"))
      return new StringLiteralExpr(getLocation(), _parserLocation.getFileName());
    else if (name.equals("__LINE__"))
      return new LongLiteralExpr(getLocation(), _parserLocation.getLineNumber());
    else
      return new ConstExpr(getLocation(), name);
  }

  /**
   * Parses the next constant
   */
  private Expr parseStaticClassField(String className)
    throws IOException
  {
    String var = parseIdentifier();

    return new StaticFieldGetExpr(getLocation(), className, var);
  }
  
  private ArrayList<Expr> parseArgs()
    throws IOException
  {
    ArrayList<Expr> args = new ArrayList<Expr>();

    parseFunctionArgs(args);

    return args;
  }
  
  private void parseFunctionArgs(ArrayList<Expr> args)
    throws IOException
  {
    int token;
    
    while ((token = parseToken()) > 0 && token != ')') {
      boolean isRef = false;
      
      if (token == '&')
	isRef = true;
      else
	_peekToken = token;

      Expr expr = parseExpr();

      if (isRef)
	expr = expr.createRef(getLocation());
      
      args.add(expr);

      token = parseToken();
      if (token == ')')
	break;
      else if (token != ',')
	throw expect("','", token);
    }
  }

  /**
   * Parses the next function
   */
  private Expr parseFunction(Expr name)
    throws IOException
  {
    expect('(');

    ArrayList<Expr> args = new ArrayList<Expr>();

    parseFunctionArgs(args);

    return new VarFunctionExpr(getLocation(), name, args);
  }

  /**
   * Parses the new expression
   */
  private Expr parseNew()
    throws IOException
  {
    int token = parseToken();

    String name = null;
    Expr nameExpr = null;

    if (token == IDENTIFIER)
      name = _lexeme;
    else {
      _peekToken = token;
      
      // nameExpr = parseTermBase();
      nameExpr = parseTermDeref();
    }

    token = parseToken();
    
    ArrayList<Expr> args = new ArrayList<Expr>();

    if (token != '(')
      _peekToken = token;
    else {
      while ((token = parseToken()) > 0 && token != ')') {
	_peekToken = token;

	args.add(parseExpr());

	token = parseToken();
	if (token == ')')
	  break;
	else if (token != ',')
	  throw error(L.l("expected ','"));
      }
    }

    if (name != null)
      return new NewExpr(getLocation(), name, args);
    else
      return new VarNewExpr(getLocation(), nameExpr, args);
  }

  /**
   * Parses the include expression
   */
  private Expr parseInclude()
    throws IOException
  {
    Expr name = parseExpr();

    return new IncludeExpr(getLocation(), _sourceFile, name);
  }

  /**
   * Parses the list(...) = value expression
   */
  private Expr parseList()
    throws IOException
  {
    ListHeadExpr leftVars = parseListHead();

    expect('=');

    Expr value = parseConditionalExpr();

    return ListExpr.create(this, leftVars, value);
  }

  /**
   * Parses the list(...) expression
   */
  private ListHeadExpr parseListHead()
    throws IOException
  {
    expect('(');

    int peek = parseToken();

    ArrayList<Expr> leftVars = new ArrayList<Expr>();

    while (peek > 0 && peek != ')') {
      if (peek == LIST) {
	leftVars.add(parseListHead());

	peek = parseToken();
      }
      else if (peek != ',') {
	_peekToken = peek;

	Expr left = parseTerm();

	leftVars.add(left);

	left.assign(this);

	peek = parseToken();
      }
      else {
	leftVars.add(null);
      }

      if (peek == ',')
	peek = parseToken();
      else
	break;
    }

    if (peek != ')')
      throw error(L.l("expected ')'"));

    return new ListHeadExpr(getLocation(), leftVars);
  }

  /**
   * Parses the exit/die expression
   */
  private Expr parseExit()
    throws IOException
  {
    int token = parseToken();

    if (token == '(') {
      ArrayList<Expr> args = parseArgs();

      if (args.size() > 0)
	return new ExitExpr(getLocation(), args.get(0));
      else
	return new ExitExpr(getLocation());
    }
    else {
      _peekToken = token;
      
      return new ExitExpr(getLocation());
    }
  }

  /**
   * Parses the exit/die expression
   */
  private Expr parseDie()
    throws IOException
  {
    int token = parseToken();

    if (token == '(') {
      ArrayList<Expr> args = parseArgs();

      if (args.size() > 0)
	return new DieExpr(getLocation(), args.get(0));
      else
	return new DieExpr(getLocation());
    }
    else {
      _peekToken = token;
      
      return new DieExpr(getLocation());
    }
  }

  /**
   * Parses the array() expression
   */
  private Expr parseArrayFunction()
    throws IOException
  {
    String name = _lexeme;

    int token = parseToken();

    if (token != '(')
      throw error(L.l("Expected '('"));

    ArrayList<Expr> keys = new ArrayList<Expr>();
    ArrayList<Expr> values = new ArrayList<Expr>();

    while ((token = parseToken()) > 0 && token != ')') {
      _peekToken = token;

      Expr value = parseRefExpr();

      token = parseToken();

      if (token == ARRAY_RIGHT) {
	Expr key = value;

	value = parseRefExpr();

	keys.add(key);
	values.add(value);

	token = parseToken();
      }
      else {
	keys.add(null);
	values.add(value);
      }
      
      if (token == ')')
	break;
      else if (token != ',')
	throw error(L.l("expected ','"));
    }

    return new ArrayFunExpr(getLocation(), keys, values);
  }

  private String parseIdentifier()
    throws IOException
  {
    int token = parseToken();

    if (token == IDENTIFIER)
      return _lexeme;
    else if (FIRST_IDENTIFIER_LEXEME <= token)
      return _lexeme;
    else
      throw error(L.l("expected identifier at {0}.", tokenName(token)));
  }

  /**
   * Parses the next token.
   */
  private int parseToken()
    throws IOException
  {
    int peekToken = _peekToken;
    if (peekToken > 0) {
      _peekToken = 0;
      return peekToken;
    }

    while (true) {
      int ch = read();

      switch (ch) {
      case -1:
	return -1;

      case ' ': case '\t': case '\n': case '\r':
	break;

      case '#':
	while ((ch = read()) != '\n' && ch != '\r' && ch >= 0) {
	  if (ch != '?') {
	  }
	  else if ((ch = read()) != '>') {
	    _peek = ch;
	  }
	  else {
	    ch = read();
	    if (ch == '\r')
	      ch = read();
	    if (ch != '\n')
	      _peek = ch;
    
	    return parsePhpText();
	  }
	}
	break;

      case '"':
	return parseEscapedString('"');

      case '`':
	{
	  int token = parseEscapedString('`');

	  switch (token) {
	  case STRING:
	    return SYSTEM_STRING;
	  case SIMPLE_STRING_ESCAPE:
	    return SIMPLE_SYSTEM_STRING;
	  case COMPLEX_STRING_ESCAPE:
	    return COMPLEX_SYSTEM_STRING;
	  default:
	    throw new IllegalStateException();
	  }
	}
	
      case '\'':
	parseStringToken('\'');
	return STRING;

      case ';': case '$': case '(': case ')': case '@':
      case '[': case ']': case ',': case '{': case '}':
      case '~':
	return ch;

      case '+':
	ch = read();
	if (ch == '=')
	  return PLUS_ASSIGN;
	else if (ch == '+')
	  return INCR;
	else
	  _peek = ch;

	return '+';

      case '-':
	ch = read();
	if (ch == '>')
	  return DEREF;
	else if (ch == '=')
	  return MINUS_ASSIGN;
	else if (ch == '-')
	  return DECR;
	else
	  _peek = ch;
	
	return '-';

      case '*':
	ch = read();
	if (ch == '=')
	  return MUL_ASSIGN;
	else
	  _peek = ch;

	return '*';

      case '/':
	ch = read();
	if (ch == '=')
	  return DIV_ASSIGN;
	else if (ch == '/') {
	  while (ch >= 0) {
	    if (ch == '\n' || ch == '\r') {
	      break;
	    }
	    else if (ch == '?') {
	      ch = read();

	      if (ch == '>') {
		if ((ch = read()) != '\r')
		  _peek = ch;
		else if ((ch = read()) != '\n')
		  _peek = ch;
		
		return parsePhpText();
	      }
	    }
	    else
	      ch = read();
	  }
	  break;
	}
	else if (ch == '*') {
	  skipMultilineComment();
	  break;
	}
	else
	  _peek = ch;

	return '/';

      case '%':
	ch = read();
	if (ch == '=')
	  return MOD_ASSIGN;
	else if (ch == '>') {
	  ch = read();
	  if (ch == '\r')
	    ch = read();
	  if (ch != '\n')
	    _peek = ch;
    
	  return parsePhpText();
        }
	else
	  _peek = ch;

	return '%';

      case ':':
	ch = read();
	if (ch == ':')
	  return SCOPE;
	else
	  _peek = ch;
	
	return ':';
	
      case '=':
	ch = read();
	if (ch == '=') {
	  ch = read();
	  if (ch == '=')
	    return EQUALS;
	  else {
	    _peek = ch;
	    return EQ;
	  }
	}
	else if (ch == '>')
	  return ARRAY_RIGHT;
	else {
	  _peek = ch;
	  return '=';
	}
	
      case '!':
	ch = read();
	if (ch == '=') {
	  ch = read();
	  if (ch == '=')
	    return NEQUALS;
	  else {
	    _peek = ch;
	    return NEQ;
	  }
	}
	else {
	  _peek = ch;
	  return '!';
	}

      case '&':
	ch = read();
	if (ch == '&')
	  return C_AND;
	else if (ch == '=')
	  return AND_ASSIGN;
	else {
	  _peek = ch;
	  return '&';
	}

      case '^':
	ch = read();
	if (ch == '=')
	  return XOR_ASSIGN;
	else
	  _peek = ch;
	
	return '^';

      case '|':
	ch = read();
	if (ch == '|')
	  return C_OR;
	else if (ch == '=')
	  return OR_ASSIGN;
	else {
	  _peek = ch;
	  return '|';
	}

      case '<':
	ch = read();
	if (ch == '<') {
	  ch = read();

	  if (ch == '=')
	    return LSHIFT_ASSIGN;
	  else if (ch == '<') {
	    return parseHeredocToken();
	  }
	  else
	    _peek = ch;
	  
	  return LSHIFT;
	}
	else if (ch == '=')
	  return LEQ;
	else if (ch == '>')
	  return NEQ;
	else if (ch == '/') {
	  StringBuilder sb = new StringBuilder();
	    
	  if (! parseTextMatch(sb, "script"))
	    throw error(L.l("expected 'script' at '{0}'", sb));

	  expect('>');

	  return parsePhpText();
	}
	else
	  _peek = ch;

	return '<';

      case '>':
	ch = read();
	if (ch == '>') {
	  ch = read();

	  if (ch == '=')
	    return RSHIFT_ASSIGN;
	  else
	    _peek = ch;
	  
	  return RSHIFT;
	}
	else if (ch == '=')
	  return GEQ;
	else
	  _peek = ch;

	return '>';

      case '?':
	ch = read();
	if (ch == '>') {
	  ch = read();
	  if (ch == '\r')
	    ch = read();
	  if (ch != '\n')
	    _peek = ch;
    
	  return parsePhpText();
        }
	else
	  _peek = ch;

	return '?';

      case '.':
	ch = read();

	if (ch == '=')
	  return APPEND_ASSIGN;
	
	_peek = ch;
	
	if ('0' <= ch && ch <= '9')
	  return parseNumberToken('.');
	else
	  return '.';

      case '0': case '1': case '2': case '3': case '4':
      case '5': case '6': case '7': case '8': case '9':
	return parseNumberToken(ch);
	
      default:
	if ('a' <= ch && ch <= 'z' ||
	    'A' <= ch && ch <= 'Z' ||
	    ch == '_') {
	  _sb.setLength(0);
	  _sb.append((char) ch);

	  for (ch = read();
	       ('a' <= ch && ch <= 'z' ||
		'A' <= ch && ch <= 'Z' ||
		'0' <= ch && ch <= '9' ||
		ch == '_');
	       ch = read()) {
	    _sb.append((char) ch);
	  }

	  _peek = ch;

	  _lexeme = _sb.toString();

	  int reserved = _reserved.get(_lexeme);

	  if (reserved > 0)
	    return reserved;

	  reserved = _insensitiveReserved.get(_lexeme.toLowerCase());
	  if (reserved > 0)
	    return reserved;
	  else
	    return IDENTIFIER;
	}
        
	throw error("unknown lexeme:" + (char) ch);
      }
    }
  }

  /**
   * Skips a multiline comment.
   */
  private void skipMultilineComment()
    throws IOException
  {
    int ch;

    while ((ch = read()) >= 0) {
      if (ch != '*') {
      }
      else if ((ch = read()) == '/')
	return;
      else
	_peek = ch;
    }
  }

  /**
   * Parses quercus text
   */
  private int parsePhpText()
    throws IOException
  {
    StringBuilder sb = new StringBuilder();

    int ch = read();
    while (ch > 0) {
      if (ch == '<') {
	int ch2;
	int ch3;
	
	if ((ch = read()) == 's' || ch == 'S') {
	  _peek = ch;
	  if (parseScriptBegin(sb)) {
	    return TEXT;
	  }
	  ch = read();
	}
	else if (ch == '%') {
	  if ((ch = read()) == '=') {
	    _lexeme = sb.toString();

	    return TEXT_ECHO;
	  }
	  else if (Character.isWhitespace(ch)) {
	    _lexeme = sb.toString();

	    return TEXT;
	  }
	}
	else if (ch != '?') {
	  sb.append('<');
	}
	else if ((ch = read()) == '=') {
	  _lexeme = sb.toString();

	  return TEXT_ECHO;
	}
	else if (Character.isWhitespace(ch)) {
	  _lexeme = sb.toString();

	  return TEXT;
	}
	else if (ch != 'p' && ch != 'P') {
	  sb.append("<?");
	}
	else if ((ch2 = read()) != 'h' && ch2 != 'H') {
	  sb.append("<?");
	  sb.append((char) ch);

	  ch = ch2;
	}
	else if ((ch3 = read()) != 'p' && ch3 != 'P') {
	  sb.append("<?");
	  sb.append((char) ch);
	  sb.append((char) ch2);

	  ch = ch3;
	}
	else if (! Character.isWhitespace((ch = read()))) {
	  sb.append("<?");
	  sb.append((char) ch);
	  sb.append((char) ch2);
	  sb.append((char) ch3);
	}
	else {
	  _lexeme = sb.toString();

	  return TEXT;
	}
      }
      else {
	sb.append((char) ch);

	ch = read();
      }
    }
    
    _lexeme = sb.toString();

    return TEXT;
  }

  /**
   * Parses the <script language="quercus"> opening
   */
  private boolean parseScriptBegin(StringBuilder sb)
    throws IOException
  {
    int begin = sb.length();
    
    sb.append('<');

    if (! parseTextMatch(sb, "script"))
      return false;

    parseWhitespace(sb);
    
    if (! parseTextMatch(sb, "language"))
      return false;
    
    parseWhitespace(sb);
    
    if (! parseTextMatch(sb, "=\"php\""))
      return false;

    parseWhitespace(sb);

    int ch = read();

    if (ch == '>') {
      sb.setLength(begin);
      return true;
    }
    else {
      _peek = ch;
      return false;
    }
  }
    
  private boolean parseTextMatch(StringBuilder sb, String text)
    throws IOException
  {
    int len = text.length();

    for (int i = 0; i < len; i++) {
      int ch = read();

      if (ch < 0)
	return false;

      if (Character.toLowerCase(ch) != text.charAt(i)) {
	_peek = ch;
	return false;
      }
      else
	sb.append((char) ch);
    }

    return true;
  }
    
  private void parseWhitespace(StringBuilder sb)
    throws IOException
  {
    int ch;

    while (Character.isWhitespace((ch = read()))) {
      sb.append((char) ch);
    }

    _peek = ch;
  }
  
  /**
   * Parses the next string token.
   */
  private void parseStringToken(int end)
    throws IOException
  {
    _sb.setLength(0);

    int ch;

    for (ch = read(); ch >= 0 && ch != end; ch = read()) {
      if (ch == '\\') {
        ch = read();

	if (end == '"') {
	  _sb.append('\\');

	  if (ch >= 0)
	    _sb.append((char) ch);
	}
	else {
	  switch (ch) {
	  case '\'': case '\\': case '\"':
	    _sb.append((char) ch);
	    break;
	  default:
	    _sb.append('\\');
	    _sb.append((char) ch);
	    break;
	  }
	}
      }
      else
        _sb.append((char) ch);
    }

    _lexeme = _sb.toString();
  }

  /**
   * Parses the next heredoc token.
   */
  private int parseHeredocToken()
    throws IOException
  {
    _sb.setLength(0);

    int ch;
    while ((ch = read()) >= 0 && ch != '\r' && ch != '\n') {
      _sb.append((char) ch);
    }

    _heredocEnd = _sb.toString();

    if (ch == '\n') {
    }
    else if (ch == '\r') {
      ch = read();
      if (ch != '\n')
	_peek = ch;
    }
    else
      _peek = ch;

    return parseEscapedString('"');
  }

  /**
   * Parses the next string
   */
  private Expr parseEscapedString(String prefix, int token, boolean isSystem)
    throws IOException
  {
    Expr expr = new StringLiteralExpr(getLocation(), prefix);

    while (true) {
      Expr tail;

      if (token == COMPLEX_STRING_ESCAPE) {
	tail = parseExpr();

	expect('}');
      }
      else if (token == SIMPLE_STRING_ESCAPE) {
	int ch = read();
	
	_sb.setLength(0);

	for (; ch > 0 && isIdentifierPart((char) ch); ch = read()) {
	  _sb.append((char) ch);
	}

	_peek = ch;

	String varName = _sb.toString();

	if (varName.equals("this"))
	  tail = new ThisExpr(getLocation(), _quercusClass);
	else
	  tail = new VarExpr(getLocation(), _function.createVar(varName));

	while (((ch = read()) == '[' ||  ch == '-')) {
	  if (ch == '[') {
	    tail = parseSimpleArrayTail(tail);
	  }
	  else {
	    if ((ch = read()) != '>') {
	      tail = AppendExpr.create(tail, new StringLiteralExpr(getLocation(), "-"));
	    }
	    else if (isIdentifierPart((char) (ch = read()))) {
	      _sb.clear();
	      for (; isIdentifierPart((char) ch); ch = read()) {
		_sb.append((char) ch);
	      }

	      tail = tail.createFieldGet(getLocation(), _sb.toString());
	    }
	    else {
	      tail = AppendExpr.create(tail, new StringLiteralExpr(getLocation(), "->"));
	    }

	    _peek = ch;
	  }
	}

	_peek = ch;
      }
      else
	throw new IllegalStateException();

      expr = AppendExpr.create(expr, tail);

      if (isSystem)
	token = parseEscapedString('`');
      else
	token = parseEscapedString('"');

      if (_sb.length() > 0)
	expr = AppendExpr.create(expr, new StringLiteralExpr(getLocation(), _sb.toString()));

      if (token == STRING)
	return expr;
    }
  }
  
  /**
   * Parses the next string
   */
  private Expr parseSimpleArrayTail(Expr tail)
    throws IOException
  {
    int ch = read();

    _sb.clear();

    if (ch == '$') {
      for (ch = read();
	   ch > 0 && isIdentifierPart((char) ch);
	   ch = read()) {
	_sb.append((char) ch);
      }

      VarExpr var = new VarExpr(getLocation(), _function.createVar(_sb.toString()));

      tail = new ArrayGetExpr(getLocation(), tail, var);
    }
    else if ('0' <= ch && ch <= '9') {
      long index = ch - '0';

      for (ch = read();
	   '0' <= ch && ch <= '9';
	   ch = read()) {
	index = 10 * index + ch - '0';
      }

      tail = new ArrayGetExpr(getLocation(), tail, new LongLiteralExpr(getLocation(), index));
    }
    else if (isIdentifierPart((char) ch)) {
      for (;
	   ch > 0 && isIdentifierPart((char) ch);
	   ch = read()) {
	_sb.append((char) ch);
      }

      Expr constExpr = new ConstExpr(getLocation(), _sb.toString());

      tail = new ArrayGetExpr(getLocation(), tail, constExpr);
    }
    else
      throw error(L.l("Unexpected character at {0}.",
		      String.valueOf((char) ch)));

    if (ch != ']')
      throw error(L.l("Expected ']' at {0}.",
		      String.valueOf((char) ch)));

    return tail;
  }

  /**
   * Parses the next string
   */
  private int parseEscapedString(char end)
    throws IOException
  {
    _sb.setLength(0);
    
    int ch;

    while ((ch = read()) > 0) {
      if (_heredocEnd == null && ch == end) {
	_lexeme = _sb.toString();
	return STRING;
      }
      else if (ch == '\\') {
        ch = read();

        switch (ch) {
        case '0': case '1': case '2': case '3':
          _sb.append((char) parseOctalEscape(ch));
          break;
        case 't':
          _sb.append('\t');
          break;
        case 'r':
          _sb.append('\r');
          break;
        case 'n':
          _sb.append('\n');
          break;
        case '\\':
        case '$':
        case '"':
        case '`':
          _sb.append((char) ch);
          break;
        case 'x':
          _sb.append((char) parseHexEscape());
          break;
	case '{':
	  ch = read();
	  _peek = ch;
	  if (ch == '$')
	    _sb.append('{');
	  else
	    _sb.append("\\{");
	  break;
        default:
          _sb.append('\\');
          _sb.append((char) ch);
	  break;
        }
      }
      else if (ch == '$') {
	ch = read();

	if (ch == '{') {
	  _peek = '$';
	  _lexeme = _sb.toString();
	  return COMPLEX_STRING_ESCAPE;
	}
	else if (isIdentifierStart((char) ch)) {
	  _peek = ch;
	  _lexeme = _sb.toString();
	  return SIMPLE_STRING_ESCAPE;
	}
	else {
	  _sb.append('$');
	  _peek = ch;
	}
      }
      else if (ch == '{') {
	ch = read();

	if (ch == '$') {
	  _peek = ch;
	  _lexeme = _sb.toString();
	  return COMPLEX_STRING_ESCAPE;
	}
	else {
	  _peek = ch;
	  _sb.append('{');
	}
      }
      /* quercus/013c
      else if ((ch == '\r' || ch == '\n') && _heredocEnd == null)
	throw error(L.l("unexpected newline in string."));
      */
      else {
        _sb.append((char) ch);

	if (_heredocEnd == null || ! _sb.endsWith(_heredocEnd)) {
	}
	else if (_sb.length() == _heredocEnd.length() ||
		 _sb.charAt(_sb.length() - _heredocEnd.length() - 1) == '\n' ||
		 _sb.charAt(_sb.length() - _heredocEnd.length() - 1) == '\r') {
	  _sb.setLength(_sb.length() - _heredocEnd.length());

	  if (_sb.length() > 0 && _sb.charAt(_sb.length() - 1) == '\n')
	    _sb.setLength(_sb.length() - 1);
	  if (_sb.length() > 0 && _sb.charAt(_sb.length() - 1) == '\r')
	    _sb.setLength(_sb.length() - 1);

	  _heredocEnd = null;
	  _lexeme = _sb.toString();
	  return STRING;
	}
      }
    }

    _lexeme = _sb.toString();

    return STRING;
  }
 
  private boolean isIdentifierStart(char ch)
  {
    return (ch >= 'a' && ch <= 'z' ||
	    ch >= 'A' && ch <= 'Z' ||
	    ch == '_');
  }
 
  private boolean isIdentifierPart(char ch)
  {
    return (ch >= 'a' && ch <= 'z' ||
	    ch >= 'A' && ch <= 'Z' ||
	    ch >= '0' && ch <= '9' ||
	    ch == '_');
  }

  private int parseOctalEscape(int ch)
    throws IOException
  {
    int value = ch - '0';

    ch = read();
    if (ch < '0' || ch > '7') {
      _peek = ch;
      return value;
    }

    value = 8 * value + ch - '0';

    ch = read();
    if (ch < '0' || ch > '7') {
      _peek = ch;
      return value;
    }

    value = 8 * value + ch - '0';

    return value;
  }

  private int parseHexEscape()
    throws IOException
  {
    int value = 0;

    while (true) {
      int ch = read();
      
      if ('0' <= ch && ch <= '9') {
	value = 16 * value + ch - '0';
      }
      else if ('a' <= ch && ch <= 'f') {
	value = 16 * value + 10 + ch - 'a';
      }
      else if ('A' <= ch && ch <= 'F') {
	value = 16 * value + 10 + ch - 'A';
      }
      else {
	_peek = ch;
	return value;
      }
    }
  }

  /**
   * Parses the next number.
   */
  private int parseNumberToken(int ch)
    throws IOException
  {
    if (ch == '0') {
      ch = read();
      if (ch == 'x' || ch == 'X')
	return parseHex();
      else if ('0' <= ch && ch <= '7')
	return parseOctal(ch);
      else {
	_peek = ch;
	ch = '0';
      }
    }
    
    _sb.setLength(0);

    int token = LONG;

    for (; '0' <= ch && ch <= '9'; ch = read()) {
      _sb.append((char) ch);
    }

    if (ch == '.') {
      token = DOUBLE;
      
      _sb.append((char) ch);
      
      for (ch = read(); '0' <= ch && ch <= '9'; ch = read()) {
	_sb.append((char) ch);
      }
    }

    if (ch == 'e' || ch == 'E') {
      token = DOUBLE;

      _sb.append((char) ch);

      ch = read();
      if (ch == '+' || ch == '-') {
	_sb.append((char) ch);
	ch = read();
      }

      if ('0' <= ch && ch <= '9') {
	for (; '0' <= ch && ch <= '9'; ch = read()) {
	  _sb.append((char) ch);
	}
      }
      else
	throw error(L.l("illegal exponent"));
    }

    _peek = ch;

    _lexeme = _sb.toString();

    return token;
  }

  /**
   * Parses the next as hex
   */
  private int parseHex()
    throws IOException
  {
    long value = 0;
    double dValue = 0;

    while (true) {
      int ch = read();

      if ('0' <= ch && ch <= '9') {
	value = 16 * value + ch - '0';
	dValue = 16 * dValue + ch - '0';
      }
      else if ('a' <= ch && ch <= 'f') {
	value = 16 * value + ch - 'a' + 10;
	dValue = 16 * dValue + ch - 'a' + 10;
      }
      else if ('A' <= ch && ch <= 'F') {
	value = 16 * value + ch - 'A' + 10;
	dValue = 16 * dValue + ch - 'A' + 10;
      }
      else {
	_peek = ch;
	break;
      }
    }

    if (value == dValue) {
      _lexeme = String.valueOf(value);
      return LONG;
    }
    else {
      _lexeme = String.valueOf(dValue);

      return DOUBLE;
    }
  }

  /**
   * Parses the next as octal
   */
  private int parseOctal(int ch)
    throws IOException
  {
    long value = 0;
    double dValue = 0;

    while (true) {
      if ('0' <= ch && ch <= '7') {
	value = 8 * value + ch - '0';
	dValue = 8 * dValue + ch - '0';
      }
      else {
	_peek = ch;
	break;
      }

      ch = read();
    }

    if (value == dValue) {
      _lexeme = String.valueOf(value);

      return LONG;
    }
    else {
      _lexeme = String.valueOf(dValue);

      return DOUBLE;
    }
  }

  private void expect(int expect)
    throws IOException
  {
    int token = parseToken();

    if (token != expect)
      throw error(L.l("expected {0} at {1}",
		      tokenName(expect),
		      tokenName(token)));
  }

  /**
   * Reads the next character.
   */
  private int read()
    throws IOException
  {
    int peek = _peek;

    if (peek >= 0) {
      _peek = -1;
      return peek;
    }

    try {
      int ch = _is.read();

      if (ch == '\r') {
	_parserLocation.incrementLineNumber();
	_hasCr = true;
      }
      else if (ch == '\n' && ! _hasCr)
	_parserLocation.incrementLineNumber();
      else
	_hasCr = false;

      return ch;
    } catch (IOException e) {
      throw new IOExceptionWrapper(getFileName() + ":" + getLine() + ":" + e, e);
    }
  }

  /**
   * Returns an error.
   */
  private IOException expect(String expected, int token)
  {
    return error(L.l("expected {0} at {1}", expected, tokenName(token)));
  }

  /**
   * Returns an error.
   */
  public IOException error(String msg)
  {
    int lineNumber = _parserLocation.getLineNumber();

    String []sourceLines = Env.getSourceLine(_sourceFile, lineNumber - 1, 3);

    if (sourceLines != null &&
	sourceLines.length > 0 &&
	sourceLines[0] != null) {
      StringBuilder sb = new StringBuilder();

      String shortFile = _parserLocation.getFileName();
      int p = shortFile.lastIndexOf('/');
      if (p > 0)
	shortFile = shortFile.substring(p + 1);

      sb.append(_parserLocation.toString())
        .append(msg)
        .append(" in");

      for (int i = 0; i < sourceLines.length && sourceLines[i] != null; i++) {
	sb.append("\n");
        sb.append(shortFile)
          .append(":")
          .append(lineNumber - 1 + i)
          .append(": ")
          .append(sourceLines[i]);
      }
    
      return new QuercusParseException(sb.toString());
    }
    else
      return new QuercusParseException(_parserLocation.toString() + msg);
  }

  /**
   * Returns the token name.
   */
  private String tokenName(int token)
  {
    switch (token) {
    case -1:
      return "end of file";
      
    case '\'':
      return "'";

    case AS: return "'as'";
      
    case TRUE: return "true";
    case FALSE: return "false";
      
    case AND_RES: return "'and'";
    case OR_RES: return "'or'";
    case XOR_RES: return "'xor'";
      
    case C_AND: return "'&&'";
    case C_OR: return "'||'";
      
    case IF: return "'if'";
    case ELSE: return "'else'";
    case ELSEIF: return "'elseif'";
    case ENDIF: return "'endif'";
      
    case WHILE: return "'while'";
    case ENDWHILE: return "'endwhile'";
    case DO: return "'do'";
      
    case FOR: return "'for'";
    case ENDFOR: return "'endfor'";
      
    case FOREACH: return "'foreach'";
    case ENDFOREACH: return "'endforeach'";
      
    case SWITCH: return "'switch'";
    case ENDSWITCH: return "'endswitch'";
      
    case ECHO: return "'echo'";
    case PRINT: return "'print'";
      
    case LIST: return "'list'";
    case CASE: return "'case'";
      
    case DEFAULT: return "'default'";
    case CLASS: return "'class'";
    case INTERFACE: return "'interface'";
    case EXTENDS: return "'extends'";
    case IMPLEMENTS: return "'implements'";
    case RETURN: return "'return'";
      
    case DIE: return "'die'";
    case EXIT: return "'exit'";
    case THROW: return "'throw'";
      
    case CLONE: return "'clone'";
    case INSTANCEOF: return "'instanceof'";
      
    case SIMPLE_STRING_ESCAPE: return "'string'";
    case COMPLEX_STRING_ESCAPE: return "'string'";
      
    case REQUIRE: return "'require'";
    case REQUIRE_ONCE: return "'require_once'";
      
    case PRIVATE: return "'private'";
    case PROTECTED: return "'protected'";
    case PUBLIC: return "'public'";
    case STATIC: return "'static'";
    case FINAL: return "'final'";
    case ABSTRACT: return "'abstract'";
      
    case GLOBAL: return "'global'";
      
    case FUNCTION: return "'function'";
      
    case THIS: return "'this'";
      
    case ARRAY_RIGHT: return "'=>'";
    case LSHIFT: return "'<<'";
      
    case IDENTIFIER:
      return "'" + _lexeme + "'";

    case LONG:
      return "integer (" + _lexeme + ")";

    case TEXT:
      return "TEXT (token " + token + ")";

    case STRING:
      return "string(" + _lexeme + ")";

    case TEXT_ECHO:
      return "<?=";
      
    default:
      if (32 <= token && token < 127)
	return "'" + (char) token + "'";
      else
	return "(token " + token + ")";
    }
  }

  /**
   * The location from which the last token was read.
   * @return
   */
  public Location getLocation()
  {
    return _parserLocation.getLocation();
  }

  private class ParserLocation {
    private int _lineNumber = 1;
    private String _fileName;
    private String _lastClassName;
    private String _lastFunctionName;

    private Location _location;

    public int getLineNumber()
    {
      return _lineNumber;
    }

    public void setLineNumber(int lineNumber)
    {
      _lineNumber = lineNumber;
      _location = null;
    }

    public void incrementLineNumber()
    {
      _lineNumber++;
      _location = null;
    }

    public String getFileName()
    {
      return _fileName;
    }

    public void setFileName(String fileName)
    {
      _fileName = fileName;
      _location = null;
    }

    public Location getLocation()
    {
      String currentFunctionName = _function == null || _function.isPageMain() ? null : _function.getName();
      String currentClassName = _quercusClass == null ? null : _quercusClass.getName();

      if (_location != null) {
        if (!equals(currentFunctionName, _lastFunctionName))
          _location = null;
        else if (!equals(currentClassName, _lastClassName))
          _location = null;
      }

      if (_location == null)
        _location = new Location(_fileName, _lineNumber, currentClassName, currentFunctionName);

      _lastFunctionName = currentFunctionName;
      _lastClassName = currentClassName;

      return _location;
    }

    private boolean equals(String s1, String s2)
    {
      return (s1 == null || s2 == null) ?  s1 == s2 : s1.equals(s2);
    }

    public String toString()
    {
      return _fileName + ":" + _lineNumber + ": ";
    }
  }

  static {
    _insensitiveReserved.put("echo", ECHO);
    _insensitiveReserved.put("print", PRINT);
    _insensitiveReserved.put("if", IF);
    _insensitiveReserved.put("else", ELSE);
    _insensitiveReserved.put("elseif", ELSEIF);
    _insensitiveReserved.put("do", DO);
    _insensitiveReserved.put("while", WHILE);
    _insensitiveReserved.put("for", FOR);
    _insensitiveReserved.put("function", FUNCTION);
    _insensitiveReserved.put("class", CLASS);
    // quercus/0261
    // _insensitiveReserved.put("new", NEW);
    _insensitiveReserved.put("return", RETURN);
    _insensitiveReserved.put("break", BREAK);
    _insensitiveReserved.put("continue", CONTINUE);
    // quercus/0260
    //    _insensitiveReserved.put("var", VAR);
    _insensitiveReserved.put("this", THIS);
    _insensitiveReserved.put("private", PRIVATE);
    _insensitiveReserved.put("protected", PROTECTED);
    _insensitiveReserved.put("public", PUBLIC);
    _insensitiveReserved.put("and", AND_RES);
    _insensitiveReserved.put("xor", XOR_RES);
    _insensitiveReserved.put("or", OR_RES);
    _insensitiveReserved.put("extends", EXTENDS);
    _insensitiveReserved.put("static", STATIC);
    _insensitiveReserved.put("include", INCLUDE);
    _insensitiveReserved.put("require", REQUIRE);
    _insensitiveReserved.put("include_once", INCLUDE_ONCE);
    _insensitiveReserved.put("require_once", REQUIRE_ONCE);
    _insensitiveReserved.put("unset", UNSET);
    _insensitiveReserved.put("foreach", FOREACH);
    _insensitiveReserved.put("as", AS);
    _insensitiveReserved.put("switch", SWITCH);
    _insensitiveReserved.put("case", CASE);
    _insensitiveReserved.put("default", DEFAULT);
    _insensitiveReserved.put("die", DIE);
    _insensitiveReserved.put("exit", EXIT);
    _insensitiveReserved.put("global", GLOBAL);
    _insensitiveReserved.put("list", LIST);
    _insensitiveReserved.put("endif", ENDIF);
    _insensitiveReserved.put("endwhile", ENDWHILE);
    _insensitiveReserved.put("endfor", ENDFOR);
    _insensitiveReserved.put("endforeach", ENDFOREACH);
    _insensitiveReserved.put("endswitch", ENDSWITCH);
    
    _insensitiveReserved.put("true", TRUE);
    _insensitiveReserved.put("false", FALSE);
    _insensitiveReserved.put("null", NULL);
    _insensitiveReserved.put("clone", CLONE);
    _insensitiveReserved.put("instanceof", INSTANCEOF);
    _insensitiveReserved.put("const", CONST);
    _insensitiveReserved.put("final", FINAL);
    _insensitiveReserved.put("abstract", ABSTRACT);
    _insensitiveReserved.put("throw", THROW);
    _insensitiveReserved.put("try", TRY);
    _insensitiveReserved.put("catch", CATCH);
    _insensitiveReserved.put("interface", INTERFACE);
    _insensitiveReserved.put("implements", IMPLEMENTS);
  }
}
