/* * 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. */ /* * SourceIdentifierFinder.java * Creation date: (February 23, 2004) * By: Iulian Radu */ package org.openquark.cal.compiler; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.openquark.cal.compiler.CompilerMessage.AbortCompilation; import org.openquark.cal.util.ArrayStack; /** * Used for finding identifiers in module sources, global functions, lambda definitions, * local function definitions, import and instance declarations for the purposes of various * pre-compilation services, such as auto-qualification for code gems and ICE command line expressions, * and renaming. * * This superclass is responsible for traversing the expression parse trees and invoking its abstract * methods on each identifier reference found. The class also keeps track of any local variable * bindings, and identifiers bound to modules via "import using" statements. * * Any errors encountered while performing visitation will be logged to a message logger specific to this finder. * This logger is available by calling getLogger(). * * Note that the visitation code is largely copied from FreeVariableFinder. This code has more of * a tendency to get out of date since it is only used for code gems and ICE command line expressions. * So, if there is a bug, please update from the corresponding places in FreeVariableFinder. * * <P>Creation date: (February 23, 2004) * * @param <I> identifier type. The type of the identifier being found e.g. SourceIdentifier, SourceModification * @param <V> variable type. The type of the variables being places on the bound variables stack e.g. String, SourceIdentifier * * @author Iulian Radu */ abstract class SourceIdentifierFinder<I, V> { /** Logger for parse error messages */ private final CompilerMessageLogger logger; /** Name of the module containing the expressions checked */ private ModuleName currentModuleName; /** A Set (of ModuleNames) of the names of modules imported by the current module. */ private final Set<ModuleName> importedModuleNames = new HashSet<ModuleName>(); /** The module name resolver corresponding to the module containing the expressions to be checked. */ private ModuleNameResolver moduleNameResolver; // Mappings (String entity name -> ModuleName module name) of entities imported from // other modules via "import using" statements private final Map<String, ModuleName> usingFunctionOrClassMethodMap = new HashMap<String, ModuleName>(); private final Map<String, ModuleName> usingDataConstructorMap = new HashMap<String, ModuleName>(); private final Map<String, ModuleName> usingTypeConstructorMap = new HashMap<String, ModuleName>(); private final Map<String, ModuleName> usingTypeClassMap = new HashMap<String, ModuleName>(); /** * Constructor for a SourceIdentifierFinder. */ SourceIdentifierFinder() { // Abort finding on fatal. TODO: Note in javadoc. this.logger = new MessageLogger(true); } ModuleName getCurrentModuleName() { return currentModuleName; } void setCurrentModuleName(ModuleName currentModuleName) { this.currentModuleName = currentModuleName; } /** * @return the logger used to log errors during visitation. */ CompilerMessageLogger getLogger() { return logger; } /** @return true if the given name is the name of an imported module. */ boolean isModuleImported(ModuleName moduleName) { return importedModuleNames.contains(moduleName); } ModuleName getModuleForImportedFunctionOrClassMethod(String unqualifiedName) { return usingFunctionOrClassMethodMap.get(unqualifiedName); } ModuleName getModuleForImportedDataConstructor(String unqualifiedName) { return usingDataConstructorMap.get(unqualifiedName); } ModuleName getModuleForImportedTypeConstructor(String unqualifiedName) { return usingTypeConstructorMap.get(unqualifiedName); } ModuleName getModuleForImportedTypeClass(String unqualifiedName) { return usingTypeClassMap.get(unqualifiedName); } /** * @return the module name resolver corresponding to the module containing the expressions to be checked. */ ModuleNameResolver getModuleNameResolver() { return moduleNameResolver; } /** * Sets the module name resolver corresponding to the module containing the expressions to be checked. * @param moduleNameResolver the module name resolver. */ void setModuleNameResolver(ModuleNameResolver moduleNameResolver) { this.moduleNameResolver = moduleNameResolver; } /** * Traverse the passed code expression and return the list of identifiers that occur within it. * The list is ordered as dictated by the getIdentifierListComparator(). * * This method is intended to be the entry point for traversing parsed code expressions. * * @param exprNode ParseTree node defining the code expression * @param moduleName name of the module this expression belongs to * @param moduleNameResolver the module name resolver for the module to which this expression belongs. * @return List of identifiers encountered in the expression. */ List<I> findIdentifiersInCodeExpression(ParseTreeNode exprNode, ModuleName moduleName, ModuleNameResolver moduleNameResolver) { ArrayStack<V> boundVariablesStack = ArrayStack.make(); List<I> identifierList = new ArrayList<I>(); setCurrentModuleName(moduleName); setModuleNameResolver(moduleNameResolver); findIdentifiersInExpr(identifierList, boundVariablesStack, exprNode); //it may not be necessary to sort, but it is safe Collections.sort(identifierList, getIdentifierListComparator()); return identifierList; } /** * Traverse the parsed module source and return the list of identifiers that occur within it. * The list is ordered as dictated by the getIdentifierListComparator(). * * This method is intended to be the entry point for traversing parsed module sources. * * @param moduleDefNode ParseTree node defining the code expression * @return List of identifiers encountered in the expression. */ List<I> findIdentifiersInModule(ParseTreeNode moduleDefNode) { List<I> identifierList = new ArrayList<I>(); setCurrentModuleName(ModuleNameUtilities.getModuleNameFromParseTree(moduleDefNode.getChild(1))); findIdentifiersInModule(identifierList, moduleDefNode); // The retrieved identifiers most likely do not appear in order, since parse // trees within the module are inspected by category Collections.sort(identifierList, getIdentifierListComparator()); return identifierList; } private void findIdentifiersInModule(List<I> identifierList, ParseTreeNode moduleDefnNode) { moduleDefnNode.verifyType(CALTreeParserTokenTypes.MODULE_DEFN); ParseTreeNode optionalCALDocNode = moduleDefnNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); findIdentifiersInCALDocComment(identifierList, optionalCALDocNode); ParseTreeNode moduleNameNode = optionalCALDocNode.nextSibling(); visitModuleNameNode(identifierList, moduleNameNode); // Check 'import' declarations ParseTreeNode importDeclarationListNode = moduleNameNode.nextSibling(); findIdentifiersInImportDeclarations(identifierList, importDeclarationListNode); //skip over friends. ParseTreeNode friendDeclarationListNode = importDeclarationListNode.nextSibling(); friendDeclarationListNode.verifyType(CALTreeParserTokenTypes.FRIEND_DECLARATION_LIST); // Categorize parse trees found in this module for easy access ParseTreeNode outerDefnListNode = friendDeclarationListNode.nextSibling(); outerDefnListNode.verifyType(CALTreeParserTokenTypes.OUTER_DEFN_LIST); ModuleLevelParseTrees moduleLevelParseTrees = new ModuleLevelParseTrees(moduleDefnNode, outerDefnListNode); // Check SC declarations and definitions for (final ParseTreeNode node : moduleLevelParseTrees.getFunctionTypeDeclarationNodes()) { findIdentifiersInFunctionDeclaration(identifierList, node); } for (final ParseTreeNode node : moduleLevelParseTrees.getFunctionDefnNodes()) { findIdentifiersInFunctionDefinition(identifierList, node); } // Check type class definitions for (final ParseTreeNode node : moduleLevelParseTrees.getTypeClassDefnNodes()) { findIdentifiersInTypeClassDefinition(identifierList, node); } // Check class instance declarations for (final ParseTreeNode node : moduleLevelParseTrees.getInstanceDefnNodes()) { findIdentifiersInInstanceDefinition(identifierList, node); } // Check data declarations for (final ParseTreeNode node : moduleLevelParseTrees.getDataDeclarationNodes()) { findIdentifiersInDataDeclaration(identifierList, node); } // Check foreign SC declarations for (final ParseTreeNode node : moduleLevelParseTrees.getForeignFunctionDefnNodes()) { findIdentifiersInForeignFunctionDeclaration(identifierList, node); } for (final ParseTreeNode node : moduleLevelParseTrees.getForeignDataDeclarationNodes()) { findIdentifiersInForeignDataDeclaration(identifierList, node); } return; } /** * Find the identifiers occurring in an expression Node. * * For example in: f x = y + x + (let y = 2 in y) the first occurrence of y * is the only identifier returned by the AllIdentifierFinder. * * @param identifierList * The identifiers encountered while traversing the parse * tree. * @param boundVariablesStack * The function is not dependent on bound variables * appearing in its definition. These are its argument * variables, or variables introduced in internal let * declarations or binder variables in a lambda declaration. * This stack varies depending on where we are in the * definition. The same variable name can occur more than once * because of scoping. * @param parseTree * expression parse tree */ void findIdentifiersInExpr(List<I> identifierList, ArrayStack<V> boundVariablesStack, ParseTreeNode parseTree) { int nodeType = parseTree.getType(); switch (nodeType) { case CALTreeParserTokenTypes.LITERAL_let : { findIdentifiersInLet(identifierList, boundVariablesStack, parseTree); return; } case CALTreeParserTokenTypes.LAMBDA_DEFN : { ParseTreeNode paramListNode = parseTree.firstChild(); paramListNode.verifyType(CALTreeParserTokenTypes.FUNCTION_PARAM_LIST); findIdentifiersInLambda(identifierList, boundVariablesStack, paramListNode, paramListNode.nextSibling()); return; } case CALTreeParserTokenTypes.LITERAL_case : { findIdentifiersInCase(identifierList, boundVariablesStack, parseTree); return; } case CALTreeParserTokenTypes.LITERAL_if : case CALTreeParserTokenTypes.BARBAR : case CALTreeParserTokenTypes.AMPERSANDAMPERSAND : case CALTreeParserTokenTypes.PLUSPLUS : case CALTreeParserTokenTypes.LESS_THAN : case CALTreeParserTokenTypes.LESS_THAN_OR_EQUALS : case CALTreeParserTokenTypes.EQUALSEQUALS : case CALTreeParserTokenTypes.NOT_EQUALS : case CALTreeParserTokenTypes.GREATER_THAN_OR_EQUALS : case CALTreeParserTokenTypes.GREATER_THAN : case CALTreeParserTokenTypes.PLUS : case CALTreeParserTokenTypes.MINUS : case CALTreeParserTokenTypes.ASTERISK : case CALTreeParserTokenTypes.SOLIDUS : case CALTreeParserTokenTypes.PERCENT: case CALTreeParserTokenTypes.COLON : case CALTreeParserTokenTypes.UNARY_MINUS: case CALTreeParserTokenTypes.POUND: case CALTreeParserTokenTypes.DOLLAR: case CALTreeParserTokenTypes.BACKQUOTE: { findIdentifiersInChildExpressions(identifierList, boundVariablesStack, parseTree); return; } case CALTreeParserTokenTypes.APPLICATION : { for (final ParseTreeNode exprNode : parseTree) { findIdentifiersInExpr(identifierList, boundVariablesStack, exprNode); } return; } // function names, class method names and variables case CALTreeParserTokenTypes.QUALIFIED_VAR : { visitFunctionOrClassMethodNameNode(identifierList, boundVariablesStack, parseTree); return; } //data constructors case CALTreeParserTokenTypes.QUALIFIED_CONS : { visitDataConsNameNode(identifierList, parseTree); return; } // literals case CALTreeParserTokenTypes.INTEGER_LITERAL : case CALTreeParserTokenTypes.FLOAT_LITERAL : case CALTreeParserTokenTypes.CHAR_LITERAL : case CALTreeParserTokenTypes.STRING_LITERAL : return; //A parenthesized expression, a tuple or the trivial type case CALTreeParserTokenTypes.TUPLE_CONSTRUCTOR : { findIdentifiersInChildExpressions(identifierList, boundVariablesStack, parseTree); return; } //A list data value case CALTreeParserTokenTypes.LIST_CONSTRUCTOR : findIdentifiersInChildExpressions(identifierList, boundVariablesStack, parseTree); return; case CALTreeParserTokenTypes.RECORD_CONSTRUCTOR: { ParseTreeNode baseRecordNode = parseTree.firstChild(); baseRecordNode.verifyType(CALTreeParserTokenTypes.BASE_RECORD); ParseTreeNode baseRecordExprNode = baseRecordNode.firstChild(); if (baseRecordExprNode != null) { findIdentifiersInExpr(identifierList, boundVariablesStack, 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); findIdentifiersInExpr(identifierList, boundVariablesStack, fieldModificationNode.getChild(1)); } return; } case CALTreeParserTokenTypes.SELECT_RECORD_FIELD: { ParseTreeNode exprNode = parseTree.firstChild(); findIdentifiersInExpr(identifierList, boundVariablesStack, exprNode); return; } case CALTreeParserTokenTypes.SELECT_DATA_CONSTRUCTOR_FIELD: { // Simulate a case expr where // expr.DCName.fieldName is converted to case expr of DCName {fieldName} -> fieldName; // The expression. ParseTreeNode exprNode = parseTree.firstChild(); findIdentifiersInExpr(identifierList, boundVariablesStack, exprNode); // The name of the DC. ParseTreeNode dcNameNode = exprNode.nextSibling(); dcNameNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS); visitDataConsNameNode(identifierList, dcNameNode); // The name of the field. ParseTreeNode fieldNameNode = dcNameNode.nextSibling(); // If it's a textual (not an ordinal) field, simulate the case as above. if (fieldNameNode.getType() == CALTreeParserTokenTypes.VAR_ID) { String calSourceForm = fieldNameNode.getText(); SourcePosition fieldNameSourcePosition = fieldNameNode.getSourcePosition(); ParseTreeNode patternVarNode = new ParseTreeNode(CALTreeParserTokenTypes.VAR_ID, calSourceForm, fieldNameSourcePosition); visitLocalVarDeclarationNode(identifierList, boundVariablesStack, patternVarNode, false); // varName is now a bound variable for the body of the lambda pushLocalVar(boundVariablesStack, patternVarNode, identifierList); // The bound expr node is a node of type QUALIFIED_VAR with the same name as the pattern var (~punning). // ie. moduleName is unspecified, unqualified name is the pattern var name. ParseTreeNode qualifiedVarNode = ParseTreeNode.makeUnqualifiedVarNode(calSourceForm, fieldNameSourcePosition); // Find in the expr node.. findIdentifiersInExpr(identifierList, boundVariablesStack, qualifiedVarNode); // pop varName.. boundVariablesStack.pop(); } return; } case CALTreeParserTokenTypes.EXPRESSION_TYPE_SIGNATURE: { ParseTreeNode exprNode = parseTree.firstChild(); findIdentifiersInExpr(identifierList, boundVariablesStack, exprNode); ParseTreeNode typeSignatureNode = exprNode.nextSibling(); typeSignatureNode.verifyType(CALTreeParserTokenTypes.TYPE_SIGNATURE); ParseTreeNode contextListNode = typeSignatureNode.firstChild(); ParseTreeNode declarationNode = contextListNode.nextSibling(); findIdentifiersInDeclaredTypeContext(identifierList, contextListNode); findIdentifiersInDeclaredTypeExpr(identifierList, declarationNode); return; } default : { parseTree.unexpectedParseTreeNode(); return; } } } /** * A helper function to find identifiers in case expressions. * * @param identifierList * @param boundVariablesStack * @param parseTree * */ void findIdentifiersInCase(List<I> identifierList, ArrayStack<V> boundVariablesStack, ParseTreeNode parseTree) { ParseTreeNode exprNode = parseTree.firstChild(); findIdentifiersInExpr(identifierList, boundVariablesStack, exprNode); ParseTreeNode altListNode = exprNode.nextSibling(); altListNode.verifyType(CALTreeParserTokenTypes.ALT_LIST); for (final ParseTreeNode altNode : altListNode) { altNode.verifyType(CALTreeParserTokenTypes.ALT); ParseTreeNode patternNode = altNode.firstChild(); int nodeKind = patternNode.getType(); switch (nodeKind) { case CALTreeParserTokenTypes.PATTERN_CONSTRUCTOR : { ParseTreeNode dataConsNameListNode = patternNode.firstChild(); ParseTreeNode argBindingsNode = dataConsNameListNode.nextSibling(); for (final ParseTreeNode qualifiedConsNode : dataConsNameListNode) { visitDataConsNameNode(identifierList, qualifiedConsNode); } switch (argBindingsNode.getType()) { case CALTreeParserTokenTypes.PATTERN_VAR_LIST : { // positional notation findIdentifiersInLambda(identifierList, boundVariablesStack, argBindingsNode, patternNode.nextSibling()); break; } case CALTreeParserTokenTypes.FIELD_BINDING_VAR_ASSIGNMENT_LIST : { // matching notation findIdentifiersInFieldBindingCase(identifierList, boundVariablesStack, argBindingsNode, patternNode.nextSibling(), null); break; } default : { patternNode.unexpectedParseTreeNode(); return; } } break; } case CALTreeParserTokenTypes.LIST_CONSTRUCTOR : // null list constructor [] case CALTreeParserTokenTypes.VIRTUAL_UNIT_DATA_CONSTRUCTOR: // Unit constructor () case CALTreeParserTokenTypes.UNDERSCORE : // _ case CALTreeParserTokenTypes.INT_PATTERN : case CALTreeParserTokenTypes.CHAR_PATTERN : { findIdentifiersInLambda(identifierList, boundVariablesStack, null, patternNode.nextSibling()); break; } case CALTreeParserTokenTypes.TUPLE_CONSTRUCTOR : case CALTreeParserTokenTypes.COLON : { findIdentifiersInLambda(identifierList, boundVariablesStack, patternNode, patternNode.nextSibling()); break; } case CALTreeParserTokenTypes.RECORD_PATTERN: { findIdentifiersInRecordCase(identifierList, boundVariablesStack, patternNode); break; } default : { patternNode.unexpectedParseTreeNode(); return; } } } return; } /** * Finds identifiers in case expression containing a record pattern * * @param identifierList * @param boundVariablesStack * @param patternNode */ void findIdentifiersInRecordCase(List<I> identifierList, ArrayStack<V> boundVariablesStack, ParseTreeNode patternNode) { ParseTreeNode baseRecordPatternNode = patternNode.firstChild(); baseRecordPatternNode.verifyType(CALTreeParserTokenTypes.BASE_RECORD_PATTERN); // Find the name of the base record variable ParseTreeNode baseRecordPatternVarNode = baseRecordPatternNode.firstChild(); ParseTreeNode basePatternVarNameNode = null; if (baseRecordPatternVarNode != null) { switch (baseRecordPatternVarNode.getType()) { case CALTreeParserTokenTypes.VAR_ID: basePatternVarNameNode = baseRecordPatternVarNode; break; case CALTreeParserTokenTypes.UNDERSCORE: break; default: { baseRecordPatternVarNode.unexpectedParseTreeNode(); return; } } } ParseTreeNode fieldBindingVarAssignmentListNode = baseRecordPatternNode.nextSibling(); findIdentifiersInFieldBindingCase(identifierList, boundVariablesStack, fieldBindingVarAssignmentListNode, patternNode.nextSibling(), basePatternVarNameNode); } /** * Finds identifiers in a case expression containing a field binding pattern. * * @param identifierList * @param boundVariablesStack * @param fieldBindingVarAssignmentListNode * @param boundExprNode * @param basePatternVarNameNode if non-null, the parse tree node for the named variable forming the base pattern for the field binding. */ void findIdentifiersInFieldBindingCase(List<I> identifierList, ArrayStack<V> boundVariablesStack, ParseTreeNode fieldBindingVarAssignmentListNode, ParseTreeNode boundExprNode, ParseTreeNode basePatternVarNameNode) { // Set pattern variables for punned fields unpunPunnedFields(fieldBindingVarAssignmentListNode); int nVars; if (basePatternVarNameNode != null) { nVars = 1; visitLocalVarDeclarationNode(identifierList, boundVariablesStack, basePatternVarNameNode, false); pushLocalVar(boundVariablesStack, basePatternVarNameNode, identifierList); } else { nVars = 0; } for (final ParseTreeNode fieldBindingVarAssignmentNode : fieldBindingVarAssignmentListNode) { ParseTreeNode fieldNameNode = fieldBindingVarAssignmentNode.firstChild(); ParseTreeNode patternVarNode = fieldNameNode.nextSibling(); switch (patternVarNode.getType()) { case CALTreeParserTokenTypes.VAR_ID: { ++nVars; visitLocalVarDeclarationNode(identifierList, boundVariablesStack, patternVarNode, false); pushLocalVar(boundVariablesStack, patternVarNode, identifierList); break; } case CALTreeParserTokenTypes.UNDERSCORE: break; default: { patternVarNode.unexpectedParseTreeNode(); return; } } } findIdentifiersInExpr(identifierList, boundVariablesStack, boundExprNode); boundVariablesStack.popN(nVars); } /** * Patch up the parse tree so that in subsequent analysis we can assume that punning doesn't occur. * * In the case of textual field names, punning means: fieldName ---> fieldName = fieldName * In the case of numeric field names, punning means: fieldName ---> fieldName = _ * This is because something like #2 is a valid numeric field name but not a valid CAL variable name. * * @param fieldBindingVarAssignmentListNode the parse tree node for the list of field binding var assignments. */ static void unpunPunnedFields(ParseTreeNode fieldBindingVarAssignmentListNode) { fieldBindingVarAssignmentListNode.verifyType(CALTreeParserTokenTypes.FIELD_BINDING_VAR_ASSIGNMENT_LIST); for (final ParseTreeNode fieldBindingVarAssignmentNode : fieldBindingVarAssignmentListNode) { fieldBindingVarAssignmentNode.verifyType(CALTreeParserTokenTypes.FIELD_BINDING_VAR_ASSIGNMENT); ParseTreeNode fieldNameNode = fieldBindingVarAssignmentNode.firstChild(); fieldNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID, CALTreeParserTokenTypes.ORDINAL_FIELD_NAME); ParseTreeNode patternVarNode = fieldNameNode.nextSibling(); if (patternVarNode == null) { if (fieldNameNode.getType() == CALTreeParserTokenTypes.VAR_ID) { //textual field names patternVarNode = new ParseTreeNode(); patternVarNode.copyContentsFrom(fieldNameNode); } else { //numeric field names patternVarNode = new ParseTreeNode(CALTreeParserTokenTypes.UNDERSCORE, "_"); } fieldNameNode.setNextSibling(patternVarNode); } } } /** * A helper function that finds the names in each of the child expressions of parseTree. * * @param identifierList * @param boundVariablesStack * @param parseTree * */ void findIdentifiersInChildExpressions(List<I> identifierList, ArrayStack<V> boundVariablesStack, ParseTreeNode parseTree) { for (final ParseTreeNode exprNode : parseTree) { findIdentifiersInExpr(identifierList, boundVariablesStack, exprNode); } return; } /** * Helper function to find names in lambda expressions. * * @param identifierList * @param boundVariablesStack * @param patternVarListNode if non-null, the parent node of the parse tree nodes for pattern vars. * @param boundExprNode * */ final void findIdentifiersInLambda(List<I> identifierList, ArrayStack<V> boundVariablesStack, ParseTreeNode patternVarListNode, ParseTreeNode boundExprNode) { findIdentifiersInLambda(identifierList, boundVariablesStack, patternVarListNode, boundExprNode, null); } /** * Helper function to find names in lambda expressions and local function definitions. * * @param identifierList * @param boundVariablesStack * @param patternVarListNode if non-null, the parent node of the parse tree nodes for pattern vars. * @param boundExprNode * @param optionalCALDocNodeForLetDefn */ void findIdentifiersInLambda(List<I> identifierList, ArrayStack<V> boundVariablesStack, ParseTreeNode patternVarListNode, ParseTreeNode boundExprNode, ParseTreeNode optionalCALDocNodeForLetDefn) { int nVars = 0; if (patternVarListNode != null) { for (final ParseTreeNode patternVarNode : patternVarListNode) { switch (patternVarNode.getType()) { case CALTreeParserTokenTypes.VAR_ID: case CALTreeParserTokenTypes.LAZY_PARAM: case CALTreeParserTokenTypes.STRICT_PARAM: { ++nVars; // varName is now a bound variable for the body of the // lambda visitLocalVarDeclarationNode(identifierList, boundVariablesStack, patternVarNode, false); pushLocalVar(boundVariablesStack, patternVarNode, identifierList); break; } case CALTreeParserTokenTypes.UNDERSCORE : break; default : { // Unexpected type boundVariablesStack.popN(nVars); patternVarNode.unexpectedParseTreeNode(); return; } } } } // if there is a CALDoc comment that needs to be checked with the parameters in scope, do it now if (optionalCALDocNodeForLetDefn != null) { findIdentifiersInCALDocComment(identifierList, boundVariablesStack, optionalCALDocNodeForLetDefn); } findIdentifiersInExpr(identifierList, boundVariablesStack, boundExprNode); boundVariablesStack.popN(nVars); return; } /** * A helper function for finding identifiers in Let expressions. * * @param identifierList * @param boundVariablesStack * @param parseTree */ void findIdentifiersInLet(List<I> identifierList, ArrayStack<V> boundVariablesStack, ParseTreeNode parseTree) { ParseTreeNode defnListNode = parseTree.firstChild(); defnListNode.verifyType(CALTreeParserTokenTypes.LET_DEFN_LIST); int nLocalFunctions = 0; List<ParseTreeNode> typeDeclNodes = new ArrayList<ParseTreeNode>(); List<ParseTreeNode> letDefnAndLocalPatternMatchDeclNodes = new ArrayList<ParseTreeNode>(); // First, collect all the optional CALDoc nodes (for later checking) Map/*String, ParseTreeNode*/<String, ParseTreeNode> funcNamesToOptionalCALDocNodes = new HashMap<String, ParseTreeNode>(); for (final ParseTreeNode defnNode : defnListNode) { defnNode.verifyType(CALTreeParserTokenTypes.LET_DEFN, CALTreeParserTokenTypes.LET_DEFN_TYPE_DECLARATION, CALTreeParserTokenTypes.LET_PATTERN_MATCH_DECL); if (defnNode.getType() == CALTreeParserTokenTypes.LET_DEFN) { ParseTreeNode optionalCALDocNode = defnNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); ParseTreeNode localSCNameNode = optionalCALDocNode.nextSibling(); localSCNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); String scName = localSCNameNode.getText(); if (optionalCALDocNode.firstChild() != null) { funcNamesToOptionalCALDocNodes.put(scName, optionalCALDocNode); } } 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); ParseTreeNode localSCNameNode = typeDeclNode.firstChild(); localSCNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); String scName = localSCNameNode.getText(); if (optionalCALDocNode.firstChild() != null) { funcNamesToOptionalCALDocNodes.put(scName, optionalCALDocNode); } } else { // must be a LET_PATTERN_MATCH_DECL // there is no CALDoc associated with a local pattern mathc declaration } } // Then, collect all the local variable bindings for (final ParseTreeNode defnNode : defnListNode) { switch (defnNode.getType()) { case (CALTreeParserTokenTypes.LET_DEFN): { ParseTreeNode optionalCALDocNode = defnNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); ParseTreeNode localFunctionNameNode = optionalCALDocNode.nextSibling(); localFunctionNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); ++nLocalFunctions; visitLocalVarDeclarationNode(identifierList, boundVariablesStack, localFunctionNameNode, false); // functionName is a bound variable for all declarations in the 'let' // and for the expression following the 'in'. pushLocalVar(boundVariablesStack, localFunctionNameNode, identifierList); letDefnAndLocalPatternMatchDeclNodes.add(defnNode); break; } case (CALTreeParserTokenTypes.LET_DEFN_TYPE_DECLARATION): { ParseTreeNode optionalCALDocNode = defnNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); ParseTreeNode typeDeclNode = optionalCALDocNode.nextSibling(); typeDeclNode.verifyType(CALTreeParserTokenTypes.TYPE_DECLARATION); typeDeclNodes.add(typeDeclNode); break; } case CALTreeParserTokenTypes.LET_PATTERN_MATCH_DECL: { // A local pattern match declaration can declare one or more locally bound variables // We will loop through the pattern to process each one letDefnAndLocalPatternMatchDeclNodes.add(defnNode); final ParseTreeNode patternMatchPatternNode = defnNode.firstChild(); switch (patternMatchPatternNode.getType()) { case CALTreeParserTokenTypes.PATTERN_CONSTRUCTOR: { // a data cons pattern // e.g. let Cons x y = foo; ... ParseTreeNode dcNameListNode = patternMatchPatternNode.firstChild(); dcNameListNode.verifyType(CALTreeParserTokenTypes.DATA_CONSTRUCTOR_NAME_LIST, CALTreeParserTokenTypes.DATA_CONSTRUCTOR_NAME_SINGLETON); ParseTreeNode dcArgBindingsNode = dcNameListNode.nextSibling(); final ParseTreeNode dcNameNode = dcNameListNode.firstChild(); visitDataConsNameNode(identifierList, dcNameNode); switch (dcArgBindingsNode.getType()) { case CALTreeParserTokenTypes.PATTERN_VAR_LIST: // a data cons pattern with positional patterns // e.g. let Cons x y = foo; ... for (final ParseTreeNode patternVarNode : dcArgBindingsNode) { if (patternVarNode.getType() == CALTreeParserTokenTypes.VAR_ID) { ++nLocalFunctions; visitLocalVarDeclarationNode(identifierList, boundVariablesStack, patternVarNode, false); // the pattern variable is a bound variable for all declarations in the 'let' // and for the expression following the 'in'. pushLocalVar(boundVariablesStack, patternVarNode, identifierList); } } break; case CALTreeParserTokenTypes.FIELD_BINDING_VAR_ASSIGNMENT_LIST: // a data cons pattern with field-pattern pairs // e.g. let Cons {head=x, tail} = foo; ... // Set pattern variables for punned fields unpunPunnedFields(dcArgBindingsNode); for (final ParseTreeNode fieldBindingVarAssignmentNode : dcArgBindingsNode) { final ParseTreeNode fieldNameNode = fieldBindingVarAssignmentNode.firstChild(); final ParseTreeNode patternVarNode = fieldNameNode.nextSibling(); if (patternVarNode.getType() == CALTreeParserTokenTypes.VAR_ID) { ++nLocalFunctions; visitLocalVarDeclarationNode(identifierList, boundVariablesStack, patternVarNode, false); // the pattern variable is a bound variable for all declarations in the 'let' // and for the expression following the 'in'. pushLocalVar(boundVariablesStack, patternVarNode, identifierList); } } break; default: dcArgBindingsNode.unexpectedParseTreeNode(); break; } break; } case CALTreeParserTokenTypes.COLON: case CALTreeParserTokenTypes.TUPLE_CONSTRUCTOR: { // a list cons pattern and a tuple pattern can be treated in a similar way, because // in both cases the node's children is the list of patterns // list cons pattern, e.g. let a:b = foo; ... // tuple pattern, e.g. let (a, b, c) = foo; ... for (final ParseTreeNode patternVarNode : patternMatchPatternNode) { if (patternVarNode.getType() == CALTreeParserTokenTypes.VAR_ID) { ++nLocalFunctions; visitLocalVarDeclarationNode(identifierList, boundVariablesStack, patternVarNode, false); // the pattern variable is a bound variable for all declarations in the 'let' // and for the expression following the 'in'. pushLocalVar(boundVariablesStack, patternVarNode, identifierList); } } break; } case CALTreeParserTokenTypes.RECORD_PATTERN: { // a record pattern // e.g. let {_ | a, b=y} = foo; ... final ParseTreeNode baseRecordPatternNode = patternMatchPatternNode.firstChild(); baseRecordPatternNode.verifyType(CALTreeParserTokenTypes.BASE_RECORD_PATTERN); final ParseTreeNode fieldBindingVarAssignmentListNode = baseRecordPatternNode.nextSibling(); // Set pattern variables for punned fields unpunPunnedFields(fieldBindingVarAssignmentListNode); for (final ParseTreeNode fieldBindingVarAssignmentNode : fieldBindingVarAssignmentListNode) { final ParseTreeNode fieldNameNode = fieldBindingVarAssignmentNode.firstChild(); final ParseTreeNode patternVarNode = fieldNameNode.nextSibling(); if (patternVarNode.getType() == CALTreeParserTokenTypes.VAR_ID) { ++nLocalFunctions; visitLocalVarDeclarationNode(identifierList, boundVariablesStack, patternVarNode, false); // the pattern variable is a bound variable for all declarations in the 'let' // and for the expression following the 'in'. pushLocalVar(boundVariablesStack, patternVarNode, identifierList); } } break; } default: { patternMatchPatternNode.unexpectedParseTreeNode(); break; } } break; } default: { defnListNode.unexpectedParseTreeNode(); break; } } } // Then scan the type declarations for (final ParseTreeNode defnNode : typeDeclNodes) { ParseTreeNode localSCNameNode = defnNode.firstChild(); localSCNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); visitLocalVarDeclarationNode(identifierList, boundVariablesStack, localSCNameNode, true); findIdentifiersInTypeDecl(identifierList, defnNode); } // Now visit the definitions of each variable for (final ParseTreeNode defnNode : letDefnAndLocalPatternMatchDeclNodes) { if (defnNode.getType() == CALTreeParserTokenTypes.LET_DEFN) { ParseTreeNode localFunctionNameNode = defnNode.getChild(1); localFunctionNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); String scName = localFunctionNameNode.getText(); ParseTreeNode varListNode = localFunctionNameNode.nextSibling(); // get the optional CALDoc node stored in the map. // this may be the CALDoc associated with the corresponding function type declaration, or if // there is none, the one associated with this function definition itself. ParseTreeNode optionalCALDocNodeFromMap = funcNamesToOptionalCALDocNodes.get(scName); findIdentifiersInLambda(identifierList, boundVariablesStack, varListNode, varListNode.nextSibling(), optionalCALDocNodeFromMap); } else { defnNode.verifyType(CALTreeParserTokenTypes.LET_PATTERN_MATCH_DECL); final ParseTreeNode patternMatchPatternNode = defnNode.firstChild(); final ParseTreeNode patternMatchExprNode = patternMatchPatternNode.nextSibling(); findIdentifiersInLambda(identifierList, boundVariablesStack, null, patternMatchExprNode); } } ParseTreeNode exprNode = defnListNode.nextSibling(); findIdentifiersInExpr(identifierList, boundVariablesStack, exprNode); boundVariablesStack.popN(nLocalFunctions); return; } /** * Determines the identifiers in a parseTree describing a type expression. * * @param identifierList * @param parseTree */ void findIdentifiersInDeclaredTypeExpr(List<I> identifierList, ParseTreeNode parseTree) { switch (parseTree.getType()) { case CALTreeParserTokenTypes.FUNCTION_TYPE_CONSTRUCTOR : { ParseTreeNode domainNode = parseTree.firstChild(); findIdentifiersInDeclaredTypeExpr(identifierList, domainNode); findIdentifiersInDeclaredTypeExpr(identifierList, domainNode.nextSibling()); return; } case CALTreeParserTokenTypes.TUPLE_TYPE_CONSTRUCTOR : { if (parseTree.hasNoChildren()) { return; } if (parseTree.hasExactlyOneChild()) { // the type (t) is equivalent to the type t. findIdentifiersInDeclaredTypeExpr(identifierList, parseTree.firstChild()); return; } for (final ParseTreeNode componentNode : parseTree) { findIdentifiersInDeclaredTypeExpr(identifierList, componentNode); } return; } case CALTreeParserTokenTypes.LIST_TYPE_CONSTRUCTOR : { findIdentifiersInDeclaredTypeExpr(identifierList, parseTree.firstChild()); return; } case CALTreeParserTokenTypes.TYPE_APPLICATION : { for (final ParseTreeNode argNode : parseTree) { findIdentifiersInDeclaredTypeExpr(identifierList, argNode); } return; } case CALTreeParserTokenTypes.QUALIFIED_CONS : { visitTypeConsNameNode(identifierList, parseTree); return; } case CALTreeParserTokenTypes.VAR_ID : { return; } case CALTreeParserTokenTypes.RECORD_TYPE_CONSTRUCTOR : { ParseTreeNode recordVarNode = parseTree.firstChild(); recordVarNode.verifyType(CALTreeParserTokenTypes.RECORD_VAR); ParseTreeNode fieldTypeAssignmentListNode = recordVarNode.nextSibling(); fieldTypeAssignmentListNode.verifyType(CALTreeParserTokenTypes.FIELD_TYPE_ASSIGNMENT_LIST); for (final ParseTreeNode fieldTypeAssignmentNode : fieldTypeAssignmentListNode) { fieldTypeAssignmentNode.verifyType(CALTreeParserTokenTypes.FIELD_TYPE_ASSIGNMENT); ParseTreeNode fieldNameNode = fieldTypeAssignmentNode.firstChild(); ParseTreeNode typeNode = fieldNameNode.nextSibling(); findIdentifiersInDeclaredTypeExpr(identifierList, typeNode); } return; } default : { parseTree.unexpectedParseTreeNode(); } } } /** * Finds identifiers in a type declaration. * * @param identifierList * @param typeDeclarationNode * */ void findIdentifiersInTypeDecl(List<I> identifierList, ParseTreeNode typeDeclarationNode) { typeDeclarationNode.verifyType(CALTreeParserTokenTypes.TYPE_DECLARATION); ParseTreeNode functionNameNode = typeDeclarationNode.firstChild(); functionNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); ParseTreeNode typeSignatureNode = functionNameNode.nextSibling(); findIdentifiersInTypeSignature(identifierList, typeSignatureNode); return; } /** * Finds identifiers froma a class context node. * * The context specifies the type variables that are qualified by type class constraints. * * @param identifierList * @param contextListNode */ void findIdentifiersInClassContext(List<I> identifierList, ParseTreeNode contextListNode) { contextListNode.verifyType(CALTreeParserTokenTypes.CLASS_CONTEXT_LIST, CALTreeParserTokenTypes.CLASS_CONTEXT_SINGLETON, CALTreeParserTokenTypes.CLASS_CONTEXT_NOTHING); for (final ParseTreeNode contextNode : contextListNode) { contextNode.verifyType(CALTreeParserTokenTypes.CLASS_CONTEXT); ParseTreeNode typeClassNameNode = contextNode.firstChild(); typeClassNameNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS); visitClassNameNode(identifierList, typeClassNameNode); } } /** * Finds identifiers from a type context node. * * The context specifies * a. the type variables that are qualified by type class constraints. * b. the row variables that have lacks fields constraints. * * @param identifierList * @param contextListNode */ void findIdentifiersInDeclaredTypeContext(List<I> identifierList, ParseTreeNode contextListNode) { contextListNode.verifyType(CALTreeParserTokenTypes.TYPE_CONTEXT_LIST, CALTreeParserTokenTypes.TYPE_CONTEXT_NOTHING, CALTreeParserTokenTypes.TYPE_CONTEXT_SINGLETON); for (final ParseTreeNode contextNode : contextListNode) { switch (contextNode.getType()) { case CALTreeParserTokenTypes.CLASS_CONTEXT : { ParseTreeNode typeClassNameNode = contextNode.firstChild(); typeClassNameNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS); visitClassNameNode(identifierList, typeClassNameNode); break; } case CALTreeParserTokenTypes.LACKS_FIELD_CONTEXT: { break; } default: { contextNode.unexpectedParseTreeNode(); } } } return; } void findIdentifiersInFunctionDefinition(List<I> identifierList, ParseTreeNode functionNode) { functionNode.verifyType(CALTreeParserTokenTypes.TOP_LEVEL_FUNCTION_DEFN); ParseTreeNode optionalCALDocNode = functionNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); findIdentifiersInCALDocComment(identifierList, optionalCALDocNode); ParseTreeNode accessModifierNode = optionalCALDocNode.nextSibling(); ParseTreeNode functionNameNode = accessModifierNode.nextSibling(); visitFunctionOrClassMethodDefinitionNameNode(identifierList, functionNameNode); ParseTreeNode paramListNode = accessModifierNode.nextSibling().nextSibling(); paramListNode.verifyType(CALTreeParserTokenTypes.FUNCTION_PARAM_LIST); ArrayStack<V> namedArgumentsStack = ArrayStack.<V>make(); for (final ParseTreeNode varNode : paramListNode) { varNode.verifyType(CALTreeParserTokenTypes.LAZY_PARAM, CALTreeParserTokenTypes.STRICT_PARAM); //todoBI //todo-jowong this doesn't work for the AllIdentifierFinder, but this method is never called in that case //since the AllIndentifier finder happens to only be used for code-gems, and code gems do not have //top level functions. pushLocalVar(namedArgumentsStack, varNode, null); } findIdentifiersInExpr(identifierList, namedArgumentsStack, paramListNode.nextSibling()); } void findIdentifiersInFunctionDeclaration(List<I> identifierList, ParseTreeNode topLevelTypeDeclarationNode) { topLevelTypeDeclarationNode.verifyType(CALTreeParserTokenTypes.TOP_LEVEL_TYPE_DECLARATION); ParseTreeNode optionalCALDocNode = topLevelTypeDeclarationNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); findIdentifiersInCALDocComment(identifierList, optionalCALDocNode); ParseTreeNode functionTypeDeclarationNode = optionalCALDocNode.nextSibling(); ParseTreeNode functionNameNode = functionTypeDeclarationNode.firstChild(); visitFunctionOrClassMethodDefinitionNameNode(identifierList, functionNameNode); ParseTreeNode typeSignatureNode = functionNameNode.nextSibling(); findIdentifiersInTypeSignature(identifierList, typeSignatureNode); } void findIdentifiersInTypeSignature(List<I> identifierList, ParseTreeNode typeSignatureNode) { typeSignatureNode.verifyType(CALTreeParserTokenTypes.TYPE_SIGNATURE); ParseTreeNode contextListNode = typeSignatureNode.firstChild(); ParseTreeNode declarationNode = contextListNode.nextSibling(); findIdentifiersInDeclaredTypeContext(identifierList, contextListNode); findIdentifiersInDeclaredTypeExpr(identifierList, declarationNode); } void findIdentifiersInDataDeclaration(List<I> identifierList, ParseTreeNode dataDeclarationNode) { dataDeclarationNode.verifyType(CALTreeParserTokenTypes.DATA_DECLARATION); ParseTreeNode optionalCALDocNode = dataDeclarationNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); findIdentifiersInCALDocComment(identifierList, optionalCALDocNode); ParseTreeNode accessModifierNode = optionalCALDocNode.nextSibling(); ParseTreeNode typeConsNameNode = accessModifierNode.nextSibling(); visitTypeConsDefnNameNode(identifierList, typeConsNameNode); ParseTreeNode typeConsParamListNode = typeConsNameNode.nextSibling(); ParseTreeNode dataConsDefnListNode = typeConsParamListNode.nextSibling(); dataConsDefnListNode.verifyType(CALTreeParserTokenTypes.DATA_CONSTRUCTOR_DEFN_LIST); for (final ParseTreeNode dataConsDefnNode : dataConsDefnListNode) { dataConsDefnNode.verifyType(CALTreeParserTokenTypes.DATA_CONSTRUCTOR_DEFN); ParseTreeNode dataConsOptionalCALDocNode = dataConsDefnNode.firstChild(); dataConsOptionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); findIdentifiersInCALDocComment(identifierList, dataConsOptionalCALDocNode); ParseTreeNode dataConsAccessModifierNode = dataConsOptionalCALDocNode.nextSibling(); ParseTreeNode dataConsNameNode = dataConsAccessModifierNode.nextSibling(); visitDataConsDefnNameNode(identifierList, dataConsNameNode); ParseTreeNode dataConsArgListNode = dataConsNameNode.nextSibling(); for(ParseTreeNode dataConsArgNode = dataConsArgListNode.firstChild(); dataConsArgNode != null; dataConsArgNode = dataConsArgNode.nextSibling()) { dataConsArgNode.verifyType(CALTreeParserTokenTypes.DATA_CONSTRUCTOR_NAMED_ARG); // Get the arg name node. ParseTreeNode dataConsArgNameNode = dataConsArgNode.firstChild(); // the arg name doesn't become an identifier.. // Get the type node. ParseTreeNode maybePlingTypeExprNode = dataConsArgNameNode.nextSibling(); ParseTreeNode dataConsArgTypeNode; if (maybePlingTypeExprNode.getType() == CALTreeParserTokenTypes.STRICT_ARG) { dataConsArgTypeNode = maybePlingTypeExprNode.firstChild(); } else { dataConsArgTypeNode = maybePlingTypeExprNode; } findIdentifiersInDeclaredTypeExpr(identifierList, dataConsArgTypeNode); } } ParseTreeNode derivingClauseNode = dataConsDefnListNode.nextSibling(); findIndentifiersInDerivingClauseNode(identifierList, derivingClauseNode); } void findIdentifiersInTypeClassDefinition(List<I> identifierList, ParseTreeNode typeClassNode) { typeClassNode.verifyType(CALTreeParserTokenTypes.TYPE_CLASS_DEFN); ParseTreeNode optionalCALDocNode = typeClassNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); findIdentifiersInCALDocComment(identifierList, optionalCALDocNode); ParseTreeNode accessModifierNode = optionalCALDocNode.nextSibling(); ParseTreeNode classContextListNode = accessModifierNode.nextSibling(); ParseTreeNode typeClassNameNode = classContextListNode.nextSibling(); findIdentifiersInClassContext(identifierList, classContextListNode); visitClassDefnNameNode(identifierList, typeClassNameNode); ParseTreeNode typeVarNode = typeClassNameNode.nextSibling(); typeVarNode.verifyType(CALTreeParserTokenTypes.VAR_ID); ParseTreeNode classMethodListNode = typeVarNode.nextSibling(); classMethodListNode.verifyType(CALTreeParserTokenTypes.CLASS_METHOD_LIST); for (final ParseTreeNode classMethodNode : classMethodListNode) { classMethodNode.verifyType(CALTreeParserTokenTypes.CLASS_METHOD); ParseTreeNode classMethodOptionalCALDocNode = classMethodNode.firstChild(); classMethodOptionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); findIdentifiersInCALDocComment(identifierList, classMethodOptionalCALDocNode); ParseTreeNode classMethodAccessModifierNode = classMethodOptionalCALDocNode.nextSibling(); ParseTreeNode classMethodNameNode = classMethodAccessModifierNode.nextSibling(); visitFunctionOrClassMethodDefinitionNameNode(identifierList, classMethodNameNode); ParseTreeNode typeSignatureNode = classMethodNameNode.nextSibling(); findIdentifiersInTypeSignature(identifierList, typeSignatureNode); } } void findIdentifiersInInstanceDefinition(List<I> identifierList, ParseTreeNode instanceNode) { instanceNode.verifyType(CALTreeParserTokenTypes.INSTANCE_DEFN); ParseTreeNode optionalCALDocNode = instanceNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); findIdentifiersInCALDocComment(identifierList, optionalCALDocNode); ParseTreeNode instanceNameNode = optionalCALDocNode.nextSibling(); instanceNameNode.verifyType(CALTreeParserTokenTypes.INSTANCE_NAME); ParseTreeNode classContextListNode = instanceNameNode.firstChild(); findIdentifiersInClassContext(identifierList, classContextListNode); ParseTreeNode qualifiedClassNameNode = classContextListNode.nextSibling(); visitClassNameNode(identifierList, qualifiedClassNameNode); ParseTreeNode instanceTypeConsNameNode = qualifiedClassNameNode.nextSibling(); if (instanceTypeConsNameNode.getType() == CALTreeParserTokenTypes.GENERAL_TYPE_CONSTRUCTOR || instanceTypeConsNameNode.getType() == CALTreeParserTokenTypes.UNPARENTHESIZED_TYPE_CONSTRUCTOR) { ParseTreeNode qualifiedTypeConsNameNode = instanceTypeConsNameNode.firstChild(); visitTypeConsNameNode(identifierList, qualifiedTypeConsNameNode); } 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); findIdentifiersInCALDocComment(identifierList, optionalInstanceMethodCALDocNode); ParseTreeNode instanceMethodNameNode = optionalInstanceMethodCALDocNode.nextSibling(); visitInstanceMethodNameNode(identifierList, instanceMethodNameNode, qualifiedClassNameNode); ParseTreeNode resolvingFunctionNameNode = instanceMethodNameNode.nextSibling(); visitFunctionOrClassMethodNameNode(identifierList, ArrayStack.<V>make(), resolvingFunctionNameNode); } } void findIdentifiersInForeignDataDeclaration(List<I> identifierList, ParseTreeNode foreignNode) { foreignNode.verifyType(CALTreeParserTokenTypes.FOREIGN_DATA_DECLARATION); ParseTreeNode optionalCALDocNode = foreignNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); findIdentifiersInCALDocComment(identifierList, optionalCALDocNode); ParseTreeNode internalAccessModifierNode = optionalCALDocNode.nextSibling(); ParseTreeNode externalNameNode = internalAccessModifierNode.nextSibling(); ParseTreeNode externalAccessModifierNode = externalNameNode.nextSibling(); ParseTreeNode consNameNode = externalAccessModifierNode.nextSibling(); visitTypeConsDefnNameNode(identifierList, consNameNode); ParseTreeNode derivingClauseNode = consNameNode.nextSibling(); findIndentifiersInDerivingClauseNode(identifierList, derivingClauseNode); } void findIndentifiersInDerivingClauseNode(List<I> identifierList, ParseTreeNode derivingClauseNode) { if (derivingClauseNode == null) { return; } derivingClauseNode.verifyType(CALTreeParserTokenTypes.LITERAL_deriving); for (final ParseTreeNode derivingTypeClassNameNode : derivingClauseNode) { visitClassNameNode(identifierList, derivingTypeClassNameNode); } } void findIdentifiersInForeignFunctionDeclaration(List<I> identifierList, ParseTreeNode foreignSCNode) { foreignSCNode.verifyType(CALTreeParserTokenTypes.FOREIGN_FUNCTION_DECLARATION); ParseTreeNode optionalCALDocNode = foreignSCNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); findIdentifiersInCALDocComment(identifierList, optionalCALDocNode); ParseTreeNode externalNameNode = optionalCALDocNode.nextSibling(); externalNameNode.verifyType(CALTreeParserTokenTypes.STRING_LITERAL); ParseTreeNode accessModifierNode = externalNameNode.nextSibling(); ParseTreeNode typeDeclarationNode = accessModifierNode.nextSibling(); findIdentifiersInTypeDecl(identifierList, typeDeclarationNode); ParseTreeNode functionNameNode = typeDeclarationNode.firstChild(); visitFunctionOrClassMethodDefinitionNameNode(identifierList, functionNameNode); } void findIdentifiersInImportDeclarations(List<I> identifierList, ParseTreeNode importDeclarationListNode) { importDeclarationListNode.verifyType(CALTreeParserTokenTypes.IMPORT_DECLARATION_LIST); // Look through each import statement for (final ParseTreeNode importDeclarationNode : importDeclarationListNode) { importDeclarationNode.verifyType(CALTreeParserTokenTypes.LITERAL_import); ParseTreeNode importedModuleNameNode = importDeclarationNode.firstChild(); visitModuleNameNode(identifierList, importedModuleNameNode); ModuleName fullyQualifiedImportedModuleName = ModuleNameUtilities.getModuleNameFromParseTree(importedModuleNameNode); importedModuleNames.add(fullyQualifiedImportedModuleName); // Examine 'import using' clauses ParseTreeNode usingClauseNode = importedModuleNameNode.nextSibling(); if (usingClauseNode != null) { usingClauseNode.verifyType(CALTreeParserTokenTypes.LITERAL_using); for (final ParseTreeNode usingItemNode : usingClauseNode) { switch (usingItemNode.getType()) { case CALTreeParserTokenTypes.LITERAL_function: { for (final ParseTreeNode functionNameNode : usingItemNode) { functionNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); usingFunctionOrClassMethodMap.put(functionNameNode.getText(), fullyQualifiedImportedModuleName); visitUnqualifiedFunctionOrClassMethodNameNode(identifierList, functionNameNode, importedModuleNameNode); } break; } case CALTreeParserTokenTypes.LITERAL_dataConstructor: { for (final ParseTreeNode dataConstructorNameNode : usingItemNode) { dataConstructorNameNode.verifyType(CALTreeParserTokenTypes.CONS_ID); usingDataConstructorMap.put(dataConstructorNameNode.getText(), fullyQualifiedImportedModuleName); visitUnqualifiedDataConsNameNode(identifierList, dataConstructorNameNode, importedModuleNameNode); } break; } case CALTreeParserTokenTypes.LITERAL_typeConstructor: { for (final ParseTreeNode typeConstructorNameNode : usingItemNode) { typeConstructorNameNode.verifyType(CALTreeParserTokenTypes.CONS_ID); usingTypeConstructorMap.put(typeConstructorNameNode.getText(), fullyQualifiedImportedModuleName); visitUnqualifiedTypeConsNameNode(identifierList, typeConstructorNameNode, importedModuleNameNode); } break; } case CALTreeParserTokenTypes.LITERAL_typeClass: { for (final ParseTreeNode typeClassNameNode : usingItemNode) { typeClassNameNode.verifyType(CALTreeParserTokenTypes.CONS_ID); usingTypeClassMap.put(typeClassNameNode.getText(), fullyQualifiedImportedModuleName); visitUnqualifiedClassNameNode(identifierList, typeClassNameNode, importedModuleNameNode); } break; } default : { usingItemNode.unexpectedParseTreeNode(); return; } } // end switch } // end for using } // end if } // end for imports // set up the module name resolver moduleNameResolver = ModuleNameResolver.make(currentModuleName, importedModuleNames); } /** * Find the identifiers in a CALDoc comment. * @param identifierList the mutable list of identifiers used to accumulate the found identifiers. * @param optionalCALDocNode the node whose optional child represents a CALDoc comment. */ void findIdentifiersInCALDocComment(List<I> identifierList, ParseTreeNode optionalCALDocNode) { findIdentifiersInCALDocComment(identifierList, ArrayStack.<V>make(), optionalCALDocNode); } /** * Find the identifiers in a CALDoc comment. * @param identifierList the mutable list of identifiers used to accumulate the found identifiers. * @param boundVariablesStack the stack of bound variables at this point. * @param optionalCALDocNode the node whose optional child represents a CALDoc comment. */ void findIdentifiersInCALDocComment(List<I> identifierList, ArrayStack<V> boundVariablesStack, ParseTreeNode optionalCALDocNode) { optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); ParseTreeNode calDocNode = optionalCALDocNode.firstChild(); if (calDocNode == null) { return; } calDocNode.verifyType(CALTreeParserTokenTypes.CALDOC_COMMENT); ParseTreeNode descriptionNode = calDocNode.firstChild(); descriptionNode.verifyType(CALTreeParserTokenTypes.CALDOC_DESCRIPTION_BLOCK); findIdentifiersInCALDocTextBlock(identifierList, descriptionNode.firstChild()); ParseTreeNode taggedBlockListNode = descriptionNode.nextSibling(); taggedBlockListNode.verifyType(CALTreeParserTokenTypes.CALDOC_TAGGED_BLOCKS); // loop through the tagged blocks to find the identifiers contained within them. for (final ParseTreeNode taggedBlockNode : taggedBlockListNode) { switch (taggedBlockNode.getType()) { case CALTreeParserTokenTypes.CALDOC_AUTHOR_BLOCK: case CALTreeParserTokenTypes.CALDOC_RETURN_BLOCK: case CALTreeParserTokenTypes.CALDOC_DEPRECATED_BLOCK: case CALTreeParserTokenTypes.CALDOC_VERSION_BLOCK: { findIdentifiersInCALDocTextBlock(identifierList, taggedBlockNode.firstChild()); break; } case CALTreeParserTokenTypes.CALDOC_ARG_BLOCK: { ParseTreeNode argNameNode = taggedBlockNode.firstChild(); argNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID, CALTreeParserTokenTypes.ORDINAL_FIELD_NAME); visitCALDocArgNameNode(identifierList, boundVariablesStack, argNameNode); findIdentifiersInCALDocTextBlock(identifierList, argNameNode.nextSibling()); break; } case CALTreeParserTokenTypes.CALDOC_SEE_BLOCK: { findIdentifiersInCALDocSeeBlock(identifierList, taggedBlockNode.firstChild()); break; } default: throw new IllegalStateException("Unexpected parse tree node " + taggedBlockNode.toDebugString() + "."); } } } /** * Find the identifiers in a CALDoc text block. * @param identifierList the mutable list of identifiers used to accumulate the found identifiers. * @param blockNode the ParseTreeNode representing a CALDoc text block. */ void findIdentifiersInCALDocTextBlock(List<I> identifierList, ParseTreeNode blockNode) { blockNode.verifyType(CALTreeParserTokenTypes.CALDOC_TEXT); findIdentifiersInCALDocTopLevelTextSegments(identifierList, blockNode); } /** * Find the identifiers in a preformatted text segment in a CALDoc comment. * @param identifierList the mutable list of identifiers used to accumulate the found identifiers. * @param blockNode the ParseTreeNode representing a CALDoc preformatted text segment. */ void findIdentifiersInCALDocPreformattedBlock(List<I> identifierList, ParseTreeNode blockNode) { blockNode.verifyType(CALTreeParserTokenTypes.CALDOC_TEXT_PREFORMATTED_BLOCK); findIdentifiersInCALDocTopLevelTextSegments(identifierList, blockNode); } /** * Find the identifiers in a CALDoc plain text segment from the given ParseTreeNode of the type * CALDOC_TEXT_BLOCK_WITHOUT_INLINE_TAGS. There should be none. * * @param identifierList the mutable list of identifiers used to accumulate the found identifiers. * @param blockNode the ParseTreeNode representing a CALDOC_TEXT_BLOCK_WITHOUT_INLINE_TAGS. */ void findIdentifiersInCALDocTextBlockWithoutInlineTags(List<I> identifierList, ParseTreeNode blockNode) { blockNode.verifyType(CALTreeParserTokenTypes.CALDOC_TEXT_BLOCK_WITHOUT_INLINE_TAGS); for (final ParseTreeNode contentNode : blockNode) { switch (contentNode.getType()) { case CALTreeParserTokenTypes.CALDOC_TEXT_LINE: case CALTreeParserTokenTypes.CALDOC_BLANK_TEXT_LINE: case CALTreeParserTokenTypes.CALDOC_TEXT_LINE_BREAK: break; default: throw new IllegalStateException("Unexpected parse tree node " + contentNode.toDebugString() + "."); } } } /** * Find the identifiers in the children of the specified ParseTreeNode, where these child nodes represent text segments in * a CALDoc comment. * @param identifierList the mutable list of identifiers used to accumulate the found identifiers. * @param parentNodeOfSegments the ParseTreeNode whose children represent text segments in a CALDoc comment. */ void findIdentifiersInCALDocTopLevelTextSegments(List<I> identifierList, ParseTreeNode parentNodeOfSegments) { for (final ParseTreeNode contentNode : parentNodeOfSegments) { switch (contentNode.getType()) { case CALTreeParserTokenTypes.CALDOC_TEXT_LINE: case CALTreeParserTokenTypes.CALDOC_BLANK_TEXT_LINE: case CALTreeParserTokenTypes.CALDOC_TEXT_LINE_BREAK: break; case CALTreeParserTokenTypes.CALDOC_TEXT_INLINE_BLOCK: { findIdentifiersInCALDocInlineTagSegment(identifierList, contentNode); break; } default: throw new IllegalStateException("Unexpected parse tree node " + contentNode.toDebugString() + "."); } } } /** * Find the identifiers in an inline tag segment in a CALDoc comment. * @param identifierList the mutable list of identifiers used to accumulate the found identifiers. * @param inlineBlockNode the ParseTreeNode representing an inline tag segment in a CALDoc comment. */ void findIdentifiersInCALDocInlineTagSegment(List<I> identifierList, ParseTreeNode inlineBlockNode) { inlineBlockNode.verifyType(CALTreeParserTokenTypes.CALDOC_TEXT_INLINE_BLOCK); ParseTreeNode inlineTagNode = inlineBlockNode.firstChild(); switch (inlineTagNode.getType()) { case CALTreeParserTokenTypes.CALDOC_TEXT_URL: { findIdentifiersInCALDocInlineURLTagSegment(identifierList, inlineTagNode); break; } case CALTreeParserTokenTypes.CALDOC_TEXT_LINK: { findIdentifiersInCALDocInlineLinkTagSegment(identifierList, inlineTagNode); break; } case CALTreeParserTokenTypes.CALDOC_TEXT_EMPHASIZED_TEXT: { findIdentifiersInCALDocInlineEmTagSegment(identifierList, inlineTagNode); break; } case CALTreeParserTokenTypes.CALDOC_TEXT_STRONGLY_EMPHASIZED_TEXT: { findIdentifiersInCALDocInlineStrongTagSegment(identifierList, inlineTagNode); break; } case CALTreeParserTokenTypes.CALDOC_TEXT_SUPERSCRIPT_TEXT: { findIdentifiersInCALDocInlineSupTagSegment(identifierList, inlineTagNode); break; } case CALTreeParserTokenTypes.CALDOC_TEXT_SUBSCRIPT_TEXT: { findIdentifiersInCALDocInlineSubTagSegment(identifierList, inlineTagNode); break; } case CALTreeParserTokenTypes.CALDOC_TEXT_SUMMARY: { findIdentifiersInCALDocInlineSummaryTagSegment(identifierList, inlineTagNode); break; } case CALTreeParserTokenTypes.CALDOC_TEXT_CODE_BLOCK: { findIdentifiersInCALDocInlineCodeTagSegment(identifierList, inlineTagNode); break; } case CALTreeParserTokenTypes.CALDOC_TEXT_ORDERED_LIST: { findIdentifiersInCALDocInlineListTagSegment(identifierList, inlineTagNode); break; } case CALTreeParserTokenTypes.CALDOC_TEXT_UNORDERED_LIST: { findIdentifiersInCALDocInlineListTagSegment(identifierList, inlineTagNode); break; } default: throw new IllegalStateException("Unexpected parse tree node " + inlineTagNode.toDebugString() + "."); } } /** * Find the identifiers in a hyperlinkable URL in a CALDoc comment. * @param identifierList the mutable list of identifiers used to accumulate the found identifiers. * @param urlNode the ParseTreeNode representing a hyperlinkable URL in a CALDoc comment. */ void findIdentifiersInCALDocInlineURLTagSegment(List<I> identifierList, ParseTreeNode urlNode) { urlNode.verifyType(CALTreeParserTokenTypes.CALDOC_TEXT_URL); ParseTreeNode contentNode = urlNode.firstChild(); findIdentifiersInCALDocTextBlockWithoutInlineTags(identifierList, contentNode); } /** * Find the identifiers in an emphasized piece of text in a CALDoc comment. * @param identifierList the mutable list of identifiers used to accumulate the found identifiers. * @param emNode the ParseTreeNode representing an emphasized piece of text in a CALDoc comment. */ void findIdentifiersInCALDocInlineEmTagSegment(List<I> identifierList, ParseTreeNode emNode) { emNode.verifyType(CALTreeParserTokenTypes.CALDOC_TEXT_EMPHASIZED_TEXT); ParseTreeNode contentNode = emNode.firstChild(); findIdentifiersInCALDocTextBlock(identifierList, contentNode); } /** * Find the identifiers in a strongly emphasized piece of text in a CALDoc comment. * @param identifierList the mutable list of identifiers used to accumulate the found identifiers. * @param strongNode the ParseTreeNode representing a strongly emphasized piece of text in a CALDoc comment. */ void findIdentifiersInCALDocInlineStrongTagSegment(List<I> identifierList, ParseTreeNode strongNode) { strongNode.verifyType(CALTreeParserTokenTypes.CALDOC_TEXT_STRONGLY_EMPHASIZED_TEXT); ParseTreeNode contentNode = strongNode.firstChild(); findIdentifiersInCALDocTextBlock(identifierList, contentNode); } /** * Find the identifiers in a superscripted piece of text in a CALDoc comment. * @param identifierList the mutable list of identifiers used to accumulate the found identifiers. * @param supNode the ParseTreeNode representing a superscripted piece of text in a CALDoc comment. */ void findIdentifiersInCALDocInlineSupTagSegment(List<I> identifierList, ParseTreeNode supNode) { supNode.verifyType(CALTreeParserTokenTypes.CALDOC_TEXT_SUPERSCRIPT_TEXT); ParseTreeNode contentNode = supNode.firstChild(); findIdentifiersInCALDocTextBlock(identifierList, contentNode); } /** * Find the identifiers in a subscripted piece of text in a CALDoc comment. * @param identifierList the mutable list of identifiers used to accumulate the found identifiers. * @param subNode the ParseTreeNode representing a subscripted piece of text in a CALDoc comment. */ void findIdentifiersInCALDocInlineSubTagSegment(List<I> identifierList, ParseTreeNode subNode) { subNode.verifyType(CALTreeParserTokenTypes.CALDOC_TEXT_SUBSCRIPT_TEXT); ParseTreeNode contentNode = subNode.firstChild(); findIdentifiersInCALDocTextBlock(identifierList, contentNode); } /** * Find the identifiers in a "@summary" inline tag segment in a CALDoc comment. * @param identifierList the mutable list of identifiers used to accumulate the found identifiers. * @param summaryNode the ParseTreeNode representing a "@summary" inline tag segment in a CALDoc comment. */ void findIdentifiersInCALDocInlineSummaryTagSegment(List<I> identifierList, ParseTreeNode summaryNode) { summaryNode.verifyType(CALTreeParserTokenTypes.CALDOC_TEXT_SUMMARY); ParseTreeNode contentNode = summaryNode.firstChild(); findIdentifiersInCALDocTextBlock(identifierList, contentNode); } /** * Find the identifiers in a code block in a CALDoc comment from the given ParseTreeNode. * @param identifierList the mutable list of identifiers used to accumulate the found identifiers. * @param codeNode the ParseTreeNode representing a code block in a CALDoc comment. */ void findIdentifiersInCALDocInlineCodeTagSegment(List<I> identifierList, ParseTreeNode codeNode) { codeNode.verifyType(CALTreeParserTokenTypes.CALDOC_TEXT_CODE_BLOCK); ParseTreeNode contentNode = codeNode.firstChild(); findIdentifiersInCALDocPreformattedBlock(identifierList, contentNode); } /** * Find the identifiers in a list in a CALDoc comment. * @param identifierList the mutable list of identifiers used to accumulate the found identifiers. * @param listNode the ParseTreeNode representing a list in a CALDoc comment. */ void findIdentifiersInCALDocInlineListTagSegment(List<I> identifierList, ParseTreeNode listNode) { listNode.verifyType(CALTreeParserTokenTypes.CALDOC_TEXT_ORDERED_LIST, CALTreeParserTokenTypes.CALDOC_TEXT_UNORDERED_LIST); for (final ParseTreeNode itemNode : listNode) { itemNode.verifyType(CALTreeParserTokenTypes.CALDOC_TEXT_LIST_ITEM); ParseTreeNode itemContentNode = itemNode.firstChild(); findIdentifiersInCALDocTextBlock(identifierList, itemContentNode); } } /** * Find the identifiers in an inline cross-reference in a CALDoc comment. * @param identifierList the mutable list of identifiers used to accumulate the found identifiers. * @param linkNode the ParseTreeNode representing an inline cross-reference in a CALDoc comment. */ void findIdentifiersInCALDocInlineLinkTagSegment(List<I> identifierList, ParseTreeNode linkNode) { linkNode.verifyType(CALTreeParserTokenTypes.CALDOC_TEXT_LINK); ParseTreeNode linkContextNode = linkNode.firstChild(); switch (linkContextNode.getType()) { case CALTreeParserTokenTypes.CALDOC_TEXT_LINK_FUNCTION: { ParseTreeNode refNode = linkContextNode.firstChild(); refNode.verifyType( CALTreeParserTokenTypes.CALDOC_UNCHECKED_QUALIFIED_VAR, CALTreeParserTokenTypes.CALDOC_CHECKED_QUALIFIED_VAR); ParseTreeNode nameNode = refNode.firstChild(); nameNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_VAR); visitFunctionOrClassMethodNameNode(identifierList, ArrayStack.<V>make(), nameNode); break; } case CALTreeParserTokenTypes.CALDOC_TEXT_LINK_MODULE: { ParseTreeNode refNode = linkContextNode.firstChild(); 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); visitModuleNameNode(identifierList, nameNode); break; } case CALTreeParserTokenTypes.CALDOC_TEXT_LINK_DATACONS: { ParseTreeNode refNode = linkContextNode.firstChild(); refNode.verifyType( CALTreeParserTokenTypes.CALDOC_UNCHECKED_QUALIFIED_CONS, CALTreeParserTokenTypes.CALDOC_CHECKED_QUALIFIED_CONS); ParseTreeNode nameNode = refNode.firstChild(); nameNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS); visitDataConsNameNode(identifierList, nameNode); break; } case CALTreeParserTokenTypes.CALDOC_TEXT_LINK_TYPECONS: { ParseTreeNode refNode = linkContextNode.firstChild(); refNode.verifyType( CALTreeParserTokenTypes.CALDOC_UNCHECKED_QUALIFIED_CONS, CALTreeParserTokenTypes.CALDOC_CHECKED_QUALIFIED_CONS); ParseTreeNode nameNode = refNode.firstChild(); nameNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS); visitTypeConsNameNode(identifierList, nameNode); break; } case CALTreeParserTokenTypes.CALDOC_TEXT_LINK_TYPECLASS: { ParseTreeNode refNode = linkContextNode.firstChild(); refNode.verifyType( CALTreeParserTokenTypes.CALDOC_UNCHECKED_QUALIFIED_CONS, CALTreeParserTokenTypes.CALDOC_CHECKED_QUALIFIED_CONS); ParseTreeNode nameNode = refNode.firstChild(); nameNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS); visitClassNameNode(identifierList, nameNode); break; } case CALTreeParserTokenTypes.CALDOC_TEXT_LINK_WITHOUT_CONTEXT: { ParseTreeNode refNode = linkContextNode.firstChild(); findIdentifiersInCALDocCrossReferenceWithoutContext(identifierList, refNode); break; } default: { throw new IllegalStateException("Unexpected parse tree node " + linkNode.toDebugString() + "."); } } } /** * Find the identifiers in a cross-reference in a CALDoc comment that appears without a 'context' keyword. * * 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 * * @param identifierList the mutable list of identifiers used to accumulate the found identifiers. * @param refNode the ParseTreeNode representing a cross-reference in a CALDoc comment. */ void findIdentifiersInCALDocCrossReferenceWithoutContext(List<I> identifierList, ParseTreeNode refNode) { refNode.verifyType( CALTreeParserTokenTypes.CALDOC_UNCHECKED_QUALIFIED_CONS, CALTreeParserTokenTypes.CALDOC_CHECKED_QUALIFIED_CONS, CALTreeParserTokenTypes.CALDOC_UNCHECKED_QUALIFIED_VAR, CALTreeParserTokenTypes.CALDOC_CHECKED_QUALIFIED_VAR); // if the cross reference contains a qualified/unqualified variable name (i.e. one that starts with a lowercase // character), than it can only be a reference to a function/class method, and we treat it as such if (refNode.getType() == CALTreeParserTokenTypes.CALDOC_UNCHECKED_QUALIFIED_VAR || refNode.getType() == CALTreeParserTokenTypes.CALDOC_CHECKED_QUALIFIED_VAR) { ParseTreeNode nameNode = refNode.firstChild(); nameNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_VAR); visitFunctionOrClassMethodNameNode(identifierList, ArrayStack.<V>make(), nameNode); } else { // the cross reference's name starts with an uppercase character. In this case we don't know // what kind of reference it is (with respect to type constructor vs. data constructor vs. type class // vs. module name), so unless the parse tree has previously gone through name resolution and its nodes // annotated, we would have to handle the node separately as an unresolved reference without 'context'. // if the parse tree has gone through name resolution successfully, then the node would have been // annotated with information about what kind of identifier this represents. CALDocChecker.CategoryForCALDocConsNameWithoutContextCrossReference category = refNode.getCategoryForCALDocConsNameWithoutContextCrossReference(); // only visit the name if the node has been previously analysed and properly resolved (i.e. category // is not null), otherwise we do not know enough about it to assign a proper SourceIdentifier.Category // to the resulting source identifier. if (category != null) { 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); ParseTreeNode nameAfterDotNode = nameBeforeDotNode.nextSibling(); nameAfterDotNode.verifyType(CALTreeParserTokenTypes.CONS_ID); if (category == CALDocChecker.CategoryForCALDocConsNameWithoutContextCrossReference.MODULE_NAME) { // we make a shallow copy of the qualified cons node, and modify the type to be HIERARCHICAL_MODULE_NAME ParseTreeNode moduleNameNode = new ParseTreeNode(); moduleNameNode.copyContentsFrom(qualifiedConsNode); moduleNameNode.initialize(CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME, "HIERARCHICAL_MODULE_NAME"); visitModuleNameNode(identifierList, moduleNameNode); } else if (category == CALDocChecker.CategoryForCALDocConsNameWithoutContextCrossReference.DATA_CONS_NAME) { visitDataConsNameNode(identifierList, qualifiedConsNode); } else if (category == CALDocChecker.CategoryForCALDocConsNameWithoutContextCrossReference.TYPE_CONS_NAME) { visitTypeConsNameNode(identifierList, qualifiedConsNode); } else if (category == CALDocChecker.CategoryForCALDocConsNameWithoutContextCrossReference.TYPE_CLASS_NAME) { visitClassNameNode(identifierList, qualifiedConsNode); } else { // there are only 4 enumerated constants in CALDocChecker.CategoryForCALDocConsNameWithoutContextCrossReference throw new IllegalStateException(); } } else { // the category is null, and therefore we treat the node as an unresolved reference without 'context'. visitCALDocCrossReferenceWithoutContextConsNameNode(identifierList, refNode); } } } /** * Find the identifiers in a CALDoc "@see" block. * @param identifierList the mutable list of identifiers used to accumulate the found identifiers. * @param seeBlockNode the node corresponding to the "@see" block. */ void findIdentifiersInCALDocSeeBlock(List<I> identifierList, ParseTreeNode seeBlockNode) { switch (seeBlockNode.getType()) { case CALTreeParserTokenTypes.CALDOC_SEE_FUNCTION_BLOCK: { for (final ParseTreeNode refNode : seeBlockNode) { refNode.verifyType( CALTreeParserTokenTypes.CALDOC_UNCHECKED_QUALIFIED_VAR, CALTreeParserTokenTypes.CALDOC_CHECKED_QUALIFIED_VAR); ParseTreeNode nameNode = refNode.firstChild(); nameNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_VAR); visitFunctionOrClassMethodNameNode(identifierList, ArrayStack.<V>make(), nameNode); } break; } case CALTreeParserTokenTypes.CALDOC_SEE_MODULE_BLOCK: { for (final ParseTreeNode refNode : seeBlockNode) { 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); visitModuleNameNode(identifierList, nameNode); } break; } case CALTreeParserTokenTypes.CALDOC_SEE_DATACONS_BLOCK: { for (final ParseTreeNode refNode : seeBlockNode) { refNode.verifyType( CALTreeParserTokenTypes.CALDOC_UNCHECKED_QUALIFIED_CONS, CALTreeParserTokenTypes.CALDOC_CHECKED_QUALIFIED_CONS); ParseTreeNode nameNode = refNode.firstChild(); nameNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS); visitDataConsNameNode(identifierList, nameNode); } break; } case CALTreeParserTokenTypes.CALDOC_SEE_TYPECONS_BLOCK: { for (final ParseTreeNode refNode : seeBlockNode) { refNode.verifyType( CALTreeParserTokenTypes.CALDOC_UNCHECKED_QUALIFIED_CONS, CALTreeParserTokenTypes.CALDOC_CHECKED_QUALIFIED_CONS); ParseTreeNode nameNode = refNode.firstChild(); nameNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS); visitTypeConsNameNode(identifierList, nameNode); } break; } case CALTreeParserTokenTypes.CALDOC_SEE_TYPECLASS_BLOCK: { for (final ParseTreeNode refNode : seeBlockNode) { refNode.verifyType( CALTreeParserTokenTypes.CALDOC_UNCHECKED_QUALIFIED_CONS, CALTreeParserTokenTypes.CALDOC_CHECKED_QUALIFIED_CONS); ParseTreeNode nameNode = refNode.firstChild(); nameNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS); visitClassNameNode(identifierList, nameNode); } break; } case CALTreeParserTokenTypes.CALDOC_SEE_BLOCK_WITHOUT_CONTEXT: { for (final ParseTreeNode refNode : seeBlockNode) { findIdentifiersInCALDocCrossReferenceWithoutContext(identifierList, refNode); } break; } default: throw new IllegalStateException("Unexpected parse tree node " + seeBlockNode.toDebugString() + "."); } } /** * The traversal encountered a local variable declaration. * @param identifierList list of identifiers * @param boundVariablesStack * @param node variable declaration node * @param isTypeExpr whether the declaration is prior to a type expression (eg: myVar :: Double) */ void visitLocalVarDeclarationNode(List<I> identifierList, ArrayStack<V> boundVariablesStack, ParseTreeNode node, boolean isTypeExpr) { // The superclass behavior is to do nothing in this case. return; } /** * Push the declared local variable to the stack. * This method is called when a local variable binding is declared in the parsed code. * i.e. within: * - lambda declaration: "(\ arg1 -> ... )" * - let declaration: "let arg1 = ... in ..." * - case statement: "case r of arg1 -> ...; {rec | arg2 = ...} -> ...;" * - function declaration: "public testFn arg1 = ... " * * @param boundVariablesStack stack holding bound variable nodes * @param varNameNode node identifying the local variable * @param identifierList list of identifiers; this be appended with the local variable identifier */ abstract void pushLocalVar(ArrayStack<V> boundVariablesStack, ParseTreeNode varNameNode, List<I> identifierList); /** * @return comparator for sorting the returned list elements */ abstract Comparator<I> getIdentifierListComparator(); /** * Reference from the code to a module name * ex: "List.head" */ abstract void visitModuleNameNode(List<I> identifierList, ParseTreeNode moduleNameNode); /** * Reference from code to unbound top level symbol (function or class method) * ex: "add 1 2" **/ abstract void visitFunctionOrClassMethodNameNode(List<I> identifierList, ArrayStack<V> boundVariablesStack, ParseTreeNode qualifiedNode); /** * Reference to class name * ex: "x :: Num a -> a;" */ abstract void visitClassNameNode(List<I> identifierList, ParseTreeNode qualifiedNode); /** * Reference to a class definition name * ex: "public class Bounded a where" */ abstract void visitClassDefnNameNode(List<I> identifierList, ParseTreeNode qualifiedNode); /** * Reference to a type constructor definition name * ex: "data public Range a" */ abstract void visitTypeConsDefnNameNode(List<I> identifierList, ParseTreeNode typeConsNameNode); /** * Reference to a data constructor definition name * ex: "Public LT" */ abstract void visitDataConsDefnNameNode(List<I> identifierList, ParseTreeNode dataConsNameNode); /** * Reference to data constructor name * ex: "True" */ abstract void visitDataConsNameNode(List<I> identifierList, ParseTreeNode qualifiedNode); /** * Reference to type constructor name * ex: "x :: Boolean" */ abstract void visitTypeConsNameNode(List<I> identifierList, ParseTreeNode qualifiedNode); /** * Reference to instance method name * ex: "instanceMethodName = resolvingFunctionName;" */ abstract void visitInstanceMethodNameNode(List<I> identifierList, ParseTreeNode instanceMethodNameNode, ParseTreeNode qualifiedClassNameNode); /** * Reference to function name in function or class method definition * ex: both occurrences of mySC in "mySC :: Boolean; public mySC = 5.0" */ abstract void visitFunctionOrClassMethodDefinitionNameNode(List<I> identifierList, ParseTreeNode qualifiedNode); /** * Reference from code specifically to unqualified function (ie: from import declaration) */ abstract void visitUnqualifiedFunctionOrClassMethodNameNode(List<I> identifierList, ParseTreeNode unqualifiedNameNode, ParseTreeNode moduleNameNode); /** * Reference from code specifically to unqualified class name (ie: from import declaration) */ abstract void visitUnqualifiedClassNameNode(List<I> identifierList, ParseTreeNode unqualifiedNameNode, ParseTreeNode moduleNameNode); /** * Reference from code specifically to unqualified data constructor (ie: from import declaration) */ abstract void visitUnqualifiedDataConsNameNode(List<I> identifierList, ParseTreeNode unqualifiedNameNode, ParseTreeNode moduleNameNode); /** * Reference from code specifically to unqualified type constructor (ie: from import declaration) */ abstract void visitUnqualifiedTypeConsNameNode(List<I> identifierList, ParseTreeNode unqualifiedNameNode, ParseTreeNode moduleNameNode); /** * Reference to an argument from a CALDoc "@arg" block. */ abstract void visitCALDocArgNameNode(List<I> identifierList, ArrayStack<V> boundVariablesStack, ParseTreeNode argNameNode); /** * A cross reference in a CALDoc comment that starts with an uppercase character and appears without a context keyword. * ex: "{at-link Nothing at-}" */ abstract void visitCALDocCrossReferenceWithoutContextConsNameNode(List <I>identifierList, ParseTreeNode refNode); /** * Parse the code text and return the list of identifiers that occur within it. * The list is ordered as dictated by the getIdentifierListComparator(). * * This method is intended to be the entry point for traversing unparsed code expressions. * * @param parser token parser with stream set to the expression text * @param treeParser tree parser to validate the parsed stream * @param compilerLogger the message logger associated with the parser and the treeParser * @param moduleName name of the module this expression belongs to * @param moduleNameResolver the module name resolver for the module to which this expression belongs. * @return List of identifiers encountered in the expression; null if problem occurs during parsing */ List<I> findIdentifiersInUnparsedExpression(CALParser parser, CALTreeParser treeParser, CompilerMessageLogger compilerLogger, ModuleName moduleName, ModuleNameResolver moduleNameResolver) { if ((parser == null) || (treeParser == null) || (moduleName == null)) { throw new NullPointerException(); } final int oldNErrorsLogged = logger.getNErrors(); try { ParseTreeNode exprNode = parseExpression(parser, treeParser, compilerLogger); if (exprNode == null) { return null; } return findIdentifiersInCodeExpression(exprNode, moduleName, moduleNameResolver); } catch (Exception e) { // An error has occurred. This is likely due to SourceIdentifierFinder // traversal algorithm being out of sync with current CAL grammar. try { if (logger.getNErrors() <= oldNErrorsLogged) { //internal coding error logger.logMessage(new CompilerMessage(new MessageKind.Fatal.InternalCodingError(), e)); } } catch (AbortCompilation ace) { // Yeah, yeah, we know //raiseError will throw a AbortCompilation since a FATAL message was sent. } return null; } catch (AbortCompilation e) { // An error has occurred. This is likely due to SourceIdentifierFinder // traversal algorithm being out of sync with current CAL grammar. return null; } } /** * Parse the module source and return the list of identifiers that occur within it. * The list is ordered as dictated by the getIdentifierListComparator(). * * This method is intended to be the entry point for traversing unparsed module sources. * Note: Any messages held by the compiler will be cleared by this method. * * @param parser token parser with stream set to the module source text * @param treeParser tree parser to validate the parsed stream * @return List of identifiers encountered in the expression; null if problem occurs during parsing */ List<I> findIdentifiersInUnparsedModule(CALParser parser, CALTreeParser treeParser) { if ((parser == null) || (treeParser == null)) { throw new NullPointerException(); } final int oldNErrorsLogged = logger.getNErrors(); try { ParseTreeNode moduleDefNode = parseModule(parser, treeParser); if (moduleDefNode == null) { return null; } return findIdentifiersInModule(moduleDefNode); } catch (Exception e) { // An error has occurred. This is likely due to SourceIdentifierFinder // traversal algorithm being out of sync with current CAL grammar. try { if (logger.getNErrors() <= oldNErrorsLogged) { //internal coding error logger.logMessage(new CompilerMessage(new MessageKind.Fatal.InternalCodingError(), e)); } } catch (AbortCompilation ace) { // Yeah, yeah, we know //raiseError will throw a AbortCompilation since a FATAL message was sent. } return null; } catch (AbortCompilation e) { // An error has occurred. This is likely due to SourceIdentifierFinder // traversal algorithm being out of sync with current CAL grammar. return null; } } /** * Parse the specified expression text and return the parset tree node representing it. * * @param parser * @param treeParser * @param compilerLogger the message logger associated with the parser and the treeParser * @return parse tree node representing expression, or null if error */ private ParseTreeNode parseExpression(CALParser parser, CALTreeParser treeParser, CompilerMessageLogger compilerLogger) { if ((parser == null) || (treeParser == null)) { throw new NullPointerException(); } int nOldErrors = logger.getNErrors(); int nOldCompilerErrors = compilerLogger.getNErrors(); // Make an AST for the expression // Call the parser to parse just an expression for us try { try { parser.codeGemExpr(); } catch (antlr.RecognitionException e) { // Recognition (syntax) error final SourceRange sourceRange = CALParser.makeSourceRangeFromException(e); // SourceIdentifierFinder: Syntax error logger.logMessage(new CompilerMessage(sourceRange, new MessageKind.Error.SyntaxError(), e)); return null; } catch (antlr.TokenStreamException e) { // Failed to lex // Bad token stream // SourceIdentifierFinder: Bad token stream logger.logMessage(new CompilerMessage(new MessageKind.Error.BadTokenStream(), e)); return null; } } catch (AbortCompilation ace) { //raiseError may throw a AbortCompilation if more than the maximum number of errors has been generated, return null; } // If there are any compiler messages at error level or greater, there was a parse error if (compilerLogger.getNErrors() > nOldCompilerErrors || logger.getNErrors() > nOldErrors) { return null; } // Walk the parse tree as a sanity check on the generated AST and of the tree parser ParseTreeNode exprNode = (ParseTreeNode) parser.getAST(); if (exprNode == null) { // SourceIdentifierFinder: Unable to generate expression tree logger.logMessage(new CompilerMessage(new MessageKind.Error.UnableToGenerateExpressionTree())); return null; } try { treeParser.codeGemExpr(exprNode); } catch (antlr.RecognitionException e) { // SourceIdentifierFinder: Failed validation AST parse: {e.toString()} logger.logMessage(new CompilerMessage(new MessageKind.Error.FailedValidationASTParse(e.toString()), e)); return null; } catch (NullPointerException e) { // Something nasty happened, possibly a bad/nonexistent AST // So, we failed to parse // SourceIdentifierFinder: Bad AST tree: logger.logMessage(new CompilerMessage(new MessageKind.Error.BadASTTree())); return null; } return exprNode; } /** * Parse the specified module source and return a reference to the parse tree node representing it * Note: Any messages held by the logger will be cleared. * * @param parser * @param treeParser * @return parse tree node representing module, or null if error */ private ParseTreeNode parseModule(CALParser parser, CALTreeParser treeParser) { if ((parser == null) || (treeParser == null)) { throw new NullPointerException(); } int nOldErrors = logger.getNErrors(); // Make an AST for the expression // Call the parser to parse just an expression for us try { try { parser.module(); } catch (antlr.RecognitionException e) { // Recognition (syntax) error final SourceRange sourceRange = CALParser.makeSourceRangeFromException(e); // SourceIdentifierFinder: Syntax error logger.logMessage(new CompilerMessage(sourceRange, new MessageKind.Error.SyntaxError(), e)); return null; } catch (antlr.TokenStreamException e) { // Failed to lex // SourceIdentifierFinder: Bad token stream logger.logMessage(new CompilerMessage(new MessageKind.Error.BadTokenStream(), e)); return null; } } catch (AbortCompilation ace) { //raiseError may throw a AbortCompilation if more than the maximum number of errors has been generated, return null; } // If there are any compiler messages at error level or greater, there was a parse error if (logger.getNErrors() > nOldErrors) { return null; } // Walk the parse tree as a sanity check on the generated AST and of the tree parser ParseTreeNode moduleDefNode = (ParseTreeNode) parser.getAST(); if (moduleDefNode == null) { logger.logMessage(new CompilerMessage(new MessageKind.Error.UnableToGenerateExpressionTree())); return null; } try { treeParser.module(moduleDefNode); } catch (antlr.RecognitionException e) { // Failed to parse AST logger.logMessage(new CompilerMessage(new MessageKind.Error.FailedValidationASTParse(e.toString()), e)); return null; } catch (NullPointerException e) { // Something nasty happened, possibly a bad/nonexistent AST // So, we failed to parse logger.logMessage(new CompilerMessage(new MessageKind.Error.BadASTTree())); return null; } return moduleDefNode; } /** * This class is responsible for locating all references to identifiers which may be local * arguments, functions, data constructors, type classes and type constructors defined * within an external or the local module. * * For each function in a module, an identifier appearing within its * defining body expression is a: * -(non built-in, non foreign) function defined within the same module * -(reference to a) function defined within a different module * -built-in function * -class method name * -local function defined using a let * -pattern variable i.e. an argument variable, a variable from a case * pattern binding, or a lambda bound variable. * -data constructor, type constructor, or type class defined in the same module * -data constructor, type constructor, or type class defined within a different module * -foreign function. * * LIMITATION (jowong): This class will not create SourceIdentifiers for @see/@link references that * are "constructor names" appearing without the 'context' keyword, because no name resolution is * performed and thus the targets of these references remain ambiguous. * * @author Iulian Radu */ static final class AllIdentifierFinder extends SourceIdentifierFinder<SourceIdentifier, SourceIdentifier> { AllIdentifierFinder() { super(); } /** * Since a module name is not really considered an identifier, we don't add it to the identifierList */ @Override void visitModuleNameNode(List<SourceIdentifier> identifierList, ParseTreeNode moduleNameNode) { moduleNameNode.verifyType(CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME, CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME_EMPTY_QUALIFIER); } /** * Adds the data constructor name to the identifierList. */ @Override void visitDataConsNameNode(List<SourceIdentifier> identifierList, ParseTreeNode qualifiedNode) { qualifiedNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS); ParseTreeNode moduleNameNode = qualifiedNode.firstChild(); visitModuleNameNode(identifierList, moduleNameNode); ModuleName rawModuleName = ModuleNameUtilities.getModuleNameOrNullFromParseTree(moduleNameNode); ModuleName moduleName = getModuleNameResolver().resolve(rawModuleName).getResolvedModuleName(); ParseTreeNode dataConsNameNode = moduleNameNode.nextSibling(); String dataConsName = dataConsNameNode.getText(); if ((moduleName == null) && (getModuleForImportedFunctionOrClassMethod(dataConsName) != null)) { moduleName = getModuleForImportedFunctionOrClassMethod(dataConsName); } ModuleName minimallyQualifiedModuleName = getModuleNameResolver().getMinimallyQualifiedModuleName(moduleName); identifierList.add(new SourceIdentifier.Qualifiable(dataConsName, dataConsNameNode.getSourceRange(), SourceIdentifier.Category.DATA_CONSTRUCTOR, rawModuleName, moduleName, minimallyQualifiedModuleName, (rawModuleName == null ? null : moduleNameNode.getAssemblySourceRange()))); return; } /** * Adds the instance method name to the identifierList. */ @Override void visitInstanceMethodNameNode(List<SourceIdentifier> identifierList, ParseTreeNode instanceMethodNameNode, ParseTreeNode qualifiedClassNameNode) { qualifiedClassNameNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_VAR); ParseTreeNode moduleNameNode = qualifiedClassNameNode.firstChild(); visitModuleNameNode(identifierList, moduleNameNode); ModuleName rawModuleName = ModuleNameUtilities.getModuleNameOrNullFromParseTree(moduleNameNode); ModuleName moduleName = getModuleNameResolver().resolve(rawModuleName).getResolvedModuleName(); ParseTreeNode classNameNode = moduleNameNode.nextSibling(); String className = classNameNode.getText(); instanceMethodNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); String instanceMethodName = instanceMethodNameNode.getText(); if ((moduleName == null) && (getModuleForImportedTypeClass(className) != null)) { moduleName = getModuleForImportedTypeClass(className); } ModuleName minimallyQualifiedModuleName = getModuleNameResolver().getMinimallyQualifiedModuleName(moduleName); identifierList.add(new SourceIdentifier.Qualifiable(instanceMethodName, instanceMethodNameNode.getSourceRange(), SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD, rawModuleName, moduleName, minimallyQualifiedModuleName, (rawModuleName == null ? null : moduleNameNode.getAssemblySourceRange()))); return; } /** * Adds the class name to the identifierList. */ @Override void visitClassNameNode(List<SourceIdentifier> identifierList, ParseTreeNode qualifiedNode) { qualifiedNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS); ParseTreeNode moduleNameNode = qualifiedNode.firstChild(); visitModuleNameNode(identifierList, moduleNameNode); ModuleName rawModuleName = ModuleNameUtilities.getModuleNameOrNullFromParseTree(moduleNameNode); ModuleName moduleName = getModuleNameResolver().resolve(rawModuleName).getResolvedModuleName(); ParseTreeNode classNameNode = moduleNameNode.nextSibling(); String className = classNameNode.getText(); if ((moduleName == null) && (getModuleForImportedTypeClass(className) != null)) { moduleName = getModuleForImportedTypeClass(className); } ModuleName minimallyQualifiedModuleName = getModuleNameResolver().getMinimallyQualifiedModuleName(moduleName); identifierList.add(new SourceIdentifier.Qualifiable(className, classNameNode.getSourceRange(), SourceIdentifier.Category.TYPE_CLASS, rawModuleName, moduleName, minimallyQualifiedModuleName, (rawModuleName == null ? null : moduleNameNode.getAssemblySourceRange()))); return; } /** * Adds the type name to the identifierList. */ @Override void visitTypeConsNameNode(List<SourceIdentifier> identifierList, ParseTreeNode qualifiedNode) { qualifiedNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS); ParseTreeNode moduleNameNode = qualifiedNode.firstChild(); visitModuleNameNode(identifierList, moduleNameNode); ModuleName rawModuleName = ModuleNameUtilities.getModuleNameOrNullFromParseTree(moduleNameNode); ModuleName moduleName = getModuleNameResolver().resolve(rawModuleName).getResolvedModuleName(); ParseTreeNode typeNameNode = moduleNameNode.nextSibling(); String typeName = typeNameNode.getText(); if ((moduleName == null) && (getModuleForImportedTypeConstructor(typeName) != null)) { moduleName = getModuleForImportedTypeConstructor(typeName); } ModuleName minimallyQualifiedModuleName = getModuleNameResolver().getMinimallyQualifiedModuleName(moduleName); identifierList.add(new SourceIdentifier.Qualifiable(typeName, typeNameNode.getSourceRange(), SourceIdentifier.Category.TYPE_CONSTRUCTOR, rawModuleName, moduleName, minimallyQualifiedModuleName, (rawModuleName == null ? null : moduleNameNode.getAssemblySourceRange()))); return; } /** * Adds the function/class method name to the identifierList if it is not defined in the bound * variables stack. */ @Override void visitFunctionOrClassMethodNameNode(List<SourceIdentifier> identifierList, ArrayStack<SourceIdentifier> boundVariablesStack, ParseTreeNode qualifiedNode) { qualifiedNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_VAR); ParseTreeNode moduleNameNode = qualifiedNode.firstChild(); visitModuleNameNode(identifierList, moduleNameNode); ModuleName rawModuleName = ModuleNameUtilities.getModuleNameOrNullFromParseTree(moduleNameNode); ModuleName moduleName = getModuleNameResolver().resolve(rawModuleName).getResolvedModuleName(); ParseTreeNode varNameNode = moduleNameNode.nextSibling(); String varName = varNameNode.getText(); if ((moduleName == null) || (moduleName.equals(getCurrentModuleName()))) { SourceIdentifier definitionIdentifier = getDefinitionFromStack(varName, boundVariablesStack); if (definitionIdentifier != null) { // Variable is pattern bound (i.e. an argument variable, a lambda bound // variable or a variable bound by a case alternative). Its module is the current module. identifierList.add(new SourceIdentifier.Qualifiable(varName, varNameNode.getSourceRange(), SourceIdentifier.Category.LOCAL_VARIABLE, rawModuleName, getCurrentModuleName(), getModuleNameResolver().getMinimallyQualifiedModuleName(getCurrentModuleName()), (rawModuleName == null ? null : moduleNameNode.getSourceRange()), definitionIdentifier)); return; } } if ((moduleName == null) && (getModuleForImportedFunctionOrClassMethod(varName) != null)) { moduleName = getModuleForImportedFunctionOrClassMethod(varName); } ModuleName minimallyQualifiedModuleName = getModuleNameResolver().getMinimallyQualifiedModuleName(moduleName); identifierList.add(new SourceIdentifier.Qualifiable(varName, varNameNode.getSourceRange(), SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD, rawModuleName, moduleName, minimallyQualifiedModuleName, (rawModuleName == null ? null : moduleNameNode.getAssemblySourceRange()))); return; } @Override void visitLocalVarDeclarationNode(List<SourceIdentifier> identifierList, ArrayStack<SourceIdentifier> boundVariablesStack, ParseTreeNode varNameNode, boolean isTypeExpr) { if (isTypeExpr) { // Declarations of types of local variables are treated as reference the local variable declaration String varName = varNameNode.getText(); SourceIdentifier definitionIdentifier = getDefinitionFromStack(varName, boundVariablesStack); if (definitionIdentifier != null) { identifierList.add(new SourceIdentifier.Qualifiable(varName, varNameNode.getSourceRange(), SourceIdentifier.Category.LOCAL_VARIABLE, null, getCurrentModuleName(), getModuleNameResolver().getMinimallyQualifiedModuleName(getCurrentModuleName()), null, definitionIdentifier)); return; } } } /** * Adds the unqualified function/class method name to the identifierList. */ @Override void visitUnqualifiedFunctionOrClassMethodNameNode(List<SourceIdentifier> identifierList, ParseTreeNode unqualifiedNameNode, ParseTreeNode moduleNameNode) { unqualifiedNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); String functionName = unqualifiedNameNode.getText(); ModuleName rawModuleName = null; ModuleName moduleName = null; if (moduleNameNode != null) { rawModuleName = ModuleNameUtilities.getModuleNameOrNullFromParseTree(moduleNameNode); moduleName = getModuleNameResolver().resolve(rawModuleName).getResolvedModuleName(); } if (moduleName == null) { rawModuleName = null; if (getModuleForImportedFunctionOrClassMethod(functionName) != null) { moduleName = getModuleForImportedFunctionOrClassMethod(functionName); } else { moduleName = getCurrentModuleName(); } } ModuleName minimallyQualifiedModuleName = getModuleNameResolver().getMinimallyQualifiedModuleName(moduleName); identifierList.add(new SourceIdentifier.Qualifiable(functionName, unqualifiedNameNode.getSourceRange(), SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD, rawModuleName, moduleName, minimallyQualifiedModuleName, (moduleNameNode == null ? null : moduleNameNode.getAssemblySourceRange()))); } /** * Adds the unqualified type class name to the identifierList. */ @Override void visitUnqualifiedClassNameNode(List<SourceIdentifier> identifierList, ParseTreeNode unqualifiedNameNode, ParseTreeNode moduleNameNode) { unqualifiedNameNode.verifyType(CALTreeParserTokenTypes.CONS_ID); String className = unqualifiedNameNode.getText(); ModuleName rawModuleName = null; ModuleName moduleName = null; if (moduleNameNode != null) { rawModuleName = ModuleNameUtilities.getModuleNameOrNullFromParseTree(moduleNameNode); moduleName = getModuleNameResolver().resolve(rawModuleName).getResolvedModuleName(); } if (moduleName == null) { rawModuleName = null; if (getModuleForImportedTypeClass(className) != null) { moduleName = getModuleForImportedTypeClass(className); } else { moduleName = getCurrentModuleName(); } } ModuleName minimallyQualifiedModuleName = getModuleNameResolver().getMinimallyQualifiedModuleName(moduleName); identifierList.add(new SourceIdentifier.Qualifiable(className, unqualifiedNameNode.getSourceRange(), SourceIdentifier.Category.TYPE_CLASS, rawModuleName, moduleName, minimallyQualifiedModuleName, (moduleNameNode == null ? null : moduleNameNode.getAssemblySourceRange()))); } /** * Adds the unqualified data constructor name to the identifierList. */ @Override void visitUnqualifiedDataConsNameNode(List<SourceIdentifier> identifierList, ParseTreeNode unqualifiedNameNode, ParseTreeNode moduleNameNode) { unqualifiedNameNode.verifyType(CALTreeParserTokenTypes.CONS_ID); String dataConsName = unqualifiedNameNode.getText(); ModuleName rawModuleName = null; ModuleName moduleName = null; if (moduleNameNode != null) { rawModuleName = ModuleNameUtilities.getModuleNameOrNullFromParseTree(moduleNameNode); moduleName = getModuleNameResolver().resolve(rawModuleName).getResolvedModuleName(); } if (moduleName == null) { rawModuleName = null; if (getModuleForImportedDataConstructor(dataConsName) != null) { moduleName = getModuleForImportedDataConstructor(dataConsName); } else { moduleName = getCurrentModuleName(); } } ModuleName minimallyQualifiedModuleName = getModuleNameResolver().getMinimallyQualifiedModuleName(moduleName); identifierList.add(new SourceIdentifier.Qualifiable(dataConsName, unqualifiedNameNode.getSourceRange(), SourceIdentifier.Category.DATA_CONSTRUCTOR, rawModuleName, moduleName, minimallyQualifiedModuleName, (moduleNameNode == null ? null : moduleNameNode.getAssemblySourceRange()))); } /** * Adds the unqualified type constructor method name to the identifierList. */ @Override void visitUnqualifiedTypeConsNameNode(List<SourceIdentifier> identifierList, ParseTreeNode unqualifiedNameNode, ParseTreeNode moduleNameNode) { unqualifiedNameNode.verifyType(CALTreeParserTokenTypes.CONS_ID); String typeConsName = unqualifiedNameNode.getText(); ModuleName rawModuleName = null; ModuleName moduleName = null; if (moduleNameNode != null) { rawModuleName = ModuleNameUtilities.getModuleNameOrNullFromParseTree(moduleNameNode); moduleName = getModuleNameResolver().resolve(rawModuleName).getResolvedModuleName(); } if (moduleName == null) { rawModuleName = null; if (getModuleForImportedTypeConstructor(typeConsName) != null) { moduleName = getModuleForImportedTypeConstructor(typeConsName); } else { moduleName = getCurrentModuleName(); } } ModuleName minimallyQualifiedModuleName = getModuleNameResolver().getMinimallyQualifiedModuleName(moduleName); identifierList.add(new SourceIdentifier.Qualifiable(typeConsName, unqualifiedNameNode.getSourceRange(), SourceIdentifier.Category.TYPE_CONSTRUCTOR, rawModuleName, moduleName, minimallyQualifiedModuleName, (moduleNameNode == null ? null : moduleNameNode.getAssemblySourceRange()))); } /** * Adds the function name from its definition to the identifierList. */ @Override void visitFunctionOrClassMethodDefinitionNameNode(List<SourceIdentifier> identifierList, ParseTreeNode functionNameNode) { functionNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); String functionName = functionNameNode.getText(); identifierList.add(new SourceIdentifier.Qualifiable(functionName, functionNameNode.getSourceRange(), SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD, null, getCurrentModuleName(), getModuleNameResolver().getMinimallyQualifiedModuleName(getCurrentModuleName()), null)); } /** * Adds the class name from its definition to the identifierList. */ @Override void visitClassDefnNameNode(List<SourceIdentifier> identifierList, ParseTreeNode typeClassNameNode) { typeClassNameNode.verifyType(CALTreeParserTokenTypes.CONS_ID); String className = typeClassNameNode.getText(); identifierList.add(new SourceIdentifier.Qualifiable(className, typeClassNameNode.getSourceRange(), SourceIdentifier.Category.TYPE_CLASS, null, getCurrentModuleName(), getModuleNameResolver().getMinimallyQualifiedModuleName(getCurrentModuleName()), null)); } /** * Adds the type constructor name from its definition to the identifierList. */ @Override void visitTypeConsDefnNameNode(List<SourceIdentifier> identifierList, ParseTreeNode typeConsNameNode) { typeConsNameNode.verifyType(CALTreeParserTokenTypes.CONS_ID); String typeConsName = typeConsNameNode.getText(); identifierList.add(new SourceIdentifier.Qualifiable(typeConsName, typeConsNameNode.getSourceRange(), SourceIdentifier.Category.TYPE_CONSTRUCTOR, null, getCurrentModuleName(), getModuleNameResolver().getMinimallyQualifiedModuleName(getCurrentModuleName()), null)); } /** * Adds the data constructor name from its definition to the identifierList. */ @Override void visitDataConsDefnNameNode(List<SourceIdentifier> identifierList, ParseTreeNode dataConsNameNode) { dataConsNameNode.verifyType(CALTreeParserTokenTypes.CONS_ID); String dataConsName = dataConsNameNode.getText(); identifierList.add(new SourceIdentifier.Qualifiable(dataConsName, dataConsNameNode.getSourceRange(), SourceIdentifier.Category.DATA_CONSTRUCTOR, null, getCurrentModuleName(), getModuleNameResolver().getMinimallyQualifiedModuleName(getCurrentModuleName()), null)); } /** * Adds the argument name in a CALDoc "@arg" block to the identifierList. */ @Override void visitCALDocArgNameNode(List<SourceIdentifier> identifierList, ArrayStack<SourceIdentifier> boundVariablesStack, ParseTreeNode argNameNode) { argNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID, CALTreeParserTokenTypes.ORDINAL_FIELD_NAME); if (argNameNode.getType() == CALTreeParserTokenTypes.ORDINAL_FIELD_NAME) { return; } String varName = argNameNode.getText(); SourceIdentifier definitionIdentifier = getDefinitionFromStack(varName, boundVariablesStack); if (definitionIdentifier != null) { // Variable is pattern bound (i.e. an argument variable, a lambda bound // variable or a variable bound by a case alternative). Its module is the current module. identifierList.add(new SourceIdentifier.Qualifiable(varName, argNameNode.getSourceRange(), SourceIdentifier.Category.LOCAL_VARIABLE, null, getCurrentModuleName(), getModuleNameResolver().getMinimallyQualifiedModuleName(getCurrentModuleName()), null, definitionIdentifier)); } } /** * For a CALDoc cross reference that is unresolved, we cannot tell what SourceIdentifier.Category it belongs to. * For this reason, we cannot do anything here without further information, and thus we skip processing this node. * * todo-jowong This is an area that can be improved upon: the proper handling of these cross references by * subclasses of SourceIdentifierFinder as well as their clients. */ @Override void visitCALDocCrossReferenceWithoutContextConsNameNode(List<SourceIdentifier> identifierList, ParseTreeNode refNode) {} /** * The local variable stack contains identifiers for local variable definitions. */ @Override void pushLocalVar(ArrayStack<SourceIdentifier> boundVariablesStack, ParseTreeNode varNameNode, List<SourceIdentifier> identifierList) { String varName = varNameNode.getText(); SourceIdentifier definition = new SourceIdentifier.Qualifiable(varName, varNameNode.getSourceRange(), SourceIdentifier.Category.LOCAL_VARIABLE_DEFINITION, null, getCurrentModuleName(), getModuleNameResolver().getMinimallyQualifiedModuleName(getCurrentModuleName()), null); boundVariablesStack.push(definition); identifierList.add(definition); } /** The identifier list contains SourceIdentifier objects */ @Override Comparator<SourceIdentifier> getIdentifierListComparator() { return SourceIdentifier.compareByStartPosition; } /** * Returns the definition of the specified variable, if it is defined on stack * * @param varName variable name to search on the stack * @param boundVariablesStack stack to search * @return parse tree node of the definition, or null if this is not locally bound */ SourceIdentifier getDefinitionFromStack(String varName, ArrayStack<SourceIdentifier> boundVariablesStack) { for (int i = boundVariablesStack.size() - 1; i >= 0; i--) { SourceIdentifier boundVarNode = boundVariablesStack.get(i); if (boundVarNode.getName().equals(varName)) { return boundVarNode; } } return null; } } }