Can now use the rblock keyword instead of sese keyword to write OoOJava programs...
[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       return new Separator(consume());
211
212       // Operators:
213     case '=':
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       return getOperator();
229
230     case '\'':
231       return getCharLiteral();
232
233     case '\"':
234       return getStringLiteral();
235
236       // a period is a special case:
237     case '.':
238       if (Character.digit(line.charAt(line_pos+1),10)!=-1)
239         return getNumericLiteral();
240       else if (isJava15 &&
241                line.charAt(line_pos+1)=='.' &&
242                line.charAt(line_pos+2)=='.') {
243         consume(); consume(); consume();
244         return new Separator('\u2026'); // unicode ellipsis character.
245       } else return new Separator(consume());
246
247     default:
248       break;
249     }
250     if (Character.isJavaIdentifierStart(line.charAt(line_pos)))
251       return getIdentifier();
252     if (Character.isDigit(line.charAt(line_pos)))
253       return getNumericLiteral();
254     throw new IOException("Illegal character on line "+line_num);
255   }
256
257   static final String[] keywords = new String[] {
258     "abstract", "assert", "atomic", "boolean", "break", "byte", "case", "catch", "char",
259     "class", "const", "continue",
260     "default", "disjoint", "do", "double",
261     "else", "enum",
262     "extends", "external", "final", "finally",
263     "flag", //keyword for failure aware computation
264     "float", "for","getoffset", "global", "goto", "if",
265     //"implements",
266     "import", "instanceof", "int",
267     //"interface",
268     "isavailable",
269     "long",
270     "native", "new", "optional", "package", "private", "protected", "public", 
271     "rblock", "return",
272     "scratch", "sese", "short", "static", "strictfp", "super", "switch", "synchronized",
273     "tag", "task", "taskexit", //keywords for failure aware computation
274     "this", "throw", "throws", "transient", "try", "void",
275     "volatile", "while"
276   };
277   Token getIdentifier() throws java.io.IOException {
278     // Get id string.
279     StringBuffer sb = new StringBuffer().append(consume());
280
281     if (!Character.isJavaIdentifierStart(sb.charAt(0)))
282       throw new IOException("Invalid Java Identifier on line "+line_num);
283     while (Character.isJavaIdentifierPart(line.charAt(line_pos)))
284       sb.append(consume());
285     String s = sb.toString();
286     // Now check against boolean literals and null literal.
287     if (s.equals("null")) return new NullLiteral();
288     if (s.equals("true")) return new BooleanLiteral(true);
289     if (s.equals("false")) return new BooleanLiteral(false);
290     // Check against keywords.
291     //  pre-java 1.5 compatibility:
292     if (!isJava15 && s.equals("enum")) return new Identifier(s);
293     //  pre-java 1.4 compatibility:
294     if (!isJava14 && s.equals("assert")) return new Identifier(s);
295     //  pre-java 1.2 compatibility:
296     if (!isJava12 && s.equals("strictfp")) return new Identifier(s);
297     // use binary search.
298     for (int l=0, r=keywords.length; r > l; ) {
299       int x = (l+r)/2, cmp = s.compareTo(keywords[x]);
300       if (cmp < 0) r=x;else l=x+1;
301       if (cmp== 0) return new Keyword(s);
302     }
303     // not a keyword.
304     return new Identifier(s);
305   }
306   NumericLiteral getNumericLiteral() throws java.io.IOException {
307     int i;
308     // leading decimal indicates float.
309     if (line.charAt(line_pos)=='.')
310       return getFloatingPointLiteral();
311     // 0x indicates Hex.
312     if (line.charAt(line_pos)=='0' &&
313         (line.charAt(line_pos+1)=='x' ||
314          line.charAt(line_pos+1)=='X')) {
315       line_pos+=2; return getIntegerLiteral(/*base*/ 16);
316     }
317     // otherwise scan to first non-numeric
318     for (i=line_pos; Character.digit(line.charAt(i),10)!=-1; )
319       i++;
320     switch(line.charAt(i)) { // discriminate based on first non-numeric
321     case '.':
322     case 'f':
323     case 'F':
324     case 'd':
325     case 'D':
326     case 'e':
327     case 'E':
328       return getFloatingPointLiteral();
329
330     case 'L':
331     case 'l':
332     default:
333       if (line.charAt(line_pos)=='0')
334         return getIntegerLiteral(/*base*/ 8);
335       return getIntegerLiteral(/*base*/ 10);
336     }
337   }
338   NumericLiteral getIntegerLiteral(int radix) throws java.io.IOException {
339     long val=0;
340     while (Character.digit(line.charAt(line_pos),radix)!=-1)
341       val = (val*radix) + Character.digit(consume(),radix);
342     if (line.charAt(line_pos) == 'l' ||
343         line.charAt(line_pos) == 'L') {
344       consume();
345       return new LongLiteral(val);
346     }
347     // we compare MAX_VALUE against val/2 to allow constants like
348     // 0xFFFF0000 to get past the test. (unsigned long->signed int)
349     if ((val/2) > Integer.MAX_VALUE ||
350         val    < Integer.MIN_VALUE)
351       throw new IOException("Constant does not fit in integer on line "+line_num);
352     return new IntegerLiteral((int)val);
353   }
354   NumericLiteral getFloatingPointLiteral() throws java.io.IOException {
355     String rep = getDigits();
356     if (line.charAt(line_pos)=='.')
357       rep+=consume() + getDigits();
358     if (line.charAt(line_pos)=='e' ||
359         line.charAt(line_pos)=='E') {
360       rep+=consume();
361       if (line.charAt(line_pos)=='+' ||
362           line.charAt(line_pos)=='-')
363         rep+=consume();
364       rep+=getDigits();
365     }
366     try {
367       switch (line.charAt(line_pos)) {
368       case 'f':
369       case 'F':
370         consume();
371         return new FloatLiteral(Float.valueOf(rep).floatValue());
372
373       case 'd':
374       case 'D':
375         consume();
376
377         /* falls through */
378       default:
379         return new DoubleLiteral(Double.valueOf(rep).doubleValue());
380       }
381     } catch (NumberFormatException e) {
382       throw new IOException("Illegal floating-point on line "+line_num+": "+e);
383     }
384   }
385   String getDigits() {
386     StringBuffer sb = new StringBuffer();
387     while (Character.digit(line.charAt(line_pos),10)!=-1)
388       sb.append(consume());
389     return sb.toString();
390   }
391
392   Operator getOperator() {
393     char first = consume();
394     char second= line.charAt(line_pos);
395
396     switch(first) {
397       // single-character operators.
398     case '~':
399     case '?':
400     case ':':
401       return new Operator(new String(new char[] {first}));
402
403       // doubled operators
404     case '+':
405     case '-':
406     case '&':
407     case '|':
408       if (first==second)
409         return new Operator(new String(new char[] {first, consume()}));
410
411     default:
412       break;
413     }
414     // Check for trailing '='
415     if (second=='=')
416       return new Operator(new String(new char[] {first, consume()}));
417
418     // Special-case '<<', '>>' and '>>>'
419     if ((first=='<' && second=='<') || // <<
420         (first=='>' && second=='>')) {  // >>
421       String op = new String(new char[] {first, consume()});
422       if (first=='>' && line.charAt(line_pos)=='>') // >>>
423         op += consume();
424       if (line.charAt(line_pos)=='=') // <<=, >>=, >>>=
425         op += consume();
426       return new Operator(op);
427     }
428
429     // Otherwise return single operator.
430     return new Operator(new String(new char[] {first}));
431   }
432
433   CharacterLiteral getCharLiteral() throws java.io.IOException {
434     char firstquote = consume();
435     char val;
436     switch (line.charAt(line_pos)) {
437     case '\\':
438       val = getEscapeSequence();
439       break;
440
441     case '\'':
442       throw new IOException("Invalid character literal on line "+line_num);
443
444     case '\n':
445       throw new IOException("Invalid character literal on line "+line_num);
446
447     default:
448       val = consume();
449       break;
450     }
451     char secondquote = consume();
452     if (firstquote != '\'' || secondquote != '\'')
453       throw new IOException("Invalid character literal on line "+line_num);
454     return new CharacterLiteral(val);
455   }
456   StringLiteral getStringLiteral() throws java.io.IOException {
457     char openquote = consume();
458     StringBuffer val = new StringBuffer();
459     while (line.charAt(line_pos)!='\"') {
460       switch(line.charAt(line_pos)) {
461       case '\\':
462         val.append(getEscapeSequence());
463         break;
464
465       case '\n':
466         throw new IOException("Invalid string literal on line " + line_num);
467
468       default:
469         val.append(consume());
470         break;
471       }
472     }
473     char closequote = consume();
474     if (openquote != '\"' || closequote != '\"')
475       throw new IOException("Invalid string literal on line " + line_num);
476
477     return new StringLiteral(val.toString().intern());
478   }
479
480   char getEscapeSequence() throws java.io.IOException {
481     if (consume() != '\\')
482       throw new IOException("Invalid escape sequence on line " + line_num);
483     switch(line.charAt(line_pos)) {
484     case 'b':
485       consume(); return '\b';
486
487     case 't':
488       consume(); return '\t';
489
490     case 'n':
491       consume(); return '\n';
492
493     case 'f':
494       consume(); return '\f';
495
496     case 'r':
497       consume(); return '\r';
498
499     case '\"':
500       consume(); return '\"';
501
502     case '\'':
503       consume(); return '\'';
504
505     case '\\':
506       consume(); return '\\';
507
508     case '0':
509     case '1':
510     case '2':
511     case '3':
512       return (char) getOctal(3);
513
514     case '4':
515     case '5':
516     case '6':
517     case '7':
518       return (char) getOctal(2);
519
520     default:
521       throw new IOException("Invalid escape sequence on line " + line_num);
522     }
523   }
524   int getOctal(int maxlength) throws java.io.IOException {
525     int i, val=0;
526     for (i=0; i<maxlength; i++)
527       if (Character.digit(line.charAt(line_pos), 8)!=-1) {
528         val = (8*val) + Character.digit(consume(), 8);
529       } else break;
530     if ((i==0) || (val>0xFF)) // impossible.
531       throw new IOException("Invalid octal escape sequence in line " + line_num);
532     return val;
533   }
534
535   char consume() {
536     return line.charAt(line_pos++);
537   }
538   void nextLine() throws java.io.IOException {
539     line=reader.readLine();
540     if (line!=null) line=line+'\n';
541     lineL = new LineList(lineL.head+line_pos, lineL); // for error reporting
542     line_pos=0;
543     line_num++;
544   }
545
546   // Deal with error messages.
547   public void errorMsg(String msg, java_cup.runtime.Symbol info) {
548     int n=line_num, c=info.left-lineL.head;
549     for (LineList p = lineL; p!=null; p=p.tail, n--)
550       if (p.head<=info.left) {
551         c=info.left-p.head; break;
552       }
553     System.err.println(msg+" at line "+n);
554     num_errors++;
555   }
556   private int num_errors = 0;
557   public int numErrors() {
558     return num_errors;
559   }
560
561   class LineList {
562     int head;
563     LineList tail;
564     LineList(int head, LineList tail) {
565       this.head = head; this.tail = tail;
566     }
567   }
568 }