/* * Base class for code generator helper. * * Copyright (c) 2005-2009 The Regents of the University of California. All * rights reserved. Permission is hereby granted, without written agreement and * without license or royalty fees, to use, copy, modify, and distribute this * software and its documentation for any purpose, provided that the above * copyright notice and the following two paragraphs appear in all copies of * this software. * * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN * "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO PROVIDE * MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. * * PT_COPYRIGHT_VERSION_2 COPYRIGHTENDKEY * */ package ptolemy.codegen.kernel; import java.io.File; import java.io.IOException; import java.lang.reflect.Method; import java.net.URL; import java.util.ArrayList; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.StringTokenizer; import ptolemy.actor.Actor; import ptolemy.actor.CompositeActor; import ptolemy.actor.IOPort; import ptolemy.actor.Receiver; import ptolemy.actor.TypedIOPort; import ptolemy.actor.parameters.ParameterPort; import ptolemy.actor.util.DFUtilities; import ptolemy.actor.util.ExplicitChangeContext; import ptolemy.codegen.actor.Director; import ptolemy.data.ArrayToken; import ptolemy.data.BooleanToken; import ptolemy.data.ObjectToken; import ptolemy.data.Token; import ptolemy.data.expr.ASTPtRootNode; import ptolemy.data.expr.ModelScope; import ptolemy.data.expr.Parameter; import ptolemy.data.expr.ParseTreeEvaluator; import ptolemy.data.expr.ParserScope; import ptolemy.data.expr.PtParser; import ptolemy.data.expr.Variable; import ptolemy.data.type.ArrayType; import ptolemy.data.type.BaseType; import ptolemy.data.type.Type; import ptolemy.data.type.Typeable; import ptolemy.domains.fsm.modal.ModalController; import ptolemy.kernel.util.Attribute; import ptolemy.kernel.util.IllegalActionException; import ptolemy.kernel.util.NameDuplicationException; import ptolemy.kernel.util.NamedObj; import ptolemy.kernel.util.Settable; import ptolemy.util.FileUtilities; import ptolemy.util.StringUtilities; ////CodeGeneratorHelper /** * Base class for code generator helper. * * <p> * Subclasses should override generateFireCode(), generateInitializeCode() * generatePostfireCode(), generatePreinitializeCode(), and generateWrapupCode() * methods by appending a corresponding code block. * * <p> * Subclasses should be sure to properly indent the code by either using the * code block functionality in methods like {@link #_generateBlockCode(String)} * or by calling {@link ptolemy.codegen.kernel.CodeStream#indent(String)}, for * example: * * <pre> * StringBuffer code = new StringBuffer(); * code.append(super.generateWrapupCode()); * code.append("// Local wrapup code"); * return processCode(CodeStream.indent(code.toString())); * </pre> * * @author Ye Zhou, Gang Zhou, Edward A. Lee, Contributors: Christopher Brooks, * Teale Fristoe * @version $Id$ * @since Ptolemy II 6.0 * @Pt.ProposedRating Yellow (eal) * @Pt.AcceptedRating Yellow (eal) */ public class CodeGeneratorHelper extends NamedObj implements ActorCodeGenerator { /** * Construct a code generator helper. */ public CodeGeneratorHelper() { this(null); } /** * Construct the code generator helper associated with the given component. * @param component The associated component. */ public CodeGeneratorHelper(NamedObj component) { this((Object) component); } /////////////////////////////////////////////////////////////////// //// public methods //// /** * Construct the code generator helper associated with the given component. * @param component The associated component. * @param name The name of helper. All periods are replaced with * underscores. */ public CodeGeneratorHelper(NamedObj component, String name) { this(component); try { setName(name.replaceAll("\\.", "_") + " helper"); } catch (IllegalActionException ex) { // This should not occur. } catch (NameDuplicationException ex) { // FIXME: May not be important to handle. } } /** * Construct a code generator helper. */ public CodeGeneratorHelper(Object object) { // FIXME: Why is this a namedObj when the analyzeActor() // method requires an Actor? _object = object; _parseTreeCodeGenerator = new ParseTreeCodeGenerator() { /** * Given a string, escape special characters as necessary for the * target language. * @param string The string to escape. * @return A new string with special characters replaced. */ public String escapeForTargetLanguage(String string) { return string; } /** * Evaluate the parse tree with the specified root node using the * specified scope to resolve the values of variables. * @param node The root of the parse tree. * @param scope The scope for evaluation. * @return The result of evaluation. * @exception IllegalActionException If an error occurs during * evaluation. */ public ptolemy.data.Token evaluateParseTree(ASTPtRootNode node, ParserScope scope) { return new Token(); } /** * Generate code that corresponds with the fire() method. * @return The generated code. */ public String generateFireCode() { return "/* ParseTreeCodeGenerator.generateFireCode() " + "not implemented in codegen.kernel.CodeGenerator */"; } }; } /** * Add a type to the Set of types used thus far. * @param typeName A string naming the type, for example "Boolean" or * "String". */ public void addNewTypeUsed(String typeName) { _codeGenerator._newTypesUsed.add(typeName); } /** * Add a functiom to the Set of functions used thus far. * @param functionName A string naming a function, for example "new". */ public void addFunctionUsed(String functionName) { _codeGenerator._typeFuncUsed.add(functionName); } /** * Find out each output port that needs to be converted for the actor * associated with this helper. Then, mark these ports along with the sink * ports (connection). * @exception IllegalActionException Not thrown in this base class. */ public void analyzeTypeConvert() throws IllegalActionException { // reset the previous type convert info. _portConversions.clear(); Actor actor = (Actor) _object; ArrayList sourcePorts = new ArrayList(); sourcePorts.addAll(actor.outputPortList()); if (actor instanceof CompositeActor) { sourcePorts.addAll(actor.inputPortList()); } Iterator ports = sourcePorts.iterator(); // for each output port. for (int i = 0; ports.hasNext(); i++) { TypedIOPort sourcePort = (TypedIOPort) ports.next(); // for each channel. for (int j = 0; j < sourcePort.getWidth(); j++) { Iterator sinks = getSinkChannels(sourcePort, j).iterator(); // for each sink channel connected. while (sinks.hasNext()) { Channel sink = (Channel) sinks.next(); TypedIOPort sinkPort = (TypedIOPort) sink.port; if (!sourcePort.getType().equals(sinkPort.getType())) { _markTypeConvert(new Channel(sourcePort, j), sink); } } } } } /** * Return true if the port is a local port. * @param forComposite True if this check is for a composite * @param port The port to be checked. * @return true if the port is a local port. */ public boolean checkLocal(boolean forComposite, IOPort port) throws IllegalActionException { return port.isInput() && !forComposite && port.isOutsideConnected() || port.isOutput() && forComposite; } /** * Return true if the port is a remote port. * @param forComposite True if this check is for a composite * @param port The port to be checked. * @return true if the port is a remote port. */ public boolean checkRemote(boolean forComposite, IOPort port) { return port.isOutput() && !forComposite || port.isInput() && forComposite; } /** * Get the corresponding type in code generation from the given Ptolemy * type. * @param ptType The given Ptolemy type. * @return The code generation type. * @exception IllegalActionException Thrown if the given ptolemy cannot be * resolved. */ public String codeGenType(Type ptType) { return _codeGenerator.codeGenType(ptType); } /** * Generate code for declaring read and write offset variables if needed. * Return empty string in this base class. * * @return The empty string. * @exception IllegalActionException Not thrown in this base class. */ public String createOffsetVariablesIfNeeded() throws IllegalActionException { return ""; } /** * Generate the fire code. In this base class, add the name of the * associated component in the comment. It checks the inline parameter of * the code generator. If the value is true, it generates the actor fire * code and the necessary type conversion code. Otherwise, it generate an * invocation to the actor function that is generated by * generateFireFunctionCode. * @return The generated code. * @exception IllegalActionException Not thrown in this base class. */ public String generateFireCode() throws IllegalActionException { StringBuffer code = new StringBuffer(); String composite = getComponent() instanceof CompositeActor ? "Composite Actor: " : ""; code.append(_eol + _codeGenerator.comment("Fire " + composite + generateName(getComponent()))); // Generate, process and store the fire code now because // we don't want to call _generateFireCode() in other places. // We should process the fire code here so the code generator can // infer information from the code blocks. _fireCode = processCode(_generateFireCode()); if (_codeGenerator.inline.getToken() == BooleanToken.TRUE) { code.append(_fireCode); code.append(generateTypeConvertFireCode()); } else if (getCodeGenerator().getContainer().getContainer() != null) { // Here we test whether the codegenerator is embedded in another actor or whether it // is at the toplevel. In it is embedded we don't need to generateTypeConvertFireCode. // Needed for jni and embeddedJava. code.append(_fireCode); } else { code.append(_generateFireInvocation(getComponent()) + ";" + _eol); } try { copyFilesToCodeDirectory(getComponent(), _codeGenerator); } catch (IOException ex) { throw new IllegalActionException(this, ex, "Problem copying files from the necessaryFiles parameter."); } return processCode(code.toString()); } /** * Generate The fire function code. This method is called when the firing * code of each actor is not inlined. Each actor's firing code is in a * function with the same name as that of the actor. * * @return The fire function code. * @exception IllegalActionException If thrown while generating fire code. */ public String generateFireFunctionCode() throws IllegalActionException { StringBuffer code = new StringBuffer(); code.append(_eol + "void " + generateName(getComponent()) + _getFireFunctionArguments() /*+ _codeGenerator.comment(getClass().getName() + ".generateFireFunctionCode()")*/ + " {" + _eol); code.append(_generateFireCode()); code.append(generateTypeConvertFireCode()); code.append("}" + _eol); try { copyFilesToCodeDirectory(getComponent(), _codeGenerator); } catch (IOException ex) { throw new IllegalActionException(this, ex, "Problem copying files from the necessaryFiles parameter."); } return processCode(code.toString()); } /** * Generate The fire function code. This method is called when generating * code for a Giotto director. Produce the fire code but do not transfer * generate any type conversion code that sends this actors output to * another actor's input. The typeConversion and moving from an output to an * input should be taken care of in a driver method not the fire method. * This works under that assumption that _generateFireCode() also generates * the code for postfire. If later we generate postfire code in a different * method please add that method call to this method * @return The fire function code. * @exception IllegalActionException If thrown while generating fire code. */ public String generateFireFunctionCode2() throws IllegalActionException { StringBuffer code = new StringBuffer(); code.append(_generateFireCode()); return processCode(code.toString()); } /** * Generate the initialize code. In this base class, return empty string. * Subclasses may extend this method to generate initialize code of the * associated component and append the code to the given string buffer. * @return The initialize code of the containing composite actor. * @exception IllegalActionException If thrown while appending to the the * block or processing the macros. */ public String generateInitializeCode() throws IllegalActionException { return _generateBlockByName(_defaultBlocks[1]); } /** * Generate mode transition code. The mode transition code generated in this * method is executed after each global iteration, e.g., in HDF model. Do * nothing in this base class. * * @param code The string buffer that the generated code is appended to. * @exception IllegalActionException Not thrown in this base class. */ public void generateModeTransitionCode(StringBuffer code) throws IllegalActionException { } /** * Generate the expression that represents the offset in the generated code. * @param offsetString The specified offset from the user. * @param port The referenced port. * @param channel The referenced port channel. * @param isWrite Whether to generate the write or read offset. * @return The expression that represents the offset in the generated code. * @exception IllegalActionException If there is problems getting the port * buffer size or the offset in the channel and offset map. */ public String generateOffset(String offsetString, IOPort port, int channel, boolean isWrite) throws IllegalActionException { ptolemy.actor.Director director = getDirector(); PortCodeGenerator portHelper = (PortCodeGenerator) _getHelper(port); return processCode(portHelper.generateOffset(offsetString, channel, isWrite, director)); } /** * Generate the postfire code. In this base class, do nothing. Subclasses * may extend this method to generate the postfire code of the associated * component and append the code to the given string buffer. * * @return The generated postfire code. * @exception IllegalActionException If thrown while appending to the the * block or processing the macros. */ public String generatePostfireCode() throws IllegalActionException { StringBuffer code = new StringBuffer(); code.append(_generateBlockByName(_defaultBlocks[3])); // Actor actor = (Actor) getComponent(); // for (IOPort port : (List<IOPort>) actor.outputPortList()) { // CodeGeneratorHelper portHelper = (CodeGeneratorHelper) _getHelper(port); // code.append(portHelper.generatePostfireCode()); // } return processCode(code.toString()); } public String generatePrefireCode() throws IllegalActionException { // FIXME: This is to be used in future re-structuring. StringBuffer code = new StringBuffer(); //Actor actor = (Actor) getComponent(); //for (IOPort port : (List<IOPort>) actor.inputPortList()) { // CodeGeneratorHelper portHelper = (CodeGeneratorHelper) _getHelper(port); // code.append(portHelper.generatePrefireCode()); //} return processCode(code.toString()); } /** * Generate the preinitialize code. In this base class, return an empty * string. This method generally does not generate any execution code and * returns an empty string. Subclasses may generate code for variable * declaration, defining constants, etc. * @return A string of the preinitialize code for the helper. * @exception IllegalActionException Not thrown in this base class. */ public String generatePreinitializeCode() throws IllegalActionException { _createBufferSizeAndOffsetMap(); return _generateBlockByName(_defaultBlocks[0]); } /** * Generate the type conversion fire code. This method is called by the * Director to append necessary fire code to handle type conversion. * @return The generated code. * @exception IllegalActionException Not thrown in this base class. */ public String generateTypeConvertFireCode() throws IllegalActionException { return generateTypeConvertFireCode(false); } /** * Generate the type conversion fire code. This method is called by the * Director to append necessary fire code to handle type conversion. * @param forComposite True if we are generating code for a composite. * @return The generated code. * @exception IllegalActionException Not thrown in this base class. */ public String generateTypeConvertFireCode(boolean forComposite) throws IllegalActionException { StringBuffer code = new StringBuffer(); // Type conversion code for inter-actor port conversion. Iterator channels = _getTypeConvertChannels().iterator(); while (channels.hasNext()) { Channel source = (Channel) channels.next(); if (!forComposite && source.port.isOutput() || forComposite && source.port.isInput()) { Iterator sinkChannels = _getTypeConvertSinkChannels(source) .iterator(); while (sinkChannels.hasNext()) { Channel sink = (Channel) sinkChannels.next(); code.append(_generateTypeConvertStatements(source, sink)); } } } return code.toString(); } /** * Generate variable declarations for inputs and outputs and parameters. * Append the declarations to the given string buffer. * @return code The generated code. * @exception IllegalActionException If the helper class for the model * director cannot be found. */ public String generateVariableDeclaration() throws IllegalActionException { return ""; } /** * Generate variable initialization for the referenced parameters. * @return code The generated code. * @exception IllegalActionException If the helper class for the model * director cannot be found. */ public String generateVariableInitialization() throws IllegalActionException { StringBuffer code = new StringBuffer(); // Generate variable initialization for referenced parameters. if (!_referencedParameters.isEmpty()) { code.append(_eol + _codeGenerator.comment(1, generateSimpleName(getComponent()) + "'s parameter initialization")); Iterator parameters = _referencedParameters.iterator(); while (parameters.hasNext()) { Parameter parameter = (Parameter) parameters.next(); try { // avoid duplication. if (!_codeGenerator._modifiedVariables.contains(parameter)) { code.append(_INDENT1 + _codeGenerator .generateVariableName(parameter) + " = " + getParameterValue( generateSimpleName(parameter), getComponent()) + ";" + _eol); } } catch (Throwable throwable) { throw new IllegalActionException(getComponent(), throwable, "Failed to generate variable initialization for \"" + parameter + "\""); } } } return code.toString(); } /** * Generate a variable name for the NamedObj. * @param namedObj The NamedObj to generate variable name for. * @see ptolemy.codegen.kernel.CodeGenerator#generateVariableName(NamedObj) * @return The variable name for the NamedObj. */ public String generateVariableName(NamedObj namedObj) { return _codeGenerator.generateVariableName(namedObj); } /** * Generate the wrapup code. In this base class, do nothing. Subclasses may * extend this method to generate the wrapup code of the associated * component and append the code to the given string buffer. * * @return The generated wrapup code. * @exception IllegalActionException If thrown while appending to the the * block or processing the macros. */ public String generateWrapupCode() throws IllegalActionException { return _generateBlockByName(_defaultBlocks[4]); } /** * Return the buffer size of a given port, which is the maximum of the * bufferSizes of all channels of the given port. * @param port The given port. * @return The buffer size of the given port. * @exception IllegalActionException If the * {@link #getBufferSize(IOPort, int)} method throws it. * @see #setBufferSize(IOPort, int, int) */ public int getBufferSize(IOPort port) throws IllegalActionException { int bufferSize = 1; if (port.getContainer() == getComponent()) { int length = 0; if (port.isInput()) { length = port.getWidth(); } else { length = port.getWidthInside(); } for (int i = 0; i < length; i++) { int channelBufferSize = getBufferSize(port, i); if (channelBufferSize > bufferSize) { bufferSize = channelBufferSize; } } } else { CodeGeneratorHelper actorHelper = (CodeGeneratorHelper) _getHelper(port .getContainer()); bufferSize = actorHelper.getBufferSize(port); } return bufferSize; } /** * Get the buffer size of the given port of this actor. * @param port The given port. * @param channelNumber The given channel. * @return The buffer size of the given port and channel. * @exception IllegalActionException If the getBufferSize() method of the * actor helper class throws it. * @see #setBufferSize(IOPort, int, int) */ public int getBufferSize(IOPort port, int channelNumber) throws IllegalActionException { return ((PortCodeGenerator) _getHelper(port)) .getBufferSize(channelNumber); } /** * Get the code generator associated with this helper class. * @return The code generator associated with this helper class. * @see #setCodeGenerator(CodeGenerator) */ public CodeGenerator getCodeGenerator() { return _codeGenerator; } /** * Get the component associated with this helper. * @return The associated component. */ public NamedObj getComponent() { return (NamedObj) _object; } /** * Return the executive director. If there is no executive * director, then return the director associated with the * object passed in to the constructor. * @return the executive director or the director of the actor. */ public ptolemy.actor.Director getDirector() { ptolemy.actor.Director director = ((Actor) _object) .getExecutiveDirector(); if (director == null) { // getComponent() is at the top level. Use its local director. director = ((Actor) _object).getDirector(); } return director; } /** * Return the helper of the director. * @return Return the helper of the director. */ public Director getDirectorHelper() throws IllegalActionException { return (Director) _getHelper(getDirector()); } /** * Return the translated token instance function invocation string. * @param functionString The string within the $tokenFunc() macro. * @param isStatic True if the method is static. * @return The translated type function invocation string. * @exception IllegalActionException The given function string is not * well-formed. */ public String getFunctionInvocation(String functionString, boolean isStatic) throws IllegalActionException { functionString = processCode(functionString); // i.e. "$tokenFunc(token::add(arg1, arg2, ...))" // this transforms to ==> // "functionTable[token.type][FUNC_add] (token, arg1, arg2, ...)" // FIXME: we need to do some more smart parsing to find the following // indexes. int commaIndex = functionString.indexOf("::"); int openFuncParenIndex = functionString.indexOf('(', commaIndex); int closeFuncParenIndex = functionString.lastIndexOf(')'); // Syntax checking. if (commaIndex == -1 || openFuncParenIndex == -1 || closeFuncParenIndex != functionString.length() - 1) { throw new IllegalActionException( "Bad Syntax with the $tokenFunc / $typeFunc macro. " + "[i.e. -- $tokenFunc(typeOrToken::func(arg1, ...))]. " + "String was:\n:" + functionString); } String typeOrToken = functionString.substring(0, commaIndex).trim(); String functionName = functionString.substring(commaIndex + 2, openFuncParenIndex).trim(); String argumentList = functionString.substring(openFuncParenIndex + 1) .trim(); if (isStatic) { // Record the referenced type function in the infoTable. _codeGenerator._typeFuncUsed.add(functionName); if (argumentList.length() == 0) { throw new IllegalActionException( "Static type function requires at least one argument(s)."); } return "functionTable[(int)" + typeOrToken + "][FUNC_" + functionName + "](" + argumentList; } else { // Record the referenced type function in the infoTable. _codeGenerator._tokenFuncUsed.add(functionName); // if it is more than just a closing paren if (argumentList.length() > 1) { argumentList = ", " + argumentList; } return "functionTable[(int)" + typeOrToken + ".type][FUNC_" + functionName + "](" + typeOrToken + argumentList; } } /** * Get the files needed by the code generated from this helper class. This * base class returns an empty set. * @return A set of strings that are header files needed by the code * generated from this helper class. * @exception IllegalActionException Not Thrown in this base class. */ public Set getHeaderFiles() throws IllegalActionException { Set files = new HashSet(); CodeStream codeStream = _getActualCodeStream(); codeStream.appendCodeBlock("includeFiles", true); String includeFilesString = codeStream.toString(); if (includeFilesString.length() > 0) { LinkedList includeFilesList = null; try { includeFilesList = StringUtilities .readLines(includeFilesString); } catch (IOException e) { throw new IllegalActionException( "Unable to read include files for " + getName()); } files.addAll(includeFilesList); } return files; } /** * Return a set of directories to include for the generated code. * @return A Set containing the contents of the actor's "includeDirectories" * block in its template. * @exception IllegalActionException If thrown when getting or reading the * CodeStream. */ public Set getIncludeDirectories() throws IllegalActionException { Set includeDirectories = new HashSet(); CodeStream codeStream = _getActualCodeStream(); codeStream.appendCodeBlock("includeDirectories", true); String includeDirectoriesString = codeStream.toString(); if (includeDirectoriesString.length() > 0) { LinkedList includeDirectoriesList = null; try { includeDirectoriesList = StringUtilities .readLines(includeDirectoriesString); } catch (IOException e) { throw new IllegalActionException( "Unable to read include directories for " + getName()); } includeDirectories.addAll(includeDirectoriesList); } return includeDirectories; } /** * Return a set of libraries to link in the generated code. * @return A Set containing the libraries in the actor's "libraries" block * in its template. * @exception IllegalActionException If thrown when getting or reading the * CodeStream. */ public Set getLibraries() throws IllegalActionException { Set libraries = new HashSet(); CodeStream codeStream = _getActualCodeStream(); codeStream.appendCodeBlock("libraries", true); String librariesString = codeStream.toString(); if (librariesString.length() > 0) { LinkedList librariesList = null; try { librariesList = StringUtilities.readLines(librariesString); } catch (IOException e) { throw new IllegalActionException( "Unable to read libraries for " + getName()); } libraries.addAll(librariesList); } return libraries; } /** * Return a set of directories to find libraries in. * @return A Set containing the directories in the actor's * "libraryDirectories" block in its template. * @exception IllegalActionException If thrown when getting or reading the * CodeStream. */ public Set getLibraryDirectories() throws IllegalActionException { Set libraryDirectories = new HashSet(); CodeStream codeStream = _getActualCodeStream(); codeStream.appendCodeBlock("libraryDirectories", true); String libraryDirectoriesString = codeStream.toString(); if (libraryDirectoriesString.length() > 0) { LinkedList libraryDirectoryList = null; try { libraryDirectoryList = StringUtilities .readLines(libraryDirectoriesString); } catch (IOException e) { throw new IllegalActionException( "Unable to read library directories for " + getName()); } libraryDirectories.addAll(libraryDirectoryList); } return libraryDirectories; } /** * Return a set of parameters that will be modified during the execution of * the model. The actor gets those variables if it implements * ExplicitChangeContext interface or it contains PortParameters. * * @return a set of parameters that will be modified. * @exception IllegalActionException If an actor throws it while getting * modified variables. */ public Set getModifiedVariables() throws IllegalActionException { Set set = new HashSet(); if (_object instanceof ExplicitChangeContext) { set .addAll(((ExplicitChangeContext) _object) .getModifiedVariables()); } Iterator inputPorts = ((Actor) _object).inputPortList().iterator(); while (inputPorts.hasNext()) { IOPort inputPort = (IOPort) inputPorts.next(); if (inputPort instanceof ParameterPort && inputPort.isOutsideConnected()) { set.add(((ParameterPort) inputPort).getParameter()); } } return set; } /** * Return the translated new constructor invocation string. Keep the types * referenced in the info table of this helper. The kernel will retrieve * this information to determine the total number of referenced types in the * model. * @param constructorString The string within the $new() macro. * @return The translated new constructor invocation string. * @exception IllegalActionException The given constructor string is not * well-formed. */ public String getNewInvocation(String constructorString) throws IllegalActionException { constructorString = processCode(constructorString); // i.e. "$new(Array(8, 8, arg1, arg2, ...))" // this transforms to ==> // "Array_new(8, arg1, arg2, ...)" int openFuncParenIndex = constructorString.indexOf('('); int closeFuncParenIndex = constructorString.lastIndexOf(')'); // Syntax checking. if (openFuncParenIndex == -1 || closeFuncParenIndex != constructorString.length() - 1) { throw new IllegalActionException( "Bad Syntax with the $new() macro. " + "[i.e. -- $new([elementType]Array(8, 8, arg1, arg2, ...))]"); } String typeName = constructorString.substring(0, openFuncParenIndex) .trim(); // Record the referenced type function in the infoTable. _codeGenerator._newTypesUsed.add(typeName); // new() should not be a polymorphic function. //_codeGenerator._typeFuncUsed.add("new"); // Transform this to a function call that needs // implementation code. return processCode("$" + typeName + "_new" + constructorString.substring(openFuncParenIndex)); } /** * Get the object associated with this helper. * @return The associated object. */ public NamedObj getObject() { return (NamedObj) _object; } /** * Return the value or an expression in the target language for the * specified parameter of the associated actor. If the parameter is * specified by an expression, then the expression will be parsed. If any * parameter referenced in that expression is specified by another * expression, the parsing continues recursively until either a parameter is * directly specified by a constant or a parameter can be directly modified * during execution in which case a reference to the parameter is generated. * * @param name The name of the parameter. * @param container The container to search upwards from. * @return The value or expression as a string. * @exception IllegalActionException If the parameter does not exist or does * not have a value. */ public String getParameterValue(String name, NamedObj container) throws IllegalActionException { if (name.contains("$")) { name = processCode(name); } StringTokenizer tokenizer = new StringTokenizer(name, ","); String attributeName = tokenizer.nextToken().trim(); String offset = null; String castType = null; if (tokenizer.hasMoreTokens()) { offset = tokenizer.nextToken().trim(); if (tokenizer.hasMoreTokens()) { throw new IllegalActionException(getComponent(), name + " does not have the correct format for" + " accessing the parameter value."); } } // Get the cast type (if any), so we can add the proper convert method. StringTokenizer tokenizer2 = new StringTokenizer(attributeName, "()", false); if (tokenizer2.countTokens() != 1 && tokenizer2.countTokens() != 2) { throw new IllegalActionException(getComponent(), "Invalid cast type: " + attributeName); } if (tokenizer2.countTokens() == 2) { castType = tokenizer2.nextToken().trim(); attributeName = tokenizer2.nextToken().trim(); } Attribute attribute = ModelScope.getScopedVariable(null, container, attributeName); if (attribute == null) { attribute = container.getAttribute(attributeName); if (attribute == null) { throw new IllegalActionException(container, "No attribute named: " + name); } } if (offset == null) { if (attribute instanceof Variable) { // FIXME: need to ensure that the returned string // is correct syntax for the target language. Variable variable = (Variable) attribute; /* * if (_codeGenerator._modifiedVariables.contains(variable)) { * return generateVariableName(variable); } else if * (variable.isStringMode()) { return "\"" + * variable.getExpression() + "\""; } */ ParseTreeCodeGenerator parseTreeCodeGenerator = getParseTreeCodeGenerator(); if (variable.isStringMode()) { return _generateTypeConvertMethod("\"" + parseTreeCodeGenerator .escapeForTargetLanguage(variable .getExpression()) + "\"", castType, "String"); } PtParser parser = new PtParser(); ASTPtRootNode parseTree = null; try { parseTree = parser.generateParseTree(variable .getExpression()); } catch (Throwable throwable) { throw new IllegalActionException(variable, throwable, "Failed to generate parse tree for \"" + name + "\". in \"" + container + "\""); } try { parseTreeCodeGenerator.evaluateParseTree(parseTree, new VariableScope(variable)); } catch (Exception ex) { StringBuffer results = new StringBuffer(); Iterator allScopedVariableNames = ModelScope .getAllScopedVariableNames(variable, container) .iterator(); while (allScopedVariableNames.hasNext()) { results.append(allScopedVariableNames.next().toString() + "\n"); } throw new IllegalActionException(getComponent(), ex, "Failed to find " + variable.getFullName() + "\n" + results.toString()); } String fireCode = processCode(parseTreeCodeGenerator .generateFireCode()); //if (castType == null && codeGenType(variable.getType()).equals("Array")) { // FIXME: this is a gross hack necessary for Case. // The problem is that if the refinement is named "{0}", then // we get into trouble because {0} is "false"? sigh. // return "Array_new(1, 1, " + fireCode + ");"; //} return _generateTypeConvertMethod(fireCode, castType, codeGenType(variable.getType())); } else /* if (attribute instanceof Settable) */{ return ((Settable) attribute).getExpression(); } // FIXME: Are there any other values that a // parameter might have? //throw new IllegalActionException(getComponent(), // "Attribute does not have a value: " + name); } else { // FIXME: if offset != null, for now we assume the value of // the parameter is fixed during execution. if (attribute instanceof Parameter) { Token token = ((Parameter) attribute).getToken(); if (token instanceof ArrayToken) { Token element = ((ArrayToken) token).getElement(Integer .valueOf(offset).intValue()); ///////////////////////////////////////////////////// ParseTreeCodeGenerator parseTreeCodeGenerator = getParseTreeCodeGenerator(); PtParser parser = new PtParser(); ASTPtRootNode parseTree = null; try { parseTree = parser .generateParseTree(element.toString()); } catch (Throwable throwable) { throw new IllegalActionException(attribute, throwable, "Failed to generate parse tree for \"" + name + "\". in \"" + container + "\""); } parseTreeCodeGenerator.evaluateParseTree(parseTree, new VariableScope((Parameter) attribute)); String elementCode = processCode(parseTreeCodeGenerator .generateFireCode()); ///////////////////////////////////////////////////// return _generateTypeConvertMethod(elementCode, castType, codeGenType(element.getType())); } throw new IllegalActionException(getComponent(), attributeName + " does not contain an ArrayToken."); } throw new IllegalActionException(getComponent(), attributeName + " is not a parameter."); } } /** * Return the parse tree to use with expressions. * @return the parse tree to use with expressions. */ public ParseTreeCodeGenerator getParseTreeCodeGenerator() { return _parseTreeCodeGenerator; } /** * Get the port that has the given name. * @param refName The given name. * @return The port that has the given name. */ public TypedIOPort getPort(String refName) { Actor actor = (Actor) _object; Iterator inputPorts = actor.inputPortList().iterator(); while (inputPorts.hasNext()) { TypedIOPort inputPort = (TypedIOPort) inputPorts.next(); // The channel is specified as $ref(port#channelNumber). if (generateSimpleName(inputPort).equals(refName)) { return inputPort; } } Iterator outputPorts = actor.outputPortList().iterator(); while (outputPorts.hasNext()) { TypedIOPort outputPort = (TypedIOPort) outputPorts.next(); // The channel is specified as $ref(port#channelNumber). if (generateSimpleName(outputPort).equals(refName)) { return outputPort; } } return null; } /** * Return the associated actor's rates for all configurations of this actor. * In this base class, return null. * @return null */ public int[][] getRates() { return null; } /** * Get the read offset in the buffer of a given channel from which a token * should be read. The channel is given by its containing port and the * channel number in that port. * @param inputPort The given port. * @param channelNumber The given channel number. * @return The offset in the buffer of a given channel from which a token * should be read. * @exception IllegalActionException Thrown if the helper class cannot be * found. * @see #setReadOffset(IOPort, int, Object) */ public Object getReadOffset(IOPort inputPort, int channelNumber) throws IllegalActionException { return ((PortCodeGenerator) _getHelper(inputPort)) .getReadOffset(channelNumber); } /** * Return the reference to the specified parameter or port of the associated * actor. For a parameter, the returned string is in the form * "fullName_parameterName". For a port, the returned string is in the form * "fullName_portName[channelNumber][offset]", if any channel number or * offset is given. * * FIXME: need documentation on the input string format. * * @param name The name of the parameter or port * @return The reference to that parameter or port (a variable name, for * example). * @exception IllegalActionException If the parameter or port does not exist * or does not have a value. */ public String getReference(String name) throws IllegalActionException { boolean isWrite = false; return getReference(name, isWrite); } /** * Generate the shared code. This is the first generate method invoked out * of all, so any initialization of variables of this helper should be done * in this method. In this base class, return an empty set. Subclasses may * generate code for variable declaration, defining constants, etc. * @return An empty set in this base class. * @exception IllegalActionException Not thrown in this base class. */ public Set getSharedCode() throws IllegalActionException { Set sharedCode = new HashSet(); _codeStream.clear(); _codeStream.appendCodeBlocks(".*shared.*"); if (!_codeStream.isEmpty()) { sharedCode.add(processCode(_codeStream.toString())); } return sharedCode; } /** * Get the size of a parameter. The size of a parameter is the length of its * array if the parameter's type is array, and 1 otherwise. * @param name The name of the parameter. * @return The expression that represents the size of a parameter or port. * @exception IllegalActionException If no port or parameter of the given * name is found. */ public String getSize(String name) throws IllegalActionException { // Try if the name is a parameter. Variable attribute = ModelScope.getScopedVariable(null, getComponent(), name); if (attribute != null) { Token token = attribute.getToken(); if (token instanceof ArrayToken) { return String.valueOf(((ArrayToken) token).length()); } return "1"; } else { TypedIOPort port = getPort(name); if (port != null) { if (port.isMultiport()) { return String.valueOf(port.getWidth()); } else { Type type = port.getType(); if (type instanceof ArrayType) { if (((ArrayType) type).hasKnownLength()) { return String.valueOf(((ArrayType) type).length()); } else { return getReference(name) + ".payload." + codeGenType(type) + "->size"; } } } } } throw new IllegalActionException(getComponent(), "Attribute not found: " + name); } /** * Return the worst case execution time (WCET) seen by this * component * @return The Worst Case Execution Time (WCET), in this base class, * the default value of 500.0 is returned. * @exception IllegalActionException If there is a problem determining * the WCET or a problem accessing the model. Not thrown in this * base class. */ public double getWCET() throws IllegalActionException { return 500.0; } /** * Get the write offset in the buffer of a given channel to which a token * should be put. The channel is given by its containing port and the * channel number in that port. * @param port The given port. * @param channelNumber The given channel number. * @return The offset in the buffer of a given channel to which a token * should be put. * @exception IllegalActionException Thrown if the helper class cannot be * found. * @see #setWriteOffset(IOPort, int, Object) */ public Object getWriteOffset(IOPort port, int channelNumber) throws IllegalActionException { return ((PortCodeGenerator) _getHelper(port)) .getWriteOffset(channelNumber); } /** * Determine if the given type is primitive. * @param cgType The given codegen type. * @return true if the given type is primitive, otherwise false. */ public boolean isPrimitive(String cgType) { return _codeGenerator.isPrimitive(cgType); } /** * Determine if the given type is primitive. * @param ptType The given ptolemy type. * @return true if the given type is primitive, otherwise false. */ public boolean isPrimitive(Type ptType) { return _codeGenerator.isPrimitive(ptType); } /** * Process the specified code, replacing macros with their values. * @param code The code to process. * @return The processed code. * @exception IllegalActionException If illegal macro names are found. */ public String processCode(String code) throws IllegalActionException { StringBuffer result = new StringBuffer(); boolean processAgain = false; int currentPos = _getMacroStartIndex(code, 0); if (currentPos < 0) { // No "$" in the string return code; } result.append(code.substring(0, currentPos)); while (currentPos < code.length()) { int openParenIndex = code.indexOf("(", currentPos + 1); if (openParenIndex == -1) { throw new IllegalActionException(getComponent(), "Failed to find open paren in \"" + code + "\"."); } int closeParenIndex = _findClosedParen(code, openParenIndex); if (closeParenIndex < 0) { // No matching close parenthesis is found. result.append(code.substring(currentPos)); return result.toString(); } int nextPos = _getMacroStartIndex(code, closeParenIndex + 1); if (nextPos < 0) { //currentPos is the last "$" nextPos = code.length(); } String subcode = code.substring(currentPos, nextPos); if (currentPos > 0 && code.charAt(currentPos - 1) == '\\') { // found "\$", do not make replacement. // FIXME: This is wrong. subcode may contain other macros // to be processed. // Should be result.append(processCode(subcode.substring(1))); result.append(subcode); currentPos = nextPos; continue; } String macro = code.substring(currentPos + 1, openParenIndex); macro = macro.trim(); String name = code.substring(openParenIndex + 1, closeParenIndex); name = processCode(name.trim()); try { String replaceString = _replaceMacro(macro, name); // If the replaceString contains '$' sign, // then we have to re-process the whole code string. if (_getMacroStartIndex(replaceString, 0) >= 0) { processAgain = true; } result.append(replaceString); } catch (Throwable throwable) { throw new IllegalActionException(this, throwable, "Failed to replace the parameter \"" + name + "\" in the macro \"" + macro + "\".\nInitial code was:\n" + code); } result.append(code.substring(closeParenIndex + 1, nextPos)); currentPos = nextPos; } if (processAgain) { return processCode(result.toString()); } return result.toString(); } /** * Reset the offsets of all channels of all input ports of the associated * actor to the default value of 0. * * @return The reset code of the associated actor. * @exception IllegalActionException If thrown while getting or setting the * offset. */ public String resetInputPortsOffset() throws IllegalActionException { StringBuffer code = new StringBuffer(); Iterator inputPorts = ((Actor) _object).inputPortList().iterator(); while (inputPorts.hasNext()) { IOPort port = (IOPort) inputPorts.next(); code.append(((PortCodeGenerator) _getHelper(port)) .initializeOffsets()); } return code.toString(); } /** * Set the buffer size of a given port. * @param port The given port. * @param channelNumber The given channel. * @param bufferSize The buffer size to be set to that port and channel. * @see #getBufferSize(IOPort) * @exception IllegalActionException */ public void setBufferSize(IOPort port, int channelNumber, int bufferSize) throws IllegalActionException { ((PortCodeGenerator) _getHelper(port)).setBufferSize(channelNumber, bufferSize); } /** * Set the code generator associated with this helper class. * @param codeGenerator The code generator associated with this helper * class. * @see #getCodeGenerator() */ public void setCodeGenerator(CodeGenerator codeGenerator) { _codeGenerator = codeGenerator; } /** * Set the read offset in a buffer of a given channel from which a token * should be read. * @param port The given port. * @param channelNumber The given channel. * @param readOffset The offset to be set to the buffer of that channel. * @exception IllegalActionException Thrown if the helper class cannot be * found. * @see #getReadOffset(IOPort, int) */ public void setReadOffset(IOPort port, int channelNumber, Object readOffset) throws IllegalActionException { ((PortCodeGenerator) _getHelper(port)).setReadOffset(channelNumber, readOffset); } /** * Set the write offset in a buffer of a given channel to which a token * should be put. * @param port The given port. * @param channelNumber The given channel. * @param writeOffset The offset to be set to the buffer of that channel. * @exception IllegalActionException If * {@link #setWriteOffset(IOPort, int, Object)} method throws it. * @see #getWriteOffset(IOPort, int) */ public void setWriteOffset(IOPort port, int channelNumber, Object writeOffset) throws IllegalActionException { ((PortCodeGenerator) _getHelper(port)).setWriteOffset(channelNumber, writeOffset); } /** * Get the corresponding type in C from the given Ptolemy type. * @param ptType The given Ptolemy type. * @return The C data type. */ public/* static */String targetType(Type ptType) { return _codeGenerator.targetType(ptType); } public String toString() { return getComponent().toString() + "'s Helper"; } ///////////////////////////////////////////////////////////////////// //// public inner classes //// /** * Return the index of a specific character in the string starting from the * given index. It find the first occurence of the character that is not * embedded inside parentheses "()". * @param ch The character to search for. * @param string The given string to search from. * @param fromIndex The index to start the search. * @return The first occurence of the character in the string that is not * embedded in parentheses. */ public static int _indexOf(String ch, String string, int fromIndex) { int parenIndex = fromIndex; int result = -1; int closedParenIndex = parenIndex; do { result = string.indexOf(ch, closedParenIndex); parenIndex = string.indexOf('(', closedParenIndex); if (parenIndex >= 0) { try { closedParenIndex = _findClosedParen(string, parenIndex); } catch (IllegalActionException e) { closedParenIndex = -1; } } } while (result > parenIndex && result < closedParenIndex); return result; } /** Parse the list of comma separted parameters. * @param parameters A comma separate list of parameters. * @return A list of parameters. */ public static List<String> parseList(String parameters) { List<String> result = new ArrayList<String>(); int previousCommaIndex = 0; int commaIndex = _indexOf(",", parameters, 0); while (commaIndex >= 0) { String item = parameters.substring(previousCommaIndex, commaIndex) .trim(); result.add(item); previousCommaIndex = commaIndex + 1; commaIndex = _indexOf(",", parameters, previousCommaIndex); } String item = parameters.substring(previousCommaIndex, parameters.length()).trim(); if (item.trim().length() > 0) { result.add(item); } return result; } /** * Copy files to the code directory. The optional * <code>fileDependencies</code> codeBlock consists of one or more lines * where each line names a file that should be copied to the directory named * by the <i>codeDirectory</i> parameter of the code generator. The file is * only copied if a file by that name does not exist in <i>codeDirectory</i> * or if the source file was more recently modified than the destination * file. * <p> * Using the <code>fileDependencies</code> code block allows actor writers * to refer to code defined in other files. * * @param namedObj If this argument is an instance of * ptolemy.actor.lib.jni.EmbeddedCActor, then the code blocks from * EmbeddedCActor's <i>embeddedCCode</i> parameter are used. * @param codeGenerator The code generator from which the <i>codeDirectory</i> * parameter is read. * @return The modification time of the most recent file. * @exception IOException If there is a problem reading the <i>codeDirectory</i> * parameter. * @exception IllegalActionException If there is a problem reading the * <i>codeDirectory</i> parameter. */ public static long copyFilesToCodeDirectory(NamedObj namedObj, CodeGenerator codeGenerator) throws IOException, IllegalActionException { // This is static so that ptolemy.actor.lib.jni.CompiledCompositeActor // will not depend on ptolemy.codegen. long lastModified = 0; CodeStream codeStream = null; codeStream = _getActualCodeStream(namedObj, codeGenerator); // Read in the optional fileDependencies code block. codeStream.appendCodeBlock("fileDependencies", true); String fileDependencies = codeStream.toString(); if (fileDependencies.length() > 0) { LinkedList fileDependenciesList = StringUtilities .readLines(fileDependencies); File codeDirectoryFile = codeGenerator._codeDirectoryAsFile(); String necessaryFileName = null; Iterator iterator = fileDependenciesList.iterator(); while (iterator.hasNext()) { necessaryFileName = (String) iterator.next(); // Look up the file as a resource. We do this so we can possibly // get it from a jar file in the release. URL necessaryURL = null; try { necessaryURL = FileUtilities.nameToURL(necessaryFileName, null, null); } catch (IOException ex) { // If the filename has no slashes, try prepending file:./ if (necessaryFileName.indexOf("/") == -1 || necessaryFileName.indexOf("\\") == -1) { try { necessaryURL = FileUtilities.nameToURL("file:./" + necessaryFileName, null, null); } catch (IOException ex2) { // Throw the original exception throw ex; } } else { // Throw the original exception throw ex; } } // Get the base filename (text after last /) String necessaryFileShortName = necessaryURL.getPath(); if (necessaryURL.getPath().lastIndexOf("/") > -1) { necessaryFileShortName = necessaryFileShortName .substring(necessaryFileShortName.lastIndexOf("/")); } File necessaryFileDestination = new File(codeDirectoryFile, necessaryFileShortName); File necessaryFileSource = new File(necessaryFileName); if (!necessaryFileDestination.exists() || necessaryFileSource.exists() && necessaryFileSource.lastModified() > necessaryFileDestination .lastModified()) { // If the dest file does not exist or is older than the // source file, we do the copy System.out.println("Copying " + necessaryFileSource + " to " + necessaryFileDestination); try { FileUtilities.binaryCopyURLToFile(necessaryURL, necessaryFileDestination); } catch (IOException ex) { String directory = "unknown"; if (!StringUtilities.getProperty("user.dir").equals("")) { directory = "\"" + StringUtilities.getProperty("user.dir") + "\""; } throw new IllegalActionException(namedObj, ex, "Failed to copy \"" + necessaryURL + "\" to \"" + necessaryFileDestination + "\". Current directory is " + directory); } } // Reopen the destination file and get its time for // comparison File necessaryFileDestination2 = new File(codeDirectoryFile, necessaryFileShortName); if (necessaryFileDestination2.lastModified() > lastModified) { lastModified = necessaryFileDestination2.lastModified(); } } } return lastModified; } /** * Generate a string that represents the offset for a dynamically determined * channel of a multiport. * @param port The referenced port. * @param isWrite Whether to generate the write or read offset. * @param channelString The string that will determine the channel. * @return The expression that represents the offset for a channel * determined dynamically in the generated code. */ public static String generateChannelOffset(IOPort port, boolean isWrite, String channelString) { // By default, return the channel offset for the first channel. if (channelString.equals("")) { channelString = "0"; } String channelOffset = CodeGeneratorHelper.generateName(port); channelOffset += isWrite ? "_writeOffset" : "_readOffset"; channelOffset += "[" + channelString + "]"; return channelOffset; } /** * Generate sanitized name for the given named object. Remove all * underscores to avoid conflicts with systems functions. * @param namedObj The named object for which the name is generated. * @return The sanitized name. */ public static String generateName(NamedObj namedObj) { String name = StringUtilities.sanitizeName(namedObj.getFullName()); // FIXME: Assume that all objects share the same top level. In this case, // having the top level in the generated name does not help to // expand the name space but merely lengthen the name string. // NamedObj parent = namedObj.toplevel(); // if (namedObj.toplevel() == namedObj) { // return "_toplevel_"; // } // String name = StringUtilities.sanitizeName(namedObj.getName(parent)); if (name.startsWith("_")) { name = name.substring(1, name.length()); } return name.replaceAll("\\$", "Dollar"); } /** * Generate sanitized name for the given named object. Remove all * underscores to avoid conflicts with systems functions. * @param namedObj The named object for which the name is generated. * @return The sanitized name. */ public static String generateSimpleName(NamedObj namedObj) { String name = StringUtilities.sanitizeName(namedObj.getName()); return name.replaceAll("\\$", "Dollar"); } /** * Return a reference to the port. * @param port The port. * @param channelAndOffset The given channel and offset. * @param isWrite True if the port is to be written to. * @return The reference to the port. * @exception IllegalActionException If the port does not * exist or does not have a value. */ public static String generatePortReference(IOPort port, String[] channelAndOffset, boolean isWrite) { StringBuffer result = new StringBuffer(); String channelOffset; if (channelAndOffset[1].equals("")) { channelOffset = CodeGeneratorHelper.generateChannelOffset(port, isWrite, channelAndOffset[0]); } else { channelOffset = channelAndOffset[1]; } result.append(generateName(port)); if (port.isMultiport()) { result.append("[" + channelAndOffset[0] + "]"); } result.append("[" + channelOffset + "]"); return result.toString(); } /** * Return an array of strings that are regular expressions of all the code * blocks that are appended automatically by default. Since the content of * the array are regex, users should use matches() instead of equals() to * compare their strings. * @return Array of string regular expressions of names of code blocks that * are appended by default. */ public static String[] getDefaultBlocks() { return _defaultBlocks; } /** * Return a reference to the attribute. * @param attribute The attribute. * @param channelAndOffset The given channel and offset. * @exception IllegalActionException If the attribute does not * exist or does not have a value. */ public String getReference(Attribute attribute, String[] channelAndOffset) throws IllegalActionException { StringBuffer result = new StringBuffer(); //FIXME: potential bug: if the attribute is not a parameter, //it will be referenced but not declared. if (attribute instanceof Parameter) { _referencedParameters.add(attribute); } result.append(_codeGenerator.generateVariableName(attribute)); if (!channelAndOffset[0].equals("")) { throw new IllegalActionException(getComponent(), "a parameter cannot have channel number."); } if (!channelAndOffset[1].equals("")) { //result.append("[" + channelAndOffset[1] + "]"); Type elementType = ((ArrayType) ((Parameter) attribute).getType()) .getElementType(); result.insert(0, "Array_get("); if (isPrimitive(elementType)) { // Generate type specific Array_get(). e.g. IntArray_get(). result.insert(0, "/*CGH77*/" + codeGenType(elementType)); } result.insert(0, "/*CGH77*/"); result.append(" ," + channelAndOffset[1] + ")"); } return result.toString(); } /** * Return a reference. * @param name The reference to be parsed. The format is * assumed to be "xxx#(yyy)", where "yyy" will be returned * @param isWrite True if the reference is to be written to. * @exception IllegalActionException If the attribute does not * exist or does not have a value. */ public String getReference(String name, boolean isWrite) throws IllegalActionException { ptolemy.actor.Director director = getDirector(); Director directorHelper = (Director) _getHelper(director); name = processCode(name); String castType = _getCastType(name); String refName = _getRefName(name); String[] channelAndOffset = _getChannelAndOffset(name); // Usually given the name of an input port, getReference(String name) // returns variable name representing the input port. Given the name // of an output port, getReference(String name) returns variable names // representing the input ports connected to the output port. // However, if the name of an input port starts with '@', // getReference(String name) returns variable names representing the // input ports connected to the given input port on the inside. // If the name of an output port starts with '@', // getReference(String name) returns variable name representing the // the given output port which has inside receivers. // The special use of '@' is for composite actor when // tokens are transferred into or out of the composite actor. boolean forComposite = false; if (refName.charAt(0) == '@') { forComposite = true; refName = refName.substring(1); } TypedIOPort port = getPort(refName); if (port != null) { if (port instanceof ParameterPort && port.numLinks() <= 0) { // Then use the parameter (attribute) instead. } else { String result = directorHelper.getReference(port, channelAndOffset, forComposite, isWrite, this); String refType = codeGenType(port.getType()); return _generateTypeConvertMethod(result, castType, refType); } } // Try if the name is a parameter. Attribute attribute = getComponent().getAttribute(refName); if (attribute != null) { String refType = _getRefType(attribute); String result = directorHelper.getReference(attribute, channelAndOffset, this); return _generateTypeConvertMethod(result, castType, refType); } throw new IllegalActionException(getComponent(), "Reference not found: " + name); } public String getReference(TypedIOPort port, String[] channelAndOffset, boolean forComposite, boolean isWrite) throws IllegalActionException { StringBuffer result = new StringBuffer(); boolean dynamicReferencesAllowed = ((BooleanToken) _codeGenerator.allowDynamicMultiportReference .getToken()).booleanValue(); int channelNumber = 0; boolean isChannelNumberInt = true; if (!channelAndOffset[0].equals("")) { // If dynamic multiport references are allowed, catch errors // when the channel specification is not an integer. if (dynamicReferencesAllowed) { try { channelNumber = Integer.valueOf(channelAndOffset[0]) .intValue(); } catch (Exception ex) { isChannelNumberInt = false; } } else { channelNumber = Integer.valueOf(channelAndOffset[0]).intValue(); } } if (!isChannelNumberInt) { // variable channel reference. if (port.isOutput()) { throw new IllegalActionException( "Variable channel reference not supported" + " for output ports"); } else { return generatePortReference(port, channelAndOffset, isWrite); } } // To support modal model, we need to check the following condition // first because an output port of a modal controller should be // mainly treated as an output port. However, during choice action, // an output port of a modal controller will receive the tokens sent // from the same port. During commit action, an output port of a modal // controller will NOT receive the tokens sent from the same port. if (checkRemote(forComposite, port)) { Receiver[][] remoteReceivers; // For the same reason as above, we cannot do: if (port.isInput())... if (port.isOutput()) { remoteReceivers = port.getRemoteReceivers(); } else { remoteReceivers = port.deepGetReceivers(); } if (remoteReceivers.length == 0) { // The channel of this output port doesn't have any sink. result.append(generateName(getComponent())); result.append("_"); result.append(generateSimpleName(port)); return result.toString(); } Channel sourceChannel = new Channel(port, channelNumber); List typeConvertSinks = _getTypeConvertSinkChannels(sourceChannel); List sinkChannels = getSinkChannels(port, channelNumber); boolean hasTypeConvertReference = false; for (int i = 0; i < sinkChannels.size(); i++) { Channel channel = (Channel) sinkChannels.get(i); IOPort sinkPort = channel.port; int sinkChannelNumber = channel.channelNumber; // Type convert. if (typeConvertSinks.contains(channel) && isPrimitive(((TypedIOPort) sourceChannel.port) .getType())) { if (!hasTypeConvertReference) { if (i != 0) { result.append(" = "); } result.append(_getTypeConvertReference(sourceChannel)); if (dynamicReferencesAllowed && port.isInput()) { if (channelAndOffset[1].trim().length() > 0) { result.append("[" + channelAndOffset[1].trim() + "]"); } else { result.append("[" + CodeGeneratorHelper .generateChannelOffset(port, isWrite, channelAndOffset[0]) + "]"); } } else { int rate = Math .max( DFUtilities .getTokenProductionRate(sourceChannel.port), DFUtilities .getTokenConsumptionRate(sourceChannel.port)); if (rate > 1 && channelAndOffset[1].trim().length() > 0) { result.append("[" + channelAndOffset[1].trim() + "]"); } } hasTypeConvertReference = true; } else { // We already generated reference for this sink. continue; } } else { if (i != 0) { result.append(" = "); } result.append(generateName(sinkPort)); if (sinkPort.isMultiport()) { result.append("[" + sinkChannelNumber + "]"); } if (channelAndOffset[1].equals("")) { channelAndOffset[1] = "0"; } result.append(generateOffset(channelAndOffset[1], sinkPort, sinkChannelNumber, true)); } } return result.toString(); } // Note that if the width is 0, then we have no connection to // the port but the port might be a PortParameter, in which // case we want the Parameter. // codegen/c/actor/lib/string/test/auto/StringCompare3.xml // tests this. if (checkLocal(forComposite, port)) { result.append(generateName(port)); //if (!channelAndOffset[0].equals("")) { if (port.isMultiport()) { // Channel number specified. This must be a multiport. result.append("[" + channelAndOffset[0] + "]"); } result.append(generateOffset(channelAndOffset[1], port, channelNumber, isWrite)); return result.toString(); } // FIXME: when does this happen? return ""; } /** * Return a list of channel objects that are the sink input ports given a * port and channel. Note the returned channels are newly created objects * and therefore not associated with the helper class. * @param port The given output port. * @param channelNumber The given channel number. * @return The list of channel objects that are the sink channels of the * given output channel. * @exception IllegalActionException */ public static List<Channel> getSinkChannels(IOPort port, int channelNumber) throws IllegalActionException { List sinkChannels = new LinkedList(); Receiver[][] remoteReceivers; // due to reason stated in getReference(String), // we cannot do: if (port.isInput())... if (port.isOutput()) { remoteReceivers = port.getRemoteReceivers(); } else { remoteReceivers = port.deepGetReceivers(); } if (remoteReceivers.length <= channelNumber || channelNumber < 0) { // This is an escape method. This class will not call this // method if the output port does not have a remote receiver. return sinkChannels; } if (remoteReceivers[channelNumber] == null) { /* * // FIXME: Is this an important warning? The reference to // * printedNullPortWarnings prevents us from making this // a static * method. if (!printedNullPortWarnings) { printedNullPortWarnings = * true; System.out.println("Warning: Channel " + channelNumber + " * of Port \"" + port + "\" was null! Total number of channels: " + * remoteReceivers.length); } */ return sinkChannels; } for (int i = 0; i < remoteReceivers[channelNumber].length; i++) { IOPort sinkPort = remoteReceivers[channelNumber][i].getContainer(); Receiver[][] portReceivers; if (sinkPort.isInput()) { portReceivers = sinkPort.getReceivers(); } else { portReceivers = sinkPort.getInsideReceivers(); } for (int j = 0; j < portReceivers.length; j++) { for (int k = 0; k < portReceivers[j].length; k++) { if (remoteReceivers[channelNumber][i] == portReceivers[j][k]) { Channel sinkChannel = new Channel(sinkPort, j); sinkChannels.add(sinkChannel); break; } } } } return sinkChannels; } // private List<String> processListOfCode(List<String> list) throws IllegalActionException { // ArrayList<String> newList = new ArrayList<String>(); // for (String code : list) { // newList.add(processCode(code)); // } // return newList; // } /** * Given a port and channel number, create a Channel that sends data to the * specified port and channel number. * @param port The port. * @param channelNumber The channel number of the port. * @return the source channel. * @exception IllegalActionException If there is a problem getting * information about the receivers or constructing the new Channel. */ public static Channel getSourceChannel(IOPort port, int channelNumber) throws IllegalActionException { Receiver[][] receivers = null; if (port.isInput()) { receivers = port.getReceivers(); } else if (port.isOutput()) { if (port.getContainer() instanceof CompositeActor) { receivers = port.getInsideReceivers(); } else { // This port is the source port, so we only // need to make a new Channel. We assume that // the given channelNumber is valid. return new Channel(port, channelNumber); } } else { assert false; } List sourcePorts = port.sourcePortList(); sourcePorts.addAll(port.insideSourcePortList()); for (TypedIOPort sourcePort : (List<TypedIOPort>) sourcePorts) { try { Channel source = new Channel(sourcePort, sourcePort .getChannelForReceiver(receivers[channelNumber][0])); if (source != null) { return source; } } catch (IllegalActionException ex) { } } return null; } public static void main(String[] args) { selfTest(); } public static void selfTest() { System.out.println(parseList("(a, b, abc)")); System.out.println(_indexOf(",", "(a, b, abc,), (a , b, abc,)", 0)); System.out.println(_indexOf(",", "a, b, abc,, (a , b, abc,)", 0)); System.out.println(_indexOf(",", ", (b), abc,, (a , b, abc,)", 0)); System.out.println(_indexOf(",", "a, (b, abc,), (a , b, abc,)", 0)); System.out .println(_indexOf(",", "(((a), b,) a),bc,, (a , b, abc,)", 0)); } /** * A class that defines a channel object. A channel object is specified by * its port and its channel index in that port. */ public static class Channel { // FindBugs suggests making this class static so as to decrease // the size of instances and avoid dangling references. /** * Construct the channel with the given port and channel number. * @param portObject The given port. * @param channel The channel number of this object in the given port. */ public Channel(IOPort portObject, int channel) { port = portObject; channelNumber = channel; } /** * Whether this channel is the same as the given object. * @param object The given object. * @return True if this channel is the same reference as the given * object, otherwise false; */ public boolean equals(Object object) { return object instanceof Channel && port.equals(((Channel) object).port) && channelNumber == ((Channel) object).channelNumber; } /** * Return the hash code for this channel. Implementing this method is * required for comparing the equality of channels. * @return Hash code for this channel. */ public int hashCode() { return port.hashCode() + channelNumber; } /** * Return the string representation of this channel. * @return The string representation of this channel. */ public String toString() { return generateSimpleName(port) + "_" + channelNumber; } /** * The port that contains this channel. */ public IOPort port; /** * The channel number of this channel. */ public int channelNumber; } /** * This class implements a scope, which is used to generate the parsed * expressions in target language. */ protected class VariableScope extends ModelScope { /** * Construct a scope consisting of the variables of the containing actor * and its containers and their scope-extending attributes. */ public VariableScope() { _variable = null; } /** * Construct a scope consisting of the variables of the container of the * given instance of Variable and its containers and their * scope-extending attributes. * @param variable The variable whose expression is under code * generation using this scope. */ public VariableScope(Variable variable) { _variable = variable; } /////////////////////////////////////////////////////////////////// //// public methods //// /** * Look up and return the macro or expression in the target language * corresponding to the specified name in the scope. * @param name The given name string. * @return The macro or expression with the specified name in the scope. * @exception IllegalActionException If thrown while getting buffer * sizes or creating ObjectToken. */ public Token get(String name) throws IllegalActionException { NamedObj container = getComponent(); if (_variable != null) { container = _variable.getContainer(); } Variable result = getScopedVariable(_variable, container, name); if (result != null) { // If the variable found is a modified variable, which means // its value can be directly changed during execution // (e.g., in commit action of a modal controller), then this // variable is declared in the target language and should be // referenced by the name anywhere it is used. if (_codeGenerator._modifiedVariables.contains(result)) { return new ObjectToken(_codeGenerator .generateVariableName(result)); } else { // This will lead to recursive call until a variable found // is either directly specified by a constant or it is a // modified variable. PtParser parser = new PtParser(); String parameterValue = getParameterValue(name, result .getContainer()); try { ASTPtRootNode parseTree = parser .generateParseTree(parameterValue); ParseTreeEvaluator evaluator = new ParseTreeEvaluator(); return evaluator.evaluateParseTree(parseTree, this); } catch (IllegalActionException ex) { // Could not evaluate the expression. This means that // the parameter value contains a variable expression. // So, we'll won't try to evaluate it. return new ObjectToken(parameterValue); } } } else { return null; } } /** * Look up and return the type of the attribute with the specified name * in the scope. Return null if such an attribute does not exist. * @param name The name of the attribute to look up. * @return The attribute with the specified name in the scope. * @exception IllegalActionException If a value in the scope exists with * the given name, but cannot be evaluated. */ public ptolemy.data.type.Type getType(String name) throws IllegalActionException { if (_variable != null) { return _variable.getParserScope().getType(name); } return null; } /** * Look up and return the type term for the specified name in the scope. * Return null if the name is not defined in this scope, or is a * constant type. * @param name The name of the type term to look up. * @return The InequalityTerm associated with the given name in the * scope. * @exception IllegalActionException If a value in the scope exists with * the given name, but cannot be evaluated. */ public ptolemy.graph.InequalityTerm getTypeTerm(String name) throws IllegalActionException { if (_variable != null) { return _variable.getParserScope().getTypeTerm(name); } return null; } /** * Return the list of identifiers within the scope. * @return The list of variable names within the scope. * @exception IllegalActionException If there is a problem getting the * identifier set from the variable. */ public Set identifierSet() throws IllegalActionException { if (_variable != null) { return _variable.getParserScope().identifierSet(); } return null; } public String toString() { return super.toString() + " variable: " + _variable + " variable.parserScope: " + (_variable == null ? "N/A, _variable is null" : _variable .getParserScope()); } /////////////////////////////////////////////////////////////////// //// private variables //// /** * If _variable is not null, then the helper scope created is for * parsing the expression specified for this variable and generating the * corresponding code in target language. */ private Variable _variable = null; } /////////////////////////////////////////////////////////////////// //// protected methods. //// /** * Generate the fire code. This method is intended to be overwritten by * sub-classes to generate actor-specific code. * @return The generated code. * @exception IllegalActionException Not thrown in this base class. */ protected String _generateFireCode() throws IllegalActionException { _codeStream.clear(); // If the component name starts with a $, then convert "$" to "Dollar" and avoid problems // with macro substitution. See codegen/c/actor/lib/test/auto/RampDollarNames.xml. _codeStream.appendCodeBlock(_defaultBlocks[2], true); // fireBlock return _codeStream.toString(); } /** * Create the buffer size and offset maps for each input port, which is * associated with this helper object. A key of the map is an IOPort of the * actor. The corresponding value is an array of channel objects. The i-th * channel object corresponds to the i-th channel of that IOPort. This * method is used to maintain a internal HashMap of channels of the actor. * The channel objects in the map are used to keep track of the buffer sizes * or offsets in their buffer. * @exception IllegalActionException If the director helper or executive * director is not found, or if {@link #setReadOffset(IOPort, int, Object)} * method throws it, or if {@link #setWriteOffset(IOPort, int, Object)} * method throws it. * */ protected void _createBufferSizeAndOffsetMap() throws IllegalActionException { _createInputBufferSizeAndOffsetMap(); } /** * Create the input buffer and offset map. * @exception IllegalActionException If thrown while getting port * information. */ protected void _createInputBufferSizeAndOffsetMap() throws IllegalActionException { //We only care about input ports where data are actually stored //except when an output port is not connected to any input port. //In that case the variable corresponding to the unconnected output //port always has size 1 and the assignment to this variable is //performed just for the side effect. Iterator inputPorts = ((Actor) _object).inputPortList().iterator(); while (inputPorts.hasNext()) { IOPort port = (IOPort) inputPorts.next(); int length = port.getWidth(); ptolemy.actor.Director director = getDirector(); Director directorHelper = (Director) _getHelper(director); for (int i = 0; i < port.getWidth(); i++) { int bufferSize = directorHelper.getBufferSize(port, i); setBufferSize(port, i, bufferSize); } for (int i = 0; i < length; i++) { setReadOffset(port, i, Integer.valueOf(0)); setWriteOffset(port, i, Integer.valueOf(0)); } } } /** * Given a block name, generate code for that block. This method is called * by actors helpers that have simple blocks that do not take parameters or * have widths. * @param blockName The name of the block. * @return The code for the given block. * @exception IllegalActionException If illegal macro names are found, or if * there is a problem parsing the code block from the helper .c file. */ protected String _generateBlockCode(String blockName) throws IllegalActionException { // We use this method to reduce code duplication for simple blocks. return _generateBlockCode(blockName, new ArrayList()); } /** * Given a block name, generate code for that block. This method is called * by actors helpers that have simple blocks that do not take parameters or * have widths. * @param blockName The name of the block. * @param args The arguments for the block. * @return The code for the given block. * @exception IllegalActionException If illegal macro names are found, or if * there is a problem parsing the code block from the helper .c file. */ protected String _generateBlockCode(String blockName, List args) throws IllegalActionException { // We use this method to reduce code duplication for simple blocks. _codeStream.clear(); _codeStream.appendCodeBlock(blockName, args); return processCode(_codeStream.toString()); } /** * Generate expression that evaluates to a result of equivalent value with * the cast type. * @param expression The given variable expression. * @param castType The given cast type. * @param refType The given type of the variable. * @return The variable expression that evaluates to a result of equivalent * value with the cast type. * @exception IllegalActionException */ protected String _generateTypeConvertMethod(String expression, String castType, String refType) throws IllegalActionException { if (castType == null || refType == null || castType.equals(refType)) { if (castType == null && (refType != null && refType.equals("Token") ) && (expression.equals("true") || expression.equals("false"))) { // FIXME: is this right? It is needed by the Case actor in // $PTII/bin/ptcg $PTII/ptolemy/codegen/c/actor/lib/hoc/test/auto/Case5.xml return("Boolean_new(" + expression + ")"); } else { return expression; } } if (castType.length() == 0 ) { throw new IllegalActionException("_generateTypeConvertMethod(\"" + expression + "\", \"" + castType + "\", \"" + refType + "\") called with castType (the 2nd arg) " + "having length 0."); } if (refType.length() == 0 ) { throw new IllegalActionException("_generateTypeConvertMethod(\"" + expression + "\", \"" + castType + "\", \"" + refType + "\") called with refType (the 3rd arg) " + "having length 0."); } expression = "$convert_" + refType + "_" + castType + "(" + expression + ")"; return processCode(expression); } /** * Generate the type conversion statement for the particular offset of the * two given channels. This assumes that the offset is the same for both * channel. Advancing the offset of one has to advance the offset of the * other. * @param source The given source channel. * @param sink The given sink channel. * @param offset The given offset. * @return The type convert statement for assigning the converted source * variable to the sink variable with the given offset. * @exception IllegalActionException If there is a problem getting the * helpers for the ports or if the conversion cannot be handled. */ protected String _generateTypeConvertStatement(Channel source, Channel sink, int offset) throws IllegalActionException { Type sourceType = ((TypedIOPort) source.port).getType(); Type sinkType = ((TypedIOPort) sink.port).getType(); // In a modal model, a refinement may have an output port which is // not connected inside, in this case the type of the port is // unknown and there is no need to generate type conversion code // because there is no token transferred from the port. if (sourceType == BaseType.UNKNOWN) { return ""; } // The references are associated with their own helper, so we need // to find the associated helper. String sourcePortChannel = generateSimpleName(source.port) + "#" + source.channelNumber + ", " + offset; String sourceRef = ((CodeGeneratorHelper) _getHelper(source.port .getContainer())).getReference(sourcePortChannel); String sinkPortChannel = generateSimpleName(sink.port) + "#" + sink.channelNumber + ", " + offset; // For composite actor, generate a variable corresponding to // the inside receiver of an output port. // FIXME: I think checking sink.port.isOutput() is enough here. if (sink.port.getContainer() instanceof CompositeActor && sink.port.isOutput()) { sinkPortChannel = "@" + sinkPortChannel; } String sinkRef = ((CodeGeneratorHelper) _getHelper(sink.port .getContainer())).getReference(sinkPortChannel, true); // When the sink port is contained by a modal controller, it is // possible that the port is both input and output port. we need // to pay special attention. Directly calling getReference() will // treat it as output port and this is not correct. // FIXME: what about offset? if (sink.port.getContainer() instanceof ModalController) { sinkRef = generateName(sink.port); if (sink.port.isMultiport()) { sinkRef = sinkRef + "[" + sink.channelNumber + "]"; } } String result = sourceRef; if (!sinkType.equals(sourceType)) { if (isPrimitive(sinkType)) { result = codeGenType(sourceType) + "to" + codeGenType(sinkType) + "(" + result + ")"; } else if (isPrimitive(sourceType)) { result = "$new(" + codeGenType(sourceType) + "(" + result + "))"; } if (sinkType != BaseType.SCALAR && sinkType != BaseType.GENERAL && !isPrimitive(sinkType)) { if (sinkType instanceof ArrayType) { if (isPrimitive(sourceType)) { result = "$new(" + codeGenType(sinkType) + "(1, 1, " + result + ", TYPE_" + codeGenType(sourceType) + "))"; } // Deep converting for ArrayType. Type elementType = ((ArrayType) sinkType).getElementType(); while (elementType instanceof ArrayType) { elementType = ((ArrayType) elementType) .getElementType(); } if (elementType != BaseType.SCALAR && elementType != BaseType.GENERAL) { result = "$typeFunc(TYPE_" + codeGenType(sinkType) + "::convert(" + result + ", /*CGH*/ TYPE_" + codeGenType(((ArrayType) sinkType) .getElementType()) + "))"; } } else { result = "$typeFunc(TYPE_" + codeGenType(sinkType) + "::convert(" + result + "))"; } } } return sinkRef + " = " + result + ";" + _eol; } /** * Generate the type conversion statements for the two given channels. * @param source The given source channel. * @param sink The given sink channel. * @return The type convert statement for assigning the converted source * variable to the sink variable. * @exception IllegalActionException If there is a problem getting the * helpers for the ports or if the conversion cannot be handled. */ protected String _generateTypeConvertStatements(Channel source, Channel sink) throws IllegalActionException { StringBuffer statements = new StringBuffer(); int rate = Math.max(DFUtilities.getTokenProductionRate(source.port), DFUtilities.getTokenConsumptionRate(source.port)); for (int offset = 0; offset < rate || offset == 0 && rate == 0; offset++) { statements.append(_generateTypeConvertStatement(source, sink, offset)); } return processCode(statements.toString()); } protected String _getCastType(String name) throws IllegalActionException { StringTokenizer tokenizer = new StringTokenizer(name, "#,", true); // Get the referenced name. String refName = tokenizer.nextToken().trim(); // Get the cast type (if any), so we can add the proper convert method. StringTokenizer tokenizer2 = new StringTokenizer(refName, "()", false); if (tokenizer2.countTokens() != 1 && tokenizer2.countTokens() != 2) { throw new IllegalActionException(getComponent(), "Invalid cast type: " + refName); } if (tokenizer2.countTokens() == 2) { String type = tokenizer2.nextToken().trim(); return type.length() > 0 ? type : null; } return null; } /** * Return the channel and offset given in a string. The result is an string * array of length 2. The first element indicates the channel index, and the * second the offset. If either element is an empty string, it means that * channel/offset is not specified. * @param name The given string. * @return An string array of length 2, containing expressions of the * channel index and offset. * @exception IllegalActionException If the channel index or offset * specified in the given string is illegal. */ protected String[] _getChannelAndOffset(String name) throws IllegalActionException { String[] result = { "", "" }; // Given expression of forms: // "port" // "port, offset", or // "port#channel, offset". int poundIndex = _indexOf("#", name, 0); int commaIndex = _indexOf(",", name, 0); if (commaIndex < 0) { commaIndex = name.length(); } if (poundIndex < 0) { poundIndex = commaIndex; } if (poundIndex < commaIndex) { result[0] = name.substring(poundIndex + 1, commaIndex); } if (commaIndex < name.length()) { result[1] = name.substring(commaIndex + 1); } return result; } /** * Return the prototype for fire functions. * @return In this base class, return "()". Derived classes, such as the C * code generator helper might return "(void)". */ protected String _getFireFunctionArguments() { return "()"; } /** * Get the code generator helper associated with the given component. * @param component The given component. * @return The code generator helper. * @exception IllegalActionException If the helper class cannot be found. */ protected ComponentCodeGenerator _getHelper(NamedObj component) throws IllegalActionException { return _codeGenerator._getHelper(component); } protected Object _getHelper(Object object) throws IllegalActionException { return _codeGenerator._getHelper(object); } /** * Return the reference to the specified parameter or port of the associated * actor. For a parameter, the returned string is in the form * "fullName_parameterName". For a port, the returned string is in the form * "fullName_portName[channelNumber][offset]", if any channel number or * offset is given. * @param name The name of the parameter or port * @param isWrite Whether to generate the write or read offset. * @return The reference to that parameter or port (a variable name, for * example). * @exception IllegalActionException If the parameter or port does not exist * or does not have a value. */ protected String _getReference(String name, boolean isWrite) throws IllegalActionException { return ""; } /** * Return the list of corresponding reference channel. This is because a * channel may map to multiple reference channels in the generated code. At * the same time, multiple channels may map to the same reference channel * (e.g. the input-output port pair of a connection). * @param port The specified port. * @param channelNumber The specified channel number. * @param isWrite Whether this is a write or read access. * @return The list of reference channel. * @exception IllegalActionException */ protected List<Channel> _getReferenceChannels(TypedIOPort port, int channelNumber, boolean isWrite) throws IllegalActionException { if (isWrite) { return getSinkChannels(port, channelNumber); } else { ArrayList<Channel> channels = new ArrayList<Channel>(); channels.add(new Channel(port, channelNumber)); return channels; } } /** * Get the set of channels that need to be type converted. * @return Set of channels that need to be type converted. */ protected Set _getTypeConvertChannels() { return _portConversions.keySet(); } /** * Generate a variable reference for the given channel. This varaible * reference is needed for type conversion. The source helper get this * reference instead of using the sink reference directly. This method * assumes the given channel is a source (output) channel. * @param channel The given source channel. * @return The variable reference for the given channel. */ protected String _getTypeConvertReference(Channel channel) { return generateName(channel.port) + "_" + channel.channelNumber; } /** * Return the replacement string of the given macro. Subclass of * CodeGenerator may overriding this method to extend or support a different * set of macros. * @param macro The given macro. * @param parameter The given parameter to the macro. * @return The replacement string of the given macro. * @exception IllegalActionException Thrown if the given macro or parameter * is not valid. */ protected String _replaceMacro(String macro, String parameter) throws IllegalActionException { // $$def(abc) // ==> abc$def(abc) int indexOfDollarSign = macro.indexOf('$'); if (indexOfDollarSign >= 0) { String result = "$" + macro.substring(0, indexOfDollarSign); String innerMacro = macro.substring(indexOfDollarSign + 1, macro .length()); result += _replaceMacro(innerMacro, parameter); return result; } if (macro.equals("get")) { return _replaceGetMacro(parameter); } else if (macro.equals("send")) { return _replaceSendMacro(parameter); } else if (macro.equals("ref")) { return getReference(parameter); } else if (macro.equals("targetType")) { Typeable typeable = _getTypeable(parameter); if (typeable != null) { return targetType(typeable.getType()); } throw new IllegalActionException(parameter + " is not a typeable object. The $targetType() " + "macro takes in a Typeable object."); } else if (macro.equals("elementType") || macro.equals("elementTargetType")) { Typeable typeable = _getTypeable(parameter); if (typeable != null && typeable.getType() instanceof ArrayType) { if (macro.equals("elementType")) { return codeGenType(((ArrayType) typeable.getType()) .getElementType()); } else { return targetType(((ArrayType) typeable.getType()) .getElementType()); } } throw new IllegalActionException(parameter + " is not of ArrayType. The $elementType() " + "macro takes in a ArrayType object."); } else if (macro.equals("type") || macro.equals("cgType")) { String type = ""; if (macro.equals("type")) { type = "TYPE_"; } Typeable typeable = _getTypeable(parameter); if (typeable != null) { return type + codeGenType(typeable.getType()); } throw new IllegalActionException(parameter + " is not a port. $type macro takes in a port."); } else if (macro.equals("val")) { return getParameterValue(parameter, getComponent()); } else if (macro.equals("size")) { return "" + getSize(parameter); } else if (macro.equals("actorSymbol")) { if (parameter.trim().length() == 0) { return generateVariableName(getComponent()); } else { return generateVariableName(getComponent()) + "_" + processCode(parameter); } } else if (macro.equals("actorClass")) { return getComponent().getClassName().replace('.', '_') + "_" + processCode(parameter); // Handle type function macros. } else if (macro.equals("new")) { return getNewInvocation(parameter); } else if (macro.equals("tokenFunc")) { return getFunctionInvocation(parameter, false); } else if (macro.equals("typeFunc")) { return getFunctionInvocation(parameter, true); } else { // Try calling a method defined in the helper first. try { Method method = getClass().getMethod(macro, new Class[0]); return (String) method.invoke(this, new Object[0]); } catch (Exception ex) { // Don't print out error, since this is probably not an user macro. } // Try to treat this as an user macro class. Method handler = null; Method checker = null; Class userMacro = null; try { userMacro = Class.forName("ptolemy.codegen.kernel.userMacro." + macro); handler = userMacro.getMethod("handleMacro", new Class[] { List.class }); checker = userMacro.getMethod("checkArguments", new Class[] { List.class }); } catch (Exception ex) { // Don't print out error, since this is probably not an user macro. return null; } try { checker.invoke(userMacro, new Object[] { parseList(parameter) }); return (String) handler.invoke(userMacro, new Object[] { parseList(parameter) }); } catch (Exception ex) { throw new IllegalActionException(this, ex, "Failed to invoke user macro ($" + macro + ")."); } } } /** * Find the paired close parenthesis given a string and an index which is * the position of an open parenthesis. Return -1 if no paired close * parenthesis is found. * @param string The given string. * @param pos The given index. * @return The index which indicates the position of the paired close * parenthesis of the string. * @exception IllegalActionException If the character at the given position * of the string is not an open parenthesis or if the index is less than 0 * or past the end of the string. */ protected static int _findClosedParen(String string, int pos) throws IllegalActionException { if (pos < 0 || pos >= string.length()) { throw new IllegalActionException("The character index " + pos + " is past the end of string \"" + string + "\", which has a length of " + string.length() + "."); } if (string.charAt(pos) != '(') { throw new IllegalActionException("The character at index " + pos + " of string: " + string + " is not a open parenthesis."); } int nextOpenParen = string.indexOf("(", pos + 1); if (nextOpenParen < 0) { nextOpenParen = string.length(); } int nextCloseParen = string.indexOf(")", pos); if (nextCloseParen < 0) { return -1; } int count = 1; int beginIndex = pos + 1; while (beginIndex > 0) { if (nextCloseParen < nextOpenParen) { count--; if (count == 0) { return nextCloseParen; } beginIndex = nextCloseParen + 1; nextCloseParen = string.indexOf(")", beginIndex); if (nextCloseParen < 0) { return -1; } } if (nextOpenParen < nextCloseParen) { count++; beginIndex = nextOpenParen + 1; nextOpenParen = string.indexOf("(", beginIndex); if (nextOpenParen < 0) { nextOpenParen = string.length(); } } } return -1; } /** * Return a number of spaces that is proportional to the argument. If the * argument is negative or zero, return an empty string. * @param level The level of indenting represented by the spaces. * @return A string with zero or more spaces. */ protected static String _getIndentPrefix(int level) { return StringUtilities.getIndentPrefix(level); } /** * The code generator that contains this helper class. */ protected CodeGenerator _codeGenerator; /** * The code stream associated with this helper. */ protected CodeStream _codeStream = new CodeStream(this); /** * End of line character. Under Unix: "\n", under Windows: "\n\r". We use a * end of line charactor so that the files we generate have the proper end * of line character for use by other native tools. */ protected final static String _eol; /** The parse tree to use with expressions. */ protected ParseTreeCodeGenerator _parseTreeCodeGenerator; /** * A HashMap that contains mapping for ports and their conversion method. * Ports that does not need to be converted do NOT have record in this map. * The codegen kernel record this mapping during the first pass over the * model. This map is used later in the code generation phase. */ protected Hashtable _portConversions = new Hashtable(); /** * A hashset that keeps track of parameters that are referenced for the * associated actor. */ protected HashSet _referencedParameters = new HashSet(); /** * Indent string for indent level 1. * @see ptolemy.util.StringUtilities#getIndentPrefix(int) */ protected final static String _INDENT1 = StringUtilities.getIndentPrefix(1); /** * Indent string for indent level 2. * @see ptolemy.util.StringUtilities#getIndentPrefix(int) */ protected final static String _INDENT2 = StringUtilities.getIndentPrefix(2); /////////////////////////////////////////////////////////////////// //// private variables //// /** The associated object. */ private final Object _object; /** * The code block table that stores the code block body (StringBuffer) with * the code block name (String) as key. */ private static final String[] _defaultBlocks = { "preinitBlock", "initBlock", "fireBlock", "postfireBlock", "wrapupBlock" }; //private boolean printedNullPortWarnings = false; /////////////////////////////////////////////////////////////////// //// private methods //// /** * Generate code for a given block. The comment includes the portion of the * blockName parameter up until the string "Block". * @param blockName The name of the block, which usually ends with the * string "Block". * @return The generated wrapup code. * @exception IllegalActionException If thrown while appending to the the * block or processing the macros. */ private String _generateBlockByName(String blockName) throws IllegalActionException { _codeStream.clear(); _codeStream.appendCodeBlock(blockName, true); // There is no need to generate comment for empty code block. if (!_codeStream.isEmpty()) { // Don't die if the blockName ends not in "Block". String shortBlockName = null; int index = blockName.lastIndexOf("Block"); if (index != -1) { shortBlockName = blockName.substring(0, index); } else { shortBlockName = blockName; } _codeStream.insert(0, _eol + _codeGenerator.comment(shortBlockName + generateSimpleName(getComponent()))); } return processCode(_codeStream.toString()); } /** * Generate the invocation of the fire function of the given component. * @param component The given component. * @return The generated code. */ private static String _generateFireInvocation(NamedObj component) { return generateName(component) + "()"; } /** * Return the actual CodeStream for this Helper. * @return The actual CodeStream. * @exception IllegalActionException If thrown by a called method. */ private CodeStream _getActualCodeStream() throws IllegalActionException { return _getActualCodeStream(getComponent(), _codeGenerator); } /** * Return the position of the first occurence of the "&" sign in the given * code string, starting from the given from position. If the "&" sign found * is escaped by "\\", it will be ignored. * @param code The given code string. * @param from The given position to start searching from. * @return The next position of the "&" sign. */ private int _getMacroStartIndex(String code, int from) { int position = from - 1; do { position = code.indexOf("$", position + 1); } while (position > 0 && code.charAt(position - 1) == '\\'); return position; } private String _getRefName(String name) throws IllegalActionException { StringTokenizer tokenizer = new StringTokenizer(name, "#,", true); if (tokenizer.countTokens() != 1 && tokenizer.countTokens() != 3 && tokenizer.countTokens() != 5) { throw new IllegalActionException(getComponent(), "Reference not found: " + name); } // Get the referenced name. String refName = tokenizer.nextToken().trim(); // Get the cast type (if any), so we can add the proper convert method. StringTokenizer tokenizer2 = new StringTokenizer(refName, "()", false); if (tokenizer2.countTokens() != 1 && tokenizer2.countTokens() != 2) { throw new IllegalActionException(getComponent(), "Invalid cast type: " + refName); } if (tokenizer2.countTokens() == 2) { // castType tokenizer2.nextToken(); } return tokenizer2.nextToken().trim(); } private String _getRefType(Attribute attribute) { if (attribute instanceof Parameter) { return codeGenType(((Parameter) attribute).getType()); } return null; } /** * If the object name is a Port or Variable, return its Port * or Variable, otherwise return null. * @param objectName The object name * @return Either the Port, Variable or null. */ private Typeable _getTypeable(String objectName) { TypedIOPort port = getPort(objectName); if (port != null) { return port; } Variable variable = _getVariable(objectName); if (variable != null) { return variable; } return null; } /** * Get the list of sink channels that the given source channel needs to be * type converted to. * @param source The given source channel. * @return List of sink channels that the given source channel needs to be * type converted to. */ private List _getTypeConvertSinkChannels(Channel source) { if (_portConversions.containsKey(source)) { return (List) _portConversions.get(source); } return new ArrayList(); } /*************************************************************************** * Return a variable that matches the given label. Null is returned, if no * such variable cannot found. * @param refName The given label. * @return A variable that matches the given label, or null if no such * variable is found. */ private Variable _getVariable(String refName) { NamedObj actor = (NamedObj) _object; for (Object attribute : actor.attributeList()) { if (attribute instanceof Variable) { if (generateSimpleName(((Variable) attribute)).equals(refName)) { return (Variable) attribute; } } } return null; } /** * Mark the given connection between the source and the sink channels as * type conversion required. * @param source The given source channel. * @param sink The given input channel. */ private void _markTypeConvert(Channel source, Channel sink) { List sinks; if (_portConversions.containsKey(source)) { sinks = (List) _portConversions.get(source); } else { sinks = new ArrayList(); _portConversions.put(source, sinks); } sinks.add(sink); } private String _replaceGetMacro(String parameter) throws IllegalActionException { // e.g. $get(0, input); List<String> parameters = parseList(parameter); TypedIOPort port = null; String channel = ""; if (parameters.size() == 2) { port = getPort(parameters.get(0)); channel = parameters.get(1); } if (port == null || channel.length() == 0) { throw new IllegalActionException(parameter + " is not acceptable by $get(). " + "The $get macro takes in as arguments " + "a channelNumber, and a port (e.g. $get(0, output)."); } PortCodeGenerator portHelper = (PortCodeGenerator) _getHelper(port); return portHelper.generateCodeForGet(channel); } private String _replaceSendMacro(String parameter) throws IllegalActionException { // e.g. $send(input, 0, token); List<String> parameters = parseList(parameter); TypedIOPort port = null; String channel = ""; String dataToken = ""; port = getPort(parameters.get(0)); channel = parameters.get(1); if (port == null || channel.length() == 0) { throw new IllegalActionException( parameter + " is not acceptable by $send(). " + "The $send macro takes in as arguments " + "a channelNumber, port, and data (e.g. $send(0, output, 45)."); } if (parameters.size() == 2) { dataToken = processCode("$ref(" + generateSimpleName(port) + "#" + channel + ")"); } else if (parameters.size() == 3) { dataToken = parameters.get(2); } PortCodeGenerator portHelper = (PortCodeGenerator) _getHelper(port); return portHelper.generateCodeForSend(channel, dataToken); } /** * Return the actual CodeStream associated with the given Actor and * CodeGenerator. Generally, this will come from the Actor's template file, * but EmbeddedCActors get their code from the embeddedCCode parameter. * @param namedObj The actor whose code to return. * @param codeGenerator The actor's CodeGenerator. * @return The actor's actual CodeStream. * @exception IllegalActionException If thrown when getting the actor's * helper. */ private static CodeStream _getActualCodeStream(NamedObj namedObj, CodeGenerator codeGenerator) throws IllegalActionException { CodeGeneratorHelper helper = null; CodeStream codeStream = null; if (namedObj != null && namedObj instanceof ptolemy.actor.lib.jni.EmbeddedCActor) { helper = (CodeGeneratorHelper) codeGenerator ._getHelper(codeGenerator.getContainer()); codeStream = new CodeStream(helper); // We have an EmbeddedCActor, read the codeBlocks from // the embeddedCCode parameter. codeStream .setCodeBlocks(((ptolemy.actor.lib.jni.EmbeddedCActor) namedObj).embeddedCCode .getExpression()); } else { helper = (CodeGeneratorHelper) codeGenerator._getHelper(namedObj); codeStream = new CodeStream(helper); } return codeStream; } static { _eol = StringUtilities.getProperty("line.separator"); } private String _fireCode = ""; }