fixed "bracket matching highlighting" bug
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpdt / internal / ui / text / FastJavaPartitionScanner.java
1 /*******************************************************************************
2  * Copyright (c) 2000, 2004 IBM Corporation 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 API and implementation
10  *******************************************************************************/
11 package net.sourceforge.phpdt.internal.ui.text;
12
13 import net.sourceforge.phpeclipse.ui.text.rules.AbstractPartitioner;
14
15 import org.eclipse.jface.text.Assert;
16 import org.eclipse.jface.text.IDocument;
17 import org.eclipse.jface.text.rules.ICharacterScanner;
18 import org.eclipse.jface.text.rules.IPartitionTokenScanner;
19 import org.eclipse.jface.text.rules.IToken;
20 import org.eclipse.jface.text.rules.RuleBasedPartitionScanner;
21 import org.eclipse.jface.text.rules.Token;
22
23 /**
24  * This scanner recognizes the JavaDoc comments, Java multi line comments, Java single line comments, Java strings.
25  */
26 public class FastJavaPartitionScanner implements IPartitionTokenScanner, IPHPPartitions {
27
28   // states
29   private static final int PHP = 0;
30
31   private static final int SINGLE_LINE_COMMENT = 1;
32
33   private static final int MULTI_LINE_COMMENT = 2;
34
35   private static final int PHPDOC = 3;
36
37   private static final int STRING_DQ = 4;
38
39   private static final int STRING_SQ = 5;
40
41   //    private static final int CHARACTER= 4;
42
43   // beginning of prefixes and postfixes
44   private static final int NONE = 0;
45
46   private static final int BACKSLASH = 1; // postfix for STRING and CHARACTER
47
48   private static final int SLASH = 2; // prefix for SINGLE_LINE or MULTI_LINE or JAVADOC
49
50   private static final int SLASH_STAR = 3; // prefix for MULTI_LINE_COMMENT or JAVADOC
51
52   private static final int SLASH_STAR_STAR = 4; // prefix for MULTI_LINE_COMMENT or JAVADOC
53
54   private static final int STAR = 5; // postfix for MULTI_LINE_COMMENT or JAVADOC
55
56   private static final int CARRIAGE_RETURN = 6; // postfix for STRING, CHARACTER and SINGLE_LINE_COMMENT
57
58   /** The scanner. */
59   private final BufferedDocumentScanner fScanner = new BufferedDocumentScanner(1000); // faster implementation
60
61   /** The offset of the last returned token. */
62   private int fTokenOffset;
63
64   /** The length of the last returned token. */
65   private int fTokenLength;
66
67   /** The state of the scanner. */
68   private int fState;
69
70   /** The last significant characters read. */
71   private int fLast;
72
73   /** The amount of characters already read on first call to nextToken(). */
74   private int fPrefixLength;
75
76   // emulate JavaPartitionScanner
77   private boolean fEmulate = false;
78
79   private int fJavaOffset;
80
81   private int fJavaLength;
82
83   private final IToken[] fTokens = new IToken[] { new Token(null), new Token(PHP_SINGLELINE_COMMENT),
84       new Token(PHP_MULTILINE_COMMENT), new Token(PHP_PHPDOC_COMMENT), new Token(PHP_STRING_DQ), new Token(PHP_STRING_SQ) };
85
86   public FastJavaPartitionScanner(boolean emulate) {
87     fEmulate = emulate;
88   }
89
90   public FastJavaPartitionScanner() {
91     this(false);
92   }
93
94   /*
95    * @see org.eclipse.jface.text.rules.ITokenScanner#nextToken()
96    */
97   public IToken nextToken() {
98
99     // emulate JavaPartitionScanner
100     if (fEmulate) {
101       if (fJavaOffset != -1 && fTokenOffset + fTokenLength != fJavaOffset + fJavaLength) {
102         fTokenOffset += fTokenLength;
103         return fTokens[PHP];
104       } else {
105         fJavaOffset = -1;
106         fJavaLength = 0;
107       }
108     }
109
110     fTokenOffset += fTokenLength;
111     fTokenLength = fPrefixLength;
112
113     while (true) {
114       final int ch = fScanner.read();
115
116       // characters
117       switch (ch) {
118       case ICharacterScanner.EOF:
119         if (fTokenLength > 0) {
120           fLast = NONE; // ignore last
121           return preFix(fState, PHP, NONE, 0);
122
123         } else {
124           fLast = NONE;
125           fPrefixLength = 0;
126           return Token.EOF;
127         }
128
129       case '\r':
130         // emulate JavaPartitionScanner
131         if (!fEmulate && fLast != CARRIAGE_RETURN) {
132           fLast = CARRIAGE_RETURN;
133           fTokenLength++;
134           continue;
135
136         } else {
137
138           switch (fState) {
139           case SINGLE_LINE_COMMENT:
140             //                                  case CHARACTER:
141             //                                  case STRING_DQ:
142             //                                  case STRING_SQ:
143             if (fTokenLength > 0) {
144               IToken token = fTokens[fState];
145
146               // emulate JavaPartitionScanner
147               if (fEmulate) {
148                 fTokenLength++;
149                 fLast = NONE;
150                 fPrefixLength = 0;
151               } else {
152                 fLast = CARRIAGE_RETURN;
153                 fPrefixLength = 1;
154               }
155
156               fState = PHP;
157               return token;
158
159             } else {
160               consume();
161               continue;
162             }
163
164           default:
165             consume();
166             continue;
167           }
168         }
169
170       case '\n':
171         switch (fState) {
172         case SINGLE_LINE_COMMENT:
173           //                            case CHARACTER:
174           //                            case STRING_DQ:
175           //                            case STRING_SQ:
176           // assert(fTokenLength > 0);
177           return postFix(fState);
178
179         default:
180           consume();
181           continue;
182         }
183
184       default:
185         if (!fEmulate && fLast == CARRIAGE_RETURN) {
186           switch (fState) {
187           case SINGLE_LINE_COMMENT:
188             //                                  case CHARACTER:
189             //                                  case STRING_DQ:
190             //                                  case STRING_SQ:
191             int last;
192             int newState;
193             switch (ch) {
194             case '/':
195               last = SLASH;
196               newState = PHP;
197               break;
198
199             case '*':
200               last = STAR;
201               newState = PHP;
202               break;
203
204             case '\'':
205               last = NONE;
206               newState = STRING_SQ;
207               break;
208
209             case '"':
210               last = NONE;
211               newState = STRING_DQ;
212               break;
213
214             case '\r':
215               last = CARRIAGE_RETURN;
216               newState = PHP;
217               break;
218
219             case '\\':
220               last = BACKSLASH;
221               newState = PHP;
222               break;
223
224             default:
225               last = NONE;
226               newState = PHP;
227               break;
228             }
229
230             fLast = NONE; // ignore fLast
231             return preFix(fState, newState, last, 1);
232
233           default:
234             break;
235           }
236         }
237       }
238
239       // states
240       switch (fState) {
241       case PHP:
242         switch (ch) {
243         case '#':
244           if (fTokenLength > 0) {
245             return preFix(PHP, SINGLE_LINE_COMMENT, NONE, 1);
246           } else {
247             preFix(PHP, SINGLE_LINE_COMMENT, NONE, 1);
248             fTokenOffset += fTokenLength;
249             fTokenLength = fPrefixLength;
250             break;
251           }
252         case '/':
253           if (fLast == SLASH) {
254             if (fTokenLength - getLastLength(fLast) > 0) {
255               return preFix(PHP, SINGLE_LINE_COMMENT, NONE, 2);
256             } else {
257               preFix(PHP, SINGLE_LINE_COMMENT, NONE, 2);
258               fTokenOffset += fTokenLength;
259               fTokenLength = fPrefixLength;
260               break;
261             }
262
263           } else {
264             fTokenLength++;
265             fLast = SLASH;
266             break;
267           }
268
269         case '*':
270           if (fLast == SLASH) {
271             if (fTokenLength - getLastLength(fLast) > 0)
272               return preFix(PHP, MULTI_LINE_COMMENT, SLASH_STAR, 2);
273             else {
274               preFix(PHP, MULTI_LINE_COMMENT, SLASH_STAR, 2);
275               fTokenOffset += fTokenLength;
276               fTokenLength = fPrefixLength;
277               break;
278             }
279
280           } else {
281             consume();
282             break;
283           }
284
285         case '\'':
286           fLast = NONE; // ignore fLast
287           if (fTokenLength > 0)
288             return preFix(PHP, STRING_SQ, NONE, 1);
289           else {
290             preFix(PHP, STRING_SQ, NONE, 1);
291             fTokenOffset += fTokenLength;
292             fTokenLength = fPrefixLength;
293             break;
294           }
295
296         case '"':
297           fLast = NONE; // ignore fLast
298           if (fTokenLength > 0)
299             return preFix(PHP, STRING_DQ, NONE, 1);
300           else {
301             preFix(PHP, STRING_DQ, NONE, 1);
302             fTokenOffset += fTokenLength;
303             fTokenLength = fPrefixLength;
304             break;
305           }
306
307         default:
308           consume();
309           break;
310         }
311         break;
312
313       case SINGLE_LINE_COMMENT:
314         consume();
315         break;
316
317       case PHPDOC:
318         switch (ch) {
319         case '/':
320           switch (fLast) {
321           case SLASH_STAR_STAR:
322             return postFix(MULTI_LINE_COMMENT);
323
324           case STAR:
325             return postFix(PHPDOC);
326
327           default:
328             consume();
329             break;
330           }
331           break;
332
333         case '*':
334           fTokenLength++;
335           fLast = STAR;
336           break;
337
338         default:
339           consume();
340           break;
341         }
342         break;
343
344       case MULTI_LINE_COMMENT:
345         switch (ch) {
346         case '*':
347           if (fLast == SLASH_STAR) {
348             fLast = SLASH_STAR_STAR;
349             fTokenLength++;
350             fState = PHPDOC;
351           } else {
352             fTokenLength++;
353             fLast = STAR;
354           }
355           break;
356
357         case '/':
358           if (fLast == STAR) {
359             return postFix(MULTI_LINE_COMMENT);
360           } else {
361             consume();
362             break;
363           }
364
365         default:
366           consume();
367           break;
368         }
369         break;
370
371       case STRING_DQ:
372         switch (ch) {
373         case '\\':
374           fLast = (fLast == BACKSLASH) ? NONE : BACKSLASH;
375           fTokenLength++;
376           break;
377
378         case '\"':
379           if (fLast != BACKSLASH) {
380             return postFix(STRING_DQ);
381
382           } else {
383             consume();
384             break;
385           }
386
387         default:
388           consume();
389           break;
390         }
391         break;
392       case STRING_SQ:
393         switch (ch) {
394         case '\\':
395           fLast = (fLast == BACKSLASH) ? NONE : BACKSLASH;
396           fTokenLength++;
397           break;
398
399         case '\'':
400           if (fLast != BACKSLASH) {
401             return postFix(STRING_SQ);
402
403           } else {
404             consume();
405             break;
406           }
407
408         default:
409           consume();
410           break;
411         }
412         break;
413       //                        case CHARACTER:
414       //                                switch (ch) {
415       //                                case '\\':
416       //                                        fLast= (fLast == BACKSLASH) ? NONE : BACKSLASH;
417       //                                        fTokenLength++;
418       //                                        break;
419       //        
420       //                                case '\'':
421       //                                        if (fLast != BACKSLASH) {
422       //                                                return postFix(CHARACTER);
423       //        
424       //                                        } else {
425       //                                                consume();
426       //                                                break;
427       //                                        }
428       //        
429       //                                default:
430       //                                        consume();
431       //                                        break;
432       //                                }
433       //                                break;
434       }
435     }
436   }
437
438   private static final int getLastLength(int last) {
439     switch (last) {
440     default:
441       return -1;
442
443     case NONE:
444       return 0;
445
446     case CARRIAGE_RETURN:
447     case BACKSLASH:
448     case SLASH:
449     case STAR:
450       return 1;
451
452     case SLASH_STAR:
453       return 2;
454
455     case SLASH_STAR_STAR:
456       return 3;
457     }
458   }
459
460   private final void consume() {
461     fTokenLength++;
462     fLast = NONE;
463   }
464
465   private final IToken postFix(int state) {
466     fTokenLength++;
467     fLast = NONE;
468     fState = PHP;
469     fPrefixLength = 0;
470     return fTokens[state];
471   }
472
473   private final IToken preFix(int state, int newState, int last, int prefixLength) {
474     // emulate JavaPartitionScanner
475     if (fEmulate && state == PHP && (fTokenLength - getLastLength(fLast) > 0)) {
476       fTokenLength -= getLastLength(fLast);
477       fJavaOffset = fTokenOffset;
478       fJavaLength = fTokenLength;
479       fTokenLength = 1;
480       fState = newState;
481       fPrefixLength = prefixLength;
482       fLast = last;
483       return fTokens[state];
484
485     } else {
486       fTokenLength -= getLastLength(fLast);
487       fLast = last;
488       fPrefixLength = prefixLength;
489       IToken token = fTokens[state];
490       fState = newState;
491       return token;
492     }
493   }
494
495   private static int getState(String contentType) {
496
497     if (contentType == null)
498       return PHP;
499
500     else if (contentType.equals(PHP_SINGLELINE_COMMENT))
501       return SINGLE_LINE_COMMENT;
502
503     else if (contentType.equals(PHP_MULTILINE_COMMENT))
504       return MULTI_LINE_COMMENT;
505
506     else if (contentType.equals(PHP_PHPDOC_COMMENT))
507       return PHPDOC;
508
509     else if (contentType.equals(PHP_STRING_DQ))
510       return STRING_DQ;
511     else if (contentType.equals(PHP_STRING_SQ))
512       return STRING_SQ;
513     //          else if (contentType.equals(JAVA_CHARACTER))
514     //                  return CHARACTER;
515
516     else
517       return PHP;
518   }
519
520   /*
521    * @see IPartitionTokenScanner#setPartialRange(IDocument, int, int, String, int)
522    */
523   public void setPartialRange(IDocument document, int offset, int length, String contentType, int partitionOffset) {
524     fScanner.setRange(document, offset, length);
525     setRange(document, offset, length);
526     fTokenOffset = partitionOffset;
527     fTokenLength = 0;
528     fPrefixLength = offset - partitionOffset;
529     fLast = NONE;
530
531     if (offset == partitionOffset) {
532       // restart at beginning of partition
533       fState = PHP;
534     } else {
535       fState = getState(contentType);
536     }
537
538     // emulate JavaPartitionScanner
539     if (fEmulate) {
540       fJavaOffset = -1;
541       fJavaLength = 0;
542     }
543   }
544
545   /*
546    * @see ITokenScanner#setRange(IDocument, int, int)
547    */
548   public void setRange(IDocument document, int offset, int length) {
549     fScanner.setRange(document, offset, length);
550     fTokenOffset = offset;
551     fTokenLength = 0;
552     fPrefixLength = 0;
553     fLast = NONE;
554     fState = PHP;
555
556     // emulate JavaPartitionScanner
557     if (fEmulate) {
558       fJavaOffset = -1;
559       fJavaLength = 0;
560     }
561   }
562
563   /*
564    * @see ITokenScanner#getTokenLength()
565    */
566   public int getTokenLength() {
567     return fTokenLength;
568   }
569
570   /*
571    * @see ITokenScanner#getTokenOffset()
572    */
573   public int getTokenOffset() {
574     if (AbstractPartitioner.DEBUG) {
575       Assert.isTrue(fTokenOffset >= 0, Integer.toString(fTokenOffset));
576     }
577     return fTokenOffset;
578   }
579
580 }