// Main type checking class

package edu.cmu.cs.l3.semant;

import edu.cmu.cs.l3.absyn.*;
import edu.cmu.cs.l3.general.*;
import edu.cmu.cs.l3.symbol.*;
import edu.cmu.cs.l3.errormsg.*;
import java.util.*;

public class TypeChecker
    implements Visitor {

    public TypeChecker(ErrorMsg error, Environment env) {
        m_error = error;
        m_env = env;
        m_needret.push(new Integer(1));
    }

    public void setError(ErrorMsg error) {
        m_error = error;
    }

    public void visitResponse(String type, Object obj) {

        int pos = 0;

        if (obj instanceof ASStatement) {
            pos = ( (ASStatement) obj).getPosition();
        }
        else if (obj instanceof ASExpression) {
            pos = ( (ASExpression) obj).getPosition();
        }
        else if (obj instanceof ASLval) {
            pos = ( (ASLval) obj).getPosition();
        }
        else if (obj instanceof ASDeclaration) {
            pos = ( (ASDeclaration) obj).getPosition();
        }
        else if (obj instanceof ASType) {
            pos = ( (ASType) obj).getPosition();
        }

        try {
            if (type.equals("ASSimpleLval")) {
                visitSimpleVar(obj);
            }
            else if (type.equals("ASIntConstant")) {
                visitIntConstant(obj);
            }
            else if (type.equals("ASLvalExpression")) {
                visitVarExp(obj);
            }
            else if (type.equals("ASBoolConstant")) {
                visitBoolConstant(obj);
            }
            else if (type.equals("ASDeclarationVariable")) {
                visitDecVar(obj);
            }
            else if (type.equals("ASUserType")) {
                visitUserType(obj);
            }
            else if (type.equals("ASDeclarationType")) {
          //      visitDecType(obj);
            }
            else if (type.equals("ASDeclarationList")) {
                visitDecList(obj);
            }
            else if (type.equals("ASAssignStatement")) {
                visitAssign(obj);
            }
            else if (type.equals("ASStatementList")) {
                visitStmList(obj);
            }
            else if (type.equals("ASIfStatement")) {
                visitIf(obj);
            }
            else if (type.equals("ASBlock")) {
                visitBlock(obj);
            }
            else if (type.equals("ASForStatement")) {
                visitFor(obj);
            }
            else if (type.equals("ASWhileStatement")) {
                visitWhile(obj);
            }
            else if (type.equals("ASFieldLval")) {
                visitFieldVariable(obj);
            }
            else if (type.equals("ASOpExpression")) {
                visitOpExp(obj);
            }
            else if (type.equals("ASPointerExpression")) {
                visitPointerExp(obj);
            }
            else if (type.equals("ASPrimitiveType")) {
                visitPrimitiveType(obj);
            }
            else if (type.equals("ASRecordType")) {
                visitRecordType(obj);
            }
            else if (type.equals("ASDerefLval")) {
                visitMemVar(obj);
            }
            else if (type.equals("ASNullConstant")) {
                visitNull(obj);
            }
            else if (type.equals("ASPointerType")) {
                visitPtrType(obj);
            }
            else if (type.equals("ASReturnStatement")) {
                visitReturn(obj);
            }
            else if (type.equals("ASDeclarationProc")) {
                visitProc(obj);
            }
            else if (type.equals("ASProcedureExpression")) {
                visitProcExp(obj);
            }
            else if (type.equals("ASExpressionStatement")) {
                visitStmExp(obj);
            }
            else if (type.equals("ASBreakStatement")) {
                visitBreak(obj);
            }
            else if (type.equals("ASContinueStatement")) {
                visitContinue(obj);
            }
            else if (type.equals("ASBlankStatement")) {

            }
            else {
                System.out.println(
                    "Severe Internal Compiler Warning: Visit passing unknown visitable!: " +
                    type);
            }
        }
        catch (TypeCheckException e) {
            m_error.error(pos, e.getMessage());
            System.out.println("(" + (m_numerrors + 1) +
                               ") errors. Cannot continue. Program aborted.");
            Globals.shutdown(-1);
        }
        catch (SymbolTableException e) {
            m_error.error(pos,
                          e.getMessage() + " " + ( (Ty) e.getBase()).getName());
            System.out.println("(" + (m_numerrors + 1) +
                               ") errors. Cannot continue. Program aborted.");
            Globals.shutdown(-1);
        }
    }

    private void visitStmExp(Object e) {

        ASExpressionStatement ae = (ASExpressionStatement) e;

        ae.getExpression().visit(this);

        ae.getTypeInfo().setSemantType(ae.getExpression().getTypeInfo().getSemantType());
    }

    public void setupProcedures(ASDeclarationList decs) throws TypeCheckException {
        int i,pos=0;
        String pname;
        try {

            for (i = 0; i < decs.getSize(); i++) {
                ASDeclaration dec = (ASDeclaration) decs.getDeclaration(i);
                if (dec instanceof ASDeclarationType) {
                    visitDecType(dec);
                }
            }
            for (i = 0; i < decs.getSize(); i++) {
                ASDeclaration dec = (ASDeclaration) decs.getDeclaration(i);
                pos = dec.getPosition();
                if (dec instanceof ASDeclarationProc) {
                    TyFunction functy = getFunctionType( (ASDeclarationProc)
                        dec);
                    ASDeclarationProc pdec = (ASDeclarationProc) dec;
                    m_env.addEntry(pdec.getName(),functy, Environment.PROCEDURE);
                }
            }
        } catch (SymbolTableException e) {
            m_error.error(pos,
                          e.getMessage() + " " + ( (Ty) e.getBase()).getName());
            System.out.println("(" + (m_numerrors + 1) +
                               ") errors. Cannot continue. Program aborted.");
            Globals.shutdown(-1);
        }
    }

    private void visitProc(Object pp) throws TypeCheckException,
        SymbolTableException {

        ASDeclarationProc ap = (ASDeclarationProc) pp;
        int i;
        List params, typarams = new ArrayList();

        TyFunction functy = getFunctionType(ap);
        params = ap.getParams();
        typarams = functy.getParameters();

        if (ap.getName().toString().equals("main") && params.size() > 0)
            error(ap.getPosition(), "main cannot take parameters");

        for (i = 0; i < params.size(); i++) {
            Pair curpair = (Pair) params.get(i);
            Ty pty = (Ty) typarams.get(i);
            m_env.addEntry( (Symbol) curpair.getLeft(), pty,
                           Environment.VALUE);
        }

        m_curfuntype = functy.getReturnType();
     //   m_env.addEntry(ap.getName(),functy,Environment.PROCEDURE);
        m_needret.push(new Integer(1));
        if (ap.getBody() != null) {
            ap.getBody().visit(this);
            if ( (functy.getReturnType() != null) &&
                ( ( (Integer) m_needret.pop()).intValue() == 1)) {
                error(ap.getPosition(), "all execution paths must return");
            }
        }
        else
            functy.setExtern();

        for (i = 0; i < params.size(); i++) {
            Pair curpair = (Pair) params.get(i);
            m_env.removeEntry( (Symbol) curpair.getLeft(),
                              Environment.VALUE, true);
        }

        ap.getTypeInfo().setSemantType(functy);
    }

    private void visitProcExp(Object p) throws TypeCheckException {

        ASProcedureExpression ap = (ASProcedureExpression) p;

        Pair res = processFunction(ap.getName(), ap.getParameters());
        Ty ty = (Ty) res.getLeft();
        Boolean extern = (Boolean) res.getRight();

        if (extern.booleanValue()) {
            ap.setName(Symbol.symbol(Globals.extFunctionName(ap.getName().toString())));
        }

        ap.getTypeInfo().setSemantType(ty);
    }

    private Pair processFunction(Symbol name, List params) throws
        TypeCheckException {

        List callparams, decparams;
        int i;
        boolean extern;

        TyFunction procty = (TyFunction) m_env.getEntry(name,Environment.PROCEDURE);
        if (procty == null) {
            throw new TypeCheckException("STOPPING: unknown function call: " +
                                         name);
        }

        extern = procty.isExtern();
        callparams = params;
        decparams = procty.getParameters();
        if (callparams.size() != decparams.size()) {
            throw new TypeCheckException(
                "STOPPING: Incorrect number of parameters for function: " +
                procty.getName() + " SUPPLIED: " + callparams.size()
                + " NEEDED: " + decparams.size());
        }
        for (i = 0; i < callparams.size(); i++) {
            ASExpression pexp = (ASExpression) callparams.get(i);
            pexp.visit(this);
            Ty cty = checkBinding( (Ty) decparams.get(i));
            Ty cty2 = checkBinding(pexp.getTypeInfo().getSemantType());
            testCompatible(cty, cty2, pexp.getTypeInfo(), pexp.getPosition());
        }

        return new Pair(procty.getReturnType(), new Boolean(extern));
    }

    private TyFunction getFunctionType(ASDeclarationProc ap) throws
        TypeCheckException {

        Ty retty;
        TyFunction fty;
        List params, typarams = new ArrayList();
        int i;

        if (ap.getReturnType() != null) {
            ap.getReturnType().visit(this);
            retty = ap.getReturnType().getTypeInfo().getSemantType();
            if (retty instanceof TyRecord || retty instanceof TyBinding) {
                error(ap.getPosition(),
                      "functions not able to return flat structs");
            }
        }
        else {
            retty = null;
        }

        params = ap.getParams();
        for (i = 0; i < params.size(); i++) {
            Pair curpair = (Pair) params.get(i);
            ASType tyname = (ASType) curpair.getRight();
            Ty ty;

            tyname.visit(this);
            ty = tyname.getTypeInfo().getSemantType();
            typarams.add(ty);
        }

        fty = new TyFunction(ap.getName().toString(), retty, typarams);
        if (ap.getBody() == null) fty.setExtern();

        return fty;
    }

    private void visitReturn(Object r) throws TypeCheckException {

        ASReturnStatement ar = (ASReturnStatement) r;
        Ty retty;

        if (m_curfuntype != null && !ar.hasReturnExpression()) {
            //throw new TypeCheckException("Return statement must return type: " + m_curfuntype.getName());
            error(ar.getPosition(),
                  "return statement must return type: " + m_curfuntype.getName());
            ar.getTypeInfo().setSemantType(m_curfuntype);
        }
        else if (m_curfuntype == null && ar.hasReturnExpression()) {
            //throw new TypeCheckException("Cannot return an expression in a procedure");
            error(ar.getPosition(),
                  "cannot return an expression in a procedure");
        }
        else if (m_curfuntype != null && ar.hasReturnExpression()) {
            ar.getReturnExpression().visit(this);
            testCompatible(m_curfuntype,
                           ar.getReturnExpression().getTypeInfo().getSemantType(),
                           ar.getReturnExpression().getTypeInfo(),
                           ar.getPosition());
            ar.getTypeInfo().setSemantType(ar.getReturnExpression().getTypeInfo().
                                           getSemantType());
            retty = ar.getReturnExpression().getTypeInfo().getSemantType();
            if (retty instanceof TyRecord) {
                error(ar.getPosition(),
                      "return cannot process structs");
            }
        }

        m_needret.pop();
        m_needret.push(new Integer(0));
    }

    private void visitBreak(Object b) {
        ASBreakStatement ab = (ASBreakStatement)b;
        if (m_loopstack.empty())
            error(ab.getPosition(), "break outside of a loop");
    }

    private void visitContinue(Object c) {
        ASContinueStatement ac = (ASContinueStatement)c;
        if (m_loopstack.empty())
            error(ac.getPosition(), "continue outside of a loop");
    }

    private void visitWhile(Object w) throws TypeCheckException {

        ASWhileStatement aw = (ASWhileStatement) w;

        aw.getTest().visit(this);
        if (! (aw.getTest().getTypeInfo().getSemantType()instanceof TyBool)) {
            //throw new TypeCheckException("While test clause must have type bool: Has Type: " + aw.getTest().getTypeInfo().getSemantType().getName());
            error(aw.getPosition(),
                  "while test clause must have type bool: has type: " +
                  aw.getTest().getTypeInfo().getSemantType().getName());
            aw.getTest().getTypeInfo().setSemantType(new TyBool());
        }

        if (aw.getTest() instanceof ASBoolConstant &&
            ((ASBoolConstant)aw.getTest()).getValue()) {
            aw.setTest(new ASOpExpression(aw.getPosition(),
                                          ASOpExpression.LT,
                                          new ASIntConstant(0,0),
                                          new ASIntConstant(0,1)));
        }

        aw.getTest().getTypeInfo().setSemantType(new TyBool());
        m_loopstack.push(new Integer(25));

        m_needret.push(new Integer(1));
        aw.getBody().visit(this);
        m_needret.pop();

        m_loopstack.pop();
    }

    private void visitFor(Object f) throws TypeCheckException {

        ASForStatement af = (ASForStatement) f;
        Ty varty, iexpty, texpty;

        af.getAssignment().visit(this);
        af.getEndTest().visit(this);
        texpty = af.getEndTest().getTypeInfo().getSemantType();
        if (! (texpty instanceof TyBool)) {
            //throw new TypeCheckException("For test clause must have type bool: Has Type: " + texpty.getName());
            error(af.getPosition(),
                  "for test clause must have type bool: has type: " +
                  texpty.getName());
            af.getEndTest().getTypeInfo().setSemantType(new TyBool());
        }

        af.getEndTest().getTypeInfo().setSemantType(new TyBool());
        m_loopstack.push(new Integer(1));

        af.getForWorkStatement().visit(this);

        m_needret.push(new Integer(1));
        af.getBody().visit(this);
        m_needret.pop();

        m_loopstack.pop();
    }

    private void visitFieldVariable(Object f) throws TypeCheckException {

        ASFieldLval af = (ASFieldLval) f;
        Ty tyrecord;
        List fieldnames, fields;
        int i;

        af.getRecord().visit(this);
        tyrecord = af.getRecord().getTypeInfo().getSemantType();
        tyrecord = checkBinding(tyrecord);
        if (! (tyrecord instanceof TyRecord)) {
            throw new TypeCheckException(
                "STOPPING: cannot access fields variables of non-struct");
        }

        af.getRecord().getTypeInfo().setSemantType(tyrecord);
        fieldnames = ( (TyRecord) tyrecord).getFieldNames();
        fields = ( (TyRecord) tyrecord).getFields();
        for (i = 0; i < fieldnames.size(); i++) {
            Symbol fname = (Symbol) fieldnames.get(i);
            if (fname == af.getField()) {
                af.getTypeInfo().setSemantType(checkBinding( (Ty) fields.get(i)));
                return;
            }
        }

        throw new TypeCheckException("STOPPING: fieldname not found in type: " +
                                     tyrecord.getName());
    }

    private void visitRecordType(Object r) {

        TyRecord tyrecord;
        Ty fty;
        ASRecordType ar = (ASRecordType) r;
        int i;
        List fields = ar.getFields(), tyfields = new ArrayList(),
            tyidents = new ArrayList();

        for (i = 0; i < fields.size(); i++) {
            ASType cfield = (ASType) ( (Pair) fields.get(i)).getRight();
            cfield.visit(this);
            fty = cfield.getTypeInfo().getSemantType();
            if (fty instanceof TyBinding && ( (TyBinding) fty).getBinding() == null) {
                error(ar.getPosition(), "cannot declare an infinite structure");
            }
            tyfields.add(fty);
            Symbol fname = (Symbol) ( (Pair) fields.get(i)).getLeft();
            if (tyidents.contains(fname)) {
                error(ar.getPosition(), "duplicate field names: " + fname);
            }
            else
                tyidents.add(fname);
        }
        tyrecord = new TyRecord(ar.getTypeName(), tyfields, tyidents);
        ar.getTypeInfo().setSemantType(tyrecord);
    }

    private void visitUserType(Object u) throws TypeCheckException {

        ASUserType au = (ASUserType) u;
        Ty uty = m_env.getEntry(au.getName(), Environment.TYPE);

        if (uty == null) {
            throw new TypeCheckException("STOPPING: User type not defined: " +
                                         au.getName());
        }

        au.getTypeInfo().setSemantType(uty);

    }

    private void visitPtrType(Object p) {

        ASPointerType ap = (ASPointerType) p;

        ap.getUnderType().visit(this);

        ap.getTypeInfo().setSemantType(new TyPointer(ap.getUnderType().
            getTypeInfo().getSemantType()));
    }

    private void visitNull(Object n) {

        ASNullConstant an = (ASNullConstant) n;

        an.getTypeInfo().setSemantType(new TyPointer(new TyNonsense()));
    }

    private void visitMemVar(Object m) throws TypeCheckException {

        ASDerefLval am = (ASDerefLval) m;
        Ty bty = null;
        List types = new ArrayList();

        am.getVariableExpression().visit(this);

        if (! (am.getVariableExpression().getTypeInfo().getSemantType()instanceof
               TyPointer)) {
            //throw new TypeCheckException("Can only dereference pointers or references");
            error(am.getPosition(),
                  "Can only dereference pointers or references");
            am.getTypeInfo().setSemantType(am.getVariableExpression().
                                           getTypeInfo().getSemantType());
            return;
        }

        bty = ( (TyPointer) am.getVariableExpression().getTypeInfo().
               getSemantType()).getUnderType();
        am.getTypeInfo().setSemantType(bty);
    }

    private void visitPrimitiveType(Object t) throws TypeCheckException {

        ASPrimitiveType at = (ASPrimitiveType) t;

        if (at.getName().toString().equals("int")) {
            at.getTypeInfo().setSemantType(new TyInt());
        }
        else if (at.getName().toString().equals("bool")) {
            at.getTypeInfo().setSemantType(new TyBool());
        }
        else {
            throw new TypeCheckException("Internal Compiler Error");
        }
    }

    private void visitPointerExp(Object p) throws TypeCheckException {

        ASPointerExpression ap = (ASPointerExpression) p;
        Ty ety, tty, ity = null;

        if (ap.getOperation() == ASPointerExpression.AMP) {
            ap.getOperand().visit(this);
            ety = new TyPointer(ap.getOperand().getTypeInfo().getSemantType());
            ap.getTypeInfo().setSemantType(ety);
            if (ap.getOperand() instanceof ASLvalExpression) {
               ASLval v = ((ASLvalExpression)ap.getOperand()).getVariable();
               if (v instanceof ASSimpleLval) {
                   ASSimpleLval sv = (ASSimpleLval) v;
                   Ty dty = m_env.getEntry(sv.getName(), Environment.VALUE);
                   if (!(dty instanceof TyRecord)) {
                       dty.setEscape(true);
                   }
               }
           }
        }
        else if (ap.getOperation() == ASPointerExpression.OFFSET) {
            ap.getOperand().visit(this);
            if (! (ap.getOperand().getTypeInfo().getSemantType()instanceof
                   TyPointer)) {
                error(ap.getPosition(),
                      "offset() can only be applied to pointers");
            }
            ap.getTypeInfo().setSemantType(new TyInt());
        }
        else if (ap.getOperation() == ASPointerExpression.SIZE) {
            ap.getOperand().visit(this);
            if (! (ap.getOperand().getTypeInfo().getSemantType()instanceof
                   TyPointer)) {
                error(ap.getPosition(),
                      "size() can only be applied to pointers");
            }
            ap.getTypeInfo().setSemantType(new TyInt());
        }
        else if (ap.getOperation() == ASPointerExpression.INC) {
            ap.getOperand().visit(this);
            if (! (ap.getOperand().getTypeInfo().getSemantType()instanceof
                   TyPointer)) {
                error(ap.getPosition(),
                      "inc() can only be applied to pointers");
            }
            ap.getTypeInfo().setSemantType(ap.getOperand().getTypeInfo().getSemantType());
        }
        else if (ap.getOperation() == ASPointerExpression.DEC) {
            ap.getOperand().visit(this);
            if (! (ap.getOperand().getTypeInfo().getSemantType()instanceof
                   TyPointer)) {
                error(ap.getPosition(),
                      "dec() can only be applied to pointers");
            }
            ap.getTypeInfo().setSemantType(ap.getOperand().getTypeInfo().getSemantType());
        }
        else {
            ap.getNumber().visit(this);
            ap.getType().visit(this);
            tty = checkBinding(ap.getType().getTypeInfo().getSemantType());

            if (ap.getNumber().getTypeInfo().getSemantType().isEquivalent(new
                TyInt())) {
                ap.getTypeInfo().setSemantType(new TyPointer(tty));
                ap.getType().getTypeInfo().setSemantType(tty);
            }
            else {
                //throw new TypeCheckException("Init expression not same type as type");
                ap.getTypeInfo().setSemantType(new TyPointer(tty));
                ap.getType().getTypeInfo().setSemantType(tty);
                error(ap.getPosition(), "size must be of type int");
            }

        }

    }

    private void visitSimpleVar(Object v) throws TypeCheckException {

        ASSimpleLval av = (ASSimpleLval) v;

        Ty vtype = m_env.getEntry(av.getName(), Environment.VALUE);
        if (vtype == null) {
            throw new TypeCheckException("STOPPING: Undefined variable: " +
                                         av.getName());
        }
        av.getTypeInfo().setSemantType(vtype);
    }

    private void visitIntConstant(Object i) {
        ASIntConstant ai = (ASIntConstant) i;
        ai.getTypeInfo().setSemantType(new TyInt());
    }

    private void visitVarExp(Object e) {

        ASLvalExpression ae = (ASLvalExpression) e;

        ae.getVariable().visit(this);
        Ty vtype = ae.getVariable().getTypeInfo().getSemantType();

        ae.getTypeInfo().setSemantType(vtype);
    }

    private void visitBoolConstant(Object b) {

        ASBoolConstant ab = (ASBoolConstant) b;

        ab.getTypeInfo().setSemantType(new TyBool());
    }

    private void visitDecVar(Object d) throws TypeCheckException,
        SymbolTableException {

        ASDeclarationVariable ad = (ASDeclarationVariable) d;
        Ty expty, varty;

        ad.getTypeInfo().getASType().visit(this);
        //ad.getInitExpression().visit(this);
        //expty = ad.getInitExpression().getTypeInfo().getSemantType();
        varty = ad.getTypeInfo().getASType().getTypeInfo().getSemantType();
        //System.out.println(expty.getName() + " " + varty.getName());
        //System.out.println(expty.getEqSize() + " " + varty.getEqSize());

        varty = checkBinding(varty);
        ad.getTypeInfo().setSemantType(varty);
        m_env.addEntry(ad.getVariableName(), varty, Environment.VALUE);
    }

    private void visitDecType(Object t) throws TypeCheckException,
        SymbolTableException {

        ASDeclarationType at = (ASDeclarationType) t;
        Ty typety;
        TyBinding tbind;

        tbind = new TyBinding(at.getName().toString());
        m_env.addEntry(at.getName(), tbind, Environment.TYPE);

        at.getType().visit(this);
        typety = at.getType().getTypeInfo().getSemantType();

        tbind.bind(typety);
    }

    private void visitDecList(Object l) throws TypeCheckException {

        ASDeclarationList al = (ASDeclarationList) l;
        int i;

        for (i = 0; i < al.getSize(); i++) {
            al.getDeclaration(i).visit(this);
        }
    }

    private void visitAssign(Object a) throws TypeCheckException {

        ASAssignStatement aa = (ASAssignStatement) a;
        Ty expty, varty;

        aa.getExpression().visit(this);
        aa.getVariable().visit(this);

        expty = aa.getExpression().getTypeInfo().getSemantType();
        varty = aa.getVariable().getTypeInfo().getSemantType();

        varty = checkBinding(varty);
        if (varty instanceof TyRecord)
            error(aa.getPosition(), "cannot assign structs to each other");
        if (testCompatible(varty, expty, aa.getExpression().getTypeInfo(),
                           aa.getPosition())) {
            aa.getTypeInfo().setSemantType(varty);
        }
    }

    private void visitStmList(Object l) {

        ASStatementList al = (ASStatementList) l;
        int i;

        for (i = 0; i < al.getSize(); i++) {
            al.getStatement(i).visit(this);
        }
    }

    private void visitIf(Object i) throws TypeCheckException {

        ASIfStatement ai = (ASIfStatement) i;
        int thenret, elseret;

        ai.getTest().visit(this);
        if (!ai.getTest().getTypeInfo().getSemantType().isEquivalent(new TyBool())) {
            //throw new TypeCheckException("If test clause must have type bool: Has Type: " + ai.getTest().getTypeInfo().getSemantType().getName());
            error(ai.getPosition(),
                  "If test clause must have type bool: Has Type: " +
                  ai.getTest().getTypeInfo().getSemantType().getName());
            ai.getTest().getTypeInfo().setSemantType(new TyBool());
        }

        m_needret.push(new Integer(1));
        ai.getThenClause().visit(this);
        thenret = ( (Integer) m_needret.pop()).intValue();
        if (ai.getElseClause() != null) {
            m_needret.push(new Integer(1));
            ai.getElseClause().visit(this);
            elseret = ( (Integer) m_needret.pop()).intValue();
        }
        else
            elseret = 1;

        if (thenret == 0 && elseret == 0) {
            m_needret.pop();
            m_needret.push(new Integer(0));
        }
    }

    private void visitBlock(Object b) {

        ASBlock ab = (ASBlock) b;

        m_env.beginScope();
        if (ab.getDeclaration() != null) {
            ab.getDeclaration().visit(this);
        }
        if (ab.getStatement() != null) {
            ab.getStatement().visit(this);
        }
        m_env.endScope();
    }

    private void visitOpExp(Object o) throws TypeCheckException {

        ASOpExpression ao = (ASOpExpression) o;

        ao.getLeft().visit(this);
        if (ao.getLeft().getTypeInfo().getSemantType()instanceof TyRecord) {
            error(ao.getPosition(),
                  "no operation defined when operator is struct");
            ao.getTypeInfo().setSemantType(new TyInt());
            return;
        }

        if (ao.getRight() != null) {
            ao.getRight().visit(this);
            if (ao.getRight().getTypeInfo().getSemantType()instanceof TyRecord) {
                error(ao.getPosition(),
                      "no operation defined when operator is struct");
                ao.getTypeInfo().setSemantType(new TyInt());
                return;
            }
        }

        switch (ao.getOperatorClass()) {
            case ASOpExpression.ARITH:
                visitOpArith(ao);
                break;
            case ASOpExpression.BITWISE:
                visitBitwise(ao);
                break;
            case ASOpExpression.BOOLOP:
                visitBoolOp(ao);
                break;
            case ASOpExpression.RELATIONAL:
                visitRelOp(ao);
                break;
            case ASOpExpression.POINTER:
                visitOpPtr(ao);
                break;
            default:
                System.out.println("TYCHECK: WARNING: Passing unknown operator");
                break;
        }
    }

    private void visitRelOp(ASOpExpression ao) throws TypeCheckException {

        Ty lty, rty;

        lty = ao.getLeft().getTypeInfo().getSemantType();
        rty = ao.getRight().getTypeInfo().getSemantType();

        if ( (lty instanceof TyPointer || rty instanceof TyPointer) ||
             (lty instanceof TyRecord || rty instanceof TyRecord) ||
             (lty instanceof TyBinding || rty instanceof TyBinding))
            error(ao.getPosition(), "cannot compare non primitives without *==, *!=");


        if (!(lty instanceof TyInt && rty instanceof TyInt))
            error(ao.getPosition(), "comparison on ints only");

        ao.getTypeInfo().setSemantType(new TyBool());
    }

    private void visitBoolOp(ASOpExpression ao) throws TypeCheckException {

        Ty lty, rty;

        lty = ao.getLeft().getTypeInfo().getSemantType();
        if (ao.getRight() == null) {
            if (lty.isEquivalent(new TyBool())) {
                ao.getTypeInfo().setSemantType(lty);
            }
            else {
                //throw new TypeCheckException("Logical not must involve a bool");
                error(ao.getPosition(), "Not takes only bool parameters");
                ao.getTypeInfo().setSemantType(lty);
            }
        }
        else {
            rty = ao.getRight().getTypeInfo().getSemantType();
            if (lty.isEquivalent(new TyBool()) && rty.isEquivalent(new TyBool())) {
                ao.getTypeInfo().setSemantType(lty);
            }
            else {
                //throw new TypeCheckException("Both operands must be bool");
                error(ao.getPosition(), "Both operands must be bool");
                ao.getTypeInfo().setSemantType(lty);
            }
        }
    }

    private void visitBitwise(ASOpExpression ao) throws TypeCheckException {
        Ty lty,rty;
        List types = new ArrayList();

        lty = ao.getLeft().getTypeInfo().getSemantType();
        rty = ao.getRight().getTypeInfo().getSemantType();
        if (lty.isEquivalent(new TyInt()) && lty.isEquivalent(rty))
            ao.getTypeInfo().setSemantType(new TyInt());
        else {
            error(ao.getPosition(), "illegal operands to operator");
            ao.getTypeInfo().setSemantType(lty);
        }

    }

    private void visitOpArith(ASOpExpression ao) throws TypeCheckException {

        Ty lty, rty;

        lty = ao.getLeft().getTypeInfo().getSemantType();
        if (ao.getOperator() == ASOpExpression.NEG) {
            if (!lty.isEquivalent(new TyInt()))
                error(ao.getPosition(), "Negation need type int");
            ao.getTypeInfo().setSemantType(new TyInt());
        }
        else {
            rty = ao.getRight().getTypeInfo().getSemantType();
            if (lty.isEquivalent(new TyInt()) && lty.isEquivalent(rty))
                ao.getTypeInfo().setSemantType(new TyInt());
            else {
                error(ao.getPosition(), "illegal operands to operator");
                ao.getTypeInfo().setSemantType(lty);
            }
        }
    }

    private void visitOpPtr(ASOpExpression ao) {

        Ty lty, rty;

        lty = ao.getLeft().getTypeInfo().getSemantType();
        rty = ao.getRight().getTypeInfo().getSemantType();

        if (ao.getOperator() == ASOpExpression.PADD ||
            ao.getOperator() == ASOpExpression.PMINUS) {
            if (lty instanceof TyPointer && rty.isEquivalent(new TyInt()))
                ao.getTypeInfo().setSemantType(lty);
            else if (rty instanceof TyPointer && lty.isEquivalent(new TyInt()))
                ao.getTypeInfo().setSemantType(rty);
            else {
                error(ao.getPosition(), "Illegal operands to operator");
                ao.getTypeInfo().setSemantType(lty);
            }
        }
        else {
            if (lty.isEquivalent(rty))
                ao.getTypeInfo().setSemantType(new TyBool());
            else if (lty instanceof TyPointer && rty instanceof TyPointer)
                ao.getTypeInfo().setSemantType(new TyBool());
            else
                error(ao.getPosition(), "weird");
        }

    }

    public static Ty checkBinding(Ty type) {

        Ty temp, build;
        Vector ulist = new Vector();
        int i;

        temp = type;
        while (temp instanceof TyPointer) {
            if (temp instanceof TyPointer) {
                temp = ( (TyPointer) temp).getUnderType();
                ulist.add(new Integer(1));
            }
        }

        if (temp instanceof TyBinding) {
            build = ( (TyBinding) temp).getBinding();
            for (i = 0; i < ulist.size(); i++) {
                int ptype = ( (Integer) ulist.get(i)).intValue();
                if (ptype == 1) {
                    build = new TyPointer(build);
                }
            }

            return build;
        }
        else {
            return type;
        }
    }

    private boolean testCompatible(Ty varty, Ty expty, ASTypeInfo tyinfo,
                                   int pos) throws TypeCheckException {

        if (expty.isEquivalent(varty)) {
            return true;
        }
        else {
            //throw new TypeCheckException("incompatible types: (" + varty.getName() + "," + expty.getName() + ")");
            error(pos,
                  "incompatible types: (" + varty.getName() + "," +
                  expty.getName() +
                  ")");
            return true;
        }
    }

    public int getNumErrors() {
        return m_numerrors;
    }

    private void error(int pos, String message) {

        m_error.error(pos, "TYCHECK: " + message);
        m_numerrors++;
    }

    private ErrorMsg m_error;
    private Environment m_env = null;
    private Ty m_curfuntype = null;
    private Stack m_needret = new Stack();
    private int m_numerrors = 0;
    private Stack m_loopstack = new Stack();
}