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