2003/05/12 05:10:30
[org.ibex.core.git] / src / org / mozilla / javascript / Parser.java
1 /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-\r
2  *\r
3  * The contents of this file are subject to the Netscape Public\r
4  * License Version 1.1 (the "License"); you may not use this file\r
5  * except in compliance with the License. You may obtain a copy of\r
6  * the License at http://www.mozilla.org/NPL/\r
7  *\r
8  * Software distributed under the License is distributed on an "AS\r
9  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr\r
10  * implied. See the License for the specific language governing\r
11  * rights and limitations under the License.\r
12  *\r
13  * The Original Code is Rhino code, released\r
14  * May 6, 1999.\r
15  *\r
16  * The Initial Developer of the Original Code is Netscape\r
17  * Communications Corporation.  Portions created by Netscape are\r
18  * Copyright (C) 1997-1999 Netscape Communications Corporation. All\r
19  * Rights Reserved.\r
20  *\r
21  * Contributor(s): \r
22  * Mike Ang\r
23  * Mike McCabe\r
24  *\r
25  * Alternatively, the contents of this file may be used under the\r
26  * terms of the GNU Public License (the "GPL"), in which case the\r
27  * provisions of the GPL are applicable instead of those above.\r
28  * If you wish to allow use of your version of this file only\r
29  * under the terms of the GPL and not to allow others to use your\r
30  * version of this file under the NPL, indicate your decision by\r
31  * deleting the provisions above and replace them with the notice\r
32  * and other provisions required by the GPL.  If you do not delete\r
33  * the provisions above, a recipient may use your version of this\r
34  * file under either the NPL or the GPL.\r
35  */\r
36 \r
37 package org.mozilla.javascript;\r
38 \r
39 import org.mozilla.javascript.ErrorReporter;\r
40 import org.mozilla.javascript.Context;\r
41 import java.io.IOException;\r
42 \r
43 /**\r
44  * This class implements the JavaScript parser.\r
45  *\r
46  * It is based on the C source files jsparse.c and jsparse.h\r
47  * in the jsref package.\r
48  *\r
49  * @see TokenStream\r
50  *\r
51  * @author Mike McCabe\r
52  * @author Brendan Eich\r
53  */\r
54 \r
55 class Parser {\r
56 \r
57     public Parser(IRFactory nf) {\r
58         this.nf = nf;\r
59     }\r
60 \r
61     private void mustMatchToken(TokenStream ts, int toMatch, String messageId)\r
62         throws IOException, JavaScriptException\r
63     {\r
64         int tt;\r
65         if ((tt = ts.getToken()) != toMatch) {\r
66             reportError(ts, messageId);\r
67             ts.ungetToken(tt); // In case the parser decides to continue\r
68         }\r
69     }\r
70     \r
71     private void reportError(TokenStream ts, String messageId) \r
72         throws JavaScriptException\r
73     {\r
74         this.ok = false;\r
75         ts.reportSyntaxError(messageId, null);\r
76         \r
77         /* Throw an exception to unwind the recursive descent parse. \r
78          * We use JavaScriptException here even though it is really \r
79          * a different use of the exception than it is usually used\r
80          * for.\r
81          */\r
82         throw new JavaScriptException(messageId);\r
83     }\r
84 \r
85     /*\r
86      * Build a parse tree from the given TokenStream.  \r
87      *\r
88      * @param ts the TokenStream to parse\r
89      *\r
90      * @return an Object representing the parsed\r
91      * program.  If the parse fails, null will be returned.  (The\r
92      * parse failure will result in a call to the current Context's\r
93      * ErrorReporter.)\r
94      */\r
95     public Object parse(TokenStream ts)\r
96         throws IOException\r
97     {\r
98         this.ok = true;\r
99         Source source = new Source();\r
100 \r
101         int tt;          // last token from getToken();\r
102         int baseLineno = ts.getLineno();  // line number where source starts\r
103 \r
104         /* so we have something to add nodes to until\r
105          * we've collected all the source */\r
106         Object tempBlock = nf.createLeaf(TokenStream.BLOCK);\r
107 \r
108         while (true) {\r
109             ts.flags |= ts.TSF_REGEXP;\r
110             tt = ts.getToken();\r
111             ts.flags &= ~ts.TSF_REGEXP;\r
112 \r
113             if (tt <= ts.EOF) {\r
114                 break;\r
115             }\r
116 \r
117             if (tt == ts.FUNCTION) {\r
118                 try {\r
119                     nf.addChildToBack(tempBlock, function(ts, source, false));\r
120                     /* function doesn't add its own final EOL,\r
121                      * because it gets SEMI + EOL from Statement when it's\r
122                      * a nested function; so we need to explicitly add an\r
123                      * EOL here.\r
124                      */\r
125                     source.append((char)ts.EOL);\r
126                     wellTerminated(ts, ts.FUNCTION);\r
127                 } catch (JavaScriptException e) {\r
128                     this.ok = false;\r
129                     break;\r
130                 }\r
131             } else {\r
132                 ts.ungetToken(tt);\r
133                 nf.addChildToBack(tempBlock, statement(ts, source));\r
134             }\r
135         }\r
136 \r
137         if (!this.ok) {\r
138             // XXX ts.clearPushback() call here?\r
139             return null;\r
140         }\r
141 \r
142         Object pn = nf.createScript(tempBlock, ts.getSourceName(),\r
143                                     baseLineno, ts.getLineno(),\r
144                                     source.buf.toString());\r
145         return pn;\r
146     }\r
147 \r
148     /*\r
149      * The C version of this function takes an argument list,\r
150      * which doesn't seem to be needed for tree generation...\r
151      * it'd only be useful for checking argument hiding, which\r
152      * I'm not doing anyway...\r
153      */\r
154     private Object parseFunctionBody(TokenStream ts, Source source)\r
155         throws IOException\r
156     {\r
157         int oldflags = ts.flags;\r
158         ts.flags &= ~(TokenStream.TSF_RETURN_EXPR\r
159                       | TokenStream.TSF_RETURN_VOID);\r
160         ts.flags |= TokenStream.TSF_FUNCTION;\r
161 \r
162         Object pn = nf.createBlock(ts.getLineno());\r
163         try {\r
164             int tt;\r
165             while((tt = ts.peekToken()) > ts.EOF && tt != ts.RC) {\r
166                 if (tt == TokenStream.FUNCTION) {\r
167                     ts.getToken();\r
168                     nf.addChildToBack(pn, function(ts, source, false));\r
169                     /* function doesn't add its own final EOL,\r
170                      * because it gets SEMI + EOL from Statement when it's\r
171                      * a nested function; so we need to explicitly add an\r
172                      * EOL here.\r
173                      */\r
174                     source.append((char)ts.EOL);\r
175                     wellTerminated(ts, ts.FUNCTION);       \r
176                 } else {\r
177                     nf.addChildToBack(pn, statement(ts, source));\r
178                 }\r
179             }\r
180         } catch (JavaScriptException e) {\r
181             this.ok = false;\r
182         } finally {\r
183             // also in finally block:\r
184             // flushNewLines, clearPushback.\r
185 \r
186             ts.flags = oldflags;\r
187         }\r
188 \r
189         return pn;\r
190     }\r
191 \r
192     private Object function(TokenStream ts, Source source, boolean isExpr)\r
193         throws IOException, JavaScriptException\r
194     {\r
195         String name = null;\r
196         Object args = nf.createLeaf(ts.LP);\r
197         Object body;\r
198         int baseLineno = ts.getLineno();  // line number where source starts\r
199 \r
200         // save a reference to the function in the enclosing source.\r
201         source.append((char) ts.FUNCTION);\r
202         source.append(source.functionNumber);\r
203         source.functionNumber++;\r
204 \r
205         // make a new Source for the enclosed function\r
206         source = new Source();\r
207 \r
208         // FUNCTION as the first token in a Source means it's a function\r
209         // definition, and not a reference.\r
210         source.append((char) ts.FUNCTION);\r
211 \r
212         if (ts.matchToken(ts.NAME)) {\r
213             // HACK: XWT does not support named functions
214             throw new IOException(
215                 "named functions are not supported, use anonymos "+
216                 "declaration (myfun = function() {...}"
217             );
218         }\r
219         else\r
220             ; // it's an anonymous function\r
221 \r
222         mustMatchToken(ts, ts.LP, "msg.no.paren.parms");\r
223         source.append((char) ts.LP);\r
224 \r
225         if (!ts.matchToken(ts.RP)) {\r
226             boolean first = true;\r
227             do {\r
228                 if (!first)\r
229                     source.append((char)ts.COMMA);\r
230                 first = false;\r
231                 mustMatchToken(ts, ts.NAME, "msg.no.parm");\r
232                 String s = ts.getString();\r
233                 nf.addChildToBack(args, nf.createName(s));\r
234 \r
235                 source.addString(ts.NAME, s);\r
236             } while (ts.matchToken(ts.COMMA));\r
237 \r
238             mustMatchToken(ts, ts.RP, "msg.no.paren.after.parms");\r
239         }\r
240         source.append((char)ts.RP);\r
241 \r
242         mustMatchToken(ts, ts.LC, "msg.no.brace.body");\r
243         source.append((char)ts.LC);\r
244         source.append((char)ts.EOL);\r
245         body = parseFunctionBody(ts, source);\r
246         mustMatchToken(ts, ts.RC, "msg.no.brace.after.body");\r
247         source.append((char)ts.RC);\r
248         // skip the last EOL so nested functions work...\r
249 \r
250         // name might be null;\r
251         return nf.createFunction(name, args, body,\r
252                                  ts.getSourceName(),\r
253                                  baseLineno, ts.getLineno(),\r
254                                  source.buf.toString(),\r
255                                  isExpr);\r
256     }\r
257 \r
258     private Object statements(TokenStream ts, Source source)\r
259         throws IOException\r
260     {\r
261         Object pn = nf.createBlock(ts.getLineno());\r
262 \r
263         int tt;\r
264         while((tt = ts.peekToken()) > ts.EOF && tt != ts.RC) {\r
265             nf.addChildToBack(pn, statement(ts, source));\r
266         }\r
267 \r
268         return pn;\r
269     }\r
270 \r
271     private Object condition(TokenStream ts, Source source)\r
272         throws IOException, JavaScriptException\r
273     {\r
274         Object pn;\r
275         mustMatchToken(ts, ts.LP, "msg.no.paren.cond");\r
276         source.append((char)ts.LP);\r
277         pn = expr(ts, source, false);\r
278         mustMatchToken(ts, ts.RP, "msg.no.paren.after.cond");\r
279         source.append((char)ts.RP);\r
280 \r
281         // there's a check here in jsparse.c that corrects = to ==\r
282 \r
283         return pn;\r
284     }\r
285 \r
286     private boolean wellTerminated(TokenStream ts, int lastExprType)\r
287         throws IOException, JavaScriptException\r
288     {\r
289         int tt = ts.peekTokenSameLine();\r
290         if (tt == ts.ERROR) {\r
291             return false;\r
292         }\r
293 \r
294         if (tt != ts.EOF && tt != ts.EOL\r
295             && tt != ts.SEMI && tt != ts.RC)\r
296             {\r
297                 int version = Context.getContext().getLanguageVersion();\r
298                 if ((tt == ts.FUNCTION || lastExprType == ts.FUNCTION) &&\r
299                     (version < Context.VERSION_1_2)) {\r
300                     /*\r
301                      * Checking against version < 1.2 and version >= 1.0\r
302                      * in the above line breaks old javascript, so we keep it\r
303                      * this way for now... XXX warning needed?\r
304                      */\r
305                     return true;\r
306                 } else {\r
307                     reportError(ts, "msg.no.semi.stmt");\r
308                 }\r
309             }\r
310         return true;\r
311     }\r
312 \r
313     // match a NAME; return null if no match.\r
314     private String matchLabel(TokenStream ts)\r
315         throws IOException, JavaScriptException\r
316     {\r
317         int lineno = ts.getLineno();\r
318 \r
319         String label = null;\r
320         int tt;\r
321         tt = ts.peekTokenSameLine();\r
322         if (tt == ts.NAME) {\r
323             ts.getToken();\r
324             label = ts.getString();\r
325         }\r
326 \r
327         if (lineno == ts.getLineno())\r
328             wellTerminated(ts, ts.ERROR);\r
329 \r
330         return label;\r
331     }\r
332 \r
333     private Object statement(TokenStream ts, Source source) \r
334         throws IOException\r
335     {\r
336         try {\r
337             return statementHelper(ts, source);\r
338         } catch (JavaScriptException e) {\r
339             // skip to end of statement\r
340             int lineno = ts.getLineno();\r
341             int t;\r
342             do {\r
343                 t = ts.getToken();\r
344             } while (t != TokenStream.SEMI && t != TokenStream.EOL && \r
345                      t != TokenStream.EOF && t != TokenStream.ERROR);\r
346             return nf.createExprStatement(nf.createName("error"), lineno);\r
347         }\r
348     }\r
349     \r
350     /**\r
351      * Whether the "catch (e: e instanceof Exception) { ... }" syntax\r
352      * is implemented.\r
353      */\r
354    \r
355     private Object statementHelper(TokenStream ts, Source source)\r
356         throws IOException, JavaScriptException\r
357     {\r
358         Object pn = null;\r
359 \r
360         // If skipsemi == true, don't add SEMI + EOL to source at the\r
361         // end of this statment.  For compound statements, IF/FOR etc.\r
362         boolean skipsemi = false;\r
363 \r
364         int tt;\r
365 \r
366         int lastExprType = 0;  // For wellTerminated.  0 to avoid warning.\r
367 \r
368         tt = ts.getToken();\r
369 \r
370         switch(tt) {\r
371         case TokenStream.IF: {\r
372             skipsemi = true;\r
373 \r
374             source.append((char)ts.IF);\r
375             int lineno = ts.getLineno();\r
376             Object cond = condition(ts, source);\r
377             source.append((char)ts.LC);\r
378             source.append((char)ts.EOL);\r
379             Object ifTrue = statement(ts, source);\r
380             Object ifFalse = null;\r
381             if (ts.matchToken(ts.ELSE)) {\r
382                 source.append((char)ts.RC);\r
383                 source.append((char)ts.ELSE);\r
384                 source.append((char)ts.LC);\r
385                 source.append((char)ts.EOL);\r
386                 ifFalse = statement(ts, source);\r
387             }\r
388             source.append((char)ts.RC);\r
389             source.append((char)ts.EOL);\r
390             pn = nf.createIf(cond, ifTrue, ifFalse, lineno);\r
391             break;\r
392         }\r
393 \r
394         case TokenStream.SWITCH: {\r
395             skipsemi = true;\r
396 \r
397             source.append((char)ts.SWITCH);\r
398             pn = nf.createSwitch(ts.getLineno());\r
399 \r
400             Object cur_case = null;  // to kill warning\r
401             Object case_statements;\r
402 \r
403             mustMatchToken(ts, ts.LP, "msg.no.paren.switch");\r
404             source.append((char)ts.LP);\r
405             nf.addChildToBack(pn, expr(ts, source, false));\r
406             mustMatchToken(ts, ts.RP, "msg.no.paren.after.switch");\r
407             source.append((char)ts.RP);\r
408             mustMatchToken(ts, ts.LC, "msg.no.brace.switch");\r
409             source.append((char)ts.LC);\r
410             source.append((char)ts.EOL);\r
411 \r
412             while ((tt = ts.getToken()) != ts.RC && tt != ts.EOF) {\r
413                 switch(tt) {\r
414                 case TokenStream.CASE:\r
415                     source.append((char)ts.CASE);\r
416                     cur_case = nf.createUnary(ts.CASE, expr(ts, source, false));\r
417                     source.append((char)ts.COLON);\r
418                     source.append((char)ts.EOL);\r
419                     break;\r
420 \r
421                 case TokenStream.DEFAULT:\r
422                     cur_case = nf.createLeaf(ts.DEFAULT);\r
423                     source.append((char)ts.DEFAULT);\r
424                     source.append((char)ts.COLON);\r
425                     source.append((char)ts.EOL);\r
426                     // XXX check that there isn't more than one default\r
427                     break;\r
428 \r
429                 default:\r
430                     reportError(ts, "msg.bad.switch");\r
431                     break;\r
432                 }\r
433                 mustMatchToken(ts, ts.COLON, "msg.no.colon.case");\r
434 \r
435                 case_statements = nf.createLeaf(TokenStream.BLOCK);\r
436 \r
437                 while ((tt = ts.peekToken()) != ts.RC && tt != ts.CASE &&\r
438                         tt != ts.DEFAULT && tt != ts.EOF) \r
439                 {\r
440                     nf.addChildToBack(case_statements, statement(ts, source));\r
441                 }\r
442                 // assert cur_case\r
443                 nf.addChildToBack(cur_case, case_statements);\r
444 \r
445                 nf.addChildToBack(pn, cur_case);\r
446             }\r
447             source.append((char)ts.RC);\r
448             source.append((char)ts.EOL);\r
449             break;\r
450         }\r
451 \r
452         case TokenStream.WHILE: {\r
453             skipsemi = true;\r
454 \r
455             source.append((char)ts.WHILE);\r
456             int lineno = ts.getLineno();\r
457             Object cond = condition(ts, source);\r
458             source.append((char)ts.LC);\r
459             source.append((char)ts.EOL);\r
460             Object body = statement(ts, source);\r
461             source.append((char)ts.RC);\r
462             source.append((char)ts.EOL);\r
463 \r
464             pn = nf.createWhile(cond, body, lineno);\r
465             break;\r
466 \r
467         }\r
468 \r
469         case TokenStream.DO: {\r
470             source.append((char)ts.DO);\r
471             source.append((char)ts.LC);\r
472             source.append((char)ts.EOL);\r
473 \r
474             int lineno = ts.getLineno();\r
475 \r
476             Object body = statement(ts, source);\r
477 \r
478             source.append((char)ts.RC);\r
479             mustMatchToken(ts, ts.WHILE, "msg.no.while.do");\r
480             source.append((char)ts.WHILE);\r
481             Object cond = condition(ts, source);\r
482 \r
483             pn = nf.createDoWhile(body, cond, lineno);\r
484             break;\r
485         }\r
486 \r
487         case TokenStream.FOR: {\r
488             skipsemi = true;\r
489 \r
490             source.append((char)ts.FOR);\r
491             int lineno = ts.getLineno();\r
492 \r
493             Object init;  // Node init is also foo in 'foo in Object'\r
494             Object cond;  // Node cond is also object in 'foo in Object'\r
495             Object incr = null; // to kill warning\r
496             Object body;\r
497 \r
498             mustMatchToken(ts, ts.LP, "msg.no.paren.for");\r
499             source.append((char)ts.LP);\r
500             tt = ts.peekToken();\r
501             if (tt == ts.SEMI) {\r
502                 init = nf.createLeaf(ts.VOID);\r
503             } else {\r
504                 if (tt == ts.VAR) {\r
505                     // set init to a var list or initial\r
506                     ts.getToken();    // throw away the 'var' token\r
507                     init = variables(ts, source, true);\r
508                 }\r
509                 else {\r
510                     init = expr(ts, source, true);\r
511                 }\r
512             }\r
513 \r
514             tt = ts.peekToken();\r
515             if (tt == ts.RELOP && ts.getOp() == ts.IN) {\r
516                 ts.matchToken(ts.RELOP);\r
517                 source.append((char)ts.IN);\r
518                 // 'cond' is the object over which we're iterating\r
519                 cond = expr(ts, source, false);\r
520             } else {  // ordinary for loop\r
521                 mustMatchToken(ts, ts.SEMI,\r
522                                "msg.no.semi.for");\r
523                 source.append((char)ts.SEMI);\r
524                 if (ts.peekToken() == ts.SEMI) {\r
525                     // no loop condition\r
526                     cond = nf.createLeaf(ts.VOID);\r
527                 } else {\r
528                     cond = expr(ts, source, false);\r
529                 }\r
530 \r
531                 mustMatchToken(ts, ts.SEMI,\r
532                                "msg.no.semi.for.cond");\r
533                 source.append((char)ts.SEMI);\r
534                 if (ts.peekToken() == ts.RP) {\r
535                     incr = nf.createLeaf(ts.VOID);\r
536                 } else {\r
537                     incr = expr(ts, source, false);\r
538                 }\r
539             }\r
540 \r
541             mustMatchToken(ts, ts.RP, "msg.no.paren.for.ctrl");\r
542             source.append((char)ts.RP);\r
543             source.append((char)ts.LC);\r
544             source.append((char)ts.EOL);\r
545             body = statement(ts, source);\r
546             source.append((char)ts.RC);\r
547             source.append((char)ts.EOL);\r
548 \r
549             if (incr == null) {\r
550                 // cond could be null if 'in obj' got eaten by the init node.\r
551                 pn = nf.createForIn(init, cond, body, lineno);\r
552             } else {\r
553                 pn = nf.createFor(init, cond, incr, body, lineno);\r
554             }\r
555             break;\r
556         }\r
557 \r
558         case TokenStream.TRY: {\r
559             int lineno = ts.getLineno();\r
560 \r
561             Object tryblock;\r
562             Object catchblocks = null;\r
563             Object finallyblock = null;\r
564 \r
565             skipsemi = true;\r
566             source.append((char)ts.TRY);\r
567             source.append((char)ts.LC);\r
568             source.append((char)ts.EOL);\r
569             tryblock = statement(ts, source);\r
570             source.append((char)ts.RC);\r
571             source.append((char)ts.EOL);\r
572 \r
573             catchblocks = nf.createLeaf(TokenStream.BLOCK);\r
574 \r
575             boolean sawDefaultCatch = false;\r
576             int peek = ts.peekToken();\r
577             if (peek == ts.CATCH) {\r
578                 while (ts.matchToken(ts.CATCH)) {\r
579                     if (sawDefaultCatch) {\r
580                         reportError(ts, "msg.catch.unreachable");\r
581                     }\r
582                     source.append((char)ts.CATCH);\r
583                     mustMatchToken(ts, ts.LP, "msg.no.paren.catch");\r
584                     source.append((char)ts.LP);\r
585 \r
586                     mustMatchToken(ts, ts.NAME, "msg.bad.catchcond");\r
587                     String varName = ts.getString();\r
588                     source.addString(ts.NAME, varName);\r
589                     \r
590                     Object catchCond = null;\r
591                     if (ts.matchToken(ts.IF)) {\r
592                         source.append((char)ts.IF);\r
593                         catchCond = expr(ts, source, false);\r
594                     } else {\r
595                         sawDefaultCatch = true;\r
596                     }\r
597 \r
598                     mustMatchToken(ts, ts.RP, "msg.bad.catchcond");\r
599                     source.append((char)ts.RP);\r
600                     mustMatchToken(ts, ts.LC, "msg.no.brace.catchblock");\r
601                     source.append((char)ts.LC);\r
602                     source.append((char)ts.EOL);\r
603                     \r
604                     nf.addChildToBack(catchblocks, \r
605                         nf.createCatch(varName, catchCond, \r
606                                        statements(ts, source), \r
607                                        ts.getLineno()));\r
608 \r
609                     mustMatchToken(ts, ts.RC, "msg.no.brace.after.body");\r
610                     source.append((char)ts.RC);\r
611                     source.append((char)ts.EOL);\r
612                 }\r
613             } else if (peek != ts.FINALLY) {\r
614                 mustMatchToken(ts, ts.FINALLY, "msg.try.no.catchfinally");\r
615             }\r
616 \r
617             if (ts.matchToken(ts.FINALLY)) {\r
618                 source.append((char)ts.FINALLY);\r
619 \r
620                 source.append((char)ts.LC);\r
621                 source.append((char)ts.EOL);\r
622                 finallyblock = statement(ts, source);\r
623                 source.append((char)ts.RC);\r
624                 source.append((char)ts.EOL);\r
625             }\r
626 \r
627             pn = nf.createTryCatchFinally(tryblock, catchblocks,\r
628                                           finallyblock, lineno);\r
629 \r
630             break;\r
631         }\r
632         case TokenStream.THROW: {\r
633             int lineno = ts.getLineno();\r
634             source.append((char)ts.THROW);\r
635             pn = nf.createThrow(expr(ts, source, false), lineno);\r
636             if (lineno == ts.getLineno())\r
637                 wellTerminated(ts, ts.ERROR);\r
638             break;\r
639         }\r
640         case TokenStream.BREAK: {\r
641             int lineno = ts.getLineno();\r
642 \r
643             source.append((char)ts.BREAK);\r
644 \r
645             // matchLabel only matches if there is one\r
646             String label = matchLabel(ts);\r
647             if (label != null) {\r
648                 source.addString(ts.NAME, label);\r
649             }\r
650             pn = nf.createBreak(label, lineno);\r
651             break;\r
652         }\r
653         case TokenStream.CONTINUE: {\r
654             int lineno = ts.getLineno();\r
655 \r
656             source.append((char)ts.CONTINUE);\r
657 \r
658             // matchLabel only matches if there is one\r
659             String label = matchLabel(ts);\r
660             if (label != null) {\r
661                 source.addString(ts.NAME, label);\r
662             }\r
663             pn = nf.createContinue(label, lineno);\r
664             break;\r
665         }\r
666         case TokenStream.WITH: {\r
667             skipsemi = true;\r
668 \r
669             source.append((char)ts.WITH);\r
670             int lineno = ts.getLineno();\r
671             mustMatchToken(ts, ts.LP, "msg.no.paren.with");\r
672             source.append((char)ts.LP);\r
673             Object obj = expr(ts, source, false);\r
674             mustMatchToken(ts, ts.RP, "msg.no.paren.after.with");\r
675             source.append((char)ts.RP);\r
676             source.append((char)ts.LC);\r
677             source.append((char)ts.EOL);\r
678 \r
679             Object body = statement(ts, source);\r
680 \r
681             source.append((char)ts.RC);\r
682             source.append((char)ts.EOL);\r
683 \r
684             pn = nf.createWith(obj, body, lineno);\r
685             break;\r
686         }\r
687         case TokenStream.VAR: {\r
688             int lineno = ts.getLineno();\r
689             pn = variables(ts, source, false);\r
690             if (ts.getLineno() == lineno)\r
691                 wellTerminated(ts, ts.ERROR);\r
692             break;\r
693         }\r
694         case TokenStream.ASSERT: {\r
695             Object retExpr = null;\r
696             int lineno = 0;\r
697             source.append((char)ts.ASSERT);\r
698 \r
699             // bail if we're not in a (toplevel) function\r
700             if ((ts.flags & ts.TSF_FUNCTION) == 0)\r
701                 reportError(ts, "msg.bad.return");\r
702 \r
703             /* This is ugly, but we don't want to require a semicolon. */\r
704             ts.flags |= ts.TSF_REGEXP;\r
705             tt = ts.peekTokenSameLine();\r
706             ts.flags &= ~ts.TSF_REGEXP;\r
707             /*\r
708             if (tt != ts.EOF && tt != ts.EOL && tt != ts.SEMI && tt != ts.RC) {\r
709                 lineno = ts.getLineno();\r
710                 retExpr = expr(ts, source, false);\r
711                 if (ts.getLineno() == lineno)\r
712                     wellTerminated(ts, ts.ERROR);\r
713                 ts.flags |= ts.TSF_RETURN_EXPR;\r
714             } else {\r
715                 ts.flags |= ts.TSF_RETURN_VOID;\r
716             }\r
717             */\r
718             // XXX ASSERT pn\r
719             pn = nf.createAssert(retExpr, lineno);\r
720             break;\r
721         }\r
722 \r
723         case TokenStream.RETURN: {\r
724             Object retExpr = null;\r
725             int lineno = 0;\r
726 \r
727             source.append((char)ts.RETURN);\r
728 \r
729             // bail if we're not in a (toplevel) function\r
730             if ((ts.flags & ts.TSF_FUNCTION) == 0)\r
731                 reportError(ts, "msg.bad.return");\r
732 \r
733             /* This is ugly, but we don't want to require a semicolon. */\r
734             ts.flags |= ts.TSF_REGEXP;\r
735             tt = ts.peekTokenSameLine();\r
736             ts.flags &= ~ts.TSF_REGEXP;\r
737 \r
738             if (tt != ts.EOF && tt != ts.EOL && tt != ts.SEMI && tt != ts.RC) {\r
739                 lineno = ts.getLineno();\r
740                 retExpr = expr(ts, source, false);\r
741                 if (ts.getLineno() == lineno)\r
742                     wellTerminated(ts, ts.ERROR);\r
743                 ts.flags |= ts.TSF_RETURN_EXPR;\r
744             } else {\r
745                 ts.flags |= ts.TSF_RETURN_VOID;\r
746             }\r
747 \r
748             // XXX ASSERT pn\r
749             pn = nf.createReturn(retExpr, lineno);\r
750             break;\r
751         }\r
752         case TokenStream.LC:\r
753             skipsemi = true;\r
754 \r
755             pn = statements(ts, source);\r
756             mustMatchToken(ts, ts.RC, "msg.no.brace.block");\r
757             break;\r
758 \r
759         case TokenStream.ERROR:\r
760             // Fall thru, to have a node for error recovery to work on\r
761         case TokenStream.EOL:\r
762         case TokenStream.SEMI:\r
763             pn = nf.createLeaf(ts.VOID);\r
764             skipsemi = true;\r
765             break;\r
766 \r
767         default: {\r
768                 lastExprType = tt;\r
769                 int tokenno = ts.getTokenno();\r
770                 ts.ungetToken(tt);\r
771                 int lineno = ts.getLineno();\r
772 \r
773                 pn = expr(ts, source, false);\r
774 \r
775                 if (ts.peekToken() == ts.COLON) {\r
776                     /* check that the last thing the tokenizer returned was a\r
777                      * NAME and that only one token was consumed.\r
778                      */\r
779                     if (lastExprType != ts.NAME || (ts.getTokenno() != tokenno))\r
780                         reportError(ts, "msg.bad.label");\r
781 \r
782                     ts.getToken();  // eat the COLON\r
783 \r
784                     /* in the C source, the label is associated with the\r
785                      * statement that follows:\r
786                      *                nf.addChildToBack(pn, statement(ts));\r
787                      */\r
788                     String name = ts.getString();\r
789                     pn = nf.createLabel(name, lineno);\r
790 \r
791                     // depend on decompiling lookahead to guess that that\r
792                     // last name was a label.\r
793                     source.append((char)ts.COLON);\r
794                     source.append((char)ts.EOL);\r
795                     return pn;\r
796                 }\r
797 \r
798                 if (lastExprType == ts.FUNCTION)\r
799                     nf.setFunctionExpressionStatement(pn);\r
800 \r
801                 pn = nf.createExprStatement(pn, lineno);\r
802                 \r
803                 /*\r
804                  * Check explicitly against (multi-line) function\r
805                  * statement.\r
806 \r
807                  * lastExprEndLine is a hack to fix an\r
808                  * automatic semicolon insertion problem with function\r
809                  * expressions; the ts.getLineno() == lineno check was\r
810                  * firing after a function definition even though the\r
811                  * next statement was on a new line, because\r
812                  * speculative getToken calls advanced the line number\r
813                  * even when they didn't succeed.\r
814                  */\r
815                 if (ts.getLineno() == lineno ||\r
816                     (lastExprType == ts.FUNCTION &&\r
817                      ts.getLineno() == lastExprEndLine))\r
818                 {\r
819                     wellTerminated(ts, lastExprType);\r
820                 }\r
821                 break;\r
822             }\r
823         }\r
824         ts.matchToken(ts.SEMI);\r
825         if (!skipsemi) {\r
826             source.append((char)ts.SEMI);\r
827             source.append((char)ts.EOL);\r
828         }\r
829 \r
830         return pn;\r
831     }\r
832 \r
833     private Object variables(TokenStream ts, Source source, boolean inForInit)\r
834         throws IOException, JavaScriptException\r
835     {\r
836         Object pn = nf.createVariables(ts.getLineno());\r
837         boolean first = true;\r
838 \r
839         source.append((char)ts.VAR);\r
840 \r
841         for (;;) {\r
842             Object name;\r
843             Object init;\r
844             mustMatchToken(ts, ts.NAME, "msg.bad.var");\r
845             String s = ts.getString();\r
846 \r
847             if (!first)\r
848                 source.append((char)ts.COMMA);\r
849             first = false;\r
850 \r
851             source.addString(ts.NAME, s);\r
852             name = nf.createName(s);\r
853 \r
854             // omitted check for argument hiding\r
855 \r
856             if (ts.matchToken(ts.ASSIGN)) {\r
857                 if (ts.getOp() != ts.NOP)\r
858                     reportError(ts, "msg.bad.var.init");\r
859 \r
860                 source.append((char)ts.ASSIGN);\r
861                 source.append((char)ts.NOP);\r
862 \r
863                 init = assignExpr(ts, source, inForInit);\r
864                 nf.addChildToBack(name, init);\r
865             }\r
866             nf.addChildToBack(pn, name);\r
867             if (!ts.matchToken(ts.COMMA))\r
868                 break;\r
869         }\r
870         return pn;\r
871     }\r
872 \r
873     private Object expr(TokenStream ts, Source source, boolean inForInit)\r
874         throws IOException, JavaScriptException\r
875     {\r
876         Object pn = assignExpr(ts, source, inForInit);\r
877         while (ts.matchToken(ts.COMMA)) {\r
878             source.append((char)ts.COMMA);\r
879             pn = nf.createBinary(ts.COMMA, pn, assignExpr(ts, source,\r
880                                                           inForInit));\r
881         }\r
882         return pn;\r
883     }\r
884 \r
885     private Object assignExpr(TokenStream ts, Source source, boolean inForInit)\r
886         throws IOException, JavaScriptException\r
887     {\r
888         Object pn = condExpr(ts, source, inForInit);\r
889 \r
890         if (ts.matchToken(ts.ASSIGN)) {\r
891             // omitted: "invalid assignment left-hand side" check.\r
892             source.append((char)ts.ASSIGN);\r
893             source.append((char)ts.getOp());\r
894             pn = nf.createBinary(ts.ASSIGN, ts.getOp(), pn,\r
895                                  assignExpr(ts, source, inForInit));\r
896         }\r
897 \r
898         return pn;\r
899     }\r
900 \r
901     private Object condExpr(TokenStream ts, Source source, boolean inForInit)\r
902         throws IOException, JavaScriptException\r
903     {\r
904         Object ifTrue;\r
905         Object ifFalse;\r
906 \r
907         Object pn = orExpr(ts, source, inForInit);\r
908 \r
909         if (ts.matchToken(ts.HOOK)) {\r
910             source.append((char)ts.HOOK);\r
911             ifTrue = assignExpr(ts, source, false);\r
912             mustMatchToken(ts, ts.COLON, "msg.no.colon.cond");\r
913             source.append((char)ts.COLON);\r
914             ifFalse = assignExpr(ts, source, inForInit);\r
915             return nf.createTernary(pn, ifTrue, ifFalse);\r
916         }\r
917 \r
918         return pn;\r
919     }\r
920 \r
921     private Object orExpr(TokenStream ts, Source source, boolean inForInit)\r
922         throws IOException, JavaScriptException\r
923     {\r
924         Object pn = andExpr(ts, source, inForInit);\r
925         if (ts.matchToken(ts.OR)) {\r
926             source.append((char)ts.OR);\r
927             pn = nf.createBinary(ts.OR, pn, orExpr(ts, source, inForInit));\r
928         }\r
929 \r
930         return pn;\r
931     }\r
932 \r
933     private Object andExpr(TokenStream ts, Source source, boolean inForInit)\r
934         throws IOException, JavaScriptException\r
935     {\r
936         Object pn = bitOrExpr(ts, source, inForInit);\r
937         if (ts.matchToken(ts.AND)) {\r
938             source.append((char)ts.AND);\r
939             pn = nf.createBinary(ts.AND, pn, andExpr(ts, source, inForInit));\r
940         }\r
941 \r
942         return pn;\r
943     }\r
944 \r
945     private Object bitOrExpr(TokenStream ts, Source source, boolean inForInit)\r
946         throws IOException, JavaScriptException\r
947     {\r
948         Object pn = bitXorExpr(ts, source, inForInit);\r
949         while (ts.matchToken(ts.BITOR)) {\r
950             source.append((char)ts.BITOR);\r
951             pn = nf.createBinary(ts.BITOR, pn, bitXorExpr(ts, source,\r
952                                                           inForInit));\r
953         }\r
954         return pn;\r
955     }\r
956 \r
957     private Object bitXorExpr(TokenStream ts, Source source, boolean inForInit)\r
958         throws IOException, JavaScriptException\r
959     {\r
960         Object pn = bitAndExpr(ts, source, inForInit);\r
961         while (ts.matchToken(ts.BITXOR)) {\r
962             source.append((char)ts.BITXOR);\r
963             pn = nf.createBinary(ts.BITXOR, pn, bitAndExpr(ts, source,\r
964                                                            inForInit));\r
965         }\r
966         return pn;\r
967     }\r
968 \r
969     private Object bitAndExpr(TokenStream ts, Source source, boolean inForInit)\r
970         throws IOException, JavaScriptException\r
971     {\r
972         Object pn = eqExpr(ts, source, inForInit);\r
973         while (ts.matchToken(ts.BITAND)) {\r
974             source.append((char)ts.BITAND);\r
975             pn = nf.createBinary(ts.BITAND, pn, eqExpr(ts, source, inForInit));\r
976         }\r
977         return pn;\r
978     }\r
979 \r
980     private Object eqExpr(TokenStream ts, Source source, boolean inForInit)\r
981         throws IOException, JavaScriptException\r
982     {\r
983         Object pn = relExpr(ts, source, inForInit);\r
984         while (ts.matchToken(ts.EQOP)) {\r
985             source.append((char)ts.EQOP);\r
986             source.append((char)ts.getOp());\r
987             pn = nf.createBinary(ts.EQOP, ts.getOp(), pn,\r
988                                  relExpr(ts, source, inForInit));\r
989         }\r
990         return pn;\r
991     }\r
992 \r
993     private Object relExpr(TokenStream ts, Source source, boolean inForInit)\r
994         throws IOException, JavaScriptException\r
995     {\r
996         Object pn = shiftExpr(ts, source);\r
997         while (ts.matchToken(ts.RELOP)) {\r
998             int op = ts.getOp();\r
999             if (inForInit && op == ts.IN) {\r
1000                 ts.ungetToken(ts.RELOP);\r
1001                 break;\r
1002             }\r
1003             source.append((char)ts.RELOP);\r
1004             source.append((char)op);\r
1005             pn = nf.createBinary(ts.RELOP, op, pn,\r
1006                                  shiftExpr(ts, source));\r
1007         }\r
1008         return pn;\r
1009     }\r
1010 \r
1011     private Object shiftExpr(TokenStream ts, Source source)\r
1012         throws IOException, JavaScriptException\r
1013     {\r
1014         Object pn = addExpr(ts, source);\r
1015         while (ts.matchToken(ts.SHOP)) {\r
1016             source.append((char)ts.SHOP);\r
1017             source.append((char)ts.getOp());\r
1018             pn = nf.createBinary(ts.getOp(), pn, addExpr(ts, source));\r
1019         }\r
1020         return pn;\r
1021     }\r
1022 \r
1023     private Object addExpr(TokenStream ts, Source source)\r
1024         throws IOException, JavaScriptException\r
1025     {\r
1026         int tt;\r
1027         Object pn = mulExpr(ts, source);\r
1028 \r
1029         while ((tt = ts.getToken()) == ts.ADD || tt == ts.SUB) {\r
1030             source.append((char)tt);\r
1031             // flushNewLines\r
1032             pn = nf.createBinary(tt, pn, mulExpr(ts, source));\r
1033         }\r
1034         ts.ungetToken(tt);\r
1035 \r
1036         return pn;\r
1037     }\r
1038 \r
1039     private Object mulExpr(TokenStream ts, Source source)\r
1040         throws IOException, JavaScriptException\r
1041     {\r
1042         int tt;\r
1043 \r
1044         Object pn = unaryExpr(ts, source);\r
1045 \r
1046         while ((tt = ts.peekToken()) == ts.MUL ||\r
1047                tt == ts.DIV ||\r
1048                tt == ts.MOD) {\r
1049             tt = ts.getToken();\r
1050             source.append((char)tt);\r
1051             pn = nf.createBinary(tt, pn, unaryExpr(ts, source));\r
1052         }\r
1053 \r
1054 \r
1055         return pn;\r
1056     }\r
1057 \r
1058     private Object unaryExpr(TokenStream ts, Source source)\r
1059         throws IOException, JavaScriptException\r
1060     {\r
1061         int tt;\r
1062 \r
1063         ts.flags |= ts.TSF_REGEXP;\r
1064         tt = ts.getToken();\r
1065         ts.flags &= ~ts.TSF_REGEXP;\r
1066 \r
1067         switch(tt) {\r
1068         case TokenStream.UNARYOP:\r
1069             source.append((char)ts.UNARYOP);\r
1070             source.append((char)ts.getOp());\r
1071             return nf.createUnary(ts.UNARYOP, ts.getOp(),\r
1072                                   unaryExpr(ts, source));\r
1073 \r
1074         case TokenStream.ADD:\r
1075         case TokenStream.SUB:\r
1076             source.append((char)ts.UNARYOP);\r
1077             source.append((char)tt);\r
1078             return nf.createUnary(ts.UNARYOP, tt, unaryExpr(ts, source));\r
1079 \r
1080         case TokenStream.INC:\r
1081         case TokenStream.DEC:\r
1082             source.append((char)tt);\r
1083             return nf.createUnary(tt, ts.PRE, memberExpr(ts, source, true));\r
1084 \r
1085         case TokenStream.DELPROP:\r
1086             source.append((char)ts.DELPROP);\r
1087             return nf.createUnary(ts.DELPROP, unaryExpr(ts, source));\r
1088 \r
1089         case TokenStream.ERROR:\r
1090             break;\r
1091 \r
1092         default:\r
1093             ts.ungetToken(tt);\r
1094 \r
1095             int lineno = ts.getLineno();\r
1096 \r
1097             Object pn = memberExpr(ts, source, true);\r
1098 \r
1099             /* don't look across a newline boundary for a postfix incop.\r
1100 \r
1101              * the rhino scanner seems to work differently than the js\r
1102              * scanner here; in js, it works to have the line number check\r
1103              * precede the peekToken calls.  It'd be better if they had\r
1104              * similar behavior...\r
1105              */\r
1106             int peeked;\r
1107             if (((peeked = ts.peekToken()) == ts.INC ||\r
1108                  peeked == ts.DEC) &&\r
1109                 ts.getLineno() == lineno)\r
1110             {\r
1111                 int pf = ts.getToken();\r
1112                 source.append((char)pf);\r
1113                 return nf.createUnary(pf, ts.POST, pn);\r
1114             }\r
1115             return pn;\r
1116         }\r
1117         return nf.createName("err"); // Only reached on error.  Try to continue.\r
1118         \r
1119     }\r
1120 \r
1121     private Object argumentList(TokenStream ts, Source source, Object listNode)\r
1122         throws IOException, JavaScriptException\r
1123     {\r
1124         boolean matched;\r
1125         ts.flags |= ts.TSF_REGEXP;\r
1126         matched = ts.matchToken(ts.RP);\r
1127         ts.flags &= ~ts.TSF_REGEXP;\r
1128         if (!matched) {\r
1129             boolean first = true;\r
1130             do {\r
1131                 if (!first)\r
1132                     source.append((char)ts.COMMA);\r
1133                 first = false;\r
1134                 nf.addChildToBack(listNode, assignExpr(ts, source, false));\r
1135             } while (ts.matchToken(ts.COMMA));\r
1136             \r
1137             mustMatchToken(ts, ts.RP, "msg.no.paren.arg");\r
1138         }\r
1139         source.append((char)ts.RP);\r
1140         return listNode;\r
1141     }\r
1142 \r
1143     private Object memberExpr(TokenStream ts, Source source,\r
1144                               boolean allowCallSyntax)\r
1145         throws IOException, JavaScriptException\r
1146     {\r
1147         int tt;\r
1148 \r
1149         Object pn;\r
1150         \r
1151         /* Check for new expressions. */\r
1152         ts.flags |= ts.TSF_REGEXP;\r
1153         tt = ts.peekToken();\r
1154         ts.flags &= ~ts.TSF_REGEXP;\r
1155         if (tt == ts.NEW) {\r
1156             /* Eat the NEW token. */\r
1157             ts.getToken();\r
1158             source.append((char)ts.NEW);\r
1159 \r
1160             /* Make a NEW node to append to. */\r
1161             pn = nf.createLeaf(ts.NEW);\r
1162             nf.addChildToBack(pn, memberExpr(ts, source, false));\r
1163 \r
1164             if (ts.matchToken(ts.LP)) {\r
1165                 source.append((char)ts.LP);\r
1166                 /* Add the arguments to pn, if any are supplied. */\r
1167                 pn = argumentList(ts, source, pn);\r
1168             }\r
1169 \r
1170             /* XXX there's a check in the C source against\r
1171              * "too many constructor arguments" - how many\r
1172              * do we claim to support?\r
1173              */\r
1174             \r
1175             /* Experimental syntax:  allow an object literal to follow a new expression,\r
1176              * which will mean a kind of anonymous class built with the JavaAdapter.\r
1177              * the object literal will be passed as an additional argument to the constructor.\r
1178              */\r
1179             tt = ts.peekToken();\r
1180             if (tt == ts.LC) {\r
1181                 nf.addChildToBack(pn, primaryExpr(ts, source));\r
1182             }\r
1183         } else {\r
1184             pn = primaryExpr(ts, source);\r
1185         }\r
1186 \r
1187         lastExprEndLine = ts.getLineno();\r
1188         while ((tt = ts.getToken()) > ts.EOF) {\r
1189             if (tt == ts.DOT) {\r
1190                 source.append((char)ts.DOT);\r
1191                 mustMatchToken(ts, ts.NAME, "msg.no.name.after.dot");\r
1192                 String s = ts.getString();\r
1193                 source.addString(ts.NAME, s);\r
1194                 pn = nf.createBinary(ts.DOT, pn,\r
1195                                      nf.createName(ts.getString()));\r
1196                 /* pn = nf.createBinary(ts.DOT, pn, memberExpr(ts))\r
1197                  * is the version in Brendan's IR C version.  Not in ECMA...\r
1198                  * does it reflect the 'new' operator syntax he mentioned?\r
1199                  */\r
1200                 lastExprEndLine = ts.getLineno();\r
1201             } else if (tt == ts.LB) {\r
1202                 source.append((char)ts.LB);\r
1203                 pn = nf.createBinary(ts.LB, pn, expr(ts, source, false));\r
1204 \r
1205                 mustMatchToken(ts, ts.RB, "msg.no.bracket.index");\r
1206                 source.append((char)ts.RB);\r
1207                 lastExprEndLine = ts.getLineno();\r
1208             } else if (allowCallSyntax && tt == ts.LP) {\r
1209                 /* make a call node */\r
1210 \r
1211                 pn = nf.createUnary(ts.CALL, pn);\r
1212                 source.append((char)ts.LP);\r
1213                 \r
1214                 /* Add the arguments to pn, if any are supplied. */\r
1215                 pn = argumentList(ts, source, pn);\r
1216                 lastExprEndLine = ts.getLineno();\r
1217             } else {\r
1218                 ts.ungetToken(tt);\r
1219 \r
1220                 break;\r
1221             }\r
1222         }\r
1223 \r
1224         return pn;\r
1225     }\r
1226 \r
1227     private Object primaryExpr(TokenStream ts, Source source)\r
1228         throws IOException, JavaScriptException\r
1229     {\r
1230         int tt;\r
1231 \r
1232         Object pn;\r
1233 \r
1234         ts.flags |= ts.TSF_REGEXP;\r
1235         tt = ts.getToken();\r
1236         ts.flags &= ~ts.TSF_REGEXP;\r
1237 \r
1238         switch(tt) {\r
1239 \r
1240         case TokenStream.FUNCTION:\r
1241             return function(ts, source, true);\r
1242 \r
1243         case TokenStream.LB:\r
1244             {\r
1245                 source.append((char)ts.LB);\r
1246                 pn = nf.createLeaf(ts.ARRAYLIT);\r
1247 \r
1248                 ts.flags |= ts.TSF_REGEXP;\r
1249                 boolean matched = ts.matchToken(ts.RB);\r
1250                 ts.flags &= ~ts.TSF_REGEXP;\r
1251 \r
1252                 if (!matched) {\r
1253                     boolean first = true;\r
1254                     do {\r
1255                         ts.flags |= ts.TSF_REGEXP;\r
1256                         tt = ts.peekToken();\r
1257                         ts.flags &= ~ts.TSF_REGEXP;\r
1258 \r
1259                         if (!first)\r
1260                             source.append((char)ts.COMMA);\r
1261                         else\r
1262                             first = false;\r
1263 \r
1264                         if (tt == ts.RB) {  // to fix [,,,].length behavior...\r
1265                             break;\r
1266                         }\r
1267 \r
1268                         if (tt == ts.COMMA) {\r
1269                             nf.addChildToBack(pn, nf.createLeaf(ts.PRIMARY,\r
1270                                                                 ts.UNDEFINED));\r
1271                         } else {\r
1272                             nf.addChildToBack(pn, assignExpr(ts, source, false));\r
1273                         }\r
1274 \r
1275                     } while (ts.matchToken(ts.COMMA));\r
1276                     mustMatchToken(ts, ts.RB, "msg.no.bracket.arg");\r
1277                 }\r
1278                 source.append((char)ts.RB);\r
1279                 return nf.createArrayLiteral(pn);\r
1280             }\r
1281 \r
1282         case TokenStream.LC: {\r
1283             pn = nf.createLeaf(ts.OBJLIT);\r
1284 \r
1285             source.append((char)ts.LC);\r
1286             if (!ts.matchToken(ts.RC)) {\r
1287 \r
1288                 boolean first = true;\r
1289             commaloop:\r
1290                 do {\r
1291                     Object property;\r
1292 \r
1293                     if (!first)\r
1294                         source.append((char)ts.COMMA);\r
1295                     else\r
1296                         first = false;\r
1297 \r
1298                     tt = ts.getToken();\r
1299                     switch(tt) {\r
1300                         // map NAMEs to STRINGs in object literal context.\r
1301                     case TokenStream.NAME:\r
1302                     case TokenStream.STRING:\r
1303                         String s = ts.getString();\r
1304                         source.addString(ts.NAME, s);\r
1305                         property = nf.createString(ts.getString());\r
1306                         break;\r
1307                     case TokenStream.NUMBER:\r
1308                         Number n = ts.getNumber();\r
1309                         source.addNumber(n);\r
1310                         property = nf.createNumber(n);\r
1311                         break;\r
1312                     case TokenStream.RC:\r
1313                         // trailing comma is OK.\r
1314                         ts.ungetToken(tt);\r
1315                         break commaloop;\r
1316                     default:\r
1317                         reportError(ts, "msg.bad.prop");\r
1318                         break commaloop;\r
1319                     }\r
1320                     mustMatchToken(ts, ts.COLON, "msg.no.colon.prop");\r
1321 \r
1322                     // OBJLIT is used as ':' in object literal for\r
1323                     // decompilation to solve spacing ambiguity.\r
1324                     source.append((char)ts.OBJLIT);\r
1325                     nf.addChildToBack(pn, property);\r
1326                     nf.addChildToBack(pn, assignExpr(ts, source, false));\r
1327 \r
1328                 } while (ts.matchToken(ts.COMMA));\r
1329 \r
1330                 mustMatchToken(ts, ts.RC, "msg.no.brace.prop");\r
1331             }\r
1332             source.append((char)ts.RC);\r
1333             return nf.createObjectLiteral(pn);\r
1334         }\r
1335 \r
1336         case TokenStream.LP:\r
1337 \r
1338             /* Brendan's IR-jsparse.c makes a new node tagged with\r
1339              * TOK_LP here... I'm not sure I understand why.  Isn't\r
1340              * the grouping already implicit in the structure of the\r
1341              * parse tree?  also TOK_LP is already overloaded (I\r
1342              * think) in the C IR as 'function call.'  */\r
1343             source.append((char)ts.LP);\r
1344             pn = expr(ts, source, false);\r
1345             source.append((char)ts.RP);\r
1346             mustMatchToken(ts, ts.RP, "msg.no.paren");\r
1347             return pn;\r
1348 \r
1349         case TokenStream.NAME:\r
1350             String name = ts.getString();\r
1351             source.addString(ts.NAME, name);\r
1352             return nf.createName(name);\r
1353 \r
1354         case TokenStream.NUMBER:\r
1355             Number n = ts.getNumber();\r
1356             source.addNumber(n);\r
1357             return nf.createNumber(n);\r
1358 \r
1359         case TokenStream.STRING:\r
1360             String s = ts.getString();\r
1361             source.addString(ts.STRING, s);\r
1362             return nf.createString(s);\r
1363 \r
1364         case TokenStream.OBJECT:\r
1365         {\r
1366             String flags = ts.regExpFlags;\r
1367             ts.regExpFlags = null;\r
1368             String re = ts.getString();\r
1369             source.addString(ts.OBJECT, '/' + re + '/' + flags);\r
1370             return nf.createRegExp(re, flags);\r
1371         }\r
1372 \r
1373         case TokenStream.PRIMARY:\r
1374             source.append((char)ts.PRIMARY);\r
1375             source.append((char)ts.getOp());\r
1376             return nf.createLeaf(ts.PRIMARY, ts.getOp());\r
1377 \r
1378         case TokenStream.RESERVED:\r
1379             reportError(ts, "msg.reserved.id");\r
1380             break;\r
1381 \r
1382         case TokenStream.ERROR:\r
1383             /* the scanner or one of its subroutines reported the error. */\r
1384             break;\r
1385 \r
1386         default:\r
1387             reportError(ts, "msg.syntax");\r
1388             break;\r
1389 \r
1390         }\r
1391         return null;    // should never reach here\r
1392     }\r
1393 \r
1394     private int lastExprEndLine; // Hack to handle function expr termination.\r
1395     private IRFactory nf;\r
1396     private ErrorReporter er;\r
1397     private boolean ok; // Did the parse encounter an error?\r
1398 }\r
1399 \r
1400 /**\r
1401  * This class saves decompilation information about the source.\r
1402  * Source information is returned from the parser as a String\r
1403  * associated with function nodes and with the toplevel script.  When\r
1404  * saved in the constant pool of a class, this string will be UTF-8\r
1405  * encoded, and token values will occupy a single byte.\r
1406 \r
1407  * Source is saved (mostly) as token numbers.  The tokens saved pretty\r
1408  * much correspond to the token stream of a 'canonical' representation\r
1409  * of the input program, as directed by the parser.  (There were a few\r
1410  * cases where tokens could have been left out where decompiler could\r
1411  * easily reconstruct them, but I left them in for clarity).  (I also\r
1412  * looked adding source collection to TokenStream instead, where I\r
1413  * could have limited the changes to a few lines in getToken... but\r
1414  * this wouldn't have saved any space in the resulting source\r
1415  * representation, and would have meant that I'd have to duplicate\r
1416  * parser logic in the decompiler to disambiguate situations where\r
1417  * newlines are important.)  NativeFunction.decompile expands the\r
1418  * tokens back into their string representations, using simple\r
1419  * lookahead to correct spacing and indentation.\r
1420 \r
1421  * Token types with associated ops (ASSIGN, SHOP, PRIMARY, etc.) are\r
1422  * saved as two-token pairs.  Number tokens are stored inline, as a\r
1423  * NUMBER token, a character representing the type, and either 1 or 4\r
1424  * characters representing the bit-encoding of the number.  String\r
1425  * types NAME, STRING and OBJECT are currently stored as a token type,\r
1426  * followed by a character giving the length of the string (assumed to\r
1427  * be less than 2^16), followed by the characters of the string\r
1428  * inlined into the source string.  Changing this to some reference to\r
1429  * to the string in the compiled class' constant pool would probably\r
1430  * save a lot of space... but would require some method of deriving\r
1431  * the final constant pool entry from information available at parse\r
1432  * time.\r
1433 \r
1434  * Nested functions need a similar mechanism... fortunately the nested\r
1435  * functions for a given function are generated in source order.\r
1436  * Nested functions are encoded as FUNCTION followed by a function\r
1437  * number (encoded as a character), which is enough information to\r
1438  * find the proper generated NativeFunction instance.\r
1439 \r
1440  * OPT source info collection is a potential performance bottleneck;\r
1441  * Source wraps a java.lang.StringBuffer, which is synchronized.  It\r
1442  * might be faster to implement Source with its own char buffer and\r
1443  * toString method.\r
1444 \r
1445  */\r
1446 \r
1447 final class Source {\r
1448     Source() {\r
1449         // OPT the default 16 is probably too small, but it's not\r
1450         // clear just what size would work best for most javascript.\r
1451         // It'd be nice to know what characterizes the javascript\r
1452         // that's out there.\r
1453         buf = new StringBuffer(64);\r
1454     }\r
1455 \r
1456     void append(char c) {\r
1457         buf.append(c);\r
1458     }\r
1459 \r
1460     void addString(int type, String str) {\r
1461         buf.append((char)type);\r
1462         // java string length < 2^16?\r
1463         buf.append((char)str.length());\r
1464         buf.append(str);\r
1465     }\r
1466 \r
1467     void addNumber(Number n) {\r
1468         buf.append((char)TokenStream.NUMBER);\r
1469 \r
1470         /* encode the number in the source stream.\r
1471          * Save as NUMBER type (char | char char char char)\r
1472          * where type is\r
1473          * 'D' - double, 'S' - short, 'J' - long.\r
1474 \r
1475          * We need to retain float vs. integer type info to keep the\r
1476          * behavior of liveconnect type-guessing the same after\r
1477          * decompilation.  (Liveconnect tries to present 1.0 to Java\r
1478          * as a float/double)\r
1479          * OPT: This is no longer true. We could compress the format.\r
1480 \r
1481          * This may not be the most space-efficient encoding;\r
1482          * the chars created below may take up to 3 bytes in\r
1483          * constant pool UTF-8 encoding, so a Double could take\r
1484          * up to 12 bytes.\r
1485          */\r
1486 \r
1487         if (n instanceof Double || n instanceof Float) {\r
1488             // if it's floating point, save as a Double bit pattern.\r
1489             // (12/15/97 our scanner only returns Double for f.p.)\r
1490             buf.append('D');\r
1491             long lbits = Double.doubleToLongBits(n.doubleValue());\r
1492 \r
1493             buf.append((char)((lbits >> 48) & 0xFFFF));\r
1494             buf.append((char)((lbits >> 32) & 0xFFFF));\r
1495             buf.append((char)((lbits >> 16) & 0xFFFF));\r
1496             buf.append((char)(lbits & 0xFFFF));\r
1497         } else {\r
1498             long lbits = n.longValue();\r
1499             // will it fit in a char?\r
1500             // (we can ignore negative values, bc they're already prefixed\r
1501             //  by UNARYOP SUB)\r
1502             // this gives a short encoding for integer values up to 2^16.\r
1503             if (lbits <= Character.MAX_VALUE) {\r
1504                 buf.append('S');\r
1505                 buf.append((char)lbits);\r
1506             } else { // Integral, but won't fit in a char. Store as a long.\r
1507                 buf.append('J');\r
1508                 buf.append((char)((lbits >> 48) & 0xFFFF));\r
1509                 buf.append((char)((lbits >> 32) & 0xFFFF));\r
1510                 buf.append((char)((lbits >> 16) & 0xFFFF));\r
1511                 buf.append((char)(lbits & 0xFFFF));\r
1512             }\r
1513         }\r
1514     }\r
1515 \r
1516     char functionNumber;\r
1517     StringBuffer buf;\r
1518 }\r
1519 \r