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