Added syntax highlighting for variables in double quoted strings
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpeclipse / phpeditor / php / PHPCodeScanner.java
1 /**********************************************************************
2  Copyright (c) 2000, 2002 IBM Corp. 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 implementation
10  www.phpeclipse.de
11  **********************************************************************/
12 package net.sourceforge.phpeclipse.phpeditor.php;
13
14 import java.util.ArrayList;
15 import java.util.List;
16
17 import net.sourceforge.phpdt.internal.ui.text.AbstractJavaScanner;
18 import net.sourceforge.phpdt.ui.text.IColorManager;
19 import net.sourceforge.phpeclipse.IPreferenceConstants;
20 import net.sourceforge.phpeclipse.phpeditor.PHPSyntaxRdr;
21 import net.sourceforge.phpeclipse.phpeditor.util.PHPWhitespaceDetector;
22 import net.sourceforge.phpeclipse.phpeditor.util.PHPWordDetector;
23
24 import org.eclipse.jface.preference.IPreferenceStore;
25 import org.eclipse.jface.text.BadLocationException;
26 import org.eclipse.jface.text.rules.EndOfLineRule;
27 import org.eclipse.jface.text.rules.ICharacterScanner;
28 import org.eclipse.jface.text.rules.IRule;
29 import org.eclipse.jface.text.rules.IToken;
30 import org.eclipse.jface.text.rules.IWordDetector;
31 import org.eclipse.jface.text.rules.MultiLineRule;
32 import org.eclipse.jface.text.rules.Token;
33 import org.eclipse.jface.text.rules.WhitespaceRule;
34 import org.eclipse.jface.text.rules.WordRule;
35
36 /**
37  * PHP Code Scanner
38  */
39 public class PHPCodeScanner extends AbstractJavaScanner {
40   
41   /**
42    * Rule to detect java operators.
43    * 
44    * @since 3.0
45    */
46   protected class OperatorRule implements IRule {
47
48     /** Java operators */
49     private final char[] PHP_OPERATORS = {
50         ';',
51         '(',
52         ')',
53         '.',
54         '=',
55         '/',
56         '\\',
57         '+',
58         '-',
59         '*',
60         '[',
61         ']',
62         '<',
63         '>',
64         ':',
65         '?',
66         '!',
67         ',',
68         '|',
69         '&',
70         '^',
71         '%',
72         '~',
73         '@' };
74
75     /** Token to return for this rule */
76     private final IToken fToken;
77
78     /** Token to return for braces */
79     private final IToken fTokenBraces;
80
81     /**
82      * Creates a new operator rule.
83      * 
84      * @param token
85      *          Token to use for this rule
86      */
87     public OperatorRule(IToken token, IToken tokenBraces) {
88       fToken = token;
89       fTokenBraces = tokenBraces;
90
91     }
92
93     /**
94      * Is this character an operator character?
95      * 
96      * @param character
97      *          Character to determine whether it is an operator character
98      * @return <code>true</code> iff the character is an operator, <code>false</code> otherwise.
99      */
100     public boolean isOperator(char character) {
101       for (int index = 0; index < PHP_OPERATORS.length; index++) {
102         if (PHP_OPERATORS[index] == character)
103           return true;
104       }
105       return false;
106     }
107
108     /*
109      * @see org.eclipse.jface.text.rules.IRule#evaluate(org.eclipse.jface.text.rules.ICharacterScanner)
110      */
111     public IToken evaluate(ICharacterScanner scanner) {
112
113       int character = scanner.read();
114       if (character == '{' || character == '}') {
115         return fTokenBraces;
116       }
117       if (isOperator((char) character)) {
118         int lastCharacter = character;
119         character = scanner.read();
120         if (!isOperator((char) character)) {
121           scanner.unread();
122           return fToken;
123         }
124         if (checkPHPTag(scanner, lastCharacter, character)) {
125           return Token.UNDEFINED;
126         }
127         do {
128           lastCharacter = character;
129           character = scanner.read();
130           if (checkPHPTag(scanner, lastCharacter, character)) {
131             return fToken;
132           }
133         } while (isOperator((char) character));
134         scanner.unread();
135         return fToken;
136       } else {
137         scanner.unread();
138         return Token.UNDEFINED;
139       }
140     }
141
142     /**
143      * Check if lastCharacter/character are a PHP start or end token ( &lt;? ... ?&gt; )
144      * 
145      * @param scanner
146      * @param lastCharacter
147      * @param character
148      * @return
149      */
150     private boolean checkPHPTag(ICharacterScanner scanner, int lastCharacter, int character) {
151       if (lastCharacter == '<' && character == '?') {
152         scanner.unread();
153         scanner.unread();
154         return true;
155       } else if (lastCharacter == '?' && character == '>') {
156         scanner.unread();
157         scanner.unread();
158         return true;
159       }
160       return false;
161     }
162   }
163
164   protected class SingleQuoteStringRule implements IRule {
165
166     /** Token to return for this rule */
167     private final IToken fToken;
168
169     public SingleQuoteStringRule(IToken token) {
170       fToken = token;
171
172     }
173
174     /*
175      * @see org.eclipse.jface.text.rules.IRule#evaluate(org.eclipse.jface.text.rules.ICharacterScanner)
176      */
177     public IToken evaluate(ICharacterScanner scanner) {
178
179       int character = scanner.read();
180
181       if (character=='\'') {
182        
183         while (true) {
184           character = scanner.read();
185           if (character == '\\') {
186             character = scanner.read();
187           } else if (character == '\'') {
188             return fToken;
189           }
190         } 
191         
192       } else {
193         scanner.unread();
194         return Token.UNDEFINED;
195       }
196     }
197
198   }
199   
200   protected class AccentStringRule implements IRule {
201
202     /** Token to return for this rule */
203     private final IToken fToken;
204
205     public AccentStringRule(IToken token) {
206       fToken = token;
207
208     }
209
210     /*
211      * @see org.eclipse.jface.text.rules.IRule#evaluate(org.eclipse.jface.text.rules.ICharacterScanner)
212      */
213     public IToken evaluate(ICharacterScanner scanner) {
214
215       int character = scanner.read();
216
217       if (character=='`') {
218        
219         while (true) {
220           character = scanner.read();
221           if (character == '\\') {
222             character = scanner.read();
223           } else if (character == '`') {
224             return fToken;
225           }
226         } 
227         
228       } else {
229         scanner.unread();
230         return Token.UNDEFINED;
231       }
232     }
233
234   }
235   
236   protected class DoubleQuoteStringRule implements IRule {
237
238     /** Token to return for this rule */
239     private final IToken fToken;
240
241     public DoubleQuoteStringRule(IToken token) {
242       fToken = token;
243
244     }
245
246     /*
247      * @see org.eclipse.jface.text.rules.IRule#evaluate(org.eclipse.jface.text.rules.ICharacterScanner)
248      */
249     public IToken evaluate(ICharacterScanner scanner) {
250
251       int character = scanner.read();
252
253       if (character=='"') {
254        
255         while (true) {
256           character = scanner.read();
257           if (character == '\\') {
258             character = scanner.read();
259           } else if (character == '"') {
260             return fToken;
261           }
262         } 
263         
264       } else {
265         scanner.unread();
266         return Token.UNDEFINED;
267       }
268     }
269
270   }
271   
272   private class PHPWordRule extends WordRule {
273     private StringBuffer fBuffer = new StringBuffer();
274
275     public PHPWordRule(IWordDetector detector) {
276       super(detector, Token.UNDEFINED);
277     }
278
279     public PHPWordRule(IWordDetector detector, IToken defaultToken) {
280       super(detector, defaultToken);
281     }
282
283     public IToken evaluate(ICharacterScanner scanner) {
284       int c = scanner.read();
285       boolean isVariable = false;
286       if (c == '<') {
287         c = scanner.read();
288         if (c != '?') {
289           scanner.unread();
290           scanner.unread();
291           return Token.UNDEFINED;
292         } else {
293           c = scanner.read();
294           if (c == '=') {  // <?=
295             return getToken(IPreferenceConstants.PHP_TAG);
296           }
297           if (c != 'p' && c != 'P') {
298             scanner.unread();
299             return getToken(IPreferenceConstants.PHP_TAG);
300           } else {
301             c = scanner.read();
302             if (c != 'h' && c != 'H') {
303               scanner.unread();
304               scanner.unread();
305               return getToken(IPreferenceConstants.PHP_TAG);
306             } else {
307               c = scanner.read();
308               if (c != 'p' && c != 'P') {
309                 scanner.unread();
310                 scanner.unread();
311                 scanner.unread();
312                 return getToken(IPreferenceConstants.PHP_TAG);
313               } else {
314                 return getToken(IPreferenceConstants.PHP_TAG);
315               }
316             }
317           }
318         }
319       }
320       if (c == '?') {
321         c = scanner.read();
322         if (c == '>') {
323           return getToken(IPreferenceConstants.PHP_TAG);
324         }
325         scanner.unread();
326         scanner.unread();
327         return Token.UNDEFINED;
328       }
329       if (fDetector.isWordStart((char) c)) {
330         if (c == '$') {
331           isVariable = true;
332         }
333         if (fColumn == UNDEFINED || (fColumn == scanner.getColumn() - 1)) {
334
335           fBuffer.setLength(0);
336           do {
337             fBuffer.append((char) c);
338             c = scanner.read();
339           } while (c != ICharacterScanner.EOF && fDetector.isWordPart((char) c));
340           scanner.unread();
341
342           if (isVariable) {
343             return getToken(IPreferenceConstants.PHP_VARIABLE);
344           }
345           IToken token = (IToken) fWords.get(fBuffer.toString());
346           if (token != null)
347             return token;
348
349           if (fDefaultToken.isUndefined())
350             unreadBuffer(scanner);
351
352           return fDefaultToken;
353         }
354       }
355
356       scanner.unread();
357       return Token.UNDEFINED;
358     }
359   }
360
361   //private PHPColorProvider fColorProvider;
362
363   private static String[] fgTokenProperties = {
364       IPreferenceConstants.PHP_MULTILINE_COMMENT,
365       IPreferenceConstants.PHP_SINGLELINE_COMMENT,
366       IPreferenceConstants.PHP_TAG,
367       IPreferenceConstants.PHP_KEYWORD,
368       IPreferenceConstants.PHP_FUNCTIONNAME,
369       IPreferenceConstants.PHP_VARIABLE,
370       IPreferenceConstants.PHP_STRING_DQ,
371       IPreferenceConstants.PHP_STRING_SQ,
372       IPreferenceConstants.PHP_TYPE,
373       IPreferenceConstants.PHP_CONSTANT,
374       IPreferenceConstants.PHP_DEFAULT,
375       IPreferenceConstants.PHP_OPERATOR,
376       IPreferenceConstants.PHP_BRACE_OPERATOR,
377       IPreferenceConstants.PHP_KEYWORD_RETURN };
378
379   /**
380    * Creates a PHP code scanner
381    */
382   // public PHPCodeScanner(JavaColorManager provider, IPreferenceStore store) {
383   public PHPCodeScanner(IColorManager manager, IPreferenceStore store) {
384     super(manager, store);
385     initialize();
386   }
387
388   /*
389    * @see AbstractJavaScanner#getTokenProperties()
390    */
391   protected String[] getTokenProperties() {
392     return fgTokenProperties;
393   }
394
395   /*
396    * @see AbstractJavaScanner#createRules()
397    */
398   protected List createRules() {
399     List rules = new ArrayList();
400     Token token = getToken(IPreferenceConstants.PHP_SINGLELINE_COMMENT);
401     // Add rule for single line comments.
402     rules.add(new EndOfLineRule("//", token)); //$NON-NLS-1$
403     rules.add(new EndOfLineRule("#", token)); //$NON-NLS-1$
404     // Add rule for strings and character constants.
405 //    token = getToken(IPreferenceConstants.PHP_STRING_SQ);
406 //    rules.add(new SingleQuoteStringRule(token));
407 //    token = getToken(IPreferenceConstants.PHP_STRING_DQ);
408 //    rules.add(new DoubleQuoteStringRule(token));
409     rules.add(new AccentStringRule(token));
410
411     token = getToken(IPreferenceConstants.PHP_MULTILINE_COMMENT);
412     rules.add(new MultiLineRule("/*", "*/", token)); //$NON-NLS-2$ //$NON-NLS-1$
413     // Add generic whitespace rule.
414     rules.add(new WhitespaceRule(new PHPWhitespaceDetector()));
415     // Add word rule for keywords, types, and constants.
416     token = getToken(IPreferenceConstants.PHP_DEFAULT);
417     PHPWordRule wordRule = new PHPWordRule(new PHPWordDetector(), token);
418
419     Token keyword = getToken(IPreferenceConstants.PHP_KEYWORD);
420     Token functionName = getToken(IPreferenceConstants.PHP_FUNCTIONNAME);
421     Token type = getToken(IPreferenceConstants.PHP_TYPE);
422     Token constant = getToken(IPreferenceConstants.PHP_CONSTANT);
423
424     ArrayList buffer = PHPSyntaxRdr.getSyntaxData();
425     //  String strbuffer = null; unused
426     PHPElement elbuffer = null;
427     String name;
428     for (int i = 0; i < buffer.size(); i++) {
429       //    while ((buffer != null)
430       //      && (!buffer.isEmpty()
431       //        && ((elbuffer = (PHPElement) buffer.remove(0)) != null))) {
432       elbuffer = (PHPElement) buffer.get(i);
433       if (elbuffer instanceof PHPKeyword) {
434         name = ((PHPKeyword) elbuffer).getName();
435         if (!name.equals("return")) {
436           wordRule.addWord(name, keyword);
437         }
438       } else if (elbuffer instanceof PHPFunction) {
439         wordRule.addWord(((PHPFunction) elbuffer).getName(), functionName);
440       } else if (elbuffer instanceof PHPType) {
441         wordRule.addWord(elbuffer.getName(), type);
442       } else if (elbuffer instanceof PHPConstant) {
443         wordRule.addWord(elbuffer.getName(), constant);
444       }
445     }
446
447     // Add word rule for keyword 'return'.
448     token = getToken(IPreferenceConstants.PHP_KEYWORD_RETURN);
449     wordRule.addWord("return", token);
450
451     //  Add rule for operators and brackets (at the end !)
452     rules.add(new OperatorRule(getToken(IPreferenceConstants.PHP_OPERATOR), getToken(IPreferenceConstants.PHP_BRACE_OPERATOR)));
453
454     rules.add(wordRule);
455
456     setDefaultReturnToken(getToken(IPreferenceConstants.PHP_DEFAULT));
457     return rules;
458   }
459 }