/*
* 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.
*/
/*
* LocalPatternMatchDeclarationChecker.java
* Created: Feb 21, 2007
* By: Joseph Wong
*/
package org.openquark.cal.compiler;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.openquark.cal.module.Cal.Core.CAL_Prelude;
/**
* This class encapsulates logic for checking pattern match declarations that appear within
* let expressions, as well as logic for desugaring them into regular local function definitions.
* <p>
* These are the checks that are done on the declarations:
* <ul>
* <li>that the pattern declares at least one pattern-bound variable
* <li>that the pattern is not the unit pattern <code>()</code>
* <li>that a data constructor pattern with positional argument patterns contains the right number of such argument patterns
* <li>that a data constructor pattern with field-pattern pairs contains field names that appear in the data constructor
* <li>that a data constructor pattern refers to a data constructor with positive arity
* <li>that a polymorphic record pattern does not contain a variable (instead of a wildcard) for its base record pattern
* <li>that a pattern does not declare a pattern-bound variable more than once
* <li>that the pattern-bound variables declared do not conflict with other local definitions already encountered in the encapsulating let definition
* </ul>
* <p>
* When desugaring a tuple pattern or a non-polymorphic record pattern, we capture the field names into a set stored as an attribute
* on the parse tree node. When it comes time for {@link CALTypeChecker} to type check the desugared definitions, the set is checked
* against to verify that the type of the defining expression is a record type containing <strong>all and only the declared fields</strong>.
* <p>
* Note that local type declarations on the pattern-bound variables are allowed, and these type declarations have associated CALDoc comments.
* On the other hand, the actual local pattern match declaration itself cannot have a type declaration nor a CALDoc comment.
* <p>
* The following demonstrates the way in which local pattern match declarations are desugared into regular
* local function definitions:
* <ul>
* <li>Example 1 - a data constructor pattern with positional args:
* <pre>
* let Prelude.Just x = foo; in ...
* </pre>
* is desugared into:
* <pre>
* let
* $pattern_x = foo;
* x = $pattern_x.Prelude.Just.value;
* in ...
* </pre>
* <li>Example 2 - a data constructor pattern with field-pattern pairs:
* <pre>
* let Prelude.Cons {head, tail=xs} = foo; in ...
* </pre>
* is desugared into:
* <pre>
* let
* $pattern_head_xs = foo;
* head = $pattern_head_xs.Prelude.Cons.head;
* xs = $pattern_head_xs.Prelude.Cons.tail;
* in ...
* </pre>
* <li>Example 3 - a list constructor (:) pattern
* <pre>
* let x:_ = foo; in ...
* </pre>
* is desugared into:
* <pre>
* let
* $pattern_x = foo;
* x = $pattern_x.Prelude.Cons.head;
* in ...
* </pre>
* <li>Example 4 - a tuple pattern
* <pre>
* let (_, x, _, z) = foo; in ...
* </pre>
* is desugared into:
* <pre>
* let
* $pattern_x_z = foo;
* x = $pattern_x_z.#2;
* z = $pattern_x_z.#4;
* in ...
* </pre>
* <li>Example 5 - a non-polymorphic record pattern semantically identical to the above tuple pattern
* <pre>
* let {#1, #2=x, #3, #4=z} = foo; in ...
* </pre>
* is desugared into:
* <pre>
* let
* $pattern_x_z = foo;
* x = $pattern_x_z.#2;
* z = $pattern_x_z.#4;
* in ...
* </pre>
* <li>Example 6 - a non-polymorphic record pattern with mixed ordinal/textual field names
* <pre>
* let {#3=x, y, z=alpha} = foo; in ...
* </pre>
* is desugared into:
* <pre>
* let
* $pattern_x_y_alpha = foo;
* x = $pattern_x_y_alpha.#3;
* y = $pattern_x_y_alpha.y;
* alpha = $pattern_x_y_alpha.z;
* in ...
* </pre>
* <li>Example 7 - a polymorphic record pattern that desugars to the same definitions as the above non-polymorphic record pattern
* (modulo the fact that the desugared definition for the non-polymorphic record pattern would contain the set of declared
* field names as an attribute on the parse tree node)
* <pre>
* let {_ | #3=x, y, z=alpha} = foo; in ...
* </pre>
* is desugared into:
* <pre>
* let
* $pattern_x_y_alpha = foo;
* x = $pattern_x_y_alpha.#3;
* y = $pattern_x_y_alpha.y;
* alpha = $pattern_x_y_alpha.z;
* in ...
* </pre>
* </ul>
*
* @author Joseph Wong
*/
final class LocalPatternMatchDeclarationChecker {
/**
* The CALCompiler associated with the calling FreeVariableFinder instance.
*/
private final CALCompiler compiler;
/**
* The calling FreeVariableFinder instance.
*/
private final FreeVariableFinder finder;
/**
* A LinkedHashMap mapping pattern-bound variable names to the corresponding VAR_ID nodes that end up in the
* desugared parse tree, in source order.
*/
private final LinkedHashMap<String, ParseTreeNode> namesToDesugaredVarNodes = new LinkedHashMap<String, ParseTreeNode>();
/**
* A LinkedHashMap mapping pattern-bound variable names to the corresponding LET_DEFN nodes that end up in the
* desugared parse tree, in source order.
*/
private final LinkedHashMap<String, ParseTreeNode> namesToDesugaredDefnNodes = new LinkedHashMap<String, ParseTreeNode>();
/**
* The PatternVariablesCollector to use for gathering up the pattern variables in the pattern match declaration.
*/
private final LocalPatternMatchDeclarationChecker.PatternVariablesCollector patternVariablesCollector;
/**
* This exception is meant to be thrown when the processing of a local pattern match declaration encounters an error in
* the declaration and cannot continue.
*
* @author Joseph Wong
*/
private static final class CannotContinuePatternMatchProcessingException extends Exception {
private static final long serialVersionUID = -4487923085595329558L;
/**
* The compiler error message to be reported for this error.
*/
private final CompilerMessage compilerMessage;
/**
* Constructs an instance of this class.
* @param compilerMessage the compiler error message to be reported for this error.
*/
CannotContinuePatternMatchProcessingException(final CompilerMessage compilerMessage) {
this.compilerMessage = compilerMessage;
}
/**
* @return the compiler error message to be reported for this error.
*/
CompilerMessage getCompilerMessage() {
return compilerMessage;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return "[CannotContinuePatternMatchProcessingException: " + compilerMessage + "]";
}
}
/**
* A utility class for collecting the pattern variables in a pattern match declaration. This class
* also handles the detection of repeated pattern variable names and also collision with the names
* of other local functions defined in the same let definition.
*
* @author Joseph Wong
*/
private static final class PatternVariablesCollector {
/**
* A LinkedHashSet of the pattern variable names, in source order.
*/
private final LinkedHashSet<String> patternVarNames = new LinkedHashSet<String>();
/**
* The source position associated with the LHS pattern in the pattern match declaration - for error reporting purposes.
*/
private final SourceRange patternMatchPatternNodeSourceRange;
/**
* The set of other local function names declared in the same let definition.
*/
private final Set<String> localFunctionNamesSet;
/**
* Constructs an instance of this class.
* @param patternMatchPatternNodeSourceRange the source range associated with the LHS pattern in the pattern match declaration - for error reporting purposes.
* @param localFunctionNamesSet the set of other local function names declared in the same let definition.
*/
PatternVariablesCollector(final SourceRange patternMatchPatternNodeSourceRange, final Set<String> localFunctionNamesSet) {
this.patternMatchPatternNodeSourceRange = patternMatchPatternNodeSourceRange;
this.localFunctionNamesSet = Collections.unmodifiableSet(localFunctionNamesSet);
}
/**
* Records the pattern-bound variable in a pattern node, if it is a VAR_ID node. If the variable is repeated
* in the pattern match declaration, or if the name has been defined elsewhere in the same let definition, a
* CannotContinuePatternMatchProcessingException is thrown.
*
* @param patternVarNode the pattern node (must be a VAR_ID or an UNDERSCORE node).
* @throws CannotContinuePatternMatchProcessingException
* if the variable is repeated in the pattern match declaration, or
* if the name has been defined elsewhere in the same let definition.
*/
void recordPatternVarNode(final ParseTreeNode patternVarNode) throws CannotContinuePatternMatchProcessingException {
patternVarNode.verifyType(CALTreeParserTokenTypes.VAR_ID, CALTreeParserTokenTypes.UNDERSCORE);
if (patternVarNode.getType() == CALTreeParserTokenTypes.VAR_ID) {
final String varName = patternVarNode.getText();
if (localFunctionNamesSet.contains(varName)) {
// Repeated definition of {functionName} in let declaration.
throw new CannotContinuePatternMatchProcessingException(
new CompilerMessage(patternVarNode, new MessageKind.Error.RepeatedDefinitionInLetDeclaration(varName)));
}
if (patternVarNames.contains(varName)) {
// Repeated variable {varName} used in binding.
throw new CannotContinuePatternMatchProcessingException(
new CompilerMessage(patternVarNode, new MessageKind.Error.RepeatedVariableUsedInBinding(varName)));
}
patternVarNames.add(varName);
}
}
/**
* 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.
*
* @return a name for the synthetic local function.
* @throws CannotContinuePatternMatchProcessingException
* if the pattern match declaration declares no pattern-bound variables.
*/
String makeSyntheticLocalFunctionName() throws CannotContinuePatternMatchProcessingException {
if (patternVarNames.isEmpty()) {
throw new CannotContinuePatternMatchProcessingException(
new CompilerMessage(patternMatchPatternNodeSourceRange, new MessageKind.Error.LocalPatternMatchDeclMustContainAtLeastOnePatternVar()));
}
return FreeVariableFinder.makeTempVarNameForDesugaredLocalPatternMatchDecl(patternVarNames);
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return "[PatternVariablesCollector: " + patternVarNames + "]";
}
}
/**
* Encapsulates the result of running the LocalPatternMatchDeclarationChecker.
*
* @author Joseph Wong
*/
static final class Result {
/**
* A LinkedHashMap mapping pattern-bound variable names to the corresponding VAR_ID nodes that end up in the
* desugared parse tree, in source order.
*/
private final LinkedHashMap/*String, ParseTreeNode*/<String, ParseTreeNode> namesToDesugaredVarNodes;
/**
* The last desugared definition that was stitched into the tree.
*/
private final ParseTreeNode lastDesugaredDefn;
/**
* Constructs an instance of this class.
* @param namesToDesugaredVarNodes a LinkedHashMap mapping pattern-bound variable names to the corresponding VAR_ID nodes
* that end up in the desugared parse tree, in source order.
* @param lastDesugaredDefn the last desugared definition that was stitched into the tree.
*/
Result(final LinkedHashMap<String, ParseTreeNode> namesToDesugaredVarNodes, final ParseTreeNode lastDesugaredDefn) {
this.namesToDesugaredVarNodes = namesToDesugaredVarNodes;
this.lastDesugaredDefn = lastDesugaredDefn;
}
/**
* @return a LinkedHashMap mapping pattern-bound variable names to the corresponding VAR_ID nodes that end up in the
* desugared parse tree, in source order.
*/
LinkedHashMap<String, ParseTreeNode> getNamesToDesugaredVarNodes() {
return namesToDesugaredVarNodes;
}
/**
* @return the last desugared definition that was stitched into the tree.
*/
ParseTreeNode getLastDesugaredDefn() {
return lastDesugaredDefn;
}
}
/**
* Constructs an instance of this class.
* @param finder the encapsulating FreeVariableFinder instance.
* @param compiler the CALCompiler instance of the FreeVariableFinder.
* @param patternVariablesCollector the PatternVariablesCollector to use for gathering up the pattern variables in the pattern match declaration.
*/
private LocalPatternMatchDeclarationChecker(final FreeVariableFinder finder, CALCompiler compiler, final LocalPatternMatchDeclarationChecker.PatternVariablesCollector patternVariablesCollector) {
this.finder = finder;
this.compiler = compiler;
this.patternVariablesCollector = patternVariablesCollector;
}
/**
* Processes a local pattern match declaration, checking the declaration for errors as well as desugaring it
* into regular local function definitions.
* <p>
* This is the main entry point of this class.
*
* @param finder the encapsulating FreeVariableFinder instance.
* @param compiler the CALCompiler instance of the FreeVariableFinder.
* @param patternMatchDeclNode the root of the subtree corresponding to a pattern match declaration.
* @param localFunctionNamesSet the set of other local function names declared in the same let definition.
* @return a Result object encapsulating the result of the processing.
*/
static LocalPatternMatchDeclarationChecker.Result processPatternMatchDeclNode(final FreeVariableFinder finder, final CALCompiler compiler, final ParseTreeNode patternMatchDeclNode, final Set<String> localFunctionNamesSet) {
final SourceRange assemblySourceRange = patternMatchDeclNode.getAssemblySourceRange();
final LocalPatternMatchDeclarationChecker.PatternVariablesCollector patternVariablesCollector = new PatternVariablesCollector(assemblySourceRange, localFunctionNamesSet);
return new LocalPatternMatchDeclarationChecker(finder, compiler, patternVariablesCollector).processPatternMatchDeclNode(patternMatchDeclNode);
}
/**
* Processes a local pattern match declaration, checking the declaration for errors as well as desugaring it
* into regular local function definitions.
* @param patternMatchDeclNode the root of the subtree corresponding to a pattern match declaration.
* @return a Result object encapsulating the result of the processing.
*/
private LocalPatternMatchDeclarationChecker.Result processPatternMatchDeclNode(final ParseTreeNode patternMatchDeclNode) {
patternMatchDeclNode.verifyType(CALTreeParserTokenTypes.LET_PATTERN_MATCH_DECL);
final ParseTreeNode patternMatchPatternNode = patternMatchDeclNode.firstChild();
final ParseTreeNode patternMatchExprNode = patternMatchPatternNode.nextSibling();
try {
switch (patternMatchPatternNode.getType()) {
case CALTreeParserTokenTypes.PATTERN_CONSTRUCTOR:
processPatternContructorNode(patternMatchDeclNode, patternMatchPatternNode, patternMatchExprNode);
break;
case CALTreeParserTokenTypes.COLON:
processColonNode(patternMatchDeclNode, patternMatchPatternNode, patternMatchExprNode);
break;
case CALTreeParserTokenTypes.TUPLE_CONSTRUCTOR:
processTupleConstructorNode(patternMatchDeclNode, patternMatchPatternNode, patternMatchExprNode);
break;
case CALTreeParserTokenTypes.RECORD_PATTERN:
processRecordPatternNode(patternMatchDeclNode, patternMatchPatternNode, patternMatchExprNode);
break;
default:
throw patternMatchPatternNode.makeExceptionForUnexpectedParseTreeNode();
}
} catch (final LocalPatternMatchDeclarationChecker.CannotContinuePatternMatchProcessingException e) {
// an error has been detected in the pattern match declaration, so report it without modifying the parse tree
compiler.logMessage(e.getCompilerMessage());
return new Result(new LinkedHashMap<String, ParseTreeNode>(), patternMatchDeclNode);
}
// Finally, if everything checks out, stitch in the new desugared definitions to replace the pattern match decl node
final ParseTreeNode lastDesugaredDefn = replacePatternMatchDeclNodeWithDesugaredDefns(patternMatchDeclNode);
return new Result(namesToDesugaredVarNodes, lastDesugaredDefn);
}
/**
* Stitches in the new desugared definitions to replace the pattern match declaration node.
* @param patternMatchDeclNode the root of the subtree corresponding to a pattern match declaration, to be
* replaced with the new desugared definitions.
*/
private ParseTreeNode replacePatternMatchDeclNodeWithDesugaredDefns(final ParseTreeNode patternMatchDeclNode) {
final ParseTreeNode origDefnNodeSibling = patternMatchDeclNode.nextSibling();
boolean firstReplacement = true;
ParseTreeNode previousSibling = patternMatchDeclNode;
for (final ParseTreeNode desugaredDefnNode : namesToDesugaredDefnNodes.values()) {
if (firstReplacement) {
// if this is the first defn, replace the original pattern match decl with this
patternMatchDeclNode.copyContentsFrom(desugaredDefnNode);
previousSibling = patternMatchDeclNode;
firstReplacement = false;
} else {
// not the first defn, so hook it up to the previous sibling
previousSibling.setNextSibling(desugaredDefnNode);
previousSibling = desugaredDefnNode;
}
}
// finally, hook up the sibling of the original pattern match decl
final ParseTreeNode lastDesugaredDefn = previousSibling;
lastDesugaredDefn.setNextSibling(origDefnNodeSibling);
return lastDesugaredDefn;
}
/**
* Processes a data cons pattern in a pattern match declaration. An CannotContinuePatternMatchProcessingException is
* thrown if the pattern contains semantic errors.
*
* @param patternMatchDeclNode the root of the subtree corresponding to the pattern match declaration.
* @param patternMatchPatternNode the root of the subtree corresponding to a data cons pattern.
* @param patternMatchExprNode the root of the subtree corresponding to the defining expression of the pattern match declaration.
* @throws CannotContinuePatternMatchProcessingException
* if the pattern contains semantic errors.
*/
private void processPatternContructorNode(final ParseTreeNode patternMatchDeclNode, final ParseTreeNode patternMatchPatternNode, final ParseTreeNode patternMatchExprNode) throws CannotContinuePatternMatchProcessingException {
patternMatchPatternNode.verifyType(CALTreeParserTokenTypes.PATTERN_CONSTRUCTOR);
ParseTreeNode dcNameListNode = patternMatchPatternNode.firstChild();
dcNameListNode.verifyType(CALTreeParserTokenTypes.DATA_CONSTRUCTOR_NAME_LIST, CALTreeParserTokenTypes.DATA_CONSTRUCTOR_NAME_SINGLETON);
ParseTreeNode dcArgBindingsNode = dcNameListNode.nextSibling();
final DataConstructor dataConstructor;
final boolean checkArity = dcArgBindingsNode.getType() == CALTreeParserTokenTypes.PATTERN_VAR_LIST;
final int impliedArity = checkArity ? dcArgBindingsNode.getNumberOfChildren() : -1;
final ParseTreeNode dcNameNode = dcNameListNode.firstChild();
dataConstructor = finder.resolveDataConsName(dcNameNode);
if (dataConstructor.getArity() == 0) {
// 0-ary data constructors cannot be used in a pattern match decl
throw new CannotContinuePatternMatchProcessingException(
new CompilerMessage(dcNameNode, new MessageKind.Error.ConstructorMustHavePositiveArityInLocalPatternMatchDecl(dataConstructor)));
} else {
//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 && 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)."
throw new CannotContinuePatternMatchProcessingException(
new CompilerMessage(dcNameNode, new MessageKind.Error.ConstructorMustHaveExactlyNArgsInLocalPatternMatchDecl(dataConstructor)));
}
}
switch (dcArgBindingsNode.getType()) {
case CALTreeParserTokenTypes.PATTERN_VAR_LIST:
desugarPatternVarListInLocalDataConsPatternMatchDecl(dcArgBindingsNode, dcNameNode, patternMatchExprNode, dataConstructor);
break;
case CALTreeParserTokenTypes.FIELD_BINDING_VAR_ASSIGNMENT_LIST:
// Perform the first pass verification and patching-up on the parse tree for the list of field bindings.
finder.firstPassProcessFieldBindingVarAssignmentListNode(dcArgBindingsNode, null, Collections.singleton(dataConstructor));
desugarFieldBindingVarAssignmentListInLocalDataConsPatternMatchDecl(dcArgBindingsNode, dcNameNode, patternMatchExprNode);
break;
default:
dcArgBindingsNode.unexpectedParseTreeNode();
break;
}
}
/**
* Processes a list cons (:) pattern in a pattern match declaration. An CannotContinuePatternMatchProcessingException is
* thrown if the pattern contains semantic errors.
*
* @param patternMatchDeclNode the root of the subtree corresponding to the pattern match declaration.
* @param patternMatchPatternNode the root of the subtree corresponding to a list cons (:) pattern.
* @param patternMatchExprNode the root of the subtree corresponding to the defining expression of the pattern match declaration.
* @throws CannotContinuePatternMatchProcessingException
* if the pattern contains semantic errors.
*/
private void processColonNode(final ParseTreeNode patternMatchDeclNode, final ParseTreeNode patternMatchPatternNode, final ParseTreeNode patternMatchExprNode) throws CannotContinuePatternMatchProcessingException {
patternMatchPatternNode.verifyType(CALTreeParserTokenTypes.COLON);
final ParseTreeNode dcNameNode = ParseTreeNode.makeQualifiedConsNode(CAL_Prelude.DataConstructors.Cons, patternMatchPatternNode.getSourcePosition());
final DataConstructor dataConstructor = finder.resolveDataConsName(dcNameNode);
desugarPatternVarListInLocalDataConsPatternMatchDecl(patternMatchPatternNode, dcNameNode, patternMatchExprNode, dataConstructor);
}
/**
* Processes a tuple pattern in a pattern match declaration. An CannotContinuePatternMatchProcessingException is
* thrown if the pattern contains semantic errors.
*
* @param patternMatchDeclNode the root of the subtree corresponding to the pattern match declaration.
* @param patternMatchPatternNode the root of the subtree corresponding to a tuple pattern.
* @param patternMatchExprNode the root of the subtree corresponding to the defining expression of the pattern match declaration.
* @throws CannotContinuePatternMatchProcessingException
* if the pattern contains semantic errors.
*/
private void processTupleConstructorNode(final ParseTreeNode patternMatchDeclNode, final ParseTreeNode patternMatchPatternNode, final ParseTreeNode patternMatchExprNode) throws CannotContinuePatternMatchProcessingException {
patternMatchPatternNode.verifyType(CALTreeParserTokenTypes.TUPLE_CONSTRUCTOR);
if (patternMatchPatternNode.hasExactlyOneChild()) {
//a one variable tuple pattern is illegal
compiler.logMessage(new CompilerMessage(patternMatchPatternNode, new MessageKind.Error.Illegal1TuplePattern()));
return;
} else if (patternMatchPatternNode.hasNoChildren()) {
// the unit pattern () cannot be used in a local pattern match decl
compiler.logMessage(new CompilerMessage(patternMatchPatternNode, new MessageKind.Error.InvalidLocalPatternMatchUnitPattern()));
return;
}
desugarLocalTuplePatternMatchDecl(patternMatchPatternNode, patternMatchExprNode);
}
/**
* Processes a record pattern in a pattern match declaration. An CannotContinuePatternMatchProcessingException is
* thrown if the pattern contains semantic errors.
*
* @param patternMatchDeclNode the root of the subtree corresponding to the pattern match declaration.
* @param patternMatchPatternNode the root of the subtree corresponding to a record pattern.
* @param patternMatchExprNode the root of the subtree corresponding to the defining expression of the pattern match declaration.
* @throws CannotContinuePatternMatchProcessingException
* if the pattern contains semantic errors.
*/
private void processRecordPatternNode(final ParseTreeNode patternMatchDeclNode, final ParseTreeNode patternMatchPatternNode, final ParseTreeNode patternMatchExprNode) throws CannotContinuePatternMatchProcessingException {
patternMatchPatternNode.verifyType(CALTreeParserTokenTypes.RECORD_PATTERN);
final ParseTreeNode baseRecordPatternNode = patternMatchPatternNode.firstChild();
baseRecordPatternNode.verifyType(CALTreeParserTokenTypes.BASE_RECORD_PATTERN);
final ParseTreeNode baseRecordPatternVarNode = baseRecordPatternNode.firstChild();
final String baseRecordVarName = null; // we do not currently support the use of non-wildcard base record patterns, so the base record var is always null
final boolean hasWildcardBaseRecordPattern;
if (baseRecordPatternVarNode != null)
{
// the base record pattern should be a variable or '_', but we only accept the '_' case.
baseRecordPatternVarNode.verifyType(CALTreeParserTokenTypes.VAR_ID, CALTreeParserTokenTypes.UNDERSCORE);
if (baseRecordPatternVarNode.getType() == CALTreeParserTokenTypes.VAR_ID) {
// we do not support having a pattern variable for the base record pattern
throw new CannotContinuePatternMatchProcessingException(
new CompilerMessage(baseRecordPatternVarNode, new MessageKind.Error.NonWildcardBaseRecordPatternNotSupportedInLocalPatternMatchDecl()));
}
hasWildcardBaseRecordPattern = true;
} else {
hasWildcardBaseRecordPattern = false;
}
final ParseTreeNode fieldBindingVarAssignmentListNode = baseRecordPatternNode.nextSibling();
// Perform the first pass verification and patching-up on the parse tree for a list of field bindings.
finder.firstPassProcessFieldBindingVarAssignmentListNode(fieldBindingVarAssignmentListNode, baseRecordVarName, Collections.<DataConstructor>emptySet());
desugarLocalRecordPatternMatchDecl(patternMatchPatternNode, patternMatchExprNode, hasWildcardBaseRecordPattern);
}
/**
* Constructs the name of the synthetic local function associated with the defining expression from a flat list of pattern nodes.
*
* @param patternVarListNode a flat list of pattern nodes.
* @return the name of the synthetic local function associated with the defining expression.
* @throws CannotContinuePatternMatchProcessingException
* if a variable is repeated in the pattern match declaration, or
* if a name has been defined elsewhere in the same let definition, or
* if the pattern match declaration declares no pattern-bound variables.
*/
private String makeTempVarNameForDesugaredLocalPatternMatchDeclFromPatternVars(final ParseTreeNode patternVarListNode) throws CannotContinuePatternMatchProcessingException {
patternVarListNode.verifyType(CALTreeParserTokenTypes.PATTERN_VAR_LIST, CALTreeParserTokenTypes.COLON, CALTreeParserTokenTypes.TUPLE_CONSTRUCTOR);
for (final ParseTreeNode patternVarNode : patternVarListNode) {
patternVariablesCollector.recordPatternVarNode(patternVarNode);
}
return patternVariablesCollector.makeSyntheticLocalFunctionName();
}
/**
* Constructs the name of the synthetic local function associated with the defining expression from a FIELD_BINDING_VAR_ASSIGNMENT_LIST nodes.
*
* @param fieldBindingVarAssignmentListNode a FIELD_BINDING_VAR_ASSIGNMENT_LIST representing field-pattern pairs.
* @return the name of the synthetic local function associated with the defining expression.
* @throws CannotContinuePatternMatchProcessingException
* if a variable is repeated in the pattern match declaration, or
* if a name has been defined elsewhere in the same let definition, or
* if the pattern match declaration declares no pattern-bound variables.
*/
private String makeTempVarNameForDesugaredLocalPatternMatchDeclFromFieldBindingVarAssignmentList(final ParseTreeNode fieldBindingVarAssignmentListNode) throws CannotContinuePatternMatchProcessingException {
fieldBindingVarAssignmentListNode.verifyType(CALTreeParserTokenTypes.FIELD_BINDING_VAR_ASSIGNMENT_LIST);
for (final ParseTreeNode fieldBindingVarAssignmentNode : fieldBindingVarAssignmentListNode) {
final ParseTreeNode fieldNameNode = fieldBindingVarAssignmentNode.firstChild();
final ParseTreeNode patternVarNode = fieldNameNode.nextSibling();
patternVariablesCollector.recordPatternVarNode(patternVarNode);
}
return patternVariablesCollector.makeSyntheticLocalFunctionName();
}
/**
* Constructs a regular local function definition for the desugared representation.
*
* @param nameNode the node to use for the local function name.
* @param desugaredExprNode the associated defining expression for the local function.
* @return the parse tree for the local function definition.
*/
private ParseTreeNode makeDesugaredLetDefnNodeForLocalPatternMatchDecl(final ParseTreeNode nameNode, final ParseTreeNode desugaredExprNode) {
nameNode.verifyType(CALTreeParserTokenTypes.VAR_ID);
final String localFunctionName = nameNode.getText();
// It is important that the desugared definition have the source position of the pattern var name,
// so that any subsequent type checking error messages can refer to the original pattern var (for which this defn is created)
final ParseTreeNode localFunctionNode = new ParseTreeNode(
CALTreeParserTokenTypes.LET_DEFN, "LET_DEFN",
nameNode.getSourcePosition());
localFunctionNode.setIsDesugaredPatternMatchForLetDefn(true); // it's important to specially mark this LET_DEFN so that the type checker can emit appropriate error messages
final ParseTreeNode optionalCALDocNode = SourceModel.makeOptionalCALDocNode(null);
final ParseTreeNode functionNameNode = new ParseTreeNode(CALTreeParserTokenTypes.VAR_ID, localFunctionName, nameNode.getSourcePosition()); // we make a copy of the name node because we don't want it to be tangled with the original parse tree
functionNameNode.setIsSyntheticVarOrRecordFieldSelection(nameNode.getIsSyntheticVarOrRecordFieldSelection()); // keep the synthetic marker as well
final ParseTreeNode paramListNode = new ParseTreeNode(CALTreeParserTokenTypes.FUNCTION_PARAM_LIST, "FUNCTION_PARAM_LIST");
final ParseTreeNode exprNode = desugaredExprNode;
localFunctionNode.setFirstChild(optionalCALDocNode);
optionalCALDocNode.setNextSibling(functionNameNode);
functionNameNode.setNextSibling(paramListNode);
paramListNode.setNextSibling(exprNode);
namesToDesugaredVarNodes.put(localFunctionName, functionNameNode);
namesToDesugaredDefnNodes.put(localFunctionName, localFunctionNode);
return localFunctionNode;
}
/**
* Desugars a data cons pattern or a list cons pattern into a series of local function definitions - one for hosting
* the defining expression of the pattern match declaration, and one for each pattern-bound variable.
*
* @param patternVarListNode a flat list of pattern nodes.
* @param dcNameNode the node representing the name of the data constructor.
* @param patternMatchExprNode the root of the subtree corresponding to the defining expression of the pattern match declaration.
* @param dataConstructor the associated DataConstructor entity.
* @throws CannotContinuePatternMatchProcessingException
* if a variable is repeated in the pattern match declaration, or
* if a name has been defined elsewhere in the same let definition, or
* if the pattern match declaration declares no pattern-bound variables.
*/
private void desugarPatternVarListInLocalDataConsPatternMatchDecl(final ParseTreeNode patternVarListNode, final ParseTreeNode dcNameNode, final ParseTreeNode patternMatchExprNode, final DataConstructor dataConstructor) throws CannotContinuePatternMatchProcessingException {
patternVarListNode.verifyType(CALTreeParserTokenTypes.PATTERN_VAR_LIST, CALTreeParserTokenTypes.COLON);
final String tempVarName = makeTempVarNameForDesugaredLocalPatternMatchDeclFromPatternVars(patternVarListNode);
////
/// Create the desugared definitions in source order of the patterns. (The order is important for the LocalFunctionIdentifierGenerator)
//
int fieldIndex = 0;
for (final ParseTreeNode patternVarNode : patternVarListNode) {
final FieldName fieldName = dataConstructor.getNthFieldName(fieldIndex);
desugarPatternVarNodeInLocalDataConsPatternMatchDecl(patternVarNode, dcNameNode, fieldName, tempVarName);
fieldIndex++;
}
////
/// Add the defining expression last. (The order is important for the LocalFunctionIdentifierGenerator)
//
desugarExprNodeInLocalPatternMatchDecl(tempVarName, patternMatchExprNode, null, true);
}
/**
* Desugars a data cons pattern into a series of local function definitions - one for hosting
* the defining expression of the pattern match declaration, and one for each pattern-bound variable.
*
* @param dcArgBindingsNode a FIELD_BINDING_VAR_ASSIGNMENT_LIST representing field-pattern pairs.
* @param dcNameNode the node representing the name of the data constructor.
* @param patternMatchExprNode the root of the subtree corresponding to the defining expression of the pattern match declaration.
* @throws CannotContinuePatternMatchProcessingException
* if a variable is repeated in the pattern match declaration, or
* if a name has been defined elsewhere in the same let definition, or
* if the pattern match declaration declares no pattern-bound variables.
*/
private void desugarFieldBindingVarAssignmentListInLocalDataConsPatternMatchDecl(final ParseTreeNode dcArgBindingsNode, final ParseTreeNode dcNameNode, final ParseTreeNode patternMatchExprNode) throws CannotContinuePatternMatchProcessingException {
dcArgBindingsNode.verifyType(CALTreeParserTokenTypes.FIELD_BINDING_VAR_ASSIGNMENT_LIST);
final String tempVarName = makeTempVarNameForDesugaredLocalPatternMatchDeclFromFieldBindingVarAssignmentList(dcArgBindingsNode);
////
/// Create the desugared definitions in source order of the patterns. (The order is important for the LocalFunctionIdentifierGenerator)
//
for (final ParseTreeNode fieldBindingVarAssignmentNode : dcArgBindingsNode) {
final ParseTreeNode fieldNameNode = fieldBindingVarAssignmentNode.firstChild();
final ParseTreeNode patternVarNode = fieldNameNode.nextSibling();
final FieldName fieldName = FieldName.make(fieldNameNode.getText());
desugarPatternVarNodeInLocalDataConsPatternMatchDecl(patternVarNode, dcNameNode, fieldName, tempVarName);
}
////
/// Add the defining expression last. (The order is important for the LocalFunctionIdentifierGenerator)
//
desugarExprNodeInLocalPatternMatchDecl(tempVarName, patternMatchExprNode, null, true);
}
/**
* Desugars a tuple pattern into a series of local function definitions - one for hosting
* the defining expression of the pattern match declaration, and one for each pattern-bound variable.
*
* @param tuplePatternNode the root of the subtree corresponding to a tuple pattern.
* @param patternMatchExprNode the root of the subtree corresponding to the defining expression of the pattern match declaration.
* @throws CannotContinuePatternMatchProcessingException
* if a variable is repeated in the pattern match declaration, or
* if a name has been defined elsewhere in the same let definition, or
* if the pattern match declaration declares no pattern-bound variables.
*/
private void desugarLocalTuplePatternMatchDecl(final ParseTreeNode tuplePatternNode, final ParseTreeNode patternMatchExprNode) throws CannotContinuePatternMatchProcessingException {
tuplePatternNode.verifyType(CALTreeParserTokenTypes.TUPLE_CONSTRUCTOR);
final String tempVarName = makeTempVarNameForDesugaredLocalPatternMatchDeclFromPatternVars(tuplePatternNode);
final SortedSet/*FieldName*/<FieldName> fieldNames = new TreeSet<FieldName>();
////
/// Create the desugared definitions in source order of the patterns. (The order is important for the LocalFunctionIdentifierGenerator)
//
int ordinal = 1; // the first ordinal field name is #1
for (final ParseTreeNode patternVarNode : tuplePatternNode) {
final FieldName fieldName = FieldName.makeOrdinalField(ordinal);
fieldNames.add(fieldName);
desugarPatternVarNodeInLocalRecordPatternMatchDecl(patternVarNode, fieldName, patternVarNode.getSourcePosition(), tempVarName);
ordinal++;
}
////
/// Add the defining expression last. (The order is important for the LocalFunctionIdentifierGenerator)
//
desugarExprNodeInLocalPatternMatchDecl(tempVarName, patternMatchExprNode, fieldNames, true);
}
/**
* Desugars a record pattern into a series of local function definitions - one for hosting
* the defining expression of the pattern match declaration, and one for each pattern-bound variable.
*
* @param recordPatternNode the root of the subtree corresponding to a record pattern.
* @param patternMatchExprNode the root of the subtree corresponding to the defining expression of the pattern match declaration.
* @param hasWildcardBaseRecordPattern whether the record pattern has a wildcard (_) base record pattern.
* @throws CannotContinuePatternMatchProcessingException
* if a variable is repeated in the pattern match declaration, or
* if a name has been defined elsewhere in the same let definition, or
* if the pattern match declaration declares no pattern-bound variables.
*/
private void desugarLocalRecordPatternMatchDecl(final ParseTreeNode recordPatternNode, final ParseTreeNode patternMatchExprNode, final boolean hasWildcardBaseRecordPattern) throws CannotContinuePatternMatchProcessingException {
final ParseTreeNode baseRecordPatternNode = recordPatternNode.firstChild();
baseRecordPatternNode.verifyType(CALTreeParserTokenTypes.BASE_RECORD_PATTERN);
final ParseTreeNode fieldBindingVarAssignmentListNode = baseRecordPatternNode.nextSibling();
final String tempVarName = makeTempVarNameForDesugaredLocalPatternMatchDeclFromFieldBindingVarAssignmentList(fieldBindingVarAssignmentListNode);
final SortedSet<FieldName> fieldNames = new TreeSet<FieldName>();
////
/// Create the desugared definitions in source order of the patterns. (The order is important for the LocalFunctionIdentifierGenerator)
//
for (final ParseTreeNode fieldBindingVarAssignmentNode : fieldBindingVarAssignmentListNode) {
final ParseTreeNode fieldNameNode = fieldBindingVarAssignmentNode.firstChild();
final ParseTreeNode patternVarNode = fieldNameNode.nextSibling();
final FieldName fieldName = FieldName.make(fieldNameNode.getText());
fieldNames.add(fieldName);
desugarPatternVarNodeInLocalRecordPatternMatchDecl(patternVarNode, fieldName, fieldNameNode.getSourcePosition(), tempVarName);
}
////
/// Add the defining expression last. (The order is important for the LocalFunctionIdentifierGenerator)
//
desugarExprNodeInLocalPatternMatchDecl(tempVarName, patternMatchExprNode, fieldNames, !hasWildcardBaseRecordPattern);
}
/**
* Constructs the synthetic local function for hosting the defining expression of the original pattern match declaration.
*
* @param tempVarName the name for the synthetic local function which is to host the defining expression of the original pattern match declaration.
* @param patternMatchExprNode the root of the subtree corresponding to the defining expression of the pattern match declaration.
* @param constrainingFieldNames a SortedSet of {@link FieldName}s corresponding to a non-polymorphic record pattern.
* Can be null if the pattern match declaration is not associated with a non-polymorphic record pattern.
* @param isNonPolymorphic whether the record pattern is non-polymorphic. This parameter is ignored if <code>constrainingFieldNames</code> is null.
*/
private void desugarExprNodeInLocalPatternMatchDecl(final String tempVarName, final ParseTreeNode patternMatchExprNode, final SortedSet/*FieldName*/<FieldName> constrainingFieldNames, boolean isNonPolymorphic) {
final ParseTreeNode tempVarNameNode = new ParseTreeNode(CALTreeParserTokenTypes.VAR_ID, tempVarName);
tempVarNameNode.setIsSyntheticVarOrRecordFieldSelection(true);
final ParseTreeNode desugaredDefnNode = makeDesugaredLetDefnNodeForLocalPatternMatchDecl(tempVarNameNode, patternMatchExprNode);
if (isNonPolymorphic) {
desugaredDefnNode.setDeclaredFieldsInNonPolymorphicRecordPatternMatchForLetDefn(constrainingFieldNames);
} else {
desugaredDefnNode.setDeclaredFieldsInPolymorphicRecordPatternMatchForLetDefn(constrainingFieldNames);
}
}
/**
* Constructs a local function definition corresponding to a pattern-bound variable in a data cons or list cons pattern.
*
* @param patternVarNode the representing the pattern-bound variable.
* @param dcNameNode the node representing the name of the data constructor.
* @param fieldName the name of the data constructor field which is bound to the pattern-bound variable.
* @param tempVarName the name for the synthetic local function which is to host the defining expression of the original pattern match declaration.
*/
private void desugarPatternVarNodeInLocalDataConsPatternMatchDecl(final ParseTreeNode patternVarNode, final ParseTreeNode dcNameNode, final FieldName fieldName, final String tempVarName) {
switch (patternVarNode.getType()) {
case CALTreeParserTokenTypes.VAR_ID:
{
// It is important that the desugared expression have the source position of the data cons name,
// so that the ErrorInfo object for reporting data cons mismatch would point at the original data cons
// in the pattern match declaration
final ParseTreeNode desugaredExprNode = new ParseTreeNode(
CALTreeParserTokenTypes.SELECT_DATA_CONSTRUCTOR_FIELD, "SELECT_DATA_CONSTRUCTOR_FIELD",
dcNameNode.getSourcePosition());
final ParseTreeNode exprNode = ParseTreeNode.makeUnqualifiedVarNode(tempVarName, null);
exprNode.firstChild().nextSibling().setIsSyntheticVarOrRecordFieldSelection(true); // mark the tempVar's VAR_ID node as synthetic
final ParseTreeNode dataConsNameNode = dcNameNode.copyParseTree(); // we make a copy because the data cons name may need to appear multiple times, once per desugared pattern defn.
final ParseTreeNode fieldNameNode = SourceModel.makeFieldNameExpr(fieldName);
desugaredExprNode.setFirstChild(exprNode);
exprNode.setNextSibling(dataConsNameNode);
dataConsNameNode.setNextSibling(fieldNameNode);
makeDesugaredLetDefnNodeForLocalPatternMatchDecl(patternVarNode, desugaredExprNode);
break;
}
case CALTreeParserTokenTypes.UNDERSCORE:
break;
default:
throw patternVarNode.makeExceptionForUnexpectedParseTreeNode();
}
}
/**
* Constructs a local function definition corresponding to a pattern-bound variable in a record pattern.
*
* @param patternVarNode the representing the pattern-bound variable.
* @param fieldName the name of the record field which is bound to the pattern-bound variable.
* @param fieldNameSourcePosition the source position of the occurence of the field name in the original source.
* @param tempVarName the name for the synthetic local function which is to host the defining expression of the original pattern match declaration.
*/
private void desugarPatternVarNodeInLocalRecordPatternMatchDecl(final ParseTreeNode patternVarNode, final FieldName fieldName, final SourcePosition fieldNameSourcePosition, final String tempVarName) {
switch (patternVarNode.getType()) {
case CALTreeParserTokenTypes.VAR_ID:
{
// This expression node does not need a source position because it cannot have an ErrorInfo object
// associated with it (because unlike data cons selection, there will not be cases of runtime data cons mismatch
// that need to be reported as runtime errors)
final ParseTreeNode desugaredExprNode = new ParseTreeNode(
CALTreeParserTokenTypes.SELECT_RECORD_FIELD, "SELECT_RECORD_FIELD");
desugaredExprNode.setIsSyntheticVarOrRecordFieldSelection(true); // mark the node as synthetic, so that type-checking errors can be reported appropriately
// It is important that the tempVar expression have the source position of the var name,
// so that type-checking errors for the expression can be associated with the pattern-bound var.
// e.g. the error for the ambiguous type of x in:
//
// public foo8 = let (x, y) = (1, 2); in (x + 10.0, x + 20 :: Prelude.Int);
//
final ParseTreeNode exprNode = ParseTreeNode.makeUnqualifiedVarNode(tempVarName, patternVarNode.getSourcePosition());
exprNode.firstChild().nextSibling().setIsSyntheticVarOrRecordFieldSelection(true); // mark the tempVar's VAR_ID node as synthetic
// It is important that the field name has the original source position,
// so that type-checking errors for the field name can be associated with the pattern.
// e.g. the error for missing fields:
//
// public foo6a = let {x} = {a = "foo", b = "bar"}; in x;
// public foo6b = let {_|y} = {a = "foo", b = "bar"}; in y;
// public foo7c = let (a, b, c, d) = ('a', 'b', 'c'); in a;
//
final ParseTreeNode fieldNameNode;
if (fieldName instanceof FieldName.Ordinal) {
fieldNameNode = new ParseTreeNode(CALTreeParserTokenTypes.ORDINAL_FIELD_NAME, fieldName.getCalSourceForm(), fieldNameSourcePosition);
} else {
fieldNameNode = new ParseTreeNode(CALTreeParserTokenTypes.VAR_ID, fieldName.getCalSourceForm(), fieldNameSourcePosition);
}
desugaredExprNode.setFirstChild(exprNode);
exprNode.setNextSibling(fieldNameNode);
makeDesugaredLetDefnNodeForLocalPatternMatchDecl(patternVarNode, desugaredExprNode);
break;
}
case CALTreeParserTokenTypes.UNDERSCORE:
break;
default:
throw patternVarNode.makeExceptionForUnexpectedParseTreeNode();
}
}
}