content: /*
   * $Header$
   * $Revision$
   * $Date$
   *
   * ====================================================================
   *
   * Copyright 2000-2002 bob mcwhirter & James Strachan.
   * All rights reserved.
   *
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are
   * met:
   * 
   *   * Redistributions of source code must retain the above copyright
   *     notice, this list of conditions and the following disclaimer.
   * 
   *   * Redistributions in binary form must reproduce the above copyright
   *     notice, this list of conditions and the following disclaimer in the
   *     documentation and/or other materials provided with the distribution.
   * 
   *   * Neither the name of the Jaxen Project nor the names of its
   *     contributors may be used to endorse or promote products derived 
   *     from this software without specific prior written permission.
   * 
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
   * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
   * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
   * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
   * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
   * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
   * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
   * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   *
   * ====================================================================
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Jaxen Project and was originally
   * created by bob mcwhirter <bob@werken.com> and
   * James Strachan <jstrachan@apache.org>.  For more information on the
   * Jaxen Project, please see <http://www.jaxen.org/>.
   *
   * $Id$
   */
  
  
  package org.jaxen.saxpath.base;
  
  import java.util.ArrayList;
  
  import org.jaxen.saxpath.Axis;
  import org.jaxen.saxpath.Operator;
  import org.jaxen.saxpath.SAXPathException;
  import org.jaxen.saxpath.XPathHandler;
  import org.jaxen.saxpath.XPathSyntaxException;
  import org.jaxen.saxpath.helpers.DefaultXPathHandler;
  
  /** Implementation of SAXPath's <code>XPathReader</code> which
   *  generates callbacks to an <code>XPathHandler</code>.
   *
   *  @author bob mcwhirter (bob@werken.com)
   */
  public class XPathReader implements org.jaxen.saxpath.XPathReader
  {
      private ArrayList  tokens;
      private XPathLexer lexer;
  
      private XPathHandler handler;
      
      private static XPathHandler defaultHandler = new DefaultXPathHandler();
  
      /**
       * Create a new <code>XPathReader</code> with a do-nothing
       * <code>XPathHandler</code>.
       */
      public XPathReader()
      {
          setXPathHandler( defaultHandler );
      }
  
      public void setXPathHandler(XPathHandler handler)
      {
          this.handler = handler;
      }
  
      public XPathHandler getXPathHandler()
      {
          return this.handler;
      }
  
      public void parse(String xpath) throws SAXPathException
      {
          setUpParse( xpath );
  
          getXPathHandler().startXPath();
  
          expr();
  
          getXPathHandler().endXPath();
  
          if ( LA(1) != TokenTypes.EOF )
          {
              XPathSyntaxException ex = createSyntaxException( "Unexpected '" + LT(1).getTokenText() + "'" );
              throw ex;
          }
  
          lexer  = null;
          tokens = null;
      }
  
      void setUpParse(String xpath)
      {
          this.tokens = new ArrayList();
          this.lexer = new XPathLexer( xpath );
      }
  
      private void pathExpr() throws SAXPathException
      {
          getXPathHandler().startPathExpr();
  
          switch ( LA(1) )
          {
              case TokenTypes.DOUBLE:
              case TokenTypes.LITERAL:
              {
                  filterExpr();
  
                  if ( LA(1) == TokenTypes.SLASH || LA(1) == TokenTypes.DOUBLE_SLASH )
                  {
                      XPathSyntaxException ex = createSyntaxException("Node-set expected");
                      throw ex;
                  }
  
                  break;
              }                
              case TokenTypes.LEFT_PAREN:
              case TokenTypes.DOLLAR:
              {
                  filterExpr();
                      
                  if ( LA(1) == TokenTypes.SLASH || LA(1) == TokenTypes.DOUBLE_SLASH)
                  {
                      locationPath( false );
                  }
                  break;
              }
              case TokenTypes.IDENTIFIER:
              {
  
                  if ( ( LA(2) == TokenTypes.LEFT_PAREN
                       &&
                         ! isNodeTypeName( LT(1) ) )
                       ||
                      ( LA(2) == TokenTypes.COLON
                        &&
                        LA(4) == TokenTypes.LEFT_PAREN) ) 
                  {
                      filterExpr();
                      
                      if ( LA(1) == TokenTypes.SLASH || LA(1) == TokenTypes.DOUBLE_SLASH)
                      {
                          locationPath( false );
                      }
                  }
                  else
                  {
                      locationPath( false );
                  }
                  break;
              }
              case TokenTypes.DOT:
              case TokenTypes.DOT_DOT:
              case TokenTypes.STAR:
              case TokenTypes.AT:
              {
                  locationPath( false );
                  break;
              }
              case TokenTypes.SLASH:
              case TokenTypes.DOUBLE_SLASH:
              {
                  locationPath( true );
                  break;
              }
              default:
              {
                  XPathSyntaxException ex = createSyntaxException( "Unexpected '" + LT(1).getTokenText() + "'" );
                  throw ex;
              }
          }
  
          getXPathHandler().endPathExpr();
      }
  
      private void literal() throws SAXPathException
      {
          Token token = match( TokenTypes.LITERAL );
  
          getXPathHandler().literal( token.getTokenText() );
      }
  
      private void functionCall() throws SAXPathException
      {
          String prefix       = null;
          String functionName = null;
  
          if ( LA(2) == TokenTypes.COLON )
          {
              prefix = match( TokenTypes.IDENTIFIER ).getTokenText();
              match( TokenTypes.COLON );
          }
          else
          {
              prefix = "";
          }
  
          functionName = match( TokenTypes.IDENTIFIER ).getTokenText();
  
          getXPathHandler().startFunction( prefix,
                                           functionName );
  
          match ( TokenTypes.LEFT_PAREN );
  
          arguments();
  
          match ( TokenTypes.RIGHT_PAREN );
  
          getXPathHandler().endFunction();
      }
  
      private void arguments() throws SAXPathException
      {
          while ( LA(1) != TokenTypes.RIGHT_PAREN )
          {
              expr();
  
              if ( LA(1) == TokenTypes.COMMA )
              {
                  match( TokenTypes.COMMA );
              }
              else
              {
                  break;
              }
          }
      }
  
      private void filterExpr() throws SAXPathException
      {
  
          getXPathHandler().startFilterExpr();
  
          switch ( LA(1) )
          {
              case TokenTypes.DOUBLE:
              {
                  Token token = match( TokenTypes.DOUBLE );
                  
                  getXPathHandler().number( Double.parseDouble( token.getTokenText() ) );
                  break;
              }
              case TokenTypes.LITERAL:
              {
                  literal();
                  break;
              }
              case TokenTypes.LEFT_PAREN:
              {
                  match( TokenTypes.LEFT_PAREN );
                  expr();
                  match( TokenTypes.RIGHT_PAREN );
                  break;
              }
              case TokenTypes.IDENTIFIER:
              {
                  functionCall();
                  break;
              }
              case TokenTypes.DOLLAR:
              {
                  variableReference();
                  break;
              }
          }
  
          predicates();
  
          getXPathHandler().endFilterExpr();
      }
  
      private void variableReference() throws SAXPathException
      {
          match( TokenTypes.DOLLAR );
  
          String prefix       = null;
          String variableName = null;
  
          if ( LA(2) == TokenTypes.COLON )
          {
              prefix = match( TokenTypes.IDENTIFIER ).getTokenText();
              match( TokenTypes.COLON );
          }
          else
          {
              prefix = "";
          }
  
          variableName = match( TokenTypes.IDENTIFIER ).getTokenText();
  
          getXPathHandler().variableReference( prefix,
                                               variableName );
      }
  
      void locationPath(boolean isAbsolute) throws SAXPathException
      {
          switch ( LA(1) )
          {
              case TokenTypes.SLASH:
              case TokenTypes.DOUBLE_SLASH:
              {
                  if ( isAbsolute )
                  {
                      absoluteLocationPath();
                  }
                  else
                  {
                      relativeLocationPath();
                  }
                  break;
              }
              case TokenTypes.AT:
              case TokenTypes.IDENTIFIER:
              case TokenTypes.DOT:
              case TokenTypes.DOT_DOT:
              case TokenTypes.STAR:
              {
                  relativeLocationPath();
                  break;
              }
              default:
              {
                  XPathSyntaxException ex = createSyntaxException( "Unexpected '" + LT(1).getTokenText() + "'" );
                  throw ex;
              }
          }
      }
  
      private void absoluteLocationPath() throws SAXPathException
      {
          getXPathHandler().startAbsoluteLocationPath();
  
          switch ( LA(1) )
          {
              case TokenTypes.SLASH:
              {
                  match( TokenTypes.SLASH );
  
                  switch ( LA(1) )
                  {
  
                      case TokenTypes.DOT:
                      case TokenTypes.DOT_DOT:
                      case TokenTypes.AT:
                      case TokenTypes.IDENTIFIER:
                      case TokenTypes.STAR:
                      {
                          steps();
                          break;
                      }
                  }
                  break;
              }
              case TokenTypes.DOUBLE_SLASH:
              {
                  getXPathHandler().startAllNodeStep( Axis.DESCENDANT_OR_SELF );
                  getXPathHandler().endAllNodeStep();
  
                  match( TokenTypes.DOUBLE_SLASH );
                  switch ( LA(1) )
                  {
                      case TokenTypes.DOT:
                      case TokenTypes.DOT_DOT:
                      case TokenTypes.AT:
                      case TokenTypes.IDENTIFIER:
                      case TokenTypes.STAR:
                      {
                          steps();
                          break;
                      }
                      default:
                          XPathSyntaxException ex = this.createSyntaxException("Location path cannot end with //");
                          throw ex;
                  }
                  break;
              }
          }
          
          getXPathHandler().endAbsoluteLocationPath();
      }
  
      private void relativeLocationPath() throws SAXPathException
      {
          getXPathHandler().startRelativeLocationPath();
  
          switch ( LA(1) )
          {
              case TokenTypes.SLASH:
              {
                  match( TokenTypes.SLASH );
                  break;
              }
              case TokenTypes.DOUBLE_SLASH:
              {
                  getXPathHandler().startAllNodeStep( Axis.DESCENDANT_OR_SELF );
                  getXPathHandler().endAllNodeStep();
  
                  match( TokenTypes.DOUBLE_SLASH );
  
                  break;
              }
          }
  
          steps();
  
          getXPathHandler().endRelativeLocationPath();
      }
  
      private void steps() throws SAXPathException
      {
          switch ( LA(1) )
          {
  
              case TokenTypes.DOT:
              case TokenTypes.DOT_DOT:
              case TokenTypes.AT:
              case TokenTypes.IDENTIFIER:
              case TokenTypes.STAR:
              {
                  step();
                  break;
              }
              case TokenTypes.EOF:
              {
                  return;
              }
              default:
              {
                  XPathSyntaxException ex = createSyntaxException( "Expected one of '.', '..', '@', '*', <QName>" );
                  throw ex;
              }
          }
  
          do
          {
              if ( ( LA(1) == TokenTypes.SLASH)
                   ||
                   ( LA(1) == TokenTypes.DOUBLE_SLASH ) )
              {
                  switch ( LA(1) )
                  {
                      case TokenTypes.SLASH:
                      {
                          match( TokenTypes.SLASH );
                          break;
                      }
                      case TokenTypes.DOUBLE_SLASH:
                      {
                          getXPathHandler().startAllNodeStep( Axis.DESCENDANT_OR_SELF );
                          getXPathHandler().endAllNodeStep();
  
                          match( TokenTypes.DOUBLE_SLASH );
                          break;
                      }
                  }
              }
              else
              {
                  return;
              }
              
              switch ( LA(1) )
              {
                  case TokenTypes.DOT:
                  case TokenTypes.DOT_DOT:
                  case TokenTypes.AT:
                  case TokenTypes.IDENTIFIER:
                  case TokenTypes.STAR:
                  {
                      step();
                      break;
                  }
                  default:
                  {
                      XPathSyntaxException ex = createSyntaxException( "Expected one of '.', '..', '@', '*', <QName>" );
                      throw ex;
                  }
              }
  
          } while ( true );
      }
  
      void step() throws SAXPathException
      {
          int axis = 0;
  
          switch ( LA(1) )
          {
              case TokenTypes.DOT:
              case TokenTypes.DOT_DOT:
              {
                  abbrStep();
                  return;
              }
              case TokenTypes.AT:
              {
                  axis = axisSpecifier();
                  break;
              }
              case TokenTypes.IDENTIFIER:
              {
                  if ( LA(2) == TokenTypes.DOUBLE_COLON )
                  {
                      axis = axisSpecifier();
                  }
                  else
                  {
                      axis = Axis.CHILD;
                  }
                  break;
              }
              case TokenTypes.STAR:
              {
                  axis = Axis.CHILD;
                  break;
              }
          }
  
          nodeTest( axis );
      }
  
      private int axisSpecifier() throws SAXPathException
      {
          int axis = 0;
  
          switch ( LA(1) )
          {
              case TokenTypes.AT:
              {
                  match( TokenTypes.AT );
                  axis = Axis.ATTRIBUTE;
                  break;
              }
              case TokenTypes.IDENTIFIER:
              {
                  Token token = LT( 1 );
  
                  axis = Axis.lookup( token.getTokenText() );
  
                  if ( axis == Axis.INVALID_AXIS )
                  {
                      throwInvalidAxis( token.getTokenText() );
                  }
  
                  match( TokenTypes.IDENTIFIER );
                  match( TokenTypes.DOUBLE_COLON );
  
                  break;
              }
          }
  
          return axis;
      }
  
      private void nodeTest(int axis) throws SAXPathException
      {
          switch ( LA(1) )
          {
              case TokenTypes.IDENTIFIER:
              {
                  switch ( LA(2) )
                  {
                      case TokenTypes.LEFT_PAREN:
                      {
                          nodeTypeTest( axis );
                          break;
                      }
                      default:
                      {
                          nameTest( axis );
                          break;
                      }
                  }
                  break;
              }
              case TokenTypes.STAR:
              {
                  nameTest( axis );
                  break;
              }
              default:
                  XPathSyntaxException ex = createSyntaxException("Expected <QName> or *");
                  throw ex;
          }
      }
  
      private void nodeTypeTest(int axis) throws SAXPathException
      {
          Token  nodeTypeToken = match( TokenTypes.IDENTIFIER );
          String nodeType      = nodeTypeToken.getTokenText();
  
          match( TokenTypes.LEFT_PAREN );
  
          if ( "processing-instruction".equals( nodeType ) )
          {
              String piName = "";
  
              if ( LA(1) == TokenTypes.LITERAL )
              {
                  piName = match( TokenTypes.LITERAL ).getTokenText();
              }
  
              match( TokenTypes.RIGHT_PAREN );
  
              getXPathHandler().startProcessingInstructionNodeStep( axis,
                                                                    piName );
  
              predicates();
  
              getXPathHandler().endProcessingInstructionNodeStep();
          }
          else if ( "node".equals( nodeType ) )
          {
              match( TokenTypes.RIGHT_PAREN );
  
              getXPathHandler().startAllNodeStep( axis );
  
              predicates();
  
              getXPathHandler().endAllNodeStep();
          }
          else if ( "text".equals( nodeType ) )
          {
              match( TokenTypes.RIGHT_PAREN );
  
              getXPathHandler().startTextNodeStep( axis );
  
              predicates();
  
              getXPathHandler().endTextNodeStep();
          }
          else if ( "comment".equals( nodeType ) )
          {
              match( TokenTypes.RIGHT_PAREN );
  
              getXPathHandler().startCommentNodeStep( axis );
  
              predicates();
  
              getXPathHandler().endCommentNodeStep();
          }
          else
          {
              XPathSyntaxException ex = createSyntaxException( "Expected node-type" );
              throw ex;
          }
      }
  
      private void nameTest(int axis) throws SAXPathException
      {
          String prefix    = null;
          String localName = null;
  
          switch ( LA(2) )
          {
              case TokenTypes.COLON:
              {
                  switch ( LA(1) )
                  {
                      case TokenTypes.IDENTIFIER:
                      {
                          prefix = match( TokenTypes.IDENTIFIER ).getTokenText();
                          match( TokenTypes.COLON );
                          break;
                      }
                  }
                  break;
              }
          }
          
          switch ( LA(1) )
          {
              case TokenTypes.IDENTIFIER:
              {
                  localName = match( TokenTypes.IDENTIFIER ).getTokenText();
                  break;
              }
              case TokenTypes.STAR:
              {
                  match( TokenTypes.STAR );
                  localName = "*";
                  break;
              }
          }
  
          if ( prefix == null )
          {
              prefix = "";
          }
          
          getXPathHandler().startNameStep( axis,
                                           prefix,
                                           localName );
  
          predicates();
  
          getXPathHandler().endNameStep();
      }
  
      private void abbrStep() throws SAXPathException
      {
          switch ( LA(1) )
          {
              case TokenTypes.DOT:
              {
                  match( TokenTypes.DOT );
                  getXPathHandler().startAllNodeStep( Axis.SELF );
                  predicates();
                  getXPathHandler().endAllNodeStep();
                  break;
              }
              case TokenTypes.DOT_DOT:
              {
                  match( TokenTypes.DOT_DOT );
                  getXPathHandler().startAllNodeStep( Axis.PARENT );
                  predicates();
                  getXPathHandler().endAllNodeStep();
                  break;
              }
          }
      }
  
      private void predicates() throws SAXPathException
      {
          while (true )
          {
              if ( LA(1) == TokenTypes.LEFT_BRACKET )
              {
                  predicate();
              }
              else
              {
                  break;
              }
          }
      }
      
      void predicate() throws SAXPathException
      {
          getXPathHandler().startPredicate();
          
          match( TokenTypes.LEFT_BRACKET );
          
          predicateExpr();
  
          match( TokenTypes.RIGHT_BRACKET );
  
          getXPathHandler().endPredicate();
      }
  
      private void predicateExpr() throws SAXPathException
      {
          expr();
      }
  
      private void expr() throws SAXPathException
      {
          orExpr();
      }
  
      private void orExpr() throws SAXPathException
      {
          getXPathHandler().startOrExpr();
          
          andExpr();
  
          boolean create = false;
  
          switch ( LA(1) )
          {
              case TokenTypes.OR:
              {
                  create = true;
                  match( TokenTypes.OR );
                  orExpr();
                  break;
              }
          }
  
          getXPathHandler().endOrExpr( create );
      }
  
      private void andExpr() throws SAXPathException
      {
          getXPathHandler().startAndExpr();
  
          equalityExpr();
  
          boolean create = false;
  
          switch ( LA(1) )
          {
              case TokenTypes.AND:
              {
                  create = true;
                  match( TokenTypes.AND );
                  andExpr();
                  break;
              }
          }
  
          getXPathHandler().endAndExpr( create );
      }
  
      private void equalityExpr() throws SAXPathException
      {
          relationalExpr();
  
          int la = LA(1);
          while (la == TokenTypes.EQUALS || la == TokenTypes.NOT_EQUALS)
          {
              switch ( la )
              {
                  case TokenTypes.EQUALS:
                  {
                      match( TokenTypes.EQUALS );
                      getXPathHandler().startEqualityExpr();
                      relationalExpr();
                      getXPathHandler().endEqualityExpr( Operator.EQUALS );
                      break;
                  }
                  case TokenTypes.NOT_EQUALS:
                  {
                      match( TokenTypes.NOT_EQUALS );
                      getXPathHandler().startEqualityExpr();
                      relationalExpr();
                      getXPathHandler().endEqualityExpr( Operator.NOT_EQUALS );
                      break;
                  }
              }
              la = LA(1);
          }
      }
      
      private void relationalExpr() throws SAXPathException
      {
  
          additiveExpr();
  
          int la = LA(1);
          // Very important: TokenTypes.LESS_THAN != Operator.LESS_THAN
          //                 TokenTypes.GREATER_THAN != Operator.GREATER_THAN
          //                 TokenTypes.GREATER_THAN_EQUALS != Operator.GREATER_THAN_EQUALS
          //                 TokenTypes.LESS_THAN_EQUALS != Operator.LESS_THAN_EQUALS
          while (la == TokenTypes.LESS_THAN_SIGN 
              || la == TokenTypes.GREATER_THAN_SIGN 
              || la == TokenTypes.LESS_THAN_OR_EQUALS_SIGN 
              || la == TokenTypes.GREATER_THAN_OR_EQUALS_SIGN ) {
              switch ( la )
              {
                  case TokenTypes.LESS_THAN_SIGN:
                  {
                      match( TokenTypes.LESS_THAN_SIGN );
                      getXPathHandler().startRelationalExpr();
                      additiveExpr();
                      getXPathHandler().endRelationalExpr( Operator.LESS_THAN );
                      break;
                  }
                  case TokenTypes.GREATER_THAN_SIGN:
                  {
                      match( TokenTypes.GREATER_THAN_SIGN );
                      getXPathHandler().startRelationalExpr();
                      additiveExpr();
                      getXPathHandler().endRelationalExpr( Operator.GREATER_THAN );
                      break;
                  }
                  case TokenTypes.GREATER_THAN_OR_EQUALS_SIGN:
                  {
                      match( TokenTypes.GREATER_THAN_OR_EQUALS_SIGN );
                      getXPathHandler().startRelationalExpr();
                      additiveExpr();
                      getXPathHandler().endRelationalExpr( Operator.GREATER_THAN_EQUALS );
                      break;
                  }
                  case TokenTypes.LESS_THAN_OR_EQUALS_SIGN:
                  {
                      match( TokenTypes.LESS_THAN_OR_EQUALS_SIGN );
                      getXPathHandler().startRelationalExpr();
                      additiveExpr();
                      getXPathHandler().endRelationalExpr( Operator.LESS_THAN_EQUALS );
                      break;
                  }
              }
              la = LA(1);
          }
      } 
  
      
      private void additiveExpr() throws SAXPathException
      {
          multiplicativeExpr();
  
          int la = LA(1);
          while (la == TokenTypes.PLUS || la == TokenTypes.MINUS)
          {
              switch ( la )
              {
                  case TokenTypes.PLUS:
                  {
                      match( TokenTypes.PLUS );
                      getXPathHandler().startAdditiveExpr();
                      multiplicativeExpr();
                      getXPathHandler().endAdditiveExpr( Operator.ADD );
                      break;
                  }
                  case TokenTypes.MINUS:
                  {
                      match( TokenTypes.MINUS );
                      getXPathHandler().startAdditiveExpr();
                      multiplicativeExpr();
                      getXPathHandler().endAdditiveExpr( Operator.SUBTRACT );
                      break;
                  }
              }
              la = LA(1);
          }
      }
  
      private void multiplicativeExpr() throws SAXPathException
      {
          unaryExpr();
         
          int la = LA(1);
          while (la == TokenTypes.STAR_OPERATOR || la == TokenTypes.DIV || la == TokenTypes.MOD)
          {
              switch ( la )
              {
                  case TokenTypes.STAR:
                  case TokenTypes.STAR_OPERATOR:
                  {
                      match( TokenTypes.STAR_OPERATOR );
                      getXPathHandler().startMultiplicativeExpr();
                      unaryExpr();
                      getXPathHandler().endMultiplicativeExpr( Operator.MULTIPLY );
                      break;
                  }
                  case TokenTypes.DIV:
                  {
                      match( TokenTypes.DIV );
                      getXPathHandler().startMultiplicativeExpr();
                      unaryExpr();
                      getXPathHandler().endMultiplicativeExpr( Operator.DIV );
                      break;
                  }
                  case TokenTypes.MOD:
                  {
                      match( TokenTypes.MOD );
                      getXPathHandler().startMultiplicativeExpr();
                      unaryExpr();
                      getXPathHandler().endMultiplicativeExpr( Operator.MOD );
                      break;
                  }
              }
              la = LA(1);
          }
  
      }
  
      private void unaryExpr() throws SAXPathException
      {
          switch ( LA(1) )
          {
              case TokenTypes.MINUS:
              {
                  getXPathHandler().startUnaryExpr();
                  match( TokenTypes.MINUS );
                  unaryExpr();
                  getXPathHandler().endUnaryExpr( Operator.NEGATIVE );
                  break;
              }
              default:
              {
                  unionExpr();
                  break;
              }
          }
  
          
      }
  
      private void unionExpr() throws SAXPathException
      {
          getXPathHandler().startUnionExpr();
  
          pathExpr();
  
          boolean create = false;
  
          switch ( LA(1) )
          {
              case TokenTypes.PIPE:
              {
                  match( TokenTypes.PIPE );
                  create = true;
                  expr();
                  break;
              }
          }
  
          getXPathHandler().endUnionExpr( create );
      }
  
      private Token match(int tokenType) throws XPathSyntaxException
      {
          LT(1);
  
          Token token = (Token) tokens.get( 0 );
  
          if ( token.getTokenType() == tokenType )
          {
              tokens.remove(0);
              return token;
          }
  
          
          XPathSyntaxException ex = createSyntaxException( "Expected: " + TokenTypes.getTokenText( tokenType ) );
          throw ex;
      }
  
      private int LA(int position)
      {
          return LT(position).getTokenType();
      }
  
      
      // XXX This method's a HotSpot; could we improve it?
      private Token LT(int position)
      {
          if ( tokens.size() <= ( position - 1 ) )
          {
              for ( int i = 0 ; i < position ; ++i )
              {
                  tokens.add( lexer.nextToken() );
              }
          }
  
          return (Token) tokens.get( position - 1 );
      }
  
      private boolean isNodeTypeName(Token name)
      {
          String text = name.getTokenText();
  
          if ( "node".equals( text )
               ||
               "comment".equals( text )
               ||
               "text".equals( text )
               ||
               "processing-instruction".equals( text ) )
          {
              return true;
          }
  
          return false;
      }
  
      private XPathSyntaxException createSyntaxException(String message)
      {
          String xpath    = this.lexer.getXPath();
          int    position = LT(1).getTokenBegin();
  
          return new XPathSyntaxException( xpath,
                                           position,
                                           message );
      }
  
      private void throwInvalidAxis(String invalidAxis) throws SAXPathException
      {
          String xpath    = this.lexer.getXPath();
          int    position = LT(1).getTokenBegin();
  
          String message  = "Expected valid axis name instead of [" + invalidAxis + "]";
  
          throw new XPathSyntaxException( xpath,
                                          position,
                                          message );
      }
  }
  