/*
* 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.
*/
/*
* CALSourceGenerator.java
* Creation date: Nov 20, 2002.
* By: Edward Lam
*/
package org.openquark.cal.compiler;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import org.openquark.cal.compiler.IdentifierResolver.LocalBindingsProcessor;
import org.openquark.cal.compiler.CompositionNode.Collector;
import org.openquark.cal.compiler.CompositionNode.CompositionArgument;
import org.openquark.cal.compiler.CompositionNode.Emitter;
import org.openquark.cal.compiler.SourceModel.Expr.Record.FieldModification;
import org.openquark.cal.module.Cal.Core.CAL_Prelude;
import org.openquark.util.Pair;
/**
* Given a connected set of nodes representing a composition tree, CALSourceGenerator can be used
* to generate the corresponding CAL source code for that tree.
* @author Edward Lam
*/
public final class CALSourceGenerator {
/** A source model call to the error function.
* This is used while generating source for type checking a composition node graph,
* when extra arguments are used in a collector definition which don't appear as emitter arguments.
*/
private static final SourceModel.Expr EXTRA_ARG_ERROR_CALL_EXPR =
CAL_Prelude.Functions.error(SourceModel.Expr.Literal.StringLit.make("extraArg"));
/**
* A helper class to represent the results of source code analysis and generation on a given subtree.
* @author Edward Lam
*/
private static class SourceModelGenerationResult {
/** The expression for the analyzed tree */
private final SourceModel.Expr expression;
/** The untraversed collectors in the analyzed tree. */
private final Set<Collector> untraversedCollectors;
/** The collectors used (by Emitters or Reflectors) in descendant aggregations,
* but not defined in that aggregation. */
private final Set<Collector> unaggregatedCollectorSet;
/**
* Constructor for a SourceModelGenerationResult.
* @param expression the source model of the expression generated.
* @param untraversedCollectors the untraversed collectors in the analyzed tree.
* @param unaggregatedCollectorSet the collectors used (by Emitters or Reflectors)
* in descendant aggregations, but not defined in that aggregation
*/
SourceModelGenerationResult(SourceModel.Expr expression, Set<Collector> untraversedCollectors, Set<Collector> unaggregatedCollectorSet) {
this.expression = expression;
this.untraversedCollectors = new HashSet<Collector>(untraversedCollectors);
this.unaggregatedCollectorSet = new HashSet<Collector>(unaggregatedCollectorSet);
}
/**
* Get the expression.
* @return the expression.
*/
SourceModel.Expr getExpression() {
return expression;
}
/**
* Get the untraversed collectors.
* @return Set the untraversed collectors in this subtree.
*/
Set<Collector> getUntraversedCollectors() {
return new HashSet<Collector>(untraversedCollectors);
}
/**
* Returns the unaggregated collectors.
* @return Set the collectors used (by Emitters) in child aggregations,
* but not defined in that aggregation.
*/
Set<Collector> getUnaggregatedCollectorSet() {
return unaggregatedCollectorSet;
}
}
/**
* A helper class to encapsulate the result of getCheckGraphSource().
* @author Edward Lam
*/
static class CheckGraphSource {
/** The source model used to generate the types in the graph. */
private final SourceModel.FunctionDefn graphFunction;
/**
* Ordered map from CompositionNode root to the arguments targeting that root.
* An iterator over the keys of the map will return the roots in the order in which they appear as args in the graph text.
*/
private final Map<CompositionNode, List<CompositionArgument>> rootToArgumentsMap;
/** The arguments which are not used by the collector which they target. */
private final List<CompositionArgument> unusedArgumentList;
/** Map from recursive emitter arg to the arg which it reflects. */
private final Map<CompositionArgument, CompositionArgument> recursiveEmitterArgumentToReflectedInputMap;
/**
* Trivial constructor for this class.
* @param graphFunction the source model used to generate types in the graph.
* @param rootToArgumentsMap
* Ordered map from CompositionNode root to the arguments targeting that root.
* @param unusedArgumentList the arguments which are not used by the collector which they target.
* @param recursiveEmitterArgumentToReflectedInputMap
* map from recursive emitter arg to the arg which it reflects.
*/
CheckGraphSource(
SourceModel.FunctionDefn graphFunction,
Map<CompositionNode, List<CompositionArgument>> rootToArgumentsMap,
List<CompositionArgument> unusedArgumentList,
Map<CompositionArgument, CompositionArgument> recursiveEmitterArgumentToReflectedInputMap) {
this.graphFunction = graphFunction;
this.rootToArgumentsMap = rootToArgumentsMap;
this.unusedArgumentList = unusedArgumentList;
this.recursiveEmitterArgumentToReflectedInputMap = recursiveEmitterArgumentToReflectedInputMap;
}
/**
* Returns the generated source model of the sc that represents a typecheck-able representation of the graph.
* @return String
*/
SourceModel.FunctionDefn getGraphFunction() {
return graphFunction;
}
/**
* Returns the rootToArgumentsMap.
* @return
* Ordered map from CompositionNode root to the arguments targeting that root.
* An iterator over the keys of the map will return the roots in the order in which they appear as args in the graph text.
*/
Map<CompositionNode, List<CompositionArgument>> getRootToArgumentsMap() {
return new LinkedHashMap<CompositionNode, List<CompositionArgument>>(rootToArgumentsMap);
}
/**
* @return The arguments which are not used by the collector which they target.
*/
List<CompositionArgument> getUnusedArgumentList() {
return new ArrayList<CompositionArgument>(unusedArgumentList);
}
/**
* @return map from recursive emitter arg to the arg which it reflects.
*/
public Map<CompositionArgument, CompositionArgument> getRecursiveEmitterArgumentToReflectedInputMap() {
return new HashMap<CompositionArgument, CompositionArgument>(recursiveEmitterArgumentToReflectedInputMap);
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return super.toString() + "\nGraph text: " + graphFunction.toSourceText() + "\n";
}
}
/**
* A class to conveniently encapsulate some information about the CompositionNode graph.
* During source generation, the GraphInfo object is created to pre-calculate some info needed during actual text generation.
*
* GraphInfo construction occurs in three steps:
* - calculation of collector-target info.
* Maps collectors to targets and vice versa.
* - calculation of non-target related collector info.
* Calculates collector names and collector arguments.
* - calculation of argument names.
*
* @author Edward Lam
*/
private static class GraphInfo {
/**
* Map to unconnected collector from its argument, if such collector definitions are elided.
* Null if such definitions are not elided.
*/
private final Map<CompositionArgument, Collector> inputToNakedCollectorMap;
/** Calculated info about collector targets. */
private final CollectorTargetInfo collectorTargetInfo;
/** Map from recursive emitter arg to the arg which it reflects. */
private final Map<CompositionArgument, CompositionArgument> recursiveEmitterArgumentToReflectedInputMap;
/** Map from collector to name. */
private final Map<Collector, String> collectorToNameMap = new HashMap<Collector, String>();
/**
* Map from collector to the arguments which appear in that collector's definition.
* Unused arguments are mapped to a null key.
*/
private final Map<Collector, List<CompositionArgument>> collectorToArgumentsMap = new HashMap<Collector, List<CompositionArgument>>();
/** Map from argument to its name. */
private final Map<CompositionArgument, String> argumentToNameMap = new HashMap<CompositionArgument, String>();
/**
* Map from collector to corresponding root node
* Because all roots are required to be rooted by a collector during type check source generation,
* collectors are attached to non-collected roots, and the original root is tracked using this map.
*/
private final Map<Collector, CompositionNode> collectedRootToRootNodeMap = new HashMap<Collector, CompositionNode>();
/** The list of arguments which are unused by the collector they target. */
private final List<CompositionArgument> unusedArgumentList = new ArrayList<CompositionArgument>();
/** If non-null, map from collector to its replacement, for all collectors targeted by
* collectors known to this info object, but not actually in this object.
* This is needed because, during generation of source for type checking composition node graphs, a group of root nodes
* will be provided, but some of the nodes targeted by these nodes will not be included. */
private final Map<Collector, SyntheticCollector> excludedCollectorReplacementMap;
/** Map caching the result of calls to getCollectorArguments()
* Maps the argument to the result. */
private Map<Collector, List<CompositionArgument>> cachedCollectorArgumentsMap = new HashMap<Collector, List<CompositionArgument>>();
/**
* This class encapsulates information about collector targets calculated during source generation.
* @author Edward Lam
*/
private static class CollectorTargetInfo {
/** The target of the graph. */
private final Collector graphTarget;
/** Map from collector to the collectors which target that collector. */
private final Map<Collector, Set<Collector>> collectorToTargetingCollectorsMap = new HashMap<Collector, Set<Collector>>();
/** Map from collector to its target. */
private final Map<Collector, Collector> collectorToTargetMap = new HashMap<Collector, Collector>();
/**
* Trivial constructor for this class..
* @param graphTarget the graph target.
*/
CollectorTargetInfo(Collector graphTarget) {
this.graphTarget = graphTarget;
}
/**
* Map a collector to its target.
* @param collector the collector in question.
* @param excludedCollectorReplacementMap
* if non-null, map from excluded collector to the collector with which it should be considered to be replaced.
*/
void mapCollectorToTarget(Collector collector, Map<Collector, SyntheticCollector> excludedCollectorReplacementMap) {
// Add the collector-target mappings.
Collector targetCollector = collector.getTargetCollector();
// If it's supposed to be an excluded collector, set the replacement as the target.
if (excludedCollectorReplacementMap != null && excludedCollectorReplacementMap.containsKey(targetCollector)) {
targetCollector = excludedCollectorReplacementMap.get(targetCollector);
}
// Special case: sometimes a synthetic collector is created to encapsulate all other collectors.
// In this case, the formerly top-level target should be treated as targeting the synthetic collector.
if (targetCollector == null && collector != graphTarget) {
targetCollector = graphTarget;
}
collectorToTargetMap.put(collector, targetCollector);
Set<Collector> collectorsTargetingTarget = collectorToTargetingCollectorsMap.get(targetCollector);
if (collectorsTargetingTarget == null) {
collectorsTargetingTarget = new HashSet<Collector>();
collectorToTargetingCollectorsMap.put(targetCollector, collectorsTargetingTarget);
}
collectorsTargetingTarget.add(collector);
}
/**
* Reconcile the info collected by this info object to reflect any collector visibility constraints
* eg. because graph target is not actually the target.
* This method should be called after all collectors have been mapped.
*/
void reconcileCollectorTargets() {
// First off, ensure the graph target has a null target.
collectorToTargetMap.put(graphTarget, null);
// (Set of Collector) Collectors which are targeted, but not mapped.
Set<Collector> unmappedTargetedCollectors = new HashSet<Collector>();
// Populate the map..
for (final Collector targetedCollector : collectorToTargetingCollectorsMap.keySet()) {
if (targetedCollector != null && !isCollectorMapped(targetedCollector)) {
unmappedTargetedCollectors.add(targetedCollector);
}
}
// Unmapped collectors are collectors which aren't used in subtrees descending from the graph target.
// Any mapped collectors which target an unmapped collector will be set instead to target the graph target.
// Below, this is referred to as "detargeting" the targeted unmapped collector.
if (!unmappedTargetedCollectors.isEmpty()) {
// Get the graph target's targeting collectors.
Set<Collector> collectorsTargetingGraphTarget = collectorToTargetingCollectorsMap.get(graphTarget);
if (collectorsTargetingGraphTarget == null) {
collectorsTargetingGraphTarget = new HashSet<Collector>();
collectorToTargetingCollectorsMap.put(graphTarget, collectorsTargetingGraphTarget);
}
// Retarget any collectors targeting a detargeted collector to the graph target.
for (final Collector collectorToDetarget : unmappedTargetedCollectors) {
Set<Collector> collectorsTargetingCollectorToDetarget = collectorToTargetingCollectorsMap.get(collectorToDetarget);
if (collectorsTargetingCollectorToDetarget != null) {
// Iterate over the collectors targeting the collector to detarget.
for (final Collector collectorTargetingCollectorToDetarget : collectorsTargetingCollectorToDetarget) {
// Do not retarget the graph target to itself!!
if (collectorTargetingCollectorToDetarget == graphTarget) {
continue;
}
// Don't add entries for other unmapped collectors
if (unmappedTargetedCollectors.contains(collectorTargetingCollectorToDetarget)) {
continue;
}
collectorToTargetMap.put(collectorTargetingCollectorToDetarget, graphTarget); // overwrites the old mapping.
collectorsTargetingGraphTarget.add(collectorTargetingCollectorToDetarget);
}
}
// Remove any mappings for collectorToDetarget.
collectorToTargetMap.remove(collectorToDetarget);
Set<Collector> collectorsTargetingDetargetTarget = collectorToTargetingCollectorsMap.get(collectorToDetarget.getTargetCollector());
if (collectorsTargetingDetargetTarget != null) {
collectorsTargetingDetargetTarget.remove(collectorToDetarget);
}
}
}
}
/**
* Return whether the given collector has been mapped.
* @param collector the collector in question.
* @return whether the given collector was mapped via call to mapCollectorToTarget().
*/
boolean isCollectorMapped(Collector collector) {
return collectorToTargetMap.containsKey(collector);
}
/**
* Get the collectors mapped by this info object.
* @return (Set of Collector)
*/
Set<Collector> getCollectors() {
return Collections.unmodifiableSet(collectorToTargetMap.keySet());
}
/**
* Get the target of the graph, as calculated by this info object.
* @return the graph target.
*/
public Collector getGraphTarget() {
return graphTarget;
}
/**
* Get a collector's target, as calculated by this info object.
* @param collector the collector in question.
* @return the collector's target, according to this info object.
*/
Collector getCollectorTarget(Collector collector) {
return collectorToTargetMap.get(collector);
}
/**
* Get the collectors targeting a given collector, as calculated by this info object.
* @param collector the collector in question.
* @return the collectors targeting a given collector, as calculated by this info object.
*/
List<Collector> getTargetingCollectors(Collector collector) {
// Create the list.
List<Collector> targetingCollectorList = new ArrayList<Collector>();
// Add all the collectors found to target this collector from the map.
Set<Collector> targetingCollectorSet = collectorToTargetingCollectorsMap.get(collector);
if (targetingCollectorSet != null) {
targetingCollectorList.addAll(targetingCollectorSet);
}
// Return the list.
return targetingCollectorList;
}
}
/**
* Constructor for an info considering a given collector to be the root.
*
* Notes:
* 1) Even though in theory, only top-level targets should be executed, in practice a client will often find
* it more convenient to execute non-top level gems,
* 2) A Collector "knows" that arguments are pointing to it, but we need to traverse the composition node graph
* to find whether the argument should appear in the source (it may not because its subtree may be unused).
* Unbound arguments will be targeted to the root
*
* @param collector the collector which is considered to be the root.
* @param scName the name to use for the root collector, or null to use the collector's name.
*/
GraphInfo(Collector collector, String scName) {
excludedCollectorReplacementMap = null;
inputToNakedCollectorMap = new HashMap<CompositionArgument, Collector>();
if (scName != null) {
collectorToNameMap.put(collector, scName);
}
collectorTargetInfo = new CollectorTargetInfo(collector);
calculateCollectorTargetInfo(collectorTargetInfo);
recursiveEmitterArgumentToReflectedInputMap = calculateFreeRecursiveEmitterArguments(collectorTargetInfo.getCollectors());
calculateCollectorNonTargetInfo(collector, Collections.<String>emptySet(), null);
calculateArgumentNames(collector, Collections.<String>emptySet(), getCollectorNames(), collectorTargetInfo);
}
/**
* Constructor for an info suitable for type checking the given root nodes.
* @param inputRootNodes the root nodes in question.
*/
GraphInfo(Set<? extends CompositionNode> inputRootNodes) {
inputToNakedCollectorMap = null;
// Calculate info for excluded collectors (ie. collectors targeted by rootNodes).
excludedCollectorReplacementMap = getExcludedCollectors(inputRootNodes);
Set<CompositionNode> rootNodesToAnalyze = new HashSet<CompositionNode>(inputRootNodes);
rootNodesToAnalyze.addAll(excludedCollectorReplacementMap.values());
// Calculate the target for the CompositionNode graph.
Collector rootNodeTarget = getGraphTarget(rootNodesToAnalyze, excludedCollectorReplacementMap);
// Ensure the target is a member of the root nodes set.
rootNodesToAnalyze.add(rootNodeTarget);
// Create a dummy collector for which the body will be the check graph expression. Set this as the graph target.
CompositionNode checkGraphConnectedNode = new CompositionNode.Value() {
public String getStringValue() {
return "0.0";
}
public SourceModel.Expr getSourceModel() {
return SourceModel.Expr.Literal.Double.make(0.0);
}
public int getNArguments() {
return 0;
}
public CompositionArgument getNodeArgument(int i) {
return null;
}
};
Collector graphTarget = new SyntheticCollector("dummyCollector", Collections.<CompositionArgument>emptyList(), checkGraphConnectedNode);
rootNodesToAnalyze.add(graphTarget);
// The set of all collector names.
Set<String> collectorNames = new HashSet<String>();
// Gather collector names, collected and uncollected roots.
Set<CompositionNode> uncollectedRoots = new HashSet<CompositionNode>();
for (final CompositionNode nextRoot : rootNodesToAnalyze) {
if (!(nextRoot instanceof Collector)) {
uncollectedRoots.add(nextRoot);
} else {
collectedRootToRootNodeMap.put((Collector)nextRoot, nextRoot);
collectorNames.add(((Collector)nextRoot).getUnqualifiedName());
}
}
// Attach collectors to uncollected roots. Add these to the root nodes..
Map<Collector, CompositionNode> collectedToUncollectedRootMap = attachCollectorsToUncollectedRoots(uncollectedRoots, rootNodeTarget,
collectorNames, excludedCollectorReplacementMap);
collectedRootToRootNodeMap.putAll(collectedToUncollectedRootMap);
// Get the set of collectors.
Set<Collector> collectorSet = new HashSet<Collector>(collectedRootToRootNodeMap.keySet());
// Calculate collector target info.
collectorTargetInfo = new CollectorTargetInfo(graphTarget);
calculateCollectorTargetInfo(collectorSet, collectorTargetInfo, excludedCollectorReplacementMap);
// Calculate free recursive emitter arguments.
recursiveEmitterArgumentToReflectedInputMap = calculateFreeRecursiveEmitterArguments(collectorTargetInfo.getCollectors());
// Get info on what arguments are targeting each collector (as targetCollectorTargetInfo).
CollectorTargetInfo targetCollectorTargetInfo = new CollectorTargetInfo(rootNodeTarget);
for (final CompositionNode rootNode : rootNodesToAnalyze) {
if (rootNode instanceof Collector && !targetCollectorTargetInfo.isCollectorMapped((Collector)rootNode)) {
calculateCollectorTargetInfoHelper((Collector)rootNode, targetCollectorTargetInfo);
}
}
// Calculate collector non-target info.
calculateCollectorNonTargetInfo(graphTarget, Collections.<String>emptySet(), targetCollectorTargetInfo);
// Calculate names for all the arguments.
calculateArgumentNames(graphTarget, Collections.<String>emptySet(), collectorNames, collectorTargetInfo);
}
/**
* Get the target from a given group of root nodes.
* @param rootNodes the roots in question.
* @param excludedCollectorReplacementMap map from excluded collector to its replacement.
* This may be modified if the graph target is not already found to be in either the set or the map.
* @return the first collector root in the set which has a null target.
* If there are no such collectors, a new temporary collector will be returned.
*/
private static Collector getGraphTarget(Set<CompositionNode> rootNodes, Map<Collector, SyntheticCollector> excludedCollectorReplacementMap) {
Collector graphTarget = null;
// Calculate the target for the CompositionNode graph..
for (final CompositionNode nextRoot : rootNodes) {
// When we find the first collector, find its outermost enclosing collector. This should be the target.
if (nextRoot instanceof Collector) {
graphTarget = (Collector)nextRoot;
Collector enclosingCollector = graphTarget.getTargetCollector();
while (enclosingCollector != null) {
graphTarget = enclosingCollector;
enclosingCollector = graphTarget.getTargetCollector();
}
break;
}
}
if (graphTarget == null) {
// If there are no collectors in the root nodes set, create a temporary one to set as the target.
graphTarget = getAsCollectorRoot(null, "tempGraphTarget");
} else {
// Ensure the graph target is either in the set of root nodes or in the map.
graphTarget = getCollector(graphTarget, rootNodes, excludedCollectorReplacementMap);
}
return graphTarget;
}
/**
* Calculate collector target info over a group of collectors.
*
* @param collectorTargetInfo the info object which will be populated.
*/
private static void calculateCollectorTargetInfo(CollectorTargetInfo collectorTargetInfo) {
calculateCollectorTargetInfoHelper(collectorTargetInfo.getGraphTarget(), collectorTargetInfo);
collectorTargetInfo.reconcileCollectorTargets();
}
/**
* Helper for calculateCollectorTargetInfo().
* Calls itself recursively to calculate collector target info.
*
* @param collector the collector under consideration.
* @param collectorTargetInfo the info object which will be populated.
*/
private static void calculateCollectorTargetInfoHelper(Collector collector, CollectorTargetInfo collectorTargetInfo) {
collectorTargetInfo.mapCollectorToTarget(collector, null);
// Get the collectors used by the subtree.
SubtreeNodeInfo collectorTreeInfo = getSubtreeNodeInfo(collector);
Set<Collector> collectorsUsed = collectorTreeInfo.getCollectorsUsed();
// Call recursively on the collectors which weren't already analysed.
for (final Collector usedCollector : collectorsUsed) {
if (!collectorTargetInfo.isCollectorMapped(usedCollector)) {
calculateCollectorTargetInfoHelper(usedCollector, collectorTargetInfo);
}
}
}
/**
* Calculate collector target info over a group of collectors.
*
* @param collectorSet (Set of Collector) the collectors involved.
* @param collectorTargetInfo the info object which will be populated.
* @param excludedCollectorReplacementMap (Collector->SyntheticCollector) map from excluded collector to its replacement.
*/
private static void calculateCollectorTargetInfo(Set<Collector> collectorSet, CollectorTargetInfo collectorTargetInfo, Map<Collector, SyntheticCollector> excludedCollectorReplacementMap) {
calculateCollectorTargetInfoHelper(collectorSet, collectorTargetInfo, excludedCollectorReplacementMap);
}
/**
* Calculate collector target info over a group of collectors.
*
* @param collectorSet the collectors involved.
* @param collectorTargetInfo the info object which will be populated.
* @param excludedCollectorReplacementMap map from excluded collector to its replacement.
*/
private static void calculateCollectorTargetInfoHelper(Set<Collector> collectorSet, CollectorTargetInfo collectorTargetInfo, Map<Collector, SyntheticCollector> excludedCollectorReplacementMap) {
// Iterate over the collectors in the graph, mapping them to their targets.
for (final Collector collector : collectorSet) {
collectorTargetInfo.mapCollectorToTarget(collector, excludedCollectorReplacementMap);
}
}
/**
* Calculate all recursive emitter arguments for the collectors in the given collectorTargetInfo.
* @param collectors the collectors for which recursive emitter arguments should be calculated.
* @return map from recursive emitter arg to the arg which it reflects.
*/
private static Map<CompositionArgument, CompositionArgument> calculateFreeRecursiveEmitterArguments(Set<Collector> collectors) {
Map<CompositionArgument, CompositionArgument> recursiveEmitterArgumentToReflectedInputMap = new HashMap<CompositionArgument, CompositionArgument>();
// Iterate over all the collectors..
for (final Collector collector : collectors) {
// Get summary info for all descendant subtrees.
SubtreeNodeInfo subtreeNodeInfo = getDescendantSubtreeNodeInfo(collector);
// If the collectors used does not contain the collector itself, it will not have a recursive emitter argument.
Set<Collector> collectorsUsed = subtreeNodeInfo.getCollectorsUsed();
if (!collectorsUsed.contains(collector)) {
continue;
}
// Map from free unburnt arguments for any emitters emitting the collector's def to the arg index.
Map<CompositionArgument, Integer> collectorEmitterArgumentToIndexMap = new HashMap<CompositionArgument, Integer>();
int nEmitterArgs = -1;
for (final CompositionNode.Emitter emitter : subtreeNodeInfo.getEmitters()) {
if (emitter.getCollectorNode() == collector) {
nEmitterArgs = emitter.getNArguments();
for (int i = 0; i < nEmitterArgs; i++) {
CompositionArgument emitterArgument = emitter.getNodeArgument(i);
if (emitterArgument.getConnectedNode() == null && !emitterArgument.isBurnt()) {
collectorEmitterArgumentToIndexMap.put(emitterArgument, Integer.valueOf(i));
}
}
}
}
// Another check for nothing to do..
if (nEmitterArgs < 0 || collectorEmitterArgumentToIndexMap.isEmpty()) {
continue;
}
// Calculate the reflected inputs for the collector.
// These are the target arguments which are also descendant free unburnt args, but which are not recursive emitter args.
List<CompositionArgument> descendantFreeUnburntArguments = subtreeNodeInfo.getFreeUnburntArguments();
Set<CompositionArgument> reflectedArguments = new LinkedHashSet<CompositionArgument>(collector.getTargetArguments());
reflectedArguments.retainAll(descendantFreeUnburntArguments);
reflectedArguments.removeAll(collectorEmitterArgumentToIndexMap.keySet());
if (nEmitterArgs != reflectedArguments.size()) {
throw new IllegalStateException("The number of reflected arguments must be equal to the number of emitter args. "
+ nEmitterArgs + " != " + reflectedArguments.size());
}
// Map from emitter arg index to the argument reflected at that index.
Map<Integer, CompositionArgument> indexToReflectedArgumentMap = new HashMap<Integer, CompositionArgument>();
int index = 0;
for (final CompositionArgument arg : reflectedArguments) {
indexToReflectedArgumentMap.put(Integer.valueOf(index), arg);
index++;
}
// Now add all recursive args to the recursiveEmitterArgumentToReflectedInputMap.
for (final Map.Entry<CompositionArgument, Integer> entry : collectorEmitterArgumentToIndexMap.entrySet()) {
final CompositionArgument collectorEmitterArgument = entry.getKey();
Integer argIndex = entry.getValue();
recursiveEmitterArgumentToReflectedInputMap.put(collectorEmitterArgument, indexToReflectedArgumentMap.get(argIndex));
}
}
return recursiveEmitterArgumentToReflectedInputMap;
}
/**
* Calls itself recursively to calculate non-target collector-related graph info.
* This includes collector-name map and collector-arguments map.
* Collector names will not be generated for collectors which already have mappings in the collectorToNameMap.
*
* @param collector the collector under consideration.
* @param visibleCollectorNames the names of collectors visible from the current collector.
* @param targetCollectorTargetInfo if non-null, the collectorTargetInfo for the definition of the graph target.
* If null, this object's collector target info object will be used.
*/
private void calculateCollectorNonTargetInfo(Collector collector, Set<String> visibleCollectorNames, CollectorTargetInfo targetCollectorTargetInfo) {
if (targetCollectorTargetInfo == null) {
targetCollectorTargetInfo = collectorTargetInfo;
}
visibleCollectorNames = new HashSet<String>(visibleCollectorNames);
// See if the collector is "naked".
// This is true if it's a) unconnected and b) has no arguments.
// b) should be true if the collector is not targeted by its input, and has no inner collectors.
// In practice, we only have to perform the first check, since if it has inner collectors,
// we will be generating check graph source, which does not optimize out naked collectors.
if (inputToNakedCollectorMap != null) {
CompositionArgument collectorInput = collector.getNodeArgument(0);
if (collector.getDeclaredType() == null && collectorInput.getConnectedNode() == null &&
getArgumentCollectorTarget(collectorInput, collector, collectorTargetInfo) != collector) {
inputToNakedCollectorMap.put(collectorInput, collector);
}
}
if (!collectorToNameMap.containsKey(collector)) {
// Calculate the collector name.
String baseCollectorName = collector.getUnqualifiedName();
String candidateCollectorName = baseCollectorName;
int nextIndex = 2;
while (visibleCollectorNames.contains(candidateCollectorName)) {
candidateCollectorName = baseCollectorName + "_" + nextIndex;
nextIndex++;
}
collectorToNameMap.put(collector, candidateCollectorName);
visibleCollectorNames.add(candidateCollectorName);
}
// Get the collectors used by the subtree.
List<Collector> targetingCollectors = collectorTargetInfo.getTargetingCollectors(collector);
// Calculate args.
SubtreeNodeInfo collectorSubtreeNodeInfo = getSubtreeNodeInfo(collector);
for (final CompositionArgument freeUnburntArgument : collectorSubtreeNodeInfo.getFreeUnburntArguments()) {
Collector argumentTarget = getArgumentCollectorTarget(freeUnburntArgument, collector, collectorTargetInfo);
// Check for the special case where an emitter argument target's the emitter's collector.
// In this case, the argument should not appear an an argument on the collector.
if (recursiveEmitterArgumentToReflectedInputMap.containsKey(freeUnburntArgument)) {
continue;
}
if (collectorTargetInfo != targetCollectorTargetInfo) {
Collector realArgumentTarget = getArgumentCollectorTarget(freeUnburntArgument, collector, targetCollectorTargetInfo);
// Check for the case of an argument which isn't used by the collector which it targets.
if (argumentTarget != null && realArgumentTarget == null) {
unusedArgumentList.add(freeUnburntArgument);
continue;
}
// Check for the case of an argument whose real target isn't mapped.
if (!collectorTargetInfo.isCollectorMapped(realArgumentTarget)) {
unusedArgumentList.add(freeUnburntArgument);
continue;
}
}
// Add to the target (which may be null).
List<CompositionArgument> argumentList = collectorToArgumentsMap.get(argumentTarget);
if (argumentList == null) {
argumentList = new ArrayList<CompositionArgument>();
collectorToArgumentsMap.put(argumentTarget, argumentList);
}
argumentList.add(freeUnburntArgument);
}
// Call recursively on targeting collectors.
for (final Collector targetingCollector : targetingCollectors) {
calculateCollectorNonTargetInfo(targetingCollector, visibleCollectorNames, targetCollectorTargetInfo);
}
}
/**
* Calls itself recursively to calculate the names of free arguments in the graph.
* @param collector the collector for which targeting argument names should be calculated.
* @param visibleArgumentNames (Set of String) the names of arguments from enclosing scopes.
* @param reservedNames (Set of String) names which cannot be used for arguments.
*/
private void calculateArgumentNames(Collector collector, Set<String> visibleArgumentNames,
Set<String> reservedNames, CollectorTargetInfo collectorTargetInfo) {
// Copy the visible arg names Set. This will be the set of arg names visible at this level.
visibleArgumentNames = new HashSet<String>(visibleArgumentNames);
// Get the arguments which appear on the collector definition.
List<CompositionArgument> argumentList = getCollectorArguments(collector);
// If this is the top-level target, also calculate argument names for unused arguments.
if (collector == collectorTargetInfo.getGraphTarget()) {
argumentList = new ArrayList<CompositionArgument>(argumentList);
argumentList.addAll(getUnusedArguments());
}
if (!argumentList.isEmpty()) {
// Iterate over the arguments on this collector, calculating the arg names.
for (final CompositionArgument argument : argumentList) {
// Calculate the argument name.
String argumentName;
if (inputToNakedCollectorMap != null && inputToNakedCollectorMap.containsKey(argument)) {
argumentName = getCollectorName(inputToNakedCollectorMap.get(argument));
} else {
String baseArgumentName = argument.getNameString();
argumentName = baseArgumentName;
int nextIndex = 2;
while (reservedNames.contains(argumentName) || visibleArgumentNames.contains(argumentName)) {
argumentName = baseArgumentName + "_" + nextIndex;
nextIndex++;
}
}
argumentToNameMap.put(argument, argumentName);
visibleArgumentNames.add(argumentName);
}
}
// Call recursively on the targeting collectors.
List<Collector> targetingCollectors = collectorTargetInfo.getTargetingCollectors(collector);
for (final Collector targetingCollector : targetingCollectors) {
calculateArgumentNames(targetingCollector, visibleArgumentNames, reservedNames, collectorTargetInfo);
}
}
/**
* Get the collectors enclosing a given collector, including the collector itself.
* @param collector the collector in question.
* @return the enclosing collectors, including the collector itself, according to the target info calculated so far.
*/
private Set<Collector> getEnclosingCollectors(Collector collector) {
Set<Collector> enclosingCollectors = new HashSet<Collector>();
for (Collector enclosingCollector = collector; enclosingCollector != null;
enclosingCollector = collectorTargetInfo.getCollectorTarget(enclosingCollector)) {
enclosingCollectors.add(enclosingCollector);
}
return enclosingCollectors;
}
/**
* Replace all missing collectors targeted by the given set of root nodes.
* Note: some targeting arguments may as a result be rooted in collectors which are not in the rootNodes set or missing collector map.
*
* @param rootNodes
* @return map from collector to the collector with which it is replaced, for every collector
* targeted from but not appearing in rootNodes (and their target's targets, and so on..).
*/
private static Map<Collector, SyntheticCollector> getExcludedCollectors(Set<? extends CompositionNode> rootNodes) {
Map<Collector, SyntheticCollector> excludedCollectorReplacementMap = new HashMap<Collector, SyntheticCollector>();
// Iterate over the root nodes, checking targets for any collectors.
for (final CompositionNode rootNode : rootNodes) {
if (rootNode instanceof Collector) {
// If the target isn't in the set of root nodes, call getCollector(), which should create it.
Collector targetCollector = ((Collector)rootNode).getTargetCollector();
if (targetCollector != null && !rootNodes.contains(targetCollector)) {
getCollector(targetCollector, rootNodes, excludedCollectorReplacementMap);
}
}
}
return excludedCollectorReplacementMap;
}
/**
* If generating for a group of root nodes, get a collector which can be used by this source generator.
* If the collector does not exist within the given set of root nodes, a synthetic one is generated and returned.
* The generated collectors will have their arguments targeted to themselves.
*
* @param collector the collector to get.
* @param rootNodes
* @param excludedCollectorReplacementMap this will be populated with any mappings.
*/
private static Collector getCollector(Collector collector, Set<? extends CompositionNode> rootNodes, Map<Collector, SyntheticCollector> excludedCollectorReplacementMap) {
// If the collector is in the set of root nodes, return the collector.
if (rootNodes.contains(collector)) {
return collector;
}
// If the collector has already had a replacement, return the replacement.
SyntheticCollector replacementCollector = excludedCollectorReplacementMap.get(collector);
if (replacementCollector != null) {
return replacementCollector;
}
// Create a replacement.
String unqualifiedName = collector.getUnqualifiedName();
Collector originalTarget = collector.getTargetCollector();
Collector targetCollector = originalTarget == null ? null : getCollector(originalTarget, rootNodes, excludedCollectorReplacementMap);
replacementCollector = new SyntheticCollector(unqualifiedName, targetCollector, null, null);
// Add to the map.
excludedCollectorReplacementMap.put(collector, replacementCollector);
return replacementCollector;
}
/**
* Return the collector which is targeted by the given argument.
* This method simply asks all enclosing collectors whether it contains a given argument as a targeting argument.
* @param compositionArgument the argument in question.
* @return the collector targeted by this argument, or null if no target can be found.
*/
private static Collector getArgumentCollectorTarget(CompositionArgument compositionArgument,
Collector rootCollector, CollectorTargetInfo collectorTargetInfo) {
for (Collector enclosingCollector = rootCollector; enclosingCollector != null;
enclosingCollector = collectorTargetInfo.getCollectorTarget(enclosingCollector)) {
if (enclosingCollector.getTargetArguments().contains(compositionArgument)) {
return enclosingCollector;
}
}
return null;
}
/**
* Attach appropriately-scoped collectors to the given composition node roots which are not collectors.
* This method assumes that the root of the graph is the "real" root (ie. we aren't trying to consider as the root
* some collector which is used in another's definition).
*
* @param uncollectedRoots the nodes which are roots, but not collectors.
* @param rootNodeTarget the target of the enclosed graph (immediately enclosed by the graph target).
* @param collectorNames the names of all known collectors. This set will be augmented with the names
* of any generated collectors.
* @param excludedCollectorReplacementMap map from collector to its replacement, for any
* collectors which are excluded.
* @return Map map from generated collector to its corresponding uncollected root.
*/
private static Map<Collector, CompositionNode> attachCollectorsToUncollectedRoots(Set<CompositionNode> uncollectedRoots, Collector rootNodeTarget,
Set<String> collectorNames, Map<Collector, SyntheticCollector> excludedCollectorReplacementMap) {
// The map that will be returned..
Map<Collector, CompositionNode> collectorToUncollectedRootMap = new HashMap<Collector, CompositionNode>();
int nextAnonymousNameIndex = 1;
// Iterate over the uncollected roots.
for (final CompositionNode uncollectedRoot : uncollectedRoots) {
// Get the free arguments and collectors in the root's subtree..
SubtreeNodeInfo subtreeNodeInfo = getSubtreeNodeInfo(uncollectedRoot);
// Get the arguments for the anonymous root:
List<CompositionArgument> freeArguments = subtreeNodeInfo.getFreeUnburntArguments();
// Iterate over the collectors, finding the parent of the deepest one (ie. the scope which encloses the smallest scope).
// This will be the maximal scope at which the root can be validly defined.
Collector deepestCollectorParent = rootNodeTarget;
// Ensure that the deepest collector parent is an original collector..
// Note: iterates over the whole excludedCollectorReplacementMap, so potentially slow.
if (deepestCollectorParent instanceof SyntheticCollector) {
for (final Map.Entry<Collector, SyntheticCollector> entry : excludedCollectorReplacementMap.entrySet()) {
final Collector excludedCollector = entry.getKey();
if (deepestCollectorParent == entry.getValue()) {
deepestCollectorParent = excludedCollector;
break;
}
}
}
for (final Collector collector : subtreeNodeInfo.getCollectorsUsed()) {
if (enclosesCollector(deepestCollectorParent, collector)) {
if (collector != deepestCollectorParent && collector.getTargetCollector() != deepestCollectorParent) {
deepestCollectorParent = collector.getTargetCollector();
}
} else if (!enclosesCollector(collector.getTargetCollector(), deepestCollectorParent)) {
CALCompiler.COMPILER_LOGGER.log(Level.SEVERE, "Inconsistent hierarchy detected for anonymous gem tree.");
return null;
}
}
// Replace with replacement if necessary.
if (excludedCollectorReplacementMap.containsKey(deepestCollectorParent)) {
deepestCollectorParent = excludedCollectorReplacementMap.get(deepestCollectorParent);
}
// Get an anonymous name which doesn't conflict with any other names.
String candidateCollectorName = "anonymousCollector";
while (collectorNames.contains(candidateCollectorName)) {
candidateCollectorName = "anonymousCollector" + nextAnonymousNameIndex;
nextAnonymousNameIndex++;
}
collectorNames.add(candidateCollectorName);
// Create the collector with that name.
Collector collectorForRoot = new SyntheticCollector(candidateCollectorName, deepestCollectorParent, freeArguments, uncollectedRoot);
// Add to the map.
collectorToUncollectedRootMap.put(collectorForRoot, uncollectedRoot);
}
return collectorToUncollectedRootMap;
}
/**
* Return whether one collector encloses another.
* This means that the first gem is an outer scope of the second.
* @return whether the first collector encloses the second.
* Also returns true if both collectors are the same, or outerCollector is null.
*/
private static boolean enclosesCollector(Collector outerCollector, Collector innerCollector) {
if (outerCollector == null) {
return true;
}
for (Collector collector = innerCollector; collector != null; collector = collector.getTargetCollector()) {
if (collector == outerCollector) {
return true;
}
}
return false;
}
/**
* Get the target of the graph, as calculated by this info object.
* @return the graph target.
*/
public Collector getGraphTarget() {
return collectorTargetInfo.getGraphTarget();
}
/**
* Get a collector's target, as calculated by this info object.
* @param collector the collector in question.
* @return the collector's target, according to this info object.
*/
public Collector getCollectorTarget(Collector collector) {
return collectorTargetInfo.getCollectorTarget(collector);
}
/**
* Get the collectors targeting a given collector, as calculated by this info object.
* @param collector the collector in question.
* @return the collectors targeting a given collector, as calculated by this info object.
*/
public List<Collector> getTargetingCollectors(Collector collector) {
return collectorTargetInfo.getTargetingCollectors(collector);
}
/**
* Get a collector's name, disambiguated in a manner appropriate for its subsequent use according to this info object.
* @param collector the collector in question.
* @return the collector name.
*/
public String getCollectorName(Collector collector) {
return collectorToNameMap.get(collector);
}
/**
* Get the names of all the collectors known to this info object.
* @return the names calculated for use by the collectors known to this info object.
*/
public Set<String> getCollectorNames() {
return new HashSet<String>(collectorToNameMap.values());
}
/**
* Get the arguments which appear on a collector's definition, in the order in which they should appear.
* @param collector the collector in question.
* @return the arguments which appear in the collector's definition, in the order in which they should appear.
* If the collector is the top-level collector (ie. the nominated target), untargeted arguments will also be added to the list.
*/
public List<CompositionArgument> getCollectorArguments(Collector collector) {
// The arguments targeting the collector, in the order in which they should appear.
List<CompositionArgument> targetArguments = cachedCollectorArgumentsMap.get(collector);
if (targetArguments == null) {
// Get the arguments calculated to be on this collector.
List<CompositionArgument> unorderedArgumentsList = collectorToArgumentsMap.get(collector);
if (unorderedArgumentsList == null) {
targetArguments = new ArrayList<CompositionArgument>();
} else {
// copy the list..
unorderedArgumentsList = new ArrayList<CompositionArgument>(unorderedArgumentsList);
// Get the arguments which target this collector.
targetArguments = new ArrayList<CompositionArgument>(collector.getTargetArguments());
// Keep all arguments targeting this collector, and also calculated to be on the collector.
targetArguments.retainAll(unorderedArgumentsList);
// Add all arguments which are calculated to be on the collector, but not actually targeting the collector.
unorderedArgumentsList.removeAll(targetArguments);
targetArguments.addAll(unorderedArgumentsList);
}
// Add untargeted arguments if the collector is the root definition.
if (getCollectorTarget(collector) == null) {
List<CompositionArgument> untargetedArguments = collectorToArgumentsMap.get(null);
if (untargetedArguments != null) {
targetArguments.addAll(untargetedArguments);
}
}
cachedCollectorArgumentsMap.put(collector, targetArguments);
}
return targetArguments;
}
/**
* Get the arguments which are unused, if any.
* @return the arguments which are unused, if any.
*/
public List<CompositionArgument> getUnusedArguments() {
return new ArrayList<CompositionArgument>(unusedArgumentList);
}
/**
* Get the original root corresponding to a synthetic collector generated for that root during source generation.
* @param collectedRoot the generated collector .
* @return the original root.
*/
CompositionNode getOriginalRoot(Collector collectedRoot) {
return collectedRootToRootNodeMap.get(collectedRoot);
}
/**
* Get the name which was calculated for the given argument.
* @param argument the argument in question.
* @return the name calculated for the given argument.
*/
public String getArgumentName(CompositionArgument argument) {
// Check for the case where it's a recursive emitter argument.
CompositionArgument recursivelyReflectedArgument = recursiveEmitterArgumentToReflectedInputMap.get(argument);
if (recursivelyReflectedArgument != null) {
argument = recursivelyReflectedArgument;
}
return argumentToNameMap.get(argument);
}
/**
* Return whether the given collector will be treated as a "naked" collector.
* These are unconnected collectors with no targeting arguments.
* Such collectors will have their definitions elided from the text (ie. they will not appear in the let block).
* @param collector the collector in question.
* @return whether the given collector will be treated as a "naked" collector.
*/
public boolean isNakedCollector(Collector collector) {
return inputToNakedCollectorMap != null && inputToNakedCollectorMap.containsValue(collector);
}
/**
* @param collector the collector in question. This is assumed to be a collector mapped by this info object.
* @return whether this collector is the replacement for an excluded collector.
*/
boolean isExcludedCollectorReplacement(Collector collector) {
return excludedCollectorReplacementMap != null && excludedCollectorReplacementMap.containsValue(collector);
}
/**
* @return map from recursive emitter arg to the arg which it reflects.
*/
public Map<CompositionArgument, CompositionArgument> getRecursiveEmitterArgumentToReflectedInputMap() {
return new HashMap<CompositionArgument, CompositionArgument>(recursiveEmitterArgumentToReflectedInputMap);
}
}
/**
* A simple class to encapsulate the results from getSubtreeNodeInfo().
* @author Edward Lam
*/
private static class SubtreeNodeInfo {
private final List<CompositionArgument> freeArguments = new ArrayList<CompositionArgument>();
private final Set<Collector> collectorsUsed = new HashSet<Collector>();
private final Set<Emitter> emitterSet = new HashSet<Emitter>();
/**
* @return the collectors used by emitters in the subtree.
*/
public Set<Collector> getCollectorsUsed() {
return collectorsUsed;
}
/**
* @return the free arguments in the subtree.
*/
public List<CompositionArgument> getFreeUnburntArguments() {
return freeArguments;
}
/**
* @return the emitters in the subtree.
*/
public Set<Emitter> getEmitters() {
return emitterSet;
}
}
/**
* A Collector generated internally by this source generator.
* @author Edward Lam
*/
private static class SyntheticCollector implements Collector {
/** The name of the collector. */
private final String unqualifiedName;
/** The arguments targeting this collector. */
private final List<CompositionArgument> targetArguments;
/** The argument to this collector. */
private final SyntheticArgument argument;
/** This collector's target collector*/
private final Collector targetCollector;
/**
* An argument on a SyntheticCollector.
* @author Edward Lam
*/
private class SyntheticArgument implements CompositionNode.CompositionArgument {
/** The composition node connected to this argument, if any. Null if not connected. */
private final CompositionNode connectedNode;
/**
* Constructor for a SyntheticArgument.
* @param connectedNode the node connected to this argument, if any. Null if not connected.
*/
SyntheticArgument(CompositionNode connectedNode) {
this.connectedNode = connectedNode;
}
/**
* {@inheritDoc}
*/
public CompositionNode getConnectedNode() {
return connectedNode;
}
/**
* {@inheritDoc}
*/
public boolean isBurnt() {
return false;
}
/**
* {@inheritDoc}
*/
public String getNameString() {
return unqualifiedName;
}
}
/**
* Constructor for a SyntheticCollector.
* @param unqualifiedName the name of the collector.
* @param targetArguments the arguments targeting this collector.
* @param connectedNode the composition node connected to the argument on this collector, or null if not connected.
* If null, the argument will be added to this collector's target arguments.
*/
SyntheticCollector(String unqualifiedName, List<CompositionArgument> targetArguments, CompositionNode connectedNode) {
this(unqualifiedName, null, targetArguments, connectedNode);
if (connectedNode == null) {
this.targetArguments.add(argument);
}
}
/**
* Constructor for a SyntheticCollector.
* @param unqualifiedName the name of the collector.
* @param targetCollector the target collector for this collector.
* @param targetArguments the arguments targeting this collector.
* If null, the argument list will have this collector's target argument as its only argument.
* @param connectedNode the composition node connected to the argument on this collector, or null if not connected.
*/
SyntheticCollector(String unqualifiedName, Collector targetCollector, List<CompositionArgument> targetArguments, CompositionNode connectedNode) {
this.unqualifiedName = unqualifiedName;
this.targetCollector = targetCollector;
this.argument = new SyntheticArgument(connectedNode);
this.targetArguments = targetArguments == null ? Collections.<CompositionArgument>singletonList(argument) : new ArrayList<CompositionArgument>(targetArguments);
}
/**
* {@inheritDoc}
*/
public String getUnqualifiedName() {
return unqualifiedName;
}
/**
* {@inheritDoc}
*/
public TypeExpr getDeclaredType() {
return null;
}
/**
* {@inheritDoc}
*/
public Collector getTargetCollector() {
return targetCollector;
}
/**
* {@inheritDoc}
*/
public List<CompositionArgument> getTargetArguments() {
return new ArrayList<CompositionArgument>(targetArguments);
}
/**
* {@inheritDoc}
*/
public int getNArguments() {
return 1;
}
/**
* {@inheritDoc}
*/
public CompositionArgument getNodeArgument(int i) {
if (i != 0) {
throw new IndexOutOfBoundsException("Collectors have exactly one input.");
}
return argument;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return "Synthetic collector " + unqualifiedName + ". Target = " + targetCollector + ". Connected node = " + argument.connectedNode;
}
}
/* This class is not intended to be instantiated. */
private CALSourceGenerator() {
}
/**
* Get the text of the function that is defined by the tree of CompositionNodes headed by rootNode.
* @param functionName the name to use for the new function.
* This can be null if scRoot is a collector, in which case the collector's name will be used.
* @param functionRoot the root of the Composition tree from which to generate the definition.
* @param scope the scope of the function.
* @return text of the resulting function.
*/
public static String getFunctionText(String functionName, CompositionNode functionRoot, Scope scope) {
return getFunctionSourceModel(functionName, functionRoot, scope).toSourceText();
}
/**
* Get the source model of the function that is defined by the tree of
* CompositionNodes headed by rootNode.
*
* @param functionName
* the name to use for the new function. This can be null if
* functionRoot is a collector, in which case the collector's
* name will be used.
* @param functionRoot
* the root of the Composition tree from which to generate the
* definition.
* @param scope
* the scope of the function.
* @return source model of the resulting function.
*/
public static SourceModel.FunctionDefn.Algebraic getFunctionSourceModel(
String functionName, CompositionNode functionRoot, Scope scope) {
// Get as a collector root.
Collector rootNode = getAsCollectorRoot(functionRoot, functionName);
GraphInfo graphInfo = new GraphInfo(rootNode, functionName);
SourceModel.Expr functionBody = getFunctionBodySourceModel(rootNode, graphInfo, null);
// The name of the function is the collector name.
functionName = graphInfo.getCollectorName(rootNode);
List<CompositionArgument> args = graphInfo.getCollectorArguments(rootNode);
SourceModel.Parameter[] freeParams = new SourceModel.Parameter[args.size()];
int counter = 0;
for (final CompositionArgument targetingArg : args) {
freeParams[counter++] = SourceModel.Parameter.make(graphInfo.getArgumentName(targetingArg), false);
}
return SourceModel.FunctionDefn.Algebraic.make(functionName, scope, freeParams, functionBody);
}
/**
* Get the arguments in the order that they would appear if the given node were used as the source generation target.
* @param rootNode the node in question
* @return the arguments in the order that they would appear in the source.
*/
public static CompositionArgument[] getFunctionArguments(CompositionNode rootNode) {
// Get as a collector root. This is necessary so that unbound arguments that don't have a target
// set properly get picked up as arguments on the root
Collector collectorRoot = getAsCollectorRoot(rootNode, "temp");
// calculate the graph info, get the args for the node.
GraphInfo graphInfo = new GraphInfo(collectorRoot, "temp");
List<CompositionArgument> nodeArgList = graphInfo.getCollectorArguments(collectorRoot);
// Convert to an array and return.
CompositionArgument[] args = new CompositionArgument[nodeArgList.size()];
return nodeArgList.toArray(args);
}
/**
* Generates source for the tree of CompositionNodes headed by rootNode in a form which
* is suitable for use in a code gem.
* This will not include the function name, scope, arguments, or a trailing semi-colon.
* @param rootNode root of the Composition tree.
* @return the resulting CAL source.
*/
public static String getSourceForCodeGem(CompositionNode rootNode) {
// Note: prevent eta-reduction of the root gem..
Collector collectorRoot = getAsCollectorRoot(rootNode, "tempCollector");
SourceModel.Expr sourceBody = getFunctionBodySourceModel(collectorRoot, new GraphInfo(collectorRoot, "tempCollector"), null);
return sourceBody.toSourceText();
}
/**
* Invoke the other getCheckGraphSource()
*
* ** FOR DEBUG PURPOSES
*
* @param rootNodes (Set of CompositionNode)
* @return the generated source
*/
public static String getDebugCheckGraphSource(Set<? extends CompositionNode> rootNodes) {
return getCheckGraphSource(rootNodes).getGraphFunction().toSourceText();
}
/**
* This generates a let expression which generates all free argument and output types simultaneously when evaluated.
*
* The expression generated is of the form:
* checkGraph root1_rootVar root2_rootVar ... rootn_rootVar unusedVar1 ... unusedVarn =
* let
* (transformed target definition);
* in
* 0.0;
*
* where the transformed target definition is the definition of the target of the composition node graph, transformed in the following ways:
*
* 1) Anonymous collectors are attached to roots which are not collectors. These collectors will have their targets set
* to an appropriate scope, and unbound arguments on the composition tree will be targeted to this collector.
* 2) Collectors which do not appear in the definition of the target are added anyway.
* 3) The "in" part of any let definition is augmented with its own "let" definition to bind checkGraph arguments
* to the types of their corresponding roots.
*
* eg. The graph defines the target as: collector1 collector2 y = collector2 + y;
* There are three roots:
* 1) The target collector, collector1, connected to Prelude.add, which in turn is connected to a collector2 emitter as its first arg.
* 2) A collector, collector2, used in the definition of collector1.
* 3) A collector, collector3, not used in the definition of collector1.
* 4) An unconnected Prelude.add gem.
* This method generates:
* checkGraph collector1_rootVar collector2_rootVar collector3_rootVar anonymousCollector_rootVar collector3_arg =
* let
* collector1 y collector2_arg =
* let
* collector2 = collector2_arg;
* collector3 = collector3_arg;
* anonymousCollector x y_2 = x + y_2;
* in
* let
* collector2_tempList = [collector2, collector2_rootVar];
* collector3_tempList = [collector3, collector3_rootVar];
* anonymousCollector_tempList = [anonymousCollector, anonymousCollector_rootVar];
* in
* collector2 + y;
* in
* let
* collector1_tempList = [collector1, collector1_rootVar];
* in
* 0.0;
*
* Note: eta-reduction is NOT applied to the graph text.
* eg. let foo x y = (foo2 x y); in .. is NOT reduced to let foo = foo2; in ..
*
* @param rootNodes the root nodes of trees for which to generate the type checking text
* @return the generated source for the sc that represents a typecheck-able representation of the graph.
*/
static CheckGraphSource getCheckGraphSource(Set<? extends CompositionNode> rootNodes) {
Map<String, Collector> rootTypeCollectorNameToCollectorMap = new LinkedHashMap<String, Collector>();
GraphInfo graphInfo = new GraphInfo(rootNodes);
Collector graphTarget = graphInfo.getGraphTarget();
SourceModel.Expr graphTargetSourceModel = getFunctionBodySourceModel(graphTarget, graphInfo, rootTypeCollectorNameToCollectorMap);
// The root->arguments map which is returned.
Map<CompositionNode, List<CompositionArgument>> rootToArgumentsMap = new LinkedHashMap<CompositionNode, List<CompositionArgument>>();
List<SourceModel.Parameter> parameters = new ArrayList<SourceModel.Parameter>();
// The root arguments..
for (final Map.Entry<String, Collector> entry : rootTypeCollectorNameToCollectorMap.entrySet()) {
final String rootTypeCollectorName = entry.getKey();
Collector correspondingCollector = entry.getValue();
// Get the original root corresponding to the collected root.
CompositionNode correspondingRoot = graphInfo.getOriginalRoot(correspondingCollector);
// Get the arguments, add to the map (but only if it's not one of the synthetically created enclosing collectors..).
if (!(correspondingRoot instanceof SyntheticCollector)) {
List<CompositionArgument> collectorArguments = graphInfo.getCollectorArguments(correspondingCollector);
rootToArgumentsMap.put(correspondingRoot, collectorArguments);
}
// Add the root type collector's name as an argument.
parameters.add(SourceModel.Parameter.make(rootTypeCollectorName, false));
}
// The uncollected arguments get stuck on the outermost collector.
for (final CompositionArgument unusedArgument : graphInfo.getCollectorArguments(graphTarget)) {
String uncollectedArgumentName = graphInfo.getArgumentName(unusedArgument);
// Add the uncollected argument name.
parameters.add(SourceModel.Parameter.make(uncollectedArgumentName, false));
}
// Add unused targeting arguments to the outermost collector
for (final CompositionArgument unusedArgument : graphInfo.getUnusedArguments()) {
String unusedArgumentName = graphInfo.getArgumentName(unusedArgument);
// Add the unused targeting argument name.
parameters.add(SourceModel.Parameter.make(unusedArgumentName, false));
}
// Create the check graph source model.
SourceModel.FunctionDefn.Algebraic checkGraph =
SourceModel.FunctionDefn.Algebraic.make(
"checkGraph", Scope.PRIVATE,
parameters.toArray(new SourceModel.Parameter[0]),
graphTargetSourceModel);
return new CheckGraphSource(
checkGraph, rootToArgumentsMap, graphInfo.getUnusedArguments(),
graphInfo.getRecursiveEmitterArgumentToReflectedInputMap());
}
/**
* If the given collector has a declared type, return its type signature.
*
* @param collector
* the collector in question.
* @return the corresponding type signature, or null if the collector's type
* is not declared.
*/
private static SourceModel.TypeSignature maybeGetTypeSignatureSourceModel(Collector collector) {
TypeExpr declaredType = collector.getDeclaredType();
if (declaredType == null) {
return null;
}
return declaredType.toSourceModel();
}
/**
* Generate the function body used for generating the type of the graph
* rooted at the given collector. See getCheckGraphSource() for details.
*
* @param sourceCollector
* the collector for which the text should be generated.
* @param graphInfo
* information about the CompositionNode graph
* @param rootTypeCollectorNameToCollectorMap
* Map to collector from the name of the root
* type variable used to generate its type. This will be
* modified.
* @return the resulting source model.
*/
private static SourceModel.Expr getFunctionBodySourceModel(
Collector sourceCollector, GraphInfo graphInfo, Map<String, Collector> rootTypeCollectorNameToCollectorMap) {
// Get the collectors targeting the source collector
List<Collector> targetingCollectors = graphInfo.getTargetingCollectors(sourceCollector);
// The "let" definitions.
List<SourceModel.LocalDefn.Function> letDefinition = new ArrayList<SourceModel.LocalDefn.Function>();
Map<String, Collector> targetingCollectorNameToCollectorMap = new LinkedHashMap<String, Collector>();
for (final Collector targetingCollector : targetingCollectors) {
// Skip, if it's a naked collector.
if (graphInfo.isNakedCollector(targetingCollector)) {
continue;
}
String targetingCollectorName = graphInfo.getCollectorName(targetingCollector);
// Add the declared type signature if any.
SourceModel.TypeSignature typeSignatureSourceModel = maybeGetTypeSignatureSourceModel(targetingCollector);
if (typeSignatureSourceModel != null) {
letDefinition.add(SourceModel.LocalDefn.Function.TypeDeclaration.make(targetingCollectorName, typeSignatureSourceModel));
}
// Get the arguments.
List<CompositionArgument> sourceCollectorArguments = graphInfo.getCollectorArguments(targetingCollector);
SourceModel.Parameter[] params = new SourceModel.Parameter[sourceCollectorArguments.size()];
int counter = 0;
for (final CompositionArgument compositionArgument : sourceCollectorArguments) {
params[counter++] = SourceModel.Parameter.make(graphInfo.getArgumentName(compositionArgument), false);
}
// Get the body of the definition.
SourceModel.Expr body = getFunctionBodySourceModel(targetingCollector, graphInfo, rootTypeCollectorNameToCollectorMap);
// Add the collector's definition.
letDefinition.add(SourceModel.LocalDefn.Function.Definition.make(targetingCollectorName, params, body));
targetingCollectorNameToCollectorMap.put(targetingCollectorName, targetingCollector);
}
// The source root definition.. (the "in" part).
SourceModel.Expr nakedInPart = getSubtreeExpressionSourceModel(sourceCollector, graphInfo, false);
SourceModel.Expr inDefinition;
if (rootTypeCollectorNameToCollectorMap == null) {
// Just the in part.
inDefinition = nakedInPart;
} else {
// Augment with an inner "let".
// Create a list to hold the let definitions for the "in" part, which are used for type checking.
List<SourceModel.LocalDefn.Function.Definition> inLetDefinitions = new ArrayList<SourceModel.LocalDefn.Function.Definition>();
// Calculate the names which shouldn't be re-used for generated inner let definitions.
Set<String> reservedNames = new HashSet<String>();
// First calculate the collectors which are in scope, and add their argument names.
Set<Collector> enclosingCollectors = graphInfo.getEnclosingCollectors(sourceCollector);
Set<Collector> collectorsInScope = new HashSet<Collector>(enclosingCollectors);
for (final Collector enclosingCollector : enclosingCollectors) {
collectorsInScope.addAll(graphInfo.getTargetingCollectors(enclosingCollector));
// Add the arguments.
for (final CompositionArgument visibleArgument : graphInfo.getCollectorArguments(enclosingCollector)) {
reservedNames.add(graphInfo.getArgumentName(visibleArgument));
}
}
// Now add the names of all collectors targeting the enclosing collectors.
for (final Collector visibleCollector : collectorsInScope) {
reservedNames.add(graphInfo.getCollectorName(visibleCollector));
}
// Iterate over the targeting collectors, generating the inner let definitions used for type checking.
for (final Map.Entry<String, Collector> entry : targetingCollectorNameToCollectorMap.entrySet()) {
final String targetingCollectorName = entry.getKey();
Collector targetingCollector = entry.getValue();
// Skip if it's an excluded collector.
if (graphInfo.isExcludedCollectorReplacement(targetingCollector)) {
continue;
}
// Generate a name for the temporary list collector.
String baseListCollectorName = targetingCollectorName + "_tempList";
String listCollectorName = baseListCollectorName;
int nextIndex = 2;
while (reservedNames.contains(listCollectorName)) {
listCollectorName = baseListCollectorName + nextIndex;
nextIndex++;
}
reservedNames.add(listCollectorName);
// Generate a name for the root type variable
String baseRootVarName = targetingCollectorName + "_rootVar";
String rootVarName = baseRootVarName;
nextIndex = 2;
while (reservedNames.contains(rootVarName)) {
rootVarName = baseRootVarName + nextIndex;
nextIndex++;
}
rootTypeCollectorNameToCollectorMap.put(rootVarName, targetingCollector);
reservedNames.add(rootVarName);
// Now add the source model for the temporary list collector.
// This will be of the form: listCollector = [targetingCollector, rootVarName];
SourceModel.Expr.List listCollectorExpr =
SourceModel.Expr.List.make(new SourceModel.Expr[]{
SourceModel.Expr.Var.makeUnqualified(targetingCollectorName),
SourceModel.Expr.Var.makeUnqualified(rootVarName) });
inLetDefinitions.add(SourceModel.LocalDefn.Function.Definition.make(listCollectorName, null, listCollectorExpr));
}
if (inLetDefinitions.size() == 0) {
inDefinition = nakedInPart;
} else {
inDefinition = SourceModel.Expr.Let.make(inLetDefinitions.toArray(new SourceModel.LocalDefn.Function[0]), nakedInPart);
}
}
if (letDefinition.size() == 0) {
// Just return the "in" part.
return inDefinition;
} else {
// Construct the "let .. in .."
return SourceModel.Expr.Let.make(letDefinition.toArray(new SourceModel.LocalDefn.Function[0]), inDefinition);
}
}
/**
* If the given node is a collector, return it.
* Otherwise, return a collector node whose argument is connected to the given node.
* @param node the node in question. Null to return an unconnected collector root.
* @param defaultSCName the default name for the new function.
* This name will be used unless rootNode is a collector, in which case the collector's name will be used.
* @return the corresponding collector.
*/
private static Collector getAsCollectorRoot(CompositionNode node, String defaultSCName) {
if (node instanceof Collector) {
return (Collector)node;
} else {
return new SyntheticCollector(defaultSCName, Collections.<CompositionArgument>emptyList(), node);
}
}
/**
* A helper method to find, descending from a given gem:
* 1) all free arguments in the gem's subtree.
* 2) all collectors for emitters used in that subtree.
* @param subtreeRoot the root for the subtree in question.
* @return the free arguments and collectors for emitters in that subtree.
*/
private static SubtreeNodeInfo getSubtreeNodeInfo(CompositionNode subtreeRoot) {
SubtreeNodeInfo resultInfo = new SubtreeNodeInfo();
if (subtreeRoot instanceof CompositionNode.Emitter) {
resultInfo.getCollectorsUsed().add(((CompositionNode.Emitter)subtreeRoot).getCollectorNode());
}
for (int i = 0, nArguments = subtreeRoot.getNArguments(); i < nArguments; i++) {
CompositionNode.CompositionArgument argument = subtreeRoot.getNodeArgument(i);
CompositionNode connectedNode = argument.getConnectedNode();
// Check the connected node.
if (connectedNode == null) {
// Unconnected node. Add to the list if it's not burnt.
if (!argument.isBurnt()) {
resultInfo.getFreeUnburntArguments().add(argument);
if (subtreeRoot instanceof CompositionNode.Emitter) {
resultInfo.getEmitters().add((Emitter)subtreeRoot);
}
}
} else {
// Connected node. Call recursively and combine the result.
SubtreeNodeInfo connectedSubtreeNodeInfo = getSubtreeNodeInfo(connectedNode);
resultInfo.getCollectorsUsed().addAll(connectedSubtreeNodeInfo.getCollectorsUsed());
resultInfo.getFreeUnburntArguments().addAll(connectedSubtreeNodeInfo.getFreeUnburntArguments());
resultInfo.getEmitters().addAll(connectedSubtreeNodeInfo.getEmitters());
}
}
return resultInfo;
}
/**
* Get SubtreeNodeInfo for a given CompositionNode, and all descendant CompositionNode subtrees..
* @param subtreeRoot the node from which to start.
* @return a SubtreeNodeInfo object which contains info for CompositionNode subtrees descendant from the given node.
*/
private static SubtreeNodeInfo getDescendantSubtreeNodeInfo(CompositionNode subtreeRoot) {
SubtreeNodeInfo resultInfo = new SubtreeNodeInfo(); // the info object to return.
Set<CompositionNode> traversedRoots = new HashSet<CompositionNode>(); // (Set of CompositionNode) the set of roots which have been traversed.
Set<CompositionNode> newRoots = Collections.singleton(subtreeRoot);
do {
Set<CompositionNode> newRootsUpdated = new HashSet<CompositionNode>();
// Iterate over the new collectors, building up the result info object, and getting an updated collection of new collectors.
for (final CompositionNode newRoot : newRoots) {
if (!traversedRoots.contains(newRoot)) {
traversedRoots.add(newRoot);
SubtreeNodeInfo subtreeNodeInfo = getSubtreeNodeInfo(newRoot);
resultInfo.getEmitters().addAll(subtreeNodeInfo.getEmitters());
resultInfo.getFreeUnburntArguments().addAll(subtreeNodeInfo.getFreeUnburntArguments());
Set<Collector> collectorsUsed = subtreeNodeInfo.getCollectorsUsed();
resultInfo.getCollectorsUsed().addAll(collectorsUsed);
newRootsUpdated.addAll(collectorsUsed);
}
}
newRootsUpdated.removeAll(traversedRoots);
newRoots = newRootsUpdated;
} while (!newRoots.isEmpty());
return resultInfo;
}
/**
* Get the expression for the subtree rooted at the given node.
*
* @param node
* the node for which the expression should be obtained.
* @param graphInfo
* the calculated graph info.
* @param isEtaReductionRoot
* Whether this is the root node at which to apply eta-reduction
* on free params. This will cause final free params on the
* reduction root to be dropped from both the returned leaf list
* and the returned expression. Note that in the case of let
* definitions, the eta-reduction root applies to the connected
* node.
* @return the result of the analysis.
*/
private static SourceModel.Expr getSubtreeExpressionSourceModel(
CompositionNode node, GraphInfo graphInfo, boolean isEtaReductionRoot) {
if (node instanceof CompositionNode.Value) {
return getSubtreeExpressionSourceModel((CompositionNode.Value)node).getExpression();
}
if (node instanceof CompositionNode.Application) {
return getSubtreeExpressionSourceModel((CompositionNode.Application)node, graphInfo,
isEtaReductionRoot);
}
if (node instanceof Collector) {
return getSubtreeExpressionSourceModel((Collector)node, graphInfo, isEtaReductionRoot);
}
if (node instanceof CompositionNode.RecordFieldSelectionNode) {
return getSubtreeExpressionSourceModel((CompositionNode.RecordFieldSelectionNode)node, graphInfo);
}
if (node instanceof CompositionNode.RecordCreationNode){
return getSubtreeExpressionSourceModel((CompositionNode.RecordCreationNode)node, graphInfo);
}
// something bad happened
throw new IllegalArgumentException(
"Can't handle this type of node: " + (node == null ? null : node.getClass()));
}
/**
* Get the expression for the subtree rooted at the given value node.
*
* @param node
* the node for which the expression should be obtained.
* @return the resulting expression.
*/
private static SourceModelGenerationResult getSubtreeExpressionSourceModel(CompositionNode.Value node) {
// This node directly represents a value
return new SourceModelGenerationResult(
node.getSourceModel(), Collections.<Collector>emptySet(), Collections.<Collector>emptySet());
}
/**
* Get the source model of the expression that is defined by the tree of
* CompositionNodes headed by rootNode.
*
* @param rootNode
* root of the definition.
* @param graphInfo
* the calculated graph info.
* @param isEtaReductionRoot
* Whether this is the root node at which to apply eta-reduction
* on free params. This will cause final free params on the
* reduction root to be dropped from both the returned leaf list
* and the returned expression. Note that in the case of let
* definitions, the eta-reduction root applies to the connected
* node.
* @return the result of traversing the roots defined by the aggregation
* rooted at rootNode..
*/
private static SourceModel.Expr getSubtreeExpressionSourceModel(
Collector rootNode, GraphInfo graphInfo, boolean isEtaReductionRoot) {
CompositionNode connectedNode = rootNode.getNodeArgument(0).getConnectedNode();
if (connectedNode == null) {
// Not connected - equivalent to a free argument.
// The equivalent subtree expression is just the name of the argument.
return SourceModel.Expr.Var.makeUnqualified(graphInfo.getArgumentName(rootNode.getNodeArgument(0)));
} else {
// Get the result of analyzing the expression for the subtree rooted
// at this node.
return getSubtreeExpressionSourceModel(connectedNode, graphInfo, isEtaReductionRoot);
}
}
/**
* Gets the source model for the subtree at the given Extractor node.
*
* @param node Extractor node
* @param graphInfo the calculated graph info
* @return the resulting expression
*/
private static SourceModel.Expr getSubtreeExpressionSourceModel(CompositionNode.RecordFieldSelectionNode node, GraphInfo graphInfo) {
// RecordFieldSelectionNode has only one node argument
Pair<List<SourceModel.Expr>, List<String>> argExprAndBurntArgLists = getArgExprsAndBurntArgs(node, graphInfo);
SourceModel.Expr expression = SourceModel.Expr.SelectRecordField.make(argExprAndBurntArgLists.fst().get(0), SourceModel.Name.Field.make(node.getFieldName()));
return getMaybeLambdaSourceModel(expression, argExprAndBurntArgLists.snd());
}
/**
* Gets the source model for the subtree at the given Extractor node.
*
* @param node Extractor node
* @param graphInfo the calculated graph info
* @return the resulting expression
*/
private static SourceModel.Expr getSubtreeExpressionSourceModel(CompositionNode.RecordCreationNode node, GraphInfo graphInfo) {
Pair<List<SourceModel.Expr>, List<String>> argExprAndBurntArgLists = getArgExprsAndBurntArgs(node, graphInfo);
List<SourceModel.Expr> argExpressionList = argExprAndBurntArgLists.fst();
// Create the record fields
int numArgExprs = argExpressionList.size();
FieldModification[] fieldMods = new FieldModification[numArgExprs];
Iterator<SourceModel.Expr> it = argExpressionList.listIterator();
for (int i = 0; i < numArgExprs; i++) {
SourceModel.Expr connectedExpr = it.next();
FieldModification fieldMod = FieldModification.Extension.make(SourceModel.Name.Field.make(node.getFieldName(i)), connectedExpr);
fieldMods[i] = fieldMod;
}
// Create the record
SourceModel.Expr expression = SourceModel.Expr.Record.make(null, fieldMods);
return getMaybeLambdaSourceModel(expression, argExprAndBurntArgLists.snd());
}
/**
* Get the expression for the subtree rooted at the given application node.
*
* There are three readability/efficiency special cases which impact the complexity of this method:
* 1) Operator textual pseudonyms are replaced by their operator form, and infixed.
* eg. Prelude.add x y becomes x + y
* 2) Eta-reduction for final argument burnings.
* eg. \w x y z -> foo w (bar x) y z becomes \w x -> foo w (bar x)
* 3) Eta-reduction for the final free arguments on the root node of the tree for which an sc is obtained.
* eg. __runTarget w x y z = foo w (bar x) y z becomes __runTarget w x = foo w (bar x)
*
* TODOEL: #3 does not completely follow contract (since it removes leaves
* from the result), and should probably instead be applied via source
* transform on whatever comes out at the end (using the parser). Such a
* facility could be more generally used (eg. when internalizing CAL code as
* a runtime optimization), and would allow us to remove the extra boolean
* argument.
*
* @param node
* the node for which the expression should be obtained.
* @param graphInfo
* the calculated graph info.
* @param isEtaReductionRoot
* Whether this is the root node at which to apply eta-reduction
* on free params. This will cause final free params on the
* reduction root to be dropped from both the returned leaf list
* and the returned expression. Note that in the case of let
* definitions, the eta-reduction root applies to the connected
* node.
* @return the resulting expression.
*/
private static SourceModel.Expr getSubtreeExpressionSourceModel(CompositionNode.Application node, GraphInfo graphInfo, boolean isEtaReductionRoot) {
// The operator for the applyable text if there is an operator.
String operatorName = null;
// The number of arguments on this node.
int nArguments = node.getNArguments();
// Get the applyable text - the text of the "thing" which can be applied to arguments.
SourceModel.Expr applyable;
// If the node is a lambda, then store its code qualification map.
// Otherwise, this value is set to null.
CodeQualificationMap codeQualificationMap = null;
if (node instanceof CompositionNode.Lambda) {
// This node contributes code directly
applyable = SourceModelUtilities.TextParsing.parseExprIntoSourceModel(((CompositionNode.Lambda) node).getLambda());
codeQualificationMap = ((CompositionNode.Lambda)node).getQualificationMap();
} else if (node instanceof CompositionNode.GemEntityNode) {
// This node invokes some function
QualifiedName functionName = ((CompositionNode.GemEntityNode) node).getName();
applyable = SourceModel.Expr.makeGemCall(functionName);
// See if this is an alias for an operator.
operatorName = OperatorInfo.getOperatorName(functionName);
//todoBI in the future we can handle Prelude.tuple2, Prelude.tuple3, ... by converting to operator form
//we used to do this for the Tuple2, Tuple3, etc data constructors, but they no longer exist.
// See if this is an alias for a tuple data constructor.
// tupleDimension = TypeExpr.getTupleDimension(functionName);
} else if (node instanceof CompositionNode.Emitter) {
// This node invokes a local function
Collector collector = ((CompositionNode.Emitter)node).getCollectorNode();
applyable = SourceModel.Expr.Var.makeUnqualified(graphInfo.getCollectorName(collector));
} else {
// something bad happened
throw new IllegalArgumentException("Can't handle this type of node: " + node.getClass());
}
// Iterate through the arguments of this node and get the expressions for the connected subtrees.
Pair<List<SourceModel.Expr>, List<String>> argExprAndBurntArgLists = getArgExprsAndBurntArgs(node, graphInfo);
List<SourceModel.Expr> argExpressionList = argExprAndBurntArgLists.fst();
List<String> burntArgNames = argExprAndBurntArgLists.snd();
// If we are type checking a composition node graph, collectors will have all targeting arguments as arguments, even those
// which would normally be unused in its definition. However, emitters will still only have a number of inputs equal to the
// number of used arguments. Therefore, we have extra arguments if an emitter is used in this situation.
if (node instanceof CompositionNode.Emitter) {
CompositionNode.Collector emitterCollector = ((CompositionNode.Emitter)node).getCollectorNode();
// Get the number of arguments on the collector as calculated by the source generator.
List<CompositionArgument> collectorArguments = graphInfo.getCollectorArguments(emitterCollector);
// Calculate the number of extra arguments by subtracting the number of arguments on the emitter.
// This number should not be different if not generating source for type checking a composition node graph.
int nExtraArgs = collectorArguments.size() - node.getNArguments();
// Add a call to the error function for each extra arg.
for (int i = 0; i < nExtraArgs; i++) {
argExpressionList.add(EXTRA_ARG_ERROR_CALL_EXPR);
}
}
// Apply eta-reduction for final argument burnings on this node.
// eg. \w x y z -> foo w (bar x) y z becomes \w x -> foo w (bar x)
if (!(node instanceof CompositionNode.Lambda)) {
while (!burntArgNames.isEmpty()) {
String lastBurntArg = burntArgNames.get(burntArgNames.size() - 1);
SourceModel.Expr lastArgExpression = argExpressionList.get(argExpressionList.size() - 1);
if (lastBurntArg.equals(lastArgExpression.toSourceText())) {
// Final arg burning. Shrink the lists of burnt arg names and arg expressions.
burntArgNames = burntArgNames.subList(0, burntArgNames.size() - 1);
argExpressionList = argExpressionList.subList(0, argExpressionList.size() - 1);
} else {
// Not a final arg burning. None of the rest of the args can be final arg burnings either.
break;
}
}
}
// Apply eta-reduction for final free unburnt params on the graph if this is the eta-reduction root
if (isEtaReductionRoot) {
// TODOEL
//
// for (int i = nArguments - 1; i > -1; i--) {
// if (nodeArgument.getConnectedNode() == null && !nodeArgument.isBurnt()) {
// // Final free unburnt root param. Shrink the lists of leaves and the arg expressions by 1.
// leaves = leaves.subList(0, leaves.size() - 1);
// argExpressionList = argExpressionList.subList(0, argExpressionList.size() - 1);
//
// } else {
// // Not a final free unburnt root param. None of the rest of the args can be final free root param either.
// break;
// }
// }
}
// Attempt to translate the subexpression into an operator expression.
SourceModel.Expr operatorExpression;
if (operatorName != null) {
// the call to translateToOperatorExpression would return null if
// the expression cannot in fact be translated into an operator
// expression.
operatorExpression = getOperatorExpressionSourceModel(operatorName, nArguments, argExpressionList);
} else {
operatorExpression = null;
}
// Create the subexpression text.
SourceModel.Expr subExpression;
// If the subExpression can be translated as an operator expression, use the operator expression
if (operatorExpression != null) {
subExpression = operatorExpression;
} else {
int nArgExpressions = argExpressionList.size();
if (nArgExpressions == 0) {
subExpression = applyable;
} else {
SourceModel.Expr[] appExprs = new SourceModel.Expr[nArgExpressions + 1];
appExprs[0] = applyable;
for (int i = 0; i < nArgExpressions; i++) {
appExprs[i+1] = argExpressionList.get(i);
}
subExpression = SourceModel.Expr.Application.make(appExprs);
}
}
if (codeQualificationMap != null) {
subExpression = removeRedundantLambda(subExpression, codeQualificationMap);
}
// Generate lambda for any burnt arguments.
SourceModel.Expr expression = getMaybeLambdaSourceModel(subExpression, burntArgNames);
return expression;
}
/**
* Iterate through the arguments of the node and get the expressions for the connected subtrees.
* @param node the current node which its arguments are being iterated
* @param graphInfo the calculated graph info
* @return a list of expressions and a list of burnt arguments
*/
private static Pair<List<SourceModel.Expr>, List<String>> getArgExprsAndBurntArgs(CompositionNode node, GraphInfo graphInfo) {
List<String> burntArgNames = new ArrayList<String>();
List<SourceModel.Expr> argExpressionList = new ArrayList<SourceModel.Expr>();
for(int i = 0, nArgs = node.getNArguments(); i < nArgs; i++){
CompositionNode.CompositionArgument nodeArgument = node.getNodeArgument(i);
CompositionNode connectedNode = nodeArgument.getConnectedNode();
if (connectedNode != null) {
// argument is bound
argExpressionList.add(getSubtreeExpressionSourceModel(connectedNode, graphInfo, false));
} else {
// argument is free
String argName = nodeArgument.isBurnt() ? nodeArgument.getNameString() : graphInfo.getArgumentName(nodeArgument);
argExpressionList.add(SourceModel.Expr.Var.makeUnqualified(argName));
// If the argument is burnt, add its name to the list of burnt arg names
if (nodeArgument.isBurnt()) {
burntArgNames.add(argName);
}
}
}
return new Pair<List<SourceModel.Expr>, List<String>>(argExpressionList, burntArgNames);
}
/**
* Translate a function application expression into an operator expression,
* if it is possible.
*
* @param operatorName
* the name of the operator.
* @param nArguments
* the number of formal parameters of the function to be applied.
* @param argExpressionList
* the list of actual arguments to the function application.
* @return a SourceModel.Expr instance representing the operator expression
* if the input is translatable as one, otherwise null.
*/
private static SourceModel.Expr getOperatorExpressionSourceModel(String operatorName, int nArguments, List<SourceModel.Expr> argExpressionList) {
int nArgExpressions = argExpressionList.size();
if (nArguments == 0 && nArgExpressions == 0) {
// zero argument operator.
return getNullaryOpSourceModel(operatorName);
} else if (nArguments == 2 && nArgExpressions == 2) {
// two argument operator.
return getBinaryExprSourceModel(
operatorName, argExpressionList.get(0), argExpressionList.get(1));
} else if (operatorName.equals("$") && nArguments == 2 && nArgExpressions == 1) {
// apply operator - one argument: just return the argument, since (Prelude.apply x) == x
return argExpressionList.get(0);
} else if (operatorName.equals("#") && nArguments == 3) {
if (nArgExpressions == 2) {
// compose operator - two arguments: just return (firstArg # secondArg)
return SourceModel.Expr.BinaryOp.Compose.make(argExpressionList.get(0), argExpressionList.get(1));
} else if (nArgExpressions == 3) {
// compose operator - three arguments: just return ((firstArg # secondArg) thirdArg)
return SourceModel.Expr.Application.make(
new SourceModel.Expr[] {
SourceModel.Expr.BinaryOp.Compose.make(argExpressionList.get(0), argExpressionList.get(1)),
argExpressionList.get(2) });
}
}
// the expression is not translatable to an operator expression, so return null.
return null;
}
/**
* Analyzes the defining expression of a lambda expression, and determine
* which occurences of identifiers within the expression refer to top-level
* functions, which refer to parameters, and which refer to locally defined
* values.
* <p>
*
* This functionality is used in performing argument substitution for
* lambdas generated for code gems. In cases where a parameter of a lambda
* differs lexically from that of the argument substituting for it, it is
* necessary to scan through the defining expression of the lambda to
* identify which variables refer to the about-to-be-renamed parameters.
* Also, it is possible that the arguments to be substituted in collide with
* unqualified references to top level functions. Therefore such references
* will also need to be identified in the scan, so that they may be
* disambiguated lated on. Finally, name bindings that are local to the
* defining expression of the lambda need to be identified, because
* they may need to be renamed so that they would not collide with the
* arguments substitution.
* <p>
*
* @author Joseph Wong
*/
private static final class LambdaDefiningExprScopeAnalyzer extends SourceModelTraverser<LambdaDefiningExprScopeAnalyzer.Scope, Void> {
/**
* Represents a scope for name bindings. Each scope, except for the
* outermost scope, has a parent scope.
*
* @author Joseph Wong
*/
private static final class Scope {
/** The parent scope of this scope. */
private final Scope parent;
/** The set of names bound in this scope. */
private final Set/*String*/<String> boundNames;
/**
* Creates a new scope with the specified parent.
*
* @param parentScope
* the parent scope.
*/
private Scope(Scope parentScope) {
parent = parentScope;
boundNames = new HashSet<String>();
}
/**
* Creates a new outermost scope, initialized with a list of
* parameters bound in this scope.
*
* @param params
*/
private Scope(SourceModel.Parameter[] params) {
this((Scope)null);
if (params != null) {
for (final SourceModel.Parameter param : params) {
boundNames.add(param.getName());
}
}
}
/**
* Using this scope as the parent, create a new scope and
* initialized it with a list of function names bound within the
* scope.
*
* @param defns
* the local definitions bound in the new scope.
* @return the new inner scope.
*/
private Scope newInnerScope(SourceModel.LocalDefn[] defns) {
final Scope result = new Scope(this);
if (defns != null) {
final LocalBindingsProcessor<Object, Void> localBindingsProcessor = new LocalBindingsProcessor<Object, Void> () {
@Override
void processLocalDefinitionBinding(final String name, final SourceModel.SourceElement localDefinition, final Object arg) {
result.boundNames.add(name);
}
};
for (final SourceModel.LocalDefn defn : defns) {
defn.accept(localBindingsProcessor, null);
}
}
return result;
}
/**
* Using this scope as the parent, create a new scope and
* initialized it with a list of parameters bound within the
* scope.
*
* @param params
* the parameters bound in the new scope.
* @return the new inner scope.
*/
private Scope newInnerScope(SourceModel.Parameter[] params) {
Scope result = new Scope(this);
if (params != null) {
for (final SourceModel.Parameter param : params) {
result.boundNames.add(param.getName());
}
}
return result;
}
/**
* Using this scope as the parent, create a new scope and
* initialized it with a list of case expression patterns bound
* within the scope.
*
* @param patterns
* the case expression patterns bound in the new
* scope.
* @return the new inner scope.
*/
private Scope newInnerScope(SourceModel.Pattern[] patterns) {
Scope result = new Scope(this);
if (patterns != null) {
for (final SourceModel.Pattern pattern : patterns) {
if (pattern instanceof SourceModel.Pattern.Var) {
SourceModel.Pattern.Var var =
(SourceModel.Pattern.Var)pattern;
result.boundNames.add(var.getName());
}
}
}
return result;
}
/**
* Using this scope as the parent, create a new scope and
* initialized it with a list of field patterns bound within the
* scope.
*
* @param fieldPatterns
* the field patterns bound in the new scope.
* @return the new inner scope.
*/
private Scope newInnerScope(SourceModel.FieldPattern[] fieldPatterns) {
Scope result = new Scope(this);
if (fieldPatterns != null) {
for (final SourceModel.FieldPattern fieldPattern : fieldPatterns) {
SourceModel.Pattern pattern = fieldPattern.getPattern();
if (pattern != null
&& pattern instanceof SourceModel.Pattern.Var) {
SourceModel.Pattern.Var var =
(SourceModel.Pattern.Var)pattern;
result.boundNames.add(var.getName());
} else {
FieldName fieldName = fieldPattern.getFieldName().getName();
if (fieldName instanceof FieldName.Textual) {
result.boundNames.add(fieldName.getCalSourceForm());
}
}
}
}
return result;
}
/**
* @param name
* the name to be checked.
* @return whether the name is bound in this scope or any or the
* ancestor scopes.
*/
private boolean isNameBound(String name) {
Scope current = this;
do {
if (current.boundNames.contains(name)) {
return true;
}
current = current.parent;
} while (current != null);
return false;
}
/**
* @param name
* the name to be checked.
* @return true iff the name is bound in the outermost scope but
* not in this scope nor in any other ancestor scopes.
*/
private boolean isNameBoundOnlyInOutermostScope(String name) {
Scope current = this;
while (current.parent != null) {
if (current.boundNames.contains(name)) {
return false;
}
current = current.parent;
}
return current.boundNames.contains(name);
}
}
/**
* Encapsulates the result of the scope analyzer.
*
* @author Joseph Wong
*/
private static final class Result {
/**
* A set of the variable references which do not resolve to any
* local name bindings.
*/
private final Set<SourceModel.Expr.Var> unqualifiedNonLocalReferences = new HashSet<SourceModel.Expr.Var>();
/**
* A set of the names which do not resolve to any local name
* bindings.
*/
private final Set<String> unqualifiedNonLocalNames = new HashSet<String>();
/** A set of the names that are bound locally. */
private final Set<String> locallyDefinedNames = new HashSet<String>();
/**
* A set of the variable references resolving to the parameters
* of the outer lambda expression.
*/
private final Set<SourceModel.Expr.Var> referencesToParameters = new HashSet<SourceModel.Expr.Var>();
}
/** The result of this analyzer. */
private final Result result = new Result();
/**
* @return the result of this analyzer.
*/
private Result getResult() {
return result;
}
/**
* Analyze the defining expression of a lambda expression, given the
* parameters of the lambda expression.
*
* @param params
* the parameters of the lambda expression.
* @param inExpr
* the defining expression of the lambda expression.
*/
private void analyzeLambdaDefiningExpr(SourceModel.Parameter[] params, SourceModel.Expr inExpr) {
inExpr.accept(this, new Scope(params));
}
/**
* Analyzes the variable reference with the supplied scoping
* information.
*
* @param var
* the variable reference.
* @param scope
* the surrounding scope.
*/
@Override
public Void visit_Expr_Var(
SourceModel.Expr.Var var, Scope scope) {
SourceModel.Name.Function varName = var.getVarName();
if (!varName.isQualified()) {
if (!scope.isNameBound(varName.getUnqualifiedName())) {
// the name cannot be resolved to bindings in the local scopes,
// therefore it must be referring to a top-level binding
result.unqualifiedNonLocalReferences.add(var);
result.unqualifiedNonLocalNames.add(varName.getUnqualifiedName());
} else {
// the name can be resolved, check to see if it refers to bindings
// in the outermost scope, i.e. the parameters of the lambda expression
if (scope.isNameBoundOnlyInOutermostScope(varName.getUnqualifiedName())) {
result.referencesToParameters.add(var);
}
}
}
return super.visit_Expr_Var(var, scope);
}
/**
* Creates a new inner scope from the names bound in the let
* expression and uses it to traverse the subexpressions.
*
* @param let
* the let expression.
* @param scope
* the surrounding scope.
*/
@Override
public Void visit_Expr_Let(
SourceModel.Expr.Let let, Scope scope) {
return super.visit_Expr_Let(let, scope.newInnerScope(let.getLocalDefinitions()));
}
/**
* Creates a new inner scope from the parameters bound in the lambda
* expression and uses it to traverse the defining expression.
*
* @param lambda
* the lambda expression.
* @param scope
* the surrounding scope.
*/
@Override
public Void visit_Expr_Lambda(
SourceModel.Expr.Lambda lambda, Scope scope) {
return super.visit_Expr_Lambda(lambda, scope.newInnerScope(lambda.getParameters()));
}
/**
* Creates a new inner scope from the parameters bound in the function
* definition and uses it to traverse the subexpressions.
*
* @param function
* the function definition.
* @param scope
* the surrounding scope.
*/
@Override
public Void visit_FunctionDefn_Algebraic(
SourceModel.FunctionDefn.Algebraic function, Scope scope) {
return super.visit_FunctionDefn_Algebraic(function, scope.newInnerScope(function.getParameters()));
}
/**
* Creates a new inner scope from the parameters bound in the local
* function definition and uses it to traverse the subexpressions.
*
* @param function
* the local function definition.
* @param scope
* the surrounding scope.
*/
@Override
public Void visit_LocalDefn_Function_Definition(
SourceModel.LocalDefn.Function.Definition function, Scope scope) {
result.locallyDefinedNames.add(function.getName());
return super.visit_LocalDefn_Function_Definition(function, scope.newInnerScope(function.getParameters()));
}
/**
* Adds the name of the declared function to the set of locally
* defined names.
*
* @param decl
* the local function type definition.
* @param scope
* the surrounding scope.
*/
@Override
public Void visit_LocalDefn_Function_TypeDeclaration(
SourceModel.LocalDefn.Function.TypeDeclaration decl, Scope scope) {
result.locallyDefinedNames.add(decl.getName());
return super.visit_LocalDefn_Function_TypeDeclaration(decl, scope);
}
/**
* Adds the name of the parameter to the set of locally defined
* names.
*
* @param parameter
* the parameter.
* @param scope
* the surrounding scope.
*/
@Override
public Void visit_Parameter(
SourceModel.Parameter parameter, Scope scope) {
result.locallyDefinedNames.add(parameter.getName());
return super.visit_Parameter(parameter, scope);
}
/**
* Adds the name of the variable pattern to the set of locally
* defined names.
*
* @param var
* the variable pattern.
* @param scope
* the surrounding scope.
*/
@Override
public Void visit_Pattern_Var(
SourceModel.Pattern.Var var, Scope scope) {
result.locallyDefinedNames.add(var.getName());
return super.visit_Pattern_Var(var, scope);
}
/**
* Creates a new inner scope from the names bound in the case
* expression alternative and uses it to traverse the
* right-hand-side expression.
*
* @param tuple
* the case alternative.
* @param scope
* the surrounding scope.
*/
@Override
public Void visit_Expr_Case_Alt_UnpackTuple(
SourceModel.Expr.Case.Alt.UnpackTuple tuple, Scope scope) {
return super.visit_Expr_Case_Alt_UnpackTuple(tuple, scope.newInnerScope(tuple.getPatterns()));
}
/**
* Creates a new inner scope from the names bound in the case
* expression alternative and uses it to traverse the
* right-hand-side expression.
*
* @param dataCons
* the case alternative.
* @param surroundingScope
* the surrounding scope.
*/
@Override
public Void visit_Expr_Case_Alt_UnpackDataCons(
SourceModel.Expr.Case.Alt.UnpackDataCons dataCons, Scope surroundingScope) {
CaseExprUnpackDataConsAltArgBindingsScopeBuilder scopeBuilder = new CaseExprUnpackDataConsAltArgBindingsScopeBuilder();
Scope newScope = dataCons.getArgBindings().accept(scopeBuilder, surroundingScope);
return super.visit_Expr_Case_Alt_UnpackDataCons(dataCons, newScope);
}
/**
* A helper class for constructing a new scope from the argument bindings
* within a data constructor case expression alternative, so that the newly
* constructed scope with the additional bindings can be used to analyze
* the right-hand-side expression of the alternative.
*
* @author Joseph Wong
*/
private static class CaseExprUnpackDataConsAltArgBindingsScopeBuilder extends SourceModelTraverser<Scope, Scope> {
/**
* Creates a new inner scope from the names bound in the case
* expression alternative and returns it, so that it may be used
* by the caller in traversing the right-hand-side expression.
*
* @param argBindings
* the argument bindings.
* @param scope
* the surrounding scope.
* @return the new scope.
*/
@Override
public Scope visit_ArgBindings_Matching(
SourceModel.ArgBindings.Matching argBindings, Scope scope) {
return scope.newInnerScope(argBindings.getFieldPatterns());
}
/**
* Creates a new inner scope from the names bound in the case
* expression alternative and returns it, so that it may be used
* by the caller in traversing the right-hand-side expression.
*
* @param argBindings
* the argument bindings.
* @param scope
* the surrounding scope.
* @return the new scope.
*/
@Override
public Scope visit_ArgBindings_Positional(
SourceModel.ArgBindings.Positional argBindings, Scope scope) {
return scope.newInnerScope(argBindings.getPatterns());
}
}
/**
* Creates a new inner scope from the names bound in the case
* expression alternative and uses it to traverse the
* right-hand-side expression.
*
* @param listCons
* the case alternative.
* @param scope
* the surrounding scope.
*/
@Override
public Void visit_Expr_Case_Alt_UnpackListCons(
SourceModel.Expr.Case.Alt.UnpackListCons listCons, Scope scope) {
return super.visit_Expr_Case_Alt_UnpackListCons(
listCons, scope.newInnerScope(
new SourceModel.Pattern[] {
listCons.getHeadPattern(), listCons.getTailPattern() }));
}
/**
* Creates a new inner scope from the names bound in the case
* expression alternative and uses it to traverse the
* right-hand-side expression.
*
* @param record
* the case alternative.
* @param scope
* the surrounding scope.
*/
@Override
public Void visit_Expr_Case_Alt_UnpackRecord(
SourceModel.Expr.Case.Alt.UnpackRecord record, Scope scope) {
if (record.getBaseRecordPattern() != null) {
// first, apply this visitor to the base record pattern
// and add this binding to the scope
record.getBaseRecordPattern().accept(this, scope);
scope = scope.newInnerScope(
new SourceModel.Pattern[] {
record.getBaseRecordPattern() });
}
for (int i = 0; i < record.getNFieldPatterns(); i++) {
record.getNthFieldPattern(i).accept(this, scope);
}
// finally, visit the alternative's expression with the base record pattern
// and all the field patterns appearing as bound names in the new scope
record.getAltExpr().accept(this, scope.newInnerScope(record.getFieldPatterns()));
return null;
}
/**
* If the supplied field is punned, add the name of the field to the
* set of locally defined names.
*
* @param pattern
* the field pattern.
* @param scope
* the surrounding scope.
*/
@Override
public Void visit_FieldPattern(
SourceModel.FieldPattern pattern, Scope scope) {
if (pattern.getPattern() != null) {
pattern.getPattern().accept(this, scope);
} else {
if (pattern.getFieldName().getName() instanceof FieldName.Textual) {
// this field is punned, so the textual field name stands for the pattern
result.locallyDefinedNames.add(pattern.getFieldName().getName().getCalSourceForm());
}
}
return null;
}
}
/**
* Performs a parameter renaming operation following a set of before->after
* mappings. Name disambiguation is performed during the renaming operation
* to resolve any name collisions.
* <p>
*
* This renaming operation is part of the algorithm for minimizing or
* completely eliminating the need for a lambda expression to wrap around a
* code gem. This is achieved by substituting arguments for names bound to
* parameters in the defining expression of the lambda. In cases where a
* parameter and its corresponding argument differ lexically, the renaming
* operation takes care of transforming the defining expression so that all
* references to the parameter now refer directly to the argument.
* <p>
*
* This renaming operation utilizes information from the
* LambdaDefiningExprScopeAnalyzer for identifying situations where name
* disambiguation needs to occur. For example, given the code gem:
*
* <pre><code>
* let
* a = x; a_2 = y;
* in
* (a, a_2, x)
* </code></pre>
*
* If the parameters <code>x</code> and <code>y</code> are to be
* replaced by <code>a</code> and <code>a_1</code> respectively, the let
* definition of <code>a = x</code> and the reference to <code>a</code>
* in <code>(a, a_2, x)</code> will need to be disambiguated. In
* particular, the new name cannot collide with either <code>a</code>,
* <code>a_1</code>, or <code>a_2</code>. Therefore, the algorithm
* chooses the name of <code>a_3</code> as the replacement. The resulting
* transformed source model would therefore correspond to the CAL source:
*
* <pre><code>
* let
* a_3 = a; a_2 = a_1;
* in
* (a_3, a_2, a)
* </code></pre>
*
* This algorithm takes care to ensure that the semantics of the code
* is preserved. For example, it will "un-pun" a punned field in a record
* pattern for a case alternative if it is deemed that the implicit
* pattern needs to be disambiguated.
* <p>
*
* @see CALSourceGenerator.LambdaDefiningExprScopeAnalyzer
*
* @author Joseph Wong
*/
private static final class ParameterRenamer extends SourceModelCopier<Void> {
/**
* A mapping of the old names to the new names for those parameters that
* need to be renamed/
*/
private final Map<String, String> paramsRenameMapping = new HashMap<String, String>();
/**
* A set of all the new names for the parameters, whether they differ
* from the old ones or not.
*/
private final Set<String> allNewParamNames = new HashSet<String>();
/**
* A mapping of all the names affected by the renaming of the
* parameters to their new names.
*/
private final Map<String, String> collateralDamageRepairMapping = new HashMap<String, String>();
/** The result of the scope analyzer. */
private final LambdaDefiningExprScopeAnalyzer.Result scopeAnalysis;
/**
* The code qualification map to use for translating unqualified references
* to top-level functions to fully qualified ones.
*/
private final CodeQualificationMap codeQualificationMap;
/**
* Constructor for ParameterRenamer.
*
* @param before
* the list of original parameter names.
* @param after
* the list of new names for the variables refering to
* the parameters.
* @param scopeAnalyzerResult
* the result of the scope analyzer.
* @param qualificationMap
* the code qualification map
*/
private ParameterRenamer(
List/*String*/<String> before,
List/*String*/<String> after,
LambdaDefiningExprScopeAnalyzer.Result scopeAnalyzerResult,
CodeQualificationMap qualificationMap) {
scopeAnalysis = scopeAnalyzerResult;
codeQualificationMap = qualificationMap;
if (before.size() != after.size()
|| new HashSet<String>(before).size() != before.size()
|| new HashSet<String>(after).size() != after.size()) {
// the list of original names must be of the same length
// as the list of new names for the renaming operation
// to work
// if the list of original names or the list of new names
// contains any duplicates, the renaming operation cannot
// preserve the semantics of the code being transformed, and
// is therefore an error condition
throw new IllegalArgumentException();
}
// first, populate the mappings for parameter renaming
int numParams = before.size();
for (int i = 0; i < numParams; i++) {
if (!LanguageInfo.isValidFunctionName(before.get(i)) ||
!LanguageInfo.isValidFunctionName(after.get(i))) {
// each name in the before and after lists must be a
// valid CAL function name, otherwise it is an error
throw new IllegalArgumentException();
}
allNewParamNames.add(after.get(i));
// populate the mapping only when the pair of before/after
// names differ
if (!before.get(i).equals(after.get(i))) {
paramsRenameMapping.put(before.get(i), after.get(i));
}
}
// then, construct a mapping of names affected by the parameter
// renaming to their new disambiguated names
for (final String localName : scopeAnalysis.locallyDefinedNames) {
if (paramsRenameMapping.containsValue(localName)) {
// only populate the mapping when there is a collision
// of an existing name defined within the expression
// with one of the new names to be substituted for the
// parameters
collateralDamageRepairMapping.put(localName, calcCollateralDamageRepair(localName));
}
}
}
/**
* Constructs a new name for an existing name affected by the
* renaming of parameters.
*
* @param origName
* the original name affected by the parameter renaming
* @return a new name guaranteed not to collide with any other name
* in use
*/
private String calcCollateralDamageRepair(String origName) {
int i = 1;
String newName = origName + "_" + i;
while (allNewParamNames.contains(newName)
|| collateralDamageRepairMapping.containsValue(newName)
|| scopeAnalysis.unqualifiedNonLocalNames.contains(newName)
|| scopeAnalysis.locallyDefinedNames.contains(newName)) {
i++;
newName = origName + "_" + i;
}
return newName;
}
/**
* Processes an argument reference in a CALDoc and rename it if necessary.
*
* @param argBlock
* the argument reference.
* @param arg
* (unused)
* @return the new argument reference, appropriately renamed if
* necessary.
*/
@Override
public SourceModel.CALDoc.TaggedBlock visit_CALDoc_TaggedBlock_Arg(
SourceModel.CALDoc.TaggedBlock.Arg argBlock, Void arg) {
FieldName argName = argBlock.getArgName().getName();
if (argName instanceof FieldName.Textual) {
String unqualifiedName = argName.getCalSourceForm();
if (collateralDamageRepairMapping.containsKey(unqualifiedName)) {
// This is an unqualified name that is defined locally
// within the body of the lambda and is affected by the
// parameter renaming. Map this name to its
// disambiguated version.
return SourceModel.CALDoc.TaggedBlock.Arg.make(
SourceModel.Name.Field.make(FieldName.makeTextualField(collateralDamageRepairMapping.get(unqualifiedName))),
argBlock.getTextBlock());
} else {
// This unqualified name does not require renaming.
return super.visit_CALDoc_TaggedBlock_Arg(argBlock, arg);
}
} else {
// Only textual argument names need to be renamed.
return super.visit_CALDoc_TaggedBlock_Arg(argBlock, arg);
}
}
/**
* Processes a variable reference and rename it if necessary.
*
* @param var
* the variable reference.
* @param arg
* (unused)
* @return the new variable reference, appropriately renamed if
* necessary.
*/
@Override
public SourceModel.Expr visit_Expr_Var(
SourceModel.Expr.Var var, Void arg) {
if (!var.getVarName().isQualified()) {
String unqualifiedName = var.getVarName().getUnqualifiedName();
if (paramsRenameMapping.containsValue(unqualifiedName)
&& scopeAnalysis.unqualifiedNonLocalReferences.contains(var)) {
// This is an unqualified name that used to refer to a
// top-level function in the current module, and which
// will now be shadowed by one of the renamed
// parameters. Explicit qualification needs to be used
// to ensure that the this variable continues to refer
// to the top-level function
return SourceModel.Expr.Var.make(
codeQualificationMap.getQualifiedName(
unqualifiedName, SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD));
} else if (paramsRenameMapping.containsKey(unqualifiedName)
&& scopeAnalysis.referencesToParameters.contains(var)) {
// This is an unqualified name that used to refer to a
// parameter. If the parameter is renamed, translate
// this name to the new name of the parameter.
return SourceModel.Expr.Var.makeUnqualified(paramsRenameMapping.get(unqualifiedName));
} else if (collateralDamageRepairMapping.containsKey(unqualifiedName)) {
// This is an unqualifed name that is defined locally
// within the body of the lambda and is affected by the
// parameter renaming. Map this name to its
// disambiguated version.
return SourceModel.Expr.Var.makeUnqualified(collateralDamageRepairMapping.get(unqualifiedName));
} else {
// This unqualifed name does not require renaming.
return super.visit_Expr_Var(var, arg);
}
} else {
// All fully qualified names are spared from the renaming process.
return super.visit_Expr_Var(var, arg);
}
}
/**
* Perform name substitution on the supplied local function
* definition and return the transformed function definition.
*
* @param function
* the local function definition.
* @param arg
* (unused)
* @return the transformed function definition.
*/
@Override
public SourceModel.LocalDefn visit_LocalDefn_Function_Definition(
SourceModel.LocalDefn.Function.Definition function, Void arg) {
String name = function.getName();
if (collateralDamageRepairMapping.containsKey(name)) {
SourceModel.CALDoc.Comment.Function newCALDocComment = null;
if (function.getCALDocComment() != null) {
newCALDocComment = (SourceModel.CALDoc.Comment.Function)function.getCALDocComment().accept(this, arg);
}
SourceModel.Parameter[] newParameters = new SourceModel.Parameter[function.getNParameters()];
for (int i = 0; i < function.getNParameters(); i++) {
newParameters[i] = (SourceModel.Parameter)function.getNthParameter(i).accept(this, arg);
}
return SourceModel.LocalDefn.Function.Definition.make(
newCALDocComment,
collateralDamageRepairMapping.get(name),
newParameters,
(SourceModel.Expr)function.getDefiningExpr().accept(this, arg));
} else {
return super.visit_LocalDefn_Function_Definition(function, arg);
}
}
/**
* Perform name substitution on the supplied local function type
* definition and return the transformed function type definition.
*
* @param decl
* the local function type definition.
* @param arg
* (unused)
* @return the transformed function type definition.
*/
@Override
public SourceModel.LocalDefn visit_LocalDefn_Function_TypeDeclaration(
SourceModel.LocalDefn.Function.TypeDeclaration decl, Void arg) {
String name = decl.getName();
if (collateralDamageRepairMapping.containsKey(name)) {
SourceModel.CALDoc.Comment.Function newCALDocComment = null;
if (decl.getCALDocComment() != null) {
newCALDocComment = (SourceModel.CALDoc.Comment.Function)decl.getCALDocComment().accept(this, arg);
}
return SourceModel.LocalDefn.Function.TypeDeclaration.make(
newCALDocComment,
collateralDamageRepairMapping.get(name),
(SourceModel.TypeSignature)decl.getDeclaredType().accept(this, arg));
} else {
return super.visit_LocalDefn_Function_TypeDeclaration(decl, arg);
}
}
/**
* Perform name substitution on the supplied parameter and return
* the transformed parameter.
*
* @param parameter
* the parameter.
* @param arg
* (unused)
* @return the transformed parameter.
*/
@Override
public SourceModel.Parameter visit_Parameter(
SourceModel.Parameter parameter, Void arg) {
String name = parameter.getName();
if (collateralDamageRepairMapping.containsKey(name)) {
return SourceModel.Parameter.make(collateralDamageRepairMapping.get(name), parameter.isStrict());
} else {
return super.visit_Parameter(parameter, arg);
}
}
/**
* Perform name substitution on the supplied case expression
* alternative variable pattern and return the transformed pattern.
*
* @param var
* the case expression alternative variable pattern.
* @param arg
* (unused)
* @return the transformed pattern.
*/
@Override
public SourceModel.Pattern visit_Pattern_Var(
SourceModel.Pattern.Var var, Void arg) {
String name = var.getName();
if (collateralDamageRepairMapping.containsKey(name)) {
return SourceModel.Pattern.Var.make(collateralDamageRepairMapping.get(name));
} else {
return super.visit_Pattern_Var(var, arg);
}
}
/**
* Perform name substitution on the supplied case expression
* alternative field pattern and return the transformed
* pattern.
*
* @param fieldPattern
* the case expression alternative field pattern.
* @param arg
* (unused)
* @return the transformed pattern.
*/
@Override
public SourceModel.FieldPattern visit_FieldPattern(
SourceModel.FieldPattern fieldPattern, Void arg) {
// the name subtitution needs to be done when the field pattern
// is punned, and the implicit pattern collides with one of the
// new names being substituted for the parameters
if (fieldPattern.getPattern() == null
&& fieldPattern.getFieldName().getName() instanceof FieldName.Textual
&& collateralDamageRepairMapping.containsKey(fieldPattern.getFieldName().getName().getCalSourceForm())) {
return SourceModel.FieldPattern.make(
SourceModel.Name.Field.make(fieldPattern.getFieldName().getName()),
SourceModel.Pattern.Var.make(
collateralDamageRepairMapping.get(fieldPattern.getFieldName().getName().getCalSourceForm())));
} else {
return super.visit_FieldPattern(fieldPattern, arg);
}
}
}
/**
* Minimize (or remove completely) the lambda associated with the
* application of a code gem.
* <p>
*
* This method operates on expressions of the form:
*
* <pre><code>
* ((\p_1 .. p_n -> definingExpr) a_1 .. a_m)
* </code></pre>
*
* For each parameter/argument pair (p_i, a_i), if p_i
* is lexically identical to a_i, then the parameter p_i is
* simply eliminated from the list of parameters, and likewise
* a_i is dropped from the list of arguments.
* <p>
*
* If a_i is a variable but not identical to p_i, an argument
* substitution is performed by using the LambdaDefiningExprScopeAnalyzer
* and ParameterRenamer visitors. The first visitor traverses the
* source model of the lambda's defining expression to identify
* the variable references appearing within it, while the second visitor
* uses the results gathered by the first visitor as a guide in
* renaming occurrences of p_i in the defining expression to a_i, while
* at the same time renaming other local name bindings therein to
* avoid name collisions.
* <p>
*
* If all of the lambda's parameters are eliminated in this fashion,
* the lambda itself is replaced by its defining expression.
* <p>
*
* For example, given the code gem:
*
* <pre><code>
* let
* a = x; a_2 = y;
* in
* (a, a_2, x)
* </code></pre>
*
* After substituting the arguments <code>a</code> for <code>x</code>
* and <code>a_1</code> for <code>y</code>, the resulting source model
* would correspond to the CAL source:
*
* <pre><code>
* let
* a_3 = a; a_2 = a_1;
* in
* (a_3, a_2, a)
* </code></pre>
*
* In particular, the local definition of <code>a</code> is renamed to
* <code>a_3</code> so as not to collide with the newly renamed
* variables of <code>a</code> and <code>a_1</code> as well as the
* pre-existing local definition of <code>a_2</code>.
* <p>
*
* Note that this algorithm does <i>not</i> substitute
* non-variable-reference expressions or qualified references to top-level
* functions for parameters. For example:
*
* <pre><code>
* (\f x y -> f x y) Prelude.add a (Prelude.add b c)
* </code></pre>
*
* is tranformed to:
*
* <pre><code>
* (\f y -> f a y) Prelude.add (Prelude.add b c)
* </code></pre>
*
* @see CALSourceGenerator.LambdaDefiningExprScopeAnalyzer
* @see CALSourceGenerator.ParameterRenamer
*
* @param origExpr
* the expression to be transformed
* @return the transformed expression with the outer-most lambda minimized
* or removed (if there were any in the original expression)
*/
private static SourceModel.Expr removeRedundantLambda(
SourceModel.Expr origExpr,
final CodeQualificationMap codeQualificationMap) {
SourceModel.verifyArg(origExpr, "origExpr");
SourceModel.verifyArg(codeQualificationMap, "codeQualificationMap");
// first, identify the cases where this operation cannot be
// performed (because origExpr is of the wrong form), and
// simply return origExpr in these cases
if (!(origExpr instanceof SourceModel.Expr.Application)) {
return origExpr;
}
SourceModel.Expr.Application app = (SourceModel.Expr.Application)origExpr;
SourceModel.Expr[] appExprs = app.getExpressions();
if (appExprs.length <= 1) {
return origExpr;
}
if (!(appExprs[0] instanceof SourceModel.Expr.Lambda)) {
return origExpr;
}
// origExpr is now verified to be of the form
// ((\p_1 .. p_n -> definingExpr) a_1 .. a_m)
// so proceed with the algorithm
SourceModel.Expr.Lambda lambda = (SourceModel.Expr.Lambda)appExprs[0];
SourceModel.Expr definingExpr = lambda.getDefiningExpr();
SourceModel.Parameter[] allParams = lambda.getParameters();
int numArgs = appExprs.length - 1;
int numArgsToProcess = Math.min(allParams.length, numArgs);
// obtain the list of parameters to be renamed and their associated
// arguments (= their new names), and the lists of parameters and
// arguments which will remain after the parameter elimination process
List<String> paramsToRename = new ArrayList<String>();
List<String> argsToRename = new ArrayList<String>();
List<SourceModel.Parameter> remainingParams = new ArrayList<SourceModel.Parameter>();
List<SourceModel.Expr> remainingArgs = new ArrayList<SourceModel.Expr>();
for (int i = 0; i < numArgsToProcess; i++) {
SourceModel.Parameter param = allParams[i];
SourceModel.Expr arg = appExprs[i + 1];
if (arg instanceof SourceModel.Expr.Var &&
!((SourceModel.Expr.Var)arg).getVarName().isQualified()) {
// the argument is a simple unqualified name, so we can substitute it
// for the parameter
SourceModel.Expr.Var var = (SourceModel.Expr.Var)arg;
paramsToRename.add(param.getName());
argsToRename.add(var.getVarName().getUnqualifiedName());
} else {
// the argument is not a simple unqualified name, so we cannot remove nor
// rename this parameter
remainingParams.add(param);
remainingArgs.add(arg);
}
}
// analyze the names within the defining expression of the lambda
LambdaDefiningExprScopeAnalyzer scopeAnalyzer = new LambdaDefiningExprScopeAnalyzer();
scopeAnalyzer.analyzeLambdaDefiningExpr(allParams, definingExpr);
// perform the appropriate renaming operations
ParameterRenamer renamer = new ParameterRenamer(paramsToRename, argsToRename, scopeAnalyzer.getResult(), codeQualificationMap);
definingExpr = (SourceModel.Expr)definingExpr.accept(renamer, null);
// now add the remaining params and args that were not looped over
for (int i = numArgsToProcess; i < allParams.length; i++) {
remainingParams.add(allParams[i]);
}
for (int i = numArgsToProcess; i < numArgs; i++) {
remainingArgs.add(appExprs[i + 1]);
}
if (remainingParams.isEmpty()) {
// all the parameters of this lambda have been eliminated, therefore
// the lambda can be completely removed
if (remainingArgs.isEmpty()) {
// so there are no remaining arguments either, therefore just
// return the defining expression
return definingExpr;
} else {
// form a new application for the defining expression
// and the remainder of the arguments
int numRemainingArgs = remainingArgs.size();
SourceModel.Expr[] newAppExprs = new SourceModel.Expr[numRemainingArgs + 1];
newAppExprs[0] = definingExpr;
for (int i = 0; i < numRemainingArgs; i++) {
newAppExprs[i + 1] = remainingArgs.get(i);
}
return SourceModel.Expr.Application.make(newAppExprs);
}
} else {
// some parameters of the lambda still remains, so construct a new
// lambda
SourceModel.Expr.Lambda newLambda = SourceModel.Expr.Lambda.make(
remainingParams.toArray(new SourceModel.Parameter[0]),
definingExpr);
if (remainingArgs.isEmpty()) {
// so there are no remaining arguments either, therefore just
// return the new lambda
return newLambda;
} else {
// form a new application for the new lambda
// and the remainder of the arguments
int numRemainingArgs = remainingArgs.size();
SourceModel.Expr[] newAppExprs = new SourceModel.Expr[numRemainingArgs + 1];
newAppExprs[0] = newLambda;
for (int i = 0; i < numRemainingArgs; i++) {
newAppExprs[i + 1] = remainingArgs.get(i);
}
return SourceModel.Expr.Application.make(newAppExprs);
}
}
}
/**
* Helper method to return an expression for an expression involving the
* "[]" and "()" data constructors
*
* @param operatorName
* either "[]" or "()"
* @return the resulting expression
*/
private static SourceModel.Expr getNullaryOpSourceModel(String operatorName) {
if (operatorName.equals("[]")) {
return SourceModel.Expr.List.make((SourceModel.Expr[])null);
} else if (operatorName.equals("()")) {
return SourceModel.Expr.Unit.make();
}
throw new IllegalArgumentException(
"CALSourceGenerator.getNullaryOpSourceModel: " + operatorName + " is not a known nullary operator");
}
/**
* Helper method to return an expression for a binary expression involving
* an operator
*
* @param operatorName
* the operator
* @param left
* the left-hand-side subexpression
* @param right
* the right-hand-side subexpression
* @return the resulting expression
*/
private static SourceModel.Expr getBinaryExprSourceModel(
String operatorName, SourceModel.Expr left, SourceModel.Expr right) {
if (operatorName.equals("&&")) {
return SourceModel.Expr.BinaryOp.And.make(left, right);
} else if (operatorName.equals("||")) {
return SourceModel.Expr.BinaryOp.Or.make(left, right);
} else if (operatorName.equals("++")) {
return SourceModel.Expr.BinaryOp.Append.make(left, right);
} else if (operatorName.equals("==")) {
return SourceModel.Expr.BinaryOp.Equals.make(left, right);
} else if (operatorName.equals("!=")) {
return SourceModel.Expr.BinaryOp.NotEquals.make(left, right);
} else if (operatorName.equals(">")) {
return SourceModel.Expr.BinaryOp.GreaterThan.make(left, right);
} else if (operatorName.equals(">=")) {
return SourceModel.Expr.BinaryOp.GreaterThanEquals.make(left, right);
} else if (operatorName.equals("<")) {
return SourceModel.Expr.BinaryOp.LessThan.make(left, right);
} else if (operatorName.equals("<=")) {
return SourceModel.Expr.BinaryOp.LessThanEquals.make(left, right);
} else if (operatorName.equals("+")) {
return SourceModel.Expr.BinaryOp.Add.make(left, right);
} else if (operatorName.equals("-")) {
return SourceModel.Expr.BinaryOp.Subtract.make(left, right);
} else if (operatorName.equals("*")) {
return SourceModel.Expr.BinaryOp.Multiply.make(left, right);
} else if (operatorName.equals("/")) {
return SourceModel.Expr.BinaryOp.Divide.make(left, right);
} else if (operatorName.equals("%")) {
return SourceModel.Expr.BinaryOp.Remainder.make(left, right);
} else if (operatorName.equals("$")) {
// instead of returning (left $ right), we simply return (left right)
return SourceModel.Expr.Application.make(new SourceModel.Expr[] {left, right});
} else if (operatorName.equals(":")) {
return SourceModel.Expr.BinaryOp.Cons.make(left, right);
}
throw new IllegalArgumentException(
"CALSourceGenerator.getBinaryExprSourceModel: " + operatorName + " is not a known binary operator");
}
/**
* Helper method to return an expression with lambda-bound arguments, if
* necessary.
*
* @param expression
* the base expression
* @param lambdaArgNames
* the arguments to bound as lambda arguments.
* @return if lambdaArgNames is empty, expression is returned. Otherwise, an
* expression is returned of the form \lambdaArg1 lambdaArg2 ...
* lambdaArgN -> expressionText
*/
private static SourceModel.Expr getMaybeLambdaSourceModel(SourceModel.Expr expression, List<String> lambdaArgNames) {
// Are there any burnt arguments?
if (lambdaArgNames.isEmpty()) {
// No burnt arguments so just return the expression itself
return expression;
} else {
// One or more burnt arguments so start a lambda and add the burnt
// parameter names, the arrow, and the expression
int numBurntArgs = lambdaArgNames.size();
SourceModel.Parameter[] burntArgs = new SourceModel.Parameter[numBurntArgs];
for (int i = 0; i < numBurntArgs; i++) {
burntArgs[i] = SourceModel.Parameter.make(lambdaArgNames.get(i), false);
}
return SourceModel.Expr.Lambda.make(burntArgs, expression);
}
}
}