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