KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > google > gwt > dev > js > rhino > Parser


1 /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  *
3  * The contents of this file are subject to the Netscape Public
4  * License Version 1.1 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of
6  * the License at http://www.mozilla.org/NPL/
7  *
8  * Software distributed under the License is distributed on an "AS
9  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr
10  * implied. See the License for the specific language governing
11  * rights and limitations under the License.
12  *
13  * The Original Code is Rhino code, released
14  * May 6, 1999.
15  *
16  * The Initial Developer of the Original Code is Netscape
17  * Communications Corporation. Portions created by Netscape are
18  * Copyright (C) 1997-1999 Netscape Communications Corporation. All
19  * Rights Reserved.
20  *
21  * Contributor(s):
22  * Mike Ang
23  * Mike McCabe
24  *
25  * Alternatively, the contents of this file may be used under the
26  * terms of the GNU Public License (the "GPL"), in which case the
27  * provisions of the GPL are applicable instead of those above.
28  * If you wish to allow use of your version of this file only
29  * under the terms of the GPL and not to allow others to use your
30  * version of this file under the NPL, indicate your decision by
31  * deleting the provisions above and replace them with the notice
32  * and other provisions required by the GPL. If you do not delete
33  * the provisions above, a recipient may use your version of this
34  * file under either the NPL or the GPL.
35  */

36 // Modified by Google
37

38 package com.google.gwt.dev.js.rhino;
39
40 import java.io.IOException JavaDoc;
41
42 /**
43  * This class implements the JavaScript parser.
44  *
45  * It is based on the C source files jsparse.c and jsparse.h in the jsref
46  * package.
47  *
48  * @see TokenStream
49  *
50  * @author Mike McCabe
51  * @author Brendan Eich
52  */

53
54 public class Parser {
55
56   public Parser(IRFactory nf) {
57     this.nf = nf;
58   }
59
60   private void mustMatchToken(TokenStream ts, int toMatch, String JavaDoc messageId)
61       throws IOException JavaDoc, JavaScriptException {
62     int tt;
63     if ((tt = ts.getToken()) != toMatch) {
64       reportError(ts, messageId);
65       ts.ungetToken(tt); // In case the parser decides to continue
66
}
67   }
68
69   private void reportError(TokenStream ts, String JavaDoc messageId)
70       throws JavaScriptException {
71     this.ok = false;
72     ts.reportSyntaxError(messageId, null);
73
74     /*
75      * Throw an exception to unwind the recursive descent parse. We use
76      * JavaScriptException here even though it is really a different use of the
77      * exception than it is usually used for.
78      */

79     throw new JavaScriptException(messageId);
80   }
81
82   /*
83    * Build a parse tree from the given TokenStream.
84    *
85    * @param ts the TokenStream to parse
86    *
87    * @return an Object representing the parsed program. If the parse fails, null
88    * will be returned. (The parse failure will result in a call to the current
89    * Context's ErrorReporter.)
90    */

91   public Object JavaDoc parse(TokenStream ts) throws IOException JavaDoc {
92     this.ok = true;
93     sourceTop = 0;
94     functionNumber = 0;
95
96     int tt; // last token from getToken();
97
int baseLineno = ts.getLineno(); // line number where source starts
98

99     /*
100      * so we have something to add nodes to until we've collected all the source
101      */

102     Object JavaDoc tempBlock = nf.createLeaf(TokenStream.BLOCK);
103     ((Node) tempBlock).setIsSyntheticBlock(true);
104
105     while (true) {
106       ts.flags |= ts.TSF_REGEXP;
107       tt = ts.getToken();
108       ts.flags &= ~ts.TSF_REGEXP;
109
110       if (tt <= ts.EOF) {
111         break;
112       }
113
114       if (tt == ts.FUNCTION) {
115         try {
116           nf.addChildToBack(tempBlock, function(ts, false));
117         } catch (JavaScriptException e) {
118           this.ok = false;
119           break;
120         }
121       } else {
122         ts.ungetToken(tt);
123         nf.addChildToBack(tempBlock, statement(ts));
124       }
125     }
126
127     if (!this.ok) {
128       // XXX ts.clearPushback() call here?
129
return null;
130     }
131
132     Object JavaDoc pn = nf.createScript(tempBlock, ts.getSourceName(), baseLineno, ts
133       .getLineno(), sourceToString(0));
134     ((Node) pn).setIsSyntheticBlock(true);
135     return pn;
136   }
137
138   /*
139    * The C version of this function takes an argument list, which doesn't seem
140    * to be needed for tree generation... it'd only be useful for checking
141    * argument hiding, which I'm not doing anyway...
142    */

143   private Object JavaDoc parseFunctionBody(TokenStream ts) throws IOException JavaDoc {
144     int oldflags = ts.flags;
145     ts.flags &= ~(TokenStream.TSF_RETURN_EXPR | TokenStream.TSF_RETURN_VOID);
146     ts.flags |= TokenStream.TSF_FUNCTION;
147
148     Object JavaDoc pn = nf.createBlock(ts.getLineno());
149     try {
150       int tt;
151       while ((tt = ts.peekToken()) > ts.EOF && tt != ts.RC) {
152         if (tt == TokenStream.FUNCTION) {
153           ts.getToken();
154           nf.addChildToBack(pn, function(ts, false));
155         } else {
156           nf.addChildToBack(pn, statement(ts));
157         }
158       }
159     } catch (JavaScriptException e) {
160       this.ok = false;
161     } finally {
162       // also in finally block:
163
// flushNewLines, clearPushback.
164

165       ts.flags = oldflags;
166     }
167
168     return pn;
169   }
170
171   private Object JavaDoc function(TokenStream ts, boolean isExpr) throws IOException JavaDoc,
172       JavaScriptException {
173     int baseLineno = ts.getLineno(); // line number where source starts
174

175     String JavaDoc name;
176     Object JavaDoc memberExprNode = null;
177     if (ts.matchToken(ts.NAME)) {
178       name = ts.getString();
179       if (!ts.matchToken(ts.LP)) {
180         if (Context.getContext().hasFeature(
181           Context.FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME)) {
182           // Extension to ECMA: if 'function <name>' does not follow
183
// by '(', assume <name> starts memberExpr
184
sourceAddString(ts.NAME, name);
185           Object JavaDoc memberExprHead = nf.createName(name);
186           name = null;
187           memberExprNode = memberExprTail(ts, false, memberExprHead);
188         }
189         mustMatchToken(ts, ts.LP, "msg.no.paren.parms");
190       }
191     } else if (ts.matchToken(ts.LP)) {
192       // Anonymous function
193
name = null;
194     } else {
195       name = null;
196       if (Context.getContext().hasFeature(
197         Context.FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME)) {
198         // Note that memberExpr can not start with '(' like
199
// in (1+2).toString, because 'function (' already
200
// processed as anonymous function
201
memberExprNode = memberExpr(ts, false);
202       }
203       mustMatchToken(ts, ts.LP, "msg.no.paren.parms");
204     }
205
206     if (memberExprNode != null) {
207       // transform 'function' <memberExpr> to <memberExpr> = function
208
// even in the decompilated source
209
sourceAdd((char) ts.ASSIGN);
210       sourceAdd((char) ts.NOP);
211     }
212
213     // save a reference to the function in the enclosing source.
214
sourceAdd((char) ts.FUNCTION);
215     sourceAdd((char) functionNumber);
216     ++functionNumber;
217
218     // Save current source top to restore it on exit not to include
219
// function to parent source
220
int savedSourceTop = sourceTop;
221     int savedFunctionNumber = functionNumber;
222     Object JavaDoc args;
223     Object JavaDoc body;
224     String JavaDoc source;
225     try {
226       functionNumber = 0;
227
228       // FUNCTION as the first token in a Source means it's a function
229
// definition, and not a reference.
230
sourceAdd((char) ts.FUNCTION);
231       if (name != null) {
232         sourceAddString(ts.NAME, name);
233       }
234       sourceAdd((char) ts.LP);
235       args = nf.createLeaf(ts.LP);
236
237       if (!ts.matchToken(ts.GWT)) {
238         boolean first = true;
239         do {
240           if (!first)
241             sourceAdd((char) ts.COMMA);
242           first = false;
243           mustMatchToken(ts, ts.NAME, "msg.no.parm");
244           String JavaDoc s = ts.getString();
245           nf.addChildToBack(args, nf.createName(s));
246
247           sourceAddString(ts.NAME, s);
248         } while (ts.matchToken(ts.COMMA));
249
250         mustMatchToken(ts, ts.GWT, "msg.no.paren.after.parms");
251       }
252       sourceAdd((char) ts.GWT);
253
254       mustMatchToken(ts, ts.LC, "msg.no.brace.body");
255       sourceAdd((char) ts.LC);
256       sourceAdd((char) ts.EOL);
257       body = parseFunctionBody(ts);
258       mustMatchToken(ts, ts.RC, "msg.no.brace.after.body");
259       sourceAdd((char) ts.RC);
260       // skip the last EOL so nested functions work...
261

262       // name might be null;
263
source = sourceToString(savedSourceTop);
264     } finally {
265       sourceTop = savedSourceTop;
266       functionNumber = savedFunctionNumber;
267     }
268
269     Object JavaDoc pn = nf.createFunction(name, args, body, ts.getSourceName(),
270       baseLineno, ts.getLineno(), source, isExpr || memberExprNode != null);
271     if (memberExprNode != null) {
272       pn = nf.createBinary(ts.ASSIGN, ts.NOP, memberExprNode, pn);
273     }
274
275     // Add EOL but only if function is not part of expression, in which
276
// case it gets SEMI + EOL from Statement.
277
if (!isExpr) {
278       if (memberExprNode != null) {
279         // Add ';' to make 'function x.f(){}' and 'x.f = function(){}'
280
// to print the same strings when decompiling
281
sourceAdd((char) ts.SEMI);
282       }
283       sourceAdd((char) ts.EOL);
284       wellTerminated(ts, ts.FUNCTION);
285     }
286
287     return pn;
288   }
289
290   private Object JavaDoc statements(TokenStream ts) throws IOException JavaDoc {
291     Object JavaDoc pn = nf.createBlock(ts.getLineno());
292
293     int tt;
294     while ((tt = ts.peekToken()) > ts.EOF && tt != ts.RC) {
295       nf.addChildToBack(pn, statement(ts));
296     }
297
298     return pn;
299   }
300
301   private Object JavaDoc condition(TokenStream ts) throws IOException JavaDoc,
302       JavaScriptException {
303     Object JavaDoc pn;
304     mustMatchToken(ts, ts.LP, "msg.no.paren.cond");
305     sourceAdd((char) ts.LP);
306     pn = expr(ts, false);
307     mustMatchToken(ts, ts.GWT, "msg.no.paren.after.cond");
308     sourceAdd((char) ts.GWT);
309
310     // there's a check here in jsparse.c that corrects = to ==
311

312     return pn;
313   }
314
315   private boolean wellTerminated(TokenStream ts, int lastExprType)
316       throws IOException JavaDoc, JavaScriptException {
317     int tt = ts.peekTokenSameLine();
318     if (tt == ts.ERROR) {
319       return false;
320     }
321
322     if (tt != ts.EOF && tt != ts.EOL && tt != ts.SEMI && tt != ts.RC) {
323       int version = Context.getContext().getLanguageVersion();
324       if ((tt == ts.FUNCTION || lastExprType == ts.FUNCTION)
325         && (version < Context.VERSION_1_2)) {
326         /*
327          * Checking against version < 1.2 and version >= 1.0 in the above line
328          * breaks old javascript, so we keep it this way for now... XXX warning
329          * needed?
330          */

331         return true;
332       } else {
333         reportError(ts, "msg.no.semi.stmt");
334       }
335     }
336     return true;
337   }
338
339   // match a NAME; return null if no match.
340
private String JavaDoc matchLabel(TokenStream ts) throws IOException JavaDoc,
341       JavaScriptException {
342     int lineno = ts.getLineno();
343
344     String JavaDoc label = null;
345     int tt;
346     tt = ts.peekTokenSameLine();
347     if (tt == ts.NAME) {
348       ts.getToken();
349       label = ts.getString();
350     }
351
352     if (lineno == ts.getLineno())
353       wellTerminated(ts, ts.ERROR);
354
355     return label;
356   }
357
358   private Object JavaDoc statement(TokenStream ts) throws IOException JavaDoc {
359     try {
360       return statementHelper(ts);
361     } catch (JavaScriptException e) {
362       // skip to end of statement
363
int lineno = ts.getLineno();
364       int t;
365       do {
366         t = ts.getToken();
367       } while (t != TokenStream.SEMI && t != TokenStream.EOL
368         && t != TokenStream.EOF && t != TokenStream.ERROR);
369       return nf.createExprStatement(nf.createName("error"), lineno);
370     }
371   }
372
373   /**
374    * Whether the "catch (e: e instanceof Exception) { ... }" syntax is
375    * implemented.
376    */

377
378   private Object JavaDoc statementHelper(TokenStream ts) throws IOException JavaDoc,
379       JavaScriptException {
380     Object JavaDoc pn = null;
381
382     // If skipsemi == true, don't add SEMI + EOL to source at the
383
// end of this statment. For compound statements, IF/FOR etc.
384
boolean skipsemi = false;
385
386     int tt;
387
388     int lastExprType = 0; // For wellTerminated. 0 to avoid warning.
389

390     tt = ts.getToken();
391
392     switch (tt) {
393       case TokenStream.IF: {
394         skipsemi = true;
395
396         sourceAdd((char) ts.IF);
397         int lineno = ts.getLineno();
398         Object JavaDoc cond = condition(ts);
399         sourceAdd((char) ts.LC);
400         sourceAdd((char) ts.EOL);
401         Object JavaDoc ifTrue = statement(ts);
402         Object JavaDoc ifFalse = null;
403         if (ts.matchToken(ts.ELSE)) {
404           sourceAdd((char) ts.RC);
405           sourceAdd((char) ts.ELSE);
406           sourceAdd((char) ts.LC);
407           sourceAdd((char) ts.EOL);
408           ifFalse = statement(ts);
409         }
410         sourceAdd((char) ts.RC);
411         sourceAdd((char) ts.EOL);
412         pn = nf.createIf(cond, ifTrue, ifFalse, lineno);
413         break;
414       }
415
416       case TokenStream.SWITCH: {
417         skipsemi = true;
418
419         sourceAdd((char) ts.SWITCH);
420         pn = nf.createSwitch(ts.getLineno());
421
422         Object JavaDoc cur_case = null; // to kill warning
423
Object JavaDoc case_statements;
424
425         mustMatchToken(ts, ts.LP, "msg.no.paren.switch");
426         sourceAdd((char) ts.LP);
427         nf.addChildToBack(pn, expr(ts, false));
428         mustMatchToken(ts, ts.GWT, "msg.no.paren.after.switch");
429         sourceAdd((char) ts.GWT);
430         mustMatchToken(ts, ts.LC, "msg.no.brace.switch");
431         sourceAdd((char) ts.LC);
432         sourceAdd((char) ts.EOL);
433
434         while ((tt = ts.getToken()) != ts.RC && tt != ts.EOF) {
435           switch (tt) {
436             case TokenStream.CASE:
437               sourceAdd((char) ts.CASE);
438               cur_case = nf.createUnary(ts.CASE, expr(ts, false));
439               sourceAdd((char) ts.COLON);
440               sourceAdd((char) ts.EOL);
441               break;
442
443             case TokenStream.DEFAULT:
444               cur_case = nf.createLeaf(ts.DEFAULT);
445               sourceAdd((char) ts.DEFAULT);
446               sourceAdd((char) ts.COLON);
447               sourceAdd((char) ts.EOL);
448               // XXX check that there isn't more than one default
449
break;
450
451             default:
452               reportError(ts, "msg.bad.switch");
453               break;
454           }
455           mustMatchToken(ts, ts.COLON, "msg.no.colon.case");
456
457           case_statements = nf.createLeaf(TokenStream.BLOCK);
458           ((Node) case_statements).setIsSyntheticBlock(true);
459
460           while ((tt = ts.peekToken()) != ts.RC && tt != ts.CASE
461             && tt != ts.DEFAULT && tt != ts.EOF) {
462             nf.addChildToBack(case_statements, statement(ts));
463           }
464           // assert cur_case
465
nf.addChildToBack(cur_case, case_statements);
466
467           nf.addChildToBack(pn, cur_case);
468         }
469         sourceAdd((char) ts.RC);
470         sourceAdd((char) ts.EOL);
471         break;
472       }
473
474       case TokenStream.WHILE: {
475         skipsemi = true;
476
477         sourceAdd((char) ts.WHILE);
478         int lineno = ts.getLineno();
479         Object JavaDoc cond = condition(ts);
480         sourceAdd((char) ts.LC);
481         sourceAdd((char) ts.EOL);
482         Object JavaDoc body = statement(ts);
483         sourceAdd((char) ts.RC);
484         sourceAdd((char) ts.EOL);
485
486         pn = nf.createWhile(cond, body, lineno);
487         break;
488
489       }
490
491       case TokenStream.DO: {
492         sourceAdd((char) ts.DO);
493         sourceAdd((char) ts.LC);
494         sourceAdd((char) ts.EOL);
495
496         int lineno = ts.getLineno();
497
498         Object JavaDoc body = statement(ts);
499
500         sourceAdd((char) ts.RC);
501         mustMatchToken(ts, ts.WHILE, "msg.no.while.do");
502         sourceAdd((char) ts.WHILE);
503         Object JavaDoc cond = condition(ts);
504
505         pn = nf.createDoWhile(body, cond, lineno);
506         break;
507       }
508
509       case TokenStream.FOR: {
510         skipsemi = true;
511
512         sourceAdd((char) ts.FOR);
513         int lineno = ts.getLineno();
514
515         Object JavaDoc init; // Node init is also foo in 'foo in Object'
516
Object JavaDoc cond; // Node cond is also object in 'foo in Object'
517
Object JavaDoc incr = null; // to kill warning
518
Object JavaDoc body;
519
520         mustMatchToken(ts, ts.LP, "msg.no.paren.for");
521         sourceAdd((char) ts.LP);
522         tt = ts.peekToken();
523         if (tt == ts.SEMI) {
524           init = nf.createLeaf(ts.VOID);
525         } else {
526           if (tt == ts.VAR) {
527             // set init to a var list or initial
528
ts.getToken(); // throw away the 'var' token
529
init = variables(ts, true);
530           } else {
531             init = expr(ts, true);
532           }
533         }
534
535         tt = ts.peekToken();
536         if (tt == ts.RELOP && ts.getOp() == ts.IN) {
537           ts.matchToken(ts.RELOP);
538           sourceAdd((char) ts.IN);
539           // 'cond' is the object over which we're iterating
540
cond = expr(ts, false);
541         } else { // ordinary for loop
542
mustMatchToken(ts, ts.SEMI, "msg.no.semi.for");
543           sourceAdd((char) ts.SEMI);
544           if (ts.peekToken() == ts.SEMI) {
545             // no loop condition
546
cond = nf.createLeaf(ts.VOID);
547           } else {
548             cond = expr(ts, false);
549           }
550
551           mustMatchToken(ts, ts.SEMI, "msg.no.semi.for.cond");
552           sourceAdd((char) ts.SEMI);
553           if (ts.peekToken() == ts.GWT) {
554             incr = nf.createLeaf(ts.VOID);
555           } else {
556             incr = expr(ts, false);
557           }
558         }
559
560         mustMatchToken(ts, ts.GWT, "msg.no.paren.for.ctrl");
561         sourceAdd((char) ts.GWT);
562         sourceAdd((char) ts.LC);
563         sourceAdd((char) ts.EOL);
564         body = statement(ts);
565         sourceAdd((char) ts.RC);
566         sourceAdd((char) ts.EOL);
567
568         if (incr == null) {
569           // cond could be null if 'in obj' got eaten by the init node.
570
pn = nf.createForIn(init, cond, body, lineno);
571         } else {
572           pn = nf.createFor(init, cond, incr, body, lineno);
573         }
574         break;
575       }
576
577       case TokenStream.TRY: {
578         int lineno = ts.getLineno();
579
580         Object JavaDoc tryblock;
581         Object JavaDoc catchblocks = null;
582         Object JavaDoc finallyblock = null;
583
584         skipsemi = true;
585         sourceAdd((char) ts.TRY);
586         sourceAdd((char) ts.LC);
587         sourceAdd((char) ts.EOL);
588         tryblock = statement(ts);
589         sourceAdd((char) ts.RC);
590         sourceAdd((char) ts.EOL);
591
592         catchblocks = nf.createLeaf(TokenStream.BLOCK);
593
594         boolean sawDefaultCatch = false;
595         int peek = ts.peekToken();
596         if (peek == ts.CATCH) {
597           while (ts.matchToken(ts.CATCH)) {
598             if (sawDefaultCatch) {
599               reportError(ts, "msg.catch.unreachable");
600             }
601             sourceAdd((char) ts.CATCH);
602             mustMatchToken(ts, ts.LP, "msg.no.paren.catch");
603             sourceAdd((char) ts.LP);
604
605             mustMatchToken(ts, ts.NAME, "msg.bad.catchcond");
606             String JavaDoc varName = ts.getString();
607             sourceAddString(ts.NAME, varName);
608
609             Object JavaDoc catchCond = null;
610             if (ts.matchToken(ts.IF)) {
611               sourceAdd((char) ts.IF);
612               catchCond = expr(ts, false);
613             } else {
614               sawDefaultCatch = true;
615             }
616
617             mustMatchToken(ts, ts.GWT, "msg.bad.catchcond");
618             sourceAdd((char) ts.GWT);
619             mustMatchToken(ts, ts.LC, "msg.no.brace.catchblock");
620             sourceAdd((char) ts.LC);
621             sourceAdd((char) ts.EOL);
622
623             nf.addChildToBack(catchblocks, nf.createCatch(varName, catchCond,
624               statements(ts), ts.getLineno()));
625
626             mustMatchToken(ts, ts.RC, "msg.no.brace.after.body");
627             sourceAdd((char) ts.RC);
628             sourceAdd((char) ts.EOL);
629           }
630         } else if (peek != ts.FINALLY) {
631           mustMatchToken(ts, ts.FINALLY, "msg.try.no.catchfinally");
632         }
633
634         if (ts.matchToken(ts.FINALLY)) {
635           sourceAdd((char) ts.FINALLY);
636
637           sourceAdd((char) ts.LC);
638           sourceAdd((char) ts.EOL);
639           finallyblock = statement(ts);
640           sourceAdd((char) ts.RC);
641           sourceAdd((char) ts.EOL);
642         }
643
644         pn = nf.createTryCatchFinally(tryblock, catchblocks, finallyblock,
645           lineno);
646
647         break;
648       }
649       case TokenStream.THROW: {
650         int lineno = ts.getLineno();
651         sourceAdd((char) ts.THROW);
652         pn = nf.createThrow(expr(ts, false), lineno);
653         if (lineno == ts.getLineno())
654           wellTerminated(ts, ts.ERROR);
655         break;
656       }
657       case TokenStream.BREAK: {
658         int lineno = ts.getLineno();
659
660         sourceAdd((char) ts.BREAK);
661
662         // matchLabel only matches if there is one
663
String JavaDoc label = matchLabel(ts);
664         if (label != null) {
665           sourceAddString(ts.NAME, label);
666         }
667         pn = nf.createBreak(label, lineno);
668         break;
669       }
670       case TokenStream.CONTINUE: {
671         int lineno = ts.getLineno();
672
673         sourceAdd((char) ts.CONTINUE);
674
675         // matchLabel only matches if there is one
676
String JavaDoc label = matchLabel(ts);
677         if (label != null) {
678           sourceAddString(ts.NAME, label);
679         }
680         pn = nf.createContinue(label, lineno);
681         break;
682       }
683       case TokenStream.DEBUGGER: {
684         int lineno = ts.getLineno();
685
686         sourceAdd((char) ts.DEBUGGER);
687
688         pn = nf.createDebugger(lineno);
689         break;
690       }
691       case TokenStream.WITH: {
692         // bruce: we don't support this is JSNI code because it's impossible
693
// to identify bindings even passably well
694
//
695

696         reportError(ts, "msg.jsni.unsupported.with");
697
698         skipsemi = true;
699
700         sourceAdd((char) ts.WITH);
701         int lineno = ts.getLineno();
702         mustMatchToken(ts, ts.LP, "msg.no.paren.with");
703         sourceAdd((char) ts.LP);
704         Object JavaDoc obj = expr(ts, false);
705         mustMatchToken(ts, ts.GWT, "msg.no.paren.after.with");
706         sourceAdd((char) ts.GWT);
707         sourceAdd((char) ts.LC);
708         sourceAdd((char) ts.EOL);
709
710         Object JavaDoc body = statement(ts);
711
712         sourceAdd((char) ts.RC);
713         sourceAdd((char) ts.EOL);
714
715         pn = nf.createWith(obj, body, lineno);
716         break;
717       }
718       case TokenStream.VAR: {
719         int lineno = ts.getLineno();
720         pn = variables(ts, false);
721         if (ts.getLineno() == lineno)
722           wellTerminated(ts, ts.ERROR);
723         break;
724       }
725       case TokenStream.RETURN: {
726         Object JavaDoc retExpr = null;
727         int lineno = 0;
728
729         sourceAdd((char) ts.RETURN);
730
731         // bail if we're not in a (toplevel) function
732
if ((ts.flags & ts.TSF_FUNCTION) == 0)
733           reportError(ts, "msg.bad.return");
734
735         /* This is ugly, but we don't want to require a semicolon. */
736         ts.flags |= ts.TSF_REGEXP;
737         tt = ts.peekTokenSameLine();
738         ts.flags &= ~ts.TSF_REGEXP;
739
740         if (tt != ts.EOF && tt != ts.EOL && tt != ts.SEMI && tt != ts.RC) {
741           lineno = ts.getLineno();
742           retExpr = expr(ts, false);
743           if (ts.getLineno() == lineno)
744             wellTerminated(ts, ts.ERROR);
745           ts.flags |= ts.TSF_RETURN_EXPR;
746         } else {
747           ts.flags |= ts.TSF_RETURN_VOID;
748         }
749
750         // XXX ASSERT pn
751
pn = nf.createReturn(retExpr, lineno);
752         break;
753       }
754       case TokenStream.LC:
755         skipsemi = true;
756
757         pn = statements(ts);
758         mustMatchToken(ts, ts.RC, "msg.no.brace.block");
759         break;
760
761       case TokenStream.ERROR:
762       // Fall thru, to have a node for error recovery to work on
763
case TokenStream.EOL:
764       case TokenStream.SEMI:
765         pn = nf.createLeaf(ts.VOID);
766         skipsemi = true;
767         break;
768
769       default: {
770         lastExprType = tt;
771         int tokenno = ts.getTokenno();
772         ts.ungetToken(tt);
773         int lineno = ts.getLineno();
774
775         pn = expr(ts, false);
776
777         if (ts.peekToken() == ts.COLON) {
778           /*
779            * check that the last thing the tokenizer returned was a NAME and
780            * that only one token was consumed.
781            */

782           if (lastExprType != ts.NAME || (ts.getTokenno() != tokenno))
783             reportError(ts, "msg.bad.label");
784
785           ts.getToken(); // eat the COLON
786

787           /*
788            * in the C source, the label is associated with the statement that
789            * follows: nf.addChildToBack(pn, statement(ts));
790            */

791           String JavaDoc name = ts.getString();
792           pn = nf.createLabel(name, lineno);
793
794           // bruce: added to make it easier to bind labels to the
795
// statements they modify
796
//
797
nf.addChildToBack(pn, statement(ts));
798
799           // depend on decompiling lookahead to guess that that
800
// last name was a label.
801
sourceAdd((char) ts.COLON);
802           sourceAdd((char) ts.EOL);
803           return pn;
804         }
805
806         if (lastExprType == ts.FUNCTION) {
807           if (nf.getLeafType(pn) != ts.FUNCTION) {
808             reportError(ts, "msg.syntax");
809           }
810         }
811
812         pn = nf.createExprStatement(pn, lineno);
813
814         /*
815          * Check explicitly against (multi-line) function statement.
816          *
817          * lastExprEndLine is a hack to fix an automatic semicolon insertion
818          * problem with function expressions; the ts.getLineno() == lineno check
819          * was firing after a function definition even though the next statement
820          * was on a new line, because speculative getToken calls advanced the
821          * line number even when they didn't succeed.
822          */

823         if (ts.getLineno() == lineno
824           || (lastExprType == ts.FUNCTION && ts.getLineno() == lastExprEndLine)) {
825           wellTerminated(ts, lastExprType);
826         }
827         break;
828       }
829     }
830     ts.matchToken(ts.SEMI);
831     if (!skipsemi) {
832       sourceAdd((char) ts.SEMI);
833       sourceAdd((char) ts.EOL);
834     }
835
836     return pn;
837   }
838
839   private Object JavaDoc variables(TokenStream ts, boolean inForInit)
840       throws IOException JavaDoc, JavaScriptException {
841     Object JavaDoc pn = nf.createVariables(ts.getLineno());
842     boolean first = true;
843
844     sourceAdd((char) ts.VAR);
845
846     for (;;) {
847       Object JavaDoc name;
848       Object JavaDoc init;
849       mustMatchToken(ts, ts.NAME, "msg.bad.var");
850       String JavaDoc s = ts.getString();
851
852       if (!first)
853         sourceAdd((char) ts.COMMA);
854       first = false;
855
856       sourceAddString(ts.NAME, s);
857       name = nf.createName(s);
858
859       // omitted check for argument hiding
860

861       if (ts.matchToken(ts.ASSIGN)) {
862         if (ts.getOp() != ts.NOP)
863           reportError(ts, "msg.bad.var.init");
864
865         sourceAdd((char) ts.ASSIGN);
866         sourceAdd((char) ts.NOP);
867
868         init = assignExpr(ts, inForInit);
869         nf.addChildToBack(name, init);
870       }
871       nf.addChildToBack(pn, name);
872       if (!ts.matchToken(ts.COMMA))
873         break;
874     }
875     return pn;
876   }
877
878   private Object JavaDoc expr(TokenStream ts, boolean inForInit) throws IOException JavaDoc,
879       JavaScriptException {
880     Object JavaDoc pn = assignExpr(ts, inForInit);
881     while (ts.matchToken(ts.COMMA)) {
882       sourceAdd((char) ts.COMMA);
883       pn = nf.createBinary(ts.COMMA, pn, assignExpr(ts, inForInit));
884     }
885     return pn;
886   }
887
888   private Object JavaDoc assignExpr(TokenStream ts, boolean inForInit)
889       throws IOException JavaDoc, JavaScriptException {
890     Object JavaDoc pn = condExpr(ts, inForInit);
891
892     if (ts.matchToken(ts.ASSIGN)) {
893       // omitted: "invalid assignment left-hand side" check.
894
sourceAdd((char) ts.ASSIGN);
895       sourceAdd((char) ts.getOp());
896       pn = nf
897         .createBinary(ts.ASSIGN, ts.getOp(), pn, assignExpr(ts, inForInit));
898     }
899
900     return pn;
901   }
902
903   private Object JavaDoc condExpr(TokenStream ts, boolean inForInit)
904       throws IOException JavaDoc, JavaScriptException {
905     Object JavaDoc ifTrue;
906     Object JavaDoc ifFalse;
907
908     Object JavaDoc pn = orExpr(ts, inForInit);
909
910     if (ts.matchToken(ts.HOOK)) {
911       sourceAdd((char) ts.HOOK);
912       ifTrue = assignExpr(ts, false);
913       mustMatchToken(ts, ts.COLON, "msg.no.colon.cond");
914       sourceAdd((char) ts.COLON);
915       ifFalse = assignExpr(ts, inForInit);
916       return nf.createTernary(pn, ifTrue, ifFalse);
917     }
918
919     return pn;
920   }
921
922   private Object JavaDoc orExpr(TokenStream ts, boolean inForInit) throws IOException JavaDoc,
923       JavaScriptException {
924     Object JavaDoc pn = andExpr(ts, inForInit);
925     if (ts.matchToken(ts.OR)) {
926       sourceAdd((char) ts.OR);
927       pn = nf.createBinary(ts.OR, pn, orExpr(ts, inForInit));
928     }
929
930     return pn;
931   }
932
933   private Object JavaDoc andExpr(TokenStream ts, boolean inForInit) throws IOException JavaDoc,
934       JavaScriptException {
935     Object JavaDoc pn = bitOrExpr(ts, inForInit);
936     if (ts.matchToken(ts.AND)) {
937       sourceAdd((char) ts.AND);
938       pn = nf.createBinary(ts.AND, pn, andExpr(ts, inForInit));
939     }
940
941     return pn;
942   }
943
944   private Object JavaDoc bitOrExpr(TokenStream ts, boolean inForInit)
945       throws IOException JavaDoc, JavaScriptException {
946     Object JavaDoc pn = bitXorExpr(ts, inForInit);
947     while (ts.matchToken(ts.BITOR)) {
948       sourceAdd((char) ts.BITOR);
949       pn = nf.createBinary(ts.BITOR, pn, bitXorExpr(ts, inForInit));
950     }
951     return pn;
952   }
953
954   private Object JavaDoc bitXorExpr(TokenStream ts, boolean inForInit)
955       throws IOException JavaDoc, JavaScriptException {
956     Object JavaDoc pn = bitAndExpr(ts, inForInit);
957     while (ts.matchToken(ts.BITXOR)) {
958       sourceAdd((char) ts.BITXOR);
959       pn = nf.createBinary(ts.BITXOR, pn, bitAndExpr(ts, inForInit));
960     }
961     return pn;
962   }
963
964   private Object JavaDoc bitAndExpr(TokenStream ts, boolean inForInit)
965       throws IOException JavaDoc, JavaScriptException {
966     Object JavaDoc pn = eqExpr(ts, inForInit);
967     while (ts.matchToken(ts.BITAND)) {
968       sourceAdd((char) ts.BITAND);
969       pn = nf.createBinary(ts.BITAND, pn, eqExpr(ts, inForInit));
970     }
971     return pn;
972   }
973
974   private Object JavaDoc eqExpr(TokenStream ts, boolean inForInit) throws IOException JavaDoc,
975       JavaScriptException {
976     Object JavaDoc pn = relExpr(ts, inForInit);
977     while (ts.matchToken(ts.EQOP)) {
978       sourceAdd((char) ts.EQOP);
979       sourceAdd((char) ts.getOp());
980       pn = nf.createBinary(ts.EQOP, ts.getOp(), pn, relExpr(ts, inForInit));
981     }
982     return pn;
983   }
984
985   private Object JavaDoc relExpr(TokenStream ts, boolean inForInit) throws IOException JavaDoc,
986       JavaScriptException {
987     Object JavaDoc pn = shiftExpr(ts);
988     while (ts.matchToken(ts.RELOP)) {
989       int op = ts.getOp();
990       if (inForInit && op == ts.IN) {
991         ts.ungetToken(ts.RELOP);
992         break;
993       }
994       sourceAdd((char) ts.RELOP);
995       sourceAdd((char) op);
996       pn = nf.createBinary(ts.RELOP, op, pn, shiftExpr(ts));
997     }
998     return pn;
999   }
1000
1001  private Object JavaDoc shiftExpr(TokenStream ts) throws IOException JavaDoc,
1002      JavaScriptException {
1003    Object JavaDoc pn = addExpr(ts);
1004    while (ts.matchToken(ts.SHOP)) {
1005      sourceAdd((char) ts.SHOP);
1006      sourceAdd((char) ts.getOp());
1007      pn = nf.createBinary(ts.SHOP, ts.getOp(), pn, addExpr(ts));
1008    }
1009    return pn;
1010  }
1011
1012  private Object JavaDoc addExpr(TokenStream ts) throws IOException JavaDoc,
1013      JavaScriptException {
1014    int tt;
1015    Object JavaDoc pn = mulExpr(ts);
1016
1017    while ((tt = ts.getToken()) == ts.ADD || tt == ts.SUB) {
1018      sourceAdd((char) tt);
1019      // flushNewLines
1020
pn = nf.createBinary(tt, pn, mulExpr(ts));
1021    }
1022    ts.ungetToken(tt);
1023
1024    return pn;
1025  }
1026
1027  private Object JavaDoc mulExpr(TokenStream ts) throws IOException JavaDoc,
1028      JavaScriptException {
1029    int tt;
1030
1031    Object JavaDoc pn = unaryExpr(ts);
1032
1033    while ((tt = ts.peekToken()) == ts.MUL || tt == ts.DIV || tt == ts.MOD) {
1034      tt = ts.getToken();
1035      sourceAdd((char) tt);
1036      pn = nf.createBinary(tt, pn, unaryExpr(ts));
1037    }
1038
1039    return pn;
1040  }
1041
1042  private Object JavaDoc unaryExpr(TokenStream ts) throws IOException JavaDoc,
1043      JavaScriptException {
1044    int tt;
1045
1046    ts.flags |= ts.TSF_REGEXP;
1047    tt = ts.getToken();
1048    ts.flags &= ~ts.TSF_REGEXP;
1049
1050    switch (tt) {
1051      case TokenStream.UNARYOP:
1052        sourceAdd((char) ts.UNARYOP);
1053        sourceAdd((char) ts.getOp());
1054        return nf.createUnary(ts.UNARYOP, ts.getOp(), unaryExpr(ts));
1055
1056      case TokenStream.ADD:
1057      case TokenStream.SUB:
1058        sourceAdd((char) ts.UNARYOP);
1059        sourceAdd((char) tt);
1060        return nf.createUnary(ts.UNARYOP, tt, unaryExpr(ts));
1061
1062      case TokenStream.INC:
1063      case TokenStream.DEC:
1064        sourceAdd((char) tt);
1065        return nf.createUnary(tt, ts.PRE, memberExpr(ts, true));
1066
1067      case TokenStream.DELPROP:
1068        sourceAdd((char) ts.DELPROP);
1069        return nf.createUnary(ts.DELPROP, unaryExpr(ts));
1070
1071      case TokenStream.ERROR:
1072        break;
1073
1074      default:
1075        ts.ungetToken(tt);
1076
1077        int lineno = ts.getLineno();
1078
1079        Object JavaDoc pn = memberExpr(ts, true);
1080
1081        /*
1082         * don't look across a newline boundary for a postfix incop.
1083         *
1084         * the rhino scanner seems to work differently than the js scanner here;
1085         * in js, it works to have the line number check precede the peekToken
1086         * calls. It'd be better if they had similar behavior...
1087         */

1088        int peeked;
1089        if (((peeked = ts.peekToken()) == ts.INC || peeked == ts.DEC)
1090          && ts.getLineno() == lineno) {
1091          int pf = ts.getToken();
1092          sourceAdd((char) pf);
1093          return nf.createUnary(pf, ts.POST, pn);
1094        }
1095        return pn;
1096    }
1097    return nf.createName("err"); // Only reached on error. Try to continue.
1098

1099  }
1100
1101  private Object JavaDoc argumentList(TokenStream ts, Object JavaDoc listNode)
1102      throws IOException JavaDoc, JavaScriptException {
1103    boolean matched;
1104    ts.flags |= ts.TSF_REGEXP;
1105    matched = ts.matchToken(ts.GWT);
1106    ts.flags &= ~ts.TSF_REGEXP;
1107    if (!matched) {
1108      boolean first = true;
1109      do {
1110        if (!first)
1111          sourceAdd((char) ts.COMMA);
1112        first = false;
1113        nf.addChildToBack(listNode, assignExpr(ts, false));
1114      } while (ts.matchToken(ts.COMMA));
1115
1116      mustMatchToken(ts, ts.GWT, "msg.no.paren.arg");
1117    }
1118    sourceAdd((char) ts.GWT);
1119    return listNode;
1120  }
1121
1122  private Object JavaDoc memberExpr(TokenStream ts, boolean allowCallSyntax)
1123      throws IOException JavaDoc, JavaScriptException {
1124    int tt;
1125
1126    Object JavaDoc pn;
1127
1128    /* Check for new expressions. */
1129    ts.flags |= ts.TSF_REGEXP;
1130    tt = ts.peekToken();
1131    ts.flags &= ~ts.TSF_REGEXP;
1132    if (tt == ts.NEW) {
1133      /* Eat the NEW token. */
1134      ts.getToken();
1135      sourceAdd((char) ts.NEW);
1136
1137      /* Make a NEW node to append to. */
1138      pn = nf.createLeaf(ts.NEW);
1139      nf.addChildToBack(pn, memberExpr(ts, false));
1140
1141      if (ts.matchToken(ts.LP)) {
1142        sourceAdd((char) ts.LP);
1143        /* Add the arguments to pn, if any are supplied. */
1144        pn = argumentList(ts, pn);
1145      }
1146
1147      /*
1148       * XXX there's a check in the C source against "too many constructor
1149       * arguments" - how many do we claim to support?
1150       */

1151
1152      /*
1153       * Experimental syntax: allow an object literal to follow a new
1154       * expression, which will mean a kind of anonymous class built with the
1155       * JavaAdapter. the object literal will be passed as an additional
1156       * argument to the constructor.
1157       */

1158      tt = ts.peekToken();
1159      if (tt == ts.LC) {
1160        nf.addChildToBack(pn, primaryExpr(ts));
1161      }
1162    } else {
1163      pn = primaryExpr(ts);
1164    }
1165
1166    return memberExprTail(ts, allowCallSyntax, pn);
1167  }
1168
1169  private Object JavaDoc memberExprTail(TokenStream ts, boolean allowCallSyntax,
1170      Object JavaDoc pn) throws IOException JavaDoc, JavaScriptException {
1171    lastExprEndLine = ts.getLineno();
1172    int tt;
1173    while ((tt = ts.getToken()) > ts.EOF) {
1174      if (tt == ts.DOT) {
1175        sourceAdd((char) ts.DOT);
1176        mustMatchToken(ts, ts.NAME, "msg.no.name.after.dot");
1177        String JavaDoc s = ts.getString();
1178        sourceAddString(ts.NAME, s);
1179        pn = nf.createBinary(ts.DOT, pn, nf.createName(ts.getString()));
1180        /*
1181         * pn = nf.createBinary(ts.DOT, pn, memberExpr(ts)) is the version in
1182         * Brendan's IR C version. Not in ECMA... does it reflect the 'new'
1183         * operator syntax he mentioned?
1184         */

1185        lastExprEndLine = ts.getLineno();
1186      } else if (tt == ts.LB) {
1187        sourceAdd((char) ts.LB);
1188        pn = nf.createBinary(ts.LB, pn, expr(ts, false));
1189
1190        mustMatchToken(ts, ts.RB, "msg.no.bracket.index");
1191        sourceAdd((char) ts.RB);
1192        lastExprEndLine = ts.getLineno();
1193      } else if (allowCallSyntax && tt == ts.LP) {
1194        /* make a call node */
1195
1196        pn = nf.createUnary(ts.CALL, pn);
1197        sourceAdd((char) ts.LP);
1198
1199        /* Add the arguments to pn, if any are supplied. */
1200        pn = argumentList(ts, pn);
1201        lastExprEndLine = ts.getLineno();
1202      } else {
1203        ts.ungetToken(tt);
1204
1205        break;
1206      }
1207    }
1208    return pn;
1209  }
1210
1211  private Object JavaDoc primaryExpr(TokenStream ts) throws IOException JavaDoc,
1212      JavaScriptException {
1213    int tt;
1214
1215    Object JavaDoc pn;
1216
1217    ts.flags |= ts.TSF_REGEXP;
1218    tt = ts.getToken();
1219    ts.flags &= ~ts.TSF_REGEXP;
1220
1221    switch (tt) {
1222
1223      case TokenStream.FUNCTION:
1224        return function(ts, true);
1225
1226      case TokenStream.LB: {
1227        sourceAdd((char) ts.LB);
1228        pn = nf.createLeaf(ts.ARRAYLIT);
1229
1230        ts.flags |= ts.TSF_REGEXP;
1231        boolean matched = ts.matchToken(ts.RB);
1232        ts.flags &= ~ts.TSF_REGEXP;
1233
1234        if (!matched) {
1235          boolean first = true;
1236          do {
1237            ts.flags |= ts.TSF_REGEXP;
1238            tt = ts.peekToken();
1239            ts.flags &= ~ts.TSF_REGEXP;
1240
1241            if (!first)
1242              sourceAdd((char) ts.COMMA);
1243            else
1244              first = false;
1245
1246            if (tt == ts.RB) { // to fix [,,,].length behavior...
1247
break;
1248            }
1249
1250            if (tt == ts.COMMA) {
1251              nf.addChildToBack(pn, nf.createLeaf(ts.PRIMARY, ts.UNDEFINED));
1252            } else {
1253              nf.addChildToBack(pn, assignExpr(ts, false));
1254            }
1255
1256          } while (ts.matchToken(ts.COMMA));
1257          mustMatchToken(ts, ts.RB, "msg.no.bracket.arg");
1258        }
1259        sourceAdd((char) ts.RB);
1260        return nf.createArrayLiteral(pn);
1261      }
1262
1263      case TokenStream.LC: {
1264        pn = nf.createLeaf(ts.OBJLIT);
1265
1266        sourceAdd((char) ts.LC);
1267        if (!ts.matchToken(ts.RC)) {
1268
1269          boolean first = true;
1270          commaloop : do {
1271            Object JavaDoc property;
1272
1273            if (!first)
1274              sourceAdd((char) ts.COMMA);
1275            else
1276              first = false;
1277
1278            tt = ts.getToken();
1279            switch (tt) {
1280              // map NAMEs to STRINGs in object literal context.
1281
case TokenStream.NAME:
1282              case TokenStream.STRING:
1283                String JavaDoc s = ts.getString();
1284                sourceAddString(ts.NAME, s);
1285                property = nf.createString(ts.getString());
1286                break;
1287              case TokenStream.NUMBER:
1288                double n = ts.getNumber();
1289                sourceAddNumber(n);
1290                property = nf.createNumber(n);
1291                break;
1292              case TokenStream.RC:
1293                // trailing comma is OK.
1294
ts.ungetToken(tt);
1295                break commaloop;
1296              default:
1297                reportError(ts, "msg.bad.prop");
1298                break commaloop;
1299            }
1300            mustMatchToken(ts, ts.COLON, "msg.no.colon.prop");
1301
1302            // OBJLIT is used as ':' in object literal for
1303
// decompilation to solve spacing ambiguity.
1304
sourceAdd((char) ts.OBJLIT);
1305            nf.addChildToBack(pn, property);
1306            nf.addChildToBack(pn, assignExpr(ts, false));
1307
1308          } while (ts.matchToken(ts.COMMA));
1309
1310          mustMatchToken(ts, ts.RC, "msg.no.brace.prop");
1311        }
1312        sourceAdd((char) ts.RC);
1313        return nf.createObjectLiteral(pn);
1314      }
1315
1316      case TokenStream.LP:
1317
1318        /*
1319         * Brendan's IR-jsparse.c makes a new node tagged with TOK_LP here...
1320         * I'm not sure I understand why. Isn't the grouping already implicit in
1321         * the structure of the parse tree? also TOK_LP is already overloaded (I
1322         * think) in the C IR as 'function call.'
1323         */

1324        sourceAdd((char) ts.LP);
1325        pn = expr(ts, false);
1326        sourceAdd((char) ts.GWT);
1327        mustMatchToken(ts, ts.GWT, "msg.no.paren");
1328        return pn;
1329
1330      case TokenStream.NAME:
1331        String JavaDoc name = ts.getString();
1332        sourceAddString(ts.NAME, name);
1333        return nf.createName(name);
1334
1335      case TokenStream.NUMBER:
1336        double n = ts.getNumber();
1337        sourceAddNumber(n);
1338        return nf.createNumber(n);
1339
1340      case TokenStream.STRING:
1341        String JavaDoc s = ts.getString();
1342        sourceAddString(ts.STRING, s);
1343        return nf.createString(s);
1344
1345      case TokenStream.REGEXP: {
1346        String JavaDoc flags = ts.regExpFlags;
1347        ts.regExpFlags = null;
1348        String JavaDoc re = ts.getString();
1349        sourceAddString(ts.REGEXP, '/' + re + '/' + flags);
1350        return nf.createRegExp(re, flags);
1351      }
1352
1353      case TokenStream.PRIMARY:
1354        sourceAdd((char) ts.PRIMARY);
1355        sourceAdd((char) ts.getOp());
1356        return nf.createLeaf(ts.PRIMARY, ts.getOp());
1357
1358      case TokenStream.RESERVED:
1359        reportError(ts, "msg.reserved.id");
1360        break;
1361
1362      case TokenStream.ERROR:
1363        /* the scanner or one of its subroutines reported the error. */
1364        break;
1365
1366      default:
1367        reportError(ts, "msg.syntax");
1368        break;
1369
1370    }
1371    return null; // should never reach here
1372
}
1373
1374  /**
1375   * The following methods save decompilation information about the source.
1376   * Source information is returned from the parser as a String associated with
1377   * function nodes and with the toplevel script. When saved in the constant
1378   * pool of a class, this string will be UTF-8 encoded, and token values will
1379   * occupy a single byte.
1380   *
1381   * Source is saved (mostly) as token numbers. The tokens saved pretty much
1382   * correspond to the token stream of a 'canonical' representation of the input
1383   * program, as directed by the parser. (There were a few cases where tokens
1384   * could have been left out where decompiler could easily reconstruct them,
1385   * but I left them in for clarity). (I also looked adding source collection to
1386   * TokenStream instead, where I could have limited the changes to a few lines
1387   * in getToken... but this wouldn't have saved any space in the resulting
1388   * source representation, and would have meant that I'd have to duplicate
1389   * parser logic in the decompiler to disambiguate situations where newlines
1390   * are important.) NativeFunction.decompile expands the tokens back into their
1391   * string representations, using simple lookahead to correct spacing and
1392   * indentation.
1393   *
1394   * Token types with associated ops (ASSIGN, SHOP, PRIMARY, etc.) are saved as
1395   * two-token pairs. Number tokens are stored inline, as a NUMBER token, a
1396   * character representing the type, and either 1 or 4 characters representing
1397   * the bit-encoding of the number. String types NAME, STRING and OBJECT are
1398   * currently stored as a token type, followed by a character giving the length
1399   * of the string (assumed to be less than 2^16), followed by the characters of
1400   * the string inlined into the source string. Changing this to some reference
1401   * to to the string in the compiled class' constant pool would probably save a
1402   * lot of space... but would require some method of deriving the final
1403   * constant pool entry from information available at parse time.
1404   *
1405   * Nested functions need a similar mechanism... fortunately the nested
1406   * functions for a given function are generated in source order. Nested
1407   * functions are encoded as FUNCTION followed by a function number (encoded as
1408   * a character), which is enough information to find the proper generated
1409   * NativeFunction instance.
1410   *
1411   */

1412  private void sourceAdd(char c) {
1413    if (sourceTop == sourceBuffer.length) {
1414      increaseSourceCapacity(sourceTop + 1);
1415    }
1416    sourceBuffer[sourceTop] = c;
1417    ++sourceTop;
1418  }
1419
1420  private void sourceAddString(int type, String JavaDoc str) {
1421    int L = str.length();
1422    // java string length < 2^16?
1423
if (Context.check && L > Character.MAX_VALUE)
1424      Context.codeBug();
1425
1426    if (sourceTop + L + 2 > sourceBuffer.length) {
1427      increaseSourceCapacity(sourceTop + L + 2);
1428    }
1429    sourceAdd((char) type);
1430    sourceAdd((char) L);
1431    str.getChars(0, L, sourceBuffer, sourceTop);
1432    sourceTop += L;
1433  }
1434
1435  private void sourceAddNumber(double n) {
1436    sourceAdd((char) TokenStream.NUMBER);
1437
1438    /*
1439     * encode the number in the source stream. Save as NUMBER type (char | char
1440     * char char char) where type is 'D' - double, 'S' - short, 'J' - long.
1441     *
1442     * We need to retain float vs. integer type info to keep the behavior of
1443     * liveconnect type-guessing the same after decompilation. (Liveconnect
1444     * tries to present 1.0 to Java as a float/double) OPT: This is no longer
1445     * true. We could compress the format.
1446     *
1447     * This may not be the most space-efficient encoding; the chars created
1448     * below may take up to 3 bytes in constant pool UTF-8 encoding, so a Double
1449     * could take up to 12 bytes.
1450     */

1451
1452    long lbits = (long) n;
1453    if (lbits != n) {
1454      // if it's floating point, save as a Double bit pattern.
1455
// (12/15/97 our scanner only returns Double for f.p.)
1456
lbits = Double.doubleToLongBits(n);
1457      sourceAdd('D');
1458      sourceAdd((char) (lbits >> 48));
1459      sourceAdd((char) (lbits >> 32));
1460      sourceAdd((char) (lbits >> 16));
1461      sourceAdd((char) lbits);
1462    } else {
1463      // we can ignore negative values, bc they're already prefixed
1464
// by UNARYOP SUB
1465
if (Context.check && lbits < 0)
1466        Context.codeBug();
1467
1468      // will it fit in a char?
1469
// this gives a short encoding for integer values up to 2^16.
1470
if (lbits <= Character.MAX_VALUE) {
1471        sourceAdd('S');
1472        sourceAdd((char) lbits);
1473      } else { // Integral, but won't fit in a char. Store as a long.
1474
sourceAdd('J');
1475        sourceAdd((char) (lbits >> 48));
1476        sourceAdd((char) (lbits >> 32));
1477        sourceAdd((char) (lbits >> 16));
1478        sourceAdd((char) lbits);
1479      }
1480    }
1481  }
1482
1483  private void increaseSourceCapacity(int minimalCapacity) {
1484    // Call this only when capacity increase is must
1485
if (Context.check && minimalCapacity <= sourceBuffer.length)
1486      Context.codeBug();
1487    int newCapacity = sourceBuffer.length * 2;
1488    if (newCapacity < minimalCapacity) {
1489      newCapacity = minimalCapacity;
1490    }
1491    char[] tmp = new char[newCapacity];
1492    System.arraycopy(sourceBuffer, 0, tmp, 0, sourceTop);
1493    sourceBuffer = tmp;
1494  }
1495
1496  private String JavaDoc sourceToString(int offset) {
1497    if (Context.check && (offset < 0 || sourceTop < offset))
1498      Context.codeBug();
1499    return new String JavaDoc(sourceBuffer, offset, sourceTop - offset);
1500  }
1501
1502  private int lastExprEndLine; // Hack to handle function expr termination.
1503
private IRFactory nf;
1504  private ErrorReporter er;
1505  private boolean ok; // Did the parse encounter an error?
1506

1507  private char[] sourceBuffer = new char[128];
1508  private int sourceTop;
1509  private int functionNumber;
1510}
1511
Popular Tags