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