src/java.xml/share/classes/com/sun/org/apache/xpath/internal/compiler/XPathParser.java
changeset 47216 71c04702a3d5
parent 45853 bfa06be36a17
child 58620 ca5f1bf5a054
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/compiler/XPathParser.java	Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,2355 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ */
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.sun.org.apache.xpath.internal.compiler;
+
+import javax.xml.transform.ErrorListener;
+import javax.xml.transform.TransformerException;
+
+import com.sun.org.apache.xalan.internal.res.XSLMessages;
+import com.sun.org.apache.xml.internal.utils.PrefixResolver;
+import com.sun.org.apache.xpath.internal.XPathProcessorException;
+import com.sun.org.apache.xpath.internal.objects.XNumber;
+import com.sun.org.apache.xpath.internal.objects.XString;
+import com.sun.org.apache.xpath.internal.res.XPATHErrorResources;
+
+/**
+ * Tokenizes and parses XPath expressions. This should really be named
+ * XPathParserImpl, and may be renamed in the future.
+ * @xsl.usage general
+ */
+public class XPathParser
+{
+        // %REVIEW% Is there a better way of doing this?
+        // Upside is minimum object churn. Downside is that we don't have a useful
+        // backtrace in the exception itself -- but we don't expect to need one.
+        static public final String CONTINUE_AFTER_FATAL_ERROR="CONTINUE_AFTER_FATAL_ERROR";
+
+  /**
+   * The XPath to be processed.
+   */
+  private OpMap m_ops;
+
+  /**
+   * The next token in the pattern.
+   */
+  transient String m_token;
+
+  /**
+   * The first char in m_token, the theory being that this
+   * is an optimization because we won't have to do charAt(0) as
+   * often.
+   */
+  transient char m_tokenChar = 0;
+
+  /**
+   * The position in the token queue is tracked by m_queueMark.
+   */
+  int m_queueMark = 0;
+
+  /**
+   * Results from checking FilterExpr syntax
+   */
+  protected final static int FILTER_MATCH_FAILED     = 0;
+  protected final static int FILTER_MATCH_PRIMARY    = 1;
+  protected final static int FILTER_MATCH_PREDICATES = 2;
+
+  /**
+   * The parser constructor.
+   */
+  public XPathParser(ErrorListener errorListener, javax.xml.transform.SourceLocator sourceLocator)
+  {
+    m_errorListener = errorListener;
+    m_sourceLocator = sourceLocator;
+  }
+
+  /**
+   * The prefix resolver to map prefixes to namespaces in the OpMap.
+   */
+  PrefixResolver m_namespaceContext;
+
+  /**
+   * Given an string, init an XPath object for selections,
+   * in order that a parse doesn't
+   * have to be done each time the expression is evaluated.
+   *
+   * @param compiler The compiler object.
+   * @param expression A string conforming to the XPath grammar.
+   * @param namespaceContext An object that is able to resolve prefixes in
+   * the XPath to namespaces.
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  public void initXPath(
+          Compiler compiler, String expression, PrefixResolver namespaceContext)
+            throws javax.xml.transform.TransformerException
+  {
+
+    m_ops = compiler;
+    m_namespaceContext = namespaceContext;
+    m_functionTable = compiler.getFunctionTable();
+
+    Lexer lexer = new Lexer(compiler, namespaceContext, this);
+
+    lexer.tokenize(expression);
+
+    m_ops.setOp(0,OpCodes.OP_XPATH);
+    m_ops.setOp(OpMap.MAPINDEX_LENGTH,2);
+
+
+        // Patch for Christine's gripe. She wants her errorHandler to return from
+        // a fatal error and continue trying to parse, rather than throwing an exception.
+        // Without the patch, that put us into an endless loop.
+        //
+        // %REVIEW% Is there a better way of doing this?
+        // %REVIEW% Are there any other cases which need the safety net?
+        //      (and if so do we care right now, or should we rewrite the XPath
+        //      grammar engine and can fix it at that time?)
+        try {
+
+      nextToken();
+      Expr();
+
+      if (null != m_token)
+      {
+        String extraTokens = "";
+
+        while (null != m_token)
+        {
+          extraTokens += "'" + m_token + "'";
+
+          nextToken();
+
+          if (null != m_token)
+            extraTokens += ", ";
+        }
+
+        error(XPATHErrorResources.ER_EXTRA_ILLEGAL_TOKENS,
+              new Object[]{ extraTokens });  //"Extra illegal tokens: "+extraTokens);
+      }
+
+    }
+    catch (com.sun.org.apache.xpath.internal.XPathProcessorException e)
+    {
+          if(CONTINUE_AFTER_FATAL_ERROR.equals(e.getMessage()))
+          {
+                // What I _want_ to do is null out this XPath.
+                // I doubt this has the desired effect, but I'm not sure what else to do.
+                // %REVIEW%!!!
+                initXPath(compiler, "/..",  namespaceContext);
+          }
+          else
+                throw e;
+    }
+
+    compiler.shrink();
+  }
+
+  /**
+   * Given an string, init an XPath object for pattern matches,
+   * in order that a parse doesn't
+   * have to be done each time the expression is evaluated.
+   * @param compiler The XPath object to be initialized.
+   * @param expression A String representing the XPath.
+   * @param namespaceContext An object that is able to resolve prefixes in
+   * the XPath to namespaces.
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  public void initMatchPattern(
+          Compiler compiler, String expression, PrefixResolver namespaceContext)
+            throws javax.xml.transform.TransformerException
+  {
+
+    m_ops = compiler;
+    m_namespaceContext = namespaceContext;
+    m_functionTable = compiler.getFunctionTable();
+
+    Lexer lexer = new Lexer(compiler, namespaceContext, this);
+
+    lexer.tokenize(expression);
+
+    m_ops.setOp(0, OpCodes.OP_MATCHPATTERN);
+    m_ops.setOp(OpMap.MAPINDEX_LENGTH, 2);
+
+    nextToken();
+    Pattern();
+
+    if (null != m_token)
+    {
+      String extraTokens = "";
+
+      while (null != m_token)
+      {
+        extraTokens += "'" + m_token + "'";
+
+        nextToken();
+
+        if (null != m_token)
+          extraTokens += ", ";
+      }
+
+      error(XPATHErrorResources.ER_EXTRA_ILLEGAL_TOKENS,
+            new Object[]{ extraTokens });  //"Extra illegal tokens: "+extraTokens);
+    }
+
+    // Terminate for safety.
+    m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
+    m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH)+1);
+
+    m_ops.shrink();
+  }
+
+  /** The error listener where syntax errors are to be sent.
+   */
+  private ErrorListener m_errorListener;
+
+  /** The source location of the XPath. */
+  javax.xml.transform.SourceLocator m_sourceLocator;
+
+  /** The table contains build-in functions and customized functions */
+  private FunctionTable m_functionTable;
+
+  /**
+   * Allow an application to register an error event handler, where syntax
+   * errors will be sent.  If the error listener is not set, syntax errors
+   * will be sent to System.err.
+   *
+   * @param handler Reference to error listener where syntax errors will be
+   *                sent.
+   */
+  public void setErrorHandler(ErrorListener handler)
+  {
+    m_errorListener = handler;
+  }
+
+  /**
+   * Return the current error listener.
+   *
+   * @return The error listener, which should not normally be null, but may be.
+   */
+  public ErrorListener getErrorListener()
+  {
+    return m_errorListener;
+  }
+
+  /**
+   * Check whether m_token matches the target string.
+   *
+   * @param s A string reference or null.
+   *
+   * @return If m_token is null, returns false (or true if s is also null), or
+   * return true if the current token matches the string, else false.
+   */
+  final boolean tokenIs(String s)
+  {
+    return (m_token != null) ? (m_token.equals(s)) : (s == null);
+  }
+
+  /**
+   * Check whether m_tokenChar==c.
+   *
+   * @param c A character to be tested.
+   *
+   * @return If m_token is null, returns false, or return true if c matches
+   *         the current token.
+   */
+  final boolean tokenIs(char c)
+  {
+    return (m_token != null) ? (m_tokenChar == c) : false;
+  }
+
+  /**
+   * Look ahead of the current token in order to
+   * make a branching decision.
+   *
+   * @param c the character to be tested for.
+   * @param n number of tokens to look ahead.  Must be
+   * greater than 1.
+   *
+   * @return true if the next token matches the character argument.
+   */
+  final boolean lookahead(char c, int n)
+  {
+
+    int pos = (m_queueMark + n);
+    boolean b;
+
+    if ((pos <= m_ops.getTokenQueueSize()) && (pos > 0)
+            && (m_ops.getTokenQueueSize() != 0))
+    {
+      String tok = ((String) m_ops.m_tokenQueue.elementAt(pos - 1));
+
+      b = (tok.length() == 1) ? (tok.charAt(0) == c) : false;
+    }
+    else
+    {
+      b = false;
+    }
+
+    return b;
+  }
+
+  /**
+   * Look behind the first character of the current token in order to
+   * make a branching decision.
+   *
+   * @param c the character to compare it to.
+   * @param n number of tokens to look behind.  Must be
+   * greater than 1.  Note that the look behind terminates
+   * at either the beginning of the string or on a '|'
+   * character.  Because of this, this method should only
+   * be used for pattern matching.
+   *
+   * @return true if the token behind the current token matches the character
+   *         argument.
+   */
+  private final boolean lookbehind(char c, int n)
+  {
+
+    boolean isToken;
+    int lookBehindPos = m_queueMark - (n + 1);
+
+    if (lookBehindPos >= 0)
+    {
+      String lookbehind = (String) m_ops.m_tokenQueue.elementAt(lookBehindPos);
+
+      if (lookbehind.length() == 1)
+      {
+        char c0 = (lookbehind == null) ? '|' : lookbehind.charAt(0);
+
+        isToken = (c0 == '|') ? false : (c0 == c);
+      }
+      else
+      {
+        isToken = false;
+      }
+    }
+    else
+    {
+      isToken = false;
+    }
+
+    return isToken;
+  }
+
+  /**
+   * look behind the current token in order to
+   * see if there is a useable token.
+   *
+   * @param n number of tokens to look behind.  Must be
+   * greater than 1.  Note that the look behind terminates
+   * at either the beginning of the string or on a '|'
+   * character.  Because of this, this method should only
+   * be used for pattern matching.
+   *
+   * @return true if look behind has a token, false otherwise.
+   */
+  private final boolean lookbehindHasToken(int n)
+  {
+
+    boolean hasToken;
+
+    if ((m_queueMark - n) > 0)
+    {
+      String lookbehind = (String) m_ops.m_tokenQueue.elementAt(m_queueMark - (n - 1));
+      char c0 = (lookbehind == null) ? '|' : lookbehind.charAt(0);
+
+      hasToken = (c0 == '|') ? false : true;
+    }
+    else
+    {
+      hasToken = false;
+    }
+
+    return hasToken;
+  }
+
+  /**
+   * Look ahead of the current token in order to
+   * make a branching decision.
+   *
+   * @param s the string to compare it to.
+   * @param n number of tokens to lookahead.  Must be
+   * greater than 1.
+   *
+   * @return true if the token behind the current token matches the string
+   *         argument.
+   */
+  private final boolean lookahead(String s, int n)
+  {
+
+    boolean isToken;
+
+    if ((m_queueMark + n) <= m_ops.getTokenQueueSize())
+    {
+      String lookahead = (String) m_ops.m_tokenQueue.elementAt(m_queueMark + (n - 1));
+
+      isToken = (lookahead != null) ? lookahead.equals(s) : (s == null);
+    }
+    else
+    {
+      isToken = (null == s);
+    }
+
+    return isToken;
+  }
+
+  /**
+   * Retrieve the next token from the command and
+   * store it in m_token string.
+   */
+  private final void nextToken()
+  {
+
+    if (m_queueMark < m_ops.getTokenQueueSize())
+    {
+      m_token = (String) m_ops.m_tokenQueue.elementAt(m_queueMark++);
+      m_tokenChar = m_token.charAt(0);
+    }
+    else
+    {
+      m_token = null;
+      m_tokenChar = 0;
+    }
+  }
+
+  /**
+   * Retrieve a token relative to the current token.
+   *
+   * @param i Position relative to current token.
+   *
+   * @return The string at the given index, or null if the index is out
+   *         of range.
+   */
+  private final String getTokenRelative(int i)
+  {
+
+    String tok;
+    int relative = m_queueMark + i;
+
+    if ((relative > 0) && (relative < m_ops.getTokenQueueSize()))
+    {
+      tok = (String) m_ops.m_tokenQueue.elementAt(relative);
+    }
+    else
+    {
+      tok = null;
+    }
+
+    return tok;
+  }
+
+  /**
+   * Retrieve the previous token from the command and
+   * store it in m_token string.
+   */
+  private final void prevToken()
+  {
+
+    if (m_queueMark > 0)
+    {
+      m_queueMark--;
+
+      m_token = (String) m_ops.m_tokenQueue.elementAt(m_queueMark);
+      m_tokenChar = m_token.charAt(0);
+    }
+    else
+    {
+      m_token = null;
+      m_tokenChar = 0;
+    }
+  }
+
+  /**
+   * Consume an expected token, throwing an exception if it
+   * isn't there.
+   *
+   * @param expected The string to be expected.
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  private final void consumeExpected(String expected)
+          throws javax.xml.transform.TransformerException
+  {
+
+    if (tokenIs(expected))
+    {
+      nextToken();
+    }
+    else
+    {
+      error(XPATHErrorResources.ER_EXPECTED_BUT_FOUND, new Object[]{ expected,
+                                                                     m_token });  //"Expected "+expected+", but found: "+m_token);
+
+          // Patch for Christina's gripe. She wants her errorHandler to return from
+          // this error and continue trying to parse, rather than throwing an exception.
+          // Without the patch, that put us into an endless loop.
+                throw new XPathProcessorException(CONTINUE_AFTER_FATAL_ERROR);
+        }
+  }
+
+  /**
+   * Consume an expected token, throwing an exception if it
+   * isn't there.
+   *
+   * @param expected the character to be expected.
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  private final void consumeExpected(char expected)
+          throws javax.xml.transform.TransformerException
+  {
+
+    if (tokenIs(expected))
+    {
+      nextToken();
+    }
+    else
+    {
+      error(XPATHErrorResources.ER_EXPECTED_BUT_FOUND,
+            new Object[]{ String.valueOf(expected),
+                          m_token });  //"Expected "+expected+", but found: "+m_token);
+
+          // Patch for Christina's gripe. She wants her errorHandler to return from
+          // this error and continue trying to parse, rather than throwing an exception.
+          // Without the patch, that put us into an endless loop.
+                throw new XPathProcessorException(CONTINUE_AFTER_FATAL_ERROR);
+    }
+  }
+
+  /**
+   * Warn the user of a problem.
+   *
+   * @param msg An error msgkey that corresponds to one of the constants found
+   *            in {@link com.sun.org.apache.xpath.internal.res.XPATHErrorResources}, which is
+   *            a key for a format string.
+   * @param args An array of arguments represented in the format string, which
+   *             may be null.
+   *
+   * @throws TransformerException if the current ErrorListoner determines to
+   *                              throw an exception.
+   */
+  void warn(String msg, Object[] args) throws TransformerException
+  {
+
+    String fmsg = XSLMessages.createXPATHWarning(msg, args);
+    ErrorListener ehandler = this.getErrorListener();
+
+    if (null != ehandler)
+    {
+      // TO DO: Need to get stylesheet Locator from here.
+      ehandler.warning(new TransformerException(fmsg, m_sourceLocator));
+    }
+    else
+    {
+      // Should never happen.
+      System.err.println(fmsg);
+    }
+  }
+
+  /**
+   * Notify the user of an assertion error, and probably throw an
+   * exception.
+   *
+   * @param b  If false, a runtime exception will be thrown.
+   * @param msg The assertion message, which should be informative.
+   *
+   * @throws RuntimeException if the b argument is false.
+   */
+  private void assertion(boolean b, String msg)
+  {
+
+    if (!b)
+    {
+      String fMsg = XSLMessages.createXPATHMessage(
+        XPATHErrorResources.ER_INCORRECT_PROGRAMMER_ASSERTION,
+        new Object[]{ msg });
+
+      throw new RuntimeException(fMsg);
+    }
+  }
+
+  /**
+   * Notify the user of an error, and probably throw an
+   * exception.
+   *
+   * @param msg An error msgkey that corresponds to one of the constants found
+   *            in {@link com.sun.org.apache.xpath.internal.res.XPATHErrorResources}, which is
+   *            a key for a format string.
+   * @param args An array of arguments represented in the format string, which
+   *             may be null.
+   *
+   * @throws TransformerException if the current ErrorListoner determines to
+   *                              throw an exception.
+   */
+  void error(String msg, Object[] args) throws TransformerException
+  {
+
+    String fmsg = XSLMessages.createXPATHMessage(msg, args);
+    ErrorListener ehandler = this.getErrorListener();
+
+    TransformerException te = new TransformerException(fmsg, m_sourceLocator);
+    if (null != ehandler)
+    {
+      // TO DO: Need to get stylesheet Locator from here.
+      ehandler.fatalError(te);
+    }
+    else
+    {
+      // System.err.println(fmsg);
+      throw te;
+    }
+  }
+
+  /**
+   * Dump the remaining token queue.
+   * Thanks to Craig for this.
+   *
+   * @return A dump of the remaining token queue, which may be appended to
+   *         an error message.
+   */
+  protected String dumpRemainingTokenQueue()
+  {
+
+    int q = m_queueMark;
+    String returnMsg;
+
+    if (q < m_ops.getTokenQueueSize())
+    {
+      String msg = "\n Remaining tokens: (";
+
+      while (q < m_ops.getTokenQueueSize())
+      {
+        String t = (String) m_ops.m_tokenQueue.elementAt(q++);
+
+        msg += (" '" + t + "'");
+      }
+
+      returnMsg = msg + ")";
+    }
+    else
+    {
+      returnMsg = "";
+    }
+
+    return returnMsg;
+  }
+
+  /**
+   * Given a string, return the corresponding function token.
+   *
+   * @param key A local name of a function.
+   *
+   * @return   The function ID, which may correspond to one of the FUNC_XXX
+   *    values found in {@link com.sun.org.apache.xpath.internal.compiler.FunctionTable}, but may
+   *    be a value installed by an external module.
+   */
+  final int getFunctionToken(String key)
+  {
+
+    int tok;
+    Integer id;
+
+    try
+    {
+      // These are nodetests, xpathparser treats them as functions when parsing
+      // a FilterExpr.
+      id = Keywords.lookupNodeTest(key);
+      if (null == id) id = m_functionTable.getFunctionID(key);
+      tok = id;
+    }
+    catch (NullPointerException npe)
+    {
+      tok = -1;
+    }
+    catch (ClassCastException cce)
+    {
+      tok = -1;
+    }
+
+    return tok;
+  }
+
+  /**
+   * Insert room for operation.  This will NOT set
+   * the length value of the operation, but will update
+   * the length value for the total expression.
+   *
+   * @param pos The position where the op is to be inserted.
+   * @param length The length of the operation space in the op map.
+   * @param op The op code to the inserted.
+   */
+  void insertOp(int pos, int length, int op)
+  {
+
+    int totalLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+    for (int i = totalLen - 1; i >= pos; i--)
+    {
+      m_ops.setOp(i + length, m_ops.getOp(i));
+    }
+
+    m_ops.setOp(pos,op);
+    m_ops.setOp(OpMap.MAPINDEX_LENGTH,totalLen + length);
+  }
+
+  /**
+   * Insert room for operation.  This WILL set
+   * the length value of the operation, and will update
+   * the length value for the total expression.
+   *
+   * @param length The length of the operation.
+   * @param op The op code to the inserted.
+   */
+  void appendOp(int length, int op)
+  {
+
+    int totalLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+    m_ops.setOp(totalLen, op);
+    m_ops.setOp(totalLen + OpMap.MAPINDEX_LENGTH, length);
+    m_ops.setOp(OpMap.MAPINDEX_LENGTH, totalLen + length);
+  }
+
+  // ============= EXPRESSIONS FUNCTIONS =================
+
+  /**
+   *
+   *
+   * Expr  ::=  OrExpr
+   *
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  protected void Expr() throws javax.xml.transform.TransformerException
+  {
+    OrExpr();
+  }
+
+  /**
+   *
+   *
+   * OrExpr  ::=  AndExpr
+   * | OrExpr 'or' AndExpr
+   *
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  protected void OrExpr() throws javax.xml.transform.TransformerException
+  {
+
+    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+    AndExpr();
+
+    if ((null != m_token) && tokenIs("or"))
+    {
+      nextToken();
+      insertOp(opPos, 2, OpCodes.OP_OR);
+      OrExpr();
+
+      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+        m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+    }
+  }
+
+  /**
+   *
+   *
+   * AndExpr  ::=  EqualityExpr
+   * | AndExpr 'and' EqualityExpr
+   *
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  protected void AndExpr() throws javax.xml.transform.TransformerException
+  {
+
+    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+    EqualityExpr(-1);
+
+    if ((null != m_token) && tokenIs("and"))
+    {
+      nextToken();
+      insertOp(opPos, 2, OpCodes.OP_AND);
+      AndExpr();
+
+      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+        m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+    }
+  }
+
+  /**
+   *
+   * @returns an Object which is either a String, a Number, a Boolean, or a vector
+   * of nodes.
+   *
+   * EqualityExpr  ::=  RelationalExpr
+   * | EqualityExpr '=' RelationalExpr
+   *
+   *
+   * @param addPos Position where expression is to be added, or -1 for append.
+   *
+   * @return the position at the end of the equality expression.
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  protected int EqualityExpr(int addPos) throws javax.xml.transform.TransformerException
+  {
+
+    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+    if (-1 == addPos)
+      addPos = opPos;
+
+    RelationalExpr(-1);
+
+    if (null != m_token)
+    {
+      if (tokenIs('!') && lookahead('=', 1))
+      {
+        nextToken();
+        nextToken();
+        insertOp(addPos, 2, OpCodes.OP_NOTEQUALS);
+
+        int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
+
+        addPos = EqualityExpr(addPos);
+        m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
+          m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
+        addPos += 2;
+      }
+      else if (tokenIs('='))
+      {
+        nextToken();
+        insertOp(addPos, 2, OpCodes.OP_EQUALS);
+
+        int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
+
+        addPos = EqualityExpr(addPos);
+        m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
+          m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
+        addPos += 2;
+      }
+    }
+
+    return addPos;
+  }
+
+  /**
+   * .
+   * @returns an Object which is either a String, a Number, a Boolean, or a vector
+   * of nodes.
+   *
+   * RelationalExpr  ::=  AdditiveExpr
+   * | RelationalExpr '<' AdditiveExpr
+   * | RelationalExpr '>' AdditiveExpr
+   * | RelationalExpr '<=' AdditiveExpr
+   * | RelationalExpr '>=' AdditiveExpr
+   *
+   *
+   * @param addPos Position where expression is to be added, or -1 for append.
+   *
+   * @return the position at the end of the relational expression.
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  protected int RelationalExpr(int addPos) throws javax.xml.transform.TransformerException
+  {
+
+    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+    if (-1 == addPos)
+      addPos = opPos;
+
+    AdditiveExpr(-1);
+
+    if (null != m_token)
+    {
+      if (tokenIs('<'))
+      {
+        nextToken();
+
+        if (tokenIs('='))
+        {
+          nextToken();
+          insertOp(addPos, 2, OpCodes.OP_LTE);
+        }
+        else
+        {
+          insertOp(addPos, 2, OpCodes.OP_LT);
+        }
+
+        int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
+
+        addPos = RelationalExpr(addPos);
+        m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
+          m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
+        addPos += 2;
+      }
+      else if (tokenIs('>'))
+      {
+        nextToken();
+
+        if (tokenIs('='))
+        {
+          nextToken();
+          insertOp(addPos, 2, OpCodes.OP_GTE);
+        }
+        else
+        {
+          insertOp(addPos, 2, OpCodes.OP_GT);
+        }
+
+        int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
+
+        addPos = RelationalExpr(addPos);
+        m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
+          m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
+        addPos += 2;
+      }
+    }
+
+    return addPos;
+  }
+
+  /**
+   * This has to handle construction of the operations so that they are evaluated
+   * in pre-fix order.  So, for 9+7-6, instead of |+|9|-|7|6|, this needs to be
+   * evaluated as |-|+|9|7|6|.
+   *
+   * AdditiveExpr  ::=  MultiplicativeExpr
+   * | AdditiveExpr '+' MultiplicativeExpr
+   * | AdditiveExpr '-' MultiplicativeExpr
+   *
+   *
+   * @param addPos Position where expression is to be added, or -1 for append.
+   *
+   * @return the position at the end of the equality expression.
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  protected int AdditiveExpr(int addPos) throws javax.xml.transform.TransformerException
+  {
+
+    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+    if (-1 == addPos)
+      addPos = opPos;
+
+    MultiplicativeExpr(-1);
+
+    if (null != m_token)
+    {
+      if (tokenIs('+'))
+      {
+        nextToken();
+        insertOp(addPos, 2, OpCodes.OP_PLUS);
+
+        int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
+
+        addPos = AdditiveExpr(addPos);
+        m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
+          m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
+        addPos += 2;
+      }
+      else if (tokenIs('-'))
+      {
+        nextToken();
+        insertOp(addPos, 2, OpCodes.OP_MINUS);
+
+        int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
+
+        addPos = AdditiveExpr(addPos);
+        m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
+          m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
+        addPos += 2;
+      }
+    }
+
+    return addPos;
+  }
+
+  /**
+   * This has to handle construction of the operations so that they are evaluated
+   * in pre-fix order.  So, for 9+7-6, instead of |+|9|-|7|6|, this needs to be
+   * evaluated as |-|+|9|7|6|.
+   *
+   * MultiplicativeExpr  ::=  UnaryExpr
+   * | MultiplicativeExpr MultiplyOperator UnaryExpr
+   * | MultiplicativeExpr 'div' UnaryExpr
+   * | MultiplicativeExpr 'mod' UnaryExpr
+   * | MultiplicativeExpr 'quo' UnaryExpr
+   *
+   * @param addPos Position where expression is to be added, or -1 for append.
+   *
+   * @return the position at the end of the equality expression.
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  protected int MultiplicativeExpr(int addPos) throws javax.xml.transform.TransformerException
+  {
+
+    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+    if (-1 == addPos)
+      addPos = opPos;
+
+    UnaryExpr();
+
+    if (null != m_token)
+    {
+      if (tokenIs('*'))
+      {
+        nextToken();
+        insertOp(addPos, 2, OpCodes.OP_MULT);
+
+        int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
+
+        addPos = MultiplicativeExpr(addPos);
+        m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
+          m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
+        addPos += 2;
+      }
+      else if (tokenIs("div"))
+      {
+        nextToken();
+        insertOp(addPos, 2, OpCodes.OP_DIV);
+
+        int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
+
+        addPos = MultiplicativeExpr(addPos);
+        m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
+          m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
+        addPos += 2;
+      }
+      else if (tokenIs("mod"))
+      {
+        nextToken();
+        insertOp(addPos, 2, OpCodes.OP_MOD);
+
+        int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
+
+        addPos = MultiplicativeExpr(addPos);
+        m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
+          m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
+        addPos += 2;
+      }
+      else if (tokenIs("quo"))
+      {
+        nextToken();
+        insertOp(addPos, 2, OpCodes.OP_QUO);
+
+        int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
+
+        addPos = MultiplicativeExpr(addPos);
+        m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
+          m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
+        addPos += 2;
+      }
+    }
+
+    return addPos;
+  }
+
+  /**
+   *
+   * UnaryExpr  ::=  UnionExpr
+   * | '-' UnaryExpr
+   *
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  protected void UnaryExpr() throws javax.xml.transform.TransformerException
+  {
+
+    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+    boolean isNeg = false;
+
+    if (m_tokenChar == '-')
+    {
+      nextToken();
+      appendOp(2, OpCodes.OP_NEG);
+
+      isNeg = true;
+    }
+
+    UnionExpr();
+
+    if (isNeg)
+      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+        m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+  }
+
+  /**
+   *
+   * StringExpr  ::=  Expr
+   *
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  protected void StringExpr() throws javax.xml.transform.TransformerException
+  {
+
+    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+    appendOp(2, OpCodes.OP_STRING);
+    Expr();
+
+    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+  }
+
+  /**
+   *
+   *
+   * StringExpr  ::=  Expr
+   *
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  protected void BooleanExpr() throws javax.xml.transform.TransformerException
+  {
+
+    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+    appendOp(2, OpCodes.OP_BOOL);
+    Expr();
+
+    int opLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos;
+
+    if (opLen == 2)
+    {
+      error(XPATHErrorResources.ER_BOOLEAN_ARG_NO_LONGER_OPTIONAL, null);  //"boolean(...) argument is no longer optional with 19990709 XPath draft.");
+    }
+
+    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, opLen);
+  }
+
+  /**
+   *
+   *
+   * NumberExpr  ::=  Expr
+   *
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  protected void NumberExpr() throws javax.xml.transform.TransformerException
+  {
+
+    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+    appendOp(2, OpCodes.OP_NUMBER);
+    Expr();
+
+    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+  }
+
+  /**
+   * The context of the right hand side expressions is the context of the
+   * left hand side expression. The results of the right hand side expressions
+   * are node sets. The result of the left hand side UnionExpr is the union
+   * of the results of the right hand side expressions.
+   *
+   *
+   * UnionExpr    ::=    PathExpr
+   * | UnionExpr '|' PathExpr
+   *
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  protected void UnionExpr() throws javax.xml.transform.TransformerException
+  {
+
+    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+    boolean continueOrLoop = true;
+    boolean foundUnion = false;
+
+    do
+    {
+      PathExpr();
+
+      if (tokenIs('|'))
+      {
+        if (false == foundUnion)
+        {
+          foundUnion = true;
+
+          insertOp(opPos, 2, OpCodes.OP_UNION);
+        }
+
+        nextToken();
+      }
+      else
+      {
+        break;
+      }
+
+      // this.m_testForDocOrder = true;
+    }
+    while (continueOrLoop);
+
+    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+          m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+  }
+
+  /**
+   * PathExpr  ::=  LocationPath
+   * | FilterExpr
+   * | FilterExpr '/' RelativeLocationPath
+   * | FilterExpr '//' RelativeLocationPath
+   *
+   * @throws XSLProcessorException thrown if the active ProblemListener and XPathContext decide
+   * the error condition is severe enough to halt processing.
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  protected void PathExpr() throws javax.xml.transform.TransformerException
+  {
+
+    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+    int filterExprMatch = FilterExpr();
+
+    if (filterExprMatch != FILTER_MATCH_FAILED)
+    {
+      // If FilterExpr had Predicates, a OP_LOCATIONPATH opcode would already
+      // have been inserted.
+      boolean locationPathStarted = (filterExprMatch==FILTER_MATCH_PREDICATES);
+
+      if (tokenIs('/'))
+      {
+        nextToken();
+
+        if (!locationPathStarted)
+        {
+          // int locationPathOpPos = opPos;
+          insertOp(opPos, 2, OpCodes.OP_LOCATIONPATH);
+
+          locationPathStarted = true;
+        }
+
+        if (!RelativeLocationPath())
+        {
+          // "Relative location path expected following '/' or '//'"
+          error(XPATHErrorResources.ER_EXPECTED_REL_LOC_PATH, null);
+        }
+
+      }
+
+      // Terminate for safety.
+      if (locationPathStarted)
+      {
+        m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
+        m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+        m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+          m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+      }
+    }
+    else
+    {
+      LocationPath();
+    }
+  }
+
+  /**
+   *
+   *
+   * FilterExpr  ::=  PrimaryExpr
+   * | FilterExpr Predicate
+   *
+   * @throws XSLProcessorException thrown if the active ProblemListener and XPathContext decide
+   * the error condition is severe enough to halt processing.
+   *
+   * @return  FILTER_MATCH_PREDICATES, if this method successfully matched a
+   *          FilterExpr with one or more Predicates;
+   *          FILTER_MATCH_PRIMARY, if this method successfully matched a
+   *          FilterExpr that was just a PrimaryExpr; or
+   *          FILTER_MATCH_FAILED, if this method did not match a FilterExpr
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  protected int FilterExpr() throws javax.xml.transform.TransformerException
+  {
+
+    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+    int filterMatch;
+
+    if (PrimaryExpr())
+    {
+      if (tokenIs('['))
+      {
+
+        // int locationPathOpPos = opPos;
+        insertOp(opPos, 2, OpCodes.OP_LOCATIONPATH);
+
+        while (tokenIs('['))
+        {
+          Predicate();
+        }
+
+        filterMatch = FILTER_MATCH_PREDICATES;
+      }
+      else
+      {
+        filterMatch = FILTER_MATCH_PRIMARY;
+      }
+    }
+    else
+    {
+      filterMatch = FILTER_MATCH_FAILED;
+    }
+
+    return filterMatch;
+
+    /*
+     * if(tokenIs('['))
+     * {
+     *   Predicate();
+     *   m_ops.m_opMap[opPos + OpMap.MAPINDEX_LENGTH] = m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] - opPos;
+     * }
+     */
+  }
+
+  /**
+   *
+   * PrimaryExpr  ::=  VariableReference
+   * | '(' Expr ')'
+   * | Literal
+   * | Number
+   * | FunctionCall
+   *
+   * @return true if this method successfully matched a PrimaryExpr
+   *
+   * @throws javax.xml.transform.TransformerException
+   *
+   */
+  protected boolean PrimaryExpr() throws javax.xml.transform.TransformerException
+  {
+
+    boolean matchFound;
+    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+    if ((m_tokenChar == '\'') || (m_tokenChar == '"'))
+    {
+      appendOp(2, OpCodes.OP_LITERAL);
+      Literal();
+
+      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+        m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+
+      matchFound = true;
+    }
+    else if (m_tokenChar == '$')
+    {
+      nextToken();  // consume '$'
+      appendOp(2, OpCodes.OP_VARIABLE);
+      QName();
+
+      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+        m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+
+      matchFound = true;
+    }
+    else if (m_tokenChar == '(')
+    {
+      nextToken();
+      appendOp(2, OpCodes.OP_GROUP);
+      Expr();
+      consumeExpected(')');
+
+      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+        m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+
+      matchFound = true;
+    }
+    else if ((null != m_token) && ((('.' == m_tokenChar) && (m_token.length() > 1) && Character.isDigit(
+            m_token.charAt(1))) || Character.isDigit(m_tokenChar)))
+    {
+      appendOp(2, OpCodes.OP_NUMBERLIT);
+      Number();
+
+      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+        m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+
+      matchFound = true;
+    }
+    else if (lookahead('(', 1) || (lookahead(':', 1) && lookahead('(', 3)))
+    {
+      matchFound = FunctionCall();
+    }
+    else
+    {
+      matchFound = false;
+    }
+
+    return matchFound;
+  }
+
+  /**
+   *
+   * Argument    ::=    Expr
+   *
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  protected void Argument() throws javax.xml.transform.TransformerException
+  {
+
+    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+    appendOp(2, OpCodes.OP_ARGUMENT);
+    Expr();
+
+    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+  }
+
+  /**
+   *
+   * FunctionCall    ::=    FunctionName '(' ( Argument ( ',' Argument)*)? ')'
+   *
+   * @return true if, and only if, a FunctionCall was matched
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  protected boolean FunctionCall() throws javax.xml.transform.TransformerException
+  {
+
+    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+    if (lookahead(':', 1))
+    {
+      appendOp(4, OpCodes.OP_EXTFUNCTION);
+
+      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1, m_queueMark - 1);
+
+      nextToken();
+      consumeExpected(':');
+
+      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 2, m_queueMark - 1);
+
+      nextToken();
+    }
+    else
+    {
+      int funcTok = getFunctionToken(m_token);
+
+      if (-1 == funcTok)
+      {
+        error(XPATHErrorResources.ER_COULDNOT_FIND_FUNCTION,
+              new Object[]{ m_token });  //"Could not find function: "+m_token+"()");
+      }
+
+      switch (funcTok)
+      {
+      case OpCodes.NODETYPE_PI :
+      case OpCodes.NODETYPE_COMMENT :
+      case OpCodes.NODETYPE_TEXT :
+      case OpCodes.NODETYPE_NODE :
+        // Node type tests look like function calls, but they're not
+        return false;
+      default :
+        appendOp(3, OpCodes.OP_FUNCTION);
+
+        m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1, funcTok);
+      }
+
+      nextToken();
+    }
+
+    consumeExpected('(');
+
+    while (!tokenIs(')') && m_token != null)
+    {
+      if (tokenIs(','))
+      {
+        error(XPATHErrorResources.ER_FOUND_COMMA_BUT_NO_PRECEDING_ARG, null);  //"Found ',' but no preceding argument!");
+      }
+
+      Argument();
+
+      if (!tokenIs(')'))
+      {
+        consumeExpected(',');
+
+        if (tokenIs(')'))
+        {
+          error(XPATHErrorResources.ER_FOUND_COMMA_BUT_NO_FOLLOWING_ARG,
+                null);  //"Found ',' but no following argument!");
+        }
+      }
+    }
+
+    consumeExpected(')');
+
+    // Terminate for safety.
+    m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
+    m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+
+    return true;
+  }
+
+  // ============= GRAMMAR FUNCTIONS =================
+
+  /**
+   *
+   * LocationPath ::= RelativeLocationPath
+   * | AbsoluteLocationPath
+   *
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  protected void LocationPath() throws javax.xml.transform.TransformerException
+  {
+
+    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+    // int locationPathOpPos = opPos;
+    appendOp(2, OpCodes.OP_LOCATIONPATH);
+
+    boolean seenSlash = tokenIs('/');
+
+    if (seenSlash)
+    {
+      appendOp(4, OpCodes.FROM_ROOT);
+
+      // Tell how long the step is without the predicate
+      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2, 4);
+      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_ROOT);
+
+      nextToken();
+    } else if (m_token == null) {
+      error(XPATHErrorResources.ER_EXPECTED_LOC_PATH_AT_END_EXPR, null);
+    }
+
+    if (m_token != null)
+    {
+      if (!RelativeLocationPath() && !seenSlash)
+      {
+        // Neither a '/' nor a RelativeLocationPath - i.e., matched nothing
+        // "Location path expected, but found "+m_token+" was encountered."
+        error(XPATHErrorResources.ER_EXPECTED_LOC_PATH,
+              new Object [] {m_token});
+      }
+    }
+
+    // Terminate for safety.
+    m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
+    m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+  }
+
+  /**
+   *
+   * RelativeLocationPath ::= Step
+   * | RelativeLocationPath '/' Step
+   * | AbbreviatedRelativeLocationPath
+   *
+   * @returns true if, and only if, a RelativeLocationPath was matched
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  protected boolean RelativeLocationPath()
+               throws javax.xml.transform.TransformerException
+  {
+    if (!Step())
+    {
+      return false;
+    }
+
+    while (tokenIs('/'))
+    {
+      nextToken();
+
+      if (!Step())
+      {
+        // RelativeLocationPath can't end with a trailing '/'
+        // "Location step expected following '/' or '//'"
+        error(XPATHErrorResources.ER_EXPECTED_LOC_STEP, null);
+      }
+    }
+
+    return true;
+  }
+
+  /**
+   *
+   * Step    ::=    Basis Predicate
+   * | AbbreviatedStep
+   *
+   * @returns false if step was empty (or only a '/'); true, otherwise
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  protected boolean Step() throws javax.xml.transform.TransformerException
+  {
+    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+    boolean doubleSlash = tokenIs('/');
+
+    // At most a single '/' before each Step is consumed by caller; if the
+    // first thing is a '/', that means we had '//' and the Step must not
+    // be empty.
+    if (doubleSlash)
+    {
+      nextToken();
+
+      appendOp(2, OpCodes.FROM_DESCENDANTS_OR_SELF);
+
+      // Have to fix up for patterns such as '//@foo' or '//attribute::foo',
+      // which translate to 'descendant-or-self::node()/attribute::foo'.
+      // notice I leave the '/' on the queue, so the next will be processed
+      // by a regular step pattern.
+
+      // Make room for telling how long the step is without the predicate
+      m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.NODETYPE_NODE);
+      m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+
+      // Tell how long the step is without the predicate
+      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1,
+          m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+
+      // Tell how long the step is with the predicate
+      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+          m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+
+      opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+    }
+
+    if (tokenIs("."))
+    {
+      nextToken();
+
+      if (tokenIs('['))
+      {
+        error(XPATHErrorResources.ER_PREDICATE_ILLEGAL_SYNTAX, null);  //"'..[predicate]' or '.[predicate]' is illegal syntax.  Use 'self::node()[predicate]' instead.");
+      }
+
+      appendOp(4, OpCodes.FROM_SELF);
+
+      // Tell how long the step is without the predicate
+      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2,4);
+      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_NODE);
+    }
+    else if (tokenIs(".."))
+    {
+      nextToken();
+      appendOp(4, OpCodes.FROM_PARENT);
+
+      // Tell how long the step is without the predicate
+      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2,4);
+      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_NODE);
+    }
+
+    // There is probably a better way to test for this
+    // transition... but it gets real hairy if you try
+    // to do it in basis().
+    else if (tokenIs('*') || tokenIs('@') || tokenIs('_')
+             || (m_token!= null && Character.isLetter(m_token.charAt(0))))
+    {
+      Basis();
+
+      while (tokenIs('['))
+      {
+        Predicate();
+      }
+
+      // Tell how long the entire step is.
+      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+        m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+    }
+    else
+    {
+      // No Step matched - that's an error if previous thing was a '//'
+      if (doubleSlash)
+      {
+        // "Location step expected following '/' or '//'"
+        error(XPATHErrorResources.ER_EXPECTED_LOC_STEP, null);
+      }
+
+      return false;
+    }
+
+    return true;
+  }
+
+  /**
+   *
+   * Basis    ::=    AxisName '::' NodeTest
+   * | AbbreviatedBasis
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  protected void Basis() throws javax.xml.transform.TransformerException
+  {
+
+    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+    int axesType;
+
+    // The next blocks guarantee that a FROM_XXX will be added.
+    if (lookahead("::", 1))
+    {
+      axesType = AxisName();
+
+      nextToken();
+      nextToken();
+    }
+    else if (tokenIs('@'))
+    {
+      axesType = OpCodes.FROM_ATTRIBUTES;
+
+      appendOp(2, axesType);
+      nextToken();
+    }
+    else
+    {
+      axesType = OpCodes.FROM_CHILDREN;
+
+      appendOp(2, axesType);
+    }
+
+    // Make room for telling how long the step is without the predicate
+    m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+
+    NodeTest(axesType);
+
+    // Tell how long the step is without the predicate
+    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1,
+      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+   }
+
+  /**
+   *
+   * Basis    ::=    AxisName '::' NodeTest
+   * | AbbreviatedBasis
+   *
+   * @return FROM_XXX axes type, found in {@link com.sun.org.apache.xpath.internal.compiler.Keywords}.
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  protected int AxisName() throws javax.xml.transform.TransformerException
+  {
+
+    Object val = Keywords.getAxisName(m_token);
+
+    if (null == val)
+    {
+      error(XPATHErrorResources.ER_ILLEGAL_AXIS_NAME,
+            new Object[]{ m_token });  //"illegal axis name: "+m_token);
+    }
+
+    int axesType = ((Integer) val).intValue();
+
+    appendOp(2, axesType);
+
+    return axesType;
+  }
+
+  /**
+   *
+   * NodeTest    ::=    WildcardName
+   * | NodeType '(' ')'
+   * | 'processing-instruction' '(' Literal ')'
+   *
+   * @param axesType FROM_XXX axes type, found in {@link com.sun.org.apache.xpath.internal.compiler.Keywords}.
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  protected void NodeTest(int axesType) throws javax.xml.transform.TransformerException
+  {
+
+    if (lookahead('(', 1))
+    {
+      Object nodeTestOp = Keywords.getNodeType(m_token);
+
+      if (null == nodeTestOp)
+      {
+        error(XPATHErrorResources.ER_UNKNOWN_NODETYPE,
+              new Object[]{ m_token });  //"Unknown nodetype: "+m_token);
+      }
+      else
+      {
+        nextToken();
+
+        int nt = ((Integer) nodeTestOp).intValue();
+
+        m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), nt);
+        m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+
+        consumeExpected('(');
+
+        if (OpCodes.NODETYPE_PI == nt)
+        {
+          if (!tokenIs(')'))
+          {
+            Literal();
+          }
+        }
+
+        consumeExpected(')');
+      }
+    }
+    else
+    {
+
+      // Assume name of attribute or element.
+      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.NODENAME);
+      m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+
+      if (lookahead(':', 1))
+      {
+        if (tokenIs('*'))
+        {
+          m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ELEMWILDCARD);
+        }
+        else
+        {
+          m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
+
+          // Minimalist check for an NCName - just check first character
+          // to distinguish from other possible tokens
+          if (!Character.isLetter(m_tokenChar) && !tokenIs('_'))
+          {
+            // "Node test that matches either NCName:* or QName was expected."
+            error(XPATHErrorResources.ER_EXPECTED_NODE_TEST, null);
+          }
+        }
+
+        nextToken();
+        consumeExpected(':');
+      }
+      else
+      {
+        m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.EMPTY);
+      }
+
+      m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+
+      if (tokenIs('*'))
+      {
+        m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ELEMWILDCARD);
+      }
+      else
+      {
+        m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
+
+        // Minimalist check for an NCName - just check first character
+        // to distinguish from other possible tokens
+        if (!Character.isLetter(m_tokenChar) && !tokenIs('_'))
+        {
+          // "Node test that matches either NCName:* or QName was expected."
+          error(XPATHErrorResources.ER_EXPECTED_NODE_TEST, null);
+        }
+      }
+
+      m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+
+      nextToken();
+    }
+  }
+
+  /**
+   *
+   * Predicate ::= '[' PredicateExpr ']'
+   *
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  protected void Predicate() throws javax.xml.transform.TransformerException
+  {
+
+    if (tokenIs('['))
+    {
+      nextToken();
+      PredicateExpr();
+      consumeExpected(']');
+    }
+  }
+
+  /**
+   *
+   * PredicateExpr ::= Expr
+   *
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  protected void PredicateExpr() throws javax.xml.transform.TransformerException
+  {
+
+    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+    appendOp(2, OpCodes.OP_PREDICATE);
+    Expr();
+
+    // Terminate for safety.
+    m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
+    m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+  }
+
+  /**
+   * QName ::=  (Prefix ':')? LocalPart
+   * Prefix ::=  NCName
+   * LocalPart ::=  NCName
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  protected void QName() throws javax.xml.transform.TransformerException
+  {
+    // Namespace
+    if(lookahead(':', 1))
+    {
+      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
+      m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+
+      nextToken();
+      consumeExpected(':');
+    }
+    else
+    {
+      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.EMPTY);
+      m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+    }
+
+    // Local name
+    m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
+    m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+
+    nextToken();
+  }
+
+  /**
+   * NCName ::=  (Letter | '_') (NCNameChar)
+   * NCNameChar ::=  Letter | Digit | '.' | '-' | '_' | CombiningChar | Extender
+   */
+  protected void NCName()
+  {
+
+    m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
+    m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+
+    nextToken();
+  }
+
+  /**
+   * The value of the Literal is the sequence of characters inside
+   * the " or ' characters>.
+   *
+   * Literal  ::=  '"' [^"]* '"'
+   * | "'" [^']* "'"
+   *
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  protected void Literal() throws javax.xml.transform.TransformerException
+  {
+
+    int last = m_token.length() - 1;
+    char c0 = m_tokenChar;
+    char cX = m_token.charAt(last);
+
+    if (((c0 == '\"') && (cX == '\"')) || ((c0 == '\'') && (cX == '\'')))
+    {
+
+      // Mutate the token to remove the quotes and have the XString object
+      // already made.
+      int tokenQueuePos = m_queueMark - 1;
+
+      m_ops.m_tokenQueue.setElementAt(null,tokenQueuePos);
+
+      Object obj = new XString(m_token.substring(1, last));
+
+      m_ops.m_tokenQueue.setElementAt(obj,tokenQueuePos);
+
+      // lit = m_token.substring(1, last);
+      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), tokenQueuePos);
+      m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+
+      nextToken();
+    }
+    else
+    {
+      error(XPATHErrorResources.ER_PATTERN_LITERAL_NEEDS_BE_QUOTED,
+            new Object[]{ m_token });  //"Pattern literal ("+m_token+") needs to be quoted!");
+    }
+  }
+
+  /**
+   *
+   * Number ::= [0-9]+('.'[0-9]+)? | '.'[0-9]+
+   *
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  protected void Number() throws javax.xml.transform.TransformerException
+  {
+
+    if (null != m_token)
+    {
+
+      // Mutate the token to remove the quotes and have the XNumber object
+      // already made.
+      double num;
+
+      try
+      {
+        // XPath 1.0 does not support number in exp notation
+        if ((m_token.indexOf('e') > -1)||(m_token.indexOf('E') > -1))
+                throw new NumberFormatException();
+        num = Double.valueOf(m_token).doubleValue();
+      }
+      catch (NumberFormatException nfe)
+      {
+        num = 0.0;  // to shut up compiler.
+
+        error(XPATHErrorResources.ER_COULDNOT_BE_FORMATTED_TO_NUMBER,
+              new Object[]{ m_token });  //m_token+" could not be formatted to a number!");
+      }
+
+      m_ops.m_tokenQueue.setElementAt(new XNumber(num),m_queueMark - 1);
+      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
+      m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+
+      nextToken();
+    }
+  }
+
+  // ============= PATTERN FUNCTIONS =================
+
+  /**
+   *
+   * Pattern  ::=  LocationPathPattern
+   * | Pattern '|' LocationPathPattern
+   *
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  protected void Pattern() throws javax.xml.transform.TransformerException
+  {
+
+    while (true)
+    {
+      LocationPathPattern();
+
+      if (tokenIs('|'))
+      {
+        nextToken();
+      }
+      else
+      {
+        break;
+      }
+    }
+  }
+
+  /**
+   *
+   *
+   * LocationPathPattern  ::=  '/' RelativePathPattern?
+   * | IdKeyPattern (('/' | '//') RelativePathPattern)?
+   * | '//'? RelativePathPattern
+   *
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  protected void LocationPathPattern() throws javax.xml.transform.TransformerException
+  {
+
+    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+    final int RELATIVE_PATH_NOT_PERMITTED = 0;
+    final int RELATIVE_PATH_PERMITTED     = 1;
+    final int RELATIVE_PATH_REQUIRED      = 2;
+
+    int relativePathStatus = RELATIVE_PATH_NOT_PERMITTED;
+
+    appendOp(2, OpCodes.OP_LOCATIONPATHPATTERN);
+
+    if (lookahead('(', 1)
+            && (tokenIs(Keywords.FUNC_ID_STRING)
+                || tokenIs(Keywords.FUNC_KEY_STRING)))
+    {
+      IdKeyPattern();
+
+      if (tokenIs('/'))
+      {
+        nextToken();
+
+        if (tokenIs('/'))
+        {
+          appendOp(4, OpCodes.MATCH_ANY_ANCESTOR);
+
+          nextToken();
+        }
+        else
+        {
+          appendOp(4, OpCodes.MATCH_IMMEDIATE_ANCESTOR);
+        }
+
+        // Tell how long the step is without the predicate
+        m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2, 4);
+        m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_FUNCTEST);
+
+        relativePathStatus = RELATIVE_PATH_REQUIRED;
+      }
+    }
+    else if (tokenIs('/'))
+    {
+      if (lookahead('/', 1))
+      {
+        appendOp(4, OpCodes.MATCH_ANY_ANCESTOR);
+
+        // Added this to fix bug reported by Myriam for match="//x/a"
+        // patterns.  If you don't do this, the 'x' step will think it's part
+        // of a '//' pattern, and so will cause 'a' to be matched when it has
+        // any ancestor that is 'x'.
+        nextToken();
+
+        relativePathStatus = RELATIVE_PATH_REQUIRED;
+      }
+      else
+      {
+        appendOp(4, OpCodes.FROM_ROOT);
+
+        relativePathStatus = RELATIVE_PATH_PERMITTED;
+      }
+
+
+      // Tell how long the step is without the predicate
+      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2, 4);
+      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_ROOT);
+
+      nextToken();
+    }
+    else
+    {
+      relativePathStatus = RELATIVE_PATH_REQUIRED;
+    }
+
+    if (relativePathStatus != RELATIVE_PATH_NOT_PERMITTED)
+    {
+      if (!tokenIs('|') && (null != m_token))
+      {
+        RelativePathPattern();
+      }
+      else if (relativePathStatus == RELATIVE_PATH_REQUIRED)
+      {
+        // "A relative path pattern was expected."
+        error(XPATHErrorResources.ER_EXPECTED_REL_PATH_PATTERN, null);
+      }
+    }
+
+    // Terminate for safety.
+    m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
+    m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+  }
+
+  /**
+   *
+   * IdKeyPattern  ::=  'id' '(' Literal ')'
+   * | 'key' '(' Literal ',' Literal ')'
+   * (Also handle doc())
+   *
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  protected void IdKeyPattern() throws javax.xml.transform.TransformerException
+  {
+    FunctionCall();
+  }
+
+  /**
+   *
+   * RelativePathPattern  ::=  StepPattern
+   * | RelativePathPattern '/' StepPattern
+   * | RelativePathPattern '//' StepPattern
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  protected void RelativePathPattern()
+              throws javax.xml.transform.TransformerException
+  {
+
+    // Caller will have consumed any '/' or '//' preceding the
+    // RelativePathPattern, so let StepPattern know it can't begin with a '/'
+    boolean trailingSlashConsumed = StepPattern(false);
+
+    while (tokenIs('/'))
+    {
+      nextToken();
+
+      // StepPattern() may consume first slash of pair in "a//b" while
+      // processing StepPattern "a".  On next iteration, let StepPattern know
+      // that happened, so it doesn't match ill-formed patterns like "a///b".
+      trailingSlashConsumed = StepPattern(!trailingSlashConsumed);
+    }
+  }
+
+  /**
+   *
+   * StepPattern  ::=  AbbreviatedNodeTestStep
+   *
+   * @param isLeadingSlashPermitted a boolean indicating whether a slash can
+   *        appear at the start of this step
+   *
+   * @return boolean indicating whether a slash following the step was consumed
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  protected boolean StepPattern(boolean isLeadingSlashPermitted)
+            throws javax.xml.transform.TransformerException
+  {
+    return AbbreviatedNodeTestStep(isLeadingSlashPermitted);
+  }
+
+  /**
+   *
+   * AbbreviatedNodeTestStep    ::=    '@'? NodeTest Predicate
+   *
+   * @param isLeadingSlashPermitted a boolean indicating whether a slash can
+   *        appear at the start of this step
+   *
+   * @return boolean indicating whether a slash following the step was consumed
+   *
+   * @throws javax.xml.transform.TransformerException
+   */
+  protected boolean AbbreviatedNodeTestStep(boolean isLeadingSlashPermitted)
+            throws javax.xml.transform.TransformerException
+  {
+
+    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+    int axesType;
+
+    // The next blocks guarantee that a MATCH_XXX will be added.
+    int matchTypePos = -1;
+
+    if (tokenIs('@'))
+    {
+      axesType = OpCodes.MATCH_ATTRIBUTE;
+
+      appendOp(2, axesType);
+      nextToken();
+    }
+    else if (this.lookahead("::", 1))
+    {
+      if (tokenIs("attribute"))
+      {
+        axesType = OpCodes.MATCH_ATTRIBUTE;
+
+        appendOp(2, axesType);
+      }
+      else if (tokenIs("child"))
+      {
+        matchTypePos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+        axesType = OpCodes.MATCH_IMMEDIATE_ANCESTOR;
+
+        appendOp(2, axesType);
+      }
+      else
+      {
+        axesType = -1;
+
+        this.error(XPATHErrorResources.ER_AXES_NOT_ALLOWED,
+                   new Object[]{ this.m_token });
+      }
+
+      nextToken();
+      nextToken();
+    }
+    else if (tokenIs('/'))
+    {
+      if (!isLeadingSlashPermitted)
+      {
+        // "A step was expected in the pattern, but '/' was encountered."
+        error(XPATHErrorResources.ER_EXPECTED_STEP_PATTERN, null);
+      }
+      axesType = OpCodes.MATCH_ANY_ANCESTOR;
+
+      appendOp(2, axesType);
+      nextToken();
+    }
+    else
+    {
+      matchTypePos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+      axesType = OpCodes.MATCH_IMMEDIATE_ANCESTOR;
+
+      appendOp(2, axesType);
+    }
+
+    // Make room for telling how long the step is without the predicate
+    m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+
+    NodeTest(axesType);
+
+    // Tell how long the step is without the predicate
+    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1,
+      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+
+    while (tokenIs('['))
+    {
+      Predicate();
+    }
+
+    boolean trailingSlashConsumed;
+
+    // For "a//b", where "a" is current step, we need to mark operation of
+    // current step as "MATCH_ANY_ANCESTOR".  Then we'll consume the first
+    // slash and subsequent step will be treated as a MATCH_IMMEDIATE_ANCESTOR
+    // (unless it too is followed by '//'.)
+    //
+    // %REVIEW%  Following is what happens today, but I'm not sure that's
+    // %REVIEW%  correct behaviour.  Perhaps no valid case could be constructed
+    // %REVIEW%  where it would matter?
+    //
+    // If current step is on the attribute axis (e.g., "@x//b"), we won't
+    // change the current step, and let following step be marked as
+    // MATCH_ANY_ANCESTOR on next call instead.
+    if ((matchTypePos > -1) && tokenIs('/') && lookahead('/', 1))
+    {
+      m_ops.setOp(matchTypePos, OpCodes.MATCH_ANY_ANCESTOR);
+
+      nextToken();
+
+      trailingSlashConsumed = true;
+    }
+    else
+    {
+      trailingSlashConsumed = false;
+    }
+
+    // Tell how long the entire step is.
+    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+
+    return trailingSlashConsumed;
+  }
+}