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