001/* A visitor for parse trees of the expression language that infers types.
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 BE LIABLE TO ANY PARTY
012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
015 SUCH DAMAGE.
016
017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022 ENHANCEMENTS, OR MODIFICATIONS.
023
024
025 */
026package ptolemy.data.expr;
027
028import java.lang.reflect.Field;
029import java.lang.reflect.Method;
030import java.lang.reflect.Modifier;
031import java.util.Arrays;
032import java.util.HashMap;
033import java.util.HashSet;
034import java.util.Iterator;
035import java.util.List;
036import java.util.Map;
037import java.util.Set;
038
039import ptolemy.data.ScalarToken;
040import ptolemy.data.StringToken;
041import ptolemy.data.type.ArrayType;
042import ptolemy.data.type.BaseType;
043import ptolemy.data.type.FixType;
044import ptolemy.data.type.FunctionType;
045import ptolemy.data.type.MatrixType;
046import ptolemy.data.type.ObjectType;
047import ptolemy.data.type.RecordType;
048import ptolemy.data.type.Type;
049import ptolemy.data.type.TypeConstant;
050import ptolemy.data.type.TypeLattice;
051import ptolemy.graph.InequalityTerm;
052import ptolemy.kernel.CompositeEntity;
053import ptolemy.kernel.Entity;
054import ptolemy.kernel.util.IllegalActionException;
055import ptolemy.kernel.util.InternalErrorException;
056import ptolemy.kernel.util.NamedObj;
057import ptolemy.math.Precision;
058
059///////////////////////////////////////////////////////////////////
060//// ParseTreeTypeInference
061
062/**
063 This class visits parse trees and infers a type for each node in the
064 parse tree.  This type is stored in the parse tree.
065
066 @author Steve Neuendorffer
067 @version $Id$
068 @since Ptolemy II 2.1
069 @Pt.ProposedRating Green (neuendor)
070 @Pt.AcceptedRating Yellow (neuendor)
071 @see ptolemy.data.expr.ASTPtRootNode
072 */
073public class ParseTreeTypeInference extends AbstractParseTreeVisitor {
074    ///////////////////////////////////////////////////////////////////
075    ////                         public methods                    ////
076
077    /** Infer the type of the parse tree with the specified root node.
078     *  @param node The root of the parse tree.
079     *  @return The result of evaluation.
080     *  @exception IllegalActionException If an evaluation error occurs.
081     */
082    public Type inferTypes(ASTPtRootNode node) throws IllegalActionException {
083        node.visit(this);
084        return _inferredChildType;
085    }
086
087    /** Infer the type of the parse tree with the specified root node using
088     *  the specified scope to resolve the values of variables.
089     *  @param node The root of the parse tree.
090     *  @param scope The scope for evaluation.
091     *  @return The result of evaluation.
092     *  @exception IllegalActionException If an error occurs during
093     *   evaluation.
094     */
095    public Type inferTypes(ASTPtRootNode node, ParserScope scope)
096            throws IllegalActionException {
097        _scope = scope;
098        node.visit(this);
099        _scope = null;
100        return _inferredChildType;
101    }
102
103    /** Set the type of the given node to be an ArrayType that is the
104     *  least upper bound of the types of the node's children.
105     *  @param node The specified node.
106     *  @exception IllegalActionException If an inference error occurs.
107     */
108    @Override
109    public void visitArrayConstructNode(ASTPtArrayConstructNode node)
110            throws IllegalActionException {
111        Type[] childTypes = _inferAllChildren(node);
112
113        _setType(node,
114                new ArrayType(
115                        (Type) TypeLattice.lattice().leastUpperBound(
116                                new HashSet<Type>(Arrays.asList(childTypes))),
117                        childTypes.length));
118    }
119
120    /** Set the type of the given node to be the type that is the
121     *  least upper bound of the types of the node's children.
122     *  @param node The specified node.
123     *  @exception IllegalActionException If an inference error occurs.
124     */
125    @Override
126    public void visitBitwiseNode(ASTPtBitwiseNode node)
127            throws IllegalActionException {
128        Type[] childTypes = _inferAllChildren(node);
129
130        // FIXME: not consistent with expression evaluator.
131        _setType(node, (Type) TypeLattice.lattice()
132                .leastUpperBound(new HashSet<Type>(Arrays.asList(childTypes))));
133    }
134
135    /** Set the type of the given node to be the return type of the
136     *  function determined for the given node.
137     *  @param node The specified node.
138     *  @exception IllegalActionException If an inference error occurs.
139     */
140    @Override
141    public void visitFunctionApplicationNode(ASTPtFunctionApplicationNode node)
142            throws IllegalActionException {
143        int argCount = node.jjtGetNumChildren() - 1;
144        final String functionName = node.getFunctionName();
145
146        // Get the child types.
147        Type[] childTypes = new Type[argCount];
148
149        for (int i = 0; i < argCount; i++) {
150            childTypes[i] = _inferChild(node, i + 1);
151
152            if (childTypes[i] == null) {
153                throw new RuntimeException("node " + node + " has null type.");
154            }
155        }
156
157        Type type = null;
158        Type baseType = null;
159
160        if (_scope != null && functionName != null) {
161            type = _scope.getType(functionName);
162            if (!(type instanceof ObjectType)) {
163                // Pretend that we cannot resolve the type if it is an
164                // ObjectType.
165                baseType = type;
166            }
167        }
168
169        if (baseType != null || functionName == null) {
170            baseType = _inferChild(node, 0);
171
172            // Handle as an array or matrix index into a named
173            // variable reference.
174            if (baseType instanceof FunctionType) {
175                _setType(node, ((FunctionType) baseType).getReturnType());
176                return;
177            } else if (argCount == 1) {
178                if (baseType instanceof ArrayType) {
179                    _setType(node, ((ArrayType) baseType).getElementType());
180                    return;
181                } else {
182                    // Child node is not an array, but it can be
183                    // losslessly converted to an array (anything can be).
184                    // Note: parse tree evaluator also support
185                    // constructs like (1)(0), where the constant 1
186                    // gets converted automatically to an array.
187                    ASTPtRootNode child = (ASTPtRootNode) node.jjtGetChild(0);
188                    _setType(child, new ArrayType(baseType));
189                    return;
190                }
191            } else if (argCount == 2) {
192                if (baseType instanceof MatrixType) {
193                    _setType(node, ((MatrixType) baseType).getElementType());
194                    return;
195                } else {
196                    // Child node is not a matrix, but it might be
197                    // losslessly convertible to a matrix.  Attempt to do that.
198                    // Note: parse tree evaluator also support
199                    // constructs like (1)(0,0), where the constant 1
200                    // gets converted automatically to a matrix.
201                    ASTPtRootNode child = (ASTPtRootNode) node.jjtGetChild(0);
202                    _setType(child,
203                            MatrixType.getMatrixTypeForElementType(baseType));
204                    return;
205                }
206            }
207
208            throw new IllegalActionException("Wrong number of indices "
209                    + "when referencing function \"" + functionName
210                    + "\".  The number of indices was " + argCount
211                    + ". For arrays, the number of indices should be "
212                    + "1. For matrices, the number of indices should be 2. "
213                    + " The type of the function was \"" + baseType + "\".");
214        }
215
216        // Note that an alternative to searching for functions by name
217        // is to define fooReturnType() in data.expr.UtilityFunctions
218        // so that fooReturnType() is found by CacheMethod.
219        // Psuedo-temporary hack for casts....
220        if (functionName.compareTo("cast") == 0 && argCount == 2) {
221            ASTPtRootNode castTypeNode = (ASTPtRootNode) node
222                    .jjtGetChild(0 + 1);
223            ParseTreeEvaluator parseTreeEvaluator = new ParseTreeEvaluator();
224
225            try {
226                ptolemy.data.Token t = parseTreeEvaluator
227                        .evaluateParseTree(castTypeNode, _scope);
228                _setType(node, t.getType());
229            } catch (IllegalActionException ex) {
230                _setType(node, childTypes[0]);
231            }
232
233            return;
234
235            // Note: We used to just do this, but in some case is it
236            // useful to have functions which are type constructors...
237            // Hence the above code.
238            //  _setType(node,
239            //     ((ASTPtRootNode) node.jjtGetChild(0 + 1)).getType());
240            //  return;
241        }
242
243        // A hack, because the result of the 'fix' function is
244        // dependent on its arguments, which should be constant.
245        if (functionName.compareTo("fix") == 0 && argCount == 3) {
246            ASTPtRootNode lengthNode = (ASTPtRootNode) node.jjtGetChild(1 + 1);
247            ASTPtRootNode integerBitsNode = (ASTPtRootNode) node
248                    .jjtGetChild(2 + 1);
249            ParseTreeEvaluator parseTreeEvaluator = new ParseTreeEvaluator();
250
251            try {
252                ptolemy.data.Token length = parseTreeEvaluator
253                        .evaluateParseTree(lengthNode, _scope);
254
255                ptolemy.data.Token integerBits = parseTreeEvaluator
256                        .evaluateParseTree(integerBitsNode, _scope);
257                _setType(node,
258                        new FixType(new Precision(
259                                ((ScalarToken) length).intValue(),
260                                ((ScalarToken) integerBits).intValue())));
261                return;
262            } catch (Throwable throwable) {
263                // Do nothing... rely on the regular method resolution
264                // to generate the right type.
265            }
266        }
267
268        if (functionName.compareTo("eval") == 0) {
269            // We can't infer the type of eval expressions...
270            _setType(node, BaseType.GENERAL);
271            return;
272        }
273
274        if (functionName.compareTo("matlab") == 0) {
275            // We can't infer the type of matlab expressions...
276            _setType(node, BaseType.GENERAL);
277            return;
278        }
279
280        if (functionName.compareTo("fold") == 0) {
281            if (argCount == 3) {
282                if (childTypes[0] instanceof FunctionType) {
283                    FunctionType function = (FunctionType) childTypes[0];
284                    if (function.getArgCount() != 2) {
285                        throw new IllegalActionException("The first argument "
286                                + "to the function \"fold\" must be a function "
287                                + "that accepts two arguments.");
288                    }
289                    if (!function.getArgType(0).isCompatible(childTypes[1])) {
290                        throw new IllegalActionException("The second "
291                                + "argument of the function \"fold\" is not "
292                                + "compatible with the first parameter to the "
293                                + "function provided to \"fold\".");
294                    }
295                    // Do not check the type of the second argument of the
296                    // function, because if the collection is a Java collection,
297                    // then it is impossible to infer the types of the elements
298                    // in it without actually knowing the collection.
299                    _setType(node, function.getReturnType());
300                    return;
301                }
302            }
303
304            throw new IllegalActionException("The function \"fold\" is "
305                    + "a higher-order function that takes exactly 3 "
306                    + "arguments. The first argument must be a function "
307                    + "that takes 2 arguments. The second must be a value "
308                    + "that can be passed to the function as its first "
309                    + "argument. The third must be a list of values that "
310                    + "can be passed to the function as its second "
311                    + "argument.");
312        }
313
314        if (functionName.equals("object") && argCount == 1) {
315            ASTPtRootNode classNameNode = (ASTPtRootNode) node.jjtGetChild(1);
316            if (classNameNode instanceof ASTPtLeafNode) {
317                ptolemy.data.Token token = ((ASTPtLeafNode) classNameNode)
318                        .getToken();
319                if (token != null && token instanceof StringToken) {
320                    String className = ((StringToken) token).stringValue();
321                    try {
322                        Class clazz = Class.forName(className);
323                        _setType(node, new ObjectType(clazz));
324                        return;
325                    } catch (ClassNotFoundException e) {
326                        throw new IllegalActionException(
327                                "Unable to load class " + className);
328                    }
329                } else if (token == null) {
330                    _setType(node, new ObjectType(Object.class));
331                    return;
332                }
333            }
334        }
335
336        // Otherwise, try to reflect the method name.
337        CachedMethod cachedMethod;
338
339        try {
340            cachedMethod = CachedMethod.findMethod(functionName, childTypes,
341                    CachedMethod.FUNCTION);
342        } catch (Exception ex) {
343            // Deal with what happens if the method is not found.
344            // FIXME: hopefully this is monotonic???
345            _setType(node, BaseType.UNKNOWN);
346            return;
347        }
348
349        if (cachedMethod.isValid()) {
350            baseType = cachedMethod.getReturnType();
351            _setType(node, baseType);
352            return;
353        }
354
355        if (type instanceof ObjectType) {
356            // If it is ObjectType, set it here.
357            _setType(node, type);
358            return;
359        }
360
361        // If we reach this point it means the function was not found on the
362        // search path.
363        StringBuffer buffer = new StringBuffer();
364
365        for (int i = 0; i < childTypes.length; i++) {
366            if (i == 0) {
367                buffer.append(childTypes[i].toString());
368            } else {
369                buffer.append(", " + childTypes[i].toString());
370            }
371        }
372
373        throw new IllegalActionException(
374                "No matching function " + functionName + "( " + buffer + " ).");
375    }
376
377    /** Set the type of the given node to be a function type whose
378     *  argument types are determined by the children of the node.
379     *  The return type of the function type is determined by
380     *  inferring the type of function's expression in a scope that
381     *  adds identifiers for each argument to the current scope.
382     *  @param node The specified node.
383     *  @exception IllegalActionException If an inference error occurs.
384     */
385    @Override
386    public void visitFunctionDefinitionNode(ASTPtFunctionDefinitionNode node)
387            throws IllegalActionException {
388        final Map map = new HashMap();
389
390        for (int i = 0; i < node._argTypes.length; i++) {
391            map.put(node.getArgumentNameList().get(i),
392                    node.getArgumentTypes()[i]);
393        }
394
395        // Push the current scope.
396        final ParserScope currentScope = _scope;
397        ParserScope functionScope = new ParserScope() {
398            @Override
399            public ptolemy.data.Token get(String name) {
400                return null;
401            }
402
403            @Override
404            public Type getType(String name) throws IllegalActionException {
405                Type type = (Type) map.get(name);
406
407                if (type == null && currentScope != null) {
408                    return currentScope.getType(name);
409                } else {
410                    return type;
411                }
412            }
413
414            @Override
415            public InequalityTerm getTypeTerm(String name)
416                    throws IllegalActionException {
417                Type type = (Type) map.get(name);
418
419                if (type == null && currentScope != null) {
420                    return currentScope.getTypeTerm(name);
421                } else {
422                    return new TypeConstant(type);
423                }
424            }
425
426            @Override
427            public Set identifierSet() throws IllegalActionException {
428                Set set = currentScope.identifierSet();
429                set.addAll(map.keySet());
430                return set;
431            }
432        };
433
434        _scope = functionScope;
435        node.getExpressionTree().visit(this);
436
437        Type returnType = _inferredChildType;
438        FunctionType type = new FunctionType(node._argTypes, returnType);
439        _setType(node, type);
440        _scope = currentScope;
441        return;
442    }
443
444    /** Set the type of the given node to be the least upper bound of
445     *  the types of the two branches of the if.
446     *  @param node The specified node.
447     *  @exception IllegalActionException If an inference error occurs.
448     */
449    @Override
450    public void visitFunctionalIfNode(ASTPtFunctionalIfNode node)
451            throws IllegalActionException {
452        Type conditionalType = _inferChild(node, 0);
453
454        if (conditionalType != BaseType.BOOLEAN) {
455            throw new IllegalActionException(
456                    "Functional-if must branch on a boolean, "
457                            + "but instead type was " + conditionalType);
458        }
459
460        Type trueType = _inferChild(node, 1);
461        Type falseType = _inferChild(node, 2);
462
463        _setType(node, (Type) TypeLattice.lattice().leastUpperBound(trueType,
464                falseType));
465    }
466
467    /** Set the type of the given node to be the type of constant the
468     *  variable refers to, if the node represents a constant, or the
469     *  type of the identifier the node refers to in the current
470     *  scope.
471     *  @param node The specified node.
472     *  @exception IllegalActionException If an inference error
473     *  occurs, or an identifier is not bound in the current scope.
474     */
475    @Override
476    public void visitLeafNode(ASTPtLeafNode node)
477            throws IllegalActionException {
478        if (node.isConstant() && node.isEvaluated()) {
479            _setType(node, node.getToken().getType());
480            return;
481        }
482
483        String name = node.getName();
484
485        Type type = null;
486        if (_scope != null) {
487            type = _scope.getType(name);
488
489            if (type != null && !(type instanceof ObjectType)) {
490                // Pretend that we cannot resolve the type if it is an
491                // ObjectType.
492                _setType(node, type);
493                return;
494            }
495        }
496
497        // Look up for constants.
498        if (Constants.get(name) != null) {
499            // A named constant that is recognized by the parser.
500            _setType(node, Constants.get(name).getType());
501            return;
502        }
503
504        if (type != null) {
505            _setType(node, type);
506            return;
507        }
508
509        throw new IllegalActionException("The ID " + name + " is undefined.");
510    }
511
512    /** Set the type of the given node to be boolean.
513     *  @param node The specified node.
514     *  @exception IllegalActionException If an inference error occurs.
515     */
516    @Override
517    public void visitLogicalNode(ASTPtLogicalNode node)
518            throws IllegalActionException {
519        _inferAllChildren(node);
520
521        // FIXME: check arguments are valid?
522        _setType(node, BaseType.BOOLEAN);
523    }
524
525    /** Set the type of the given node to be an MatrixType based on the
526     *  least upper bound of the types of the node's children.
527     *  @param node The specified node.
528     *  @exception IllegalActionException If an inference error occurs.
529     */
530    @Override
531    public void visitMatrixConstructNode(ASTPtMatrixConstructNode node)
532            throws IllegalActionException {
533        Type[] childTypes = _inferAllChildren(node);
534
535        Type elementType = (Type) TypeLattice.lattice()
536                .leastUpperBound(new HashSet<Type>(Arrays.asList(childTypes)));
537
538        Type matrixType = MatrixType.getMatrixTypeForElementType(elementType);
539        _setType(node, matrixType);
540    }
541
542    /** Set the type of the given node to be the return type of the
543     *  method determined for the given node.
544     *  @param node The specified node.
545     *  @exception IllegalActionException If an inference error occurs.
546     */
547    @Override
548    public void visitMethodCallNode(ASTPtMethodCallNode node)
549            throws IllegalActionException {
550
551        Type[] childTypes = _inferAllChildren(node);
552
553        // Handle indexing into a record.
554        if (childTypes.length == 1 && childTypes[0] instanceof RecordType) {
555            RecordType type = (RecordType) childTypes[0];
556
557            if (type.labelSet().contains(node.getMethodName())) {
558                _setType(node, type.get(node.getMethodName()));
559                return;
560            }
561        }
562
563        // Fix to Kepler bug #6629:
564        // Depending on the order in which type constraints are satisfied,
565        // some child types may remain UNKNOWN for one or more iterations
566        // of Mogensen's algorithm. Hence, keep the type of the node
567        // to UNKNOWN until the types for the children are resolved.
568        if (childTypes.length == 1 && childTypes[0] == BaseType.UNKNOWN) {
569            _setType(node, BaseType.UNKNOWN);
570        } else {
571            _setType(node, _methodCall(node.getMethodName(), childTypes));
572        }
573    }
574
575    /** Set the type of the given node to be the type of the first
576     *  child of the given node.
577     *  @param node The specified node.
578     *  @exception IllegalActionException If an inference error occurs.
579     */
580    @Override
581    public void visitPowerNode(ASTPtPowerNode node)
582            throws IllegalActionException {
583        Type[] childTypes = _inferAllChildren(node);
584
585        // FIXME: Check that exponents are valid??
586        Type baseType = childTypes[0];
587        _setType(node, baseType);
588    }
589
590    /** Set the type of the given node to be the least upper bound
591     *  type of the types of the node's children.
592     *  @param node The specified node.
593     *  @exception IllegalActionException If an inference error occurs.
594     */
595    @Override
596    public void visitProductNode(ASTPtProductNode node)
597            throws IllegalActionException {
598        Type[] childTypes = _inferAllChildren(node);
599
600        List lexicalTokenList = node.getLexicalTokenList();
601        int numChildren = node.jjtGetNumChildren();
602
603        Type resultType = childTypes[0];
604        for (int i = 1; i < numChildren; i++) {
605            Token operator = (Token) lexicalTokenList.get(i - 1);
606            Type nextType = childTypes[i];
607            if (operator.kind == PtParserConstants.MULTIPLY) {
608                resultType = resultType.multiply(nextType);
609            } else if (operator.kind == PtParserConstants.DIVIDE) {
610                resultType = resultType.divide(nextType);
611            } else if (operator.kind == PtParserConstants.MODULO) {
612                resultType = resultType.modulo(nextType);
613            } else {
614                _assert(false, node, "Invalid operation");
615            }
616        }
617        _setType(node, resultType);
618    }
619
620    /** Set the type of the given node to be a record token that
621     *  contains fields for each name in the record construction,
622     *  where the type of each field in the record is determined by
623     *  the corresponding type of the child nodes.
624     *  @param node The specified node.
625     *  @exception IllegalActionException If an inference error occurs.
626     */
627    @Override
628    public void visitRecordConstructNode(ASTPtRecordConstructNode node)
629            throws IllegalActionException {
630        Type[] childTypes = _inferAllChildren(node);
631
632        String[] names = (String[]) node.getFieldNames()
633                .toArray(new String[node.jjtGetNumChildren()]);
634
635        _setType(node, new RecordType(names, childTypes));
636    }
637
638    /** Set the type of the given node to be boolean.
639     *  @param node The specified node.
640     *  @exception IllegalActionException If an inference error occurs.
641     */
642    @Override
643    public void visitRelationalNode(ASTPtRelationalNode node)
644            throws IllegalActionException {
645        /* Type[] childTypes = */_inferAllChildren(node);
646
647        // FIXME: Check args are booleans?
648        _setType(node, BaseType.BOOLEAN);
649    }
650
651    /** Set the type of the given node to be the type of the first
652     *  child of the given node.
653     *  @param node The specified node.
654     *  @exception IllegalActionException If an inference error occurs.
655     */
656    @Override
657    public void visitShiftNode(ASTPtShiftNode node)
658            throws IllegalActionException {
659        Type[] childTypes = _inferAllChildren(node);
660
661        // FIXME: Check others are scalars?
662        Type baseType = childTypes[0];
663        _setType(node, baseType);
664    }
665
666    /** Set the type of the given node to be the least upper bound
667     *  type of the types of the node's children.
668     *  @param node The specified node.
669     *  @exception IllegalActionException If an inference error occurs.
670     */
671    @Override
672    public void visitSumNode(ASTPtSumNode node) throws IllegalActionException {
673        Type[] childTypes = _inferAllChildren(node);
674
675        List lexicalTokenList = node.getLexicalTokenList();
676        int numChildren = node.jjtGetNumChildren();
677
678        Type resultType = childTypes[0];
679        for (int i = 1; i < numChildren; i++) {
680            Token operator = (Token) lexicalTokenList.get(i - 1);
681            Type nextType = childTypes[i];
682            if (operator.kind == PtParserConstants.PLUS) {
683                resultType = resultType.add(nextType);
684            } else if (operator.kind == PtParserConstants.MINUS) {
685                resultType = resultType.subtract(nextType);
686            } else {
687                _assert(false, node, "Invalid operation");
688            }
689        }
690        _setType(node, resultType);
691    }
692
693    /** Set the type of the given node to be the type of the
694     *  child of the given node.
695     *  @param node The specified node.
696     *  @exception IllegalActionException If an inference error occurs.
697     */
698    @Override
699    public void visitUnaryNode(ASTPtUnaryNode node)
700            throws IllegalActionException {
701        Type[] childTypes = _inferAllChildren(node);
702        Type baseType = childTypes[0];
703        if (node.isMinus()) {
704            _setType(node, baseType.zero().subtract(baseType));
705        } else {
706            _setType(node, baseType);
707        }
708    }
709
710    ///////////////////////////////////////////////////////////////////
711    ////                         protected methods                 ////
712
713    /**
714     * Assert that the given boolean value, which describes the given
715     * parse tree node is true.  If it is false, then throw a new
716     * InternalErrorException that describes the node that includes
717     * the given message.
718     * @param flag If false, then throw an InternalErrorException
719     * @param node The node
720     * @param message The message included in the exception
721     */
722    protected void _assert(boolean flag, ASTPtRootNode node, String message) {
723        if (!flag) {
724            throw new InternalErrorException(message + ": " + node.toString());
725        }
726    }
727
728    /** Get the return type of a method belonging to the specified class, or the
729     *  type of a field belonging to it.
730     */
731    protected Type _getMethodReturnType(Class<?> clazz, String methodName,
732            Type[] argTypes) throws IllegalActionException {
733        Class<?> result = null;
734
735        if (argTypes.length == 1) {
736            Field[] fields = clazz.getFields();
737            for (Field field : fields) {
738                if (field.getName().equals(methodName)
739                        && Modifier.isPublic(field.getModifiers())) {
740                    result = field.getType();
741                    break;
742                }
743            }
744        }
745
746        Method[] methods = clazz.getMethods();
747        int argCount = argTypes.length - 1;
748        for (Method method : methods) {
749            if (method.getName().equals(methodName)
750                    && Modifier.isPublic(method.getModifiers())) {
751                Class<?>[] parameterTypes = method.getParameterTypes();
752                if (parameterTypes.length != argCount) {
753                    continue;
754                }
755                boolean compatible = true;
756                for (int i = 0; compatible && i < argCount; i++) {
757                    Class<?> argumentType = ConversionUtilities
758                            .convertTokenTypeToJavaType(argTypes[i + 1]);
759                    if (!parameterTypes[i].isAssignableFrom(argumentType)) {
760                        compatible = false;
761                    }
762                }
763                if (compatible) {
764                    result = method.getReturnType();
765                    break;
766                }
767            }
768        }
769
770        if (result == null) {
771            return null;
772        } else {
773            if (Variable.class.isAssignableFrom(result)) {
774                return BaseType.UNKNOWN;
775            } else {
776                return ConversionUtilities.convertJavaTypeToTokenType(result);
777            }
778        }
779    }
780
781    /** Return the type of the identifier with the given name.
782     *  @exception IllegalActionException If the identifier is undefined.
783     */
784    protected Type _getTypeForName(String name) throws IllegalActionException {
785        Type type = null;
786        if (_scope != null) {
787            type = _scope.getType(name);
788
789            if (type != null && !(type instanceof ObjectType)) {
790                // Pretend that we cannot resolve the type if it is an
791                // ObjectType.
792                return type;
793            }
794        }
795
796        // Look up for constants.
797        if (Constants.get(name) != null) {
798            // A named constant that is recognized by the parser.
799            return Constants.get(name).getType();
800        }
801
802        if (type != null) {
803            return type;
804        }
805
806        throw new IllegalActionException("The ID " + name + " is undefined.");
807
808        //        return BaseType.GENERAL;
809    }
810
811    /** Loop through all of the children of this node,
812     *  visiting each one of them, which will cause their token
813     *  value to be determined.
814     */
815    protected Type[] _inferAllChildren(ASTPtRootNode node)
816            throws IllegalActionException {
817        Type[] types = new Type[node.jjtGetNumChildren()];
818        int numChildren = node.jjtGetNumChildren();
819
820        for (int i = 0; i < numChildren; i++) {
821            _inferChild(node, i);
822
823            Type type = _inferredChildType;
824
825            if (type == null) {
826                throw new RuntimeException(
827                        "node " + node.jjtGetChild(i) + " has no type.");
828            }
829
830            types[i] = type;
831        }
832
833        return types;
834    }
835
836    /** Visit the child with the given index of the given node.
837     *  This is usually called while visiting the given node.
838     */
839    protected Type _inferChild(ASTPtRootNode node, int i)
840            throws IllegalActionException {
841        ASTPtRootNode child = (ASTPtRootNode) node.jjtGetChild(i);
842        child.visit(this);
843        return _inferredChildType;
844    }
845
846    /** Test if the given identifier is valid.
847     */
848    protected boolean _isValidName(String name) throws IllegalActionException {
849        if (_scope != null) {
850            try {
851                return _scope.getType(name) != null;
852            } catch (Exception ex) {
853                return false;
854            }
855        } else {
856            return false;
857        }
858    }
859
860    /** Infer the type of the specified method.  The type of the object on which
861     *  the method is evaluated should be the first argument.
862     *  @param methodName The method name.
863     *  @param argTypes An array of argument types.
864     *  @exception IllegalActionException If an evaluation error occurs.
865     *  @see ParseTreeEvaluator#_methodCall(String, Type[], Object[])
866     */
867    protected Type _methodCall(String methodName, Type[] argTypes)
868            throws IllegalActionException {
869        CachedMethod cachedMethod = CachedMethod.findMethod(methodName,
870                argTypes, CachedMethod.METHOD);
871
872        if (cachedMethod.isValid()) {
873            Type type = cachedMethod.getReturnType();
874            return type;
875        }
876
877        if (argTypes[0] instanceof ObjectType) {
878            Object object = ((ObjectType) argTypes[0]).getValue();
879            if (object != null) {
880                if (object instanceof NamedObj) {
881                    Object result = ((NamedObj) object)
882                            .getAttribute(methodName);
883                    if (result == null && object instanceof Entity) {
884                        result = ((Entity) object).getPort(methodName);
885                    }
886                    if (result == null && object instanceof CompositeEntity) {
887                        result = ((CompositeEntity) object)
888                                .getEntity(methodName);
889                        if (result == null) {
890                            result = ((CompositeEntity) object)
891                                    .getRelation(methodName);
892                        }
893                    }
894
895                    if (result == null) {
896                        List attributes = ((NamedObj) object)
897                                .attributeList(ContainmentExtender.class);
898                        Iterator attrIterator = attributes.iterator();
899                        while (result == null && attrIterator.hasNext()) {
900                            ContainmentExtender extender = (ContainmentExtender) attrIterator
901                                    .next();
902                            result = extender.getContainedObject(methodName);
903                        }
904                    }
905                    if (result != null) {
906                        if (result instanceof Variable) {
907                            // If the variable is a Matrix or an Array, the return type
908                            // will be the element type, not the Matrix or Array.
909                            Type type = ((Variable) result).getType();
910                            if (type instanceof MatrixType) {
911                                return ((MatrixType) type).getElementType();
912                            }
913                            if (type instanceof ArrayType) {
914                                return ((ArrayType) type).getElementType();
915                            }
916                            return type;
917                        } else {
918                            return new ObjectType(result, result.getClass());
919                        }
920                    }
921                }
922            }
923
924            Class<?> valueClass = ((ObjectType) argTypes[0]).getValueClass();
925            if (valueClass == null) {
926                valueClass = Object.class;
927            }
928            Set<Class<?>> classes = new HashSet<Class<?>>();
929            classes.add(valueClass);
930            while (!classes.isEmpty()) {
931                Iterator<Class<?>> iterator = classes.iterator();
932                valueClass = iterator.next();
933                iterator.remove();
934
935                if (!Modifier.isPublic(valueClass.getModifiers())) {
936                    for (Class<?> interf : valueClass.getInterfaces()) {
937                        classes.add(interf);
938                    }
939                    Class<?> superclass = valueClass.getSuperclass();
940                    if (superclass != null) {
941                        classes.add(superclass);
942                    }
943                } else {
944                    Type result = _getMethodReturnType(valueClass, methodName,
945                            argTypes);
946                    if (result != null) {
947                        return result;
948                    }
949                }
950            }
951        }
952
953        // If we reach this point it means the function was not found on
954        // the search path.
955        StringBuffer buffer = new StringBuffer();
956
957        for (int i = 1; i < argTypes.length; i++) {
958            if (i == 1) {
959                buffer.append(argTypes[i].toString());
960            } else {
961                buffer.append(", " + argTypes[i].toString());
962            }
963        }
964
965        throw new IllegalActionException("No matching method " + argTypes[0]
966                + "." + methodName + "( " + buffer + " ).");
967    }
968
969    protected void _setType(ASTPtRootNode node, Type type) {
970        //      System.out.println("type of " + node + " is " + type);
971        _inferredChildType = type;
972        node.setType(type);
973    }
974
975    protected Type _inferredChildType;
976
977    protected ParserScope _scope;
978}