Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/apache/xpath/compiler/XPathParser.java


1   /*
2    * Copyright 1999-2004 The Apache Software Foundation.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  /*
17   * $Id: XPathParser.java,v 1.28 2004/02/17 04:32:49 minchau Exp $
18   */
19  package org.apache.xpath.compiler;
20  
21  import javax.xml.transform.ErrorListener;
22  import javax.xml.transform.TransformerException;
23  
24  import org.apache.xalan.res.XSLMessages;
25  import org.apache.xml.utils.PrefixResolver;
26  import org.apache.xpath.XPathProcessorException;
27  import org.apache.xpath.objects.XNumber;
28  import org.apache.xpath.objects.XString;
29  import org.apache.xpath.res.XPATHErrorResources;
30  
31  /**
32   * Tokenizes and parses XPath expressions. This should really be named
33   * XPathParserImpl, and may be renamed in the future.
34   * @xsl.usage general
35   */
36  public class XPathParser
37  {
38    // %REVIEW% Is there a better way of doing this?
39    // Upside is minimum object churn. Downside is that we don't have a useful
40    // backtrace in the exception itself -- but we don't expect to need one.
41    static public final String CONTINUE_AFTER_FATAL_ERROR="CONTINUE_AFTER_FATAL_ERROR";
42  
43    /**
44     * The XPath to be processed.
45     */
46    private OpMap m_ops;
47  
48    /**
49     * The next token in the pattern.
50     */
51    transient String m_token;
52  
53    /**
54     * The first char in m_token, the theory being that this
55     * is an optimization because we won't have to do charAt(0) as
56     * often.
57     */
58    transient char m_tokenChar = 0;
59  
60    /**
61     * The position in the token queue is tracked by m_queueMark.
62     */
63    int m_queueMark = 0;
64  
65    /**
66     * Results from checking FilterExpr syntax
67     */
68    protected final static int FILTER_MATCH_FAILED     = 0;
69    protected final static int FILTER_MATCH_PRIMARY    = 1;
70    protected final static int FILTER_MATCH_PREDICATES = 2;
71  
72    /**
73     * The parser constructor.
74     */
75    public XPathParser(ErrorListener errorListener, javax.xml.transform.SourceLocator sourceLocator)
76    {
77      m_errorListener = errorListener;
78      m_sourceLocator = sourceLocator;
79    }
80  
81    /**
82     * The prefix resolver to map prefixes to namespaces in the OpMap.
83     */
84    PrefixResolver m_namespaceContext;
85  
86    /**
87     * Given an string, init an XPath object for selections,
88     * in order that a parse doesn't
89     * have to be done each time the expression is evaluated.
90     * 
91     * @param compiler The compiler object.
92     * @param expression A string conforming to the XPath grammar.
93     * @param namespaceContext An object that is able to resolve prefixes in
94     * the XPath to namespaces.
95     *
96     * @throws javax.xml.transform.TransformerException
97     */
98    public void initXPath(
99            Compiler compiler, String expression, PrefixResolver namespaceContext)
100             throws javax.xml.transform.TransformerException
101   {
102 
103     m_ops = compiler;
104     m_namespaceContext = namespaceContext;
105 
106     Lexer lexer = new Lexer(compiler, namespaceContext, this);
107 
108     lexer.tokenize(expression);
109 
110     m_ops.setOp(0,OpCodes.OP_XPATH);
111     m_ops.setOp(OpMap.MAPINDEX_LENGTH,2);
112     
113     
114   // Patch for Christine's gripe. She wants her errorHandler to return from
115   // a fatal error and continue trying to parse, rather than throwing an exception.
116   // Without the patch, that put us into an endless loop.
117   //
118   // %REVIEW% Is there a better way of doing this?
119   // %REVIEW% Are there any other cases which need the safety net?
120   //   (and if so do we care right now, or should we rewrite the XPath
121   //  grammar engine and can fix it at that time?)
122   try {
123 
124       nextToken();
125       Expr();
126 
127       if (null != m_token)
128       {
129         String extraTokens = "";
130 
131         while (null != m_token)
132         {
133           extraTokens += "'" + m_token + "'";
134 
135           nextToken();
136 
137           if (null != m_token)
138             extraTokens += ", ";
139         }
140 
141         error(XPATHErrorResources.ER_EXTRA_ILLEGAL_TOKENS,
142               new Object[]{ extraTokens });  //"Extra illegal tokens: "+extraTokens);
143       }
144 
145     } 
146     catch (org.apache.xpath.XPathProcessorException e)
147     {
148     if(CONTINUE_AFTER_FATAL_ERROR.equals(e.getMessage()))
149     {
150     // What I _want_ to do is null out this XPath.
151     // I doubt this has the desired effect, but I'm not sure what else to do.
152     // %REVIEW%!!!
153     initXPath(compiler, "/..",  namespaceContext);
154     }
155     else
156     throw e;
157     }
158 
159     compiler.shrink();
160   }
161 
162   /**
163    * Given an string, init an XPath object for pattern matches,
164    * in order that a parse doesn't
165    * have to be done each time the expression is evaluated.
166    * @param compiler The XPath object to be initialized.
167    * @param expression A String representing the XPath.
168    * @param namespaceContext An object that is able to resolve prefixes in
169    * the XPath to namespaces.
170    *
171    * @throws javax.xml.transform.TransformerException
172    */
173   public void initMatchPattern(
174           Compiler compiler, String expression, PrefixResolver namespaceContext)
175             throws javax.xml.transform.TransformerException
176   {
177 
178     m_ops = compiler;
179     m_namespaceContext = namespaceContext;
180 
181     Lexer lexer = new Lexer(compiler, namespaceContext, this);
182 
183     lexer.tokenize(expression);
184 
185     m_ops.setOp(0, OpCodes.OP_MATCHPATTERN);
186     m_ops.setOp(OpMap.MAPINDEX_LENGTH, 2);
187 
188     nextToken();
189     Pattern();
190 
191     if (null != m_token)
192     {
193       String extraTokens = "";
194 
195       while (null != m_token)
196       {
197         extraTokens += "'" + m_token + "'";
198 
199         nextToken();
200 
201         if (null != m_token)
202           extraTokens += ", ";
203       }
204 
205       error(XPATHErrorResources.ER_EXTRA_ILLEGAL_TOKENS,
206             new Object[]{ extraTokens });  //"Extra illegal tokens: "+extraTokens);
207     }
208 
209     // Terminate for safety.
210     m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
211     m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH)+1);
212 
213     m_ops.shrink();
214   }
215 
216   /** The error listener where syntax errors are to be sent.
217    */
218   private ErrorListener m_errorListener;
219   
220   /** The source location of the XPath. */
221   javax.xml.transform.SourceLocator m_sourceLocator;
222 
223   /**
224    * Allow an application to register an error event handler, where syntax 
225    * errors will be sent.  If the error listener is not set, syntax errors 
226    * will be sent to System.err.
227    * 
228    * @param handler Reference to error listener where syntax errors will be 
229    *                sent.
230    */
231   public void setErrorHandler(ErrorListener handler)
232   {
233     m_errorListener = handler;
234   }
235 
236   /**
237    * Return the current error listener.
238    *
239    * @return The error listener, which should not normally be null, but may be.
240    */
241   public ErrorListener getErrorListener()
242   {
243     return m_errorListener;
244   }
245 
246   /**
247    * Check whether m_token matches the target string. 
248    *
249    * @param s A string reference or null.
250    *
251    * @return If m_token is null, returns false (or true if s is also null), or 
252    * return true if the current token matches the string, else false.
253    */
254   final boolean tokenIs(String s)
255   {
256     return (m_token != null) ? (m_token.equals(s)) : (s == null);
257   }
258 
259   /**
260    * Check whether m_tokenChar==c. 
261    *
262    * @param c A character to be tested.
263    *
264    * @return If m_token is null, returns false, or return true if c matches 
265    *         the current token.
266    */
267   final boolean tokenIs(char c)
268   {
269     return (m_token != null) ? (m_tokenChar == c) : false;
270   }
271 
272   /**
273    * Look ahead of the current token in order to
274    * make a branching decision.
275    *
276    * @param c the character to be tested for.
277    * @param n number of tokens to look ahead.  Must be
278    * greater than 1.
279    *
280    * @return true if the next token matches the character argument.
281    */
282   final boolean lookahead(char c, int n)
283   {
284 
285     int pos = (m_queueMark + n);
286     boolean b;
287 
288     if ((pos <= m_ops.getTokenQueueSize()) && (pos > 0)
289             && (m_ops.getTokenQueueSize() != 0))
290     {
291       String tok = ((String) m_ops.m_tokenQueue.elementAt(pos - 1));
292 
293       b = (tok.length() == 1) ? (tok.charAt(0) == c) : false;
294     }
295     else
296     {
297       b = false;
298     }
299 
300     return b;
301   }
302 
303   /**
304    * Look behind the first character of the current token in order to
305    * make a branching decision.
306    * 
307    * @param c the character to compare it to.
308    * @param n number of tokens to look behind.  Must be
309    * greater than 1.  Note that the look behind terminates
310    * at either the beginning of the string or on a '|'
311    * character.  Because of this, this method should only
312    * be used for pattern matching.
313    *
314    * @return true if the token behind the current token matches the character 
315    *         argument.
316    */
317   private final boolean lookbehind(char c, int n)
318   {
319 
320     boolean isToken;
321     int lookBehindPos = m_queueMark - (n + 1);
322 
323     if (lookBehindPos >= 0)
324     {
325       String lookbehind = (String) m_ops.m_tokenQueue.elementAt(lookBehindPos);
326 
327       if (lookbehind.length() == 1)
328       {
329         char c0 = (lookbehind == null) ? '|' : lookbehind.charAt(0);
330 
331         isToken = (c0 == '|') ? false : (c0 == c);
332       }
333       else
334       {
335         isToken = false;
336       }
337     }
338     else
339     {
340       isToken = false;
341     }
342 
343     return isToken;
344   }
345 
346   /**
347    * look behind the current token in order to
348    * see if there is a useable token.
349    * 
350    * @param n number of tokens to look behind.  Must be
351    * greater than 1.  Note that the look behind terminates
352    * at either the beginning of the string or on a '|'
353    * character.  Because of this, this method should only
354    * be used for pattern matching.
355    * 
356    * @return true if look behind has a token, false otherwise.
357    */
358   private final boolean lookbehindHasToken(int n)
359   {
360 
361     boolean hasToken;
362 
363     if ((m_queueMark - n) > 0)
364     {
365       String lookbehind = (String) m_ops.m_tokenQueue.elementAt(m_queueMark - (n - 1));
366       char c0 = (lookbehind == null) ? '|' : lookbehind.charAt(0);
367 
368       hasToken = (c0 == '|') ? false : true;
369     }
370     else
371     {
372       hasToken = false;
373     }
374 
375     return hasToken;
376   }
377 
378   /**
379    * Look ahead of the current token in order to
380    * make a branching decision.
381    * 
382    * @param s the string to compare it to.
383    * @param n number of tokens to lookahead.  Must be
384    * greater than 1.
385    *
386    * @return true if the token behind the current token matches the string 
387    *         argument.
388    */
389   private final boolean lookahead(String s, int n)
390   {
391 
392     boolean isToken;
393 
394     if ((m_queueMark + n) <= m_ops.getTokenQueueSize())
395     {
396       String lookahead = (String) m_ops.m_tokenQueue.elementAt(m_queueMark + (n - 1));
397 
398       isToken = (lookahead != null) ? lookahead.equals(s) : (s == null);
399     }
400     else
401     {
402       isToken = (null == s);
403     }
404 
405     return isToken;
406   }
407 
408   /**
409    * Retrieve the next token from the command and
410    * store it in m_token string.
411    */
412   private final void nextToken()
413   {
414 
415     if (m_queueMark < m_ops.getTokenQueueSize())
416     {
417       m_token = (String) m_ops.m_tokenQueue.elementAt(m_queueMark++);
418       m_tokenChar = m_token.charAt(0);
419     }
420     else
421     {
422       m_token = null;
423       m_tokenChar = 0;
424     }
425   }
426 
427   /**
428    * Retrieve a token relative to the current token.
429    * 
430    * @param i Position relative to current token.
431    *
432    * @return The string at the given index, or null if the index is out 
433    *         of range.
434    */
435   private final String getTokenRelative(int i)
436   {
437 
438     String tok;
439     int relative = m_queueMark + i;
440 
441     if ((relative > 0) && (relative < m_ops.getTokenQueueSize()))
442     {
443       tok = (String) m_ops.m_tokenQueue.elementAt(relative);
444     }
445     else
446     {
447       tok = null;
448     }
449 
450     return tok;
451   }
452 
453   /**
454    * Retrieve the previous token from the command and
455    * store it in m_token string.
456    */
457   private final void prevToken()
458   {
459 
460     if (m_queueMark > 0)
461     {
462       m_queueMark--;
463 
464       m_token = (String) m_ops.m_tokenQueue.elementAt(m_queueMark);
465       m_tokenChar = m_token.charAt(0);
466     }
467     else
468     {
469       m_token = null;
470       m_tokenChar = 0;
471     }
472   }
473 
474   /**
475    * Consume an expected token, throwing an exception if it
476    * isn't there.
477    *
478    * @param expected The string to be expected.
479    *
480    * @throws javax.xml.transform.TransformerException
481    */
482   private final void consumeExpected(String expected)
483           throws javax.xml.transform.TransformerException
484   {
485 
486     if (tokenIs(expected))
487     {
488       nextToken();
489     }
490     else
491     {
492       error(XPATHErrorResources.ER_EXPECTED_BUT_FOUND, new Object[]{ expected,
493                                                                      m_token });  //"Expected "+expected+", but found: "+m_token);
494 
495     // Patch for Christina's gripe. She wants her errorHandler to return from
496     // this error and continue trying to parse, rather than throwing an exception.
497     // Without the patch, that put us into an endless loop.
498     throw new XPathProcessorException(CONTINUE_AFTER_FATAL_ERROR);
499   }
500   }
501 
502   /**
503    * Consume an expected token, throwing an exception if it
504    * isn't there.
505    *
506    * @param expected the character to be expected.
507    *
508    * @throws javax.xml.transform.TransformerException
509    */
510   private final void consumeExpected(char expected)
511           throws javax.xml.transform.TransformerException
512   {
513 
514     if (tokenIs(expected))
515     {
516       nextToken();
517     }
518     else
519     {
520       error(XPATHErrorResources.ER_EXPECTED_BUT_FOUND,
521             new Object[]{ String.valueOf(expected),
522                           m_token });  //"Expected "+expected+", but found: "+m_token);
523 
524     // Patch for Christina's gripe. She wants her errorHandler to return from
525     // this error and continue trying to parse, rather than throwing an exception.
526     // Without the patch, that put us into an endless loop.
527     throw new XPathProcessorException(CONTINUE_AFTER_FATAL_ERROR);
528     }
529   }
530 
531   /**
532    * Warn the user of a problem.
533    *
534    * @param msg An error msgkey that corresponds to one of the constants found 
535    *            in {@link org.apache.xpath.res.XPATHErrorResources}, which is 
536    *            a key for a format string.
537    * @param args An array of arguments represented in the format string, which 
538    *             may be null.
539    *
540    * @throws TransformerException if the current ErrorListoner determines to 
541    *                              throw an exception.
542    */
543   void warn(String msg, Object[] args) throws TransformerException
544   {
545 
546     String fmsg = XSLMessages.createXPATHWarning(msg, args);
547     ErrorListener ehandler = this.getErrorListener();
548 
549     if (null != ehandler)
550     {
551       // TO DO: Need to get stylesheet Locator from here.
552       ehandler.warning(new TransformerException(fmsg, m_sourceLocator));
553     }
554     else
555     {
556       // Should never happen.
557       System.err.println(fmsg);
558     }
559   }
560 
561   /**
562    * Notify the user of an assertion error, and probably throw an
563    * exception.
564    *
565    * @param b  If false, a runtime exception will be thrown.
566    * @param msg The assertion message, which should be informative.
567    * 
568    * @throws RuntimeException if the b argument is false.
569    */
570   private void assertion(boolean b, String msg)
571   {
572 
573     if (!b)
574     {
575       String fMsg = XSLMessages.createXPATHMessage(
576         XPATHErrorResources.ER_INCORRECT_PROGRAMMER_ASSERTION,
577         new Object[]{ msg });
578 
579       throw new RuntimeException(fMsg);
580     }
581   }
582 
583   /**
584    * Notify the user of an error, and probably throw an
585    * exception.
586    *
587    * @param msg An error msgkey that corresponds to one of the constants found 
588    *            in {@link org.apache.xpath.res.XPATHErrorResources}, which is 
589    *            a key for a format string.
590    * @param args An array of arguments represented in the format string, which 
591    *             may be null.
592    *
593    * @throws TransformerException if the current ErrorListoner determines to 
594    *                              throw an exception.
595    */
596   void error(String msg, Object[] args) throws TransformerException
597   {
598 
599     String fmsg = XSLMessages.createXPATHMessage(msg, args);
600     ErrorListener ehandler = this.getErrorListener();
601 
602     TransformerException te = new TransformerException(fmsg, m_sourceLocator);
603     if (null != ehandler)
604     {
605       // TO DO: Need to get stylesheet Locator from here.
606       ehandler.fatalError(te);
607     }
608     else
609     {
610       // System.err.println(fmsg);
611       throw te;
612     }
613   }
614 
615   /**
616    * Dump the remaining token queue.
617    * Thanks to Craig for this.
618    *
619    * @return A dump of the remaining token queue, which may be appended to 
620    *         an error message.
621    */
622   protected String dumpRemainingTokenQueue()
623   {
624 
625     int q = m_queueMark;
626     String returnMsg;
627 
628     if (q < m_ops.getTokenQueueSize())
629     {
630       String msg = "\n Remaining tokens: (";
631 
632       while (q < m_ops.getTokenQueueSize())
633       {
634         String t = (String) m_ops.m_tokenQueue.elementAt(q++);
635 
636         msg += (" '" + t + "'");
637       }
638 
639       returnMsg = msg + ")";
640     }
641     else
642     {
643       returnMsg = "";
644     }
645 
646     return returnMsg;
647   }
648 
649   /**
650    * Given a string, return the corresponding function token.
651    *
652    * @param key A local name of a function.
653    *
654    * @return   The function ID, which may correspond to one of the FUNC_XXX 
655    *    values found in {@link org.apache.xpath.compiler.FunctionTable}, but may 
656    *    be a value installed by an external module.
657    */
658   final int getFunctionToken(String key)
659   {
660 
661     int tok;
662 
663     try
664     {
665       tok = ((Integer) (Keywords.m_functions.get(key))).intValue();
666     }
667     catch (NullPointerException npe)
668     {
669       tok = -1;
670     }
671     catch (ClassCastException cce)
672     {
673       tok = -1;
674     }
675 
676     return tok;
677   }
678 
679   /**
680    * Insert room for operation.  This will NOT set
681    * the length value of the operation, but will update
682    * the length value for the total expression.
683    *
684    * @param pos The position where the op is to be inserted.
685    * @param length The length of the operation space in the op map.
686    * @param op The op code to the inserted.
687    */
688   void insertOp(int pos, int length, int op)
689   {
690 
691     int totalLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
692 
693     for (int i = totalLen - 1; i >= pos; i--)
694     {
695       m_ops.setOp(i + length, m_ops.getOp(i));
696     }
697 
698     m_ops.setOp(pos,op);
699     m_ops.setOp(OpMap.MAPINDEX_LENGTH,totalLen + length);
700   }
701 
702   /**
703    * Insert room for operation.  This WILL set
704    * the length value of the operation, and will update
705    * the length value for the total expression.
706    *
707    * @param length The length of the operation.
708    * @param op The op code to the inserted.
709    */
710   void appendOp(int length, int op)
711   {
712 
713     int totalLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
714 
715     m_ops.setOp(totalLen, op);
716     m_ops.setOp(totalLen + OpMap.MAPINDEX_LENGTH, length);
717     m_ops.setOp(OpMap.MAPINDEX_LENGTH, totalLen + length);
718   }
719 
720   // ============= EXPRESSIONS FUNCTIONS =================
721 
722   /**
723    *
724    *
725    * Expr  ::=  OrExpr
726    *
727    *
728    * @throws javax.xml.transform.TransformerException
729    */
730   protected void Expr() throws javax.xml.transform.TransformerException
731   {
732     OrExpr();
733   }
734 
735   /**
736    *
737    *
738    * OrExpr  ::=  AndExpr
739    * | OrExpr 'or' AndExpr
740    *
741    *
742    * @throws javax.xml.transform.TransformerException
743    */
744   protected void OrExpr() throws javax.xml.transform.TransformerException
745   {
746 
747     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
748 
749     AndExpr();
750 
751     if ((null != m_token) && tokenIs("or"))
752     {
753       nextToken();
754       insertOp(opPos, 2, OpCodes.OP_OR);
755       OrExpr();
756 
757       m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
758         m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
759     }
760   }
761 
762   /**
763    *
764    *
765    * AndExpr  ::=  EqualityExpr
766    * | AndExpr 'and' EqualityExpr
767    *
768    *
769    * @throws javax.xml.transform.TransformerException
770    */
771   protected void AndExpr() throws javax.xml.transform.TransformerException
772   {
773 
774     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
775 
776     EqualityExpr(-1);
777 
778     if ((null != m_token) && tokenIs("and"))
779     {
780       nextToken();
781       insertOp(opPos, 2, OpCodes.OP_AND);
782       AndExpr();
783 
784       m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
785         m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
786     }
787   }
788 
789   /**
790    *
791    * @returns an Object which is either a String, a Number, a Boolean, or a vector
792    * of nodes.
793    *
794    * EqualityExpr  ::=  RelationalExpr
795    * | EqualityExpr '=' RelationalExpr
796    *
797    *
798    * @param addPos Position where expression is to be added, or -1 for append.
799    *
800    * @return the position at the end of the equality expression.
801    *
802    * @throws javax.xml.transform.TransformerException
803    */
804   protected int EqualityExpr(int addPos) throws javax.xml.transform.TransformerException
805   {
806 
807     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
808 
809     if (-1 == addPos)
810       addPos = opPos;
811 
812     RelationalExpr(-1);
813 
814     if (null != m_token)
815     {
816       if (tokenIs('!') && lookahead('=', 1))
817       {
818         nextToken();
819         nextToken();
820         insertOp(addPos, 2, OpCodes.OP_NOTEQUALS);
821 
822         int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
823 
824         addPos = EqualityExpr(addPos);
825         m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
826           m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
827         addPos += 2;
828       }
829       else if (tokenIs('='))
830       {
831         nextToken();
832         insertOp(addPos, 2, OpCodes.OP_EQUALS);
833 
834         int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
835 
836         addPos = EqualityExpr(addPos);
837         m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
838           m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
839         addPos += 2;
840       }
841     }
842 
843     return addPos;
844   }
845 
846   /**
847    * .
848    * @returns an Object which is either a String, a Number, a Boolean, or a vector
849    * of nodes.
850    *
851    * RelationalExpr  ::=  AdditiveExpr
852    * | RelationalExpr '<' AdditiveExpr
853    * | RelationalExpr '>' AdditiveExpr
854    * | RelationalExpr '<=' AdditiveExpr
855    * | RelationalExpr '>=' AdditiveExpr
856    *
857    *
858    * @param addPos Position where expression is to be added, or -1 for append.
859    *
860    * @return the position at the end of the relational expression.
861    *
862    * @throws javax.xml.transform.TransformerException
863    */
864   protected int RelationalExpr(int addPos) throws javax.xml.transform.TransformerException
865   {
866 
867     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
868 
869     if (-1 == addPos)
870       addPos = opPos;
871 
872     AdditiveExpr(-1);
873 
874     if (null != m_token)
875     {
876       if (tokenIs('<'))
877       {
878         nextToken();
879 
880         if (tokenIs('='))
881         {
882           nextToken();
883           insertOp(addPos, 2, OpCodes.OP_LTE);
884         }
885         else
886         {
887           insertOp(addPos, 2, OpCodes.OP_LT);
888         }
889 
890         int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
891 
892         addPos = RelationalExpr(addPos);
893         m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH, 
894           m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
895         addPos += 2;
896       }
897       else if (tokenIs('>'))
898       {
899         nextToken();
900 
901         if (tokenIs('='))
902         {
903           nextToken();
904           insertOp(addPos, 2, OpCodes.OP_GTE);
905         }
906         else
907         {
908           insertOp(addPos, 2, OpCodes.OP_GT);
909         }
910 
911         int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
912 
913         addPos = RelationalExpr(addPos);
914         m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
915           m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
916         addPos += 2;
917       }
918     }
919 
920     return addPos;
921   }
922 
923   /**
924    * This has to handle construction of the operations so that they are evaluated
925    * in pre-fix order.  So, for 9+7-6, instead of |+|9|-|7|6|, this needs to be
926    * evaluated as |-|+|9|7|6|.
927    *
928    * AdditiveExpr  ::=  MultiplicativeExpr
929    * | AdditiveExpr '+' MultiplicativeExpr
930    * | AdditiveExpr '-' MultiplicativeExpr
931    *
932    *
933    * @param addPos Position where expression is to be added, or -1 for append.
934    *
935    * @return the position at the end of the equality expression.
936    *
937    * @throws javax.xml.transform.TransformerException
938    */
939   protected int AdditiveExpr(int addPos) throws javax.xml.transform.TransformerException
940   {
941 
942     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
943 
944     if (-1 == addPos)
945       addPos = opPos;
946 
947     MultiplicativeExpr(-1);
948 
949     if (null != m_token)
950     {
951       if (tokenIs('+'))
952       {
953         nextToken();
954         insertOp(addPos, 2, OpCodes.OP_PLUS);
955 
956         int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
957 
958         addPos = AdditiveExpr(addPos);
959         m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
960           m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
961         addPos += 2;
962       }
963       else if (tokenIs('-'))
964       {
965         nextToken();
966         insertOp(addPos, 2, OpCodes.OP_MINUS);
967 
968         int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
969 
970         addPos = AdditiveExpr(addPos);
971         m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH, 
972           m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
973         addPos += 2;
974       }
975     }
976 
977     return addPos;
978   }
979 
980   /**
981    * This has to handle construction of the operations so that they are evaluated
982    * in pre-fix order.  So, for 9+7-6, instead of |+|9|-|7|6|, this needs to be
983    * evaluated as |-|+|9|7|6|.
984    *
985    * MultiplicativeExpr  ::=  UnaryExpr
986    * | MultiplicativeExpr MultiplyOperator UnaryExpr
987    * | MultiplicativeExpr 'div' UnaryExpr
988    * | MultiplicativeExpr 'mod' UnaryExpr
989    * | MultiplicativeExpr 'quo' UnaryExpr
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 MultiplicativeExpr(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    UnaryExpr();
1006
1007    if (null != m_token)
1008    {
1009      if (tokenIs('*'))
1010      {
1011        nextToken();
1012        insertOp(addPos, 2, OpCodes.OP_MULT);
1013
1014        int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1015
1016        addPos = MultiplicativeExpr(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("div"))
1022      {
1023        nextToken();
1024        insertOp(addPos, 2, OpCodes.OP_DIV);
1025
1026        int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1027
1028        addPos = MultiplicativeExpr(addPos);
1029        m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1030          m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1031        addPos += 2;
1032      }
1033      else if (tokenIs("mod"))
1034      {
1035        nextToken();
1036        insertOp(addPos, 2, OpCodes.OP_MOD);
1037
1038        int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1039
1040        addPos = MultiplicativeExpr(addPos);
1041        m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1042          m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1043        addPos += 2;
1044      }
1045      else if (tokenIs("quo"))
1046      {
1047        nextToken();
1048        insertOp(addPos, 2, OpCodes.OP_QUO);
1049
1050        int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1051
1052        addPos = MultiplicativeExpr(addPos);
1053        m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1054          m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1055        addPos += 2;
1056      }
1057    }
1058
1059    return addPos;
1060  }
1061
1062  /**
1063   *
1064   * UnaryExpr  ::=  UnionExpr
1065   * | '-' UnaryExpr
1066   *
1067   *
1068   * @throws javax.xml.transform.TransformerException
1069   */
1070  protected void UnaryExpr() throws javax.xml.transform.TransformerException
1071  {
1072
1073    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1074    boolean isNeg = false;
1075
1076    if (m_tokenChar == '-')
1077    {
1078      nextToken();
1079      appendOp(2, OpCodes.OP_NEG);
1080
1081      isNeg = true;
1082    }
1083
1084    UnionExpr();
1085
1086    if (isNeg)
1087      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1088        m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1089  }
1090
1091  /**
1092   *
1093   * StringExpr  ::=  Expr
1094   *
1095   *
1096   * @throws javax.xml.transform.TransformerException
1097   */
1098  protected void StringExpr() throws javax.xml.transform.TransformerException
1099  {
1100
1101    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1102
1103    appendOp(2, OpCodes.OP_STRING);
1104    Expr();
1105
1106    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1107      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1108  }
1109
1110  /**
1111   *
1112   *
1113   * StringExpr  ::=  Expr
1114   *
1115   *
1116   * @throws javax.xml.transform.TransformerException
1117   */
1118  protected void BooleanExpr() throws javax.xml.transform.TransformerException
1119  {
1120
1121    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1122
1123    appendOp(2, OpCodes.OP_BOOL);
1124    Expr();
1125
1126    int opLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos;
1127
1128    if (opLen == 2)
1129    {
1130      error(XPATHErrorResources.ER_BOOLEAN_ARG_NO_LONGER_OPTIONAL, null);  //"boolean(...) argument is no longer optional with 19990709 XPath draft.");
1131    }
1132
1133    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, opLen);
1134  }
1135
1136  /**
1137   *
1138   *
1139   * NumberExpr  ::=  Expr
1140   *
1141   *
1142   * @throws javax.xml.transform.TransformerException
1143   */
1144  protected void NumberExpr() throws javax.xml.transform.TransformerException
1145  {
1146
1147    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1148
1149    appendOp(2, OpCodes.OP_NUMBER);
1150    Expr();
1151
1152    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1153      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1154  }
1155
1156  /**
1157   * The context of the right hand side expressions is the context of the
1158   * left hand side expression. The results of the right hand side expressions
1159   * are node sets. The result of the left hand side UnionExpr is the union
1160   * of the results of the right hand side expressions.
1161   *
1162   *
1163   * UnionExpr    ::=    PathExpr
1164   * | UnionExpr '|' PathExpr
1165   *
1166   *
1167   * @throws javax.xml.transform.TransformerException
1168   */
1169  protected void UnionExpr() throws javax.xml.transform.TransformerException
1170  {
1171
1172    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1173    boolean continueOrLoop = true;
1174    boolean foundUnion = false;
1175
1176    do
1177    {
1178      PathExpr();
1179
1180      if (tokenIs('|'))
1181      {
1182        if (false == foundUnion)
1183        {
1184          foundUnion = true;
1185
1186          insertOp(opPos, 2, OpCodes.OP_UNION);
1187        }
1188
1189        nextToken();
1190      }
1191      else
1192      {
1193        break;
1194      }
1195
1196      // this.m_testForDocOrder = true;
1197    }
1198    while (continueOrLoop);
1199
1200    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1201          m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1202  }
1203
1204  /**
1205   * PathExpr  ::=  LocationPath
1206   * | FilterExpr
1207   * | FilterExpr '/' RelativeLocationPath
1208   * | FilterExpr '//' RelativeLocationPath
1209   *
1210   * @throws XSLProcessorException thrown if the active ProblemListener and XPathContext decide
1211   * the error condition is severe enough to halt processing.
1212   *
1213   * @throws javax.xml.transform.TransformerException
1214   */
1215  protected void PathExpr() throws javax.xml.transform.TransformerException
1216  {
1217
1218    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1219
1220    int filterExprMatch = FilterExpr();
1221
1222    if (filterExprMatch != FILTER_MATCH_FAILED)
1223    {
1224      // If FilterExpr had Predicates, a OP_LOCATIONPATH opcode would already
1225      // have been inserted.
1226      boolean locationPathStarted = (filterExprMatch==FILTER_MATCH_PREDICATES);
1227
1228      if (tokenIs('/'))
1229      {
1230        nextToken();
1231
1232        if (!locationPathStarted)
1233        {
1234          // int locationPathOpPos = opPos;
1235          insertOp(opPos, 2, OpCodes.OP_LOCATIONPATH);
1236
1237          locationPathStarted = true;
1238        }
1239
1240        if (!RelativeLocationPath())
1241        {
1242          // "Relative location path expected following '/' or '//'"
1243          error(XPATHErrorResources.ER_EXPECTED_REL_LOC_PATH, null);
1244        }
1245
1246      }
1247
1248      // Terminate for safety.
1249      if (locationPathStarted)
1250      {
1251        m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
1252        m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1253        m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1254          m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1255      }
1256    }
1257    else
1258    {
1259      LocationPath();
1260    }
1261  }
1262
1263  /**
1264   *
1265   *
1266   * FilterExpr  ::=  PrimaryExpr
1267   * | FilterExpr Predicate
1268   *
1269   * @throws XSLProcessorException thrown if the active ProblemListener and XPathContext decide
1270   * the error condition is severe enough to halt processing.
1271   *
1272   * @return  FILTER_MATCH_PREDICATES, if this method successfully matched a
1273   *          FilterExpr with one or more Predicates;
1274   *          FILTER_MATCH_PRIMARY, if this method successfully matched a
1275   *          FilterExpr that was just a PrimaryExpr; or
1276   *          FILTER_MATCH_FAILED, if this method did not match a FilterExpr
1277   *
1278   * @throws javax.xml.transform.TransformerException
1279   */
1280  protected int FilterExpr() throws javax.xml.transform.TransformerException
1281  {
1282
1283    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1284
1285    int filterMatch;
1286
1287    if (PrimaryExpr())
1288    {
1289      if (tokenIs('['))
1290      {
1291
1292        // int locationPathOpPos = opPos;
1293        insertOp(opPos, 2, OpCodes.OP_LOCATIONPATH);
1294
1295        while (tokenIs('['))
1296        {
1297          Predicate();
1298        }
1299
1300        filterMatch = FILTER_MATCH_PREDICATES;
1301      }
1302      else
1303      {
1304        filterMatch = FILTER_MATCH_PRIMARY;
1305      }
1306    }
1307    else
1308    {
1309      filterMatch = FILTER_MATCH_FAILED;
1310    }
1311
1312    return filterMatch;
1313
1314    /*
1315     * if(tokenIs('['))
1316     * {
1317     *   Predicate();
1318     *   m_ops.m_opMap[opPos + OpMap.MAPINDEX_LENGTH] = m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] - opPos;
1319     * }
1320     */
1321  }
1322
1323  /**
1324   *
1325   * PrimaryExpr  ::=  VariableReference
1326   * | '(' Expr ')'
1327   * | Literal
1328   * | Number
1329   * | FunctionCall
1330   *
1331   * @return true if this method successfully matched a PrimaryExpr
1332   *
1333   * @throws javax.xml.transform.TransformerException
1334   *
1335   */
1336  protected boolean PrimaryExpr() throws javax.xml.transform.TransformerException
1337  {
1338
1339    boolean matchFound;
1340    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1341
1342    if ((m_tokenChar == '\'') || (m_tokenChar == '"'))
1343    {
1344      appendOp(2, OpCodes.OP_LITERAL);
1345      Literal();
1346
1347      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, 
1348        m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1349
1350      matchFound = true;
1351    }
1352    else if (m_tokenChar == '$')
1353    {
1354      nextToken();  // consume '$'
1355      appendOp(2, OpCodes.OP_VARIABLE);
1356      QName();
1357      
1358      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1359        m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1360
1361      matchFound = true;
1362    }
1363    else if (m_tokenChar == '(')
1364    {
1365      nextToken();
1366      appendOp(2, OpCodes.OP_GROUP);
1367      Expr();
1368      consumeExpected(')');
1369
1370      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1371        m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1372
1373      matchFound = true;
1374    }
1375    else if ((null != m_token) && ((('.' == m_tokenChar) && (m_token.length() > 1) && Character.isDigit(
1376            m_token.charAt(1))) || Character.isDigit(m_tokenChar)))
1377    {
1378      appendOp(2, OpCodes.OP_NUMBERLIT);
1379      Number();
1380
1381      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1382        m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1383
1384      matchFound = true;
1385    }
1386    else if (lookahead('(', 1) || (lookahead(':', 1) && lookahead('(', 3)))
1387    {
1388      matchFound = FunctionCall();
1389    }
1390    else
1391    {
1392      matchFound = false;
1393    }
1394
1395    return matchFound;
1396  }
1397
1398  /**
1399   *
1400   * Argument    ::=    Expr
1401   *
1402   *
1403   * @throws javax.xml.transform.TransformerException
1404   */
1405  protected void Argument() throws javax.xml.transform.TransformerException
1406  {
1407
1408    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1409
1410    appendOp(2, OpCodes.OP_ARGUMENT);
1411    Expr();
1412
1413    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1414      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1415  }
1416
1417  /**
1418   *
1419   * FunctionCall    ::=    FunctionName '(' ( Argument ( ',' Argument)*)? ')'
1420   *
1421   * @return true if, and only if, a FunctionCall was matched
1422   *
1423   * @throws javax.xml.transform.TransformerException
1424   */
1425  protected boolean FunctionCall() throws javax.xml.transform.TransformerException
1426  {
1427
1428    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1429
1430    if (lookahead(':', 1))
1431    {
1432      appendOp(4, OpCodes.OP_EXTFUNCTION);
1433
1434      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1, m_queueMark - 1);
1435
1436      nextToken();
1437      consumeExpected(':');
1438
1439      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 2, m_queueMark - 1);
1440
1441      nextToken();
1442    }
1443    else
1444    {
1445      int funcTok = getFunctionToken(m_token);
1446
1447      if (-1 == funcTok)
1448      {
1449        error(XPATHErrorResources.ER_COULDNOT_FIND_FUNCTION,
1450              new Object[]{ m_token });  //"Could not find function: "+m_token+"()");
1451      }
1452
1453      switch (funcTok)
1454      {
1455      case OpCodes.NODETYPE_PI :
1456      case OpCodes.NODETYPE_COMMENT :
1457      case OpCodes.NODETYPE_TEXT :
1458      case OpCodes.NODETYPE_NODE :
1459        // Node type tests look like function calls, but they're not
1460        return false;
1461      default :
1462        appendOp(3, OpCodes.OP_FUNCTION);
1463
1464        m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1, funcTok);
1465      }
1466
1467      nextToken();
1468    }
1469
1470    consumeExpected('(');
1471
1472    while (!tokenIs(')') && m_token != null)
1473    {
1474      if (tokenIs(','))
1475      {
1476        error(XPATHErrorResources.ER_FOUND_COMMA_BUT_NO_PRECEDING_ARG, null);  //"Found ',' but no preceding argument!");
1477      }
1478
1479      Argument();
1480
1481      if (!tokenIs(')'))
1482      {
1483        consumeExpected(',');
1484
1485        if (tokenIs(')'))
1486        {
1487          error(XPATHErrorResources.ER_FOUND_COMMA_BUT_NO_FOLLOWING_ARG,
1488                null);  //"Found ',' but no following argument!");
1489        }
1490      }
1491    }
1492
1493    consumeExpected(')');
1494
1495    // Terminate for safety.
1496    m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
1497    m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1498    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, 
1499      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1500
1501    return true;
1502  }
1503
1504  // ============= GRAMMAR FUNCTIONS =================
1505
1506  /**
1507   *
1508   * LocationPath ::= RelativeLocationPath
1509   * | AbsoluteLocationPath
1510   *
1511   *
1512   * @throws javax.xml.transform.TransformerException
1513   */
1514  protected void LocationPath() throws javax.xml.transform.TransformerException
1515  {
1516
1517    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1518
1519    // int locationPathOpPos = opPos;
1520    appendOp(2, OpCodes.OP_LOCATIONPATH);
1521
1522    boolean seenSlash = tokenIs('/');
1523
1524    if (seenSlash)
1525    {
1526      appendOp(4, OpCodes.FROM_ROOT);
1527
1528      // Tell how long the step is without the predicate
1529      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2, 4);
1530      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_ROOT);
1531
1532      nextToken();
1533    }
1534
1535    if (m_token != null)
1536    {
1537      if (!RelativeLocationPath() && !seenSlash)
1538      {
1539        // Neither a '/' nor a RelativeLocationPath - i.e., matched nothing
1540        // "Location path expected, but found "+m_token+" was encountered."
1541        error(XPATHErrorResources.ER_EXPECTED_LOC_PATH, 
1542              new Object [] {m_token});
1543      }
1544    }
1545
1546    // Terminate for safety.
1547    m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
1548    m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1549    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1550      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1551  }
1552
1553  /**
1554   *
1555   * RelativeLocationPath ::= Step
1556   * | RelativeLocationPath '/' Step
1557   * | AbbreviatedRelativeLocationPath
1558   *
1559   * @returns true if, and only if, a RelativeLocationPath was matched
1560   *
1561   * @throws javax.xml.transform.TransformerException
1562   */
1563  protected boolean RelativeLocationPath()
1564               throws javax.xml.transform.TransformerException
1565  {
1566    if (!Step())
1567    {
1568      return false;
1569    }
1570
1571    while (tokenIs('/'))
1572    {
1573      nextToken();
1574
1575      if (!Step())
1576      {
1577        // RelativeLocationPath can't end with a trailing '/'
1578        // "Location step expected following '/' or '//'"
1579        error(XPATHErrorResources.ER_EXPECTED_LOC_STEP, null);
1580      }
1581    }
1582
1583    return true;
1584  }
1585
1586  /**
1587   *
1588   * Step    ::=    Basis Predicate
1589   * | AbbreviatedStep
1590   *
1591   * @returns false if step was empty (or only a '/'); true, otherwise
1592   *
1593   * @throws javax.xml.transform.TransformerException
1594   */
1595  protected boolean Step() throws javax.xml.transform.TransformerException
1596  {
1597    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1598
1599    boolean doubleSlash = tokenIs('/');
1600
1601    // At most a single '/' before each Step is consumed by caller; if the
1602    // first thing is a '/', that means we had '//' and the Step must not
1603    // be empty.
1604    if (doubleSlash)
1605    {
1606      nextToken();
1607
1608      appendOp(2, OpCodes.FROM_DESCENDANTS_OR_SELF);
1609
1610      // Have to fix up for patterns such as '//@foo' or '//attribute::foo',
1611      // which translate to 'descendant-or-self::node()/attribute::foo'.
1612      // notice I leave the '/' on the queue, so the next will be processed
1613      // by a regular step pattern.
1614
1615      // Make room for telling how long the step is without the predicate
1616      m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1617      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.NODETYPE_NODE);
1618      m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1619
1620      // Tell how long the step is without the predicate
1621      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1,
1622          m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1623
1624      // Tell how long the step is with the predicate
1625      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1626          m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1627
1628      opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1629    }
1630
1631    if (tokenIs("."))
1632    {
1633      nextToken();
1634
1635      if (tokenIs('['))
1636      {
1637        error(XPATHErrorResources.ER_PREDICATE_ILLEGAL_SYNTAX, null);  //"'..[predicate]' or '.[predicate]' is illegal syntax.  Use 'self::node()[predicate]' instead.");
1638      }
1639
1640      appendOp(4, OpCodes.FROM_SELF);
1641
1642      // Tell how long the step is without the predicate
1643      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2,4);
1644      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_NODE);
1645    }
1646    else if (tokenIs(".."))
1647    {
1648      nextToken();
1649      appendOp(4, OpCodes.FROM_PARENT);
1650
1651      // Tell how long the step is without the predicate
1652      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2,4);
1653      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_NODE);
1654    }
1655
1656    // There is probably a better way to test for this 
1657    // transition... but it gets real hairy if you try 
1658    // to do it in basis().
1659    else if (tokenIs('*') || tokenIs('@') || tokenIs('_')
1660             || (m_token!= null && Character.isLetter(m_token.charAt(0))))
1661    {
1662      Basis();
1663
1664      while (tokenIs('['))
1665      {
1666        Predicate();
1667      }
1668
1669      // Tell how long the entire step is.
1670      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1671        m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos); 
1672    }
1673    else
1674    {
1675      // No Step matched - that's an error if previous thing was a '//'
1676      if (doubleSlash)
1677      {
1678        // "Location step expected following '/' or '//'"
1679        error(XPATHErrorResources.ER_EXPECTED_LOC_STEP, null);
1680      }
1681
1682      return false;
1683    }
1684
1685    return true;
1686  }
1687
1688  /**
1689   *
1690   * Basis    ::=    AxisName '::' NodeTest
1691   * | AbbreviatedBasis
1692   *
1693   * @throws javax.xml.transform.TransformerException
1