Example #1
0
    def eq_type(self, t1, t2):
        """
        Helper function to check if two given type node is that of the
        same type. Precondition is that both t1 and t2 are that of class Type
        """
        t1_is_type = isinstance(t1, ast.Type) or isinstance(
            t1, ast.Array_Type) or isinstance(t1, ast.Dict_Type)
        t2_is_type = isinstance(t2, ast.Type) or isinstance(
            t2, ast.Array_Type) or isinstance(t2, ast.Dict_Type)

        if not (t1_is_type and t2_is_type):
            raise ParseError(
                f"eq_type invoked on non-type objects, {t1.__class__} and {t2.__class__}"
            )

        # {} <- type name dict
        # {} <- ast.dict_type(None, None)
        if isinstance(t1, ast.Type) and isinstance(t2, ast.Type):
            return t1.name == t2.name
        elif isinstance(t1, ast.Array_Type) and isinstance(t2, ast.Array_Type):
            return self.eq_type(t1.innerType, t2.innerType)
        elif isinstance(t1, ast.Dict_Type) and isinstance(t2, ast.Dict_Type):
            t2_is_empty_dict = self.eq_type(
                t2.key_type, ast.Type("NullType")) and self.eq_type(
                    t2.val_type, ast.Type("NullType"))
            if t2_is_empty_dict:
                return True

            return self.eq_type(t1.key_type, t2.key_type) and self.eq_type(
                t1.val_type, t2.val_type)
        else:
            return False
Example #2
0
    def check_ArrayBuiltinCall(self, node: ast.ArrayBuiltinCall,
                               st: SymbolTable):
        """
        builtinFunction is one of .pop, .append, or .remove.
        """

        # first, verify that variable is an array
        array_type = st.lookup_variable(node.arrayID, node.coord)
        if not isinstance(array_type, ast.Array_Type):
            raise ParseError(
                f"Tried to call {node.builtinFunction} on non array variable of type {array_type.name}",
                node.coord,
            )

        # check if argumentExpression is of the right type.
        inner_type = array_type.innerType
        argument_expr_type = self.typecheck(node.argumentExpression)

        if node.builtinFunction == ".pop":
            if not self.eq_type(argument_expr_type, ast.Type("int")):
                raise ParseError(
                    f"Tried to pop from array with non integer index of type {argument_expr_type.name}",
                    node.coord,
                )
            else:
                return inner_type
        else:
            if not self.eq_type(argument_expr_type, inner_type):
                raise ParseError(
                    f"Mismatch of types on call of {node.builtinFunction}, expected {inner_type.name}"
                )
            else:
                return ast.Type("NullType")
Example #3
0
    def check_StmtList(self, node: ast.StmtList, st):
        """
        Iterates through a StmtList, and determines the type of each statement.
        Used for every scope be it if statements, while loops, etc...
        """
        retTypeFound = False
        retType = ast.Type("NullType")
        for stmt in node.stmt_lst:
            if retTypeFound:
                raise ParseError(
                    f"Found extra statement(s) after function returned",
                    node.coord)
            elif isinstance(stmt, ast.RetStmt) or isinstance(
                    stmt, ast.BreakStmt):
                retTypeFound = True
                retType = self.typecheck(stmt, st)
            elif isinstance(stmt, ast.IfStmt):
                ifStmtType = self.typecheck(stmt, st)
                if not self.eq_type(ifStmtType, ast.Type("NullType")):
                    retTypeFound = True
                    retType = ifStmtType
            else:
                stmtType = self.typecheck(stmt, st)

        return retType
Example #4
0
    def check_WhileStmt(self, node: ast.WhileStmt, st: SymbolTable):
        cond_type = self.typecheck(node.cond, st)
        if not self.eq_type(cond_type, ast.Type("bool")):
            raise ParseError("Condition in While Statement not of type bool.",
                             node.coord)

        st.push_while_scope()
        self.typecheck(node.body, st)
        st.pop_while_scope()
        return ast.Type("NullType")
Example #5
0
    def check_ElifStmt(self, node: ast.ElifStmt, st: SymbolTable):
        cond_type = self.typecheck(node.cond, st)
        if not self.eq_type(cond_type, ast.Type("bool")):
            raise ParseError("Condition in Elif not of type bool.", node.coord)

        trueBodyType = self.typecheck(node.true_body, st)
        if node.false_body is not None:
            falseBodyType = self.typecheck(node.false_body, st)

        if not node.false_body or self.eq_type(falseBodyType,
                                               ast.Type("NullType")):
            return ast.Type("NullType")
        return trueBodyType
Example #6
0
    def check_DictBuiltinCall(self, node: ast.DictBuiltinCall,
                              st: SymbolTable):
        # Check that ID is actually dict
        # idType = st.lookup_variable(node.dictID, node.coord)
        # if not self.eq_type(idType, ast.Type("dict")):
        #     raise ParseError(
        #         f"Tried to call {node.builtinFunction} on non dictionary variable of type {idType}",
        #         node.coord,
        #     )

        key_type, val_type = st.lookup_dict(node.dictID, node.coord)
        arg_type = self.typecheck(node.argumentExpression.exprs[0], st)
        if not self.eq_type(key_type, arg_type):
            raise ParseError(
                f"Expected Key Type: {key_type}, but recieved Type {arg_type} for dict {node.dictID}",
                node.coord,
            )
        if node.builtinFunction == ".get":
            return val_type
        elif node.builtinFunction == ".set":
            if len(node.argumentExpression.exprs) != 2:
                raise ParseError(
                    f"Builtin function 'set' requires 2 arguments.",
                    node.coord)
            real_val_type = self.typecheck(node.argumentExpression.exprs[1],
                                           st)
            if not self.eq_type(val_type, real_val_type):
                raise ParseError(
                    f"Expected Value Type: {val_type}, but recieved Type {real_val_type} for dict {node.dictID}",
                    node.coord,
                )
            return ast.Type("NullType")
Example #7
0
    def check_IfStmt(self, node: ast.IfStmt, st: SymbolTable):
        cond_type = self.typecheck(node.cond, st)
        if not self.eq_type(cond_type, ast.Type("bool")):
            raise ParseError(
                f"Condition of If statment was type {cond_type.name}, expected bool",
                node.coord,
            )

        trueBodyType = self.typecheck(node.true_body, st)
        if node.false_body is not None:
            falseBodyType = self.typecheck(node.false_body, st)

        if not node.false_body or self.eq_type(falseBodyType,
                                               ast.Type("NullType")):
            return ast.Type("NullType")
        return trueBodyType
Example #8
0
 def p_primitive_type(self, p):
     """
     primitive_type : INT
                     | STRING
                     | BOOLEAN
                     | NULLTYPE
     """
     p[0] = ast.Type(p[1], p.lineno(1))
Example #9
0
    def check_UnaryOp(self, node: ast.UnaryOp, st):
        """
        | "not" expression
        | "-"expression # only for integer types
        """
        expression_type = self.typecheck(node.expr, st)
        if node.op == "not":
            if not self.eq_type(expression_type, ast.Type("bool")):
                raise ParseError(
                    f"Tried calling 'not' with non boolean variable of type {expression_type.name}"
                )
        else:
            if not self.eq_type(expression_type, ast.Type("int")):
                raise ParseError(
                    f"Tried calling '-' with non integer variable of type {expression_type.name}"
                )

        return expression_type
Example #10
0
    def check_VarAssign(self, node: ast.VarAssign, st: SymbolTable):
        """
        variableName "=" expression # variableName must already be declared
        """

        declaredType = st.lookup_variable(node.name, node.coord)
        expressionType = self.typecheck(node.expr, st)
        if not self.eq_type(declaredType, expressionType):
            raise ParseError(
                f"Variable {node.name} was declared as type {declaredType.name} but was assigned value of {expressionType.name} type",
                node.coord,
            )
        return ast.Type("NullType")
Example #11
0
    def check_BinOp(self, node: ast.BinOp, st):
        """
        NOTE
        You should also check if the type of the left and right operation
        makes sense in the context of the operator (ie., you should not be
        able to add/subtract/multiply/divide strings or booleans). In this
        example, it only checks if the left and right expressions are of the
        same type, but that won't be sufficient for your project.

        | expression "+" expression # integer or array types only
        | expression "-" expression # integer type only
        | expression "*" expression # integer type only
        | expression "/" expression # integer type only
        | expression "and" expression # boolean types only
        | expression "or" expression  # boolean types only
        | expression "==" expression
        | expression "!=" expression
        | expression "%" expression # integer type only
        | expression "<" expression # integer type only
        | expression ">" expression # integer type only
        | expression "<=" expression # integer type only
        | expression ">=" expression # integer type only

        """
        left_type = self.typecheck(node.left, st)
        right_type = self.typecheck(node.right, st)

        if node.op in {"+", "-", "*", "/", "%", "<", ">", "<=", ">="}:
            if (not self.eq_type(left_type, ast.Type("int"))) or (
                    not self.eq_type(right_type, ast.Type("int"))):
                raise ParseError(
                    f"Tried to call integer binary operation on {left_type.name} and {right_type.name} types",
                    node.coord,
                )
            return (ast.Type("bool")
                    if node.op in {"<", ">", "<=", ">="} else ast.Type("int"))

        if node.op in {"and", "or"}:
            if (not self.eq_type(left_type, ast.Type("bool"))) or (
                    not self.eq_type(right_type, ast.Type("bool"))):
                raise ParseError(
                    f"Tried to call boolean binary operation on {left_type.name} and {right_type.name} types",
                    node.coord,
                )
            return ast.Type("bool")

        return ast.Type("bool")
Example #12
0
    def check_ArraySlice(self, node: ast.ArraySlice, st: SymbolTable):
        """
        | arrayName"["expression ":" expression "]" # expressions must be of type integer
        """
        # first check that arrayName is a valid array ID
        array_type = st.lookup_variable(node.name, node.coord)
        if not isinstance(array_type, ast.Array_Type):
            raise ParseError(
                f"Tried to slice non array variable of type {array_type.name}",
                node.coord,
            )

        # then check that expression is of type int
        expr1_type = self.typecheck(node.expr1)
        expr2_type = self.typecheck(node.expr2)
        if not self.eq_type(expr1_type, ast.Type("int")) or not self.eq_type(
                expr2_type, ast.Type("int")):
            raise ParseError(
                f"Tried to slice array with non integer indices of types {expr1_type.name}, {expr2_type.name}",
                node.coord,
            )

        # return type of the array
        return array_type
Example #13
0
    def check_VarDecl(self, node: ast.VarDecl, st):
        """
        Checks if the expr declared with the variable has the same type that the
        variable was declared with.
        """
        declared_type = node.type
        expression_type = self.typecheck(node.expr, st)
        # If array Type expression Type can be [None]

        if not self.eq_type(declared_type, expression_type):
            raise ParseError(
                f"Tried to declare variable of type {declared_type.name} but with value of type {expression_type.name}",
                node.coord,
            )
        if isinstance(declared_type, ast.Dict_Type):
            st.declare_dict(node.name, node.type.key_type, node.type.val_type,
                            node.coord)
        st.declare_variable(node.name, node.type, node.coord)

        return ast.Type("NullType")
Example #14
0
    def check_ArrayIndex(self, node: ast.ArrayIndex, st: SymbolTable):
        """
        | arrayName"["expression"]" # expression must be of type integer
        """
        # first check that arrayName is a valid array ID
        array_type = st.lookup_variable(node.name, node.coord)
        if not isinstance(array_type, ast.Array_Type):
            raise ParseError(
                f"Tried to index non array variable of type {array_type.name}",
                node.coord,
            )

        # then check that expression is of type int
        expr_type = self.typecheck(node.expr, st)
        if not self.eq_type(expr_type, ast.Type("int")):
            raise ParseError(
                f"Tried to index an array with non integer index of type {expr_type.name}",
                node.coord,
            )

        # return inner typer of the array
        return array_type.innerType
Example #15
0
    def check_FunctionDefn(self, node: ast.FunctionDefn, st: SymbolTable):
        """
        "def" functionName "(" parameters ")" "->" type ":" functionBlock
        """
        # add each param to the symboltable
        # use symbol table to type check functionblock based on return type of function
        # if type check passes, then remove params from symbol table
        # save functional type in symbol table

        st.push_scope()
        st.push_return_scope(node.ret_type)
        # pushes expected return type to symbol table

        # Go through the parameters
        param_types = []
        if node.params.params:
            for param in node.params.params:
                if isinstance(param.type, ast.Dict_Type):
                    st.declare_dict(param.name, param.type.key_type,
                                    param.type.val_type, param.coord)
                st.declare_variable(param.name, param.type, param.coord)
                param_types.append(param.type)

        body_return_type = self.typecheck(node.body, st)

        st.pop_return_scope()
        st.pop_scope()

        if not self.eq_type(node.ret_type, body_return_type):
            raise ParseError(
                f"Return type of function body {body_return_type.name} doesn't match declared return type of function {node.ret_type.name}",
                node.coord,
            )

        st.declare_function(node.name, param_types, node.ret_type, node.coord)

        return ast.Type("NullType")
Example #16
0
 def p_empty_dict(self, p):
     """
     empty_dict : EMPTYDICT
     """
     p[0] = ast.Constant(ast.Dict_Type(ast.Type("NullType"), ast.Type("NullType")), p[1], p.lineno(1))
Example #17
0
 def p_literal_string(self, p):
     """
     literal : STRINGLITERAL
     """
     p[0] = ast.Constant(ast.Type("str"), p[1], p.lineno(1))
Example #18
0
    def check_BreakStmt(self, node: ast.BreakStmt, st: SymbolTable):
        if not st.is_in_while_scope():
            raise ParseError("Attempted to break from outside while loop",
                             node.coord)

        return ast.Type("NullType")
Example #19
0
 def p_literal_boolean(self, p):
     """
     literal : TRUE
             | FALSE
     """
     p[0] = ast.Constant(ast.Type("bool"), p[1], p.lineno(1))
Example #20
0
 def p_literal_number(self, p):
     """
     literal : NUMBER
     """
     p[0] = ast.Constant(ast.Type("int"), p[1], p.lineno(1))