/** * Apply a function to the children of the specified node. This also handles indexing into * matrices and arrays, which look like function calls. * * <p>In the simplest cases, if the function is being applied to an expression that evaluated to a * FunctionToken, an ArrayToken, or a MatrixToken, then the function application is simply applied * to the available arguments. * * <p>More complex is if the function is being applied to an expression that does not evaluate as * above, resulting in three cases: Of primary interest is a function node that represents the * invocation of a Java method registered with the expression parser. This method uses the * reflection mechanism in the CachedMethod class to find the correct method, based on the types * of the arguments and invoke it. See that class for information about how method arguments are * matched. * * <p>A second case is the eval() function, which is handled specially in this method. The * argument to the function is evaluated, and the parsed as a string using the expression parser. * The result is then evaluated *in this evaluator*. This has the effect that any identifiers are * evaluated in the same scope as the original expression. * * <p>A third case is the matlab() function, which is also handled specially in this method, * allowing the evaluation of expressions in matlab if matlab is installed. The format of the * function is covered in {@link ptolemy.data.expr.MatlabUtilities#evaluate(String, Set, * ParserScope)} . * * @param node The specified node. * @exception IllegalActionException If an evaluation error occurs. */ public void visitFunctionApplicationNode(ASTPtFunctionApplicationNode node) throws IllegalActionException { // First check to see if the name references a valid variable. ptolemy.data.Token value = null; String functionName = node.getFunctionName(); if ((functionName != null) && (_scope != null)) { value = _scope.get(functionName); } // The first child contains the function name as an id. It is // ignored, and not evaluated unless necessary. int argCount = node.jjtGetNumChildren() - 1; Type[] argTypes = new Type[argCount]; ptolemy.data.Token[] argValues = new ptolemy.data.Token[argCount]; // First try to find a signature using argument token values. for (int i = 0; i < argCount; i++) { // Save the resulting value. _evaluateChild(node, i + 1); ptolemy.data.Token token = _evaluatedChildToken; argValues[i] = token; argTypes[i] = token.getType(); } if ((value != null) || (functionName == null)) { // The value of the first child should be either a FunctionToken, // an ArrayToken, or a MatrixToken. ptolemy.data.Token result; // Evaluate it, if necessary. if (value == null) { value = _evaluateChild(node, 0); } if (value instanceof ArrayToken) { if (argCount == 1) { result = _evaluateArrayIndex(node, value, argValues[0]); } else { // FIXME need better error message when the first child // is, say, an array expression throw new IllegalActionException( "Wrong number of indices " + "when referencing " + node.getFunctionName()); } } else if (value instanceof MatrixToken) { if (argCount == 2) { result = _evaluateMatrixIndex(node, value, argValues[0], argValues[1]); } else { // FIXME need better error message when the first child // is, say, a matrix expression throw new IllegalActionException( "Wrong number of indices " + "when referencing " + node.getFunctionName()); } } else if (value instanceof FunctionToken) { FunctionToken function = (FunctionToken) value; // check number of children against number of arguments of // function if (function.getNumberOfArguments() != argCount) { throw new IllegalActionException( "Wrong number of " + "arguments when applying function " + value.toString()); } result = function.apply(argValues); } else { // FIXME: It might be the a parameter is // shadowing a built-in function, in which // case, thrown an exception seems bogus. // The value cannot be indexed or applied // throw exception. throw new IllegalActionException("Cannot index or apply arguments to " + value.toString()); } _evaluatedChildToken = (result); return; } if (node.getFunctionName().compareTo("eval") == 0) { if (argCount == 1) { ptolemy.data.Token token = argValues[0]; if (token instanceof StringToken) { // Note that we do not want to store a reference to // the parser, because parsers take up alot of memory. PtParser parser = new PtParser(); ASTPtRootNode tree = parser.generateParseTree(((StringToken) token).stringValue()); // Note that we evaluate the recursed parse tree // in the same scope as this parse tree. tree.visit(this); // _evaluatedChildToken = (tree.getToken()); // FIXME cache? return; } } throw new IllegalActionException( "The function \"eval\" is" + " reserved for reinvoking the parser, and takes" + " exactly one String argument."); } if (node.getFunctionName().compareTo("matlab") == 0) { _evaluateChild(node, 1); ptolemy.data.Token token = _evaluatedChildToken; if (token instanceof StringToken) { String expression = ((StringToken) token).stringValue(); ParseTreeFreeVariableCollector collector = new ParseTreeFreeVariableCollector(); Set freeVariables = collector.collectFreeVariables(node, _scope); _evaluatedChildToken = MatlabUtilities.evaluate(expression, freeVariables, _scope); return; } else { throw new IllegalActionException( "The function \"matlab\" is" + " reserved for invoking the matlab engine, and takes" + " a string matlab expression argument followed by" + " a list of variable names that the matlab expression" + " refers to."); } } // If not a special function, then reflect the name of the function. ptolemy.data.Token result = _functionCall(node.getFunctionName(), argTypes, argValues); _evaluatedChildToken = (result); }
/** * Set the type of the given node to be the return type of the function determined for the given * node. * * @param node The specified node. * @exception IllegalActionException If an inference error occurs. */ public void visitFunctionApplicationNode(ASTPtFunctionApplicationNode node) throws IllegalActionException { int argCount = node.jjtGetNumChildren() - 1; String functionName = node.getFunctionName(); // Get the child types. Type[] childTypes = new Type[argCount]; for (int i = 0; i < argCount; i++) { childTypes[i] = _inferChild(node, i + 1); if (childTypes[i] == null) { throw new RuntimeException("node " + node + " has null type."); } } Type baseType = null; if ((_scope != null) && (functionName != null)) { baseType = _scope.getType(functionName); } if ((baseType != null) || (functionName == null)) { baseType = _inferChild(node, 0); // Handle as an array or matrix index into a named // variable reference. if (baseType instanceof FunctionType) { _setType(node, ((FunctionType) baseType).getReturnType()); return; } else if (argCount == 1) { if (baseType instanceof ArrayType) { _setType(node, ((ArrayType) baseType).getElementType()); return; } else { _assert( true, node, "Cannot use array " + "indexing on '" + node.getFunctionName() + "' because it does not have an array type."); } } else if (argCount == 2) { if (baseType instanceof MatrixType) { _setType(node, ((MatrixType) baseType).getElementType()); return; } else { _assert( true, node, "Cannot use matrix " + "indexing on '" + node.getFunctionName() + "' because it does not have a matrix type."); } } throw new IllegalActionException( "Wrong number of indices " + "when referencing " + functionName); } // Psuedo-temporary hack for casts.... if ((functionName.compareTo("cast") == 0) && (argCount == 2)) { ASTPtRootNode castTypeNode = ((ASTPtRootNode) node.jjtGetChild(0 + 1)); ParseTreeEvaluator parseTreeEvaluator = new ParseTreeEvaluator(); try { ptolemy.data.Token t = parseTreeEvaluator.evaluateParseTree(castTypeNode, _scope); _setType(node, t.getType()); } catch (IllegalActionException ex) { _setType(node, childTypes[0]); } return; // Note: We used to just do this, but in some case is it // useful to have functions which are type constructors... // Hence the above code. // _setType(node, // ((ASTPtRootNode) node.jjtGetChild(0 + 1)).getType()); // return; } // A hack, because the result of the 'fix' function is // dependent on its arguments, which should be constant. if ((functionName.compareTo("fix") == 0) && (argCount == 3)) { ASTPtRootNode lengthNode = ((ASTPtRootNode) node.jjtGetChild(1 + 1)); ASTPtRootNode integerBitsNode = ((ASTPtRootNode) node.jjtGetChild(2 + 1)); ParseTreeEvaluator parseTreeEvaluator = new ParseTreeEvaluator(); try { ptolemy.data.Token length = parseTreeEvaluator.evaluateParseTree(lengthNode, _scope); ptolemy.data.Token integerBits = parseTreeEvaluator.evaluateParseTree(integerBitsNode, _scope); _setType( node, new FixType( new Precision( ((ScalarToken) length).intValue(), ((ScalarToken) integerBits).intValue()))); return; } catch (Throwable throwable) { // Do nothing... rely on the regular method resolution // to generate the right type. } } if (functionName.compareTo("eval") == 0) { // We can't infer the type of eval expressions... _setType(node, BaseType.GENERAL); return; } if (functionName.compareTo("matlab") == 0) { // We can't infer the type of matlab expressions... _setType(node, BaseType.GENERAL); return; } // Otherwise, try to reflect the method name. CachedMethod cachedMethod; try { cachedMethod = CachedMethod.findMethod(functionName, childTypes, CachedMethod.FUNCTION); } catch (Exception ex) { // Deal with what happens if the method is not found. // FIXME: hopefully this is monotonic??? _setType(node, BaseType.UNKNOWN); return; } if (cachedMethod.isValid()) { Type type = cachedMethod.getReturnType(); _setType(node, type); } else { // If we reach this point it means the function was not found on // the search path. StringBuffer buffer = new StringBuffer(); for (int i = 0; i < childTypes.length; i++) { if (i == 0) { buffer.append(childTypes[i].toString()); } else { buffer.append(", " + childTypes[i].toString()); } } throw new IllegalActionException( "No matching function " + node.getFunctionName() + "( " + buffer + " )."); } }