2003/06/13 09:19:10
[org.ibex.core.git] / src / org / xwt / js / Parser.java
1 // Copyright 2003 Adam Megacz, see the COPYING file for licensing [GPL]
2 package org.xwt.js;
3
4 // FIXME: line number accuracy
5
6 import org.xwt.util.*;
7 import java.io.*;
8
9 /**
10  *  Parses a stream of lexed tokens into a tree of CompiledFunction's.
11  *
12
13  *  There are three kinds of things we parse: blocks, statements,
14  *  expressions.  Expressions are a special type of statement that
15  *  evaluates to a value (for example, "break" is not an expression,
16  *  but "3+2" is).  AssignmentTargets are a special kind of expression
17  *  that can be 'put' to (for example, "foo()" is not an
18  *  assignmentTarget, but "foo[7]" is). FIXME.
19
20  *
21  *  Technically it would be a better design for this class to build an
22  *  intermediate parse tree and use that to emit bytecode.  Here's the
23  *  tradeoff:
24  *
25  *  Advantages of building a parse tree:
26  *  - easier to apply optimizations
27  *  - would let us handle more sophisticated languages than JavaScript
28  *
29  *  Advantages of leaving out the parse tree
30  *  - faster compilation
31  *  - less load on the garbage collector
32  *  - much simpler code, easier to understand
33  *  - less error-prone
34  *
35  *  Fortunately JS is such a simple language that we can get away with
36  *  the half-assed approach and still produce a working, complete
37  *  compiler.
38  *
39  *  The bytecode language emitted doesn't really cause any appreciable
40  *  semantic loss, and is itself a parseable language very similar to
41  *  Forth or a postfix variant of LISP.  This means that the bytecode
42  *  can be transformed into a parse tree, which can be manipulated.
43  *  So if we ever want to add an optimizer, it could easily be done by
44  *  producing a parse tree from the bytecode, optimizing that tree,
45  *  and then re-emitting the bytecode.  The parse tree node class
46  *  would also be much simpler since the bytecode language has so few
47  *  operators.
48  *
49  *  Actually, the above paragraph is slightly inaccurate -- there are
50  *  places where we push a value and then perform an arbitrary number
51  *  of operations using it before popping it; this doesn't parse well.
52  *  But these cases are clearly marked and easy to change if we do
53  *  need to move to a parse tree format.
54  */
55 class Parser extends Lexer implements ByteCodes {
56
57
58     // Constructors //////////////////////////////////////////////////////
59
60     public Parser(Reader r, String sourceName, int line) throws IOException { super(r, sourceName, line); }
61
62     /** for debugging */
63     public static void main(String[] s) throws Exception {
64         CompiledFunction block = new CompiledFunction("stdin", 0, new InputStreamReader(System.in), null);
65         if (block == null) return;
66         System.out.println(block);
67     }
68
69
70     // Statics ////////////////////////////////////////////////////////////
71
72     static byte[] precedence = new byte[MAX_TOKEN + 1];
73     static boolean[] isRightAssociative = new boolean[MAX_TOKEN + 1];
74     static {
75         isRightAssociative[ASSIGN] = true;
76
77         precedence[ASSIGN] = 1;
78         precedence[HOOK] = 2;
79         precedence[COMMA] = 3;
80         precedence[OR] = precedence[AND] = 4;
81         precedence[GT] = precedence[GE] = 5;
82         precedence[BITOR] = 6;
83         precedence[BITXOR] = 7;
84         precedence[BITAND] = 8;
85         precedence[EQ] = precedence[NE] = 9;
86         precedence[LT] = precedence[LE] = 10;
87         precedence[SHEQ] = precedence[SHNE] = 11;
88         precedence[LSH] = precedence[RSH] = precedence[URSH] = 12;
89         precedence[ADD] = precedence[SUB] = 13;
90         precedence[MUL] = precedence[DIV] = precedence[MOD] = 14;
91         precedence[BITNOT] =  15;
92         precedence[INC] = precedence[DEC] = 16;
93         precedence[LP] = 17;
94         precedence[LB] = 18;
95         precedence[DOT] = 19;
96     }
97
98
99     // Parsing Logic /////////////////////////////////////////////////////////
100
101     /** gets a token and throws an exception if it is not <tt>code</tt> */
102     private void consume(int code) throws IOException {
103         if (getToken() != code) throw new ParserException("expected " + codeToString[code] + ", got " + (op == -1 ? "EOF" : codeToString[op]));
104     }
105
106     /**
107      *  Parse the largest possible expression containing no operators
108      *  of precedence below <tt>minPrecedence</tt> and append the
109      *  bytecodes for that expression to <tt>appendTo</tt>; the
110      *  appended bytecodes MUST grow the stack by exactly one element.
111      */
112     private void startExpr(CompiledFunction appendTo, int minPrecedence) throws IOException {
113         int tok = getToken();
114         CompiledFunction b = appendTo;
115
116         switch (tok) {
117         case -1: throw new ParserException("expected expression");
118
119         // all of these simply push values onto the stack
120         case NUMBER: b.add(line, LITERAL, number); break;
121         case STRING: b.add(line, LITERAL, string); break;
122         case THIS: b.add(line, TOPSCOPE, null); break;
123         case NULL: b.add(line, LITERAL, null); break;
124         case TRUE: case FALSE: b.add(line, LITERAL, new Boolean(tok == TRUE)); break;
125
126         case LB: {
127             b.add(line, ARRAY, new Integer(0));                       // push an array onto the stack
128             int size0 = b.size();
129             int i = 0;
130             if (peekToken() != RB)
131                 while(true) {                                         // iterate over the initialization values
132                     int size = b.size();
133                     if (peekToken() == COMMA || peekToken() == RB)
134                         b.add(line, LITERAL, null);                   // for stuff like [1,,2,]
135                     else
136                         startExpr(b, -1);                             // push the value onto the stack
137                     b.add(line, LITERAL, new Integer(i++));           // push the index in the array to place it into
138                     b.add(line, PUT);                                 // put it into the array
139                     b.add(line, POP);                                 // discard the value remaining on the stack
140                     if (peekToken() == RB) break;
141                     consume(COMMA);
142                 }
143             b.set(size0 - 1, new Integer(i));                         // back at the ARRAY instruction, write the size of the array
144             consume(RB);
145             break;
146         }
147         case SUB: {  // negative literal (like "3 * -1")
148             consume(NUMBER);
149             b.add(line, LITERAL, new Double(number.doubleValue() * -1));
150             break;
151         }
152         case LP: {  // grouping (not calling)
153             startExpr(b, -1);
154             consume(RP);
155             break;
156         }
157         case INC: case DEC: {  // prefix (not postfix)
158             startExpr(b, precedence[tok]);
159             b.set(b.size() - 1, tok, new Boolean(true));    // FIXME, ugly; need startAssignTarget
160             break;
161         }
162         case BANG: case BITNOT: case TYPEOF: {
163             startExpr(b, precedence[tok]);
164             b.add(line, tok);
165             break;
166         }
167         case LC: { // object constructor
168             b.add(line, OBJECT, null);                                           // put an object on the stack
169             if (peekToken() != RC)
170                 while(true) {
171                     if (peekToken() != NAME && peekToken() != STRING)
172                         throw new ParserException("expected NAME or STRING");
173                     getToken();
174                     b.add(line, LITERAL, string);                                // grab the key
175                     consume(COLON);
176                     startExpr(b, -1);                                            // grab the value
177                     b.add(line, PUT);                                            // put the value into the object
178                     b.add(line, POP);                                            // discard the remaining value
179                     if (peekToken() == RC) break;
180                     consume(COMMA);
181                     if (peekToken() == RC) break;                                // we permit {,,} -- I'm not sure if ECMA does
182                 }
183             consume(RC);
184             break;
185         }
186         case NAME: {    // FIXME; this is an lvalue
187             String name = string;
188             if (peekToken() == ASSIGN) {
189                 consume(ASSIGN);
190                 b.add(line, TOPSCOPE);
191                 b.add(line, LITERAL, name);
192                 startExpr(b, minPrecedence);
193                 b.add(line, PUT);
194                 b.add(line, SWAP);
195                 b.add(line, POP);
196             } else {
197                 b.add(line, TOPSCOPE);
198                 b.add(line, LITERAL, name);
199                 b.add(line, GET);
200             }
201             break;
202         }
203         case FUNCTION: {
204             consume(LP);
205             int numArgs = 0;
206             CompiledFunction b2 = new CompiledFunction(sourceName, line, null);    
207             b.add(line, NEWFUNCTION, b2);
208
209             // function prelude; arguments array is already on the stack
210             b2.add(line, TOPSCOPE);                                                // push the scope onto the stack
211             b2.add(line, SWAP);                                                    // swap 'this' and 'arguments'
212
213             b2.add(line, LITERAL, "arguments");                                    // declare arguments (equivalent to 'var arguments;')
214             b2.add(line, DECLARE);
215
216             b2.add(line, LITERAL, "arguments");                                    // set this.arguments and leave the value on the stack
217             b2.add(line, SWAP);
218             b2.add(line, PUT);
219             b2.add(line, SWAP);
220             b2.add(line, POP);
221
222             while(peekToken() != RP) {                              // run through the list of argument names
223                 if (peekToken() == NAME) {
224                     consume(NAME);                                  // a named argument
225                     
226                     b2.add(line, LITERAL, string);                  // declare the name
227                     b2.add(line, DECLARE);
228                     
229                     b2.add(line, LITERAL, new Integer(numArgs));    // retrieve it from the arguments array
230                     b2.add(line, GET_PRESERVE);
231                     b2.add(line, SWAP);
232                     b2.add(line, POP);
233                     
234                     b2.add(line, TOPSCOPE);                         // put it to the current scope
235                     b2.add(line, SWAP);
236                     b2.add(line, LITERAL, string);
237                     b2.add(line, SWAP);
238                     b2.add(line, PUT);
239                     
240                     b2.add(line, POP);                              // clean the stack
241                     b2.add(line, POP);
242                 }
243                 if (peekToken() == RP) break;
244                 consume(COMMA);
245                 numArgs++;
246             }
247             consume(RP);
248
249             b2.add(line, POP);                                      // pop off the arguments array
250
251             parseStatement(b2, null);                               // the function body
252
253             b2.add(line, LITERAL, null);                            // in case we "fall out the bottom", return NULL
254             b2.add(line, RETURN);
255
256             break;
257         }
258         default: throw new ParserException("expected expression, found " + codeToString[tok] + ", which cannot start an expression");
259         }
260
261         // attempt to continue the expression
262         continueExpr(b, minPrecedence);
263     }
264
265     /**
266      *  Assuming that a complete expression has just been parsed,
267      *  <tt>continueExpr</tt> will attempt to extend this expression by
268      *  parsing additional tokens and appending additional bytecodes.
269      *
270      *  No operators with precedence less than <tt>minPrecedence</tt>
271      *  will be parsed.
272      *
273      *  If any bytecodes are appended, they will not alter the stack
274      *  depth.
275      */
276     private void continueExpr(CompiledFunction b, int minPrecedence) throws IOException {
277         if (b == null) throw new Error("got null b; this should never happen");
278         int tok = getToken();
279         if (tok == -1) return;
280         if (minPrecedence != -1 && (precedence[tok] < minPrecedence || (precedence[tok] == minPrecedence && !isRightAssociative[tok]))) {
281             pushBackToken();
282             return;
283         }
284
285         switch (tok) {
286         case ASSIGN_BITOR: case ASSIGN_BITXOR: case ASSIGN_BITAND: case ASSIGN_LSH: case ASSIGN_RSH: case ASSIGN_URSH:
287         case ASSIGN_ADD: case ASSIGN_SUB: case ASSIGN_MUL: case ASSIGN_DIV: case ASSIGN_MOD: {
288             b.set(b.size() - 1, b.GET_PRESERVE, new Boolean(true));  // FIXME should use AssignTarget
289             startExpr(b, precedence[tok - 1]);
290             b.add(line, tok - 1);
291             b.add(line, PUT);
292             b.add(line, SWAP);
293             b.add(line, POP);
294             break;
295         }
296         case INC: case DEC: { // postfix
297             b.set(b.size() - 1, tok, new Boolean(false));   // FIXME use assignmenttarget
298             break;
299         }
300         case LP: {  // invocation (not grouping)
301             int i = 0;
302             while(peekToken() != RP) {
303                 i++;
304                 if (peekToken() != COMMA) {
305                     startExpr(b, -1);
306                     if (peekToken() == RP) break;
307                 }
308                 consume(COMMA);
309             }
310             consume(RP);
311             b.add(line, CALL, new Integer(i));
312             break;
313         }
314         case BITOR: case BITXOR: case BITAND: case SHEQ: case SHNE: case LSH:
315         case RSH: case URSH: case ADD: case MUL: case DIV: case MOD:
316         case GT: case GE: case EQ: case NE: case LT: case LE: case SUB: {
317             startExpr(b, precedence[tok]);
318             b.add(line, tok);
319             break;
320         }
321         case OR: case AND: {
322             b.add(line, tok == AND ? b.JF : b.JT, new Integer(0));                     // test to see if we can short-circuit
323             int size = b.size();
324             startExpr(b, precedence[tok]);                                             // otherwise check the second value
325             b.add(line, JMP, new Integer(2));                                          // leave the second value on the stack and jump to the end
326             b.add(line, LITERAL, tok == AND ? new Boolean(false) : new Boolean(true)); // target of the short-circuit jump is here
327             b.set(size - 1, new Integer(b.size() - size));                             // write the target of the short-circuit jump
328             break;
329         }
330         case DOT: {  // FIXME, assigntarget
331             consume(NAME);
332             String target = string;
333             if (peekToken() == ASSIGN) {
334                 consume(ASSIGN);
335                 b.add(line, LITERAL, target);
336                 startExpr(b, -1);
337                 b.add(line, PUT);
338                 b.add(line, SWAP);
339                 b.add(line, POP);
340             } else {
341                 b.add(line, LITERAL, target);
342                 b.add(line, GET);
343             }
344             break;
345         }
346         case LB: { // subscripting (not array constructor)
347             startExpr(b, -1);
348             consume(RB);
349             if (peekToken() == ASSIGN) { // FIXME: assigntarget
350                 consume(ASSIGN);
351                 startExpr(b, -1);
352                 b.add(line, PUT);
353                 b.add(line, SWAP);
354                 b.add(line, POP);
355             } else {
356                 b.add(line, GET);
357             }
358             break;
359         }
360         case HOOK: {
361             b.add(line, JF, new Integer(0));                      // jump to the if-false expression
362             int size = b.size();
363             startExpr(b, -1);                                     // write the if-true expression
364             b.add(line, JMP, new Integer(0));                     // if true, jump *over* the if-false expression     
365             b.set(size - 1, new Integer(b.size() - size + 1));    // now we know where the target of the jump is
366             consume(COLON);
367             size = b.size();
368             startExpr(b, -1);                                     // write the if-false expression
369             b.set(size - 1, new Integer(b.size() - size + 1));    // this is the end; jump to here
370             break;
371         }
372         default: {
373             pushBackToken();
374             return;
375         }
376         }
377
378         continueExpr(b, minPrecedence);                           // try to continue the expression
379     }
380     
381     /** Parse a block of statements which must be surrounded by LC..RC. */
382     void parseBlock(CompiledFunction b) throws IOException { parseBlock(b, null); }
383     void parseBlock(CompiledFunction b, String label) throws IOException {
384         if (peekToken() == -1) return;
385         else if (peekToken() != LC) parseStatement(b, null);
386         else {
387             consume(LC);
388             while(peekToken() != RC && peekToken() != -1) parseStatement(b, null);
389             consume(RC);
390         }
391     }
392
393     /** Parse a single statement, consuming the RC or SEMI which terminates it. */
394     void parseStatement(CompiledFunction b, String label) throws IOException {
395         int tok = peekToken();
396         if (tok == -1) return;
397         switch(tok = getToken()) {
398             
399         case THROW: case ASSERT: case RETURN: {
400             if (tok == RETURN && peekToken() == SEMI) b.add(line, LITERAL, null);
401             else startExpr(b, -1);
402             b.add(line, tok);
403             consume(SEMI);
404             break;
405         }
406             
407         case BREAK: case CONTINUE: {
408             if (peekToken() == NAME) consume(NAME);
409             b.add(line, tok, string);
410             consume(SEMI);
411             break;
412         }
413             
414         case VAR: {
415             b.add(line, TOPSCOPE);                               // push the current scope
416             while(true) {
417                 consume(NAME);
418                 String name = string;
419                 b.add(line, LITERAL, name);                // push the name to be declared
420                 b.add(line, DECLARE);                      // declare it
421                 if (peekToken() == ASSIGN) {           // if there is an '=' after the variable name
422                     b.add(line, LITERAL, name);            // put the var name back on the stack
423                     consume(ASSIGN);
424                     startExpr(b, -1);
425                     b.add(line, PUT);
426                     b.add(line, POP);
427                 }
428                 if (peekToken() != COMMA) break;
429                 consume(COMMA);
430             }
431             b.add(line, POP);
432             if ((mostRecentlyReadToken != RC || peekToken() == SEMI) && peekToken() != -1) consume(SEMI);
433             break;
434         }
435             
436         case IF: {
437             consume(LP);
438             startExpr(b, -1);
439             consume(RP);
440             
441             b.add(line, JF, new Integer(0));
442             int size = b.size();
443             parseStatement(b, null);
444             
445             if (peekToken() == ELSE) {
446                 consume(ELSE);
447                 b.set(size - 1, new Integer(2 + b.size() - size));
448                 b.add(line, JMP, new Integer(0));
449                 size = b.size();
450                 parseStatement(b, null);
451             }
452             b.set(size - 1, new Integer(1 + b.size() - size));
453             break;
454         }
455             
456         case WHILE: {
457             consume(LP);
458             if (label != null) b.add(line, LABEL, label);
459             b.add(line, LOOP);
460             int size = b.size();
461             b.add(line, POP);
462             startExpr(b, -1);
463             b.add(line, JT, new Integer(2));
464             b.add(line, BREAK);
465             consume(RP);
466             parseStatement(b, null);
467             b.add(line, CONTINUE);                                    // if we fall out of the end, definately continue
468             b.set(size - 1, new Integer(b.size() - size + 1));  // end of the loop
469             break;
470         }
471             
472         case SWITCH: {
473             consume(LP);
474             if (label != null) b.add(line, LABEL, label);
475             b.add(line, LOOP);
476             int size0 = b.size();
477             startExpr(b, -1);
478             consume(RP);
479             consume(LC);
480             while(true)
481                 if (peekToken() == CASE) {
482                     consume(CASE);
483                     b.add(line, DUP);
484                     startExpr(b, -1);
485                     consume(COLON);
486                     b.add(line, EQ);
487                     b.add(line, JF, new Integer(0));
488                     int size = b.size();
489                     while(peekToken() != CASE && peekToken() != DEFAULT && peekToken() != RC) {
490                         int size2 = b.size();
491                         parseStatement(b, null);
492                         if (size2 == b.size()) break;
493                     }
494                     b.set(size - 1, new Integer(1 + b.size() - size));
495                 } else if (peekToken() == DEFAULT) {
496                     consume(DEFAULT);
497                     consume(COLON);
498                     while(peekToken() != CASE && peekToken() != DEFAULT && peekToken() != RC) {
499                         int size2 = b.size();
500                         parseStatement(b, null);
501                         if (size2 == b.size()) break;
502                     }
503                 } else if (peekToken() == RC) {
504                     consume(RC);
505                     b.add(line, BREAK);
506                     break;
507                 } else {
508                     throw new ParserException("expected CASE, DEFAULT, or RC; got " + codeToString[peekToken()]);
509                 }
510             b.add(line, BREAK);
511             b.set(size0 - 1, new Integer(b.size() - size0 + 1));      // end of the loop
512             break;
513         }
514             
515         case DO: {
516             if (label != null) b.add(line, LABEL, label);
517             b.add(line, LOOP);
518             int size = b.size();
519             parseStatement(b, null);
520             consume(WHILE);
521             consume(LP);
522             startExpr(b, -1);
523             b.add(line, JT, new Integer(2));
524             b.add(line, BREAK);
525             b.add(line, CONTINUE);
526             consume(RP);
527             consume(SEMI);
528             b.set(size - 1, new Integer(b.size() - size + 1));      // end of the loop
529             break;
530         }
531             
532         case TRY: {
533             // We deliberately allow you to omit braces in catch{}/finally{} if they are single statements...
534             b.add(line, TRY);
535             int size = b.size();
536             parseBlock(b, null);
537             b.add(line, POP);                                 // pop the TryMarker
538             b.add(line, JMP);                                 // jump forward to the end of the catch block
539             int size2 = b.size();
540             b.set(size - 1, new Integer(b.size() - size + 1));// the TRY argument points at the start of the CATCH block
541             
542             if (peekToken() == CATCH) {
543                 getToken();
544                 consume(LP);
545                 consume(NAME);
546                 consume(RP);
547                 // FIXME, we need an extra scope here
548                 b.add(line, TOPSCOPE);                        // the exception is on top of the stack; put it to the variable
549                 b.add(line, SWAP);
550                 b.add(line, LITERAL);
551                 b.add(line, SWAP);
552                 b.add(line, PUT);
553                 b.add(line, POP);
554                 b.add(line, POP);
555                 parseStatement(b, null);
556             }
557             
558             b.set(size2 - 1, new Integer(b.size() - size2 + 1)); // jump here if no exception was thrown
559             
560             // FIXME: not implemented correctly
561             if (peekToken() == FINALLY) {
562                 consume(FINALLY);
563                 parseStatement(b, null);
564             }
565             break;
566         }
567             
568         case FOR: {
569             consume(LP);
570             
571             tok = getToken();
572             boolean hadVar = false;
573             if (tok == VAR) { hadVar = true; tok = getToken(); }
574             String varName = string;
575             boolean forIn = peekToken() == IN;
576             pushBackToken(tok, varName);
577             
578             if (forIn) {
579                 // FIXME: break needs to work in here
580                 consume(NAME);
581                 consume(IN);
582                 startExpr(b, -1);
583                 b.add(line, PUSHKEYS);
584                 b.add(line, LITERAL, "length");
585                 b.add(line, GET);
586                 consume(RP);
587                 CompiledFunction b2 = new CompiledFunction(sourceName, line, null);
588                     
589                 b.add(line, NEWSCOPE);
590                     
591                 b.add(line, LITERAL, new Integer(1));
592                 b.add(line, SUB);
593                 b.add(line, DUP);
594                 b.add(line, LITERAL, new Integer(0));
595                 b.add(line, LT);
596                 b.add(line, JT, new Integer(7));
597                 b.add(line, GET_PRESERVE);
598                 b.add(line, LITERAL, varName);
599                 b.add(line, LITERAL, varName);
600                 b.add(line, DECLARE);
601                 b.add(line, PUT);
602                 parseStatement(b, null);
603                     
604                 b.add(line, OLDSCOPE);
605                     
606                 break;
607                     
608             } else {
609                 if (hadVar) pushBackToken(VAR, null);
610                 b.add(line, NEWSCOPE);
611                     
612                 parseStatement(b, null);
613                 CompiledFunction e2 = new CompiledFunction(sourceName, line, null);
614                 if (peekToken() != SEMI) {
615                     startExpr(e2, -1);
616                 } else {
617                     e2.add(line, b.LITERAL, Boolean.TRUE);
618                 }
619                 consume(SEMI);
620                 if (label != null) b.add(line, LABEL, label);
621                 b.add(line, LOOP);
622                 int size2 = b.size();
623                     
624                 b.add(line, JT, new Integer(0));
625                 int size = b.size();
626                 if (peekToken() != RP) {
627                     startExpr(b, -1);
628                     b.add(line, POP);
629                 }
630                 b.set(size - 1, new Integer(b.size() - size + 1));
631                 consume(RP);
632                     
633                 b.paste(e2);
634                 b.add(line, JT, new Integer(2));
635                 b.add(line, BREAK);
636                 parseStatement(b, null);
637                 b.add(line, CONTINUE);
638                 b.set(size2 - 1, new Integer(b.size() - size2 + 1));      // end of the loop
639                     
640                 b.add(line, OLDSCOPE);
641                 break;
642             }
643         }
644                 
645         case NAME: {
646             String possiblyTheLabel = string;
647             if (peekToken() == COLON) {
648                 consume(COLON);
649                 label = possiblyTheLabel;
650                 parseStatement(b, label);
651                 break;
652             } else {
653                 pushBackToken(NAME, possiblyTheLabel);
654                 startExpr(b, -1);
655                 b.add(line, POP);
656                 if ((mostRecentlyReadToken != RC || peekToken() == SEMI) && peekToken() != -1) consume(SEMI);
657                 break;
658             }
659         }
660
661         case SEMI: return;
662         case LC: {
663             pushBackToken();
664             parseBlock(b, label);
665             break;
666         }
667
668         default: {
669             pushBackToken();
670             startExpr(b, -1);
671             b.add(line, POP);
672             if ((mostRecentlyReadToken != RC || peekToken() == SEMI) && peekToken() != -1) consume(SEMI);
673             break;
674         }
675         }
676     }
677
678
679     // ParserException //////////////////////////////////////////////////////////////////////
680     
681     private class ParserException extends IOException { public ParserException(String s) { super(sourceName + ":" + line + " " + s); } }
682     
683 }
684