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 <entity name="exp" class="ptolemy.actor.lib.Expression"> 079 <port name="in" class="ptolemy.actor.TypedIOPort"> 080 <property name="input"/> 081 </port> 082 </entity> 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}