001    /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
002     *
003     * The contents of this file are subject to the Netscape Public
004     * License Version 1.1 (the "License"); you may not use this file
005     * except in compliance with the License. You may obtain a copy of
006     * the License at http://www.mozilla.org/NPL/
007     *
008     * Software distributed under the License is distributed on an "AS
009     * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr
010     * implied. See the License for the specific language governing
011     * rights and limitations under the License.
012     *
013     * The Original Code is Rhino code, released
014     * May 6, 1999.
015     *
016     * The Initial Developer of the Original Code is Netscape
017     * Communications Corporation.  Portions created by Netscape are
018     * Copyright (C) 1997-1999 Netscape Communications Corporation. All
019     * Rights Reserved.
020     *
021     * Contributor(s): 
022     * Mike Ang
023     * Mike McCabe
024     *
025     * Alternatively, the contents of this file may be used under the
026     * terms of the GNU Public License (the "GPL"), in which case the
027     * provisions of the GPL are applicable instead of those above.
028     * If you wish to allow use of your version of this file only
029     * under the terms of the GPL and not to allow others to use your
030     * version of this file under the NPL, indicate your decision by
031     * deleting the provisions above and replace them with the notice
032     * and other provisions required by the GPL.  If you do not delete
033     * the provisions above, a recipient may use your version of this
034     * file under either the NPL or the GPL.
035     */
036    // Modified by Google
037    
038    package com.google.gwt.dev.js.rhino;
039    
040    import java.io.IOException;
041    
042    /**
043     * This class implements the JavaScript parser.
044     * 
045     * It is based on the C source files jsparse.c and jsparse.h in the jsref
046     * package.
047     * 
048     * @see TokenStream
049     */
050    
051    public class Parser {
052      public Parser(IRFactory nf, boolean insideFunction) {
053          this.nf = nf;
054          this.insideFunction = insideFunction;
055      }
056    
057      private void mustMatchToken(TokenStream ts, int toMatch, String messageId)
058          throws IOException, JavaScriptException {
059        int tt;
060        if ((tt = ts.getToken()) != toMatch) {
061          reportError(ts, messageId);
062          ts.ungetToken(tt); // In case the parser decides to continue
063        }
064      }
065    
066      private void reportError(TokenStream ts, String messageId)
067          throws JavaScriptException {
068        this.ok = false;
069        ts.reportSyntaxError(messageId, null);
070    
071        /*
072         * Throw an exception to unwind the recursive descent parse. We use
073         * JavaScriptException here even though it is really a different use of the
074         * exception than it is usually used for.
075         */
076        throw new JavaScriptException(messageId);
077      }
078    
079      /*
080       * Build a parse tree from the given TokenStream.
081       * 
082       * @param ts the TokenStream to parse
083       * 
084       * @return an Object representing the parsed program. If the parse fails, null
085       * will be returned. (The parse failure will result in a call to the current
086       * Context's ErrorReporter.)
087       */
088      public Object parse(TokenStream ts) throws IOException {
089        this.ok = true;
090        sourceTop = 0;
091        functionNumber = 0;
092    
093        int tt; // last token from getToken();
094        int baseLineno = ts.getLineno(); // line number where source starts
095    
096        /*
097         * so we have something to add nodes to until we've collected all the source
098         */
099        Object tempBlock = nf.createLeaf(TokenStream.BLOCK);
100        ((Node) tempBlock).setIsSyntheticBlock(true);
101    
102        while (true) {
103          ts.flags |= ts.TSF_REGEXP;
104          tt = ts.getToken();
105          ts.flags &= ~ts.TSF_REGEXP;
106    
107          if (tt <= ts.EOF) {
108            break;
109          }
110    
111          if (tt == ts.FUNCTION) {
112            try {
113              nf.addChildToBack(tempBlock, function(ts, false));
114            } catch (JavaScriptException e) {
115              this.ok = false;
116              break;
117            }
118          } else {
119            ts.ungetToken(tt);
120            nf.addChildToBack(tempBlock, statement(ts));
121          }
122        }
123    
124        if (!this.ok) {
125          // XXX ts.clearPushback() call here?
126          return null;
127        }
128    
129        Object pn = nf.createScript(tempBlock, ts.getSourceName(), baseLineno, ts
130          .getLineno(), null);
131        ((Node) pn).setIsSyntheticBlock(true);
132        return pn;
133      }
134    
135      /*
136       * The C version of this function takes an argument list, which doesn't seem
137       * to be needed for tree generation... it'd only be useful for checking
138       * argument hiding, which I'm not doing anyway...
139       */
140      private Object parseFunctionBody(TokenStream ts) throws IOException {
141        int oldflags = ts.flags;
142        ts.flags &= ~(TokenStream.TSF_RETURN_EXPR | TokenStream.TSF_RETURN_VOID);
143        ts.flags |= TokenStream.TSF_FUNCTION;
144    
145        Object pn = nf.createBlock(ts.getLineno());
146        try {
147          int tt;
148          while ((tt = ts.peekToken()) > ts.EOF && tt != ts.RC) {
149            if (tt == TokenStream.FUNCTION) {
150              ts.getToken();
151              nf.addChildToBack(pn, function(ts, false));
152            } else {
153              nf.addChildToBack(pn, statement(ts));
154            }
155          }
156        } catch (JavaScriptException e) {
157          this.ok = false;
158        } finally {
159          // also in finally block:
160          // flushNewLines, clearPushback.
161    
162          ts.flags = oldflags;
163        }
164    
165        return pn;
166      }
167    
168      private Object function(TokenStream ts, boolean isExpr) throws IOException,
169          JavaScriptException {
170        int baseLineno = ts.getLineno(); // line number where source starts
171    
172        String name;
173        Object memberExprNode = null;
174        if (ts.matchToken(ts.NAME)) {
175          name = ts.getString();
176          if (!ts.matchToken(ts.LP)) {
177            if (Context.getContext().hasFeature(
178              Context.FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME)) {
179              // Extension to ECMA: if 'function <name>' does not follow
180              // by '(', assume <name> starts memberExpr
181              Object memberExprHead = nf.createName(name);
182              name = null;
183              memberExprNode = memberExprTail(ts, false, memberExprHead);
184            }
185            mustMatchToken(ts, ts.LP, "msg.no.paren.parms");
186          }
187        } else if (ts.matchToken(ts.LP)) {
188          // Anonymous function
189          name = null;
190        } else {
191          name = null;
192          if (Context.getContext().hasFeature(
193            Context.FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME)) {
194            // Note that memberExpr can not start with '(' like
195            // in (1+2).toString, because 'function (' already
196            // processed as anonymous function
197            memberExprNode = memberExpr(ts, false);
198          }
199          mustMatchToken(ts, ts.LP, "msg.no.paren.parms");
200        }
201    
202        ++functionNumber;
203    
204        // Save current source top to restore it on exit not to include
205        // function to parent source
206        int savedSourceTop = sourceTop;
207        int savedFunctionNumber = functionNumber;
208        Object args;
209        Object body;
210        try {
211          functionNumber = 0;
212          args = nf.createLeaf(ts.LP);
213    
214          if (!ts.matchToken(ts.GWT)) {
215            do {
216              mustMatchToken(ts, ts.NAME, "msg.no.parm");
217              String s = ts.getString();
218              nf.addChildToBack(args, nf.createName(s));
219    
220            } while (ts.matchToken(ts.COMMA));
221    
222            mustMatchToken(ts, ts.GWT, "msg.no.paren.after.parms");
223          }
224    
225          mustMatchToken(ts, ts.LC, "msg.no.brace.body");
226          body = parseFunctionBody(ts);
227          mustMatchToken(ts, ts.RC, "msg.no.brace.after.body");
228          // skip the last EOL so nested functions work...
229        } finally {
230          sourceTop = savedSourceTop;
231          functionNumber = savedFunctionNumber;
232        }
233    
234        Object pn = nf.createFunction(name, args, body, ts.getSourceName(),
235          baseLineno, ts.getLineno(), null, isExpr || memberExprNode != null);
236        if (memberExprNode != null) {
237          pn = nf.createBinary(ts.ASSIGN, ts.NOP, memberExprNode, pn);
238        }
239    
240        // Add EOL but only if function is not part of expression, in which
241        // case it gets SEMI + EOL from Statement.
242        if (!isExpr) {
243          wellTerminated(ts, ts.FUNCTION);
244        }
245    
246        return pn;
247      }
248    
249      private Object statements(TokenStream ts) throws IOException {
250        Object pn = nf.createBlock(ts.getLineno());
251    
252        int tt;
253        while ((tt = ts.peekToken()) > ts.EOF && tt != ts.RC) {
254          nf.addChildToBack(pn, statement(ts));
255        }
256    
257        return pn;
258      }
259    
260      private Object condition(TokenStream ts) throws IOException,
261          JavaScriptException {
262        Object pn;
263        mustMatchToken(ts, ts.LP, "msg.no.paren.cond");
264        pn = expr(ts, false);
265        mustMatchToken(ts, ts.GWT, "msg.no.paren.after.cond");
266    
267        // there's a check here in jsparse.c that corrects = to ==
268    
269        return pn;
270      }
271    
272      private boolean wellTerminated(TokenStream ts, int lastExprType)
273          throws IOException, JavaScriptException {
274        int tt = ts.peekTokenSameLine();
275        if (tt == ts.ERROR) {
276          return false;
277        }
278    
279        if (tt != ts.EOF && tt != ts.EOL && tt != ts.SEMI && tt != ts.RC) {
280          int version = Context.getContext().getLanguageVersion();
281          if ((tt == ts.FUNCTION || lastExprType == ts.FUNCTION)
282            && (version < Context.VERSION_1_2)) {
283            /*
284             * Checking against version < 1.2 and version >= 1.0 in the above line
285             * breaks old javascript, so we keep it this way for now... XXX warning
286             * needed?
287             */
288            return true;
289          } else {
290            reportError(ts, "msg.no.semi.stmt");
291          }
292        }
293        return true;
294      }
295    
296      // match a NAME; return null if no match.
297      private String matchLabel(TokenStream ts) throws IOException,
298          JavaScriptException {
299        int lineno = ts.getLineno();
300    
301        String label = null;
302        int tt;
303        tt = ts.peekTokenSameLine();
304        if (tt == ts.NAME) {
305          ts.getToken();
306          label = ts.getString();
307        }
308    
309        if (lineno == ts.getLineno())
310          wellTerminated(ts, ts.ERROR);
311    
312        return label;
313      }
314    
315      private Object statement(TokenStream ts) throws IOException {
316        try {
317          return statementHelper(ts);
318        } catch (JavaScriptException e) {
319          // skip to end of statement
320          int lineno = ts.getLineno();
321          int t;
322          do {
323            t = ts.getToken();
324          } while (t != TokenStream.SEMI && t != TokenStream.EOL
325            && t != TokenStream.EOF && t != TokenStream.ERROR);
326          return nf.createExprStatement(nf.createName("error"), lineno);
327        }
328      }
329    
330      /**
331       * Whether the "catch (e: e instanceof Exception) { ... }" syntax is
332       * implemented.
333       */
334    
335      private Object statementHelper(TokenStream ts) throws IOException,
336          JavaScriptException {
337        Object pn = null;
338    
339        // If skipsemi == true, don't add SEMI + EOL to source at the
340        // end of this statment. For compound statements, IF/FOR etc.
341        boolean skipsemi = false;
342    
343        int tt;
344    
345        int lastExprType = 0; // For wellTerminated. 0 to avoid warning.
346    
347        tt = ts.getToken();
348    
349        switch (tt) {
350          case TokenStream.IF: {
351            skipsemi = true;
352    
353            int lineno = ts.getLineno();
354            Object cond = condition(ts);
355            Object ifTrue = statement(ts);
356            Object ifFalse = null;
357            if (ts.matchToken(ts.ELSE)) {
358              ifFalse = statement(ts);
359            }
360            pn = nf.createIf(cond, ifTrue, ifFalse, lineno);
361            break;
362          }
363    
364          case TokenStream.SWITCH: {
365            skipsemi = true;
366    
367            pn = nf.createSwitch(ts.getLineno());
368    
369            Object cur_case = null; // to kill warning
370            Object case_statements;
371    
372            mustMatchToken(ts, ts.LP, "msg.no.paren.switch");
373            nf.addChildToBack(pn, expr(ts, false));
374            mustMatchToken(ts, ts.GWT, "msg.no.paren.after.switch");
375            mustMatchToken(ts, ts.LC, "msg.no.brace.switch");
376    
377            while ((tt = ts.getToken()) != ts.RC && tt != ts.EOF) {
378              switch (tt) {
379                case TokenStream.CASE:
380                  cur_case = nf.createUnary(ts.CASE, expr(ts, false));
381                  break;
382    
383                case TokenStream.DEFAULT:
384                  cur_case = nf.createLeaf(ts.DEFAULT);
385                  // XXX check that there isn't more than one default
386                  break;
387    
388                default:
389                  reportError(ts, "msg.bad.switch");
390                  break;
391              }
392              mustMatchToken(ts, ts.COLON, "msg.no.colon.case");
393    
394              case_statements = nf.createLeaf(TokenStream.BLOCK);
395              ((Node) case_statements).setIsSyntheticBlock(true);
396    
397              while ((tt = ts.peekToken()) != ts.RC && tt != ts.CASE
398                && tt != ts.DEFAULT && tt != ts.EOF) {
399                nf.addChildToBack(case_statements, statement(ts));
400              }
401              // assert cur_case
402              nf.addChildToBack(cur_case, case_statements);
403    
404              nf.addChildToBack(pn, cur_case);
405            }
406            break;
407          }
408    
409          case TokenStream.WHILE: {
410            skipsemi = true;
411    
412            int lineno = ts.getLineno();
413            Object cond = condition(ts);
414            Object body = statement(ts);
415    
416            pn = nf.createWhile(cond, body, lineno);
417            break;
418    
419          }
420    
421          case TokenStream.DO: {
422            int lineno = ts.getLineno();
423    
424            Object body = statement(ts);
425    
426            mustMatchToken(ts, ts.WHILE, "msg.no.while.do");
427            Object cond = condition(ts);
428    
429            pn = nf.createDoWhile(body, cond, lineno);
430            break;
431          }
432    
433          case TokenStream.FOR: {
434            skipsemi = true;
435    
436            int lineno = ts.getLineno();
437    
438            Object init; // Node init is also foo in 'foo in Object'
439            Object cond; // Node cond is also object in 'foo in Object'
440            Object incr = null; // to kill warning
441            Object body;
442    
443            mustMatchToken(ts, ts.LP, "msg.no.paren.for");
444            tt = ts.peekToken();
445            if (tt == ts.SEMI) {
446              init = nf.createLeaf(ts.VOID);
447            } else {
448              if (tt == ts.VAR) {
449                // set init to a var list or initial
450                ts.getToken(); // throw away the 'var' token
451                init = variables(ts, true);
452              } else {
453                init = expr(ts, true);
454              }
455            }
456    
457            tt = ts.peekToken();
458            if (tt == ts.RELOP && ts.getOp() == ts.IN) {
459              ts.matchToken(ts.RELOP);
460              // 'cond' is the object over which we're iterating
461              cond = expr(ts, false);
462            } else { // ordinary for loop
463              mustMatchToken(ts, ts.SEMI, "msg.no.semi.for");
464              if (ts.peekToken() == ts.SEMI) {
465                // no loop condition
466                cond = nf.createLeaf(ts.VOID);
467              } else {
468                cond = expr(ts, false);
469              }
470    
471              mustMatchToken(ts, ts.SEMI, "msg.no.semi.for.cond");
472              if (ts.peekToken() == ts.GWT) {
473                incr = nf.createLeaf(ts.VOID);
474              } else {
475                incr = expr(ts, false);
476              }
477            }
478    
479            mustMatchToken(ts, ts.GWT, "msg.no.paren.for.ctrl");
480            body = statement(ts);
481    
482            if (incr == null) {
483              // cond could be null if 'in obj' got eaten by the init node.
484              pn = nf.createForIn(init, cond, body, lineno);
485            } else {
486              pn = nf.createFor(init, cond, incr, body, lineno);
487            }
488            break;
489          }
490    
491          case TokenStream.TRY: {
492            int lineno = ts.getLineno();
493    
494            Object tryblock;
495            Object catchblocks = null;
496            Object finallyblock = null;
497    
498            skipsemi = true;
499            tryblock = statement(ts);
500    
501            catchblocks = nf.createLeaf(TokenStream.BLOCK);
502    
503            boolean sawDefaultCatch = false;
504            int peek = ts.peekToken();
505            if (peek == ts.CATCH) {
506              while (ts.matchToken(ts.CATCH)) {
507                if (sawDefaultCatch) {
508                  reportError(ts, "msg.catch.unreachable");
509                }
510                mustMatchToken(ts, ts.LP, "msg.no.paren.catch");
511    
512                mustMatchToken(ts, ts.NAME, "msg.bad.catchcond");
513                String varName = ts.getString();
514    
515                Object catchCond = null;
516                if (ts.matchToken(ts.IF)) {
517                  catchCond = expr(ts, false);
518                } else {
519                  sawDefaultCatch = true;
520                }
521    
522                mustMatchToken(ts, ts.GWT, "msg.bad.catchcond");
523                mustMatchToken(ts, ts.LC, "msg.no.brace.catchblock");
524    
525                nf.addChildToBack(catchblocks, nf.createCatch(varName, catchCond,
526                  statements(ts), ts.getLineno()));
527    
528                mustMatchToken(ts, ts.RC, "msg.no.brace.after.body");
529              }
530            } else if (peek != ts.FINALLY) {
531              mustMatchToken(ts, ts.FINALLY, "msg.try.no.catchfinally");
532            }
533    
534            if (ts.matchToken(ts.FINALLY)) {
535              finallyblock = statement(ts);
536            }
537    
538            pn = nf.createTryCatchFinally(tryblock, catchblocks, finallyblock,
539              lineno);
540    
541            break;
542          }
543          case TokenStream.THROW: {
544            int lineno = ts.getLineno();
545            pn = nf.createThrow(expr(ts, false), lineno);
546            if (lineno == ts.getLineno())
547              wellTerminated(ts, ts.ERROR);
548            break;
549          }
550          case TokenStream.BREAK: {
551            int lineno = ts.getLineno();
552    
553            // matchLabel only matches if there is one
554            String label = matchLabel(ts);
555            pn = nf.createBreak(label, lineno);
556            break;
557          }
558          case TokenStream.CONTINUE: {
559            int lineno = ts.getLineno();
560    
561            // matchLabel only matches if there is one
562            String label = matchLabel(ts);
563            pn = nf.createContinue(label, lineno);
564            break;
565          }
566          case TokenStream.DEBUGGER: {
567            int lineno = ts.getLineno();
568            pn = nf.createDebugger(lineno);
569            break;
570          }
571          case TokenStream.WITH: {
572            // bruce: we don't support this is JSNI code because it's impossible
573            // to identify bindings even passably well
574            //
575            
576            reportError(ts, "msg.jsni.unsupported.with");
577    
578            skipsemi = true;
579    
580            int lineno = ts.getLineno();
581            mustMatchToken(ts, ts.LP, "msg.no.paren.with");
582            Object obj = expr(ts, false);
583            mustMatchToken(ts, ts.GWT, "msg.no.paren.after.with");
584            Object body = statement(ts);
585            pn = nf.createWith(obj, body, lineno);
586            break;
587          }
588          case TokenStream.VAR: {
589            int lineno = ts.getLineno();
590            pn = variables(ts, false);
591            if (ts.getLineno() == lineno)
592              wellTerminated(ts, ts.ERROR);
593            break;
594          }
595          case TokenStream.RETURN: {
596            Object retExpr = null;
597            int lineno = 0;
598            // bail if we're not in a (toplevel) function
599            if ((!insideFunction) && ((ts.flags & ts.TSF_FUNCTION) == 0)) {
600                reportError(ts, "msg.bad.return");
601            }
602    
603            /* This is ugly, but we don't want to require a semicolon. */
604            ts.flags |= ts.TSF_REGEXP;
605            tt = ts.peekTokenSameLine();
606            ts.flags &= ~ts.TSF_REGEXP;
607    
608            if (tt != ts.EOF && tt != ts.EOL && tt != ts.SEMI && tt != ts.RC) {
609              lineno = ts.getLineno();
610              retExpr = expr(ts, false);
611              if (ts.getLineno() == lineno)
612                wellTerminated(ts, ts.ERROR);
613              ts.flags |= ts.TSF_RETURN_EXPR;
614            } else {
615              ts.flags |= ts.TSF_RETURN_VOID;
616            }
617    
618            // XXX ASSERT pn
619            pn = nf.createReturn(retExpr, lineno);
620            break;
621          }
622          case TokenStream.LC:
623            skipsemi = true;
624    
625            pn = statements(ts);
626            mustMatchToken(ts, ts.RC, "msg.no.brace.block");
627            break;
628    
629          case TokenStream.ERROR:
630          // Fall thru, to have a node for error recovery to work on
631          case TokenStream.EOL:
632          case TokenStream.SEMI:
633            pn = nf.createLeaf(ts.VOID);
634            skipsemi = true;
635            break;
636    
637          default: {
638            lastExprType = tt;
639            int tokenno = ts.getTokenno();
640            ts.ungetToken(tt);
641            int lineno = ts.getLineno();
642    
643            pn = expr(ts, false);
644    
645            if (ts.peekToken() == ts.COLON) {
646              /*
647               * check that the last thing the tokenizer returned was a NAME and
648               * that only one token was consumed.
649               */
650              if (lastExprType != ts.NAME || (ts.getTokenno() != tokenno))
651                reportError(ts, "msg.bad.label");
652    
653              ts.getToken(); // eat the COLON
654    
655              /*
656               * in the C source, the label is associated with the statement that
657               * follows: nf.addChildToBack(pn, statement(ts));
658               */
659              String name = ts.getString();
660              pn = nf.createLabel(name, lineno);
661    
662              // bruce: added to make it easier to bind labels to the
663              // statements they modify
664              //
665              nf.addChildToBack(pn, statement(ts));
666    
667              // depend on decompiling lookahead to guess that that
668              // last name was a label.
669              return pn;
670            }
671    
672            if (lastExprType == ts.FUNCTION) {
673              if (nf.getLeafType(pn) != ts.FUNCTION) {
674                reportError(ts, "msg.syntax");
675              }
676            }
677    
678            pn = nf.createExprStatement(pn, lineno);
679    
680            /*
681             * Check explicitly against (multi-line) function statement.
682             * 
683             * lastExprEndLine is a hack to fix an automatic semicolon insertion
684             * problem with function expressions; the ts.getLineno() == lineno check
685             * was firing after a function definition even though the next statement
686             * was on a new line, because speculative getToken calls advanced the
687             * line number even when they didn't succeed.
688             */
689            if (ts.getLineno() == lineno
690              || (lastExprType == ts.FUNCTION && ts.getLineno() == lastExprEndLine)) {
691              wellTerminated(ts, lastExprType);
692            }
693            break;
694          }
695        }
696        ts.matchToken(ts.SEMI);
697    
698        return pn;
699      }
700    
701      private Object variables(TokenStream ts, boolean inForInit)
702          throws IOException, JavaScriptException {
703        Object pn = nf.createVariables(ts.getLineno());
704    
705        for (;;) {
706          Object name;
707          Object init;
708          mustMatchToken(ts, ts.NAME, "msg.bad.var");
709          String s = ts.getString();
710          name = nf.createName(s);
711    
712          // omitted check for argument hiding
713    
714          if (ts.matchToken(ts.ASSIGN)) {
715            if (ts.getOp() != ts.NOP)
716              reportError(ts, "msg.bad.var.init");
717    
718            init = assignExpr(ts, inForInit);
719            nf.addChildToBack(name, init);
720          }
721          nf.addChildToBack(pn, name);
722          if (!ts.matchToken(ts.COMMA))
723            break;
724        }
725        return pn;
726      }
727    
728      private Object expr(TokenStream ts, boolean inForInit) throws IOException,
729          JavaScriptException {
730        Object pn = assignExpr(ts, inForInit);
731        while (ts.matchToken(ts.COMMA)) {
732          pn = nf.createBinary(ts.COMMA, pn, assignExpr(ts, inForInit));
733        }
734        return pn;
735      }
736    
737      private Object assignExpr(TokenStream ts, boolean inForInit)
738          throws IOException, JavaScriptException {
739        Object pn = condExpr(ts, inForInit);
740    
741        if (ts.matchToken(ts.ASSIGN)) {
742          // omitted: "invalid assignment left-hand side" check.
743          pn = nf
744            .createBinary(ts.ASSIGN, ts.getOp(), pn, assignExpr(ts, inForInit));
745        }
746    
747        return pn;
748      }
749    
750      private Object condExpr(TokenStream ts, boolean inForInit)
751          throws IOException, JavaScriptException {
752        Object ifTrue;
753        Object ifFalse;
754    
755        Object pn = orExpr(ts, inForInit);
756    
757        if (ts.matchToken(ts.HOOK)) {
758          ifTrue = assignExpr(ts, false);
759          mustMatchToken(ts, ts.COLON, "msg.no.colon.cond");
760          ifFalse = assignExpr(ts, inForInit);
761          return nf.createTernary(pn, ifTrue, ifFalse);
762        }
763    
764        return pn;
765      }
766    
767      private Object orExpr(TokenStream ts, boolean inForInit) throws IOException,
768          JavaScriptException {
769        Object pn = andExpr(ts, inForInit);
770        if (ts.matchToken(ts.OR)) {
771          pn = nf.createBinary(ts.OR, pn, orExpr(ts, inForInit));
772        }
773    
774        return pn;
775      }
776    
777      private Object andExpr(TokenStream ts, boolean inForInit) throws IOException,
778          JavaScriptException {
779        Object pn = bitOrExpr(ts, inForInit);
780        if (ts.matchToken(ts.AND)) {
781          pn = nf.createBinary(ts.AND, pn, andExpr(ts, inForInit));
782        }
783    
784        return pn;
785      }
786    
787      private Object bitOrExpr(TokenStream ts, boolean inForInit)
788          throws IOException, JavaScriptException {
789        Object pn = bitXorExpr(ts, inForInit);
790        while (ts.matchToken(ts.BITOR)) {
791          pn = nf.createBinary(ts.BITOR, pn, bitXorExpr(ts, inForInit));
792        }
793        return pn;
794      }
795    
796      private Object bitXorExpr(TokenStream ts, boolean inForInit)
797          throws IOException, JavaScriptException {
798        Object pn = bitAndExpr(ts, inForInit);
799        while (ts.matchToken(ts.BITXOR)) {
800          pn = nf.createBinary(ts.BITXOR, pn, bitAndExpr(ts, inForInit));
801        }
802        return pn;
803      }
804    
805      private Object bitAndExpr(TokenStream ts, boolean inForInit)
806          throws IOException, JavaScriptException {
807        Object pn = eqExpr(ts, inForInit);
808        while (ts.matchToken(ts.BITAND)) {
809          pn = nf.createBinary(ts.BITAND, pn, eqExpr(ts, inForInit));
810        }
811        return pn;
812      }
813    
814      private Object eqExpr(TokenStream ts, boolean inForInit) throws IOException,
815          JavaScriptException {
816        Object pn = relExpr(ts, inForInit);
817        while (ts.matchToken(ts.EQOP)) {
818          pn = nf.createBinary(ts.EQOP, ts.getOp(), pn, relExpr(ts, inForInit));
819        }
820        return pn;
821      }
822    
823      private Object relExpr(TokenStream ts, boolean inForInit) throws IOException,
824          JavaScriptException {
825        Object pn = shiftExpr(ts);
826        while (ts.matchToken(ts.RELOP)) {
827          int op = ts.getOp();
828          if (inForInit && op == ts.IN) {
829            ts.ungetToken(ts.RELOP);
830            break;
831          }
832    
833          pn = nf.createBinary(ts.RELOP, op, pn, shiftExpr(ts));
834        }
835        return pn;
836      }
837    
838      private Object shiftExpr(TokenStream ts) throws IOException,
839          JavaScriptException {
840        Object pn = addExpr(ts);
841        while (ts.matchToken(ts.SHOP)) {
842          pn = nf.createBinary(ts.SHOP, ts.getOp(), pn, addExpr(ts));
843        }
844        return pn;
845      }
846    
847      private Object addExpr(TokenStream ts) throws IOException,
848          JavaScriptException {
849        int tt;
850        Object pn = mulExpr(ts);
851    
852        while ((tt = ts.getToken()) == ts.ADD || tt == ts.SUB) {
853          // flushNewLines
854          pn = nf.createBinary(tt, pn, mulExpr(ts));
855        }
856        ts.ungetToken(tt);
857    
858        return pn;
859      }
860    
861      private Object mulExpr(TokenStream ts) throws IOException,
862          JavaScriptException {
863        int tt;
864    
865        Object pn = unaryExpr(ts);
866    
867        while ((tt = ts.peekToken()) == ts.MUL || tt == ts.DIV || tt == ts.MOD) {
868          tt = ts.getToken();
869          pn = nf.createBinary(tt, pn, unaryExpr(ts));
870        }
871    
872        return pn;
873      }
874    
875      private Object unaryExpr(TokenStream ts) throws IOException,
876          JavaScriptException {
877        int tt;
878    
879        ts.flags |= ts.TSF_REGEXP;
880        tt = ts.getToken();
881        ts.flags &= ~ts.TSF_REGEXP;
882    
883        switch (tt) {
884          case TokenStream.UNARYOP:
885            return nf.createUnary(ts.UNARYOP, ts.getOp(), unaryExpr(ts));
886    
887          case TokenStream.ADD:
888          case TokenStream.SUB:
889            return nf.createUnary(ts.UNARYOP, tt, unaryExpr(ts));
890    
891          case TokenStream.INC:
892          case TokenStream.DEC:
893            return nf.createUnary(tt, ts.PRE, memberExpr(ts, true));
894    
895          case TokenStream.DELPROP:
896            return nf.createUnary(ts.DELPROP, unaryExpr(ts));
897    
898          case TokenStream.ERROR:
899            break;
900    
901          default:
902            ts.ungetToken(tt);
903    
904            int lineno = ts.getLineno();
905    
906            Object pn = memberExpr(ts, true);
907    
908            /*
909             * don't look across a newline boundary for a postfix incop.
910             * 
911             * the rhino scanner seems to work differently than the js scanner here;
912             * in js, it works to have the line number check precede the peekToken
913             * calls. It'd be better if they had similar behavior...
914             */
915            int peeked;
916            if (((peeked = ts.peekToken()) == ts.INC || peeked == ts.DEC)
917              && ts.getLineno() == lineno) {
918              int pf = ts.getToken();
919              return nf.createUnary(pf, ts.POST, pn);
920            }
921            return pn;
922        }
923        return nf.createName("err"); // Only reached on error. Try to continue.
924    
925      }
926    
927      private Object argumentList(TokenStream ts, Object listNode)
928          throws IOException, JavaScriptException {
929        boolean matched;
930        ts.flags |= ts.TSF_REGEXP;
931        matched = ts.matchToken(ts.GWT);
932        ts.flags &= ~ts.TSF_REGEXP;
933        if (!matched) {
934          do {
935            nf.addChildToBack(listNode, assignExpr(ts, false));
936          } while (ts.matchToken(ts.COMMA));
937    
938          mustMatchToken(ts, ts.GWT, "msg.no.paren.arg");
939        }
940        return listNode;
941      }
942    
943      private Object memberExpr(TokenStream ts, boolean allowCallSyntax)
944          throws IOException, JavaScriptException {
945        int tt;
946    
947        Object pn;
948    
949        /* Check for new expressions. */
950        ts.flags |= ts.TSF_REGEXP;
951        tt = ts.peekToken();
952        ts.flags &= ~ts.TSF_REGEXP;
953        if (tt == ts.NEW) {
954          /* Eat the NEW token. */
955          ts.getToken();
956    
957          /* Make a NEW node to append to. */
958          pn = nf.createLeaf(ts.NEW);
959          nf.addChildToBack(pn, memberExpr(ts, false));
960    
961          if (ts.matchToken(ts.LP)) {
962            /* Add the arguments to pn, if any are supplied. */
963            pn = argumentList(ts, pn);
964          }
965    
966          /*
967           * XXX there's a check in the C source against "too many constructor
968           * arguments" - how many do we claim to support?
969           */
970    
971          /*
972           * Experimental syntax: allow an object literal to follow a new
973           * expression, which will mean a kind of anonymous class built with the
974           * JavaAdapter. the object literal will be passed as an additional
975           * argument to the constructor.
976           */
977          tt = ts.peekToken();
978          if (tt == ts.LC) {
979            nf.addChildToBack(pn, primaryExpr(ts));
980          }
981        } else {
982          pn = primaryExpr(ts);
983        }
984    
985        return memberExprTail(ts, allowCallSyntax, pn);
986      }
987    
988      private Object memberExprTail(TokenStream ts, boolean allowCallSyntax,
989          Object pn) throws IOException, JavaScriptException {
990        lastExprEndLine = ts.getLineno();
991        int tt;
992        while ((tt = ts.getToken()) > ts.EOF) {
993          if (tt == ts.DOT) {
994            mustMatchToken(ts, ts.NAME, "msg.no.name.after.dot");
995            String s = ts.getString();
996            pn = nf.createBinary(ts.DOT, pn, nf.createName(ts.getString()));
997            /*
998             * pn = nf.createBinary(ts.DOT, pn, memberExpr(ts)) is the version in
999             * Brendan's IR C version. Not in ECMA... does it reflect the 'new'
1000             * operator syntax he mentioned?
1001             */
1002            lastExprEndLine = ts.getLineno();
1003          } else if (tt == ts.LB) {
1004            pn = nf.createBinary(ts.LB, pn, expr(ts, false));
1005    
1006            mustMatchToken(ts, ts.RB, "msg.no.bracket.index");
1007            lastExprEndLine = ts.getLineno();
1008          } else if (allowCallSyntax && tt == ts.LP) {
1009            /* make a call node */
1010            pn = nf.createUnary(ts.CALL, pn);
1011    
1012            /* Add the arguments to pn, if any are supplied. */
1013            pn = argumentList(ts, pn);
1014            lastExprEndLine = ts.getLineno();
1015          } else {
1016            ts.ungetToken(tt);
1017    
1018            break;
1019          }
1020        }
1021        return pn;
1022      }
1023    
1024      private Object primaryExpr(TokenStream ts) throws IOException,
1025          JavaScriptException {
1026        int tt;
1027    
1028        Object pn;
1029    
1030        ts.flags |= ts.TSF_REGEXP;
1031        tt = ts.getToken();
1032        ts.flags &= ~ts.TSF_REGEXP;
1033    
1034        switch (tt) {
1035    
1036          case TokenStream.FUNCTION:
1037            return function(ts, true);
1038    
1039          case TokenStream.LB: {
1040            pn = nf.createLeaf(ts.ARRAYLIT);
1041    
1042            ts.flags |= ts.TSF_REGEXP;
1043            boolean matched = ts.matchToken(ts.RB);
1044            ts.flags &= ~ts.TSF_REGEXP;
1045    
1046            if (!matched) {
1047              do {
1048                ts.flags |= ts.TSF_REGEXP;
1049                tt = ts.peekToken();
1050                ts.flags &= ~ts.TSF_REGEXP;
1051    
1052                if (tt == ts.RB) { // to fix [,,,].length behavior...
1053                  break;
1054                }
1055    
1056                if (tt == ts.COMMA) {
1057                  nf.addChildToBack(pn, nf.createLeaf(ts.PRIMARY, ts.UNDEFINED));
1058                } else {
1059                  nf.addChildToBack(pn, assignExpr(ts, false));
1060                }
1061    
1062              } while (ts.matchToken(ts.COMMA));
1063              mustMatchToken(ts, ts.RB, "msg.no.bracket.arg");
1064            }
1065    
1066            return nf.createArrayLiteral(pn);
1067          }
1068    
1069          case TokenStream.LC: {
1070            pn = nf.createLeaf(ts.OBJLIT);
1071    
1072            if (!ts.matchToken(ts.RC)) {
1073    
1074              commaloop : do {
1075                Object property;
1076    
1077                tt = ts.getToken();
1078                switch (tt) {
1079                  // map NAMEs to STRINGs in object literal context.
1080                  case TokenStream.NAME:
1081                  case TokenStream.STRING:
1082                    property = nf.createString(ts.getString());
1083                    break;
1084                  case TokenStream.NUMBER_INT:
1085                    int n = (int) ts.getNumber();
1086                    property = nf.createNumber(n);
1087                    break;
1088                  case TokenStream.NUMBER:
1089                    double d = ts.getNumber();
1090                    property = nf.createNumber(d);
1091                    break;
1092                  case TokenStream.RC:
1093                    // trailing comma is OK.
1094                    ts.ungetToken(tt);
1095                    break commaloop;
1096                  default:
1097                    reportError(ts, "msg.bad.prop");
1098                    break commaloop;
1099                }
1100                mustMatchToken(ts, ts.COLON, "msg.no.colon.prop");
1101    
1102                // OBJLIT is used as ':' in object literal for
1103                // decompilation to solve spacing ambiguity.
1104                nf.addChildToBack(pn, property);
1105                nf.addChildToBack(pn, assignExpr(ts, false));
1106    
1107              } while (ts.matchToken(ts.COMMA));
1108    
1109              mustMatchToken(ts, ts.RC, "msg.no.brace.prop");
1110            }
1111            return nf.createObjectLiteral(pn);
1112          }
1113    
1114          case TokenStream.LP:
1115    
1116            /*
1117             * Brendan's IR-jsparse.c makes a new node tagged with TOK_LP here...
1118             * I'm not sure I understand why. Isn't the grouping already implicit in
1119             * the structure of the parse tree? also TOK_LP is already overloaded (I
1120             * think) in the C IR as 'function call.'
1121             */
1122            pn = expr(ts, false);
1123            mustMatchToken(ts, ts.GWT, "msg.no.paren");
1124            return pn;
1125    
1126          case TokenStream.NAME:
1127            String name = ts.getString();
1128            return nf.createName(name);
1129    
1130          case TokenStream.NUMBER_INT:
1131            int n = (int)ts.getNumber();
1132            return nf.createNumber(n);
1133    
1134        case TokenStream.NUMBER:
1135            double d = ts.getNumber();
1136            return nf.createNumber(d);
1137    
1138          case TokenStream.STRING:
1139            String s = ts.getString();
1140            return nf.createString(s);
1141    
1142          case TokenStream.REGEXP: {
1143            String flags = ts.regExpFlags;
1144            ts.regExpFlags = null;
1145            String re = ts.getString();
1146            return nf.createRegExp(re, flags);
1147          }
1148    
1149          case TokenStream.PRIMARY:
1150            return nf.createLeaf(ts.PRIMARY, ts.getOp());
1151    
1152          case TokenStream.ERROR:
1153            /* the scanner or one of its subroutines reported the error. */
1154            break;
1155    
1156          default:
1157            reportError(ts, "msg.syntax");
1158            break;
1159    
1160        }
1161        return null; // should never reach here
1162      }
1163    
1164      private int lastExprEndLine; // Hack to handle function expr termination.
1165      private IRFactory nf;
1166      private ErrorReporter er;
1167      private boolean ok; // Did the parse encounter an error?
1168    
1169      private int sourceTop;
1170      private int functionNumber;
1171      private final boolean insideFunction;
1172    }