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