1) Fixed issue #347: Syntax highlight doesn't like apostrophe in heredoc.
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpeclipse / phpeditor / php / PHPPartitionScanner.java
1 /**********************************************************************
2  Copyright (c) 2002  Widespace, OU  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://solareclipse.sourceforge.net/legal/cpl-v10.html
7
8  Contributors:
9  Igor Malinin - initial contribution
10
11  $Id: PHPPartitionScanner.java,v 1.35 2007-03-17 14:07:31 axelcl Exp $
12  **********************************************************************/
13 package net.sourceforge.phpeclipse.phpeditor.php;
14
15 import java.util.HashMap;
16 import java.util.Map;
17
18 import net.sourceforge.phpdt.internal.compiler.parser.Scanner;
19 import net.sourceforge.phpeclipse.ui.text.rules.AbstractPartitioner;
20
21 //incastrix
22 //import org.eclipse.jface.text.Assert;
23 import org.eclipse.core.runtime.Assert;
24 import org.eclipse.jface.text.BadLocationException;
25 import org.eclipse.jface.text.IDocument;
26 import org.eclipse.jface.text.rules.ICharacterScanner;
27 import org.eclipse.jface.text.rules.IPartitionTokenScanner;
28 import org.eclipse.jface.text.rules.IToken;
29 import org.eclipse.jface.text.rules.Token;
30
31 /**
32  * 
33  * 
34  * @author Igor Malinin
35  */
36 public class PHPPartitionScanner implements IPartitionTokenScanner {
37         public static final String PHP_SCRIPTING_AREA = "__php_scripting_area ";
38
39         public static final int STATE_DEFAULT = 0;
40
41         // public static final int STATE_TAG = 1;
42         // public static final int STATE_SCRIPT = 2;
43
44         private IDocument document;
45
46         // private int begin;
47
48         private int end;
49
50         private int offset;
51
52         private int length;
53
54         private int position;
55
56         // private int state;
57
58         private Map tokens = new HashMap();
59
60         public PHPPartitionScanner() {
61         }
62
63         /*
64          * @see org.eclipse.jface.text.rules.ITokenScanner#nextToken()
65          */
66         public IToken nextToken() {
67                 offset += length;
68
69                 /*
70                  * switch (state) { case STATE_TAG: return nextTagToken(); }
71                  */
72
73                 switch (read()) {
74                 case ICharacterScanner.EOF:
75                         // state = STATE_DEFAULT;
76                         return getToken(null);
77
78                 case '<':
79                         switch (read()) {
80                         case ICharacterScanner.EOF:
81                                 // state = STATE_DEFAULT;
82                                 return getToken(null);
83
84                         case '?': // <?
85                                 // int ch = read();
86                                 //
87                                 // switch (ch) {
88                                 // case ICharacterScanner.EOF:
89                                 // state = STATE_DEFAULT;
90                                 // return getToken(PHP_SCRIPTING_AREA);
91                                 // }
92                                 return scanUntilPHPEndToken(PHP_SCRIPTING_AREA);
93                         }
94
95                         unread();
96                 }
97
98                 loop: while (true) {
99                         switch (read()) {
100                         case ICharacterScanner.EOF:
101                                 // state = STATE_DEFAULT;
102                                 return getToken(null);
103
104                         case '<':
105                                 switch (read()) {
106                                 case ICharacterScanner.EOF:
107                                         // state = STATE_DEFAULT;
108                                         return getToken(null);
109
110                                 case '?':
111                                         unread();
112                                         break;
113
114                                 case '<':
115                                         unread();
116
117                                 default:
118                                         continue loop;
119                                 }
120
121                                 unread();
122
123                                 // state = STATE_DEFAULT;
124                                 return getToken(null);
125                         }
126                 }
127         }
128
129         private IToken scanUntilPHPEndToken(String token) {
130                 int ch = read();
131                 while (true) {
132                         switch (ch) {
133                         case ICharacterScanner.EOF:
134                                 // state = STATE_DEFAULT;
135                                 return getToken(token);
136                         case '"': // double quoted string
137                                 // read until end of double quoted string
138                                 if (!readUntilEscapedDQ()) {
139                                         // state = STATE_DEFAULT;
140                                         return getToken(token);
141                                 }
142                                 break;
143                         case '<': // heredoc string
144                                 ch = read();
145                                 switch (ch) {
146                                 case ICharacterScanner.EOF:
147                                         break;
148                                 case '<':
149                                         ch = read();
150                                         switch (ch) {
151                                         case ICharacterScanner.EOF:
152                                                 break;
153                                         case '<':
154                                                 // read until end of heredoc string
155                                                 if (!readUntilEscapedHEREDOC()) {
156                                                         // state = STATE_DEFAULT;
157                                                         return getToken(token);
158                                                 }
159                                         }
160                                 }
161                                 break;
162                         case '\'': // single quoted string
163                                 // read until end of single quoted string
164                                 if (!readUntilEscapedSQ()) {
165                                         // state = STATE_DEFAULT;
166                                         return getToken(token);
167                                 }
168                                 break;
169                         case '/': // comment start?
170                                 ch = read();
171                                 switch (ch) {
172                                 case ICharacterScanner.EOF:
173                                         break;
174                                 case '/':
175                                         // read until end of line
176                                         if (!readSingleLine()) {
177                                                 // state = STATE_DEFAULT;
178                                                 return getToken(token);
179                                         }
180                                         break;
181                                 case '*':
182                                         // read until end of comment
183                                         if (!readMultiLineComment()) {
184                                                 // state = STATE_DEFAULT;
185                                                 return getToken(token);
186                                         }
187                                         break;
188                                 default:
189                                         continue;
190                                 }
191                                 break;
192                         case '#': // line comment
193                                 // read until end of line
194                                 if (!readSingleLine()) {
195                                         // state = STATE_DEFAULT;
196                                         return getToken(token);
197                                 }
198                                 break;
199                         case '?':
200                                 ch = read();
201                                 switch (ch) {
202                                 case ICharacterScanner.EOF:
203                                 case '>':
204                                         // state = STATE_DEFAULT;
205                                         return getToken(token);
206
207                                 case '?':
208                                         continue;
209                                 default:
210                                         continue;
211                                 }
212                         }
213
214                         ch = read();
215                 }
216         }
217
218         private IToken getToken(String type) {
219                 length = position - offset;
220
221                 if (length == 0) {
222                         return Token.EOF;
223                 }
224
225                 // if (length<0) {
226                 // try {
227                 // System.out.println("Length<0:"+document.get(offset,5)+""+length);
228                 // } catch (BadLocationException e) {
229                 // e.printStackTrace();
230                 // }
231                 // }
232
233                 if (type == null) {
234                         return Token.UNDEFINED;
235                 }
236
237                 IToken token = (IToken) tokens.get(type);
238                 if (token == null) {
239                         token = new Token(type);
240                         tokens.put(type, token);
241                 }
242
243                 return token;
244         }
245
246         private int read() {
247                 if (position >= end) {
248                         return ICharacterScanner.EOF;
249                 }
250
251                 try {
252                         return document.getChar(position++);
253                 } catch (BadLocationException e) {
254                         --position;
255                         return ICharacterScanner.EOF;
256                 }
257         }
258
259         private boolean readUntilEscapedDQ() {
260                 // search last double quoted character
261                 try {
262                         char ch;
263                         while (true) {
264                                 if (position >= end) {
265                                         return false;
266                                 }
267                                 ch = document.getChar(position++);
268                                 if (ch == '\\') {
269                                         if (position >= end) {
270                                                 return false;
271                                         }
272                                         ch = document.getChar(position++); // ignore escaped
273                                         // character
274                                 } else if (ch == '"') {
275                                         return true;
276                                 }
277                         }
278                 } catch (BadLocationException e) {
279                         --position;
280                 }
281                 return false;
282         }
283
284         private boolean readUntilEscapedSQ() {
285                 // search last single quoted character
286                 try {
287                         char ch;
288                         while (true) {
289                                 if (position >= end) {
290                                         return false;
291                                 }
292                                 ch = document.getChar(position++);
293                                 if (ch == '\\') {
294                                         if (position >= end) {
295                                                 return false;
296                                         }
297                                         ch = document.getChar(position++); // ignore escaped
298                                         // character
299                                 } else if (ch == '\'') {
300                                         return true;
301                                 }
302                         }
303                 } catch (BadLocationException e) {
304                         --position;
305                 }
306                 return false;
307         }
308
309         /**
310          * Read until HEREDOC ends
311          * 
312          * @return
313          */
314         private boolean readUntilEscapedHEREDOC() {
315                 try {
316                         char ch;
317                         StringBuffer buf = new StringBuffer();
318                         char[] heredocIdent;
319                         
320                         if (position >= end) {
321                                 return false;
322                         }
323                         
324                         ch = document.getChar(position++);
325                         
326                         
327                         while (ch == ' ') {
328                                 if (position >= end) {
329                                         return false;
330                                 }
331                                 ch = document.getChar(position++);
332                         }
333                         
334                         if (!Scanner.isPHPIdentifierStart(ch)) {
335                                 return false;
336                         }
337                         
338                         while (Scanner.isPHPIdentifierPart(ch)) {
339                                 buf.append(ch);
340                                 if (position >= end) {
341                                         return false;
342                                 }
343                                 ch = document.getChar(position++);
344                         }
345                         
346                         heredocIdent = buf.toString().toCharArray();
347                         
348                         while (true) {
349                                 if (position >= end) {
350                                         return false;
351                                 }
352                                 
353                                 ch = document.getChar (position++);                                     // Get the next character from file
354                                 
355                                 if (ch == '\n') {                                   // heredoc could end after a newline
356                                         int pos = 0;
357                                         
358                                         while (true) {
359                                                 if (position >= end) {                                          // If we are at the end of file
360                                                         return false;                                                   // Return                                                                       
361                                                 }
362                                                 
363                                                 if (pos == heredocIdent.length) {                       // If the found length equals the length of heredoc id
364                                                         return true;                                                    // we found the end of heredoc
365                                                 }
366                                                 
367                                                 ch = document.getChar (position++);                     // Ignore escaped character
368                                                 
369                                                 if (ch != heredocIdent[pos]) {                          // If current character doesn't match the heredoc id
370                                                         break;                                                                  // break the heredoc end search
371                                                 }
372                                                 
373                                                 pos++;                                                                          // Character matched the heredoc id so far
374                                         }
375                                 }
376                         }
377                 } catch (BadLocationException e) {
378                         --position;
379                 }
380                 return false;
381         }
382
383         private boolean readSingleLine() {
384                 try {
385                         do {
386                                 if (position >= end) {
387                                         return false;
388                                 }
389                         } while (document.getChar(position++) != '\n');
390                         return true;
391                 } catch (BadLocationException e) {
392                         --position;
393                 }
394                 return false;
395         }
396
397         private boolean readMultiLineComment() {
398                 try {
399                         char ch;
400                         while (true) {
401                                 if (position >= end) {
402                                         return false;
403                                 }
404                                 ch = document.getChar(position++);
405                                 if (ch == '*') {
406                                         if (position >= end) {
407                                                 return false;
408                                         }
409                                         if (document.getChar(position) == '/') {
410                                                 position++;
411                                                 return true;
412                                         }
413                                 }
414                         }
415                 } catch (BadLocationException e) {
416                         --position;
417                 }
418                 return false;
419         }
420
421         private void unread() {
422                 --position;
423         }
424
425         /*
426          * @see org.eclipse.jface.text.rules.ITokenScanner#getTokenOffset()
427          */
428         public int getTokenOffset() {
429                 if (AbstractPartitioner.DEBUG) {
430                         Assert.isTrue(offset >= 0, Integer.toString(offset));
431                 }
432                 return offset;
433         }
434
435         /*
436          * @see org.eclipse.jface.text.rules.ITokenScanner#getTokenLength()
437          */
438         public int getTokenLength() {
439                 return length;
440         }
441
442         /*
443          * @see org.eclipse.jface.text.rules.ITokenScanner#setRange(IDocument, int,
444          *      int)
445          */
446         public void setRange(IDocument document, int offset, int length) {
447                 this.document = document;
448                 // this.begin = offset;
449                 this.end = offset + length;
450
451                 this.offset = offset;
452                 this.position = offset;
453                 this.length = 0;
454         }
455
456         /*
457          * @see org.eclipse.jface.text.rules.IPartitionTokenScanner
458          */
459         public void setPartialRange(IDocument document, int offset, int length,
460                         String contentType, int partitionOffset) {
461                 // state = STATE_DEFAULT;
462                 if (partitionOffset > -1) {
463                         int delta = offset - partitionOffset;
464                         if (delta > 0) {
465                                 setRange(document, partitionOffset, length + delta);
466                                 return;
467                         }
468                 }
469                 setRange(document, partitionOffset, length);
470         }
471
472         // private boolean isContinuationPartition(IDocument document, int offset) {
473         // try {
474         // String type = document.getContentType(offset - 1);
475         //
476         // if (type != IDocument.DEFAULT_CONTENT_TYPE) {
477         // return true;
478         // }
479         // } catch (BadLocationException e) {}
480         //
481         // return false;
482         // }
483 }