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