/*
* xtc - The eXTensible Compiler
* Copyright (C) 2007-2008 Robert Grimm
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*/
package xtc.type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import xtc.Constants;
import xtc.Constants.FuzzyBoolean;
import xtc.tree.Printer;
import xtc.util.Utilities;
/**
* Common type operations for <em>Rats!</em> ASTs.
*
* <p />This class supports two views on a grammar's generic AST. The
* first view is dynamically typed, with all generic AST nodes
* represented by the canonical node type. The second view is
* statically typed, with all generic AST nodes represented by tuples
* organized into variants. Either way, this class supports the
* following types:<ul>
*
* <li>The void type represented by {@link VoidT}.</li>
*
* <li>The unit type represented by {@link UnitT}.</li>
*
* <li>Characters represented by an {@link InternalT} with name
* "char".</li>
*
* <li>Strings represented by an {@link InternalT} with name
* "string".</li>
*
* <li>Tokens represented by an {@link InternalT} with name
* "token".</li>
*
* <li>Dynamically typed nodes represented by an {@link InternalT}
* with name "node".</li>
*
* <li>Statically typed nodes represented by a {@link VariantT} with
* {@link TupleT} elements.</li>
*
* <li>Parse tree annotations represented by an {@link InternalT}
* with name "formatting".</li>
*
* <li>Lists represented by an {@link InternalT} with name
* "list".</li>
*
* <li>Actions represented by an {@link InternalT} with name
* "action".</li>
*
* </ul>
*
* All node types have the {@link Constants#ATT_NODE} attribute; the
* dynamically typed node representing generic productions also has
* the {@link Constants#ATT_GENERIC} attribute.
*
* <p />In addition to generic ASTs, this class also supports
* user-defined types, which must be represented by types that are not
* listed above.
*
* <p />Concrete subclasses specify the mapping between strings and
* types. For mapping internal representations back to strings, the
* void type has name "void", the unit type has name "unit", and the
* wildcard has name "?".
*
* @author Robert Grimm
* @version $Revision: 1.39 $
*/
public abstract class AST {
/** The set of internal type names. */
public static final Set<String> INTERNAL;
/** The canonical void type, which is {@link VoidT#TYPE}. */
public static final Type VOID = VoidT.TYPE;
/** The canonical any type. */
public static final Type ANY;
/** The canonical character reference type. */
public static final Type CHAR;
/** The canonical string type. */
public static final Type STRING;
/** The canonical token type. */
public static final Type TOKEN;
/** The canonical dynamically typed node type. */
public static final Type NODE;
/** The canonical null node type. */
public static final Type NULL_NODE;
/** The canonical dynamically typed generic node type. */
public static final Type GENERIC;
/** The canonical formatting node type. */
public static final Type FORMATTING;
/** The canonical parameterized list type. */
public static final Type LIST;
/** The canonical list instantiated with a wildcard element type. */
public static final Type WILD_LIST;
/** The canonical parameterized action type. */
public static final Type ACTION;
/** The canonical action instantiated with a wildcard element type. */
public static final Type WILD_ACTION;
static {
// Create the set of internal type names.
Set<String> internal = new HashSet<String>();
internal.add("any");
internal.add("char");
internal.add("string");
internal.add("token");
internal.add("node");
internal.add("formatting");
internal.add("list");
internal.add("action");
INTERNAL = Collections.unmodifiableSet(internal);
// Create the canonical types.
ANY = new InternalT("any");
CHAR = new InternalT("char");
STRING = new InternalT("string");
TOKEN = new InternalT("token");
TOKEN.addAttribute(Constants.ATT_NODE);
NODE = new InternalT("node");
NODE.addAttribute(Constants.ATT_NODE);
NULL_NODE = new UnitT();
NULL_NODE.addAttribute(Constants.ATT_NODE);
GENERIC = new InternalT("node");
GENERIC.addAttribute(Constants.ATT_NODE);
GENERIC.addAttribute(Constants.ATT_GENERIC);
FORMATTING = new InternalT("formatting");
FORMATTING.addAttribute(Constants.ATT_NODE);
LIST = new ParameterizedT(new NamedParameter("element"),
new InternalT("list"));
WILD_LIST = new InstantiatedT(Wildcard.TYPE, LIST);
ACTION = new ParameterizedT(new NamedParameter("element"),
new InternalT("action"));
WILD_ACTION = new InstantiatedT(Wildcard.TYPE, ACTION);
// Seal the canincal types.
ANY.seal();
CHAR.seal();
STRING.seal();
TOKEN.seal();
NULL_NODE.seal();
NODE.seal();
GENERIC.seal();
FORMATTING.seal();
LIST.seal();
WILD_LIST.seal();
ACTION.seal();
WILD_ACTION.seal();
}
// ==========================================================================
/** The map from strings to type representations. */
protected final Map<String,Type> externToIntern;
/**
* The map from internal type names to external types. Valid type
* names are void, unit, any, char, string, token, node, formatting,
* list, and action as well as ? for wildcards.
*/
protected final Map<String,String> internToExtern;
/**
* The list of imported module names. Each module name should end
* with the separator necessary for creating a fully qualified type
* name by appending a simple name.
*/
protected final List<String> importedModules;
/** The map from simple type names to fully qualified type names. */
protected final Map<String, String> importedTypes;
/** The map from variant names to variant types. */
protected final Map<String, VariantT> variants;
/** The map from variant names to nodes. */
protected final Map<String, Set<String>> variantNodes;
/** The map from unqualified variant names to original names. */
protected final Map<String, String> originalNames;
/** The map from tuple names to tuple types. */
protected final Map<String, TupleT> tuples;
/** The map from tuple names to variants containing the tuples. */
protected final Map<String, List<VariantT>> tupleVariants;
/**
* Create a new instance. This constructor allocates the internal
* data structures for mapping between external and internal types
* but does not initialize them.
*
* @see #initialize(boolean,boolean,boolean,boolean)
*/
public AST() {
externToIntern = new HashMap<String,Type>();
internToExtern = new HashMap<String,String>();
importedModules = new ArrayList<String>();
importedTypes = new HashMap<String,String>();
variants = new HashMap<String, VariantT>();
variantNodes = new HashMap<String, Set<String>>();
originalNames = new HashMap<String, String>();
tuples = new HashMap<String, TupleT>();
tupleVariants = new HashMap<String, List<VariantT>>();
}
/**
* Initialize the mapping between external and internal
* representations. This method fills the {@link #externToIntern}
* and {@link #internToExtern} data structures.
*
* @param hasNode Flag to indicate use of built-in nodes.
* @param hasToken Flag to indicate use of tokens.
* @param hasFormatting Flag to indicate use of formatting.
* @param hasAction Flag to indicate use of actions.
*/
public abstract void initialize(boolean hasNode, boolean hasToken,
boolean hasFormatting, boolean hasAction);
// ==========================================================================
/**
* Import the specified module. This method adds the module to the
* list of imported modules {@link #importedModules}. The specified
* module name must end with the appropriate separator.
*
* @param module The module name.
*/
public void importModule(String module) {
if (! importedModules.contains(module)) {
importedModules.add(module);
}
}
/**
* Import the specified type. This method adds a mapping from the
* specified simple type name to the specified qualified type name
* to the imported types {@link #importedTypes}.
*
* @param qualified The fully qualified name.
* @param simple The simple name.
*/
public void importType(String qualified, String simple) {
if (importedTypes.containsKey(simple)) {
assert qualified.equals(importedTypes.get(simple));
} else {
importedTypes.put(simple, qualified);
}
}
// ==========================================================================
/**
* Determine whether the specified string represents the void type.
*
* @param s The type as a string.
* @return <code>true</code> if the string represents the void type.
*/
public abstract boolean isVoid(String s);
/**
* Determine whether the specified string represents the generic
* node type.
*
* @param s The type as a string.
* @return <code>true</code> if the string represents the generic
* node type.
*/
public abstract boolean isGenericNode(String s);
// ==========================================================================
/**
* Convert the specified string representation of a type into the
* type. This method defers to {@link #internList(String)}, {@link
* #internAction(String)}, and {@link #internUser(String)} for list,
* action, and user-defined types, respectively.
*
* @param s The type as a string.
* @return The type.
* @throws IllegalArgumentException Signals that the string
* representation is not a valid type.
*/
public Type intern(String s) {
// Try the map from strings to types.
Type type = externToIntern.get(s);
if (null != type) return type;
// Try as a list.
type = internList(s);
if (! type.isError()) return type;
// Try as an action.
type = internAction(s);
if (! type.isError()) return type;
// Treat as user-defined.
return internUser(s);
}
/**
* Convert the specified string representation of a list type into
* the type.
*
* @param s The list type as a string.
* @return The type or {@link ErrorT#TYPE} if the string does not
* represent a list.
*/
protected abstract Type internList(String s);
/**
* Convert the specified string representation of an action type
* into the type.
*
* @param s The action type as a string.
* @return The type or {@link ErrorT#TYPE} if the string does not
* represent an action.
*/
protected abstract Type internAction(String s);
/**
* Convert the specified string representation of a user-defined
* type into its internal representation.
*
* @param s The user-defined type as a string.
* @return The type.
* @throws IllegalArgumentException Signals that the string
* representation is not a valid type.
*/
protected abstract Type internUser(String s);
// ==========================================================================
/**
* Convert the specified type to a string. This method defers to
* {@link #externList(Type)}, {@link #externAction(Type)}, and
* {@link #externUser(Type)} for list, action, and user-defined
* types, respectively.
*
* @param type The type.
* @return The type as a string.
*/
public String extern(Type type) {
String s;
switch (type.tag()) {
case VARIANT:
case TUPLE:
if (type.hasAttribute(Constants.ATT_NODE)) {
return internToExtern.get("node");
} else {
return externUser(type);
}
case INTERNAL: {
String name = type.resolve().toInternal().getName();
if ("list".equals(name)) {
return externList(type);
} else if ("action".equals(name)) {
return externAction(type);
} else if (INTERNAL.contains(name)) {
return internToExtern.get(name);
} else {
return externUser(type);
}
}
case UNIT:
return type.hasAttribute(Constants.ATT_NODE) ?
internToExtern.get("node") : internToExtern.get("unit");
case VOID:
return internToExtern.get("void");
case WILDCARD:
return internToExtern.get(type.resolve().toWildcard().getName());
case ERROR:
throw new AssertionError("Error type");
default:
return externUser(type);
}
}
/**
* Convert the specified list type to a string.
*
* @param type The list type.
* @return The type as a string.
*/
protected abstract String externList(Type type);
/**
* Convert the specified action type to a string.
*
* @param type The action type.
* @return The type as a string.
*/
protected abstract String externAction(Type type);
/**
* Convert the specified user-defined type to a string.
*
* @param type The user-defined type.
* @return The type as a string.
*/
protected abstract String externUser(Type type);
// ==========================================================================
/**
* Determine whether instances of the specified type have a source
* location. This method defers to {@link #hasLocationUser(Type)}
* for user-defined types.
*
* @param type The type.
* @return The inexact answer.
*/
public FuzzyBoolean hasLocation(Type type) {
switch (type.tag()) {
case VARIANT:
case TUPLE:
if (type.hasAttribute(Constants.ATT_NODE)) {
return FuzzyBoolean.TRUE;
} else {
return hasLocationUser(type);
}
case INTERNAL:
if (type.hasAttribute(Constants.ATT_NODE)) {
return FuzzyBoolean.TRUE;
} else {
String name = type.resolve().toInternal().getName();
if ("any".equals(name)) {
return FuzzyBoolean.MAYBE;
} else if (INTERNAL.contains(name)) {
return FuzzyBoolean.FALSE;
} else {
return hasLocationUser(type);
}
}
case UNIT:
return type.hasAttribute(Constants.ATT_NODE) ?
FuzzyBoolean.TRUE : FuzzyBoolean.FALSE;
case VOID:
return FuzzyBoolean.FALSE;
case NAMED_PARAMETER:
case INTERNAL_PARAMETER:
case WILDCARD:
return FuzzyBoolean.MAYBE;
case ERROR:
throw new AssertionError("Error type");
default:
return hasLocationUser(type);
}
}
/**
* Determine whether instances of the specified user-defined type
* have a source location.
*
* @param type The type.
* @return The inexact answer.
*/
protected abstract FuzzyBoolean hasLocationUser(Type type);
// ==========================================================================
/**
* Determine whether the specified type is optional.
*
* @param type The type.
* @return <code>true</code> if the specified type is optional.
*/
public static boolean isOptional(Type type) {
return type.hasAttribute(Constants.ATT_OPTIONAL);
}
/**
* Determine whether the specified type is variable.
*
* @param type The type.
* @return <code>true</code> if the specified type is variable.
*/
public static boolean isVariable(Type type) {
return type.hasAttribute(Constants.ATT_VARIABLE);
}
// ==========================================================================
/**
* Determine whether the specified type is the void type.
*
* @param type The type.
* @return <code>true</code> if the type is the void type.
*/
public static boolean isVoid(Type type) {
return type.resolve().isVoid();
}
/**
* Determine whether the specified type is the any type.
*
* @param type The type.
* @return <code>true</code> if the type is the any type.
*/
public static boolean isAny(Type type) {
Type r = type.resolve();
return r.isInternal() && "any".equals(r.toInternal().getName());
}
/**
* Determine whether the specified type is a character.
*
* @param type The type.
* @return <code>true</code> if the type is a character.
*/
public static boolean isChar(Type type) {
Type r = type.resolve();
return r.isInternal() && "char".equals(r.toInternal().getName());
}
/**
* Determine whether the specified type is a string.
*
* @param type The type.
* @return <code>true</code> if the type is a string.
*/
public static boolean isString(Type type) {
Type r = type.resolve();
return r.isInternal() && "string".equals(r.toInternal().getName());
}
/**
* Determine whether the specified type is a token.
*
* @param type The type.
* @return <code>true</code> if the type is a token.
*/
public static boolean isToken(Type type) {
Type r = type.resolve();
return r.isInternal() && "token".equals(r.toInternal().getName());
}
/**
* Determine whether the specified type is a node.
*
* @param type The type.
* @return <code>true</code> if the type is a node.
*/
public static boolean isNode(Type type) {
return type.hasAttribute(Constants.ATT_NODE);
}
/**
* Determine whether the specified type is a dynamically typed node.
*
* @param type The type.
* @return <code>true</code> if the type is a dynamically typed
* node.
*/
public static boolean isDynamicNode(Type type) {
Type r = type.resolve();
return r.isInternal() && "node".equals(r.toInternal().getName());
}
/**
* Determine whether the specified type is a null node.
*
* @param type The type.
* @return <code>true</code> if the type is a null node.
*/
public static boolean isNullNode(Type type) {
return type.hasAttribute(Constants.ATT_NODE) && type.resolve().isUnit();
}
/**
* Determine whether the specified type is a statically typed node.
*
* @param type The type.
* @return <code>true</code> if the type is a statically typed node.
*/
public static boolean isStaticNode(Type type) {
return type.hasAttribute(Constants.ATT_NODE) && type.resolve().isVariant();
}
/**
* Determine whether the specified type is a generic node.
*
* @param type The type.
* @return <code>true</code> if the type is a generic node.
*/
public static boolean isGenericNode(Type type) {
return (type.hasAttribute(Constants.ATT_GENERIC) &&
type.hasAttribute(Constants.ATT_NODE));
}
/**
* Determine whether the specified type is a formatting node.
*
* @param type The type.
* @return <code>true</code> if the type is a formatting node.
*/
public static boolean isFormatting(Type type) {
Type r = type.resolve();
return r.isInternal() && "formatting".equals(r.toInternal().getName());
}
/**
* Determine whether the specified type is a list.
*
* @param type The type.
* @return <code>true</code> if the type is a list.
*/
public static boolean isList(Type type) {
Type r = type.resolve();
return r.isInternal() && "list".equals(r.toInternal().getName());
}
/**
* Determine whether the specified type is an action.
*
* @param type The type.
* @return <code>true</code> if the type is an action.
*/
public static boolean isAction(Type type) {
Type r = type.resolve();
return r.isInternal() && "action".equals(r.toInternal().getName());
}
/**
* Get the specified instantiated type's only argument.
*
* @param type The instantiated type.
* @return The argument type.
*/
public static Type getArgument(Type type) {
assert type.hasInstantiated();
assert 1 == type.toInstantiated().getArguments().size();
return type.toInstantiated().getArguments().get(0);
}
/**
* Determine whether the specified type is user-defined.
*
* @param type The type.
* @return <code>true</code> if the type is a user-defined type.
*/
public static boolean isUser(Type type) {
switch (type.tag()) {
case VARIANT:
case TUPLE:
return ! type.hasAttribute(Constants.ATT_NODE);
case INTERNAL:
return ! INTERNAL.contains(type.resolve().toInternal().getName());
case UNIT:
case VOID:
case ERROR:
case NAMED_PARAMETER:
case INTERNAL_PARAMETER:
case WILDCARD:
return false;
default:
return true;
}
}
// ==========================================================================
/**
* Mark the specified type as optional.
*
* @param type The type.
* @return The optional type.
*/
public static Type markOptional(Type type) {
return type.annotate().attribute(Constants.ATT_OPTIONAL);
}
/**
* Mark the specified type as variable.
*
* @param type The type.
* @return The variable type.
*/
public static Type markVariable(Type type) {
return type.annotate().attribute(Constants.ATT_VARIABLE);
}
// ==========================================================================
/**
* Convert the specified production's name into a variant name.
* This method converts the name from <em>Rats!</em>' camel case
* naming convention into ML's lower case with underscores naming
* convention. It preserves any qualifier.
*
* @param name The production's name.
* @return The corresponding variant name.
*/
public String toVariantName(String name) {
if (Utilities.isQualified(name)) {
final String qualifier = Utilities.getQualifier(name);
final String unqual = Utilities.getName(name);
return Utilities.qualify(qualifier, Utilities.split(unqual, '_'));
} else {
return Utilities.split(name, '_');
}
}
/**
* Determine whether a variant type with the specified name has been
* created before. This method first converts the name from
* <em>Rats!</em>' camel case naming convention into ML's lower case
* with underscores naming convention. It then checks whether a
* variant type with that name has been returned by {@link
* #toVariant(String,boolean)} before.
*
* @see #toVariantName(String)
*
* @param name The name in camel case.
* @return <code>true</code> if a variant type with the name exists.
*/
public boolean hasVariant(String name) {
return variants.containsKey(toVariantName(name));
}
/**
* Get the variant type with the specified name. This method first
* converts the name from <em>Rats!</em>' camel case naming
* convention into ML's lower case with underscores naming
* convention. Then, if this method has not been invoked on the
* specified name before, it returns a new variant type with an
* empty list of tuples. Otherwise, it simply returns the
* previously created variant type. The returned variant type has
* the {@link Constants#ATT_NODE} attribute.
*
* @see #toVariantName(String)
*
* @param name The name in camel case.
* @param poly The flag for whether the variant is polymorphic.
* @return The corresponding variant type.
*/
public VariantT toVariant(String name, boolean poly) {
final String vname = toVariantName(name);
final VariantT variant;
if (variants.containsKey(vname)) {
variant = variants.get(vname);
assert poly == variant.isPolymorphic();
} else {
variant = new VariantT(vname, poly, new ArrayList<TupleT>());
variant.addAttribute(Constants.ATT_NODE);
variants.put(vname, variant);
variantNodes.put(vname, new HashSet<String>());
originalNames.put(vname, name);
}
return variant;
}
/**
* Get the original name for the specified variant.
*
* <p />The specified variant must have been created with {@link
* #toVariant(String,boolean)}.
*
* @param variant The variant.
* @return The original name in <em>Rats!</em>' camel case.
*/
public String toOriginal(VariantT variant) {
final String vname = variant.getName();
// Make sure the variant is registered.
assert null != vname;
assert variants.containsKey(vname);
assert variant == variants.get(vname);
return originalNames.get(vname);
}
// ==========================================================================
/**
* Determine whether a tuple type with the specified name has been
* created before.
*
* @param name The name.
* @return <code>true</code> if a tuple type with the name exists.
*/
public boolean hasTuple(String name) {
return tuples.containsKey(name);
}
/**
* Determine whether the tuple type with the specified name has been
* created before and is monomorphic.
*
* @param name The name.
* @return <code>true</code> if a tuple type with the name exists
* and is monomorphic.
*/
public boolean isMonomorphic(String name) {
return (tuples.containsKey(name) &&
(0 < tupleVariants.get(name).size()) &&
(! tupleVariants.get(name).get(0).isPolymorphic()));
}
/**
* Get the tuple type with the specified name. If this method has
* not been invoked on the specified name before, it returns a new
* tuple type, which is incomplete. Otherwise, it simply returns
* the previoulsy created tuple type.
*
* @param name The name.
* @return The corresponding tuple type.
*/
public TupleT toTuple(String name) {
final TupleT tuple;
if (tuples.containsKey(name)) {
tuple = tuples.get(name);
} else {
tuple = new TupleT(name);
tuples.put(name, tuple);
tupleVariants.put(name, new ArrayList<VariantT>());
}
return tuple;
}
/**
* Add the specified tuple type to the specified variant type. If
* the tuple is not a member of the specified variant, this method
* adds it, while also updating its internal state.
*
* <p />The specified tuple must have been created with {@link
* #toTuple(String)} or {@link #toTuple(VariantT)}. The specified
* variant must have been created with {@link
* #toVariant(String,boolean)}.
*
* @param tuple The tuple.
* @param variant The variant.
*/
public void add(TupleT tuple, VariantT variant) {
final String tname = tuple.getName();
final String vname = variant.getName();
// Make sure the tuple is registered.
assert tuples.containsKey(tname);
assert tuple == tuples.get(tname);
assert tupleVariants.containsKey(tname);
// Make sure the variant is registered.
if (null == vname) {
assert variant.isPolymorphic();
} else {
assert variants.containsKey(vname);
assert variant == variants.get(vname);
}
// Actually add the tuple.
final Type t = variant.lookup(tname);
if (t.isError()) {
assert variant.isPolymorphic() ||
(null != vname &&
! variantNodes.get(vname).contains(tuple.getSimpleName()));
if (! variant.isPolymorphic()) {
variantNodes.get(vname).add(tuple.getSimpleName());
}
if (null != vname) {
tupleVariants.get(tname).add(variant);
}
variant.getTuples().add(tuple);
} else {
assert t == tuple;
}
}
/**
* Get the specified tuple's variants.
*
* <p />The specified tuple must have been created with {@link
* #toTuple(String)} or {@link #toTuple(VariantT)}. It must have
* been added to any variants with {@link #add(TupleT,VariantT)}.
*
* @param tuple The tuple.
* @return The tuple's variants.
*/
public List<VariantT> toVariants(TupleT tuple) {
final String tname = tuple.getName();
// Make sure the tuple is registered.
assert tuples.containsKey(tname);
assert tuple == tuples.get(tname);
List<VariantT> variants = tupleVariants.get(tname);
assert null != variants;
return variants;
}
// ==========================================================================
/**
* Get the polymorphic tuple for the specified variant.
*
* <p />The specified variant must have been created with {@link
* #toVariant(String,boolean)}.
*
* @param variant The variant.
* @return The corresponding polymorphic tuple.
*/
public TupleT toTuple(VariantT variant) {
final String vname = variant.getName();
// Make sure the variant is registered.
assert ! variant.isPolymorphic();
assert variants.containsKey(vname);
assert variant == variants.get(vname);
final String qualifier = variant.getQualifier();
String tname = "Some" + Utilities.unqualify(originalNames.get(vname));
if (isMonomorphic(Utilities.qualify(qualifier, tname))) {
tname = "Just" + tname;
while (isMonomorphic(Utilities.qualify(qualifier, tname))) {
tname = tname + "1";
}
}
TupleT tuple = toTuple(Utilities.qualify(qualifier, tname));
if (null == tuple.getTypes()) {
tuple.setTypes(new ArrayList<Type>(1));
tuple.getTypes().add(variant);
}
return tuple;
}
// ==========================================================================
/**
* Determine whether the specified variants overlap. Two variants
* overlap if they include tuples representing the same generic
* node.
*
* <p />The specified variants must have been created with {@link
* #toVariant(String,boolean)}.
*
* @param v1 The first variant.
* @param v2 The second variant.
* @return <code>true</code> if the two variants overlap.
*/
public boolean overlap(VariantT v1, VariantT v2) {
if (v1.isPolymorphic()) {
for (TupleT tuple : v1.getTuples()) {
if (overlap(tuple.getTypes().get(0).toVariant(), v2)) return true;
}
} else if (v2.isPolymorphic()) {
for (TupleT tuple : v2.getTuples()) {
if (overlap(v1, tuple.getTypes().get(0).toVariant())) return true;
}
} else {
final String vname1 = v1.getName();
final String vname2 = v2.getName();
assert variants.containsKey(vname1);
assert v1 == variants.get(vname1);
assert variants.containsKey(vname2);
assert v2 == variants.get(vname2);
final Set<String> nodes1 = variantNodes.get(v1.getName());
final Set<String> nodes2 = variantNodes.get(v2.getName());
for (String s : nodes1) {
if (nodes2.contains(s)) return true;
}
}
return false;
}
// ==========================================================================
/** The metadata for a grammar's statically typed nodes. */
public static class MetaData {
/** Create a new metadata record. */
public MetaData() {
reachable = new HashSet<String>();
modularize = false;
}
/** The names of reachable variants. */
public Set<String> reachable;
/** The flag for requiring separate modules. */
public boolean modularize;
}
/**
* Determine the metadata for the specified variant.
*
* @param variant The variant.
* @return The corresponding metadata.
*/
public MetaData getMetaData(VariantT variant) {
MetaData meta = new MetaData();
fillIn(variant, meta, new HashSet<String>());
return meta;
}
/**
* Fill in the specified metadata for the specified type.
*
* @param type The type.
* @param meta The metadata.
* @param names The simple variant names processed so far.
*/
private void fillIn(Type type, MetaData meta, Set<String> names) {
switch (type.tag()) {
case VARIANT: {
final VariantT variant = type.resolve().toVariant();
final String qname = variant.getName();
final String sname = variant.getSimpleName();
final List<TupleT> tuples = variant.getTuples();
if (null == qname) {
for (TupleT t : tuples) fillIn(t, meta, names);
} else if (! meta.reachable.contains(qname)) {
meta.reachable.add(qname);
if (names.contains(sname)) {
meta.modularize = true;
} else {
names.add(sname);
}
for (TupleT t : tuples) fillIn(t, meta, names);
}
} break;
case TUPLE: {
final List<Type> types = type.resolve().toTuple().getTypes();
if (null != types) for (Type t : types) fillIn(t, meta, names);
} break;
case INTERNAL: {
final String name = type.resolve().toInternal().getName();
if ("list".equals(name) || "action".equals(name)) {
if (type.hasInstantiated()) {
fillIn(getArgument(type), meta, names);
}
}
} break;
default:
// Nothing to do.
}
}
// ==========================================================================
/**
* Create a new list type.
*
* @param element The element type.
* @return The corresponding list type.
*/
public static Type listOf(Type element) {
return new InstantiatedT(element, LIST);
}
/**
* Create a new action type.
*
* @param element The element type.
* @return The corresponding action type.
*/
public static Type actionOf(Type element) {
return new InstantiatedT(element, ACTION);
}
// ==========================================================================
/**
* Unify the specified types. If the strict flag is set, statically
* and dynamically typed nodes do not unify. If the flag is not
* set, they do unify and otherwise incompatible types unify to the
* any type. This method defers to {@link
* #unify(VariantT,VariantT)} for statically typed nodes and to
* {@link #unifyUser(Type,Type,boolean)} for user-defined types.
*
* @param t1 The first type.
* @param t2 The second type.
* @param strict The flag for strict unification.
* @return The unified type or {@link ErrorT#TYPE} if the two types
* do not unify.
*/
public Type unify(Type t1, Type t2, boolean strict) {
// Get the trivial cases out of the way.
if (t1 == t2) return t1;
if (t1.hasError() || t2.hasError()) return ErrorT.TYPE;
if (t1.resolve().isParameter()) return t2;
if (t2.resolve().isParameter()) return t1;
// Now, do the real work.
Type result = unify1(t1, t2, strict);
// Adjust the result for non-strict unification.
if (result.isError() && ! strict) result = ANY;
// Preserve any variable or optional attribute, in this precedence
// order.
if (! result.isError()) {
if (isVariable(t1) || isVariable(t2)) {
result = markVariable(result);
} else if (isOptional(t1) || isOptional(t2)) {
result = markOptional(result);
}
}
// Done.
return result;
}
/**
* Actually unify the specified types.
*
* @param t1 The first type.
* @param t2 The second type.
* @param strict The flag for strict unification.
* @return The unified type or {@link ErrorT#TYPE} if the two types
* do not unify.
*/
private Type unify1(Type t1, Type t2, boolean strict) {
Type r1 = t1.resolve(), r2 = t2.resolve();
if (t1.hasInstantiated() && t2.hasInstantiated()) {
InstantiatedT i1 = t1.toInstantiated(), i2 = t2.toInstantiated();
List<Type> a1 = i1.getArguments(), a2 = i2.getArguments();
if (a1.size() != a2.size()) return ErrorT.TYPE;
Type base = unify(i1.getType(), i2.getType(), true);
if (base.isError()) return ErrorT.TYPE;
List<Type> args = new ArrayList<Type>(a1.size());
for (int i=0; i<a1.size(); i++) {
Type t = unify(a1.get(i), a2.get(i), true);
if (t.isError()) return ErrorT.TYPE;
args.add(t);
}
return new InstantiatedT(args, base);
} else if (t1.hasInstantiated() || t2.hasInstantiated()) {
return ErrorT.TYPE;
} else if (isUser(t1) && isUser(t2)) {
return unifyUser(t1, t2, strict);
} else if (isNode(t1) && isNode(t2)) {
if (isNullNode(t1)) {
return r2;
} else if (isNullNode(t2)) {
return r1;
} else if (isToken(t1) && isToken(t2)) {
return TOKEN;
} else if ((isToken(t1) || isDynamicNode(t1) || isFormatting(t1)) &&
(isToken(t2) || isDynamicNode(t2) || isFormatting(t2))) {
return NODE;
} else if (r1.isVariant() && r2.isVariant()) {
return unify(r1.toVariant(), r2.toVariant());
} else if (strict) {
return ErrorT.TYPE;
} else {
return NODE;
}
} else if (r1.isVoid() && r2.isVoid()) {
return VoidT.TYPE;
} else if (r1.isUnit() && r2.isUnit()) {
return UnitT.TYPE;
} else if (isChar(t1) && isChar(t2)) {
return CHAR;
} else if (isString(t1) && isString(t2)) {
return STRING;
} else if (isList(t1) && isList(t2)) {
return LIST;
} else if (isAction(t1) && isAction(t2)) {
return ACTION;
} else {
return ErrorT.TYPE;
}
}
/**
* Unify the specified statically typed nodes. Statically typed
* nodes unify through polymorphic variant types, unless they
* reference the same underlying AST node.
*
* @param v1 The first variant.
* @param v2 The second variant.
* @return The unified variant.
*/
protected Type unify(VariantT v1, VariantT v2) {
// Get the trivial case out of the way.
if ((null != v1.getName()) && v1.getName().equals(v2.getName())) return v1;
// Unify the two variants.
List<TupleT> tuples = new ArrayList<TupleT>();
VariantT result = new VariantT(null, true, tuples);
result.addAttribute(Constants.ATT_NODE);
if (v1.isPolymorphic()) {
tuples.addAll(v1.getTuples());
if (v2.isPolymorphic()) {
for (TupleT tuple : v2.getTuples()) {
if (! tuples.contains(tuple)) {
final VariantT v3 = tuple.getTypes().get(0).toVariant();
if (overlap(v1, v3)) return ErrorT.TYPE;
tuples.add(tuple);
}
}
} else {
TupleT tuple = toTuple(v2);
if (! tuples.contains(tuple)) {
if (overlap(v1, v2)) return ErrorT.TYPE;
tuples.add(tuple);
}
}
} else if (v2.isPolymorphic()) {
tuples.addAll(v2.getTuples());
TupleT tuple = toTuple(v1);
if (! tuples.contains(tuple)) {
if (overlap(v1, v2)) return ErrorT.TYPE;
tuples.add(tuple);
}
} else {
if (overlap(v1, v2)) return ErrorT.TYPE;
tuples.add(toTuple(v1));
tuples.add(toTuple(v2));
}
return result;
}
/**
* Unify the specified user-defined types. Note that this method
* need not handle instantiated types but must preserve parameterize
* types.
*
* @param t1 The first user-defined type.
* @param t2 The second user-defined type.
* @param strict The flag for strict unification.
* @return The unified type or {@link ErrorT#TYPE} if the two types
* do not unify.
*/
protected abstract Type unifyUser(Type t1, Type t2, boolean strict);
// ==========================================================================
/**
* Flatten the specified tuple type. If the specified tuple has a
* list element, this method combines the first such list type with
* all succeeding element types into a single list type, modifying
* the specified tuple type.
*
* @param tuple The tuple type.
* @param strict The flag for strict unification.
* @return The updated tuple type or {@link ErrorT#TYPE} if types
* cannot be unified.
*/
public Type flatten(TupleT tuple, boolean strict) {
final List<Type> types = tuple.getTypes();
final int size = types.size();
int index = -1; // The index of the first list.
Type element = null; // The element type of the list.
boolean optional = false; // The flag for optional elements.
for (int i=0; i<size; i++) {
Type t = types.get(i);
if (-1 == index) {
if (isList(t)) {
index = i;
t = getArgument(t);
if (isOptional(t)) optional = true;
t = t.deannotate();
element = t;
}
} else {
if (isList(t)) t = getArgument(t);
if (isOptional(t)) optional = true;
t = t.deannotate();
element = unify(element, t, strict);
if (element.isError()) return ErrorT.TYPE;
}
}
if (-1 != index) {
// Create the flattened list type.
if (optional) element = markOptional(element);
Type list = listOf(element);
if (isVariable(types.get(index))) list = markVariable(list);
// Update the tuple type.
types.subList(index, size).clear();
types.add(list);
}
return tuple;
}
// ==========================================================================
/**
* Combine the specified tuple types into a consistent type. The
* types must be tuples with the same name.
*
* @param tuple1 The first tuple.
* @param tuple2 The second tuple.
* @param flatten The flag for flattening lists.
* @param strict The flag for strict unification.
* @return The combined tuple type or {@link ErrorT#TYPE} if the two
* tuple types cannot be combined into a consistent type.
*/
public Type combine(TupleT tuple1, TupleT tuple2,
boolean flatten, boolean strict) {
assert tuple1.getName().equals(tuple2.getName());
// If both tuple types are equal, we are done.
if ((tuple1 == tuple2) || tuple1.equals(tuple2)) return tuple1;
// Set up.
final List<Type> types1 = tuple1.getTypes(), types2 = tuple2.getTypes();
final int size1 = types1.size(), size2 = types2.size();
// If lists are flattened, then find the first list across both
// tuple types and determine the combined list element type.
int listIdx = Integer.MAX_VALUE;
Type elemT = Wildcard.TYPE;
boolean variable = false; // Flag for list being variable.
boolean optional = false; // Flag for element being optional.
if (flatten) {
// Find the first list type across both tuple types.
for (int i=0; i<size1; i++) {
Type t = types1.get(i);
if (isList(t)) {
listIdx = i;
variable = isVariable(t);
break;
}
}
for (int i=0; i<size2; i++) {
Type t = types2.get(i);
if (isList(t)) {
if (i < listIdx) {
listIdx = i;
variable = isVariable(t);
}
break;
}
}
// Determine the combined list element type.
if (Integer.MAX_VALUE != listIdx) {
for (int i=listIdx; i<size1; i++) {
Type t = types1.get(i);
if (isList(t)) t = getArgument(t);
if (isOptional(t)) optional = true;
t = t.deannotate();
elemT = unify(elemT, t, strict);
if (elemT.isError()) return ErrorT.TYPE;
}
for (int i=listIdx; i<size2; i++) {
Type t = types2.get(i);
if (isList(t)) t = getArgument(t);
if (isOptional(t)) optional = true;
t = t.deannotate();
elemT = unify(elemT, t, strict);
if (elemT.isError()) return ErrorT.TYPE;
}
}
}
// Determine the combined tuple's element types.
final List<Type> types3 = new ArrayList<Type>(Math.max(size1, size2));
final int size3 = Math.min(Math.max(size1, size2), listIdx);
for (int i=0; i<size3; i++) {
final Type t1 = (i < size1) ? types1.get(i) : null;
final Type t2 = (i < size2) ? types2.get(i) : null;
Type t3;
if (null == t1) {
t3 = markVariable(t2.deannotate());
} else if (null == t2) {
t3 = markVariable(t1.deannotate());
} else {
t3 = unify(t1.deannotate(), t2.deannotate(), strict);
if (t3.isError()) return ErrorT.TYPE;
if (isVariable(t1) || isVariable(t2)) {
t3 = markVariable(t3);
} else if (isOptional(t1) || isOptional(t2)) {
t3 = markOptional(t3);
} else if ((t1.resolve().isWildcard() || t2.resolve().isWildcard()) &&
! t3.resolve().isWildcard()) {
t3 = markOptional(t3);
}
}
types3.add(t3);
}
if (Integer.MAX_VALUE != listIdx) {
if (optional) elemT = markOptional(elemT);
Type list = listOf(elemT);
if (variable || (listIdx > size1) || (listIdx > size2)) {
// The new trailing list is variable if (1) the original list
// is variable or (2) the original list is at a position
// beyond one tuple's elements plus one (which accounts for
// the empty list not having any children).
list = markVariable(list);
}
types3.add(list);
}
// Done.
return new TupleT(tuple1.getName(), types3);
}
// ==========================================================================
/**
* Ensure that the specified type is concrete. This method replaces
* occurrences of the wildcard type with the specified replacement;
* though it does not process variant types to avoid infinite
* recursions. It assumes that list and action types are
* instantiated.
*
* @see #concretizeTuples(VariantT,Type)
*
* @param type The type.
* @param concrete The concrete replacement for wildcards.
* @return The concrete type.
*/
public Type concretize(Type type, Type concrete) {
Type resolved = type.resolve(), result = null;
if (resolved.isWildcard()) {
result = concrete;
} else if (resolved.isTuple()) {
TupleT tuple = resolved.toTuple();
List<Type> elements = tuple.getTypes();
boolean isCopy = false;
for (int i=0; i<elements.size(); i++) {
Type element = concretize(elements.get(i), concrete);
if (elements.get(i) != element) {
if (! isCopy) {
elements = new ArrayList<Type>(elements);
isCopy = true;
}
elements.set(i, element);
}
}
if (isCopy) result = new TupleT(tuple.getName(), elements);
} else if (isList(type)) {
Type el = concretize(getArgument(type), concrete);
if (el != getArgument(type)) result = listOf(el);
} else if (isAction(type)) {
Type el = concretize(getArgument(type), concrete);
if (el != getArgument(type)) result = actionOf(el);
}
if (null == result) {
return type;
} else {
if (isVariable(type)) {
result = markVariable(result);
} else if (isOptional(type)) {
result = markOptional(result);
}
return result;
}
}
/**
* Concretize the specified variant type's tuples. This method
* updates any tuples in place.
*
* @see #concretize(Type,Type)
*
* @param variant The variant.
* @param concrete The concrete replacement for wildcards.
*/
public void concretizeTuples(VariantT variant, Type concrete) {
List<TupleT> tuples = variant.getTuples();
if (null != tuples) {
for (int i=0; i<tuples.size(); i++) {
tuples.set(i, concretize(tuples.get(i), concrete).toTuple());
}
}
}
// ==========================================================================
/**
* Print the specified type.
*
* @param printer The printer.
* @param type The type.
* @param refIsDecl The flag for whether a variant type reference
* also is a declaration.
* @param qualified The flag for printing qualified names.
* @param module The current module name, which may be <code>null</code>.
*/
public void print(Type type, Printer printer, boolean refIsDecl,
boolean qualified, String module) {
switch (type.tag()) {
case VARIANT: {
final VariantT variant = type.resolve().toVariant();
final boolean poly = variant.isPolymorphic();
final List<TupleT> tuples = variant.getTuples();
final String name;
if (! qualified ||
(null != module && module.equals(variant.getQualifier()))) {
name = variant.getSimpleName();
} else {
name = variant.getName();
}
if (null == name) {
printer.pln('[').incr().incr();
for (TupleT tuple : tuples) {
printer.indent().p("| `");
print(tuple, true, printer, qualified, module);
printer.pln();
}
printer.decr().decr().indentMore().p(']');
} else if (! refIsDecl) {
printer.p(name);
} else if (poly) {
printer.indent().p("mltype ").p(name).pln(" = [").incr();
for (TupleT tuple : tuples) {
printer.indent().p("| `");
print(tuple, true, printer, qualified, module);
printer.pln();
}
printer.decr().indent().pln("];");
} else if (1 == tuples.size()) {
printer.indent().p("mltype ").p(name).p(" = ");
print(tuples.get(0), false, printer, qualified, module);
printer.pln(" ;");
} else {
printer.indent().p("mltype ").p(name).pln(" =").incr();
for (Iterator<TupleT> iter = tuples.iterator(); iter.hasNext(); ) {
printer.indent().p("| ");
print(iter.next(), false, printer, qualified, module);
if (iter.hasNext()) printer.pln();
}
printer.pln(" ;").decr();
}
} break;
case TUPLE:
print(type.resolve().toTuple(), false, printer, qualified, module);
break;
case INTERNAL: {
final String name = type.resolve().toInternal().getName();
if ("list".equals(name) || "action".equals(name)) {
if (type.hasInstantiated()) {
print(type.toInstantiated().getArguments().get(0),
printer, false, qualified, module);
} else {
print(type.toParameterized().getParameters().get(0),
printer, false, qualified, module);
}
printer.p(' ');
}
printer.p(name);
} break;
case UNIT:
case VOID:
printer.p("bottom");
break;
case NAMED_PARAMETER:
case INTERNAL_PARAMETER:
case WILDCARD:
printer.p("'").p(type.resolve().toString());
break;
case ERROR:
printer.p("<error>");
break;
default:
throw new AssertionError("Invalid type " + type);
}
if (! refIsDecl) {
if (type.hasAttribute(Constants.ATT_VARIABLE)) {
printer.p(" var");
} else if (type.hasAttribute(Constants.ATT_OPTIONAL)) {
printer.p(" opt");
}
}
}
/**
* Print the specified tuple.
*
* @param tuple The tuple.
* @param poly The flag for polymorphic tuples.
* @param printer The printer.
* @param qualified The flag for printing qualified names.
* @param module The current module name, which may be <code>null</code>.
*/
private void print(TupleT tuple, boolean poly, Printer printer,
boolean qualified, String module) {
String name = tuple.getName();
if (! qualified || ! poly ||
(null != module && module.equals(Utilities.getQualifier(name)))) {
name = tuple.getSimpleName();
}
printer.p(name);
final List<Type> types = tuple.getTypes();
if ((null != types) && ! types.isEmpty()) {
printer.p(" of ");
for (Iterator<Type> iter = types.iterator(); iter.hasNext(); ) {
Type t = iter.next();
if (t.resolve().isVariant() &&
(null == t.resolve().toVariant().getName())) {
print(t, printer, false, qualified, module);
if (iter.hasNext()) printer.p(" * ");
} else {
printer.buffer();
print(t, printer, false, qualified, module);
if (iter.hasNext()) printer.p(" * ");
printer.fitMore();
}
}
}
}
}