update Lexer
[IRC.git] / Robust / src / Lex / Lexer.java
1 package Lex;
2
3 import java.io.Reader;
4 import java.io.LineNumberReader;
5 import Parse.Sym;
6
7 /* Java lexer.
8  * Copyright (C) 2002 C. Scott Ananian <cananian@alumni.princeton.edu>
9  * This program is released under the terms of the GPL; see the file
10  * COPYING for more details.  There is NO WARRANTY on this code.
11  */
12
13 public class Lexer {
14   LineNumberReader reader;
15   boolean isJava12;
16   boolean isJava14;
17   boolean isJava15;
18   String line = null;
19   int line_pos = 1;
20   int line_num = 0;
21   LineList lineL = new LineList(-line_pos, null); // sentinel for line #0
22   
23   public Lexer(Reader reader) {
24     this.reader = new LineNumberReader(new EscapedUnicodeReader(reader));
25     this.isJava12 = true;
26     this.isJava14 = true;
27   }
28   
29   public java_cup.runtime.Symbol nextToken() throws java.io.IOException {
30     java_cup.runtime.Symbol sym =
31       lookahead==null ? _nextToken() : lookahead.get();
32     last = sym;
33     return sym;
34   }
35   private boolean shouldBePLT() throws java.io.IOException {
36     // look ahead to see if this LT should be changed to a PLT
37     if (last==null || last.sym!=Sym.IDENTIFIER)
38       return false;
39     if (lookahead==null) lookahead = new FIFO(new FIFO.Getter() {
40         java_cup.runtime.Symbol next() throws java.io.IOException
41         { return _nextToken(); }
42       });
43     int i=0;
44     // skip past IDENTIFIER (DOT IDENTIFIER)*
45     if (lookahead.peek(i++).sym != Sym.IDENTIFIER)
46       return false;
47     while (lookahead.peek(i).sym == Sym.DOT) {
48       i++;
49       if (lookahead.peek(i++).sym != Sym.IDENTIFIER)
50         return false;
51     }
52     // skip past (LBRACK RBRACK)*
53     while (lookahead.peek(i).sym == Sym.LBRACK) {
54       i++;
55       if (lookahead.peek(i++).sym != Sym.RBRACK)
56         return false;
57     }
58     // now the next sym has to be one of LT GT COMMA EXTENDS IMPLEMENTS
59     switch(lookahead.peek(i).sym) {
60     default:
61       return false;
62     case Sym.LT:
63     case Sym.GT:
64     case Sym.COMMA:
65     case Sym.EXTENDS:
66       return true;
67     }
68   }
69   private java_cup.runtime.Symbol last = null;
70   private FIFO lookahead = null;
71   public java_cup.runtime.Symbol _nextToken() throws java.io.IOException {
72     /* tokens are:
73      *  Identifiers/Keywords/true/false/null (start with java letter)
74      *  numeric literal (start with number)
75      *  character literal (start with single quote)
76      *  string (start with double quote)
77      *  separator (parens, braces, brackets, semicolon, comma, period)
78      *  operator (equals, plus, minus, etc)
79      *  whitespace
80      *  comment (start with slash)
81      */
82     InputElement ie;
83     int startpos, endpos;
84     do {
85       startpos = lineL.head + line_pos;
86       ie = getInputElement();
87       if (ie instanceof DocumentationComment)
88         comment = ((Comment)ie).getComment();
89     } while (!(ie instanceof Token));
90     endpos = lineL.head + line_pos - 1;
91
92     //System.out.println(ie.toString()); // uncomment to debug lexer.
93     java_cup.runtime.Symbol sym = ((Token)ie).token();
94     // fix up left/right positions.
95     sym.left = startpos; sym.right = endpos;
96     // return token.
97     return sym;
98   }
99   public boolean debug_lex() throws java.io.IOException {
100     InputElement ie = getInputElement();
101     System.out.println(ie);
102     return !(ie instanceof EOF);
103   }
104
105   String comment;
106   public String lastComment() { return comment; }
107   public void clearComment() { comment=""; }
108   
109   InputElement getInputElement() throws java.io.IOException {
110     if (line_num == 0)
111       nextLine();
112     if (line==null)
113       return new EOF();
114     if (line.length()<=line_pos) {      // end of line.
115       nextLine();
116       if (line==null)
117         return new EOF();
118     }
119     
120     switch (line.charAt(line_pos)) {
121
122       // White space:
123     case ' ':   // ASCII SP
124     case '\t':  // ASCII HT
125     case '\f':  // ASCII FF
126     case '\n':  // LineTerminator
127       return new WhiteSpace(consume());
128
129       // EOF character:
130     case '\020': // ASCII SUB
131       consume();
132       return new EOF();
133
134       // Comment prefix:
135     case '/':
136       return getComment();
137
138       // else, a Token
139     default:
140       return getToken();
141     }
142   }
143   // May get Token instead of Comment.
144   InputElement getComment() throws java.io.IOException {
145     String comment;
146     // line.charAt(line_pos+0) is '/'
147     switch (line.charAt(line_pos+1)) {
148     case '/': // EndOfLineComment
149       comment = line.substring(line_pos+2);
150       line_pos = line.length();
151       return new EndOfLineComment(comment);
152     case '*': // TraditionalComment or DocumentationComment
153       line_pos += 2;
154       if (line.charAt(line_pos)=='*') { // DocumentationComment
155         return snarfComment(new DocumentationComment());
156       } else { // TraditionalComment
157         return snarfComment(new TraditionalComment());
158       }
159     default: // it's a token, not a comment.
160       return getToken();
161     }
162   }
163
164   Comment snarfComment(Comment c) throws java.io.IOException {
165     StringBuffer text=new StringBuffer();
166     while(true) { // Grab CommentTail
167       while (line.charAt(line_pos)!='*') { // Add NotStar to comment.
168         int star_pos = line.indexOf('*', line_pos);
169         if (star_pos<0) {
170           text.append(line.substring(line_pos));
171           c.appendLine(text.toString()); text.setLength(0);
172           line_pos = line.length();
173           nextLine();
174           if (line==null) 
175             throw new Error("Unterminated comment at end of file.");
176         } else {
177           text.append(line.substring(line_pos, star_pos));
178           line_pos=star_pos;
179         }
180       }
181       // At this point, line.charAt(line_pos)=='*'
182       // Grab CommentTailStar starting at line_pos+1.
183       if (line.charAt(line_pos+1)=='/') { // safe because line ends with '\n'
184         c.appendLine(text.toString()); line_pos+=2; return c;
185       }
186       text.append(line.charAt(line_pos++)); // add the '*'
187     }
188   }
189
190   Token getToken() {
191     // Tokens are: Identifiers, Keywords, Literals, Separators, Operators.
192     switch (line.charAt(line_pos)) {
193       // Separators: (period is a special case)
194     case '(':
195     case ')':
196     case '{':
197     case '}':
198     case '[':
199     case ']':
200     case ';':
201     case ',':
202       return new Separator(consume());
203
204       // Operators:
205     case '=':
206     case '>':
207     case '<':
208     case '!':
209     case '~':
210     case '?':
211     case ':':
212     case '&':
213     case '|':
214     case '+':
215     case '-':
216     case '*':
217     case '/':
218     case '^':
219     case '%':
220       return getOperator();
221     case '\'':
222       return getCharLiteral();
223     case '\"':
224       return getStringLiteral();
225
226       // a period is a special case:
227     case '.':
228       if (Character.digit(line.charAt(line_pos+1),10)!=-1)
229         return getNumericLiteral();
230       else if (isJava15 &&
231                line.charAt(line_pos+1)=='.' &&
232                line.charAt(line_pos+2)=='.') {
233         consume(); consume(); consume();
234         return new Separator('\u2026'); // unicode ellipsis character.
235       } else return new Separator(consume());
236     default: 
237       break;
238     }
239     if (Character.isJavaIdentifierStart(line.charAt(line_pos)))
240       return getIdentifier();
241     if (Character.isDigit(line.charAt(line_pos)))
242       return getNumericLiteral();
243     throw new Error("Illegal character on line "+line_num);
244   }
245
246   static final String[] keywords = new String[] {
247     "abstract", "assert", "boolean", "break", "byte", "case", "catch", "char",
248     "class", "const", "continue", "default", "do", "double", "else", "enum",
249     "extends", "final", "finally", "float", "for", "goto", "if", 
250     "implements", "import", "instanceof", "int", "interface", "long", 
251     "native", "new", "package", "private", "protected", "public", 
252     "return", "short", "static", "strictfp", "super", "switch",
253     "synchronized", "this", "throw", "throws", "transient", "try", "void",
254     "volatile", "while" };
255   Token getIdentifier() {
256     // Get id string.
257     StringBuffer sb = new StringBuffer().append(consume());
258
259     if (!Character.isJavaIdentifierStart(sb.charAt(0)))
260       throw new Error("Invalid Java Identifier on line "+line_num);
261     while (Character.isJavaIdentifierPart(line.charAt(line_pos)))
262       sb.append(consume());
263     String s = sb.toString();
264     // Now check against boolean literals and null literal.
265     if (s.equals("null")) return new NullLiteral();
266     if (s.equals("true")) return new BooleanLiteral(true);
267     if (s.equals("false")) return new BooleanLiteral(false);
268     // Check against keywords.
269     //  pre-java 1.5 compatibility:
270     if (!isJava15 && s.equals("enum")) return new Identifier(s);
271     //  pre-java 1.4 compatibility:
272     if (!isJava14 && s.equals("assert")) return new Identifier(s);
273     //  pre-java 1.2 compatibility:
274     if (!isJava12 && s.equals("strictfp")) return new Identifier(s);
275     // use binary search.
276     for (int l=0, r=keywords.length; r > l; ) {
277       int x = (l+r)/2, cmp = s.compareTo(keywords[x]);
278       if (cmp < 0) r=x; else l=x+1;
279       if (cmp== 0) return new Keyword(s);
280     }
281     // not a keyword.
282     return new Identifier(s);
283   }
284   NumericLiteral getNumericLiteral() {
285     int i;
286     // leading decimal indicates float.
287     if (line.charAt(line_pos)=='.')
288       return getFloatingPointLiteral();
289     // 0x indicates Hex.
290     if (line.charAt(line_pos)=='0' &&
291         (line.charAt(line_pos+1)=='x' ||
292          line.charAt(line_pos+1)=='X')) {
293       line_pos+=2; return getIntegerLiteral(/*base*/16);
294     }
295     // otherwise scan to first non-numeric
296     for (i=line_pos; Character.digit(line.charAt(i),10)!=-1; )
297       i++;
298     switch(line.charAt(i)) { // discriminate based on first non-numeric
299     case '.':
300     case 'f':
301     case 'F':
302     case 'd':
303     case 'D':
304     case 'e':
305     case 'E':
306       return getFloatingPointLiteral();
307     case 'L':
308     case 'l':
309     default:
310       if (line.charAt(line_pos)=='0')
311         return getIntegerLiteral(/*base*/8);
312       return getIntegerLiteral(/*base*/10);
313     }
314   }
315   NumericLiteral getIntegerLiteral(int radix) {
316     long val=0;
317     while (Character.digit(line.charAt(line_pos),radix)!=-1)
318       val = (val*radix) + Character.digit(consume(),radix);
319     if (line.charAt(line_pos) == 'l' ||
320         line.charAt(line_pos) == 'L') {
321       consume();
322       return new LongLiteral(val);
323     } 
324     // we compare MAX_VALUE against val/2 to allow constants like
325     // 0xFFFF0000 to get past the test. (unsigned long->signed int)
326     if ((val/2) > Integer.MAX_VALUE ||
327          val    < Integer.MIN_VALUE)
328       throw new Error("Constant does not fit in integer on line "+line_num);
329     return new IntegerLiteral((int)val);
330   }
331   NumericLiteral getFloatingPointLiteral() {
332     String rep = getDigits();
333     if (line.charAt(line_pos)=='.')
334       rep+=consume() + getDigits();
335     if (line.charAt(line_pos)=='e' ||
336         line.charAt(line_pos)=='E') {
337       rep+=consume();
338       if (line.charAt(line_pos)=='+' ||
339           line.charAt(line_pos)=='-')
340         rep+=consume();
341       rep+=getDigits();
342     }
343     try {
344       switch (line.charAt(line_pos)) {
345       case 'f':
346       case 'F':
347         consume();
348         return new FloatLiteral(Float.valueOf(rep).floatValue());
349       case 'd':
350       case 'D':
351         consume();
352         /* falls through */
353       default:
354         return new DoubleLiteral(Double.valueOf(rep).doubleValue());
355       }
356     } catch (NumberFormatException e) {
357       throw new Error("Illegal floating-point on line "+line_num+": "+e);
358     }
359   }
360   String getDigits() {
361     StringBuffer sb = new StringBuffer();
362     while (Character.digit(line.charAt(line_pos),10)!=-1)
363       sb.append(consume());
364     return sb.toString();
365   }
366
367   Operator getOperator() {
368     char first = consume();
369     char second= line.charAt(line_pos);
370
371     switch(first) {
372       // single-character operators.
373     case '~':
374     case '?':
375     case ':':
376       return new Operator(new String(new char[] {first}));
377       // doubled operators
378     case '+':
379     case '-':
380     case '&':
381     case '|':
382       if (first==second) 
383         return new Operator(new String(new char[] {first, consume()}));
384     default:
385       break;
386     }
387     // Check for trailing '='
388     if (second=='=')
389         return new Operator(new String(new char[] {first, consume()}));
390
391     // Special-case '<<', '>>' and '>>>'
392     if ((first=='<' && second=='<') || // <<
393         (first=='>' && second=='>')) {  // >>
394       String op = new String(new char[] {first, consume()});
395       if (first=='>' && line.charAt(line_pos)=='>') // >>>
396         op += consume();
397       if (line.charAt(line_pos)=='=') // <<=, >>=, >>>=
398         op += consume();
399       return new Operator(op);
400     }
401
402     // Otherwise return single operator.
403     return new Operator(new String(new char[] {first}));
404   }
405
406   CharacterLiteral getCharLiteral() {
407     char firstquote = consume();
408     char val;
409     switch (line.charAt(line_pos)) {
410     case '\\':
411       val = getEscapeSequence();
412       break;
413     case '\'':
414       throw new Error("Invalid character literal on line "+line_num);
415     case '\n':
416       throw new Error("Invalid character literal on line "+line_num);
417     default:
418       val = consume();
419       break;
420     }
421     char secondquote = consume();
422     if (firstquote != '\'' || secondquote != '\'')
423       throw new Error("Invalid character literal on line "+line_num);
424     return new CharacterLiteral(val);
425   }
426   StringLiteral getStringLiteral() {
427     char openquote = consume();
428     StringBuffer val = new StringBuffer();
429     while (line.charAt(line_pos)!='\"') {
430       switch(line.charAt(line_pos)) {
431       case '\\':
432         val.append(getEscapeSequence());
433         break;
434       case '\n':
435         throw new Error("Invalid string literal on line " + line_num);
436       default:
437         val.append(consume());
438         break;
439       }
440     }
441     char closequote = consume();
442     if (openquote != '\"' || closequote != '\"')
443       throw new Error("Invalid string literal on line " + line_num);
444     
445     return new StringLiteral(val.toString().intern());
446   }
447
448   char getEscapeSequence() {
449     if (consume() != '\\')
450       throw new Error("Invalid escape sequence on line " + line_num);
451     switch(line.charAt(line_pos)) {
452     case 'b':
453       consume(); return '\b';
454     case 't':
455       consume(); return '\t';
456     case 'n':
457       consume(); return '\n';
458     case 'f':
459       consume(); return '\f';
460     case 'r':
461       consume(); return '\r';
462     case '\"':
463       consume(); return '\"';
464     case '\'':
465       consume(); return '\'';
466     case '\\':
467       consume(); return '\\';
468     case '0':
469     case '1':
470     case '2':
471     case '3':
472       return (char) getOctal(3);
473     case '4':
474     case '5':
475     case '6':
476     case '7':
477       return (char) getOctal(2);
478     default:
479       throw new Error("Invalid escape sequence on line " + line_num);
480     }
481   }
482   int getOctal(int maxlength) {
483     int i, val=0;
484     for (i=0; i<maxlength; i++)
485       if (Character.digit(line.charAt(line_pos), 8)!=-1) {
486         val = (8*val) + Character.digit(consume(), 8);
487       } else break;
488     if ((i==0) || (val>0xFF)) // impossible.
489       throw new Error("Invalid octal escape sequence in line " + line_num);
490     return val;
491   }
492
493   char consume() { return line.charAt(line_pos++); }
494   void nextLine() throws java.io.IOException {
495     line=reader.readLine();
496     if (line!=null) line=line+'\n'; 
497     lineL = new LineList(lineL.head+line_pos, lineL); // for error reporting
498     line_pos=0; 
499     line_num++; 
500   }
501
502   // Deal with error messages.
503   public void errorMsg(String msg, java_cup.runtime.Symbol info) {
504     int n=line_num, c=info.left-lineL.head;
505     for (LineList p = lineL; p!=null; p=p.tail, n--)
506         if (p.head<=info.left) { c=info.left-p.head; break; }
507     System.err.println(msg+" at line "+n);
508     num_errors++;
509   }
510   private int num_errors = 0;
511   public int numErrors() { return num_errors; }
512   
513   class LineList {
514     int head;
515     LineList tail;
516     LineList(int head, LineList tail) { this.head = head; this.tail = tail; }
517   }
518 }