2003/06/16 06:24:31
[org.ibex.core.git] / src / org / xwt / js / Lexer.java
1 // Derived from org.mozilla.javascript.TokenStream [NPL]
2
3 /**
4  * The contents of this file are subject to the Netscape Public
5  * License Version 1.1 (the "License"); you may not use this file
6  * except in compliance with the License. You may obtain a copy of
7  * the License at http://www.mozilla.org/NPL/
8  *
9  * Software distributed under the License is distributed on an "AS
10  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
11  * implied. See the License for the specific language governing
12  * rights and limitations under the License.
13  *
14  * The Initial Developer of the Original Code is Netscape
15  * Communications Corporation.
16  *
17  * Contributor(s): Roger Lawrence, Mike McCabe
18  */
19
20 // FIXME: mark lots of these methods 'final' so they get inlined
21
22 package org.xwt.js;
23 import java.io.*;
24
25 /** Lexes a stream of characters into a stream of Tokens */
26 class Lexer implements Tokens {
27
28     /** for debugging */
29     public static void main(String[] s) throws Exception {
30         Lexer l = new Lexer(new InputStreamReader(System.in), "stdin", 0);
31         int tok = 0;
32         while((tok = l.getToken()) != -1) System.out.println(codeToString[tok]);
33     }
34
35     /** the token that was just parsed */
36     protected int op;
37  
38    /** the most recently parsed token, <i>regardless of pushbacks</i> */
39     protected int mostRecentlyReadToken;
40
41     /** if the token just parsed was a NUMBER, this is the numeric value */
42     protected Number number = null;
43
44     /** if the token just parsed was a NAME or STRING, this is the string value */
45     protected String string = null;
46
47     /** the line number of the most recently <i>lexed</i> token */
48     private int line = 0;
49
50     /** the line number of the most recently <i>parsed</i> token */
51     protected int parserLine = 0;
52
53     /** the column number of the current token */
54     protected int col = 0;
55
56     /** the name of the source code file being lexed */
57     protected String sourceName;
58
59     private SmartReader in;
60     public Lexer(Reader r, String sourceName, int line) throws IOException {
61         this.sourceName = sourceName;
62         this.line = line;
63         this.parserLine = line;
64         in = new SmartReader(r);
65     }
66
67
68     // Predicates ///////////////////////////////////////////////////////////////////////
69
70     private static boolean isAlpha(int c) { return ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')); }
71     private static boolean isDigit(int c) { return (c >= '0' && c <= '9'); }
72     private static int xDigitToInt(int c) {
73         if ('0' <= c && c <= '9') return c - '0';
74         else if ('a' <= c && c <= 'f') return c - ('a' - 10);
75         else if ('A' <= c && c <= 'F') return c - ('A' - 10);
76         else return -1;
77     }
78
79     
80     // Token Subtype Handlers /////////////////////////////////////////////////////////
81
82     private int getKeyword(String s) throws IOException {
83         char c;
84         switch (s.length()) {
85             case 2: c=s.charAt(1);
86                 if (c=='f') { if (s.charAt(0)=='i') return IF; }
87                 else if (c=='n') { if (s.charAt(0)=='i') return IN; }
88                 else if (c=='o') { if (s.charAt(0)=='d') return DO; }
89                 break;
90             case 3: switch (s.charAt(0)) {
91                 case 'a': if (s.charAt(2)=='d' && s.charAt(1)=='n') return AND; break;
92                 case 'f': if (s.charAt(2)=='r' && s.charAt(1)=='o') return FOR; break;
93                 case 'i': if (s.charAt(2)=='t' && s.charAt(1)=='n') return RESERVED;
94                 case 'n': if (s.charAt(2)=='w' && s.charAt(1)=='e') return RESERVED;
95                 case 't': if (s.charAt(2)=='y' && s.charAt(1)=='r') return TRY; break;
96                 case 'v': if (s.charAt(2)=='r' && s.charAt(1)=='a') return VAR; break;
97                 } break;
98             case 4: switch (s.charAt(0)) {
99                 case 'b': return s.equals("byte") ? RESERVED : -1;
100                 case 'c': c=s.charAt(3);
101                     if (c=='e') { if (s.charAt(2)=='s' && s.charAt(1)=='a') return CASE; }
102                     else if (c=='r') { if (s.charAt(2)=='a' && s.charAt(1)=='h') return RESERVED; }
103                     return -1;
104                 case 'e': c=s.charAt(3);
105                     if (c=='e') { if (s.charAt(2)=='s' && s.charAt(1)=='l') return ELSE; }
106                     else if (c=='m') { if (s.charAt(2)=='u' && s.charAt(1)=='n') return RESERVED; }
107                     return -1;
108                 case 'g': return s.equals("goto") ? RESERVED : -1;
109                 case 'l': return s.equals("long") ? RESERVED : -1;
110                 case 'n': return s.equals("null") ? NULL : -1;
111                 case 't': c=s.charAt(3);
112                     if (c=='e') { if (s.charAt(2)=='u' && s.charAt(1)=='r') return TRUE; }
113                     else if (c=='s') { if (s.charAt(2)=='i' && s.charAt(1)=='h') return THIS; }
114                     return -1;
115                 case 'w': if (s.equals("with")) return RESERVED; else return -1;
116                 case 'v': if (s.equals("void")) return RESERVED; else return -1;
117                 } break;
118             case 5: switch (s.charAt(2)) {
119                 case 'a': return s.equals("class") ? RESERVED : -1;
120                 case 'e': return s.equals("break") ? BREAK : -1;
121                 case 'i': return s.equals("while") ? WHILE : -1;
122                 case 'l': return s.equals("false") ? FALSE : -1;
123                 case 'n': c=s.charAt(0);
124                     if (s.equals("const")) return RESERVED;
125                     else if (s.equals("final")) return RESERVED;
126                     return -1;
127                 case 'o': c=s.charAt(0);
128                     if (c == 'c') return s.equals("float") ? RESERVED : -1;
129                     else if (c=='s') return s.equals("final") ? RESERVED : -1;
130                     break;
131                 case 'p': return s.equals("super") ? RESERVED : -1;
132                 case 'r': return s.equals("throw") ? THROW : -1;
133                 case 't': return s.equals("catch") ? CATCH : -1;
134                 } break;
135             case 6: switch (s.charAt(1)) {
136                 case 'a': return s.equals("class") ? RESERVED : -1;
137                 case 'e': c=s.charAt(0);
138                     if (s.equals("delete")) return RESERVED;
139                     else if (c=='r') return s.equals("return") ? RETURN : -1;
140                     break;
141                 case 'h': return s.equals("throws") ? RESERVED : -1;
142                 case 'o': return s.equals("double") ? RESERVED : -1;
143                 case 's': return s.equals("assert") ? ASSERT : -1;
144                 case 'u': return s.equals("public") ? RESERVED : -1;
145                 case 'w': return s.equals("switch") ? SWITCH : -1;
146                 case 'y': return s.equals("typeof") ? TYPEOF : -1;
147                 } break;
148             case 7: switch (s.charAt(1)) {
149                 case 'a': return s.equals("package") ? RESERVED : -1;
150                 case 'e': return s.equals("default") ? DEFAULT : -1;
151                 case 'i': return s.equals("finally") ? FINALLY : -1;
152                 case 'o': return s.equals("boolean") ? RESERVED : -1;
153                 case 'r': return s.equals("private") ? RESERVED : -1;
154                 case 'x': return s.equals("extends") ? RESERVED : -1;
155                 } break;
156             case 8: switch (s.charAt(0)) {
157                 case 'a': return s.equals("abstract") ? RESERVED : -1;
158                 case 'c': return s.equals("continue") ? CONTINUE : -1;
159                 case 'd': return s.equals("debugger") ? RESERVED : -1;
160                 case 'f': return s.equals("function") ? FUNCTION : -1;
161                 case 'v': return s.equals("volatile") ? RESERVED : -1;
162                 } break;
163             case 9: c=s.charAt(0);
164                 if (c=='i') return s.equals("interface") ? RESERVED : -1;
165                 else if (c=='p') return s.equals("protected") ? RESERVED : -1;
166                 else if (c=='t') return s.equals("transient") ? RESERVED : -1;
167                 break;
168             case 10: c=s.charAt(1);
169                 if (c=='m') return s.equals("implements") ? RESERVED : -1;
170                 else if (c=='n' && s.equals("instanceof")) return RESERVED;
171                 break;
172             case 12: return s.equals("synchronized") ? RESERVED : -1;
173             }
174         return -1;
175     }
176
177     private int getIdentifier(int c) throws IOException {
178         in.startString();
179         while (Character.isJavaIdentifierPart((char)(c = in.read())));
180         in.unread();
181         String str = in.getString();
182         int result = getKeyword(str);
183         if (result == RESERVED) throw new LexerException("The reserved word \"" + str + "\" is not permitted in XWT scripts");
184         if (result != -1) return result;
185         this.string = str.intern();
186         return NAME;
187     }
188     
189     private int getNumber(int c) throws IOException {
190         int base = 10;
191         in.startString();
192         double dval = Double.NaN;
193         long longval = 0;
194         boolean isInteger = true;
195         
196         // figure out what base we're using
197         if (c == '0') {
198             if (Character.toLowerCase((char)(c = in.read())) == 'x') { base = 16; in.startString(); }
199             else if (isDigit(c)) base = 8;
200         }
201         
202         while (0 <= xDigitToInt(c) && !(base < 16 && isAlpha(c))) c = in.read();
203         if (base == 10 && (c == '.' || c == 'e' || c == 'E')) {
204             isInteger = false;
205             if (c == '.') do { c = in.read(); } while (isDigit(c));
206             if (c == 'e' || c == 'E') {
207                 c = in.read();
208                 if (c == '+' || c == '-') c = in.read();
209                 if (!isDigit(c)) throw new LexerException("float listeral did not have an exponent value");
210                 do { c = in.read(); } while (isDigit(c));
211             }
212         }
213         in.unread();
214
215         String numString = in.getString();
216         if (base == 10 && !isInteger) {
217             try { dval = (Double.valueOf(numString)).doubleValue(); }
218             catch (NumberFormatException ex) { throw new LexerException("invalid numeric literal: \"" + numString + "\""); }
219         } else {
220             if (isInteger) {
221                 longval = Long.parseLong(numString, base);
222                 dval = (double)longval;
223             } else {
224                 dval = Double.parseDouble(numString);
225                 longval = (long) dval;
226                 if (longval == dval) isInteger = true;
227             }
228         }
229         
230         if (!isInteger) this.number = new Double(dval);
231         else if (Byte.MIN_VALUE <= longval && longval <= Byte.MAX_VALUE) this.number = new Byte((byte)longval);
232         else if (Short.MIN_VALUE <= longval && longval <= Short.MAX_VALUE) this.number = new Short((short)longval);
233         else if (Integer.MIN_VALUE <= longval && longval <= Integer.MAX_VALUE) this.number = new Integer((int)longval);
234         else this.number = new Double(longval);
235         return NUMBER;
236     }
237     
238     private int getString(int c) throws IOException {
239         StringBuffer stringBuf = null;
240         int quoteChar = c;
241         int val = 0;
242         c = in.read();
243         in.startString(); // start after the first "
244         while(c != quoteChar) {
245             if (c == '\n' || c == -1) throw new LexerException("unterminated string literal");
246             if (c == '\\') {
247                 if (stringBuf == null) {
248                     in.unread();   // Don't include the backslash
249                     stringBuf = new StringBuffer(in.getString());
250                     in.read();
251                 }
252                 switch (c = in.read()) {
253                 case 'b': c = '\b'; break;
254                 case 'f': c = '\f'; break;
255                 case 'n': c = '\n'; break;
256                 case 'r': c = '\r'; break;
257                 case 't': c = '\t'; break;
258                 case 'v': c = '\u000B'; break;
259                 case '\\': c = '\\'; break;
260                 case 'u': {
261                     int v = 0;
262                     for(int i=0; i<4; i++) {
263                         int ci = in.read();
264                         if (!((ci >= '0' && ci <= '9') || (ci >= 'a' && ci <= 'f') || (ci >= 'A' && ci <= 'F')))
265                             throw new LexerException("illegal character '" + ((char)c) + "' in \\u unicode escape sequence");
266                         v = (v << 8) | Integer.parseInt(ci + "", 16);
267                     }
268                     c = (char)v;
269                     break;
270                 }
271                 default:
272                     // just use the character that was escaped
273                     break;
274                 }
275             }
276             if (stringBuf != null) stringBuf.append((char) c);
277             c = in.read();
278         }
279         if (stringBuf != null) this.string = stringBuf.toString().intern();
280         else {
281             in.unread(); // miss the trailing "
282             this.string = in.getString().intern();
283             in.read();
284         }
285         return STRING;
286     }
287
288     public int _getToken() throws IOException {
289         int c;
290         do { c = in.read(); } while (c == '\u0020' || c == '\u0009' || c == '\u000C' || c == '\u000B' || c == '\n' );
291         if (c == -1) return -1;
292         if (c == '\\' || Character.isJavaIdentifierStart((char)c)) return getIdentifier(c);
293         if (isDigit(c) || (c == '.' && isDigit(in.peek()))) return getNumber(c);
294         if (c == '"' || c == '\'') return getString(c);
295         switch (c) {
296         case ';': return SEMI;
297         case '[': return LB;
298         case ']': return RB;
299         case '{': return LC;
300         case '}': return RC;
301         case '(': return LP;
302         case ')': return RP;
303         case ',': return COMMA;
304         case '?': return HOOK;
305         case ':': return COLON;
306         case '.': return DOT;
307         case '|': return in.match('|') ? OR : (in.match('=') ? ASSIGN_BITOR : BITOR);
308         case '^': return in.match('=') ? ASSIGN_BITXOR : BITXOR;
309         case '&': return in.match('&') ? AND : in.match('=') ? ASSIGN_BITAND : BITAND;
310         case '=': return !in.match('=') ? ASSIGN : in.match('=') ? SHEQ : EQ;
311         case '!': return !in.match('=') ? BANG : in.match('=') ? SHNE : NE;
312         case '%': return in.match('=') ? ASSIGN_MOD : MOD;
313         case '~': return BITNOT;
314         case '+': return in.match('=') ? ASSIGN_ADD : in.match('+') ? INC : ADD;
315         case '-': return in.match('=') ? ASSIGN_SUB: in.match('-') ? DEC : SUB;
316         case '*': return in.match('=') ? ASSIGN_MUL : MUL;
317         case '<': return !in.match('<') ? (in.match('=') ? LE : LT) : in.match('=') ? ASSIGN_LSH : LSH;
318         case '>': return !in.match('>') ? (in.match('=') ? GE : GT) :
319             in.match('>') ? (in.match('=') ? ASSIGN_URSH : URSH) : (in.match('=') ? ASSIGN_RSH : RSH);
320         case '/':
321             if (in.match('=')) return ASSIGN_DIV;
322             if (in.match('/')) { while ((c = in.read()) != -1 && c != '\n'); in.unread(); return getToken(); }
323             if (!in.match('*')) return DIV;
324             while ((c = in.read()) != -1 && !(c == '*' && in.match('/'))) {
325                 if (c == '\n' || c != '/' || !in.match('*')) continue;
326                 if (in.match('/')) return getToken();
327                 throw new LexerException("nested comments are not permitted");
328             }
329             if (c == -1) throw new LexerException("unterminated comment");
330             return getToken();  // `goto retry'
331         default: throw new LexerException("illegal character: \'" + ((char)c) + "\'");
332         }
333     }
334
335
336     // SmartReader ////////////////////////////////////////////////////////////////
337
338     /** a Reader that tracks line numbers and can push back tokens */
339     private class SmartReader {
340         PushbackReader reader = null;
341         int lastread = -1;
342
343         public SmartReader(Reader r) { reader = new PushbackReader(r); }
344         public void unread() throws IOException { unread((char)lastread); }
345         public void unread(char c) throws IOException {
346             reader.unread(c);
347             if (accumulator != null) accumulator.setLength(accumulator.length() - 1);
348         }
349         public boolean match(char c) throws IOException { if (peek() == c) { reader.read(); return true; } else return false; }
350         public int peek() throws IOException {
351             int peeked = reader.read();
352             if (peeked != -1) reader.unread((char)peeked);
353             return peeked;
354         }
355         public int read() throws IOException {
356             lastread = reader.read();
357             if (accumulator != null) accumulator.append((char)lastread);
358             if (lastread != '\n' && lastread != '\r') col++;
359             if (lastread == '\n') { parserLine = ++line; col = 0; }
360             return lastread;
361         }
362
363         // FEATURE: could be much more efficient
364         StringBuffer accumulator = null;
365         public void startString() {
366             accumulator = new StringBuffer();
367             accumulator.append((char)lastread);
368         }
369         public String getString() throws IOException {
370             String ret = accumulator.toString();
371             accumulator = null;
372             return ret;
373         }
374     }
375
376
377     // Token PushBack code ////////////////////////////////////////////////////////////
378
379     private int pushBackDepth = 0;
380     private int[] pushBackInts = new int[10];
381     private Object[] pushBackObjects = new Object[10];
382
383     /** push back a token */
384     public void pushBackToken(int op, Object obj) {
385         if (pushBackDepth >= pushBackInts.length - 1) {
386             int[] newInts = new int[pushBackInts.length * 2];
387             System.arraycopy(pushBackInts, 0, newInts, 0, pushBackInts.length);
388             pushBackInts = newInts;
389             Object[] newObjects = new Object[pushBackObjects.length * 2];
390             System.arraycopy(pushBackObjects, 0, newObjects, 0, pushBackObjects.length);
391             pushBackObjects = newObjects;
392         }
393         pushBackInts[pushBackDepth] = op;
394         pushBackObjects[pushBackDepth] = obj;
395         pushBackDepth++;
396     }
397
398     /** push back the most recently read token */
399     public void pushBackToken() { pushBackToken(op, number != null ? (Object)number : (Object)string); }
400
401     /** read a token but leave it in the stream */
402     public int peekToken() throws IOException {
403         int ret = getToken();
404         pushBackToken();
405         return ret;
406     }
407
408     /** read a token */
409     public int getToken() throws IOException {
410         number = null;
411         string = null;
412         if (pushBackDepth == 0) {
413             mostRecentlyReadToken = op;
414             return op = _getToken();
415         }
416         pushBackDepth--;
417         op = pushBackInts[pushBackDepth];
418         if (pushBackObjects[pushBackDepth] != null) {
419             number = pushBackObjects[pushBackDepth] instanceof Number ? (Number)pushBackObjects[pushBackDepth] : null;
420             string = pushBackObjects[pushBackDepth] instanceof String ? (String)pushBackObjects[pushBackDepth] : null;
421         }
422         return op;
423     }
424
425     class LexerException extends IOException {
426         public LexerException(String s) { super(sourceName + ":" + line + "," + col + ": " + s); }
427     }
428 }