0001: /* *****************************************************************************
0002: * JS2Doc.java
0003: * ****************************************************************************/
0004:
0005: /* J_LZ_COPYRIGHT_BEGIN *******************************************************
0006: * Copyright 2006-2007 Laszlo Systems, Inc. All Rights Reserved. *
0007: * Use is subject to license terms. *
0008: * J_LZ_COPYRIGHT_END *********************************************************/
0009:
0010: package org.openlaszlo.js2doc;
0011:
0012: import java.io.*;
0013: import java.util.*;
0014: import java.util.logging.*;
0015: import java.util.regex.*;
0016: import org.openlaszlo.js2doc.JS2DocUtils.InternalError;
0017: import org.openlaszlo.sc.Compiler;
0018: import org.openlaszlo.sc.parser.*;
0019: import org.openlaszlo.utils.FileUtils;
0020: import org.w3c.dom.*;
0021:
0022: /**
0023: *
0024: *
0025: */
0026: public class JS2Doc {
0027:
0028: static private Logger logger = Logger
0029: .getLogger("org.openlaszlo.js2doc");
0030:
0031: static public class DocumentationError extends RuntimeException {
0032: /** Constructs an instance.
0033: * @param message a string
0034: */
0035: public DocumentationError(String message) {
0036: super ("js2doc Error " + message);
0037: }
0038: }
0039:
0040: private static class Visitor {
0041:
0042: String baseDirectory;
0043: String relativeBase;
0044: String libraryID;
0045: String unitID;
0046: String objectID;
0047: ConditionalState currentState;
0048:
0049: Set runtimeOptions;
0050: Map runtimeAliases;
0051: List buildOptions;
0052:
0053: // TODO [jgrandy 12/3/2006] turn into parameter
0054: static private String unitCommentMarker = "copyright";
0055:
0056: public Visitor(String baseDirectory, String relativeBase,
0057: String libraryID, Set runtimeOptions,
0058: Map runtimeAliases, List buildOptions) {
0059:
0060: this .baseDirectory = baseDirectory;
0061: this .relativeBase = relativeBase;
0062: this .libraryID = libraryID;
0063: this .unitID = null;
0064: this .objectID = null;
0065: this .runtimeOptions = new HashSet(runtimeOptions);
0066: this .runtimeAliases = new HashMap(runtimeAliases);
0067: this .buildOptions = new ArrayList(buildOptions);
0068: this .currentState = new ConditionalState(
0069: ConditionalState.trueValue, this .runtimeOptions,
0070: this .buildOptions);
0071: }
0072:
0073: public Visitor(Visitor previous) {
0074: this (previous.baseDirectory, previous.relativeBase,
0075: previous.libraryID, previous.runtimeOptions,
0076: previous.runtimeAliases, previous.buildOptions);
0077: }
0078:
0079: public void setConditionalState(ConditionalState startingState) {
0080: if (startingState == null)
0081: this .currentState = new ConditionalState(
0082: ConditionalState.trueValue,
0083: this .runtimeOptions, this .buildOptions);
0084: else
0085: this .currentState = new ConditionalState(startingState);
0086: }
0087:
0088: public void visitToplevelStatement(SimpleNode parseNode,
0089: org.w3c.dom.Element docNode) {
0090: //logger.fine(parseNode.getClass().getName());
0091: if (parseNode instanceof ASTProgram
0092: || parseNode instanceof ASTStatement
0093: || parseNode instanceof ASTDirectiveBlock) {
0094: SimpleNode[] children = parseNode.getChildren();
0095: for (int i = 0; i < children.length; i++) {
0096: this .visitToplevelStatement(children[i], docNode);
0097: }
0098: } else if (parseNode instanceof ASTVariableStatement) {
0099: visitVariableStatement(parseNode, docNode);
0100: } else if (parseNode instanceof ASTAssignmentExpression) {
0101: visitTopLevelAssignmentExpression(parseNode, docNode);
0102: } else if (parseNode instanceof ASTFunctionDeclaration) {
0103: visitFunctionDeclaration(parseNode, docNode);
0104: } else if (parseNode instanceof ASTClassDefinition) {
0105: visitClassDeclaration(parseNode, docNode, null);
0106: } else if (parseNode instanceof ASTIfDirective) {
0107: visitTopLevelIfDirective(parseNode, docNode);
0108: } else if (parseNode instanceof ASTIncludeDirective) {
0109: visitIncludeDirective(parseNode, docNode, true);
0110: } else if (parseNode instanceof ASTCallExpression) {
0111: visitCallExpression(parseNode, docNode);
0112: } else if (parseNode instanceof ASTModifiedDefinition) {
0113: visitModifiedDefinition(parseNode, docNode);
0114: } else if (parseNode instanceof ASTPragmaDirective) {
0115: // do nothing
0116: } else {
0117: logger.warning("Unhandled toplevel statement type "
0118: + parseNode.getClass().getName());
0119: }
0120: }
0121:
0122: public void visitUnit(SimpleNode parseNode,
0123: org.w3c.dom.Element docNode, String unitPath) {
0124: String oldUnitID = this .unitID;
0125: try {
0126: org.w3c.dom.Element unitNode = docNode
0127: .getOwnerDocument().createElement("unit");
0128: docNode.appendChild(unitNode);
0129:
0130: String comment = parseNode.getComment();
0131: if (comment != null) {
0132: Comment parsedComment = Comment
0133: .extractFirstJS2DocFromCommentSequence(comment);
0134: if (parsedComment != null
0135: && parsedComment
0136: .hasField(unitCommentMarker))
0137: this .processComment(unitNode, parsedComment);
0138: }
0139:
0140: // no need to use java.io.File.separatorChar here, since the separator
0141: // will appear unix-style (as '/') in the source code
0142: String relativePath = this .relativeBase + '/'
0143: + unitPath;
0144: // relativeBase may (erroneously) start with a '/', or may be the empty
0145: // string, so cover both cases here to ensure we have a truly relative
0146: // path.
0147: if (relativePath.charAt(0) == '/')
0148: relativePath = relativePath.substring(1);
0149:
0150: unitNode.setAttribute("path", relativePath);
0151: //unitNode.setAttribute("path", unitPath);
0152:
0153: // no need to use java.io.File.separatorChar here, since the separator
0154: // will appear unix-style (as '/') in the source code
0155: this .unitID = unitPath.replace('/', '.').replace(' ',
0156: '_');
0157: if (this .libraryID != null)
0158: this .unitID = this .libraryID + "." + this .unitID;
0159: unitNode.setAttribute("id", this .unitID);
0160:
0161: //System.out.println("visiting '" + this.unitID + "' from '" + oldUnitID + "'");
0162:
0163: if (oldUnitID != null)
0164: unitNode.setAttribute("unitid", oldUnitID);
0165:
0166: describeConditionalState(unitNode);
0167:
0168: // *don't* nest -- we're building a flat list
0169: this .visitToplevelStatement(parseNode, docNode);
0170: } finally {
0171: this .unitID = oldUnitID;
0172: }
0173: }
0174:
0175: protected void visitVariableStatement(SimpleNode parseNode,
0176: org.w3c.dom.Element docNode) {
0177: VariableDeclarationsInfo decls = collectVariableDeclarations(parseNode);
0178: SimpleNode[] vars = decls.variables;
0179: for (int i = 0; i < vars.length; i++) {
0180: this .visitVariableDeclaration(vars[i], docNode,
0181: decls.commonComment);
0182: }
0183: }
0184:
0185: class VariableDeclarationsInfo {
0186: String commonComment;
0187: SimpleNode[] variables;
0188: }
0189:
0190: protected VariableDeclarationsInfo collectVariableDeclarations(
0191: SimpleNode parseNode) {
0192: VariableDeclarationsInfo decls = new VariableDeclarationsInfo();
0193:
0194: SimpleNode[] children = parseNode.getChildren();
0195: checkChildrenLowerBounds(parseNode, 1, 1,
0196: "collectVariableDeclarations");
0197:
0198: SimpleNode child = children[0];
0199: decls.commonComment = parseNode.getComment();
0200: if (child instanceof ASTVariableDeclaration) {
0201: decls.variables = children;
0202: } else if (child instanceof ASTStatementList) {
0203: // children of StatementList are VariableDeclarations
0204: decls.variables = child.getChildren();
0205: } else {
0206: throw new InternalError(
0207: "Unexpected node type in collectVariableDeclarations"
0208: + parseNode.getClass().getName(),
0209: parseNode);
0210: }
0211: return decls;
0212: }
0213:
0214: private void processObjectLiteral(
0215: ASTObjectLiteral objectLiteralNode,
0216: org.w3c.dom.Element objectNode) {
0217: SimpleNode[] children = objectLiteralNode.getChildren();
0218:
0219: int i = 0;
0220: while (i < children.length) {
0221: SimpleNode idNode = children[i++];
0222: SimpleNode valueNode = children[i++];
0223:
0224: String ivarName;
0225: if (idNode instanceof ASTIdentifier) {
0226: ivarName = ((ASTIdentifier) idNode).getName();
0227:
0228: PropertyReference propRef = new PropertyReference(
0229: objectNode, ivarName, this .currentState);
0230:
0231: String comment = objectLiteralNode.getComment();
0232: if (comment == null)
0233: comment = idNode.getComment();
0234: if (comment == null)
0235: comment = valueNode.getComment();
0236:
0237: propRef.redefineProperty(comment);
0238:
0239: if (propRef.hasProperty())
0240: propRef.redefineValue(valueNode);
0241: else
0242: logger
0243: .warning("couldn't redefine object ivar value b/c ivar name couldn't be resolved");
0244: }
0245: }
0246: }
0247:
0248: protected void visitVariableDeclaration(SimpleNode parseNode,
0249: org.w3c.dom.Element docNode, String commonComment) {
0250:
0251: SimpleNode[] children = parseNode.getChildren();
0252: // child 1 is the variable name, child 2 is the (optional) initial value
0253: checkChildrenLowerBounds(parseNode, 1, 2,
0254: "visitVariableDeclaration");
0255:
0256: ASTIdentifier nameNode = (ASTIdentifier) children[0];
0257: String propertyName = nameNode.getName();
0258:
0259: PropertyReference propRef = new PropertyReference(docNode,
0260: propertyName, this .currentState);
0261:
0262: String comment = parseNode.getComment();
0263: if (comment == null) {
0264: comment = commonComment;
0265: } else if (commonComment != null) {
0266: logger
0267: .warning("Conflict between comments associated with property declaration and property statement; choosing comment closer to declaration");
0268: }
0269:
0270: org.w3c.dom.Element property = propRef
0271: .redefineProperty(comment);
0272:
0273: if (this .unitID != null)
0274: property.setAttribute("unitid", unitID);
0275:
0276: if (children.length == 2) {
0277: if (propRef.hasProperty()) {
0278: SimpleNode valueNode = children[1];
0279: propRef.redefineValue(valueNode);
0280:
0281: if (valueNode instanceof ASTObjectLiteral) {
0282: this .processObjectLiteral(
0283: (ASTObjectLiteral) valueNode, propRef
0284: .getValue());
0285: }
0286:
0287: } else
0288: logger
0289: .warning("tried to redefine variable decl but variable was not found");
0290: }
0291: }
0292:
0293: protected void visitTopLevelAssignmentExpression(
0294: SimpleNode parseNode, org.w3c.dom.Element docNode) {
0295: // child 1 is the lhs, child 2 is the assignment operator, child 3 is the rhs
0296: checkChildrenLowerBounds(parseNode, 3, 3,
0297: "visitTopLevelAssignmentExpression");
0298: SimpleNode[] children = parseNode.getChildren();
0299:
0300: SimpleNode lhs = children[0], op = children[1], rhs = children[2];
0301:
0302: boolean opIsSimpleAssignment = (((ASTOperator) op)
0303: .getOperator() == ParserConstants.ASSIGN);
0304:
0305: if (opIsSimpleAssignment) {
0306:
0307: try {
0308: PropertyReference propRef = this .resolveBinding(
0309: docNode, lhs, this .currentState);
0310:
0311: if (propRef.isValid()) {
0312: org.w3c.dom.Element property = propRef
0313: .redefineProperty(parseNode
0314: .getComment());
0315: if (this .unitID != null)
0316: property.setAttribute("unitid", unitID);
0317:
0318: if (propRef.hasProperty()) {
0319: propRef.redefineValue(rhs);
0320:
0321: if (rhs instanceof ASTObjectLiteral) {
0322: this .processObjectLiteral(
0323: (ASTObjectLiteral) rhs, propRef
0324: .getValue());
0325: }
0326:
0327: } else
0328: logger
0329: .warning("tried to redefine property but couldn't resolve property reference");
0330: } else {
0331: logger.fine("unresolved assignment target");
0332: }
0333: } catch (JS2DocUtils.InternalError e) {
0334: logger
0335: .warning("error while processing toplevel assignment expression (rhs)");
0336: }
0337: }
0338: }
0339:
0340: protected void visitFunctionDeclaration(SimpleNode parseNode,
0341: org.w3c.dom.Element docNode) {
0342: SimpleNode[] children = parseNode.getChildren();
0343: // child 1 is the function name, child 2 is the parameter list, child 3 is the function body
0344: checkChildrenLowerBounds(parseNode, 3, 3,
0345: "visitFunctionDeclaration");
0346:
0347: ASTIdentifier nameNode = (ASTIdentifier) children[0];
0348: String fnName = nameNode.getName();
0349:
0350: PropertyReference propRef = new PropertyReference(docNode,
0351: fnName, this .currentState);
0352:
0353: org.w3c.dom.Element property = propRef
0354: .redefineProperty(parseNode.getComment());
0355:
0356: if (this .unitID != null)
0357: property.setAttribute("unitid", unitID);
0358:
0359: if (propRef.hasProperty())
0360: propRef.redefineValue(parseNode);
0361: else
0362: logger
0363: .warning("tried to redefine function but couldn't resolve function name");
0364: }
0365:
0366: protected void visitClassDeclaration(SimpleNode parseNode,
0367: org.w3c.dom.Element docNode,
0368: ASTModifiedDefinition moddef) {
0369: SimpleNode[] children = parseNode.getChildren();
0370: checkChildrenLowerBounds(parseNode, 2, 0,
0371: "visitClassDefinition");
0372:
0373: ASTIdentifier nameNode = (ASTIdentifier) children[1];
0374: String className = nameNode.getName();
0375:
0376: SimpleNode parseNodeForDoc = (moddef != null) ? moddef
0377: : parseNode;
0378:
0379: PropertyReference propRef = new PropertyReference(docNode,
0380: className, this .currentState);
0381:
0382: org.w3c.dom.Element property = propRef
0383: .redefineProperty(parseNodeForDoc.getComment());
0384:
0385: if (this .unitID != null)
0386: property.setAttribute("unitid", unitID);
0387:
0388: org.w3c.dom.Element classNode = propRef
0389: .redefineValue(parseNode);
0390:
0391: if (classNode == null)
0392: throw new InternalError(
0393: "null element returned from PropertyReference.redefineValue",
0394: parseNode);
0395:
0396: if (children.length > 4) {
0397: String oldObjectID = this .objectID;
0398: ConditionalState oldState = this .currentState;
0399: try {
0400: this .objectID = property.getAttribute("id");
0401:
0402: this .currentState = new ConditionalState(
0403: ConditionalState.trueValue,
0404: this .runtimeOptions, this .buildOptions);
0405:
0406: final int n = children.length;
0407: for (int i = 4; i < n; i++) {
0408: SimpleNode decl = children[i];
0409: this .visitClassStatement(decl, classNode, null);
0410: }
0411: } finally {
0412: this .objectID = oldObjectID;
0413: this .currentState = oldState;
0414: }
0415: }
0416: }
0417:
0418: protected void visitClassStatement(SimpleNode parseNode,
0419: org.w3c.dom.Element docNode,
0420: ASTModifiedDefinition moddef) {
0421: if (parseNode instanceof ASTStatement
0422: || parseNode instanceof ASTClassDirectiveBlock) {
0423: SimpleNode[] children = parseNode.getChildren();
0424: for (int i = 0; i < children.length; i++) {
0425: this
0426: .visitClassStatement(children[i], docNode,
0427: null);
0428: }
0429: } else if (parseNode instanceof ASTVariableStatement) {
0430: visitFieldStatement(parseNode, docNode, moddef, null);
0431: } else if (parseNode instanceof ASTFunctionDeclaration) {
0432: visitMethodDeclaration(parseNode, docNode, moddef, null);
0433: } else if (parseNode instanceof ASTCallExpression) {
0434: visitCallExpression(parseNode, docNode);
0435: } else if (parseNode instanceof ASTAssignmentExpression) {
0436: visitClassAssignmentExpression(parseNode, docNode);
0437: } else if (parseNode instanceof ASTClassIfDirective) {
0438: visitClassIfDirective(parseNode, docNode);
0439: } else if (parseNode instanceof ASTIncludeDirective) {
0440: visitIncludeDirective(parseNode, docNode, false);
0441: } else if (parseNode instanceof ASTModifiedDefinition) {
0442: visitModifiedDefinition(parseNode, docNode);
0443: } else if (parseNode instanceof ASTPragmaDirective) {
0444: // do nothing
0445: } else {
0446: logger.warning("Unhandled class statement type "
0447: + parseNode.getClass().getName());
0448: }
0449: }
0450:
0451: protected void visitFieldStatement(SimpleNode parseNode,
0452: org.w3c.dom.Element docNode,
0453: ASTModifiedDefinition moddef, String commonComment) {
0454: VariableDeclarationsInfo decls = collectVariableDeclarations(parseNode);
0455: String comment = (decls.commonComment != null) ? decls.commonComment
0456: : commonComment;
0457: SimpleNode[] vars = decls.variables;
0458: for (int i = 0; i < vars.length; i++) {
0459: this .visitFieldDeclaration(vars[i], docNode, comment,
0460: moddef.isStatic());
0461: }
0462: }
0463:
0464: protected org.w3c.dom.Element ensurePrototypeNode(
0465: org.w3c.dom.Element docNode) {
0466: PropertyReference propRef = new PropertyReference(docNode,
0467: "prototype", null);
0468: propRef.ensureProperty();
0469: return propRef.getValue();
0470: }
0471:
0472: protected org.w3c.dom.Element ensureIVarsNode(
0473: org.w3c.dom.Element docNode) {
0474: PropertyReference propRef = new PropertyReference(docNode,
0475: "__ivars__", null);
0476: propRef.ensureProperty();
0477: return propRef.getValue();
0478: }
0479:
0480: protected void visitFieldDeclaration(SimpleNode parseNode,
0481: org.w3c.dom.Element docNode, String commonComment,
0482: boolean isStatic) {
0483: SimpleNode[] children = parseNode.getChildren();
0484: checkChildrenLowerBounds(parseNode, 1, 2,
0485: "visitFieldDeclaration");
0486:
0487: ASTIdentifier nameNode = (ASTIdentifier) children[0];
0488: String fieldName = nameNode.getName();
0489:
0490: org.w3c.dom.Element targetNode = docNode;
0491:
0492: if (isStatic == false)
0493: targetNode = this .ensureIVarsNode(docNode);
0494:
0495: if (targetNode != null) {
0496: PropertyReference propRef = new PropertyReference(
0497: targetNode, fieldName, this .currentState);
0498:
0499: String comment = parseNode.getComment();
0500: if (comment == null) {
0501: comment = commonComment;
0502: } else if (commonComment != null) {
0503: logger
0504: .warning("Conflict between comments associated with property declaration and property statement; choosing comment closer to declaration");
0505: }
0506:
0507: propRef.redefineProperty(comment);
0508:
0509: if (children.length > 1) {
0510: if (propRef.hasProperty())
0511: propRef.redefineValue(children[1]);
0512: else
0513: logger
0514: .warning("couldn't redefine field value b/c field name couldn't be resolved");
0515: }
0516: }
0517: }
0518:
0519: protected void visitMethodDeclaration(SimpleNode parseNode,
0520: org.w3c.dom.Element docNode,
0521: ASTModifiedDefinition moddef, String comment) {
0522: SimpleNode[] children = parseNode.getChildren();
0523: checkChildrenLowerBounds(parseNode, 3, 3,
0524: "visitMethodDeclaration");
0525:
0526: ASTIdentifier nameNode = (ASTIdentifier) children[0];
0527: String fieldName = nameNode.getName();
0528:
0529: org.w3c.dom.Element targetNode = docNode;
0530:
0531: if (moddef.isStatic() == false)
0532: targetNode = this .ensurePrototypeNode(docNode);
0533:
0534: if (targetNode != null) {
0535: PropertyReference propRef = new PropertyReference(
0536: targetNode, fieldName, this .currentState);
0537:
0538: String nearComment = parseNode.getComment();
0539: if (nearComment == null)
0540: nearComment = comment;
0541:
0542: propRef.redefineProperty(nearComment);
0543:
0544: if (propRef.hasProperty())
0545: propRef.redefineValue(parseNode);
0546: else
0547: logger.warning("couldn't resolve method name");
0548: }
0549: }
0550:
0551: protected void visitModifiedDefinition(SimpleNode parseNode,
0552: org.w3c.dom.Element docNode) {
0553: SimpleNode[] children = parseNode.getChildren();
0554: checkChildrenLowerBounds(parseNode, 1, 1,
0555: "visitModifiedDefinition");
0556:
0557: String comment = parseNode.getComment();
0558: ASTModifiedDefinition moddef = (ASTModifiedDefinition) parseNode;
0559:
0560: SimpleNode child = children[0];
0561: if (child instanceof ASTVariableStatement)
0562: this .visitFieldStatement(child, docNode, moddef,
0563: comment);
0564: else if (child instanceof ASTFunctionDeclaration)
0565: this .visitMethodDeclaration(child, docNode, moddef,
0566: comment);
0567: else if (child instanceof ASTClassDefinition)
0568: this .visitClassDeclaration(child, docNode, moddef);
0569: else
0570: throw new InternalError("Unexpected node type "
0571: + parseNode.getClass().getName(), parseNode);
0572: }
0573:
0574: protected void visitClassAssignmentExpression(
0575: SimpleNode parseNode, org.w3c.dom.Element docNode) {
0576: SimpleNode[] children = parseNode.getChildren();
0577: checkChildrenLowerBounds(parseNode, 3, 3,
0578: "visitClassAssignmentExpression");
0579:
0580: // JS2DocUtils.debugPrintNode(parseNode);
0581:
0582: SimpleNode lhs = children[0];
0583: SimpleNode op = children[1];
0584: SimpleNode rhs = children[2];
0585:
0586: boolean opIsSimpleAssignment = (((ASTOperator) op)
0587: .getOperator() == ParserConstants.ASSIGN);
0588:
0589: if (opIsSimpleAssignment) {
0590: PropertyReference propRef = this .resolveBinding(
0591: docNode, lhs, this .currentState);
0592: propRef.redefineProperty(parseNode.getComment());
0593: if (propRef.hasProperty())
0594: propRef.redefineValue(rhs);
0595: else {
0596: logger
0597: .fine("couldn't resolve class assignment target");
0598: }
0599: }
0600: }
0601:
0602: protected void visitIncludeDirective(SimpleNode parseNode,
0603: org.w3c.dom.Element docNode, boolean isTopLevel) {
0604: SimpleNode[] children = parseNode.getChildren();
0605: checkChildrenLowerBounds(parseNode, 1, 1,
0606: "visitIncludeDirective");
0607:
0608: ASTLiteral path = (ASTLiteral) children[0];
0609:
0610: if (this .baseDirectory == null)
0611: throw new InternalError(
0612: "include directive encountered without having file base",
0613: parseNode);
0614:
0615: // ?? error checking?
0616:
0617: String sourcePath = (String) path.getValue();
0618: File sourceFile = new File(baseDirectory + File.separator
0619: + sourcePath);
0620:
0621: try {
0622: org.openlaszlo.sc.Compiler.Parser p = new org.openlaszlo.sc.Compiler.Parser();
0623: SimpleNode parseRoot = p.parse(FileUtils
0624: .readFileString(sourceFile));
0625:
0626: // TODO [jgrandy 11/24/06] process first comment into unit's <doc> element
0627: // -- in particular, objectID won't be propagated
0628:
0629: Visitor visitor = new Visitor(this );
0630: visitor.setConditionalState(this .currentState);
0631: visitor.unitID = this .unitID;
0632: visitor.visitUnit(parseRoot, docNode, sourcePath);
0633:
0634: } catch (IOException e) {
0635: throw new DocumentationError(
0636: "Could not read included file " + sourceFile);
0637: }
0638: }
0639:
0640: protected void visitEventValue(SimpleNode parseNode,
0641: org.w3c.dom.Element docNode) {
0642: docNode.setAttribute("type", "LzEvent");
0643: docNode.setAttribute("value", "LzNullEvent");
0644: }
0645:
0646: protected void visitEventDeclaration(SimpleNode parseNode,
0647: org.w3c.dom.Element docNode) {
0648: SimpleNode[] children = parseNode.getChildren();
0649: SimpleNode argList = children[1];
0650: SimpleNode[] args = argList.getChildren();
0651: checkChildrenLowerBounds(parseNode, 2, 2,
0652: "visitEventDeclaration argList");
0653:
0654: SimpleNode eventOwnerNode = args[0];
0655: PropertyReference propRef = this .resolveBinding(docNode,
0656: eventOwnerNode, this .currentState);
0657:
0658: if (propRef.hasProperty()) {
0659: logger.fine("found referent "
0660: + propRef.getProperty().getTagName());
0661:
0662: org.w3c.dom.Element propValue = propRef.getValue();
0663:
0664: if (propValue != null) {
0665: SimpleNode secondArg = args[1];
0666: if (!(secondArg instanceof ASTLiteral))
0667: throw new InternalError(
0668: "Expected literal second argument to DeclareEvent",
0669: parseNode);
0670:
0671: String eventName = (String) ((ASTLiteral) secondArg)
0672: .getValue();
0673:
0674: propRef = new PropertyReference(propValue,
0675: eventName, this .currentState);
0676: propRef.redefineProperty(parseNode.getComment());
0677:
0678: this .visitEventValue(parseNode, propRef
0679: .getProperty());
0680: }
0681: } else {
0682: logger.warning("Dangling referent in DeclareEvent");
0683: }
0684: }
0685:
0686: protected void visitCallExpression(SimpleNode parseNode,
0687: org.w3c.dom.Element docNode) {
0688: SimpleNode[] children = parseNode.getChildren();
0689: checkChildrenLowerBounds(parseNode, 2, 2,
0690: "visitCallExpression");
0691:
0692: SimpleNode functionName = children[0];
0693: if (functionName instanceof ASTIdentifier) {
0694:
0695: String nameString = ((ASTIdentifier) functionName)
0696: .getName();
0697:
0698: if (nameString.equals("DeclareEvent"))
0699: visitEventDeclaration(parseNode, docNode);
0700: }
0701: }
0702:
0703: protected void visitIfDirective(SimpleNode parseNode,
0704: org.w3c.dom.Element docNode, boolean isTopLevel) {
0705: SimpleNode[] children = parseNode.getChildren();
0706:
0707: checkChildrenLowerBounds(parseNode, 2, 3,
0708: "visitIfDirective");
0709:
0710: // children[0] is conditional
0711: // children[1] is first directive block
0712: // children[2] is optional second directive block
0713: // "if (x) {} else if (y) {}" is handled recursively: the second
0714: // directive block will contain a new if directive.
0715:
0716: //JS2DocUtils.debugPrintNode(parseNode);
0717: //System.out.println("\n");
0718:
0719: SimpleNode conditional = children[0];
0720: SimpleNode trueDirective = children[1];
0721: SimpleNode falseDirective = ((children.length > 2) ? children[2]
0722: : null);
0723:
0724: ConditionalState previousState = this .currentState;
0725:
0726: ConditionalState directiveCondState = visitDirectiveConditional(
0727: conditional, docNode);
0728: if (directiveCondState == null)
0729: throw new InternalError(
0730: "ConditionalState returned is null", parseNode);
0731:
0732: ConditionalState positiveState = previousState
0733: .and(directiveCondState);
0734:
0735: logger.fine("previousState before lhs of if statement: "
0736: + previousState.toString());
0737: logger.finer("directiveCondState for lhs of if statement: "
0738: + directiveCondState.toString());
0739: logger.finer("positiveState for lhs of if statement: "
0740: + positiveState.toString());
0741:
0742: if (positiveState.inferredValue != ConditionalState.falseValue) {
0743: try {
0744: this .currentState = positiveState;
0745:
0746: if (isTopLevel)
0747: this .visitToplevelStatement(trueDirective,
0748: docNode);
0749: else
0750: this .visitClassStatement(trueDirective,
0751: docNode, null);
0752: } finally {
0753: this .currentState = previousState;
0754: }
0755: }
0756:
0757: if (falseDirective != null) {
0758:
0759: ConditionalState negativeState = previousState
0760: .and(directiveCondState.not());
0761: logger.finer("negativeState for rhs of if statement: "
0762: + negativeState.toString());
0763:
0764: if (negativeState.inferredValue == ConditionalState.falseValue) {
0765: logger
0766: .fine("Branch of if statement is not reachable");
0767: } else {
0768: SimpleNode[] condChildren = falseDirective
0769: .getChildren();
0770:
0771: try {
0772: this .currentState = negativeState;
0773: for (int i = 0; i < condChildren.length; i++) {
0774: SimpleNode condChild = condChildren[i];
0775: if (isTopLevel)
0776: this .visitToplevelStatement(condChild,
0777: docNode);
0778: else
0779: this .visitClassStatement(condChild,
0780: docNode, null);
0781: }
0782: } finally {
0783: this .currentState = previousState;
0784: }
0785: }
0786: }
0787: }
0788:
0789: protected void visitTopLevelIfDirective(SimpleNode parseNode,
0790: org.w3c.dom.Element docNode) {
0791: visitIfDirective(parseNode, docNode, true);
0792: }
0793:
0794: protected void visitClassIfDirective(SimpleNode parseNode,
0795: org.w3c.dom.Element docNode) {
0796: visitIfDirective(parseNode, docNode, false);
0797: }
0798:
0799: protected ConditionalState visitDirectiveConditional(
0800: SimpleNode conditional, org.w3c.dom.Element docNode) {
0801:
0802: ConditionalState condState = new ConditionalState(
0803: ConditionalState.falseValue, this .runtimeOptions,
0804: this .buildOptions);
0805:
0806: if (conditional == null)
0807: throw new InternalError(
0808: "Null conditional in DirectiveConditional",
0809: conditional);
0810:
0811: if (conditional instanceof ASTLiteral) {
0812: Object value = ((ASTLiteral) conditional).getValue();
0813: if (value instanceof Boolean) {
0814: boolean bval = ((Boolean) value).booleanValue();
0815: condState.inferredValue = (bval == true) ? ConditionalState.trueValue
0816: : ConditionalState.falseValue;
0817: condState.trueCases.clear();
0818: if (condState.inferredValue == ConditionalState.trueValue) {
0819: condState.trueCases
0820: .addAll(condState.falseCases);
0821: condState.falseCases.clear();
0822: }
0823: } else {
0824: logger.warning("Conditional: Unknown literal type "
0825: + conditional.getClass().getName());
0826: }
0827: } else if (conditional instanceof ASTIdentifier) {
0828: String value = ((ASTIdentifier) conditional).getName();
0829: if (value.startsWith("$")) {
0830: String token = value.substring(1);
0831: if (this .runtimeOptions.contains(token)
0832: || this .buildOptions.contains(token)) {
0833: condState.trueCases.clear();
0834: condState.addTrueCase(token);
0835: } else if (this .runtimeAliases.containsKey(token)) {
0836: condState = new ConditionalState(
0837: (ConditionalState) this .runtimeAliases
0838: .get(token));
0839: } else {
0840: logger
0841: .warning("Conditional: Unmatched '$' identifier "
0842: + value);
0843: }
0844: } else {
0845: // warning: unmatched conditional
0846: logger.warning("Conditional: Unmatched identifier "
0847: + value);
0848: }
0849: } else if (conditional instanceof ASTOrExpressionSequence) {
0850: SimpleNode[] orChildren = conditional.getChildren();
0851: if (orChildren.length != 2)
0852: throw new InternalError(
0853: "Unexpected number of child nodes in ASTOrExpressionSequence",
0854: conditional);
0855: ConditionalState op1State = visitDirectiveConditional(
0856: orChildren[0], docNode);
0857: ConditionalState op2State = visitDirectiveConditional(
0858: orChildren[1], docNode);
0859: condState = op1State.or(op2State);
0860:
0861: } else if (conditional instanceof ASTAndExpressionSequence) {
0862: SimpleNode[] andChildren = conditional.getChildren();
0863: if (andChildren.length != 2)
0864: throw new InternalError(
0865: "Unexpected number of child nodes in ASTAndExpressionSequence",
0866: conditional);
0867: ConditionalState op1State = visitDirectiveConditional(
0868: andChildren[0], docNode);
0869: ConditionalState op2State = visitDirectiveConditional(
0870: andChildren[1], docNode);
0871:
0872: // punt if we have a combination of two indeterminate values, since we don't know
0873: // how to represent "a && b" using the ConditionalState class
0874: if (op1State.inferredValue == ConditionalState.indeterminateValue
0875: && op2State.inferredValue == ConditionalState.indeterminateValue) {
0876: condState = new ConditionalState(
0877: ConditionalState.trueValue,
0878: this .runtimeOptions, this .buildOptions);
0879: } else {
0880: condState = op1State.and(op2State);
0881: }
0882: } else if (conditional instanceof ASTUnaryExpression) {
0883: SimpleNode[] opChildren = conditional.getChildren();
0884: if (opChildren.length != 2)
0885: throw new InternalError(
0886: "Unexpected number of child nodes in ASTUnaryExpression",
0887: conditional);
0888:
0889: SimpleNode operator = opChildren[0];
0890: SimpleNode operand = opChildren[1];
0891:
0892: if (!(operator instanceof ASTOperator))
0893: throw new InternalError(
0894: "Expected operator inside ASTUnaryExpression",
0895: conditional);
0896: if (((ASTOperator) operator).getOperator() == ParserConstants.BANG) {
0897: ConditionalState opState = visitDirectiveConditional(
0898: operand, docNode);
0899: condState = opState.not();
0900: } else {
0901: logger.warning("Unhandled unary operator"
0902: + ((ASTOperator) operator).getOperator());
0903: }
0904:
0905: } else {
0906: logger.fine("Unhandled conditional type"
0907: + conditional.getClass().getName());
0908: // warning: unmatched conditional
0909: }
0910:
0911: return condState;
0912: }
0913:
0914: private void describeConditionalState(
0915: org.w3c.dom.Element docNode) {
0916: JS2DocUtils.describeConditionalState(this .currentState,
0917: docNode);
0918: }
0919:
0920: private void checkChildrenLowerBounds(SimpleNode node, int min,
0921: int expectedMax, String methodName) {
0922: JS2DocUtils.checkChildrenLowerBounds(node, min,
0923: expectedMax, methodName);
0924: }
0925:
0926: protected PropertyReference resolveBinding(
0927: org.w3c.dom.Element owner, SimpleNode lvalDesc,
0928: ConditionalState state) {
0929: return new PropertyReference(owner, lvalDesc, state);
0930: }
0931:
0932: protected void processComment(org.w3c.dom.Element node,
0933: Comment parsedComment) {
0934:
0935: if (!parsedComment.isEmpty()) {
0936: parsedComment.appendAsXML(node);
0937:
0938: }
0939: }
0940: }
0941:
0942: static public Document toXML(String inputString, File sourceFile,
0943: String sourceRoot, String libraryID, Set runtimeOptions,
0944: List runtimeAliases, List buildOptions) {
0945: org.openlaszlo.sc.Compiler.Parser p = new org.openlaszlo.sc.Compiler.Parser();
0946: SimpleNode parseRoot = p.parse(inputString);
0947:
0948: org.w3c.dom.Document doc = null;
0949:
0950: Map runtimeAliasesMap = new HashMap();
0951: final int n = runtimeAliases.size();
0952: for (int i = 0; i < n; i++) {
0953: String[] e = (String[]) runtimeAliases.get(i);
0954: if (e.length == 0)
0955: throw new InternalError("invalid runtime alias",
0956: parseRoot);
0957: ConditionalState aliasState = new ConditionalState(
0958: ConditionalState.falseValue, runtimeOptions,
0959: buildOptions);
0960: for (int j = 1; j < e.length; j++) {
0961: aliasState.addTrueCase(e[j]);
0962: }
0963: runtimeAliasesMap.put(e[0], aliasState);
0964: }
0965:
0966: javax.xml.parsers.DocumentBuilderFactory factory = javax.xml.parsers.DocumentBuilderFactory
0967: .newInstance();
0968:
0969: try {
0970: doc = factory.newDocumentBuilder().newDocument();
0971:
0972: Element docRoot = doc.createElement("js2doc");
0973: doc.appendChild(docRoot);
0974:
0975: if (runtimeOptions != null)
0976: docRoot.setAttribute("runtimeoptions", JS2DocUtils
0977: .optionsToString(runtimeOptions));
0978: if (buildOptions != null)
0979: docRoot.setAttribute("buildoptions", JS2DocUtils
0980: .optionsToString(buildOptions));
0981:
0982: if (sourceFile != null) {
0983: String baseDirectory = sourceFile.getParent();
0984: if (baseDirectory == null)
0985: throw new InternalError(
0986: "couldn't get base directory for source file",
0987: parseRoot);
0988: if (sourceRoot == null)
0989: throw new InternalError("source root is null",
0990: parseRoot);
0991:
0992: String relativeBase = FileUtils.relativePath(
0993: baseDirectory, sourceRoot);
0994:
0995: Visitor visitor = new Visitor(baseDirectory,
0996: relativeBase, libraryID, runtimeOptions,
0997: runtimeAliasesMap, buildOptions);
0998:
0999: visitor.visitUnit(parseRoot, docRoot, sourceFile
1000: .getName());
1001: } else {
1002: Visitor visitor = new Visitor(null, null, null,
1003: runtimeOptions, runtimeAliasesMap, buildOptions);
1004: visitor.visitToplevelStatement(parseRoot, docRoot);
1005: }
1006:
1007: ReprocessComments.reprocess(docRoot, true);
1008:
1009: doc.normalizeDocument();
1010:
1011: } catch (javax.xml.parsers.ParserConfigurationException e) {
1012: doc = null;
1013: e.printStackTrace();
1014: } catch (InternalError e) {
1015: doc = null;
1016: if (e.node != null)
1017: JS2DocUtils.debugPrintNode(e.node);
1018: e.printStackTrace();
1019: }
1020:
1021: return doc;
1022: }
1023:
1024: }
|