def __init__(self):
     self.scope = SymbolTable(None, 'main')
class TypeChecker(NodeVisitor):

    def __init__(self):
        self.scope = SymbolTable(None, 'main')

    def visit_BinExpr(self, node):
        type1 = self.visit(node.left)
        type2 = self.visit(node.right)
        if ttype[node.op][type1][type2] is None:
            print "Bad expression {} in line {}".format(node.op, node.line)
        return ttype[node.op][type1][type2]

    def visit_Const(self, node):
        if re.match(r"(\+|-){0,1}(\d+\.\d+|\.\d+)", node.val):
            return self.visit_Float(node)
        elif re.match(r"(\+|-){0,1}\d+", node.val):
            return self.visit_Integer(node)
        elif re.match(r"\A('.*'|\".*\")\Z", node.val):
            return self.visit_String(node)
        else:
            variable = self.scope.get(node.val)
            if variable is None:
                print "Variable {} in line {} hasn't been declared".format(node.val, node.line)
            else:
                return variable.type


    def visit_Integer(self, node):
        return 'int'

    def visit_Float(self,node):
        return 'float'

    def visit_String(self,node):
        return 'string'

    def visit_Declaration(self, node):
        visited_inits = self.visit(node.inits)
        if visited_inits is not None:
            for curr in self.visit(node.inits):
                if self.scope.get_not_parent(curr[0]) is not None:
                    print "Variable {} in line {} has been declared earlier".format(curr[0], node.line)
                else:
                    if node.type == curr[1] or (node.type == 'float' and curr[1] == 'int'):
                        self.scope.put(curr[0], VariableSymbol(curr[0], node.type))
                    elif node.type == 'int' and curr[1] == 'float':
                        print "Warning! You lost float precision in line {}".format(node.line)
                        self.scope.put(curr[0], VariableSymbol(curr[0], node.type))
                    else:
                        print "Type mismatch in line {}".format(node.line)

    def visit_ReturnInstr(self, node):
        self.scope.put('return', 'return')
        if not isinstance(self.scope.get(self.scope.name), FunctionSymbol):
            print "Return outside function in line {}".format(node.line)
        if self.scope.parent.get(self.scope.name) is not None:
            expr = self.visit(node.expr)
            if self.scope.parent.get(self.scope.name).type == 'int' and expr == 'float':
                print "Warning! You lost float precision in line {}".format(node.line)
            elif not (self.scope.parent.get(self.scope.name).type == expr or (self.scope.parent.get(self.scope.name).type == 'float' and expr=='int')):
                print "Type mismatch in line {}".format(node.line)

    def visit_PrintInstr(self, node):
        self.visit(node.expr)

    def visit_Init(self, node):
        return (node.id, self.visit(node.expr))

    def visit_AssignmentInstr(self, node):
        type = self.visit(node.expr)
        if type is None:
            return None
        var_from_scope = self.scope.get(node.id)
        if var_from_scope is None:
            print "Variable {} in line {} hasn't been declared".format(node.id, node.line)
        else:
            if var_from_scope.type == 'int' and type == 'float':
                print "Warning! You lost float precision in line {}".format(node.line)
                self.scope.put(node.id, VariableSymbol(node.id, type))
            elif not (var_from_scope.type == type or (var_from_scope.type == 'float' and type=='int')):
                print "Type mismatch in line {}".format(node.line)


        return (node.id, type)

    def visit_ChoiceInstr(self, node):
        self.visit(node.condition)
        self.visit(node.instruction)
        self.visit(node.else_instr)

    def visit_WhileInstr(self, node):
        self.visit(node.condition)
        self.scope = self.scope.pushScope("loop")
        self.visit(node.instruction)
        self.scope = self.scope.popScope()

    def visit_RepeatInstr(self, node):
        self.visit(node.condition)
        self.scope = self.scope.pushScope("loop")
        self.visit(node.instruction)
        self.scope = self.scope.popScope()

    def visit_ContinueInstr(self, node):
        if self.scope.name != "loop":
            print "Continue outside the loop in line {}".format(node.line)

    def visit_BreakInstr(self, node):
        if self.scope.name != "loop":
            print "Break outside the loop in line {}".format(node.line)

    def visit_CompoundInstr(self, node):
        self.visit(node.declarations)
        self.visit(node.instructions_opt)

    def visit_CastFunction(self, node):
        function = self.scope.get(node.functionName)
        if function is None:
            print "Function {} in line {} has not been declared".format(node.functionName, node.line)
            return
        args_cast = self.visit(node.args)
        args_scope = self.visit(function.arguments)
        if len(args_cast) != len(args_scope):
            print "Wrong numbers of arguments in line {}".format(node.line)
        else:
            for arg in args_scope:
                i = args_scope.index(arg)
                if arg[1] == 'int' and args_cast[i] == 'float':
                    print "Warning! You lost float precision in line {}".format(node.line)
                elif not (arg[1] == args_cast[i] or (arg[1] == 'float' and args_cast[i] == 'int')):
                    print "Function arguments mismatch in line {}".format(node.line)

        return function.type


    def visit_ExprInBrackets(self, node):
        return self.visit(node.expr)

    def visit_Function(self, node):
        if self.scope.get(node.id) is not None:
            print "Function {} in line {} has been declared earlier".format(node.id, node.line)
        else:
            self.scope.put(node.id, FunctionSymbol(node.id, node.type, node.args_list_or_empty))
            self.scope = self.scope.pushScope(node.id)
            self.visit(node.args_list_or_empty)
            self.visit(node.compound_instr)
            if node.type != "void" and self.scope.get_not_parent('return') is None:
                print "No return for function in line {}".format(node.line)
            self.scope = self.scope.popScope()

    def visit_Argument(self, node):
        self.scope.put(node.id, VariableSymbol(node.id, node.type))
        return (node.id, node.type)

    def visit_Block(self, node):
        self.scope = self.scope.pushScope("block")
        self.visit(node.declarations)
        self.visit(node.fundefs_opt)
        self.visit(node.instructions_opt)
        self.scope = self.scope.popScope()