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