/*
 * xtc - The eXTensible Compiler
 * Copyright (C) 2007-2009 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.math.BigInteger;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import xtc.Constants;
import xtc.Limits;

import xtc.tree.Attribute;

import xtc.util.Utilities;

/**
 * Common type operations for the C language.
 *
 * @author Robert Grimm
 * @version $Revision: 1.44 $
 */
public class C {

  /** Create a new instance. */
  public C() { /* Nothing to do. */ }

  // =========================================================================

  /** The flag for GCC's legacy structure layout. */
  public static final boolean GCC_LEGACY_LAYOUT;

  static {
    GCC_LEGACY_LAYOUT =
      Limits.COMPILER_NAME.equals("gcc") &&
      (Limits.COMPILER_VERSION_MAJOR < 4 ||
       (Limits.COMPILER_VERSION_MAJOR == 4 &&
        Limits.COMPILER_VERSION_MINOR == 0));
  }

  // =========================================================================

  /**
   * The canonical implicit int type.  In K&R C, a missing type
   * specifier is treated as an int.  We preserve knowledge about this
   * lack of type specifier through this type, which has a {@link
   * Constants#ATT_IMPLICIT} attribute.
   */
  public static final IntegerT IMPLICIT = new IntegerT(NumberT.Kind.INT);

  static {
    IMPLICIT.addAttribute(Constants.ATT_IMPLICIT);
    IMPLICIT.seal();
  }

  /** The integer kind of the sizeof type. */
  protected static final NumberT.Kind KIND_SIZEOF =
    IntegerT.fromRank(Limits.SIZEOF_RANK, false);

  /** The canonical sizeof type. */
  public static final IntegerT SIZEOF = new IntegerT(KIND_SIZEOF);

  static {
    SIZEOF.seal();
  }

  /** The integer kind of the pointer difference type. */
  protected static final NumberT.Kind KIND_PTR_DIFF =
    IntegerT.fromRank(Limits.PTRDIFF_RANK, true);

  /** The canonical pointer difference type. */
  public static final IntegerT PTR_DIFF = new IntegerT(KIND_PTR_DIFF);

  static {
    PTR_DIFF.seal();
  }

  /** The number kind of the wchar_t type. */
  protected static final NumberT.Kind KIND_WCHAR =
    IntegerT.fromRank(Limits.WCHAR_RANK, Limits.IS_WCHAR_SIGNED);

  /** The canonical wide char type. */
  public static final IntegerT WCHAR = new IntegerT(KIND_WCHAR);

  static {
    WCHAR.seal();
  }

  // =========================================================================

  /**
   * Determine whether the specified type is a char.
   *
   * @param type The type.
   * @return <code>true</code> if the type is a char.
   */
  public boolean isChar(Type type) {
    if (type.hasEnum()) return false;

    type = type.resolve();

    if (type.isInteger()) {
      switch (type.toInteger().getKind()) {
      case CHAR:
      case S_CHAR:
      case U_CHAR:
        return true;
      default:
        return false;
      }
    } else {
      return false;
    }
  }

  /**
   * Determine whether the specified type is a wide char.
   *
   * @param type The type.
   * @return <code>true</code> if the type is a wide char.
   */
  public boolean isWideChar(Type type) {
    if (type.hasEnum()) return false;

    type = type.resolve();

    if (type.isInteger()) {
      return NumberT.equal(KIND_WCHAR, type.toInteger().getKind());
    } else {
      return false;
    }
  }

  /**
   * Determine whether the specified type is a string.
   *
   * @param type The type.
   * @return <code>true</code> if the type is a string.
   */
  public boolean isString(Type type) {
    type = type.resolve();

    return type.isArray() && isChar(type.toArray().getType());
  }

  /**
   * Determine whether the specified type is a wide string.
   *
   * @param type The type.
   * @return <code>true</code> if the type is a wide string.
   */
  public boolean isWideString(Type type) {
    type = type.resolve();

    return type.isArray() && isWideChar(type.toArray().getType());
  }

  /**
   * Determine whether the specified type is a string literal.
   *
   * @param type The type.
   * @return <code>true</code> if the type is a string literal.
   */
  public boolean isStringLiteral(Type type) {
    return (isString(type) || isWideString(type)) && type.hasConstant();
  }

  // =========================================================================

  /**
   * Determine whether the specified type is integral.
   *
   * @param type The type.
   * @return <code>true</code> if the specified type is integral.
   */
  public boolean isIntegral(Type type) {
    switch (type.tag()) {
    case BOOLEAN:
    case INTEGER:
      return true;
    default:
      return false;
    }
  }

  /**
   * Determine whether the specified type is real.
   *
   * @param type The type.
   * @return <code>true</code> if the specified type is real.
   */
  public boolean isReal(Type type) {
    switch (type.tag()) {
    case BOOLEAN:
    case INTEGER:
      return true;
    case FLOAT:
      switch (type.resolve().toFloat().getKind()) {
      case FLOAT_COMPLEX:
      case DOUBLE_COMPLEX:
      case LONG_DOUBLE_COMPLEX:
        return false;
      default:
        return true;
      }
    default:
      return false;
    }
  }

  /**
   * Determine whether the specified type is arithmetic.
   *
   * @param type The type.
   * @return <code>true</code> if the specified type is arithmetic.
   */
  public boolean isArithmetic(Type type) {
    switch (type.tag()) {
    case BOOLEAN:
    case INTEGER:
    case FLOAT:
      return true;
    default:
      return false;
    }
  }

  /**
   * Determine whether the specified type is scalar.
   *
   * @param type The type.
   * @return <code>true</code> if the specified type is scalar.
   */
  public boolean isScalar(Type type) {
    switch (type.tag()) {
    case BOOLEAN:
    case INTEGER:
    case FLOAT:
    case POINTER:
      return true;
    default:
      return false;
    }
  }

  /**
   * Determine whether the specified type is incomplete.
   *
   * <p />Per C99 6.2.5, a type is incomplete if it does not contain
   * sufficient information for determining its size.  However, per
   * C99 6.7.2.1, a struct type with more than one named member may
   * have an incomplete type as its last member, while still being
   * considered complete.
   *
   * @param type The type.
   * @return <code>true</code> if the type is incomplete.
   */
  public boolean isIncomplete(Type type) {
    // Handle alias and enum types first since they are wrapped and do
    // not have a tag.
    while (type.isWrapped()) {
      switch (type.wtag()) {
      case ALIAS:
        return null == type.toAlias().getType();
      case ENUM:
        return null == type.toEnum().getMembers();
      default:
        type = type.toWrapped().getType();
      }
    }

    // The type has been resolved.
    switch (type.tag()) {
    case VOID:
      // Void always is incomplete.
      return true;
    case ARRAY: {
      // An array is incomplete if (1) it is not variable and has no
      // lenght, (2) it has an incomplete member type, or (3) it has a
      // trailing array.
      ArrayT a = type.toArray();
      return (((! a.isVarLength()) && (! a.hasLength())) ||
              isIncomplete(a.getType()) ||
              hasTrailingArray(a.getType()));
    }
    case STRUCT: {
      // A struct is incomplete if (1) it has no members, (2) any
      // member but the last member is incomplete, (3) the last member
      // is not an array but is incomplete, or (4) the last member is
      // an array with an incomplete element type.
      List<VariableT> members = type.toStruct().getMembers();
      if (null == members) return true;
      for (Iterator<VariableT> iter = members.iterator(); iter.hasNext(); ) {
        VariableT member = iter.next();

        if (iter.hasNext() || (Type.Tag.ARRAY != member.tag())) {
          // In general, members may not be incomplete.  We allow
          // struct members with trailing incomplete arrays because
          // GCC allows them.
          if (isIncomplete(member)) return true;
        } else {
          // The last member is an array.  We allow struct element
          // types with trailing incomplete arrays because GCC allows
          // them.
          ArrayT array = member.resolve().toArray();
          if (isIncomplete(array.getType())) return true;
        }
      }
      return false;
    }
    case UNION: {
      // A union is incomplete if (1) it has no members or (2) any
      // member is incomplete.
      List<VariableT> members = type.toUnion().getMembers();
      if (null == members) return true;
      for (Type t : members) {
        if (isIncomplete(t)) return true;
      }
      return false;
    }
    default:
      return false;
    }
  }

  /**
   * Determine whether the specified type has a trailing array.  This
   * method checks whether the specified type is either a struct type
   * with an incomplete array as its last member or a union containing
   * such a member.
   *
   * @param type The type.
   * @return <code>true</code> if the type has a trailing array.
   */
  public boolean hasTrailingArray(Type type) {
    switch (type.tag()) {
    case STRUCT: {
      // A struct has a trailing array if its last member is a fixed
      // length array without a concrete length.
      List<VariableT> members = type.resolve().toStruct().getMembers();
      if (null != members) {
        int  size = members.size();
        Type last = 0 < size ? members.get(size-1) : null;
        
        if ((null != last) && (Type.Tag.ARRAY == last.tag())) {
          ArrayT a = last.resolve().toArray();
          return (! a.isVarLength()) && (! a.hasLength());
        }
      }
      return false;
    }
    case UNION: {
      // A union has a trailing array if any member has a trailing
      // array.
      List<VariableT> members = type.resolve().toUnion().getMembers();
      if (null != members) {
        for (Type t : members) {
          if (hasTrailingArray(t)) return true;
        }
      }
      return false;
    }
    default:
      return false;
    }
  }

  /**
   * Determine whether the specified type is variably modified.
   * Consistent with C99 6.7.5-3, this method checks whether the type
   * contains a variable length array type.
   *
   * @param type The type.
   * @return <code>true</code> if the type is variably modified.
   */
  public boolean isVariablyModified(Type type) {
    switch (type.tag()) {
    case POINTER:
      return isVariablyModified(type.resolve().toPointer().getType());
    case ARRAY:
      ArrayT a = type.resolve().toArray();
      return a.isVarLength() || isVariablyModified(a.getType());
    default:
      return false;
    }
  }

  /**
   * Determine whether the specified type is qualified as constant.
   * Consistent with C99 6.3.2.1, this method checks whether the type
   * has a {@link Constants#ATT_CONSTANT} attribute and, if it is an
   * array, struct, or union, whether any member or element of the
   * type has that attribute.
   *
   * @param type The type.
   * @return <code>true</code> if the specified type is constant
   *   qualified.
   */
  public boolean isConstant(Type type) {
    if (type.hasAttribute(Constants.ATT_CONSTANT)) return true;

    switch (type.tag()) {
    case ARRAY:
      type = type.resolve().toArray().getType();
      return (null == type) ? false : isConstant(type);
    case STRUCT:
    case UNION:
      for (Type member : type.toTagged().getMembers()) {
        if (isConstant(member)) return true;
      }
      return false;
    default:
      return false;
    }
  }

  /**
   * Determine whether the specified type represents a modifiable
   * lvalue.  Consistent with C99 6.3.2.1, this method checks that the
   * type represents an lvalue, is not incomplete, and is not
   * qualified as constant.  For struct and union types, this method
   * also checks that no member is qualified as constant.
   *
   * @param type The type.
   * @return <code>true</code> if the type represents a modifiable
   *   lvalue.
   */
  public boolean isModifiable(Type type) {
    if (! type.hasShape()) return false;
    if (isIncomplete(type)) return false;
    if (Type.Tag.ARRAY == type.tag()) return false;
    return ! isConstant(type);
  }

  // =========================================================================

  /**
   * Determine whether this type has a constant reference.  This
   * method takes the pointer decay of arrays and functions into
   * account and returns <code>true</code> either if the specified
   * type has a constant reference or if this type is an array or
   * function and has a constant reference.
   *
   * @param type The type.
   * @return <code>true</code> if the type has a constant reference.
   */
  public boolean hasConstRef(Type type) {
    if (type.hasConstant() && type.getConstant().isReference()) return true;

    switch (type.tag()) {
    case ARRAY:
    case FUNCTION:
      return type.hasShape() && type.getShape().isConstant();
    default:
      return false;
    }
  }

  /**
   * Get the specified type's constant reference.
   *
   * @param type The type.
   * @return The constant reference.
   * @throws IllegalArgumentException Signals that the type does not
   *   have a constant reference.
   */
  public Reference getConstRef(Type type) {
    if (type.hasConstant()) {
      Constant constant = type.getConstant();
      if (! constant.isReference()) {
        throw new IllegalArgumentException("Constant not a reference " + type);
      }
      return constant.refValue();
    }

    switch (type.tag()) {
    case ARRAY:
    case FUNCTION:
      if (type.hasShape()) {
        Reference ref = type.getShape();
        if (! ref.isConstant()) {
          throw new IllegalArgumentException("Shaped not constant " + type);
        }
        return ref;
      }
      // Fall through.
    default:
      throw new
        IllegalArgumentException("Type without constant reference " + type);
    }
  }

  // =========================================================================

  /**
   * Determine whether the specified type has any qualifiers.
   *
   * @param type The type.
   * @return <code>true</code> if the type has any qualifiers.
   */
  public boolean hasQualifiers(Type type) {
    return (type.hasAttribute(Constants.ATT_CONSTANT) ||
            type.hasAttribute(Constants.ATT_RESTRICT) ||
            type.hasAttribute(Constants.ATT_VOLATILE));
  }

  /**
   * Determine whether the specified type has at least the qualifiers
   * of the specified template.
   *
   * @param type The type.
   * @param template The template.
   * @return <code>true</code> if the type has at least the template's
   *   qualifiers.
   */
  public boolean hasQualifiers(Type type, Type template) {
    if (template.hasAttribute(Constants.ATT_CONSTANT) &&
        (! type.hasAttribute(Constants.ATT_CONSTANT))) {
      return false;
    }

    if (template.hasAttribute(Constants.ATT_RESTRICT) &&
        (! type.hasAttribute(Constants.ATT_RESTRICT))) {
      return false;
    }

    if (template.hasAttribute(Constants.ATT_VOLATILE) &&
        (! type.hasAttribute(Constants.ATT_VOLATILE))) {
      return false;
    }

    return true;
  }

  /**
   * Determine whether the specified type has the same qualifiers as
   * the specified template.
   *
   * @param type The type.
   * @param template The template.
   * @return <code>true</code> if the type has the same qualifiers
   *   as the template.
   */
  public boolean hasSameQualifiers(Type type, Type template) {
    return ((type.hasAttribute(Constants.ATT_CONSTANT) ==
             template.hasAttribute(Constants.ATT_CONSTANT)) &&
            (type.hasAttribute(Constants.ATT_RESTRICT) ==
             template.hasAttribute(Constants.ATT_RESTRICT)) &&
            (type.hasAttribute(Constants.ATT_VOLATILE) ==
             template.hasAttribute(Constants.ATT_VOLATILE)));
  }

  /**
   * Qualify the specified type with the qualifiers of the specified
   * template.  If the template has any qualifiers, this method first
   * {@link Type#annotate() annotates} the specified type first.
   *
   * @param type The type.
   * @param template The template.
   * @return The qualified type.
   */
  public Type qualify(Type type, Type template) {
    if (! hasQualifiers(template)) return type;

    type = type.annotate();
    if (template.hasAttribute(Constants.ATT_CONSTANT)) {
      type = type.attribute(Constants.ATT_CONSTANT);
    }
    if (template.hasAttribute(Constants.ATT_RESTRICT)) {
      type = type.attribute(Constants.ATT_RESTRICT);
    }
    if (template.hasAttribute(Constants.ATT_VOLATILE)) {
      type = type.attribute(Constants.ATT_VOLATILE);
    }
    return type;
  }

  // =========================================================================

  /**
   * Reattribute the specified type with the specified template's GCC
   * attributes.
   *
   * @param type The type.
   * @param template The template.
   * @return The reattributed type.
   */
  public Type reattribute(Type type, Type template) {
    boolean annotated = false;

    do {
      // If the template has any attributes, check them out.
      if (template.hasAttributes()) {
        for (Attribute att : template.attributes()) {
          if (Constants.NAME_GCC.equals(att.getName()) &&
              (! type.hasAttribute(att))) {
            if (! annotated) {
              type      = type.annotate();
              annotated = true;
            }
            type.addAttribute(att);
          }
        }
      }

      // If the template is a wrapped type, continue with the wrapped type.
      template = template.isWrapped() ? template.toWrapped().getType() : null;
    } while (null != template);

    return type;
  }

  // =========================================================================

  /**
   * Convert the specified type to an rvalue.  If the specified type
   * has a shape, this method returns the resolved type, annotated
   * with any qualifiers and constant value.  Otherwise, it returns
   * the specified type.
   *
   * @param type The type.
   * @return The type as an rvalue.
   */
  public Type toRValue(Type type) {
    if (! type.hasShape()) return type;

    Type result = type.hasEnum() ? type.toEnum() : type.resolve();
    if (hasQualifiers(type) || type.hasConstant()) {
      result = qualify(result.annotate(), type);
      if (type.hasConstant()) {
        result = result.constant(type.getConstant().getValue());
      }
    }
    return result;
  }

  // =========================================================================

  /** The memoized thread-local flag. */
  private Boolean threadlocal = null;

  /**
   * Determine whether the current target supports thread-local
   * storage.
   *
   * @return <code>true</code> if the current target supports
   *   thread-local storage.
   */
  public boolean hasThreadLocals() {
    if (null == threadlocal) {
      threadlocal = Limits.IS_ELF ? Boolean.TRUE : Boolean.FALSE;
    }

    return threadlocal;
  }

  // =========================================================================

  /**
   * Get the specified type's alignment in bytes.
   *
   * @param type The type.
   * @return The type's alignment.
   * @throws IllegalArgumentException Signals that the type does not
   *   have a static alignment.
   */
  public long getAlignment(Type type) {
    return getAlignment(type, true);
  }

  /**
   * Get the specified type's alignment in bytes.  A type's natural
   * alignment is the type's alignment outside arrays, structures, and
   * unions.
   *
   * @param type The type.
   * @param natural The flag for determining the natural alignment.
   * @return The type's alignment.
   * @throws IllegalArgumentException
   *   Signals that the type does not have a static alignment.
   */
  public long getAlignment(Type type, boolean natural) {
    final Type resolved = type.resolve();

    if (resolved.isStruct() || resolved.isUnion()) {
      if (isPacked(type)) {
        // If the structure or union is packed, its alignment is 1.
        return 1;
      } else {
        // Otherwise, the alignment is the maximum of the overall
        // alignment and the members' alignments.
        long alignment = Math.max(1, getAligned(type));
        for (Type t : type.toTagged().getMembers()) {
          // Bit-fields with a zero width do not count.
          if (0 != t.toVariable().getWidth()) {
            alignment = Math.max(alignment, getAlignment(t, false));
          }
        }
        return alignment;
      }
    }

    final long alignment = getAligned(type);
    if (-1 != alignment) return alignment;

    switch (type.tag()) {
    case VOID:
      return Limits.VOID_ALIGN;

    case BOOLEAN:
      return natural ? Limits.BOOL_NAT_ALIGN : Limits.BOOL_ALIGN;

    case INTEGER:
    case FLOAT:
      switch (type.resolve().toNumber().getKind()) {
      case CHAR:
      case S_CHAR:
      case U_CHAR:
        return 1;
      case SHORT:
      case U_SHORT:
        return natural ? Limits.SHORT_NAT_ALIGN : Limits.SHORT_ALIGN;
      case INT:
      case S_INT:
      case U_INT:
        return natural ? Limits.INT_NAT_ALIGN : Limits.INT_ALIGN;
      case LONG:
      case U_LONG:
        return natural ? Limits.LONG_NAT_ALIGN : Limits.LONG_ALIGN;
      case LONG_LONG:
      case U_LONG_LONG:
        return natural ? Limits.LONG_LONG_NAT_ALIGN : Limits.LONG_LONG_ALIGN;
      case FLOAT:
        return natural ? Limits.FLOAT_NAT_ALIGN : Limits.FLOAT_ALIGN;
      case DOUBLE:
        return natural ? Limits.DOUBLE_NAT_ALIGN : Limits.DOUBLE_ALIGN;
      case LONG_DOUBLE:
        return natural ? Limits.LONG_DOUBLE_NAT_ALIGN : Limits.LONG_DOUBLE_ALIGN;
      case FLOAT_COMPLEX:
        return natural ? Limits.FLOAT_NAT_ALIGN : Limits.FLOAT_ALIGN;
      case DOUBLE_COMPLEX:
        return natural ? Limits.DOUBLE_NAT_ALIGN : Limits.DOUBLE_ALIGN;
      case LONG_DOUBLE_COMPLEX:
        return natural ? Limits.LONG_DOUBLE_NAT_ALIGN : Limits.LONG_DOUBLE_ALIGN;
      default:
        throw new
          AssertionError("Invalid number kind " + type.toNumber().getKind());
      }

    case POINTER:
      return natural ? Limits.POINTER_NAT_ALIGN : Limits.POINTER_ALIGN;

    case ARRAY:
      return getAlignment(type.resolve().toArray().getType(), natural);

    case FUNCTION:
      return Limits.FUNCTION_ALIGN;

    default:
      throw new IllegalArgumentException("Type without alignment " + type);
    }
  }

  // =========================================================================

  /**
   * Get the specified type's size in bytes.
   *
   * @param type The type.
   * @return The type's size.
   * @throws IllegalArgumentException Signals that the type does not
   *   have a static size.
   */
  public long getSize(Type type) {
    switch (type.tag()) {
    case VOID:
      return Limits.VOID_SIZE;

    case BOOLEAN:
      return Limits.BOOL_SIZE;

    case INTEGER:
    case FLOAT:
      switch (type.resolve().toNumber().getKind()) {
      case CHAR:
      case S_CHAR:
      case U_CHAR:
        return 1;
      case SHORT:
      case U_SHORT:
        return Limits.SHORT_SIZE;
      case INT:
      case S_INT:
      case U_INT:
        return Limits.INT_SIZE;
      case LONG:
      case U_LONG:
        return Limits.LONG_SIZE;
      case LONG_LONG:
      case U_LONG_LONG:
        return Limits.LONG_LONG_SIZE;
      case FLOAT:
        return Limits.FLOAT_SIZE;
      case DOUBLE:
        return Limits.DOUBLE_SIZE;
      case LONG_DOUBLE:
        return Limits.LONG_DOUBLE_SIZE;
      case FLOAT_COMPLEX:
        return 2 * Limits.FLOAT_SIZE;
      case DOUBLE_COMPLEX:
        return 2 * Limits.DOUBLE_SIZE;
      case LONG_DOUBLE_COMPLEX:
        return 2 * Limits.LONG_DOUBLE_SIZE;
      default:
        throw new
          AssertionError("Invalid number kind " + type.toNumber().getKind());
      }

    case POINTER:
      return Limits.POINTER_SIZE;

    case ARRAY: {
      ArrayT array = type.resolve().toArray();
      if (array.hasLength()) {
        return getSize(array.getType()) * array.getLength();
      } else {
        throw new IllegalArgumentException("Array without size");
      }
    }

    case STRUCT:
      return layout(type.resolve().toStruct(), null);

    case UNION: {
      long size = 0;
      for (Type t : type.toTagged().getMembers()) {
        size = Math.max(size, getSize(t));
      }
      return size;
    }

    case FUNCTION:
      return Limits.FUNCTION_SIZE;

    default:
      throw new IllegalArgumentException("Type without size " + type);
    }
  }

  // =========================================================================

  /**
   * Get the specified member's offset for the specified structure or
   * union.
   *
   * @param type The structure or union.
   * @param name The member's name.
   * @return The offset or -1 if the structure or union does not have
   *   a member with the specified name or the member is a bit-field.
   */
  public long getOffset(StructOrUnionT type, String name) {
    if (type.isStruct()) {
      return layout(type.toStruct(), name);
    } else {
      for (VariableT var : type.getMembers()) {
        if (var.hasName(name)) {
          return 0;
        } else if (! var.hasName() && ! var.hasWidth()) {
          final long offset = getOffset(var.toStructOrUnion(), name);
          if (-1 != offset) return offset;
        }
      }
      return -1;
    }
  }

  // =========================================================================

  /**
   * Lay out the specified structure.
   *
   * @param type The structure.
   * @param name The member name or <code>null</code> for the entire
   *   structure.
   * @return The offset/size or -1 if the structure does not have a
   *   member with the specified name or the member is a bit-field.
   */
  public long layout(StructT type, String name) {
    final List<VariableT> members     = type.getMembers();
    final int             memberCount = members.size();
    final boolean         hasTrailing = hasTrailingArray(type);
    final boolean         isPacked    = isPacked(type);

    long size     = 0;
    long bitCount = 0; // The number of bits we have seen so far.
    long bitSize  = 0; // The size of the member we are filling with bits.
    long bitAlign = 1; // The alignment of the member we are filling with bits.
    long maxAlign = Math.max(1, getAligned(type));

    for (int i=0; i<memberCount; i++) {
      // Ignore any trailing array.
      if (hasTrailing && i == memberCount-1) break;

      // Process the member.
      final VariableT var = members.get(i);

      // Determine whether this member is the last one affecting the
      // structure's size.
      final boolean isLastMember =
        i == memberCount-1 || (hasTrailing && i == memberCount-2);

      // Determine the member's alignment.
      final long varAlign = getAlignment(var, false);
      final long align = isPacked ? 1 : varAlign;
      if (0 != var.getWidth()) maxAlign = Math.max(align, maxAlign);

      if (! var.hasKind(VariableT.Kind.BITFIELD)) {
        // Process the regular member.
        final long mod = size % align;
        if (0 != mod) size += (align - mod);

        if (null != name) {
          if (var.hasName(name)) {
            return size;
          } else if (! var.hasName()) {
            final long offset = getOffset(var.toStructOrUnion(), name);
            if (-1 != offset) return size + offset;
          }
        }

        size += getSize(var);

      } else {
        // Process bit-field.
        final int width = var.getWidth();
        assert -1 < width;

        if (isPacked && (GCC_LEGACY_LAYOUT || 0 != width)) {
          // In packed structures, a bit-field just adds the specified
          // number of bits to the structure.  GCC 4.0 and earlier
          // ignore the member's alignment for zero width bit-fields.
          bitCount += width;

          if (isLastMember || -1 == members.get(i+1).getWidth()) {
            size += (bitCount / Limits.CHAR_BITS);
            if (0 != bitCount % Limits.CHAR_BITS) size++;

            bitCount = 0;
            bitSize  = 0;
            bitAlign = 1;
          }

        } else if (0 == width) {
          size += (bitCount / Limits.CHAR_BITS);
          if (0 != bitCount % Limits.CHAR_BITS) size++;

          // Use this member's alignment for padding.
          final long mod = size % varAlign;
          if (0 != mod) size += (varAlign - mod);

          bitCount = 0;
          bitSize  = 0;
          bitAlign = 1;

        } else {
          if (0 == bitSize) {
            bitCount = width;
            bitSize  = getSize(var);
            bitAlign = varAlign;

          } else if (bitCount + width <= Limits.toWidth(bitSize)) {
            bitCount += width;

          } else {
            size += bitSize;

            // Use the original member's alignment for padding.
            final long mod = size % bitAlign;
            if (0 != mod) size += (bitAlign - mod);

            bitCount = width;
            bitSize  = getSize(var);
            bitAlign = varAlign;
          }

          if (isLastMember || -1 == members.get(i+1).getWidth()) {
            size += (bitCount / Limits.CHAR_BITS);
            if (0 != bitCount % Limits.CHAR_BITS) size++;

            // Any necessary padding is either emitted when processing
            // the next non-bit-field member or when completing the
            // size calculation through the code below.

            bitCount = 0;
            bitSize  = 0;
            bitAlign = 1;
          }
        }
      }
    }

    if (null != name) return -1;

    // Account for padding to comply with the maximum alignment.
    final long mod = size % maxAlign;
    if (0 != mod) size += (maxAlign - mod);

    return size;
  }

  /** The canonical GCC packed attribute. */
  private static final Attribute PACKED = 
    new Attribute(Constants.NAME_GCC, new Attribute("packed", null));

  /**
   * Determine whether the specified type has a GCC packed attribute.
   *
   * @param type The type.
   * @return <code>true</code> if the type has a packed attribute.
   */
  protected boolean isPacked(Type type) {
    return type.hasAttribute(PACKED);
  }

  /**
   * Get the specified type's alignment.  If the specified type has a
   * GCC aligned attribute, this method returns the corresponding
   * alignment.  Otherwise, it returns -1.
   *
   * @param type The type.
   * @return The alignment or -1 if the type does not have an aligned
   *   attribute.
   */
  protected long getAligned(Type type) {
    long alignment = -1;

    do {
      for (Attribute att : type.attributes()) {
        if (Constants.NAME_GCC.equals(att.getName())) {
          att = (Attribute)att.getValue();
          if ("aligned".equals(att.getName())) {
            if (null == att.getValue()) {
              alignment =
                Math.max(Limits.LONG_LONG_ALIGN, Limits.LONG_DOUBLE_ALIGN);
            } else {
              alignment = ((BigInteger)att.getValue()).longValue();
            }
          }
        }
      }

      if (type.isWrapped()) {
        type = type.toWrapped().getType();
      } else {
        break;
      }
    } while (true);

    return alignment;
  }

  // =========================================================================

  /**
   * Get the specified number's size in bits.
   *
   * @param number The number.
   * @return The size in bits.
   */
  public long getWidth(Type number) {
    switch (number.tag()) {
    case BOOLEAN:
    case INTEGER:
    case FLOAT:
      return getSize(number) * Limits.CHAR_BITS;
    default:
      throw new AssertionError("Not a C number " + number);
    }
  }

  /**
   * Fit the specified number to the closest integer type, starting
   * with <code>int</code>.
   *
   * @param number The number.
   * @return The closest containing integer type or {@link
   *   ErrorT#TYPE} if the number is too large.
   */
  public Type fit(BigInteger number) {
    if (Limits.fitsInt(number)) {
      return NumberT.INT;
    } else if (Limits.fitsUnsignedInt(number)) {
      return NumberT.U_INT;
    } else if (Limits.fitsLong(number)) {
      return NumberT.LONG;
    } else if (Limits.fitsUnsignedLong(number)) {
      return NumberT.U_LONG;
    } else if (Limits.fitsLongLong(number)) {
      return NumberT.LONG_LONG;
    } else if (Limits.fitsUnsignedLongLong(number)) {
      return NumberT.U_LONG_LONG;
    } else {
      return ErrorT.TYPE;
    }
  }

  /**
   * Determine whether the specified number fits the specified integer
   * type.
   *
   * @param number The number.
   * @param type The integer type.
   * @return <code>true</code> if the number fits the type.
   */
  public boolean fits(BigInteger number, Type type) {
    switch (type.tag()) {
    case BOOLEAN:
      return Limits.fitsUnsignedChar(number);
    case INTEGER:
      switch (type.resolve().toInteger().getKind()) {
      case CHAR:
        return Limits.IS_CHAR_SIGNED?
          Limits.fitsChar(number) : Limits.fitsUnsignedChar(number);
      case S_CHAR:
        return Limits.fitsChar(number);
      case U_CHAR:
        return Limits.fitsUnsignedChar(number);
      case SHORT:
        return Limits.fitsShort(number);
      case U_SHORT:
        return Limits.fitsUnsignedShort(number);
      case INT:
      case S_INT:
        return Limits.fitsInt(number);
      case U_INT:
        return Limits.fitsUnsignedInt(number);
      case LONG:
        return Limits.fitsLong(number);
      case U_LONG:
        return Limits.fitsUnsignedLong(number);
      case LONG_LONG:
        return Limits.fitsLongLong(number);
      case U_LONG_LONG:
        return Limits.fitsUnsignedLongLong(number);
      }
    default:
      throw new AssertionError("Not a C integer " + type);
    }
  }

  /**
   * Mask the specified number as a value of this integer type.
   *
   * @param number The number.
   * @param type The type.
   * @return The number masked as a value of this type.
   */
  public BigInteger mask(BigInteger number, Type type) {
    switch (type.tag()) {
    case BOOLEAN:
      return (0 != number.signum()) ? BigInteger.ONE : BigInteger.ZERO;
    case INTEGER:
      switch (type.resolve().toInteger().getKind()) {
      case CHAR:
        return Limits.IS_CHAR_SIGNED ? Limits.maskAsSignedChar(number) :
        Limits.maskAsUnsignedChar(number);
      case S_CHAR:
        return Limits.maskAsSignedChar(number);
      case U_CHAR:
        return Limits.maskAsUnsignedChar(number);
      case SHORT:
        return Limits.maskAsShort(number);
      case U_SHORT:
        return Limits.maskAsUnsignedShort(number);
      case INT:
      case S_INT:
        return Limits.maskAsInt(number);
      case U_INT:
        return Limits.maskAsUnsignedInt(number);
      case LONG:
        return Limits.maskAsLong(number);
      case U_LONG:
        return Limits.maskAsUnsignedLong(number);
      case LONG_LONG:
        return Limits.maskAsLongLong(number);
      case U_LONG_LONG:
        return Limits.maskAsUnsignedLongLong(number);
      }
    default:
      throw new AssertionError("Not a C integer " + type);
    }
  }

  // =========================================================================

  /**
   * Get the specified type's designation.
   *
   * @param type The type.
   * @return The designation.
   */
  public String toDesignation(Type type) {
    switch (type.tag()) {
    case BOOLEAN:
    case INTEGER:
    case FLOAT:
    case POINTER:
      return "scalar";
    case ARRAY:
      return "array";
    case STRUCT:
      return "struct";
    case UNION:
      return "union";
    case FUNCTION:
      return "function";
    case INTERNAL:
      return type.resolve().toInternal().getName();
    default:
      throw new AssertionError("Not a C type " + type);
    }
  }

  // =========================================================================

  /**
   * Integer promote the specified type.  This method resolves the
   * type and, if the type is integral, then performs C's integer
   * promotion (C99 6.3.1.1).  Additionally, it normalizes implicit
   * and signed int types to int types.
   *
   * @param type The type.
   * @return The integer promoted type.
   */
  public Type promote(Type type) {
    // Flag for whether the type represents a bit-field and int is not
    // signed.
    boolean flip = ((! Limits.IS_INT_SIGNED) &&
                    type.hasVariable() &&
                    type.toVariable().hasWidth());

    type = type.resolve();

    switch (type.tag()) {
    case BOOLEAN:
      return NumberT.INT;
    case INTEGER:
      switch (type.toInteger().getKind()) {
      case CHAR:
      case S_CHAR:
      case U_CHAR:
      case SHORT:
        // Shorts and types of lesser rank always fit into an int.
        return NumberT.INT;
      case U_SHORT:      
        if (Limits.SHORT_SIZE < Limits.INT_SIZE) {
          // Unsigned shorts fit into regular ints if their size is
          // smaller.
          return NumberT.INT;
        } else {
          return NumberT.U_INT;
        }
      case INT:
        return flip ? NumberT.U_INT : NumberT.INT;
      case S_INT:
        return NumberT.INT; // Normalize to INT.
      case U_INT:
      case LONG:
      case U_LONG:
      case LONG_LONG:
      case U_LONG_LONG:
        // Nothing to promote.
        return type;
      default:
        throw new AssertionError("Not a C integer " + type);
      }
    default:
      return type;
    }
  }

  /**
   * Argument promote this type.  This method resolves the type and,
   * if the type is an integral type or a float, then performs C's
   * default argument promotions (C99 6.5.2.2).
   *
   * @param type The type.
   * @return The argument promoted type.
   */
  public Type promoteArgument(Type type) {
    Type resolved = type.resolve();

    if (resolved.isFloat()) {
      if (NumberT.Kind.FLOAT == resolved.toFloat().getKind()) {
        return NumberT.DOUBLE;
      } else {
        return resolved;
      }
    } else {
      return promote(type);
    }
  }

  /**
   * Pointerize the specified type.  This method resolves the type
   * and, if the type is an array or function, then performs C's
   * pointer decay (C99 6.3.2.1).
   *
   * @param type The type.
   * @return The pointerized type.
   */
  public Type pointerize(Type type) {
    type = type.resolve();

    switch (type.tag()) {
    case ARRAY:
      return new PointerT(type.toArray().getType());
    case FUNCTION:
      return new PointerT(type);
    default:
      return type;
    }
  }

  // =========================================================================

  /**
   * Perform the usual arithmetic conversions.  Per C99 6.3.1.8, this
   * method performs the usual arithmetic conversions for the
   * specified two types and returns the type of the corresponding
   * result.
   *
   * @param t1 The first type.
   * @param t2 The second type.
   * @return The converted type.
   * @throws IllegalArgumentException Signals that either type is
   *   not arithmetic.
   */
  public Type convert(Type t1, Type t2) {
    // We can't combine non-arithmetic types.
    if ((! isArithmetic(t1)) || t1.hasError()) {
      throw new IllegalArgumentException("Not an arithmetic type " + t1);
    } else if ((! isArithmetic(t2)) || t2.hasError()) {
      throw new IllegalArgumentException("Not an arithmetic type " + t2);
    }

    // Promote the types.  Note that promotion turns enums,
    // enumerators, and bit-fields into integers.
    t1 = promote(t1);
    t2 = promote(t2);

    // Now, we are left with integers and floats.
    NumberT.Kind k1 = ((NumberT)t1).getKind();
    NumberT.Kind k2 = ((NumberT)t2).getKind();

    // First, we convert any complex types.
    if ((! isReal(t1)) || (! isReal(t2))) {
      if ((NumberT.Kind.LONG_DOUBLE_COMPLEX == k1) ||
          (NumberT.Kind.LONG_DOUBLE_COMPLEX == k2)) {
        return NumberT.LONG_DOUBLE_COMPLEX;

      } else if ((NumberT.Kind.DOUBLE_COMPLEX == k1) ||
                 (NumberT.Kind.DOUBLE_COMPLEX == k2)) {
        return NumberT.DOUBLE_COMPLEX;
        
      } if ((NumberT.Kind.FLOAT_COMPLEX == k1) ||
            (NumberT.Kind.FLOAT_COMPLEX == k2)) {
        return NumberT.FLOAT_COMPLEX;
      }
    }

    // Next, we convert any real types.
    if ((NumberT.Kind.LONG_DOUBLE == k1) ||
        (NumberT.Kind.LONG_DOUBLE == k2)) {
      return NumberT.LONG_DOUBLE;

    } else if ((NumberT.Kind.DOUBLE == k1) ||
               (NumberT.Kind.DOUBLE == k2)) {
      return NumberT.DOUBLE;

    } else if ((NumberT.Kind.FLOAT == k1) ||
               (NumberT.Kind.FLOAT == k2)) {
      return NumberT.FLOAT;
    }

    // Otherwise, the integer promotions are performed... Well, they
    // have already been performed above and we are guaranteed to have
    // integers here.
    IntegerT i1 = t1.toInteger();
    IntegerT i2 = t2.toInteger();

    // If both operands have the same type, ...
    if (k1 == k2) return i1;

    // Otherwise,...
    if (i1.isSigned() == i2.isSigned()) {
      return (k1.ordinal() < k2.ordinal()) ? i2 : i1;
    }

    // Otherwise,...
    if (! i1.isSigned()) {
      if (k1.ordinal() > k2.ordinal()) {
        return i1;
      }
    } else {
      if (k2.ordinal() > k1.ordinal()) {
        return i2;
      }
    }

    // Otherwise,...
    if (i1.isSigned()) {
      if (getSize(i1) > getSize(i2)) {
        return i1;
      }
    } else {
      if (getSize(i2) > getSize(i1)) {
        return i2;
      }
    }

    // Otherwise,...
    if (i1.isSigned()) {
      if (NumberT.Kind.INT == k1) {
        return NumberT.U_INT;
      } else if (NumberT.Kind.LONG == k1) {
        return NumberT.U_LONG;
      } else {
        return NumberT.U_LONG_LONG;
      }
    } else {
      if (NumberT.Kind.INT == k2) {
        return NumberT.U_INT;
      } else if (NumberT.Kind.LONG == k2) {
        return NumberT.U_LONG;
      } else {
        return NumberT.U_LONG_LONG;
      }
    }
  }

  // =========================================================================

  /**
   * Compose the specified types.  This method determines whether the
   * two types are compatible while also constructing a composite type
   * as specified in C99 6.2.7.  If the types are compatible, the
   * resulting type is wrapped exactly as the first type.  If the
   * types are not compatible, the resulting type is {@link
   * ErrorT#TYPE}.
   *
   * <p />Note that if both types are derived types, this method
   * ensures that any referenced types have the same qualifiers.
   * However, it does not ensure that the two types have the same
   * qualifiers.  As a result, two types <code>t1</code> and
   * <code>t2</code> are compatible if:
   * <pre>
   * C.hasSameQualfiers(t1, t2) && (! C.compose(t1, t2).isError())
   * </pre>
   *
   * <p />Further note that the composed type does not preserve any
   * annotations or wraped types and thus needs to be annotated with
   * the two type's qualifiers etc.
   *
   * @see #equal(Type,Type)
   *
   * @param t1 The first type.
   * @param t2 The second type.
   * @param pedantic The flag for pedantic composition.
   * @return The composed type.
   */
  public Type compose(Type t1, Type t2, boolean pedantic) {
    return compose(t1, t2, pedantic, true);
  }

  /**
   * Compose the specified types.
   *
   * @param t1 The first type.
   * @param t2 The second type.
   * @param pedantic The flag for pedantic composition.
   * @param recursive The flag for recursive invocations.
   * @return The composed type.
   */
  protected Type compose(Type t1, Type t2, boolean pedantic, boolean recursive) {
    if (recursive) {
      // Preserve any wrapped types.
      if (t1.isEnum()) {
        return t1.equals(t2) ? t1 : ErrorT.TYPE;
        
      } else if (t1.isWrapped()) {
        Type w1 = t1.toWrapped().getType();
        Type c  = compose(w1, t2, pedantic, true);
        
        if (c.isError()) {
          return ErrorT.TYPE;
          
        } else if (w1 == c) {
          return t1;
          
        } else {
          switch (t1.wtag()) {
          case ALIAS:
            return new AliasT(t1, t1.toAlias().getName(), c);
          case ANNOTATED:
            return new AnnotatedT(t1, c);
          case ENUMERATOR: {
            EnumeratorT e = t1.toEnumerator();
            return new EnumeratorT(t1, c, e.getName(), e.getValue());
          }
          case VARIABLE: {
            VariableT v = t1.toVariable();
            return v.hasWidth() ?
              new VariableT(t1, c, v.getName(), v.getWidth())
              : new VariableT(t1, c, v.getKind(), v.getName());
          }
          default:
            throw new AssertionError("Invalid type " + t1);
          }
        }
      }

    } else {
      // Unwrap t1 while still checking enums.
      while (t1.isWrapped()) {
        if (t1.isEnum()) {
          return t1.equals(t2) ? t1 : ErrorT.TYPE;
        } else {
          t1 = t1.toWrapped().getType();
        }
      }
    }

    // t1 has already been resolved; resolve t2 as well.
    t2 = t2.resolve();

    // Make sure both types have the same tag.
    if (t1 == t2) return t1;
    if (t1.tag() != t2.tag()) return ErrorT.TYPE;

    // Now, do the type-specific composition.
    switch (t1.tag()) {
    case ERROR:
      return ErrorT.TYPE;

    case VOID:
    case BOOLEAN:
      return t1;

    case FLOAT:
    case INTEGER:
      return NumberT.equal(t1.toNumber().getKind(), t2.toNumber().getKind()) ?
        t1 : ErrorT.TYPE;

    case INTERNAL:
      return t1.toInternal().getName().equals(t2.toInternal().getName()) ?
        t1 : ErrorT.TYPE;

    case LABEL:
      return t1.toLabel().getName().equals(t2.toLabel().getName()) ?
        t1 : ErrorT.TYPE;

    case STRUCT:
    case UNION:
      return t1 == t2 ? t1 : ErrorT.TYPE;

    case POINTER: {
      // C99 6.7.2, 6.7.5.1
      Type pt1 = t1.toPointer().getType();
      Type pt2 = t2.toPointer().getType();
      if (! hasSameQualifiers(pt1, pt2)) return ErrorT.TYPE;
      Type ptc = compose(pt1, pt2, pedantic, true);
      if (ptc.isError()) return ErrorT.TYPE;
      return pt1 == ptc ? t1 : new PointerT(t1, ptc);
    }

    case ARRAY:
      return composeArrays(t1.toArray(), t2.toArray());

    case FUNCTION:
      return composeFunctions(t1.toFunction(), t2.toFunction(), pedantic);

    default:
      throw new AssertionError("Not a C type " + t1);
    }
  }

  /**
   * Compose the specified array types (C99 6.2.7).
   *
   * @param a1 The first array.
   * @param a2 The second array.
   * @return The composed type.
   */
  protected Type composeArrays(ArrayT a1, ArrayT a2) {
    if (! hasSameQualifiers(a1.getType(), a2.getType())) return ErrorT.TYPE;
    Type el = compose(a1.getType(), a2.getType(), true);
    if (el.isError()) return ErrorT.TYPE;
    
    if (a1.isVarLength()) {
      if (el == a1.getType()) {
        return a1;
      } else {
        return new ArrayT(a1, el, a1.isVarLength(), a1.getLength());
      }
    } else if (a2.isVarLength()) {
      if (el == a2.getType()) {
        return a2;
      } else {
        return new ArrayT(a2, el, a2.isVarLength(), a2.getLength());
      }
    }
    
    if (a1.hasLength() && a2.hasLength()) {
      if (a1.getLength() == a2.getLength()) {
        if (el == a1.getType()) {
          return a1;
        } else {
          return new ArrayT(a1, el, a1.isVarLength(), a1.getLength());
        }
      } else {
        return ErrorT.TYPE;
      }
    }
    
    if (a1.hasLength()) {
      if (el == a1.getType()) {
        return a1;
      } else {
        return new ArrayT(a1, el, a1.isVarLength(), a1.getLength());
      }
    }
    
    if (a2.hasLength()) {
      if (el == a1.getType()) {
        return a2;
      } else {
        return new ArrayT(a2, el, a2.isVarLength(), a2.getLength());
      }
    }
    
    return el == a1.getType() ?
      a1 : new ArrayT(a1, el, a1.isVarLength(), a1.getLength());
  }

  /**
   * Compose the specified function types (C99 6.2.7).  Note that this
   * method ignores any exceptions, which are not part of the C
   * language anyway.
   *
   * @param f1 The first function.
   * @param f2 The second function.
   * @param pedantic The flag for pedantic composition.
   * @return The composed type.
   */
  protected Type composeFunctions(FunctionT f1, FunctionT f2, boolean pedantic) {
    // Compare the names.
    if (null == f1.getName()) {
      if (null != f2.getName()) return ErrorT.TYPE;
    } else {
      if (! f1.getName().equals(f2.getName())) return ErrorT.TYPE;
    }

    // The flag for whether the component types differ from this type.
    boolean differs = false;

    // Compare the results.
    if (! hasSameQualifiers(f1.getResult(), f2.getResult())) return ErrorT.TYPE;
    final Type res = compose(f1.getResult(), f2.getResult(), true);
    if (res.isError()) return ErrorT.TYPE;
    if (f1.getResult() != res) differs = true;

    // Process functions with old-style declarations, since we ignore
    // their parameters.
    if (f1.hasAttribute(Constants.ATT_STYLE_OLD)) {
      if (f2.hasAttribute(Constants.ATT_STYLE_OLD)) {
        // Both types are functions without a parameter type list.
        // However, if type information from the function definition
        // is available, we preserve (but not check) it.
        if (f1.hasAttribute(Constants.ATT_DEFINED) ||
            (! f2.hasAttribute(Constants.ATT_DEFINED))) {
          return differs ?
            new FunctionT(f1, res, f1.getParameters(), f1.isVarArgs()) : f1;
        } else {
          return new FunctionT(f2, res, f2.getParameters(), f2.isVarArgs());
        }

      } else {
        // The other type is a function type with a parameter type
        // list.  It is compatible with this function type as long as
        // it is not variable and this function type is declared.
        if (f2.isVarArgs() && (! f1.hasAttribute(Constants.ATT_DEFINED))) {
          return ErrorT.TYPE;
        } else {
          return new FunctionT(f2, res, f2.getParameters(), f2.isVarArgs());
        }
      }

    } else if (f2.hasAttribute(Constants.ATT_STYLE_OLD)) {
      // This type is a function type with a parameter type list.  It
      // is compatible with the other function type as long as it is
      // not variable and the other function type is declared.
      if (f1.isVarArgs() && (! f2.hasAttribute(Constants.ATT_DEFINED))) {
        return ErrorT.TYPE;
      } else {
        return differs ?
          new FunctionT(f1, res, f1.getParameters(), f1.isVarArgs()) : f1;
      }
    }

    // Neither type is a function type with an old-style declaration.
    // Continue by checking the parameters.
    if (f1.getParameters().size() != f2.getParameters().size()) {
      return ErrorT.TYPE;
    }
    if (f1.isVarArgs() != f2.isVarArgs()) return ErrorT.TYPE;

    final int size = f1.getParameters().size();
    List<Type> par = differs ? new ArrayList<Type>(f1.getParameters()) : null;
    for (int i=0; i<size; i++) {
      final Type p1 = f1.getParameters().get(i);
      final Type p2 = f2.getParameters().get(i);
      if (pedantic && ! hasSameQualifiers(p1, p2)) return ErrorT.TYPE;
      final Type p3 = compose(p1, p2, true);
      if (p3.isError()) return ErrorT.TYPE;

      if (p1 != p3) {
        if (null == par) par = new ArrayList<Type>(f1.getParameters());
        differs = true;
        par.set(i, p3);
      }
    }

    // Ignore the exceptions and we are done.
    if (! differs) return f1;

    if (null == par) par = new ArrayList<Type>(f1.getParameters());

    final FunctionT result = new FunctionT(f1, res, par, f1.isVarArgs());
    return result;
  }

  /**
   * Determine whether the specified types are equal to each other.
   * Calling this method on types <code>t1</code> and <code>t2</code>
   * is equivalent to:
   * <pre>
   * C.hasSameQualifiers(t1, t2) && (! C.compose(t1, t2).isError())
   * </pre>
   *
   * @param t1 The first type.
   * @param t2 The second type.
   * @return <code>true</code> if the types are equal.
   */
  public boolean equal(Type t1, Type t2) {
    return hasSameQualifiers(t1, t2) && (! compose(t1, t2, true).isError());
  }

  // =========================================================================

  /** The factor for chars. */
  protected final BigInteger FACTOR_CHAR =
    BigInteger.valueOf(2).pow(Limits.CHAR_BITS);

  /** The factor for wide chars. */
  protected final BigInteger FACTOR_WIDE =
    BigInteger.valueOf(2).pow(Limits.CHAR_BITS * Limits.WCHAR_SIZE);

  /**
   * Type the specified C character literal.  This method determines
   * the type for the specified character literal, which may be a wide
   * character literal, and returns that type.  The type is annotated
   * with the literal's constant value, even if the value does not fit
   * the type.
   *
   * @param literal The literal.
   * @return The corresponding constant valued type.
   */
  public Type typeCharacter(String literal) {
    // The flag for a wide character literal.
    boolean isWide = false;

    // Strip wide marker and ticks.
    if (literal.startsWith("L")) {
      literal = literal.substring(2, literal.length()-1);
      isWide  = true;
    } else {
      literal = literal.substring(1, literal.length()-1);
    }

    // Unescape.
    literal = Utilities.unescape(literal);

    // Determine the value.
    BigInteger value  = BigInteger.ZERO;
    BigInteger factor = isWide ? FACTOR_WIDE : FACTOR_CHAR;

    final int  length = literal.length();
    for (int i=0; i<length; i++) {
      value = value.multiply(factor).
        add(BigInteger.valueOf(literal.charAt(i)));
    }

    // Determine the type, which according to C99 6.4.4.4 is an int
    // for chars.
    return isWide ? WCHAR.annotate().constant(value) :
      NumberT.CHAR.annotate().constant(value);
  }

  /**
   * Type the specified integer literal.  This method returns the type
   * for the specified C integer literal wrapped in the literal's
   * constant value.  If the specified literal does not fit any type,
   * this method returns the largest appropriate type.
   *
   * @param literal The literal.
   * @return The corresponding type and constant value.
   */
  public Type typeInteger(String literal) {
    // Extract the suffix.
    boolean isUnsigned = false;
    boolean isLong     = false;
    boolean isLongLong = false;
    int     idx        = literal.length();

    for ( ; idx>0; idx--) {
      char c = literal.charAt(idx-1);

      if (('u' == c) || ('U' == c)) {
        isUnsigned = true;

      } else if (('l' == c) || ('L' == c)) {
        if (isLong) {
          isLong     = false;
          isLongLong = true;
        } else {
          isLong     = true;
        }

      } else {
        break;
      }
    }
    literal = literal.substring(0, idx);

    // Extract the radix.
    int radix = 10;

    if (literal.startsWith("0x") || literal.startsWith("0X")) {
      radix   = 16;
      literal = literal.substring(2);

    } else if (literal.startsWith("0")) {
      radix   = 8;
    }

    // Extract the value.
    final BigInteger value = new BigInteger(literal, radix);

    // Find a fitting type (C99 6.4.4.1)
    IntegerT type = null;

    if (isUnsigned) {
      if (isLongLong) {
        if (Limits.fitsUnsignedLongLong(value)) {
          type = NumberT.U_LONG_LONG;
        }

      } else if (isLong) {
        if (Limits.fitsUnsignedLong(value)) {
          type = NumberT.U_LONG;
        } else if (Limits.fitsUnsignedLongLong(value)) {
          type = NumberT.U_LONG_LONG;
        }

      } else {
        if (Limits.fitsUnsignedInt(value)) {
          type = NumberT.U_INT;
        } else if (Limits.fitsUnsignedLong(value)) {
          type = NumberT.U_LONG;
        } else if (Limits.fitsUnsignedLongLong(value)) {
          type = NumberT.U_LONG_LONG;
        }
      }

      // Patch in the biggest type.
      if (null == type) type = NumberT.U_LONG_LONG;

    } else if (10 == radix) {
      if (isLongLong) {
        if (Limits.fitsLongLong(value)) {
          type = NumberT.LONG_LONG;
        }

      } else if (isLong) {
        if (Limits.fitsLong(value)) {
          type = NumberT.LONG;
        } else if (Limits.fitsLongLong(value)) {
          type = NumberT.LONG_LONG;
        }

      } else {
        if (Limits.fitsInt(value)) {
          type = NumberT.INT;
        } else if (Limits.fitsLong(value)) {
          type = NumberT.LONG;
        } else if (Limits.fitsLongLong(value)) {
          type = NumberT.LONG_LONG;
        }
      }

      // Patch in the biggest type.
      if (null == type) type = NumberT.LONG_LONG;

    } else {
      if (isLongLong) {
        if (Limits.fitsLongLong(value)) {
          type = NumberT.LONG_LONG;
        } else if (Limits.fitsUnsignedLongLong(value)) {
          type = NumberT.U_LONG_LONG;
        }

      } else if (isLong) {
        if (Limits.fitsLong(value)) {
          type = NumberT.LONG;
        } else if (Limits.fitsUnsignedLong(value)) {
          type = NumberT.U_LONG;
        } else if (Limits.fitsLongLong(value)) {
          type = NumberT.LONG_LONG;
        } else if (Limits.fitsUnsignedLongLong(value)) {
          type = NumberT.U_LONG_LONG;
        }

      } else {
        if (Limits.fitsInt(value)) {
          type = NumberT.INT;
        } else if (Limits.fitsUnsignedInt(value)) {
          type = NumberT.U_INT;
        } else if (Limits.fitsLong(value)) {
          type = NumberT.LONG;
        } else if (Limits.fitsUnsignedLong(value)) {
          type = NumberT.U_LONG;
        } else if (Limits.fitsLongLong(value)) {
          type = NumberT.LONG_LONG;
        } else if (Limits.fitsUnsignedLongLong(value)) {
          type = NumberT.U_LONG_LONG;
        }
      }

      // Patch in the biggest type.
      if (null == type) type = NumberT.U_LONG_LONG;
    }

    // Done.
    return type.annotate().constant(value);
  }

  /**
   * Type the specified floating point literal.  This method returns
   * the type for the specified C floating point literal wrapped in
   * the literal's constant value.
   *
   * @param literal The literal.
   * @return The corresponding type.
   */
  public Type typeFloat(String literal) {
    char    suffix = literal.charAt(literal.length()-1);
    boolean chop   = false;
    FloatT  type;

    switch (suffix) {
    case 'f':
    case 'F':
      chop = true;
      type = NumberT.FLOAT;
      break;
    case 'l':
    case 'L':
      chop = true;
      type = NumberT.LONG_DOUBLE;
      break;
    case 'd':
    case 'D':
      chop = true;
      // Fall through.
    default:
      type = NumberT.DOUBLE;
    }

    if (chop) literal = literal.substring(0, literal.length()-1);
    return type.annotate().constant(Double.valueOf(literal));
  }

}
