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