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