initial implementation
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpeclipse / phpeditor / php / PHPPartitionScanner.java
1 /**
2  * This program and the accompanying materials
3  * are made available under the terms of the Common Public License v1.0
4  * which accompanies this distribution, and is available at
5  * http://www.eclipse.org/legal/cpl-v10.html
6  * Created on 05.03.2003
7  *
8  * @author Stefan Langer (musk)
9  * @version $Revision: 1.21 $
10  */
11 package net.sourceforge.phpeclipse.phpeditor.php;
12
13 import java.util.ArrayList;
14 import java.util.HashMap;
15 import java.util.Map;
16
17 import org.eclipse.jface.text.Assert;
18 import org.eclipse.jface.text.BadLocationException;
19 import org.eclipse.jface.text.IDocument;
20 import org.eclipse.jface.text.ITypedRegion;
21 import org.eclipse.jface.text.rules.ICharacterScanner;
22 import org.eclipse.jface.text.rules.IPartitionTokenScanner;
23 import org.eclipse.jface.text.rules.IToken;
24 import org.eclipse.jface.text.rules.Token;
25
26 /**
27  * 
28  */
29 public class PHPPartitionScanner implements IPartitionTokenScanner {
30   private static final boolean DEBUG = false;
31
32   private boolean fInString = false;
33   private boolean fInDoubString = false;
34   private IDocument fDocument = null;
35   private int fOffset = -1;
36   private String fContentType = IPHPPartitionScannerConstants.HTML;
37   private String fPrevContentType = IPHPPartitionScannerConstants.HTML;
38   private boolean partitionBorder = false;
39   private int fTokenOffset;
40   private int fEnd = -1;
41   private int fLength;
42   private int fCurrentLength;
43   private int fFileType;
44   private Map tokens = new HashMap();
45
46   public PHPPartitionScanner(int fileType) {
47     this.tokens.put(IPHPPartitionScannerConstants.PHP, new Token(IPHPPartitionScannerConstants.PHP));
48     this.tokens.put(
49       IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT,
50       new Token(IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT));
51     this.tokens.put(IPHPPartitionScannerConstants.HTML, new Token(IPHPPartitionScannerConstants.HTML));
52     this.tokens.put(
53       IPHPPartitionScannerConstants.HTML_MULTILINE_COMMENT,
54       new Token(IPHPPartitionScannerConstants.HTML_MULTILINE_COMMENT));
55
56     this.tokens.put(IPHPPartitionScannerConstants.SMARTY, new Token(IPHPPartitionScannerConstants.SMARTY));
57     this.tokens.put(
58       IPHPPartitionScannerConstants.SMARTY_MULTILINE_COMMENT,
59       new Token(IPHPPartitionScannerConstants.SMARTY_MULTILINE_COMMENT));
60
61     this.tokens.put(IDocument.DEFAULT_CONTENT_TYPE, new Token(IDocument.DEFAULT_CONTENT_TYPE));
62     fFileType = fileType;
63   }
64
65   private IToken getToken(String type) {
66     fLength = fCurrentLength;
67     if (DEBUG) {
68
69       try {
70         if (fLength <= 0) {
71           int line = fDocument.getLineOfOffset(fOffset);
72           System.err.println("Error at " + line + " offset:" + String.valueOf(fOffset - fDocument.getLineOffset(line)));
73         }
74       } catch (BadLocationException e) { // should never happen
75         // TODO Write stacktrace to log
76         e.printStackTrace();
77       }
78     }
79     Assert.isTrue(fLength > 0, "Partition length <= 0!");
80     fCurrentLength = 0;
81     // String can never cross partition borders so reset string detection
82     fInString = false;
83     fInDoubString = false;
84     IToken token = (IToken) this.tokens.get(type);
85     Assert.isNotNull(token, "Token for type \"" + type + "\" not found!");
86     if (DEBUG) {
87       System.out.println("Partition: fTokenOffset=" + fTokenOffset + " fContentType=" + type + " fLength=" + fLength);
88     }
89     return token;
90   }
91
92   /* (non-Javadoc)
93    * @see org.eclipse.jface.text.rules.IPartitionTokenScanner#setPartialRange(org.eclipse.jface.text.IDocument, int, int, java.lang.String, int)
94    */
95   public void setPartialRange(IDocument document, int offset, int length, String contentType, int partitionOffset) {
96     if (DEBUG) {
97       System.out.println("*****");
98       System.out.println("PartialRange: contentType=" + contentType + " partitionOffset=" + partitionOffset);
99     }
100
101     try {
102       if (partitionOffset > -1) {
103         partitionBorder = false;
104         // because of strings we have to parse the whole partition
105         this.setRange(document, partitionOffset, offset - partitionOffset + length);
106         // sometimes we get a wrong partition so we retrieve the partition
107         // directly from the document
108         fContentType = fDocument.getContentType(partitionOffset);
109       } else
110         this.setRange(document, offset, length);
111
112     } catch (BadLocationException e) {
113       // should never happen
114       // TODO print stack trace to log
115       // fall back just scan the whole document again
116       this.setRange(document, 0, fDocument.getLength());
117     }
118
119   }
120
121   /* (non-Javadoc)
122    * @see org.eclipse.jface.text.rules.ITokenScanner#getTokenLength()
123    */
124   public int getTokenLength() {
125     return fLength;
126   }
127
128   /* (non-Javadoc)
129    * @see org.eclipse.jface.text.rules.ITokenScanner#getTokenOffset()
130    */
131   public int getTokenOffset() {
132     return fTokenOffset;
133   }
134
135   /* (non-Javadoc)
136    * @see org.eclipse.jface.text.rules.ITokenScanner#nextToken()
137    */
138   public IToken nextToken() {
139     int c;
140
141     // check if we are not allready at the end of the
142     // file
143     if ((c = read()) == ICharacterScanner.EOF) {
144       partitionBorder = false;
145       return Token.EOF;
146     } else
147       unread();
148
149     if (partitionBorder) {
150       fTokenOffset = fOffset;
151       partitionBorder = false;
152     }
153
154     while ((c = read()) != ICharacterScanner.EOF) {
155       switch (c) {
156         case '<' :
157           if (!isInString(IPHPPartitionScannerConstants.PHP)
158             && fContentType != IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT
159             && checkPattern(new char[] { '?', 'p', 'h', 'p' }, true)) {
160             if (fContentType != IPHPPartitionScannerConstants.PHP && fCurrentLength > 5) {
161               unread(5);
162               IToken token = getToken(fContentType);
163               // save previouse contenttype
164               //TODO build stack for previouse contenttype 
165               fPrevContentType = fContentType;
166
167               fContentType = IPHPPartitionScannerConstants.PHP;
168
169               return token;
170             } else
171               fContentType = IPHPPartitionScannerConstants.PHP;
172
173             // remember offset of this partition
174             fTokenOffset = fOffset - 5;
175             fCurrentLength = 5;
176           } else if (
177             !isInString(IPHPPartitionScannerConstants.PHP)
178               && fContentType != IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT
179               && checkPattern(new char[] { '?' }, false)) {
180             if (fContentType != IPHPPartitionScannerConstants.PHP && fCurrentLength > 2) {
181               unread(2);
182               IToken token = getToken(fContentType);
183               // save previouse contenttype
184               fPrevContentType = fContentType;
185               fContentType = IPHPPartitionScannerConstants.PHP;
186               return token;
187             } else
188               fContentType = IPHPPartitionScannerConstants.PHP;
189             // remember offset of this partition
190             fTokenOffset = fOffset - 2;
191             fCurrentLength = 2;
192           } else if (
193             !isInString(IPHPPartitionScannerConstants.PHP)
194               && (fContentType != IPHPPartitionScannerConstants.PHP) // BUG #769044
195               && (fContentType != IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT) // BUG #769044
196               && checkPattern(new char[] { '!', '-', '-' })) { // return previouse partition
197             if (fContentType != IPHPPartitionScannerConstants.HTML_MULTILINE_COMMENT && fCurrentLength > 4) {
198               unread(4);
199               IToken token = getToken(fContentType);
200               fContentType = IPHPPartitionScannerConstants.HTML_MULTILINE_COMMENT;
201               return token;
202             } else
203               fContentType = IPHPPartitionScannerConstants.HTML_MULTILINE_COMMENT;
204
205             fTokenOffset = fOffset - 4;
206             fCurrentLength = 4;
207           }
208           break;
209         case '?' :
210           if (!isInString(IPHPPartitionScannerConstants.PHP) && fContentType == IPHPPartitionScannerConstants.PHP) {
211             if ((c = read()) == '>') {
212               if (fPrevContentType != null)
213                 fContentType = fPrevContentType;
214               else
215                 fContentType = IPHPPartitionScannerConstants.HTML;
216               partitionBorder = true;
217               return getToken(IPHPPartitionScannerConstants.PHP);
218             } else if (c != ICharacterScanner.EOF)
219               unread();
220           }
221           break;
222         case '-' :
223           if (!isInString(IPHPPartitionScannerConstants.PHP)
224             && fContentType == IPHPPartitionScannerConstants.HTML_MULTILINE_COMMENT
225             && checkPattern(new char[] { '-', '>' })) {
226             fContentType = IPHPPartitionScannerConstants.HTML;
227             partitionBorder = true;
228             return getToken(IPHPPartitionScannerConstants.HTML_MULTILINE_COMMENT);
229           }
230           break;
231         case '{' : // SMARTY code starts here ?
232           if (fFileType == IPHPPartitionScannerConstants.SMARTY_FILE) {
233             if ((c = read()) == '*') {
234               if (DEBUG) {
235                 System.out.println(
236                   "SMARTYDOC_TOKEN start "
237                     + fTokenOffset
238                     + " fContentType="
239                     + fContentType
240                     + " fLength="
241                     + fLength
242                     + " fOffset="
243                     + fOffset
244                     + " fCurrentLength="
245                     + fCurrentLength);
246               }
247               if (fContentType != IPHPPartitionScannerConstants.SMARTY_MULTILINE_COMMENT && fCurrentLength > 2) {
248                 // SMARTY doc code starts here 
249                 unread(2);
250                 IToken token = getToken(fContentType);
251                 fContentType = IPHPPartitionScannerConstants.SMARTY_MULTILINE_COMMENT;
252                 return token;
253                 //              } else if (fContentType == IPHPPartitionScannerConstants.HTML && fOffset == 2) {
254                 //                fContentType = IPHPPartitionScannerConstants.SMARTY_MULTILINE_COMMENT;
255               } else { // if (fContentType == IPHPPartitionScannerConstants.SMARTY_MULTILINE_COMMENT) {
256                 fContentType = IPHPPartitionScannerConstants.SMARTY_MULTILINE_COMMENT;
257                 fTokenOffset = fOffset - 2;
258                 fCurrentLength = 2;
259               }
260               break;
261             }
262             if (DEBUG) {
263               System.out.println(
264                 "SMARTY_TOKEN start "
265                   + fTokenOffset
266                   + " fContentType="
267                   + fContentType
268                   + " fLength="
269                   + fLength
270                   + " fOffset="
271                   + fOffset);
272             }
273             if (c != ICharacterScanner.EOF) {
274                 unread();
275             }
276             if (fContentType != IPHPPartitionScannerConstants.SMARTY && fCurrentLength > 1) {
277               unread(1);
278               IToken token = getToken(fContentType);
279               fContentType = IPHPPartitionScannerConstants.SMARTY;
280               return token;
281               //            } else if (fContentType == IPHPPartitionScannerConstants.HTML && fOffset==1) {
282               //              fContentType = IPHPPartitionScannerConstants.SMARTY;
283             } else {
284               fContentType = IPHPPartitionScannerConstants.SMARTY;
285               fTokenOffset = fOffset - 1;
286               fCurrentLength = 1;
287             }
288           }
289           break;
290         case '}' : // SMARTY code ends here ?
291           if (fFileType == IPHPPartitionScannerConstants.SMARTY_FILE && fContentType == IPHPPartitionScannerConstants.SMARTY) {
292             if (DEBUG) {
293               System.out.println(
294                 "SMARTY_TOKEN end "
295                   + fTokenOffset
296                   + " fContentType="
297                   + fContentType
298                   + " fLength="
299                   + fLength
300                   + " fOffset="
301                   + fOffset);
302             }
303             fContentType = IPHPPartitionScannerConstants.HTML;
304             partitionBorder = true;
305             return getToken(IPHPPartitionScannerConstants.SMARTY);
306           }
307           break;
308         case '/' :
309           if (!isInString(IPHPPartitionScannerConstants.PHP) && (c = read()) == '*') { // MULTINE COMMENT JAVASCRIPT, CSS, PHP
310             if (fContentType == IPHPPartitionScannerConstants.PHP && fCurrentLength > 2) {
311               unread(2);
312               IToken token = getToken(fContentType);
313               fContentType = IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT;
314               return token;
315             } else if (fContentType == IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT) {
316               fTokenOffset = fOffset - 2;
317               fCurrentLength = 2;
318             }
319
320           } else if (!isInString(IPHPPartitionScannerConstants.PHP) && c != ICharacterScanner.EOF)
321             unread();
322           break;
323         case '*' :
324           if (!isInString(IPHPPartitionScannerConstants.PHP) && (c = read()) == '/') {
325             if (fContentType == IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT) {
326               fContentType = IPHPPartitionScannerConstants.PHP;
327               partitionBorder = true;
328               return getToken(IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT);
329             } else if (fContentType == IPHPPartitionScannerConstants.CSS_MULTILINE_COMMENT) {
330             } else if (fContentType == IPHPPartitionScannerConstants.JS_MULTILINE_COMMENT) {
331             }
332           } else if (fFileType == IPHPPartitionScannerConstants.SMARTY_FILE && (c = read()) == '}') {
333             if (DEBUG) {
334               System.out.println(
335                 "SMARTYDOC_TOKEN end "
336                   + fTokenOffset
337                   + " fContentType="
338                   + fContentType
339                   + " fLength="
340                   + fLength
341                   + " fOffset="
342                   + fOffset);
343             }
344             if (fContentType == IPHPPartitionScannerConstants.SMARTY_MULTILINE_COMMENT) {
345               fContentType = IPHPPartitionScannerConstants.HTML;
346               partitionBorder = true;
347               return getToken(IPHPPartitionScannerConstants.SMARTY_MULTILINE_COMMENT);
348             }
349           } else if (!isInString(IPHPPartitionScannerConstants.PHP) && c != ICharacterScanner.EOF) {
350             unread();
351           }
352           break;
353         case '\'' :
354           if (!fInDoubString)
355             fInString = !fInString;
356           break;
357         case '"' :
358           // toggle String mode
359           if (!fInString)
360             fInDoubString = !fInDoubString;
361           break;
362       }
363     } // end of file reached but we have to return the
364     // last partition.
365     return getToken(fContentType);
366   }
367   /* (non-Javadoc)
368    * @see org.eclipse.jface.text.rules.ITokenScanner#setRange(org.eclipse.jface.text.IDocument, int, int)
369    */
370   public void setRange(IDocument document, int offset, int length) {
371     if (DEBUG) {
372       System.out.println("SET RANGE: offset=" + offset + " length=" + length);
373     }
374
375     fDocument = document;
376     fOffset = offset;
377     fTokenOffset = offset;
378     fCurrentLength = 0;
379     fLength = 0;
380     fEnd = fOffset + length;
381     fInString = false;
382     fInDoubString = false;
383     fContentType = IPHPPartitionScannerConstants.HTML;
384     //        String[] prev = getPartitionStack(offset);
385   }
386
387   private int read() {
388     try {
389       if (fOffset < fEnd) {
390         fCurrentLength++;
391         return fDocument.getChar(fOffset++);
392       }
393       return ICharacterScanner.EOF;
394     } catch (BadLocationException e) {
395       // should never happen
396       // TODO write stacktrace to log
397       fOffset = fEnd;
398       return ICharacterScanner.EOF;
399     }
400   }
401
402   private void unread() {
403     --fOffset;
404     --fCurrentLength;
405   }
406
407   private void unread(int num) {
408     fOffset -= num;
409     fCurrentLength -= num;
410   }
411
412   private boolean checkPattern(char[] pattern) {
413     return checkPattern(pattern, false);
414   }
415
416   /**
417    * Check if next character sequence read from document is equals to 
418    * the provided pattern. Pattern is read from left to right until the 
419    * first character read doesn't match. If this happens all read characters are
420    * unread.
421    * @param pattern The pattern to check.
422    * @return <code>true</code> if pattern is equals else returns <code>false</code>.
423    */
424   private boolean checkPattern(char[] pattern, boolean ignoreCase) {
425     int prevOffset = fOffset;
426     int prevLength = fCurrentLength;
427     for (int i = 0; i < pattern.length; i++) {
428       int c = read();
429
430       if (c == ICharacterScanner.EOF || !letterEquals(c, pattern[i], ignoreCase)) {
431         fOffset = prevOffset;
432         fCurrentLength = prevLength;
433         return false;
434       }
435     }
436
437     return true;
438   }
439
440   private boolean letterEquals(int test, char letter, boolean ignoreCase) {
441     if (test == letter)
442       return true;
443     else if (ignoreCase && Character.isLowerCase(letter) && test == Character.toUpperCase(letter))
444       return true;
445     else if (ignoreCase && Character.isUpperCase(letter) && test == Character.toLowerCase(letter))
446       return true;
447
448     return false;
449   }
450
451   /**
452    * Checks wether the offset is in a <code>String</code> and the specified 
453    * contenttype is the current content type.
454    * Strings are delimited, mutual exclusive, by a " or by a '.
455    * 
456    * @param contentType The contenttype to check.
457    * @return <code>true</code> if the current offset is in a string else 
458    *                    returns false.
459    */
460   private boolean isInString(String contentType) {
461     if (fContentType == contentType)
462       return (fInString || fInDoubString);
463     else
464       return false;
465   }
466
467   /**
468    * Returns the previouse partition stack for the given offset.
469    * 
470    * @param offset The offset to return the previouse partitionstack for.
471    * 
472    * @return The stack as a string array.
473    */
474   private String[] getPartitionStack(int offset) {
475     ArrayList types = new ArrayList();
476     int tmpOffset = 0;
477     try {
478       ITypedRegion region = fDocument.getPartition(offset);
479       tmpOffset = region.getOffset();
480       while (tmpOffset - 1 > 0) {
481         region = fDocument.getPartition(tmpOffset - 1);
482         tmpOffset = region.getOffset();
483         types.add(0, region.getType());
484       }
485     } catch (BadLocationException e) {
486       if (DEBUG) {
487         e.printStackTrace();
488       }
489     }
490
491     String[] retVal = new String[types.size()];
492
493     retVal = (String[]) types.toArray(retVal);
494     return retVal;
495   }
496
497 }