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}