info.fulloo.trygve.parser.Pass1Listener.java Source code

Java tutorial

Introduction

Here is the source code for info.fulloo.trygve.parser.Pass1Listener.java

Source

package info.fulloo.trygve.parser;

/*
 * Trygve IDE 2.0
 *   Copyright (c)2016 James O. Coplien, jcoplien@gmail.com
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 * 
 *  For further information about the trygve project, please contact
 *  Jim Coplien at jcoplien@gmail.com
 * 
 */

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.RuleContext;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.misc.Interval;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.antlr.v4.runtime.tree.TerminalNodeImpl;

import info.fulloo.trygve.configuration.ConfigurationOptions;
import info.fulloo.trygve.declarations.AccessQualifier;
import info.fulloo.trygve.declarations.ActualArgumentList;
import info.fulloo.trygve.declarations.ActualOrFormalParameterList;
import info.fulloo.trygve.declarations.BodyPart;
import info.fulloo.trygve.declarations.Declaration;
import info.fulloo.trygve.declarations.Declaration.ClassOrContextDeclaration;
import info.fulloo.trygve.declarations.Declaration.ErrorDeclaration;
import info.fulloo.trygve.declarations.Declaration.InterfaceDeclaration;
import info.fulloo.trygve.declarations.Declaration.ObjectSubclassDeclaration;
import info.fulloo.trygve.declarations.Declaration.RoleArrayDeclaration;
import info.fulloo.trygve.declarations.FormalParameterList;
import info.fulloo.trygve.declarations.Message;
import info.fulloo.trygve.declarations.TemplateInstantiationInfo;
import info.fulloo.trygve.declarations.Declaration.ClassDeclaration;
import info.fulloo.trygve.declarations.Declaration.ContextDeclaration;
import info.fulloo.trygve.declarations.Declaration.DeclarationList;
import info.fulloo.trygve.declarations.Declaration.ExprAndDeclList;
import info.fulloo.trygve.declarations.Declaration.MethodDeclaration;
import info.fulloo.trygve.declarations.Declaration.MethodSignature;
import info.fulloo.trygve.declarations.Declaration.ObjectDeclaration;
import info.fulloo.trygve.declarations.Declaration.RoleDeclaration;
import info.fulloo.trygve.declarations.Declaration.StagePropDeclaration;
import info.fulloo.trygve.declarations.Declaration.StagePropArrayDeclaration;
import info.fulloo.trygve.declarations.Declaration.TemplateDeclaration;
import info.fulloo.trygve.declarations.Declaration.TypeDeclarationList;
import info.fulloo.trygve.declarations.Type;
import info.fulloo.trygve.declarations.Type.BuiltInType;
import info.fulloo.trygve.declarations.Type.ClassOrContextType;
import info.fulloo.trygve.declarations.TypeDeclaration;
import info.fulloo.trygve.declarations.Type.ArrayType;
import info.fulloo.trygve.declarations.Type.ClassType;
import info.fulloo.trygve.declarations.Type.ContextType;
import info.fulloo.trygve.declarations.Type.ErrorType;
import info.fulloo.trygve.declarations.Type.RoleType;
import info.fulloo.trygve.declarations.Type.StagePropType;
import info.fulloo.trygve.declarations.Type.TemplateParameterType;
import info.fulloo.trygve.declarations.Type.TemplateType;
import info.fulloo.trygve.declarations.Type.InterfaceType;
import info.fulloo.trygve.declarations.Type.VarargsType;
import info.fulloo.trygve.error.ErrorLogger;
import info.fulloo.trygve.error.ErrorLogger.ErrorIncidenceType;
import info.fulloo.trygve.expressions.Constant;
import info.fulloo.trygve.expressions.Expression;
import info.fulloo.trygve.expressions.Expression.ExpressionList;
import info.fulloo.trygve.expressions.ExpressionStackAPI;
import info.fulloo.trygve.expressions.Constant.BooleanConstant;
import info.fulloo.trygve.expressions.Expression.ArrayExpression;
import info.fulloo.trygve.expressions.Expression.ArrayIndexExpression;
import info.fulloo.trygve.expressions.Expression.ArrayIndexExpressionUnaryOp;
import info.fulloo.trygve.expressions.Expression.AssignmentExpression;
import info.fulloo.trygve.expressions.Expression.InternalAssignmentExpression;
import info.fulloo.trygve.expressions.Expression.BlockExpression;
import info.fulloo.trygve.expressions.Expression.BreakExpression;
import info.fulloo.trygve.expressions.Expression.ContinueExpression;
import info.fulloo.trygve.expressions.Expression.DoWhileExpression;
import info.fulloo.trygve.expressions.Expression.DupMessageExpression;
import info.fulloo.trygve.expressions.Expression.ErrorExpression;
import info.fulloo.trygve.expressions.Expression.IdentityBooleanExpression;
import info.fulloo.trygve.expressions.Expression.IndexExpression;
import info.fulloo.trygve.expressions.Expression.LastIndexExpression;
import info.fulloo.trygve.expressions.Expression.ForExpression;
import info.fulloo.trygve.expressions.Expression.IdentifierExpression;
import info.fulloo.trygve.expressions.Expression.IfExpression;
import info.fulloo.trygve.expressions.Expression.MessageExpression;
import info.fulloo.trygve.expressions.Expression.NewArrayExpression;
import info.fulloo.trygve.expressions.Expression.NewExpression;
import info.fulloo.trygve.expressions.Expression.NullExpression;
import info.fulloo.trygve.expressions.Expression.PowerExpression;
import info.fulloo.trygve.expressions.Expression.ProductExpression;
import info.fulloo.trygve.expressions.Expression.QualifiedClassMemberExpression;
import info.fulloo.trygve.expressions.Expression.QualifiedClassMemberExpressionUnaryOp;
import info.fulloo.trygve.expressions.Expression.QualifiedIdentifierExpression;
import info.fulloo.trygve.expressions.Expression.QualifiedIdentifierExpressionUnaryOp;
import info.fulloo.trygve.expressions.Expression.RelopExpression;
import info.fulloo.trygve.expressions.Expression.ReturnExpression;
import info.fulloo.trygve.expressions.Expression.RoleArrayIndexExpression;
import info.fulloo.trygve.expressions.Expression.SumExpression;
import info.fulloo.trygve.expressions.Expression.SwitchBodyElement;
import info.fulloo.trygve.expressions.Expression.SwitchExpression;
import info.fulloo.trygve.expressions.Expression.TopOfStackExpression;
import info.fulloo.trygve.expressions.Expression.UnaryAbelianopExpression;
import info.fulloo.trygve.expressions.Expression.UnaryopExpressionWithSideEffect;
import info.fulloo.trygve.expressions.Expression.WhileExpression;
import info.fulloo.trygve.expressions.Expression.UnaryopExpressionWithSideEffect.PreOrPost;
import info.fulloo.trygve.expressions.MethodInvocationEnvironmentClass;
import info.fulloo.trygve.mylibrary.SimpleList;
import info.fulloo.trygve.parser.KantParser.Abelian_atomContext;
import info.fulloo.trygve.parser.KantParser.Abelian_exprContext;
import info.fulloo.trygve.parser.KantParser.Abelian_productContext;
import info.fulloo.trygve.parser.KantParser.Abelian_unary_opContext;
import info.fulloo.trygve.parser.KantParser.Argument_listContext;
import info.fulloo.trygve.parser.KantParser.BlockContext;
import info.fulloo.trygve.parser.KantParser.Boolean_atomContext;
import info.fulloo.trygve.parser.KantParser.Boolean_exprContext;
import info.fulloo.trygve.parser.KantParser.Boolean_productContext;
import info.fulloo.trygve.parser.KantParser.Boolean_unary_opContext;
import info.fulloo.trygve.parser.KantParser.Builtin_type_nameContext;
import info.fulloo.trygve.parser.KantParser.Compound_type_nameContext;
import info.fulloo.trygve.parser.KantParser.Do_while_exprContext;
import info.fulloo.trygve.parser.KantParser.ExprContext;
import info.fulloo.trygve.parser.KantParser.Expr_and_decl_listContext;
import info.fulloo.trygve.parser.KantParser.For_exprContext;
import info.fulloo.trygve.parser.KantParser.Identifier_listContext;
import info.fulloo.trygve.parser.KantParser.If_exprContext;
import info.fulloo.trygve.parser.KantParser.MessageContext;
import info.fulloo.trygve.parser.KantParser.Method_declContext;
import info.fulloo.trygve.parser.KantParser.Method_decl_hookContext;
import info.fulloo.trygve.parser.KantParser.Method_signatureContext;
import info.fulloo.trygve.parser.KantParser.Object_declContext;
import info.fulloo.trygve.parser.KantParser.ProgramContext;
import info.fulloo.trygve.parser.KantParser.Switch_bodyContext;
import info.fulloo.trygve.parser.KantParser.Switch_exprContext;
import info.fulloo.trygve.parser.KantParser.Type_and_expr_and_decl_listContext;
import info.fulloo.trygve.parser.KantParser.Type_declarationContext;
import info.fulloo.trygve.parser.KantParser.While_exprContext;
import info.fulloo.trygve.semantic_analysis.Program;
import info.fulloo.trygve.semantic_analysis.StaticScope;
import info.fulloo.trygve.semantic_analysis.StaticScope.StaticInterfaceScope;

public class Pass1Listener extends Pass0Listener {
    public Pass1Listener(final ParsingData parsingData) {
        super(parsingData);
        printProductionsDebug = ConfigurationOptions.tracePass1();
        stackSnapshotDebug = ConfigurationOptions.stackSnapshotDebug();
    }

    private static class DeclarationsAndInitializers {
        public DeclarationsAndInitializers(final List<ObjectDeclaration> objectDecls,
                final List<BodyPart> initializations) {
            objectDecls_ = objectDecls;
            initializations_ = initializations;
        }

        public List<ObjectDeclaration> objectDecls() {
            return objectDecls_;
        }

        public List<BodyPart> initializations() {
            return initializations_;
        }

        private final List<ObjectDeclaration> objectDecls_;
        private final List<BodyPart> initializations_;
    }

    // -----------------------------------------------------------------------------------

    @Override
    public void exitProgram(KantParser.ProgramContext ctx) {
        // : type_declaration_list main
        // | type_declaration_list

        final TypeDeclarationList currentList = parsingData_.popTypeDeclarationList();
        final TypeDeclarationList templateInstantiationList = parsingData_.currentTemplateInstantiationList();

        if (null == ctx.main()) {
            if (null != ctx.getStop()) {
                errorHook5p2(ErrorIncidenceType.Fatal, ctx.getStop().getLine(), "Missing main expression.", "", "",
                        "");
            } else {
                errorHook5p2(ErrorIncidenceType.Fatal, 1, "Missing main expression.",
                        " Did you enter any program at all?", "", "");
            }
            new Program(null, currentList, templateInstantiationList); // static singleton
        } else {
            final Expression main = parsingData_.popExpression();
            new Program(main, currentList, templateInstantiationList); // static singleton
        }

        printProductionsDebug = false;
        stackSnapshotDebug = false;

        if (printProductionsDebug) {
            if (null != ctx.main()) {
                System.err.println("program : type_declaration_list main");
            } else {
                System.err.println("program : type_declaration_list [ERROR]");
            }
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void enterMain(KantParser.MainContext ctx) {
        // main
        //   : expr

        /* nothing */
    }

    @Override
    public void exitMain(KantParser.MainContext ctx) {
        // main
        //   : expr

        if (printProductionsDebug) {
            System.err.println("main : expr");
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void enterType_declaration_list(KantParser.Type_declaration_listContext ctx) {
        // type_declaration_list
        //      : type_declaration
        //      | type_declaration_list type_declaration
        //       | /* null */

        /* nothing */
    }

    @Override
    public void enterContext_declaration(KantParser.Context_declarationContext ctx) {
        // context_declaration : 'context' JAVA_ID (implements_list)* '{' context_body '}'
        final String name = ctx.JAVA_ID().getText();

        if (null != ctx.context_body()) {
            final ContextDeclaration oldContext = currentContext_;
            currentContext_ = this.lookupOrCreateContextDeclaration(name, ctx.getStart().getLine());
            currentContext_.setParentContext(oldContext);
            parsingData_.pushContextDeclaration(currentContext_);
        } else {
            assert false;
        }
    }

    @Override
    public void enterInterface_declaration(KantParser.Interface_declarationContext ctx) {
        // interface_declaration : 'interface' JAVA_ID '{' interface_body '}'
        final String name = ctx.JAVA_ID().getText();

        if (null != ctx.interface_body()) {
            currentInterface_ = this.lookupOrCreateInterfaceDeclaration(name, ctx.getStart().getLine());
            currentScope_ = currentInterface_.enclosedScope();
        } else {
            assert false; // need diagnostic message for user later
        }
    }

    @Override
    protected ContextDeclaration lookupOrCreateContextDeclaration(final String name, final int lineNumber) {
        // Pass 1 - 4 version
        final ContextDeclaration contextDecl = currentScope_.lookupContextDeclarationRecursive(name);
        assert null != contextDecl; // maybe turn into an error message later

        // TRIAL -seems to work
        contextDecl.enclosedScope().setParentScope(currentScope_);

        currentScope_ = contextDecl.enclosedScope();
        assert null != currentScope_; // maybe turn into an error message later
        return contextDecl;
    }

    @Override
    protected TemplateDeclaration lookupOrCreateTemplateDeclaration(final String name,
            final TypeDeclaration rawBaseType, final Type baseType, final int lineNumber) {
        // Pass 1 - 3 version
        final TemplateDeclaration newTemplate = currentScope_.lookupTemplateDeclarationRecursive(name);
        return newTemplate;
    }

    @Override
    protected InterfaceDeclaration lookupOrCreateInterfaceDeclaration(final String name, final int lineNumber) {
        // Pass 1 - 4 version
        final InterfaceDeclaration newInterface = currentScope_.lookupInterfaceDeclarationRecursive(name);
        assert null != newInterface;
        return newInterface;
    }

    @Override
    protected ClassDeclaration lookupOrCreateClassDeclaration(final String name,
            final ClassDeclaration rawBaseClass, final ClassType baseType, final int lineNumber) {
        assert null != currentScope_;
        ClassDeclaration newClass = currentScope_.lookupClassDeclaration(name);
        StaticScope classScope = null;
        if (null == newClass) {
            assert false; // shouldn't be finding new classes in Pass 1...
            classScope = new StaticScope(currentScope_);
            newClass = this.lookupOrCreateNewClassDeclaration(name, classScope, rawBaseClass, lineNumber);
            assert null != rawBaseClass;
            final ClassType newClassType = new ClassType(name, classScope, (ClassType) rawBaseClass.type());

            currentScope_.declareType(newClassType);
            classScope.setDeclaration(newClass);
            newClass.setType(newClassType);
        } else {
            currentScope_.updateClassDeclaration(newClass);
            classScope = newClass.enclosedScope();
        }
        currentScope_ = classScope;
        return newClass;
    }

    private void insertDefaultCtor(final StaticScope scope, final TypeDeclaration decl) {
        // Let's make one
        final FormalParameterList parameterList = new FormalParameterList();
        final ObjectDeclaration selfDecl = new ObjectDeclaration("this", decl.type(), decl.lineNumber());
        parameterList.addFormalParameter(selfDecl);
        final StaticScope constructorScope = new StaticScope(scope);
        final MethodDeclaration newCtor = new MethodDeclaration(decl.name(), constructorScope, null,
                AccessQualifier.PublicAccess, decl.lineNumber(), false);
        constructorScope.setDeclaration(newCtor);
        newCtor.addParameterList(parameterList);
        scope.declareMethod(newCtor, this);
        final Expression returnStatement = new ReturnExpression(decl.name(), null, decl.lineNumber(), decl.type(),
                constructorScope);
        final ExprAndDeclList ctorBody = new ExprAndDeclList(decl.lineNumber());
        ctorBody.addBodyPart(returnStatement);
        newCtor.setBody(ctorBody);
    }

    private void checkNeedsCtor(final TypeDeclaration decl) {
        // All classes and Context should have a default constructor.
        // If someone invokes "new Foo()" we want to enact a constructor,
        // if for no other reason than to execute the in-situ initializations.

        assert decl instanceof ClassDeclaration || decl instanceof ContextDeclaration;
        final StaticScope scope = decl.enclosedScope();
        final ActualArgumentList argumentList = new ActualArgumentList();
        final Expression self = new IdentifierExpression("this", decl.type(), decl.enclosedScope(),
                decl.lineNumber());
        argumentList.addFirstActualParameter(self);
        final MethodDeclaration theConstructor = scope.lookupMethodDeclaration(decl.name(), argumentList, false);
        if (null == theConstructor) {
            // Let's make one
            insertDefaultCtor(scope, decl);
        }
    }

    private void exitType_declarationCommon() {
        // This is the Pass 1-4 version
        // type_declaration : context_declaration
        //                  | class_declaration
        //                  | interface_declaration

        // One version serves passes 1 - 4
        assert null != currentScope_;
        final Declaration rawNewDeclaration = currentScope_.associatedDeclaration();
        assert rawNewDeclaration instanceof TypeDeclaration;
        final TypeDeclaration newDeclaration = (TypeDeclaration) rawNewDeclaration;

        final StaticScope newDeclarationParentScope = newDeclaration.enclosingScope();
        final Type environment = Expression.nearestEnclosingMegaTypeOf(newDeclarationParentScope);
        if (null == environment) {
            // Only declare it only if it's at most global scope.
            // Otherwise, it will be declared in the types of
            // the enclosing scope, and compilation will compile
            // it as a component of the outer one
            parsingData_.currentTypeDeclarationList().addDeclaration(newDeclaration);
        }

        if (newDeclaration instanceof ClassDeclaration || newDeclaration instanceof ContextDeclaration) {
            checkNeedsCtor(newDeclaration);
        }

        final StaticScope parentScope = currentScope_.parentScope();
        currentScope_ = parentScope;

        if (newDeclaration instanceof ClassDeclaration) {
            if (null != ((ClassDeclaration) newDeclaration).generatingTemplate()) {
                parsingData_.popTemplateDeclaration();
                assert false; // this never seems to get invoked
            } else {
                parsingData_.popClassDeclaration();
                // implements_list is taken care of along the way
                assert true;
            }
        } else if (newDeclaration instanceof ContextDeclaration) {
            parsingData_.popContextDeclaration();
        } else if (newDeclaration instanceof TemplateDeclaration) {
            parsingData_.popTemplateDeclaration();
        }

        currentInterface_ = null;
        currentRoleOrStageProp_ = null;
    }

    @Override
    public void exitType_declaration(KantParser.Type_declarationContext ctx) {
        // type_declaration : context_declaration
        //                  | class_declaration
        //                  | interface_declaration

        if (printProductionsDebug) {
            if (null != ctx.context_declaration()) {
                System.err.println("type_declaration : context_declaration");
            } else if (null != ctx.class_declaration()) {
                System.err.println("type_declaration : class_declaration");
            } else if (null != ctx.interface_declaration()) {
                System.err.println("type_declaration : interface_declaration");
            } else {
                assert false;
            }
        }
    }

    @Override
    public void exitContext_declaration(KantParser.Context_declarationContext ctx) {
        // : 'context' JAVA_ID '{' context_body '}'

        exitType_declarationCommon();

        this.implementsCheck(
                (ClassOrContextDeclaration) currentContext_.type().enclosedScope().associatedDeclaration(),
                ctx.getStart().getLine());

        if (null != currentContext_) {
            currentContext_ = currentContext_.parentContext();
        }

        if (printProductionsDebug) {
            System.err.println("context_declaration : 'context' JAVA_ID '{' context_body '}'");
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void enterClass_declaration(KantParser.Class_declarationContext ctx) {
        // class_declaration : 'class'   JAVA_ID type_parameters (implements_list)* '{' class_body '}'
        //                   | 'class'   JAVA_ID type_parameters 'extends' JAVA_ID (implements_list)* '{' class_body '}'
        //                   | 'class'   JAVA_ID (implements_list)* '{' class_body '}'
        //                   | 'class'   JAVA_ID 'extends' JAVA_ID (implements_list)* '{' class_body '}'

        final Type objectBaseClass = StaticScope.globalScope().lookupTypeDeclaration("Object");
        assert null != objectBaseClass;
        assert objectBaseClass instanceof ClassType;
        ClassType baseType = (ClassType) objectBaseClass;

        final String name = ctx.JAVA_ID(0).getText();
        ClassDeclaration rawBaseClass = null;

        if (null != ctx.class_body()) {
            final TerminalNode baseClassNode = ctx.JAVA_ID(1);

            if (null != baseClassNode) {
                final String baseTypeName = baseClassNode.getText();
                final Type rawBaseType = currentScope_.lookupTypeDeclarationRecursive(baseTypeName);
                rawBaseClass = currentScope_.lookupClassDeclarationRecursive(baseTypeName);
                if ((rawBaseType instanceof ClassType) == false) {
                    // Leave to pass 2
                    errorHook6p2(ErrorIncidenceType.Fatal, ctx.getStart().getLine(), "Base type `", baseTypeName,
                            "' is not a declared class type as base of `", name, "'.", "");
                } else {
                    baseType = (ClassType) rawBaseType;
                    if (baseType.name().equals(name)) {
                        errorHook5p2(ErrorIncidenceType.Fatal, ctx.getStart().getLine(), "Er, no.", "", "", "");
                    }
                }
            } else {
                // Redundant: for readability...
                rawBaseClass = (ClassDeclaration) objectBaseClass.enclosedScope().associatedDeclaration();
            }

            if (null != ctx.type_parameters()) {
                final TemplateDeclaration newTemplate = this.lookupOrCreateTemplateDeclaration(name, rawBaseClass,
                        baseType, ctx.getStart().getLine());
                currentScope_ = newTemplate.enclosedScope();
                parsingData_.pushTemplateDeclaration(newTemplate);
            } else {
                final ClassDeclaration newClass = this.lookupOrCreateClassDeclaration(name, rawBaseClass, baseType,
                        ctx.getStart().getLine());
                currentScope_ = newClass.enclosedScope();
                parsingData_.pushClassDeclaration(newClass);
            }
        } else {
            assert false;
        }
    }

    @Override
    public void exitClass_declaration(KantParser.Class_declarationContext ctx) {
        // class_declaration : 'class'   JAVA_ID type_parameters (implements_list)* '{' class_body '}'
        //                   | 'class'   JAVA_ID type_parameters 'extends' JAVA_ID (implements_list)* '{' class_body '}'
        //                   | 'class'   JAVA_ID (implements_list)* '{' class_body '}'
        //                   | 'class'   JAVA_ID 'extends' JAVA_ID (implements_list)* '{' class_body '}'
        //                   | 'class'   JAVA_ID (implements_list)* 'extends' JAVA_ID '{' class_body '}'

        final Declaration rawNewDeclaration = currentScope_.associatedDeclaration();
        assert rawNewDeclaration instanceof TypeDeclaration;
        final TypeDeclaration newDeclaration = (TypeDeclaration) rawNewDeclaration;

        if (newDeclaration instanceof ClassDeclaration || newDeclaration instanceof ContextDeclaration) {
            // (Could be a template, in which case we skip it)
            this.implementsCheck((ClassOrContextDeclaration) newDeclaration, ctx.getStart().getLine());
        }

        exitType_declarationCommon();

        if (printProductionsDebug) {
            if (null != ctx.type_parameters() && null != ctx.class_body() && null == ctx.JAVA_ID(1)) {
                System.err.println(
                        "class_declaration : 'class' JAVA_ID type_parameters (implements_list)* '{' class_body '}'");
            } else if (null != ctx.type_parameters() && null != ctx.class_body() && null != ctx.JAVA_ID(1)) {
                System.err.println(
                        "class_declaration : JAVA_ID type_parameters 'extends' JAVA_ID (implements_list)* '{' class_body '}'");
            } else if (null == ctx.type_parameters() && null != ctx.class_body() && null == ctx.JAVA_ID(1)) {
                System.err.println("class_declaration : JAVA_ID (implements_list)* '{' class_body '}'");
            } else if (null == ctx.type_parameters() && null != ctx.class_body() && null != ctx.JAVA_ID(1)) {
                System.err.println(
                        "class_declaration : JAVA_ID 'extends' JAVA_ID (implements_list)* '{' class_body '}'");
            } else {
                assert false;
            }
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void exitInterface_declaration(KantParser.Interface_declarationContext ctx) {
        // interface_declaration : 'interface' JAVA_ID '{' interface_body '}'

        exitType_declarationCommon();

        if (printProductionsDebug) {
            System.err.println("interface_declaration : 'interface' JAVA_ID '{' interface_body   '}'");
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    protected void implementsCheck(final ClassOrContextDeclaration newDeclaration, int lineNumber) {
        // nothing on pass one
    }

    @Override
    public void exitImplements_list(KantParser.Implements_listContext ctx) {
        // : 'implements' JAVA_ID
        // | implements_list ',' JAVA_ID

        final String interfaceName = ctx.JAVA_ID().getText();
        InterfaceDeclaration anInterface = currentScope_.lookupInterfaceDeclarationRecursive(interfaceName);

        if (null == anInterface) {
            errorHook6p2(ErrorIncidenceType.Fatal, ctx.getStart().getLine(), "Interface ", interfaceName,
                    " is not declared.", "", "", "");
            anInterface = new InterfaceDeclaration(" error", null, ctx.getStart().getLine());
        }

        final ClassOrContextType classOrContextType = (ClassOrContextType) parsingData_
                .currentClassOrContextDeclaration().type();
        this.addInterfaceTypeSuitableToPass(classOrContextType, (InterfaceType) anInterface.type());

        if (printProductionsDebug) {
            if (ctx.implements_list() != null) {
                System.err.println("implements_list : implements_list ',' JAVA_ID");
            } else {
                System.err.println("implements_list : JAVA_ID");
            }
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    protected void addInterfaceTypeSuitableToPass(final ClassOrContextType classType,
            final InterfaceType interfaceType) {
        // nothing on pass one
    }

    @Override
    public void exitType_parameters(KantParser.Type_parametersContext ctx) {
        // : '<' type_parameter (',' type_parameter)* '>'
        // Pop from the expression stack and add to current template declaration
        final TemplateDeclaration currentTemplateDecl = parsingData_.currentTemplateDeclaration();
        final Map<String, String> dupMap = new LinkedHashMap<String, String>();
        final int numberOfActualParameters = ctx.type_parameter().size();
        for (int i = 0; i < numberOfActualParameters; i++) {
            final IdentifierExpression type_name = (IdentifierExpression) parsingData_.popExpression();
            if (dupMap.containsKey(type_name.name())) {
                errorHook5p2(ErrorIncidenceType.Fatal, ctx.getStart().getLine(),
                        "Duplicate template parameter name: ", type_name.name(), "", "");
                currentTemplateDecl.addTypeParameter(new IdentifierExpression("$error$", new ErrorType(),
                        currentScope_, ctx.getStart().getLine()), numberOfActualParameters);
            } else {
                dupMap.put(type_name.name(), type_name.name());
                currentTemplateDecl.addTypeParameter(type_name, numberOfActualParameters);
            }
        }

        if (printProductionsDebug) {
            System.err.println("type_parameters : '<' type_parameter (',' type_parameter)* '>'");
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void exitType_parameter(KantParser.Type_parameterContext ctx) {
        // : type_name ('extends' type_name)?
        // Push it on the expression stack (not ideal, but its there)

        // But, first, pop off the type_name entry... We don't use it here
        @SuppressWarnings("unused")
        final Object unused_type_name = parsingData_.popRawExpression();

        final TemplateDeclaration currentTemplateDecl = parsingData_.currentTemplateDeclaration();
        final StaticScope scope = currentTemplateDecl.enclosedScope();
        ClassType baseClassType = null;
        if (ctx.type_name().size() > 1) {
            final String baseClassName = ctx.type_name(1).getText();
            final Type rawBaseClassType = scope.lookupTypeDeclarationRecursive(baseClassName);
            assert null == rawBaseClassType || rawBaseClassType instanceof ClassType;
            baseClassType = (ClassType) rawBaseClassType; // may be null
        }
        final Type type = new TemplateParameterType(ctx.type_name(0).getText(), baseClassType);
        final Expression type_name = new IdentifierExpression(ctx.type_name(0).getText(), type, scope,
                ctx.getStart().getLine());
        parsingData_.pushExpression(type_name);

        if (printProductionsDebug) {
            System.err.println("type_parameter : type_name ('extends' type_name)?");
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void enterType_list(KantParser.Type_listContext ctx) {
        // : '<' type_name (',' type_name)* '>'
        final ArrayList<String> typeNameList = new ArrayList<String>();
        parsingData_.pushTypeNameList(typeNameList);
    }

    @Override
    public void exitType_list(KantParser.Type_listContext ctx) {
        // : '<' type_name (',' type_name)* '>'
        final List<String> currentTypeNameList = parsingData_.currentTypeNameList();
        for (int i = 0; i < ctx.type_name().size(); i++) {
            // But, first, pop off the type_name entry... We don't use it here
            @SuppressWarnings("unused")
            final Object unused_type_name = parsingData_.popRawExpression();

            currentTypeNameList.add(ctx.type_name(i).getText());
        }

        if (printProductionsDebug) {
            System.err.println("type_list : '<' type_name (',' type_name)* '>'");
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void enterContext_body(KantParser.Context_bodyContext ctx) {
        // context_body
        //      : context_body context_body_element
        //      | context_body_element
        //      | /* null */

        /* nothing */
    }

    @Override
    public void enterContext_body_element(KantParser.Context_body_elementContext ctx) {
        // context_body_element
        //   : method_decl
        //   | object_decl
        //   | role_decl
        //   | stageprop_decl

        /* nothing */
    }

    @Override
    public void enterRole_decl(KantParser.Role_declContext ctx) {
        super.enterRole_decl(ctx);
    }

    @Override
    public void exitRole_decl(KantParser.Role_declContext ctx) {
        super.exitRole_decl(ctx);

        final String vecText = ctx.role_vec_modifier().getText();
        final boolean isRoleArray = vecText.length() > 0; // "[]"
        if (printProductionsDebug) {
            if (ctx.self_methods() == null && ctx.access_qualifier() == null) {
                System.err.print("role_decl : ");
                if (isRoleArray)
                    System.err.print("[] ");
                System.err.println("'role' JAVA_ID '{' role_body '}'");
            } else if (ctx.self_methods() != null && ctx.access_qualifier() == null) {
                System.err.print("role_decl : ");
                if (isRoleArray)
                    System.err.print("[] ");
                System.err.println("'role' JAVA_ID '{' role_body '}' REQUIRES '{' self_methods '}'");
            } else if (ctx.self_methods() == null && ctx.access_qualifier() != null) {
                System.err.println("role_decl : access_qualifier ");
                if (isRoleArray)
                    System.err.print("[] ");
                System.err.println("'role' JAVA_ID '{' role_body '}'");
            } else {
                System.err.print("role_decl : access_qualifier ");
                if (isRoleArray)
                    System.err.print("[] ");
                System.err.println("'role' JAVA_ID '{' role_body '}' REQUIRES '{' self_methods '}'");
            }
        }

        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    protected void lookupOrCreateRoleDeclaration(final String roleName, final int lineNumber,
            final boolean isRoleArray) {
        final RoleDeclaration requestedRole = currentScope_.lookupRoleOrStagePropDeclaration(roleName);
        if (null != requestedRole) {
            currentRoleOrStageProp_ = requestedRole;

            // The way parsing is designed, these things should
            // be defined once on pass 0 and then referenced only
            // on subsequent passes.
        }
        // caller may reset currentScope - NOT us
    }

    @Override
    public void enterRole_body(KantParser.Role_bodyContext ctx) {
        // role_body
        //   : method_decl
        //   | role_body method_decl
        //   | object_decl            // illegal
        //   | role_body object_decl      // illegal - for better error messages only
        //   | method_signature ';'*
        //   | role_body method_signature ';'*
        //  | /* null */

        if (null != ctx.method_signature()) {
            final FormalParameterList formalParameterList = new FormalParameterList();
            parsingData_.pushFormalParameterList(formalParameterList);
        }
    }

    @Override
    public void exitRole_body(KantParser.Role_bodyContext ctx) {
        // : method_decl
        // | role_body method_decl
        // | object_decl            // illegal
        // | role_body object_decl      // illegal - for better error messages only
        // | method_signature ';'*
        // | role_body method_signature ';'*
        // | /* null */

        if (null != ctx.object_decl()) {
            @SuppressWarnings("unused")
            final DeclarationList objectDecl = parsingData_.popDeclarationList();
            // We have issued an error message about this already elsewhere
        }

        if (null != ctx.method_signature()) {
            final MethodSignature signature = parsingData_.popMethodSignature();
            final FormalParameterList plInProgress = parsingData_.popFormalParameterList();

            // Add a declaration of "this." These are class instance methods, never
            // role methods, so there is no need to add a current$context argument
            final ObjectDeclaration self = new ObjectDeclaration("this", currentRoleOrStageProp_.type(),
                    ctx.getStart().getLine());
            plInProgress.addFormalParameter(self);

            signature.addParameterList(plInProgress);
            currentRoleOrStageProp_.addPublishedSignature(signature);

            // It should also exist in the "requires" section!
            final MethodSignature requiresSignature = currentRoleOrStageProp_
                    .lookupRequiredMethodSignatureDeclaration(signature.name());
            if (null == requiresSignature) {
                // Silent error on pass1; O.K. to gripe on pass 2
                errorHook6p2(ErrorIncidenceType.Fatal, signature.lineNumber(), "Published signature for `",
                        signature.getText(), "' must also be in `requires' section.", "", "", "");
            } else {
                if (requiresSignature.formalParameterList().alignsWith(plInProgress) == false) {
                    errorHook6p2(ErrorIncidenceType.Fatal, signature.lineNumber(), "Published signature for `",
                            signature.getText(), "' doesn't match that in the `requires' section (line ",
                            String.format("%d", requiresSignature.lineNumber()), ").", "");
                }
            }
        }

        if (printProductionsDebug) {
            if (ctx.role_body() == null && ctx.method_decl() != null) {
                System.err.println("role_body : method_decl");
            } else if (ctx.role_body() != null && ctx.method_decl() != null) {
                System.err.println("role_body : role_body method_decl");
            } else if (ctx.role_body() == null && ctx.object_decl() != null) {
                System.err.println("role_body : object_decl");
            } else {
                System.err.println("role_body : role_body object_decl");
            }
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void enterSelf_methods(KantParser.Self_methodsContext ctx) {
        // : self_methods ';' method_signature
        // | method_signature
        // | self_methods /* null */ ';'

        parsingData_.pushFormalParameterList(new FormalParameterList());
    }

    @Override
    public void exitSelf_methods(KantParser.Self_methodsContext ctx) {
        // : self_methods ';' method_signature
        // | method_signature
        // | self_methods /* null */ ';'

        final Method_signatureContext contextForSignature = ctx.method_signature();
        if (null != contextForSignature) {
            final MethodSignature signature = parsingData_.popMethodSignature();
            final FormalParameterList plInProgress = parsingData_.popFormalParameterList();

            // Add a declaration of "this." These are class instance methods, never
            // role methods, so there is no need to add a current$context argument
            final ObjectDeclaration self = new ObjectDeclaration("this", currentRoleOrStageProp_.type(),
                    ctx.getStart().getLine());
            plInProgress.addFormalParameter(self);

            signature.addParameterList(plInProgress);
            currentRoleOrStageProp_.addRequiredSignatureOnSelf(signature, this);
        }

        if (printProductionsDebug) {
            if (ctx.self_methods() != null) {
                System.err.println("self_methods : self_methods ';' method_signature");
            } else {
                System.err.println("self_methods : method_signature");
            }
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void enterStageprop_decl(KantParser.Stageprop_declContext ctx) {
        // stageprop_decl
        // : 'stageprop' role_vec_modifier JAVA_ID '{' stageprop_body '}'
        // | 'stageprop' role_vec_modifier JAVA_ID '{' stageprop_body '}' REQUIRES '{' self_methods '}'
        // | access_qualifier 'stageprop' JAVA_ID '{' stageprop_body '}'
        // | access_qualifier 'stageprop' JAVA_ID '{' stageprop_body '}' REQUIRES '{' self_methods '}'
        // | 'stageprop' role_vec_modifier JAVA_ID '{' '}'
        // | 'stageprop' role_vec_modifier JAVA_ID '{' '}' REQUIRES '{' self_methods '}'
        // | access_qualifier 'stageprop' JAVA_ID '{' '}'
        // | access_qualifier 'stageprop' JAVA_ID '{' '}' REQUIRES '{' self_methods '}'
        //
        // Pass1 logic. INVOKED BY CORRESPONDING PASS2 RULE

        final String vecText = ctx.role_vec_modifier().getText();
        final boolean isStagePropArray = vecText.length() > 0; // "[]"

        if (null != ctx.access_qualifier()) {
            final String accessQualifier = ctx.access_qualifier().getText();
            if (0 != accessQualifier.length()) {
                errorHook5p1(ErrorIncidenceType.Warning, ctx.getStart().getLine(),
                        "WARNING: Gratuitous access qualifier `", ctx.access_qualifier().getText(), "' ignored",
                        ".");
            }
        }

        final TerminalNode JAVA_ID = ctx.JAVA_ID();
        if (null != JAVA_ID) {
            // It *can* be null. Once had an object declaration inside
            // a role - resulting grammar error got here with that
            // null condition. Not much to do but to punt

            final String stagePropName = JAVA_ID.getText();
            lookupOrCreateStagePropDeclaration(stagePropName, ctx.getStart().getLine(), isStagePropArray);

            final Declaration currentScopesDecl = currentScope_.associatedDeclaration();
            if (!(currentScopesDecl instanceof ContextDeclaration)) {
                errorHook5p1(ErrorIncidenceType.Fatal, ctx.getStart().getLine(), "Stageprop ", stagePropName,
                        " can be declared only in a Context scope - not ", currentScope_.name());
            }
            currentScope_ = currentRoleOrStageProp_.enclosedScope();
        } else {
            currentRoleOrStageProp_ = null;
        }
    }

    @Override
    public void exitStageprop_decl(KantParser.Stageprop_declContext ctx) {
        // stageprop_decl
        // : 'stageprop' role_vec_modifier JAVA_ID '{' role_body '}'
        // | 'stageprop' role_vec_modifier JAVA_ID '{' role_body '}' REQUIRES '{' self_methods '}'
        // | access_qualifier 'stageprop' JAVA_ID '{' role_body '}'
        // | access_qualifier 'stageprop' JAVA_ID '{' role_body '}' REQUIRES '{' self_methods '}'
        // | 'stageprop' role_vec_modifier JAVA_ID '{' '}'
        // | 'stageprop' role_vec_modifier JAVA_ID '{' '}' REQUIRES '{' self_methods '}'
        // | access_qualifier 'stageprop' JAVA_ID '{' '}'
        // | access_qualifier 'stageprop' JAVA_ID '{' '}' REQUIRES '{' self_methods '}'

        final String vecText = ctx.role_vec_modifier().getText();
        final boolean isStagePropArray = vecText.length() > 0; // "[]"

        if (null != currentRoleOrStageProp_) {
            // The IF statement is just to recover from bad
            // behaviour elicited by syntax errors. See comment
            // elsewhere (in exitRole_decl?)
            final Type rawType = currentRoleOrStageProp_.type();
            assert rawType instanceof StagePropType;
            final StagePropType type = (StagePropType) rawType;
            type.setBacklinkToRoleDecl(currentRoleOrStageProp_);

            // Make sure self_methods are const
            if (currentRoleOrStageProp_.requiresConstMethods()) {
                final Map<String, List<MethodSignature>> requiredSelfSignatures = currentRoleOrStageProp_
                        .requiredSelfSignatures();
                for (Map.Entry<String, List<MethodSignature>> iter : requiredSelfSignatures.entrySet()) {
                    final String methodName = iter.getKey();
                    final List<MethodSignature> signatures = iter.getValue();
                    for (final MethodSignature signature : signatures) {
                        if (false == signature.hasConstModifier() && false == signature.isUnusedInThisContext()) {
                            // If the published version has its unused flag set, we're Ok
                            final MethodSignature publishedSignature = currentRoleOrStageProp_
                                    .lookupPublishedSignatureDeclaration(signature);
                            if (null != publishedSignature && publishedSignature.isUnusedInThisContext() == true) {
                                ; // an "unused" flag is as good as a const
                            } else {
                                errorHook6p2(ErrorIncidenceType.Warning, ctx.getStart().getLine(),
                                        "WARNING: Signatures for functions required by stageprops like `",
                                        currentRoleOrStageProp_.name(), "' should have a const modifier: method `",
                                        methodName, "' does not.", "");
                            }
                        }
                    }
                }
            }

            currentRoleOrStageProp_ = null;
            currentScope_ = currentScope_.parentScope();
        }

        if (printProductionsDebug) {
            if (ctx.self_methods() == null && ctx.access_qualifier() == null) {
                System.err.print("stageprop_decl : ");
                if (isStagePropArray)
                    System.err.print("[] ");
                System.err.println("'stageprop' JAVA_ID '{' role_body '}'");
            } else if (ctx.self_methods() != null && ctx.access_qualifier() == null) {
                System.err.print("stageprop_decl : ");
                if (isStagePropArray)
                    System.err.print("[] ");
                System.err.println("'stageprop' JAVA_ID '{' role_body '}' REQUIRES '{' self_methods '}'");
            } else if (ctx.self_methods() == null && ctx.access_qualifier() != null) {
                System.err.println("stageprop_decl : access_qualifier ");
                if (isStagePropArray)
                    System.err.print("[] ");
                System.err.println("'stageprop' JAVA_ID '{' role_body '}'");
            } else {
                System.err.print("stageprop_decl : access_qualifier ");
                if (isStagePropArray)
                    System.err.print("[] ");
                System.err.println("'stageprop' JAVA_ID '{' role_body '}' REQUIRES '{' self_methods '}'");
            }
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void enterStageprop_body(KantParser.Stageprop_bodyContext ctx) {
        // stageprop_body
        //   : method_decl
        //   | stageprop_body method_decl
        //  | object_decl
        //  | stageprop_body object_decl
        //   | method_signature ';'*
        //   | role_body method_signature ';'*
        //   | method_signature UNUSED ';'*
        //   | role_body method_signature UNUSED ';'*

        if (null != ctx.method_signature()) {
            final FormalParameterList formalParameterList = new FormalParameterList();
            parsingData_.pushFormalParameterList(formalParameterList);
        }
    }

    @Override
    public void exitStageprop_body(KantParser.Stageprop_bodyContext ctx) {
        // stageprop_body
        //   : method_decl
        //   | stageprop_body method_decl
        //  | object_decl
        //  | stageprop_body object_decl
        //   | method_signature ';'*
        //   | role_body method_signature ';'*
        //   | method_signature UNUSED ';'*
        //   | role_body method_signature UNUSED ';'*

        if (null != ctx.object_decl()) {
            @SuppressWarnings("unused")
            final DeclarationList objectDecl = parsingData_.popDeclarationList();
            // We have issued an error message about this already elsewhere
        }

        final boolean unusedFlag = null != ctx.UNUSED();

        if (null != ctx.method_signature()) {
            final MethodSignature signature = parsingData_.popMethodSignature();
            signature.setUnused(unusedFlag);
            final FormalParameterList plInProgress = parsingData_.popFormalParameterList();

            // Add a declaration of "this." These are class instance methods, never
            // role methods, so there is no need to add a current$context argument
            final ObjectDeclaration self = new ObjectDeclaration("this", currentRoleOrStageProp_.type(),
                    ctx.getStart().getLine());
            plInProgress.addFormalParameter(self);

            signature.addParameterList(plInProgress);
            currentRoleOrStageProp_.addPublishedSignature(signature);

            // It should also exist in the "requires" section!
            final MethodSignature requiresSignature = currentRoleOrStageProp_
                    .lookupRequiredMethodSignatureDeclaration(signature.name());
            if (null == requiresSignature) {
                // Silent error on pass1; O.K. to gripe on pass 2
                errorHook6p2(ErrorIncidenceType.Fatal, signature.lineNumber(), "Published signature for `",
                        signature.getText(), "' must also be in `requires' section.", "", "", "");
            } else {
                if (requiresSignature.formalParameterList().alignsWith(plInProgress) == false) {
                    errorHook6p2(ErrorIncidenceType.Fatal, signature.lineNumber(), "Published signature for `",
                            signature.getText(), "' doesn't match that in the `requires' section (line ",
                            String.format("%d", requiresSignature.lineNumber()), ").", "");
                }
            }
        }

        if (printProductionsDebug) {
            if (ctx.stageprop_body() == null && ctx.method_decl() != null) {
                System.err.println("stageprop_body : method_decl");
            } else if (ctx.stageprop_body() != null && ctx.method_decl() != null) {
                System.err.println("stageprop_body : role_body method_decl");
            } else if (ctx.stageprop_body() == null && ctx.object_decl() != null) {
                System.err.println("stageprop_body : object_decl");
            } else {
                System.err.println("stageprop_body : role_body object_decl");
            }
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void enterClass_body(KantParser.Class_bodyContext ctx) {
        // class_body
        //   : class_body class_body_element
        //   | class_body_element
        //   | /* null */

        /* nothing */
    }

    @Override
    public void enterClass_body_element(KantParser.Class_body_elementContext ctx) {
        // class_body_element
        //   : method_decl
        //   | object_decl
        //  | type_declaration

        /* nothing */

        if (printProductionsDebug) {
            if (null != ctx.method_decl()) {
                System.err.println("class_body_element : method_decl");
            } else if (null != ctx.object_decl()) {
                System.err.println("class_body_element : object_decl");
            } else if (null != ctx.type_declaration()) {
                System.err.println("class_body_element : type_declaration");
            } else {
                assert false;
            }
        }
    }

    @Override
    public void enterInterface_body(KantParser.Interface_bodyContext ctx) {
        parsingData_.pushFormalParameterList(new FormalParameterList());
    }

    @Override
    public void exitInterface_body(KantParser.Interface_bodyContext ctx) {
        // : interface_body ';' method_signature
        // | method_signature
        // | interface_body /* null */ ';'

        final Method_signatureContext contextForSignature = ctx.method_signature();
        if (null != contextForSignature) {
            final MethodSignature signature = parsingData_.popMethodSignature();
            final FormalParameterList plInProgress = parsingData_.popFormalParameterList();

            // Add a declaration of "this." These are class instance methods, never
            // role methods, so there is no need to add a current$context argument
            final ObjectDeclaration self = new ObjectDeclaration("this", currentInterface_.type(),
                    ctx.getStart().getLine());
            plInProgress.addFormalParameter(self);

            signature.addParameterList(plInProgress);
            currentInterface_.addSignature(signature);

            // Add it to type, too
            final InterfaceType interfaceType = (InterfaceType) currentInterface_.type();
            this.addSignatureSuitableToPass(interfaceType, signature);
        }

        if (printProductionsDebug) {
            if (null != ctx.interface_body()) {
                System.err.println("interface_body : interface_body ';' method_signature");
            } else if (null != ctx.method_signature()) {
                System.err.println("interface_body : method_signature");
            } else {
                System.err.println("interface_body : /* null */ ';'");
            }
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    protected void addSignatureSuitableToPass(final InterfaceType interfaceType, final MethodSignature signature) {
        // nothing in pass 1
    }

    @Override
    public void enterMethod_decl(KantParser.Method_declContext ctx) {
        // : method_decl_hook '{' expr_and_decl_list '}'

        // This one seems different enough that we'll have our own pass 2 version.
        // In Pass 1, get just enough to define the scope for the enclosed
        // declarations.

        // Set up the block
        final ExprAndDeclList newList = new ExprAndDeclList(ctx.getStart().getLine());
        parsingData_.pushExprAndDecl(newList);

        final int lineNumber = ctx.getStart().getLine();
        final StaticScope newScope = new StaticScope(currentScope_);
        final Method_decl_hookContext declHookContext = ctx.method_decl_hook();
        final Method_signatureContext signatureCtx = declHookContext.method_signature();
        final String methodSelector = signatureCtx.method_name().getText();
        final String access_level_string = signatureCtx.access_qualifier().getText();
        final AccessQualifier accessQualifier = AccessQualifier.accessQualifierFromString(access_level_string);
        Type returnType = StaticScope.globalScope().lookupTypeDeclaration("void");

        if (currentScope_.associatedDeclaration() instanceof ContextDeclaration
                || currentScope_.associatedDeclaration() instanceof ClassDeclaration) {
            if (methodSelector.equals(currentScope_.name())) {
                returnType = null;
            }
        }

        final FormalParameterList pl = new FormalParameterList();

        // No signature on the parsingData_.methodSignature() stack yet, since
        // we're just entering the production. The MethodDeclaration object will
        // create its own default MethodSignature.
        //
        // There is no "static" modifier in the grammar, so all user-declared functions
        // will be non-static for now
        final MethodDeclaration currentMethod = new MethodDeclaration(methodSelector, newScope, returnType,
                accessQualifier, lineNumber, false);
        currentMethod.addParameterList(pl);

        newScope.setDeclaration(currentMethod);

        currentScope_.declareMethod(currentMethod, this);
        currentScope_ = newScope;

        parsingData_.pushFormalParameterList(pl);
    }

    @Override
    public void exitMethod_decl_hook(KantParser.Method_decl_hookContext ctx) {
        // method_decl_hook
        //   : method_signature
        final int lineNumber = ctx.getStart().getLine();

        final Declaration associatedDeclaration = currentScope_.parentScope().associatedDeclaration();
        if (null == associatedDeclaration) {
            assert null != associatedDeclaration;
        }
        final Type classOrRoleOrContextType = associatedDeclaration.type();

        boolean isRoleMethodInvocation = classOrRoleOrContextType instanceof RoleType;

        if (classOrRoleOrContextType instanceof ClassType || isRoleMethodInvocation
                || classOrRoleOrContextType instanceof ContextType
                || classOrRoleOrContextType instanceof TemplateType) {
            ;
        } else {
            assert classOrRoleOrContextType instanceof ClassType || isRoleMethodInvocation
                    || classOrRoleOrContextType instanceof ContextType
                    || classOrRoleOrContextType instanceof TemplateType;
        }

        // Add declaration of "this" as a formal parameter
        final ObjectDeclaration self = new ObjectDeclaration("this", classOrRoleOrContextType, lineNumber);
        parsingData_.currentFormalParameterList().addFormalParameter(self);

        if (isRoleMethodInvocation) {
            // Add declaration of "current$context" as a formal parameter,
            // right next to "this"
            Type contextType = classOrRoleOrContextType;
            if (contextType instanceof RoleType) {
                final StaticScope scope = contextType.enclosedScope();
                contextType = Expression.nearestEnclosingMegaTypeOf(scope.parentScope());
            }
            final ObjectDeclaration currentContext = new ObjectDeclaration("current$context", contextType,
                    lineNumber);
            parsingData_.currentFormalParameterList().addFormalParameter(currentContext);
        }

        for (int i = 0; i < parsingData_.currentFormalParameterList().count(); i++) {
            final Declaration objectDeclaration = parsingData_.currentFormalParameterList().parameterAtPosition(i);

            // Nothing on Pass 1
            // Processed on Pass 2
            // Functionality duplicated on Passes 3 & 4
            if (objectDeclaration instanceof ObjectDeclaration) {
                declareFormalParametersSuitableToPass(currentScope_, (ObjectDeclaration) objectDeclaration);
            }
        }

        // Can't we associate the signature with the parameter list here? Experiment.
        parsingData_.currentMethodSignature().addParameterList(parsingData_.currentFormalParameterList());

        if (printProductionsDebug) {
            System.err.println("method_decl_hook : method_signature");
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void exitMethod_decl(KantParser.Method_declContext ctx) {
        // : method_decl_hook '{' type_and_expr_and_decl_list '}'

        // Declare parameters in the new scope
        // This is definitely a Pass2 thing, so there is a special Pass 2 version

        assert currentScope_.associatedDeclaration() instanceof MethodDeclaration;

        final MethodDeclaration currentMethod = (MethodDeclaration) currentScope_.associatedDeclaration();
        assert currentMethod instanceof MethodDeclaration;

        final MethodSignature sig = parsingData_.currentMethodSignature();
        final Type returnType = sig.returnType();
        currentMethod.setReturnType(returnType);

        final StaticScope parentScope = currentScope_.parentScope();
        currentScope_ = parentScope;

        // Keep the stack clean
        final MethodSignature methodSignatureInProgress = parsingData_.popMethodSignature();
        currentMethod.setHasConstModifier(methodSignatureInProgress.hasConstModifier());

        parsingData_.popFormalParameterList(); // hope this is the right place
        parsingData_.popExprAndDecl(); // Move to Context, Role, Class, StageProp productions???

        if (printProductionsDebug) {
            System.err.println("method_decl : method_decl_hook '{' type_and_expr_and_decl_list '}'");
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    private Type customStringToType(final String returnTypeName) {
        // This mainly turns both String and String[] into
        // reasonable types
        Type returnType = null;
        final int lastRightBracket = returnTypeName.trim().lastIndexOf(']');
        if (lastRightBracket == returnTypeName.length() - 1) {
            // It is an array. Get the base type
            final int lastLeftBracket = returnTypeName.lastIndexOf('[');
            if (lastLeftBracket != -1) {
                final String baseType = returnTypeName.substring(0, lastLeftBracket).trim();
                returnType = currentScope_.lookupTypeDeclarationRecursive(baseType);
                if (null != returnType) {
                    returnType = new ArrayType(baseType + "_$array", returnType);
                }
            } else {
                // This probably never works, and is probably some kind
                // of syntax error. Hope it pops up somewhere
                returnType = currentScope_.lookupTypeDeclarationRecursive(returnTypeName);
            }
        } else {
            returnType = currentScope_.lookupTypeDeclarationRecursive(returnTypeName);
            // null is O.K. as a return type!
        }
        return returnType;
    }

    @Override
    public void enterMethod_signature(KantParser.Method_signatureContext ctx) {
        // : access_qualifier return_type method_name '(' param_list ')' CONST*
        // | access_qualifier return_type method_name CONST*
        // | access_qualifier method_name '(' param_list ')' CONST*

        final String name = ctx.method_name().getText();
        String returnTypeName = "";
        final String accessQualifierString = null != ctx.access_qualifier() ? ctx.access_qualifier().getText() : "";
        final AccessQualifier accessQualifier = AccessQualifier.accessQualifierFromString(accessQualifierString);

        Type returnType = null;
        // There may not be any return type at all - as for a constructor
        final KantParser.Return_typeContext returnTypeContext = ctx.return_type();
        if (null != returnTypeContext) {
            returnTypeName = returnTypeContext.getText();
            returnType = customStringToType(returnTypeName);
        }

        final int lineNumber = ctx.getStart().getLine();
        final MethodSignature currentMethod = new MethodSignature(name, returnType, accessQualifier, lineNumber,
                false);

        if (null != ctx.CONST()) {
            currentMethod.setHasConstModifier(ctx.CONST().size() > 0);
        }

        parsingData_.pushMethodSignature(currentMethod);
    }

    @Override
    public void exitMethod_signature(KantParser.Method_signatureContext ctx) {
        // : access_qualifier return_type method_name '(' param_list ')' CONST*
        // | access_qualifier return_type method_name CONST*
        // | access_qualifier method_name '(' param_list ')' CONST*

        // Update return type (e.g., if it is a template, we will now
        // have more information)
        final KantParser.Return_typeContext returnTypeContext = ctx.return_type();
        if (null != returnTypeContext) {
            final String returnTypeName = returnTypeContext.getText();

            // final Type updatedReturnType = currentScope_.lookupTypeDeclarationRecursive(returnTypeName);
            final Type updatedReturnType = customStringToType(returnTypeName);
            final MethodSignature currentMethodSignature = parsingData_.currentMethodSignature();
            assert null != currentMethodSignature;
            currentMethodSignature.setReturnType(updatedReturnType);
            // null is NOT O.K. here as a return type, I think... Or can it be a ctor?
        }

        if (printProductionsDebug) {
            if ((null != ctx.CONST()) && (ctx.CONST().size() > 0)) {
                if (ctx.return_type() != null && ctx.param_list() != null) {
                    System.err.println(
                            "method_signature : access_qualifier return_type method_name '(' param_list ')' CONST");
                } else if (ctx.return_type() != null && ctx.param_list() == null) {
                    System.err.println("method_signature : access_qualifier return_type method_name COSNT");
                } else {
                    System.err.println("method_signature : access_qualifier method_name '(' param_list ')' CONST");
                }
            } else {
                if (ctx.return_type() != null && ctx.param_list() != null) {
                    System.err.println(
                            "method_signature : access_qualifier return_type method_name '(' param_list ')'");
                } else if (ctx.return_type() != null && ctx.param_list() == null) {
                    System.err.println("method_signature : access_qualifier return_type method_name");
                } else {
                    System.err.println("method_signature : access_qualifier method_name '(' param_list ')'");
                }
            }
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void enterExpr_and_decl_list(KantParser.Expr_and_decl_listContext ctx) {
    }

    private void addInitializationsForObjectDecls(final DeclarationList object_decls) {
        // "Tail-recursive parsing" causes this loop to visit the
        // initializations in right-to-left order. Cachec them
        // away in a list and then add them backwards. The result
        // is that multiple initializations within a declaration will
        // be evaluated left-to-right.
        final ExprAndDeclList currentExprAndDecl = parsingData_.currentExprAndDecl();
        Stack<Expression> initializationExprs = new Stack<Expression>();
        Expression initializationExpression = null;
        for (final BodyPart bp : object_decls.declarations()) {
            if (bp instanceof ObjectDeclaration) {
                final ObjectDeclaration odecl = (ObjectDeclaration) bp;
                initializationExpression = odecl.initializationExpression();
                if (null != initializationExpression) {
                    assert initializationExpression instanceof AssignmentExpression;
                    initializationExprs.push(initializationExpression);
                }
            }
        }
        while (initializationExprs.size() > 0) {
            initializationExpression = initializationExprs.pop();
            currentExprAndDecl.addBodyPart(initializationExpression);
        }
    }

    @Override
    public void exitType_and_expr_and_decl_list(KantParser.Type_and_expr_and_decl_listContext ctx) {
        // type_and_expr_and_decl_list : expr_and_decl_list
        //                             | expr_and_decl_list type_declaration
        //                             | type_declaration expr_and_decl_list

        ; // nothing

        if (printProductionsDebug) {
            if (null != ctx.type_declaration()) {
                if (ctx.type_declaration().start.getStartIndex() < ctx.expr_and_decl_list().start.getStartIndex()) {
                    System.err.println("type_expr_and_decl_list : type_declaration expr_and_decl_list");
                } else {
                    System.err.println("type_expr_and_decl_list : expr_and_decl_list type_declaration");
                }
            } else {
                System.err.println("type_expr_and_decl_list : expr_and_decl_list");
            }
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void exitExpr_and_decl_list(KantParser.Expr_and_decl_listContext ctx) {
        //     : object_decl
        //      | expr ';' object_decl
        //      | expr_and_decl_list object_decl
        //      | expr_and_decl_list expr
        //      | expr_and_decl_list /* null-expr */ ';'
        //      | /* null */

        final KantParser.Expr_and_decl_listContext expr_and_decl_list = ctx.expr_and_decl_list();
        DeclarationList object_decl = null;
        BodyPart expr = null;
        final ExprAndDeclList currentExprAndDecl = parsingData_.currentExprAndDecl();
        if (null != ctx.expr() && parsingData_.currentExpressionExists()) {
            expr = parsingData_.popExpression();
        }
        if (null != ctx.object_decl()) {
            // stumbling check
            if (parsingData_.currentDeclarationListExists()) {
                object_decl = parsingData_.popDeclarationList();
            } else {
                object_decl = null;
            }
        }
        if (null != expr && null != object_decl) {
            currentExprAndDecl.addBodyPart(expr);
            addInitializationsForObjectDecls(object_decl);

            // Does this really add anything?
            currentExprAndDecl.addBodyPart(object_decl);
        } else if (null != expr_and_decl_list && null != object_decl) {
            addInitializationsForObjectDecls(object_decl);
            currentExprAndDecl.addBodyPart(object_decl);
        } else if (null != object_decl) {
            currentExprAndDecl.addBodyPart(object_decl);
            addInitializationsForObjectDecls(object_decl);
        } else if (null != expr_and_decl_list && null != expr) {
            currentExprAndDecl.addBodyPart(expr);
        } else if (null != ctx.expr_and_decl_list() && null == ctx.expr() && null == ctx.object_decl()) {
            // just a gratuitous null statement that we can ignore
        } else {
            // null list - it's O.K.
        }

        if (printProductionsDebug) {
            if (null != ctx.expr() && null != ctx.object_decl()) {
                System.err.println("expr_and_decl_list : expr ';' object_decl");
            } else if (null != ctx.expr_and_decl_list() && null != ctx.object_decl()) {
                System.err.println("expr_and_decl_list : expr_and_decl_list object_decl");
            } else if (null == expr && null != object_decl) {
                System.err.println("expr_and_decl_list : object_decl");
            } else if (null != expr_and_decl_list && null != ctx.expr()) {
                System.err.println("expr_and_decl_list : expr_and_decl_list expr");
            } else {
                System.err.println("expr_and_decl_list : /* null */");
            }
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void exitReturn_type(KantParser.Return_typeContext ctx) {
        // return_type
        //      : type_name '[' ']'
        //      : type_name
        //      | /* null */

        // We need just to pop the type that the type_name production
        // put on the stack

        if (null != ctx.type_name()) {
            @SuppressWarnings("unused")
            final ExpressionStackAPI unusedType = parsingData_.popRawExpression();
        }

        if (printProductionsDebug) {
            System.err.println("return_type : type_name");
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void exitMethod_name(KantParser.Method_nameContext ctx) {
        // method_name
        //   : JAVA_ID
        //  | ABELIAN_MULOP
        //  | ABELIAN_SUMOP

        /* nothing  it's pure text, so we just do text processing */
        /* in the productions that depend on this one. */
    }

    @Override
    public void exitAccess_qualifier(KantParser.Access_qualifierContext ctx) {
        //   access_qualifier
        //      : 'public'
        //      | 'private'
        //      | /* null */

        /* nothing */
        if (printProductionsDebug) {
            System.err.println("access_qualifier : public|private|/*null*/");
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void enterObject_decl(KantParser.Object_declContext ctx) {
        // object_decl
        //   : access_qualifier compound_type_name identifier_list ';'
        //   | access_qualifier compound_type_name identifier_list
        //   | compound_type_name identifier_list /* null expr */ ';'
        //   | compound_type_name identifier_list
    }

    @Override
    public void exitObject_decl(KantParser.Object_declContext ctx) {
        // object_decl
        //   : access_qualifier compound_type_name identifier_list ';'
        //   | access_qualifier compound_type_name identifier_list
        //   | compound_type_name identifier_list /* null expr */ ';'
        //   | compound_type_name identifier_list

        // One semantic routine serves all three passes

        final int lineNumber = ctx.getStart().getLine();

        final KantParser.Access_qualifierContext accessQualifierContext = ctx.access_qualifier();
        final String accessQualifierString = accessQualifierContext != null ? accessQualifierContext.getText() : "";
        AccessQualifier accessQualifier = AccessQualifier.accessQualifierFromString(accessQualifierString);
        if (null == accessQualifier) {
            accessQualifier = AccessQualifier.accessQualifierFromString(" default");
        }
        List<ObjectDeclaration> declaredObjectDeclarations = null;

        final Compound_type_nameContext compound_type_name = ctx.compound_type_name();
        if (null == compound_type_name) {
            // It can happen if there's a bad syntax error.
            // Just punt for now.
            return;
        }

        final List<ParseTree> children = compound_type_name.children;
        final int numberOfChildren = children.size();
        final String typeName = children.get(0).getText();
        boolean isArray = false;
        if (3 == numberOfChildren) {
            final String firstModifier = children.get(1).getText();
            final String secondModifier = children.get(2).getText();
            if (firstModifier.equals("[") && secondModifier.equals("]")) {
                // Is an array declaration
                isArray = true;
            }
        }

        final Declaration associatedDeclaration = currentScope_.associatedDeclaration();
        if (associatedDeclaration instanceof StagePropDeclaration) {
            errorHook6p2(ErrorIncidenceType.Fatal, lineNumber,
                    "Stage props are stateless, so the declaration of objects of type `", typeName, "' in `",
                    associatedDeclaration.name(), "' are not allowed.", "");
            declaredObjectDeclarations = new ArrayList<ObjectDeclaration>(); // empty list just to keep things happy
        } else if (associatedDeclaration instanceof RoleDeclaration) {
            errorHook6p2(ErrorIncidenceType.Fatal, lineNumber,
                    "Roles are stateless, so the declaration of objects of type `", typeName, "' in `",
                    associatedDeclaration.name(), "' are not allowed.", "");
            declaredObjectDeclarations = new ArrayList<ObjectDeclaration>(); // empty list just to keep things happy
        } else {
            Type type = currentScope_.lookupTypeDeclarationRecursive(typeName);

            if (null != type && isArray) {
                // A derived type
                final String aName = type.getText() + "_$array";
                type = new ArrayType(aName, type);
            }

            if (null == type) {
                errorHook5p2(ErrorIncidenceType.Fatal, ctx.getStart().getLine(), "Type `", typeName,
                        "' undefined for declaration.", "");

                // Put in some reasonable type to avoid stumbling
                type = new ErrorType();
            }

            final Identifier_listContext identifier_list = ctx.identifier_list();
            final DeclarationsAndInitializers idInfo = this.processIdentifierList(identifier_list, type, lineNumber,
                    accessQualifier);
            declaredObjectDeclarations = idInfo.objectDecls();

            final RuleContext myParent = ctx.parent;
            ForExpression currentForExpression = null;
            BlockExpression currentBlockExpression = null;
            if (myParent instanceof For_exprContext) {
                currentForExpression = parsingData_.currentForExpression();
                assert null != currentForExpression;
                currentForExpression.addObjectDeclForBlock(declaredObjectDeclarations);
            } else if (ifIsInABlockContext(myParent)) {
                currentBlockExpression = parsingData_.currentBlockExpression();
                assert null != currentBlockExpression;
                currentBlockExpression.addObjectDeclForBlock(declaredObjectDeclarations);
            }

            // Maybe here...  Add the initializations to the expression in progress
            final List<BodyPart> intializationExprs = idInfo.initializations();
            if (0 < intializationExprs.size()) {
                // There may not even be a currentExprAndDecl... We used to presume that initializations
                // occurred where there were ExprAndDecl blocks in play. We changed this because we now
                // allow inline initialization of object members in the class syntax. Now we must handle
                // the initialisations in the constructor. Queue them up to the declaration if so.

                // In any case, errors can cause currentExprAndDecl() to be empty, so we
                // need to bail out accordingly

                if (parsingData_.currentExprAndDeclExists()) {
                    for (int z = 0; z < intializationExprs.size(); z++) {
                        if (null != currentForExpression) {
                            // For loops are special
                            final List<BodyPart> bodyParts = new ArrayList<BodyPart>();
                            bodyParts.add(intializationExprs.get(z));
                            currentForExpression.addInitExprs(bodyParts);
                        } else if (null != currentBlockExpression) {
                            // Blocks are... kind of special...
                            // Well, no. We'll handle them through the declarations as well.
                            // currentBlockExpression.bodyParts().add(intializationExprs.get(z));
                        } else {
                            // We can't add to currentExprAndDecl here, as we did before,
                            // because the timing is wrong. Defer its processing to
                            // exitExpr_and_decl_list where we'll slot it in with the
                            // corresponding declaration. We effect this by tying the
                            // initialization to the individual ObjectDeclaration above
                            // in processIdentifierList.

                            // No: currentExprAndDecl.addBodyPart(intializationExprs.get(z));
                        }
                    }
                } else if (currentScope_.associatedDeclaration() instanceof ClassDeclaration
                        || currentScope_.associatedDeclaration() instanceof ContextDeclaration) {
                    // Then this is an in situ initialization - i.e., someone is putting the
                    // initialization with the declaration of an instance object instead of
                    // initializing it in the constructor

                    final ObjectSubclassDeclaration declaration = (ObjectSubclassDeclaration) currentScope_
                            .associatedDeclaration();
                    declaration.addInSituInitializers(intializationExprs);
                } else {
                    final DeclarationList declarationList = new DeclarationList(lineNumber);
                    declarationList.addDeclaration(new ErrorDeclaration(null));
                    parsingData_.pushDeclarationList(declarationList);
                    return; // punt - error return
                }
            }

            for (final ObjectDeclaration aDecl : declaredObjectDeclarations) {
                this.nameCheck(aDecl.name(), lineNumber);
            }
        }

        // Package all the stuff in declaredObjectDeclarations into an ExprAndDeclList
        // and push it onto the ExprAndDecl stack.
        final DeclarationList declarationList = new DeclarationList(lineNumber);

        for (final ObjectDeclaration aDecl : declaredObjectDeclarations) {
            declarationList.addDeclaration(aDecl);
        }

        parsingData_.pushDeclarationList(declarationList);

        if (printProductionsDebug) {
            if (ctx.access_qualifier() != null) {
                System.err.print("object_decl : access_qualifier compound_type_name identifier_list (");
                final int size = declaredObjectDeclarations.size();
                for (int y = 0; y < size; y++) {
                    final ObjectDeclaration aDecl = declaredObjectDeclarations.get(y);
                    System.err.print(aDecl.name());
                    if (y != size - 1) {
                        System.err.print(", ");
                    }
                }
                System.err.println(") ';'");
            } else {
                System.err.println("object_decl : compound_type_name identifier_list ';'");
            }
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void exitCompound_type_name(KantParser.Compound_type_nameContext ctx) {
        // : type_name '[' ']'
        // | type_name

        // We need just to pop the type that the type_name production
        // put on the stack

        @SuppressWarnings("unused")
        final ExpressionStackAPI unusedType = parsingData_.popRawExpression();

        if (printProductionsDebug) {
            System.err.println("compound_type_name : type_name ...");
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    protected Type commonTemplateInstantiationHandling(final String templateName, final int lineNumber,
            final List<String> typeNameList) {
        /*
        final StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append(templateName);
        stringBuffer.append("<");
        for (int i = 0; i < typeNameList.size(); i++) {
           final String parameterName = typeNameList.get(i);
           stringBuffer.append(parameterName);
           if (i < typeNameList.size() - 1) {
        stringBuffer.append(",");
           }
        }
        stringBuffer.append(">");
        final String typeName = stringBuffer.toString();
        */

        // Create a new class!
        final Type type = this.lookupOrCreateTemplateInstantiation(templateName, typeNameList, lineNumber);
        return type;
    }

    @Override
    public void exitBuiltin_type_name(KantParser.Builtin_type_nameContext ctx) {
        if (printProductionsDebug) {
            System.err.print("builtin_type_name : ('");
            System.err.print(ctx.getText());
            System.err.println("')");
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void exitType_name(KantParser.Type_nameContext ctx) {
        // type_name
        //   : JAVA_ID
        //  | JAVA_ID type_list
        //   | builtin_type_name

        // All four passes

        Type type = null;
        String typeName = null;
        if (null != ctx.JAVA_ID() && null == ctx.type_list()) {
            //  | type_name : JAVA_ID
            typeName = ctx.JAVA_ID().getText();

            type = currentScope_.lookupTypeDeclarationRecursive(typeName);
            if (null == type) {
                type = StaticScope.globalScope().lookupTypeDeclaration("void");
            }
        } else if (null == ctx.JAVA_ID() && null == ctx.type_list()) {
            //  | type_name : builtin_type_name
            typeName = ctx.getText();

            type = currentScope_.lookupTypeDeclarationRecursive(typeName);
            if (null == type) {
                type = StaticScope.globalScope().lookupTypeDeclaration("void");
            }
        } else if (null != ctx.JAVA_ID() && null != ctx.type_list()) {
            // | JAVA_ID type_list
            // Must be in the context of a template instantiation in progress

            // Create a new class!
            final List<String> typeNameList = parsingData_.popTypeNameList();
            type = this.commonTemplateInstantiationHandling(ctx.JAVA_ID().getText(), ctx.getStart().getLine(),
                    typeNameList);
            this.updateTypesAccordingToPass(type, typeNameList);
        } else {
            assert false;
        }

        if (null != type) {
            // error stumbling null check
            parsingData_.pushExpression(type);
        } else {
            parsingData_.pushExpression(new ErrorExpression(null));
        }

        if (printProductionsDebug) {
            if (null != ctx.JAVA_ID()) {
                System.err.print("type_name : JAVA_ID (");
                System.err.print(ctx.getText());
                System.err.println(")");
            } else if (null != ctx.JAVA_ID() && null != ctx.type_list()) {
                System.err.print("type_name : JAVA_ID (");
                System.err.print(ctx.JAVA_ID().getText());
                System.err.println(") '<' JAVA_ID (");
                System.err.print(ctx.type_list().getText());
                System.err.println(") '>'");
            } else {
                System.err.print("type_name : (");
                System.err.print(ctx.getText());
                System.err.println(")");
            }
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    protected void updateTypesAccordingToPass(final Type type, final List<String> typeNameList) {
        // nothing
    }

    @Override
    public void exitIdentifier_list(KantParser.Identifier_listContext ctx) {
        // identifier_list
        //   : JAVA_ID
        //   | identifier_list ',' JAVA_ID
        //   | JAVA_ID ASSIGN expr
        //   | identifier_list ',' JAVA_ID ASSIGN expr

        /* nothing */
        if (printProductionsDebug) {
            if (null != ctx.JAVA_ID() && null == ctx.expr()) {
                System.err.print("identifier_list : JAVA_ID [");
                System.err.print(ctx.JAVA_ID().getText());
                System.err.println("]");
            } else if (null != ctx.JAVA_ID() && null != ctx.identifier_list()) {
                System.err.print("identifier_list : identifier_list ',' JAVA_ID [");
                System.err.print(ctx.JAVA_ID().getText());
                System.err.println("]");
            } else if (null != ctx.JAVA_ID() && null != ctx.expr()) {
                System.err.println("identifier_list : JAVA_ID ASSIGN expr");
            } else if (null != ctx.identifier_list() && null != ctx.JAVA_ID() && null != ctx.expr()) {
                System.err.println("identifier_list : identifier_list ',' JAVA_ID ASSIGN expr");
            } else {
                assert false;
            }
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void exitParam_list(KantParser.Param_listContext ctx) {
        // : param_decl
        // | param_list ',' param_decl
        // | /* null */

        // Declare parameters in the new scope
        // Save this for Pass 2.  Hmmm. Doesn't seem necessary.

        if (printProductionsDebug) {
            if (ctx.param_decl() != null && ctx.param_list() == null) {
                System.err.println("param_list : param_decl");
            } else if (ctx.param_list() != null) {
                System.err.println("param_list : param_list ';' param_decl");
            } else {
                System.err.println("param_list : /* null */");
            }
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void exitParam_decl(KantParser.Param_declContext ctx) {
        // param_decl
        //      : compound_type_name JAVA_ID

        // We need just to pop the type that the type_name production
        // put on the stack

        if (parsingData_.currentExpressionExists()) {
            // This above check shouldn't be needed
            @SuppressWarnings("unused")
            final ExpressionStackAPI unusedType = parsingData_.popRawExpression();
        }

        if (printProductionsDebug) {
            System.err.println("param_decl : compound_type_name JAVA_ID");
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void exitExpr(KantParser.ExprContext ctx) {
        // expr
        //   : abelian_expr
        //   | boolean_expr
        //   | block
        //   | if_expr
        //   | for_expr
        //   | while_expr
        //   | do_while_expr
        //   | switch_expr
        //   | BREAK
        //   | CONTINUE
        //   | RETURN expr
        //  | RETURN

        Expression expression = null;
        final int lineNumber = ctx.getStart().getLine();

        if (null != ctx.abelian_expr()) {
            if (parsingData_.currentExpressionExists()) {
                // Error stumbling (undefined method)
                expression = parsingData_.popExpression();
            } else {
                expression = new ErrorExpression(null);
            }
            if (printProductionsDebug) {
                System.err.println("expr : abelian_expr");
            }
        } else if (null != ctx.boolean_expr()) {
            expression = parsingData_.popExpression();
            if (printProductionsDebug) {
                System.err.println("expr : boolean_expr");
            }
        } else if (null != ctx.block()) {
            expression = parsingData_.popExpression();
            if (printProductionsDebug) {
                System.err.println("expr : block");
            }
        } else if (null != ctx.if_expr()) {
            // | if_expr.
            // It should be on top of the stack, so just let it be
            expression = parsingData_.popExpression();
            if (printProductionsDebug) {
                System.err.println("expr : if_expr");
            }
        } else if (null != ctx.for_expr()) {
            expression = parsingData_.popExpression();
            if (printProductionsDebug) {
                System.err.println("expr : for_expr");
            }
        } else if (null != ctx.while_expr()) {
            expression = parsingData_.popExpression();
            if (printProductionsDebug) {
                System.err.println("expr : while_expr");
            }
        } else if (null != ctx.do_while_expr()) {
            expression = parsingData_.popExpression();
            if (printProductionsDebug) {
                System.err.println("expr : do_while_expr");
            }
        } else if (null != ctx.switch_expr()) {
            if (parsingData_.currentExpressionExists()) {
                expression = parsingData_.popExpression();
            } else {
                expression = new ErrorExpression(null);
            }
            if (printProductionsDebug) {
                System.err.println("expr : switch_expr");
            }
        } else if (null != ctx.BREAK()) {
            final long nestingLevelInsideBreakable = nestingLevelInsideBreakable(ctx.BREAK());
            Expression currentBreakableExpression = null;
            if (parsingData_.currentBreakableExpressionExists()) {
                currentBreakableExpression = parsingData_.currentBreakableExpression();
            }
            if (null == currentBreakableExpression) {
                errorHook5p2(ErrorIncidenceType.Fatal, lineNumber, "There is no switch or loop statement to break",
                        "", "", "");
            } else if (nestingLevelInsideBreakable == -1) {
                errorHook5p2(ErrorIncidenceType.Fatal, lineNumber,
                        "The break statement is not in the scope of any switch or loop statement", "", "", "");
            }
            if (null != currentBreakableExpression) {
                expression = new BreakExpression(lineNumber, currentBreakableExpression,
                        nestingLevelInsideBreakable);
            } else {
                expression = new ErrorExpression(null);
            }
            if (printProductionsDebug) {
                System.err.print("expr : BREAK (nesting level is ");
                System.err.print(nestingLevelInsideBreakable);
                System.err.println(")");
            }
        } else if (null != ctx.CONTINUE()) {
            final long nestingLevelInsideBreakable = nestingLevelInsideBreakableForContinue(ctx.CONTINUE());
            final Expression currentContinuableExpression = parsingData_.nearestContinuableLoop();
            assert currentContinuableExpression instanceof SwitchExpression == false;
            if (null == currentContinuableExpression) {
                errorHook5p2(ErrorIncidenceType.Fatal, lineNumber, "There is no loop statement to continue", "", "",
                        "");
            } else if (nestingLevelInsideBreakable == -1) {
                errorHook5p2(ErrorIncidenceType.Fatal, lineNumber,
                        "The continue statement is not in the scope of any loop statement", "", "", "");
            }
            expression = new ContinueExpression(lineNumber, currentContinuableExpression,
                    nestingLevelInsideBreakable);
            if (printProductionsDebug) {
                System.err.println("expr : CONTINUE");
            }
        } else if (null != ctx.RETURN()) {
            expression = processReturnExpression(ctx);
        } else {
            // Could be a parsing error
            expression = new ErrorExpression(null);
            if (printProductionsDebug) {
                System.err.println("expr : <internal error>");
            }
        }

        parsingData_.pushExpression(expression);

        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    private Expression processReturnExpression(KantParser.ExprContext ctx) {
        // The expression being returned is popped from
        // parsingData_.popExpression() in this.expressionFromReturnStatement
        // It may be null.

        final ExprContext exprContext = ctx.expr();
        final Type nearestEnclosingMegaType = Expression.nearestEnclosingMegaTypeOf(currentScope_);
        Expression expression = this.expressionFromReturnStatement(ctx.expr(), ctx.getParent(), ctx.getStart());

        final MethodSignature currentMethod = parsingData_.currentMethodSignature();
        final String methodName = null == currentMethod ? "<null>" : currentMethod.name();

        if (null != exprContext && null != expression) {
            expression.setResultIsConsumed(true); // consumed by the return statement

            if (null != expression && this.getClass().getSimpleName().equals("Pass1Listener") == false) {
                if (null == expression.type()) {
                    assert null != expression.type();
                }
                assert null != expression.type().pathName();
                if (expression.type().pathName().equals("void")) {
                    assert true; // tests/chord_identifier7.k
                    expression = new TopOfStackExpression();
                }
            }

            // Now, speaking of the return statement... Methods stick one in at
            // the end for free. But we have an explicit return here and it
            // may not be at the end. If it is, then there may just be extra
            // redundant return code. But watch for scope management stuff.

            if (null != nearestEnclosingMegaType) { // error stumbling
                if (expression instanceof ReturnExpression) {
                    errorHook5p2(ErrorIncidenceType.Fatal, ctx.getStart().getLine(),
                            "You may not return another `return' expression.", "", "", "");
                    expression = new ErrorExpression(expression);
                } else {
                    // Check to make sure it is of the right type
                    final Type methodReturnType = currentMethod.returnType();

                    assert null != expression;
                    final Type expressionType = expression.type();
                    if (null != methodReturnType && null != expressionType) {
                        if (methodReturnType.pathName().equals(expressionType.pathName())) {
                            ; // we're cool
                        } else if (expressionType.pathName().equals("Null")) {
                            ; // Null converts to anything
                        } else if (methodReturnType.canBeConvertedFrom(expressionType)) {
                            // We're almost cool...
                            errorHook5p2(ErrorIncidenceType.Warning, ctx.getStart().getLine(),
                                    "WARNING: substituting object of type `", methodReturnType.name(), "' for `",
                                    expression.getText() + "'.");
                            expression = expression.promoteTo(methodReturnType);
                            expression.setResultIsConsumed(true);
                        } else {
                            errorHook5p2(ErrorIncidenceType.Fatal, ctx.getStart().getLine(),
                                    "Type mismatch in return statement. Expected `", methodReturnType.name(),
                                    "' and found `", expression.type().getText() + "'.");
                        }
                    }
                }
                expression = new ReturnExpression(methodName, expression, ctx.getStart().getLine(),
                        nearestEnclosingMegaType, currentScope_);
            } else {
                expression = new ReturnExpression(methodName, null, ctx.getStart().getLine(),
                        nearestEnclosingMegaType, currentScope_);
            }
        } else {
            expression = new ReturnExpression(methodName, null, ctx.getStart().getLine(), nearestEnclosingMegaType,
                    currentScope_);
        }

        if (printProductionsDebug) {
            if (null == ctx.expr()) {
                System.err.println("expr : RETURN");
            } else {
                System.err.println("expr : RETURN expr");
            }
        }

        return expression;
    }

    @Override
    public void exitAbelian_expr(KantParser.Abelian_exprContext ctx) {
        // : abelian_product (ABELIAN_SUMOP abelian_product)*
        // | <assoc=right> abelian_expr ASSIGN expr
        // | if_expr

        Expression expression = null;
        MethodInvocationEnvironmentClass originMethodClass, targetMethodClass;
        final int lineNumber = ctx.getStart().getLine();

        if (null != ctx.abelian_product() && ctx.abelian_product().size() > 1 && null != ctx.ABELIAN_SUMOP()) {
            //   | abelian_expr op=('+' | '-') abelian_expr

            // Make it left-associative
            final int abelianSumopSize = ctx.ABELIAN_SUMOP().size();
            final Stack<Expression> exprStack = new Stack<Expression>();
            for (int i = 0; i < abelianSumopSize; i++) {
                final Expression expr2 = parsingData_.popExpression();
                exprStack.push(expr2);
            }

            if (parsingData_.currentExpressionExists()) {
                expression = parsingData_.popExpression();

                for (int i = 0; i < abelianSumopSize; i++) {
                    final Expression expr2 = exprStack.pop();
                    final String operatorAsString = ctx.ABELIAN_SUMOP(i).getText();

                    // WAIT: What if the type has implemented its
                    // own version of '+' or '-'?
                    final ActualArgumentList params = new ActualArgumentList();
                    params.addActualArgument(expr2);
                    params.addFirstActualParameter(expression);
                    final MethodDeclaration myOperatorFunc = null != expression && null != expression.type()
                            && null != expression.type().enclosedScope()
                                    ? expression.type().enclosedScope()
                                            .lookupMethodDeclarationWithConversionIgnoringParameter(
                                                    operatorAsString, params, false, /*parameterToIgnore*/ null)
                                    : null;
                    if (null != myOperatorFunc) {
                        // This dude or dudette has defined their own
                        // operator. Arrange a full-blown method call
                        // to handle it
                        final Type enclosingMegaType = Expression.nearestEnclosingMegaTypeOf(currentScope_);
                        final Message message = new Message(operatorAsString, params, lineNumber,
                                enclosingMegaType);

                        expression.setResultIsConsumed(true);
                        expr2.setResultIsConsumed(true);
                        originMethodClass = currentScope_.methodInvocationEnvironmentClass();
                        if (expression.type() instanceof RoleType) {
                            targetMethodClass = MethodInvocationEnvironmentClass.RoleEnvironment;
                        } else if (expression.type() instanceof ClassType) {
                            targetMethodClass = MethodInvocationEnvironmentClass.ClassEnvironment;
                        } else if (expression.type() instanceof BuiltInType) {
                            targetMethodClass = MethodInvocationEnvironmentClass.ClassEnvironment;
                        } else if (expression.type() instanceof ContextType) {
                            targetMethodClass = MethodInvocationEnvironmentClass.ContextEnvironment;
                        } else {
                            targetMethodClass = MethodInvocationEnvironmentClass.Unknown;
                        }

                        boolean isPolymorphic = true;
                        if (amInConstructor()) {
                            if (expression instanceof IdentifierExpression) {
                                if (((IdentifierExpression) expression).name().equals("this")) {
                                    isPolymorphic = false;
                                }
                            }
                        }

                        message.setReturnType(myOperatorFunc.type());
                        expression = new MessageExpression(expression, message, myOperatorFunc.type(), lineNumber,
                                false, originMethodClass, targetMethodClass, isPolymorphic);
                    } else if (expression.type() instanceof RoleType) {
                        // Check if it is in the requires section for a Role

                        final Type type = expression.type();
                        final Declaration associatedDeclaration = (type instanceof StagePropType)
                                ? ((StagePropType) type).associatedDeclaration()
                                : ((RoleType) type).associatedDeclaration();
                        final Map<String, List<MethodSignature>> requiresSection = (associatedDeclaration instanceof StagePropDeclaration)
                                ? ((StagePropDeclaration) associatedDeclaration).requiredSelfSignatures()
                                : ((RoleDeclaration) associatedDeclaration).requiredSelfSignatures();
                        final List<MethodSignature> newMethodSignatures = requiresSection.get(operatorAsString);
                        if (null != newMethodSignatures) {
                            // TODO: Should probably do more signature-checking here to make
                            // sure this is going to fly at run time. Tests?
                            final Type enclosingMegaType = Expression.nearestEnclosingMegaTypeOf(currentScope_);
                            originMethodClass = currentScope_.methodInvocationEnvironmentClass();
                            targetMethodClass = associatedDeclaration.type().enclosedScope()
                                    .methodInvocationEnvironmentClass();
                            final Message message = new Message(operatorAsString, params, lineNumber,
                                    enclosingMegaType);
                            expression = new MessageExpression(expression, message, expr2.type(), lineNumber, false,
                                    originMethodClass, targetMethodClass, !amInConstructor());
                        } else {
                            expression = new SumExpression(expression, operatorAsString, expr2, ctx.getStart(),
                                    this);
                        }
                    } else {
                        // No need to set x.setResultIsConsumed for the two operands,
                        // because the SumExpression constructor does it.
                        expression = new SumExpression(expression, operatorAsString, expr2, ctx.getStart(), this);
                    }
                }
            } else {
                expression = new ErrorExpression(null);
            }

            if (printProductionsDebug) {
                System.err.print("abelian_expr : abelian_expr ");
                for (int i = 0; i < ctx.ABELIAN_SUMOP().size(); i++) {
                    System.err.print("`");
                    System.err.print(ctx.ABELIAN_SUMOP(i).getText());
                    System.err.print("' abelian_expr ");
                }
                System.err.println();
            }
        } else if (null != ctx.abelian_product() && ctx.abelian_product().size() == 1) {
            expression = parsingData_.popExpression();
            if (printProductionsDebug) {
                System.err.println("abelian_expr : abelian_product ");
            }
        } else if ((null != ctx.expr()) && (null != ctx.abelian_expr()) && null != ctx.ASSIGN()) {
            // : lhs '=' rhs
            Expression rhs = null, lhs = null;
            if (parsingData_.currentExpressionExists()) {
                rhs = parsingData_.popExpression();
            } else {
                rhs = new ErrorExpression(null);
            }
            if (parsingData_.currentExpressionExists()) {
                lhs = parsingData_.popExpression();
            } else {
                lhs = new ErrorExpression(null);
            }
            // rhs.setResultIsConsumed(true);   // done by assignmentExpr call
            expression = this.assignmentExpr(lhs, ctx.ASSIGN().getText(), rhs, ctx);
            if (printProductionsDebug) {
                System.err.println("abelian_expr : abelian_expr ASSIGN expr");
            }
        } else if (null != ctx.if_expr()) {
            // | if_expr
            if (parsingData_.currentExpressionExists()) {
                expression = parsingData_.popExpression();
            } else {
                expression = new ErrorExpression(null);
            }
        } else {
            assert false;
        }

        if (null != expression) {
            // null check is error stumbling check
            parsingData_.pushExpression(expression);
        }

        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    private Expression handleRelopCall(final Expression lhs, final String operationAsString, final Expression rhs,
            int lineNumber) {

        Expression expression = null;
        ActualArgumentList argumentList = new ActualArgumentList();
        argumentList.addActualArgument(rhs);
        final Type lhsType = lhs.type();
        assert null != lhsType;
        final Expression self = new IdentifierExpression("t$his", lhsType, currentScope_, lhs.lineNumber());
        argumentList.addFirstActualParameter(self);

        MethodSignature methodSignature = null;
        if (lhsType instanceof InterfaceType) {
            methodSignature = ((InterfaceType) lhsType)
                    .lookupMethodSignatureWithConversionIgnoringParameter("compareTo", argumentList, null);
        } else {
            final MethodDeclaration methodDecl = lhsType.enclosedScope()
                    .lookupMethodDeclarationRecursive("compareTo", argumentList, false);
            methodSignature = methodDecl.signature();
        }
        if (null != methodSignature) {
            // O.K., it does have a compareTo. Generate code that will call
            // compareTo on the users' behalf

            // Nice constants
            final StaticScope globalScope = StaticScope.globalScope();
            final Type stringType = globalScope.lookupTypeDeclaration("String");
            final Type intType = globalScope.lookupTypeDeclaration("int");
            final Type booleanType = globalScope.lookupTypeDeclaration("boolean");
            final Type enclosingMegaType = Expression.nearestEnclosingMegaTypeOf(currentScope_);

            // The final result
            ExpressionList expressionList = null;

            // Generate some temporaries
            String variableNameForOperatorString, variableNameForCompareToResult;
            variableNameForOperatorString = "parseTemp$" + Integer.toString(kantParserVariableGeneratorCounter_);
            kantParserVariableGeneratorCounter_++;
            variableNameForCompareToResult = "parseTemp$" + Integer.toString(kantParserVariableGeneratorCounter_);
            kantParserVariableGeneratorCounter_++;

            final ObjectDeclaration variableForOperatorStringDecl = new ObjectDeclaration(
                    variableNameForOperatorString, stringType, lineNumber);
            currentScope_.declareObject(variableForOperatorStringDecl, this);
            final ObjectDeclaration variableForCompareToResultDecl = new ObjectDeclaration(
                    variableNameForCompareToResult, stringType, lineNumber);
            currentScope_.declareObject(variableForCompareToResultDecl, this);

            final IdentifierExpression operatorString = new IdentifierExpression(variableNameForOperatorString,
                    stringType, currentScope_, lineNumber);
            final IdentifierExpression compareToResult = new IdentifierExpression(variableNameForCompareToResult,
                    intType, currentScope_, lineNumber);

            // Get an expression for the operator string
            final Expression operatorStringExpression = Constant
                    .makeConstantExpressionFrom("\"" + operationAsString + "\"");
            operatorStringExpression.setResultIsConsumed(true);

            // Assign the operator string into its expression
            final AssignmentExpression assignOpString = new InternalAssignmentExpression(operatorString, "=",
                    operatorStringExpression, lineNumber, this);
            assignOpString.setResultIsConsumed(false);
            expressionList = new ExpressionList(assignOpString, enclosingMegaType);

            // Set up the argument list for the call to "compareTo".
            // If we got here it should be in the "requires" section
            // of the Role definition
            argumentList = new ActualArgumentList();
            argumentList.addActualArgument(rhs);
            argumentList.addFirstActualParameter(lhs);

            final Message compareToMessage = new Message("compareTo", argumentList, lineNumber, enclosingMegaType);
            compareToMessage.setReturnType(intType);

            IdentifierExpression qualifier = new IdentifierExpression("this", enclosingMegaType, currentScope_,
                    lineNumber);

            final QualifiedIdentifierExpression newLhs = new QualifiedIdentifierExpression(qualifier, lhs.name(),
                    lhs.type());
            newLhs.setResultIsConsumed(true);

            // The compareTo message is applied to its first own
            // argument - in this case, lhs
            MethodInvocationEnvironmentClass originMethodClass, targetMethodClass;

            originMethodClass = currentScope_.methodInvocationEnvironmentClass();
            targetMethodClass = lhs.type().enclosedScope().methodInvocationEnvironmentClass();

            expression = new MessageExpression(lhs, compareToMessage, intType, lineNumber, false, originMethodClass,
                    targetMethodClass, !amInConstructor());
            expression.setResultIsConsumed(true); // leave the compareTo return value on the stack

            // Assign result of compareTo
            final AssignmentExpression assignCompareTo = new InternalAssignmentExpression(compareToResult, "=",
                    expression, lineNumber, this);
            assignCompareTo.setResultIsConsumed(false);
            expressionList.addExpression(assignCompareTo);

            final ActualArgumentList paramListToConverter = new ActualArgumentList();

            paramListToConverter.addActualArgument(compareToResult);
            paramListToConverter.addActualArgument(operatorString);

            final Message convertMessage = new Message("compareTo$toBoolean", paramListToConverter, lineNumber,
                    enclosingMegaType);
            convertMessage.setReturnType(booleanType);

            final Type objectType = StaticScope.globalScope().lookupTypeDeclaration("Object");
            final Expression classObjectExpression = new IdentifierExpression("Object", objectType,
                    StaticScope.globalScope(), lineNumber);
            originMethodClass = currentScope_.methodInvocationEnvironmentClass();
            targetMethodClass = MethodInvocationEnvironmentClass.ClassEnvironment;
            expression = new MessageExpression(classObjectExpression, convertMessage, booleanType, lineNumber, true,
                    originMethodClass, targetMethodClass, !amInConstructor());
            expression.setResultIsConsumed(true);

            // This sucks. Expression lists usually take the first seed
            // argument as the type of the list. Override it.
            expressionList.setType(booleanType);
            expressionList.setResultIsConsumed(true);
            expressionList.addExpression(expression);

            expression = expressionList;
        } else {
            // This was kind of the last chance. We've probably
            // already given the user some diagnostics. Now let's
            // give her some advice.
            errorHook6p2(ErrorIncidenceType.Fatal, lineNumber, "To use `", operationAsString + "' on object `",
                    lhs.name(), "' you must declare a compareTo(", rhs.type().name() + " argument",
                    ") operation in the `requires' section of " + lhs.type().name());
            expression = new ErrorExpression(lhs);
        }

        return expression;
    }

    private Expression handleRelopCallWithRoleLHS(final Expression lhs, final String operationAsString,
            final Expression rhs, int lineNumber) {
        assert lhs.type() instanceof RoleType;

        Expression expression = null;
        ActualArgumentList argumentList = new ActualArgumentList();
        argumentList.addActualArgument(rhs);
        final RoleType roleType = (RoleType) lhs.type();
        assert null != roleType;
        final Expression self = new IdentifierExpression("t$his", roleType, currentScope_, // just this?
                lhs.lineNumber());
        argumentList.addFirstActualParameter(self);

        final MethodDeclaration methodDecl = rhs.type().enclosedScope().lookupMethodDeclarationIgnoringParameter(
                operationAsString, argumentList, "this", /* conversionAllowed = */ false);
        if (null == methodDecl) {
            // There is an invocation of a boolean operator on a Role
            // or Stage Prop. Since Roles are inherently untyped,
            // they don't know how to do relops. And for right now
            // we don't allow users to define relops, since doing it
            // consistently is hard... We do, however, allow them to
            // define a compareTo method in the Java Comparable interface
            // tradition. We've already pre-checked above whether it
            // has one. Let's check again:

            if (roleType.hasCompareToOrHasOperatorForArgOfType(operationAsString, rhs.type())) {
                // O.K., it does. Generate code that will call
                // compareTo on the users' behalf

                // Nice constants
                final Type stringType = StaticScope.globalScope().lookupTypeDeclaration("String");
                final Type intType = StaticScope.globalScope().lookupTypeDeclaration("int");
                final Type booleanType = StaticScope.globalScope().lookupTypeDeclaration("boolean");
                final Type enclosingMegaType = Expression.nearestEnclosingMegaTypeOf(currentScope_);

                // The final result
                ExpressionList expressionList = null;

                // Generate some temporaries
                String variableNameForOperatorString, variableNameForCompareToResult;
                variableNameForOperatorString = "parseTemp$"
                        + Integer.toString(kantParserVariableGeneratorCounter_);
                kantParserVariableGeneratorCounter_++;
                variableNameForCompareToResult = "parseTemp$"
                        + Integer.toString(kantParserVariableGeneratorCounter_);
                kantParserVariableGeneratorCounter_++;

                final ObjectDeclaration variableForOperatorStringDecl = new ObjectDeclaration(
                        variableNameForOperatorString, stringType, lineNumber);
                currentScope_.declareObject(variableForOperatorStringDecl, this);
                final ObjectDeclaration variableForCompareToResultDecl = new ObjectDeclaration(
                        variableNameForCompareToResult, stringType, lineNumber);
                currentScope_.declareObject(variableForCompareToResultDecl, this);

                final IdentifierExpression operatorString = new IdentifierExpression(variableNameForOperatorString,
                        stringType, currentScope_, lineNumber);
                final IdentifierExpression compareToResult = new IdentifierExpression(
                        variableNameForCompareToResult, intType, currentScope_, lineNumber);

                // Get an expression for the operator string
                final Expression operatorStringExpression = Constant
                        .makeConstantExpressionFrom("\"" + operationAsString + "\"");
                operatorStringExpression.setResultIsConsumed(true);

                // Assign the operator string into its expression
                final AssignmentExpression assignOpString = new InternalAssignmentExpression(operatorString, "=",
                        operatorStringExpression, lineNumber, this);
                assignOpString.setResultIsConsumed(false);
                expressionList = new ExpressionList(assignOpString, enclosingMegaType);

                // Set up the argument list for the call to "compareTo".
                // If we got here it should be in the "requires" section
                // of the Role definition
                argumentList = new ActualArgumentList();
                argumentList.addActualArgument(rhs);
                argumentList.addFirstActualParameter(lhs);

                final Message compareToMessage = new Message("compareTo", argumentList, lineNumber,
                        enclosingMegaType);
                compareToMessage.setReturnType(intType);

                // Are we in a Role or just in a Context?
                // (Probably unnecessary, but it keeps a tidy house early)
                IdentifierExpression qualifier = null;
                if (null != currentScope_.lookupObjectDeclaration("current$context")) {
                    // In another Role  get the Context pointer
                    // to qualify *this* Role
                    qualifier = new IdentifierExpression("current$context", currentContext_.type(), currentScope_,
                            lineNumber);
                } else {
                    // We're at the Context level  just use "this"
                    qualifier = new IdentifierExpression("this", currentContext_.type(), currentScope_, lineNumber);
                }

                final QualifiedIdentifierExpression newLhs = new QualifiedIdentifierExpression(qualifier,
                        lhs.name(), lhs.type());
                newLhs.setResultIsConsumed(true);

                // The compareTo message is applied to its first own
                // argument - in this case, lhs
                MethodInvocationEnvironmentClass originMethodClass, targetMethodClass;

                originMethodClass = currentScope_.methodInvocationEnvironmentClass();
                targetMethodClass = lhs.type().enclosedScope().methodInvocationEnvironmentClass();

                expression = new MessageExpression(lhs, compareToMessage, intType, lineNumber, false,
                        originMethodClass, targetMethodClass, !amInConstructor());
                expression.setResultIsConsumed(true);

                // Assign result of compareTo
                final AssignmentExpression assignCompareTo = new InternalAssignmentExpression(compareToResult, "=",
                        expression, lineNumber, this);
                assignCompareTo.setResultIsConsumed(false);
                expressionList.addExpression(assignCompareTo);

                final ActualArgumentList paramListToConverter = new ActualArgumentList();

                paramListToConverter.addActualArgument(compareToResult);
                paramListToConverter.addActualArgument(operatorString);

                final Message convertMessage = new Message("compareTo$toBoolean", paramListToConverter, lineNumber,
                        enclosingMegaType);
                convertMessage.setReturnType(booleanType);

                // Because Roles are bound to objects  and knowledge of their classes
                // only at run time, we can't put compareTo$toBoolean in the Role
                // scope. It's a static, so it can live anywhere. It could live in
                // Class or Object but that's a bit of an overly broad scope for
                // it. We could just clone a declaration of it in every scope where
                // there is a converTo method. But that doesn't allow end users to
                // create their own types with convertTo methods in the interest
                // of making the built-in relational operators work for them. So
                // we put it up in Object. Looking it up through rhs should work.
                final Type objectType = StaticScope.globalScope().lookupTypeDeclaration("Object");
                final Expression classObjectExpression = new IdentifierExpression("Object", objectType,
                        StaticScope.globalScope(), lineNumber);
                originMethodClass = currentScope_.methodInvocationEnvironmentClass();
                targetMethodClass = MethodInvocationEnvironmentClass.ClassEnvironment;
                expression = new MessageExpression(classObjectExpression, convertMessage, booleanType, lineNumber,
                        true, originMethodClass, targetMethodClass, !amInConstructor());
                expression.setResultIsConsumed(true);

                // This sucks. Expression lists usually take the first seed
                // argument as the type of the list. Override it.
                expressionList.setType(booleanType);
                expressionList.setResultIsConsumed(true);
                expressionList.addExpression(expression);

                expression = expressionList;
            } else {
                // This was kind of the last chance. We've probably
                // already given the user some diagnostics. Now let's
                // give her some advice.
                if (lhs.isntError() && rhs.isntError()) {
                    errorHook6p2(ErrorIncidenceType.Fatal, lineNumber, "To use `",
                            operationAsString + "' on Role `", lhs.name(), "' you must declare a compareTo(",
                            rhs.type().name() + " argument",
                            ") operation in the `requires' section of " + lhs.name());
                }
                expression = new ErrorExpression(lhs);
            }
        } else {
            expression = new RelopExpression(lhs, operationAsString, rhs);
        }
        return expression;
    }

    @Override
    public void exitBoolean_expr(KantParser.Boolean_exprContext ctx) {
        // : boolean_product (ABELIAN_SUMOP abelian_product)*
        // | boolean_expr op=('&&' | '||' | '^' ) boolean_expr
        // | if_expr
        // | abelian_expr op1=('is' | 'Is') op2 =('not' | 'Not') abelian_expr
        // | abelian_expr op=('!=' | '==' | GT | LT | '>=' | '<='| (is/isnot variants)) abelian_expr
        // | <assoc=right> boolean_expr ASSIGN expr

        Expression expression = null;
        final int lineNumber = ctx.getStart().getLine();
        boolean useCompareTo = false;

        if (null != ctx.op1 && null != ctx.op2) {
            // 'is' 'not'
            Expression rhs = null, lhs = null;
            if (parsingData_.currentExpressionExists()) {
                rhs = parsingData_.popExpression();
            } else {
                rhs = new ErrorExpression(null);
            }
            if (parsingData_.currentExpressionExists()) {
                lhs = parsingData_.popExpression();
            } else {
                lhs = new ErrorExpression(null);
            }
            lhs.setResultIsConsumed(true);
            rhs.setResultIsConsumed(true);

            expression = new IdentityBooleanExpression(lhs, "is not", rhs);
        } else if ((null != ctx.abelian_expr() && ctx.abelian_expr().size() == 1) && (null != ctx.boolean_expr())
                && (null == ctx.boolean_expr() || ctx.boolean_expr().size() == 0) && null == ctx.ASSIGN()) {
            // : abelian_expr
            Expression rhs = null;
            if (parsingData_.currentExpressionExists()) {
                rhs = parsingData_.popExpression();
            } else {
                rhs = new ErrorExpression(null);
            }

            // rhs.setResultIsConsumed(true);   // done by assignmentExpr call
            expression = rhs;
            if (printProductionsDebug) {
                System.err.println("boolean_expr : abelian_expr");
            }
        } else if (null != ctx.if_expr()) {
            // | if_expr
            expression = parsingData_.popExpression();
            if (printProductionsDebug) {
                System.err.println("boolean_expr : if_expr ");
            }
        } else if (null != ctx.boolean_product() && ctx.boolean_product().size() > 1
                && null != ctx.BOOLEAN_SUMOP()) {
            // | boolean_expr op=('&&' | '||' | '^' ) boolean_expr

            // Make it left-associative
            final int booleanSumopSize = ctx.BOOLEAN_SUMOP().size();
            final Stack<Expression> exprStack = new Stack<Expression>();
            for (int i = 0; i < booleanSumopSize; i++) {
                final Expression expr2 = parsingData_.popExpression();
                exprStack.push(expr2);
            }

            if (parsingData_.currentExpressionExists()) {
                expression = parsingData_.popExpression();
                for (int i = 0; i < booleanSumopSize; i++) {
                    final Expression expr2 = exprStack.pop();
                    final String operatorAsString = ctx.BOOLEAN_SUMOP(i).getText();
                    expression = new SumExpression(expression, operatorAsString, expr2, ctx.getStart(), this);
                }
            } else {
                expression = new ErrorExpression(null);
            }

            if (printProductionsDebug) {
                System.err.print("boolean_expr : boolean_expr ");
                for (int i = 0; i < ctx.BOOLEAN_SUMOP().size(); i++) {
                    System.err.print("`");
                    System.err.print(ctx.BOOLEAN_SUMOP(i).getText());
                    System.err.print("' boolean_expr ");
                }
                System.err.println();
            }
        } else if (null != ctx.boolean_product() && ctx.boolean_product().size() == 1) {
            expression = parsingData_.popExpression();
            if (printProductionsDebug) {
                System.err.println("boolean_expr : boolean_product ");
            }
        } else if (null != ctx.boolean_expr() && ctx.boolean_expr().size() > 1 && null != ctx.op
                && ctx.op.getText().length() > 0) {
            //   | abelian_expr op=('&&') abelian_expr
            final Token relationalOperator = ctx.op;

            final Expression rhs = parsingData_.currentExpressionExists() ? parsingData_.popExpression()
                    : new ErrorExpression(null);
            final Expression lhs = parsingData_.currentExpressionExists() ? parsingData_.popExpression()
                    : new ErrorExpression(null);
            lhs.setResultIsConsumed(true);
            rhs.setResultIsConsumed(true);
            if (lhs.isntError() && rhs.isntError() && lhs.type().canBeConvertedFrom(rhs.type()) == false) {
                errorHook5p2(ErrorIncidenceType.Fatal, lineNumber, "Expression '" + rhs.getText(),
                        "' is not of the right type (", lhs.type().getText(), ").");
            }

            assert null != relationalOperator;
            final String operationAsString = relationalOperator.getText();
            expression = new RelopExpression(lhs, operationAsString, rhs);

            if (printProductionsDebug) {
                System.err.print("relop_expr : boolean_expr ");
                System.err.print(operationAsString);
                System.err.println(" boolean_expr");
            }
        } else if ((null != ctx.boolean_expr()) && (ctx.boolean_expr().size() == 1) && (ctx.expr() != null)
                && null != ctx.ASSIGN()) {
            // : lhs '=' rhs
            Expression rhs = null, lhs = null;
            if (parsingData_.currentExpressionExists()) {
                rhs = parsingData_.popExpression();
            } else {
                rhs = new ErrorExpression(null);
            }
            if (parsingData_.currentExpressionExists()) {
                lhs = parsingData_.popExpression();
            } else {
                lhs = new ErrorExpression(null);
            }
            // rhs.setResultIsConsumed(true);   // done by assignmentExpr call
            expression = this.assignmentExpr(lhs, ctx.ASSIGN().getText(), rhs, ctx);
            if (printProductionsDebug) {
                System.err.println("boolean_expr : boolean_expr ASSIGN boolean_expr");
            }
        } else if (null != ctx.abelian_expr() && ctx.abelian_expr().size() > 1 && null != ctx.op
                && ctx.op.getText().length() > 0) {
            //   | abelian_expr op=('<=' | '>=' | '<' | '>' | '==' | '!=' | (is /isnot variants)) abelian_expr
            final Token relationalOperator = ctx.op;

            Expression rhs = null, lhs = null;
            if (parsingData_.currentExpressionExists()) {
                rhs = parsingData_.popExpression();
            } else {
                rhs = new ErrorExpression(null);
            }
            if (parsingData_.currentExpressionExists()) {
                lhs = parsingData_.popExpression();
            } else {
                lhs = new ErrorExpression(null);
            }
            lhs.setResultIsConsumed(true);
            rhs.setResultIsConsumed(true);

            assert null != relationalOperator;
            final String operationAsString = relationalOperator.getText();

            if (operationAsString.equals("is") || operationAsString.equals("Is")
                    || operationAsString.equals("isnot") || operationAsString.equals("IsNot")) {
                expression = new IdentityBooleanExpression(lhs, operationAsString, rhs);
            } else {
                // Refactor into method
                final Type lhsType = null == lhs ? null : lhs.type(), rhsType = null == rhs ? null : rhs.type();

                if (null != lhsType && null != rhsType) {
                    if (lhsType.canBeConvertedFrom(rhsType) == false && lhs.isntError() && lhsType.isntError()
                            && rhs.isntError() && rhsType.isntError()) {
                        errorHook5p2(ErrorIncidenceType.Fatal, lineNumber, "Expression `" + rhs.getText(),
                                "' is not of the right type (", lhs.type().getText(), ").");
                    }

                    if (lhsType.canBeLhsOfBinaryOperatorForRhsType(operationAsString, rhsType)) {
                        ; // O.K.
                    } else if (lhs.isntError() && lhs.type().isntError() && rhs.isntError()
                            && rhsType.isntError()) {
                        errorHook5p2(ErrorIncidenceType.Fatal, lineNumber,
                                "You may not apply '" + operationAsString, "' to objects of type `",
                                lhs.type().getText(), "'.");
                    }
                }

                final ActualArgumentList parameterList = new ActualArgumentList();
                parameterList.addActualArgument(lhs);
                parameterList.addActualArgument(rhs);
                if (null != lhs && null != rhs && null != lhsType && (lhsType instanceof InterfaceType) == false
                        && lhs.isntError() && rhs.isntError() && lhsType.isntError() && rhsType.isntError()
                        && (operationAsString.equals("<") || operationAsString.equals(">")
                                || operationAsString.equals("<=") || operationAsString.equals(">=")
                                || operationAsString.equals("==") || operationAsString.equals("!="))
                        && null != lhsType.enclosedScope().lookupMethodDeclarationRecursive("compareTo",
                                parameterList, false)) {
                    useCompareTo = true; // then, yeah...
                } else if (null != lhs && null != rhs && null != lhsType && lhsType instanceof InterfaceType
                        && (operationAsString.equals("<") || operationAsString.equals(">")
                                || operationAsString.equals("<=") || operationAsString.equals(">=")
                                || operationAsString.equals("==") || operationAsString.equals("!="))
                        && null != ((StaticInterfaceScope) lhs.type().enclosedScope())
                                .lookupMethodDeclarationWithConversionIgnoringParameter("compareTo", parameterList,
                                        false, "this")) {
                    useCompareTo = true; // then, yeah...
                } else if (null != rhs && null != rhsType
                        && rhs.type().canBeRhsOfBinaryOperator(operationAsString)) {
                    ; // O.K.
                } else if (rhs instanceof NullExpression) {
                    ; // can always compare with NULL
                } else if (rhs.isntError() && null != rhs.type() && rhs.type().isntError()) {
                    errorHook5p2(ErrorIncidenceType.Fatal, lineNumber,
                            "You may not use an object of type '"
                                    + (null == rhs || null == rhs.type() ? "<unknown>" : rhs.type().getText()),
                            "' as an argument to `", operationAsString, "'.");
                }

                if (lhs.type() instanceof RoleType) {
                    // We may need to convert the operator
                    // to a call of compareTo
                    if ((operationAsString.equals("==") || operationAsString.equals("!="))
                            && rhs.type().pathName().equals("Null")) {
                        // Comparing to NULL is O.K...  for now....
                        expression = new RelopExpression(lhs, operationAsString, rhs);
                    } else {
                        expression = handleRelopCallWithRoleLHS(lhs, operationAsString, rhs, lineNumber);
                    }
                } else {
                    if (useCompareTo && lhs.type() instanceof BuiltInType == false
                            && rhs.type().pathName().equals("Null") == false) {
                        // We found above that things are all set up to use compareTo. Use it.
                        expression = handleRelopCall(lhs, operationAsString, rhs, lineNumber);
                    } else {
                        expression = new RelopExpression(lhs, operationAsString, rhs);
                    }
                }
            }

            if (printProductionsDebug) {
                System.err.print("boolean_expr : abelian_expr ");
                System.err.print(operationAsString);
                System.err.println(" abelian_expr");
            }

        } else {
            assert false;
        }

        if (null != expression) {
            // null check is error stumbling check
            parsingData_.pushExpression(expression);
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void exitAbelian_product(KantParser.Abelian_productContext ctx) {
        // : abelian_unary_op (ABELIAN_MULOP abelian_unary_op)*
        // | <assoc=right> abelian_product POW abelian_atom

        Expression expression = null;

        if (null != ctx.abelian_unary_op() && ctx.abelian_unary_op().size() > 1 && null != ctx.ABELIAN_MULOP()) {
            //   | abelian_expr op=('*' | '/' | '%') abelian_expr

            final int abelianMulopSize = ctx.ABELIAN_MULOP().size();
            final Stack<Expression> exprStack = new Stack<Expression>();
            for (int i = 0; i < abelianMulopSize; i++) {
                if (parsingData_.currentExpressionExists()) {
                    final Expression expr2 = parsingData_.popExpression();
                    exprStack.push(expr2);
                } else {
                    break;
                }
            }

            if (parsingData_.currentExpressionExists()) {
                expression = parsingData_.popExpression();
                for (int i = 0; i < abelianMulopSize; i++) {
                    final Expression expr2 = exprStack.pop();
                    final String operatorAsString = ctx.ABELIAN_MULOP(i).getText();
                    expression = new ProductExpression(expression, operatorAsString, expr2, ctx.getStart(), this);
                }
            } else {
                expression = new ErrorExpression(null);
            }

            if (printProductionsDebug) {
                System.err.print("abelian_product : abelian_unary_op ");
                for (int i = 0; i < ctx.ABELIAN_MULOP().size(); i++) {
                    System.err.print("`");
                    System.err.print(ctx.ABELIAN_MULOP(i).getText());
                    System.err.print("' abelian_unary_op ");
                }
                System.err.println();
            }
        } else if (null != ctx.abelian_unary_op() && ctx.abelian_unary_op().size() == 1) {
            expression = parsingData_.popExpression();

            if (printProductionsDebug) {
                System.err.println("abelian_product : abelian_unary_op ");
            }
        } else if (null != ctx.abelian_product() && null != ctx.abelian_atom() && null != ctx.POW()) {
            // : <assoc=right>abelian_expr POW abelian_atom
            final Expression expr2 = parsingData_.popExpression();
            final Expression expr1 = parsingData_.popExpression();

            final String operatorAsString = ctx.POW().getText();

            expression = new PowerExpression(expr1, operatorAsString, expr2, ctx.getStart(), this);

            if (printProductionsDebug) {
                System.err.println("abelian_expr : abelian_expr POW expr");
            }
        } else {
            assert false;
        }

        if (null != expression) {
            // null check is error stumbling check
            parsingData_.pushExpression(expression);
        }

        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void exitBoolean_product(KantParser.Boolean_productContext ctx) {
        // : boolean_unary_op (BOOLEAN_MULOP boolean_unary_op)*

        Expression expression = null;
        List<KantParser.Boolean_unary_opContext> unaryOp = ctx.boolean_unary_op();
        assert null != unaryOp;

        if (null != unaryOp && unaryOp.size() == 1
                && (null == ctx.BOOLEAN_MULOP() || 0 == ctx.BOOLEAN_MULOP().size())) {
            if (parsingData_.currentExpressionExists()) {
                expression = parsingData_.popExpression();
            } else {
                expression = new ErrorExpression(null);
            }

            if (printProductionsDebug) {
                System.err.println("boolean_product : boolean_unary_op ");
            }
        } else if (null != unaryOp && (unaryOp.size() > 1) && null != ctx.BOOLEAN_MULOP()
                && 0 < ctx.BOOLEAN_MULOP().size()) {
            // boolean_unary_op (BOOLEAN_MULOP boolean_unary_op)*

            final int booleanMulopSize = ctx.BOOLEAN_MULOP().size();
            final Stack<Expression> exprStack = new Stack<Expression>();
            for (int i = 0; i < booleanMulopSize; i++) {
                if (parsingData_.currentExpressionExists()) {
                    final Expression expr2 = parsingData_.popExpression();
                    exprStack.push(expr2);
                } else {
                    break;
                }
            }

            if (parsingData_.currentExpressionExists()) {
                expression = parsingData_.popExpression();
                for (int i = 0; i < booleanMulopSize; i++) {
                    final Expression expr2 = exprStack.pop();
                    final String operatorAsString = ctx.BOOLEAN_MULOP(i).getText();
                    expression = new ProductExpression(expression, operatorAsString, expr2, ctx.getStart(), this);
                }
            } else {
                expression = new ErrorExpression(null);
            }

            if (printProductionsDebug) {
                System.err.print("boolean_product : boolean_unary_op ");
                for (int i = 0; i < ctx.BOOLEAN_MULOP().size(); i++) {
                    System.err.print("`");
                    System.err.print(ctx.BOOLEAN_MULOP(i).getText());
                    System.err.print("' boolean_unary_op ");
                }
                System.err.println();
            }
        } else {
            assert false;
        }

        if (null != expression) {
            // null check is error stumbling check
            parsingData_.pushExpression(expression);
        }

        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void exitAbelian_unary_op(KantParser.Abelian_unary_opContext ctx) {
        // :  ABELIAN_SUMOP abelian_atom
        // |  LOGICAL_NEGATION abelian_atom
        // |  abelian_atom
        Expression expression = null;

        if (null != ctx.ABELIAN_SUMOP()) {
            // ABELIAN_SUMOP abelian_atom
            if (parsingData_.currentExpressionExists()) {
                expression = parsingData_.popExpression();
                expression = new UnaryAbelianopExpression(expression, ctx.ABELIAN_SUMOP().getText(), this);
            } else {
                expression = new ErrorExpression(null);
            }

            if (printProductionsDebug) {
                System.err.println("abelian_unary_op : '-' abelian_atom");
            }
        } else {
            if (parsingData_.currentExpressionExists()) {
                // Only do it if it's there to protect against error stumbling.
                expression = parsingData_.popExpression();
            } else {
                expression = new ErrorExpression(null);
            }
            if (printProductionsDebug) {
                System.err.println("abelian_unary_op : abelian_atom");
            }
        }

        if (null != expression) {
            // null check is error stumbling check
            parsingData_.pushExpression(expression);
        }

        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void exitBoolean_unary_op(KantParser.Boolean_unary_opContext ctx) {
        // :  LOGICAL_NEGATION boolean_expr
        // |  boolean_atom

        Expression expression = null;

        if (null != ctx.LOGICAL_NEGATION()) {
            //   | LOGICAL_NEGATION boolean_expr

            expression = parsingData_.popExpression();
            final Type type = expression.type();
            if (StaticScope.globalScope().lookupTypeDeclaration("boolean").canBeConvertedFrom(type)) {
                ; // is O.K.
            } else {
                errorHook5p2(ErrorIncidenceType.Fatal, ctx.getStart().getLine(), "Expression `",
                        expression.getText(), "' is not of type boolean.", "");
            }
            expression = new UnaryAbelianopExpression(expression, "!", this);

            if (printProductionsDebug) {
                System.err.println("boolean_unary_op : '!' boolean_expr");
            }
        } else {
            // |  boolean_atom

            if (parsingData_.currentExpressionExists()) {
                // Only do it if it's there to protect against error stumbling.
                expression = parsingData_.popExpression();
            } else {
                expression = new ErrorExpression(null);
            }
            if (printProductionsDebug) {
                System.err.println("boolean_unary_op : boolean_atom");
            }
        }

        if (null != expression) {
            // null check is error stumbling check
            parsingData_.pushExpression(expression);
        }

        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void enterAbelian_atom(KantParser.Abelian_atomContext ctx) {
        //  | NEW JAVA_ID type_list '(' argument_list ')'
        if (null != ctx.NEW() && null != ctx.type_list() && null != ctx.argument_list()) {
            parsingData_.pushArgumentList(new ActualArgumentList());
        }
    }

    private Expression checkNakedNew(final Expression newExpr) {
        if (newExpr instanceof NewExpression == false && newExpr instanceof NewArrayExpression == false) {
            assert newExpr instanceof NewExpression || newExpr instanceof NewArrayExpression;
        }
        Expression retval = newExpr;

        // It's possible to write an expression of the form:
        //
        //      new foo
        //
        // without binding the result. That means that the
        // object reference count is never manipulated, which
        // means that the object is never reclaimed. If we
        // have one of those, just give it a temporary home
        // in the current activation record. Activation record
        // closure will clean it up.

        if (newExpr.resultIsConsumed() == false && this instanceof Pass4Listener) {
            // Pass4Listener check above is so we do this only once,
            // avoiding multiple declarations of the temporary
            // variable
            final String tempName = "temp$" + parsingData_.variableGeneratorCounter_;
            parsingData_.variableGeneratorCounter_++;
            final ObjectDeclaration tempVariableDecl = new ObjectDeclaration(tempName, newExpr.type(),
                    newExpr.lineNumber());
            ;
            currentScope_.declareObject(tempVariableDecl, this);

            final IdentifierExpression tempVariable = new IdentifierExpression(tempName, newExpr.type(),
                    currentScope_, newExpr.lineNumber());

            retval = new InternalAssignmentExpression(tempVariable, "=", newExpr, newExpr.lineNumber(), this);
        }
        return retval;
    }

    @Override
    public void exitAbelian_atom(KantParser.Abelian_atomContext ctx) {
        //  abelian_expr         
        //  | NEW message
        //   | NEW type_name
        //   | NEW type_name '[' expr ']'
        //  | NEW JAVA_ID type_list '(' argument_list ')'
        //  | abelian_atom '.' JAVA_ID
        //  | abelian_atom '.' message
        //  | builtin_type_name '.' message
        //   | null_expr
        //   | JAVA_ID
        //   | abelian_atom ABELIAN_INCREMENT_OP
        //   | ABELIAN_INCREMENT_OP abelian_atom
        //   | constant
        //   | '(' abelian_expr ')'
        //   | abelian_atom '[' expr ']'
        //   | abelian_atom '[' expr ']' ABELIAN_INCREMENT_OP
        //   | ABELIAN_INCREMENT_OP abelian_atom '[' expr ']'
        //   | ABELIAN_INCREMENT_OP abelian_atom '.' JAVA_ID
        //   | abelian_atom '.' JAVA_ID ABELIAN_INCREMENT_OP
        //   | /* this. */ message
        //   | abelian_atom '.' CLONE

        // All passes. EXPLICITLY INVOKED FROM PASS 4

        final int lineNumber = ctx.getStart().getLine();
        Expression expression = null;

        if (null != ctx.NEW() && null != ctx.type_list() && null != ctx.argument_list()) {
            //  | NEW JAVA_ID type_list '(' argument_list ')'
            Type type = null;
            final ActualArgumentList argument_list = parsingData_.popArgumentList();
            final List<String> typeParameterNameList = parsingData_.popTypeNameList();

            final String JAVA_ID = ctx.JAVA_ID().getText();
            final TemplateDeclaration templateDeclaration = currentScope_
                    .lookupTemplateDeclarationRecursive(JAVA_ID);
            if (null == templateDeclaration) {
                errorHook5p2(ErrorIncidenceType.Fatal, lineNumber, "Cannot find template ", JAVA_ID, "", "");
                type = new ErrorType();
                expression = new ErrorExpression(null);
            } else {
                final StringBuffer typeNameBuffer = new StringBuffer();
                typeNameBuffer.append(JAVA_ID);
                typeNameBuffer.append("<");
                for (int i = 0; i < typeParameterNameList.size(); i++) {
                    typeNameBuffer.append(typeParameterNameList.get(i));
                    if (i < typeParameterNameList.size() - 1) {
                        typeNameBuffer.append(",");
                    }
                }
                typeNameBuffer.append(">");
                final String compoundTypeName = typeNameBuffer.toString();
                type = currentScope_.lookupTypeDeclarationRecursive(compoundTypeName);
                if (null == type) {
                    type = this.lookupOrCreateTemplateInstantiation(JAVA_ID, typeParameterNameList, lineNumber);
                    if (null == type) {
                        errorHook5p2(ErrorIncidenceType.Internal, lineNumber, "Cannot find template instantiation ",
                                compoundTypeName, "", "");
                        type = new ErrorType();
                    }
                }

                final Type enclosingMegaType = Expression.nearestEnclosingMegaTypeOf(currentScope_);
                final Message message = new Message(compoundTypeName, argument_list, lineNumber, enclosingMegaType);
                final NewExpression newExpr = new NewExpression(type, message, lineNumber, enclosingMegaType);
                ctorCheck(type, message, lineNumber);
                addSelfAccordingToPass(type, message, currentScope_);
                expression = newExpr;
            }

            final MethodDeclaration constructor = type.enclosedScope().lookupMethodDeclaration(JAVA_ID,
                    argument_list, false);
            if (null != constructor) {
                final boolean isAccessible = currentScope_.canAccessDeclarationWithAccessibility(constructor,
                        constructor.accessQualifier(), lineNumber);
                if (isAccessible == false) {
                    errorHook6p2(ErrorIncidenceType.Fatal, lineNumber, "Cannot access constructor `",
                            constructor.signature().getText(), "' with `", constructor.accessQualifier().asString(),
                            "' access qualifier.", "");
                }

                // Create a new message just for checking (not dispatching)
                final String methodSelectorName = constructor.name();
                final Type enclosingType = Expression.nearestEnclosingMegaTypeOf(currentScope_);
                final Message message = new Message(methodSelectorName, argument_list, lineNumber, enclosingType);
                final List<String> nonmatchingMethods = message.validInRunningEnviroment(constructor);
                if (0 < nonmatchingMethods.size()) {
                    errorHook5p2(ErrorIncidenceType.Fatal, lineNumber, "The parameters to script `",
                            methodSelectorName + message.argumentList().selflessGetText(),
                            "' have scripts that are unavailable outside this Context, ",
                            "though some formal parameters of " + methodSelectorName
                                    + " presume they are available (they are likely Role scripts):");
                    for (final String badMethod : nonmatchingMethods) {
                        errorHook5p2(ErrorIncidenceType.Fatal, lineNumber, "\t", badMethod, "", "");
                    }
                }
            }

            expression = checkNakedNew(expression);

            if (printProductionsDebug) {
                System.err.println("abelian_atom : NEW JAVA_ID type_list '(' argument_list ')'");
            }
        } else if (null == ctx.ABELIAN_INCREMENT_OP() && null != ctx.abelian_atom() && null != ctx.JAVA_ID()) {
            //   | abelian_atom '.' JAVA_ID
            // The following line DOES pop the expression stack
            final ExpressionStackAPI rawExpression = this.exprFromExprDotJAVA_ID(ctx.JAVA_ID(), ctx.getStart(),
                    null);
            assert rawExpression instanceof Expression;
            expression = (Expression) rawExpression;
            if (printProductionsDebug) {
                System.err.print("abelian_atom : abelian_atom '.' JAVA_ID (");
                System.err.print(ctx.JAVA_ID().getText());
                System.err.println(")");
            }
        } else if (null == ctx.builtin_type_name() && null != ctx.abelian_atom() && null != ctx.message()) {
            //   | abelian_atom '.' message
            // This routine actually does pop the expressions stack (and the Message stack)
            expression = this.messageSend(ctx.getStart(), ctx.abelian_atom(), null);

            if (null == expression) {
                errorHook5p2(ErrorIncidenceType.Fatal, lineNumber, "No match for call: ",
                        ctx.abelian_atom().getText(), ".", ctx.message().getText());
                expression = new ErrorExpression(null);
            }

            if (printProductionsDebug) {
                System.err.println("abelian_product : abelian_atom '.' message");
            }
        } else if (null == ctx.NEW() && null != ctx.builtin_type_name() && null != ctx.message()) {
            //   | builtin_type_name '.' message
            //    (e.g., String.join)

            // This routine actually does pop the expressions stack (and the Message stack)
            expression = this.messageSend(ctx.getStart(), ctx.abelian_atom(), ctx.builtin_type_name());

            if (null == expression || expression.isError()) {
                errorHook5p2(ErrorIncidenceType.Fatal, lineNumber, "No match for call: ", ctx.type_name().getText(),
                        ".", ctx.message().getText());
                expression = new ErrorExpression(null);
            }

            if (printProductionsDebug) {
                System.err.println("type_name : abelian_atom '.' message");
            }
        } else if (null != ctx.NEW()) {
            // NEW message
            // NEW type_name '[' expr ']'

            final KantParser.ExprContext sizeExprCtx = (null == ctx.expr()) ? null
                    : ((ctx.expr().size() == 0) ? null : ctx.expr(0));
            final List<ParseTree> ctxChildren = ctx.children;
            final Token ctxGetStart = ctx.getStart();
            final MessageContext ctxMessage = ctx.message();
            expression = this.newExpr(ctxChildren, ctxGetStart, sizeExprCtx, ctxMessage);

            if (expression instanceof NullExpression == false && expression.isntError()) {
                expression = checkNakedNew(expression);
            }

            if (printProductionsDebug) {
                System.err.print("expr : ");
                if (expression instanceof NewExpression) {
                    System.err.print(((NewExpression) expression).getText());
                } else {
                    System.err.print("<unknown class>");
                }
                System.err.println("");
            }
        } else if (null != ctx.null_expr()) {
            //   | null_expr
            expression = parsingData_.popExpression();

            if (printProductionsDebug) {
                System.err.print("abelian_atom : null_expr");
            }
        } else if (null != ctx.JAVA_ID() && null == ctx.ABELIAN_INCREMENT_OP() && (null == ctx.abelian_expr())) {
            //   | JAVA_ID

            expression = idExpr(ctx.JAVA_ID(), ctx.getStart());

            if (printProductionsDebug) {
                System.err.print("abelian_atom : JAVA_ID (");
                System.err.print(ctx.JAVA_ID().getText());
                System.err.println(")");
            }
        } else if (null != ctx.abelian_atom() && null != ctx.ABELIAN_INCREMENT_OP()
                && (null == ctx.abelian_expr())) {
            //   | abelian_atom ABELIAN_INCREMENT_OP
            //   | ABELIAN_INCREMENT_OP abelian_atom
            final Interval AbelianAtomInterval = ctx.abelian_atom().getSourceInterval();
            final Interval OperatorInterval = ctx.ABELIAN_INCREMENT_OP().getSourceInterval();
            final PreOrPost preOrPost = AbelianAtomInterval.startsAfter(OperatorInterval)
                    ? UnaryopExpressionWithSideEffect.PreOrPost.Pre
                    : UnaryopExpressionWithSideEffect.PreOrPost.Post;

            if (parsingData_.currentExpressionExists()) {
                expression = parsingData_.popExpression();
                assert null != expression;
                expression = new UnaryopExpressionWithSideEffect(expression, ctx.ABELIAN_INCREMENT_OP().getText(),
                        preOrPost);
                assert null != expression;
            } else {
                expression = new ErrorExpression(null);
            }

            if (printProductionsDebug) {
                switch (preOrPost) {
                case Post:
                    System.err.print("abelian_atom : abelian_atom (");
                    System.err.print(ctx.abelian_atom().getText());
                    System.err.println(") ABELIAN_INCREMENT_OP");
                    break;
                case Pre:
                    System.err.print("abelian_atom : ABELIAN_INCREMENT_OP abelian_atom (");
                    System.err.print(ctx.abelian_atom().getText());
                    System.err.println(")");
                    break;
                }
            }

            checkForIncrementOpViolatingIdentifierConstness((UnaryopExpressionWithSideEffect) expression,
                    ctx.getStart());
        } else if (null != ctx.ABELIAN_INCREMENT_OP() && null != ctx.abelian_atom() && null != ctx.JAVA_ID()) {
            //   | ABELIAN_INCREMENT_OP abelian_atom '.' JAVA_ID
            final ExpressionStackAPI rawExpression = this.exprFromExprDotJAVA_ID(ctx.JAVA_ID(), ctx.getStart(),
                    ctx.ABELIAN_INCREMENT_OP());
            assert rawExpression instanceof Expression;
            expression = (Expression) rawExpression;
            if (printProductionsDebug) {
                System.err.print("abelian_atom : '++' expr '.' JAVA_ID (");
                System.err.print(ctx.JAVA_ID().getText());
                System.err.println(")");
            }
        } else if (null != ctx.constant()) {
            //   | constant

            // expression = Expression.makeConstantExpressionFrom(ctx.constant().getText());
            // is on the stack. We now have a "constant : ..." production,
            // so we don't make it here
            expression = parsingData_.popExpression();
            if (printProductionsDebug) {
                System.err.println("abelian_atom : constant");
            }
        } else if (null != ctx.abelian_expr() && null == ctx.JAVA_ID() && null == ctx.CLONE()
                && null == ctx.message() && (null == ctx.expr() || ctx.expr().size() == 0)
                && null == ctx.ABELIAN_INCREMENT_OP()) {
            //   | '(' abelian_expr ')'
            expression = parsingData_.popExpression();

            if (printProductionsDebug) {
                System.err.println("abelian_atom : '(' abelian_expr ')'");
            }
        } else if (null != ctx.abelian_atom() && null == ctx.JAVA_ID() && null == ctx.CLONE()
                && null == ctx.message() && null != ctx.expr() && (ctx.expr().size() == 1)
                && null == ctx.ABELIAN_INCREMENT_OP()) {
            //   | abelian_atom '[' expr ']'
            final Expression indexExpr = parsingData_.currentExpressionExists() ? parsingData_.popExpression()
                    : new ErrorExpression(null);
            indexExpr.setResultIsConsumed(true);
            final Expression rawArrayBase = parsingData_.currentExpressionExists() ? parsingData_.popExpression()
                    : new ErrorExpression(indexExpr);

            // The fidelity of this varies according to how much
            // type information we have at hand
            expression = processIndexExpression(rawArrayBase, indexExpr, lineNumber);

            if (printProductionsDebug) {
                System.err.println("abelian_atom : abelian_expr '[' expr ']'");
            }
        } else if (null != ctx.abelian_atom() && null == ctx.JAVA_ID() && null == ctx.CLONE()
                && null == ctx.message() && null != ctx.expr() && (ctx.expr().size() > 0)
                && null != ctx.ABELIAN_INCREMENT_OP()) {
            //   | abelian_atom '[' expr ']' ABELIAN_INCREMENT_OP
            //   | ABELIAN_INCREMENT_OP abelian_atom '[' expr ']'

            ParseTree arrayBase;
            KantParser.ExprContext theIndex;
            if (ctx.expr().size() == 2) {
                arrayBase = ctx.expr(0);
                theIndex = ctx.expr(1);
            } else {
                arrayBase = ctx.abelian_atom();
                theIndex = ctx.expr(0);
            }
            final ExpressionStackAPI rawExpression = processIndexedArrayElement(arrayBase, theIndex,
                    ctx.ABELIAN_INCREMENT_OP());
            assert rawExpression instanceof Expression;
            expression = (Expression) rawExpression;
            checkForIncrementOpViolatingConstness((ArrayIndexExpressionUnaryOp) expression, ctx.getStart());
            if (printProductionsDebug) {
                if (null != ctx.abelian_expr()) {
                    System.err.println("abelian_atom : abelian_expr '[' expr ']' ABELIAN_INCREMENT_OP");
                } else {
                    System.err.println("abelian_atom : ABELIAN_INCREMENT_OP expr '[' expr ']'");
                }
            }
        } else if (null != ctx.abelian_atom() && null != ctx.JAVA_ID() && null == ctx.CLONE()
                && null == ctx.message() && (null == ctx.expr() || (ctx.expr().size() == 0))
                && null != ctx.ABELIAN_INCREMENT_OP()) {
            //   | abelian_atom '.' JAVA_ID ABELIAN_INCREMENT_OP
            final ExpressionStackAPI rawExpression = this.exprFromExprDotJAVA_ID(ctx.JAVA_ID(), ctx.getStart(),
                    ctx.ABELIAN_INCREMENT_OP());
            assert rawExpression instanceof Expression;
            expression = (Expression) rawExpression;
            if (printProductionsDebug) {
                System.err.print("abelian_atom : abelian_atom '.' JAVA_ID (");
                System.err.print(ctx.JAVA_ID().getText());
                System.err.println(") ++");
            }
        } else if ((null == ctx.abelian_atom()) && (null == ctx.abelian_expr()) && null != ctx.message()) {
            //   | /* this. */ message
            // This routine actually does pop the expressions stack (and the Message stack)

            expression = this.messageSend(ctx.getStart(), null, null);

            if (printProductionsDebug) {
                System.err.println("abelian_atom : /* this. */ message");
            }
        } else if (null != ctx.abelian_atom() && null != ctx.CLONE()) {
            //   | abelian_atom '.' CLONE

            final Expression qualifier = parsingData_.popExpression();
            expression = new DupMessageExpression(qualifier, qualifier.type());

            if (qualifier.isError() || qualifier.type().isError()) {
                expression = new ErrorExpression(expression);
            }

            if (printProductionsDebug) {
                System.err.println("abelian_atom : abelian_expr '.' 'clone'");
            }
        } else {
            assert false;
        }

        if (null != expression) {
            // null check is error stumbling check
            parsingData_.pushExpression(expression);
        }

        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void enterBoolean_atom(KantParser.Boolean_atomContext ctx) {
        // : constant
        // JAVA_ID
        // | null_expr
        // | '(' boolean_expr ')'

        // nothing
    }

    @Override
    public void exitBoolean_atom(KantParser.Boolean_atomContext ctx) {
        // : constant
        // | null_expr
        // | JAVA_ID
        // | '(' boolean_expr ')'

        // All passes.

        Expression expression = null;

        if (null != ctx.null_expr()) {
            // | null_expr
            expression = parsingData_.popExpression();

            if (printProductionsDebug) {
                System.err.print("abelian_atom : null_expr");
            }
        } else if (null != ctx.JAVA_ID() && (null == ctx.boolean_expr())) {
            // | JAVA_ID

            expression = idExpr(ctx.JAVA_ID(), ctx.getStart());
            if (printProductionsDebug) {
                System.err.print("boolean_atom : JAVA_ID (");
                System.err.print(ctx.JAVA_ID().getText());
                System.err.println(")");
            }
        } else if (null != ctx.constant()) {
            //   | constant

            // expression = Expression.makeConstantExpressionFrom(ctx.constant().getText());
            // is on the stack. We now have a "constant : ..." production,
            // so we don't make it here
            expression = parsingData_.popExpression();
            if (printProductionsDebug) {
                System.err.println("boolean_atom : constant");
            }
        } else if (null != ctx.boolean_expr() && null == ctx.JAVA_ID()) {
            //   | '(' boolean_expr ')'
            expression = parsingData_.popExpression();

            if (printProductionsDebug) {
                System.err.println("boolean_atom : '(' boolean_expr ')'");
            }
        } else {
            assert false;
        }

        if (null != expression) {
            // null check is error stumbling check
            parsingData_.pushExpression(expression);
        }

        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    protected Expression processIndexExpression(final Expression rawArrayBase, final Expression indexExpr,
            final int lineNumber) {
        // Pass 1 version. Overridden in Pass 2

        Expression expression = null;

        // On pass one, types may not yet be set up so we may
        // stumble here (particularly if there is a forward reference
        // to a type). So be generous.
        final Type arrayBaseType = rawArrayBase.type();
        if (arrayBaseType instanceof ArrayType) {
            final ArrayType arrayType = (ArrayType) arrayBaseType; // instance of ArrayType
            final Type baseType = arrayType.baseType(); // like int
            final ArrayExpression arrayBase = new ArrayExpression(rawArrayBase, baseType);
            arrayBase.setResultIsConsumed(true);
            expression = new ArrayIndexExpression(arrayBase, indexExpr, lineNumber);
        } else {
            expression = new ErrorExpression(null);
        }
        return expression;
    }

    // I wanted to postpone this stuff until Pass 2, but omitting it
    // from Pass 1 causes the parse stack to get messed up here in
    // this pass. I still refrain from passing the argument info
    // in to the semantic analysis routines on Pass 1

    @Override
    public void enterMessage(KantParser.MessageContext ctx) {
        //    : method_name '(' argument_list ')'
        //  | type_name '(' argument_list ')'

        parsingData_.pushArgumentList(new ActualArgumentList());
    }

    @Override
    public void exitMessage(KantParser.MessageContext ctx) {
        //    : method_name '(' argument_list ')'
        //  | type_name '(' argument_list ')'

        String selectorName = null;
        if (null != ctx.method_name()) {
            selectorName = ctx.method_name().getText();
        } else if (null != ctx.type_name()) {
            final ExpressionStackAPI typeName = parsingData_.popRawExpression();
            selectorName = typeName.name();
        } else {
            assert false;
        }
        // Leave argument list processing to Pass 2...

        final Type enclosingMegaType = Expression.nearestEnclosingMegaTypeOf(currentScope_);
        final Message newMessage = new Message(selectorName, null, ctx.getStart().getLine(), enclosingMegaType);
        parsingData_.pushMessage(newMessage);

        // ... but clean up the stack by popping off the arguments
        // This is definitely Pass 2 stuff.
        @SuppressWarnings("unused")
        ActualArgumentList argumentList = parsingData_.popArgumentList();

        if (printProductionsDebug) {
            System.err.print("message : message_name '(' argument_list ')' (");
            System.err.print(selectorName);
            System.err.println(")");
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void enterBlock(KantParser.BlockContext ctx) {
        //   : '{' expr_and_decl_list '}'
        //   | '{' '}'

        final int lineNumber = ctx.getStart().getLine();

        // Set up the block
        currentScope_ = new StaticScope(currentScope_, true);
        final ExprAndDeclList newList = new ExprAndDeclList(lineNumber);
        parsingData_.pushExprAndDecl(newList);

        final Type enclosingMegaType = Expression.nearestEnclosingMegaTypeOf(currentScope_);
        final BlockExpression blockExpression = new BlockExpression(lineNumber, newList, currentScope_,
                enclosingMegaType);
        currentScope_.setDeclaration(null); /// hmmm....

        parsingData_.pushBlockExpression(blockExpression);
    }

    @Override
    public void exitBlock(KantParser.BlockContext ctx) {
        //   : '{' expr_and_decl_list '}'
        //   | '{' '}'
        // Just for balance. Do something with this later.
        // And we pop it in any case, because we pushed it unconditionally above

        @SuppressWarnings("unused")
        final ExprAndDeclList exprAndDeclList = parsingData_.popExprAndDecl();
        final BlockExpression expression = parsingData_.popBlockExpression();
        expression.parserIsDone();

        parsingData_.pushExpression(expression);

        currentScope_ = currentScope_.parentScope();

        if (printProductionsDebug) {
            if (null != ctx.expr_and_decl_list()) {
                System.err.println("block : '{' expr_and_decl_list '}'");
            } else {
                System.err.println("block : '{' '}'");
            }
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void enterExpr_or_null(KantParser.Expr_or_nullContext ctx) {
    }

    @Override
    public void exitExpr_or_null(KantParser.Expr_or_nullContext ctx) {
        //  : expr
        //   | /* null */
        //   ;
        if (ctx.expr() == null) {
            final Expression expression = new ErrorExpression(null);
            parsingData_.pushExpression(expression);
        } else {
            // just leave alone
        }

        if (printProductionsDebug) {
            if (ctx.expr() != null) {
                System.err.println("expr_or_null : expr");
            } else {
                System.err.println("expr_or_null : /* null */");
            }
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void exitIf_expr(KantParser.If_exprContext ctx) {
        // Version for all passes
        // if_expr
        //       : 'if' '(' expr ')' expr
        //       | 'if' '(' expr ')' expr 'else' expr
        //       ;

        @SuppressWarnings("unused")
        final ExprContext thenPartCtx = ctx.expr(1);

        final ExprContext elsePartCtx = ctx.expr().size() > 2 ? ctx.expr(2) : null;

        Expression elsePart = null;
        if (null != elsePartCtx) {
            elsePart = parsingData_.popExpression();
        } else {
            elsePart = new NullExpression();
        }
        final Expression thenPart = parsingData_.currentExpressionExists() ? parsingData_.popExpression()
                : new ErrorExpression(null);
        final Expression conditional = parsingData_.currentExpressionExists() ? parsingData_.popExpression()
                : new ErrorExpression(null);
        final Type conditionalType = conditional.type();
        if (null != conditionalType && conditionalType.name().equals("boolean") == false
                && conditional.isntError()) {
            errorHook5p2(ErrorIncidenceType.Fatal, ctx.getStart().getLine(), "Conditional expression `",
                    conditional.getText(), "' is not of type boolean", "");
        }

        final Expression ifExpression = new IfExpression(conditional, thenPart, elsePart);
        parsingData_.pushExpression(ifExpression);

        if (printProductionsDebug) {
            if (null != elsePartCtx) {
                System.err.println("if_expr : 'if' '(' relop_expr ')' expr 'else' expr");
            } else {
                System.err.println("if_expr : 'if' '(' relop_expr ')' expr");
            }
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void exitTrivial_object_decl(KantParser.Trivial_object_declContext ctx) {
        // trivial_object_decl : compound_type_name JAVA_ID

        final int lineNumber = ctx.getStart().getLine();

        final String idName = ctx.JAVA_ID().getText();

        final Compound_type_nameContext compound_type_name = ctx.compound_type_name();
        if (null == compound_type_name) {
            // It can happen if there's a bad syntax error.
            // Just punt for now.
            return;
        }
        final List<ParseTree> children = compound_type_name.children;
        final int numberOfChildren = children.size();
        final String typeName = children.get(0).getText();
        if (numberOfChildren == 3) {
            final String firstModifier = children.get(1).getText();
            final String secondModifier = children.get(2).getText();
            if (firstModifier.equals("[") && secondModifier.equals("]")) {
                // Is an array declaration
                errorHook5p2(ErrorIncidenceType.Fatal, lineNumber,
                        "An array declaration is not appropriate for this context", "", "", "");
            }
        }

        this.nameCheck(idName, lineNumber);

        Type type = currentScope_.lookupTypeDeclarationRecursive(typeName);
        if (null == type) {
            errorHook5p2(ErrorIncidenceType.Fatal, ctx.getStart().getLine(), "Type `", typeName,
                    "' seems not to be declared in any enclosing scope", "");
            type = new ErrorType();
        }

        final ObjectDeclaration objectDecl = new ObjectDeclaration(idName, type, ctx.getStart().getLine());
        currentScope_.declareObject(objectDecl, this);
    }

    @Override
    public void enterFor_expr(KantParser.For_exprContext ctx) {
        //  : 'for' '(' expr ';'    expr ';' expr ')' expr
        //  | 'for' '(' object_decl expr ';' expr ')' expr
        //  | 'for' '(' JAVA_ID ':' expr ')' expr
        //  | 'for' '(' trivial_object_decl ':' expr ')' expr

        final StaticScope newScope = new StaticScope(currentScope_, true);

        // This is just a placeholder so that enclosed declarations get
        // registered. We fill in the meat in exitFor_expr
        final ForExpression forExpression = new ForExpression(null, null, null, null, newScope,
                ctx.getStart().getLine(), parsingDataArgumentAccordingToPass());
        newScope.setDeclaration(null); /// hmmm.... TODO: Fix this for while and do/while loops too

        currentScope_ = newScope;
        parsingData_.pushForExpression(forExpression);
    }

    @Override
    public void exitFor_expr(KantParser.For_exprContext ctx) {
        //  : 'for' '(' expr ';'    expr ';' expr ')' expr
        //  | 'for' '(' object_decl expr ';' expr ')' expr
        //  | 'for' '(' JAVA_ID ':' expr ')' expr
        //  | 'for' '(' trivial_object_decl ':' expr ')' expr

        ForExpression expression = null;
        final int lineNumber = ctx.getStart().getLine();

        if ((null == ctx.JAVA_ID()) && (null == ctx.object_decl()) && (null == ctx.trivial_object_decl())
                && (ctx.expr().size() == 4)) {
            //  : 'for' '(' expr ';'    expr ';' expr ')' expr
            final Expression body = parsingData_.popExpression();
            final Expression increment = parsingData_.popExpression();
            final Expression conditional = parsingData_.popExpression();
            final Expression initializer = parsingData_.popExpression();

            expression = parsingData_.popForExpression();

            body.setResultIsConsumed(false);
            increment.setResultIsConsumed(true);
            conditional.setResultIsConsumed(true);

            final Type conditionalType = conditional.type();

            if (conditionalType.name().equals("boolean") == false) {
                errorHook5p2(ErrorIncidenceType.Fatal, lineNumber, "Conditional expression `",
                        conditional.getText(), "' is not of type boolean", "");
                final BooleanConstant falseExpr = new BooleanConstant(true);
                expression.reInit(initializer, falseExpr, increment, body);
            } else {
                assert conditional.type().name().equals("boolean");
                expression.reInit(initializer, conditional, increment, body);
            }
        } else if ((null == ctx.JAVA_ID()) && (null != ctx.object_decl()) && (ctx.expr().size() == 3)) {
            //  | 'for' '(' object_decl expr ';' expr ')' expr

            final Expression body = parsingData_.currentExpressionExists() ? parsingData_.popExpression()
                    : new ErrorExpression(null);
            final Expression increment = parsingData_.currentExpressionExists() ? parsingData_.popExpression()
                    : new ErrorExpression(null);
            final Expression conditional = parsingData_.currentExpressionExists() ? parsingData_.popExpression()
                    : new ErrorExpression(null);
            expression = parsingData_.popForExpression();

            body.setResultIsConsumed(false);
            increment.setResultIsConsumed(true);
            conditional.setResultIsConsumed(true);

            final Type conditionalType = conditional.type();

            if (conditionalType.name().equals("boolean") == false) {
                errorHook5p2(ErrorIncidenceType.Fatal, lineNumber, "Conditional expression `",
                        conditional.getText(), "' is not of type boolean", "");
                final BooleanConstant falseExpr = new BooleanConstant(true);
                expression.reInit(null, falseExpr, increment, body);
            } else {
                assert conditional.type().name().equals("boolean");
                expression.reInit(null, conditional, increment, body);
            }
        } else if ((null != ctx.JAVA_ID()) && (null == ctx.trivial_object_decl()) && (ctx.expr().size() == 2)) {
            //  | 'for' '(' JAVA_ID ':' expr ')' expr
            final Expression body = parsingData_.popExpression();
            final Expression thingToIncrementOver = parsingData_.popExpression();
            final String JAVA_IDasString = ctx.JAVA_ID().getText();
            final ObjectDeclaration JAVA_ID_DECL = currentScope_.lookupObjectDeclarationRecursive(JAVA_IDasString);
            if (null == JAVA_ID_DECL) {
                errorHook5p2(ErrorIncidenceType.Fatal, lineNumber, "Loop identifier `", JAVA_IDasString,
                        "' is not declared", "");
            }
            expression = parsingData_.popForExpression();

            body.setResultIsConsumed(false);
            thingToIncrementOver.setResultIsConsumed(true);

            final Type typeIncrementingOver = thingToIncrementOver.type();
            if (typeIncrementingOver instanceof ArrayType == false
                    && typeIncrementingOver.name().startsWith("List<") == false
                    && typeIncrementingOver.name().startsWith("Set<") == false
                    && typeIncrementingOver.name().startsWith("Map<") == false) {
                if (thingToIncrementOver.isntError()) {
                    errorHook5p2(ErrorIncidenceType.Fatal, lineNumber, "Expression `",
                            thingToIncrementOver.getText(), "' is not iterable", "");
                }
            }

            expression.reInitIterativeFor(JAVA_ID_DECL, thingToIncrementOver, body);
        } else if ((null != ctx.trivial_object_decl()) && (ctx.expr().size() == 2)) {
            //  | 'for' '(' trivial_object_decl ':' expr ')' expr

            Expression thingToIncrementOver = null;
            final Expression body = parsingData_.popExpression();
            if (parsingData_.currentExpressionExists()) {
                thingToIncrementOver = parsingData_.popExpression();
            } else {
                thingToIncrementOver = new ErrorExpression(body);
            }
            expression = parsingData_.popForExpression();

            final List<ParseTree> children = ctx.trivial_object_decl().children;
            final int numberOfChildren = children.size();
            assert 2 == numberOfChildren;
            final String JAVA_IDasString = ctx.trivial_object_decl().JAVA_ID().getText();

            // final String JAVA_IDasString = ctx.JAVA_ID().getText();
            final ObjectDeclaration JAVA_ID_DECL = currentScope_.lookupObjectDeclaration(JAVA_IDasString);
            if (null == JAVA_ID_DECL) {
                errorHook5p2(ErrorIncidenceType.Fatal, lineNumber, "Loop identifier `", JAVA_IDasString,
                        "' is not declared", " (strange error)");
            }

            body.setResultIsConsumed(false);
            thingToIncrementOver.setResultIsConsumed(true);

            final Type typeIncrementingOver = thingToIncrementOver.type();

            boolean isRoleArray = false;
            if (typeIncrementingOver instanceof RoleType) {
                final RoleType roleType = (RoleType) typeIncrementingOver;
                isRoleArray = roleType.isArray();
            }

            if (isRoleArray) {
                ; // an O.K. possibility
            } else if (typeIncrementingOver instanceof ArrayType == false && null != typeIncrementingOver
                    && typeIncrementingOver.name().startsWith("List<") == false
                    && typeIncrementingOver.name().startsWith("Set<") == false
                    && typeIncrementingOver.name().startsWith("Map<") == false) {
                if (thingToIncrementOver.isntError()) {
                    errorHook5p2(ErrorIncidenceType.Fatal, lineNumber, "Expression `",
                            thingToIncrementOver.getText(), "' is not iterable", "");
                }
            }

            expression.reInitIterativeFor(JAVA_ID_DECL, thingToIncrementOver, body);
        } else {
            assert false;
        }

        parsingData_.pushExpression(expression);

        currentScope_ = currentScope_.parentScope();

        if (printProductionsDebug) {
            if ((null == ctx.JAVA_ID()) && (null == ctx.object_decl()) && (null == ctx.trivial_object_decl())
                    && (ctx.expr().size() == 4)) {
                System.err.println("for_expr : 'for' '(' expr ';' expr ';' expr ')' expr");
            } else if ((null == ctx.JAVA_ID()) && (null != ctx.object_decl()) && (ctx.expr().size() == 3)) {
                System.err.println("for_expr : 'for' '(' object_decl expr ';' expr ')' expr");
            } else if ((null != ctx.JAVA_ID()) && (null == ctx.trivial_object_decl()) && (ctx.expr().size() == 2)) {
                System.err.println("for_expr : 'for' '(' JAVA_ID ':' expr ')' expr");
            } else if ((null != ctx.trivial_object_decl()) && (ctx.expr().size() == 2)) {
                System.err.println("for_expr : 'for' '(' trivial_object_decl ':' expr ')' expr");
            } else {
                System.err.println("undefined production");
                assert false;
            }
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void enterWhile_expr(KantParser.While_exprContext ctx) {
        // while_expr
        //   : 'while' '(' expr ')' expr
        //   ;

        // This is just a placeholder so that enclosed declarations get
        // registered. We fill in the meat in exitWhile_expr
        final Type nearestEnclosingMegaType = Expression.nearestEnclosingMegaTypeOf(currentScope_);
        final WhileExpression whileExpression = new WhileExpression(null, null, ctx.getStart().getLine(),
                parsingDataArgumentAccordingToPass(), nearestEnclosingMegaType);

        parsingData_.pushWhileExpression(whileExpression);
    }

    @Override
    public void exitWhile_expr(KantParser.While_exprContext ctx) {
        // while_expr
        //   : 'while' '(' expr ')' expr

        final Expression body = parsingData_.popExpression();
        final Expression conditional = parsingData_.currentExpressionExists() ? parsingData_.popExpression()
                : new ErrorExpression(null);
        final WhileExpression expression = parsingData_.currentWhileExpressionExists()
                ? parsingData_.popWhileExpression()
                : null;

        body.setResultIsConsumed(true);
        conditional.setResultIsConsumed(true);

        if (conditional.type() != StaticScope.globalScope().lookupTypeDeclaration("boolean")) {
            errorHook5p2(ErrorIncidenceType.Fatal, ctx.getStart().getLine(),
                    "Condition in `while' statement is not of type boolean", conditional.getText(), " of type ",
                    conditional.type().name());
        }

        if (expression != null) {
            expression.reInit(conditional, body);
            parsingData_.pushExpression(expression);
        } else {
            parsingData_.pushExpression(new ErrorExpression(null));
        }

        if (printProductionsDebug) {
            if (null != ctx.expr() && null != ctx.expr(1)) {
                System.err.println("while_expr : 'while' '(' expr ')' expr");
            } else {
                System.err.println("while_expr : 'while' '(' ??? ')' expr");
            }
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void enterDo_while_expr(KantParser.Do_while_exprContext ctx) {
        // do_while_expr
        //   : 'do' expr 'while' '(' expr ')'
        //   ;

        // This is just a placeholder so that enclosed declarations get
        // registered. We fill in the meat in exitFor_expr
        final Type nearestEnclosingMegaType = Expression.nearestEnclosingMegaTypeOf(currentScope_);
        final DoWhileExpression doWhileExpression = new DoWhileExpression(null, null, ctx.getStart().getLine(),
                parsingDataArgumentAccordingToPass(), nearestEnclosingMegaType);

        parsingData_.pushDoWhileExpression(doWhileExpression);
    }

    @Override
    public void exitDo_while_expr(KantParser.Do_while_exprContext ctx) {
        // do_while_expr
        //   : 'do' expr 'while' '(' expr ')'

        final Expression conditional = parsingData_.currentExpressionExists() ? parsingData_.popExpression()
                : new ErrorExpression(null);
        final Expression body = parsingData_.currentExpressionExists() ? parsingData_.popExpression()
                : new ErrorExpression(null);
        final Expression expression = parsingData_.currentDoWhileExpressionExists()
                ? parsingData_.popDoWhileExpression()
                : new ErrorExpression(null);

        body.setResultIsConsumed(true);
        conditional.setResultIsConsumed(true);

        if (conditional.type() != StaticScope.globalScope().lookupTypeDeclaration("boolean")) {
            errorHook5p2(ErrorIncidenceType.Fatal, ctx.getStart().getLine(),
                    "Condition in `do / while' statement is not of type boolean", conditional.getText(),
                    " of type ", conditional.type().name());
        }

        if (expression instanceof DoWhileExpression) {
            ((DoWhileExpression) expression).reInit(conditional, body);
        }

        parsingData_.pushExpression(expression);

        if (printProductionsDebug) {
            if (null != ctx.expr() && null != ctx.expr(1)) {
                System.err.println("do_while_expr : 'do' expr 'while' '(' expr ')'");
            } else {
                System.err.println("do_while_expr : 'do' expr 'while' '(' ??? ')'");
            }
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void enterSwitch_expr(KantParser.Switch_exprContext ctx) {
        // : 'switch' '(' expr ')' '{'  ( switch_body )* '}'

        currentScope_ = new StaticScope(currentScope_, true);

        final Type enclosingMegaType = Expression.nearestEnclosingMegaTypeOf(currentScope_);
        final SwitchExpression switchExpression = new SwitchExpression(parsingDataArgumentAccordingToPass(),
                enclosingMegaType, currentScope_);
        parsingData_.pushSwitchExpr(switchExpression);
    }

    @Override
    public void exitSwitch_expr(KantParser.Switch_exprContext ctx) {
        // : 'switch' '(' expr ')' '{' ( switch_body )* '}'
        // One version serves passes 1 - 4

        final SwitchExpression switchExpression = parsingData_.popSwitchExpr();

        // Set all the goodies. The body is already taken care of.
        if (parsingData_.currentExpressionExists()) { // error stumbling check
            final Expression expr = parsingData_.popExpression();
            expr.setResultIsConsumed(true);
            switchExpression.addExpression(expr);
            parsingData_.pushExpression(switchExpression);

            final Expression expressionToSwitchOn = switchExpression.switchExpression();
            if (null != expressionToSwitchOn) {
                Type potentialSwitchExpressionType = null;
                final Type switchExpressionType = expressionToSwitchOn.type();
                boolean stillEvaluating = true;
                for (final SwitchBodyElement aCase : switchExpression.orderedSwitchBodyElements()) {
                    // See if all case body expressions are of the same type
                    if (null == potentialSwitchExpressionType && stillEvaluating) {
                        if (aCase.hasNoBody() == false) {
                            potentialSwitchExpressionType = aCase.type();
                        }
                    }

                    if (null != potentialSwitchExpressionType) {
                        if (stillEvaluating) {
                            final String caseTypePathName = aCase.type().pathName();
                            if (caseTypePathName.equals("void")) {
                                // it could be because there is no
                                // expression to go with the case statement.
                                // If so, don't let it upset things.
                                if (aCase.hasNoBody()) {
                                    stillEvaluating = true;
                                } else {
                                    stillEvaluating = caseTypePathName.equals("void");
                                }
                            } else {
                                stillEvaluating = caseTypePathName.equals(potentialSwitchExpressionType.pathName());
                            }
                        }

                        // Make sure that all case test expressions are of type of switch expression
                        if (aCase.isDefault())
                            continue;
                        if (switchExpressionType.canBeConvertedFrom(aCase.expression().type()) == false
                                && switchExpressionType.isntError()) {
                            errorHook6p2(ErrorIncidenceType.Warning, ctx.getStart().getLine(),
                                    "Case statement with expression of type `", aCase.type().name(),
                                    "' is incompatible with switch expression of type `",
                                    switchExpressionType.name(), "'.", "");
                        }
                    }
                } /* for */

                if (stillEvaluating) { // then we made it through all the cases!
                    // We have a consistent expression type since all case
                    // statements yield the same type
                    switchExpression.setExpressionType(potentialSwitchExpressionType);
                }
            } /* if */
        } /* if */

        currentScope_ = currentScope_.parentScope();

        if (printProductionsDebug) {
            System.err.println("switch_expr : 'switch' '(' expr ')' '{' ( switch_body )* '}'");
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void enterSwitch_body(KantParser.Switch_bodyContext ctx) {
        // : ( 'case' constant | 'default' ) ':' expr_and_decl_list

        final ExprAndDeclList newList = new ExprAndDeclList(ctx.getStart().getLine());
        parsingData_.pushExprAndDecl(newList);
    }

    @Override
    public void exitSwitch_body(KantParser.Switch_bodyContext ctx) {
        // : ( 'case' constant | 'default' ) ':' expr_and_decl_list

        Constant constant = null;
        boolean isDefault = false;
        if (null != ctx.constant()) {
            final Expression temp = parsingData_.popExpression();
            if (temp instanceof Constant == false) {
                ErrorLogger.error(ErrorIncidenceType.Internal, ctx.getStart().getLine(),
                        "Case statement has non-const expression: `", temp.getText(), "'", "");
                constant = new Constant.IntegerConstant(0);
            } else {
                constant = (Constant) temp;
            }
            if (null != parsingData_.currentSwitchExpr().elementForConstant(constant)) {
                errorHook5p2(ErrorIncidenceType.Fatal, ctx.getStart().getLine(),
                        "Switch statement has multiple clauses for ", constant.getText(), ".", "");
            }
        } else {
            isDefault = true;
            if (parsingData_.currentSwitchExpr().hasDefault()) {
                errorHook5p2(ErrorIncidenceType.Fatal, ctx.getStart().getLine(),
                        "Switch statement has multiple default clauses", "", "", "");
            }
        }

        final ExprAndDeclList expr_and_decl_list = parsingData_.popExprAndDecl();

        if (null != constant) {
            constant.setResultIsConsumed(true);
        }

        final Type enclosingMegaType = Expression.nearestEnclosingMegaTypeOf(currentScope_);
        final SwitchBodyElement switchBodyElement = new SwitchBodyElement(constant, isDefault, expr_and_decl_list,
                enclosingMegaType);
        parsingData_.currentSwitchExpr().addSwitchBodyElement(switchBodyElement);

        if (printProductionsDebug) {
            System.err.println("switch_body : ( 'case' constant | 'default' ) ':' expr_and_decl_list");
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void exitNull_expr(KantParser.Null_exprContext ctx) {
        // null_expr : NULL

        final Expression expression = new NullExpression();

        parsingData_.pushExpression(expression);

        if (printProductionsDebug) {
            System.err.println("null_expr : NULL");
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void enterConstant(KantParser.ConstantContext ctx) {
    }

    @Override
    public void exitConstant(KantParser.ConstantContext ctx) {
        // constant
        //   : STRING
        //   | INTEGER
        //   | FLOAT
        //   | BOOLEAN
        final Expression rawConstant = Expression.makeConstantExpressionFrom(ctx.getText());
        assert rawConstant instanceof Constant;
        final Constant constant = (Constant) rawConstant;
        parsingData_.pushExpression(constant);

        if (printProductionsDebug) {
            System.err.print("constant : ");
            System.err.println(ctx.getText());
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    @Override
    public void enterArgument_list(KantParser.Argument_listContext ctx) {
    }

    @Override
    public void exitArgument_list(KantParser.Argument_listContext ctx) {
        // : expr
        // | argument_list ',' expr
        // | /* null */

        // All done in pass 2

        if (ctx.expr() != null) {
            final Expression expr = parsingData_.popExpression();
            expr.setResultIsConsumed(true);
            parsingData_.currentArgumentList().addActualArgument(expr);
        } else {
            // no actual argument - OK
        }

        if (printProductionsDebug) {
            if (ctx.expr() != null && ctx.argument_list() == null) {
                System.err.println("argument_list : expr");
            } else if (ctx.argument_list() != null) {
                System.err.println("argument_list : argument_list ',' expr");
            } else {
                System.err.println("argument_list : /* null */");
            }
        }
        if (stackSnapshotDebug)
            stackSnapshotDebug();
    }

    // --------------------------------------------------------------------------------------

    @Override
    protected void lookupOrCreateStagePropDeclaration(final String roleName, final int lineNumber,
            final boolean isStagePropArray) {
        final RoleDeclaration requestedStageProp = currentScope_.lookupRoleOrStagePropDeclaration(roleName);
        if (null != requestedStageProp) {
            currentRoleOrStageProp_ = requestedStageProp;

            // The way parsing is designed, these things should
            // be defined once on pass 0 and then referenced only
            // on subsequent passes.
        }
        // caller may reset currentScope - NOT us
    }

    protected Type lookupOrCreateTemplateInstantiation(final String templateName,
            final List<String> parameterTypeNames, final int lineNumber) {
        // This varies by pass. On the last pass we first remove the instantiation, so that the
        // new one picks up the body created in Pass 3.
        return lookupOrCreateTemplateInstantiationCommon(templateName, parameterTypeNames, lineNumber);
    }

    protected Type lookupOrCreateTemplateInstantiationCommon(final String templateName,
            final List<String> parameterTypeNames, final int lineNumber) {
        Type retval = null;
        final TemplateDeclaration templateDeclaration = currentScope_
                .lookupTemplateDeclarationRecursive(templateName);
        if (null == templateDeclaration) {
            errorHook5p2(ErrorIncidenceType.Fatal, lineNumber, "Template ", templateName, " is not defined. ", "");
        } else {
            final StringBuffer stringBuffer = new StringBuffer();
            stringBuffer.append(templateName);
            stringBuffer.append("<");
            int i = 0;
            for (final String parameterTypeName : parameterTypeNames) {
                stringBuffer.append(parameterTypeName);
                i++;
                if (i < parameterTypeNames.size()) {
                    stringBuffer.append(",");
                }
            }
            stringBuffer.append(">");
            final String typeName = stringBuffer.toString();

            final StaticScope templateScope = templateDeclaration.enclosingScope();
            final StaticScope templateEnclosedScope = templateDeclaration.enclosedScope();
            final TypeDeclaration baseClass = templateDeclaration.baseClass();
            final String baseClassName = null == baseClass ? "void" : baseClass.name();
            final ClassDeclaration baseClassDecl = null == baseClass ? null
                    : templateScope.lookupClassDeclarationRecursive(baseClassName);

            ClassDeclaration classDeclaration = currentScope_.lookupClassDeclarationRecursive(typeName);
            if (null == classDeclaration) {
                // Create a new type vector from the type parameters
                final TemplateInstantiationInfo newTypes = new TemplateInstantiationInfo(templateDeclaration,
                        typeName);
                for (final String aTypeName : parameterTypeNames) {
                    final Type correspondingType = currentScope_.lookupTypeDeclarationRecursive(aTypeName);
                    if (null == correspondingType) {
                        errorHook5p2(ErrorIncidenceType.Fatal, lineNumber, "Cannot find type named ", aTypeName,
                                " in instantiation of ", templateDeclaration.name());
                        newTypes.add(new ErrorType());
                    } else {
                        newTypes.add(correspondingType);
                    }
                }

                // templateEnclosedScope isn't really used, because a new enclosedScope_ object
                // is created by ClassDeclaration.elaborateFromTemplate(templateDeclaration)
                classDeclaration = new ClassDeclaration(typeName, templateEnclosedScope, baseClassDecl, lineNumber);
                classDeclaration.elaborateFromTemplate(templateDeclaration, newTypes);
                final Type rawNewType = classDeclaration.type();
                assert rawNewType instanceof ClassType;
                final ClassType newType = (ClassType) rawNewType;
                newTypes.setClassType(newType);

                templateScope.declareType(newType);
                templateScope.declareClass(classDeclaration);

                retval = newType;

                // Here's where we queue template instantiatons for code generation
                parsingData_.currentTemplateInstantiationList().addDeclaration(classDeclaration);
            } else {
                retval = currentScope_.lookupTypeDeclarationRecursive(typeName);
                assert null != retval;
            }
        }
        return retval;
    }

    protected void updateInitializationLists(final Expression initializationExpr, final ObjectDeclaration objDecl) {
        /* Nothing on Pass 1 */
    }

    private boolean ifIsInABlockContext(final RuleContext myParentArg) {
        RuleContext myParent = myParentArg;
        boolean retval = false;
        while (myParent instanceof ProgramContext == false) {
            if (myParent instanceof BlockContext) {
                retval = true;
                break;
            } else if (myParent instanceof Expr_and_decl_listContext) {
                myParent = myParent.getParent();
            } else {
                break;
            }
        }
        return retval;
    }

    public static long nestingLevelInsideBreakable(final ParseTree ctxArg) {
        ParseTree ctx = ctxArg;

        // This counts the number of dynamic scopes between a given
        // context and any enclosing "breakable" context (While, DoWhile,
        // For, and Switch). It is used to tell "break" statements how
        // many activation records to close when they are run.
        //
        // In fact we need to count only Block scopes, it seems. The
        // only language constructs that push a scope are message
        // sends, blocks, and For loops. Since we stop at For loops
        // (because they are a breakable) we don't need to count them
        // (which we otherwise would because they push a scope).
        //
        // We do count the actual breakable level if it has
        // a scope.
        //
        // It's declared static only because it doesn't refer to "this".
        long retval = 0;

        for (; (null != ctx) && ((ctx instanceof ProgramContext) == false); ctx = ctx.getParent()) {
            if (ctx instanceof BlockContext) {
                retval++;
            } else if (ctx instanceof For_exprContext) {
                retval++;
                break;
            } else if (ctx instanceof While_exprContext) {
                break;
            } else if (ctx instanceof Do_while_exprContext) {
                break;
            } else if (ctx instanceof Switch_exprContext) {
                break;
            } else if (ctx instanceof Method_declContext) {
                // It doesn't matter how many open blocks we have
                // passed along the way - if we run into something
                // like a method scope, then there's no more chance
                // for finding a breakable scope in which this break
                // has meaning
                retval = -1;
                break;
            }
        }

        return retval;
    }

    public static long nestingLevelInsideBreakableForContinue(final ParseTree ctxArg) {
        ParseTree ctx = ctxArg;

        // Like nestingLevelInsideBreakable, but for Continue rather than break.
        // The main difference is that we don't count the context of the for
        // loop itself, since it stays open across a continue execution
        long retval = 0;

        for (; (null != ctx) && ((ctx instanceof ProgramContext) == false); ctx = ctx.getParent()) {
            if (ctx instanceof BlockContext) {
                retval++;
            } else if (ctx instanceof For_exprContext) {
                break;
            } else if (ctx instanceof While_exprContext) {
                break;
            } else if (ctx instanceof Do_while_exprContext) {
                break;
            } else if (ctx instanceof Switch_exprContext) {
                break;
            } else if (ctx instanceof Method_declContext) {
                // It doesn't matter how many open blocks we have
                // passed along the way - if we run into something
                // like a method scope, then there's no more chance
                // for finding a breakable scope in which this continue
                // statement has meaning
                retval = -1;
                break;
            }
        }
        return retval;
    }

    protected SimpleList variablesToInitialize_, initializationExpressions_;

    private DeclarationsAndInitializers processIdentifierList(final Identifier_listContext identifier_list,
            final Type type, final int lineNumber, final AccessQualifier accessQualifier) {
        variablesToInitialize_ = new SimpleList();
        initializationExpressions_ = new SimpleList();
        final List<ObjectDeclaration> objectDecls = new ArrayList<ObjectDeclaration>();

        this.processIdentifierListRecursive(identifier_list, type, lineNumber, accessQualifier);

        final List<BodyPart> intializationExpressionsToReturn = new ArrayList<BodyPart>();

        final long initializationCount = variablesToInitialize_.count();
        for (int j = 0; j < initializationCount; j++) {
            final Expression initializationExpression = (Expression) initializationExpressions_.objectAtIndex(j);
            initializationExpression.setResultIsConsumed(true);
            final ObjectDeclaration objDecl = (ObjectDeclaration) variablesToInitialize_.objectAtIndex(j);
            Type expressionType = initializationExpression.type();
            final Type declarationType = objDecl.type();

            if (null != declarationType && null != expressionType
                    && declarationType.canBeConvertedFrom(expressionType)) {

                // Still need this, though old initialization framework is gone
                objectDecls.add(objDecl);

                final IdentifierExpression lhs = new IdentifierExpression(objDecl.name(), declarationType,
                        currentScope_, lineNumber);
                final AssignmentExpression initialization = new AssignmentExpression(lhs, "=",
                        initializationExpression, identifier_list.getStart().getLine(), this);
                intializationExpressionsToReturn.add(initialization);

                // New initialization association
                objDecl.setInitialization(initialization);
            } else if (expressionType.isntError() && declarationType.isntError()
                    && initializationExpression.isntError()) {
                errorHook5p2(ErrorIncidenceType.Fatal, objDecl.lineNumber(), "Type mismatch in initialization of `",
                        objDecl.name(), "'.", "");
            }
        }

        final DeclarationsAndInitializers retval = new DeclarationsAndInitializers(objectDecls,
                intializationExpressionsToReturn);
        return retval;
    }

    private void processIdentifierListRecursive(final Identifier_listContext identifier_list, final Type type,
            final int lineNumber, final AccessQualifier accessQualifier) {
        final List<ParseTree> children = identifier_list.children;
        Token tok;
        ObjectDeclaration objDecl = null;
        for (ParseTree pt : children) {
            if (pt instanceof TerminalNodeImpl) {
                final TerminalNodeImpl tnpt = (TerminalNodeImpl) pt;
                if (tnpt.getChildCount() == 0) {
                    tok = tnpt.getSymbol();
                    final String tokAsText = tok.getText();
                    if (tokAsText.equals("=") == true) {
                        ; // we pick it up under the ExprContext check below
                    } else if (tokAsText.equals(",") == true) {
                        ; // skip it; it separates elements
                    } else {
                        this.nameCheck(tokAsText, lineNumber);
                        objDecl = this.pass1InitialDeclarationCheck(tokAsText, lineNumber);
                        if (null == objDecl) {
                            objDecl = new ObjectDeclaration(tokAsText, type, lineNumber);
                            declareObjectSuitableToPass(currentScope_, objDecl);
                            objDecl.setAccess(accessQualifier, currentScope_, lineNumber);
                        } else {
                            // Doesn't hurt to update type
                            objDecl.updateType(type);
                        }
                    }
                } else {
                    for (int i = 0; i < tnpt.getChildCount(); i++) {
                        final ParseTree pt2 = tnpt.getChild(i);
                        assert pt2 instanceof TerminalNodeImpl;
                        final TerminalNodeImpl tnpt2 = (TerminalNodeImpl) pt2;
                        tok = tnpt2.getSymbol();
                        final String tokAsText = tok.getText();
                        this.nameCheck(tokAsText, lineNumber);
                        if (tokAsText.equals("=") == true) {
                            ; // we get it with the ExprContext catch below
                        } else if (tokAsText.equals(",") == true) {
                            ; // skip it; it separates elements
                        } else {
                            objDecl = this.pass1InitialDeclarationCheck(tokAsText, lineNumber);
                            if (null == objDecl) {
                                objDecl = new ObjectDeclaration(tokAsText, type, lineNumber);
                                declareObjectSuitableToPass(currentScope_, objDecl);
                                objDecl.setAccess(accessQualifier, currentScope_, lineNumber);
                            } else {
                                // Doesn't hurt to update type
                                objDecl.updateType(type);
                            }
                        }
                    }
                }
            } else if (pt instanceof Identifier_listContext) {
                this.processIdentifierListRecursive((Identifier_listContext) pt, type, lineNumber, accessQualifier);
                // System.err.print("Alert: ");
                // System.err.println(pt.getText());
            } else if (pt instanceof ExprContext) {
                if (parsingData_.currentExpressionExists()) {
                    final Expression initializationExpr = parsingData_.popExpression();
                    assert initializationExpr != null;
                    assert objDecl != null;
                    updateInitializationLists(initializationExpr, objDecl);
                }
            } else {
                assert false;
            }
        }
    }

    public ObjectDeclaration pass1InitialDeclarationCheck(final String name, final int lineNumber) {
        final ObjectDeclaration objDecl = currentScope_.lookupObjectDeclaration(name);
        if (null != objDecl) {
            final String addedMessage = "(earlier declaration at line " + Integer.toString(objDecl.lineNumber())
                    + ").";
            errorHook5p1(ErrorIncidenceType.Fatal, lineNumber, "Identifier `", name, "' declared multiple times ",
                    addedMessage);
        }
        return objDecl;
    }

    @Override
    public void enterParam_list(KantParser.Param_listContext ctx) {
        if (ctx.param_decl() != null) {
            // : param_decl
            // | param_list ',' param_decl
            // | param_list ELLIPSIS
            // | param_list ',' ELLIPSIS
            // | /* null */

            // Do most of this parameter stuff here on the second pass, because
            // we should have most of the type information by then
            if (null == ctx.ELLIPSIS()) {
                String formalParameterName = ctx.param_decl().JAVA_ID().getText();
                final String paramTypeName = ctx.param_decl().compound_type_name().getText();
                String paramTypeBaseName = null;

                // Handle vector arguments
                final List<ParseTree> children = ctx.param_decl().compound_type_name().children;
                final int numberOfChildren = children.size();
                paramTypeBaseName = children.get(0).getText();
                boolean isArray = false;
                if (numberOfChildren == 3) {
                    final String firstModifier = children.get(1).getText();
                    final String secondModifier = children.get(2).getText();
                    if (firstModifier.equals("[") && secondModifier.equals("]")) {
                        // Is an array declaration
                        isArray = true;
                    }
                }

                Type paramType = currentScope_.lookupTypeDeclarationRecursive(paramTypeBaseName);
                if (null == paramType) {
                    errorHook5p2(ErrorIncidenceType.Fatal, ctx.getStart().getLine(), "Parameter type `",
                            paramTypeName, "' not declared for `", formalParameterName + "'.");
                    paramType = new ErrorType();
                } else if (formalParameterName.equals("this")) {
                    errorHook5p2(ErrorIncidenceType.Fatal, ctx.getStart().getLine(),
                            "You cannot name a formal parameter `this'.", "", "", "");
                    paramType = new ErrorType();
                } else if (isArray) {
                    // A derived type
                    final String aName = paramType.getText() + "_$array";
                    paramType = new ArrayType(aName, paramType);
                }

                Declaration newFormalParameter = new ObjectDeclaration(formalParameterName, paramType,
                        ctx.getStart().getLine());
                if (paramType.isError()) {
                    newFormalParameter = new ErrorDeclaration("");
                }

                // They go in reverse order...
                if (newFormalParameter.name().equals("this")) {
                    formalParameterName = "_error" + String.valueOf(parsingData_.variableGeneratorCounter_);
                    parsingData_.variableGeneratorCounter_++;
                    newFormalParameter = new ObjectDeclaration(formalParameterName, paramType,
                            ctx.getStart().getLine());
                }
                if (parsingData_.currentFormalParameterList().containsVarargs()) {
                    errorHook5p2(ErrorIncidenceType.Fatal, ctx.getStart().getLine(), "Formal parameter `",
                            newFormalParameter.name(),
                            "' comes after `...', which must be the last element in a parameter list.", "");
                }
                parsingData_.currentFormalParameterList().addFormalParameter(newFormalParameter);
            } else {
                // ...
                final MethodSignature currentMethod = parsingData_.currentMethodSignature();
                final Type paramType = new VarargsType(currentMethod.name());
                final String formalParameterName = "...";
                final ObjectDeclaration newFormalParameter = new ObjectDeclaration(formalParameterName, paramType,
                        ctx.getStart().getLine());
                parsingData_.currentFormalParameterList().addFormalParameter(newFormalParameter);
            }
        } else {
            // empty parameter list - it's OK. For now.
        }
    }

    private Expression processIndexedArrayElement(final ParseTree arrayExprCtx,
            final KantParser.ExprContext sexpCtx, final TerminalNode ABELIAN_INCREMENT_OPCtx) {
        // | abelian_expr '[' expr ']' ABELIAN_INCREMENT_OP
        // | ABELIAN_INCREMENT_OP expr '[' expr ']'

        Expression retval = null;
        final Expression indexExpr = parsingData_.popExpression();
        indexExpr.setResultIsConsumed(true);

        final Expression rawArrayBase = parsingData_.popExpression();
        final Type rawArrayBaseType = rawArrayBase.type();
        assert rawArrayBaseType instanceof ArrayType;
        final ArrayType arrayType = (ArrayType) rawArrayBaseType;
        final Type baseType = arrayType.baseType();
        final ArrayExpression arrayBase = new ArrayExpression(rawArrayBase, baseType);
        arrayBase.setResultIsConsumed(true);

        final Interval JavaIDInterval = arrayExprCtx.getSourceInterval();
        final Interval OperatorInterval = ABELIAN_INCREMENT_OPCtx.getSourceInterval();
        final UnaryopExpressionWithSideEffect.PreOrPost preOrPost = JavaIDInterval.startsAfter(OperatorInterval)
                ? UnaryopExpressionWithSideEffect.PreOrPost.Pre
                : UnaryopExpressionWithSideEffect.PreOrPost.Post;
        retval = new ArrayIndexExpressionUnaryOp(arrayBase, indexExpr, ABELIAN_INCREMENT_OPCtx.getText(), preOrPost,
                sexpCtx.getStart().getLine());
        return retval;
    }

    protected void checkExprDeclarationLevel(RuleContext ctxParent, Token ctxGetStart) {
        /* Nothing */
    }

    // ----------------------------------------------------------------------------------------

    // WARNING. Tricky code here
    protected void declareObject(final StaticScope s, final ObjectDeclaration objdecl) {
        s.declareObject(objdecl, this);
    }

    protected void declareFormalParametersSuitableToPass(final StaticScope scope, final ObjectDeclaration objDecl) {
        scope.declareObject(objDecl, this);
    }

    private ExpressionStackAPI exprFromExprDotJAVA_ID(final TerminalNode ctxJAVA_ID, final Token ctxGetStart,
            final TerminalNode ctxABELIAN_INCREMENT_OP) {
        // Certified Pass 1 version ;-) There is no longer any Pass 2+ version
        UnaryopExpressionWithSideEffect.PreOrPost preOrPost;
        Type type = null;
        final ExpressionStackAPI qualifier = parsingData_.popRawExpression();
        Expression expression = null;
        final String javaIdString = ctxJAVA_ID.getText();
        preOrPost = UnaryopExpressionWithSideEffect.PreOrPost.Post;

        if (null != ctxABELIAN_INCREMENT_OP) {
            final Interval JavaIDInterval = ctxJAVA_ID.getSourceInterval();
            final Interval OperatorInterval = ctxABELIAN_INCREMENT_OP.getSourceInterval();
            preOrPost = JavaIDInterval.startsAfter(OperatorInterval) ? UnaryopExpressionWithSideEffect.PreOrPost.Pre
                    : UnaryopExpressionWithSideEffect.PreOrPost.Post;
        }

        assert null != qualifier;
        if (null != qualifier.type() && qualifier.type().name().equals("Class")) {
            // This is where we handle types like "System" for System.out.print*
            // Now we need to get the actual class of that name
            final Type rawClass = currentScope_.lookupTypeDeclarationRecursive(qualifier.name());
            assert rawClass instanceof ClassType;
            final ClassType theClass = (ClassType) rawClass;

            final ObjectDeclaration odecl = theClass.type().enclosedScope().lookupObjectDeclaration(javaIdString);
            if (null != odecl) {
                // It must be static
                final ObjectDeclaration odecl2 = theClass.type().enclosedScope()
                        .lookupStaticDeclaration(javaIdString);
                if (null == odecl2) {
                    errorHook5p2(ErrorIncidenceType.Fatal, ctxGetStart.getLine(),
                            "Attempt to access instance member `", javaIdString,
                            "' as a member of class `" + theClass.name(), "'.");
                    type = new ErrorType();
                } else {
                    type = odecl.type();
                }
                assert type != null;

                if (null != ctxABELIAN_INCREMENT_OP) {
                    expression = new QualifiedClassMemberExpressionUnaryOp(theClass, javaIdString, type,
                            ctxABELIAN_INCREMENT_OP.getText(), preOrPost);
                } else {
                    expression = new QualifiedClassMemberExpression(theClass, javaIdString, type);
                }
            } else {
                errorHook5p2(ErrorIncidenceType.Fatal, ctxGetStart.getLine(), "Member `", javaIdString,
                        "' of `" + qualifier.name(), "' is not defined.");
                expression = new ErrorExpression(null);
            }
        } else {
            final Expression object = (Expression) qualifier;
            object.setResultIsConsumed(true);
            final Type objectType = object.type();
            Declaration odecl = null;
            if (objectType.isntError()) {
                odecl = objectType.enclosedScope().lookupObjectDeclarationRecursive(javaIdString);
            }

            if (null != odecl) {
                type = odecl.type();
                assert type != null;

                if (null != ctxABELIAN_INCREMENT_OP) {
                    expression = new QualifiedIdentifierExpressionUnaryOp(object, javaIdString, type,
                            ctxABELIAN_INCREMENT_OP.getText(), preOrPost);
                } else {
                    expression = new QualifiedIdentifierExpression(object, javaIdString, type);
                }

                if (odecl instanceof ObjectDeclaration) {
                    final ObjectDeclaration odeclAsOdecl = (ObjectDeclaration) odecl;
                    final boolean isAccessible = currentScope_.canAccessDeclarationWithAccessibility(odeclAsOdecl,
                            odeclAsOdecl.accessQualifier_, ctxGetStart.getLine());
                    if (isAccessible == false) {
                        errorHook6p2(ErrorIncidenceType.Fatal, ctxGetStart.getLine(), "Cannot access expression `",
                                expression.getText(), "' with `", odeclAsOdecl.accessQualifier_.asString(),
                                "' access qualifier.", "");
                    }
                } else if (odecl.isntError()) {
                    errorHook5p2(ErrorIncidenceType.Fatal, ctxGetStart.getLine(), "Cannot access expression `",
                            expression.getText(), "' with non-object type declaration", "");
                }
            } else if (null == (expression = degenerateProcedureCheck(qualifier, objectType, javaIdString,
                    ctxGetStart.getLine()))) {
                if (object.isntError() && object.type().isntError()) {
                    errorHook5p2(ErrorIncidenceType.Fatal, ctxGetStart.getLine(), "Field `", javaIdString,
                            "' not found as member of `", object.type().name() + "'.");
                }
                type = new ErrorType();
                odecl = new ErrorDeclaration("");
                expression = new ErrorExpression(null);
            }
        }

        return null == expression ? new ErrorExpression(null) : expression;
    }

    private Expression degenerateProcedureCheck(final ExpressionStackAPI object, final Type objectType,
            final String methodSelectorName, final int lineNumber) {
        // Eiffel-style feature invocation
        Expression retval = null;

        if (object instanceof Expression && null != objectType) {
            final ActualArgumentList argumentList = new ActualArgumentList();

            if (objectType instanceof RoleType) {
                IdentifierExpression currentContext = new IdentifierExpression("current$context",
                        currentContext_.type(), currentScope_, lineNumber);
                if (((RoleType) objectType).isAParameterlessRequiresMethod(methodSelectorName) == false) {
                    argumentList.addFirstActualParameter(currentContext);
                }
                argumentList.addActualArgument((Expression) object);
            } else {
                argumentList.addFirstActualParameter((Expression) object);
            }
            MethodDeclaration methodDecl = objectType.enclosedScope()
                    .lookupMethodDeclarationRecursive(methodSelectorName, argumentList, false);
            if (null == methodDecl) {
                if (objectType instanceof RoleType) {
                    // Check "requires" methods.

                    final RoleDeclaration roleDeclaration = ((RoleType) objectType).associatedDeclaration();
                    final MethodSignature roleMethodSignature = roleDeclaration
                            .lookupRequiredMethodSignatureDeclaration(methodSelectorName);
                    if (null != roleMethodSignature) {
                        if (0 == roleMethodSignature.formalParameterList().userParameterCount()) {
                            // o.k.
                            methodDecl = new MethodDeclaration(roleMethodSignature, currentScope_, lineNumber);
                            methodDecl.setReturnType(roleMethodSignature.returnType());
                        }
                    }
                }
            }

            if (null != methodDecl) {
                final Message message = new Message(methodSelectorName, argumentList, lineNumber,
                        Expression.nearestEnclosingMegaTypeOf(currentScope_));
                MethodInvocationEnvironmentClass originMethodClass = MethodInvocationEnvironmentClass.Unknown;
                if (null != currentScope_.associatedDeclaration()) {
                    originMethodClass = currentScope_.methodInvocationEnvironmentClass();
                } else {
                    final Type anotherType = ((Expression) object).enclosingMegaType();
                    if (null != anotherType) {
                        final StaticScope anotherScope = anotherType.enclosedScope();
                        originMethodClass = anotherScope.methodInvocationEnvironmentClass();
                    } else {
                        originMethodClass = MethodInvocationEnvironmentClass.ClassEnvironment; // outermost scope
                    }
                }

                MethodInvocationEnvironmentClass targetMethodClass = null;
                if (objectType instanceof RoleType) {
                    // Requires methods get ClassEnvironment designation
                    final RoleType roleType = (RoleType) object.type();
                    final RoleDeclaration roleDecl = (RoleDeclaration) roleType.associatedDeclaration();
                    final MethodSignature requiredSignatureDecl = roleDecl
                            .lookupRequiredMethodSignatureDeclaration(message.selectorName());
                    final MethodSignature publishedDecl = null == requiredSignatureDecl ? null
                            : roleDecl.lookupPublishedSignatureDeclaration(requiredSignatureDecl);
                    if (null != requiredSignatureDecl) {
                        if (null == publishedDecl) {
                            final StaticScope currentMethodScope = Expression
                                    .nearestEnclosingMethodScopeAround(currentScope_);
                            errorHook6p2(ErrorIncidenceType.Fatal, lineNumber, "Context script `",
                                    currentMethodScope.associatedDeclaration().name(),
                                    "' may enact only Role scripts. Script `",
                                    message.selectorName() + message.argumentList().selflessGetText(),
                                    "' is an instance script from a class and is inaccessible to Context `",
                                    roleDecl.enclosingScope().name() + "'.");
                        } else {
                            targetMethodClass = MethodInvocationEnvironmentClass.ClassEnvironment;
                        }
                    } else {
                        targetMethodClass = MethodInvocationEnvironmentClass.RoleEnvironment;
                    }
                } else if (objectType instanceof ContextType)
                    targetMethodClass = MethodInvocationEnvironmentClass.ContextEnvironment;
                else
                    targetMethodClass = MethodInvocationEnvironmentClass.ClassEnvironment;

                final boolean isStatic = (null != methodDecl) && (null != methodDecl.signature())
                        && methodDecl.signature().isStatic();

                boolean isPolymorphic = true;
                if (amInConstructor()) {
                    if (object instanceof IdentifierExpression) {
                        // Don't dynamically dispatch methods from within a constructor
                        if (((IdentifierExpression) object).name().equals("this")) {
                            isPolymorphic = false;
                        }
                    }
                }

                retval = new MessageExpression((Expression) object, message, methodDecl.returnType(), lineNumber,
                        isStatic, originMethodClass, targetMethodClass, isPolymorphic);
            }
        }
        return retval;
    }

    public <ExprType> Expression newExpr(final List<ParseTree> ctxChildren, final Token ctxGetStart,
            final ExprType ctxExpr, final MessageContext ctxMessage) {
        // : NEW message
        // | NEW type_name '[' expr ']'

        // Called in all passes.
        Expression expression = null; // guaranteed non-null return
        final Message message = parsingData_.popMessage();
        final Type enclosingMegaType = Expression.nearestEnclosingMegaTypeOf(currentScope_);
        if (null == ctxExpr && null != ctxMessage) {
            // : 'new' message
            final String classOrContextName = message.selectorName(); // I know -- kludge ...
            final Type type = currentScope_.lookupTypeDeclarationRecursive(classOrContextName);
            if ((type instanceof ClassType) == false && (type instanceof ContextType) == false
                    && (type instanceof BuiltInType) == false) {
                if (type instanceof TemplateParameterType) {
                    // then it's Ok
                    expression = new NewExpression(type, message, ctxMessage.getStart().getLine(),
                            enclosingMegaType);
                    addSelfAccordingToPass(type, message, currentScope_);
                } else {
                    errorHook5p2(ErrorIncidenceType.Fatal, ctxGetStart.getLine(), "`new ", classOrContextName,
                            "': can apply `new' only to a class or Context type", "");
                    expression = new ErrorExpression(null);
                }
            } else {
                // On the first pass, message doesn't yet have an argument list
                expression = new NewExpression(type, message, ctxMessage.getStart().getLine(), enclosingMegaType);

                // This adds a hokey argument to the message that
                // is used mainly for signature checking - to see
                // if there is a constructor that matches the
                // arguments of the "new" message.
                addSelfAccordingToPass(type, message, currentScope_);
            }

            // Is there a constructor?
            // This does anything on Passes 2 and 3 only
            ctorCheck(type, message, ctxGetStart.getLine());
        } else if (null != ctxExpr && null == ctxMessage) {
            // | 'new' type_name '[' abelian_expr ']'
            final Expression expr = parsingData_.popExpression();
            final ExpressionStackAPI raw_type_expression = parsingData_.popRawExpression();
            assert raw_type_expression instanceof Type;
            final Type type_name_expression = (Type) raw_type_expression;
            final String typeName = type_name_expression.name();
            final Type type = currentScope_.lookupTypeDeclarationRecursive(typeName);
            if (null == type) {
                if (expr.isntError()) {
                    errorHook5p2(ErrorIncidenceType.Fatal, ctxGetStart.getLine(), "'new ", typeName,
                            " [] for undefined type: ", typeName);
                }
                expression = new ErrorExpression(expr);
            } else {
                expr.setResultIsConsumed(true);
                expression = new NewArrayExpression(type, expr, enclosingMegaType);
            }
        } else {
            assert false; // internal error of some kind
        }

        assert null != expression;
        return expression;
    }

    public void addSelfAccordingToPass(final Type type, final Message message, final StaticScope scope) {
        /* Nothing */
    }

    public void ctorCheck(final Type type, final Message message, final int lineNumber) {
        /* Nothing */
    }

    protected boolean amInConstructor() {
        boolean retval = false;
        final Declaration declaration = currentScope_.associatedDeclaration();
        if (declaration instanceof MethodDeclaration) {
            final MethodDeclaration currentMethodDeclaration = (MethodDeclaration) declaration;
            final Type methodsMegaType = Expression.nearestEnclosingMegaTypeOf(currentScope_);
            final String methodName = currentMethodDeclaration.name();
            final String megaTypeName = methodsMegaType.name();
            retval = methodName.equals(megaTypeName);
        }
        return retval;
    }

    // This is a template function mainly for historial reasons data back to
    // expr and sexpr, and should be updated. We separated those non-terminals
    // as a workaround for left-recursion in some of the productions. In some sense
    // we blew up the grammar to avoid the ambiguity; here, we bring the leaves
    // (the processing for the duplicate productions) back together.

    // You find something analogous with exitExpr here in pass 1.

    public <ExprType> Expression messageSend(final Token ctxGetStart, final ExprType ctx_abelianAtom,
            final Builtin_type_nameContext ctx_typeName) {
        // | expr '.' message
        // | message
        // Pass 1 version
        // Pop the expression for the indicated object and message
        Expression object = null;
        Type type = null, enclosingMegaType = null;
        MethodDeclaration mdecl = null;

        if (null != ctx_abelianAtom) {
            if (parsingData_.currentExpressionExists()) {
                object = parsingData_.popExpression();
            } else {
                object = new ErrorExpression(null);
            }
        } else if (null != ctx_typeName) {
            // e.g. String.join
            final String typeName = ctx_typeName.getText();
            final Type theType = currentScope_.lookupTypeDeclarationRecursive(typeName);
            final Type classType = StaticScope.globalScope().lookupTypeDeclaration("Class");
            object = new IdentifierExpression(theType.name(), classType, classType.enclosedScope().parentScope(),
                    ctxGetStart.getLine());
        } else {
            final StaticScope nearestMethodScope = Expression.nearestEnclosingMethodScopeAround(currentScope_);
            enclosingMegaType = Expression.nearestEnclosingMegaTypeOf(currentScope_);
            if (null == enclosingMegaType) {
                object = new ErrorExpression(null);
            } else {
                object = new IdentifierExpression("this", enclosingMegaType, nearestMethodScope,
                        ctxGetStart.getLine());
            }
        }
        assert null != object;

        final Message message = parsingData_.popMessage();

        if (null == enclosingMegaType && (object instanceof NullExpression || object.isError())) {
            // Because this here is Pass 1 code this really does nothing.
            // We'll catch it again on Pass 2
            errorHook5p2(ErrorIncidenceType.Fatal, ctxGetStart.getLine(), "Invoking method `",
                    message.selectorName(), "' on implied object `this' in a non-object context.", "");
        } else {
            final Type objectType = object.type();
            if (null == objectType)
                return new ErrorExpression(object); // error stumbling avoidance

            final String methodSelectorName = message.selectorName();
            final ClassDeclaration classdecl = currentScope_.lookupClassDeclarationRecursive(objectType.name());
            mdecl = classdecl != null
                    ? classdecl.enclosedScope().lookupMethodDeclaration(methodSelectorName, null, true)
                    : null;

            if (null != mdecl) {
                if (objectType.name().equals("Class")) {
                    // Is of the form ClassType.classMethod()
                    assert object instanceof IdentifierExpression;
                    if (false == mdecl.signature().isStatic()) {
                        errorHook5p2(ErrorIncidenceType.Fatal, ctxGetStart.getLine(),
                                "Attempt to call instance method `" + mdecl.signature().getText(),
                                "' as though it were a static method of class `", objectType.name(), "'.");
                    }
                }
            }

            if (null == mdecl) {
                // final String className = classdecl != null? classdecl.name(): " <unresolved>.";
                // skip it - we'll barked at the user in pass 2
                // errorHook5p2(ErrorIncidenceType.Fatal, ctxGetStart.getLine(), "Script `", methodSelectorName, "' not declared in class ", className);
                type = StaticScope.globalScope().lookupTypeDeclaration("void");
            } else {
                type = mdecl.returnType();

                // This is tautological a no-op, because we
                // castrate this stuff within Pass 1
                // checkForMessageSendViolatingConstness(mdecl.signature(), ctxGetStart);
            }

            // If there is an error in the method return type, type might still be null
            if (null == type) {
                type = StaticScope.globalScope().lookupTypeDeclaration("void");
            }

            if (null != type && type instanceof TemplateParameterType) {
                // Is a template type. Change the return type into a bona fide type here
                final StaticScope objectScope = objectType.enclosedScope();
                final TemplateInstantiationInfo templateInstantiationInfo = objectScope.templateInstantiationInfo();
                type = templateInstantiationInfo.classSubstitionForTemplateTypeNamed(type.name());
            }

            assert type != null;
        }

        object.setResultIsConsumed(true);

        MethodInvocationEnvironmentClass originMethodClass = MethodInvocationEnvironmentClass.Unknown;
        if (null != currentScope_.associatedDeclaration()) {
            originMethodClass = currentScope_.methodInvocationEnvironmentClass();
        } else {
            final Type anotherType = object.enclosingMegaType();
            if (null != anotherType) {
                final StaticScope anotherScope = anotherType.enclosedScope();
                originMethodClass = anotherScope.methodInvocationEnvironmentClass();
            } else {
                originMethodClass = MethodInvocationEnvironmentClass.ClassEnvironment; // outermost scope
            }
        }
        final MethodInvocationEnvironmentClass targetMethodClass = null == message.enclosingMegaType()
                ? MethodInvocationEnvironmentClass.ClassEnvironment
                : message.enclosingMegaType().enclosedScope().methodInvocationEnvironmentClass();
        final boolean isStatic = (null != mdecl) && (null != mdecl.signature()) && mdecl.signature().isStatic();

        boolean isPolymorphic = true;
        if (amInConstructor()) {
            if (object instanceof IdentifierExpression) {
                // Don't dynamically dispatch methods from within a constructor
                if (((IdentifierExpression) object).name().equals("this")) {
                    isPolymorphic = false;
                }
            }
        }

        if (null != type) {
            // Should probably never be true - here...
            final boolean isAConstructor = type.name().equals(message.selectorName());
            if (false == isAConstructor) {
                // Update the message type properly. Constructors are weird in
                // that the message type is void whereas the expression type is
                // in terms of the thing being constructed.
                message.setReturnType(type);
            }
        }

        final MessageExpression retval = new MessageExpression(object, message, type, ctxGetStart.getLine(),
                isStatic, originMethodClass, targetMethodClass, isPolymorphic);

        return retval;
    }

    protected MethodDeclaration processReturnTypeLookupMethodDeclarationIn(final TypeDeclaration classDecl,
            final String methodSelectorName, final ActualOrFormalParameterList parameterList) {
        // Pass 1 version. Pass 2 / 3 version ignores "this" in signature,
        // and checks the signature
        final StaticScope classScope = classDecl.enclosedScope();
        return classScope.lookupMethodDeclaration(methodSelectorName, parameterList, true);
    }

    protected MethodDeclaration processReturnTypeLookupMethodDeclarationIgnoringRoleStuffIn(
            final TypeDeclaration classDecl, final String methodSelectorName,
            final ActualOrFormalParameterList parameterList) {
        // Pass 1 version. Pass 2 / 3 version is the same for now,
        // but checks the signature
        final StaticScope classScope = classDecl.enclosedScope();
        return classScope.lookupMethodDeclarationIgnoringRoleStuff(methodSelectorName, parameterList);
    }

    protected MethodDeclaration processReturnTypeLookupMethodDeclarationUpInheritanceHierarchy(
            final TypeDeclaration classDecl, final String methodSelectorName,
            final ActualOrFormalParameterList parameterList) {
        // Pass 1 version. Pass 2 / 3 version ignores "this" in signature,
        // and checks the signature
        StaticScope classScope = classDecl.enclosedScope();
        MethodDeclaration retval = classScope.lookupMethodDeclaration(methodSelectorName, parameterList, true);
        if (null == retval) {
            if (classDecl instanceof ClassDeclaration) { // should be
                ClassDeclaration classDeclAsClassDecl = (ClassDeclaration) classDecl;
                final ClassDeclaration baseClassDeclaration = classDeclAsClassDecl.baseClassDeclaration();
                if (null != baseClassDeclaration) {
                    classScope = baseClassDeclaration.enclosedScope();
                    retval = classScope.lookupMethodDeclaration(methodSelectorName, parameterList, true);
                } else {
                    retval = null;
                }
            }
        }
        return retval;
    }

    protected Type processReturnType(final Token ctxGetStart, final Expression object, final Type objectType,
            final Message message) {
        final String objectTypeName = objectType.name();
        final ClassDeclaration classDecl = currentScope_.lookupClassDeclarationRecursive(objectTypeName);
        final RoleDeclaration roleDecl = currentScope_.lookupRoleOrStagePropDeclarationRecursive(objectTypeName);
        final ContextDeclaration contextDecl = currentScope_.lookupContextDeclarationRecursive(objectTypeName);
        final InterfaceDeclaration interfaceDecl = currentScope_
                .lookupInterfaceDeclarationRecursive(objectTypeName);

        final String methodSelectorName = message.selectorName();
        assert null != methodSelectorName;

        MethodDeclaration mdecl = null;
        Type returnType = null;
        final ActualArgumentList actualArgumentList = message.argumentList();
        boolean roleHint = false;

        if (null != classDecl) {
            mdecl = processReturnTypeLookupMethodDeclarationUpInheritanceHierarchy(classDecl, methodSelectorName,
                    actualArgumentList);
            if (null == mdecl) {
                final Type currentEnclosingType = Expression.nearestEnclosingMegaTypeOf(currentScope_);
                if (currentEnclosingType instanceof TemplateType) {
                    // Ingore parameters as in Pass 1. We may not find a match with a template type...
                    mdecl = classDecl.enclosedScope().lookupMethodDeclarationRecursive(methodSelectorName,
                            actualArgumentList, true);
                    if (null == mdecl && actualArgumentList.isntError()) {
                        errorHook6p2(ErrorIncidenceType.Fatal, ctxGetStart.getLine(), "Script `",
                                methodSelectorName + actualArgumentList.selflessGetText(),
                                "' not declared in class `", classDecl.name(), "'.", "");
                    }
                } else if (actualArgumentList.isntError()) {
                    // Look at Object. Could be an assert or something
                    final ClassDeclaration objectDecl = currentScope_.lookupClassDeclarationRecursive("Object");
                    assert null != objectDecl;
                    mdecl = processReturnTypeLookupMethodDeclarationIgnoringRoleStuffIn(objectDecl,
                            methodSelectorName, actualArgumentList);

                    if (null == mdecl) {
                        errorHook6p2(ErrorIncidenceType.Fatal, ctxGetStart.getLine(), "Script `",
                                methodSelectorName + actualArgumentList.selflessGetText(),
                                "' not declared in class `", classDecl.name(), "'.", "");
                    }
                }
            }

            final ClassDeclaration baseClassDeclaration = classDecl.baseClassDeclaration();
            if (null != baseClassDeclaration) {
                final String baseClassName = baseClassDeclaration.name();
                boolean noerrors = true;
                if (methodSelectorName.equals(baseClassName) && null != mdecl && mdecl.enclosingScope().pathName()
                        .equals(baseClassDeclaration.enclosedScope().pathName())) {
                    // We are invoking a base class constructor. Cool.
                    // Make sure it's the first thing in the class.
                    if (parsingData_.currentExprAndDeclExists()) {
                        final ExprAndDeclList currentExprAndDecl = parsingData_.currentExprAndDecl();
                        if (currentExprAndDecl.bodyParts().isEmpty() == false) {
                            errorHook5p2(ErrorIncidenceType.Fatal, ctxGetStart.getLine(),
                                    "Call of base class constructor `", baseClassName,
                                    "' must be the first statement in the derived class constructor.", "");
                            noerrors = false;
                        }
                        if (mdecl.accessQualifier() != AccessQualifier.PublicAccess) {
                            errorHook5p2(ErrorIncidenceType.Fatal, ctxGetStart.getLine(),
                                    "Call of base class constructor for class `", baseClassName,
                                    "', which is not accessible to class `", classDecl.name() + "'.");
                            noerrors = false;
                        }
                    }

                    // Make sure the current method is a constructor!
                    final MethodSignature currentMethod = parsingData_.currentMethodSignature();
                    final ClassDeclaration currentClass = parsingData_.currentClassDeclaration();
                    if (currentClass.name().equals(currentMethod.name()) == false) {
                        errorHook5p2(ErrorIncidenceType.Fatal, ctxGetStart.getLine(), "Base class constructor `",
                                baseClassName, "' can be explicitly invoked only from a derived class constructor.",
                                "");
                        noerrors = false;
                    }

                    if (noerrors) {
                        // There is code that automatically generates a
                        // base class constructor call (to a default constructor)
                        // if it can find one. If the programmer has taken over,
                        // cancel the automatic one.

                        // Get the calling method
                        final StaticScope callingScope = classDecl.enclosedScope();
                        FormalParameterList paramsToCurrentMethod = currentMethod.formalParameterList();
                        final String callingConstructorName = classDecl.name();
                        final MethodDeclaration callingMethod = callingScope
                                .lookupMethodDeclaration(callingConstructorName, paramsToCurrentMethod, false);
                        assert null != callingMethod;
                        callingMethod.hasManualBaseClassConstructorInvocations(true);
                    }
                }
            }
        } else if (null != roleDecl) {
            // Calling a role method
            mdecl = processReturnTypeLookupMethodDeclarationIn(roleDecl, methodSelectorName, actualArgumentList);

            if (null == mdecl) {
                // First, check in requires list
                final Map<String, List<MethodSignature>> requiresSection = roleDecl.requiredSelfSignatures();
                final List<MethodSignature> possibleRequiredFunctions = requiresSection.get(methodSelectorName);
                if (null != possibleRequiredFunctions) {
                    for (final MethodSignature aRequiredFunction : possibleRequiredFunctions) {
                        // We don't insist on parameter type matching in Pass 1. Pass 2 will catch that.
                        // But we'll try.
                        if (aRequiredFunction.formalParameterList().alignsWithUsingConversion(actualArgumentList)) {
                            mdecl = new MethodDeclaration(aRequiredFunction, roleDecl.enclosedScope(),
                                    aRequiredFunction.lineNumber());
                            mdecl.addParameterList(aRequiredFunction.formalParameterList());
                            mdecl.setReturnType(mdecl.returnType());
                            break;
                        }
                    }

                    // Even if the signatures don't match, it may be because we have incomplete
                    // type information. For now, give it a pass if the selector name is O.K.
                    if (possibleRequiredFunctions.size() > 0) {
                        final MethodSignature aRequiredFunction = possibleRequiredFunctions.get(0);
                        mdecl = new MethodDeclaration(aRequiredFunction, roleDecl.enclosedScope(),
                                aRequiredFunction.lineNumber());
                        mdecl.addParameterList(aRequiredFunction.formalParameterList());
                        mdecl.setReturnType(mdecl.returnType());
                    }
                }

                if (null == mdecl) {
                    // If this is a Role variable, it's fair game to look for
                    // methods in what will be a base class for every Role-player:
                    // class object
                    final ClassDeclaration objectDecl = currentScope_.lookupClassDeclarationRecursive("Object");
                    assert null != objectDecl;

                    mdecl = processReturnTypeLookupMethodDeclarationIn(objectDecl, methodSelectorName,
                            actualArgumentList);
                    if (null == mdecl) {
                        mdecl = processReturnTypeLookupMethodDeclarationIgnoringRoleStuffIn(objectDecl,
                                methodSelectorName, actualArgumentList);
                        roleHint = true;
                    }
                }
            }
            if (null == mdecl) {
                if (actualArgumentList.isntError()) {
                    errorHook5p2(ErrorIncidenceType.Fatal, ctxGetStart.getLine(), "Script `",
                            methodSelectorName + actualArgumentList.selflessGetText(), "' not declared in Role `",
                            roleDecl.name() + "'.");
                }
                if (message.lineNumber() < roleDecl.lineNumber()) {
                    final MethodSignature enclosingMethod = parsingData_.currentMethodSignature();
                    if (null != enclosingMethod) {
                        errorHook5p2(ErrorIncidenceType.Fatal, ctxGetStart.getLine(),
                                "\tTry moving the declaration of `", roleDecl.name(),
                                "' before the definition of method `", enclosingMethod.getText() + "'.");
                    } else {
                        errorHook5p2(ErrorIncidenceType.Fatal, ctxGetStart.getLine(),
                                "\tTry moving the declaration of `", roleDecl.name(),
                                "' before the invocation of `", methodSelectorName + "'.");
                    }
                }
            }
        } else if (null != contextDecl) {
            mdecl = processReturnTypeLookupMethodDeclarationUpInheritanceHierarchy(contextDecl, methodSelectorName,
                    actualArgumentList);
            if (null == mdecl) {
                errorHook5p2(ErrorIncidenceType.Fatal, ctxGetStart.getLine(), "Script `",
                        methodSelectorName + actualArgumentList.selflessGetText(), "' not declared in Context `",
                        contextDecl.name() + "'.");
            }
        } else if (null != interfaceDecl) {
            final MethodSignature methodSignature = interfaceDecl
                    .lookupMethodSignatureDeclaration(methodSelectorName, actualArgumentList);
            if (null == methodSignature) {
                if (actualArgumentList.isntError()) {
                    errorHook5p2(ErrorIncidenceType.Fatal, ctxGetStart.getLine(), "Script `",
                            methodSelectorName + actualArgumentList.selflessGetText(),
                            "' not declared in Interface `", interfaceDecl.name() + "'.");
                }
                returnType = new ErrorType();
            } else {
                returnType = methodSignature.returnType();
            }
        } else if (objectTypeName.equals("Class")) {
            final ClassDeclaration classDeclaration = currentScope_.lookupClassDeclarationRecursive(object.name());
            if (null == classDeclaration) {
                if (object.isntError()) {
                    errorHook5p2(ErrorIncidenceType.Fatal, ctxGetStart.getLine(),
                            "Cannot find class, Role, or interface `", object.name(), "'", "");
                }
            } else {
                mdecl = classDeclaration.enclosedScope().lookupMethodDeclaration(methodSelectorName,
                        actualArgumentList, false);
                if (null == mdecl) {
                    mdecl = classDeclaration.enclosedScope().lookupMethodDeclarationWithConversionIgnoringParameter(
                            methodSelectorName, actualArgumentList, false, /*parameterToIgnore*/ null);
                    if (null == mdecl) {
                        errorHook5p2(ErrorIncidenceType.Fatal, ctxGetStart.getLine(),
                                "Cannot find static script `" + methodSelectorName + actualArgumentList.getText(),
                                "' of class `", object.name(), "'.");
                    }
                }
            }
        } else if (objectTypeName.endsWith("_$array")) {
            if (methodSelectorName.equals("size") && actualArgumentList.count() == 1) {
                returnType = StaticScope.globalScope().lookupTypeDeclaration("int"); // is O.K.
            } else if (methodSelectorName.equals("at") && actualArgumentList.count() == 2) {
                returnType = ((ArrayType) objectType).baseType(); // is O.K.
            } else if (methodSelectorName.equals("atPut") && actualArgumentList.count() == 3) {
                returnType = StaticScope.globalScope().lookupTypeDeclaration("void"); // is O.K.
            } else {
                if (object.name().length() > 0) {
                    if (object.isntError()) {
                        errorHook5p2(ErrorIncidenceType.Fatal, ctxGetStart.getLine(),
                                "Cannot find class, Role, or interface for `", object.name(), "'.", "");
                    }
                } else {
                    errorHook5p2(ErrorIncidenceType.Fatal, ctxGetStart.getLine(),
                            "Cannot find class, Role, or interface of this ", "type", "", "");
                }
                assert null == mdecl;
            }
        } else {
            if (object.name().length() > 0) {
                if (object.isntError() && objectType.isntError()) {
                    errorHook5p2(ErrorIncidenceType.Fatal, ctxGetStart.getLine(),
                            "Cannot find class, Role, or interface for `", object.name(), "'.", "");
                }
            } else {
                errorHook5p2(ErrorIncidenceType.Fatal, ctxGetStart.getLine(),
                        "Cannot find class, Role, or interface of this ", "type", "", "");
            }
            assert null == mdecl;
        }

        if (null != mdecl) {
            final FormalParameterList formals = mdecl.formalParameterList();
            assert formals != null;
            final ActualArgumentList actuals = message.argumentList();
            assert actuals != null;

            final TypeDeclaration typeDecl = null != classDecl ? classDecl : contextDecl;

            // Type check is polymorphic in compiler passes
            this.typeCheckIgnoringParameter(formals, actuals, mdecl, typeDecl, "this", ctxGetStart, roleHint);

            returnType = mdecl.returnType();
        }

        returnType = null == returnType ? StaticScope.globalScope().lookupTypeDeclaration("void") : returnType;

        return returnType;
    }

    protected MethodDeclaration getEnclosingMethodDecl() {
        StaticScope scope = currentScope_;
        MethodDeclaration retval = null;
        while (scope != StaticScope.globalScope()
                && false == (currentScope_.associatedDeclaration() instanceof MethodDeclaration)) {
            scope = scope.parentScope();
        }
        if (null != scope && scope != StaticScope.globalScope()) {
            final Declaration rawRetval = scope.associatedDeclaration();
            assert null == rawRetval || rawRetval instanceof MethodDeclaration;
            retval = (MethodDeclaration) rawRetval;
        }
        return retval;
    }

    protected MethodDeclaration methodWithinWhichIAmDeclared(StaticScope scope) {
        // Like getEnclosingMethodDecl, but returns null if there
        // is an intervening class, role
        MethodDeclaration retval = null;
        while (scope != null && scope != StaticScope.globalScope()
                && false == (currentScope_.associatedDeclaration() instanceof MethodDeclaration)) {
            final Declaration associatedDeclaration = scope.associatedDeclaration();
            if (associatedDeclaration instanceof ContextDeclaration) {
                scope = null;
            } else if (associatedDeclaration instanceof StagePropDeclaration) {
                scope = null;
            } else if (associatedDeclaration instanceof RoleDeclaration) {
                scope = null;
            } else if (associatedDeclaration instanceof ClassDeclaration) {
                scope = null;
            } else {
                scope = scope.parentScope();
            }
        }
        if (null != scope && scope != StaticScope.globalScope()) {
            final Declaration rawDeclaration = scope.associatedDeclaration();
            assert null == rawDeclaration || rawDeclaration instanceof MethodDeclaration;
            retval = (MethodDeclaration) rawDeclaration;
        }
        return retval;
    }

    protected RoleDeclaration isRoleAssignmentWithinContext(String idName) {
        RoleDeclaration retval = null;
        final MethodDeclaration enclosingMethod = getEnclosingMethodDecl();
        final Declaration associatedDeclaration = null != enclosingMethod
                ? enclosingMethod.enclosingScope().associatedDeclaration()
                : null;
        if (null != enclosingMethod && associatedDeclaration instanceof ContextDeclaration) {
            final ContextDeclaration contextDeclaration = (ContextDeclaration) associatedDeclaration;
            final RoleDeclaration roleDeclaration = contextDeclaration.enclosedScope()
                    .lookupRoleOrStagePropDeclaration(idName);
            retval = roleDeclaration;
        }
        return retval;
    }

    private class ClassAndObjectDeclaration {
        public ClassAndObjectDeclaration(final ClassType classType, final ObjectDeclaration objectDecl) {
            classType_ = classType;
            objectDecl_ = objectDecl;
        }

        public ClassType classType() {
            return classType_;
        }

        public ObjectDeclaration objectDecl() {
            return objectDecl_;
        }

        private ClassType classType_;
        private ObjectDeclaration objectDecl_;
    }

    private ClassAndObjectDeclaration isMemberOfEnclosingObject(final String idName) {
        ClassAndObjectDeclaration retval = null;
        final Type nearestEnclosingMegaType = Expression.nearestEnclosingMegaTypeOf(currentScope_);
        if (null != nearestEnclosingMegaType) {
            if (nearestEnclosingMegaType instanceof ClassType) {
                ClassType nearestEnclosingClass = (ClassType) nearestEnclosingMegaType;
                do {
                    final StaticScope classScope = nearestEnclosingClass.enclosedScope();
                    final ObjectDeclaration odecl = classScope.lookupObjectDeclaration(idName);
                    if (null != retval) {
                        retval = new ClassAndObjectDeclaration(nearestEnclosingClass, odecl);
                        break;
                    } else {
                        // Try the base class
                        nearestEnclosingClass = nearestEnclosingClass.baseClass();
                    }
                } while (null != nearestEnclosingClass);
            }
        }
        return retval;
    }

    public Expression idExpr(final TerminalNode ctxJAVA_ID, final Token ctxGetStart) {
        // Pass 1 version
        final StaticScope globalScope = StaticScope.globalScope();
        Expression expression = null;
        Type type = null;
        StaticScope declaringScope = null;
        final String idName = ctxJAVA_ID.getText();
        ObjectDeclaration objdecl = currentScope_.lookupObjectDeclarationRecursive(idName);
        final RoleDeclaration roleDecl = currentScope_.lookupRoleOrStagePropDeclarationRecursive(idName);
        final StaticScope nearestEnclosingMethodScope = Expression.nearestEnclosingMethodScopeAround(currentScope_);
        ClassAndObjectDeclaration classAndObjectDeclaration = null;
        if (idName.equals("index") || idName.equals("lastIndex")) {
            // This is a legal identifier if invoked from within the
            // scope of a Role, where the Role is declared as a Role
            // vector type
            type = StaticScope.globalScope().lookupTypeDeclaration("void"); // default/error value
            expression = new NullExpression();
            if (null == currentRoleOrStageProp_) {
                errorHook5p2(ErrorIncidenceType.Fatal, ctxGetStart.getLine(), "Symbol `", idName,
                        "' may be used only within certain Role methods.", "");
                type = new ErrorType();
            } else {
                if (currentRoleOrStageProp_.isArray()) {
                    expression = idName.equals("index")
                            ? new IndexExpression(currentRoleOrStageProp_, currentContext_)
                            : new LastIndexExpression(currentRoleOrStageProp_, currentContext_);
                } else {
                    errorHook6p2(ErrorIncidenceType.Fatal, ctxGetStart.getLine(), "Symbol `", idName,
                            "' may be used only within a Role vector method. The Role ",
                            currentRoleOrStageProp_.name(), " is a not a vector.", "");
                    type = new ErrorType();
                }
            }
        } else if (null != objdecl) {
            if (null != this.isRoleAssignmentWithinContext(idName)) {
                type = StaticScope.globalScope().lookupTypeDeclaration("void");
            } else {
                type = objdecl.type();
            }
            declaringScope = objdecl.enclosingScope();

            if (null == type) {
                type = StaticScope.globalScope().lookupTypeDeclaration("void");
            }

            // NOTE: This will also lump in references to Role identifiers
            // They are distinguished by its enclosing scope
            expression = new IdentifierExpression(idName, type, declaringScope, ctxGetStart.getLine());
        } else if (null != roleDecl) {
            // Someone is invoking a role. Cool.
            declaringScope = roleDecl.enclosingScope();
            final Type rawRoleType = declaringScope.lookupTypeDeclaration(idName); // Type$RoleType
            if (null != rawRoleType) { // stumbling check
                assert rawRoleType instanceof RoleType;
                final RoleType roleType = (RoleType) rawRoleType;
                if (this.isInsideMethodDeclaration(ctxJAVA_ID)) {
                    final IdentifierExpression qualifier = new IdentifierExpression("this", roleType,
                            nearestEnclosingMethodScope, ctxGetStart.getLine());
                    qualifier.setResultIsConsumed(true);
                    expression = new QualifiedIdentifierExpression(qualifier, idName, roleType);
                } else {
                    errorHook5p2(ErrorIncidenceType.Unimplemented, ctxGetStart.getLine(),
                            "Static initializers for Roles are unimplemented.", "", "", "");
                    expression = new ErrorExpression(null);
                }
            } else {
                expression = new ErrorExpression(null);
            }
        } else if (null != (classAndObjectDeclaration = isMemberOfEnclosingObject(idName))) {
            objdecl = classAndObjectDeclaration.objectDecl();
            final Type classType = classAndObjectDeclaration.classType();
            final IdentifierExpression qualifier = new IdentifierExpression("this", classType,
                    nearestEnclosingMethodScope, ctxGetStart.getLine());
            qualifier.setResultIsConsumed(true);
            expression = new QualifiedIdentifierExpression(qualifier, idName, objdecl.type());
        } else {
            final ClassDeclaration cdecl = currentScope_.lookupClassDeclarationRecursive(idName);
            if (null != cdecl) {
                type = globalScope.lookupTypeDeclaration("Class");
                declaringScope = globalScope;
            } else if (null == declaringScope) {
                // NOTE: This will also lump in references to Role identifiers
                // They are distinguished by their enclosing scope
                declaringScope = Expression.nearestEnclosingMethodScopeAround(currentScope_);
            }

            if (null == type) {
                type = StaticScope.globalScope().lookupTypeDeclaration("void");
            }

            expression = new IdentifierExpression(idName, type, declaringScope, ctxGetStart.getLine());
        }

        assert null != expression;
        return expression;
    }

    private boolean isInsideMethodDeclaration(final TerminalNode ctx) {
        ParseTree walker = ctx;
        boolean retval = false;
        while (!(walker instanceof Type_declarationContext)) {
            if (walker instanceof TerminalNodeImpl) {
                ;
            } else if (walker instanceof ExprContext) {
                ;
                // } else if (walker instanceof Expr_listContext) {
                //    ;
            } else if (walker instanceof Expr_and_decl_listContext) {
                ;
            } else if (walker instanceof Type_and_expr_and_decl_listContext) {
                ;
            } else if (walker instanceof Method_declContext) {
                retval = true;
                break;
            } else if (walker instanceof ProgramContext) {
                retval = false;
                break;
            } else if (walker instanceof Abelian_exprContext) {
                ;
            } else if (walker instanceof Abelian_atomContext) {
                ;
            } else if (walker instanceof Abelian_unary_opContext) {
                ;
            } else if (walker instanceof Abelian_productContext) {
                ;
            } else if (walker instanceof Identifier_listContext) {
                ;
            } else if (walker instanceof Object_declContext) {
                ;
            } else if (walker instanceof Argument_listContext) {
                ;
            } else if (walker instanceof MessageContext) {
                ;
            } else if (walker instanceof BlockContext) {
                ;
            } else if (walker instanceof For_exprContext) {
                ;
            } else if (walker instanceof While_exprContext) {
                ;
            } else if (walker instanceof Boolean_exprContext) {
                ;
            } else if (walker instanceof If_exprContext) {
                ;
            } else if (walker instanceof Boolean_unary_opContext) {
                ;
            } else if (walker instanceof Boolean_productContext) {
                ;
            } else if (walker instanceof Switch_bodyContext) {
                ;
            } else if (walker instanceof Switch_exprContext) {
                ;
            } else if (walker instanceof Do_while_exprContext) {
                ;
            } else if (walker instanceof Boolean_atomContext) { // pong.k
                ;
            } else {
                assert false;
                retval = false;
                break;
            }
            walker = walker.getParent();
        }

        return retval;
    }

    public void binopTypeCheck(final Expression leftExpr, final String operationAsString,
            final Expression rightExpr, final Token ctxGetStart) {
        /* Nothing */
    }

    protected void typeCheckIgnoringParameter(final FormalParameterList formals, final ActualArgumentList actuals,
            final MethodDeclaration mdecl, final TypeDeclaration classdecl, final String parameterToIgnore,
            final Token ctxGetStart, final boolean roleHint) {
        /* Nothing */
    }

    protected void reportMismatchesWith(final int lineNumber, final RoleType lhsType, final Type rhsType) {
        /* Nothing */
    }

    public <ContextArgType extends ParserRuleContext> Expression assignmentExpr(final Expression lhs,
            final String operator, final Expression rhs, final ContextArgType ctx) {
        // abelian_expr ASSIGN expr
        assert null != rhs;
        assert null != lhs;

        final Token ctxGetStart = ctx.getStart();
        final int lineNumber = ctxGetStart.getLine();

        final Type lhsType = lhs.type(), rhsType = rhs.type();

        boolean tf = lhsType instanceof RoleType;
        tf = null != rhsType;
        if (lhsType instanceof RoleType && rhsType instanceof ArrayType) {
            if (((RoleType) lhsType).isArray()) {
                tf = lhsType.canBeConvertedFrom(((ArrayType) rhsType).baseType(), lineNumber, this);
            } else {
                // Maybe the Role just wants to be played by an array, building on
                // its at and atPut interface
                tf = lhsType.canBeConvertedFrom(rhsType, lineNumber, this);
                if (tf == false) {
                    if (lhs.isntError() && rhs.isntError() && rhsType.isntError()) {
                        errorHook6p2(ErrorIncidenceType.Fatal, lineNumber, "Type of `", lhs.getText(),
                                "' is incompatible with expression type `", rhsType.name(), "'.", "");
                    }
                }
            }
        } else if (null != lhsType && null != rhsType) {
            tf = lhsType.canBeConvertedFrom(rhsType, lineNumber, this);
        } else {
            tf = false;
        }

        if (lhs.name().equals("this")) {
            errorHook5p2(ErrorIncidenceType.Noncompliant, lineNumber, "You're on your own here.", "", "", "");
        }

        if (lhs.name().equals("index") || lhs.name().equals("lastIndex")) {
            errorHook5p2(ErrorIncidenceType.Fatal, lineNumber,
                    "`index' is a reserved word which is a read-only property of a Role vector element,",
                    " and may not be assigned.", "", "");
        } else if (lhsType instanceof RoleType && null != rhsType && rhsType instanceof ArrayType) {
            final Type baseType = ((ArrayType) rhsType).baseType();
            if (lhsType.canBeConvertedFrom(baseType)) {
                this.checkRoleClassNameCollision((RoleType) lhsType, baseType, ctxGetStart.getLine());
            } else {
                // Maybe the Role is trying to be an array, using the at and atPut
                // facilities...
                if (lhsType.canBeConvertedFrom(rhsType)) {
                    this.checkRoleClassNameCollision((RoleType) lhsType, rhsType, ctxGetStart.getLine());
                } else {
                    errorHook6p2(ErrorIncidenceType.Fatal, lineNumber, "Role vector elements of type `",
                            lhsType.name(), "' cannot be played by objects of type `",
                            ((ArrayType) rhsType).baseType().name(), "':", "");
                }
            }
        } else if (lhsType instanceof RoleType && null != rhsType) {
            final boolean isRoleArray = lhsType instanceof RoleType
                    && ((RoleType) lhsType).associatedDeclaration() instanceof RoleArrayDeclaration;
            final boolean isStagePropArray = lhsType instanceof StagePropType
                    && ((StagePropType) lhsType).associatedDeclaration() instanceof StagePropArrayDeclaration;
            if ((isRoleArray || isStagePropArray) && rhsType instanceof ClassType
                    && rhsType.name().startsWith("List<")) {
                // includes stage props
                final String ofWhatThisIsAList = rhsType.name().substring(5, rhsType.name().length() - 1);
                final Type rhsBaseType = currentScope_.lookupTypeDeclarationRecursive(ofWhatThisIsAList);
                if (null != rhsBaseType && rhsBaseType.isntError()) { // error stumbling check
                    tf = lhsType.canBeConvertedFrom(rhsBaseType, lineNumber, this);
                    if (false == tf && lhs.isntError() && rhs.isntError()) {
                        errorHook6p2(ErrorIncidenceType.Fatal, lineNumber, "Roles in `", lhsType.name(),
                                "' cannot be played by objects of type `", rhsBaseType.name(), "':", "");
                        this.reportMismatchesWith(lineNumber, (RoleType) lhsType, rhsBaseType);
                    }
                }
            } else if (lhsType.canBeConvertedFrom(rhsType) == false && lhs.isntError() && rhs.isntError()
                    && lhsType.isntError() && rhsType.isntError()) {
                errorHook6p2(ErrorIncidenceType.Fatal, lineNumber, "Role `", lhsType.name(),
                        "' cannot be played by object of type `", rhsType.name(), "':", "");
                this.reportMismatchesWith(lineNumber, (RoleType) lhsType, rhsType);
            }
            this.checkRoleClassNameCollision((RoleType) lhsType, rhsType, ctxGetStart.getLine());
        } else if (null != lhsType && null != rhsType && lhsType.canBeConvertedFrom(rhsType) == false
                && lhs.isntError() && rhs.isntError() && rhsType.isntError()) {
            errorHook6p2(ErrorIncidenceType.Fatal, lineNumber, "Type of `", lhsType.name(),
                    "' is incompatible with expression type `", rhsType.name(), "'.", "");
        } else if (lhs instanceof ArrayIndexExpression) {
            final Type anotherLhsType = ((ArrayIndexExpression) lhs).baseType();
            if (null != anotherLhsType && null != rhsType && anotherLhsType.canBeConvertedFrom(rhsType) == false
                    && lhs.isntError() && rhs.isntError() && rhsType.isntError()) {
                errorHook6p2(ErrorIncidenceType.Fatal, lineNumber, "Type of `", lhs.getText(),
                        "' is incompatible with expression type `", rhsType.name(), "'.", "");
            }
        } else if (lhs instanceof RoleArrayIndexExpression) {
            if (lhsType.canBeConvertedFrom(rhsType) == false && lhs.isntError() && rhs.isntError()) {
                errorHook6p2(ErrorIncidenceType.Fatal, lineNumber, "Role `", lhsType.name(),
                        "' cannot be played by object of type `", rhsType.name(), "':", "");
                this.reportMismatchesWith(lineNumber, (RoleType) lhsType, rhsType);
            }
        } else if ((lhs instanceof IdentifierExpression) == false
                && (lhs instanceof QualifiedIdentifierExpression) == false && lhs.isntError() && rhs.isntError()) {
            errorHook5p2(ErrorIncidenceType.Fatal, lineNumber,
                    "Can assign only to an identifier, qualified identifier, or vector element.", "", "", "");
        }

        rhs.setResultIsConsumed(true);

        final AssignmentExpression retval = new AssignmentExpression(lhs, operator, rhs, lineNumber, this);
        checkForAssignmentViolatingConstness(retval, ctx.getStart());

        return retval;
    }

    private void checkRoleClassNameCollision(final RoleType lhsType, final Type baseType, int lineNumber) {
        if (baseType instanceof ClassType) {
            // There should be no duplicates between signatures in RoleType
            // and those in Class Type
            final ClassType classType = (ClassType) baseType;
            final List<MethodDeclaration> classMethodList = classType.enclosedScope().methodDeclarations();
            for (final MethodDeclaration methodDeclaration : classMethodList) {
                final ActualOrFormalParameterList parameterList = methodDeclaration.formalParameterList();
                final String methodSelector = methodDeclaration.name();
                final MethodDeclaration correspondingRoleMethod = lhsType.enclosedScope()
                        .lookupMethodDeclarationIgnoringRoleStuff(methodSelector, parameterList);
                if (null != correspondingRoleMethod) {
                    errorHook6p2(ErrorIncidenceType.Warning, lineNumber, "WARNING: Both class `" + baseType.name(),
                            "' and Role `" + lhsType.name(), "' contain the same script signature `",
                            correspondingRoleMethod.signature().getText(),
                            "'. This results in several scripts of the same name in the same object",
                            " and may not behave as you expected.");
                }
            }
        }
    }

    protected void checkForIncrementOpViolatingExpressionConstness(UnaryopExpressionWithSideEffect assignment,
            KantParser.ExprContext ctx) {
        /* nothing on pass 1 */
    }

    protected void checkForIncrementOpViolatingConstness(ArrayIndexExpressionUnaryOp expression,
            Token ctxGetStart) {
        /* nothing on pass 1 */
    }

    protected void checkForIncrementOpViolatingIdentifierConstness(UnaryopExpressionWithSideEffect assignment,
            Token ctxGetStart) {
        /* nothing on pass 1 */
    }

    protected void checkForAssignmentViolatingConstness(AssignmentExpression assignment, Token ctxGetStart) {
        /* nothing on pass 1 */
    }

    protected void checkForMessageSendViolatingConstness(MethodSignature signature, Token ctxGetStart) {
        /* nothing on pass 1 */
    }

    protected <ExprType> Expression expressionFromReturnStatement(final ExprType ctxExpr, final RuleContext unused,
            final Token ctxGetStart) {
        // Pass 1 version. There is another version for Pass 3 / 4.
        Expression retval = null;
        if (null != ctxExpr) {
            final Expression returnExpression = parsingData_.popExpression();
            returnExpression.setResultIsConsumed(true);
            retval = returnExpression;
        }
        return retval;
    }

    public void nameCheck(final String name, int lineNumber) {
        if (name.equals("this") || name.equals("Ralph") || name.equals("Sue") || name.equals("index")
                || name.equals("lastIndex")) {
            errorHook5p2(ErrorIncidenceType.Fatal, lineNumber,
                    "Please avoid the use of the names `this', `Sue', `index', `lastIndex' and `Ralph' for identifiers.",
                    "", "", "");
        }
    }

    @Override
    protected void errorHook5p1(final ErrorIncidenceType errorType, final int i, final String s1, final String s2,
            final String s3, final String s4) {
        ErrorLogger.error(errorType, i, s1, s2, s3, s4);
    }

    protected ParsingData parsingDataArgumentAccordingToPass() {
        return parsingData_;
    }

    private void stackSnapshotDebug() {
        parsingData_.stackSnapshotDebug();
    }

    public static int kantParserVariableGeneratorCounter_ = 101;
}