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 }