/* * Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Business Objects nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * CALDocChecker.java * Creation date: Aug 17, 2005. * By: Joseph Wong */ package org.openquark.cal.compiler; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import org.openquark.cal.module.Cal.Core.CAL_Prelude; import org.openquark.util.Pair; /** * Checks the CALDoc comments in CAL source code. The process of checking the * comments produce as an end result instances of CALDocComment representing * these comments. These CALDocComment objects are then associated with the * appropriate entities stored in the ModuleTypeInfo, making them available * to clients for later retrieval. * * @author Joseph Wong */ class CALDocChecker { /** The CALCompiler associated with this checker. */ private final CALCompiler compiler; /** The module type info of the current module. */ private final ModuleTypeInfo currentModuleTypeInfo; /** The FreeVariableChecker associated with this checker. */ private final FreeVariableFinder finder; /** The CALTypeChecker associated with this checker. */ private final CALTypeChecker typeChecker; /** * An internal exception class used to signal that the processing of a CALDoc comment should * end after having encountered an error in the comment. * * @author Joseph Wong */ private static final class InvalidCALDocException extends Exception { private static final long serialVersionUID = -5609261520985652737L; /** Private constructor for this internal exception class. */ private InvalidCALDocException() {} } /** * A type-safe enumeration for constants representing the different 'categories' into which * a CALDoc cross-reference appearing without 'context' keywords may be resolved. * * Such a reference may appear in a CALDoc comment, as in: * {at-link Prelude at-} - a module reference without context * {at-link Nothing at-} - a data constructor reference without context * at-see Prelude.Int - a type constructor reference without context * at-see Eq - a type class reference without context * * @author Joseph Wong */ static final class CategoryForCALDocConsNameWithoutContextCrossReference { /** * Represents the fact that a CALDoc cross-reference appearing without a 'context' keyword unambiguously resolves to a module name. */ static final CategoryForCALDocConsNameWithoutContextCrossReference MODULE_NAME = new CategoryForCALDocConsNameWithoutContextCrossReference("module name"); /** * Represents the fact that a CALDoc cross-reference appearing without a 'context' keyword unambiguously resolves to a type constructor name. */ static final CategoryForCALDocConsNameWithoutContextCrossReference TYPE_CONS_NAME = new CategoryForCALDocConsNameWithoutContextCrossReference("type constructor name"); /** * Represents the fact that a CALDoc cross-reference appearing without a 'context' keyword unambiguously resolves to a data constructor name. */ static final CategoryForCALDocConsNameWithoutContextCrossReference DATA_CONS_NAME = new CategoryForCALDocConsNameWithoutContextCrossReference("data constructor name"); /** * Represents the fact that a CALDoc cross-reference appearing without a 'context' keyword unambiguously resolves to a type class name. */ static final CategoryForCALDocConsNameWithoutContextCrossReference TYPE_CLASS_NAME = new CategoryForCALDocConsNameWithoutContextCrossReference("type class name"); /** The display name for the enumeration constant. */ private final String displayName; /** Private constructor for this type-safe enumeration. */ private CategoryForCALDocConsNameWithoutContextCrossReference(String displayName) { this.displayName = displayName; } /** @return the display name for the enumeration constant. */ @Override public String toString() { return displayName; } } /** * Constructs a CALDocChecker. * * @param compiler * @param currentModuleTypeInfo * @param finder * @param typeChecker */ CALDocChecker( CALCompiler compiler, ModuleTypeInfo currentModuleTypeInfo, FreeVariableFinder finder, CALTypeChecker typeChecker) { this.compiler = compiler; this.currentModuleTypeInfo = currentModuleTypeInfo; this.finder = finder; this.typeChecker = typeChecker; } /** * Checks the CALDoc comments within the specified module definition. The checks * include verifying that identifiers contained within the comment are indeed * resolvable to entities within the module and/or other imported modules. * * @param moduleDefnNode the module definition node whose subtree is to be checked. */ void checkCALDocCommentsInModuleDefn(ParseTreeNode moduleDefnNode) { moduleDefnNode.verifyType(CALTreeParserTokenTypes.MODULE_DEFN); // check the module's CALDoc comment. ParseTreeNode optionalCALDocNode = moduleDefnNode.firstChild(); CALDocComment caldoc = checkAndBuildCALDocComment(optionalCALDocNode, null, null, false, false, false); // associate the CALDoc with the entity currentModuleTypeInfo.setCALDocComment(caldoc); // check the children. ParseTreeNode moduleNameNode = optionalCALDocNode.nextSibling(); moduleNameNode.verifyType(CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME, CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME_EMPTY_QUALIFIER); ParseTreeNode importDeclarationListNode = moduleNameNode.nextSibling(); importDeclarationListNode.verifyType(CALTreeParserTokenTypes.IMPORT_DECLARATION_LIST); ParseTreeNode friendDeclarationListNode = importDeclarationListNode.nextSibling(); friendDeclarationListNode.verifyType(CALTreeParserTokenTypes.FRIEND_DECLARATION_LIST); ParseTreeNode outerDefnListNode = friendDeclarationListNode.nextSibling(); outerDefnListNode.verifyType(CALTreeParserTokenTypes.OUTER_DEFN_LIST); checkCALDocCommentsInOuterDefnList(outerDefnListNode); } /** * Checks the CALDoc comments within the specified outer definition list. The checks * include verifying that identifiers contained within the comment are indeed * resolvable to entities within the module and/or other imported modules. * * @param outerDefnListNode the outer definition list node whose subtree is to be checked. */ void checkCALDocCommentsInOuterDefnList(ParseTreeNode outerDefnListNode) { outerDefnListNode.verifyType(CALTreeParserTokenTypes.OUTER_DEFN_LIST); for (final ParseTreeNode outerDefnNode : outerDefnListNode) { switch (outerDefnNode.getType()) { case CALTreeParserTokenTypes.TOP_LEVEL_TYPE_DECLARATION: { checkCALDocCommentsInFunctionTypeDeclaration(outerDefnNode); break; } case CALTreeParserTokenTypes.TOP_LEVEL_FUNCTION_DEFN: { checkCALDocCommentsInAlgebraicFunctionDefn(outerDefnNode); break; } case CALTreeParserTokenTypes.FOREIGN_FUNCTION_DECLARATION: { checkCALDocCommentsInForeignFunctionDefn(outerDefnNode); break; } case CALTreeParserTokenTypes.PRIMITIVE_FUNCTION_DECLARATION: { checkCALDocCommentsInPrimitiveFunctionDefn(outerDefnNode); break; } case CALTreeParserTokenTypes.DATA_DECLARATION: { checkCALDocCommentsInAlgebraicTypeDefn(outerDefnNode); break; } case CALTreeParserTokenTypes.FOREIGN_DATA_DECLARATION: { checkCALDocCommentsInForeignTypeDefn(outerDefnNode); break; } case CALTreeParserTokenTypes.TYPE_CLASS_DEFN: { checkCALDocCommentsInTypeClassDefn(outerDefnNode); break; } case CALTreeParserTokenTypes.INSTANCE_DEFN: { checkCALDocCommentsInInstanceDefn(outerDefnNode); break; } default: { outerDefnNode.unexpectedParseTreeNode(); break; } } } } /** * Checks the CALDoc comments within the specified function type declaration. The checks * include verifying that identifiers contained within the comment are indeed * resolvable to entities within the module and/or other imported modules. * * @param topLevelTypeDeclarationNode the function type declaration node whose subtree is to be checked. */ private void checkCALDocCommentsInFunctionTypeDeclaration(ParseTreeNode topLevelTypeDeclarationNode) { topLevelTypeDeclarationNode.verifyType(CALTreeParserTokenTypes.TOP_LEVEL_TYPE_DECLARATION); ParseTreeNode optionalCALDocNode = topLevelTypeDeclarationNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); ParseTreeNode typeDeclarationNode = optionalCALDocNode.nextSibling(); typeDeclarationNode.verifyType(CALTreeParserTokenTypes.TYPE_DECLARATION); ParseTreeNode functionNameNode = typeDeclarationNode.firstChild(); functionNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); String functionName = functionNameNode.getText(); ParseTreeNode topLevelFunctionDefnNode = typeChecker.getFunctionDefinitionNode(functionName); if (topLevelFunctionDefnNode == null) { // the function is declared but not defined. This is illegal in CAL. compiler.logMessage(new CompilerMessage(functionNameNode, new MessageKind.Error.DefinitionMissing(functionName))); } else { // fetch the parameter list from the function definition. topLevelFunctionDefnNode.verifyType(CALTreeParserTokenTypes.TOP_LEVEL_FUNCTION_DEFN); ParseTreeNode paramListNode = topLevelFunctionDefnNode.getChild(3); paramListNode.verifyType(CALTreeParserTokenTypes.FUNCTION_PARAM_LIST); // fetch the entity and then the type expression from the entity associated with the function. Function entity = currentModuleTypeInfo.getFunction(functionName); if (entity == null) { String displayName = getQualifiedNameDisplayString(functionNameNode); // TypeChecker: unknown function or variable {displayName}. compiler.logMessage(new CompilerMessage(functionNameNode, new MessageKind.Error.UnknownFunctionOrVariable(displayName))); } else { TypeExpr typeExpr = entity.getTypeExpr(); // check the function's CALDoc with the function's type expression and the parameter list from the function definition. CALDocComment caldoc = checkAndBuildCALDocComment(optionalCALDocNode, paramListNode, typeExpr, true, true, false); // associate the CALDoc with the entity entity.setCALDocComment(caldoc); } } } /** * Checks the CALDoc comments within the specified algebraic function definition. The checks * include verifying that identifiers contained within the comment are indeed * resolvable to entities within the module and/or other imported modules. * * @param topLevelFunctionDefnNode the algebraic function definition node whose subtree is to be checked. */ private void checkCALDocCommentsInAlgebraicFunctionDefn(ParseTreeNode topLevelFunctionDefnNode) { topLevelFunctionDefnNode.verifyType(CALTreeParserTokenTypes.TOP_LEVEL_FUNCTION_DEFN); ParseTreeNode optionalCALDocNode = topLevelFunctionDefnNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); ParseTreeNode accessModifierNode = optionalCALDocNode.nextSibling(); accessModifierNode.verifyType(CALTreeParserTokenTypes.ACCESS_MODIFIER); ParseTreeNode functionNameNode = accessModifierNode.nextSibling(); functionNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); String functionName = functionNameNode.getText(); ParseTreeNode paramListNode = functionNameNode.nextSibling(); paramListNode.verifyType(CALTreeParserTokenTypes.FUNCTION_PARAM_LIST); ParseTreeNode definingExprNode = paramListNode.nextSibling(); checkCALDocCommentsInExpr(definingExprNode); // if the function has an associated type declaration, then the CALDoc comment must appear // immediately before the type declaration, and not immediately before the function definition. if (typeChecker.hasFunctionTypeDeclaration(functionName)) { if (optionalCALDocNode.firstChild() != null) { compiler.logMessage( new CompilerMessage( optionalCALDocNode.firstChild(), new MessageKind.Error.CALDocCommentForAlgebraicFunctionMustAppearBeforeTypeDeclaration(functionName))); } } else { // There is no associated type declaration, so it is okay to have a CALDoc comment before this function definition. // fetch the entity and then the type expression from the entity associated with the function. FunctionalAgent entity = currentModuleTypeInfo.getFunction(functionName); if (entity == null) { String displayName = getQualifiedNameDisplayString(functionNameNode); // TypeChecker: unknown function or variable {displayName}. compiler.logMessage(new CompilerMessage(functionNameNode, new MessageKind.Error.UnknownFunctionOrVariable(displayName))); } else { TypeExpr typeExpr = entity.getTypeExpr(); // check the function's CALDoc with the function's type expression. CALDocComment caldoc = checkAndBuildCALDocComment(optionalCALDocNode, paramListNode, typeExpr, true, true, false); // associate the CALDoc with the entity entity.setCALDocComment(caldoc); } } } /** * Checks the CALDoc comments within the specified expression. The checks * include verifying that identifiers contained within the comment are indeed * resolvable to entities within the module and/or other imported modules. * * @param parseTree the expression node whose subtree is to be checked. */ void checkCALDocCommentsInExpr(ParseTreeNode parseTree) { int nodeType = parseTree.getType(); switch (nodeType) { case CALTreeParserTokenTypes.VIRTUAL_LET_NONREC: case CALTreeParserTokenTypes.VIRTUAL_LET_REC: { checkCALDocCommentsInLet(parseTree); break; } case CALTreeParserTokenTypes.LAMBDA_DEFN: { ParseTreeNode paramListNode = parseTree.firstChild(); paramListNode.verifyType(CALTreeParserTokenTypes.FUNCTION_PARAM_LIST); ParseTreeNode definingExprNode = paramListNode.nextSibling(); checkCALDocCommentsInExpr(definingExprNode); break; } case CALTreeParserTokenTypes.LITERAL_if: { ParseTreeNode conditionNode = parseTree.firstChild(); checkCALDocCommentsInExpr(conditionNode); ParseTreeNode ifTrueNode = conditionNode.nextSibling(); checkCALDocCommentsInExpr(ifTrueNode); ParseTreeNode ifFalseNode = ifTrueNode.nextSibling(); checkCALDocCommentsInExpr(ifFalseNode); break; } case CALTreeParserTokenTypes.VIRTUAL_DATA_CONSTRUCTOR_CASE: { checkCALDocCommentsInDataConstructorCase(parseTree); break; } case CALTreeParserTokenTypes.VIRTUAL_TUPLE_CASE: { checkCALDocCommentsInTupleCase(parseTree); break; } case CALTreeParserTokenTypes.VIRTUAL_RECORD_CASE: { checkCALDocCommentsInRecordCase(parseTree); break; } case CALTreeParserTokenTypes.BARBAR: case CALTreeParserTokenTypes.AMPERSANDAMPERSAND: case CALTreeParserTokenTypes.PLUSPLUS: case CALTreeParserTokenTypes.EQUALSEQUALS: case CALTreeParserTokenTypes.NOT_EQUALS: case CALTreeParserTokenTypes.GREATER_THAN_OR_EQUALS: case CALTreeParserTokenTypes.GREATER_THAN: case CALTreeParserTokenTypes.LESS_THAN: case CALTreeParserTokenTypes.LESS_THAN_OR_EQUALS: case CALTreeParserTokenTypes.PLUS: case CALTreeParserTokenTypes.MINUS: case CALTreeParserTokenTypes.ASTERISK: case CALTreeParserTokenTypes.SOLIDUS: case CALTreeParserTokenTypes.PERCENT: case CALTreeParserTokenTypes.COLON: case CALTreeParserTokenTypes.POUND: { ParseTreeNode arg1Node = parseTree.firstChild(); checkCALDocCommentsInExpr(arg1Node); ParseTreeNode arg2Node = arg1Node.nextSibling(); checkCALDocCommentsInExpr(arg2Node); break; } case CALTreeParserTokenTypes.UNARY_MINUS: { ParseTreeNode arg1Node = parseTree.firstChild(); checkCALDocCommentsInExpr(arg1Node); break; } case CALTreeParserTokenTypes.DOLLAR: { // Treat the node representing the $ operator as an application node checkCALDocCommentsInApplication(parseTree); break; } case CALTreeParserTokenTypes.BACKQUOTE: { // Skip the backquoted node checkCALDocCommentsInApplication(parseTree.firstChild()); break; } case CALTreeParserTokenTypes.APPLICATION: { checkCALDocCommentsInApplication(parseTree); break; } //variables and functions case CALTreeParserTokenTypes.QUALIFIED_VAR: //data constructors case CALTreeParserTokenTypes.QUALIFIED_CONS: //literals case CALTreeParserTokenTypes.CHAR_LITERAL : case CALTreeParserTokenTypes.INTEGER_LITERAL : case CALTreeParserTokenTypes.FLOAT_LITERAL : case CALTreeParserTokenTypes.STRING_LITERAL : { // nothing to do break; } case CALTreeParserTokenTypes.LIST_CONSTRUCTOR : { for (final ParseTreeNode elementNode : parseTree) { checkCALDocCommentsInExpr(elementNode); } break; } case CALTreeParserTokenTypes.TUPLE_CONSTRUCTOR : { for (final ParseTreeNode componentNode : parseTree) { checkCALDocCommentsInExpr(componentNode); } break; } case CALTreeParserTokenTypes.RECORD_CONSTRUCTOR : { ParseTreeNode baseRecordNode = parseTree.firstChild(); baseRecordNode.verifyType(CALTreeParserTokenTypes.BASE_RECORD); ParseTreeNode baseRecordExprNode = baseRecordNode.firstChild(); if (baseRecordExprNode != null) { checkCALDocCommentsInExpr(baseRecordExprNode); } ParseTreeNode fieldModificationListNode = baseRecordNode.nextSibling(); fieldModificationListNode.verifyType(CALTreeParserTokenTypes.FIELD_MODIFICATION_LIST); for (final ParseTreeNode fieldModificationNode : fieldModificationListNode) { fieldModificationNode.verifyType(CALTreeParserTokenTypes.FIELD_EXTENSION, CALTreeParserTokenTypes.FIELD_VALUE_UPDATE); ParseTreeNode fieldNameNode = fieldModificationNode.firstChild(); ParseTreeNode valueExprNode = fieldNameNode.nextSibling(); checkCALDocCommentsInExpr(valueExprNode); } break; } case CALTreeParserTokenTypes.SELECT_DATA_CONSTRUCTOR_FIELD : { checkCALDocCommentsInDataConstructorFieldSelection(parseTree); break; } case CALTreeParserTokenTypes.SELECT_RECORD_FIELD: { ParseTreeNode exprNode = parseTree.firstChild(); checkCALDocCommentsInExpr(exprNode); break; } case CALTreeParserTokenTypes.EXPRESSION_TYPE_SIGNATURE: { ParseTreeNode exprNode = parseTree.firstChild(); checkCALDocCommentsInExpr(exprNode); break; } default : { parseTree.unexpectedParseTreeNode(); break; } } } /** * Checks the CALDoc comments within the specified application expression. The checks * include verifying that identifiers contained within the comment are indeed * resolvable to entities within the module and/or other imported modules. * * @param parseTree the application expression node whose subtree is to be checked. */ private void checkCALDocCommentsInApplication(ParseTreeNode parseTree) { for (final ParseTreeNode exprNode : parseTree) { checkCALDocCommentsInExpr(exprNode); } } /** * Checks the CALDoc comments within the specified record case. The checks * include verifying that identifiers contained within the comment are indeed * resolvable to entities within the module and/or other imported modules. * * @param recordCaseNode the record case node whose subtree is to be checked. */ private void checkCALDocCommentsInRecordCase(ParseTreeNode recordCaseNode) { recordCaseNode.verifyType(CALTreeParserTokenTypes.VIRTUAL_RECORD_CASE); ParseTreeNode conditionNode = recordCaseNode.firstChild(); checkCALDocCommentsInExpr(conditionNode); ParseTreeNode altListNode = conditionNode.nextSibling(); altListNode.verifyType(CALTreeParserTokenTypes.ALT_LIST); if (!altListNode.hasExactlyOneChild()) { //record-case patterns have only 1 alternative. This should be caught earlier in static analysis. throw new IllegalArgumentException(); } ParseTreeNode altNode = altListNode.firstChild(); altNode.verifyType(CALTreeParserTokenTypes.ALT); ParseTreeNode patternNode = altNode.firstChild(); patternNode.verifyType(CALTreeParserTokenTypes.RECORD_PATTERN); ParseTreeNode exprNode = patternNode.nextSibling(); checkCALDocCommentsInExpr(exprNode); } /** * Checks the CALDoc comments within the specified data constructor case. The checks * include verifying that identifiers contained within the comment are indeed * resolvable to entities within the module and/or other imported modules. * * @param caseNode the data constructor case node whose subtree is to be checked. */ private void checkCALDocCommentsInDataConstructorCase(ParseTreeNode caseNode) { caseNode.verifyType(CALTreeParserTokenTypes.VIRTUAL_DATA_CONSTRUCTOR_CASE); ParseTreeNode conditionNode = caseNode.firstChild(); checkCALDocCommentsInExpr(conditionNode); ParseTreeNode altListNode = conditionNode.nextSibling(); altListNode.verifyType(CALTreeParserTokenTypes.ALT_LIST); for (final ParseTreeNode altNode : altListNode) { altNode.verifyType(CALTreeParserTokenTypes.ALT); ParseTreeNode patternNode = altNode.firstChild(); ParseTreeNode exprNode = patternNode.nextSibling(); checkCALDocCommentsInExpr(exprNode); } } /** * Checks the CALDoc comments within the specified tupe case. The checks * include verifying that identifiers contained within the comment are indeed * resolvable to entities within the module and/or other imported modules. * * @param caseNode the tuple case node whose subtree is to be checked. */ private void checkCALDocCommentsInTupleCase(ParseTreeNode caseNode) { caseNode.verifyType(CALTreeParserTokenTypes.VIRTUAL_TUPLE_CASE); ParseTreeNode conditionNode = caseNode.firstChild(); checkCALDocCommentsInExpr(conditionNode); ParseTreeNode altListNode = conditionNode.nextSibling(); altListNode.verifyType(CALTreeParserTokenTypes.ALT_LIST); if (!altListNode.hasExactlyOneChild()) { //tuple-case patterns have only 1 alternative. This should be caught earlier in static analysis. throw new IllegalArgumentException(); } ParseTreeNode altNode = altListNode.firstChild(); altNode.verifyType(CALTreeParserTokenTypes.ALT); ParseTreeNode patternNode = altNode.firstChild(); patternNode.verifyType(CALTreeParserTokenTypes.TUPLE_CONSTRUCTOR); ParseTreeNode exprNode = patternNode.nextSibling(); checkCALDocCommentsInExpr(exprNode); } /** * Checks the CALDoc comments within the specified data constructor field selection. The checks * include verifying that identifiers contained within the comment are indeed * resolvable to entities within the module and/or other imported modules. * * @param selectNode the field selection node whose subtree is to be checked. */ private void checkCALDocCommentsInDataConstructorFieldSelection(ParseTreeNode selectNode) { selectNode.verifyType(CALTreeParserTokenTypes.SELECT_DATA_CONSTRUCTOR_FIELD); ParseTreeNode conditionNode = selectNode.firstChild(); checkCALDocCommentsInExpr(conditionNode); ParseTreeNode dcNameNode = conditionNode.nextSibling(); dcNameNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS); ParseTreeNode fieldNameNode = dcNameNode.nextSibling(); fieldNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID, CALTreeParserTokenTypes.ORDINAL_FIELD_NAME); ParseTreeNode qualifiedVarNode = fieldNameNode.nextSibling(); qualifiedVarNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_VAR); checkCALDocCommentsInExpr(qualifiedVarNode); } /** * Checks the CALDoc comments within the specified let expression. The checks * include verifying that identifiers contained within the comment are indeed * resolvable to entities within the module and/or other imported modules. * * @param letNode the let expression node whose subtree is to be checked. */ private void checkCALDocCommentsInLet(ParseTreeNode letNode) { ParseTreeNode defnListNode = letNode.firstChild(); defnListNode.verifyType(CALTreeParserTokenTypes.LET_DEFN_LIST); Map<String, ParseTreeNode> funcNamesToDefnNodes = new HashMap<String, ParseTreeNode>(); Set<String> functionsWithTypeDecls = new HashSet<String>(); // First pass: gather the function definition nodes into a map, // and the names of the functions with type declarations into a set. for (final ParseTreeNode defnNode : defnListNode) { defnNode.verifyType(CALTreeParserTokenTypes.LET_DEFN, CALTreeParserTokenTypes.LET_DEFN_TYPE_DECLARATION); if (defnNode.getType() == CALTreeParserTokenTypes.LET_DEFN) { ParseTreeNode optionalCALDocNode = defnNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); ParseTreeNode localFunctionNameNode = optionalCALDocNode.nextSibling(); String functionName = localFunctionNameNode.getText(); ParseTreeNode paramListNode = localFunctionNameNode.nextSibling(); paramListNode.verifyType(CALTreeParserTokenTypes.FUNCTION_PARAM_LIST); ParseTreeNode definingExprNode = paramListNode.nextSibling(); checkCALDocCommentsInExpr(definingExprNode); funcNamesToDefnNodes.put(functionName, defnNode); } else if (defnNode.getType() == CALTreeParserTokenTypes.LET_DEFN_TYPE_DECLARATION) { ParseTreeNode optionalCALDocNode = defnNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); ParseTreeNode typeDeclNode = optionalCALDocNode.nextSibling(); typeDeclNode.verifyType(CALTreeParserTokenTypes.TYPE_DECLARATION); String functionName = typeDeclNode.firstChild().getText(); functionsWithTypeDecls.add(functionName); } } // Second pass: perform the actual checking of the CALDoc comments for (final ParseTreeNode defnNode : defnListNode) { if (defnNode.getType() == CALTreeParserTokenTypes.LET_DEFN) { ParseTreeNode optionalCALDocNode = defnNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); ParseTreeNode localFunctionNameNode = optionalCALDocNode.nextSibling(); String functionName = localFunctionNameNode.getText(); ParseTreeNode paramListNode = localFunctionNameNode.nextSibling(); paramListNode.verifyType(CALTreeParserTokenTypes.FUNCTION_PARAM_LIST); // if we have previously encountered a type declaration for this function // in the first pass, then we require that the CALDoc comment be associated // with the type declaration and not with the function definition. if (functionsWithTypeDecls.contains(functionName)) { if (optionalCALDocNode.firstChild() != null) { compiler.logMessage( new CompilerMessage( optionalCALDocNode.firstChild(), new MessageKind.Error.CALDocCommentForAlgebraicFunctionMustAppearBeforeTypeDeclaration(functionName))); } } else { // the information on the function's type is stored as // data on the optionalCALDocNode associated with the function definition. // we use the information in the type to determine how many @arg tags // is allowed to be in the CALDoc comment. int nTopLevelArrowsInType = optionalCALDocNode.getFunctionTypeForLocalFunctionCALDocComment().getArity(); CALDocComment calDocComment = checkAndBuildCALDocComment(optionalCALDocNode, paramListNode, nTopLevelArrowsInType, true, true, false); LocalFunctionIdentifier identifier = typeChecker.getLocalFunctionIdentifier(functionName); if(identifier != null) { Function toplevelFunction = currentModuleTypeInfo.getFunction(identifier.getToplevelFunctionName().getUnqualifiedName()); Function localFunction = toplevelFunction.getLocalFunction(identifier); if(localFunction != null) { localFunction.setCALDocComment(calDocComment); } } } } else if (defnNode.getType() == CALTreeParserTokenTypes.LET_DEFN_TYPE_DECLARATION) { ParseTreeNode optionalCALDocNode = defnNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); ParseTreeNode typeDeclNode = optionalCALDocNode.nextSibling(); typeDeclNode.verifyType(CALTreeParserTokenTypes.TYPE_DECLARATION); String functionName = typeDeclNode.firstChild().getText(); // we need to fetch the function definition node corresponding to this // type declaration so that we can check the @arg tags against // the declared parameters of the function. ParseTreeNode funcDefnNode = funcNamesToDefnNodes.get(functionName); if (funcDefnNode == null) { compiler.logMessage(new CompilerMessage(defnNode, new MessageKind.Error.DefinitionMissing(functionName))); } else { ParseTreeNode funcDefnOptionalCALDocNode = funcDefnNode.firstChild(); funcDefnOptionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); ParseTreeNode localFunctionNameNode = funcDefnOptionalCALDocNode.nextSibling(); // get the node containing the parameters of the function (for checking against the @arg tags). ParseTreeNode paramListNode = localFunctionNameNode.nextSibling(); paramListNode.verifyType(CALTreeParserTokenTypes.FUNCTION_PARAM_LIST); // the information on the function's type is stored as // data on the optionalCALDocNode associated with the function definition. // we use the information in the type to determine how many @arg tags // is allowed to be in the CALDoc comment. int nTopLevelArrowsInType = funcDefnOptionalCALDocNode.getFunctionTypeForLocalFunctionCALDocComment().getArity(); CALDocComment calDocComment = checkAndBuildCALDocComment(optionalCALDocNode, paramListNode, nTopLevelArrowsInType, true, true, false); LocalFunctionIdentifier identifier = typeChecker.getLocalFunctionIdentifier(functionName); if(identifier != null) { Function toplevelFunction = currentModuleTypeInfo.getFunction(identifier.getToplevelFunctionName().getUnqualifiedName()); Function localFunction = toplevelFunction.getLocalFunction(identifier); if(localFunction != null) { localFunction.setCALDocComment(calDocComment); } } } } } ParseTreeNode inExprNode = defnListNode.nextSibling(); if (inExprNode != null) { checkCALDocCommentsInExpr(inExprNode); } } /** * Checks the CALDoc comments within the specified foreign function definition. The checks * include verifying that identifiers contained within the comment are indeed * resolvable to entities within the module and/or other imported modules. * * @param foreignFunctionDeclarationNode the foreign function definition node whose subtree is to be checked. */ private void checkCALDocCommentsInForeignFunctionDefn(ParseTreeNode foreignFunctionDeclarationNode) { foreignFunctionDeclarationNode.verifyType(CALTreeParserTokenTypes.FOREIGN_FUNCTION_DECLARATION); ParseTreeNode optionalCALDocNode = foreignFunctionDeclarationNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); ParseTreeNode externalNameNode = optionalCALDocNode.nextSibling(); externalNameNode.verifyType(CALTreeParserTokenTypes.STRING_LITERAL); ParseTreeNode accessModifierNode = externalNameNode.nextSibling(); accessModifierNode.verifyType(CALTreeParserTokenTypes.ACCESS_MODIFIER); ParseTreeNode typeDeclarationNode = accessModifierNode.nextSibling(); typeDeclarationNode.verifyType(CALTreeParserTokenTypes.TYPE_DECLARATION); ParseTreeNode functionNameNode = typeDeclarationNode.firstChild(); functionNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); String functionName = functionNameNode.getText(); // fetch the entity and then the type expression from the entity associated with the foreign function. FunctionalAgent entity = currentModuleTypeInfo.getFunction(functionName); if (entity == null) { String displayName = getQualifiedNameDisplayString(functionNameNode); // TypeChecker: unknown function or variable {displayName}. compiler.logMessage(new CompilerMessage(functionNameNode, new MessageKind.Error.UnknownFunctionOrVariable(displayName))); } else { TypeExpr typeExpr = entity.getTypeExpr(); // check the foreign function's CALDoc with the function's type expression. CALDocComment caldoc = checkAndBuildCALDocComment(optionalCALDocNode, null, typeExpr, true, true, false); // associate the CALDoc with the entity entity.setCALDocComment(caldoc); } } /** * Checks the CALDoc comments within the specified primitive function definition. The checks * include verifying that identifiers contained within the comment are indeed * resolvable to entities within the module and/or other imported modules. * * @param primitiveFunctionNode the primitive function definition node whose subtree is to be checked. */ private void checkCALDocCommentsInPrimitiveFunctionDefn(ParseTreeNode primitiveFunctionNode) { primitiveFunctionNode.verifyType(CALTreeParserTokenTypes.PRIMITIVE_FUNCTION_DECLARATION); ParseTreeNode optionalCALDocNode = primitiveFunctionNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); ParseTreeNode accessModifierNode = optionalCALDocNode.nextSibling(); accessModifierNode.verifyType(CALTreeParserTokenTypes.ACCESS_MODIFIER); ParseTreeNode typeDeclarationNode = accessModifierNode.nextSibling(); typeDeclarationNode.verifyType(CALTreeParserTokenTypes.TYPE_DECLARATION); ParseTreeNode functionNameNode = typeDeclarationNode.firstChild(); functionNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); String functionName = functionNameNode.getText(); // fetch the entity and then the type expression from the entity associated with the primitive function. FunctionalAgent entity = currentModuleTypeInfo.getFunction(functionName); if (entity == null) { String displayName = getQualifiedNameDisplayString(functionNameNode); // TypeChecker: unknown function or variable {displayName}. compiler.logMessage(new CompilerMessage(functionNameNode, new MessageKind.Error.UnknownFunctionOrVariable(displayName))); } else { TypeExpr typeExpr = entity.getTypeExpr(); // check the primitive function's CALDoc with the function's type expression. CALDocComment caldoc = checkAndBuildCALDocComment(optionalCALDocNode, null, typeExpr, true, true, false); // associate the CALDoc with the entity entity.setCALDocComment(caldoc); } } /** * Checks the CALDoc comments within the specified algebraic type definition. The checks * include verifying that identifiers contained within the comment are indeed * resolvable to entities within the module and/or other imported modules. * * @param algebraicTypeDefnNode the algebraic type definition node whose subtree is to be checked. */ private void checkCALDocCommentsInAlgebraicTypeDefn(ParseTreeNode algebraicTypeDefnNode) { algebraicTypeDefnNode.verifyType(CALTreeParserTokenTypes.DATA_DECLARATION); ParseTreeNode optionalCALDocNode = algebraicTypeDefnNode.firstChild(); // check the class's CALDoc comment. CALDocComment caldoc = checkAndBuildCALDocComment(optionalCALDocNode, null, null, false, false, false); // check the children. ParseTreeNode accessModifierNode = optionalCALDocNode.nextSibling(); accessModifierNode.verifyType(CALTreeParserTokenTypes.ACCESS_MODIFIER); ParseTreeNode typeNameNode = accessModifierNode.nextSibling(); typeNameNode.verifyType(CALTreeParserTokenTypes.CONS_ID); ParseTreeNode typeParamListNode = typeNameNode.nextSibling(); typeParamListNode.verifyType(CALTreeParserTokenTypes.TYPE_CONS_PARAM_LIST); ParseTreeNode dataConsDefnListNode = typeParamListNode.nextSibling(); dataConsDefnListNode.verifyType(CALTreeParserTokenTypes.DATA_CONSTRUCTOR_DEFN_LIST); for (final ParseTreeNode dataConsDefnNode : dataConsDefnListNode) { checkCALDocCommentsInDataConsDefn(dataConsDefnNode); } // associate the CALDoc with the entity currentModuleTypeInfo.getTypeConstructor(typeNameNode.getText()).setCALDocComment(caldoc); } /** * Checks the CALDoc comments within the specified data constructor definition. The checks * include verifying that identifiers contained within the comment are indeed * resolvable to entities within the module and/or other imported modules. * * @param dataConsDefnNode the data constructor definition node whose subtree is to be checked. */ private void checkCALDocCommentsInDataConsDefn(ParseTreeNode dataConsDefnNode) { dataConsDefnNode.verifyType(CALTreeParserTokenTypes.DATA_CONSTRUCTOR_DEFN); ParseTreeNode optionalCALDocNode = dataConsDefnNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); ParseTreeNode scopeNode = optionalCALDocNode.nextSibling(); scopeNode.verifyType(CALTreeParserTokenTypes.ACCESS_MODIFIER); ParseTreeNode dataConsNameNode = scopeNode.nextSibling(); dataConsNameNode.verifyType(CALTreeParserTokenTypes.CONS_ID); ParseTreeNode dataConsArgListNode = dataConsNameNode.nextSibling(); dataConsArgListNode.verifyType(CALTreeParserTokenTypes.DATA_CONSTRUCTOR_ARG_LIST); CALDocComment caldoc = checkAndBuildCALDocComment(optionalCALDocNode, dataConsArgListNode, dataConsArgListNode.getNumberOfChildren(), true, false, true); // associate the CALDoc with the entity (currentModuleTypeInfo.getDataConstructor(dataConsNameNode.getText())).setCALDocComment(caldoc); } /** * Checks the CALDoc comments within the specified foreign type definition. The checks * include verifying that identifiers contained within the comment are indeed * resolvable to entities within the module and/or other imported modules. * * @param foreignTypeDefnNode the foreign type definition node whose subtree is to be checked. */ private void checkCALDocCommentsInForeignTypeDefn(ParseTreeNode foreignTypeDefnNode) { foreignTypeDefnNode.verifyType(CALTreeParserTokenTypes.FOREIGN_DATA_DECLARATION); ParseTreeNode optionalCALDocNode = foreignTypeDefnNode.firstChild(); CALDocComment caldoc = checkAndBuildCALDocComment(optionalCALDocNode, null, null, false, false, false); ParseTreeNode implementationScopeNode = optionalCALDocNode.nextSibling(); implementationScopeNode.verifyType(CALTreeParserTokenTypes.ACCESS_MODIFIER); ParseTreeNode externalNameNode = implementationScopeNode.nextSibling(); externalNameNode.verifyType(CALTreeParserTokenTypes.STRING_LITERAL); ParseTreeNode accessModifierNode = externalNameNode.nextSibling(); accessModifierNode.verifyType(CALTreeParserTokenTypes.ACCESS_MODIFIER); ParseTreeNode typeNameNode = accessModifierNode.nextSibling(); typeNameNode.verifyType(CALTreeParserTokenTypes.CONS_ID); String typeName = typeNameNode.getText(); // associate the CALDoc with the entity currentModuleTypeInfo.getTypeConstructor(typeName).setCALDocComment(caldoc); } /** * Checks the CALDoc comments within the specified type class definition. The checks * include verifying that identifiers contained within the comment are indeed * resolvable to entities within the module and/or other imported modules. * * @param typeClassDefnNode the type class definition node whose subtree is to be checked. */ private void checkCALDocCommentsInTypeClassDefn(ParseTreeNode typeClassDefnNode) { typeClassDefnNode.verifyType(CALTreeParserTokenTypes.TYPE_CLASS_DEFN); ParseTreeNode optionalCALDocNode = typeClassDefnNode.firstChild(); // check the instance's CALDoc comment. CALDocComment caldoc = checkAndBuildCALDocComment(optionalCALDocNode, null, null, false, false, false); // check the children. ParseTreeNode scopeNode = optionalCALDocNode.nextSibling(); scopeNode.verifyType(CALTreeParserTokenTypes.ACCESS_MODIFIER); ParseTreeNode contextListNode = scopeNode.nextSibling(); contextListNode.verifyType(CALTreeParserTokenTypes.CLASS_CONTEXT_LIST, CALTreeParserTokenTypes.CLASS_CONTEXT_SINGLETON, CALTreeParserTokenTypes.CLASS_CONTEXT_NOTHING); ParseTreeNode typeClassNameNode = contextListNode.nextSibling(); typeClassNameNode.verifyType(CALTreeParserTokenTypes.CONS_ID); ParseTreeNode typeVarNode = typeClassNameNode.nextSibling(); typeVarNode.verifyType(CALTreeParserTokenTypes.VAR_ID); ParseTreeNode classMethodListNode = typeVarNode.nextSibling(); classMethodListNode.verifyType(CALTreeParserTokenTypes.CLASS_METHOD_LIST); String className = typeClassNameNode.getText(); TypeClass typeClass = currentModuleTypeInfo.getTypeClass(className); for (final ParseTreeNode classMethodNode : classMethodListNode) { checkCALDocCommentsInClassMethodDefn(classMethodNode, typeClass); } // associate the CALDoc with the entity typeClass.setCALDocComment(caldoc); } /** * Checks the CALDoc comments within the specified class method definition. The checks * include verifying that identifiers contained within the comment are indeed * resolvable to entities within the module and/or other imported modules. * * @param classMethodNode the class method definition node whose subtree is to be checked. * @param typeClass the type class entity. */ private void checkCALDocCommentsInClassMethodDefn(ParseTreeNode classMethodNode, TypeClass typeClass) { classMethodNode.verifyType(CALTreeParserTokenTypes.CLASS_METHOD); ParseTreeNode optionalCALDocNode = classMethodNode.firstChild(); ParseTreeNode scopeNode = optionalCALDocNode.nextSibling(); scopeNode.verifyType(CALTreeParserTokenTypes.ACCESS_MODIFIER); ParseTreeNode classMethodNameNode = scopeNode.nextSibling(); classMethodNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); String methodName = classMethodNameNode.getText(); TypeExpr typeExpr = currentModuleTypeInfo.getClassMethod(methodName).getTypeExpr(); // check the class method CALDoc with the type expression calculated from the declared type signature. CALDocComment caldoc = checkAndBuildCALDocComment(optionalCALDocNode, null, typeExpr, true, true, false); // associate the CALDoc with the entity typeClass.getClassMethod(methodName).setCALDocComment(caldoc); } /** * Constructs a ClassInstanceIdentifier from an instance type cons node. * * @param instanceTypeConsNode the instance type cons node. * @param typeClassName the name of the type class of the instance being declared. * @return a new ClassInstanceIdentifier representing the name of the instance. */ private ClassInstanceIdentifier buildClassInstanceIdentifier(ParseTreeNode instanceTypeConsNode, QualifiedName typeClassName) { switch(instanceTypeConsNode.getType()) { case CALTreeParserTokenTypes.GENERAL_TYPE_CONSTRUCTOR: case CALTreeParserTokenTypes.UNPARENTHESIZED_TYPE_CONSTRUCTOR: { ParseTreeNode typeConsNameNode = instanceTypeConsNode.firstChild(); return new ClassInstanceIdentifier.TypeConstructorInstance(typeClassName, typeConsNameNode.toQualifiedName()); } case CALTreeParserTokenTypes.FUNCTION_TYPE_CONSTRUCTOR: { return new ClassInstanceIdentifier.TypeConstructorInstance(typeClassName, CAL_Prelude.TypeConstructors.Function); } case CALTreeParserTokenTypes.UNIT_TYPE_CONSTRUCTOR: { return new ClassInstanceIdentifier.TypeConstructorInstance(typeClassName, CAL_Prelude.TypeConstructors.Unit); } case CALTreeParserTokenTypes.LIST_TYPE_CONSTRUCTOR: { return new ClassInstanceIdentifier.TypeConstructorInstance(typeClassName, CAL_Prelude.TypeConstructors.List); } case CALTreeParserTokenTypes.RECORD_TYPE_CONSTRUCTOR: { return new ClassInstanceIdentifier.UniversalRecordInstance(typeClassName); } default: { instanceTypeConsNode.unexpectedParseTreeNode(); return null; } } } /** * Checks the CALDoc comments within the specified instance definition. The checks * include verifying that identifiers contained within the comment are indeed * resolvable to entities within the module and/or other imported modules. * * @param instanceDefnNode the instance definition node whose subtree is to be checked. */ private void checkCALDocCommentsInInstanceDefn(ParseTreeNode instanceDefnNode) { instanceDefnNode.verifyType(CALTreeParserTokenTypes.INSTANCE_DEFN); ParseTreeNode optionalCALDocNode = instanceDefnNode.firstChild(); // check the instance's CALDoc comment. CALDocComment caldoc = checkAndBuildCALDocComment(optionalCALDocNode, null, null, false, false, false); // check the children. ParseTreeNode instanceNameNode = optionalCALDocNode.nextSibling(); instanceNameNode.verifyType(CALTreeParserTokenTypes.INSTANCE_NAME); ParseTreeNode contextListNode = instanceNameNode.firstChild(); contextListNode.verifyType(CALTreeParserTokenTypes.CLASS_CONTEXT_LIST, CALTreeParserTokenTypes.CLASS_CONTEXT_SINGLETON, CALTreeParserTokenTypes.CLASS_CONTEXT_NOTHING); ParseTreeNode typeClassNameNode = contextListNode.nextSibling(); QualifiedName typeClassName = typeClassNameNode.toQualifiedName(); ClassInstanceIdentifier instanceName = buildClassInstanceIdentifier(typeClassNameNode.nextSibling(), typeClassName); ClassInstance instance = currentModuleTypeInfo.getClassInstance(instanceName); // associate the CALDoc with the entity instance.setCALDocComment(caldoc); ParseTreeNode instanceMethodListNode = instanceNameNode.nextSibling(); instanceMethodListNode.verifyType(CALTreeParserTokenTypes.INSTANCE_METHOD_LIST); for (final ParseTreeNode instanceMethodNode : instanceMethodListNode) { instanceMethodNode.verifyType(CALTreeParserTokenTypes.INSTANCE_METHOD); ParseTreeNode optionalInstanceMethodCALDocNode = instanceMethodNode.firstChild(); optionalInstanceMethodCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); ParseTreeNode instanceMethodNameNode = optionalInstanceMethodCALDocNode.nextSibling(); instanceMethodNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); String instanceMethodName = instanceMethodNameNode.getText(); ParseTreeNode instanceMethodDefnNode = instanceMethodNameNode.nextSibling(); instanceMethodDefnNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_VAR); // fetch the entity and then the type expression from the instance method's defining function. FunctionalAgent entity = retrieveQualifiedVar(instanceMethodDefnNode); if (entity == null) { String displayName = getQualifiedNameDisplayString(instanceMethodDefnNode); // TypeChecker: unknown function or variable {displayName}. compiler.logMessage(new CompilerMessage(instanceMethodDefnNode.getChild(1), new MessageKind.Error.UnknownFunctionOrVariable(displayName))); } TypeExpr typeExpr = entity.getTypeExpr(); // check the instance method CALDoc with the defining function's type expression. CALDocComment methodCALDoc = checkAndBuildCALDocComment(optionalInstanceMethodCALDocNode, null, typeExpr, true, true, false); // associate the CALDoc with the entity instance.setMethodCALDocComment(instanceMethodName, methodCALDoc); } } /** * Constructs a CALDocComment.TextBlock from a parse tree node representing a text block * in a CALDoc comment. * * @param blockNode the node representing the text block. * @param inPreformattedContext whether the text is in a preformatted context, and to be dealt with accordingly. * @param summaryCollector the SummaryCollector to use in building up the comment's summary. * @return a new CALDocComment.TextBlock object representing the text block. */ private CALDocComment.TextBlock buildCALDocTextBlock(ParseTreeNode blockNode, boolean inPreformattedContext, CALDocComment.SummaryCollector summaryCollector) throws InvalidCALDocException { blockNode.verifyType(CALTreeParserTokenTypes.CALDOC_TEXT); return new CALDocComment.TextBlock(buildCALDocParagraphs(blockNode, inPreformattedContext, summaryCollector)); } /** * Constructs a CALDocComment.TextBlock from a parse tree node representing a preformatted text segment in a CALDoc comment. * * @param blockNode the ParseTreeNode representing a CALDoc preformatted text segment. * @param summaryCollector the SummaryCollector to use in building up the comment's summary. * @return a new CALDocComment.TextBlock representing the preformatted text segment. */ private CALDocComment.TextBlock buildCALDocPreformattedBlock(ParseTreeNode blockNode, CALDocComment.SummaryCollector summaryCollector) throws InvalidCALDocException { blockNode.verifyType(CALTreeParserTokenTypes.CALDOC_TEXT_PREFORMATTED_BLOCK); return new CALDocComment.TextBlock(buildCALDocParagraphs(blockNode, true, summaryCollector)); } /** * Constructs a CALDocComment.TextParagraph from a parse tree node whose children represent the text segments of the paragraph. * It is an error for the these segments to contain a paragraph break. * * @param parentNodeOfSegments the ParseTreeNode whose children represent text segments in a CALDoc comment. * @param inPreformattedContext whether the text is in a preformatted context, and to be dealt with accordingly. * @param contextTagName the name of the enclosing inline tag segment, for error reporting purposes. * @param summaryCollector the SummaryCollector to use in building up the comment's summary. * @return a new CALDocComment.TextParagraph representing the single text paragraph. */ private CALDocComment.TextParagraph checkAndBuildCALDocSingleTextParagraph(ParseTreeNode parentNodeOfSegments, boolean inPreformattedContext, String contextTagName, CALDocComment.SummaryCollector summaryCollector) throws InvalidCALDocException { List<CALDocComment.Segment> segments = new ArrayList<CALDocComment.Segment>(); //// /// A plain text segment is represented as one or more of CALDOC_TEXT_LINE, CALDOC_BLANK_TEXT_LINE and /// CALDOC_TEXT_LINE_BREAK. We loop through the children of the supplied node aggregating contiguous blocks /// of these nodes into plain text segments. A CALDOC_TEXT_INLINE_BLOCK node represents an inline block and is /// handled as an independent segment. // //// /// In the algorithm below, we trim any excess whitespace and newlines at the start of the paragraph and /// at the start of each line. /// /// We accomplish that by keeping track of whether the paragraph is empty - we trim whitespace /// for as long as nothing has been aggregated into the paragraph. /// /// We skip this step if we're in a pre-formatted context, since whitespace is preserved in that case. // boolean startOfLine = true; StringBuilder plainSegmentBuffer = new StringBuilder(); boolean paragraphIsEmpty = true; loop: for (final ParseTreeNode contentNode : parentNodeOfSegments) { switch (contentNode.getType()) { case CALTreeParserTokenTypes.CALDOC_TEXT_LINE: { String text = contentNode.getText(); if (startOfLine && !inPreformattedContext) { text = CALDocLexer.trimLeadingCALDocWhitespace(text); } plainSegmentBuffer.append(text); if (paragraphIsEmpty && text.length() > 0) { paragraphIsEmpty = false; } startOfLine = false; break; } case CALTreeParserTokenTypes.CALDOC_BLANK_TEXT_LINE: { String text = contentNode.getText(); if (!inPreformattedContext) { if (startOfLine) { text = ""; } else { text = " "; } } plainSegmentBuffer.append(text); if (paragraphIsEmpty && text.length() > 0) { paragraphIsEmpty = false; } // we don't change start-of-line status here break; } case CALTreeParserTokenTypes.CALDOC_TEXT_LINE_BREAK: { // process newlines only if there's some text in the buffer already - we ignore initial newlines in a paragraph if (!paragraphIsEmpty) { if (startOfLine) { // we just saw a newline before, so this is a second one.... that's a paragraph break! compiler.logMessage(new CompilerMessage(contentNode, new MessageKind.Error.ParagraphBreakCannotAppearHereInsideCALDocCommentInlineTag(contextTagName))); break loop; } else { plainSegmentBuffer.append('\n'); startOfLine = true; } } break; } case CALTreeParserTokenTypes.CALDOC_TEXT_INLINE_BLOCK: { // process the buffered up plain text first if (plainSegmentBuffer.length() > 0) { // convert the plain text buffer into a segment and add it to the list CALDocComment.PlainTextSegment plainTextSegment = CALDocComment.PlainTextSegment.makeWithEscapedText(plainSegmentBuffer.toString()); segments.add(plainTextSegment); // reset the buffer afterwards for future use plainSegmentBuffer = new StringBuilder(); } // then process the inline tag segment ParseTreeNode inlineBlockNode = contentNode; ParseTreeNode inlineTagNode = inlineBlockNode.firstChild(); int inlineTagNodeType = inlineTagNode.getType(); if (inlineTagNodeType == CALTreeParserTokenTypes.CALDOC_TEXT_ORDERED_LIST || inlineTagNodeType == CALTreeParserTokenTypes.CALDOC_TEXT_UNORDERED_LIST) { // this is an implicit paragraph break! not allowed! String badTagName; if (inlineTagNodeType == CALTreeParserTokenTypes.CALDOC_TEXT_ORDERED_LIST) { badTagName = "{@orderedList}"; } else { badTagName = "{@unorderedList}"; } compiler.logMessage(new CompilerMessage(contentNode, new MessageKind.Error.ThisParticularInlineTagCannotAppearHereInsideCALDocCommentInlineTag(badTagName, contextTagName))); } else { addCALDocInlineTagSegmentToList(contentNode, inPreformattedContext, segments, summaryCollector); } paragraphIsEmpty = false; startOfLine = false; break; } default: throw new IllegalStateException("Unexpected parse tree node " + contentNode.toDebugString() + "."); } } // one final flush of the plain text buffer into a segment if (plainSegmentBuffer.length() > 0) { CALDocComment.PlainTextSegment plainTextSegment = CALDocComment.PlainTextSegment.makeWithEscapedText(plainSegmentBuffer.toString()); segments.add(plainTextSegment); } return new CALDocComment.TextParagraph(segments.toArray(CALDocComment.TextParagraph.NO_SEGMENTS)); } /** * Constructs a string from a parse tree node whose children represent the text segments of a single text paragraph without * inline tags. It is an error for the these segments to contain a paragraph break or inline tag segments. * * @param parentNodeOfSegments the ParseTreeNode whose children represent text segments in a CALDoc comment. * @param inPreformattedContext whether the text is in a preformatted context, and to be dealt with accordingly. * @param contextTagName the name of the enclosing inline tag segment, for error reporting purposes. * @return a string representing a single text paragraph without inline tags. */ private String checkAndBuildCALDocSingleTextParagraphWithoutInlineTags(ParseTreeNode parentNodeOfSegments, boolean inPreformattedContext, String contextTagName) { //// /// A plain text segment is represented as one or more of CALDOC_TEXT_LINE, CALDOC_BLANK_TEXT_LINE and /// CALDOC_TEXT_LINE_BREAK. We loop through the children of the supplied node aggregating contiguous blocks /// of these nodes into plain text segments. A CALDOC_TEXT_INLINE_BLOCK node represents an inline block and is /// not allowed in a paragraph without inline tags. Such a node appearing would result in a compiler error. // //// /// In the algorithm below, we trim any excess whitespace and newlines at the start of the paragraph and /// at the start of each line. /// /// We accomplish that by keeping track of whether the paragraph is empty - we trim whitespace /// for as long as nothing has been aggregated into the paragraph. /// /// We skip this step if we're in a preformatted context, since whitespace is preserved in that case. // boolean startOfLine = true; StringBuilder plainSegmentBuffer = new StringBuilder(); boolean paragraphIsEmpty = true; loop: for (final ParseTreeNode contentNode : parentNodeOfSegments) { switch (contentNode.getType()) { case CALTreeParserTokenTypes.CALDOC_TEXT_LINE: { String text = contentNode.getText(); if (startOfLine && !inPreformattedContext) { text = CALDocLexer.trimLeadingCALDocWhitespace(text); } plainSegmentBuffer.append(text); if (paragraphIsEmpty && text.length() > 0) { paragraphIsEmpty = false; } startOfLine = false; break; } case CALTreeParserTokenTypes.CALDOC_BLANK_TEXT_LINE: { String text = contentNode.getText(); if (!inPreformattedContext) { if (startOfLine) { text = ""; } else { text = " "; } } plainSegmentBuffer.append(text); if (paragraphIsEmpty && text.length() > 0) { paragraphIsEmpty = false; } // we don't change start-of-line status here break; } case CALTreeParserTokenTypes.CALDOC_TEXT_LINE_BREAK: { // process newlines only if there's some text in the buffer already - we ignore initial newlines in a paragraph if (!paragraphIsEmpty) { if (startOfLine) { // we just saw a newline before, so this is a second one.... that's a paragraph break! compiler.logMessage(new CompilerMessage(contentNode, new MessageKind.Error.ParagraphBreakCannotAppearHereInsideCALDocCommentInlineTag(contextTagName))); break loop; } else { plainSegmentBuffer.append('\n'); startOfLine = true; } } break; } case CALTreeParserTokenTypes.CALDOC_TEXT_INLINE_BLOCK: { compiler.logMessage(new CompilerMessage(contentNode, new MessageKind.Error.InlineTagCannotAppearHereInsideCALDocCommentInlineTag(contextTagName))); break loop; } default: throw new IllegalStateException("Unexpected parse tree node " + contentNode.toDebugString() + "."); } } // one final flush of the plain text buffer into a segment return plainSegmentBuffer.toString(); } /** * Constructs a string from a parse tree node representing a single-paragraph text block without * inline tags, of the type CALDOC_TEXT_BLOCK_WITHOUT_INLINE_TAGS. * It is an error for the the segments therein to contain a paragraph break or inline tag segments. * * @param blockNode the node representing the text block of the type CALDOC_TEXT_BLOCK_WITHOUT_INLINE_TAGS. * @param inPreformattedContext whether the text is in a preformatted context, and to be dealt with accordingly. * @param contextTagName the name of the enclosing inline tag segment, for error reporting purposes. * @return a string representing a single text paragraph without inline tags. */ private String checkAndBuildCALDocSingleParagraphTextBlockWithoutInlineTags(ParseTreeNode blockNode, boolean inPreformattedContext, String contextTagName) { blockNode.verifyType(CALTreeParserTokenTypes.CALDOC_TEXT_BLOCK_WITHOUT_INLINE_TAGS); return checkAndBuildCALDocSingleTextParagraphWithoutInlineTags(blockNode, inPreformattedContext, contextTagName); } /** * Constructs a CALDocComment.TextParagraph from a parse tree node representing a text block * in a preformatted context in a CALDoc comment. Paragraph breaks are treated simply as newlines, and * so the result is always just one paragraph. * * @param parentNodeOfSegments the ParseTreeNode whose children represent text segments in a CALDoc comment. * @param summaryCollector the SummaryCollector to use in building up the comment's summary. * @return a new CALDocComment.TextParagraph representing the single preformatted paragraph. */ private CALDocComment.TextParagraph buildCALDocTextBlockInPreformattedContext(ParseTreeNode parentNodeOfSegments, CALDocComment.SummaryCollector summaryCollector) throws InvalidCALDocException { List<CALDocComment.Segment> segments = new ArrayList<CALDocComment.Segment>(); //// /// A plain text segment is represented as one or more of CALDOC_TEXT_LINE, CALDOC_BLANK_TEXT_LINE and /// CALDOC_TEXT_LINE_BREAK. We loop through the children of the supplied node aggregating contiguous blocks /// of these nodes into plain text segments. A CALDOC_TEXT_INLINE_BLOCK node represents an inline block and is /// handled as an independent segment. // //// /// In the algorithm below, we preserve all whitespace as is. // StringBuilder plainSegmentBuffer = new StringBuilder(); for (final ParseTreeNode contentNode : parentNodeOfSegments) { switch (contentNode.getType()) { case CALTreeParserTokenTypes.CALDOC_TEXT_LINE: case CALTreeParserTokenTypes.CALDOC_BLANK_TEXT_LINE: { String text = contentNode.getText(); plainSegmentBuffer.append(text); break; } case CALTreeParserTokenTypes.CALDOC_TEXT_LINE_BREAK: { plainSegmentBuffer.append('\n'); break; } case CALTreeParserTokenTypes.CALDOC_TEXT_INLINE_BLOCK: { // process the buffered up plain text first if (plainSegmentBuffer.length() > 0) { // convert the plain text buffer into a segment and add it to the list CALDocComment.PlainTextSegment plainTextSegment = CALDocComment.PlainTextSegment.makeWithEscapedText(plainSegmentBuffer.toString()); segments.add(plainTextSegment); // reset the buffer afterwards for future use plainSegmentBuffer = new StringBuilder(); } // then process the inline tag segment addCALDocInlineTagSegmentToList(contentNode, true, segments, summaryCollector); break; } default: throw new IllegalStateException("Unexpected parse tree node " + contentNode.toDebugString() + "."); } } // one final flush of the plain text buffer into a segment if (plainSegmentBuffer.length() > 0) { CALDocComment.PlainTextSegment plainTextSegment = CALDocComment.PlainTextSegment.makeWithEscapedText(plainSegmentBuffer.toString()); segments.add(plainTextSegment); } return new CALDocComment.TextParagraph(segments.toArray(CALDocComment.TextParagraph.NO_SEGMENTS)); } /** * Constructs an array of CALDocComment.Paragraph from a parse tree node whose children represent text segments * in a CALDoc comment. The segments are processed and aggregated into paragraphs. * * @param parentNodeOfSegments the ParseTreeNode whose children represent text segments in a CALDoc comment. * @param inPreformattedContext whether the text is in a preformatted context, and to be dealt with accordingly. * @param summaryCollector the SummaryCollector to use in building up the comment's summary. * @return an array of CALDocComment.Paragraph representing the paragraphs formed from the segments. */ private CALDocComment.Paragraph[] buildCALDocParagraphs(ParseTreeNode parentNodeOfSegments, boolean inPreformattedContext, CALDocComment.SummaryCollector summaryCollector) throws InvalidCALDocException { /// If the text is in a preformatted context, then we delegate to the method for constructing preformatted text blocks, /// and create a one-element array containing the resulting paragraph. // if (inPreformattedContext) { return new CALDocComment.Paragraph[] { buildCALDocTextBlockInPreformattedContext(parentNodeOfSegments, summaryCollector) }; } else { /// Keep track of the paragraphs aggregated so far. // List<CALDocComment.Paragraph> paragraphs = new ArrayList<CALDocComment.Paragraph>(); /// Keep track of the segments forming the current paragraph being built. // List<CALDocComment.Segment> segments = new ArrayList<CALDocComment.Segment>(); //// /// A plain text segment is represented as one or more of CALDOC_TEXT_LINE, CALDOC_BLANK_TEXT_LINE and /// CALDOC_TEXT_LINE_BREAK. We loop through the children of the supplied node aggregating contiguous blocks /// of these nodes into plain text segments. A CALDOC_TEXT_INLINE_BLOCK node represents an inline block and is /// handled as an independent segment. // //// /// In the algorithm below, we trim any excess whitespace and newlines at the start of the paragraph and /// at the start of each line. /// /// We accomplish that by keeping track of whether the paragraph is empty - we trim whitespace /// for as long as nothing has been aggregated into the paragraph. /// /// We skip this step if we're in a preformatted context, since whitespace is preserved in that case. // //// /// We perform paragraph-breaking in the logic below. We treat a sequence of 2 or more newlines, potentially /// separated by whitespace, as a single paragraph break. We also treat {@unorderedList} and {@orderedList} /// as demarcations of new paragraphs (because lists are to be presented as separate from the surrounding text). // boolean startOfLine = true; StringBuilder plainSegmentBuffer = new StringBuilder(); boolean paragraphIsEmpty = true; for (final ParseTreeNode contentNode : parentNodeOfSegments) { switch (contentNode.getType()) { case CALTreeParserTokenTypes.CALDOC_TEXT_LINE: { String text = contentNode.getText(); if (startOfLine && !inPreformattedContext) { text = CALDocLexer.trimLeadingCALDocWhitespace(text); } plainSegmentBuffer.append(text); if (paragraphIsEmpty && text.length() > 0) { paragraphIsEmpty = false; } startOfLine = false; break; } case CALTreeParserTokenTypes.CALDOC_BLANK_TEXT_LINE: { String text = contentNode.getText(); if (!inPreformattedContext) { if (startOfLine) { text = ""; } else { text = " "; } } plainSegmentBuffer.append(text); if (paragraphIsEmpty && text.length() > 0) { paragraphIsEmpty = false; } // we don't change start-of-line status here break; } case CALTreeParserTokenTypes.CALDOC_TEXT_LINE_BREAK: { if (startOfLine) { // we just saw a newline before, so this is a second one.... that's a paragraph break! // one final flush of the plain text buffer into a segment if (plainSegmentBuffer.length() > 0) { CALDocComment.PlainTextSegment plainTextSegment = CALDocComment.PlainTextSegment.makeWithEscapedText(plainSegmentBuffer.toString()); segments.add(plainTextSegment); } plainSegmentBuffer = new StringBuilder(); if (segments.size() > 0) { CALDocComment.TextParagraph textParagraph = new CALDocComment.TextParagraph(segments.toArray(CALDocComment.TextParagraph.NO_SEGMENTS)); paragraphs.add(textParagraph); } segments = new ArrayList<CALDocComment.Segment>(); paragraphIsEmpty = true; } else { // insert newlines only if there's some text in the buffer already - we ignore initial newlines in a paragraph if (!paragraphIsEmpty) { plainSegmentBuffer.append('\n'); } startOfLine = true; } break; } case CALTreeParserTokenTypes.CALDOC_TEXT_INLINE_BLOCK: { // process the buffered up plain text first if (plainSegmentBuffer.length() > 0) { // convert the plain text buffer into a segment and add it to the list CALDocComment.PlainTextSegment plainTextSegment = CALDocComment.PlainTextSegment.makeWithEscapedText(plainSegmentBuffer.toString()); segments.add(plainTextSegment); // reset the buffer afterwards for future use plainSegmentBuffer = new StringBuilder(); } // then process the inline tag segment ParseTreeNode inlineBlockNode = contentNode; ParseTreeNode inlineTagNode = inlineBlockNode.firstChild(); switch (inlineTagNode.getType()) { case CALTreeParserTokenTypes.CALDOC_TEXT_ORDERED_LIST: case CALTreeParserTokenTypes.CALDOC_TEXT_UNORDERED_LIST: { // this is an implicit paragraph break! // one final flush of the plain text buffer into a segment if (plainSegmentBuffer.length() > 0) { CALDocComment.PlainTextSegment plainTextSegment = CALDocComment.PlainTextSegment.makeWithEscapedText(plainSegmentBuffer.toString()); segments.add(plainTextSegment); } plainSegmentBuffer = new StringBuilder(); if (segments.size() > 0) { CALDocComment.TextParagraph textParagraph = new CALDocComment.TextParagraph(segments.toArray(CALDocComment.TextParagraph.NO_SEGMENTS)); paragraphs.add(textParagraph); } segments = new ArrayList<CALDocComment.Segment>(); paragraphIsEmpty = true; // now process the list paragraph paragraphs.add(buildCALDocInlineListTagSegment(inlineTagNode, summaryCollector)); break; } default: { addCALDocInlineTagSegmentToList(contentNode, inPreformattedContext, segments, summaryCollector); paragraphIsEmpty = false; break; } } startOfLine = false; break; } default: throw new IllegalStateException("Unexpected parse tree node " + contentNode.toDebugString() + "."); } } // one final flush of the last paragraph if (plainSegmentBuffer.length() > 0) { CALDocComment.PlainTextSegment plainTextSegment = CALDocComment.PlainTextSegment.makeWithEscapedText(plainSegmentBuffer.toString()); segments.add(plainTextSegment); } if (segments.size() > 0) { CALDocComment.TextParagraph textParagraph = new CALDocComment.TextParagraph(segments.toArray(CALDocComment.TextParagraph.NO_SEGMENTS)); paragraphs.add(textParagraph); } return paragraphs.toArray(CALDocComment.TextBlock.NO_PARAGRAPHS); } } /** * Creates a CALDocComment.Segment from a parse tree node representing an inline tag segment, and adds it to the given list. * * @param inlineBlockNode the ParseTreeNode representing an inline tag segment in a CALDoc comment. * @param inPreformattedContext whether the text is in a preformatted context, and to be dealt with accordingly. * @param segmentsList the list to which the new CALDocComment.Segment is to be added. * @param summaryCollector the SummaryCollector to use in building up the comment's summary. */ private void addCALDocInlineTagSegmentToList(ParseTreeNode inlineBlockNode, boolean inPreformattedContext, List<CALDocComment.Segment> segmentsList, CALDocComment.SummaryCollector summaryCollector) throws InvalidCALDocException { inlineBlockNode.verifyType(CALTreeParserTokenTypes.CALDOC_TEXT_INLINE_BLOCK); ParseTreeNode inlineTagNode = inlineBlockNode.firstChild(); switch (inlineTagNode.getType()) { case CALTreeParserTokenTypes.CALDOC_TEXT_URL: segmentsList.add(buildCALDocInlineURLTagSegment(inlineTagNode)); return; case CALTreeParserTokenTypes.CALDOC_TEXT_LINK: segmentsList.add(buildCALDocInlineLinkTagSegment(inlineTagNode)); return; case CALTreeParserTokenTypes.CALDOC_TEXT_EMPHASIZED_TEXT: segmentsList.add(buildCALDocInlineEmTagSegment(inlineTagNode, inPreformattedContext, summaryCollector)); return; case CALTreeParserTokenTypes.CALDOC_TEXT_STRONGLY_EMPHASIZED_TEXT: segmentsList.add(buildCALDocInlineStrongTagSegment(inlineTagNode, inPreformattedContext, summaryCollector)); return; case CALTreeParserTokenTypes.CALDOC_TEXT_SUPERSCRIPT_TEXT: segmentsList.add(buildCALDocInlineSupTagSegment(inlineTagNode, inPreformattedContext, summaryCollector)); return; case CALTreeParserTokenTypes.CALDOC_TEXT_SUBSCRIPT_TEXT: segmentsList.add(buildCALDocInlineSubTagSegment(inlineTagNode, inPreformattedContext, summaryCollector)); return; case CALTreeParserTokenTypes.CALDOC_TEXT_SUMMARY: addCALDocInlineSummaryTagSegmentToList(inlineTagNode, inPreformattedContext, segmentsList, summaryCollector); return; case CALTreeParserTokenTypes.CALDOC_TEXT_CODE_BLOCK: segmentsList.add(buildCALDocInlineCodeTagSegment(inlineTagNode, summaryCollector)); return; default: throw new IllegalStateException("Unexpected parse tree node " + inlineTagNode.toDebugString() + "."); } } /** * Constructs a CALDocComment.URLSegment from a parse tree node representing a hyperlinkable URL in a CALDoc comment. * @param urlNode the ParseTreeNode representing a hyperlinkable URL in a CALDoc comment. * @return a new CALDocComment.URLSegment object representing the URL. */ private CALDocComment.URLSegment buildCALDocInlineURLTagSegment(ParseTreeNode urlNode) { urlNode.verifyType(CALTreeParserTokenTypes.CALDOC_TEXT_URL); ParseTreeNode contentNode = urlNode.firstChild(); String content = checkAndBuildCALDocSingleParagraphTextBlockWithoutInlineTags(contentNode, false, "{@url}"); return CALDocComment.URLSegment.makeWithEscapedText(content); } /** * Constructs a CALDocComment.LinkSegment from a parse tree node representing an inline cross-reference in a CALDoc comment. * @param linkNode the ParseTreeNode representing an inline cross-reference in a CALDoc comment. * @return a new CALDocComment.LinkSegment object representing the inline cross-reference. */ private CALDocComment.LinkSegment buildCALDocInlineLinkTagSegment(ParseTreeNode linkNode) throws InvalidCALDocException { linkNode.verifyType(CALTreeParserTokenTypes.CALDOC_TEXT_LINK); // first make sure that function names and data constructor names are resolved and fully qualified // (for the case where the {@link} segment lacks the 'context' keyword, attempt to resolve it as a // data constructor and store the result of the resolution as an attribute on the refNode, i.e. linktNode's first grandchild) finder.findFreeVariablesInCALDocInlineLinkTagSegment(linkNode); ParseTreeNode linkContextNode = linkNode.firstChild(); switch (linkContextNode.getType()) { case CALTreeParserTokenTypes.CALDOC_TEXT_LINK_FUNCTION: return new CALDocComment.FunctionOrClassMethodLinkSegment(makeCALDocFunctionOrClassMethodCrossReference(linkContextNode.firstChild())); case CALTreeParserTokenTypes.CALDOC_TEXT_LINK_MODULE: return new CALDocComment.ModuleLinkSegment(makeCALDocModuleCrossReference(linkContextNode.firstChild())); case CALTreeParserTokenTypes.CALDOC_TEXT_LINK_DATACONS: return new CALDocComment.DataConsLinkSegment(makeCALDocDataConsCrossReference(linkContextNode.firstChild())); case CALTreeParserTokenTypes.CALDOC_TEXT_LINK_TYPECONS: return new CALDocComment.TypeConsLinkSegment(makeCALDocTypeConsCrossReference(linkContextNode.firstChild())); case CALTreeParserTokenTypes.CALDOC_TEXT_LINK_TYPECLASS: return new CALDocComment.TypeClassLinkSegment(makeCALDocTypeClassCrossReference(linkContextNode.firstChild())); case CALTreeParserTokenTypes.CALDOC_TEXT_LINK_WITHOUT_CONTEXT: return buildCALDocInlineLinkTagWithoutContextSegment(linkContextNode.firstChild()); default: throw new IllegalStateException("Unexpected parse tree node " + linkNode.toDebugString() + "."); } } /** * Constructs a CALDocComment.LinkSegment from a parse tree node representing a cross-reference appearing without * a 'context' keyword in a CALDoc comment. * @param refNode the ParseTreeNode representing a cross-reference in a CALDoc comment. * @return a new CALDocComment.LinkSegment object representing the inline cross-reference. */ private CALDocComment.LinkSegment buildCALDocInlineLinkTagWithoutContextSegment(ParseTreeNode refNode) throws InvalidCALDocException { switch (refNode.getType()) { case CALTreeParserTokenTypes.CALDOC_UNCHECKED_QUALIFIED_VAR: case CALTreeParserTokenTypes.CALDOC_CHECKED_QUALIFIED_VAR: { return new CALDocComment.FunctionOrClassMethodLinkSegment(makeCALDocFunctionOrClassMethodCrossReference(refNode)); } case CALTreeParserTokenTypes.CALDOC_UNCHECKED_QUALIFIED_CONS: case CALTreeParserTokenTypes.CALDOC_CHECKED_QUALIFIED_CONS: return buildCALDocInlineLinkTagConsNameWithoutContextSegment(refNode); default: throw new IllegalStateException("Unexpected parse tree node " + refNode.toDebugString() + "."); } } /** * Constructs a CALDocComment.LinkSegment from a parse tree node representing a cross-reference containing * an identifier starting with an uppercase character and appearing without a 'context' keyword in a CALDoc comment. * @param refNode the ParseTreeNode representing a cross-reference in a CALDoc comment. * @return a new CALDocComment.LinkSegment object representing the inline cross-reference. */ private CALDocComment.LinkSegment buildCALDocInlineLinkTagConsNameWithoutContextSegment(ParseTreeNode refNode) throws InvalidCALDocException { Pair<CategoryForCALDocConsNameWithoutContextCrossReference, CALDocComment.Reference> pair = buildCALDocConsNameWithoutContextCrossReference(refNode, true); CategoryForCALDocConsNameWithoutContextCrossReference category = pair.fst(); CALDocComment.Reference reference = pair.snd(); if (category == CategoryForCALDocConsNameWithoutContextCrossReference.MODULE_NAME) { return new CALDocComment.ModuleLinkSegment((CALDocComment.ModuleReference)reference); } else if (category == CategoryForCALDocConsNameWithoutContextCrossReference.DATA_CONS_NAME) { return new CALDocComment.DataConsLinkSegment((CALDocComment.ScopedEntityReference)reference); } else if (category == CategoryForCALDocConsNameWithoutContextCrossReference.TYPE_CONS_NAME) { return new CALDocComment.TypeConsLinkSegment((CALDocComment.ScopedEntityReference)reference); } else if (category == CategoryForCALDocConsNameWithoutContextCrossReference.TYPE_CLASS_NAME) { return new CALDocComment.TypeClassLinkSegment((CALDocComment.ScopedEntityReference)reference); } else { throw new IllegalStateException(); } } /* * todo-jowong: look into refactoring the following name resolution code. */ /** * Constructs a Pair (CategoryForCALDocConsNameWithoutContextCrossReference, CALDocComment.Reference) from a parse * tree node representing a cross-reference containing an identifier starting with an uppercase character and * appearing without a 'context' keyword in a CALDoc comment. * * The CategoryForCALDocConsNameWithoutContextCrossReference is the category which the reference resolves to. * * The CALDocComment.Reference is the actual object representing the cross-reference. * * @param refNode the ParseTreeNode representing a cross-reference in a CALDoc comment. * @param isLinkBlock true if this reference is in a "@link" block, false if this reference is in an "@see" block. * @return a Pair (CategoryForCALDocConsNameWithoutContextCrossReference, CALDocComment.Reference) */ private Pair<CategoryForCALDocConsNameWithoutContextCrossReference, CALDocComment.Reference> buildCALDocConsNameWithoutContextCrossReference(ParseTreeNode refNode, boolean isLinkBlock) throws InvalidCALDocException { refNode.verifyType(CALTreeParserTokenTypes.CALDOC_UNCHECKED_QUALIFIED_CONS, CALTreeParserTokenTypes.CALDOC_CHECKED_QUALIFIED_CONS); boolean isUnchecked = (refNode.getType() == CALTreeParserTokenTypes.CALDOC_UNCHECKED_QUALIFIED_CONS); ParseTreeNode qualifiedConsNode = refNode.firstChild(); qualifiedConsNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS); ParseTreeNode nameBeforeDotNode = qualifiedConsNode.firstChild(); nameBeforeDotNode.verifyType(CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME, CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME_EMPTY_QUALIFIER); String nameBeforeDot = ModuleNameUtilities.getMaybeModuleNameStringFromParseTree(nameBeforeDotNode); boolean isUnqualified = (nameBeforeDot.length() == 0); ParseTreeNode nameAfterDotNode = nameBeforeDotNode.nextSibling(); nameAfterDotNode.verifyType(CALTreeParserTokenTypes.CONS_ID); String nameAfterDot = nameAfterDotNode.getText(); String refName; if (isUnqualified) { refName = nameAfterDot; } else { refName = nameBeforeDot + "." + nameAfterDot; } /* * We simply do not handle unchecked references without context, e.g. {@link "Prelude"@} * * We make this restriction because * - If the quoted reference is already resolvable, there's no need for the quotes. * * Furthermore, if the module defining the referenced entity changes (e.g. additions are made), * the reference may then resolve to more than one entity... (because quoted references can * refer to private entries) * * **This would be a compile error if the modules were compiled together, but would have * escaped detection if the change was compiled after the reference is compiled. Therefore * all references of this kind must be disallowed in the first place.** * * - If the quoted reference is not resolvable, then we need more context to be able to tell * what the reference is supposed to refer to. * * - If the quoted reference is resolvable to more than one entity, then it is ambiguous. * * * In short: visibility of entities are non-local for quoted references... therefore all such * references must be completely unambiguous and must not require any resolution. * Hence they must appear with a context keyword. */ if (isUnchecked) { compiler.logMessage( new CompilerMessage( refNode, new MessageKind.Error.UncheckedCrossReferenceIsAmbiguousInCALDocComment(refName))); throw new InvalidCALDocException(); } /// Check to see if the name can be resolved as a module name (could be unqualified like M or qualified like K.L.M). // ModuleName resolvedAsModuleNameOrNull = null; ModuleNameResolver.ResolutionResult moduleNameResolution = currentModuleTypeInfo.getModuleNameResolver().resolve(ModuleName.make(refName)); if (moduleNameResolution.isKnownUnambiguous()) { resolvedAsModuleNameOrNull = moduleNameResolution.getResolvedModuleName(); } /// Check to see if the name can be resolved as a data constructor name. We count on the FreeVariableFinder /// to have done the resolution already. So we simply fetch the result it obtained via the custom attribute /// on the refNode). // QualifiedName resolvedAsDataConsNameOrNull = refNode.getCALDocCrossReferenceResolvedAsDataConstructor(); final boolean suppressErrorMessageLogging = true; /// Check to see if the name can be resolved as a type constructor name. We perform the resolution attempt here. // ParseTreeNode copyOfQualifiedConsForTypeConsResolution = qualifiedConsNode.copyParseTree(); TypeConstructor resolvedAsTypeConsOrNull = DataDeclarationChecker.resolveTypeConsName(copyOfQualifiedConsForTypeConsResolution, currentModuleTypeInfo, compiler, suppressErrorMessageLogging); QualifiedName resolvedAsTypeConsNameOrNull = (resolvedAsTypeConsOrNull == null) ? null : resolvedAsTypeConsOrNull.getName(); /// Check to see if the name can be resolved as a type class name. We perform the resolution attempt here. // ParseTreeNode copyOfQualifiedConsNodeForTypeClassResolution = qualifiedConsNode.copyParseTree(); TypeClass resolvedAsTypeClassOrNull = TypeClassChecker.resolveClassName(copyOfQualifiedConsNodeForTypeClassResolution, currentModuleTypeInfo, compiler, suppressErrorMessageLogging); QualifiedName resolvedAsTypeClassNameOrNull = (resolvedAsTypeClassOrNull == null) ? null : resolvedAsTypeClassOrNull.getName(); // If the reference can be resolved as more than one kind of entity, then it does not matter // whether the reference is a checked or unchecked reference - it is an error because it is ambiguous. int numWaysToResolveName = 0; if (resolvedAsModuleNameOrNull != null) { numWaysToResolveName++; } if (resolvedAsTypeClassNameOrNull != null) { numWaysToResolveName++; } if (resolvedAsTypeConsNameOrNull != null) { numWaysToResolveName++; } if (resolvedAsDataConsNameOrNull != null) { numWaysToResolveName++; } if (numWaysToResolveName > 1) { /// It is a compile-time error for the name to be resolvable to more than one entity. // // Construct the error message and log it. List<String> alternativesList = new ArrayList<String>(); if (resolvedAsModuleNameOrNull != null) { alternativesList.add(makeDisambiguatedCALDocCrossReferenceSourceText(refName, "module", isUnchecked, isLinkBlock)); } if (resolvedAsTypeClassNameOrNull != null) { alternativesList.add(makeDisambiguatedCALDocCrossReferenceSourceText(refName, "typeClass", isUnchecked, isLinkBlock)); } if (resolvedAsTypeConsNameOrNull != null) { alternativesList.add(makeDisambiguatedCALDocCrossReferenceSourceText(refName, "typeConstructor", isUnchecked, isLinkBlock)); } if (resolvedAsDataConsNameOrNull != null) { alternativesList.add(makeDisambiguatedCALDocCrossReferenceSourceText(refName, "dataConstructor", isUnchecked, isLinkBlock)); } String[] alternatives = alternativesList.toArray(new String[0]); compiler.logMessage( new CompilerMessage( refNode, new MessageKind.Error.CrossReferenceCanBeResolvedToMoreThanOneEntityInCALDocComment(refName, alternatives))); throw new InvalidCALDocException(); } else if (numWaysToResolveName == 1) { /// This is the success case, where the name is resolvable to exactly one entity. // if (resolvedAsModuleNameOrNull != null) { // now that we have actually resolved the reference, run the deprecation check on it. SourceRange sourceRange = CALCompiler.getSourceRangeForCompilerMessage(qualifiedConsNode, currentModuleTypeInfo.getModuleName()); compiler.checkResolvedModuleReference(resolvedAsModuleNameOrNull, sourceRange); ModuleNameUtilities.setModuleNameIntoParseTree(nameBeforeDotNode, resolvedAsModuleNameOrNull); CategoryForCALDocConsNameWithoutContextCrossReference category = CategoryForCALDocConsNameWithoutContextCrossReference.MODULE_NAME; refNode.setCategoryForCALDocConsNameWithoutContextCrossReference(category); return new Pair<CategoryForCALDocConsNameWithoutContextCrossReference, CALDocComment.Reference>(category, new CALDocComment.ModuleReference(resolvedAsModuleNameOrNull, isUnchecked, refName)); } else if (resolvedAsDataConsNameOrNull != null) { // now that we have actually resolved the reference, run the deprecation check on it. FreeVariableFinder.checkResolvedDataConsReference(compiler, qualifiedConsNode, resolvedAsDataConsNameOrNull, false); ModuleNameUtilities.setModuleNameIntoParseTree(nameBeforeDotNode, resolvedAsDataConsNameOrNull.getModuleName()); CategoryForCALDocConsNameWithoutContextCrossReference category = CategoryForCALDocConsNameWithoutContextCrossReference.DATA_CONS_NAME; refNode.setCategoryForCALDocConsNameWithoutContextCrossReference(category); return new Pair<CategoryForCALDocConsNameWithoutContextCrossReference, CALDocComment.Reference>(category, new CALDocComment.ScopedEntityReference(resolvedAsDataConsNameOrNull, isUnchecked, nameBeforeDot)); } else if (resolvedAsTypeConsNameOrNull != null) { // now that we have actually resolved the reference, run the deprecation check on it. DataDeclarationChecker.checkResolvedTypeConsReference(compiler, qualifiedConsNode, resolvedAsTypeConsNameOrNull, currentModuleTypeInfo.getModuleName(), false); ModuleNameUtilities.setModuleNameIntoParseTree(nameBeforeDotNode, resolvedAsTypeConsNameOrNull.getModuleName()); CategoryForCALDocConsNameWithoutContextCrossReference category = CategoryForCALDocConsNameWithoutContextCrossReference.TYPE_CONS_NAME; refNode.setCategoryForCALDocConsNameWithoutContextCrossReference(category); return new Pair<CategoryForCALDocConsNameWithoutContextCrossReference, CALDocComment.Reference>(category, new CALDocComment.ScopedEntityReference(resolvedAsTypeConsNameOrNull, isUnchecked, nameBeforeDot)); } else if (resolvedAsTypeClassNameOrNull != null) { // now that we have actually resolved the reference, run the deprecation check on it. TypeClassChecker.checkResolvedTypeClassReference(compiler, qualifiedConsNode, resolvedAsTypeClassNameOrNull, false); ModuleNameUtilities.setModuleNameIntoParseTree(nameBeforeDotNode, resolvedAsTypeClassNameOrNull.getModuleName()); CategoryForCALDocConsNameWithoutContextCrossReference category = CategoryForCALDocConsNameWithoutContextCrossReference.TYPE_CLASS_NAME; refNode.setCategoryForCALDocConsNameWithoutContextCrossReference(category); return new Pair<CategoryForCALDocConsNameWithoutContextCrossReference, CALDocComment.Reference>(category, new CALDocComment.ScopedEntityReference(resolvedAsTypeClassNameOrNull, isUnchecked, nameBeforeDot)); } else { throw new IllegalStateException(); } } else { /// The reference cannot be resolved as any visible module/type constructor/data constructor/type class. /// This is a compile time error. // if (isUnqualified) { //// /// Check to see if there are any alternatives had the unqualified name been fully qualified. // SortedSet<QualifiedName> candidateQualifiedNames = new TreeSet<QualifiedName>(); int nImportedModules = currentModuleTypeInfo.getNImportedModules(); for (int i = 0; i < nImportedModules; ++i) { ModuleTypeInfo importedModule = currentModuleTypeInfo.getNthImportedModule(i); TypeConstructor typeConsCandidate = importedModule.getTypeConstructor(nameAfterDot); if (typeConsCandidate != null && currentModuleTypeInfo.isEntityVisible(typeConsCandidate)) { candidateQualifiedNames.add(typeConsCandidate.getName()); } DataConstructor dataConsCandidate = importedModule.getDataConstructor(nameAfterDot); if (dataConsCandidate != null && currentModuleTypeInfo.isEntityVisible(dataConsCandidate)) { candidateQualifiedNames.add(dataConsCandidate.getName()); } TypeClass typeClassCandidate = importedModule.getTypeClass(nameAfterDot); if (typeClassCandidate != null && currentModuleTypeInfo.isEntityVisible(typeClassCandidate)) { candidateQualifiedNames.add(typeClassCandidate.getName()); } } int nCandidates = candidateQualifiedNames.size(); /// We choose an error message to use depending on the number of candidates we were able to identify. // if (nCandidates == 0) { compiler.logMessage( new CompilerMessage( refNode, new MessageKind.Error.CheckedCrossReferenceCannotBeResolvedInCALDocComment(nameAfterDot))); } else if (nCandidates == 1) { QualifiedName candidate = candidateQualifiedNames.first(); compiler.logMessage( new CompilerMessage( refNode, new MessageKind.Error.CheckedCrossReferenceCannotBeResolvedInCALDocCommentWithSingleSuggestion(nameAfterDot, candidate))); } else { compiler.logMessage( new CompilerMessage( refNode, new MessageKind.Error.CheckedCrossReferenceCannotBeResolvedInCALDocCommentWithMultipleSuggestions( nameAfterDot, candidateQualifiedNames.toArray(new QualifiedName[nCandidates])))); } } else { compiler.logMessage( new CompilerMessage( refNode, new MessageKind.Error.CheckedCrossReferenceCannotBeResolvedInCALDocComment(nameBeforeDot + "." + nameAfterDot))); } throw new InvalidCALDocException(); } } /** * Constructs the source text form of a disambiguated CALDoc cross reference, either in an "@link" or an "@see" block. * @param refName the (qualified/unqualified) name of the reference. * @param contextKeyword the context keyword used for disambiguation. * @param isUnchecked true if the reference is a quoted, unchecked reference. * @param isLinkBlock true if this reference is in a "@link" block, false if this reference is in an "@see" block. * @return the source text form of the requested CALDoc cross reference. */ private String makeDisambiguatedCALDocCrossReferenceSourceText(String refName, String contextKeyword, boolean isUnchecked, boolean isLinkBlock) { String reference; if (isUnchecked) { reference = '\"' + refName + '\"'; } else { reference = refName; } if (isLinkBlock) { return "{@link " + contextKeyword + " = " + reference + "@}"; } else { return "@see " + contextKeyword + " = " + reference; } } /** * Constructs a CALDocComment.EmphasizedSegment from a parse tree node representing an emphasized piece of text in a CALDoc comment. * @param emNode the ParseTreeNode representing an emphasized piece of text in a CALDoc comment. * @param inPreformattedContext whether the text is in a preformatted context, and to be dealt with accordingly. * @param summaryCollector the SummaryCollector to use in building up the comment's summary. * @return a new CALDocComment.EmphasizedSegment object representing the emphasized piece of text. */ private CALDocComment.EmphasizedSegment buildCALDocInlineEmTagSegment(ParseTreeNode emNode, boolean inPreformattedContext, CALDocComment.SummaryCollector summaryCollector) throws InvalidCALDocException { emNode.verifyType(CALTreeParserTokenTypes.CALDOC_TEXT_EMPHASIZED_TEXT); ParseTreeNode contentNode = emNode.firstChild(); CALDocComment.TextParagraph content = checkAndBuildCALDocSingleTextParagraph(contentNode, inPreformattedContext, "{@em}", summaryCollector); return new CALDocComment.EmphasizedSegment(content); } /** * Constructs a CALDocComment.StronglyEmphasizedSegment from a parse tree node representing a strongly emphasized piece of text in a CALDoc comment. * @param strongNode the ParseTreeNode representing a strongly emphasized piece of text in a CALDoc comment. * @param inPreformattedContext whether the text is in a preformatted context, and to be dealt with accordingly. * @param summaryCollector the SummaryCollector to use in building up the comment's summary. * @return a new CALDocComment.StronglyEmphasizedSegment object representing the strongly emphasized piece of text. */ private CALDocComment.StronglyEmphasizedSegment buildCALDocInlineStrongTagSegment(ParseTreeNode strongNode, boolean inPreformattedContext, CALDocComment.SummaryCollector summaryCollector) throws InvalidCALDocException { strongNode.verifyType(CALTreeParserTokenTypes.CALDOC_TEXT_STRONGLY_EMPHASIZED_TEXT); ParseTreeNode contentNode = strongNode.firstChild(); CALDocComment.TextParagraph content = checkAndBuildCALDocSingleTextParagraph(contentNode, inPreformattedContext, "{@strong}", summaryCollector); return new CALDocComment.StronglyEmphasizedSegment(content); } /** * Constructs a CALDocComment.SuperscriptSegment from a parse tree node representing a superscripted piece of text in a CALDoc comment. * @param supNode the ParseTreeNode representing a superscripted piece of text in a CALDoc comment. * @param inPreformattedContext whether the text is in a preformatted context, and to be dealt with accordingly. * @param summaryCollector the SummaryCollector to use in building up the comment's summary. * @return a new CALDocComment.SuperscriptSegment object representing the superscripted piece of text. */ private CALDocComment.SuperscriptSegment buildCALDocInlineSupTagSegment(ParseTreeNode supNode, boolean inPreformattedContext, CALDocComment.SummaryCollector summaryCollector) throws InvalidCALDocException { supNode.verifyType(CALTreeParserTokenTypes.CALDOC_TEXT_SUPERSCRIPT_TEXT); ParseTreeNode contentNode = supNode.firstChild(); CALDocComment.TextParagraph content = checkAndBuildCALDocSingleTextParagraph(contentNode, inPreformattedContext, "{@sup}", summaryCollector); return new CALDocComment.SuperscriptSegment(content); } /** * Constructs a CALDocComment.SubscriptSegment from a parse tree node representing a subscripted piece of text in a CALDoc comment. * @param subNode the ParseTreeNode representing a subscripted piece of text in a CALDoc comment. * @param inPreformattedContext whether the text is in a preformatted context, and to be dealt with accordingly. * @param summaryCollector the SummaryCollector to use in building up the comment's summary. * @return a new CALDocComment.SubscriptSegment object representing the subscripted piece of text. */ private CALDocComment.SubscriptSegment buildCALDocInlineSubTagSegment(ParseTreeNode subNode, boolean inPreformattedContext, CALDocComment.SummaryCollector summaryCollector) throws InvalidCALDocException { subNode.verifyType(CALTreeParserTokenTypes.CALDOC_TEXT_SUBSCRIPT_TEXT); ParseTreeNode contentNode = subNode.firstChild(); CALDocComment.TextParagraph content = checkAndBuildCALDocSingleTextParagraph(contentNode, inPreformattedContext, "{@sub}", summaryCollector); return new CALDocComment.SubscriptSegment(content); } /** * Gathers the segments constructed from a parse tree node representing a "@summary" inline tag segment in a CALDoc comment, * and adds them to the given list while submitting them to the given SummaryCollector. * * @param summaryNode the ParseTreeNode representing a "@summary" inline tag segment in a CALDoc comment. * @param inPreformattedContext whether the text is in a preformatted context, and to be dealt with accordingly. * @param segmentsList the list to which the new CALDocComment.Segment is to be added. * @param summaryCollector the SummaryCollector to use in building up the comment's summary. */ private void addCALDocInlineSummaryTagSegmentToList(ParseTreeNode summaryNode, boolean inPreformattedContext, List<CALDocComment.Segment> segmentsList, CALDocComment.SummaryCollector summaryCollector) throws InvalidCALDocException { summaryNode.verifyType(CALTreeParserTokenTypes.CALDOC_TEXT_SUMMARY); ParseTreeNode contentNode = summaryNode.firstChild(); CALDocComment.TextParagraph content = checkAndBuildCALDocSingleTextParagraph(contentNode, inPreformattedContext, "{@summary}", summaryCollector); for (int i = 0, n = content.getNSegments(); i < n; i++) { segmentsList.add(content.getNthSegment(i)); } // associate the summary with the comment via the builder summaryCollector.addSummaryParagraph(content); } /** * Constructs a CALDocComment.CodeSegment from a parse tree node representing a code block in a CALDoc comment. * @param codeNode the ParseTreeNode representing a code block in a CALDoc comment. * @param summaryCollector the SummaryCollector to use in building up the comment's summary. * @return a new CALDocComment.CodeSegment object representing the code block. */ private CALDocComment.CodeSegment buildCALDocInlineCodeTagSegment(ParseTreeNode codeNode, CALDocComment.SummaryCollector summaryCollector) throws InvalidCALDocException { codeNode.verifyType(CALTreeParserTokenTypes.CALDOC_TEXT_CODE_BLOCK); ParseTreeNode contentNode = codeNode.firstChild(); CALDocComment.TextBlock content = buildCALDocPreformattedBlock(contentNode, summaryCollector); return new CALDocComment.CodeSegment(content); } /** * Constructs a CALDocComment.ListParagraph from a parse tree node representing a list in a CALDoc comment. * @param listNode the ParseTreeNode representing a list in a CALDoc comment. * @param summaryCollector the SummaryCollector to use in building up the comment's summary. * @return a new CALDocComment.ListParagraph object representing the list. */ private CALDocComment.ListParagraph buildCALDocInlineListTagSegment(ParseTreeNode listNode, CALDocComment.SummaryCollector summaryCollector) throws InvalidCALDocException { listNode.verifyType(CALTreeParserTokenTypes.CALDOC_TEXT_ORDERED_LIST, CALTreeParserTokenTypes.CALDOC_TEXT_UNORDERED_LIST); List<CALDocComment.ListItem> items = new ArrayList<CALDocComment.ListItem>(); for (final ParseTreeNode itemNode : listNode) { itemNode.verifyType(CALTreeParserTokenTypes.CALDOC_TEXT_LIST_ITEM); ParseTreeNode itemContentNode = itemNode.firstChild(); CALDocComment.TextBlock content = buildCALDocTextBlock(itemContentNode, false, summaryCollector); CALDocComment.ListItem item = new CALDocComment.ListItem(content); items.add(item); } CALDocComment.ListItem[] itemsArray = items.toArray(CALDocComment.ListParagraph.NO_ITEMS); boolean isOrdered = listNode.getType() == CALTreeParserTokenTypes.CALDOC_TEXT_ORDERED_LIST; return new CALDocComment.ListParagraph(isOrdered, itemsArray); } /** * Checks whether the CALDoc associated with a particular source element is * valid - that all names referenced can be resolved and that it contains * only the allowed tags. * * @param optionalCALDocNode * the optionalCALDoc node, with a type of * OPTIONAL_CALDOC_COMMENT. * @param paramListNode * the node whose children are the declared parameters, or null * if the source element being commented does not have * parameters. * @param functionTypeExpr * the type expression of the function/class method/instance * method, or null if the source element being commented is not * associated with a type expression. * @param allowsArgTag * whether the "@arg" tag is allowed in the comment. * @param allowsReturnTag * whether the "@return" tag is allowed in the comment. * @param allowsOrdinalArgName * whether the name appearing in an "@arg" tag is allowed to be an ordinal field name (e.g. #3). * @return a new instance of CALDocComment representing the comment. */ private CALDocComment checkAndBuildCALDocComment(ParseTreeNode optionalCALDocNode, ParseTreeNode paramListNode, TypeExpr functionTypeExpr, boolean allowsArgTag, boolean allowsReturnTag, boolean allowsOrdinalArgName) { int nTopLevelArrowsInType; if (functionTypeExpr != null) { nTopLevelArrowsInType = functionTypeExpr.getArity(); } else { if (!allowsArgTag) { nTopLevelArrowsInType = -1; } else { // if the "@arg" tag is allowed but information about the function/method's type is not supplied, // then that's an internal programming error. throw new IllegalArgumentException(); } } return checkAndBuildCALDocComment(optionalCALDocNode, paramListNode, nTopLevelArrowsInType, allowsArgTag, allowsReturnTag, allowsOrdinalArgName); } /** * Checks whether the CALDoc associated with a particular source element is * valid - that all names referenced can be resolved and that it contains * only the allowed tags. * * @param optionalCALDocNode * the optionalCALDoc node, with a type of * OPTIONAL_CALDOC_COMMENT. * @param paramListNode * the node whose children are the declared parameters, or null * if the source element being commented does not have * parameters. * @param nTopLevelArrowsInType * the number of top-level arrows in the type expression of the * function/class method/instance method, or -1 if the source * element being commented is not associated with a type * expression. * @param allowsArgTag * whether the "@arg" tag is allowed in the comment. * @param allowsReturnTag * whether the "@return" tag is allowed in the comment. * @param allowsOrdinalArgName * whether the name appearing in an "@arg" tag is allowed to be an ordinal field name (e.g. #3). * @return a new instance of CALDocComment representing the comment, or null if there is no comment. */ private CALDocComment checkAndBuildCALDocComment(ParseTreeNode optionalCALDocNode, ParseTreeNode paramListNode, int nTopLevelArrowsInType, boolean allowsArgTag, boolean allowsReturnTag, boolean allowsOrdinalArgName) { optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); ParseTreeNode calDocNode = optionalCALDocNode.firstChild(); if (calDocNode == null) { return null; } // if the "@arg" tag is allowed but information about the function/method's type is not supplied, // then that's an internal programming error. if (nTopLevelArrowsInType < 0 && allowsArgTag) { throw new IllegalArgumentException(); } boolean errorInCALDoc = false; CALDocComment.Builder builder = new CALDocComment.Builder(); calDocNode.verifyType(CALTreeParserTokenTypes.CALDOC_COMMENT); ParseTreeNode descriptionNode = calDocNode.firstChild(); descriptionNode.verifyType(CALTreeParserTokenTypes.CALDOC_DESCRIPTION_BLOCK); try { builder.setDescriptionBlock(buildCALDocTextBlock(descriptionNode.firstChild(), false, builder)); } catch (InvalidCALDocException e) { errorInCALDoc = true; } ParseTreeNode taggedBlockListNode = descriptionNode.nextSibling(); taggedBlockListNode.verifyType(CALTreeParserTokenTypes.CALDOC_TAGGED_BLOCKS); ParseTreeNode nextParamToCheck; if (paramListNode == null) { nextParamToCheck = null; } else { nextParamToCheck = paramListNode.firstChild(); while (nextParamToCheck != null && nextParamToCheck.getText().startsWith("$dictvar")) { nextParamToCheck = nextParamToCheck.nextSibling(); } } int nArgsSeen = 0; // loop through the tagged blocks to perform checking on the identifiers // contained within them. for (final ParseTreeNode taggedBlockNode : taggedBlockListNode) { // check to see that the argument names match the parameter list, and are // declared in order switch (taggedBlockNode.getType()) { case CALTreeParserTokenTypes.CALDOC_AUTHOR_BLOCK: // nothing to check, since there can be any number of @author blocks in a CALDoc comment // so just add it to the builder try { builder.addAuthorBlock(buildCALDocTextBlock(taggedBlockNode.firstChild(), false, builder)); } catch (InvalidCALDocException e) { errorInCALDoc = true; } break; case CALTreeParserTokenTypes.CALDOC_ARG_BLOCK: { nArgsSeen++; if (allowsArgTag) { // if there are more @arg tags then there are top level arrows in the type expression // then there are too many @arg tags. if (nArgsSeen > nTopLevelArrowsInType) { compiler.logMessage( new CompilerMessage( taggedBlockNode, new MessageKind.Error.TooManyArgTagsInCALDocComment())); errorInCALDoc = true; } ParseTreeNode argNameNode = taggedBlockNode.firstChild(); argNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID, CALTreeParserTokenTypes.ORDINAL_FIELD_NAME); String argName = argNameNode.getText(); if (nextParamToCheck != null) { nextParamToCheck.verifyType(CALTreeParserTokenTypes.LAZY_PARAM, CALTreeParserTokenTypes.STRICT_PARAM, CALTreeParserTokenTypes.DATA_CONSTRUCTOR_NAMED_ARG); String paramName; if (nextParamToCheck.getType() == CALTreeParserTokenTypes.DATA_CONSTRUCTOR_NAMED_ARG) { paramName = nextParamToCheck.firstChild().getText(); } else { while (nextParamToCheck != null && nextParamToCheck.getText().startsWith("$dictvar")) { nextParamToCheck = nextParamToCheck.nextSibling(); nextParamToCheck.verifyType(CALTreeParserTokenTypes.LAZY_PARAM, CALTreeParserTokenTypes.STRICT_PARAM); } paramName = FreeVariableFinder.getDisplayName(nextParamToCheck.getText()); } if (!allowsOrdinalArgName && argNameNode.getType() == CALTreeParserTokenTypes.ORDINAL_FIELD_NAME) { // if the param name appearing after the @arg tag is an ordinal field name but ordinal argument // names are not allowed (e.g. for functions/class methods/instance methods), then it is an error. compiler.logMessage( new CompilerMessage( argNameNode, new MessageKind.Error.InvalidArgNameInCALDocComment(argName))); errorInCALDoc = true; } else if (!argName.equals(paramName)) { // if the param name appearing after the @arg tag is not the same as the param name // in the param list, then it is an error. compiler.logMessage( new CompilerMessage( argNameNode, new MessageKind.Error.ArgNameDoesNotMatchDeclaredNameInCALDocComment(argName, paramName))); errorInCALDoc = true; } // advance to the next param for the next check nextParamToCheck = nextParamToCheck.nextSibling(); } try { builder.addArgBlock(new CALDocComment.ArgBlock(FieldName.make(argName), buildCALDocTextBlock(argNameNode.nextSibling(), false, builder))); } catch (InvalidCALDocException e) { errorInCALDoc = true; } } else { // the "@arg" tag is not allowed in this comment compiler.logMessage( new CompilerMessage( taggedBlockNode, new MessageKind.Error.DisallowedTagInCALDocComment("@arg"))); errorInCALDoc = true; } break; } case CALTreeParserTokenTypes.CALDOC_RETURN_BLOCK: { if (allowsReturnTag) { if (builder.hasReturnBlock()) { compiler.logMessage( new CompilerMessage( taggedBlockNode, new MessageKind.Error.SingletonTagAppearsMoreThanOnceInCALDocComment("@return"))); errorInCALDoc = true; } else { try { builder.setReturnBlock(buildCALDocTextBlock(taggedBlockNode.firstChild(), false, builder)); } catch (InvalidCALDocException e) { errorInCALDoc = true; } } } else { // the "@return" tag is not allowed in this comment compiler.logMessage( new CompilerMessage( taggedBlockNode, new MessageKind.Error.DisallowedTagInCALDocComment("@return"))); errorInCALDoc = true; } break; } case CALTreeParserTokenTypes.CALDOC_DEPRECATED_BLOCK: { if (builder.hasDeprecatedBlock()) { compiler.logMessage( new CompilerMessage( taggedBlockNode, new MessageKind.Error.SingletonTagAppearsMoreThanOnceInCALDocComment("@deprecated"))); errorInCALDoc = true; } else { try { builder.setDeprecatedBlock(buildCALDocTextBlock(taggedBlockNode.firstChild(), false, builder)); } catch (InvalidCALDocException e) { errorInCALDoc = true; } } break; } case CALTreeParserTokenTypes.CALDOC_VERSION_BLOCK: { if (builder.hasVersionBlock()) { compiler.logMessage( new CompilerMessage( taggedBlockNode, new MessageKind.Error.SingletonTagAppearsMoreThanOnceInCALDocComment("@version"))); errorInCALDoc = true; } else { try { builder.setVersionBlock(buildCALDocTextBlock(taggedBlockNode.firstChild(), false, builder)); } catch (InvalidCALDocException e) { errorInCALDoc = true; } } break; } case CALTreeParserTokenTypes.CALDOC_SEE_BLOCK: { if (!checkAndBuildCALDocSeeBlock(taggedBlockNode.firstChild(), builder)) { errorInCALDoc = true; } break; } default: { taggedBlockNode.unexpectedParseTreeNode(); errorInCALDoc = true; break; } } } if (!errorInCALDoc) { return builder.toComment(); } else { return null; } } /** * Constructs a QualifiedName from a parse tree node that represents an unchecked reference to * a function or class method. A resolution attempt is made to resolve the name using the module * type info and the import using clauses. If the name is resolvable then the module name of an * unqualified reference can be obtained. Otherwise we default to using the current module's name, * since an unqualified reference can only refer to local entities unless the name appears in an * import using clause. * * @param nameNode the parse tree node representing a qualified name. * @return a new QualifiedName. */ private QualifiedName buildCALDocUncheckedSeeReferenceFunctionOrClassMethodName(ParseTreeNode nameNode) { nameNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_VAR); ParseTreeNode moduleNameNode = nameNode.firstChild(); String moduleName = ModuleNameUtilities.getMaybeModuleNameStringFromParseTree(moduleNameNode); if (moduleName.length() == 0) { ParseTreeNode unqualifiedNameNode = moduleNameNode.nextSibling(); String unqualifiedName = unqualifiedNameNode.getText(); // if the name is already resolvable, then simply use the fully qualified name of the entity FunctionalAgent entity = currentModuleTypeInfo.getFunctionOrClassMethod(unqualifiedName); if (entity != null) { return entity.getName(); } else { // check the import using clauses ModuleName moduleNameToUse = currentModuleTypeInfo.getModuleOfUsingFunctionOrClassMethod(unqualifiedName); if (moduleNameToUse == null) { // the name cannot currently be resolved, so default to using the current module name moduleNameToUse = currentModuleTypeInfo.getModuleName(); } return QualifiedName.make(moduleNameToUse, unqualifiedName); } } else { return nameNode.toQualifiedName(); } } /** * Constructs a QualifiedName from a parse tree node that represents an unchecked reference to * a data constructor. A resolution attempt is made to resolve the name using the module * type info and the import using clauses. If the name is resolvable then the module name of an * unqualified reference can be obtained. Otherwise we default to using the current module's name, * since an unqualified reference can only refer to local entities unless the name appears in an * import using clause. * * @param nameNode the parse tree node representing a qualified name. * @return a new QualifiedName. */ private QualifiedName buildCALDocUncheckedSeeReferenceDataConsName(ParseTreeNode nameNode) { nameNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS); ParseTreeNode moduleNameNode = nameNode.firstChild(); String moduleName = ModuleNameUtilities.getMaybeModuleNameStringFromParseTree(moduleNameNode); if (moduleName.length() == 0) { ParseTreeNode unqualifiedNameNode = moduleNameNode.nextSibling(); String unqualifiedName = unqualifiedNameNode.getText(); // if the name is already resolvable, then simply use the fully qualified name of the entity FunctionalAgent entity = currentModuleTypeInfo.getDataConstructor(unqualifiedName); if (entity != null) { return entity.getName(); } else { // check the import using clauses ModuleName moduleNameToUse = currentModuleTypeInfo.getModuleOfUsingDataConstructor(unqualifiedName); if (moduleNameToUse == null) { // the name cannot currently be resolved, so default to using the current module name moduleNameToUse = currentModuleTypeInfo.getModuleName(); } return QualifiedName.make(moduleNameToUse, unqualifiedName); } } else { return nameNode.toQualifiedName(); } } /** * Constructs a QualifiedName from a parse tree node that represents an unchecked reference to * a type constructor. A resolution attempt is made to resolve the name using the module * type info and the import using clauses. If the name is resolvable then the module name of an * unqualified reference can be obtained. Otherwise we default to using the current module's name, * since an unqualified reference can only refer to local entities unless the name appears in an * import using clause. * * @param nameNode the parse tree node representing a qualified name. * @return a new QualifiedName. */ private QualifiedName buildCALDocUncheckedSeeReferenceTypeConsName(ParseTreeNode nameNode) { nameNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS); ParseTreeNode moduleNameNode = nameNode.firstChild(); String moduleName = ModuleNameUtilities.getMaybeModuleNameStringFromParseTree(moduleNameNode); if (moduleName.length() == 0) { ParseTreeNode unqualifiedNameNode = moduleNameNode.nextSibling(); String unqualifiedName = unqualifiedNameNode.getText(); // if the name is already resolvable, then simply use the fully qualified name of the entity TypeConstructor typeCons = currentModuleTypeInfo.getTypeConstructor(unqualifiedName); if (typeCons != null) { return typeCons.getName(); } else { // check the import using clauses ModuleName moduleNameToUse = currentModuleTypeInfo.getModuleOfUsingTypeConstructor(unqualifiedName); if (moduleNameToUse == null) { // the name cannot currently be resolved, so default to using the current module name moduleNameToUse = currentModuleTypeInfo.getModuleName(); } return QualifiedName.make(moduleNameToUse, unqualifiedName); } } else { return nameNode.toQualifiedName(); } } /** * Constructs a QualifiedName from a parse tree node that represents an unchecked reference to * a type class. A resolution attempt is made to resolve the name using the module * type info and the import using clauses. If the name is resolvable then the module name of an * unqualified reference can be obtained. Otherwise we default to using the current module's name, * since an unqualified reference can only refer to local entities unless the name appears in an * import using clause. * * @param nameNode the parse tree node representing a qualified name. * @return a new QualifiedName. */ private QualifiedName buildCALDocUncheckedSeeReferenceTypeClassName(ParseTreeNode nameNode) { nameNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS); ParseTreeNode moduleNameNode = nameNode.firstChild(); String moduleName = ModuleNameUtilities.getMaybeModuleNameStringFromParseTree(moduleNameNode); if (moduleName.length() == 0) { ParseTreeNode unqualifiedNameNode = moduleNameNode.nextSibling(); String unqualifiedName = unqualifiedNameNode.getText(); // if the name is already resolvable, then simply use the fully qualified name of the type class TypeClass typeClass = currentModuleTypeInfo.getTypeClass(unqualifiedName); if (typeClass != null) { return typeClass.getName(); } else { // check the import using clauses ModuleName moduleNameToUse = currentModuleTypeInfo.getModuleOfUsingTypeClass(unqualifiedName); if (moduleNameToUse == null) { // the name cannot currently be resolved, so default to using the current module name moduleNameToUse = currentModuleTypeInfo.getModuleName(); } return QualifiedName.make(moduleNameToUse, unqualifiedName); } } else { return nameNode.toQualifiedName(); } } /** * Check the contents of a CALDoc '@see' block to make sure that * all the identifiers contained within it could be resolved. * * @param seeBlockNode * @param builder the CALDocComment.Builder to use in building the CALDocComment instance. * @return true iff there are no errors in the '@see' block. */ private boolean checkAndBuildCALDocSeeBlock(ParseTreeNode seeBlockNode, CALDocComment.Builder builder) { boolean hasError = false; // first make sure that function names and data constructor names are resolved and fully qualified finder.findFreeVariablesInCALDocSeeBlock(seeBlockNode); switch (seeBlockNode.getType()) { case CALTreeParserTokenTypes.CALDOC_SEE_FUNCTION_BLOCK: { for (final ParseTreeNode refNode : seeBlockNode) { try { builder.addFunctionOrClassMethodReference(makeCALDocFunctionOrClassMethodCrossReference(refNode)); } catch (InvalidCALDocException e) { hasError = true; } } break; } case CALTreeParserTokenTypes.CALDOC_SEE_MODULE_BLOCK: { for (final ParseTreeNode refNode : seeBlockNode) { try { builder.addModuleReference(makeCALDocModuleCrossReference(refNode)); } catch (InvalidCALDocException e) { hasError = true; } } break; } case CALTreeParserTokenTypes.CALDOC_SEE_DATACONS_BLOCK: { for (final ParseTreeNode refNode : seeBlockNode) { try { builder.addDataConstructorReference(makeCALDocDataConsCrossReference(refNode)); } catch (InvalidCALDocException e) { hasError = true; } } break; } case CALTreeParserTokenTypes.CALDOC_SEE_TYPECONS_BLOCK: { for (final ParseTreeNode refNode : seeBlockNode) { try { builder.addTypeConstructorReference(makeCALDocTypeConsCrossReference(refNode)); } catch (InvalidCALDocException e) { hasError = true; } } break; } case CALTreeParserTokenTypes.CALDOC_SEE_TYPECLASS_BLOCK: { for (final ParseTreeNode refNode : seeBlockNode) { try { builder.addTypeClassReference(makeCALDocTypeClassCrossReference(refNode)); } catch (InvalidCALDocException e) { hasError = true; } } break; } case CALTreeParserTokenTypes.CALDOC_SEE_BLOCK_WITHOUT_CONTEXT: { for (final ParseTreeNode refNode : seeBlockNode) { try { switch (refNode.getType()) { case CALTreeParserTokenTypes.CALDOC_UNCHECKED_QUALIFIED_VAR: case CALTreeParserTokenTypes.CALDOC_CHECKED_QUALIFIED_VAR: { builder.addFunctionOrClassMethodReference(makeCALDocFunctionOrClassMethodCrossReference(refNode)); break; } case CALTreeParserTokenTypes.CALDOC_UNCHECKED_QUALIFIED_CONS: case CALTreeParserTokenTypes.CALDOC_CHECKED_QUALIFIED_CONS: { Pair<CategoryForCALDocConsNameWithoutContextCrossReference, CALDocComment.Reference> pair = buildCALDocConsNameWithoutContextCrossReference(refNode, false); CategoryForCALDocConsNameWithoutContextCrossReference category = pair.fst(); CALDocComment.Reference reference = pair.snd(); if (category == CategoryForCALDocConsNameWithoutContextCrossReference.MODULE_NAME) { builder.addModuleReference((CALDocComment.ModuleReference)reference); } else if (category == CategoryForCALDocConsNameWithoutContextCrossReference.DATA_CONS_NAME) { builder.addDataConstructorReference((CALDocComment.ScopedEntityReference)reference); } else if (category == CategoryForCALDocConsNameWithoutContextCrossReference.TYPE_CONS_NAME) { builder.addTypeConstructorReference((CALDocComment.ScopedEntityReference)reference); } else if (category == CategoryForCALDocConsNameWithoutContextCrossReference.TYPE_CLASS_NAME) { builder.addTypeClassReference((CALDocComment.ScopedEntityReference)reference); } else { throw new IllegalStateException(); } break; } default: throw new IllegalStateException("Unexpected parse tree node " + refNode.toDebugString() + "."); } } catch (InvalidCALDocException e) { hasError = true; } } break; } default: { seeBlockNode.unexpectedParseTreeNode(); hasError = true; break; } } return !hasError; } /** * Creates a CALDocComment.ScopedEntityReference from a parse tree node representing * a function name appearing in a CALDoc "@see"/"@link" block. * * @param refNode the ParseTreeNode representing a function name appearing in a CALDoc "@see"/"@link" block. * @return a new CALDocComment.ScopedEntityReference object representing the cross reference. */ private CALDocComment.ScopedEntityReference makeCALDocFunctionOrClassMethodCrossReference(ParseTreeNode refNode) throws InvalidCALDocException { refNode.verifyType( CALTreeParserTokenTypes.CALDOC_UNCHECKED_QUALIFIED_VAR, CALTreeParserTokenTypes.CALDOC_CHECKED_QUALIFIED_VAR); ParseTreeNode nameNode = refNode.firstChild(); nameNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_VAR); if (refNode.getType() == CALTreeParserTokenTypes.CALDOC_CHECKED_QUALIFIED_VAR) { FunctionalAgent entity = retrieveQualifiedVar(nameNode); if (entity == null) { String displayName = getQualifiedNameDisplayString(nameNode); // TypeChecker: unknown function or variable {displayName}. compiler.logMessage(new CompilerMessage(nameNode.getChild(1), new MessageKind.Error.UnknownFunctionOrVariable(displayName))); throw new InvalidCALDocException(); } else { ParseTreeNode moduleNameNode = nameNode.firstChild(); String moduleNameInSource = ModuleNameUtilities.getMaybeModuleNameStringUnmodifiedAccordingToOriginalSource(moduleNameNode); return new CALDocComment.ScopedEntityReference(entity.getName(), true, moduleNameInSource); } } else { QualifiedName name = buildCALDocUncheckedSeeReferenceFunctionOrClassMethodName(nameNode); // the previous method call would have set the data field of the moduleNameNode appropriately if the name is unqualified ParseTreeNode moduleNameNode = nameNode.firstChild(); String moduleNameInSource = ModuleNameUtilities.getMaybeModuleNameStringUnmodifiedAccordingToOriginalSource(moduleNameNode); return new CALDocComment.ScopedEntityReference(name, false, moduleNameInSource); } } /** * Creates a CALDoc.ModuleReference from a parse tree node representing * a module name appearing in a CALDoc "@see"/"@link" block. * * @param refNode the ParseTreeNode representing a module name appearing in a CALDoc "@see"/"@link" block. * @return a new CALDocComment.ModuleReference object representing the cross reference. */ private CALDocComment.ModuleReference makeCALDocModuleCrossReference(ParseTreeNode refNode) throws InvalidCALDocException { refNode.verifyType( CALTreeParserTokenTypes.CALDOC_UNCHECKED_MODULE_NAME, CALTreeParserTokenTypes.CALDOC_CHECKED_MODULE_NAME); ParseTreeNode nameNode = refNode.firstChild(); nameNode.verifyType(CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME, CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME_EMPTY_QUALIFIER); ModuleName moduleName = resolveModuleName(nameNode); String moduleNameInSource = ModuleNameUtilities.getMaybeModuleNameStringUnmodifiedAccordingToOriginalSource(nameNode); if (refNode.getType() == CALTreeParserTokenTypes.CALDOC_CHECKED_MODULE_NAME) { if (!moduleName.equals(currentModuleTypeInfo.getModuleName())) { ModuleTypeInfo moduleTypeInfo = currentModuleTypeInfo.getImportedModule(moduleName); if (moduleTypeInfo == null) { compiler.logMessage(new CompilerMessage(nameNode, new MessageKind.Error.ModuleHasNotBeenImported(moduleName, currentModuleTypeInfo.getModuleName()))); throw new InvalidCALDocException(); } else { // Now we need to run a deprecation check on the checked module reference SourceRange sourceRange = CALCompiler.getSourceRangeForCompilerMessage(nameNode, currentModuleTypeInfo.getModuleName()); compiler.checkResolvedModuleReference(moduleName, sourceRange); return new CALDocComment.ModuleReference(moduleName, true, moduleNameInSource); } } else { // Now we need to run a deprecation check on the checked module reference SourceRange sourceRange = CALCompiler.getSourceRangeForCompilerMessage(nameNode, currentModuleTypeInfo.getModuleName()); compiler.checkResolvedModuleReference(moduleName, sourceRange); return new CALDocComment.ModuleReference(moduleName, true, moduleNameInSource); } } else { return new CALDocComment.ModuleReference(moduleName, false, moduleNameInSource); } } /** * Creates a CALDocComment.ScopedEntityReference from a parse tree node representing * a data constructor name appearing in a CALDoc "@see"/"@link" block. * * @param refNode the ParseTreeNode representing a data constructor name appearing in a CALDoc "@see"/"@link" block. * @return a new CALDocComment.ScopedEntityReference object representing the cross reference. */ private CALDocComment.ScopedEntityReference makeCALDocDataConsCrossReference(ParseTreeNode refNode) throws InvalidCALDocException { refNode.verifyType( CALTreeParserTokenTypes.CALDOC_UNCHECKED_QUALIFIED_CONS, CALTreeParserTokenTypes.CALDOC_CHECKED_QUALIFIED_CONS); ParseTreeNode nameNode = refNode.firstChild(); nameNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS); if (refNode.getType() == CALTreeParserTokenTypes.CALDOC_CHECKED_QUALIFIED_CONS) { FunctionalAgent entity = retrieveQualifiedDataConstructor(nameNode); if (entity == null) { String displayName = getQualifiedNameDisplayString(nameNode); // TypeChecker: unknown data constructor {displayName}. compiler.logMessage(new CompilerMessage(nameNode.getChild(1), new MessageKind.Error.UnknownDataConstructor(displayName))); throw new InvalidCALDocException(); } else { ParseTreeNode moduleNameNode = nameNode.firstChild(); String moduleNameInSource = ModuleNameUtilities.getMaybeModuleNameStringUnmodifiedAccordingToOriginalSource(moduleNameNode); return new CALDocComment.ScopedEntityReference(entity.getName(), true, moduleNameInSource); } } else { QualifiedName name = buildCALDocUncheckedSeeReferenceDataConsName(nameNode); // the previous method call would have set the data field of the moduleNameNode appropriately if the name is unqualified ParseTreeNode moduleNameNode = nameNode.firstChild(); String moduleNameInSource = ModuleNameUtilities.getMaybeModuleNameStringUnmodifiedAccordingToOriginalSource(moduleNameNode); return new CALDocComment.ScopedEntityReference(name, false, moduleNameInSource); } } /** * Creates a CALDocComment.ScopedEntityReference from a parse tree node representing * a type constructor name appearing in a CALDoc "@see"/"@link" block. * * @param refNode the ParseTreeNode representing a type constructor name appearing in a CALDoc "@see"/"@link" block. * @return a new CALDocComment.ScopedEntityReference object representing the cross reference. */ private CALDocComment.ScopedEntityReference makeCALDocTypeConsCrossReference(ParseTreeNode refNode) throws InvalidCALDocException { refNode.verifyType( CALTreeParserTokenTypes.CALDOC_UNCHECKED_QUALIFIED_CONS, CALTreeParserTokenTypes.CALDOC_CHECKED_QUALIFIED_CONS); ParseTreeNode nameNode = refNode.firstChild(); nameNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS); if (refNode.getType() == CALTreeParserTokenTypes.CALDOC_CHECKED_QUALIFIED_CONS) { TypeConstructor typeCons = DataDeclarationChecker.resolveTypeConsName(nameNode, currentModuleTypeInfo, compiler); if (typeCons == null) { // the previous call to resolveTypeConsName would have logged an error message // to the logger already. throw new InvalidCALDocException(); } else { ParseTreeNode moduleNameNode = nameNode.firstChild(); String moduleNameInSource = ModuleNameUtilities.getMaybeModuleNameStringUnmodifiedAccordingToOriginalSource(moduleNameNode); return new CALDocComment.ScopedEntityReference(typeCons.getName(), true, moduleNameInSource); } } else { QualifiedName name = buildCALDocUncheckedSeeReferenceTypeConsName(nameNode); // the previous method call would have set the data field of the moduleNameNode appropriately if the name is unqualified ParseTreeNode moduleNameNode = nameNode.firstChild(); String moduleNameInSource = ModuleNameUtilities.getMaybeModuleNameStringUnmodifiedAccordingToOriginalSource(moduleNameNode); return new CALDocComment.ScopedEntityReference(name, false, moduleNameInSource); } } /** * Creates a CALDocComment.ScopedEntityReference from a parse tree node representing * a type class name appearing in a CALDoc "@see"/"@link" block. * * @param refNode the ParseTreeNode representing a type class name appearing in a CALDoc "@see"/"@link" block. * @return a new CALDocComment.ScopedEntityReference object representing the cross reference. */ private CALDocComment.ScopedEntityReference makeCALDocTypeClassCrossReference(ParseTreeNode refNode) throws InvalidCALDocException { refNode.verifyType( CALTreeParserTokenTypes.CALDOC_UNCHECKED_QUALIFIED_CONS, CALTreeParserTokenTypes.CALDOC_CHECKED_QUALIFIED_CONS); ParseTreeNode nameNode = refNode.firstChild(); nameNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS); if (refNode.getType() == CALTreeParserTokenTypes.CALDOC_CHECKED_QUALIFIED_CONS) { TypeClass typeClass = TypeClassChecker.resolveClassName(nameNode, currentModuleTypeInfo, compiler); if (typeClass == null) { String displayName = getQualifiedNameDisplayString(nameNode); compiler.logMessage(new CompilerMessage(nameNode, new MessageKind.Error.UndefinedTypeClass(displayName))); throw new InvalidCALDocException(); } else { ParseTreeNode moduleNameNode = nameNode.firstChild(); String moduleNameInSource = ModuleNameUtilities.getMaybeModuleNameStringUnmodifiedAccordingToOriginalSource(moduleNameNode); return new CALDocComment.ScopedEntityReference(typeClass.getName(), true, moduleNameInSource); } } else { QualifiedName name = buildCALDocUncheckedSeeReferenceTypeClassName(nameNode); // the previous method call would have set the data field of the moduleNameNode appropriately if the name is unqualified ParseTreeNode moduleNameNode = nameNode.firstChild(); String moduleNameInSource = ModuleNameUtilities.getMaybeModuleNameStringUnmodifiedAccordingToOriginalSource(moduleNameNode); return new CALDocComment.ScopedEntityReference(name, false, moduleNameInSource); } } /** * Retrieves the DataConstructor named by the specified qualified data constructor * reference. * * @param qualifiedDataConsNode * the qualified data constructor reference. * @return the DataConstructor named by the specified qualified data constructor * reference, or null if there is no such data constructor. */ private DataConstructor retrieveQualifiedDataConstructor(ParseTreeNode qualifiedDataConsNode) { qualifiedDataConsNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS); QualifiedName dataConsName = qualifiedDataConsNode.toQualifiedName(); ModuleName moduleName = dataConsName.getModuleName(); String unqualifiedDataConsName = dataConsName.getUnqualifiedName(); if (moduleName.equals(currentModuleTypeInfo.getModuleName())) { return currentModuleTypeInfo.getDataConstructor(unqualifiedDataConsName); } ModuleTypeInfo importedModuleTypeInfo = currentModuleTypeInfo.getImportedModule(moduleName); if (importedModuleTypeInfo == null) { return null; } DataConstructor dataCons = importedModuleTypeInfo.getDataConstructor(unqualifiedDataConsName); if (dataCons == null) { return null; } if (currentModuleTypeInfo.isEntityVisible(dataCons)) { return dataCons; } return null; } /** * Retrieves the FunctionalAgent named by the specified qualified var reference. * * @param qualifiedVarNode * the qualified var reference. * @return the FunctionalAgent named by the specified qualified var reference, or * null if there is no such function or class method. */ private FunctionalAgent retrieveQualifiedVar(ParseTreeNode qualifiedVarNode) { qualifiedVarNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_VAR); QualifiedName varName = qualifiedVarNode.toQualifiedName(); ModuleName moduleName = varName.getModuleName(); String unqualifiedName = varName.getUnqualifiedName(); if (moduleName.equals(currentModuleTypeInfo.getModuleName())) { return currentModuleTypeInfo.getFunctionOrClassMethod(unqualifiedName); } ModuleTypeInfo importedModuleTypeInfo = currentModuleTypeInfo.getImportedModule(moduleName); if (importedModuleTypeInfo == null) { return null; } FunctionalAgent entity = importedModuleTypeInfo.getFunctionOrClassMethod(unqualifiedName); if (entity == null) { return null; } if (currentModuleTypeInfo.isEntityVisible(entity)) { return entity; } return null; } /** * Returns a string representation of the qualified name as represented by the parse tree. * @param nameNode the parse tree representing a qualified name. * @return the string representation of the qualified name. */ private String getQualifiedNameDisplayString(ParseTreeNode nameNode) { return nameNode.toQualifiedName().getUnqualifiedName(); } /** * Resolves the module name represented by the given parse tree, and modifies it to represent the resolved name * (if it differs from the original name). This differs from {@link ModuleNameUtilities#resolveMaybeModuleNameInParseTree} * in that an InvalidCALDocException is thrown if the name is ambiguous. * * @param moduleNameNode the root of the parse tree representing a module name. * @return the resolved name for the given module name. * @throws InvalidCALDocException when the given module name cannot be unambiguously resolved. */ private ModuleName resolveModuleName(ParseTreeNode moduleNameNode) throws InvalidCALDocException { ModuleName moduleName = ModuleNameUtilities.getModuleNameFromParseTree(moduleNameNode); ModuleNameResolver.ResolutionResult resolution = currentModuleTypeInfo.getModuleNameResolver().resolve(moduleName); if (!resolution.isResolvedModuleNameEqualToOriginalModuleName()) { ModuleNameUtilities.setModuleNameIntoParseTree(moduleNameNode, resolution.getResolvedModuleName()); } if (resolution.isAmbiguous()) { compiler.logMessage(new CompilerMessage(moduleNameNode, new MessageKind.Error.AmbiguousPartiallyQualifiedFormModuleName(moduleName, resolution.getPotentialMatches()))); throw new InvalidCALDocException(); } return resolution.getResolvedModuleName(); } }