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