Bug #1248155: avoid NPE in parser
[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.29 2005-05-15 23:24:41 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.phpeclipse.ui.text.rules.AbstractPartitioner;
19
20 import org.eclipse.jface.text.Assert;
21 import org.eclipse.jface.text.BadLocationException;
22 import org.eclipse.jface.text.IDocument;
23 import org.eclipse.jface.text.rules.ICharacterScanner;
24 import org.eclipse.jface.text.rules.IPartitionTokenScanner;
25 import org.eclipse.jface.text.rules.IToken;
26 import org.eclipse.jface.text.rules.Token;
27
28 /**
29  * 
30  * 
31  * @author Igor Malinin
32  */
33 public class PHPPartitionScanner implements IPartitionTokenScanner {
34   public static final String PHP_SCRIPTING_AREA = "__php_scripting_area ";
35
36   public static final int STATE_DEFAULT = 0;
37
38   //    public static final int STATE_TAG = 1;
39   //    public static final int STATE_SCRIPT = 2;
40
41   private IDocument document;
42
43   private int begin;
44
45   private int end;
46
47   private int offset;
48
49   private int length;
50
51   private int position;
52
53   private int state;
54
55   private Map tokens = new HashMap();
56
57   public PHPPartitionScanner() {
58   }
59
60   /*
61    * @see org.eclipse.jface.text.rules.ITokenScanner#nextToken()
62    */
63   public IToken nextToken() {
64     offset += length;
65
66     /*
67      * switch (state) { case STATE_TAG: return nextTagToken(); }
68      */
69
70     switch (read()) {
71     case ICharacterScanner.EOF:
72       state = STATE_DEFAULT;
73       return getToken(null);
74
75     case '<':
76       switch (read()) {
77       case ICharacterScanner.EOF:
78         state = STATE_DEFAULT;
79         return getToken(null);
80
81       case '?': // <?
82 //        int ch = read();
83 //
84 //        switch (ch) {
85 //        case ICharacterScanner.EOF:
86 //          state = STATE_DEFAULT;
87 //          return getToken(PHP_SCRIPTING_AREA);
88 //        }
89         return scanUntilPHPEndToken(PHP_SCRIPTING_AREA);
90       }
91
92       unread();
93     }
94
95     loop: while (true) {
96       switch (read()) {
97       case ICharacterScanner.EOF:
98         state = STATE_DEFAULT;
99         return getToken(null);
100
101       case '<':
102         switch (read()) {
103         case ICharacterScanner.EOF:
104           state = STATE_DEFAULT;
105           return getToken(null);
106
107         case '?':
108           unread();
109           break;
110
111         case '<':
112           unread();
113
114         default:
115           continue loop;
116         }
117
118         unread();
119
120         state = STATE_DEFAULT;
121         return getToken(null);
122       }
123     }
124   }
125
126   private IToken scanUntilPHPEndToken(String token) {
127     int ch = read();
128     while (true) {
129       switch (ch) {
130       case ICharacterScanner.EOF:
131         state = STATE_DEFAULT;
132         return getToken(token);
133       case '"': // double quoted string
134         // read until end of double quoted string
135         if (!readUntilEscapedDQ()) {
136           state = STATE_DEFAULT;
137           return getToken(token);
138         }
139         break;
140       case '\'': // single quoted string
141         // read until end of single quoted string
142         if (!readUntilEscapedSQ()) {
143           state = STATE_DEFAULT;
144           return getToken(token);
145         }
146         break;
147       case '/': // comment start?
148         ch = read();
149         switch (ch) {
150         case ICharacterScanner.EOF:
151           break;
152         case '/':
153           // read until end of line
154           if (!readSingleLine()) {
155             state = STATE_DEFAULT;
156             return getToken(token);
157           }
158           break;
159         case '*':
160           // read until end of comment
161           if (!readMultiLineComment()) {
162             state = STATE_DEFAULT;
163             return getToken(token);
164           }
165           break;
166         default:
167           continue;
168         }
169         break;
170       case '#': // line comment
171         // read until end of line
172         if (!readSingleLine()) {
173           state = STATE_DEFAULT;
174           return getToken(token);
175         }
176         break;
177       case '?':
178         ch = read();
179         switch (ch) {
180         case ICharacterScanner.EOF:
181         case '>':
182           state = STATE_DEFAULT;
183           return getToken(token);
184
185         case '?':
186           continue;
187         default:
188           continue;
189         }
190       }
191
192       ch = read();
193     }
194   }
195
196   private IToken getToken(String type) {
197     length = position - offset;
198
199     if (length == 0) {
200       return Token.EOF;
201     }
202
203 //    if (length<0) {
204 //      try {
205 //        System.out.println("Length<0:"+document.get(offset,5)+""+length);
206 //      } catch (BadLocationException e) {
207 //        e.printStackTrace();
208 //      }
209 //    }
210     
211     if (type == null) {
212       return Token.UNDEFINED;
213     }
214
215     IToken token = (IToken) tokens.get(type);
216     if (token == null) {
217       token = new Token(type);
218       tokens.put(type, token);
219     }
220
221     return token;
222   }
223
224   private int read() {
225     if (position >= end) {
226       return ICharacterScanner.EOF;
227     }
228
229     try {
230       return document.getChar(position++);
231     } catch (BadLocationException e) {
232       --position;
233       return ICharacterScanner.EOF;
234     }
235   }
236
237   private boolean readUntilEscapedDQ() {
238     // search last double quoted character
239     try {
240       char ch;
241       while (true) {
242         if (position >= end) {
243           return false;
244         }
245         ch = document.getChar(position++);
246         if (ch == '\\') {
247           if (position >= end) {
248             return false;
249           }
250           ch = document.getChar(position++); // ignore escaped character
251         } else if (ch == '"') {
252           return true;
253         }
254       }
255     } catch (BadLocationException e) {
256       --position;
257     }
258     return false;
259   }
260   
261   private boolean readUntilEscapedSQ() {
262     // search last single quoted character
263     try {  
264       char ch;
265       while (true) {
266         if (position >= end) {
267           return false;
268         }
269         ch = document.getChar(position++); 
270         if (ch == '\\') {
271           if (position >= end) {
272             return false;
273           }
274           ch = document.getChar(position++); // ignore escaped character
275         } else if (ch == '\'') {
276           return true;
277         }
278       }
279     } catch (BadLocationException e) {
280       --position;
281     }
282     return false;
283   }
284
285   private boolean readSingleLine() {
286     try {
287       do {
288         if (position >= end) {
289           return false;
290         }
291       } while (document.getChar(position++) != '\n'); 
292       return true;
293     } catch (BadLocationException e) {
294       --position;
295     }
296     return false;
297   }
298
299   private boolean readMultiLineComment() {
300     try {
301       char ch;
302       while (true) {
303         if (position >= end) {
304           return false;
305         }
306         ch = document.getChar(position++);
307         if (ch == '*') {
308           if (position >= end) {
309             return false;
310           }
311           if (document.getChar(position) == '/') {
312             position++;
313             return true;
314           }
315         }
316       }
317     } catch (BadLocationException e) {
318       --position;
319     }
320     return false;
321   }
322
323   private void unread() {
324     --position;
325   }
326
327   /*
328    * @see org.eclipse.jface.text.rules.ITokenScanner#getTokenOffset()
329    */
330   public int getTokenOffset() {
331     if (AbstractPartitioner.DEBUG) {
332       Assert.isTrue(offset >= 0, Integer.toString(offset));
333     }
334     return offset;
335   }
336
337   /*
338    * @see org.eclipse.jface.text.rules.ITokenScanner#getTokenLength()
339    */
340   public int getTokenLength() {
341     return length;
342   }
343
344   /*
345    * @see org.eclipse.jface.text.rules.ITokenScanner#setRange(IDocument, int, int)
346    */
347   public void setRange(IDocument document, int offset, int length) {
348     this.document = document;
349     this.begin = offset;
350     this.end = offset + length;
351
352     this.offset = offset;
353     this.position = offset;
354     this.length = 0;
355   }
356
357   /*
358    * @see org.eclipse.jface.text.rules.IPartitionTokenScanner
359    */
360   public void setPartialRange(IDocument document, int offset, int length, String contentType, int partitionOffset) {
361     state = STATE_DEFAULT;
362     if (partitionOffset > -1) {
363       int delta = offset - partitionOffset;
364       if (delta > 0) {
365         setRange(document, partitionOffset, length + delta);
366         return;
367       }
368     }
369     setRange(document, partitionOffset, length);
370   }
371
372   //    private boolean isContinuationPartition(IDocument document, int offset) {
373   //            try {
374   //                    String type = document.getContentType(offset - 1);
375   //
376   //                    if (type != IDocument.DEFAULT_CONTENT_TYPE) {
377   //                            return true;
378   //                    }
379   //            } catch (BadLocationException e) {}
380   //
381   //            return false;
382   //    }
383 }