/* An actor that evaluates expressions. Copyright (c) 1998-2014 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.actor.lib; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import ptolemy.actor.IOPort; import ptolemy.actor.TypedAtomicActor; import ptolemy.actor.TypedIOPort; import ptolemy.data.DoubleToken; import ptolemy.data.IntToken; 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.ParseTreeFreeVariableCollector; import ptolemy.data.expr.ParseTreeTypeInference; import ptolemy.data.expr.PtParser; import ptolemy.data.expr.Variable; import ptolemy.data.type.BaseType; import ptolemy.data.type.MonotonicFunction; import ptolemy.data.type.Type; import ptolemy.data.type.TypeConstant; import ptolemy.graph.InequalityTerm; import ptolemy.kernel.CompositeEntity; import ptolemy.kernel.util.Attribute; import ptolemy.kernel.util.IllegalActionException; import ptolemy.kernel.util.NameDuplicationException; import ptolemy.kernel.util.Settable; import ptolemy.kernel.util.StringAttribute; import ptolemy.kernel.util.Workspace; /////////////////////////////////////////////////////////////////// //// Expression /**
On each firing, evaluate an expression that may include references to the inputs, current time, and a count of the firing. The ports are referenced by the identifiers that have the same name as the port. To use this class, instantiate it, then add ports (instances of TypedIOPort). In vergil, you can add ports by right clicking on the icon and selecting "Configure Ports". In MoML you can add ports by just including ports of class TypedIOPort, set to be inputs, as in the following example:
<entity name="exp" class="ptolemy.actor.lib.Expression"> <port name="in" class="ptolemy.actor.TypedIOPort"> <property name="input"/> </port> </entity>
This actor is type-polymorphic. The types of the inputs can be arbitrary and the types of the outputs are inferred from the expression based on the types inferred for the inputs.
The expression parameter specifies an expression that can refer to the inputs by name. By default, the expression is empty, and attempting to execute the actor without setting it triggers an exception. This actor can be used instead of many of the arithmetic actors, such as AddSubtract, MultiplyDivide, and TrigFunction. However, those actors will be usually be more efficient, and sometimes more convenient to use.
The expression language understood by this actor is the same as that used to set any parameter value, with the exception that the expressions evaluated by this actor can refer to the values of inputs, and to the current time by the identifier name "time", and to the current iteration count by the identifier named "iteration."
This actor requires its all of its inputs to be present. If inputs are not all present, then an exception will be thrown.
NOTE: There are a number of limitations in the current implementation. Primarily, multiports are not supported.
@author Xiaojun Liu, Edward A. Lee, Steve Neuendorffer @version $Id: Expression.java 70398 2014-10-22 23:44:32Z cxh $ @since Ptolemy II 0.2 @Pt.ProposedRating Green (neuendor) @Pt.AcceptedRating Green (neuendor) */ public class Expression extends TypedAtomicActor { /** Construct an actor with the given container and name. * @param container The container. * @param name The name of this actor. * @exception IllegalActionException If the actor cannot be contained * by the proposed container. * @exception NameDuplicationException If the container already has an * actor with this name. */ public Expression(CompositeEntity container, String name) throws NameDuplicationException, IllegalActionException { super(container, name); output = new TypedIOPort(this, "output", false, true); expression = new StringAttribute(this, "expression"); expression.setExpression(""); // Do not show parameter value even if the preference // "_showParameters" has value "Overridden parameters only". Parameter hide = new Parameter(expression, "_hide"); hide.setExpression("true"); // Prevent the extra Configure button that would appear // to change this. hide.setVisibility(Settable.NONE); _setOutputTypeConstraint(); } /////////////////////////////////////////////////////////////////// //// ports and parameters //// /** The output port. */ public TypedIOPort output; /** The expression that is evaluated to produce the output. */ public StringAttribute expression; /////////////////////////////////////////////////////////////////// //// public methods //// /** React to a change in the value of an attribute. * @param attribute The attribute whose type changed. * @exception IllegalActionException Not thrown in this base class. */ @Override public void attributeChanged(Attribute attribute) throws IllegalActionException { if (attribute == expression) { _parseTree = null; } } /** Clone the actor into the specified workspace. This calls the * base class and then creates new ports and parameters. * @param workspace The workspace for the new object. * @return A new actor. * @exception CloneNotSupportedException If a derived class contains * an attribute that cannot be cloned. */ @Override public Object clone(Workspace workspace) throws CloneNotSupportedException { Expression newObject = (Expression) super.clone(workspace); newObject._iterationCount = 1; newObject._parseTree = null; newObject._parseTreeEvaluator = null; newObject._scope = null; newObject._setOutputTypeConstraint(); newObject._tokenMap = null; return newObject; } /** Evaluate the expression and send its result to the output. * @exception IllegalActionException If the evaluation of the expression * triggers it, or the evaluation yields a null result, or the evaluation * yields an incompatible type, or if there is no director, or if a * connected input has no tokens. */ @Override public void fire() throws IllegalActionException { super.fire(); Iterator inputPorts = inputPortList().iterator(); while (inputPorts.hasNext()) { IOPort port = (IOPort) inputPorts.next(); // FIXME: Handle multiports if (port.isOutsideConnected()) { if (port.hasToken(0)) { Token inputToken = port.get(0); _tokenMap.put(port.getName(), inputToken); } else { throw new IllegalActionException(this, "Input port " + port.getName() + " has no data."); } } } try { // Note: this code parallels code in the OutputTypeFunction class // below. if (_parseTree == null) { // Note that the parser is NOT retained, since in most // cases the expression doesn't change, and the parser // requires a large amount of memory. PtParser parser = new PtParser(); _parseTree = parser.generateParseTree(expression .getExpression()); } if (_parseTreeEvaluator == null) { _parseTreeEvaluator = new ParseTreeEvaluator(); } if (_scope == null) { _scope = new VariableScope(); } _result = _parseTreeEvaluator.evaluateParseTree(_parseTree, _scope); } catch (Throwable throwable) { // Chain exceptions to get the actor that threw the exception. // Note that if evaluateParseTree does a divide by zero, we // need to catch an ArithmeticException here. throw new IllegalActionException(this, throwable, "Expression invalid."); } if (_result == null) { throw new IllegalActionException(this, "Expression yields a null result: " + expression.getExpression()); } output.send(0, _result); } /** Initialize the iteration count to 1. * @exception IllegalActionException If the parent class throws it. */ @Override public void initialize() throws IllegalActionException { super.initialize(); if (getPort("time") != null) { throw new IllegalActionException( this, "This actor has a port named \"time\", " + "which will not be read, instead the " + "reserved system variable \"time\" will be read. " + "Delete the \"time\" port to avoid this message."); } if (getPort("iteration") != null) { throw new IllegalActionException( this, "This actor has a port named \"iteration\", " + "which will not be read, instead the " + "reserved system variable \"iteration\" will be read. " + "Delete the \"iteration\" port to avoid this message."); } _iterationCount = 1; } /** Increment the iteration count. * @exception IllegalActionException If the superclass throws it. */ @Override public boolean postfire() throws IllegalActionException { _iterationCount++; // This actor never requests termination, but _stopRequested // might have been set elsewhere. return super.postfire(); } /** Prefire this actor. Return false if an input port has no * data, otherwise return true. * @exception IllegalActionException If the superclass throws it. */ @Override public boolean prefire() throws IllegalActionException { Iterator inputPorts = inputPortList().iterator(); while (inputPorts.hasNext()) { IOPort port = (IOPort) inputPorts.next(); // FIXME: Handle multiports if (port.isOutsideConnected()) { if (!port.hasToken(0)) { return false; } } } return super.prefire(); } /** Preinitialize this actor. */ @Override public void preinitialize() throws IllegalActionException { super.preinitialize(); _tokenMap = new HashMap