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