/* * 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. */ /* * FreeVariableFinder.java * Creation date: (February 6, 2001) * By: Bo Ilic */ package org.openquark.cal.compiler; import java.math.BigInteger; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.openquark.cal.util.ArrayStack; import org.openquark.cal.util.Graph; import org.openquark.cal.util.Vertex; import org.openquark.cal.util.VertexBuilder; import org.openquark.cal.util.VertexBuilderList; /** * Used for finding free variables in global function, lambda definition or local * function definitions. Free variables, in general, are the variables upon which * the given expression or definition actually depends on. * <p> * For each function in a module, an identifier appearing within its defining * body expression is a: * <ul> * <li>(non built-in, non foreign) function defined within the same module * <li>(reference to a) function defined within a different module * <li>built-in function * <li>class method name * <li>local function defined using a let * <li>pattern variable i.e. an argument variable, a variable from a case pattern binding, or * a lambda bound variable. * <li>data constructor defined in the same module * <li>data constructor defined within a different module * <li>foreign function. * </ul> * <p> * This class is responsible for augmenting the parse tree of unqualified functions and * data constructors to explicitly fill in the module name, so that further analysis can assume * that all identifier names are fully qualified. * <p> * It is also responsible for reporting an error if an identifier does not exist in one of the * categories above. * <p> * It isolates the non built-in, non foreign defined within the same module, * which is needed for dependency analysis. * <p> * It augments data constructor field selection expression nodes with the qualified var which would * appear in the rhs of the alt for a corresponding case expression. * <p> * It converts unused pattern variables in case expressions to wildcard (_) patterns. * This is primarily an optimization for the runtime. * <p> * It checks and desugars local pattern match declarations (e.g. let (x, y, z) = foo; in ...) via the helper class * {@link LocalPatternMatchDeclarationChecker}. * <p> * It provides module unique names for all the local bound variables appearing in the function's definition. * For example, f x = 2.0 + x would be converted to f f$x$1 = 2.0 + f$x$1. This makes latter compiler passes, * in particular Johnsson style lambda lifting much easier. * <p> * In some ways this class could more generally be called the "StaticAnalyzer" because it detects certain * simple "static" errors that can be reported in the first pass of a the parse-tree, such as * repeated symbol definitions. * <p> * Creation date: (February 6, 2001) * @author Bo Ilic */ class FreeVariableFinder { private final CALCompiler compiler; private final ModuleTypeInfo currentModuleTypeInfo; /** Names of all functions defined in the current module at the top level, not including built-ins and foreign functions.*/ private final Set<String> topLevelFunctionNamesSet; /** * If true, then resolve unqualified names that occur in the parse tree, or report an error if this can not be done. * Also, do some extra error checking on the correctness of the parse trees. * Since free variable finding is done on the first full traversal of the parse tree of function * definitions, this class is a convenient place to put certain error checking. */ private boolean firstPass; /** * The names of all possible dependee var names from the current module. * The sets returned by the public methods of this class will be a subset of this set. */ private Collection<String> possibleDependeeNamesSet; /** * The names of built-in and foreign functions will never be returned as a dependee (unless cloaked by * scoping). Here are some additional names that should not be returned. These names are all * assumed to belong to the current module. */ private Set<String> ignorableFunctionNamesSet; /** * Statistics on the number of pattern vars that are unused and then converted to * wildcard pattern variables (_). */ private static final boolean GENERATE_CONVERTED_PATTERN_VAR_STATISTICS = false; private int nUnusedPatternVars; private static int totalNUnusedPatternVars; /** * A stack for the use of bound variables. Its special feature is that it keeps track of how * often a given bound variable is used in its defining expression. * * It is also used to rename the bound variables in a function's definition so that they are * unique within a given module. * * @author Bo Ilic */ private static final class BoundVariablesStack { private final ArrayStack<BoundVariable> stack; /** * name of the top-level function in which we are doing symbol resolution. * null if we are working on a subexpression of the function's definition, and * don't want to assign unique names to the bound variables. */ private final String topLevelFunctionName; /** * every time a BoundVariable is pushed onto the BoundVariableStack, the varCount is * incremented by 1. This allows us to assign a unique int to each bound variable within * a function, and thus have an unambiguous name for each local symbol defined within * the function */ private int varCount; private static final class BoundVariable { private final ParseTreeNode varNameNode; private int useCount; /** * name to prepend to the variable's name */ private final String prefix; private final int varNumber; private BoundVariable(ParseTreeNode varNameNode, int varNumber, String prefix) { this.varNameNode = varNameNode; this.varNumber = varNumber; this.prefix = prefix; } private BoundVariable(String varName) { this (new ParseTreeNode(CALTreeParserTokenTypes.VAR_ID, varName), -1, null); } @Override public boolean equals(Object other) { return getVarName().equals(((BoundVariable)other).getVarName()); } @Override public int hashCode() { return getVarName().hashCode(); } int getUseCount() { return useCount; } void incrementUseCount() { ++useCount; } String getVarName () { return varNameNode.getText(); } int getVarNumber () { return varNumber; } String getUniqueName() { if (prefix != null) { return prefix + "$" + getVarName() + "$" + varNumber; } //names have already been made unique by an earlier pass. return getVarName(); } @Override public String toString() { return getUniqueName(); } } BoundVariablesStack(String topLevelFunctionName) { stack = ArrayStack.make(); this.topLevelFunctionName = topLevelFunctionName; } void push(ParseTreeNode varNameNode) { stack.push(new BoundVariable(varNameNode, ++varCount, topLevelFunctionName)); } void popN(int n, boolean updateWithUniqueNames) { if (updateWithUniqueNames) { for (int i = 0; i < n; ++i) { BoundVariable boundVar = stack.pop(); boundVar.varNameNode.setText(boundVar.getUniqueName()); } return; } stack.popN(n); } /** * Determine if the stack contains the given varName. * @param varName * @param incrementUseCount if the stack contains varName, and this flag is true, then * increment the use count of varName. Note: it is the most recently pushed varName whose * use count will be incremented. * @return BoundVar non-null if the BoundVariables stack contains a varName with this name. */ BoundVariable contains(String varName, boolean incrementUseCount) { int index = stack.lastIndexOf(new BoundVariable(varName)); if (index == -1) { return null; } BoundVariable var = stack.get(index); if (incrementUseCount) { var.incrementUseCount(); } return var; } /** * @param varName * @return the use count (>= 0) if varName is in the stack and -1 if not. The use count is for * the last added symbol in the stack. */ int getUseCount(String varName) { int index = stack.lastIndexOf(new BoundVariable(varName)); if (index == -1) { return -1; } return stack.get(index).getUseCount(); } /** * @param nElements number of elements to get from the top of the stack * @return (String Set) the var names in the stack, with the uniqueness transform applied. */ Set<String> getUniqueVarNamesSet(int nElements) { if (topLevelFunctionName == null) { //should only be doing this if we are disambiguating names throw new IllegalStateException(); } Set<String> varNamesSet = new HashSet<String>(); for (int i = stack.size() - nElements, n = stack.size(); i < n; ++i) { varNamesSet.add(stack.get(i).getUniqueName()); } return varNamesSet; } Set<String> getUniqueVarNamesSet() { return getUniqueVarNamesSet(stack.size()); } @Override public String toString() { if (stack.size() == 0) { return "<empty stack>"; } StringBuilder result = new StringBuilder(); for (int i = stack.size() - 1; i >= 0; --i) { result.append(i).append(' ').append(stack.get(i).toString()).append('\n'); } return result.toString(); } } /** * FreeVariableFinder constructor comment. * * @param compiler * @param topLevelFunctionNamesSet names of all functions defined in the current module at the top level, not including built-in or foreign functions. * @param currentModuleTypeInfo */ FreeVariableFinder(CALCompiler compiler, Set<String> topLevelFunctionNamesSet, ModuleTypeInfo currentModuleTypeInfo) { if (compiler == null || topLevelFunctionNamesSet == null || currentModuleTypeInfo == null) { throw new NullPointerException(); } this.compiler = compiler; this.topLevelFunctionNamesSet = topLevelFunctionNamesSet; this.currentModuleTypeInfo = currentModuleTypeInfo; } /** * Perform dependency analysis to divide up the top-level functions defined in a module into * a topologically ordered set of strongly connected components. * * This function does the first full traversal of all function definition expression trees. * It does certain static checking and updates the parse-trees as a side effect. * -all symbols that are not fully qualified are fully qualified, or an error if the symbol * doesn't exist or is not visible. * -static checks that certain symbols such as case pattern variables are not duplicated * e.g. "case xs of x : x" will give an error * -local dependency order transformations of let blocks * * Creation date: (1/18/01 6:11:23 PM) * @param functionNameToDefinitionNodeMap * @return Graph */ Graph<String> performFunctionDependencyAnalysis(Map<String, ParseTreeNode> functionNameToDefinitionNodeMap) { //Makes the dependency graph for the top level functions defined in a module. VertexBuilderList<String> vertexBuilderList = new VertexBuilderList<String>(); for (final Map.Entry<String, ParseTreeNode> entry : functionNameToDefinitionNodeMap.entrySet()) { String functionName = entry.getKey(); ParseTreeNode functionParseTree = entry.getValue(); Set<String> freeVariablesSet = findDependeeFunctionNames(functionParseTree); vertexBuilderList.add(new VertexBuilder<String>(functionName, freeVariablesSet)); } if (FreeVariableFinder.GENERATE_CONVERTED_PATTERN_VAR_STATISTICS) { FreeVariableFinder.totalNUnusedPatternVars += nUnusedPatternVars; System.out.println("module " + currentModuleTypeInfo.getModuleName()); System.out.println("number of unused pattern vars converted = " + nUnusedPatternVars); System.out.println("total unused pattern vars converted =" + FreeVariableFinder.totalNUnusedPatternVars); } // should never fail. It is a redundant check since makeSCDependencyGraph should throw an exception otherwise. if (!vertexBuilderList.makesValidGraph(topLevelFunctionNamesSet)) { throw new IllegalStateException("Internal coding error during dependency analysis."); } Graph<String> g = new Graph<String>(vertexBuilderList); g = g.calculateStronglyConnectedComponents(); //Previously we gave an error if there were no functions defined within the module. //However, we might want to have a module consisting only of data declarations so this //is now OK. return g; } /** * Returns the names of the top level non built-in functions defined within the current * module upon which the definition of the function defined by functionParseTree depends. * As a side effect, unqualified names occurring in functionParseTree are augmented with their correct * qualification. * * Creation date: (2/6/01 9:16:32 AM) * @return Set of global non built-in or foreign function names upon which the function definition depends * ordered by their first occurrence in the expression text. * @param functionParseTree parse tree of the function definition */ private Set<String> findDependeeFunctionNames(ParseTreeNode functionParseTree) { functionParseTree.verifyType(CALTreeParserTokenTypes.TOP_LEVEL_FUNCTION_DEFN); initState(true, Collections.<String>emptySet(), topLevelFunctionNamesSet); ParseTreeNode functionNameNode = functionParseTree.getChild(2); ParseTreeNode paramListNode = functionNameNode.nextSibling(); paramListNode.verifyType(CALTreeParserTokenTypes.FUNCTION_PARAM_LIST); BoundVariablesStack boundVariablesStack = new BoundVariablesStack(functionNameNode.getText()); Set<String> freeVariablesSet = new LinkedHashSet<String>(); findFreeVariablesInBoundExpression(freeVariablesSet, boundVariablesStack, paramListNode); return freeVariablesSet; } /** * Resolve unqualified identifiers within a function definition. * This method can be used on an adjunct to an existing module. * @param functionParseTree parse tree of the function definition */ void resolveUnqualifiedIdentifiers(ParseTreeNode functionParseTree) { functionParseTree.verifyType(CALTreeParserTokenTypes.TOP_LEVEL_FUNCTION_DEFN); Set<String> dependeeNamesSet = new HashSet<String>(); int nFunctions = currentModuleTypeInfo.getNFunctions(); for (int i = 0; i < nFunctions; i++) { Function function = currentModuleTypeInfo.getNthFunction(i); dependeeNamesSet.add(function.getName().getUnqualifiedName()); } topLevelFunctionNamesSet.addAll(dependeeNamesSet); // Add the name from the function itself, in case it's recursive. ParseTreeNode varNode = functionParseTree.getChild(2); varNode.verifyType(CALTreeParserTokenTypes.VAR_ID); String functionName = varNode.getText(); topLevelFunctionNamesSet.add(functionName); initState(true, Collections.<String>emptySet(), topLevelFunctionNamesSet); ParseTreeNode paramListNode = functionParseTree.getChild(3); paramListNode.verifyType(CALTreeParserTokenTypes.FUNCTION_PARAM_LIST); BoundVariablesStack boundVariablesStack = new BoundVariablesStack(functionName); Set<String> freeVariablesSet = new HashSet<String>(); // called for its side effect of resolving unqualified identifiers. findFreeVariablesInBoundExpression(freeVariablesSet, boundVariablesStack, paramListNode); } /** * Returns the names of the lambda bound variables, local function names (i.e. not * defined within a let or letrec) and function argument names that occur free * in the parseTree defining the given lambda or local function definition. Note that normally, * the returned set will not contain the names of top level functions. However, because of variable * hiding, it may. e.g. * x = 2; * y = 3; * f y = 2 + \z -> x + y + z; * Then only y is free in \z -> x + y + z, in the sense of this method. * * Creation date: (2/6/01 1:42:51 PM) * @param possibleDependeeNamesSet Set names of the function argument and lambda bound * variables encountered so far in the parse of the global functions that this lambda or local * let definition is a part of. The set returned by this function will be a subset of * this set. * @param paramListNode variables for the lambda definition or local let definition. * @return Set the free variables encountered ordered by their first occurrence in the expression text. */ Set<String> findFreeNamesInLambdaExpr(Collection<String> possibleDependeeNamesSet, ParseTreeNode paramListNode) { paramListNode.verifyType(CALTreeParserTokenTypes.FUNCTION_PARAM_LIST); initState(false, topLevelFunctionNamesSet, possibleDependeeNamesSet); BoundVariablesStack boundVariablesStack = new BoundVariablesStack(null); Set<String> freeVariablesSet = new LinkedHashSet<String>(); findFreeVariablesInBoundExpression(freeVariablesSet, boundVariablesStack, paramListNode); return freeVariablesSet; } Set<String> findFreeNamesInExpr(Collection<String> possibleDependeeNamesSet, ParseTreeNode exprNode) { initState(false, topLevelFunctionNamesSet, possibleDependeeNamesSet); BoundVariablesStack boundVariablesStack = new BoundVariablesStack(null); Set<String> freeVariablesSet = new LinkedHashSet<String>(); findFreeVariablesInExpr(freeVariablesSet, boundVariablesStack, exprNode); return freeVariablesSet; } /** * Finds the free variables used in expressions of the form x1,x2,...xn -> e where x1,.., xn are * bound variables over the expression's body. * * Creation date: (9/12/00 10:14:54 AM) * @param freeVariablesSet * @param boundVariablesStack * @param parseTree parent of x1,x2,...xn and preceding sibling of e */ private void findFreeVariablesInBoundExpression(Set<String> freeVariablesSet, BoundVariablesStack boundVariablesStack, ParseTreeNode parseTree) { Set<String> varNamesSet = (firstPass ? new HashSet<String>() : null); int nVars = 0; for (final ParseTreeNode patternVarNode : parseTree) { patternVarNode.verifyType(CALTreeParserTokenTypes.LAZY_PARAM, CALTreeParserTokenTypes.STRICT_PARAM); String varName = patternVarNode.getText(); if (firstPass && !varNamesSet.add(varName)) { // Repeated variable {varName} used in binding. compiler.logMessage(new CompilerMessage(patternVarNode, new MessageKind.Error.RepeatedVariableUsedInBinding(varName))); } ++nVars; // varName is now a bound variable for the body of the lambda boundVariablesStack.push(patternVarNode); } findFreeVariablesInExpr(freeVariablesSet, boundVariablesStack, parseTree.nextSibling()); boundVariablesStack.popN(nVars, firstPass); } /** * Finds the free variables used in expressions of the form x1,x2,...xn -> e where x1,.., xn are * bound variables over the expression's body as defined by a case pattern. * For example: Cons x xs -> (length xs + 1, x) * <p> * Case patterns are special at the moment because case pattern variables are allowed to be wildcards. * <p> * Creation date: (9/12/00 10:14:54 AM) * @param freeVariablesSet * @param boundVariablesStack * @param patternVarListNode the parent of the variables x1, x2, ..., xn (can include the wildcard pattern _) * @param boundExprNode the expression e */ private void findFreeVariablesInCasePatternVarListExpression( Set<String> freeVariablesSet, BoundVariablesStack boundVariablesStack, ParseTreeNode patternVarListNode, ParseTreeNode boundExprNode) { Set<String> varNamesSet = (firstPass ? new HashSet<String>() : null); int nVars = 0; for (final ParseTreeNode patternVarNode : patternVarListNode) { switch (patternVarNode.getType()) { case CALTreeParserTokenTypes.VAR_ID: { String varName = patternVarNode.getText(); if (firstPass && !varNamesSet.add(varName)) { // Repeated variable {varName} used in binding. compiler.logMessage(new CompilerMessage(patternVarNode, new MessageKind.Error.RepeatedVariableUsedInBinding(varName))); } ++nVars; // varName is now a bound variable for the body of the lambda boundVariablesStack.push(patternVarNode); break; } case CALTreeParserTokenTypes.UNDERSCORE: break; default: { patternVarNode.unexpectedParseTreeNode(); return; } } } findFreeVariablesInExpr(freeVariablesSet, boundVariablesStack, boundExprNode); if (firstPass) { //now we convert unused pattern bound variables to wildcards. //This saves time in later compilation stages, but it is primarily an optimization //put in for the runtime. //For example: //MyDataCons x y z w -> y //is converted to //MyDataCons _ y _ _ -> y for (final ParseTreeNode patternVarNode : patternVarListNode) { convertUnusedVar(boundVariablesStack, patternVarNode); } } boundVariablesStack.popN(nVars, firstPass); } /** * Find the free variables occurring in a case pattern expression where the argument bindings are * provided using field bindings (ie. matching notation). * * Also, if firstPass is true, unused field bindings are removed and the parse tree is patched up so that * punned field names are unpunned. * * @param freeVariablesSet * @param boundVariablesStack * @param dcArgBindingsNode the parent node of the arg bindings. * @param boundExprNode * @param patternDataConstructors (Set of DataConstructor objects) The data constructors used in this particular pattern in declaration order. */ private void findFreeVariablesInCasePatternMatchingExpression( Set<String> freeVariablesSet, BoundVariablesStack boundVariablesStack, ParseTreeNode dcArgBindingsNode, ParseTreeNode boundExprNode, Set<DataConstructor> patternDataConstructors) { if (firstPass) { // Perform the first pass verification and patching-up on the parse tree for the list of field bindings. firstPassProcessFieldBindingVarAssignmentListNode(dcArgBindingsNode, null, patternDataConstructors); } else { // Just verify the type. dcArgBindingsNode.verifyType(CALTreeParserTokenTypes.FIELD_BINDING_VAR_ASSIGNMENT_LIST); } Set<String> varNamesSet = (firstPass ? new HashSet<String>() : null); int nVars = 0; for (final ParseTreeNode fieldBindingVarAssignmentNode : dcArgBindingsNode) { ParseTreeNode fieldNameNode = fieldBindingVarAssignmentNode.firstChild(); ParseTreeNode patternVarNode = fieldNameNode.nextSibling(); switch (patternVarNode.getType()) { case CALTreeParserTokenTypes.VAR_ID: { String varName = patternVarNode.getText(); if (firstPass && !varNamesSet.add(varName)) { // Repeated variable {varName} used in binding. compiler.logMessage(new CompilerMessage(patternVarNode, new MessageKind.Error.RepeatedVariableUsedInBinding(varName))); } ++nVars; // varName is now a bound variable for the body of the lambda boundVariablesStack.push(patternVarNode); break; } case CALTreeParserTokenTypes.UNDERSCORE: break; default: { patternVarNode.unexpectedParseTreeNode(); return; } } } findFreeVariablesInExpr(freeVariablesSet, boundVariablesStack, boundExprNode); if (firstPass) { // Now convert pattern vars for unused field bindings to wildcards. //This saves some work and simplifies assumptions in later compilation stages, //For example: //MyDataCons {x=x, y=y, z=z, w=w} -> y //is converted to //MyDataCons {x=_, y=y, z=_, w=_} -> y for (final ParseTreeNode fieldBindingVarAssignmentNode : dcArgBindingsNode) { ParseTreeNode fieldNameNode = fieldBindingVarAssignmentNode.firstChild(); ParseTreeNode patternVarNode = fieldNameNode.nextSibling(); if (patternVarNode.getType() == CALTreeParserTokenTypes.VAR_ID) { if (boundVariablesStack.getUseCount(patternVarNode.getText()) == 0) { // An unused var. convertUnusedVar(boundVariablesStack, patternVarNode); if (FreeVariableFinder.GENERATE_CONVERTED_PATTERN_VAR_STATISTICS) { ++nUnusedPatternVars; } } } } } boundVariablesStack.popN(nVars, firstPass); } /** * A helper function for findFreeVariablesInCase. * Finds the free vars in a record pattern expression. * * @param freeVariablesSet * @param boundVariablesStack * @param patternNode * @return false iff an unexpected parse tree node were encountered. */ private boolean findFreeVarsInRecordPatternExpression(Set<String> freeVariablesSet, BoundVariablesStack boundVariablesStack, ParseTreeNode patternNode) { ParseTreeNode baseRecordPatternNode = patternNode.firstChild(); baseRecordPatternNode.verifyType(CALTreeParserTokenTypes.BASE_RECORD_PATTERN); ParseTreeNode baseRecordPatternVarNode = baseRecordPatternNode.firstChild(); String baseRecordVarName = null; if (baseRecordPatternVarNode != null) { switch (baseRecordPatternVarNode.getType()) { case CALTreeParserTokenTypes.VAR_ID: baseRecordVarName = baseRecordPatternVarNode.getText(); break; case CALTreeParserTokenTypes.UNDERSCORE: break; default: { baseRecordPatternVarNode.unexpectedParseTreeNode(); return false; } } } ParseTreeNode fieldBindingVarAssignmentListNode = baseRecordPatternNode.nextSibling(); if (firstPass) { // Perform the first pass verification and patching-up on the parse tree for a list of field bindings. firstPassProcessFieldBindingVarAssignmentListNode(fieldBindingVarAssignmentListNode, baseRecordVarName, Collections.<DataConstructor>emptySet()); } int nVars; if (baseRecordVarName != null) { nVars = 1; boundVariablesStack.push(baseRecordPatternVarNode); } else { nVars = 0; } for (final ParseTreeNode fieldBindingVarAssignmentNode : fieldBindingVarAssignmentListNode) { ParseTreeNode fieldNameNode = fieldBindingVarAssignmentNode.firstChild(); ParseTreeNode patternVarNode = fieldNameNode.nextSibling(); switch (patternVarNode.getType()) { case CALTreeParserTokenTypes.VAR_ID: { //String patternVar = patternVarNode.getText(); ++nVars; boundVariablesStack.push(patternVarNode); break; } case CALTreeParserTokenTypes.UNDERSCORE: break; default: { patternVarNode.unexpectedParseTreeNode(); return false; } } } findFreeVariablesInExpr(freeVariablesSet, boundVariablesStack, patternNode.nextSibling()); if (firstPass) { //now we convert unused pattern bound variables to wildcards. //This saves time in latter compilation stages, but it is primarily an optimization //put in for the runtime. //For example: //case r of {s | field1 = f1, field2 = f2, field3 = f3} -> e //is converted to {_ | field1 = _, field2 = f2, field3 = _} //if only f2 is used in e and s, f1 and f3 are not used. if (baseRecordPatternVarNode != null) { convertUnusedVar(boundVariablesStack, baseRecordPatternVarNode); } for (final ParseTreeNode fieldBindingVarAssignmentNode : fieldBindingVarAssignmentListNode) { ParseTreeNode patternVarNode = fieldBindingVarAssignmentNode.getChild(1); convertUnusedVar(boundVariablesStack, patternVarNode); } } boundVariablesStack.popN(nVars, firstPass); return true; } /** * A helper function that finds the free variables in each of the child expressions of parseTree. * * Creation date: (2/28/01 11:31:54 AM) * @param freeVariablesSet * @param boundVariablesStack * @param parseTree */ private void findFreeVariablesInChildExpressions(Set<String> freeVariablesSet, BoundVariablesStack boundVariablesStack, ParseTreeNode parseTree) { for (final ParseTreeNode exprNode : parseTree) { findFreeVariablesInExpr(freeVariablesSet, boundVariablesStack, exprNode); } } /** * Find the free variables occurring in an expression Node. Intuitively, the free variables * are the subset of possibleDependeeNamesSet on which the parseTree actually depends on. * * For example in: * f x = y + x + (let y = 2 in y) * the first occurence of y is the only free variable in the defining expression of f. * * Creation date: (8/31/00 12:26:07 PM) * @param freeVariablesSet The free variables 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 */ private void findFreeVariablesInExpr(Set<String> freeVariablesSet, BoundVariablesStack boundVariablesStack, ParseTreeNode parseTree) { int nodeType = parseTree.getType(); switch (nodeType) { case CALTreeParserTokenTypes.LITERAL_let : case CALTreeParserTokenTypes.VIRTUAL_LET_NONREC: case CALTreeParserTokenTypes.VIRTUAL_LET_REC: { findFreeVariablesInLet(freeVariablesSet, boundVariablesStack, parseTree); return; } case CALTreeParserTokenTypes.LAMBDA_DEFN : { ParseTreeNode paramListNode = parseTree.firstChild(); paramListNode.verifyType(CALTreeParserTokenTypes.FUNCTION_PARAM_LIST); findFreeVariablesInBoundExpression(freeVariablesSet, boundVariablesStack, paramListNode); return; } case CALTreeParserTokenTypes.LITERAL_case : case CALTreeParserTokenTypes.VIRTUAL_DATA_CONSTRUCTOR_CASE: case CALTreeParserTokenTypes.VIRTUAL_RECORD_CASE: case CALTreeParserTokenTypes.VIRTUAL_TUPLE_CASE: { findFreeVariablesInCase(freeVariablesSet, boundVariablesStack, parseTree); return; } case CALTreeParserTokenTypes.SELECT_DATA_CONSTRUCTOR_FIELD: { findFreeVariablesInDataConsFieldSelection(freeVariablesSet, 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: { findFreeVariablesInChildExpressions(freeVariablesSet, boundVariablesStack, parseTree); return; } case CALTreeParserTokenTypes.APPLICATION : { for (final ParseTreeNode exprNode : parseTree) { findFreeVariablesInExpr(freeVariablesSet, boundVariablesStack, exprNode); } return; } // function names, class method names and variables case CALTreeParserTokenTypes.QUALIFIED_VAR : findFreeVariablesInQualifiedVar(freeVariablesSet, boundVariablesStack, parseTree); return; //data constructors case CALTreeParserTokenTypes.QUALIFIED_CONS : { if (firstPass) { resolveDataConsName(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 : { findFreeVariablesInChildExpressions(freeVariablesSet, boundVariablesStack, parseTree); return; } //A list data value case CALTreeParserTokenTypes.LIST_CONSTRUCTOR : findFreeVariablesInChildExpressions(freeVariablesSet, boundVariablesStack, parseTree); return; case CALTreeParserTokenTypes.RECORD_CONSTRUCTOR: { ParseTreeNode baseRecordNode = parseTree.firstChild(); baseRecordNode.verifyType(CALTreeParserTokenTypes.BASE_RECORD); ParseTreeNode baseRecordExprNode = baseRecordNode.firstChild(); if (baseRecordExprNode != null) { findFreeVariablesInExpr(freeVariablesSet, boundVariablesStack, baseRecordExprNode); } ParseTreeNode fieldModificationListNode = baseRecordNode.nextSibling(); fieldModificationListNode.verifyType(CALTreeParserTokenTypes.FIELD_MODIFICATION_LIST); if (firstPass) { //check that there are no duplicate field names e.g. {colour = "red", height = 2.0, colour = "blue"} //is not allowed at this stage because of the duplicate field name colour. //if this is a record literal rather than a record extension, we cannot have a field update //i.e. {colour := "red", height = 2.0} is a static error flagged at this point, whereas //{{} | colour := "red", height = 2.0} will be a type-check error flagged at a latter stage of //compilation. Set<FieldName> fieldNamesSet = new HashSet<FieldName>(); for (final ParseTreeNode fieldModificationNode : fieldModificationListNode) { fieldModificationNode.verifyType(CALTreeParserTokenTypes.FIELD_EXTENSION, CALTreeParserTokenTypes.FIELD_VALUE_UPDATE); if (fieldModificationNode.getType() == CALTreeParserTokenTypes.FIELD_VALUE_UPDATE && baseRecordExprNode == null) { //"The field value update operator ':=' can not be used in a record literal value." compiler.logMessage(new CompilerMessage(fieldModificationNode, new MessageKind.Error.FieldValueUpdateOperatorUsedInRecordLiteralValue())); } ParseTreeNode fieldNameNode = fieldModificationNode.firstChild(); FieldName fieldName = compiler.getTypeChecker().getFieldName(fieldNameNode); if (!fieldNamesSet.add(fieldName)) { // Repeated field name {fieldName.getCalSourceForm()} in record literal value. compiler.logMessage(new CompilerMessage(fieldNameNode, new MessageKind.Error.RepeatedFieldNameInRecordLiteralValue(fieldName))); } } } for (final ParseTreeNode fieldModificationNode : fieldModificationListNode) { findFreeVariablesInExpr(freeVariablesSet, boundVariablesStack, fieldModificationNode.getChild(1)); } return; } case CALTreeParserTokenTypes.SELECT_RECORD_FIELD: case CALTreeParserTokenTypes.EXPRESSION_TYPE_SIGNATURE: { ParseTreeNode exprNode = parseTree.firstChild(); findFreeVariablesInExpr(freeVariablesSet, boundVariablesStack, exprNode); return; } default : { parseTree.unexpectedParseTreeNode(); return; } } } /** * Find the free variables occurring in a data constructor field selection expression. * * Also, if firstPass is true, and the expression is a data constructor field selection (dot operator - selection from a * data constructor valued-expression, eg. [1.0].Cons.head), the parse tree for the select node is augmented by addition * of a child node for the qualified var which would appear in the rhs of the alt for a corresponding case expression. * The transformation which makes use of this additional node comes in a later in the compilation stage. * * @param freeVariablesSet * @param boundVariablesStack * @param selectNode parse tree for the select expression. */ private void findFreeVariablesInDataConsFieldSelection(Set<String> freeVariablesSet, BoundVariablesStack boundVariablesStack, ParseTreeNode selectNode) { selectNode.verifyType(CALTreeParserTokenTypes.SELECT_DATA_CONSTRUCTOR_FIELD); ParseTreeNode exprNode = selectNode.firstChild(); // Find in the expr from which to select. findFreeVariablesInExpr(freeVariablesSet, boundVariablesStack, exprNode); ParseTreeNode dcNameNode = exprNode.nextSibling(); dcNameNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS); ParseTreeNode fieldNameNode = dcNameNode.nextSibling(); fieldNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID, CALTreeParserTokenTypes.ORDINAL_FIELD_NAME); DataConstructor dataConstructor = resolveDataConsName(dcNameNode); FieldName fieldName = compiler.getTypeChecker().getFieldName(fieldNameNode); // Check that the field name exists. boolean fieldNameExists = (dataConstructor.getFieldIndex(fieldName) > -1); if (!fieldNameExists) { MessageKind messageKind; if (dataConstructor.getArity() == 0) { messageKind = new MessageKind.Error.ZeroFieldDataConstructorFieldReference(dataConstructor, fieldName); } else { messageKind = new MessageKind.Error.UnknownDataConstructorField(dataConstructor, fieldName); } compiler.logMessage(new CompilerMessage(fieldNameNode, messageKind)); } // Simulate finding in the bound part of a case expr. String patternVarName; if (fieldNameNode.getType() == CALTreeParserTokenTypes.VAR_ID) { // textual field. patternVarName = fieldNameNode.getText(); } else { // ordinal field. /* * Don't convert the field name to a valid pattern var if the field name is not the name of a field in the data constructor. * This is to guard against subsequent analysis turning up errors against an identifier which doesn't appear in the code. * For instance, * "[1.0].Cons.#1" * * eventually is transformed to * case [1.0] of * Cons {#1=field1} -> field1; * * Since #1 is not a field of Cons, the field1 pattern var (on the left) is not bound to it. * Further analysis would attempt to bind the field1 identifier (on the right) to something else. * This might result in an error using the undefined identifier field1 (if it's not defined), * or an ambiguous reference error (if there were multiple field1's visible). */ if (fieldNameExists) { patternVarName = "field" + ((FieldName.Ordinal)fieldName).getOrdinal(); } else { patternVarName = fieldNameNode.getText(); } } SourcePosition fieldNameSourcePosition = fieldNameNode.getSourcePosition(); ParseTreeNode qualifiedVarNode; if (firstPass) { // The bound expr node is a node of type QUALIFIED_VAR with the same name as the pattern var. // ie. moduleName is unspecified, unqualified name is the pattern var name. qualifiedVarNode = ParseTreeNode.makeUnqualifiedVarNode(patternVarName, fieldNameSourcePosition); // // Set the qualified var node for the alt expression as the next sibling. // selectNode is "augmented" with this node, and children become: expr qualifiedCons fieldName qualifiedVar // fieldNameNode.setNextSibling(qualifiedVarNode); } else { qualifiedVarNode = fieldNameNode.nextSibling(); } ParseTreeNode varIdNodeCopy = new ParseTreeNode(); varIdNodeCopy.copyContentsFrom(qualifiedVarNode.getChild(1)); // Find in the qualified var. boundVariablesStack.push(varIdNodeCopy); findFreeVariablesInQualifiedVar(freeVariablesSet, boundVariablesStack, qualifiedVarNode); boundVariablesStack.popN(1, firstPass); } /** * A helper function for findFreeVariables. * * If firstPass is true: * It also checks that there are no duplicate patterns in the case expression. Haskell allows for repeated patterns. * The reason for this is that their pattern matching syntax is more complicated, allowing for guards and nested patterns, * and so it becomes more difficult to enforce the constraint that patterns should not overlap. So in Haskell, patterns * are matched sequentially, with the first pattern that matches being taken. In CAL, we can't have duplicate patterns, * and the wildcard pattern must be last if present. This hopefully will give users the right intuition * as to how the pattern matching syntax works. * * @param freeVariablesSet * @param boundVariablesStack * @param caseExprNode */ private void findFreeVariablesInCase (Set<String> freeVariablesSet, BoundVariablesStack boundVariablesStack, ParseTreeNode caseExprNode) { if (firstPass) { caseExprNode.verifyType(CALTreeParserTokenTypes.LITERAL_case); } else { caseExprNode.verifyType(CALTreeParserTokenTypes.VIRTUAL_DATA_CONSTRUCTOR_CASE, CALTreeParserTokenTypes.VIRTUAL_RECORD_CASE, CALTreeParserTokenTypes.VIRTUAL_TUPLE_CASE); } ParseTreeNode exprNode = caseExprNode.firstChild(); findFreeVariablesInExpr(freeVariablesSet, boundVariablesStack, exprNode); ParseTreeNode altListNode = exprNode.nextSibling(); altListNode.verifyType(CALTreeParserTokenTypes.ALT_LIST); final Set<Object> consNamesSet; if (firstPass) { consNamesSet = new HashSet<Object>(); //assume the data constructor case, and correct that assumption later if incorrect caseExprNode.setType(CALTreeParserTokenTypes.VIRTUAL_DATA_CONSTRUCTOR_CASE); } else { consNamesSet = null; } for (final ParseTreeNode altNode : altListNode) { altNode.verifyType(CALTreeParserTokenTypes.ALT); ParseTreeNode patternNode = altNode.firstChild(); int nodeKind = patternNode.getType(); switch (nodeKind) { case CALTreeParserTokenTypes.PATTERN_CONSTRUCTOR : { ParseTreeNode dcNameListNode = patternNode.firstChild(); dcNameListNode.verifyType(CALTreeParserTokenTypes.DATA_CONSTRUCTOR_NAME_LIST, CALTreeParserTokenTypes.DATA_CONSTRUCTOR_NAME_SINGLETON); ParseTreeNode dcArgBindingsNode = dcNameListNode.nextSibling(); //(Set of DataConstructor objects) The data constructors used in this particular pattern in declaration order. final Set<DataConstructor> patternDataConstructors = firstPass ? new LinkedHashSet<DataConstructor>() : null; if (firstPass) { final boolean checkArity = dcArgBindingsNode.getType() == CALTreeParserTokenTypes.PATTERN_VAR_LIST; final int impliedArity = checkArity ? dcArgBindingsNode.getNumberOfChildren() : -1; for (final ParseTreeNode dcNameNode : dcNameListNode) { DataConstructor dataConstructor = resolveDataConsName(dcNameNode); QualifiedName dataConsName = dataConstructor.getName(); if (!consNamesSet.add(dataConsName)) { // Repeated pattern {dataConstructor} in case expression. compiler.logMessage(new CompilerMessage(patternNode, new MessageKind.Error.RepeatedPatternInCaseExpression(dataConsName.getQualifiedName()))); break; } //we check that the implied arity is correct for pattern var list based case unpackings. //we do this here in order to provide a reasonable error position (based on dcNameNode) in the case //that something is wrong. This is because the patternVarList may be empty, in which case it will not //have a source position. if (checkArity) { if (dataConstructor.getArity() != impliedArity) { // Check that the number of variables expected by the data constructor corresponds to the number actually supplied in the pattern. //"The data constructor {0} must have exactly {1} pattern argument(s) in its case alternative." compiler.logMessage(new CompilerMessage(dcNameNode, new MessageKind.Error.ConstructorMustHaveExactlyNArgsInPattern(dataConstructor))); } } else { patternDataConstructors.add(dataConstructor); } } } switch (dcArgBindingsNode.getType()) { case CALTreeParserTokenTypes.PATTERN_VAR_LIST: { findFreeVariablesInCasePatternVarListExpression(freeVariablesSet, boundVariablesStack, dcArgBindingsNode, patternNode.nextSibling()); break; } case CALTreeParserTokenTypes.FIELD_BINDING_VAR_ASSIGNMENT_LIST: { findFreeVariablesInCasePatternMatchingExpression(freeVariablesSet, boundVariablesStack, dcArgBindingsNode, patternNode.nextSibling(), patternDataConstructors); break; } default: { dcArgBindingsNode.unexpectedParseTreeNode(); return; } } break; } case CALTreeParserTokenTypes.INT_PATTERN : { ParseTreeNode intListNode = patternNode.firstChild(); intListNode.verifyType(CALTreeParserTokenTypes.MAYBE_MINUS_INT_LIST); if (firstPass) { for (final ParseTreeNode maybeMinusIntNode : intListNode) { final boolean minus; final ParseTreeNode intLiteralNode; if (maybeMinusIntNode.getType() == CALTreeParserTokenTypes.MINUS) { minus = true; intLiteralNode = maybeMinusIntNode.firstChild(); } else { minus = false; intLiteralNode = maybeMinusIntNode; } intLiteralNode.verifyType(CALTreeParserTokenTypes.INTEGER_LITERAL); String symbolText = intLiteralNode.getText(); if (minus) { symbolText = "-" + symbolText; } Integer integerValue; try { integerValue = Integer.valueOf(symbolText); } catch (NumberFormatException nfe) { boolean parseableAsBigInteger = true; try { new BigInteger(symbolText); } catch (NumberFormatException nfe2) { parseableAsBigInteger = false; } if (parseableAsBigInteger) { // {symbolText} is outside of range for the Int type. The valid range is -2147483648 to 2147483647 (inclusive). compiler.logMessage(new CompilerMessage(maybeMinusIntNode, new MessageKind.Error.IntLiteralOutOfRange(symbolText))); break; } else { // Unable to parse {symbolText} to an integer literal. compiler.logMessage(new CompilerMessage(maybeMinusIntNode, new MessageKind.Error.UnableToParseToIntegerLiteral(symbolText))); break; } } // Set the data on the maybeMinusIntNode to be the Integer object. maybeMinusIntNode.setIntegerValueForMaybeMinusIntLiteral(integerValue); // Check against the Integer, not the symbol, to catch adding both -0 and 0. if (!consNamesSet.add(integerValue)) { // Repeated pattern value {value} in case expression. compiler.logMessage(new CompilerMessage(maybeMinusIntNode, new MessageKind.Error.RepeatedPatternValueInCaseExpression(symbolText))); break; } } } ParseTreeNode boundExprNode = patternNode.nextSibling(); findFreeVariablesInExpr(freeVariablesSet, boundVariablesStack, boundExprNode); break; } case CALTreeParserTokenTypes.CHAR_PATTERN : { ParseTreeNode charListNode = patternNode.firstChild(); charListNode.verifyType(CALTreeParserTokenTypes.CHAR_LIST); if (firstPass) { for (final ParseTreeNode charLiteralNode : charListNode) { charLiteralNode.verifyType(CALTreeParserTokenTypes.CHAR_LITERAL); String symbolText = charLiteralNode.getText(); Character charValue = null; try { char c = StringEncoder.unencodeChar(symbolText); charValue = Character.valueOf(c); } catch (IllegalArgumentException e) { // The encoded character did not have a valid format. // This should never happen for nodes created by the parser. // Unable to parse {symbolText} to a character literal. compiler.logMessage(new CompilerMessage(charLiteralNode, new MessageKind.Error.UnableToParseToCharacterLiteral(symbolText))); } // Set the data on the charLiteralNode to be the Character object. charLiteralNode.setCharacterValueForCharLiteral(charValue); // Check against the Character, not the symbol, to catch adding unescaped and escaped forms.. if (!consNamesSet.add(charValue)) { // Repeated pattern value {value} in case expression. compiler.logMessage(new CompilerMessage(charLiteralNode, new MessageKind.Error.RepeatedPatternValueInCaseExpression(symbolText))); break; } } } ParseTreeNode boundExprNode = patternNode.nextSibling(); findFreeVariablesInExpr(freeVariablesSet, boundVariablesStack, boundExprNode); break; } case CALTreeParserTokenTypes.LIST_CONSTRUCTOR : case CALTreeParserTokenTypes.COLON : case CALTreeParserTokenTypes.UNDERSCORE : { if (firstPass) { String symbolText = patternNode.getText(); if (!consNamesSet.add(symbolText)) { // Repeated pattern {symbolText} in case expression. compiler.logMessage(new CompilerMessage(patternNode, new MessageKind.Error.RepeatedPatternInCaseExpression(symbolText))); break; } if (nodeKind == CALTreeParserTokenTypes.UNDERSCORE && altNode.nextSibling() != null) { // The wildcard pattern '_' must be the final pattern in a case expression. compiler.logMessage(new CompilerMessage(patternNode, new MessageKind.Error.WildcardPatternMustBeFinalInCaseExpression())); break; } } findFreeVariablesInCasePatternVarListExpression(freeVariablesSet, boundVariablesStack, patternNode, patternNode.nextSibling()); break; } case CALTreeParserTokenTypes.TUPLE_CONSTRUCTOR : case CALTreeParserTokenTypes.VIRTUAL_UNIT_DATA_CONSTRUCTOR: { if (firstPass) { if (patternNode.hasExactlyOneChild()) { //a one variable tuple pattern is illegal compiler.logMessage(new CompilerMessage(patternNode, new MessageKind.Error.Illegal1TuplePattern())); break ; } if (patternNode.hasNoChildren()) { patternNode.setType(CALTreeParserTokenTypes.VIRTUAL_UNIT_DATA_CONSTRUCTOR); //a unit-case expression can only have one pattern- the one that matches (). if (!altListNode.hasExactlyOneChild()) { compiler.logMessage(new CompilerMessage(patternNode, new MessageKind.Error.OnlyOnePatternAllowedInCaseExpressionWithUnitPattern())); } } else { //this is a tuple case expression, not a data constructor case expression caseExprNode.setType(CALTreeParserTokenTypes.VIRTUAL_TUPLE_CASE); //a tuple-case expression can only have one pattern- the one that decomposes the tuple. if (!altListNode.hasExactlyOneChild()) { compiler.logMessage(new CompilerMessage(patternNode, new MessageKind.Error.OnlyOnePatternAllowedInCaseExpressionWithTuplePattern())); } } String symbolText = patternNode.getText(); if(!consNamesSet.add(symbolText)) { // Repeated pattern {symbolText} in case expression. compiler.logMessage(new CompilerMessage(patternNode, new MessageKind.Error.RepeatedPatternInCaseExpression(symbolText))); break; } } findFreeVariablesInCasePatternVarListExpression(freeVariablesSet, boundVariablesStack, patternNode, patternNode.nextSibling()); break; } case CALTreeParserTokenTypes.RECORD_PATTERN: { if (firstPass) { //a record-case expression can only have one pattern- the one that decomposes the record. if (!altListNode.hasExactlyOneChild()) { compiler.logMessage(new CompilerMessage(patternNode, new MessageKind.Error.OnlyOnePatternAllowedInCaseExpressionWithRecordPattern())); } //this is a record case expression, not a data constructor case expression caseExprNode.setType(CALTreeParserTokenTypes.VIRTUAL_RECORD_CASE); } boolean wasOK = findFreeVarsInRecordPatternExpression(freeVariablesSet, boundVariablesStack, patternNode); if (!wasOK) { return; } break; } default : { patternNode.unexpectedParseTreeNode(); return; } } } } /** * Perform the first pass verification and patching-up on the parse tree for a list of field bindings. * * 1. check that each field name used in the pattern is actually a field of the data constructor in question. * * 2. check that there are no duplicate fieldnames. * * 3. check that there are no duplicate pattern variable names, taking into account punning * (which is where instead of writing "fieldName = patternVar" we just write "fieldName" and * take the fieldName's name as the patternVar). For example: in the case of * {r | field1, field2 = x} the pattern variables are {r, field1, x}. * * In the case of punning, the parse tree is patched up so that subsequent analysis can assume that punning doesn't occur. * * @param fieldBindingVarAssignmentListNode the parse tree for a list of field bindings. * @param visiblePatternVarName if non-null, the pattern variable (eg. base record var) inherited by this list of bindings. * @param patternDataConstructors (Set of DataConstructor objects) The data constructors used in this particular pattern in declaration order. * Will be an empty set in the case of a record pattern. */ void firstPassProcessFieldBindingVarAssignmentListNode( ParseTreeNode fieldBindingVarAssignmentListNode, String visiblePatternVarName, Set<DataConstructor> patternDataConstructors) { fieldBindingVarAssignmentListNode.verifyType(CALTreeParserTokenTypes.FIELD_BINDING_VAR_ASSIGNMENT_LIST); Set<FieldName> fieldNamesSet = new HashSet<FieldName>(); Set<String> patternVarNamesSet = new HashSet<String>(); if (visiblePatternVarName != null) { patternVarNamesSet.add(visiblePatternVarName); } for (final ParseTreeNode fieldBindingVarAssignmentNode : fieldBindingVarAssignmentListNode) { fieldBindingVarAssignmentNode.verifyType(CALTreeParserTokenTypes.FIELD_BINDING_VAR_ASSIGNMENT); ParseTreeNode fieldNameNode = fieldBindingVarAssignmentNode.firstChild(); FieldName fieldName = compiler.getTypeChecker().getFieldName(fieldNameNode); //verify that the field name actually *is* a field for all the data constructors for (final DataConstructor dataConstructor : patternDataConstructors) { if (dataConstructor.getFieldIndex(fieldName) == -1) { MessageKind messageKind; if (dataConstructor.getArity() == 0) { messageKind = new MessageKind.Error.ZeroFieldDataConstructorFieldReference(dataConstructor, fieldName); } else { messageKind = new MessageKind.Error.UnknownDataConstructorField(dataConstructor, fieldName); } //Called when a data constructor case alt using matching notation attempts to bind a field which doesn't exist. compiler.logMessage(new CompilerMessage(fieldNameNode, messageKind)); } } if (!fieldNamesSet.add(fieldName)) { // Repeated field name {fieldName} in field binding pattern. compiler.logMessage(new CompilerMessage(fieldNameNode, new MessageKind.Error.RepeatedFieldNameInFieldBindingPattern(fieldName))); } //handle the case of punning. At this point we patch up the parse tree so that in subsequent analysis //we 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. 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); } switch (patternVarNode.getType()) { case CALTreeParserTokenTypes.VAR_ID: { String patternVarName = patternVarNode.getText(); if (!patternVarNamesSet.add(patternVarName)) { // Repeated pattern variable {patternVarName} in field binding pattern. compiler.logMessage(new CompilerMessage(patternVarNode, new MessageKind.Error.RepeatedPatternVariableInFieldBindingPattern(patternVarName))); } break; } case CALTreeParserTokenTypes.UNDERSCORE: break; default: { patternVarNode.unexpectedParseTreeNode(); return; } } } } /** * A helper function to convert an unused pattern var node to a wildcard node. * @param boundVariablesStack * @param patternVarNode */ private void convertUnusedVar(BoundVariablesStack boundVariablesStack, ParseTreeNode patternVarNode) { if (patternVarNode.getType() == CALTreeParserTokenTypes.VAR_ID) { String patternVarNodeText = patternVarNode.getText(); if (boundVariablesStack.getUseCount(patternVarNodeText) == 0) { patternVarNode.setType(CALTreeParserTokenTypes.UNDERSCORE); patternVarNode.setText("_"); patternVarNode.setUnusedVarNameForWildcardPatternVar(patternVarNodeText); // Another option is just to leave the text. if (FreeVariableFinder.GENERATE_CONVERTED_PATTERN_VAR_STATISTICS) { ++nUnusedPatternVars; } } } } /** * A helper function for findFreeVariables. * * Also, reorders the let definitions (introducing additional ones if needed as a side-effect) * into minimal dependency groups. * * Creation date: (8/31/00 2:29:29 PM) * @param freeVariablesSet * @param boundVariablesStack * @param letNode */ private void findFreeVariablesInLet(Set<String> freeVariablesSet, BoundVariablesStack boundVariablesStack, ParseTreeNode letNode) { if (firstPass) { letNode.verifyType(CALTreeParserTokenTypes.LITERAL_let); } else { letNode.verifyType(CALTreeParserTokenTypes.VIRTUAL_LET_NONREC, CALTreeParserTokenTypes.VIRTUAL_LET_REC); } ParseTreeNode defnListNode = letNode.firstChild(); defnListNode.verifyType(CALTreeParserTokenTypes.LET_DEFN_LIST); Set<String> localFunctionNamesSet = (firstPass ? new HashSet<String>() : null); int nLocalFunctions = 0; for (ParseTreeNode defnNode = defnListNode.firstChild(); defnNode != null; defnNode = defnNode.nextSibling()) { if (defnNode.getType() == CALTreeParserTokenTypes.LET_DEFN) { ParseTreeNode optionalCALDocNode = defnNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); ParseTreeNode localFunctionNameNode = optionalCALDocNode.nextSibling(); localFunctionNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); ++nLocalFunctions; // Having two local functions of the same name in a single letrec is an error. String functionName = localFunctionNameNode.getText(); if (firstPass && !localFunctionNamesSet.add(functionName)) { // Repeated definition of {functionName} in let declaration. compiler.logMessage(new CompilerMessage(localFunctionNameNode, new MessageKind.Error.RepeatedDefinitionInLetDeclaration(functionName))); } // functionName is a bound variable for all declarations in the 'let' and for the expression // following the 'in'. boundVariablesStack.push(localFunctionNameNode); } else if (defnNode.getType() == CALTreeParserTokenTypes.LET_PATTERN_MATCH_DECL) { /// Desugar the pattern match declarations // if (firstPass) { final LocalPatternMatchDeclarationChecker.Result desugaringResult = LocalPatternMatchDeclarationChecker.processPatternMatchDeclNode(this, compiler, defnNode, localFunctionNamesSet); final LinkedHashMap<String, ParseTreeNode> desugaredVarsMap = desugaringResult.getNamesToDesugaredVarNodes(); final ParseTreeNode lastDesugaredDefn = desugaringResult.getLastDesugaredDefn(); for (final Map.Entry<String, ParseTreeNode> entry : desugaredVarsMap.entrySet()) { final String functionName = entry.getKey(); final ParseTreeNode functionNameNode = entry.getValue(); // Having two local functions of the same name in a single letrec is an error. // But this should have been caught by the desugaring step already, so this is impossible if (!localFunctionNamesSet.add(functionName)) { throw new IllegalStateException("The duplicate names declared in a pattern match declaration should have been caught before this point."); } ++nLocalFunctions; // functionName is a bound variable for all declarations in the 'let' and for the expression // following the 'in'. boundVariablesStack.push(functionNameNode); } // for the outer loop iteration to proceed properly, we set defnNode to the last desugared definition node defnNode = lastDesugaredDefn; } } } //check that there is at most 1 local type declaration for each local function definition. //check that a local function is not defined more than once. if (firstPass) { final Set<String> declaredLocals = new HashSet<String>(); for (final ParseTreeNode defnNode : defnListNode) { 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(); String localFunctionName = localSCNameNode.getText(); if (!localFunctionNamesSet.contains(localFunctionName)) { // the local function is declared but not defined. This is illegal in Haskell and CAL. compiler.logMessage(new CompilerMessage(typeDeclNode, new MessageKind.Error.DefinitionMissing(localFunctionName))); } if (!declaredLocals.add(localFunctionName)) { // the local function has already been declared. This is an attempted redeclaration. compiler.logMessage(new CompilerMessage(typeDeclNode, new MessageKind.Error.RepeatedTypeDeclaration(localFunctionName))); } //update the name of the local function used in the type declaration to its unique name BoundVariablesStack.BoundVariable boundVar = boundVariablesStack.contains(localFunctionName, false); localSCNameNode.setText(boundVar.getUniqueName()); } } } /// Go through the bodies of the local function definitions // for (final ParseTreeNode defnNode : defnListNode) { if (defnNode.getType() == CALTreeParserTokenTypes.LET_DEFN) { ParseTreeNode optionalCALDocNode = defnNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); ParseTreeNode localFunctionNameNode = optionalCALDocNode.nextSibling(); ParseTreeNode varListNode = localFunctionNameNode.nextSibling(); findFreeVariablesInBoundExpression(freeVariablesSet, boundVariablesStack, varListNode); } } ParseTreeNode exprNode = defnListNode.nextSibling(); findFreeVariablesInExpr(freeVariablesSet, boundVariablesStack, exprNode); if (firstPass) { //update with the uniqueness transformed local names localFunctionNamesSet = boundVariablesStack.getUniqueVarNamesSet(nLocalFunctions); } boundVariablesStack.popN(nLocalFunctions, firstPass); //now do local dependency order transforms. What this means is that we reorder the let group into minimal //mutually dependent sets of symbols, and distinguish recursive lets from non-recursive lets. //e.g. let f1 = e1; f2 = e2; f3 = e3; f4 = e4; in e //could be transformed into //letNonRec f3 = e3; in letRec f1 = e1; f4 = e4; in letRec f2 = e2; in e //This implies in this case that: // the definition of f3 does not depend on f1, f2, f4 // f1 and f4 depend recursively on each other but not on f2. // f2 depends recursively on itself. if (firstPass) { //we don't want to look at top level functions and any local symbols bound in //an enclosing scope of this let block. Set<String> ignorableNamesSet = new HashSet<String>(topLevelFunctionNamesSet); ignorableNamesSet.addAll(boundVariablesStack.getUniqueVarNamesSet()); //essentially this is a map from the names of the functions defined in this let block to the //list of the functions defined in this let block upon which their definition depends. i.e. [(String, [String])]. VertexBuilderList<String> vertexBuilderList = new VertexBuilderList<String>(); Map<String, ParseTreeNode> functionNameToDefinitionNode = new HashMap<String, ParseTreeNode>(); //String -> ParseTreeNode Map<String, ParseTreeNode> functionNameToDeclarationNode = new HashMap<String, ParseTreeNode>(); //String -> ParseTreeNode FreeVariableFinder localDependeesFinder = new FreeVariableFinder(compiler, topLevelFunctionNamesSet, currentModuleTypeInfo); localDependeesFinder.initState(false, ignorableNamesSet, localFunctionNamesSet); for (final ParseTreeNode defnNode : defnListNode) { switch (defnNode.getType()) { case CALTreeParserTokenTypes.LET_DEFN: { ParseTreeNode optionalCALDocNode = defnNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); ParseTreeNode localSCNameNode = optionalCALDocNode.nextSibling(); String localSCName = localSCNameNode.getText(); functionNameToDefinitionNode.put(localSCName, defnNode); ParseTreeNode varListNode = localSCNameNode.nextSibling(); Set<String> letDependeesSet = new LinkedHashSet<String>(); localDependeesFinder.findFreeVariablesInBoundExpression(letDependeesSet, new BoundVariablesStack(null), varListNode); vertexBuilderList.add(new VertexBuilder<String>(localSCName, letDependeesSet)); 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); String localSCName = typeDeclNode.firstChild().getText(); functionNameToDeclarationNode.put(localSCName, defnNode); break; } default: { defnNode.unexpectedParseTreeNode(); break; } } } //modify the parseTree structure based on the connected component analysis ParseTreeNode currentLetNode = letNode; Graph<String> g = new Graph<String>(vertexBuilderList); g = g.calculateStronglyConnectedComponents(); for (int i = 0, nComponents = g.getNStronglyConnectedComponents(); i < nComponents; ++i) { Graph<String>.Component component = g.getStronglyConnectedComponent(i); ParseTreeNode currentLetDefnListNode = new ParseTreeNode(CALTreeParserTokenTypes.LET_DEFN_LIST, "LET_DEFN_LIST"); for (int j = 0, componentSize = component.size(); j < componentSize; ++j) { Vertex<String> functionVertex = component.getVertex(j); String functionName = functionVertex.getName(); //determine if a letNonRec or a letRec. Note that even if the component size is 1 it could //be a recursive definition e.g. "let f x = if x < 0 then 0 else f (x - 1) + x in ..." if (componentSize == 1 && !functionVertex.getAdjacentVertices().contains(functionVertex)) { currentLetNode.setType(CALTreeParserTokenTypes.VIRTUAL_LET_NONREC); currentLetNode.setText("letNonRec"); } else { currentLetNode.setType(CALTreeParserTokenTypes.VIRTUAL_LET_REC); currentLetNode.setText("letRec"); } ParseTreeNode functionDeclarationNode = functionNameToDeclarationNode.get(functionName); //we group the function declaration, if one is given, immediately before the function that it is a declaration for if (functionDeclarationNode != null) { functionDeclarationNode.setNextSibling(null); //clear out the previously set next sibling currentLetDefnListNode.addChild(functionDeclarationNode); } ParseTreeNode functionDefinitionNode = functionNameToDefinitionNode.get(functionName); functionDefinitionNode.setNextSibling(null); //clear out the previously set next sibling currentLetDefnListNode.addChild(functionDefinitionNode); } currentLetNode.setFirstChild(currentLetDefnListNode); if (i < nComponents - 1){ ParseTreeNode newLetLiteralNode = new ParseTreeNode(CALTreeParserTokenTypes.LITERAL_let, "let"); currentLetDefnListNode.setNextSibling(newLetLiteralNode); currentLetNode = newLetLiteralNode; } else { currentLetDefnListNode.setNextSibling(exprNode); } } } } /** * Constructs a name, based on the pattern-bound variable names, for the synthetic local function for * hosting the defining expression of the pattern match declaration, which is to be added to the desugared tree. * * @param patternVarNames a Collection of the pattern variable names, in source order. * @return a name for the synthetic local function. */ static String makeTempVarNameForDesugaredLocalPatternMatchDecl(Collection<String> patternVarNames) { final StringBuilder nameBuffer = new StringBuilder("$pattern"); for (final String patternVarName : patternVarNames) { nameBuffer.append('_').append(patternVarName); } return nameBuffer.toString(); } /** * Helper function for findFreeVariables. * Look up the data constructor corresponding to the parse tree for a qualified cons which names it. * * The main purpose of this method is its side effect. * It explicitly sets the module name for unqualified data constructors so that further analysis can assume * that all data constructor names are fully qualified. It also reports an error if the data constructor * cannot be resolved. * * Creation date: (8/31/00 2:46:34 PM) * @param qualifiedConsNode ParseTreeNode * @return DataConstructor the data constructor that the qualifiedConsNode sub-tree can be resolved to refer to * or null if an error message is logged . */ DataConstructor resolveDataConsName(ParseTreeNode qualifiedConsNode) { return resolveDataConsName(qualifiedConsNode, false); } /** * Helper function for findFreeVariables. * Look up the data constructor corresponding to the parse tree for a qualified cons which names it. * * The main purpose of this method is its side effect. * It explicitly sets the module name for unqualified data constructors so that further analysis can assume * that all data constructor names are fully qualified. It also reports an error if the data constructor * cannot be resolved. * * @param qualifiedConsNode ParseTreeNode * @param suppressErrorMessageLogging whether to suppress the logging of error messages to the CALCompiler instance * @return DataConstructor the data constructor that the qualifiedConsNode sub-tree can be resolved to refer to * or null if an error message is logged . */ private DataConstructor resolveDataConsName(ParseTreeNode qualifiedConsNode, boolean suppressErrorMessageLogging) { ParseTreeNode moduleNameNode = qualifiedConsNode.firstChild(); String maybeModuleName = ModuleNameUtilities.resolveMaybeModuleNameInParseTree(moduleNameNode, currentModuleTypeInfo, compiler, suppressErrorMessageLogging); ParseTreeNode dataConsNameNode = moduleNameNode.nextSibling(); String dataConsName = dataConsNameNode.getText(); ModuleName currentModuleName = currentModuleTypeInfo.getModuleName(); if (maybeModuleName.length() > 0) { //an explicitly qualified data constructor ModuleName moduleName = ModuleName.make(maybeModuleName); QualifiedName qualifiedDataConsName = QualifiedName.make(moduleName, dataConsName); if (moduleName.equals(currentModuleName)) { //the data constructor must be in the current module DataConstructor dataCons = currentModuleTypeInfo.getDataConstructor(dataConsName); if (dataCons == null) { if (!suppressErrorMessageLogging) { // The data constructor {qualifiedDataConsName} does not exist in {currentModuleName}. compiler.logMessage(new CompilerMessage(dataConsNameNode, new MessageKind.Error.DataConstructorDoesNotExistInModule(qualifiedDataConsName, currentModuleName))); } return null; } checkResolvedDataConsReference(compiler, qualifiedConsNode, dataCons.getName(), suppressErrorMessageLogging); //Success- the data constructor was found in the current module's environment return dataCons; } //the data constructor must be in an imported module ModuleTypeInfo importedModuleTypeInfo = currentModuleTypeInfo.getImportedModule(moduleName); if (importedModuleTypeInfo == null) { if (!suppressErrorMessageLogging) { // The module {moduleName} has not been imported into {currentModuleName}. compiler.logMessage(new CompilerMessage(moduleNameNode, new MessageKind.Error.ModuleHasNotBeenImported(moduleName, currentModuleName))); } return null; } DataConstructor dataCons = importedModuleTypeInfo.getDataConstructor(dataConsName); if (dataCons == null) { if (!suppressErrorMessageLogging) { // The data constructor does not exist. compiler.logMessage(new CompilerMessage(dataConsNameNode, new MessageKind.Error.DataConstructorDoesNotExist(qualifiedDataConsName))); } return null; } else if (!currentModuleTypeInfo.isEntityVisible(dataCons)) { if (!suppressErrorMessageLogging) { // The data constructor {qualifiedDataConsName} is not visible in {currentModuleName}. //"The data constructor {0} is not visible in module {1}." compiler.logMessage(new CompilerMessage(dataConsNameNode, new MessageKind.Error.DataConstructorNotVisible(dataCons, currentModuleName))); } return null; } checkResolvedDataConsReference(compiler, qualifiedConsNode, dataCons.getName(), suppressErrorMessageLogging); //Success- the module is indeed imported and has a visible data constructor of the specified name. return dataCons; } //An unqualified data cons if (!firstPass) { //names should already have been resolved, and so calling this method is redundant. throw new IllegalStateException(); } DataConstructor dataCons = currentModuleTypeInfo.getDataConstructor(dataConsName); if (dataCons != null) { checkResolvedDataConsReference(compiler, qualifiedConsNode, dataCons.getName(), suppressErrorMessageLogging); //The data constructor is defined in the current module ModuleNameUtilities.setModuleNameIntoParseTree(moduleNameNode, currentModuleName); return dataCons; } //We now know that the data constructor can't be defined within the current module. //check if it is a "using dataConstructor" and then patch up the module name. ModuleName usingModuleName = currentModuleTypeInfo.getModuleOfUsingDataConstructor(dataConsName); if (usingModuleName != null) { final QualifiedName qualifiedDataConsName = QualifiedName.make(usingModuleName, dataConsName); checkResolvedDataConsReference(compiler, qualifiedConsNode, qualifiedDataConsName, suppressErrorMessageLogging); ModuleNameUtilities.setModuleNameIntoParseTree(moduleNameNode, usingModuleName); //this call is guaranteed to return a data constructor due to earlier static checks on the using clause return currentModuleTypeInfo.getVisibleDataConstructor(qualifiedDataConsName); } if (!suppressErrorMessageLogging) { //We now know that the data constructor can't be defined within the current module and is not a "using dataConstructor". //Check if it is defined in another module. //This will be an error since the user must supply a module qualification, but we //can attempt to give a good error message. List<QualifiedName> candidateDataConsNames = new ArrayList<QualifiedName>(); int nImportedModules = currentModuleTypeInfo.getNImportedModules(); for (int i = 0; i < nImportedModules; ++i) { DataConstructor candidate = currentModuleTypeInfo.getNthImportedModule(i).getDataConstructor(dataConsName); if (candidate != null && currentModuleTypeInfo.isEntityVisible(candidate)) { candidateDataConsNames.add(candidate.getName()); } } int numCandidates = candidateDataConsNames.size(); if(numCandidates == 0) { // Attempt to use undefined data constructor {dataConsName}. compiler.logMessage(new CompilerMessage(dataConsNameNode, new MessageKind.Error.AttemptToUseUndefinedDataConstructor(dataConsName))); } else if(numCandidates == 1) { QualifiedName candidateDataConsName = candidateDataConsNames.get(0); // Attempt to use undefined data constructor {dataConsName}. Was {candidateDataConsName} intended? compiler.logMessage(new CompilerMessage(dataConsNameNode, new MessageKind.Error.AttemptToUseUndefinedDataConstructorSuggestion(dataConsName, candidateDataConsName))); } else { // The reference to the data constructor {dataConsName} is ambiguous. It could mean any of {candidateDataConsNames.toString()}. compiler.logMessage(new CompilerMessage(dataConsNameNode, new MessageKind.Error.AmbiguousDataConstructorReference(dataConsName, candidateDataConsNames))); } } return null; } /** * Performs late static checks on a data constructor reference that has already been resolved. * * Currently, this performs a deprecation check on a data constructor reference, logging a warning message if the data constructor is deprecated. * @param compiler the compiler instance. * @param nameNode the parse tree node representing the reference. * @param qualifiedName the qualified name of the reference. * @param suppressCompilerMessageLogging whether to suppress message logging. */ static void checkResolvedDataConsReference(final CALCompiler compiler, final ParseTreeNode nameNode, final QualifiedName qualifiedName, final boolean suppressCompilerMessageLogging) { if (!suppressCompilerMessageLogging) { if (compiler.getDeprecationScanner().isDataConsDeprecated(qualifiedName)) { compiler.logMessage(new CompilerMessage(nameNode, new MessageKind.Warning.DeprecatedDataCons(qualifiedName))); } } } /** * Adds the given variable to the freeVariablesSet if this is appropriate. * As a side effect, if this.firstPass is true, it explicitly sets the module name * for unqualified variable (so that further analysis can assume that all variable names are * fully qualified) and reports an error if the identifier cannot be resolved. * * Creation date: (8/31/00 2:46:34 PM) * @param freeVariablesSet * @param boundVariablesStack * @param qualifiedVarNode ParseTreeNode */ private void findFreeVariablesInQualifiedVar(Set<String> freeVariablesSet, BoundVariablesStack boundVariablesStack, ParseTreeNode qualifiedVarNode) { qualifiedVarNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_VAR); ParseTreeNode moduleNameNode = qualifiedVarNode.firstChild(); String maybeModuleName = ModuleNameUtilities.resolveMaybeModuleNameInParseTree(moduleNameNode, currentModuleTypeInfo, compiler, false); ParseTreeNode varNameNode = moduleNameNode.nextSibling(); String varName = varNameNode.getText(); ModuleName currentModuleName = currentModuleTypeInfo.getModuleName(); if (maybeModuleName.length() > 0) { //an explicitly qualified variable ModuleName moduleName = ModuleName.make(maybeModuleName); if (moduleName.equals(currentModuleName)) { //variable belongs to the current module if (!firstPass) { //bound variable, such as the x in "f x = x;" cannot be qualified in CAL source i.e. //f x = CurrentModule.x; //is illegal. However, they are qualified as part of internal analysis with the current module name. BoundVariablesStack.BoundVariable boundVar = boundVariablesStack.contains(varName, true); if (boundVar != null) { //variable is pattern bound (i.e. an argument variable, a lambda bound variable or a variable //bound by a case alternative). return; } } if (possibleDependeeNamesSet.contains(varName)) { //variable is a top-level non built-in function defined in the current module freeVariablesSet.add(varName); checkResolvedFunctionOrClassMethodReference(compiler, qualifiedVarNode, QualifiedName.make(moduleName, varName), false, currentModuleName); return; } if (compiler.getTypeChecker().isPrimitiveOrForeignFunctionName(varName)) { //use of a built-in or foreign function in qualified form is OK checkResolvedFunctionOrClassMethodReference(compiler, qualifiedVarNode, QualifiedName.make(moduleName, varName), false, currentModuleName); return; } if (ignorableFunctionNamesSet.contains(varName) || isHiddenFunction(varName) || currentModuleTypeInfo.getClassMethod(varName) != null) { //condition 1: used when finding the free variables within a lambda bound expression to do lambda lifting //condition 2: ignore hidden dictionary variables //condition 3: ignore class methods return; } if (!firstPass && varName.indexOf('$') != -1) { //ignore the names that have been made unique return; } QualifiedName qualifiedVarName = QualifiedName.make(moduleName, varName); // Attempt to use undefined identifier {qualifiedVarName}. compiler.logMessage(new CompilerMessage(varNameNode, new MessageKind.Error.AttemptToUseUndefinedIdentifier(qualifiedVarName.getQualifiedName()))); return; } //variable belongs to a different module if (isHiddenFunction(varName) || !this.firstPass) { return; } ModuleTypeInfo importedModuleTypeInfo = currentModuleTypeInfo.getImportedModule(moduleName); if (importedModuleTypeInfo == null) { // The module {moduleName} has not been imported into {currentModuleName}. compiler.logMessage(new CompilerMessage(moduleNameNode, new MessageKind.Error.ModuleHasNotBeenImported(moduleName, currentModuleName))); } FunctionalAgent entity = importedModuleTypeInfo.getFunctionOrClassMethod(varName); if (entity == null) { QualifiedName qualifiedVarName = QualifiedName.make(moduleName, varName); // The identifier {qualifiedVarName} does not exist. compiler.logMessage(new CompilerMessage(varNameNode, new MessageKind.Error.IdentifierDoesNotExist(qualifiedVarName.getQualifiedName()))); } else if (!currentModuleTypeInfo.isEntityVisible(entity) && !qualifiedVarNode.isInternallyGenerated() ) { // The identifier {qualifiedVarName} is not visible in {currentModuleName}. MessageKind message; if (entity instanceof Function) { //"The function {0} is not visible in module {1}." message = new MessageKind.Error.FunctionNotVisible((Function)entity, currentModuleName); } else { //"The class method {0} is not visible in module {1}." message = new MessageKind.Error.ClassMethodNotVisible((ClassMethod)entity, currentModuleName); } compiler.logMessage(new CompilerMessage(varNameNode, message)); } final boolean isClassMethod = (entity instanceof ClassMethod); checkResolvedFunctionOrClassMethodReference(compiler, qualifiedVarNode, entity.getName(), isClassMethod, currentModuleName); //Success- the module is indeed imported and has a visible variable of the specified name. return; } //An unqualified variable if (!firstPass) { // Should not encounter an unqualified name at this point. throw new IllegalStateException("Should not encounter an unqualified name at this point."); } //At this point we need to decide if the unqualified variable is //a. a top-level function defined in the current module (and thus should be added to the freeVariablesSet) //b. a pattern bound variable //c. a visible function defined in another module //We patch up the parse tree to supply the missing module name. BoundVariablesStack.BoundVariable boundVar = boundVariablesStack.contains(varName, true); if (boundVar != 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. ModuleNameUtilities.setModuleNameIntoParseTree(moduleNameNode, currentModuleName); //disambiguate the name //the local symbol x defined in top level function f will be replaced by //f$x$n //where n is the nth local symbol defined within f. varNameNode.setText(boundVar.getUniqueName()); return; } if (possibleDependeeNamesSet.contains(varName)) { checkResolvedFunctionOrClassMethodReference(compiler, qualifiedVarNode, QualifiedName.make(currentModuleName, varName), false, currentModuleName); //variable is a top-level function defined in the current module freeVariablesSet.add(varName); ModuleNameUtilities.setModuleNameIntoParseTree(moduleNameNode, currentModuleName); return; } if (compiler.getTypeChecker().isPrimitiveOrForeignFunctionName(varName) || currentModuleTypeInfo.getClassMethod(varName) != null) { //1. use of an unqualified built-in or foreign function //2. use of an unqualified class method final boolean isClassMethod = (currentModuleTypeInfo.getClassMethod(varName) != null); checkResolvedFunctionOrClassMethodReference(compiler, qualifiedVarNode, QualifiedName.make(currentModuleName, varName), isClassMethod, currentModuleName); ModuleNameUtilities.setModuleNameIntoParseTree(moduleNameNode, currentModuleName); return; } //We now know that the variable can't be defined within the current module. //check if it is a "using function" and then patch up the module name. ModuleName usingModuleName = currentModuleTypeInfo.getModuleOfUsingFunctionOrClassMethod(varName); if (usingModuleName != null) { final boolean isClassMethod = (currentModuleTypeInfo.getImportedModule(usingModuleName).getClassMethod(varName) != null); checkResolvedFunctionOrClassMethodReference(compiler, qualifiedVarNode, QualifiedName.make(usingModuleName, varName), isClassMethod, currentModuleName); ModuleNameUtilities.setModuleNameIntoParseTree(moduleNameNode, usingModuleName); return; } //We now know that the variable can't be defined within the current module and is not a "using function". //Check if it is defined in another module. //This will be an error since the user must supply a module qualification, but we //can attempt to give a good error message. List<QualifiedName> candidateEntityNames = new ArrayList<QualifiedName>(); int nImportedModules = currentModuleTypeInfo.getNImportedModules(); for (int i = 0; i < nImportedModules; ++i) { FunctionalAgent candidate = currentModuleTypeInfo.getNthImportedModule(i).getFunctionOrClassMethod(varName); if (candidate != null && currentModuleTypeInfo.isEntityVisible(candidate)) { candidateEntityNames.add(candidate.getName()); } } int numCandidates = candidateEntityNames.size(); if(numCandidates == 0) { // Attempt to use undefined function {varName}. compiler.logMessage(new CompilerMessage(varNameNode, new MessageKind.Error.AttemptToUseUndefinedFunction(varName))); } else if(numCandidates == 1) { QualifiedName candidateName = candidateEntityNames.get(0); // Attempt to use undefined function {varName}. Was {candidateName} intended? compiler.logMessage(new CompilerMessage(varNameNode, new MessageKind.Error.AttemptToUseUndefinedFunctionSuggestion(varName, candidateName))); } else { // The reference to the function {varName} is ambiguous. It could mean any of {candidateEntityNames}. compiler.logMessage(new CompilerMessage(varNameNode, new MessageKind.Error.AmbiguousFunctionReference(varName, candidateEntityNames))); } } /** * Performs late static checks on a function or class method reference that has already been resolved. * * Currently, this performs 1) a deprecation check on a function or class method reference, logging a warning message if the function or method is deprecated, and * 2) a check that if the reference is to a foreign function, then the corresponding Java method/field/constructor should be resolvable and loadable, logging * an error message if it cannot be loaded. * @param compiler the compiler instance. * @param nameNode the parse tree node representing the reference. * @param qualifiedName the qualified name of the reference. * @param isClassMethod true if the reference is to a class method, false if it is a function reference. * @param currentModuleName the name of the current module. */ private static void checkResolvedFunctionOrClassMethodReference(final CALCompiler compiler, final ParseTreeNode nameNode, final QualifiedName qualifiedName, final boolean isClassMethod, final ModuleName currentModuleName) { // we only do the check when the function name is not an internal name if (qualifiedName.getUnqualifiedName().indexOf('$') < 0) { // 1) deprecation check on a function or class method reference, logging a warning message if the function or method is deprecated. if (compiler.getDeprecationScanner().isFunctionOrClassMethodDeprecated(qualifiedName)) { final MessageKind.Warning messageKind; if (isClassMethod) { messageKind = new MessageKind.Warning.DeprecatedClassMethod(qualifiedName); } else { messageKind = new MessageKind.Warning.DeprecatedFunction(qualifiedName); } compiler.logMessage(new CompilerMessage(nameNode, messageKind)); } if (!isClassMethod) { // 2) check that if the reference is to a foreign function, then the corresponding Java method/field/constructor should be resolvable and loadable. // only perform the check on references to entities outside the current module if (!qualifiedName.getModuleName().equals(currentModuleName)) { // Check if the function is a foreign function final ModuleTypeInfo moduleTypeInfo = compiler.getPackager().getModuleTypeInfo(qualifiedName.getModuleName()); final Function function = moduleTypeInfo.getFunction(qualifiedName.getUnqualifiedName()); final ForeignFunctionInfo foreignFunctionInfo = function.getForeignFunctionInfo(); if (foreignFunctionInfo != null) { // Force resolution to occur try { foreignFunctionInfo.resolveForeignEntities(); } catch (UnableToResolveForeignEntityException e) { compiler.logMessage(new CompilerMessage( nameNode, new MessageKind.Error.AttemptToUseForeignFunctionThatFailedToLoad(qualifiedName.toSourceText()), e)); } } } } } } /** * Initialize the state. * Creation date: (8/16/01 12:48:22 PM) */ private void initState(boolean firstPass, Set<String> ignorableSCNamesSet, Collection<String> possibleDependeeNamesSet) { this.firstPass = firstPass; this.ignorableFunctionNamesSet = ignorableSCNamesSet; this.possibleDependeeNamesSet = possibleDependeeNamesSet; } /** * Find the free variables occurring within a CALDoc "@link" inline cross-reference. * * For the case where the "@link" segment lacks the 'context' keyword, attempt to resolve it as a * data constructor and store the result of the resolution as an attribute on the refNode, i.e. linkNode's first grandchild. * * @param linkNode the ParseTreeNode representing an inline cross-reference in a CALDoc comment. */ void findFreeVariablesInCALDocInlineLinkTagSegment(ParseTreeNode linkNode) { initState(true, Collections.<String>emptySet(), topLevelFunctionNamesSet); // since we're not interested in pushing bound variables onto the stack, // we could specify null for the top level function name. BoundVariablesStack boundVariablesStack = new BoundVariablesStack(null); Set<String> freeVariablesSet = new LinkedHashSet<String>(); 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); if (refNode.getType() == CALTreeParserTokenTypes.CALDOC_CHECKED_QUALIFIED_VAR) { findFreeVariablesInQualifiedVar(freeVariablesSet, boundVariablesStack, 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); if (refNode.getType() == CALTreeParserTokenTypes.CALDOC_CHECKED_QUALIFIED_CONS) { resolveDataConsName(nameNode); } break; } case CALTreeParserTokenTypes.CALDOC_TEXT_LINK_MODULE: case CALTreeParserTokenTypes.CALDOC_TEXT_LINK_TYPECONS: case CALTreeParserTokenTypes.CALDOC_TEXT_LINK_TYPECLASS: // these kinds of references are not processed by the FreeVariableFinder. break; case CALTreeParserTokenTypes.CALDOC_TEXT_LINK_WITHOUT_CONTEXT: { ParseTreeNode refNode = linkContextNode.firstChild(); findFreeVariablesInCALDocCrossReferenceWithoutContext(refNode, freeVariablesSet, boundVariablesStack); break; } default: { linkContextNode.unexpectedParseTreeNode(); break; } } } /* * todo-jowong: look into refactoring the following name resolution code. */ /** * Find the free variables occurring within a CALDoc cross-reference 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 refNode the ParseTreeNode representing a cross-reference in a CALDoc comment. */ private void findFreeVariablesInCALDocCrossReferenceWithoutContext(ParseTreeNode refNode, Set<String> freeVariablesSet, BoundVariablesStack boundVariablesStack) { // The refNode can represent a checked or unchecked qualified var or cons name. refNode.verifyType( CALTreeParserTokenTypes.CALDOC_UNCHECKED_QUALIFIED_VAR, CALTreeParserTokenTypes.CALDOC_CHECKED_QUALIFIED_VAR, CALTreeParserTokenTypes.CALDOC_UNCHECKED_QUALIFIED_CONS, CALTreeParserTokenTypes.CALDOC_CHECKED_QUALIFIED_CONS); if (refNode.getType() == CALTreeParserTokenTypes.CALDOC_CHECKED_QUALIFIED_VAR) { // Proceed regularly if refNode contains a checked qualified var. ParseTreeNode nameNode = refNode.firstChild(); nameNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_VAR); findFreeVariablesInQualifiedVar(freeVariablesSet, boundVariablesStack, nameNode); } else if (refNode.getType() == CALTreeParserTokenTypes.CALDOC_CHECKED_QUALIFIED_CONS || refNode.getType() == CALTreeParserTokenTypes.CALDOC_UNCHECKED_QUALIFIED_CONS) { // If refNode contains a qualified cons, either checked or unchecked, then it may represent a data constructor // (or not.. it could also be a type constructor, a type class, or a module). // We proceed here by cloning the subtree, and then attempting to resolved the cloned subtree // as a data constructor. If the resolution succeeds, then mark the original refNode as // "resolvable as a data constructor". ParseTreeNode nameNode = refNode.firstChild(); nameNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS); ParseTreeNode copyOfNameNode = nameNode.copyParseTree(); // perform the resolution step, which would modify the cloned subtree final boolean suppressErrorMessageLogging = true; DataConstructor dataCons = resolveDataConsName(copyOfNameNode, suppressErrorMessageLogging); if (dataCons != null) { refNode.setCALDocCrossReferenceResolvedAsDataConstructor(dataCons.getName()); } } } /** * Find the free variables occurring within a CALDoc "@see" block. * @param seeBlockNode the node representing the "@see" block. */ void findFreeVariablesInCALDocSeeBlock(ParseTreeNode seeBlockNode) { initState(true, Collections.<String>emptySet(), topLevelFunctionNamesSet); // since we're not interested in pushing bound variables onto the stack, // we could specify null for the top level function name. BoundVariablesStack boundVariablesStack = new BoundVariablesStack(null); Set<String> freeVariablesSet = new LinkedHashSet<String>(); 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); if (refNode.getType() == CALTreeParserTokenTypes.CALDOC_CHECKED_QUALIFIED_VAR) { findFreeVariablesInQualifiedVar(freeVariablesSet, boundVariablesStack, 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); if (refNode.getType() == CALTreeParserTokenTypes.CALDOC_CHECKED_QUALIFIED_CONS) { resolveDataConsName(nameNode); } } break; } case CALTreeParserTokenTypes.CALDOC_SEE_BLOCK_WITHOUT_CONTEXT: { for (final ParseTreeNode refNode : seeBlockNode) { findFreeVariablesInCALDocCrossReferenceWithoutContext(refNode, freeVariablesSet, boundVariablesStack); } break; } case CALTreeParserTokenTypes.CALDOC_SEE_MODULE_BLOCK: case CALTreeParserTokenTypes.CALDOC_SEE_TYPECONS_BLOCK: case CALTreeParserTokenTypes.CALDOC_SEE_TYPECLASS_BLOCK: // these kinds of references are not processed by the FreeVariableFinder. break; default: { seeBlockNode.unexpectedParseTreeNode(); break; } } } /** * Returns true if the function is internally created. * @return boolean * @param functionName name of the function */ private static boolean isHiddenFunction(String functionName) { return functionName.length() > 0 && functionName.charAt(0) == '$'; } /** * The FreeVariableFinder makes local names unique by changing a local name such as x occurring in the definition * of f to something like f$x$9. We want to get back to x for the purpose of error messages. * @param uniqueName * @return the name without uniqueness markup via $ signs. */ static String getDisplayName(String uniqueName) { final int firstDollar = uniqueName.indexOf('$'); if (firstDollar == -1) { return uniqueName; } final int secondDollar = uniqueName.indexOf('$', firstDollar + 1); if (secondDollar == -1) { return uniqueName; } if (firstDollar + 1 == secondDollar) { // if the first and second dollar signs are adjacent, then this is a // synthetically generated name... return "<internal synthetic variable>"; } return uniqueName.substring(firstDollar + 1, secondDollar); } /** * Returns whether a uniquified variable name correspond to an internally generated synthetic name. * @param uniqueName the uniquified variable name to check. * @return true if the uniquified variable name correspond to an internally generated synthetic name. */ static boolean isInternalSyntheticVariable(final String uniqueName) { final int firstDollar = uniqueName.indexOf('$'); if (firstDollar == -1) { return false; } final int secondDollar = uniqueName.indexOf('$', firstDollar + 1); if (secondDollar == -1) { return false; } // if the first and second dollar signs are adjacent, then this is a // synthetically generated name... return (firstDollar + 1 == secondDollar); } }