Example #1
0
class TypeChecker(object):

    ttype = {}

    ttype[('+', 'int', 'int')] = 'int'
    ttype[('-', 'int', 'int')] = 'int'
    ttype[('*', 'int', 'int')] = 'int'
    ttype[('/', 'int', 'int')] = 'int'
    ttype[('%', 'int', 'int')] = 'int'
    ttype[('^', 'int', 'int')] = 'int'
    ttype[('|', 'int', 'int')] = 'int'
    ttype[('&', 'int', 'int')] = 'int'
    ttype[('<', 'int', 'int')] = 'int'
    ttype[('>', 'int', 'int')] = 'int'
    ttype[('<=', 'int', 'int')] = 'int'
    ttype[('>=', 'int', 'int')] = 'int'
    ttype[('==', 'int', 'int')] = 'int'
    ttype[('!=', 'int', 'int')] = 'int'
    ttype[('>>', 'int', 'int')] = 'int'
    ttype[('<<', 'int', 'int')] = 'int'
    ttype[('&&', 'int', 'int')] = 'int'
    ttype[('||', 'int', 'int')] = 'int'

    ttype[('+', 'float', 'float')] = 'float'
    ttype[('-', 'float', 'float')] = 'float'
    ttype[('*', 'float', 'float')] = 'float'
    ttype[('/', 'float', 'float')] = 'float'
    ttype[('<', 'float', 'float')] = 'int'
    ttype[('>', 'float', 'float')] = 'int'
    ttype[('<=', 'float', 'float')] = 'int'
    ttype[('>=', 'float', 'float')] = 'int'
    ttype[('==', 'float', 'float')] = 'int'
    ttype[('!=', 'float', 'float')] = 'int'

    ttype[('+', 'float', 'int')] = 'float'
    ttype[('-', 'float', 'int')] = 'float'
    ttype[('*', 'float', 'int')] = 'float'
    ttype[('/', 'float', 'int')] = 'float'
    ttype[('+', 'int', 'float')] = 'float'
    ttype[('-', 'int', 'float')] = 'float'
    ttype[('*', 'int', 'float')] = 'float'
    ttype[('/', 'int', 'float')] = 'float'

    ttype[('<', 'float', 'int')] = 'int'
    ttype[('>', 'float', 'int')] = 'int'
    ttype[('<=', 'float', 'int')] = 'int'
    ttype[('>=', 'float', 'int')] = 'int'
    ttype[('==', 'float', 'int')] = 'int'
    ttype[('!=', 'float', 'int')] = 'int'
    ttype[('<', 'int', 'float')] = 'int'
    ttype[('>', 'int', 'float')] = 'int'
    ttype[('<=', 'int', 'float')] = 'int'
    ttype[('>=', 'int', 'float')] = 'int'
    ttype[('==', 'int', 'float')] = 'int'
    ttype[('!=', 'int', 'float')] = 'int'

    ttype[('+', 'string', 'string')] = 'string'
    ttype[('*', 'string', 'int')] = 'string'
    ttype[('<', 'string', 'string')] = 'int'
    ttype[('>', 'string', 'string')] = 'int'
    ttype[('<=', 'string', 'string')] = 'int'
    ttype[('>=', 'string', 'string')] = 'int'
    ttype[('!=', 'string', 'string')] = 'int'
    ttype[('==', 'string', 'string')] = 'int'

    tconv = {}
    tconv[('int', 'float')] = True

    @staticmethod
    def conversion_possible(type1, type2):
        return TypeChecker.tconv.get((type1, type2)) is not None

    def __init__(self):
        self.scope = SymbolTable(None, 'global')
        self.return_type = None
        self.returned = None
        self.breakable = False

    def visit_BinExpr(self, node):
        type1 = type2 = None

        type1 = node.left.accept(self)

        type2 = node.right.accept(self)

        op = node.operator

        if type1 is None or type2 is None:
            return None

        try:
            return TypeChecker.ttype[(op, type1, type2)]
        except(KeyError):
            print("Cannot evaluate {0} {1} {2} - incompatible types at {3}:{4}".format(type1, op, type2, node.pos[0], node.pos[1]))
        return None

    def visit_Variable(self, node):
        value = self.scope.get(node.name)

        if value != None:
            return value.type
        else:
            print("Undefined variable {0} at {1}:{2}".format(node.name, node.pos[0], node.pos[1]))
            return None


    def visit_If(self, node):
        condition_type = node.condition.accept(self)
        if condition_type != 'int':
            print("Condition must evaluate to integer at {0}:{1}".format(node.condition.pos[0], node.condition.pos[1]))
        node.block_if.accept(self)
        if node.block_else != None:
            node.block_else.accept(self)

    def visit_While(self, node):
        condition_type = node.condition.accept(self)
        if condition_type != 'int':
            print("Condition must evaluate to integer at {0}:{1}".format(node.condition.pos[0], node.condition.pos[1]))
        old_breakable = self.breakable
        self.breakable = True
        node.body.accept(self)
        self.breakable = old_breakable

    def visit_Repeat(self, node):
        condition_type = node.condition.accept(self)
        if condition_type != 'int':
            print("Condition must evaluate to integer at {0}:{1}".format(node.condition.pos[0], node.condition.pos[1]))
        old_breakable = self.breakable
        self.breakable = True
        node.body.accept(self)
        self.breakable = old_breakable

    def visit_Fundef(self, node):
        symbol = self.scope.getDirect(node.name)
        if symbol is None:
            self.scope.put(node.name, node)
        else:
            print("Symbol {0} already defined at {1}:{2}. First defined at {3}:{4}".format(
                node.name, node.pos[0], node.pos[1], symbol.pos[0], symbol.pos[1]))

        # Create new scope for function
        self.scope = SymbolTable(self.scope, node.name)
        # Leave information about return type of the function
        self.return_type = node.return_type
        self.returned = False
        for argument in node.arguments:
            argument.accept(self)
        node.body.accept(self)

        if not self.returned:
            print("No return statement found in function {0} defined at {1}:{2}".format(node.name, node.pos[0], node.pos[1]))
        # Clear information about return type
        self.return_type = None
        self.returned = False
        # Get the hell out of function scope, after its done
        self.scope = self.scope.parent

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

    def visit_Declaration(self, node):
        for init in node.inits:
            init.type = node.type
            defined_var = self.scope.getDirect(init.name.name)
            if defined_var is not None:
                print("Variable {0} already defined at {1}:{2}. First defined at {3}:{4}.".format(
                    init.name, init.pos[0], init.pos[1], defined_var.pos[0], defined_var.pos[1]))
            else:
                self.scope.put(init.name.name, init.value)
                init_type = init.accept(self)
                if init_type != node.type and not TypeChecker.conversion_possible(init_type, node.type):
                    print("Cannot convert {0} to {1} at {2}:{3}".format(init_type, node.type, init.pos[0], init.pos[1]))

    def visit_Init(self, node):
        declaration = self.scope.get(node.name.name)
        if declaration is None:
            print("Undefined variable {0} at {1}:{2}".format(node.name, node.name.pos[0], node.name.pos[1]))
            return None

        value_type = node.value.accept(self)
        return value_type

    def visit_CompoundInstruction(self, node):
        self.scope = SymbolTable(self.scope, "compound")

        for declaration in node.decls:
            declaration.accept(self)
        for instruction in node.instrs:
            instruction.accept(self)

        # Get the hell out of function scope, after its done
        self.scope = self.scope.parent

    def visit_Funcall(self, node):
        function = self.scope.get(node.name.name)

        if function is None:
            print("Function {0} does not exist at {1}:{2}".format(node.name, node.name.pos[0], node.name.pos[1]))
            return None
        elif function.type != "function":
            print("Cannot call as function an ordinary variable {0} at {1}:{2}".format(node.name, node.name.pos[0], node.name.pos[1]))
            return None

        if len(function.arguments) != len(node.args):
            print("Incorrect number of arguments. {0} requires {1}, {2} provided at {3}:{4}".format(function.name, len(function.arguments), len(node.args), node.pos[0], node.pos[1]))
            return None

        # zip reduce because map reduce is for suckers
        arg_pairs = zip(node.args, function.arguments)
        for pair in arg_pairs:
            passed_argument = pair[0]
            defined_argument = pair[1]
            passed_argument_type = passed_argument.accept(self)
            if passed_argument_type != defined_argument.type and not TypeChecker.conversion_possible(passed_argument_type, defined_argument.type):
                print("Cannot convert {0} to {1} at {2}:{3}".format(passed_argument_type, defined_argument.type, passed_argument.pos[0], passed_argument.pos[1]))
                return None
        return function.return_type

    def visit_Assignment(self, node):
        variable = self.scope.get(node.id.name)
        if variable is None:
            print("Undefined variable {0} at {1}:{2}".format(node.id, node.id.pos[0], node.id.pos[1]))
            return None
        elif variable.type == "function":
            print("Cannot assign to function {0} at {1}:{2}".format(node.id, node.id.pos[0], node.id.pos[1]))

        expression_node = node.expr

        assigned_type = expression_node.accept(self)
        if assigned_type is None:
            return None

        if assigned_type != variable.type and not TypeChecker.conversion_possible(assigned_type, variable.type):
            print("Cannot assign {0} to {1} at {2}:{3}".format(assigned_type, variable.type, node.id.pos[0], node.id.pos[1]))
            return None
        return assigned_type

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

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

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

    def visit_AST(self, node):
        for decl in node.decl:
            decl.accept(self)
        for fundef in node.fundef:
            fundef.accept(self)
        for instr in node.instr:
            instr.accept(self)

    def visit_Return(self, node):
        return_type = node.expr.accept(self)
        if self.return_type == None:
            print("Return statement used outside function at {0}:{1}".format(node.pos[0], node.pos[1]))
        elif return_type != None and self.return_type != return_type and not TypeChecker.conversion_possible(return_type, self.return_type):
            print("Impossible to convert {0} to {1} in return statement at {2}:{3}".format(return_type, self.return_type, node.pos[0], node.pos[1]))
        else:
            self.returned = True
        return return_type

    def visit_Continue(self, node):
        if not self.breakable:
            print("Nothing to 'continue' at {0}:{1}".format(node.pos[0], node.pos[1]))

    def visit_Break(self, node):
        if not self.breakable:
            print("Nothing to 'break' from at {0}:{1}".format(node.pos[0], node.pos[1]))