/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Business Objects nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* DataConstructor.java
* Created: Sept 16, 2002
* By: Bo Ilic
*/
package org.openquark.cal.compiler;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.openquark.cal.internal.serialization.ModuleSerializationTags;
import org.openquark.cal.internal.serialization.RecordInputStream;
import org.openquark.cal.internal.serialization.RecordOutputStream;
import org.openquark.cal.internal.serialization.RecordInputStream.RecordHeaderInfo;
/**
* Represents data constructors in CAL. Data constructors are the uppercase symbols
* introduced on the right hand side of a data declaration in CAL and represent a way
* to construct a value of an algebraic data type.
*
* @author Bo Ilic
*/
public final class DataConstructor extends FunctionalAgent {
/** the 0-based ordinal within the type. For example, if data Season = Winter | Spring | Summer | Fall; then Summer has ordinal 2.*/
private int ordinal;
/** the i-th element of this array indicates whether the i-th data constructor arg has a strictness annotation */
private boolean[] argStrictness;
/** the names of the data constructor fields, for any fields which are named.
* The length of this array should be the same as the length of the argStrictness array (ie. length == arity). */
private FieldName[] fieldNames;
/** (FieldName->Integer) map from field name to the index of the argument with that field name in this data constructor. */
private final Map<FieldName, Integer> fieldNameToIndexMap = new HashMap<FieldName, Integer>();
/** indicates whether any arguments are strict. Computed from argStrictness. DO NOT SERIALIZE. */
transient private boolean hasStrictArgs;
private static final int serializationSchema = 0;
/**
* Construct a DataConstructor. This must remain package scope. Construction of DataConstructors is controlled so that
* only 1 DataConstructor object is created to represent a given data constructor i.e. DataConstructor objects can be compared for
* equality using ==.
*
* @param dataConsName the data constructor's name
* @param scope the scope of the data constructor
* @param fieldNames the names of the data constructor fields, for any fields which are named.
* Null array values for unnamed args.
* The array itself can be null if this data constructor has no arguments.
* @param typeExpr the type of the data constructor
* @param argStrictness indicates whether the corresponding argument is strict of not.
* @param ordinal the 0-based ordinal within the type
*/
DataConstructor(QualifiedName dataConsName, Scope scope, FieldName[] fieldNames,
TypeExpr typeExpr, boolean[] argStrictness, int ordinal) {
super(dataConsName, scope, getArgumentNamesFromFieldNames(fieldNames), typeExpr, null);
if (!isValidInternalDataConstructorName(dataConsName)) {
throw new IllegalArgumentException("DataConstructor constructor: the argument 'dataConsName' is invalid.");
}
this.ordinal = ordinal;
this.argStrictness = argStrictness;
int arity = argStrictness.length; // throws an NPE if argStrictness is null.
boolean shouldHaveStrictArgs = false;
for (int i = 0; i < arity; ++i) {
if (argStrictness[i]) {
shouldHaveStrictArgs = true;
break;
}
}
this.hasStrictArgs = shouldHaveStrictArgs;
if (fieldNames == null) {
this.fieldNames = new FieldName[0];
if (arity != 0) {
throw new IllegalArgumentException("DataConstructor constructor: fieldNames is null, but data constructor has arguments.");
}
} else {
int fieldNamesLength = fieldNames.length;
this.fieldNames = new FieldName[fieldNamesLength];
System.arraycopy(fieldNames, 0, this.fieldNames, 0, fieldNamesLength);
if (arity != fieldNamesLength) {
String errorMessage = "DataConstructor constructor: argStrictness.length != fieldNames.length " +
"(" + arity + " != " + fieldNamesLength + ")";
throw new IllegalArgumentException(errorMessage);
}
for (int i = 0; i < fieldNamesLength; i++) {
fieldNameToIndexMap.put(fieldNames[i], Integer.valueOf(i));
}
}
}
// Zero argument constructor used for serialization.
private DataConstructor () {
hasStrictArgs = false;
}
/**
* Helper function to return "reasonable" argument names from an array of field names.
* This is necessary because ordinal field names such as #1 are not valid argument names.
*
* The argument name for a textual field name is simply its cal source form.
* The argument name for an ordinal field name is "field"+ordinal, plus a disambiguating
* suffix if this causes a collision with an existing name.
*
* For instance,
* field names {foo, #1} becomes ["foo", "field1"]
* field names {field10, #10} becomes ["field10", "field10_1"]
*
* @param fieldNames an array of field names, or null for no field names. Should not contain nulls.
* @return null if fieldNames is null, otherwise an array of argument names,
* where the argument name at index i corresponds to the ith field name in the array.
*/
private static String[] getArgumentNamesFromFieldNames(FieldName[] fieldNames) {
if (fieldNames == null) {
return null;
}
// The set of argument names used so far.
Set<String> argNamesSet = new HashSet<String>();
String[] argumentNames = new String[fieldNames.length];
// Iterate over the array, assigning argument names directly from the textual form of the field names.
for (int i = 0; i < fieldNames.length; i++) {
FieldName fieldName = fieldNames[i];
if (fieldName instanceof FieldName.Textual) {
String argName = fieldName.getCalSourceForm();
argumentNames[i] = argName;
if (!argNamesSet.add(argName)) {
throw new IllegalArgumentException("Duplicate field name: " + argName);
}
} else if (fieldName == null) {
throw new NullPointerException("Null field name.");
}
}
// Iterate again over the array, calculating argument names from the ordinal form of the field names.
for (int i = 0; i < fieldNames.length; i++) {
FieldName fieldName = fieldNames[i];
if (fieldName instanceof FieldName.Ordinal) {
FieldName.Ordinal ordinalFieldName = (FieldName.Ordinal)fieldName;
// Calculate a base name.
String baseName = "field" + ordinalFieldName.getOrdinal();
// Disambiguate the name.
String argName = baseName;
int index = 1;
while (!argNamesSet.add(argName)) {
argName = baseName + "_" + index;
index++;
}
// Assign.
argumentNames[i] = argName;
}
}
return argumentNames;
}
/**
* Returns true if the identifier is a lexically valid CAL data constructor name or
* a valid internal data constructor name.
* @param identifier
* @return boolean true if the indentifier is a lexically valid CAL data constructor name.
*/
static private boolean isValidInternalDataConstructorName (QualifiedName identifier) {
String name = identifier.getUnqualifiedName();
if (LanguageInfo.isValidDataConstructorName(name)) {
return true;
}
return name != null && name.startsWith("$");
}
/**
* @see org.openquark.cal.compiler.FunctionalAgent#getForm()
*/
@Override
public FunctionalAgent.Form getForm() {
return FunctionalAgent.Form.DATA_CONSTRUCTOR;
}
/**
* Get the arity of this DataConstructor.
* This is the number of arguments that this DataConstructor, considered as a function
* accepts. For example, it is 2 for Cons (i.e. ":") and 0 for Nil (i.e. "[]").
* Creation date: (3/20/01 1:45:23 PM)
* @return int
*/
public int getArity() {
return argStrictness.length;
//the below is also correct, but less efficient
//return getTypeExpr().getNApplications();
}
/**
* @param n index into field names. This should be in the range [0, arity - 1].
* @return the nth field name in this data constructor. Null if the nth field is not named.
*/
public FieldName getNthFieldName(int n) {
return fieldNames[n];
}
/**
* @param fieldName a field name
* @return the 0-based index of the data constructor argument with that field name,
* or -1 if this data constructor does not have a field with that name.
*/
public int getFieldIndex(FieldName fieldName) {
Integer fieldIndex = fieldNameToIndexMap.get(fieldName);
if (fieldIndex != null) {
return fieldIndex.intValue();
}
return -1;
}
/**
* The strictness annotations referred to here are indicated by ! in the data declaration source syntax.
* @param argN 0-based index into the arguments of this data constructor
* @return boolean whether the argN-th argument has a strictness annotation.
*/
public final boolean isArgStrict(int argN) {
return argStrictness[argN];
}
/**
* @return true if any of the arguments are strict.
*/
public final boolean hasStrictArgs () {
return hasStrictArgs;
}
/**
* @return a boolean array where each value indicates the strictness of the corresponding argument.
*/
public final boolean[] getArgStrictness () {
boolean[] retVal = new boolean [argStrictness.length];
System.arraycopy(argStrictness, 0, retVal, 0, retVal.length);
return retVal;
}
/**
* @return int the 0-based ordinal within the type.
*/
public final int getOrdinal() {
return ordinal;
}
/**
* Get the type constructor to which this data constructor belongs.
* For example, for Prelude.Left, which has type a -> Either a b, this
* will return "Either a b".
*
* @return TypeConsApp
*/
public final TypeConsApp getTypeConsApp() {
return getTypeExpr().getResultType().rootTypeConsApp();
}
/**
* Get the type constructor to which this data constructor belongs.
* For example, for Prelude.Left, which has type a -> Either a b, this
* will return the type constructor "Either".
*
* @return TypeConstructor
*/
public final TypeConstructor getTypeConstructor() {
return getTypeConsApp().getRoot();
}
/**
* Write this DataConstructor to a RecordOutputStream.
* @param s
* @throws IOException
*/
@Override
final void write (RecordOutputStream s) throws IOException {
s.startRecord(ModuleSerializationTags.DATA_CONSTRUCTOR, serializationSchema);
super.writeContent(s);
s.writeShortCompressed(ordinal);
s.writeShortCompressed(argStrictness.length);
byte[] bas = RecordOutputStream.booleanArrayToBitArray(argStrictness);
s.write(bas);
// write the field names
for (final FieldName fieldName : fieldNames) {
if (fieldName != null) {
s.writeBoolean(true);
FieldNameIO.writeFieldName(fieldName, s);
} else {
s.writeBoolean(false);
}
}
s.endRecord();
}
/**
* Load an instance of a DataConstructor from the RecordInputStream.
* The read position will be before the record header.
* @param s
* @param mti
* @param msgLogger the logger to which to log deserialization messages.
* @return an instance of DataConstructor.
* @throws IOException
*/
static final DataConstructor load (RecordInputStream s, ModuleTypeInfo mti, CompilerMessageLogger msgLogger) throws IOException {
// Look for Record header.
RecordHeaderInfo rhi = s.findRecord(ModuleSerializationTags.DATA_CONSTRUCTOR);
if(rhi == null) {
throw new IOException("Unable to find DataConstructor record header.");
}
DeserializationHelper.checkSerializationSchema(rhi.getSchema(), serializationSchema, mti.getModuleName(), "DataConstructor", msgLogger);
DataConstructor dc = new DataConstructor();
try {
dc.read(s, mti, msgLogger);
} catch (IOException e) {
// Add some context to the error message.
QualifiedName dcName = dc.getName();
throw new IOException ("Error loading DataConstructor " + (dcName == null ? "" : dcName.getQualifiedName()) + ": " + e.getLocalizedMessage());
}
return dc;
}
/**
* Read the content of a DataConstructor from the RecordInputStream.
* The read position will be after the record header.
* @param s
* @param mti
* @param msgLogger the logger to which to log deserialization messages.
* @throws IOException
*/
private void read (RecordInputStream s, ModuleTypeInfo mti, CompilerMessageLogger msgLogger) throws IOException {
super.readContent(s, mti, msgLogger);
ordinal = s.readShortCompressed();
int nArgs = s.readShortCompressed();
int nBytes = (nArgs + 7) /8;
byte[] bas = new byte[nBytes];
for (int i = 0; i < bas.length; ++i) {
bas[i] = s.readByte();
}
argStrictness = RecordInputStream.bitArrayToBooleans(bas, nArgs);
for (int i = 0; i < nArgs; ++i) {
if(argStrictness[i]) {
hasStrictArgs = true;
break;
}
}
// Read the field names.
this.fieldNames = new FieldName[nArgs];
for (int i = 0; i < nArgs; i++) {
boolean hasFieldName = s.readBoolean();
if (hasFieldName) {
FieldName fieldName = FieldNameIO.load(s, mti.getModuleName(), msgLogger);
fieldNames[i] = fieldName;
fieldNameToIndexMap.put(fieldName, Integer.valueOf(i));
}
}
s.skipRestOfRecord();
}
}