/* * 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. */ /* * Function.java * Created: June 6, 2001 * By: Bo Ilic */ package org.openquark.cal.compiler; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; 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; import org.openquark.cal.util.ArrayMap; /** * Provides an implementation of FunctionalAgent suitable for use in the type checker * with functions or pattern bound variables. * <P> * Creation date: (6/6/01 2:00:01 PM) * @author Bo Ilic */ public final class Function extends FunctionalAgent { private static final int serializationSchema = 0; private FunctionalAgent.Form form; /** the foreign function info of the entity, or null if not a foreign function. */ private ForeignFunctionInfo foreignFunctionInfo; /** indicates whether this function is declared as 'primitive' in the source. */ private boolean isDeclaredPrimitive; /** the level at which this entity was defined in the source code. */ private int nestingLevel; /** This is used when a function is being typechecked. For a function defined in a let, the flag is * removed when the function's definition has been typechecked i.e. the containing function does * not need to be finished with typechecking, only the body of the expression defining the function. */ private boolean typeCheckingDone; /** (QualifiedName -> Integer) * Map from gem name K to number of times that this function refers to K in its body. */ private Map<QualifiedName, Integer> dependeeToFrequencyMap; /** * If this function was defined at a nestingLevel > 0, then this is an identifier that * uniquely specifies this local function. If it was defined at the toplevel, then * localFunctionIdentifier must be null. */ private LocalFunctionIdentifier localFunctionIdentifier; /** * (LocalFunctionIdentifier -> Function) Map containing all of the local functions that are defined in this * function. This map will be empty for all non-toplevel functions (ie, those with nestingLevel > 0). */ private ArrayMap<LocalFunctionIdentifier, Function> localFunctionsMap; /** * True if TypeExpr contains uninstantiated non-generic TypeVars or RecordVars. * * We need a separate flag for this because the TypeExpr does not contain enough * information to deduce which uninstantiated variables are non-generic (since a * variable is only non-generic with respect to a certain set of non-generics). * * The TypeDeclarationInserter uses this to determine when it is possible to create * an explicit textual description of the type for this function (it's not possible * to write an explicit type declaration that distinguishes generic type variables * from non-generic type variables, so all type variables in a type declaration are * treated as generic). */ private boolean typeContainsUninstantiatedNonGenerics; /** * Construct a function entity. * Creation date: (7/21/00 2:33:25 PM) * * @param functionName the name of the function * @param scope the scope of the function * @param namedArguments the explicitly named arguments of the function * Can be null for entities with no explicitly specified argument names, * or contain nulls for argument names which aren't specified. * @param typeExpr the type of the function * @param form the kind or type of entity being added * @param nestingLevel the level at which this entity is defined in CAL source. */ Function(QualifiedName functionName, Scope scope, String[] namedArguments, TypeExpr typeExpr, FunctionalAgent.Form form, int nestingLevel) { super(functionName, scope, namedArguments, typeExpr, null); if (form != FunctionalAgent.Form.PATTERNVAR && form != FunctionalAgent.Form.FUNCTION) { throw new IllegalArgumentException("Function constructor: invalid value for 'form'"); } //todoBI Prelude.if should not be treated as a function entity /* if (!LanguageInfo.isValidFunctionName(functionName.getUnqualifiedName())) { throw new IllegalArgumentException("Function constructor: the argument 'functionName' is invalid."); } */ this.form = form; this.isDeclaredPrimitive = false; this.nestingLevel = nestingLevel; this.typeCheckingDone = form != FunctionalAgent.Form.FUNCTION; this.dependeeToFrequencyMap = new HashMap<QualifiedName, Integer>(); this.localFunctionsMap = new ArrayMap<LocalFunctionIdentifier, Function>(); } // Zero argument constructor for serialization. private Function () { } /** * Returns the kind of entity this is. "Form" is used as a synonym for "kind" or "type" * because of the overloaded meanings of the last 2 terms in the type checker! * Creation date: (6/4/01 1:49:57 PM) * @return FunctionalAgent.Fom */ @Override public FunctionalAgent.Form getForm() { return form; } /** * @return ForeignFunctionInfo the foreign function info of this foreign * function entity and null if it is not a foreign function entity. */ public final ForeignFunctionInfo getForeignFunctionInfo() { return foreignFunctionInfo; } /** * Creation date: (May 2, 2002) * @param foreignFunctionInfo */ final void setForeignFunctionInfo(ForeignFunctionInfo foreignFunctionInfo) { this.foreignFunctionInfo = foreignFunctionInfo; } /** * Sets the LocalFunctionIdentifier for this local function. Nesting level must * be >0; attempting to set a LocalFunctionIdentifier on a toplevel (ie, nesting level == 0) * function will result in an IllegalArgumentException. * @param localFunctionIdentifier */ void setLocalFunctionIdentifier(LocalFunctionIdentifier localFunctionIdentifier) { if(nestingLevel == 0) { throw new IllegalArgumentException("Toplevel functions cannot have a LocalFunctionIdentifier"); } this.localFunctionIdentifier = localFunctionIdentifier; } /** * @return The LocalFunctionIdentifier for this function if it has one, or * null otherwise. */ LocalFunctionIdentifier getLocalFunctionIdentifier() { return localFunctionIdentifier; } /** * Add a local function to the list of local functions defined in this function. * @param localFunction LocalFunction */ void addLocalFunction(Function localFunction) { if(nestingLevel > 0) { throw new IllegalArgumentException("Only toplevel functions can contain local functions"); } localFunctionsMap.put(localFunction.getLocalFunctionIdentifier(), localFunction); } /** * Returns the local function whose identifier is identifier, or null if no such * local function is defined in this function. * @param identifier LocalFunctionIdentifier of local function to retrieve * @return LocalFunction */ public Function getLocalFunction(LocalFunctionIdentifier identifier) { return localFunctionsMap.get(identifier); } /** * Returns the local function whose identifier is identifier, or null if no such * local function is defined in this function. * @return LocalFunction */ public Function getLocalFunction(QualifiedName qn, String identifier, int index) { return localFunctionsMap.get(new LocalFunctionIdentifier(qn, identifier, index)); } /** * @param n 0-based index * @return Function of the nth local function defined within this function */ public Function getNthLocalFunction(int n) { return localFunctionsMap.getNthValue(n); } /** * @return Number of local functions defined within this function */ public int getNLocalFunctions() { return localFunctionsMap.size(); } /** * Sets the flag that specifies whether the TypeExpr for this function contains * uninstantiated nongeneric RecordVars or TypeVars. * @param typeContainsUninstantiatedNonGenerics */ void setTypeContainsUninstantiatedNonGenerics(boolean typeContainsUninstantiatedNonGenerics) { this.typeContainsUninstantiatedNonGenerics = typeContainsUninstantiatedNonGenerics; } /** * @return True if the TypeExpr for this function contains uninstantiated * nongeneric RecordVars or TypeVars. * * We need a separate flag for this because the TypeExpr does not contain enough * information to deduce which uninstantiated variables are non-generic (since a * variable is only non-generic with respect to a certain set of non-generics). * * The TypeDeclarationInserter uses this to determine when it is possible to create * an explicit textual description of the type for this function (it's not possible * to write an explicit type declaration that distinguishes generic type variables * from non-generic type variables, so all type variables in a type declaration are * treated as generic). */ boolean typeContainsUninstantiatedNonGenerics() { return typeContainsUninstantiatedNonGenerics; } /** * @return whether the function is declared as 'primitive' in CAL source */ public boolean isPrimitive() { return isDeclaredPrimitive; } /** * Sets this entity as representing a function that is declared as 'primitive' in the source */ void setAsPrimitive() { isDeclaredPrimitive = true; } /** * Returns the nesting level at which the entity is defined. Top level function are defined at * level 0. Functions defined within a top level let are at level 1, ... * Creation date: (4/18/01 5:21:58 PM) * @return int */ @Override int getNestingLevel() { return nestingLevel; } /** * Creation date: (6/6/01 2:48:40 PM) * @return boolean returns false if the entity is in the process of being type checked * and thus subject to a change in type. */ @Override boolean isTypeCheckingDone() { return typeCheckingDone; } /** * Finished type checking this entity, and so its type should now only * ever be used in a copied form. * Creation date: (6/6/01 2:49:12 PM) */ @Override void setTypeCheckingDone() { super.setTypeCheckingDone(); typeCheckingDone = true; } /** * Make a top-level function. * * @return Function * @param functionName the function's name * @param typeExpr TypeExpr the type expression * @param scope Scope the scope of the function */ static Function makeTopLevelFunction(QualifiedName functionName, TypeExpr typeExpr, Scope scope) { return makeTopLevelFunction(functionName, null, typeExpr, scope); } /** * Make a top-level function. * * @return Function * @param functionName the function's name * @param argumentNames the names of the arguments to the entity. Null if none are known. * @param typeExpr TypeExpr the type expression * @param scope Scope the scope of the function */ static Function makeTopLevelFunction(QualifiedName functionName, String[] argumentNames, TypeExpr typeExpr, Scope scope) { //the function is defined at the top-level final int nestingLevel = 0; return new Function(functionName, scope, argumentNames, typeExpr, FunctionalAgent.Form.FUNCTION, nestingLevel); } /** * Insert the method's description here. * Creation date: (6/6/01 2:53:31 PM) * @return String */ @Override public String toString() { return super.toString(); } /** * The FreeVariableFinder makes local names unique by changing a local name such as x occurring in the definition * of f to something like f$x$9. We want to get back to the original local name x. For a top-level function this * method simply returns its name. * @return the unqualified display name for this entity. */ public String getUnqualifiedDisplayName() { return FreeVariableFinder.getDisplayName(getName().getUnqualifiedName()); } /** * @return (QualifiedName -> Integer) Unmodifiable Map from dependees of this gem to number of times they * are referenced. */ Map<QualifiedName, Integer> getDependeeToFrequencyMap() { return Collections.unmodifiableMap(dependeeToFrequencyMap); } /** * Add a dependee reference to the raw metric data for this function. If the dependee * already has an entry in the raw data, its frequency is incremented by referenceCount; * otherwise, it is added to the dependee frequency map with frequency of refrenceCount. * @param dependee QualifiedName of a gem to which this function refers * @param referenceCount Number of references to add for this dependee */ void addDependee(QualifiedName dependee, int referenceCount) { Integer currentCount = dependeeToFrequencyMap.get(dependee); if (currentCount != null) { dependeeToFrequencyMap.put(dependee, Integer.valueOf(currentCount.intValue() + referenceCount)); } else { dependeeToFrequencyMap.put(dependee, Integer.valueOf(referenceCount)); } } /** * Write this instance of Function to the RecordOutputStream. * @param s * @throws IOException */ @Override final void write (RecordOutputStream s) throws IOException { s.startRecord(ModuleSerializationTags.FUNCTION_ENTITY, serializationSchema); super.writeContent(s); s.writeShortCompressed(nestingLevel); byte[] flags = RecordOutputStream.booleanArrayToBitArray(new boolean[]{ typeCheckingDone, foreignFunctionInfo != null, isDeclaredPrimitive, localFunctionIdentifier != null, typeContainsUninstantiatedNonGenerics }); if (flags.length != 1) { throw new IOException("Unexpected number of flag bytes saveing Function."); } s.writeByte(flags[0]); form.write(s); if (foreignFunctionInfo != null) { try { foreignFunctionInfo.write(s); } catch (UnableToResolveForeignEntityException e) { // If we are serializing the info, then it must not have been originally deserialized lazily final IllegalStateException illegalStateException = new IllegalStateException("Java entities in the ForeignFunctionInfo should have been eagerly resolved during the compilation process"); illegalStateException.initCause(e); throw illegalStateException; } } s.writeInt(dependeeToFrequencyMap.size()); for (final Map.Entry<QualifiedName, Integer> entry : dependeeToFrequencyMap.entrySet()) { QualifiedName key = entry.getKey(); Integer frequency = entry.getValue(); s.writeQualifiedName(key); s.writeInt(frequency.intValue()); } int nLocalFunctions = localFunctionsMap.size(); s.writeInt(nLocalFunctions); for(int i = 0; i < nLocalFunctions; i++) { Function localFunction = localFunctionsMap.getNthValue(i); localFunction.write(s); } if(localFunctionIdentifier != null) { localFunctionIdentifier.write(s); } s.endRecord(); } /** * Load an instance of Function from the RecordInputStream. * 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 Function, or null if there was a problem resolving classes. * @throws IOException */ static final Function load (RecordInputStream s, ModuleTypeInfo mti, CompilerMessageLogger msgLogger) throws IOException { int nErrorsBeforeLoad = msgLogger.getNErrors(); Function function = new Function(); try { function.read(s, mti, msgLogger); } catch (IOException e) { // Try to add context to the error message. QualifiedName functionName = function.getName(); throw new IOException ("Error loading Function " + (functionName == null ? "" : functionName.getQualifiedName()) + ": " + e.getLocalizedMessage()); } // Check if entity state would be inconsistent by looking for logger errors. if (msgLogger.getNErrors() > nErrorsBeforeLoad) { return null; } return function; } /** * Load the content of this instance of Function from the RecordInputStream. * Read position will be before the record header. * * State may be inconsistent if there was a problem during deserialization. * In this case, problems will have been added to the logger. * * @param s * @param mti * @param msgLogger the logger to which to log deserialization messages. * @throws IOException */ private final void read (RecordInputStream s, ModuleTypeInfo mti, CompilerMessageLogger msgLogger) throws IOException { // Look for Record header. RecordHeaderInfo rhi = s.findRecord(ModuleSerializationTags.FUNCTION_ENTITY); if (rhi == null) { throw new IOException("Unable to find Function record header."); } DeserializationHelper.checkSerializationSchema(rhi.getSchema(), serializationSchema, mti.getModuleName(), "Function", msgLogger); // Read the EnvironmentEntity content super.readContent(s, mti, msgLogger); nestingLevel = s.readShortCompressed(); byte flags = s.readByte(); typeCheckingDone = (flags & 0x01) > 0; boolean hasFFI = (flags & 0x02) > 0; isDeclaredPrimitive = (flags & 0x04) > 0; boolean hasLocalFunctionIdentifier = (flags & 0x08) > 0; typeContainsUninstantiatedNonGenerics = (flags & 0x10) > 0; form = FunctionalAgent.Form.load(s); if (hasFFI) { // This can return null, leaving the function entity in an inconsistent state: foreignFunctionInfo = ForeignFunctionInfo.load(s, mti.getModuleName(), mti.getModule().getForeignClassLoader(), msgLogger); } int nDependees = s.readInt(); dependeeToFrequencyMap = new HashMap<QualifiedName, Integer>(); for (int i = 0; i < nDependees; i++) { QualifiedName key = s.readQualifiedName(); Integer frequency = Integer.valueOf(s.readInt()); dependeeToFrequencyMap.put(key, frequency); } int nLocalFunctions = s.readInt(); localFunctionsMap = new ArrayMap<LocalFunctionIdentifier, Function>(); for(int i = 0; i < nLocalFunctions; i++) { Function localFunction = Function.load(s, mti, msgLogger); localFunctionsMap.put(localFunction.getLocalFunctionIdentifier(), localFunction); } if(hasLocalFunctionIdentifier) { localFunctionIdentifier = LocalFunctionIdentifier.load(s, mti.getModuleName(), msgLogger); } s.skipRestOfRecord(); } }