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