1 /*
   2  * reserved comment block
   3  * DO NOT REMOVE OR ALTER!
   4  */
   5 /*
   6  * Copyright 1999-2004 The Apache Software Foundation.
   7  *
   8  * Licensed under the Apache License, Version 2.0 (the "License");
   9  * you may not use this file except in compliance with the License.
  10  * You may obtain a copy of the License at
  11  *
  12  *     http://www.apache.org/licenses/LICENSE-2.0
  13  *
  14  * Unless required by applicable law or agreed to in writing, software
  15  * distributed under the License is distributed on an "AS IS" BASIS,
  16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17  * See the License for the specific language governing permissions and
  18  * limitations under the License.
  19  */
  20 /*
  21  * $Id: XPathParser.java,v 1.2.4.1 2005/09/14 19:46:02 jeffsuttor Exp $
  22  */
  23 package com.sun.org.apache.xpath.internal.compiler;
  24 
  25 import javax.xml.transform.ErrorListener;
  26 import javax.xml.transform.TransformerException;
  27 
  28 import com.sun.org.apache.xalan.internal.res.XSLMessages;
  29 import com.sun.org.apache.xml.internal.utils.PrefixResolver;
  30 import com.sun.org.apache.xpath.internal.XPathProcessorException;
  31 import com.sun.org.apache.xpath.internal.domapi.XPathStylesheetDOM3Exception;
  32 import com.sun.org.apache.xpath.internal.objects.XNumber;
  33 import com.sun.org.apache.xpath.internal.objects.XString;
  34 import com.sun.org.apache.xpath.internal.res.XPATHErrorResources;
  35 
  36 /**
  37  * Tokenizes and parses XPath expressions. This should really be named
  38  * XPathParserImpl, and may be renamed in the future.
  39  * @xsl.usage general
  40  */
  41 public class XPathParser
  42 {
  43         // %REVIEW% Is there a better way of doing this?
  44         // Upside is minimum object churn. Downside is that we don't have a useful
  45         // backtrace in the exception itself -- but we don't expect to need one.
  46         static public final String CONTINUE_AFTER_FATAL_ERROR="CONTINUE_AFTER_FATAL_ERROR";
  47 
  48   /**
  49    * The XPath to be processed.
  50    */
  51   private OpMap m_ops;
  52 
  53   /**
  54    * The next token in the pattern.
  55    */
  56   transient String m_token;
  57 
  58   /**
  59    * The first char in m_token, the theory being that this
  60    * is an optimization because we won't have to do charAt(0) as
  61    * often.
  62    */
  63   transient char m_tokenChar = 0;
  64 
  65   /**
  66    * The position in the token queue is tracked by m_queueMark.
  67    */
  68   int m_queueMark = 0;
  69 
  70   /**
  71    * Results from checking FilterExpr syntax
  72    */
  73   protected final static int FILTER_MATCH_FAILED     = 0;
  74   protected final static int FILTER_MATCH_PRIMARY    = 1;
  75   protected final static int FILTER_MATCH_PREDICATES = 2;
  76 
  77   /**
  78    * The parser constructor.
  79    */
  80   public XPathParser(ErrorListener errorListener, javax.xml.transform.SourceLocator sourceLocator)
  81   {
  82     m_errorListener = errorListener;
  83     m_sourceLocator = sourceLocator;
  84   }
  85 
  86   /**
  87    * The prefix resolver to map prefixes to namespaces in the OpMap.
  88    */
  89   PrefixResolver m_namespaceContext;
  90 
  91   /**
  92    * Given an string, init an XPath object for selections,
  93    * in order that a parse doesn't
  94    * have to be done each time the expression is evaluated.
  95    *
  96    * @param compiler The compiler object.
  97    * @param expression A string conforming to the XPath grammar.
  98    * @param namespaceContext An object that is able to resolve prefixes in
  99    * the XPath to namespaces.
 100    *
 101    * @throws javax.xml.transform.TransformerException
 102    */
 103   public void initXPath(
 104           Compiler compiler, String expression, PrefixResolver namespaceContext)
 105             throws javax.xml.transform.TransformerException
 106   {
 107 
 108     m_ops = compiler;
 109     m_namespaceContext = namespaceContext;
 110     m_functionTable = compiler.getFunctionTable();
 111 
 112     Lexer lexer = new Lexer(compiler, namespaceContext, this);
 113 
 114     lexer.tokenize(expression);
 115 
 116     m_ops.setOp(0,OpCodes.OP_XPATH);
 117     m_ops.setOp(OpMap.MAPINDEX_LENGTH,2);
 118 
 119 
 120         // Patch for Christine's gripe. She wants her errorHandler to return from
 121         // a fatal error and continue trying to parse, rather than throwing an exception.
 122         // Without the patch, that put us into an endless loop.
 123         //
 124         // %REVIEW% Is there a better way of doing this?
 125         // %REVIEW% Are there any other cases which need the safety net?
 126         //      (and if so do we care right now, or should we rewrite the XPath
 127         //      grammar engine and can fix it at that time?)
 128         try {
 129 
 130       nextToken();
 131       Expr();
 132 
 133       if (null != m_token)
 134       {
 135         String extraTokens = "";
 136 
 137         while (null != m_token)
 138         {
 139           extraTokens += "'" + m_token + "'";
 140 
 141           nextToken();
 142 
 143           if (null != m_token)
 144             extraTokens += ", ";
 145         }
 146 
 147         error(XPATHErrorResources.ER_EXTRA_ILLEGAL_TOKENS,
 148               new Object[]{ extraTokens });  //"Extra illegal tokens: "+extraTokens);
 149       }
 150 
 151     }
 152     catch (com.sun.org.apache.xpath.internal.XPathProcessorException e)
 153     {
 154           if(CONTINUE_AFTER_FATAL_ERROR.equals(e.getMessage()))
 155           {
 156                 // What I _want_ to do is null out this XPath.
 157                 // I doubt this has the desired effect, but I'm not sure what else to do.
 158                 // %REVIEW%!!!
 159                 initXPath(compiler, "/..",  namespaceContext);
 160           }
 161           else
 162                 throw e;
 163     }
 164 
 165     compiler.shrink();
 166   }
 167 
 168   /**
 169    * Given an string, init an XPath object for pattern matches,
 170    * in order that a parse doesn't
 171    * have to be done each time the expression is evaluated.
 172    * @param compiler The XPath object to be initialized.
 173    * @param expression A String representing the XPath.
 174    * @param namespaceContext An object that is able to resolve prefixes in
 175    * the XPath to namespaces.
 176    *
 177    * @throws javax.xml.transform.TransformerException
 178    */
 179   public void initMatchPattern(
 180           Compiler compiler, String expression, PrefixResolver namespaceContext)
 181             throws javax.xml.transform.TransformerException
 182   {
 183 
 184     m_ops = compiler;
 185     m_namespaceContext = namespaceContext;
 186     m_functionTable = compiler.getFunctionTable();
 187 
 188     Lexer lexer = new Lexer(compiler, namespaceContext, this);
 189 
 190     lexer.tokenize(expression);
 191 
 192     m_ops.setOp(0, OpCodes.OP_MATCHPATTERN);
 193     m_ops.setOp(OpMap.MAPINDEX_LENGTH, 2);
 194 
 195     nextToken();
 196     Pattern();
 197 
 198     if (null != m_token)
 199     {
 200       String extraTokens = "";
 201 
 202       while (null != m_token)
 203       {
 204         extraTokens += "'" + m_token + "'";
 205 
 206         nextToken();
 207 
 208         if (null != m_token)
 209           extraTokens += ", ";
 210       }
 211 
 212       error(XPATHErrorResources.ER_EXTRA_ILLEGAL_TOKENS,
 213             new Object[]{ extraTokens });  //"Extra illegal tokens: "+extraTokens);
 214     }
 215 
 216     // Terminate for safety.
 217     m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
 218     m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH)+1);
 219 
 220     m_ops.shrink();
 221   }
 222 
 223   /** The error listener where syntax errors are to be sent.
 224    */
 225   private ErrorListener m_errorListener;
 226 
 227   /** The source location of the XPath. */
 228   javax.xml.transform.SourceLocator m_sourceLocator;
 229 
 230   /** The table contains build-in functions and customized functions */
 231   private FunctionTable m_functionTable;
 232 
 233   /**
 234    * Allow an application to register an error event handler, where syntax
 235    * errors will be sent.  If the error listener is not set, syntax errors
 236    * will be sent to System.err.
 237    *
 238    * @param handler Reference to error listener where syntax errors will be
 239    *                sent.
 240    */
 241   public void setErrorHandler(ErrorListener handler)
 242   {
 243     m_errorListener = handler;
 244   }
 245 
 246   /**
 247    * Return the current error listener.
 248    *
 249    * @return The error listener, which should not normally be null, but may be.
 250    */
 251   public ErrorListener getErrorListener()
 252   {
 253     return m_errorListener;
 254   }
 255 
 256   /**
 257    * Check whether m_token matches the target string.
 258    *
 259    * @param s A string reference or null.
 260    *
 261    * @return If m_token is null, returns false (or true if s is also null), or
 262    * return true if the current token matches the string, else false.
 263    */
 264   final boolean tokenIs(String s)
 265   {
 266     return (m_token != null) ? (m_token.equals(s)) : (s == null);
 267   }
 268 
 269   /**
 270    * Check whether m_tokenChar==c.
 271    *
 272    * @param c A character to be tested.
 273    *
 274    * @return If m_token is null, returns false, or return true if c matches
 275    *         the current token.
 276    */
 277   final boolean tokenIs(char c)
 278   {
 279     return (m_token != null) ? (m_tokenChar == c) : false;
 280   }
 281 
 282   /**
 283    * Look ahead of the current token in order to
 284    * make a branching decision.
 285    *
 286    * @param c the character to be tested for.
 287    * @param n number of tokens to look ahead.  Must be
 288    * greater than 1.
 289    *
 290    * @return true if the next token matches the character argument.
 291    */
 292   final boolean lookahead(char c, int n)
 293   {
 294 
 295     int pos = (m_queueMark + n);
 296     boolean b;
 297 
 298     if ((pos <= m_ops.getTokenQueueSize()) && (pos > 0)
 299             && (m_ops.getTokenQueueSize() != 0))
 300     {
 301       String tok = ((String) m_ops.m_tokenQueue.elementAt(pos - 1));
 302 
 303       b = (tok.length() == 1) ? (tok.charAt(0) == c) : false;
 304     }
 305     else
 306     {
 307       b = false;
 308     }
 309 
 310     return b;
 311   }
 312 
 313   /**
 314    * Look behind the first character of the current token in order to
 315    * make a branching decision.
 316    *
 317    * @param c the character to compare it to.
 318    * @param n number of tokens to look behind.  Must be
 319    * greater than 1.  Note that the look behind terminates
 320    * at either the beginning of the string or on a '|'
 321    * character.  Because of this, this method should only
 322    * be used for pattern matching.
 323    *
 324    * @return true if the token behind the current token matches the character
 325    *         argument.
 326    */
 327   private final boolean lookbehind(char c, int n)
 328   {
 329 
 330     boolean isToken;
 331     int lookBehindPos = m_queueMark - (n + 1);
 332 
 333     if (lookBehindPos >= 0)
 334     {
 335       String lookbehind = (String) m_ops.m_tokenQueue.elementAt(lookBehindPos);
 336 
 337       if (lookbehind.length() == 1)
 338       {
 339         char c0 = (lookbehind == null) ? '|' : lookbehind.charAt(0);
 340 
 341         isToken = (c0 == '|') ? false : (c0 == c);
 342       }
 343       else
 344       {
 345         isToken = false;
 346       }
 347     }
 348     else
 349     {
 350       isToken = false;
 351     }
 352 
 353     return isToken;
 354   }
 355 
 356   /**
 357    * look behind the current token in order to
 358    * see if there is a useable token.
 359    *
 360    * @param n number of tokens to look behind.  Must be
 361    * greater than 1.  Note that the look behind terminates
 362    * at either the beginning of the string or on a '|'
 363    * character.  Because of this, this method should only
 364    * be used for pattern matching.
 365    *
 366    * @return true if look behind has a token, false otherwise.
 367    */
 368   private final boolean lookbehindHasToken(int n)
 369   {
 370 
 371     boolean hasToken;
 372 
 373     if ((m_queueMark - n) > 0)
 374     {
 375       String lookbehind = (String) m_ops.m_tokenQueue.elementAt(m_queueMark - (n - 1));
 376       char c0 = (lookbehind == null) ? '|' : lookbehind.charAt(0);
 377 
 378       hasToken = (c0 == '|') ? false : true;
 379     }
 380     else
 381     {
 382       hasToken = false;
 383     }
 384 
 385     return hasToken;
 386   }
 387 
 388   /**
 389    * Look ahead of the current token in order to
 390    * make a branching decision.
 391    *
 392    * @param s the string to compare it to.
 393    * @param n number of tokens to lookahead.  Must be
 394    * greater than 1.
 395    *
 396    * @return true if the token behind the current token matches the string
 397    *         argument.
 398    */
 399   private final boolean lookahead(String s, int n)
 400   {
 401 
 402     boolean isToken;
 403 
 404     if ((m_queueMark + n) <= m_ops.getTokenQueueSize())
 405     {
 406       String lookahead = (String) m_ops.m_tokenQueue.elementAt(m_queueMark + (n - 1));
 407 
 408       isToken = (lookahead != null) ? lookahead.equals(s) : (s == null);
 409     }
 410     else
 411     {
 412       isToken = (null == s);
 413     }
 414 
 415     return isToken;
 416   }
 417 
 418   /**
 419    * Retrieve the next token from the command and
 420    * store it in m_token string.
 421    */
 422   private final void nextToken()
 423   {
 424 
 425     if (m_queueMark < m_ops.getTokenQueueSize())
 426     {
 427       m_token = (String) m_ops.m_tokenQueue.elementAt(m_queueMark++);
 428       m_tokenChar = m_token.charAt(0);
 429     }
 430     else
 431     {
 432       m_token = null;
 433       m_tokenChar = 0;
 434     }
 435   }
 436 
 437   /**
 438    * Retrieve a token relative to the current token.
 439    *
 440    * @param i Position relative to current token.
 441    *
 442    * @return The string at the given index, or null if the index is out
 443    *         of range.
 444    */
 445   private final String getTokenRelative(int i)
 446   {
 447 
 448     String tok;
 449     int relative = m_queueMark + i;
 450 
 451     if ((relative > 0) && (relative < m_ops.getTokenQueueSize()))
 452     {
 453       tok = (String) m_ops.m_tokenQueue.elementAt(relative);
 454     }
 455     else
 456     {
 457       tok = null;
 458     }
 459 
 460     return tok;
 461   }
 462 
 463   /**
 464    * Retrieve the previous token from the command and
 465    * store it in m_token string.
 466    */
 467   private final void prevToken()
 468   {
 469 
 470     if (m_queueMark > 0)
 471     {
 472       m_queueMark--;
 473 
 474       m_token = (String) m_ops.m_tokenQueue.elementAt(m_queueMark);
 475       m_tokenChar = m_token.charAt(0);
 476     }
 477     else
 478     {
 479       m_token = null;
 480       m_tokenChar = 0;
 481     }
 482   }
 483 
 484   /**
 485    * Consume an expected token, throwing an exception if it
 486    * isn't there.
 487    *
 488    * @param expected The string to be expected.
 489    *
 490    * @throws javax.xml.transform.TransformerException
 491    */
 492   private final void consumeExpected(String expected)
 493           throws javax.xml.transform.TransformerException
 494   {
 495 
 496     if (tokenIs(expected))
 497     {
 498       nextToken();
 499     }
 500     else
 501     {
 502       error(XPATHErrorResources.ER_EXPECTED_BUT_FOUND, new Object[]{ expected,
 503                                                                      m_token });  //"Expected "+expected+", but found: "+m_token);
 504 
 505           // Patch for Christina's gripe. She wants her errorHandler to return from
 506           // this error and continue trying to parse, rather than throwing an exception.
 507           // Without the patch, that put us into an endless loop.
 508                 throw new XPathProcessorException(CONTINUE_AFTER_FATAL_ERROR);
 509         }
 510   }
 511 
 512   /**
 513    * Consume an expected token, throwing an exception if it
 514    * isn't there.
 515    *
 516    * @param expected the character to be expected.
 517    *
 518    * @throws javax.xml.transform.TransformerException
 519    */
 520   private final void consumeExpected(char expected)
 521           throws javax.xml.transform.TransformerException
 522   {
 523 
 524     if (tokenIs(expected))
 525     {
 526       nextToken();
 527     }
 528     else
 529     {
 530       error(XPATHErrorResources.ER_EXPECTED_BUT_FOUND,
 531             new Object[]{ String.valueOf(expected),
 532                           m_token });  //"Expected "+expected+", but found: "+m_token);
 533 
 534           // Patch for Christina's gripe. She wants her errorHandler to return from
 535           // this error and continue trying to parse, rather than throwing an exception.
 536           // Without the patch, that put us into an endless loop.
 537                 throw new XPathProcessorException(CONTINUE_AFTER_FATAL_ERROR);
 538     }
 539   }
 540 
 541   /**
 542    * Warn the user of a problem.
 543    *
 544    * @param msg An error msgkey that corresponds to one of the constants found
 545    *            in {@link com.sun.org.apache.xpath.internal.res.XPATHErrorResources}, which is
 546    *            a key for a format string.
 547    * @param args An array of arguments represented in the format string, which
 548    *             may be null.
 549    *
 550    * @throws TransformerException if the current ErrorListoner determines to
 551    *                              throw an exception.
 552    */
 553   void warn(String msg, Object[] args) throws TransformerException
 554   {
 555 
 556     String fmsg = XSLMessages.createXPATHWarning(msg, args);
 557     ErrorListener ehandler = this.getErrorListener();
 558 
 559     if (null != ehandler)
 560     {
 561       // TO DO: Need to get stylesheet Locator from here.
 562       ehandler.warning(new TransformerException(fmsg, m_sourceLocator));
 563     }
 564     else
 565     {
 566       // Should never happen.
 567       System.err.println(fmsg);
 568     }
 569   }
 570 
 571   /**
 572    * Notify the user of an assertion error, and probably throw an
 573    * exception.
 574    *
 575    * @param b  If false, a runtime exception will be thrown.
 576    * @param msg The assertion message, which should be informative.
 577    *
 578    * @throws RuntimeException if the b argument is false.
 579    */
 580   private void assertion(boolean b, String msg)
 581   {
 582 
 583     if (!b)
 584     {
 585       String fMsg = XSLMessages.createXPATHMessage(
 586         XPATHErrorResources.ER_INCORRECT_PROGRAMMER_ASSERTION,
 587         new Object[]{ msg });
 588 
 589       throw new RuntimeException(fMsg);
 590     }
 591   }
 592 
 593   /**
 594    * Notify the user of an error, and probably throw an
 595    * exception.
 596    *
 597    * @param msg An error msgkey that corresponds to one of the constants found
 598    *            in {@link com.sun.org.apache.xpath.internal.res.XPATHErrorResources}, which is
 599    *            a key for a format string.
 600    * @param args An array of arguments represented in the format string, which
 601    *             may be null.
 602    *
 603    * @throws TransformerException if the current ErrorListoner determines to
 604    *                              throw an exception.
 605    */
 606   void error(String msg, Object[] args) throws TransformerException
 607   {
 608 
 609     String fmsg = XSLMessages.createXPATHMessage(msg, args);
 610     ErrorListener ehandler = this.getErrorListener();
 611 
 612     TransformerException te = new TransformerException(fmsg, m_sourceLocator);
 613     if (null != ehandler)
 614     {
 615       // TO DO: Need to get stylesheet Locator from here.
 616       ehandler.fatalError(te);
 617     }
 618     else
 619     {
 620       // System.err.println(fmsg);
 621       throw te;
 622     }
 623   }
 624 
 625   /**
 626    * This method is added to support DOM 3 XPath API.
 627    * <p>
 628    * This method is exactly like error(String, Object[]); except that
 629    * the underlying TransformerException is
 630    * XpathStylesheetDOM3Exception (which extends TransformerException).
 631    * <p>
 632    * So older XPath code in Xalan is not affected by this. To older XPath code
 633    * the behavior of whether error() or errorForDOM3() is called because it is
 634    * always catching TransformerException objects and is oblivious to
 635    * the new subclass of XPathStylesheetDOM3Exception. Older XPath code
 636    * runs as before.
 637    * <p>
 638    * However, newer DOM3 XPath code upon catching a TransformerException can
 639    * can check if the exception is an instance of XPathStylesheetDOM3Exception
 640    * and take appropriate action.
 641    *
 642    * @param msg An error msgkey that corresponds to one of the constants found
 643    *            in {@link com.sun.org.apache.xpath.internal.res.XPATHErrorResources}, which is
 644    *            a key for a format string.
 645    * @param args An array of arguments represented in the format string, which
 646    *             may be null.
 647    *
 648    * @throws TransformerException if the current ErrorListoner determines to
 649    *                              throw an exception.
 650    */
 651   void errorForDOM3(String msg, Object[] args) throws TransformerException
 652   {
 653 
 654         String fmsg = XSLMessages.createXPATHMessage(msg, args);
 655         ErrorListener ehandler = this.getErrorListener();
 656 
 657         TransformerException te = new XPathStylesheetDOM3Exception(fmsg, m_sourceLocator);
 658         if (null != ehandler)
 659         {
 660           // TO DO: Need to get stylesheet Locator from here.
 661           ehandler.fatalError(te);
 662         }
 663         else
 664         {
 665           // System.err.println(fmsg);
 666           throw te;
 667         }
 668   }
 669   /**
 670    * Dump the remaining token queue.
 671    * Thanks to Craig for this.
 672    *
 673    * @return A dump of the remaining token queue, which may be appended to
 674    *         an error message.
 675    */
 676   protected String dumpRemainingTokenQueue()
 677   {
 678 
 679     int q = m_queueMark;
 680     String returnMsg;
 681 
 682     if (q < m_ops.getTokenQueueSize())
 683     {
 684       String msg = "\n Remaining tokens: (";
 685 
 686       while (q < m_ops.getTokenQueueSize())
 687       {
 688         String t = (String) m_ops.m_tokenQueue.elementAt(q++);
 689 
 690         msg += (" '" + t + "'");
 691       }
 692 
 693       returnMsg = msg + ")";
 694     }
 695     else
 696     {
 697       returnMsg = "";
 698     }
 699 
 700     return returnMsg;
 701   }
 702 
 703   /**
 704    * Given a string, return the corresponding function token.
 705    *
 706    * @param key A local name of a function.
 707    *
 708    * @return   The function ID, which may correspond to one of the FUNC_XXX
 709    *    values found in {@link com.sun.org.apache.xpath.internal.compiler.FunctionTable}, but may
 710    *    be a value installed by an external module.
 711    */
 712   final int getFunctionToken(String key)
 713   {
 714 
 715     int tok;
 716 
 717     Object id;
 718 
 719     try
 720     {
 721       // These are nodetests, xpathparser treats them as functions when parsing
 722       // a FilterExpr.
 723       id = Keywords.lookupNodeTest(key);
 724       if (null == id) id = m_functionTable.getFunctionID(key);
 725       tok = ((Integer) id).intValue();
 726     }
 727     catch (NullPointerException npe)
 728     {
 729       tok = -1;
 730     }
 731     catch (ClassCastException cce)
 732     {
 733       tok = -1;
 734     }
 735 
 736     return tok;
 737   }
 738 
 739   /**
 740    * Insert room for operation.  This will NOT set
 741    * the length value of the operation, but will update
 742    * the length value for the total expression.
 743    *
 744    * @param pos The position where the op is to be inserted.
 745    * @param length The length of the operation space in the op map.
 746    * @param op The op code to the inserted.
 747    */
 748   void insertOp(int pos, int length, int op)
 749   {
 750 
 751     int totalLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
 752 
 753     for (int i = totalLen - 1; i >= pos; i--)
 754     {
 755       m_ops.setOp(i + length, m_ops.getOp(i));
 756     }
 757 
 758     m_ops.setOp(pos,op);
 759     m_ops.setOp(OpMap.MAPINDEX_LENGTH,totalLen + length);
 760   }
 761 
 762   /**
 763    * Insert room for operation.  This WILL set
 764    * the length value of the operation, and will update
 765    * the length value for the total expression.
 766    *
 767    * @param length The length of the operation.
 768    * @param op The op code to the inserted.
 769    */
 770   void appendOp(int length, int op)
 771   {
 772 
 773     int totalLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
 774 
 775     m_ops.setOp(totalLen, op);
 776     m_ops.setOp(totalLen + OpMap.MAPINDEX_LENGTH, length);
 777     m_ops.setOp(OpMap.MAPINDEX_LENGTH, totalLen + length);
 778   }
 779 
 780   // ============= EXPRESSIONS FUNCTIONS =================
 781 
 782   /**
 783    *
 784    *
 785    * Expr  ::=  OrExpr
 786    *
 787    *
 788    * @throws javax.xml.transform.TransformerException
 789    */
 790   protected void Expr() throws javax.xml.transform.TransformerException
 791   {
 792     OrExpr();
 793   }
 794 
 795   /**
 796    *
 797    *
 798    * OrExpr  ::=  AndExpr
 799    * | OrExpr 'or' AndExpr
 800    *
 801    *
 802    * @throws javax.xml.transform.TransformerException
 803    */
 804   protected void OrExpr() throws javax.xml.transform.TransformerException
 805   {
 806 
 807     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
 808 
 809     AndExpr();
 810 
 811     if ((null != m_token) && tokenIs("or"))
 812     {
 813       nextToken();
 814       insertOp(opPos, 2, OpCodes.OP_OR);
 815       OrExpr();
 816 
 817       m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
 818         m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
 819     }
 820   }
 821 
 822   /**
 823    *
 824    *
 825    * AndExpr  ::=  EqualityExpr
 826    * | AndExpr 'and' EqualityExpr
 827    *
 828    *
 829    * @throws javax.xml.transform.TransformerException
 830    */
 831   protected void AndExpr() throws javax.xml.transform.TransformerException
 832   {
 833 
 834     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
 835 
 836     EqualityExpr(-1);
 837 
 838     if ((null != m_token) && tokenIs("and"))
 839     {
 840       nextToken();
 841       insertOp(opPos, 2, OpCodes.OP_AND);
 842       AndExpr();
 843 
 844       m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
 845         m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
 846     }
 847   }
 848 
 849   /**
 850    *
 851    * @returns an Object which is either a String, a Number, a Boolean, or a vector
 852    * of nodes.
 853    *
 854    * EqualityExpr  ::=  RelationalExpr
 855    * | EqualityExpr '=' RelationalExpr
 856    *
 857    *
 858    * @param addPos Position where expression is to be added, or -1 for append.
 859    *
 860    * @return the position at the end of the equality expression.
 861    *
 862    * @throws javax.xml.transform.TransformerException
 863    */
 864   protected int EqualityExpr(int addPos) throws javax.xml.transform.TransformerException
 865   {
 866 
 867     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
 868 
 869     if (-1 == addPos)
 870       addPos = opPos;
 871 
 872     RelationalExpr(-1);
 873 
 874     if (null != m_token)
 875     {
 876       if (tokenIs('!') && lookahead('=', 1))
 877       {
 878         nextToken();
 879         nextToken();
 880         insertOp(addPos, 2, OpCodes.OP_NOTEQUALS);
 881 
 882         int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
 883 
 884         addPos = EqualityExpr(addPos);
 885         m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
 886           m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
 887         addPos += 2;
 888       }
 889       else if (tokenIs('='))
 890       {
 891         nextToken();
 892         insertOp(addPos, 2, OpCodes.OP_EQUALS);
 893 
 894         int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
 895 
 896         addPos = EqualityExpr(addPos);
 897         m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
 898           m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
 899         addPos += 2;
 900       }
 901     }
 902 
 903     return addPos;
 904   }
 905 
 906   /**
 907    * .
 908    * @returns an Object which is either a String, a Number, a Boolean, or a vector
 909    * of nodes.
 910    *
 911    * RelationalExpr  ::=  AdditiveExpr
 912    * | RelationalExpr '<' AdditiveExpr
 913    * | RelationalExpr '>' AdditiveExpr
 914    * | RelationalExpr '<=' AdditiveExpr
 915    * | RelationalExpr '>=' AdditiveExpr
 916    *
 917    *
 918    * @param addPos Position where expression is to be added, or -1 for append.
 919    *
 920    * @return the position at the end of the relational expression.
 921    *
 922    * @throws javax.xml.transform.TransformerException
 923    */
 924   protected int RelationalExpr(int addPos) throws javax.xml.transform.TransformerException
 925   {
 926 
 927     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
 928 
 929     if (-1 == addPos)
 930       addPos = opPos;
 931 
 932     AdditiveExpr(-1);
 933 
 934     if (null != m_token)
 935     {
 936       if (tokenIs('<'))
 937       {
 938         nextToken();
 939 
 940         if (tokenIs('='))
 941         {
 942           nextToken();
 943           insertOp(addPos, 2, OpCodes.OP_LTE);
 944         }
 945         else
 946         {
 947           insertOp(addPos, 2, OpCodes.OP_LT);
 948         }
 949 
 950         int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
 951 
 952         addPos = RelationalExpr(addPos);
 953         m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
 954           m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
 955         addPos += 2;
 956       }
 957       else if (tokenIs('>'))
 958       {
 959         nextToken();
 960 
 961         if (tokenIs('='))
 962         {
 963           nextToken();
 964           insertOp(addPos, 2, OpCodes.OP_GTE);
 965         }
 966         else
 967         {
 968           insertOp(addPos, 2, OpCodes.OP_GT);
 969         }
 970 
 971         int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
 972 
 973         addPos = RelationalExpr(addPos);
 974         m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
 975           m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
 976         addPos += 2;
 977       }
 978     }
 979 
 980     return addPos;
 981   }
 982 
 983   /**
 984    * This has to handle construction of the operations so that they are evaluated
 985    * in pre-fix order.  So, for 9+7-6, instead of |+|9|-|7|6|, this needs to be
 986    * evaluated as |-|+|9|7|6|.
 987    *
 988    * AdditiveExpr  ::=  MultiplicativeExpr
 989    * | AdditiveExpr '+' MultiplicativeExpr
 990    * | AdditiveExpr '-' MultiplicativeExpr
 991    *
 992    *
 993    * @param addPos Position where expression is to be added, or -1 for append.
 994    *
 995    * @return the position at the end of the equality expression.
 996    *
 997    * @throws javax.xml.transform.TransformerException
 998    */
 999   protected int AdditiveExpr(int addPos) throws javax.xml.transform.TransformerException
1000   {
1001 
1002     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1003 
1004     if (-1 == addPos)
1005       addPos = opPos;
1006 
1007     MultiplicativeExpr(-1);
1008 
1009     if (null != m_token)
1010     {
1011       if (tokenIs('+'))
1012       {
1013         nextToken();
1014         insertOp(addPos, 2, OpCodes.OP_PLUS);
1015 
1016         int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1017 
1018         addPos = AdditiveExpr(addPos);
1019         m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1020           m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1021         addPos += 2;
1022       }
1023       else if (tokenIs('-'))
1024       {
1025         nextToken();
1026         insertOp(addPos, 2, OpCodes.OP_MINUS);
1027 
1028         int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1029 
1030         addPos = AdditiveExpr(addPos);
1031         m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1032           m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1033         addPos += 2;
1034       }
1035     }
1036 
1037     return addPos;
1038   }
1039 
1040   /**
1041    * This has to handle construction of the operations so that they are evaluated
1042    * in pre-fix order.  So, for 9+7-6, instead of |+|9|-|7|6|, this needs to be
1043    * evaluated as |-|+|9|7|6|.
1044    *
1045    * MultiplicativeExpr  ::=  UnaryExpr
1046    * | MultiplicativeExpr MultiplyOperator UnaryExpr
1047    * | MultiplicativeExpr 'div' UnaryExpr
1048    * | MultiplicativeExpr 'mod' UnaryExpr
1049    * | MultiplicativeExpr 'quo' UnaryExpr
1050    *
1051    * @param addPos Position where expression is to be added, or -1 for append.
1052    *
1053    * @return the position at the end of the equality expression.
1054    *
1055    * @throws javax.xml.transform.TransformerException
1056    */
1057   protected int MultiplicativeExpr(int addPos) throws javax.xml.transform.TransformerException
1058   {
1059 
1060     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1061 
1062     if (-1 == addPos)
1063       addPos = opPos;
1064 
1065     UnaryExpr();
1066 
1067     if (null != m_token)
1068     {
1069       if (tokenIs('*'))
1070       {
1071         nextToken();
1072         insertOp(addPos, 2, OpCodes.OP_MULT);
1073 
1074         int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1075 
1076         addPos = MultiplicativeExpr(addPos);
1077         m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1078           m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1079         addPos += 2;
1080       }
1081       else if (tokenIs("div"))
1082       {
1083         nextToken();
1084         insertOp(addPos, 2, OpCodes.OP_DIV);
1085 
1086         int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1087 
1088         addPos = MultiplicativeExpr(addPos);
1089         m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1090           m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1091         addPos += 2;
1092       }
1093       else if (tokenIs("mod"))
1094       {
1095         nextToken();
1096         insertOp(addPos, 2, OpCodes.OP_MOD);
1097 
1098         int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1099 
1100         addPos = MultiplicativeExpr(addPos);
1101         m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1102           m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1103         addPos += 2;
1104       }
1105       else if (tokenIs("quo"))
1106       {
1107         nextToken();
1108         insertOp(addPos, 2, OpCodes.OP_QUO);
1109 
1110         int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1111 
1112         addPos = MultiplicativeExpr(addPos);
1113         m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1114           m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1115         addPos += 2;
1116       }
1117     }
1118 
1119     return addPos;
1120   }
1121 
1122   /**
1123    *
1124    * UnaryExpr  ::=  UnionExpr
1125    * | '-' UnaryExpr
1126    *
1127    *
1128    * @throws javax.xml.transform.TransformerException
1129    */
1130   protected void UnaryExpr() throws javax.xml.transform.TransformerException
1131   {
1132 
1133     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1134     boolean isNeg = false;
1135 
1136     if (m_tokenChar == '-')
1137     {
1138       nextToken();
1139       appendOp(2, OpCodes.OP_NEG);
1140 
1141       isNeg = true;
1142     }
1143 
1144     UnionExpr();
1145 
1146     if (isNeg)
1147       m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1148         m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1149   }
1150 
1151   /**
1152    *
1153    * StringExpr  ::=  Expr
1154    *
1155    *
1156    * @throws javax.xml.transform.TransformerException
1157    */
1158   protected void StringExpr() throws javax.xml.transform.TransformerException
1159   {
1160 
1161     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1162 
1163     appendOp(2, OpCodes.OP_STRING);
1164     Expr();
1165 
1166     m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1167       m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1168   }
1169 
1170   /**
1171    *
1172    *
1173    * StringExpr  ::=  Expr
1174    *
1175    *
1176    * @throws javax.xml.transform.TransformerException
1177    */
1178   protected void BooleanExpr() throws javax.xml.transform.TransformerException
1179   {
1180 
1181     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1182 
1183     appendOp(2, OpCodes.OP_BOOL);
1184     Expr();
1185 
1186     int opLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos;
1187 
1188     if (opLen == 2)
1189     {
1190       error(XPATHErrorResources.ER_BOOLEAN_ARG_NO_LONGER_OPTIONAL, null);  //"boolean(...) argument is no longer optional with 19990709 XPath draft.");
1191     }
1192 
1193     m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, opLen);
1194   }
1195 
1196   /**
1197    *
1198    *
1199    * NumberExpr  ::=  Expr
1200    *
1201    *
1202    * @throws javax.xml.transform.TransformerException
1203    */
1204   protected void NumberExpr() throws javax.xml.transform.TransformerException
1205   {
1206 
1207     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1208 
1209     appendOp(2, OpCodes.OP_NUMBER);
1210     Expr();
1211 
1212     m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1213       m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1214   }
1215 
1216   /**
1217    * The context of the right hand side expressions is the context of the
1218    * left hand side expression. The results of the right hand side expressions
1219    * are node sets. The result of the left hand side UnionExpr is the union
1220    * of the results of the right hand side expressions.
1221    *
1222    *
1223    * UnionExpr    ::=    PathExpr
1224    * | UnionExpr '|' PathExpr
1225    *
1226    *
1227    * @throws javax.xml.transform.TransformerException
1228    */
1229   protected void UnionExpr() throws javax.xml.transform.TransformerException
1230   {
1231 
1232     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1233     boolean continueOrLoop = true;
1234     boolean foundUnion = false;
1235 
1236     do
1237     {
1238       PathExpr();
1239 
1240       if (tokenIs('|'))
1241       {
1242         if (false == foundUnion)
1243         {
1244           foundUnion = true;
1245 
1246           insertOp(opPos, 2, OpCodes.OP_UNION);
1247         }
1248 
1249         nextToken();
1250       }
1251       else
1252       {
1253         break;
1254       }
1255 
1256       // this.m_testForDocOrder = true;
1257     }
1258     while (continueOrLoop);
1259 
1260     m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1261           m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1262   }
1263 
1264   /**
1265    * PathExpr  ::=  LocationPath
1266    * | FilterExpr
1267    * | FilterExpr '/' RelativeLocationPath
1268    * | FilterExpr '//' RelativeLocationPath
1269    *
1270    * @throws XSLProcessorException thrown if the active ProblemListener and XPathContext decide
1271    * the error condition is severe enough to halt processing.
1272    *
1273    * @throws javax.xml.transform.TransformerException
1274    */
1275   protected void PathExpr() throws javax.xml.transform.TransformerException
1276   {
1277 
1278     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1279 
1280     int filterExprMatch = FilterExpr();
1281 
1282     if (filterExprMatch != FILTER_MATCH_FAILED)
1283     {
1284       // If FilterExpr had Predicates, a OP_LOCATIONPATH opcode would already
1285       // have been inserted.
1286       boolean locationPathStarted = (filterExprMatch==FILTER_MATCH_PREDICATES);
1287 
1288       if (tokenIs('/'))
1289       {
1290         nextToken();
1291 
1292         if (!locationPathStarted)
1293         {
1294           // int locationPathOpPos = opPos;
1295           insertOp(opPos, 2, OpCodes.OP_LOCATIONPATH);
1296 
1297           locationPathStarted = true;
1298         }
1299 
1300         if (!RelativeLocationPath())
1301         {
1302           // "Relative location path expected following '/' or '//'"
1303           error(XPATHErrorResources.ER_EXPECTED_REL_LOC_PATH, null);
1304         }
1305 
1306       }
1307 
1308       // Terminate for safety.
1309       if (locationPathStarted)
1310       {
1311         m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
1312         m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1313         m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1314           m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1315       }
1316     }
1317     else
1318     {
1319       LocationPath();
1320     }
1321   }
1322 
1323   /**
1324    *
1325    *
1326    * FilterExpr  ::=  PrimaryExpr
1327    * | FilterExpr Predicate
1328    *
1329    * @throws XSLProcessorException thrown if the active ProblemListener and XPathContext decide
1330    * the error condition is severe enough to halt processing.
1331    *
1332    * @return  FILTER_MATCH_PREDICATES, if this method successfully matched a
1333    *          FilterExpr with one or more Predicates;
1334    *          FILTER_MATCH_PRIMARY, if this method successfully matched a
1335    *          FilterExpr that was just a PrimaryExpr; or
1336    *          FILTER_MATCH_FAILED, if this method did not match a FilterExpr
1337    *
1338    * @throws javax.xml.transform.TransformerException
1339    */
1340   protected int FilterExpr() throws javax.xml.transform.TransformerException
1341   {
1342 
1343     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1344 
1345     int filterMatch;
1346 
1347     if (PrimaryExpr())
1348     {
1349       if (tokenIs('['))
1350       {
1351 
1352         // int locationPathOpPos = opPos;
1353         insertOp(opPos, 2, OpCodes.OP_LOCATIONPATH);
1354 
1355         while (tokenIs('['))
1356         {
1357           Predicate();
1358         }
1359 
1360         filterMatch = FILTER_MATCH_PREDICATES;
1361       }
1362       else
1363       {
1364         filterMatch = FILTER_MATCH_PRIMARY;
1365       }
1366     }
1367     else
1368     {
1369       filterMatch = FILTER_MATCH_FAILED;
1370     }
1371 
1372     return filterMatch;
1373 
1374     /*
1375      * if(tokenIs('['))
1376      * {
1377      *   Predicate();
1378      *   m_ops.m_opMap[opPos + OpMap.MAPINDEX_LENGTH] = m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] - opPos;
1379      * }
1380      */
1381   }
1382 
1383   /**
1384    *
1385    * PrimaryExpr  ::=  VariableReference
1386    * | '(' Expr ')'
1387    * | Literal
1388    * | Number
1389    * | FunctionCall
1390    *
1391    * @return true if this method successfully matched a PrimaryExpr
1392    *
1393    * @throws javax.xml.transform.TransformerException
1394    *
1395    */
1396   protected boolean PrimaryExpr() throws javax.xml.transform.TransformerException
1397   {
1398 
1399     boolean matchFound;
1400     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1401 
1402     if ((m_tokenChar == '\'') || (m_tokenChar == '"'))
1403     {
1404       appendOp(2, OpCodes.OP_LITERAL);
1405       Literal();
1406 
1407       m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1408         m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1409 
1410       matchFound = true;
1411     }
1412     else if (m_tokenChar == '$')
1413     {
1414       nextToken();  // consume '$'
1415       appendOp(2, OpCodes.OP_VARIABLE);
1416       QName();
1417 
1418       m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1419         m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1420 
1421       matchFound = true;
1422     }
1423     else if (m_tokenChar == '(')
1424     {
1425       nextToken();
1426       appendOp(2, OpCodes.OP_GROUP);
1427       Expr();
1428       consumeExpected(')');
1429 
1430       m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1431         m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1432 
1433       matchFound = true;
1434     }
1435     else if ((null != m_token) && ((('.' == m_tokenChar) && (m_token.length() > 1) && Character.isDigit(
1436             m_token.charAt(1))) || Character.isDigit(m_tokenChar)))
1437     {
1438       appendOp(2, OpCodes.OP_NUMBERLIT);
1439       Number();
1440 
1441       m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1442         m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1443 
1444       matchFound = true;
1445     }
1446     else if (lookahead('(', 1) || (lookahead(':', 1) && lookahead('(', 3)))
1447     {
1448       matchFound = FunctionCall();
1449     }
1450     else
1451     {
1452       matchFound = false;
1453     }
1454 
1455     return matchFound;
1456   }
1457 
1458   /**
1459    *
1460    * Argument    ::=    Expr
1461    *
1462    *
1463    * @throws javax.xml.transform.TransformerException
1464    */
1465   protected void Argument() throws javax.xml.transform.TransformerException
1466   {
1467 
1468     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1469 
1470     appendOp(2, OpCodes.OP_ARGUMENT);
1471     Expr();
1472 
1473     m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1474       m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1475   }
1476 
1477   /**
1478    *
1479    * FunctionCall    ::=    FunctionName '(' ( Argument ( ',' Argument)*)? ')'
1480    *
1481    * @return true if, and only if, a FunctionCall was matched
1482    *
1483    * @throws javax.xml.transform.TransformerException
1484    */
1485   protected boolean FunctionCall() throws javax.xml.transform.TransformerException
1486   {
1487 
1488     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1489 
1490     if (lookahead(':', 1))
1491     {
1492       appendOp(4, OpCodes.OP_EXTFUNCTION);
1493 
1494       m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1, m_queueMark - 1);
1495 
1496       nextToken();
1497       consumeExpected(':');
1498 
1499       m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 2, m_queueMark - 1);
1500 
1501       nextToken();
1502     }
1503     else
1504     {
1505       int funcTok = getFunctionToken(m_token);
1506 
1507       if (-1 == funcTok)
1508       {
1509         error(XPATHErrorResources.ER_COULDNOT_FIND_FUNCTION,
1510               new Object[]{ m_token });  //"Could not find function: "+m_token+"()");
1511       }
1512 
1513       switch (funcTok)
1514       {
1515       case OpCodes.NODETYPE_PI :
1516       case OpCodes.NODETYPE_COMMENT :
1517       case OpCodes.NODETYPE_TEXT :
1518       case OpCodes.NODETYPE_NODE :
1519         // Node type tests look like function calls, but they're not
1520         return false;
1521       default :
1522         appendOp(3, OpCodes.OP_FUNCTION);
1523 
1524         m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1, funcTok);
1525       }
1526 
1527       nextToken();
1528     }
1529 
1530     consumeExpected('(');
1531 
1532     while (!tokenIs(')') && m_token != null)
1533     {
1534       if (tokenIs(','))
1535       {
1536         error(XPATHErrorResources.ER_FOUND_COMMA_BUT_NO_PRECEDING_ARG, null);  //"Found ',' but no preceding argument!");
1537       }
1538 
1539       Argument();
1540 
1541       if (!tokenIs(')'))
1542       {
1543         consumeExpected(',');
1544 
1545         if (tokenIs(')'))
1546         {
1547           error(XPATHErrorResources.ER_FOUND_COMMA_BUT_NO_FOLLOWING_ARG,
1548                 null);  //"Found ',' but no following argument!");
1549         }
1550       }
1551     }
1552 
1553     consumeExpected(')');
1554 
1555     // Terminate for safety.
1556     m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
1557     m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1558     m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1559       m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1560 
1561     return true;
1562   }
1563 
1564   // ============= GRAMMAR FUNCTIONS =================
1565 
1566   /**
1567    *
1568    * LocationPath ::= RelativeLocationPath
1569    * | AbsoluteLocationPath
1570    *
1571    *
1572    * @throws javax.xml.transform.TransformerException
1573    */
1574   protected void LocationPath() throws javax.xml.transform.TransformerException
1575   {
1576 
1577     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1578 
1579     // int locationPathOpPos = opPos;
1580     appendOp(2, OpCodes.OP_LOCATIONPATH);
1581 
1582     boolean seenSlash = tokenIs('/');
1583 
1584     if (seenSlash)
1585     {
1586       appendOp(4, OpCodes.FROM_ROOT);
1587 
1588       // Tell how long the step is without the predicate
1589       m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2, 4);
1590       m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_ROOT);
1591 
1592       nextToken();
1593     } else if (m_token == null) {
1594       error(XPATHErrorResources.ER_EXPECTED_LOC_PATH_AT_END_EXPR, null);
1595     }
1596 
1597     if (m_token != null)
1598     {
1599       if (!RelativeLocationPath() && !seenSlash)
1600       {
1601         // Neither a '/' nor a RelativeLocationPath - i.e., matched nothing
1602         // "Location path expected, but found "+m_token+" was encountered."
1603         error(XPATHErrorResources.ER_EXPECTED_LOC_PATH,
1604               new Object [] {m_token});
1605       }
1606     }
1607 
1608     // Terminate for safety.
1609     m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
1610     m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1611     m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1612       m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1613   }
1614 
1615   /**
1616    *
1617    * RelativeLocationPath ::= Step
1618    * | RelativeLocationPath '/' Step
1619    * | AbbreviatedRelativeLocationPath
1620    *
1621    * @returns true if, and only if, a RelativeLocationPath was matched
1622    *
1623    * @throws javax.xml.transform.TransformerException
1624    */
1625   protected boolean RelativeLocationPath()
1626                throws javax.xml.transform.TransformerException
1627   {
1628     if (!Step())
1629     {
1630       return false;
1631     }
1632 
1633     while (tokenIs('/'))
1634     {
1635       nextToken();
1636 
1637       if (!Step())
1638       {
1639         // RelativeLocationPath can't end with a trailing '/'
1640         // "Location step expected following '/' or '//'"
1641         error(XPATHErrorResources.ER_EXPECTED_LOC_STEP, null);
1642       }
1643     }
1644 
1645     return true;
1646   }
1647 
1648   /**
1649    *
1650    * Step    ::=    Basis Predicate
1651    * | AbbreviatedStep
1652    *
1653    * @returns false if step was empty (or only a '/'); true, otherwise
1654    *
1655    * @throws javax.xml.transform.TransformerException
1656    */
1657   protected boolean Step() throws javax.xml.transform.TransformerException
1658   {
1659     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1660 
1661     boolean doubleSlash = tokenIs('/');
1662 
1663     // At most a single '/' before each Step is consumed by caller; if the
1664     // first thing is a '/', that means we had '//' and the Step must not
1665     // be empty.
1666     if (doubleSlash)
1667     {
1668       nextToken();
1669 
1670       appendOp(2, OpCodes.FROM_DESCENDANTS_OR_SELF);
1671 
1672       // Have to fix up for patterns such as '//@foo' or '//attribute::foo',
1673       // which translate to 'descendant-or-self::node()/attribute::foo'.
1674       // notice I leave the '/' on the queue, so the next will be processed
1675       // by a regular step pattern.
1676 
1677       // Make room for telling how long the step is without the predicate
1678       m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1679       m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.NODETYPE_NODE);
1680       m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1681 
1682       // Tell how long the step is without the predicate
1683       m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1,
1684           m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1685 
1686       // Tell how long the step is with the predicate
1687       m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1688           m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1689 
1690       opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1691     }
1692 
1693     if (tokenIs("."))
1694     {
1695       nextToken();
1696 
1697       if (tokenIs('['))
1698       {
1699         error(XPATHErrorResources.ER_PREDICATE_ILLEGAL_SYNTAX, null);  //"'..[predicate]' or '.[predicate]' is illegal syntax.  Use 'self::node()[predicate]' instead.");
1700       }
1701 
1702       appendOp(4, OpCodes.FROM_SELF);
1703 
1704       // Tell how long the step is without the predicate
1705       m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2,4);
1706       m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_NODE);
1707     }
1708     else if (tokenIs(".."))
1709     {
1710       nextToken();
1711       appendOp(4, OpCodes.FROM_PARENT);
1712 
1713       // Tell how long the step is without the predicate
1714       m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2,4);
1715       m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_NODE);
1716     }
1717 
1718     // There is probably a better way to test for this
1719     // transition... but it gets real hairy if you try
1720     // to do it in basis().
1721     else if (tokenIs('*') || tokenIs('@') || tokenIs('_')
1722              || (m_token!= null && Character.isLetter(m_token.charAt(0))))
1723     {
1724       Basis();
1725 
1726       while (tokenIs('['))
1727       {
1728         Predicate();
1729       }
1730 
1731       // Tell how long the entire step is.
1732       m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1733         m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1734     }
1735     else
1736     {
1737       // No Step matched - that's an error if previous thing was a '//'
1738       if (doubleSlash)
1739       {
1740         // "Location step expected following '/' or '//'"
1741         error(XPATHErrorResources.ER_EXPECTED_LOC_STEP, null);
1742       }
1743 
1744       return false;
1745     }
1746 
1747     return true;
1748   }
1749 
1750   /**
1751    *
1752    * Basis    ::=    AxisName '::' NodeTest
1753    * | AbbreviatedBasis
1754    *
1755    * @throws javax.xml.transform.TransformerException
1756    */
1757   protected void Basis() throws javax.xml.transform.TransformerException
1758   {
1759 
1760     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1761     int axesType;
1762 
1763     // The next blocks guarantee that a FROM_XXX will be added.
1764     if (lookahead("::", 1))
1765     {
1766       axesType = AxisName();
1767 
1768       nextToken();
1769       nextToken();
1770     }
1771     else if (tokenIs('@'))
1772     {
1773       axesType = OpCodes.FROM_ATTRIBUTES;
1774 
1775       appendOp(2, axesType);
1776       nextToken();
1777     }
1778     else
1779     {
1780       axesType = OpCodes.FROM_CHILDREN;
1781 
1782       appendOp(2, axesType);
1783     }
1784 
1785     // Make room for telling how long the step is without the predicate
1786     m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1787 
1788     NodeTest(axesType);
1789 
1790     // Tell how long the step is without the predicate
1791     m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1,
1792       m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1793    }
1794 
1795   /**
1796    *
1797    * Basis    ::=    AxisName '::' NodeTest
1798    * | AbbreviatedBasis
1799    *
1800    * @return FROM_XXX axes type, found in {@link com.sun.org.apache.xpath.internal.compiler.Keywords}.
1801    *
1802    * @throws javax.xml.transform.TransformerException
1803    */
1804   protected int AxisName() throws javax.xml.transform.TransformerException
1805   {
1806 
1807     Object val = Keywords.getAxisName(m_token);
1808 
1809     if (null == val)
1810     {
1811       error(XPATHErrorResources.ER_ILLEGAL_AXIS_NAME,
1812             new Object[]{ m_token });  //"illegal axis name: "+m_token);
1813     }
1814 
1815     int axesType = ((Integer) val).intValue();
1816 
1817     appendOp(2, axesType);
1818 
1819     return axesType;
1820   }
1821 
1822   /**
1823    *
1824    * NodeTest    ::=    WildcardName
1825    * | NodeType '(' ')'
1826    * | 'processing-instruction' '(' Literal ')'
1827    *
1828    * @param axesType FROM_XXX axes type, found in {@link com.sun.org.apache.xpath.internal.compiler.Keywords}.
1829    *
1830    * @throws javax.xml.transform.TransformerException
1831    */
1832   protected void NodeTest(int axesType) throws javax.xml.transform.TransformerException
1833   {
1834 
1835     if (lookahead('(', 1))
1836     {
1837       Object nodeTestOp = Keywords.getNodeType(m_token);
1838 
1839       if (null == nodeTestOp)
1840       {
1841         error(XPATHErrorResources.ER_UNKNOWN_NODETYPE,
1842               new Object[]{ m_token });  //"Unknown nodetype: "+m_token);
1843       }
1844       else
1845       {
1846         nextToken();
1847 
1848         int nt = ((Integer) nodeTestOp).intValue();
1849 
1850         m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), nt);
1851         m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1852 
1853         consumeExpected('(');
1854 
1855         if (OpCodes.NODETYPE_PI == nt)
1856         {
1857           if (!tokenIs(')'))
1858           {
1859             Literal();
1860           }
1861         }
1862 
1863         consumeExpected(')');
1864       }
1865     }
1866     else
1867     {
1868 
1869       // Assume name of attribute or element.
1870       m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.NODENAME);
1871       m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1872 
1873       if (lookahead(':', 1))
1874       {
1875         if (tokenIs('*'))
1876         {
1877           m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ELEMWILDCARD);
1878         }
1879         else
1880         {
1881           m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
1882 
1883           // Minimalist check for an NCName - just check first character
1884           // to distinguish from other possible tokens
1885           if (!Character.isLetter(m_tokenChar) && !tokenIs('_'))
1886           {
1887             // "Node test that matches either NCName:* or QName was expected."
1888             error(XPATHErrorResources.ER_EXPECTED_NODE_TEST, null);
1889           }
1890         }
1891 
1892         nextToken();
1893         consumeExpected(':');
1894       }
1895       else
1896       {
1897         m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.EMPTY);
1898       }
1899 
1900       m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1901 
1902       if (tokenIs('*'))
1903       {
1904         m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ELEMWILDCARD);
1905       }
1906       else
1907       {
1908         m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
1909 
1910         // Minimalist check for an NCName - just check first character
1911         // to distinguish from other possible tokens
1912         if (!Character.isLetter(m_tokenChar) && !tokenIs('_'))
1913         {
1914           // "Node test that matches either NCName:* or QName was expected."
1915           error(XPATHErrorResources.ER_EXPECTED_NODE_TEST, null);
1916         }
1917       }
1918 
1919       m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1920 
1921       nextToken();
1922     }
1923   }
1924 
1925   /**
1926    *
1927    * Predicate ::= '[' PredicateExpr ']'
1928    *
1929    *
1930    * @throws javax.xml.transform.TransformerException
1931    */
1932   protected void Predicate() throws javax.xml.transform.TransformerException
1933   {
1934 
1935     if (tokenIs('['))
1936     {
1937       nextToken();
1938       PredicateExpr();
1939       consumeExpected(']');
1940     }
1941   }
1942 
1943   /**
1944    *
1945    * PredicateExpr ::= Expr
1946    *
1947    *
1948    * @throws javax.xml.transform.TransformerException
1949    */
1950   protected void PredicateExpr() throws javax.xml.transform.TransformerException
1951   {
1952 
1953     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1954 
1955     appendOp(2, OpCodes.OP_PREDICATE);
1956     Expr();
1957 
1958     // Terminate for safety.
1959     m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
1960     m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1961     m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1962       m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1963   }
1964 
1965   /**
1966    * QName ::=  (Prefix ':')? LocalPart
1967    * Prefix ::=  NCName
1968    * LocalPart ::=  NCName
1969    *
1970    * @throws javax.xml.transform.TransformerException
1971    */
1972   protected void QName() throws javax.xml.transform.TransformerException
1973   {
1974     // Namespace
1975     if(lookahead(':', 1))
1976     {
1977       m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
1978       m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1979 
1980       nextToken();
1981       consumeExpected(':');
1982     }
1983     else
1984     {
1985       m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.EMPTY);
1986       m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1987     }
1988 
1989     // Local name
1990     m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
1991     m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1992 
1993     nextToken();
1994   }
1995 
1996   /**
1997    * NCName ::=  (Letter | '_') (NCNameChar)
1998    * NCNameChar ::=  Letter | Digit | '.' | '-' | '_' | CombiningChar | Extender
1999    */
2000   protected void NCName()
2001   {
2002 
2003     m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
2004     m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
2005 
2006     nextToken();
2007   }
2008 
2009   /**
2010    * The value of the Literal is the sequence of characters inside
2011    * the " or ' characters>.
2012    *
2013    * Literal  ::=  '"' [^"]* '"'
2014    * | "'" [^']* "'"
2015    *
2016    *
2017    * @throws javax.xml.transform.TransformerException
2018    */
2019   protected void Literal() throws javax.xml.transform.TransformerException
2020   {
2021 
2022     int last = m_token.length() - 1;
2023     char c0 = m_tokenChar;
2024     char cX = m_token.charAt(last);
2025 
2026     if (((c0 == '\"') && (cX == '\"')) || ((c0 == '\'') && (cX == '\'')))
2027     {
2028 
2029       // Mutate the token to remove the quotes and have the XString object
2030       // already made.
2031       int tokenQueuePos = m_queueMark - 1;
2032 
2033       m_ops.m_tokenQueue.setElementAt(null,tokenQueuePos);
2034 
2035       Object obj = new XString(m_token.substring(1, last));
2036 
2037       m_ops.m_tokenQueue.setElementAt(obj,tokenQueuePos);
2038 
2039       // lit = m_token.substring(1, last);
2040       m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), tokenQueuePos);
2041       m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
2042 
2043       nextToken();
2044     }
2045     else
2046     {
2047       error(XPATHErrorResources.ER_PATTERN_LITERAL_NEEDS_BE_QUOTED,
2048             new Object[]{ m_token });  //"Pattern literal ("+m_token+") needs to be quoted!");
2049     }
2050   }
2051 
2052   /**
2053    *
2054    * Number ::= [0-9]+('.'[0-9]+)? | '.'[0-9]+
2055    *
2056    *
2057    * @throws javax.xml.transform.TransformerException
2058    */
2059   protected void Number() throws javax.xml.transform.TransformerException
2060   {
2061 
2062     if (null != m_token)
2063     {
2064 
2065       // Mutate the token to remove the quotes and have the XNumber object
2066       // already made.
2067       double num;
2068 
2069       try
2070       {
2071         // XPath 1.0 does not support number in exp notation
2072         if ((m_token.indexOf('e') > -1)||(m_token.indexOf('E') > -1))
2073                 throw new NumberFormatException();
2074         num = Double.valueOf(m_token).doubleValue();
2075       }
2076       catch (NumberFormatException nfe)
2077       {
2078         num = 0.0;  // to shut up compiler.
2079 
2080         error(XPATHErrorResources.ER_COULDNOT_BE_FORMATTED_TO_NUMBER,
2081               new Object[]{ m_token });  //m_token+" could not be formatted to a number!");
2082       }
2083 
2084       m_ops.m_tokenQueue.setElementAt(new XNumber(num),m_queueMark - 1);
2085       m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
2086       m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
2087 
2088       nextToken();
2089     }
2090   }
2091 
2092   // ============= PATTERN FUNCTIONS =================
2093 
2094   /**
2095    *
2096    * Pattern  ::=  LocationPathPattern
2097    * | Pattern '|' LocationPathPattern
2098    *
2099    *
2100    * @throws javax.xml.transform.TransformerException
2101    */
2102   protected void Pattern() throws javax.xml.transform.TransformerException
2103   {
2104 
2105     while (true)
2106     {
2107       LocationPathPattern();
2108 
2109       if (tokenIs('|'))
2110       {
2111         nextToken();
2112       }
2113       else
2114       {
2115         break;
2116       }
2117     }
2118   }
2119 
2120   /**
2121    *
2122    *
2123    * LocationPathPattern  ::=  '/' RelativePathPattern?
2124    * | IdKeyPattern (('/' | '//') RelativePathPattern)?
2125    * | '//'? RelativePathPattern
2126    *
2127    *
2128    * @throws javax.xml.transform.TransformerException
2129    */
2130   protected void LocationPathPattern() throws javax.xml.transform.TransformerException
2131   {
2132 
2133     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
2134 
2135     final int RELATIVE_PATH_NOT_PERMITTED = 0;
2136     final int RELATIVE_PATH_PERMITTED     = 1;
2137     final int RELATIVE_PATH_REQUIRED      = 2;
2138 
2139     int relativePathStatus = RELATIVE_PATH_NOT_PERMITTED;
2140 
2141     appendOp(2, OpCodes.OP_LOCATIONPATHPATTERN);
2142 
2143     if (lookahead('(', 1)
2144             && (tokenIs(Keywords.FUNC_ID_STRING)
2145                 || tokenIs(Keywords.FUNC_KEY_STRING)))
2146     {
2147       IdKeyPattern();
2148 
2149       if (tokenIs('/'))
2150       {
2151         nextToken();
2152 
2153         if (tokenIs('/'))
2154         {
2155           appendOp(4, OpCodes.MATCH_ANY_ANCESTOR);
2156 
2157           nextToken();
2158         }
2159         else
2160         {
2161           appendOp(4, OpCodes.MATCH_IMMEDIATE_ANCESTOR);
2162         }
2163 
2164         // Tell how long the step is without the predicate
2165         m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2, 4);
2166         m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_FUNCTEST);
2167 
2168         relativePathStatus = RELATIVE_PATH_REQUIRED;
2169       }
2170     }
2171     else if (tokenIs('/'))
2172     {
2173       if (lookahead('/', 1))
2174       {
2175         appendOp(4, OpCodes.MATCH_ANY_ANCESTOR);
2176 
2177         // Added this to fix bug reported by Myriam for match="//x/a"
2178         // patterns.  If you don't do this, the 'x' step will think it's part
2179         // of a '//' pattern, and so will cause 'a' to be matched when it has
2180         // any ancestor that is 'x'.
2181         nextToken();
2182 
2183         relativePathStatus = RELATIVE_PATH_REQUIRED;
2184       }
2185       else
2186       {
2187         appendOp(4, OpCodes.FROM_ROOT);
2188 
2189         relativePathStatus = RELATIVE_PATH_PERMITTED;
2190       }
2191 
2192 
2193       // Tell how long the step is without the predicate
2194       m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2, 4);
2195       m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_ROOT);
2196 
2197       nextToken();
2198     }
2199     else
2200     {
2201       relativePathStatus = RELATIVE_PATH_REQUIRED;
2202     }
2203 
2204     if (relativePathStatus != RELATIVE_PATH_NOT_PERMITTED)
2205     {
2206       if (!tokenIs('|') && (null != m_token))
2207       {
2208         RelativePathPattern();
2209       }
2210       else if (relativePathStatus == RELATIVE_PATH_REQUIRED)
2211       {
2212         // "A relative path pattern was expected."
2213         error(XPATHErrorResources.ER_EXPECTED_REL_PATH_PATTERN, null);
2214       }
2215     }
2216 
2217     // Terminate for safety.
2218     m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
2219     m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
2220     m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
2221       m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
2222   }
2223 
2224   /**
2225    *
2226    * IdKeyPattern  ::=  'id' '(' Literal ')'
2227    * | 'key' '(' Literal ',' Literal ')'
2228    * (Also handle doc())
2229    *
2230    *
2231    * @throws javax.xml.transform.TransformerException
2232    */
2233   protected void IdKeyPattern() throws javax.xml.transform.TransformerException
2234   {
2235     FunctionCall();
2236   }
2237 
2238   /**
2239    *
2240    * RelativePathPattern  ::=  StepPattern
2241    * | RelativePathPattern '/' StepPattern
2242    * | RelativePathPattern '//' StepPattern
2243    *
2244    * @throws javax.xml.transform.TransformerException
2245    */
2246   protected void RelativePathPattern()
2247               throws javax.xml.transform.TransformerException
2248   {
2249 
2250     // Caller will have consumed any '/' or '//' preceding the
2251     // RelativePathPattern, so let StepPattern know it can't begin with a '/'
2252     boolean trailingSlashConsumed = StepPattern(false);
2253 
2254     while (tokenIs('/'))
2255     {
2256       nextToken();
2257 
2258       // StepPattern() may consume first slash of pair in "a//b" while
2259       // processing StepPattern "a".  On next iteration, let StepPattern know
2260       // that happened, so it doesn't match ill-formed patterns like "a///b".
2261       trailingSlashConsumed = StepPattern(!trailingSlashConsumed);
2262     }
2263   }
2264 
2265   /**
2266    *
2267    * StepPattern  ::=  AbbreviatedNodeTestStep
2268    *
2269    * @param isLeadingSlashPermitted a boolean indicating whether a slash can
2270    *        appear at the start of this step
2271    *
2272    * @return boolean indicating whether a slash following the step was consumed
2273    *
2274    * @throws javax.xml.transform.TransformerException
2275    */
2276   protected boolean StepPattern(boolean isLeadingSlashPermitted)
2277             throws javax.xml.transform.TransformerException
2278   {
2279     return AbbreviatedNodeTestStep(isLeadingSlashPermitted);
2280   }
2281 
2282   /**
2283    *
2284    * AbbreviatedNodeTestStep    ::=    '@'? NodeTest Predicate
2285    *
2286    * @param isLeadingSlashPermitted a boolean indicating whether a slash can
2287    *        appear at the start of this step
2288    *
2289    * @return boolean indicating whether a slash following the step was consumed
2290    *
2291    * @throws javax.xml.transform.TransformerException
2292    */
2293   protected boolean AbbreviatedNodeTestStep(boolean isLeadingSlashPermitted)
2294             throws javax.xml.transform.TransformerException
2295   {
2296 
2297     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
2298     int axesType;
2299 
2300     // The next blocks guarantee that a MATCH_XXX will be added.
2301     int matchTypePos = -1;
2302 
2303     if (tokenIs('@'))
2304     {
2305       axesType = OpCodes.MATCH_ATTRIBUTE;
2306 
2307       appendOp(2, axesType);
2308       nextToken();
2309     }
2310     else if (this.lookahead("::", 1))
2311     {
2312       if (tokenIs("attribute"))
2313       {
2314         axesType = OpCodes.MATCH_ATTRIBUTE;
2315 
2316         appendOp(2, axesType);
2317       }
2318       else if (tokenIs("child"))
2319       {
2320         matchTypePos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
2321         axesType = OpCodes.MATCH_IMMEDIATE_ANCESTOR;
2322 
2323         appendOp(2, axesType);
2324       }
2325       else
2326       {
2327         axesType = -1;
2328 
2329         this.error(XPATHErrorResources.ER_AXES_NOT_ALLOWED,
2330                    new Object[]{ this.m_token });
2331       }
2332 
2333       nextToken();
2334       nextToken();
2335     }
2336     else if (tokenIs('/'))
2337     {
2338       if (!isLeadingSlashPermitted)
2339       {
2340         // "A step was expected in the pattern, but '/' was encountered."
2341         error(XPATHErrorResources.ER_EXPECTED_STEP_PATTERN, null);
2342       }
2343       axesType = OpCodes.MATCH_ANY_ANCESTOR;
2344 
2345       appendOp(2, axesType);
2346       nextToken();
2347     }
2348     else
2349     {
2350       matchTypePos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
2351       axesType = OpCodes.MATCH_IMMEDIATE_ANCESTOR;
2352 
2353       appendOp(2, axesType);
2354     }
2355 
2356     // Make room for telling how long the step is without the predicate
2357     m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
2358 
2359     NodeTest(axesType);
2360 
2361     // Tell how long the step is without the predicate
2362     m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1,
2363       m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
2364 
2365     while (tokenIs('['))
2366     {
2367       Predicate();
2368     }
2369 
2370     boolean trailingSlashConsumed;
2371 
2372     // For "a//b", where "a" is current step, we need to mark operation of
2373     // current step as "MATCH_ANY_ANCESTOR".  Then we'll consume the first
2374     // slash and subsequent step will be treated as a MATCH_IMMEDIATE_ANCESTOR
2375     // (unless it too is followed by '//'.)
2376     //
2377     // %REVIEW%  Following is what happens today, but I'm not sure that's
2378     // %REVIEW%  correct behaviour.  Perhaps no valid case could be constructed
2379     // %REVIEW%  where it would matter?
2380     //
2381     // If current step is on the attribute axis (e.g., "@x//b"), we won't
2382     // change the current step, and let following step be marked as
2383     // MATCH_ANY_ANCESTOR on next call instead.
2384     if ((matchTypePos > -1) && tokenIs('/') && lookahead('/', 1))
2385     {
2386       m_ops.setOp(matchTypePos, OpCodes.MATCH_ANY_ANCESTOR);
2387 
2388       nextToken();
2389 
2390       trailingSlashConsumed = true;
2391     }
2392     else
2393     {
2394       trailingSlashConsumed = false;
2395     }
2396 
2397     // Tell how long the entire step is.
2398     m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
2399       m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
2400 
2401     return trailingSlashConsumed;
2402   }
2403 }