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 }