0001: /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
0002: *
0003: * The contents of this file are subject to the Netscape Public
0004: * License Version 1.1 (the "License"); you may not use this file
0005: * except in compliance with the License. You may obtain a copy of
0006: * the License at http://www.mozilla.org/NPL/
0007: *
0008: * Software distributed under the License is distributed on an "AS
0009: * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr
0010: * implied. See the License for the specific language governing
0011: * rights and limitations under the License.
0012: *
0013: * The Original Code is Rhino code, released
0014: * May 6, 1999.
0015: *
0016: * The Initial Developer of the Original Code is Netscape
0017: * Communications Corporation. Portions created by Netscape are
0018: * Copyright (C) 1997-1999 Netscape Communications Corporation. All
0019: * Rights Reserved.
0020: *
0021: * Contributor(s):
0022: * Mike Ang
0023: * Mike McCabe
0024: *
0025: * Alternatively, the contents of this file may be used under the
0026: * terms of the GNU Public License (the "GPL"), in which case the
0027: * provisions of the GPL are applicable instead of those above.
0028: * If you wish to allow use of your version of this file only
0029: * under the terms of the GPL and not to allow others to use your
0030: * version of this file under the NPL, indicate your decision by
0031: * deleting the provisions above and replace them with the notice
0032: * and other provisions required by the GPL. If you do not delete
0033: * the provisions above, a recipient may use your version of this
0034: * file under either the NPL or the GPL.
0035: */
0036: // Modified by Google
0037: package com.google.gwt.dev.js.rhino;
0038:
0039: import java.io.IOException;
0040:
0041: /**
0042: * This class implements the JavaScript parser.
0043: *
0044: * It is based on the C source files jsparse.c and jsparse.h in the jsref
0045: * package.
0046: *
0047: * @see TokenStream
0048: *
0049: * @author Mike McCabe
0050: * @author Brendan Eich
0051: */
0052:
0053: public class Parser {
0054:
0055: public Parser(IRFactory nf) {
0056: this .nf = nf;
0057: }
0058:
0059: private void mustMatchToken(TokenStream ts, int toMatch,
0060: String messageId) throws IOException, JavaScriptException {
0061: int tt;
0062: if ((tt = ts.getToken()) != toMatch) {
0063: reportError(ts, messageId);
0064: ts.ungetToken(tt); // In case the parser decides to continue
0065: }
0066: }
0067:
0068: private void reportError(TokenStream ts, String messageId)
0069: throws JavaScriptException {
0070: this .ok = false;
0071: ts.reportSyntaxError(messageId, null);
0072:
0073: /*
0074: * Throw an exception to unwind the recursive descent parse. We use
0075: * JavaScriptException here even though it is really a different use of the
0076: * exception than it is usually used for.
0077: */
0078: throw new JavaScriptException(messageId);
0079: }
0080:
0081: /*
0082: * Build a parse tree from the given TokenStream.
0083: *
0084: * @param ts the TokenStream to parse
0085: *
0086: * @return an Object representing the parsed program. If the parse fails, null
0087: * will be returned. (The parse failure will result in a call to the current
0088: * Context's ErrorReporter.)
0089: */
0090: public Object parse(TokenStream ts) throws IOException {
0091: this .ok = true;
0092: sourceTop = 0;
0093: functionNumber = 0;
0094:
0095: int tt; // last token from getToken();
0096: int baseLineno = ts.getLineno(); // line number where source starts
0097:
0098: /*
0099: * so we have something to add nodes to until we've collected all the source
0100: */
0101: Object tempBlock = nf.createLeaf(TokenStream.BLOCK);
0102: ((Node) tempBlock).setIsSyntheticBlock(true);
0103:
0104: while (true) {
0105: ts.flags |= ts.TSF_REGEXP;
0106: tt = ts.getToken();
0107: ts.flags &= ~ts.TSF_REGEXP;
0108:
0109: if (tt <= ts.EOF) {
0110: break;
0111: }
0112:
0113: if (tt == ts.FUNCTION) {
0114: try {
0115: nf.addChildToBack(tempBlock, function(ts, false));
0116: } catch (JavaScriptException e) {
0117: this .ok = false;
0118: break;
0119: }
0120: } else {
0121: ts.ungetToken(tt);
0122: nf.addChildToBack(tempBlock, statement(ts));
0123: }
0124: }
0125:
0126: if (!this .ok) {
0127: // XXX ts.clearPushback() call here?
0128: return null;
0129: }
0130:
0131: Object pn = nf.createScript(tempBlock, ts.getSourceName(),
0132: baseLineno, ts.getLineno(), sourceToString(0));
0133: ((Node) pn).setIsSyntheticBlock(true);
0134: return pn;
0135: }
0136:
0137: /*
0138: * The C version of this function takes an argument list, which doesn't seem
0139: * to be needed for tree generation... it'd only be useful for checking
0140: * argument hiding, which I'm not doing anyway...
0141: */
0142: private Object parseFunctionBody(TokenStream ts) throws IOException {
0143: int oldflags = ts.flags;
0144: ts.flags &= ~(TokenStream.TSF_RETURN_EXPR | TokenStream.TSF_RETURN_VOID);
0145: ts.flags |= TokenStream.TSF_FUNCTION;
0146:
0147: Object pn = nf.createBlock(ts.getLineno());
0148: try {
0149: int tt;
0150: while ((tt = ts.peekToken()) > ts.EOF && tt != ts.RC) {
0151: if (tt == TokenStream.FUNCTION) {
0152: ts.getToken();
0153: nf.addChildToBack(pn, function(ts, false));
0154: } else {
0155: nf.addChildToBack(pn, statement(ts));
0156: }
0157: }
0158: } catch (JavaScriptException e) {
0159: this .ok = false;
0160: } finally {
0161: // also in finally block:
0162: // flushNewLines, clearPushback.
0163:
0164: ts.flags = oldflags;
0165: }
0166:
0167: return pn;
0168: }
0169:
0170: private Object function(TokenStream ts, boolean isExpr)
0171: throws IOException, JavaScriptException {
0172: int baseLineno = ts.getLineno(); // line number where source starts
0173:
0174: String name;
0175: Object memberExprNode = null;
0176: if (ts.matchToken(ts.NAME)) {
0177: name = ts.getString();
0178: if (!ts.matchToken(ts.LP)) {
0179: if (Context.getContext().hasFeature(
0180: Context.FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME)) {
0181: // Extension to ECMA: if 'function <name>' does not follow
0182: // by '(', assume <name> starts memberExpr
0183: sourceAddString(ts.NAME, name);
0184: Object memberExprHead = nf.createName(name);
0185: name = null;
0186: memberExprNode = memberExprTail(ts, false,
0187: memberExprHead);
0188: }
0189: mustMatchToken(ts, ts.LP, "msg.no.paren.parms");
0190: }
0191: } else if (ts.matchToken(ts.LP)) {
0192: // Anonymous function
0193: name = null;
0194: } else {
0195: name = null;
0196: if (Context.getContext().hasFeature(
0197: Context.FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME)) {
0198: // Note that memberExpr can not start with '(' like
0199: // in (1+2).toString, because 'function (' already
0200: // processed as anonymous function
0201: memberExprNode = memberExpr(ts, false);
0202: }
0203: mustMatchToken(ts, ts.LP, "msg.no.paren.parms");
0204: }
0205:
0206: if (memberExprNode != null) {
0207: // transform 'function' <memberExpr> to <memberExpr> = function
0208: // even in the decompilated source
0209: sourceAdd((char) ts.ASSIGN);
0210: sourceAdd((char) ts.NOP);
0211: }
0212:
0213: // save a reference to the function in the enclosing source.
0214: sourceAdd((char) ts.FUNCTION);
0215: sourceAdd((char) functionNumber);
0216: ++functionNumber;
0217:
0218: // Save current source top to restore it on exit not to include
0219: // function to parent source
0220: int savedSourceTop = sourceTop;
0221: int savedFunctionNumber = functionNumber;
0222: Object args;
0223: Object body;
0224: String source;
0225: try {
0226: functionNumber = 0;
0227:
0228: // FUNCTION as the first token in a Source means it's a function
0229: // definition, and not a reference.
0230: sourceAdd((char) ts.FUNCTION);
0231: if (name != null) {
0232: sourceAddString(ts.NAME, name);
0233: }
0234: sourceAdd((char) ts.LP);
0235: args = nf.createLeaf(ts.LP);
0236:
0237: if (!ts.matchToken(ts.GWT)) {
0238: boolean first = true;
0239: do {
0240: if (!first)
0241: sourceAdd((char) ts.COMMA);
0242: first = false;
0243: mustMatchToken(ts, ts.NAME, "msg.no.parm");
0244: String s = ts.getString();
0245: nf.addChildToBack(args, nf.createName(s));
0246:
0247: sourceAddString(ts.NAME, s);
0248: } while (ts.matchToken(ts.COMMA));
0249:
0250: mustMatchToken(ts, ts.GWT, "msg.no.paren.after.parms");
0251: }
0252: sourceAdd((char) ts.GWT);
0253:
0254: mustMatchToken(ts, ts.LC, "msg.no.brace.body");
0255: sourceAdd((char) ts.LC);
0256: sourceAdd((char) ts.EOL);
0257: body = parseFunctionBody(ts);
0258: mustMatchToken(ts, ts.RC, "msg.no.brace.after.body");
0259: sourceAdd((char) ts.RC);
0260: // skip the last EOL so nested functions work...
0261:
0262: // name might be null;
0263: source = sourceToString(savedSourceTop);
0264: } finally {
0265: sourceTop = savedSourceTop;
0266: functionNumber = savedFunctionNumber;
0267: }
0268:
0269: Object pn = nf.createFunction(name, args, body, ts
0270: .getSourceName(), baseLineno, ts.getLineno(), source,
0271: isExpr || memberExprNode != null);
0272: if (memberExprNode != null) {
0273: pn = nf.createBinary(ts.ASSIGN, ts.NOP, memberExprNode, pn);
0274: }
0275:
0276: // Add EOL but only if function is not part of expression, in which
0277: // case it gets SEMI + EOL from Statement.
0278: if (!isExpr) {
0279: if (memberExprNode != null) {
0280: // Add ';' to make 'function x.f(){}' and 'x.f = function(){}'
0281: // to print the same strings when decompiling
0282: sourceAdd((char) ts.SEMI);
0283: }
0284: sourceAdd((char) ts.EOL);
0285: wellTerminated(ts, ts.FUNCTION);
0286: }
0287:
0288: return pn;
0289: }
0290:
0291: private Object statements(TokenStream ts) throws IOException {
0292: Object pn = nf.createBlock(ts.getLineno());
0293:
0294: int tt;
0295: while ((tt = ts.peekToken()) > ts.EOF && tt != ts.RC) {
0296: nf.addChildToBack(pn, statement(ts));
0297: }
0298:
0299: return pn;
0300: }
0301:
0302: private Object condition(TokenStream ts) throws IOException,
0303: JavaScriptException {
0304: Object pn;
0305: mustMatchToken(ts, ts.LP, "msg.no.paren.cond");
0306: sourceAdd((char) ts.LP);
0307: pn = expr(ts, false);
0308: mustMatchToken(ts, ts.GWT, "msg.no.paren.after.cond");
0309: sourceAdd((char) ts.GWT);
0310:
0311: // there's a check here in jsparse.c that corrects = to ==
0312:
0313: return pn;
0314: }
0315:
0316: private boolean wellTerminated(TokenStream ts, int lastExprType)
0317: throws IOException, JavaScriptException {
0318: int tt = ts.peekTokenSameLine();
0319: if (tt == ts.ERROR) {
0320: return false;
0321: }
0322:
0323: if (tt != ts.EOF && tt != ts.EOL && tt != ts.SEMI
0324: && tt != ts.RC) {
0325: int version = Context.getContext().getLanguageVersion();
0326: if ((tt == ts.FUNCTION || lastExprType == ts.FUNCTION)
0327: && (version < Context.VERSION_1_2)) {
0328: /*
0329: * Checking against version < 1.2 and version >= 1.0 in the above line
0330: * breaks old javascript, so we keep it this way for now... XXX warning
0331: * needed?
0332: */
0333: return true;
0334: } else {
0335: reportError(ts, "msg.no.semi.stmt");
0336: }
0337: }
0338: return true;
0339: }
0340:
0341: // match a NAME; return null if no match.
0342: private String matchLabel(TokenStream ts) throws IOException,
0343: JavaScriptException {
0344: int lineno = ts.getLineno();
0345:
0346: String label = null;
0347: int tt;
0348: tt = ts.peekTokenSameLine();
0349: if (tt == ts.NAME) {
0350: ts.getToken();
0351: label = ts.getString();
0352: }
0353:
0354: if (lineno == ts.getLineno())
0355: wellTerminated(ts, ts.ERROR);
0356:
0357: return label;
0358: }
0359:
0360: private Object statement(TokenStream ts) throws IOException {
0361: try {
0362: return statementHelper(ts);
0363: } catch (JavaScriptException e) {
0364: // skip to end of statement
0365: int lineno = ts.getLineno();
0366: int t;
0367: do {
0368: t = ts.getToken();
0369: } while (t != TokenStream.SEMI && t != TokenStream.EOL
0370: && t != TokenStream.EOF && t != TokenStream.ERROR);
0371: return nf.createExprStatement(nf.createName("error"),
0372: lineno);
0373: }
0374: }
0375:
0376: /**
0377: * Whether the "catch (e: e instanceof Exception) { ... }" syntax is
0378: * implemented.
0379: */
0380:
0381: private Object statementHelper(TokenStream ts) throws IOException,
0382: JavaScriptException {
0383: Object pn = null;
0384:
0385: // If skipsemi == true, don't add SEMI + EOL to source at the
0386: // end of this statment. For compound statements, IF/FOR etc.
0387: boolean skipsemi = false;
0388:
0389: int tt;
0390:
0391: int lastExprType = 0; // For wellTerminated. 0 to avoid warning.
0392:
0393: tt = ts.getToken();
0394:
0395: switch (tt) {
0396: case TokenStream.IF: {
0397: skipsemi = true;
0398:
0399: sourceAdd((char) ts.IF);
0400: int lineno = ts.getLineno();
0401: Object cond = condition(ts);
0402: sourceAdd((char) ts.LC);
0403: sourceAdd((char) ts.EOL);
0404: Object ifTrue = statement(ts);
0405: Object ifFalse = null;
0406: if (ts.matchToken(ts.ELSE)) {
0407: sourceAdd((char) ts.RC);
0408: sourceAdd((char) ts.ELSE);
0409: sourceAdd((char) ts.LC);
0410: sourceAdd((char) ts.EOL);
0411: ifFalse = statement(ts);
0412: }
0413: sourceAdd((char) ts.RC);
0414: sourceAdd((char) ts.EOL);
0415: pn = nf.createIf(cond, ifTrue, ifFalse, lineno);
0416: break;
0417: }
0418:
0419: case TokenStream.SWITCH: {
0420: skipsemi = true;
0421:
0422: sourceAdd((char) ts.SWITCH);
0423: pn = nf.createSwitch(ts.getLineno());
0424:
0425: Object cur_case = null; // to kill warning
0426: Object case_statements;
0427:
0428: mustMatchToken(ts, ts.LP, "msg.no.paren.switch");
0429: sourceAdd((char) ts.LP);
0430: nf.addChildToBack(pn, expr(ts, false));
0431: mustMatchToken(ts, ts.GWT, "msg.no.paren.after.switch");
0432: sourceAdd((char) ts.GWT);
0433: mustMatchToken(ts, ts.LC, "msg.no.brace.switch");
0434: sourceAdd((char) ts.LC);
0435: sourceAdd((char) ts.EOL);
0436:
0437: while ((tt = ts.getToken()) != ts.RC && tt != ts.EOF) {
0438: switch (tt) {
0439: case TokenStream.CASE:
0440: sourceAdd((char) ts.CASE);
0441: cur_case = nf.createUnary(ts.CASE, expr(ts, false));
0442: sourceAdd((char) ts.COLON);
0443: sourceAdd((char) ts.EOL);
0444: break;
0445:
0446: case TokenStream.DEFAULT:
0447: cur_case = nf.createLeaf(ts.DEFAULT);
0448: sourceAdd((char) ts.DEFAULT);
0449: sourceAdd((char) ts.COLON);
0450: sourceAdd((char) ts.EOL);
0451: // XXX check that there isn't more than one default
0452: break;
0453:
0454: default:
0455: reportError(ts, "msg.bad.switch");
0456: break;
0457: }
0458: mustMatchToken(ts, ts.COLON, "msg.no.colon.case");
0459:
0460: case_statements = nf.createLeaf(TokenStream.BLOCK);
0461: ((Node) case_statements).setIsSyntheticBlock(true);
0462:
0463: while ((tt = ts.peekToken()) != ts.RC && tt != ts.CASE
0464: && tt != ts.DEFAULT && tt != ts.EOF) {
0465: nf.addChildToBack(case_statements, statement(ts));
0466: }
0467: // assert cur_case
0468: nf.addChildToBack(cur_case, case_statements);
0469:
0470: nf.addChildToBack(pn, cur_case);
0471: }
0472: sourceAdd((char) ts.RC);
0473: sourceAdd((char) ts.EOL);
0474: break;
0475: }
0476:
0477: case TokenStream.WHILE: {
0478: skipsemi = true;
0479:
0480: sourceAdd((char) ts.WHILE);
0481: int lineno = ts.getLineno();
0482: Object cond = condition(ts);
0483: sourceAdd((char) ts.LC);
0484: sourceAdd((char) ts.EOL);
0485: Object body = statement(ts);
0486: sourceAdd((char) ts.RC);
0487: sourceAdd((char) ts.EOL);
0488:
0489: pn = nf.createWhile(cond, body, lineno);
0490: break;
0491:
0492: }
0493:
0494: case TokenStream.DO: {
0495: sourceAdd((char) ts.DO);
0496: sourceAdd((char) ts.LC);
0497: sourceAdd((char) ts.EOL);
0498:
0499: int lineno = ts.getLineno();
0500:
0501: Object body = statement(ts);
0502:
0503: sourceAdd((char) ts.RC);
0504: mustMatchToken(ts, ts.WHILE, "msg.no.while.do");
0505: sourceAdd((char) ts.WHILE);
0506: Object cond = condition(ts);
0507:
0508: pn = nf.createDoWhile(body, cond, lineno);
0509: break;
0510: }
0511:
0512: case TokenStream.FOR: {
0513: skipsemi = true;
0514:
0515: sourceAdd((char) ts.FOR);
0516: int lineno = ts.getLineno();
0517:
0518: Object init; // Node init is also foo in 'foo in Object'
0519: Object cond; // Node cond is also object in 'foo in Object'
0520: Object incr = null; // to kill warning
0521: Object body;
0522:
0523: mustMatchToken(ts, ts.LP, "msg.no.paren.for");
0524: sourceAdd((char) ts.LP);
0525: tt = ts.peekToken();
0526: if (tt == ts.SEMI) {
0527: init = nf.createLeaf(ts.VOID);
0528: } else {
0529: if (tt == ts.VAR) {
0530: // set init to a var list or initial
0531: ts.getToken(); // throw away the 'var' token
0532: init = variables(ts, true);
0533: } else {
0534: init = expr(ts, true);
0535: }
0536: }
0537:
0538: tt = ts.peekToken();
0539: if (tt == ts.RELOP && ts.getOp() == ts.IN) {
0540: ts.matchToken(ts.RELOP);
0541: sourceAdd((char) ts.IN);
0542: // 'cond' is the object over which we're iterating
0543: cond = expr(ts, false);
0544: } else { // ordinary for loop
0545: mustMatchToken(ts, ts.SEMI, "msg.no.semi.for");
0546: sourceAdd((char) ts.SEMI);
0547: if (ts.peekToken() == ts.SEMI) {
0548: // no loop condition
0549: cond = nf.createLeaf(ts.VOID);
0550: } else {
0551: cond = expr(ts, false);
0552: }
0553:
0554: mustMatchToken(ts, ts.SEMI, "msg.no.semi.for.cond");
0555: sourceAdd((char) ts.SEMI);
0556: if (ts.peekToken() == ts.GWT) {
0557: incr = nf.createLeaf(ts.VOID);
0558: } else {
0559: incr = expr(ts, false);
0560: }
0561: }
0562:
0563: mustMatchToken(ts, ts.GWT, "msg.no.paren.for.ctrl");
0564: sourceAdd((char) ts.GWT);
0565: sourceAdd((char) ts.LC);
0566: sourceAdd((char) ts.EOL);
0567: body = statement(ts);
0568: sourceAdd((char) ts.RC);
0569: sourceAdd((char) ts.EOL);
0570:
0571: if (incr == null) {
0572: // cond could be null if 'in obj' got eaten by the init node.
0573: pn = nf.createForIn(init, cond, body, lineno);
0574: } else {
0575: pn = nf.createFor(init, cond, incr, body, lineno);
0576: }
0577: break;
0578: }
0579:
0580: case TokenStream.TRY: {
0581: int lineno = ts.getLineno();
0582:
0583: Object tryblock;
0584: Object catchblocks = null;
0585: Object finallyblock = null;
0586:
0587: skipsemi = true;
0588: sourceAdd((char) ts.TRY);
0589: sourceAdd((char) ts.LC);
0590: sourceAdd((char) ts.EOL);
0591: tryblock = statement(ts);
0592: sourceAdd((char) ts.RC);
0593: sourceAdd((char) ts.EOL);
0594:
0595: catchblocks = nf.createLeaf(TokenStream.BLOCK);
0596:
0597: boolean sawDefaultCatch = false;
0598: int peek = ts.peekToken();
0599: if (peek == ts.CATCH) {
0600: while (ts.matchToken(ts.CATCH)) {
0601: if (sawDefaultCatch) {
0602: reportError(ts, "msg.catch.unreachable");
0603: }
0604: sourceAdd((char) ts.CATCH);
0605: mustMatchToken(ts, ts.LP, "msg.no.paren.catch");
0606: sourceAdd((char) ts.LP);
0607:
0608: mustMatchToken(ts, ts.NAME, "msg.bad.catchcond");
0609: String varName = ts.getString();
0610: sourceAddString(ts.NAME, varName);
0611:
0612: Object catchCond = null;
0613: if (ts.matchToken(ts.IF)) {
0614: sourceAdd((char) ts.IF);
0615: catchCond = expr(ts, false);
0616: } else {
0617: sawDefaultCatch = true;
0618: }
0619:
0620: mustMatchToken(ts, ts.GWT, "msg.bad.catchcond");
0621: sourceAdd((char) ts.GWT);
0622: mustMatchToken(ts, ts.LC, "msg.no.brace.catchblock");
0623: sourceAdd((char) ts.LC);
0624: sourceAdd((char) ts.EOL);
0625:
0626: nf.addChildToBack(catchblocks, nf.createCatch(
0627: varName, catchCond, statements(ts), ts
0628: .getLineno()));
0629:
0630: mustMatchToken(ts, ts.RC, "msg.no.brace.after.body");
0631: sourceAdd((char) ts.RC);
0632: sourceAdd((char) ts.EOL);
0633: }
0634: } else if (peek != ts.FINALLY) {
0635: mustMatchToken(ts, ts.FINALLY,
0636: "msg.try.no.catchfinally");
0637: }
0638:
0639: if (ts.matchToken(ts.FINALLY)) {
0640: sourceAdd((char) ts.FINALLY);
0641:
0642: sourceAdd((char) ts.LC);
0643: sourceAdd((char) ts.EOL);
0644: finallyblock = statement(ts);
0645: sourceAdd((char) ts.RC);
0646: sourceAdd((char) ts.EOL);
0647: }
0648:
0649: pn = nf.createTryCatchFinally(tryblock, catchblocks,
0650: finallyblock, lineno);
0651:
0652: break;
0653: }
0654: case TokenStream.THROW: {
0655: int lineno = ts.getLineno();
0656: sourceAdd((char) ts.THROW);
0657: pn = nf.createThrow(expr(ts, false), lineno);
0658: if (lineno == ts.getLineno())
0659: wellTerminated(ts, ts.ERROR);
0660: break;
0661: }
0662: case TokenStream.BREAK: {
0663: int lineno = ts.getLineno();
0664:
0665: sourceAdd((char) ts.BREAK);
0666:
0667: // matchLabel only matches if there is one
0668: String label = matchLabel(ts);
0669: if (label != null) {
0670: sourceAddString(ts.NAME, label);
0671: }
0672: pn = nf.createBreak(label, lineno);
0673: break;
0674: }
0675: case TokenStream.CONTINUE: {
0676: int lineno = ts.getLineno();
0677:
0678: sourceAdd((char) ts.CONTINUE);
0679:
0680: // matchLabel only matches if there is one
0681: String label = matchLabel(ts);
0682: if (label != null) {
0683: sourceAddString(ts.NAME, label);
0684: }
0685: pn = nf.createContinue(label, lineno);
0686: break;
0687: }
0688: case TokenStream.DEBUGGER: {
0689: int lineno = ts.getLineno();
0690:
0691: sourceAdd((char) ts.DEBUGGER);
0692:
0693: pn = nf.createDebugger(lineno);
0694: break;
0695: }
0696: case TokenStream.WITH: {
0697: // bruce: we don't support this is JSNI code because it's impossible
0698: // to identify bindings even passably well
0699: //
0700:
0701: reportError(ts, "msg.jsni.unsupported.with");
0702:
0703: skipsemi = true;
0704:
0705: sourceAdd((char) ts.WITH);
0706: int lineno = ts.getLineno();
0707: mustMatchToken(ts, ts.LP, "msg.no.paren.with");
0708: sourceAdd((char) ts.LP);
0709: Object obj = expr(ts, false);
0710: mustMatchToken(ts, ts.GWT, "msg.no.paren.after.with");
0711: sourceAdd((char) ts.GWT);
0712: sourceAdd((char) ts.LC);
0713: sourceAdd((char) ts.EOL);
0714:
0715: Object body = statement(ts);
0716:
0717: sourceAdd((char) ts.RC);
0718: sourceAdd((char) ts.EOL);
0719:
0720: pn = nf.createWith(obj, body, lineno);
0721: break;
0722: }
0723: case TokenStream.VAR: {
0724: int lineno = ts.getLineno();
0725: pn = variables(ts, false);
0726: if (ts.getLineno() == lineno)
0727: wellTerminated(ts, ts.ERROR);
0728: break;
0729: }
0730: case TokenStream.RETURN: {
0731: Object retExpr = null;
0732: int lineno = 0;
0733:
0734: sourceAdd((char) ts.RETURN);
0735:
0736: // bail if we're not in a (toplevel) function
0737: if ((ts.flags & ts.TSF_FUNCTION) == 0)
0738: reportError(ts, "msg.bad.return");
0739:
0740: /* This is ugly, but we don't want to require a semicolon. */
0741: ts.flags |= ts.TSF_REGEXP;
0742: tt = ts.peekTokenSameLine();
0743: ts.flags &= ~ts.TSF_REGEXP;
0744:
0745: if (tt != ts.EOF && tt != ts.EOL && tt != ts.SEMI
0746: && tt != ts.RC) {
0747: lineno = ts.getLineno();
0748: retExpr = expr(ts, false);
0749: if (ts.getLineno() == lineno)
0750: wellTerminated(ts, ts.ERROR);
0751: ts.flags |= ts.TSF_RETURN_EXPR;
0752: } else {
0753: ts.flags |= ts.TSF_RETURN_VOID;
0754: }
0755:
0756: // XXX ASSERT pn
0757: pn = nf.createReturn(retExpr, lineno);
0758: break;
0759: }
0760: case TokenStream.LC:
0761: skipsemi = true;
0762:
0763: pn = statements(ts);
0764: mustMatchToken(ts, ts.RC, "msg.no.brace.block");
0765: break;
0766:
0767: case TokenStream.ERROR:
0768: // Fall thru, to have a node for error recovery to work on
0769: case TokenStream.EOL:
0770: case TokenStream.SEMI:
0771: pn = nf.createLeaf(ts.VOID);
0772: skipsemi = true;
0773: break;
0774:
0775: default: {
0776: lastExprType = tt;
0777: int tokenno = ts.getTokenno();
0778: ts.ungetToken(tt);
0779: int lineno = ts.getLineno();
0780:
0781: pn = expr(ts, false);
0782:
0783: if (ts.peekToken() == ts.COLON) {
0784: /*
0785: * check that the last thing the tokenizer returned was a NAME and
0786: * that only one token was consumed.
0787: */
0788: if (lastExprType != ts.NAME
0789: || (ts.getTokenno() != tokenno))
0790: reportError(ts, "msg.bad.label");
0791:
0792: ts.getToken(); // eat the COLON
0793:
0794: /*
0795: * in the C source, the label is associated with the statement that
0796: * follows: nf.addChildToBack(pn, statement(ts));
0797: */
0798: String name = ts.getString();
0799: pn = nf.createLabel(name, lineno);
0800:
0801: // bruce: added to make it easier to bind labels to the
0802: // statements they modify
0803: //
0804: nf.addChildToBack(pn, statement(ts));
0805:
0806: // depend on decompiling lookahead to guess that that
0807: // last name was a label.
0808: sourceAdd((char) ts.COLON);
0809: sourceAdd((char) ts.EOL);
0810: return pn;
0811: }
0812:
0813: if (lastExprType == ts.FUNCTION) {
0814: if (nf.getLeafType(pn) != ts.FUNCTION) {
0815: reportError(ts, "msg.syntax");
0816: }
0817: }
0818:
0819: pn = nf.createExprStatement(pn, lineno);
0820:
0821: /*
0822: * Check explicitly against (multi-line) function statement.
0823: *
0824: * lastExprEndLine is a hack to fix an automatic semicolon insertion
0825: * problem with function expressions; the ts.getLineno() == lineno check
0826: * was firing after a function definition even though the next statement
0827: * was on a new line, because speculative getToken calls advanced the
0828: * line number even when they didn't succeed.
0829: */
0830: if (ts.getLineno() == lineno
0831: || (lastExprType == ts.FUNCTION && ts.getLineno() == lastExprEndLine)) {
0832: wellTerminated(ts, lastExprType);
0833: }
0834: break;
0835: }
0836: }
0837: ts.matchToken(ts.SEMI);
0838: if (!skipsemi) {
0839: sourceAdd((char) ts.SEMI);
0840: sourceAdd((char) ts.EOL);
0841: }
0842:
0843: return pn;
0844: }
0845:
0846: private Object variables(TokenStream ts, boolean inForInit)
0847: throws IOException, JavaScriptException {
0848: Object pn = nf.createVariables(ts.getLineno());
0849: boolean first = true;
0850:
0851: sourceAdd((char) ts.VAR);
0852:
0853: for (;;) {
0854: Object name;
0855: Object init;
0856: mustMatchToken(ts, ts.NAME, "msg.bad.var");
0857: String s = ts.getString();
0858:
0859: if (!first)
0860: sourceAdd((char) ts.COMMA);
0861: first = false;
0862:
0863: sourceAddString(ts.NAME, s);
0864: name = nf.createName(s);
0865:
0866: // omitted check for argument hiding
0867:
0868: if (ts.matchToken(ts.ASSIGN)) {
0869: if (ts.getOp() != ts.NOP)
0870: reportError(ts, "msg.bad.var.init");
0871:
0872: sourceAdd((char) ts.ASSIGN);
0873: sourceAdd((char) ts.NOP);
0874:
0875: init = assignExpr(ts, inForInit);
0876: nf.addChildToBack(name, init);
0877: }
0878: nf.addChildToBack(pn, name);
0879: if (!ts.matchToken(ts.COMMA))
0880: break;
0881: }
0882: return pn;
0883: }
0884:
0885: private Object expr(TokenStream ts, boolean inForInit)
0886: throws IOException, JavaScriptException {
0887: Object pn = assignExpr(ts, inForInit);
0888: while (ts.matchToken(ts.COMMA)) {
0889: sourceAdd((char) ts.COMMA);
0890: pn = nf.createBinary(ts.COMMA, pn,
0891: assignExpr(ts, inForInit));
0892: }
0893: return pn;
0894: }
0895:
0896: private Object assignExpr(TokenStream ts, boolean inForInit)
0897: throws IOException, JavaScriptException {
0898: Object pn = condExpr(ts, inForInit);
0899:
0900: if (ts.matchToken(ts.ASSIGN)) {
0901: // omitted: "invalid assignment left-hand side" check.
0902: sourceAdd((char) ts.ASSIGN);
0903: sourceAdd((char) ts.getOp());
0904: pn = nf.createBinary(ts.ASSIGN, ts.getOp(), pn, assignExpr(
0905: ts, inForInit));
0906: }
0907:
0908: return pn;
0909: }
0910:
0911: private Object condExpr(TokenStream ts, boolean inForInit)
0912: throws IOException, JavaScriptException {
0913: Object ifTrue;
0914: Object ifFalse;
0915:
0916: Object pn = orExpr(ts, inForInit);
0917:
0918: if (ts.matchToken(ts.HOOK)) {
0919: sourceAdd((char) ts.HOOK);
0920: ifTrue = assignExpr(ts, false);
0921: mustMatchToken(ts, ts.COLON, "msg.no.colon.cond");
0922: sourceAdd((char) ts.COLON);
0923: ifFalse = assignExpr(ts, inForInit);
0924: return nf.createTernary(pn, ifTrue, ifFalse);
0925: }
0926:
0927: return pn;
0928: }
0929:
0930: private Object orExpr(TokenStream ts, boolean inForInit)
0931: throws IOException, JavaScriptException {
0932: Object pn = andExpr(ts, inForInit);
0933: if (ts.matchToken(ts.OR)) {
0934: sourceAdd((char) ts.OR);
0935: pn = nf.createBinary(ts.OR, pn, orExpr(ts, inForInit));
0936: }
0937:
0938: return pn;
0939: }
0940:
0941: private Object andExpr(TokenStream ts, boolean inForInit)
0942: throws IOException, JavaScriptException {
0943: Object pn = bitOrExpr(ts, inForInit);
0944: if (ts.matchToken(ts.AND)) {
0945: sourceAdd((char) ts.AND);
0946: pn = nf.createBinary(ts.AND, pn, andExpr(ts, inForInit));
0947: }
0948:
0949: return pn;
0950: }
0951:
0952: private Object bitOrExpr(TokenStream ts, boolean inForInit)
0953: throws IOException, JavaScriptException {
0954: Object pn = bitXorExpr(ts, inForInit);
0955: while (ts.matchToken(ts.BITOR)) {
0956: sourceAdd((char) ts.BITOR);
0957: pn = nf.createBinary(ts.BITOR, pn,
0958: bitXorExpr(ts, inForInit));
0959: }
0960: return pn;
0961: }
0962:
0963: private Object bitXorExpr(TokenStream ts, boolean inForInit)
0964: throws IOException, JavaScriptException {
0965: Object pn = bitAndExpr(ts, inForInit);
0966: while (ts.matchToken(ts.BITXOR)) {
0967: sourceAdd((char) ts.BITXOR);
0968: pn = nf.createBinary(ts.BITXOR, pn, bitAndExpr(ts,
0969: inForInit));
0970: }
0971: return pn;
0972: }
0973:
0974: private Object bitAndExpr(TokenStream ts, boolean inForInit)
0975: throws IOException, JavaScriptException {
0976: Object pn = eqExpr(ts, inForInit);
0977: while (ts.matchToken(ts.BITAND)) {
0978: sourceAdd((char) ts.BITAND);
0979: pn = nf.createBinary(ts.BITAND, pn, eqExpr(ts, inForInit));
0980: }
0981: return pn;
0982: }
0983:
0984: private Object eqExpr(TokenStream ts, boolean inForInit)
0985: throws IOException, JavaScriptException {
0986: Object pn = relExpr(ts, inForInit);
0987: while (ts.matchToken(ts.EQOP)) {
0988: sourceAdd((char) ts.EQOP);
0989: sourceAdd((char) ts.getOp());
0990: pn = nf.createBinary(ts.EQOP, ts.getOp(), pn, relExpr(ts,
0991: inForInit));
0992: }
0993: return pn;
0994: }
0995:
0996: private Object relExpr(TokenStream ts, boolean inForInit)
0997: throws IOException, JavaScriptException {
0998: Object pn = shiftExpr(ts);
0999: while (ts.matchToken(ts.RELOP)) {
1000: int op = ts.getOp();
1001: if (inForInit && op == ts.IN) {
1002: ts.ungetToken(ts.RELOP);
1003: break;
1004: }
1005: sourceAdd((char) ts.RELOP);
1006: sourceAdd((char) op);
1007: pn = nf.createBinary(ts.RELOP, op, pn, shiftExpr(ts));
1008: }
1009: return pn;
1010: }
1011:
1012: private Object shiftExpr(TokenStream ts) throws IOException,
1013: JavaScriptException {
1014: Object pn = addExpr(ts);
1015: while (ts.matchToken(ts.SHOP)) {
1016: sourceAdd((char) ts.SHOP);
1017: sourceAdd((char) ts.getOp());
1018: pn = nf.createBinary(ts.SHOP, ts.getOp(), pn, addExpr(ts));
1019: }
1020: return pn;
1021: }
1022:
1023: private Object addExpr(TokenStream ts) throws IOException,
1024: JavaScriptException {
1025: int tt;
1026: Object pn = mulExpr(ts);
1027:
1028: while ((tt = ts.getToken()) == ts.ADD || tt == ts.SUB) {
1029: sourceAdd((char) tt);
1030: // flushNewLines
1031: pn = nf.createBinary(tt, pn, mulExpr(ts));
1032: }
1033: ts.ungetToken(tt);
1034:
1035: return pn;
1036: }
1037:
1038: private Object mulExpr(TokenStream ts) throws IOException,
1039: JavaScriptException {
1040: int tt;
1041:
1042: Object pn = unaryExpr(ts);
1043:
1044: while ((tt = ts.peekToken()) == ts.MUL || tt == ts.DIV
1045: || tt == ts.MOD) {
1046: tt = ts.getToken();
1047: sourceAdd((char) tt);
1048: pn = nf.createBinary(tt, pn, unaryExpr(ts));
1049: }
1050:
1051: return pn;
1052: }
1053:
1054: private Object unaryExpr(TokenStream ts) throws IOException,
1055: JavaScriptException {
1056: int tt;
1057:
1058: ts.flags |= ts.TSF_REGEXP;
1059: tt = ts.getToken();
1060: ts.flags &= ~ts.TSF_REGEXP;
1061:
1062: switch (tt) {
1063: case TokenStream.UNARYOP:
1064: sourceAdd((char) ts.UNARYOP);
1065: sourceAdd((char) ts.getOp());
1066: return nf
1067: .createUnary(ts.UNARYOP, ts.getOp(), unaryExpr(ts));
1068:
1069: case TokenStream.ADD:
1070: case TokenStream.SUB:
1071: sourceAdd((char) ts.UNARYOP);
1072: sourceAdd((char) tt);
1073: return nf.createUnary(ts.UNARYOP, tt, unaryExpr(ts));
1074:
1075: case TokenStream.INC:
1076: case TokenStream.DEC:
1077: sourceAdd((char) tt);
1078: return nf.createUnary(tt, ts.PRE, memberExpr(ts, true));
1079:
1080: case TokenStream.DELPROP:
1081: sourceAdd((char) ts.DELPROP);
1082: return nf.createUnary(ts.DELPROP, unaryExpr(ts));
1083:
1084: case TokenStream.ERROR:
1085: break;
1086:
1087: default:
1088: ts.ungetToken(tt);
1089:
1090: int lineno = ts.getLineno();
1091:
1092: Object pn = memberExpr(ts, true);
1093:
1094: /*
1095: * don't look across a newline boundary for a postfix incop.
1096: *
1097: * the rhino scanner seems to work differently than the js scanner here;
1098: * in js, it works to have the line number check precede the peekToken
1099: * calls. It'd be better if they had similar behavior...
1100: */
1101: int peeked;
1102: if (((peeked = ts.peekToken()) == ts.INC || peeked == ts.DEC)
1103: && ts.getLineno() == lineno) {
1104: int pf = ts.getToken();
1105: sourceAdd((char) pf);
1106: return nf.createUnary(pf, ts.POST, pn);
1107: }
1108: return pn;
1109: }
1110: return nf.createName("err"); // Only reached on error. Try to continue.
1111:
1112: }
1113:
1114: private Object argumentList(TokenStream ts, Object listNode)
1115: throws IOException, JavaScriptException {
1116: boolean matched;
1117: ts.flags |= ts.TSF_REGEXP;
1118: matched = ts.matchToken(ts.GWT);
1119: ts.flags &= ~ts.TSF_REGEXP;
1120: if (!matched) {
1121: boolean first = true;
1122: do {
1123: if (!first)
1124: sourceAdd((char) ts.COMMA);
1125: first = false;
1126: nf.addChildToBack(listNode, assignExpr(ts, false));
1127: } while (ts.matchToken(ts.COMMA));
1128:
1129: mustMatchToken(ts, ts.GWT, "msg.no.paren.arg");
1130: }
1131: sourceAdd((char) ts.GWT);
1132: return listNode;
1133: }
1134:
1135: private Object memberExpr(TokenStream ts, boolean allowCallSyntax)
1136: throws IOException, JavaScriptException {
1137: int tt;
1138:
1139: Object pn;
1140:
1141: /* Check for new expressions. */
1142: ts.flags |= ts.TSF_REGEXP;
1143: tt = ts.peekToken();
1144: ts.flags &= ~ts.TSF_REGEXP;
1145: if (tt == ts.NEW) {
1146: /* Eat the NEW token. */
1147: ts.getToken();
1148: sourceAdd((char) ts.NEW);
1149:
1150: /* Make a NEW node to append to. */
1151: pn = nf.createLeaf(ts.NEW);
1152: nf.addChildToBack(pn, memberExpr(ts, false));
1153:
1154: if (ts.matchToken(ts.LP)) {
1155: sourceAdd((char) ts.LP);
1156: /* Add the arguments to pn, if any are supplied. */
1157: pn = argumentList(ts, pn);
1158: }
1159:
1160: /*
1161: * XXX there's a check in the C source against "too many constructor
1162: * arguments" - how many do we claim to support?
1163: */
1164:
1165: /*
1166: * Experimental syntax: allow an object literal to follow a new
1167: * expression, which will mean a kind of anonymous class built with the
1168: * JavaAdapter. the object literal will be passed as an additional
1169: * argument to the constructor.
1170: */
1171: tt = ts.peekToken();
1172: if (tt == ts.LC) {
1173: nf.addChildToBack(pn, primaryExpr(ts));
1174: }
1175: } else {
1176: pn = primaryExpr(ts);
1177: }
1178:
1179: return memberExprTail(ts, allowCallSyntax, pn);
1180: }
1181:
1182: private Object memberExprTail(TokenStream ts,
1183: boolean allowCallSyntax, Object pn) throws IOException,
1184: JavaScriptException {
1185: lastExprEndLine = ts.getLineno();
1186: int tt;
1187: while ((tt = ts.getToken()) > ts.EOF) {
1188: if (tt == ts.DOT) {
1189: sourceAdd((char) ts.DOT);
1190: mustMatchToken(ts, ts.NAME, "msg.no.name.after.dot");
1191: String s = ts.getString();
1192: sourceAddString(ts.NAME, s);
1193: pn = nf.createBinary(ts.DOT, pn, nf.createName(ts
1194: .getString()));
1195: /*
1196: * pn = nf.createBinary(ts.DOT, pn, memberExpr(ts)) is the version in
1197: * Brendan's IR C version. Not in ECMA... does it reflect the 'new'
1198: * operator syntax he mentioned?
1199: */
1200: lastExprEndLine = ts.getLineno();
1201: } else if (tt == ts.LB) {
1202: sourceAdd((char) ts.LB);
1203: pn = nf.createBinary(ts.LB, pn, expr(ts, false));
1204:
1205: mustMatchToken(ts, ts.RB, "msg.no.bracket.index");
1206: sourceAdd((char) ts.RB);
1207: lastExprEndLine = ts.getLineno();
1208: } else if (allowCallSyntax && tt == ts.LP) {
1209: /* make a call node */
1210:
1211: pn = nf.createUnary(ts.CALL, pn);
1212: sourceAdd((char) ts.LP);
1213:
1214: /* Add the arguments to pn, if any are supplied. */
1215: pn = argumentList(ts, pn);
1216: lastExprEndLine = ts.getLineno();
1217: } else {
1218: ts.ungetToken(tt);
1219:
1220: break;
1221: }
1222: }
1223: return pn;
1224: }
1225:
1226: private Object primaryExpr(TokenStream ts) throws IOException,
1227: JavaScriptException {
1228: int tt;
1229:
1230: Object pn;
1231:
1232: ts.flags |= ts.TSF_REGEXP;
1233: tt = ts.getToken();
1234: ts.flags &= ~ts.TSF_REGEXP;
1235:
1236: switch (tt) {
1237:
1238: case TokenStream.FUNCTION:
1239: return function(ts, true);
1240:
1241: case TokenStream.LB: {
1242: sourceAdd((char) ts.LB);
1243: pn = nf.createLeaf(ts.ARRAYLIT);
1244:
1245: ts.flags |= ts.TSF_REGEXP;
1246: boolean matched = ts.matchToken(ts.RB);
1247: ts.flags &= ~ts.TSF_REGEXP;
1248:
1249: if (!matched) {
1250: boolean first = true;
1251: do {
1252: ts.flags |= ts.TSF_REGEXP;
1253: tt = ts.peekToken();
1254: ts.flags &= ~ts.TSF_REGEXP;
1255:
1256: if (!first)
1257: sourceAdd((char) ts.COMMA);
1258: else
1259: first = false;
1260:
1261: if (tt == ts.RB) { // to fix [,,,].length behavior...
1262: break;
1263: }
1264:
1265: if (tt == ts.COMMA) {
1266: nf.addChildToBack(pn, nf.createLeaf(ts.PRIMARY,
1267: ts.UNDEFINED));
1268: } else {
1269: nf.addChildToBack(pn, assignExpr(ts, false));
1270: }
1271:
1272: } while (ts.matchToken(ts.COMMA));
1273: mustMatchToken(ts, ts.RB, "msg.no.bracket.arg");
1274: }
1275: sourceAdd((char) ts.RB);
1276: return nf.createArrayLiteral(pn);
1277: }
1278:
1279: case TokenStream.LC: {
1280: pn = nf.createLeaf(ts.OBJLIT);
1281:
1282: sourceAdd((char) ts.LC);
1283: if (!ts.matchToken(ts.RC)) {
1284:
1285: boolean first = true;
1286: commaloop: do {
1287: Object property;
1288:
1289: if (!first)
1290: sourceAdd((char) ts.COMMA);
1291: else
1292: first = false;
1293:
1294: tt = ts.getToken();
1295: switch (tt) {
1296: // map NAMEs to STRINGs in object literal context.
1297: case TokenStream.NAME:
1298: case TokenStream.STRING:
1299: String s = ts.getString();
1300: sourceAddString(ts.NAME, s);
1301: property = nf.createString(ts.getString());
1302: break;
1303: case TokenStream.NUMBER:
1304: double n = ts.getNumber();
1305: sourceAddNumber(n);
1306: property = nf.createNumber(n);
1307: break;
1308: case TokenStream.RC:
1309: // trailing comma is OK.
1310: ts.ungetToken(tt);
1311: break commaloop;
1312: default:
1313: reportError(ts, "msg.bad.prop");
1314: break commaloop;
1315: }
1316: mustMatchToken(ts, ts.COLON, "msg.no.colon.prop");
1317:
1318: // OBJLIT is used as ':' in object literal for
1319: // decompilation to solve spacing ambiguity.
1320: sourceAdd((char) ts.OBJLIT);
1321: nf.addChildToBack(pn, property);
1322: nf.addChildToBack(pn, assignExpr(ts, false));
1323:
1324: } while (ts.matchToken(ts.COMMA));
1325:
1326: mustMatchToken(ts, ts.RC, "msg.no.brace.prop");
1327: }
1328: sourceAdd((char) ts.RC);
1329: return nf.createObjectLiteral(pn);
1330: }
1331:
1332: case TokenStream.LP:
1333:
1334: /*
1335: * Brendan's IR-jsparse.c makes a new node tagged with TOK_LP here...
1336: * I'm not sure I understand why. Isn't the grouping already implicit in
1337: * the structure of the parse tree? also TOK_LP is already overloaded (I
1338: * think) in the C IR as 'function call.'
1339: */
1340: sourceAdd((char) ts.LP);
1341: pn = expr(ts, false);
1342: sourceAdd((char) ts.GWT);
1343: mustMatchToken(ts, ts.GWT, "msg.no.paren");
1344: return pn;
1345:
1346: case TokenStream.NAME:
1347: String name = ts.getString();
1348: sourceAddString(ts.NAME, name);
1349: return nf.createName(name);
1350:
1351: case TokenStream.NUMBER:
1352: double n = ts.getNumber();
1353: sourceAddNumber(n);
1354: return nf.createNumber(n);
1355:
1356: case TokenStream.STRING:
1357: String s = ts.getString();
1358: sourceAddString(ts.STRING, s);
1359: return nf.createString(s);
1360:
1361: case TokenStream.REGEXP: {
1362: String flags = ts.regExpFlags;
1363: ts.regExpFlags = null;
1364: String re = ts.getString();
1365: sourceAddString(ts.REGEXP, '/' + re + '/' + flags);
1366: return nf.createRegExp(re, flags);
1367: }
1368:
1369: case TokenStream.PRIMARY:
1370: sourceAdd((char) ts.PRIMARY);
1371: sourceAdd((char) ts.getOp());
1372: return nf.createLeaf(ts.PRIMARY, ts.getOp());
1373:
1374: case TokenStream.RESERVED:
1375: reportError(ts, "msg.reserved.id");
1376: break;
1377:
1378: case TokenStream.ERROR:
1379: /* the scanner or one of its subroutines reported the error. */
1380: break;
1381:
1382: default:
1383: reportError(ts, "msg.syntax");
1384: break;
1385:
1386: }
1387: return null; // should never reach here
1388: }
1389:
1390: /**
1391: * The following methods save decompilation information about the source.
1392: * Source information is returned from the parser as a String associated with
1393: * function nodes and with the toplevel script. When saved in the constant
1394: * pool of a class, this string will be UTF-8 encoded, and token values will
1395: * occupy a single byte.
1396: *
1397: * Source is saved (mostly) as token numbers. The tokens saved pretty much
1398: * correspond to the token stream of a 'canonical' representation of the input
1399: * program, as directed by the parser. (There were a few cases where tokens
1400: * could have been left out where decompiler could easily reconstruct them,
1401: * but I left them in for clarity). (I also looked adding source collection to
1402: * TokenStream instead, where I could have limited the changes to a few lines
1403: * in getToken... but this wouldn't have saved any space in the resulting
1404: * source representation, and would have meant that I'd have to duplicate
1405: * parser logic in the decompiler to disambiguate situations where newlines
1406: * are important.) NativeFunction.decompile expands the tokens back into their
1407: * string representations, using simple lookahead to correct spacing and
1408: * indentation.
1409: *
1410: * Token types with associated ops (ASSIGN, SHOP, PRIMARY, etc.) are saved as
1411: * two-token pairs. Number tokens are stored inline, as a NUMBER token, a
1412: * character representing the type, and either 1 or 4 characters representing
1413: * the bit-encoding of the number. String types NAME, STRING and OBJECT are
1414: * currently stored as a token type, followed by a character giving the length
1415: * of the string (assumed to be less than 2^16), followed by the characters of
1416: * the string inlined into the source string. Changing this to some reference
1417: * to to the string in the compiled class' constant pool would probably save a
1418: * lot of space... but would require some method of deriving the final
1419: * constant pool entry from information available at parse time.
1420: *
1421: * Nested functions need a similar mechanism... fortunately the nested
1422: * functions for a given function are generated in source order. Nested
1423: * functions are encoded as FUNCTION followed by a function number (encoded as
1424: * a character), which is enough information to find the proper generated
1425: * NativeFunction instance.
1426: *
1427: */
1428: private void sourceAdd(char c) {
1429: if (sourceTop == sourceBuffer.length) {
1430: increaseSourceCapacity(sourceTop + 1);
1431: }
1432: sourceBuffer[sourceTop] = c;
1433: ++sourceTop;
1434: }
1435:
1436: private void sourceAddString(int type, String str) {
1437: int L = str.length();
1438: // java string length < 2^16?
1439: if (Context.check && L > Character.MAX_VALUE)
1440: Context.codeBug();
1441:
1442: if (sourceTop + L + 2 > sourceBuffer.length) {
1443: increaseSourceCapacity(sourceTop + L + 2);
1444: }
1445: sourceAdd((char) type);
1446: sourceAdd((char) L);
1447: str.getChars(0, L, sourceBuffer, sourceTop);
1448: sourceTop += L;
1449: }
1450:
1451: private void sourceAddNumber(double n) {
1452: sourceAdd((char) TokenStream.NUMBER);
1453:
1454: /*
1455: * encode the number in the source stream. Save as NUMBER type (char | char
1456: * char char char) where type is 'D' - double, 'S' - short, 'J' - long.
1457: *
1458: * We need to retain float vs. integer type info to keep the behavior of
1459: * liveconnect type-guessing the same after decompilation. (Liveconnect
1460: * tries to present 1.0 to Java as a float/double) OPT: This is no longer
1461: * true. We could compress the format.
1462: *
1463: * This may not be the most space-efficient encoding; the chars created
1464: * below may take up to 3 bytes in constant pool UTF-8 encoding, so a Double
1465: * could take up to 12 bytes.
1466: */
1467:
1468: long lbits = (long) n;
1469: if (lbits != n) {
1470: // if it's floating point, save as a Double bit pattern.
1471: // (12/15/97 our scanner only returns Double for f.p.)
1472: lbits = Double.doubleToLongBits(n);
1473: sourceAdd('D');
1474: sourceAdd((char) (lbits >> 48));
1475: sourceAdd((char) (lbits >> 32));
1476: sourceAdd((char) (lbits >> 16));
1477: sourceAdd((char) lbits);
1478: } else {
1479: // we can ignore negative values, bc they're already prefixed
1480: // by UNARYOP SUB
1481: if (Context.check && lbits < 0)
1482: Context.codeBug();
1483:
1484: // will it fit in a char?
1485: // this gives a short encoding for integer values up to 2^16.
1486: if (lbits <= Character.MAX_VALUE) {
1487: sourceAdd('S');
1488: sourceAdd((char) lbits);
1489: } else { // Integral, but won't fit in a char. Store as a long.
1490: sourceAdd('J');
1491: sourceAdd((char) (lbits >> 48));
1492: sourceAdd((char) (lbits >> 32));
1493: sourceAdd((char) (lbits >> 16));
1494: sourceAdd((char) lbits);
1495: }
1496: }
1497: }
1498:
1499: private void increaseSourceCapacity(int minimalCapacity) {
1500: // Call this only when capacity increase is must
1501: if (Context.check && minimalCapacity <= sourceBuffer.length)
1502: Context.codeBug();
1503: int newCapacity = sourceBuffer.length * 2;
1504: if (newCapacity < minimalCapacity) {
1505: newCapacity = minimalCapacity;
1506: }
1507: char[] tmp = new char[newCapacity];
1508: System.arraycopy(sourceBuffer, 0, tmp, 0, sourceTop);
1509: sourceBuffer = tmp;
1510: }
1511:
1512: private String sourceToString(int offset) {
1513: if (Context.check && (offset < 0 || sourceTop < offset))
1514: Context.codeBug();
1515: return new String(sourceBuffer, offset, sourceTop - offset);
1516: }
1517:
1518: private int lastExprEndLine; // Hack to handle function expr termination.
1519: private IRFactory nf;
1520: private ErrorReporter er;
1521: private boolean ok; // Did the parse encounter an error?
1522:
1523: private char[] sourceBuffer = new char[128];
1524: private int sourceTop;
1525: private int functionNumber;
1526: }
|