OSDN Git Service

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