001/* An actor that evaluates expressions.
002
003 Copyright (c) 1998-2014 The Regents of the University of California.
004 All rights reserved.
005 Permission is hereby granted, without written agreement and without
006 license or royalty fees, to use, copy, modify, and distribute this
007 software and its documentation for any purpose, provided that the above
008 copyright notice and the following two paragraphs appear in all copies
009 of this software.
010
011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
015 SUCH DAMAGE.
016
017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022 ENHANCEMENTS, OR MODIFICATIONS.
023
024 PT_COPYRIGHT_VERSION_2
025 COPYRIGHTENDKEY
026
027 */
028package ptolemy.actor.lib;
029
030import java.util.HashMap;
031import java.util.Iterator;
032import java.util.LinkedList;
033import java.util.List;
034import java.util.Map;
035import java.util.Set;
036
037import ptolemy.actor.IOPort;
038import ptolemy.actor.TypedAtomicActor;
039import ptolemy.actor.TypedIOPort;
040import ptolemy.data.DoubleToken;
041import ptolemy.data.IntToken;
042import ptolemy.data.Token;
043import ptolemy.data.expr.ASTPtRootNode;
044import ptolemy.data.expr.ModelScope;
045import ptolemy.data.expr.Parameter;
046import ptolemy.data.expr.ParseTreeEvaluator;
047import ptolemy.data.expr.ParseTreeFreeVariableCollector;
048import ptolemy.data.expr.ParseTreeTypeInference;
049import ptolemy.data.expr.PtParser;
050import ptolemy.data.expr.Variable;
051import ptolemy.data.type.BaseType;
052import ptolemy.data.type.MonotonicFunction;
053import ptolemy.data.type.Type;
054import ptolemy.data.type.TypeConstant;
055import ptolemy.graph.InequalityTerm;
056import ptolemy.kernel.CompositeEntity;
057import ptolemy.kernel.util.Attribute;
058import ptolemy.kernel.util.IllegalActionException;
059import ptolemy.kernel.util.NameDuplicationException;
060import ptolemy.kernel.util.Settable;
061import ptolemy.kernel.util.StringAttribute;
062import ptolemy.kernel.util.Workspace;
063
064///////////////////////////////////////////////////////////////////
065//// Expression
066
067/**
068 <p>On each firing, evaluate an expression that may include references
069 to the inputs, current time, and a count of the firing.  The ports
070 are referenced by the identifiers that have the same name as the
071 port.  To use this class, instantiate it, then add ports (instances
072 of TypedIOPort).  In vergil, you can add ports by right clicking on
073 the icon and selecting "Configure Ports".  In MoML you can add
074 ports by just including ports of class TypedIOPort, set to be
075 inputs, as in the following example:</p>
076
077 <pre>
078 &lt;entity name="exp" class="ptolemy.actor.lib.Expression"&gt;
079 &lt;port name="in" class="ptolemy.actor.TypedIOPort"&gt;
080 &lt;property name="input"/&gt;
081 &lt;/port&gt;
082 &lt;/entity&gt;
083 </pre>
084
085 <p> This actor is type-polymorphic.  The types of the inputs can be
086 arbitrary and the types of the outputs are inferred from the
087 expression based on the types inferred for the inputs.</p>
088
089 <p> The <i>expression</i> parameter specifies an expression that
090 can refer to the inputs by name.  By default, the expression is
091 empty, and attempting to execute the actor without setting it
092 triggers an exception.  This actor can be used instead of many of
093 the arithmetic actors, such as AddSubtract, MultiplyDivide, and
094 TrigFunction.  However, those actors will be usually be more
095 efficient, and sometimes more convenient to use.</p>
096
097 <p> The expression language understood by this actor is the same as
098 <a href="../../../../expressions.htm">that used to set any
099 parameter value</a>, with the exception that the expressions
100 evaluated by this actor can refer to the values of inputs, and to
101 the current time by the identifier name "time", and to the current
102 iteration count by the identifier named "iteration."</p>
103
104 <p> This actor requires its all of its inputs to be present.  If
105 inputs are not all present, then an exception will be thrown.</p>
106
107 <p> NOTE: There are a number of limitations in the current
108 implementation.  Primarily, multiports are not supported.</p>
109
110 @author Xiaojun Liu, Edward A. Lee, Steve Neuendorffer
111 @version $Id$
112 @since Ptolemy II 0.2
113 @Pt.ProposedRating Green (neuendor)
114 @Pt.AcceptedRating Green (neuendor)
115 */
116public class Expression extends TypedAtomicActor {
117    /** Construct an actor with the given container and name.
118     *  @param container The container.
119     *  @param name The name of this actor.
120     *  @exception IllegalActionException If the actor cannot be contained
121     *   by the proposed container.
122     *  @exception NameDuplicationException If the container already has an
123     *   actor with this name.
124     */
125    public Expression(CompositeEntity container, String name)
126            throws NameDuplicationException, IllegalActionException {
127        super(container, name);
128
129        output = new TypedIOPort(this, "output", false, true);
130        expression = new StringAttribute(this, "expression");
131        expression.setExpression("");
132
133        // Do not show parameter value even if the preference
134        // "_showParameters" has value "Overridden parameters only".
135        Parameter hide = new Parameter(expression, "_hide");
136        hide.setExpression("true");
137        // Prevent the extra Configure button that would appear
138        // to change this.
139        hide.setVisibility(Settable.NONE);
140
141        _setOutputTypeConstraint();
142    }
143
144    ///////////////////////////////////////////////////////////////////
145    ////                     ports and parameters                  ////
146
147    /** The output port. */
148    public TypedIOPort output;
149
150    /** The expression that is evaluated to produce the output.
151     */
152    public StringAttribute expression;
153
154    ///////////////////////////////////////////////////////////////////
155    ////                         public methods                    ////
156
157    /** React to a change in the value of an attribute.
158     *  @param attribute The attribute whose type changed.
159     *  @exception IllegalActionException Not thrown in this base class.
160     */
161    @Override
162    public void attributeChanged(Attribute attribute)
163            throws IllegalActionException {
164        if (attribute == expression) {
165            _parseTree = null;
166        }
167    }
168
169    /** Clone the actor into the specified workspace. This calls the
170     *  base class and then creates new ports and parameters.
171     *  @param workspace The workspace for the new object.
172     *  @return A new actor.
173     *  @exception CloneNotSupportedException If a derived class contains
174     *   an attribute that cannot be cloned.
175     */
176    @Override
177    public Object clone(Workspace workspace) throws CloneNotSupportedException {
178        Expression newObject = (Expression) super.clone(workspace);
179        newObject._iterationCount = 1;
180        newObject._parseTree = null;
181        newObject._parseTreeEvaluator = null;
182        newObject._scope = null;
183        newObject._setOutputTypeConstraint();
184        newObject._tokenMap = null;
185        return newObject;
186    }
187
188    /** Evaluate the expression and send its result to the output.
189     *  @exception IllegalActionException If the evaluation of the expression
190     *   triggers it, or the evaluation yields a null result, or the evaluation
191     *   yields an incompatible type, or if there is no director, or if a
192     *   connected input has no tokens.
193     */
194    @Override
195    public void fire() throws IllegalActionException {
196        super.fire();
197        Iterator inputPorts = inputPortList().iterator();
198
199        while (inputPorts.hasNext()) {
200            IOPort port = (IOPort) inputPorts.next();
201
202            // FIXME: Handle multiports
203            if (port.isOutsideConnected()) {
204                if (port.hasToken(0)) {
205                    Token inputToken = port.get(0);
206                    _tokenMap.put(port.getName(), inputToken);
207                } else {
208                    throw new IllegalActionException(this,
209                            "Input port " + port.getName() + " has no data.");
210                }
211            }
212        }
213
214        try {
215            // Note: this code parallels code in the OutputTypeFunction class
216            // below.
217            if (_parseTree == null) {
218                // Note that the parser is NOT retained, since in most
219                // cases the expression doesn't change, and the parser
220                // requires a large amount of memory.
221                PtParser parser = new PtParser();
222                _parseTree = parser
223                        .generateParseTree(expression.getExpression());
224            }
225
226            if (_parseTreeEvaluator == null) {
227                _parseTreeEvaluator = new ParseTreeEvaluator();
228            }
229
230            if (_scope == null) {
231                _scope = new VariableScope();
232            }
233
234            _result = _parseTreeEvaluator.evaluateParseTree(_parseTree, _scope);
235        } catch (Throwable throwable) {
236            // Chain exceptions to get the actor that threw the exception.
237            // Note that if evaluateParseTree does a divide by zero, we
238            // need to catch an ArithmeticException here.
239            throw new IllegalActionException(this, throwable,
240                    "Expression invalid.");
241        }
242
243        if (_result == null) {
244            throw new IllegalActionException(this,
245                    "Expression yields a null result: "
246                            + expression.getExpression());
247        }
248
249        output.send(0, _result);
250    }
251
252    /** Initialize the iteration count to 1.
253     *  @exception IllegalActionException If the parent class throws it.
254     */
255    @Override
256    public void initialize() throws IllegalActionException {
257        super.initialize();
258        if (getPort("time") != null) {
259            throw new IllegalActionException(this,
260                    "This actor has a port named \"time\", "
261                            + "which will not be read, instead the "
262                            + "reserved system variable \"time\" will be read. "
263                            + "Delete the \"time\" port to avoid this message.");
264        }
265        if (getPort("iteration") != null) {
266            throw new IllegalActionException(this,
267                    "This actor has a port named \"iteration\", "
268                            + "which will not be read, instead the "
269                            + "reserved system variable \"iteration\" will be read. "
270                            + "Delete the \"iteration\" port to avoid this message.");
271        }
272        _iterationCount = 1;
273    }
274
275    /** Increment the iteration count.
276     *  @exception IllegalActionException If the superclass throws it.
277     */
278    @Override
279    public boolean postfire() throws IllegalActionException {
280        _iterationCount++;
281
282        // This actor never requests termination, but _stopRequested
283        // might have been set elsewhere.
284        return super.postfire();
285    }
286
287    /** Prefire this actor.  Return false if an input port has no
288     *  data, otherwise return true.
289     *  @exception IllegalActionException If the superclass throws it.
290     */
291    @Override
292    public boolean prefire() throws IllegalActionException {
293        Iterator inputPorts = inputPortList().iterator();
294
295        while (inputPorts.hasNext()) {
296            IOPort port = (IOPort) inputPorts.next();
297
298            // FIXME: Handle multiports
299            if (port.isOutsideConnected()) {
300                if (!port.hasToken(0)) {
301                    return false;
302                }
303            }
304        }
305
306        return super.prefire();
307    }
308
309    /** Preinitialize this actor.
310     */
311    @Override
312    public void preinitialize() throws IllegalActionException {
313        super.preinitialize();
314        _tokenMap = new HashMap<String, Token>();
315    }
316
317    ///////////////////////////////////////////////////////////////////
318    ////                         protected variables               ////
319
320    /** Variable storing the result of the expression evaluation so that
321     *  subclasses can access it in an overridden fire() method.
322     */
323    protected Token _result;
324
325    /** Map from input port name to input value.
326     *  The fire() method populates this map.
327     *  This is protected so that if a subclass overrides fire(), it
328     *  can determine the values of the inputs.
329     */
330    protected Map<String, Token> _tokenMap;
331
332    ///////////////////////////////////////////////////////////////////
333    ////                         private methods                   ////
334    // Add a constraint to the type output port of this object.
335    private void _setOutputTypeConstraint() {
336        output.setTypeAtLeast(new OutputTypeFunction());
337    }
338
339    private class VariableScope extends ModelScope {
340        /** Look up and return the attribute with the specified name in the
341         *  scope. Return null if such an attribute does not exist.
342         *  @return The attribute with the specified name in the scope.
343         */
344        @Override
345        public Token get(String name) throws IllegalActionException {
346            if (name.equals("time")) {
347                return new DoubleToken(
348                        getDirector().getModelTime().getDoubleValue());
349            } else if (name.equals("iteration")) {
350                return new IntToken(_iterationCount);
351            }
352
353            Token token = _tokenMap.get(name);
354
355            if (token != null) {
356                return token;
357            }
358
359            Variable result = getScopedVariable(null, Expression.this, name);
360
361            if (result != null) {
362                return result.getToken();
363            }
364
365            return null;
366        }
367
368        /** Look up and return the type of the attribute with the
369         *  specified name in the scope. Return null if such an
370         *  attribute does not exist.
371         *  @return The attribute with the specified name in the scope.
372         */
373        @Override
374        public Type getType(String name) throws IllegalActionException {
375            if (name.equals("time")) {
376                return BaseType.DOUBLE;
377            } else if (name.equals("iteration")) {
378                return BaseType.INT;
379            }
380
381            // Check the port names.
382            TypedIOPort port = (TypedIOPort) getPort(name);
383
384            if (port != null) {
385                return port.getType();
386            }
387
388            Variable result = getScopedVariable(null, Expression.this, name);
389
390            if (result != null) {
391                return (Type) result.getTypeTerm().getValue();
392            }
393
394            return null;
395        }
396
397        /** Look up and return the type term for the specified name
398         *  in the scope. Return null if the name is not defined in this
399         *  scope, or is a constant type.
400         *  @return The InequalityTerm associated with the given name in
401         *  the scope.
402         *  @exception IllegalActionException If a value in the scope
403         *  exists with the given name, but cannot be evaluated.
404         */
405        @Override
406        public ptolemy.graph.InequalityTerm getTypeTerm(String name)
407                throws IllegalActionException {
408            if (name.equals("time")) {
409                return new TypeConstant(BaseType.DOUBLE);
410            } else if (name.equals("iteration")) {
411                return new TypeConstant(BaseType.INT);
412            }
413
414            // Check the port names.
415            TypedIOPort port = (TypedIOPort) getPort(name);
416
417            if (port != null) {
418                return port.getTypeTerm();
419            }
420
421            Variable result = getScopedVariable(null, Expression.this, name);
422
423            if (result != null) {
424                return result.getTypeTerm();
425            }
426
427            return null;
428        }
429
430        /** Return the list of identifiers within the scope.
431         *  @return The list of identifiers within the scope.
432         */
433        @Override
434        public Set identifierSet() {
435            return getAllScopedVariableNames(null, Expression.this);
436        }
437    }
438
439    // This class implements a monotonic function of the type of
440    // the output port.
441    // The function value is determined by type inference on the
442    // expression, in the scope of this Expression actor.
443    private class OutputTypeFunction extends MonotonicFunction {
444        ///////////////////////////////////////////////////////////////
445        ////                       public inner methods            ////
446
447        /** Return the function result.
448         *  @return A Type.
449         *  @exception IllegalActionException If inferring types for the
450         *  expression fails.
451         */
452        @Override
453        public Object getValue() throws IllegalActionException {
454            try {
455                // Deal with the singularity at UNKNOWN..  Assume that if
456                // any variable that the expression depends on is UNKNOWN,
457                // then the type of the whole expression is unknown..
458                // This allows us to properly find functions that do exist
459                // (but not for UNKNOWN arguments), and to give good error
460                // messages when functions are not found.
461                InequalityTerm[] terms = getVariables();
462
463                for (InequalityTerm term : terms) {
464                    if (term != this && term.getValue() == BaseType.UNKNOWN) {
465                        return BaseType.UNKNOWN;
466                    }
467                }
468
469                // Note: This code is similar to the token evaluation
470                // code above.
471                if (_parseTree == null) {
472                    // Note that the parser is NOT retained, since in most
473                    // cases the expression doesn't change, and the parser
474                    // requires a large amount of memory.
475                    PtParser parser = new PtParser();
476                    _parseTree = parser
477                            .generateParseTree(expression.getExpression());
478                }
479
480                if (_scope == null) {
481                    _scope = new VariableScope();
482                }
483
484                Type type = _typeInference.inferTypes(_parseTree, _scope);
485                return type;
486            } catch (Exception ex) {
487                throw new IllegalActionException(Expression.this, ex,
488                        "An error occurred during expression type inference of \""
489                                + expression.getExpression() + "\".");
490            }
491        }
492
493        /** Return the type variable in this inequality term. If the type
494         *  of input ports are not declared, return an one element array
495         *  containing the inequality term representing the type of the port;
496         *  otherwise, return an empty array.
497         *  @return An array of InequalityTerm.
498         */
499        @Override
500        public InequalityTerm[] getVariables() {
501            // Return an array that contains type terms for all of the
502            // inputs and all of the parameters that are free variables for
503            // the expression.
504            try {
505                if (_parseTree == null) {
506                    PtParser parser = new PtParser();
507                    _parseTree = parser
508                            .generateParseTree(expression.getExpression());
509                }
510
511                if (_scope == null) {
512                    _scope = new VariableScope();
513                }
514
515                Set set = _variableCollector.collectFreeVariables(_parseTree,
516                        _scope);
517                List termList = new LinkedList();
518
519                for (Iterator elements = set.iterator(); elements.hasNext();) {
520                    String name = (String) elements.next();
521                    InequalityTerm term = _scope.getTypeTerm(name);
522
523                    if (term != null && term.isSettable()) {
524                        termList.add(term);
525                    }
526                }
527
528                return (InequalityTerm[]) termList
529                        .toArray(new InequalityTerm[termList.size()]);
530            } catch (IllegalActionException ex) {
531                return new InequalityTerm[0];
532            }
533        }
534
535        /** Override the base class to give a description of this term.
536         *  @return A description of this term.
537         */
538        @Override
539        public String getVerboseString() {
540            return expression.getExpression();
541        }
542
543        ///////////////////////////////////////////////////////////////
544        ////                       private inner variable          ////
545        private ParseTreeTypeInference _typeInference = new ParseTreeTypeInference();
546
547        private ParseTreeFreeVariableCollector _variableCollector = new ParseTreeFreeVariableCollector();
548    }
549
550    ///////////////////////////////////////////////////////////////////
551    ////                         private variables                 ////
552    private int _iterationCount = 1;
553
554    private ASTPtRootNode _parseTree = null;
555
556    private ParseTreeEvaluator _parseTreeEvaluator = null;
557
558    private VariableScope _scope = null;
559}