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