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