001/* A visitor for parse trees of the expression language. 002 003 Copyright (c) 1998-2018 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 OR RESEARCH IN MOTION 012 LIMITED BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, 013 INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS 014 SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA 015 OR RESEARCH IN MOTION LIMITED HAVE BEEN ADVISED OF THE POSSIBILITY OF 016 SUCH DAMAGE. 017 018 THE UNIVERSITY OF CALIFORNIA AND RESEARCH IN MOTION LIMITED 019 SPECIFICALLY DISCLAIM ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 020 THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 021 PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" 022 BASIS, AND THE UNIVERSITY OF CALIFORNIA AND RESEARCH IN MOTION 023 LIMITED HAVE NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 024 ENHANCEMENTS, OR MODIFICATIONS. 025 026 027 */ 028package ptolemy.data.expr; 029 030import java.lang.reflect.Field; 031import java.lang.reflect.InvocationTargetException; 032import java.lang.reflect.Method; 033import java.lang.reflect.Modifier; 034import java.util.HashSet; 035import java.util.Iterator; 036import java.util.List; 037import java.util.Set; 038 039import ptolemy.data.ArrayToken; 040import ptolemy.data.BitwiseOperationToken; 041import ptolemy.data.BooleanToken; 042import ptolemy.data.FunctionToken; 043import ptolemy.data.IntToken; 044import ptolemy.data.MatrixToken; 045import ptolemy.data.ObjectToken; 046import ptolemy.data.OrderedRecordToken; 047import ptolemy.data.PartiallyOrderedToken; 048import ptolemy.data.RecordToken; 049import ptolemy.data.ScalarToken; 050import ptolemy.data.StringToken; 051import ptolemy.data.UnionToken; 052import ptolemy.data.type.FunctionType; 053import ptolemy.data.type.Type; 054import ptolemy.data.type.TypeLattice; 055import ptolemy.kernel.CompositeEntity; 056import ptolemy.kernel.Entity; 057import ptolemy.kernel.util.IllegalActionException; 058import ptolemy.kernel.util.InternalErrorException; 059import ptolemy.kernel.util.NamedObj; 060 061/////////////////////////////////////////////////////////////////// 062//// ParseTreeEvaluator 063 064/** 065 This class evaluates a parse tree given a reference to its root node. 066 It implements a visitor that visits the parse tree in depth-first order, 067 evaluating each node and storing the result as a token in the node. 068 Two exceptions are logic nodes and the ternary if node (the ? : construct), 069 which do not necessarily evaluate all children nodes. 070 071 @author Steve Neuendorffer 072 @version $Id$ 073 @since Ptolemy II 2.1 074 @Pt.ProposedRating Green (neuendor) 075 @Pt.AcceptedRating Yellow (neuendor) 076 @see ptolemy.data.expr.ASTPtRootNode 077 */ 078public class ParseTreeEvaluator extends AbstractParseTreeVisitor { 079 /////////////////////////////////////////////////////////////////// 080 //// public methods //// 081 082 /** Evaluate the parse tree with the specified root node. 083 * @param node The root of the parse tree. 084 * @return The result of evaluation. 085 * @exception IllegalActionException If an evaluation error occurs. 086 */ 087 public ptolemy.data.Token evaluateParseTree(ASTPtRootNode node) 088 throws IllegalActionException { 089 return evaluateParseTree(node, null); 090 } 091 092 /** Evaluate the parse tree with the specified root node using 093 * the specified scope to resolve the values of variables. 094 * @param node The root of the parse tree. 095 * @param scope The scope for evaluation. 096 * @return The result of evaluation. 097 * @exception IllegalActionException If an error occurs during 098 * evaluation. 099 */ 100 public ptolemy.data.Token evaluateParseTree(ASTPtRootNode node, 101 ParserScope scope) throws IllegalActionException { 102 _scope = scope; 103 104 // Evaluate the value of the root node. 105 node.visit(this); 106 107 // and return it. 108 _scope = null; 109 return _evaluatedChildToken; 110 } 111 112 /** Trace the evaluation of the parse tree with the specified root 113 * node using the specified scope to resolve the values of 114 * variables. 115 * @param node The root of the parse tree. 116 * @param scope The scope for evaluation. 117 * @return The trace of the evaluation. 118 * @exception IllegalActionException If an error occurs during 119 * evaluation. 120 */ 121 public String traceParseTreeEvaluation(ASTPtRootNode node, 122 ParserScope scope) throws IllegalActionException { 123 _scope = scope; 124 _trace = new StringBuffer(); 125 _depth = 0; 126 _traceEnter(node); 127 128 try { 129 // Evaluate the value of the root node. 130 node.visit(this); 131 _traceLeave(node); 132 } catch (Exception ex) { 133 // If an exception occurs, then bind the exception into 134 // the trace and return the trace. 135 _trace(ex.toString()); 136 } 137 138 _scope = null; 139 140 // Return the trace. 141 String trace = _trace.toString(); 142 _trace = null; 143 return trace; 144 } 145 146 /** Construct an ArrayToken that contains the tokens from the 147 * children of the specified node. 148 * @param node The specified node. 149 * @exception IllegalActionException If an evaluation error occurs. 150 */ 151 @Override 152 public void visitArrayConstructNode(ASTPtArrayConstructNode node) 153 throws IllegalActionException { 154 if (node.isConstant() && node.isEvaluated()) { 155 _evaluatedChildToken = node.getToken(); 156 return; 157 } 158 159 ptolemy.data.Token[] tokens = _evaluateAllChildren(node); 160 161 if (tokens.length == 0) { 162 _evaluatedChildToken = ArrayToken.NIL; 163 node.setToken(_evaluatedChildToken); 164 return; 165 } 166 167 int numChildren = node.jjtGetNumChildren(); 168 169 // Convert up to LUB. 170 ptolemy.data.type.Type elementType = tokens[0].getType(); 171 172 for (int i = 0; i < numChildren; i++) { 173 Type valueType = tokens[i].getType(); 174 175 if (!elementType.equals(valueType)) { 176 elementType = TypeLattice.leastUpperBound(elementType, 177 valueType); 178 } 179 } 180 181 for (int i = 0; i < numChildren; i++) { 182 tokens[i] = elementType.convert(tokens[i]); 183 } 184 185 _evaluatedChildToken = new ArrayToken(elementType, tokens); 186 187 if (node.isConstant()) { 188 node.setToken(_evaluatedChildToken); 189 } 190 } 191 192 /** Evaluate a bitwise operator on the children of the specified 193 * node, where the particular operator is property of the node. 194 * @param node The specified node. 195 * @exception IllegalActionException If an evaluation error occurs. 196 */ 197 @Override 198 public void visitBitwiseNode(ASTPtBitwiseNode node) 199 throws IllegalActionException { 200 if (node.isConstant() && node.isEvaluated()) { 201 _evaluatedChildToken = node.getToken(); 202 return; 203 } 204 205 ptolemy.data.Token[] tokens = _evaluateAllChildren(node); 206 207 int numChildren = node.jjtGetNumChildren(); 208 209 _assert(numChildren > 0, node, 210 "The number of child nodes must be greater than zero"); 211 212 ptolemy.data.Token result = tokens[0]; 213 214 if (!(result instanceof BitwiseOperationToken)) { 215 throw new IllegalActionException("Operation " 216 + node.getOperator().image + " not defined on " + result 217 + " which does not support bitwise operations."); 218 } 219 220 BitwiseOperationToken bitwiseResult = (BitwiseOperationToken) result; 221 222 // Make sure that exactly one of AND, OR, XOR is set. 223 _assert(node.isBitwiseAnd() ^ node.isBitwiseOr() ^ node.isBitwiseXor(), 224 node, "Invalid operation"); 225 226 for (int i = 1; i < numChildren; i++) { 227 ptolemy.data.Token nextToken = tokens[i]; 228 229 if (!(nextToken instanceof BitwiseOperationToken)) { 230 throw new IllegalActionException("Operation " 231 + node.getOperator().image + " not defined on " + result 232 + " which does not support bitwise operations."); 233 } 234 235 if (node.isBitwiseAnd()) { 236 bitwiseResult = bitwiseResult.bitwiseAnd(nextToken); 237 } else if (node.isBitwiseOr()) { 238 bitwiseResult = bitwiseResult.bitwiseOr(nextToken); 239 } else { 240 bitwiseResult = bitwiseResult.bitwiseXor(nextToken); 241 } 242 } 243 244 _evaluatedChildToken = (ptolemy.data.Token) bitwiseResult; 245 246 if (node.isConstant()) { 247 node.setToken(_evaluatedChildToken); 248 } 249 } 250 251 /** Apply a function to the children of the specified node. 252 * This also handles indexing into matrices and arrays, which look 253 * like function calls. 254 * 255 * In the simplest cases, if the function is being applied to an 256 * expression that evaluated to a FunctionToken, an ArrayToken, 257 * or a MatrixToken, then the function application is simply 258 * applied to the available arguments. 259 * 260 * More complex is if the function is being applied to an 261 * expression that does not evaluate as above, resulting in three 262 * cases: Of primary interest is a function node that represents the 263 * invocation of a Java method registered with the expression 264 * parser. This method uses the reflection mechanism in the 265 * CachedMethod class to find the correct method, based on the 266 * types of the arguments and invoke it. See that class for 267 * information about how method arguments are matched. 268 * 269 * A second case is the eval() function, which is handled 270 * specially in this method. The argument to the function is 271 * evaluated, and the parsed as a string using the expression 272 * parser. The result is then evaluated *in this evaluator*. 273 * This has the effect that any identifiers are evaluated in the 274 * same scope as the original expression. 275 * 276 * A third case is the matlab() function, which is also handled 277 * specially in this method, allowing the evaluation of 278 * expressions in matlab if matlab is installed. The format 279 * of the function is covered in 280 * {@link ptolemy.data.expr.MatlabUtilities#evaluate(String, Set, ParserScope)} 281 * . 282 * @param node The specified node. 283 * @exception IllegalActionException If an evaluation error occurs. 284 */ 285 @Override 286 public void visitFunctionApplicationNode(ASTPtFunctionApplicationNode node) 287 throws IllegalActionException { 288 // First check to see if the name references a valid variable. 289 ptolemy.data.Token value = null; 290 ptolemy.data.Token scopedValue = null; 291 String functionName = node.getFunctionName(); 292 293 if (functionName != null && _scope != null) { 294 scopedValue = _scope.get(functionName); 295 if (!(scopedValue instanceof ObjectToken)) { 296 // Pretend that we cannot resolve the name if it is an 297 // ObjectToken. 298 value = scopedValue; 299 } 300 } 301 302 // The first child contains the function name as an id. It is 303 // ignored, and not evaluated unless necessary. 304 int argCount = node.jjtGetNumChildren() - 1; 305 Type[] argTypes = new Type[argCount]; 306 ptolemy.data.Token[] argValues = new ptolemy.data.Token[argCount]; 307 308 // First try to find a signature using argument token values. 309 for (int i = 0; i < argCount; i++) { 310 // Save the resulting value. 311 _evaluateChild(node, i + 1); 312 313 ptolemy.data.Token token = _evaluatedChildToken; 314 argValues[i] = token; 315 argTypes[i] = token.getType(); 316 } 317 318 if (value != null || functionName == null) { 319 // The value of the first child should be either a FunctionToken, 320 // an ArrayToken, or a MatrixToken. 321 ptolemy.data.Token result; 322 323 // Evaluate it, if necessary. 324 if (value == null) { 325 value = _evaluateChild(node, 0); 326 } 327 328 if (value instanceof ArrayToken) { 329 if (argCount == 1) { 330 result = _evaluateArrayIndex(node, value, argValues[0]); 331 } else { 332 //FIXME need better error message when the first child 333 // is, say, an array expression 334 throw new IllegalActionException("Wrong number of indices " 335 + "when referencing " + node.getFunctionName()); 336 } 337 } else if (value instanceof MatrixToken) { 338 if (argCount == 2) { 339 result = _evaluateMatrixIndex(node, value, argValues[0], 340 argValues[1]); 341 } else { 342 //FIXME need better error message when the first child 343 // is, say, a matrix expression 344 throw new IllegalActionException("Wrong number of indices " 345 + "when referencing " + node.getFunctionName()); 346 } 347 } else if (value instanceof FunctionToken) { 348 FunctionToken function = (FunctionToken) value; 349 350 // check number of children against number of arguments of 351 // function 352 if (function.getNumberOfArguments() != argCount) { 353 throw new IllegalActionException("Wrong number of " 354 + "arguments when applying function " 355 + value.toString()); 356 } 357 358 result = function.apply(argValues); 359 } else { 360 // If the argument is a scalar that can be 361 // losslessly coverted to an array or matrix 362 // and the indexes have value 0 then the evaluation 363 // is just the value itself. 364 if (argCount == 2) { 365 // Possible matrix promotion, where we allow 366 // scalar(0,0) to simply have value scalar. 367 if (argValues[0] instanceof IntToken 368 && ((IntToken) argValues[0]).intValue() == 0 369 && argValues[1] instanceof IntToken 370 && ((IntToken) argValues[1]).intValue() == 0) { 371 // If there is a corresponding matrix type, 372 // then return the value. To find out whether there 373 // is a corresponding matrix type, just try to create 374 // one. 375 try { 376 ptolemy.data.Token[] tmp = new ptolemy.data.Token[1]; 377 tmp[0] = value; 378 MatrixToken.arrayToMatrix(tmp, 1, 1); 379 } catch (IllegalActionException ex) { 380 // No such matrix. 381 throw new IllegalActionException( 382 "Cannot apply array indexing to " 383 + value.toString()); 384 } 385 result = value; 386 } else { 387 // Either the arguments are not ints or 388 // they are not both zero. 389 throw new IllegalActionException( 390 "Invalid matrix indexing for " 391 + value.toString()); 392 } 393 } else if (argCount == 1) { 394 // Possible array promotion, where we allow 395 // scalar(0) to simply have value scalar. 396 if (argValues[0] instanceof IntToken 397 && ((IntToken) argValues[0]).intValue() == 0) { 398 result = value; 399 } else { 400 // Either the argument is not an int or 401 // it is not zero. 402 throw new IllegalActionException( 403 "Invalid array indexing for " 404 + value.toString()); 405 } 406 } else { 407 // FIXME: It might be the a parameter is 408 // shadowing a built-in function, in which 409 // case, thrown an exception seems bogus. 410 411 // The value cannot be indexed or applied 412 // throw exception. 413 throw new IllegalActionException( 414 "Cannot index or apply arguments to " 415 + value.toString()); 416 } 417 } 418 419 _evaluatedChildToken = result; 420 return; 421 } 422 423 if (node.getFunctionName().compareTo("eval") == 0) { 424 if (argCount == 1) { 425 ptolemy.data.Token token = argValues[0]; 426 427 if (token instanceof StringToken) { 428 // Note that we do not want to store a reference to 429 // the parser, because parsers take up alot of memory. 430 PtParser parser = new PtParser(); 431 ASTPtRootNode tree = parser.generateParseTree( 432 ((StringToken) token).stringValue()); 433 434 // Note that we evaluate the recursed parse tree 435 // in the same scope as this parse tree. 436 tree.visit(this); 437 438 // _evaluatedChildToken = (tree.getToken()); 439 // FIXME cache? 440 return; 441 } 442 } 443 444 throw new IllegalActionException("The function \"eval\" is" 445 + " reserved for reinvoking the parser, and takes" 446 + " exactly one String argument."); 447 } 448 449 if (node.getFunctionName().compareTo("matlab") == 0) { 450 _evaluateChild(node, 1); 451 452 ptolemy.data.Token token = _evaluatedChildToken; 453 454 if (token instanceof StringToken) { 455 String expression = ((StringToken) token).stringValue(); 456 ParseTreeFreeVariableCollector collector = new ParseTreeFreeVariableCollector(); 457 Set freeVariables = collector.collectFreeVariables(node, 458 _scope); 459 _evaluatedChildToken = MatlabUtilities.evaluate(expression, 460 freeVariables, _scope); 461 return; 462 } else { 463 throw new IllegalActionException("The function \"matlab\" is" 464 + " reserved for invoking the matlab engine, and takes" 465 + " a string matlab expression argument followed by" 466 + " a list of variable names that the matlab expression" 467 + " refers to."); 468 } 469 } 470 471 if (node.getFunctionName().compareTo("fold") == 0) { 472 if (argCount == 3) { 473 if (argValues[0] instanceof FunctionToken) { 474 FunctionToken function = (FunctionToken) argValues[0]; 475 if (((FunctionType) function.getType()) 476 .getArgCount() != 2) { 477 throw new IllegalActionException("The first argument " 478 + "to the function \"fold\" must be a function " 479 + "that accepts two arguments."); 480 } 481 ptolemy.data.Token current = argValues[1]; 482 if (argValues[2] instanceof ArrayToken) { 483 ArrayToken array = (ArrayToken) argValues[2]; 484 for (int i = 0; i < array.length(); i++) { 485 current = function.apply(new ptolemy.data.Token[] { 486 current, array.getElement(i) }); 487 } 488 _evaluatedChildToken = current; 489 return; 490 } else if (argValues[2] instanceof ObjectToken) { 491 Object object = ((ObjectToken) argValues[2]).getValue(); 492 if (object.getClass().isArray()) { 493 Object[] array = (Object[]) object; 494 for (Object element : array) { 495 Object second = element; 496 if (!(second instanceof ptolemy.data.Token)) { 497 second = ConversionUtilities 498 .convertJavaTypeToToken(second); 499 } 500 current = function.apply( 501 new ptolemy.data.Token[] { current, 502 (ptolemy.data.Token) second }); 503 } 504 _evaluatedChildToken = current; 505 return; 506 } else if (object instanceof Iterable) { 507 Iterator iterator = ((Iterable) object).iterator(); 508 while (iterator.hasNext()) { 509 Object second = iterator.next(); 510 if (!(second instanceof ptolemy.data.Token)) { 511 second = ConversionUtilities 512 .convertJavaTypeToToken(second); 513 } 514 current = function.apply( 515 new ptolemy.data.Token[] { current, 516 (ptolemy.data.Token) second }); 517 } 518 _evaluatedChildToken = current; 519 return; 520 } 521 } 522 } 523 } 524 525 throw new IllegalActionException("The function \"fold\" is " 526 + "a higher-order function that takes exactly 3 " 527 + "arguments. The first argument must be a function " 528 + "that takes 2 arguments. The second must be a value " 529 + "that can be passed to the function as its first " 530 + "argument. The third must be a list of values that " 531 + "can be passed to the function as its second " 532 + "argument."); 533 } 534 535 if (functionName.equals("object") && argCount == 1) { 536 ASTPtRootNode classNameNode = (ASTPtRootNode) node.jjtGetChild(1); 537 if (classNameNode instanceof ASTPtLeafNode) { 538 ptolemy.data.Token token = ((ASTPtLeafNode) classNameNode) 539 .getToken(); 540 if (token != null && token instanceof StringToken) { 541 String className = ((StringToken) token).stringValue(); 542 _evaluatedChildToken = ObjectToken.object(className); 543 return; 544 } 545 } 546 } 547 548 // If not a special function, then reflect the name of the function. 549 ptolemy.data.Token result = null; 550 try { 551 result = _functionCall(node.getFunctionName(), argTypes, argValues); 552 } catch (IllegalActionException e) { 553 // Try to consider "expression" as "this.expression" and invoke 554 // method again. This allows expressions such as "getContainer()" to 555 // be evaluated on an arbitrary variable without using "this". 556 // -- tfeng (01/19/2009) 557 boolean success = false; 558 if (argValues.length == 0) { 559 ptolemy.data.Token thisToken = _scope.get("this"); 560 if (thisToken != null) { 561 argTypes = new Type[] { thisToken.getType() }; 562 argValues = new ptolemy.data.Token[] { thisToken }; 563 result = _methodCall(node.getFunctionName(), argTypes, 564 argValues); 565 success = true; 566 } 567 } 568 if (!success) { 569 throw e; 570 } 571 } 572 573 if (result == null && scopedValue instanceof ObjectToken) { 574 // If it is ObjectToken, set it here. 575 result = scopedValue; 576 } 577 578 _evaluatedChildToken = result; 579 } 580 581 /** Define a function, where the children specify the argument types 582 * and the expression. The expression is not evaluated. The resulting 583 * token in the node is an instance of FunctionToken. 584 * @param node The specified node. 585 * @exception IllegalActionException If an evaluation error occurs. 586 */ 587 @Override 588 public void visitFunctionDefinitionNode(ASTPtFunctionDefinitionNode node) 589 throws IllegalActionException { 590 ASTPtRootNode cloneTree; 591 592 ParseTreeSpecializer specializer = new ParseTreeSpecializer(); 593 cloneTree = specializer.specialize(node.getExpressionTree(), 594 node.getArgumentNameList(), _scope); 595 596 // Infer the return type. 597 if (_typeInference == null) { 598 _typeInference = new ParseTreeTypeInference(); 599 } 600 601 _typeInference.inferTypes(node, _scope); 602 603 FunctionType type = (FunctionType) node.getType(); 604 ExpressionFunction definedFunction = new ExpressionFunction( 605 node.getArgumentNameList(), node.getArgumentTypes(), cloneTree); 606 FunctionToken result = new FunctionToken(definedFunction, type); 607 _evaluatedChildToken = result; 608 return; 609 } 610 611 /** Evaluate the first child, and depending on its (boolean) result, 612 * evaluate either the second or the third child. The result of 613 * that evaluation becomes the result of the specified node. 614 * @param node The specified node. 615 * @exception IllegalActionException If an evaluation error occurs. 616 */ 617 @Override 618 public void visitFunctionalIfNode(ASTPtFunctionalIfNode node) 619 throws IllegalActionException { 620 if (node.isConstant() && node.isEvaluated()) { 621 _evaluatedChildToken = node.getToken(); 622 return; 623 } 624 625 int numChildren = node.jjtGetNumChildren(); 626 627 if (numChildren != 3) { 628 // A functional-if node MUST have three children in the parse 629 // tree. 630 throw new InternalErrorException( 631 "PtParser error: a functional-if node does not have " 632 + "three children in the parse tree."); 633 } 634 635 // evaluate the first sub-expression 636 _evaluateChild(node, 0); 637 638 ptolemy.data.Token test = _evaluatedChildToken; 639 640 if (!(test instanceof BooleanToken)) { 641 throw new IllegalActionException( 642 "Functional-if must branch on a boolean, but instead was " 643 + test.toString() + " an instance of " 644 + test.getClass().getName()); 645 } 646 647 boolean value = ((BooleanToken) test).booleanValue(); 648 649 // Choose the correct sub-expression to evaluate, 650 // and type check the other. 651 if (_typeInference == null) { 652 _typeInference = new ParseTreeTypeInference(); 653 } 654 655 ASTPtRootNode tokenChild; 656 ASTPtRootNode typeChild; 657 658 if (value) { 659 tokenChild = (ASTPtRootNode) node.jjtGetChild(1); 660 typeChild = (ASTPtRootNode) node.jjtGetChild(2); 661 } else { 662 tokenChild = (ASTPtRootNode) node.jjtGetChild(2); 663 typeChild = (ASTPtRootNode) node.jjtGetChild(1); 664 } 665 666 tokenChild.visit(this); 667 668 ptolemy.data.Token token = _evaluatedChildToken; 669 Type type = _typeInference.inferTypes(typeChild, _scope); 670 671 Type conversionType = (Type) TypeLattice.lattice().leastUpperBound(type, 672 token.getType()); 673 674 token = conversionType.convert(token); 675 _evaluatedChildToken = token; 676 677 if (node.isConstant()) { 678 node.setToken(_evaluatedChildToken); 679 } 680 } 681 682 /** Evaluate a numeric constant or an identifier. In the case of an 683 * identifier, its value is obtained from the scope or from the list 684 * of registered constants. 685 * @param node The specified node. 686 * @exception IllegalActionException If an evaluation error occurs. 687 */ 688 @Override 689 public void visitLeafNode(ASTPtLeafNode node) 690 throws IllegalActionException { 691 if (node.isConstant() && node.isEvaluated()) { 692 _evaluatedChildToken = node.getToken(); 693 return; 694 } 695 696 String name = node.getName(); 697 698 // The node refers to a variable, or something else that is in 699 // scope. 700 ptolemy.data.Token value = null; 701 702 if (_scope != null) { 703 value = _scope.get(name); 704 } 705 706 // Look up for constants. 707 // Pretend that we cannot resolve the name if it is an ObjectToken. 708 if (value == null || value instanceof ObjectToken) { 709 // A named constant that is recognized by the parser. 710 ptolemy.data.Token constant = Constants.get(name); 711 if (constant != null) { 712 // Assign value only if no constant can be found, because the 713 // value could be an ObjectToken that has been temporarily 714 // ignored. 715 value = constant; 716 } 717 } 718 719 // Set the value, if we found one. 720 if (value != null) { 721 _evaluatedChildToken = value; 722 return; 723 } 724 725 throw new UndefinedConstantOrIdentifierException(node.getName()); 726 } 727 728 /** Evaluate a logical AND or OR on the children of the specified node. 729 * @param node The specified node. 730 * @exception IllegalActionException If an evaluation error occurs. 731 */ 732 @Override 733 public void visitLogicalNode(ASTPtLogicalNode node) 734 throws IllegalActionException { 735 if (node.isConstant() && node.isEvaluated()) { 736 _evaluatedChildToken = node.getToken(); 737 return; 738 } 739 740 // Note that we do not always evaluate all of the children... 741 // We perform short-circuit evaluation instead and evaluate the 742 // children in order until the final value is determined, after 743 // which point no more children are evaluated. 744 int numChildren = node.jjtGetNumChildren(); 745 _assert(numChildren > 0, node, 746 "The number of child nodes must be greater than zero"); 747 748 _evaluateChild(node, 0); 749 750 ptolemy.data.Token result = _evaluatedChildToken; 751 752 if (!(result instanceof BooleanToken)) { 753 throw new IllegalActionException( 754 "Cannot perform logical " + "operation on " + result 755 + " which is a " + result.getClass().getName()); 756 } 757 758 // Make sure that exactly one of AND or OR is set. 759 _assert(node.isLogicalAnd() ^ node.isLogicalOr(), node, 760 "Invalid operation"); 761 762 // Perform both the short-circuit AND and short-circuit OR in 763 // one piece of code. 764 // FIXME: I dislike that this is not done in the token classes... 765 boolean flag = node.isLogicalAnd(); 766 767 for (int i = 0; i < numChildren; i++) { 768 ASTPtRootNode child = (ASTPtRootNode) node.jjtGetChild(i); 769 770 // Evaluate the child 771 child.visit(this); 772 773 // Get its value. 774 ptolemy.data.Token nextToken = _evaluatedChildToken; 775 776 if (!(nextToken instanceof BooleanToken)) { 777 throw new IllegalActionException( 778 "Cannot perform logical " + "operation on " + nextToken 779 + " which is a " + result.getClass().getName()); 780 } 781 782 if (flag != ((BooleanToken) nextToken).booleanValue()) { 783 _evaluatedChildToken = BooleanToken.getInstance(!flag); 784 785 // Note short-circuit eval. 786 return; 787 } 788 } 789 790 _evaluatedChildToken = BooleanToken.getInstance(flag); 791 792 if (node.isConstant()) { 793 node.setToken(_evaluatedChildToken); 794 } 795 } 796 797 /** Construct a matrix containing the children nodes. 798 * The specified node ends up with a MatrixToken value. 799 * @param node The specified node. 800 * @exception IllegalActionException If an evaluation error occurs. 801 */ 802 @Override 803 public void visitMatrixConstructNode(ASTPtMatrixConstructNode node) 804 throws IllegalActionException { 805 if (node.isConstant() && node.isEvaluated()) { 806 _evaluatedChildToken = node.getToken(); 807 return; 808 } 809 810 ptolemy.data.Token[] tokens = _evaluateAllChildren(node); 811 812 ptolemy.data.Token result = null; 813 814 if (node.getForm() == 1) { 815 //int numChildren = node.jjtGetNumChildren(); 816 result = MatrixToken.arrayToMatrix(tokens, node.getRowCount(), 817 node.getColumnCount()); 818 } else if (node.getForm() == 2) { 819 try { 820 int columnCount = MatrixToken.determineSequenceLength( 821 (ScalarToken) tokens[0], (ScalarToken) tokens[1], 822 (ScalarToken) tokens[2]); 823 824 // Make sure that all following rows have the same number 825 // of columns. 826 for (int i = 1; i < node.getRowCount(); ++i) { 827 if (columnCount != MatrixToken.determineSequenceLength( 828 (ScalarToken) tokens[3 * i], 829 (ScalarToken) tokens[3 * i + 1], 830 (ScalarToken) tokens[3 * i + 2])) { 831 throw new IllegalActionException("Matrix " 832 + "should have the same number of columns " 833 + "for all rows."); 834 } 835 } 836 837 ptolemy.data.Token[] matrixTokens = new ptolemy.data.Token[node 838 .getRowCount() * columnCount]; 839 840 for (int i = 0; i < node.getRowCount(); i++) { 841 ptolemy.data.Token[] newTokens = MatrixToken.createSequence( 842 tokens[3 * i], tokens[3 * i + 1], columnCount); 843 System.arraycopy(newTokens, 0, matrixTokens, 844 columnCount * i, columnCount); 845 } 846 847 result = MatrixToken.arrayToMatrix(matrixTokens, 848 node.getRowCount(), columnCount); 849 } catch (IllegalActionException ex) { 850 // FIXME: better detail message that includes the thing 851 // we were parsing. 852 throw new IllegalActionException(null, null, ex, 853 "Matrix Token construction failed."); 854 } 855 } 856 857 _evaluatedChildToken = result; 858 859 if (node.isConstant()) { 860 node.setToken(_evaluatedChildToken); 861 } 862 } 863 864 /** Apply a method to the children of the specified node, where the 865 * first child is the object on which the method is defined and the 866 * rest of the children are arguments. This also handles indexing into 867 * a record, which looks the same. 868 * @param node The specified node. 869 * @exception IllegalActionException If an evaluation error occurs. 870 */ 871 @Override 872 public void visitMethodCallNode(ASTPtMethodCallNode node) 873 throws IllegalActionException { 874 // Method calls are generally not cached... They are repeated 875 // every time the tree is evaluated. 876 int argCount = node.jjtGetNumChildren(); 877 ptolemy.data.Token[] tokens = _evaluateAllChildren(node); 878 879 // Handle indexing into a record. 880 if (argCount == 1 && tokens[0] instanceof RecordToken) { 881 RecordToken record = (RecordToken) tokens[0]; 882 883 if (record.labelSet().contains(node.getMethodName())) { 884 _evaluatedChildToken = record.get(node.getMethodName()); 885 return; 886 } 887 } 888 889 // The first child is the object to invoke the method on. 890 Type[] argTypes = new Type[argCount]; 891 Object[] argValues = new Object[argCount]; 892 893 // First try to find a signature using argument token values. 894 for (int i = 0; i < argCount; i++) { 895 // Save the resulting value. 896 ptolemy.data.Token token = tokens[i]; 897 argValues[i] = token; 898 argTypes[i] = token.getType(); 899 } 900 901 ptolemy.data.Token result = _methodCall(node.getMethodName(), argTypes, 902 argValues); 903 904 _evaluatedChildToken = result; 905 } 906 907 /** Evaluate the power operator on the children of the specified node. 908 * @param node The specified node. 909 * @exception IllegalActionException If an evaluation error occurs. 910 */ 911 @Override 912 public void visitPowerNode(ASTPtPowerNode node) 913 throws IllegalActionException { 914 if (node.isConstant() && node.isEvaluated()) { 915 _evaluatedChildToken = node.getToken(); 916 return; 917 } 918 919 ptolemy.data.Token[] tokens = _evaluateAllChildren(node); 920 int numChildren = node.jjtGetNumChildren(); 921 _assert(numChildren > 0, node, 922 "The number of child nodes must be greater than zero"); 923 924 // Operator is always exponentiation 925 // Note that since we use an iterative integer method, instead of 926 // a logarithmic method, the fastest thing is to apply the 927 // exponentiation inside out, i.e. left to right. 928 ptolemy.data.Token result = tokens[0]; 929 930 for (int i = 1; i < numChildren; i++) { 931 int times = 1; 932 ptolemy.data.Token token = tokens[i]; 933 934 // Note that we check for ScalarTokens because anything 935 // that has a meaningful intValue() method, such as 936 // ShortToken or UnsignedByteToken will also work here. 937 if (!(token instanceof ScalarToken)) { 938 throw new IllegalActionException( 939 "Exponent must be ScalarToken and have a valid " 940 + "lossless conversion to integer. " 941 + "Integer, short or unsigned byte meet " 942 + "these criteria.\n" 943 + "Use pow(10, 3.5) for non-integer exponents"); 944 } 945 946 try { 947 times = ((ptolemy.data.ScalarToken) token).intValue(); 948 } catch (IllegalActionException ex) { 949 throw new IllegalActionException("Exponent must have a valid " 950 + "lossless conversion to integer. " 951 + "Integer, short or unsigned byte meet " 952 + "these criteria.\n" 953 + "Use pow(10, 3.5) for non-integer exponents"); 954 } 955 956 result = result.pow(times); 957 } 958 959 _evaluatedChildToken = result; 960 961 if (node.isConstant()) { 962 node.setToken(_evaluatedChildToken); 963 } 964 } 965 966 /** Multiply the children of the specified node. 967 * @param node The specified node. 968 * @exception IllegalActionException If an evaluation error occurs. 969 */ 970 @Override 971 public void visitProductNode(ASTPtProductNode node) 972 throws IllegalActionException { 973 if (node.isConstant() && node.isEvaluated()) { 974 _evaluatedChildToken = node.getToken(); 975 return; 976 } 977 978 ptolemy.data.Token[] tokens = _evaluateAllChildren(node); 979 List lexicalTokenList = node.getLexicalTokenList(); 980 int numChildren = node.jjtGetNumChildren(); 981 _assert(numChildren > 0, node, 982 "The number of child nodes must be greater than zero"); 983 _assert(numChildren == lexicalTokenList.size() + 1, node, 984 "The number of child nodes is " 985 + "not equal to number of operators plus one"); 986 987 ptolemy.data.Token result = tokens[0]; 988 989 for (int i = 1; i < numChildren; i++) { 990 Token operator = (Token) lexicalTokenList.get(i - 1); 991 ptolemy.data.Token nextToken = tokens[i]; 992 993 if (operator.kind == PtParserConstants.MULTIPLY) { 994 result = result.multiply(nextToken); 995 } else if (operator.kind == PtParserConstants.DIVIDE) { 996 result = result.divide(nextToken); 997 } else if (operator.kind == PtParserConstants.MODULO) { 998 result = result.modulo(nextToken); 999 } else { 1000 _assert(false, node, "Invalid operation"); 1001 } 1002 } 1003 1004 _evaluatedChildToken = result; 1005 1006 if (node.isConstant()) { 1007 node.setToken(_evaluatedChildToken); 1008 } 1009 } 1010 1011 /** Construct a record by assigning the fields values given by 1012 * the children nodes. 1013 * @param node The record constructor node. 1014 * @exception IllegalActionException If an evaluation error occurs. 1015 */ 1016 @Override 1017 public void visitRecordConstructNode(ASTPtRecordConstructNode node) 1018 throws IllegalActionException { 1019 if (node.isConstant() && node.isEvaluated()) { 1020 _evaluatedChildToken = node.getToken(); 1021 return; 1022 } 1023 1024 ptolemy.data.Token[] tokens = _evaluateAllChildren(node); 1025 1026 int numChildren = node.jjtGetNumChildren(); 1027 1028 _assert(node.getFieldNames().size() == numChildren, node, 1029 "The number of labels and values does not " 1030 + "match in parsing a record expression."); 1031 1032 String[] labels = (String[]) node.getFieldNames() 1033 .toArray(new String[numChildren]); 1034 1035 if (node instanceof ASTPtOrderedRecordConstructNode) { 1036 _evaluatedChildToken = new OrderedRecordToken(labels, tokens); 1037 } else { 1038 _evaluatedChildToken = new RecordToken(labels, tokens); 1039 } 1040 1041 if (node.isConstant()) { 1042 node.setToken(_evaluatedChildToken); 1043 } 1044 } 1045 1046 @Override 1047 public void visitRelationalNode(ASTPtRelationalNode node) 1048 throws IllegalActionException { 1049 if (node.isConstant() && node.isEvaluated()) { 1050 _evaluatedChildToken = node.getToken(); 1051 return; 1052 } 1053 1054 ptolemy.data.Token[] tokens = _evaluateAllChildren(node); 1055 1056 int numChildren = node.jjtGetNumChildren(); 1057 _assert(numChildren == 2, node, 1058 "The number of child nodes must be two"); 1059 1060 Token operator = node.getOperator(); 1061 ptolemy.data.Token leftToken = tokens[0]; 1062 ptolemy.data.Token rightToken = tokens[1]; 1063 ptolemy.data.Token result; 1064 1065 if (operator.kind == PtParserConstants.EQUALS) { 1066 result = leftToken.isEqualTo(rightToken); 1067 } else if (operator.kind == PtParserConstants.NOTEQUALS) { 1068 result = leftToken.isEqualTo(rightToken).not(); 1069 } else { 1070 if (!(leftToken instanceof PartiallyOrderedToken 1071 && rightToken instanceof PartiallyOrderedToken)) { 1072 throw new IllegalActionException("The " + operator.image 1073 + " operator can only be applied on partial orders."); 1074 } 1075 1076 PartiallyOrderedToken leftScalar = (PartiallyOrderedToken) leftToken; 1077 PartiallyOrderedToken rightScalar = (PartiallyOrderedToken) rightToken; 1078 1079 if (operator.kind == PtParserConstants.GTE) { 1080 result = rightScalar.isLessThan(leftScalar) 1081 .or(leftToken.isEqualTo(rightToken)); 1082 } else if (operator.kind == PtParserConstants.GT) { 1083 result = rightScalar.isLessThan(leftScalar); 1084 } else if (operator.kind == PtParserConstants.LTE) { 1085 result = leftScalar.isLessThan(rightScalar) 1086 .or(leftToken.isEqualTo(rightToken)); 1087 } else if (operator.kind == PtParserConstants.LT) { 1088 result = leftScalar.isLessThan(rightScalar); 1089 } else { 1090 throw new IllegalActionException( 1091 "Invalid operation " + operator.image + " between " 1092 + leftToken.getClass().getName() + " and " 1093 + rightToken.getClass().getName()); 1094 } 1095 } 1096 1097 _evaluatedChildToken = result; 1098 1099 if (node.isConstant()) { 1100 node.setToken(_evaluatedChildToken); 1101 } 1102 } 1103 1104 /** Apply a shift operator to the children of the specified node. 1105 * @param node The specified node. 1106 * @exception IllegalActionException If an evaluation error occurs. 1107 */ 1108 @Override 1109 public void visitShiftNode(ASTPtShiftNode node) 1110 throws IllegalActionException { 1111 if (node.isConstant() && node.isEvaluated()) { 1112 _evaluatedChildToken = node.getToken(); 1113 return; 1114 } 1115 1116 ptolemy.data.Token[] tokens = _evaluateAllChildren(node); 1117 1118 int numChildren = node.jjtGetNumChildren(); 1119 _assert(numChildren == 2, node, 1120 "The number of child nodes must be two"); 1121 1122 Token operator = node.getOperator(); 1123 ptolemy.data.Token token = tokens[0]; 1124 ptolemy.data.Token bitsToken = tokens[1]; 1125 ptolemy.data.Token result = null; 1126 1127 if (!(token instanceof ScalarToken)) { 1128 throw new IllegalActionException( 1129 "The " + operator + " operator requires " 1130 + "the left operand to be a scalar."); 1131 } 1132 1133 if (!(bitsToken instanceof ScalarToken)) { 1134 throw new IllegalActionException( 1135 "The " + operator + " operator requires " 1136 + "the right operand to be a scalar."); 1137 } 1138 1139 // intValue() is used rather than testing for IntToken 1140 // because any token with an intValue() is OK. However, 1141 // we need a try...catch to generate a proper error message. 1142 try { 1143 if (operator.kind == PtParserConstants.SHL) { 1144 result = ((ScalarToken) token) 1145 .leftShift(((ScalarToken) bitsToken).intValue()); 1146 } else if (operator.kind == PtParserConstants.SHR) { 1147 result = ((ScalarToken) token) 1148 .rightShift(((ScalarToken) bitsToken).intValue()); 1149 } else if (operator.kind == PtParserConstants.LSHR) { 1150 result = ((ScalarToken) token).logicalRightShift( 1151 ((ScalarToken) bitsToken).intValue()); 1152 } else { 1153 _assert(false, node, "Invalid operation"); 1154 } 1155 } catch (IllegalActionException ex) { 1156 throw new IllegalActionException( 1157 "The " + operator + " operator requires " 1158 + "the right operand to have an integer value."); 1159 } 1160 1161 _evaluatedChildToken = result; 1162 1163 if (node.isConstant()) { 1164 node.setToken(_evaluatedChildToken); 1165 } 1166 } 1167 1168 /** Apply a sum operator to the children of the specified node. 1169 * @param node The specified node. 1170 * @exception IllegalActionException If an evaluation error occurs. 1171 */ 1172 @Override 1173 public void visitSumNode(ASTPtSumNode node) throws IllegalActionException { 1174 if (node.isConstant() && node.isEvaluated()) { 1175 _evaluatedChildToken = node.getToken(); 1176 return; 1177 } 1178 1179 ptolemy.data.Token[] tokens = _evaluateAllChildren(node); 1180 List lexicalTokenList = node.getLexicalTokenList(); 1181 int numChildren = node.jjtGetNumChildren(); 1182 _assert(numChildren > 0, node, 1183 "The number of child nodes must be greater than zero"); 1184 _assert(numChildren == lexicalTokenList.size() + 1, node, 1185 "The number of child nodes is " 1186 + "not equal to number of operators plus one"); 1187 1188 ptolemy.data.Token result = tokens[0]; 1189 1190 for (int i = 1; i < numChildren; i++) { 1191 Token operator = (Token) lexicalTokenList.get(i - 1); 1192 ptolemy.data.Token nextToken = tokens[i]; 1193 1194 if (operator.kind == PtParserConstants.PLUS) { 1195 result = result.add(nextToken); 1196 } else if (operator.kind == PtParserConstants.MINUS) { 1197 result = result.subtract(nextToken); 1198 } else { 1199 _assert(false, node, "Invalid operation"); 1200 } 1201 } 1202 1203 _evaluatedChildToken = result; 1204 1205 if (node.isConstant()) { 1206 node.setToken(_evaluatedChildToken); 1207 } 1208 } 1209 1210 /** Apply a unary operator to the single child of the specified node. 1211 * @param node The specified node. 1212 */ 1213 @Override 1214 public void visitUnaryNode(ASTPtUnaryNode node) 1215 throws IllegalActionException { 1216 if (node.isConstant() && node.isEvaluated()) { 1217 _evaluatedChildToken = node.getToken(); 1218 return; 1219 } 1220 1221 ptolemy.data.Token[] tokens = _evaluateAllChildren(node); 1222 _assert(node.jjtGetNumChildren() == 1, node, 1223 "Unary node must have exactly one child!"); 1224 1225 ptolemy.data.Token result = tokens[0]; 1226 1227 if (node.isMinus()) { 1228 result = result.zero().subtract(result); 1229 } else if (node.isNot()) { 1230 if (result instanceof BooleanToken) { 1231 result = ((BooleanToken) result).not(); 1232 } else { 1233 throw new IllegalActionException( 1234 "Not operator not support for non-boolean token: " 1235 + result.toString()); 1236 } 1237 } else if (node.isBitwiseNot()) { 1238 if (!(result instanceof BitwiseOperationToken)) { 1239 throw new IllegalActionException("Bitwise negation" 1240 + " not defined on " + result 1241 + " which does not support bitwise operations."); 1242 } 1243 1244 result = (ptolemy.data.Token) ((BitwiseOperationToken) result) 1245 .bitwiseNot(); 1246 } else { 1247 _assert(false, node, "Unrecognized unary node"); 1248 } 1249 1250 _evaluatedChildToken = result; 1251 1252 if (node.isConstant()) { 1253 node.setToken(_evaluatedChildToken); 1254 } 1255 } 1256 1257 /** Construct a union by assigning the label value given by 1258 * the children nodes. 1259 * @param node The union constructor node. 1260 * @exception IllegalActionException If an evaluation error occurs. 1261 */ 1262 @Override 1263 public void visitUnionConstructNode(ASTPtUnionConstructNode node) 1264 throws IllegalActionException { 1265 if (node.isConstant() && node.isEvaluated()) { 1266 _evaluatedChildToken = node.getToken(); 1267 return; 1268 } 1269 1270 ptolemy.data.Token[] tokens = _evaluateAllChildren(node); 1271 1272 int numChildren = node.jjtGetNumChildren(); 1273 1274 _assert(node.getLabelNames().size() == numChildren, node, 1275 "The number of labels and values does not " 1276 + "match in parsing a record expression."); 1277 1278 String[] labels = (String[]) node.getLabelNames() 1279 .toArray(new String[numChildren]); 1280 1281 //_assert(labels.length == 1, node, 1282 // "has more than one member type of the union."); 1283 1284 //If there is more than one members in the union, take the first 1285 //member value as the value of the union. 1286 if (labels.length > 0) { 1287 _evaluatedChildToken = new UnionToken(labels[0], tokens[0]); 1288 } 1289 _evaluatedChildToken = new UnionToken(labels[0], tokens[0]); 1290 1291 if (node.isConstant()) { 1292 node.setToken(_evaluatedChildToken); 1293 } 1294 } 1295 1296 /////////////////////////////////////////////////////////////////// 1297 //// protected methods //// 1298 1299 /** Assert that the given boolean value, which describes the given 1300 * parse tree node, is true. If it is false, then throw a new 1301 * InternalErrorException that describes the node and includes 1302 * the given message. 1303 * @param flag The flag that is asserted to be true. 1304 * @param node The node on which the assertion is asserted. 1305 * @param message The message to include in the exception. 1306 * @exception InternalErrorException If the assertion is violated. 1307 * Note that this is a runtime exception, so it need not be declared 1308 * explicitly. 1309 */ 1310 protected void _assert(boolean flag, ASTPtRootNode node, String message) { 1311 if (!flag) { 1312 throw new InternalErrorException(message + ": " + node.toString()); 1313 } 1314 } 1315 1316 /** Loop through all of the children of this node, 1317 * visiting each one of them; this will cause their token 1318 * value to be determined. 1319 * @param node The node whose children are evaluated. 1320 * @return The values of the children. 1321 * @exception IllegalActionException If an evaluation error occurs. 1322 */ 1323 protected ptolemy.data.Token[] _evaluateAllChildren(ASTPtRootNode node) 1324 throws IllegalActionException { 1325 int numChildren = node.jjtGetNumChildren(); 1326 ptolemy.data.Token[] tokens = new ptolemy.data.Token[numChildren]; 1327 1328 for (int i = 0; i < numChildren; i++) { 1329 // FindBugs: Return value of ASTPtRootNode.jjtGetChild(int) ignored, but method has no side effect 1330 /* ASTPtRootNode child = (ASTPtRootNode) *//*node.jjtGetChild(i);*/ 1331 tokens[i] = _evaluateChild(node, i); 1332 } 1333 1334 return tokens; 1335 } 1336 1337 /** Evaluate the array index operation represented by the given node. 1338 * @param node The node that caused this method to be called. 1339 * @param value The token that is being indexed into, which must 1340 * be an ArrayToken. 1341 * @param index The index, which must be an integer token. 1342 * @return The element of the given token at the given index. 1343 * @exception IllegalActionException If an evaluation error occurs. 1344 */ 1345 protected ptolemy.data.Token _evaluateArrayIndex(ASTPtRootNode node, 1346 ptolemy.data.Token value, ptolemy.data.Token index) 1347 throws IllegalActionException { 1348 if (!(value instanceof ArrayToken)) { 1349 throw new IllegalActionException( 1350 "Array indexing cannot be applied to '" + value.toString() 1351 + "' because its value is not an array."); 1352 } 1353 1354 if (!(index instanceof IntToken)) { 1355 throw new IllegalActionException( 1356 "Array indexing requires an integer. Got: " + index); 1357 } 1358 1359 int integerIndex = ((IntToken) index).intValue(); 1360 1361 try { 1362 return ((ArrayToken) value).getElement(integerIndex); 1363 } catch (ArrayIndexOutOfBoundsException ex) { 1364 throw new IllegalActionException("The index '" + index 1365 + "' is out of bounds on the array '" + value + "'."); 1366 } 1367 } 1368 1369 /** Evaluate the child with the given index of the given node. 1370 * This is usually called while visiting the given node. 1371 * @param node The node 1372 * @param i The index of the node 1373 * @return The token 1374 * @exception IllegalActionException If an evaluation error occurs. 1375 */ 1376 protected ptolemy.data.Token _evaluateChild(ASTPtRootNode node, int i) 1377 throws IllegalActionException { 1378 ASTPtRootNode child = (ASTPtRootNode) node.jjtGetChild(i); 1379 _traceEnter(child); 1380 child.visit(this); 1381 _traceLeave(child); 1382 return _evaluatedChildToken; 1383 } 1384 1385 /** Evaluate the Matrix index operation represented by the given node. 1386 * @param node The node that caused this method to be called. 1387 * @param value The token that is being indexed into, which must 1388 * be a MatrixToken. 1389 * @param rowIndex The row index, which must be an integer token. 1390 * @param columnIndex The column index, which must be an integer token. 1391 * @return The element of the given token at the given index. 1392 * @exception IllegalActionException If an evaluation error occurs. 1393 */ 1394 protected ptolemy.data.Token _evaluateMatrixIndex(ASTPtRootNode node, 1395 ptolemy.data.Token value, ptolemy.data.Token rowIndex, 1396 ptolemy.data.Token columnIndex) throws IllegalActionException { 1397 if (!(value instanceof MatrixToken)) { 1398 throw new IllegalActionException( 1399 "Matrix indexing cannot be applied to '" + value.toString() 1400 + "' because its value is not a matrix."); 1401 } 1402 1403 if (!(rowIndex instanceof IntToken)) { 1404 throw new IllegalActionException( 1405 "Matrix row index must be an integer. Got: " + rowIndex); 1406 } 1407 1408 if (!(columnIndex instanceof IntToken)) { 1409 throw new IllegalActionException( 1410 "Matrix column index must be an integer. Got: " 1411 + columnIndex); 1412 } 1413 1414 int integerRowIndex = ((IntToken) rowIndex).intValue(); 1415 int integerColumnIndex = ((IntToken) columnIndex).intValue(); 1416 1417 try { 1418 return ((MatrixToken) value).getElementAsToken(integerRowIndex, 1419 integerColumnIndex); 1420 } catch (ArrayIndexOutOfBoundsException ex) { 1421 throw new IllegalActionException("The index (" + rowIndex + "," 1422 + columnIndex + ") is out of bounds on the matrix '" + value 1423 + "'."); 1424 } 1425 } 1426 1427 /** Evaluate the specified function. The function must be defined 1428 * as one of the registered functions with PtParser. 1429 * @param functionName The function name. 1430 * @param argTypes An array of argument types. 1431 * @param argValues An array of argument values. 1432 * @return The value of returned by the specified method. 1433 * @exception IllegalActionException If an evaluation error occurs. 1434 */ 1435 protected ptolemy.data.Token _functionCall(String functionName, 1436 Type[] argTypes, Object[] argValues) throws IllegalActionException { 1437 CachedMethod method = CachedMethod.findMethod(functionName, argTypes, 1438 CachedMethod.FUNCTION); 1439 1440 if (method.isValid()) { 1441 if (_trace != null) { 1442 _trace("Invoking " + method.methodDescription()); 1443 _trace("as " + method); 1444 } 1445 1446 ptolemy.data.Token result = method.invoke(argValues); 1447 return result; 1448 } else { 1449 throw new IllegalActionException( 1450 "No function found matching " + method.toString()); 1451 } 1452 } 1453 1454 /** Evaluate the specified method. The object on which the method 1455 * is evaluated should be the first argument. 1456 * @param methodName The method name. 1457 * @param argTypes An array of argument types. 1458 * @param argValues An array of argument values. 1459 * @return The value of returned by the specified method. 1460 * @exception IllegalActionException If an evaluation error occurs. 1461 */ 1462 protected ptolemy.data.Token _methodCall(String methodName, Type[] argTypes, 1463 Object[] argValues) throws IllegalActionException { 1464 1465 CachedMethod method = CachedMethod.findMethod(methodName, argTypes, 1466 CachedMethod.METHOD); 1467 1468 if (method.isValid()) { 1469 if (_trace != null) { 1470 _trace("Invoking " + method.methodDescription()); 1471 _trace("as " + method); 1472 } 1473 1474 ptolemy.data.Token result = method.invoke(argValues); 1475 return result; 1476 } 1477 1478 if (argValues[0] instanceof ObjectToken) { 1479 ObjectToken objectToken = (ObjectToken) argValues[0]; 1480 Object object = objectToken.getValue(); 1481 if (object != null) { 1482 if (object instanceof NamedObj) { 1483 Object result = ((NamedObj) object) 1484 .getAttribute(methodName); 1485 if (result == null && object instanceof Entity) { 1486 result = ((Entity) object).getPort(methodName); 1487 } 1488 if (result == null && object instanceof CompositeEntity) { 1489 result = ((CompositeEntity) object) 1490 .getEntity(methodName); 1491 if (result == null) { 1492 result = ((CompositeEntity) object) 1493 .getRelation(methodName); 1494 } 1495 } 1496 1497 if (result == null) { 1498 List attributes = ((NamedObj) object) 1499 .attributeList(ContainmentExtender.class); 1500 Iterator attrIterator = attributes.iterator(); 1501 while (result == null && attrIterator.hasNext()) { 1502 ContainmentExtender extender = (ContainmentExtender) attrIterator 1503 .next(); 1504 result = extender.getContainedObject(methodName); 1505 } 1506 } 1507 if (result != null) { 1508 if (result instanceof Variable) { 1509 ptolemy.data.Token token = ((Variable) result) 1510 .getToken(); 1511 // If the token is a Matrix or an Array, then it is 1512 // getting indexed by the argument(s). 1513 if (token instanceof MatrixToken) { 1514 // The first argument is the object on which this is evaluated. 1515 // E.g., Container in Container.parameter(0). 1516 // If no argument is given, return the token itself. 1517 if (argValues.length < 2) { 1518 return token; 1519 } 1520 if (!(argValues[1] instanceof IntToken)) { 1521 throw new IllegalActionException( 1522 "Expected integer index for accessing " 1523 + ((Variable) result) 1524 .getFullName() 1525 + " but got " + token); 1526 } 1527 int row = ((IntToken) (argValues[1])) 1528 .intValue(); 1529 // If no column argument is given, assume it is zero. 1530 int column = 0; 1531 if (argValues.length >= 3) { 1532 if (!(argValues[2] instanceof IntToken)) { 1533 throw new IllegalActionException( 1534 "Expected integer index for accessing " 1535 + ((Variable) result) 1536 .getFullName() 1537 + " but got " + token); 1538 } 1539 column = ((IntToken) (argValues[2])) 1540 .intValue(); 1541 } 1542 return ((MatrixToken) token) 1543 .getElementAsToken(row, column); 1544 } 1545 if (token instanceof ArrayToken) { 1546 // The first argument is the object on which this is evaluated. 1547 // E.g., Container in Container.parameter(0). 1548 // If no argument is given, return the token itself. 1549 if (argValues.length < 2 1550 || !(argValues[1] instanceof IntToken)) { 1551 return token; 1552 } 1553 int index = ((IntToken) (argValues[1])) 1554 .intValue(); 1555 return ((ArrayToken) token).getElement(index); 1556 } 1557 1558 return token; 1559 } else { 1560 return new ObjectToken(result, result.getClass()); 1561 } 1562 } 1563 } 1564 } 1565 1566 Class<?> valueClass = object == null ? objectToken.getValueClass() 1567 : object.getClass(); 1568 Set<Class<?>> classes = new HashSet<Class<?>>(); 1569 classes.add(valueClass); 1570 while (!classes.isEmpty()) { 1571 Iterator<Class<?>> iterator = classes.iterator(); 1572 valueClass = iterator.next(); 1573 iterator.remove(); 1574 1575 if (!Modifier.isPublic(valueClass.getModifiers())) { 1576 for (Class<?> interf : valueClass.getInterfaces()) { 1577 classes.add(interf); 1578 } 1579 Class<?> superclass = valueClass.getSuperclass(); 1580 if (superclass != null) { 1581 classes.add(superclass); 1582 } 1583 } else { 1584 ptolemy.data.Token result = _invokeMethod(valueClass, 1585 object, methodName, argTypes, argValues); 1586 if (result != null) { 1587 return result; 1588 } 1589 } 1590 } 1591 1592 if (object == null) { 1593 throw new IllegalActionException("The object on which method " 1594 + "\"" + methodName + "\" is invoked on is null, but " 1595 + "the method is not found or is not static."); 1596 } 1597 } 1598 1599 throw new IllegalActionException( 1600 "No method found matching " + method.toString()); 1601 } 1602 1603 /** Add a record to the current trace corresponding to the given message. 1604 * If the trace is null, do nothing. 1605 * @param string The string 1606 */ 1607 protected void _trace(String string) { 1608 if (_trace != null) { 1609 for (int i = 0; i < _depth; i++) { 1610 _trace.append(" "); 1611 } 1612 1613 _trace.append(string); 1614 _trace.append("\n"); 1615 } 1616 } 1617 1618 /** Add a record to the current trace corresponding to the start 1619 * of the evaluation of the given node. If the trace is null, then 1620 * do nothing. 1621 * @param node The node. 1622 */ 1623 protected void _traceEnter(ASTPtRootNode node) { 1624 if (_trace != null) { 1625 for (int i = 0; i < _depth; i++) { 1626 _trace.append(" "); 1627 } 1628 1629 _trace.append("Entering node " + node.getClass().getName() + "\n"); 1630 _depth++; 1631 } 1632 } 1633 1634 /** Add a record to the current trace corresponding to the completion 1635 * of the evaluation of the given node. If the trace is null, then 1636 * do nothing. 1637 * @param node The node. 1638 */ 1639 protected void _traceLeave(ASTPtRootNode node) { 1640 if (_trace != null) { 1641 _depth--; 1642 1643 for (int i = 0; i < _depth; i++) { 1644 _trace.append(" "); 1645 } 1646 1647 _trace.append("Node " + node.getClass().getName() + " evaluated to " 1648 + _evaluatedChildToken + "\n"); 1649 } 1650 } 1651 1652 // Temporary storage for the result of evaluating a child node. 1653 // This is protected so that derived classes can access it. 1654 protected ptolemy.data.Token _evaluatedChildToken = null; 1655 1656 protected ParseTreeTypeInference _typeInference = null; 1657 1658 /////////////////////////////////////////////////////////////////// 1659 //// protected methods //// 1660 1661 /** Invoke a method of the class for the given object, or retrieve a field 1662 * of it. 1663 */ 1664 private ptolemy.data.Token _invokeMethod(Class<?> clazz, Object object, 1665 String methodName, Type[] argTypes, Object[] argValues) 1666 throws IllegalActionException { 1667 Object result = null; 1668 1669 if (object != null && argTypes.length == 1) { 1670 Field[] fields = clazz.getFields(); 1671 for (Field field : fields) { 1672 if (field.getName().equals(methodName) 1673 && Modifier.isPublic(field.getModifiers())) { 1674 try { 1675 result = field.get(object); 1676 } catch (IllegalArgumentException e) { 1677 } catch (IllegalAccessException e) { 1678 } 1679 } 1680 } 1681 } 1682 1683 Method[] methods = clazz.getMethods(); 1684 int argCount = argTypes.length - 1; 1685 Object[] args = new Object[argCount]; 1686 for (Method method : methods) { 1687 if (method.getName().equals(methodName) 1688 && Modifier.isPublic(method.getModifiers())) { 1689 Class<?>[] parameterTypes = method.getParameterTypes(); 1690 if (parameterTypes.length != argCount) { 1691 continue; 1692 } 1693 boolean compatible = true; 1694 for (int i = 0; compatible && i < argCount; i++) { 1695 Class<?> argumentType = ConversionUtilities 1696 .convertTokenTypeToJavaType(argTypes[i + 1]); 1697 if (!parameterTypes[i].isAssignableFrom(argumentType)) { 1698 compatible = false; 1699 } else { 1700 Object argument = argValues[i + 1]; 1701 if (argument instanceof ObjectToken) { 1702 args[i] = ((ObjectToken) argument).getValue(); 1703 } else if (argument instanceof ptolemy.data.Token) { 1704 args[i] = ConversionUtilities 1705 .convertTokenToJavaType( 1706 (ptolemy.data.Token) argument); 1707 } else { 1708 args[i] = argument; 1709 } 1710 } 1711 } 1712 if (compatible && (object != null 1713 || Modifier.isStatic(method.getModifiers()))) { 1714 try { 1715 result = method.invoke(object, args); 1716 if (result == null) { 1717 result = new ObjectToken(null, 1718 method.getReturnType()); 1719 } 1720 break; 1721 } catch (IllegalArgumentException e) { 1722 } catch (IllegalAccessException e) { 1723 } catch (InvocationTargetException e) { 1724 } 1725 } 1726 } 1727 } 1728 1729 if (result == null) { 1730 return null; 1731 } else { 1732 return ConversionUtilities.convertJavaTypeToToken(result); 1733 } 1734 } 1735 1736 /////////////////////////////////////////////////////////////////// 1737 //// private variables //// 1738 1739 private int _depth = 0; 1740 1741 private ParserScope _scope = null; 1742 1743 private StringBuffer _trace = null; 1744}