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