improved/refactored php syntax parser
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpeclipse / phpeditor / php / PHPPartitionScanner.java
1 /**********************************************************************
2 Copyright (c) 2000, 2002 IBM Corp. and others.
3 All rights reserved. This program and the accompanying materials
4 are made available under the terms of the Common Public License v1.0
5 which accompanies this distribution, and is available at
6 http://www.eclipse.org/legal/cpl-v10.html
7
8 Contributors:
9     IBM Corporation - Initial implementation
10     Klaus Hartlage - www.eclipseproject.de
11 **********************************************************************/
12 package net.sourceforge.phpeclipse.phpeditor.php;
13
14 import java.io.CharArrayWriter;
15 import java.util.ArrayList;
16 import java.util.List;
17
18 import org.eclipse.jface.text.rules.ICharacterScanner;
19 import org.eclipse.jface.text.rules.IPredicateRule;
20 import org.eclipse.jface.text.rules.IToken;
21 import org.eclipse.jface.text.rules.IWordDetector;
22 import org.eclipse.jface.text.rules.MultiLineRule;
23 import org.eclipse.jface.text.rules.RuleBasedPartitionScanner;
24 import org.eclipse.jface.text.rules.Token;
25 import org.eclipse.jface.text.rules.WordRule;
26
27 /**
28  * This scanner recognizes the JavaDoc comments and Java multi line comments.
29  */
30 public class PHPPartitionScanner extends RuleBasedPartitionScanner {
31
32   private final static String SKIP = "__skip"; //$NON-NLS-1$
33   public final static String HTML_MULTILINE_COMMENT = "__html_multiline_comment"; //$NON-NLS-1$
34   //    public final static String JAVA_DOC= "__java_javadoc"; //$NON-NLS-1$
35   public final static String PHP = "__php";
36   //  public final static String HTML = "__html";
37
38   public final static IToken php = new Token(PHP);
39   //  public final static IToken html = new Token(HTML);
40   public final static IToken comment = new Token(HTML_MULTILINE_COMMENT);
41
42   protected final static char[] php0EndSequence = { '<', '?' };
43   protected final static char[] php1EndSequence = { '<', '?', 'p', 'h', 'p' };
44   protected final static char[] php2EndSequence = { '<', '?', 'P', 'H', 'P' };
45
46   private StringBuffer test;
47
48   public class PHPMultiLineRule extends MultiLineRule {
49
50     public PHPMultiLineRule(String startSequence, String endSequence, IToken token) {
51       super(startSequence, endSequence, token);
52     }
53
54     public PHPMultiLineRule(String startSequence, String endSequence, IToken token, char escapeCharacter) {
55       super(startSequence, endSequence, token, escapeCharacter);
56     }
57
58     protected boolean endSequenceDetected(ICharacterScanner scanner) {
59       int c;
60       int c2;
61
62       boolean lineCommentMode = false;
63       boolean multiLineCommentMode = false;
64       boolean stringMode = false;
65
66       char[][] delimiters = scanner.getLegalLineDelimiters();
67       while ((c = scanner.read()) != ICharacterScanner.EOF) {
68         if (c == '#') {
69           // read until end of line
70           while ((c = scanner.read()) != ICharacterScanner.EOF) {
71             if (fEndSequence.length > 0 && c == fEndSequence[0]) {
72               // Check if the specified end sequence has been found.
73               if (sequenceDetected(scanner, fEndSequence, true))
74                 return true;
75             } else if (c == '\n') {
76               break;
77             }
78           }
79           continue;
80         } else if (c == '/' && (c = scanner.read()) != ICharacterScanner.EOF) {
81           if (c == '/') {
82             // read until end of line
83             while ((c = scanner.read()) != ICharacterScanner.EOF) {
84               if (fEndSequence.length > 0 && c == fEndSequence[0]) {
85                 // Check if the specified end sequence has been found.
86                 if (sequenceDetected(scanner, fEndSequence, true))
87                   return true;
88               } else if (c == '\n') {
89                 break;
90               }
91             }
92             continue;
93           } else if (c == '*') {
94             // multi-line comment
95             while ((c = scanner.read()) != ICharacterScanner.EOF) {
96               if (c == '*' && (c = scanner.read()) != ICharacterScanner.EOF) {
97                 if (c == '/') {
98                   break;
99                 }
100                 scanner.unread();
101               }
102             }
103
104             continue;
105           } else {
106             scanner.unread();
107           }
108         } else if (c == '"') {
109           // string mode
110           while ((c = scanner.read()) != ICharacterScanner.EOF) {
111             if (c == '\\') {
112               c = scanner.read();
113             } else if (c == '"') {
114               break;
115             }
116           }
117           continue;
118         } else if (c == '\'') {
119           // string mode
120           while ((c = scanner.read()) != ICharacterScanner.EOF) {
121             if (c == '\\') {
122               c = scanner.read();
123             } else if (c == '\'') {
124               break;
125             }
126           }
127           continue;
128         }
129
130         if (c == fEscapeCharacter) {
131           // Skip the escaped character.
132           scanner.read();
133         } else if (fEndSequence.length > 0 && c == fEndSequence[0]) {
134           // Check if the specified end sequence has been found.
135           if (sequenceDetected(scanner, fEndSequence, true))
136             return true;
137         } else if (fBreaksOnEOL) {
138           // Check for end of line since it can be used to terminate the pattern.
139           for (int i = 0; i < delimiters.length; i++) {
140             if (c == delimiters[i][0] && sequenceDetected(scanner, delimiters[i], false))
141               return true;
142           }
143         }
144       } 
145       boolean phpMode = false;
146       if (c == ICharacterScanner.EOF) {
147         phpMode = true;
148       }
149       scanner.unread();
150       return phpMode;
151     }
152   }
153
154   //  public class HTMLMultiLineRule extends MultiLineRule {
155   //
156   //    public HTMLMultiLineRule(String startSequence, String endSequence, IToken token) {
157   //      super(startSequence, endSequence, token);
158   //    }
159   //
160   //    public HTMLMultiLineRule(String startSequence, String endSequence, IToken token, char escapeCharacter) {
161   //      super(startSequence, endSequence, token, escapeCharacter);
162   //    }
163   //
164   //    protected boolean endSequenceDetected(ICharacterScanner scanner) {
165   //      int c;
166   //
167   //      char[][] delimiters = scanner.getLegalLineDelimiters();
168   //      while ((c = scanner.read()) != ICharacterScanner.EOF) {
169   //        if (c == '<') {
170   //          //       scanner.unread();
171   //          if (sequenceDetected(scanner, php2EndSequence, true)) {
172   //            // <?PHP
173   //            scanner.unread();
174   //            scanner.unread();
175   //            scanner.unread();
176   //            scanner.unread();
177   //            scanner.unread();
178   //            return true;
179   //          }
180   //          if (sequenceDetected(scanner, php1EndSequence, true)) {
181   //            // <?php
182   //            scanner.unread();
183   //            scanner.unread();
184   //            scanner.unread();
185   //            scanner.unread();
186   //            scanner.unread();
187   //            return true;
188   //          }
189   //          if (sequenceDetected(scanner, php0EndSequence, true)) {
190   //            // <?
191   //            scanner.unread();
192   //            scanner.unread();
193   //            return true;
194   //          }
195   //          //      scanner.read();
196   //        }
197   //
198   //      }
199   //      scanner.unread();
200   //      return false;
201   //    }
202   //
203   //    protected IToken doEvaluate(ICharacterScanner scanner, boolean resume) {
204   //
205   //      if (resume) {
206   //
207   //        if (endSequenceDetected(scanner))
208   //          return fToken;
209   //
210   //      } else {
211   //
212   //        int c = scanner.read();
213   //        //     if (c == fStartSequence[0]) {
214   //        //       if (sequenceDetected(scanner, fStartSequence, false)) {
215   //        if (endSequenceDetected(scanner))
216   //          return fToken;
217   //        //       }
218   //        //     }
219   //      }
220   //
221   //      scanner.unread();
222   //      return Token.UNDEFINED;
223   //    }
224   //
225   //    public IToken evaluate(ICharacterScanner scanner, boolean resume) {
226   //      if (fColumn == UNDEFINED)
227   //        return doEvaluate(scanner, resume);
228   //
229   //      int c = scanner.read();
230   //      scanner.unread();
231   //      //    if (c == fStartSequence[0])
232   //      return (fColumn == scanner.getColumn() ? doEvaluate(scanner, resume) : Token.UNDEFINED);
233   //      //    else
234   //      //      return Token.UNDEFINED;
235   //    }
236   //  }
237
238   public class HTMLPatternRule implements IPredicateRule {
239
240     protected static final int UNDEFINED = -1;
241
242     /** The token to be returned on success */
243     protected IToken fToken;
244
245     /** The pattern's column constrain */
246     protected int fColumn = UNDEFINED;
247     /** The pattern's escape character */
248     protected char fEscapeCharacter;
249     /** Indicates whether end of line termines the pattern */
250     protected boolean fBreaksOnEOL;
251
252     /**
253      * Creates a rule for the given starting and ending sequence.
254      * When these sequences are detected the rule will return the specified token.
255      * Alternatively, the sequence can also be ended by the end of the line.
256      * Any character which follows the given escapeCharacter will be ignored.
257      *
258      * @param startSequence the pattern's start sequence
259      * @param endSequence the pattern's end sequence, <code>null</code> is a legal value
260      * @param token the token which will be returned on success
261      * @param escapeCharacter any character following this one will be ignored
262      * @param indicates whether the end of the line also termines the pattern
263      */
264     public HTMLPatternRule(IToken token) {
265       fToken = token;
266       fEscapeCharacter = (char) 0;
267       fBreaksOnEOL = false;
268     }
269
270     /**
271      * Sets a column constraint for this rule. If set, the rule's token
272      * will only be returned if the pattern is detected starting at the 
273      * specified column. If the column is smaller then 0, the column
274      * constraint is considered removed.
275      *
276      * @param column the column in which the pattern starts
277      */
278     public void setColumnConstraint(int column) {
279       if (column < 0)
280         column = UNDEFINED;
281       fColumn = column;
282     }
283
284     /**
285      * Evaluates this rules without considering any column constraints.
286      *
287      * @param scanner the character scanner to be used
288      * @return the token resulting from this evaluation
289      */
290     protected IToken doEvaluate(ICharacterScanner scanner) {
291       return doEvaluate(scanner, false);
292     }
293
294     /**
295      * Evaluates this rules without considering any column constraints. Resumes
296      * detection, i.e. look sonly for the end sequence required by this rule if the
297      * <code>resume</code> flag is set.
298      *
299      * @param scanner the character scanner to be used
300      * @param resume <code>true</code> if detection should be resumed, <code>false</code> otherwise
301      * @return the token resulting from this evaluation
302      * @since 2.0
303      */
304     protected IToken doEvaluate(ICharacterScanner scanner, boolean resume) {
305
306       if (resume) {
307
308         if (endSequenceDetected(scanner))
309           return fToken;
310
311       } else {
312
313         int c = scanner.read();
314         //      if (c == fStartSequence[0]) {
315         //        if (sequenceDetected(scanner, fStartSequence, false)) {
316         if (endSequenceDetected(scanner))
317           return fToken;
318         //        }
319         //      }
320       }
321
322       scanner.unread();
323       return Token.UNDEFINED;
324     }
325
326     /*
327      * @see IRule#evaluate
328      */
329     public IToken evaluate(ICharacterScanner scanner) {
330       return evaluate(scanner, false);
331     }
332
333     /**
334      * Returns whether the end sequence was detected. As the pattern can be considered 
335      * ended by a line delimiter, the result of this method is <code>true</code> if the 
336      * rule breaks on the end  of the line, or if the EOF character is read.
337      *
338      * @param scanner the character scanner to be used
339      * @return <code>true</code> if the end sequence has been detected
340      */
341     protected boolean endSequenceDetected(ICharacterScanner scanner) {
342       int c;
343
344       char[][] delimiters = scanner.getLegalLineDelimiters();
345       while ((c = scanner.read()) != ICharacterScanner.EOF) {
346         if (c == '<') {
347           //       scanner.unread();
348           if (sequenceDetected(scanner, php2EndSequence, true)) {
349             // <?PHP
350             scanner.unread();
351             scanner.unread();
352             scanner.unread();
353             scanner.unread();
354             scanner.unread();
355             return true;
356           }
357           if (sequenceDetected(scanner, php1EndSequence, true)) {
358             // <?php
359             scanner.unread();
360             scanner.unread();
361             scanner.unread();
362             scanner.unread();
363             scanner.unread();
364             return true;
365           }
366           if (sequenceDetected(scanner, php0EndSequence, true)) {
367             // <?
368             scanner.unread();
369             scanner.unread();
370             return true;
371           }
372           //      scanner.read();
373         }
374
375       }
376       scanner.unread();
377       return false;
378     }
379
380     /**
381      * Returns whether the next characters to be read by the character scanner
382      * are an exact match with the given sequence. No escape characters are allowed 
383      * within the sequence. If specified the sequence is considered to be found
384      * when reading the EOF character.
385      *
386      * @param scanner the character scanner to be used
387      * @param sequence the sequence to be detected
388      * @param eofAllowed indicated whether EOF terminates the pattern
389      * @return <code>true</code> if the given sequence has been detected
390      */
391     protected boolean sequenceDetected(ICharacterScanner scanner, char[] sequence, boolean eofAllowed) {
392       for (int i = 1; i < sequence.length; i++) {
393         int c = scanner.read();
394         if (c == ICharacterScanner.EOF && eofAllowed) {
395           return true;
396         } else if (c != sequence[i]) {
397           // Non-matching character detected, rewind the scanner back to the start.
398           scanner.unread();
399           for (int j = i - 1; j > 0; j--)
400             scanner.unread();
401           return false;
402         }
403       }
404
405       return true;
406     }
407
408     /*
409      * @see IPredicateRule#evaluate(ICharacterScanner, boolean)
410      * @since 2.0
411      */
412     public IToken evaluate(ICharacterScanner scanner, boolean resume) {
413       if (fColumn == UNDEFINED)
414         return doEvaluate(scanner, resume);
415
416       int c = scanner.read();
417       scanner.unread();
418       //    if (c == fStartSequence[0])
419       return (fColumn == scanner.getColumn() ? doEvaluate(scanner, resume) : Token.UNDEFINED);
420       //    else
421       //      return Token.UNDEFINED;
422     }
423
424     /*
425      * @see IPredicateRule#getSuccessToken()
426      * @since 2.0
427      */
428     public IToken getSuccessToken() {
429       return fToken;
430     }
431   }
432   /**
433    * Detector for empty comments.
434    */
435   static class EmptyCommentDetector implements IWordDetector {
436
437     /* (non-Javadoc)
438     * Method declared on IWordDetector
439         */
440     public boolean isWordStart(char c) {
441       return (c == '/');
442     }
443
444     /* (non-Javadoc)
445     * Method declared on IWordDetector
446         */
447     public boolean isWordPart(char c) {
448       return (c == '*' || c == '/');
449     }
450   };
451
452   /**
453    * 
454    */
455   static class WordPredicateRule extends WordRule implements IPredicateRule {
456
457     private IToken fSuccessToken;
458
459     public WordPredicateRule(IToken successToken) {
460       super(new EmptyCommentDetector());
461       fSuccessToken = successToken;
462       addWord("/**/", fSuccessToken);
463     }
464
465     /*
466      * @see org.eclipse.jface.text.rules.IPredicateRule#evaluate(ICharacterScanner, boolean)
467      */
468     public IToken evaluate(ICharacterScanner scanner, boolean resume) {
469       return super.evaluate(scanner);
470     }
471
472     /*
473      * @see org.eclipse.jface.text.rules.IPredicateRule#getSuccessToken()
474      */
475     public IToken getSuccessToken() {
476       return fSuccessToken;
477     }
478   };
479
480   /**
481    * Creates the partitioner and sets up the appropriate rules.
482    */
483   public PHPPartitionScanner() {
484     super();
485
486     //    IToken php = new Token(PHP);
487     //    IToken html = new Token(HTML);
488     //    IToken comment = new Token(HTML_MULTILINE_COMMENT);
489
490     List rules = new ArrayList();
491
492     // Add rule for single line comments.
493     //  rules.add(new EndOfLineRule("//", Token.UNDEFINED));
494
495     // Add rule for strings and character constants.
496     //          rules.add(new SingleLineRule("\"", "\"", Token.UNDEFINED, '\\'));
497     //  rules.add(new SingleLineRule("'", "'", Token.UNDEFINED, '\\')); 
498
499     // Add special case word rule.
500     //    rules.add(new WordPredicateRule(comment));
501
502     // Add rules for multi-line comments and javadoc.
503     //rules.add(new MultiLineRule("/**", "*/", javaDoc));
504     //  rules.add(new HTMLMultiLineRule("<", "<?", html));
505
506     rules.add(new MultiLineRule("<!--", "-->", comment));
507     rules.add(new PHPMultiLineRule("<?\r", "?>", php));
508     rules.add(new PHPMultiLineRule("<?\n", "?>", php));
509     rules.add(new PHPMultiLineRule("<?\t", "?>", php));
510     rules.add(new PHPMultiLineRule("<? ", "?>", php));
511     rules.add(new PHPMultiLineRule("<?php", "?>", php));
512     rules.add(new PHPMultiLineRule("<?PHP", "?>", php));
513
514     //    rules.add(new HTMLPatternRule(html)); // "<", "<?",
515     //Add rule for processing instructions
516
517     IPredicateRule[] result = new IPredicateRule[rules.size()];
518     rules.toArray(result);
519     setPredicateRules(result);
520     //    setDefaultReturnToken(html);
521   }
522
523   //    public IToken nextToken() {
524   //      
525   //      if (fContentType == null || fRules == null)
526   //        return getNextToken();
527   //      
528   //      fTokenOffset= fOffset;
529   //      fColumn= UNDEFINED;
530   //      boolean resume= (fPartitionOffset < fOffset);
531   //          
532   //      IPredicateRule rule;
533   //      IToken token;
534   //      
535   //      for (int i= 0; i < fRules.length; i++) {
536   //        rule= (IPredicateRule) fRules[i];
537   //        token= rule.getSuccessToken();
538   //        if (fContentType.equals(token.getData())) {
539   //          if (resume)
540   //            fTokenOffset= fPartitionOffset;
541   //          token= rule.evaluate(this, resume);
542   //          if (!token.isUndefined()) {
543   //            fContentType= null;
544   //            return token;
545   //          }
546   //        }
547   //      }
548   //      
549   //      fContentType= null;
550   //      return getNextToken();
551   //    }
552   //    
553   //    public IToken getNextToken() {
554   //      
555   //      IToken token;
556   //      
557   //      while (true) {
558   //        
559   //        fTokenOffset= fOffset;
560   //        fColumn= UNDEFINED;
561   //        
562   //        if (fRules != null) {
563   //          for (int i= 0; i < fRules.length; i++) {
564   //            token= (fRules[i].evaluate(this));
565   //            if (!token.isUndefined())
566   //              return token;
567   //          }
568   //        }
569   //        
570   //        if (read() == EOF)
571   //          return Token.EOF;
572   //        else
573   //          return fDefaultReturnToken;
574   //      }
575   //    }
576 }