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