Exemple #1
0
    def _get_args_with_prototype(self, func_ctype, il_code, symbol_table, c):
        """Return list of argument ILValues for function this represents.

        Use _get_args_with_prototype when the function this represents
        has a prototype. This function converts all passed arguments to
        expected types.
        """
        arg_types = func_ctype.args

        if len(arg_types) != len(self.args):
            err = ("incorrect number of arguments for function call"
                   f" (expected {len(arg_types)}, have {len(self.args)})")

            if self.args:
                raise CompilerError(err, self.args[-1].r)
            else:
                raise CompilerError(err, self.r)

        final_args = []
        for arg_given, arg_type in zip(self.args, arg_types):
            arg = arg_given.make_il(il_code, symbol_table, c)
            check_cast(arg, arg_type, arg_given.r)
            final_args.append(
                set_type(arg, arg_type.make_unqual(), il_code))
        return final_args
Exemple #2
0
    def make_il(self, il_code, symbol_table, c):
        """Make code for this node."""
        lval = self.expr.lvalue(il_code, symbol_table, c)

        if not lval or not lval.modable():
            err = f"operand of {self.descrip} operator not a modifiable lvalue"
            raise CompilerError(err, self.expr.r)

        val = self.expr.make_il(il_code, symbol_table, c)
        one = ILValue(val.ctype)
        if val.ctype.is_arith():
            il_code.register_literal_var(one, 1)
        elif val.ctype.is_pointer() and val.ctype.arg.is_complete():
            il_code.register_literal_var(one, val.ctype.arg.size)
        elif val.ctype.is_pointer():
            # technically, this message is not quite right because for
            # non-object types, a type can be neither complete nor incomplete
            err = "invalid arithmetic on pointer to incomplete type"
            raise CompilerError(err, self.expr.r)
        else:
            err = f"invalid type for {self.descrip} operator"
            raise CompilerError(err, self.expr.r)

        new_val = ILValue(val.ctype)

        if self.return_new:
            il_code.add(self.cmd(new_val, val, one))
            lval.set_to(new_val, il_code, self.expr.r)
            return new_val
        else:
            old_val = ILValue(val.ctype)
            il_code.add(value_cmds.Set(old_val, val))
            il_code.add(self.cmd(new_val, val, one))
            lval.set_to(new_val, il_code, self.expr.r)
            return old_val
Exemple #3
0
    def make_il(self, il_code, symbol_table, c):
        """Make code for this node."""

        # This is of function pointer type, so func.arg is the function type.
        func = self.func.make_il(il_code, symbol_table, c)

        if not func.ctype.is_pointer() or not func.ctype.arg.is_function():
            descrip = "called object is not a function pointer"
            raise CompilerError(descrip, self.func.r)
        elif (func.ctype.arg.ret.is_incomplete()
              and not func.ctype.arg.ret.is_void()):
            # TODO: C11 spec says a function cannot return an array type,
            # but I can't determine how a function would ever be able to return
            # an array type.
            descrip = "function returns non-void incomplete type"
            raise CompilerError(descrip, self.func.r)

        if func.ctype.arg.no_info:
            final_args = self._get_args_without_prototype(
                il_code, symbol_table, c)
        else:
            final_args = self._get_args_with_prototype(
                func.ctype.arg, il_code, symbol_table, c)

        ret = ILValue(func.ctype.arg.ret)
        il_code.add(control_cmds.Call(func, final_args, ret))
        return ret
Exemple #4
0
def check_cast(il_value, ctype, range):
    """Emit warnings/errors of casting il_value to given ctype.

    This method does not actually cast the values. If values cannot be
    cast, an error is raised by this method.

    il_value - ILValue to convert
    ctype - CType to convert to
    range - Range for error reporting

    """
    # Cast between compatible types is always okay
    if il_value.ctype.weak_compat(ctype):
        return

    # Cast between arithmetic types is always okay
    if ctype.is_arith() and il_value.ctype.is_arith():
        return

    # Cast between weak compatible structs is okay
    if (ctype.is_struct_union() and il_value.ctype.is_struct_union() and
         il_value.ctype.weak_compat(ctype)):
        return

    elif ctype.is_pointer() and il_value.ctype.is_pointer():

        # both operands are pointers to qualified or unqualified versions
        # of compatible types, and the type pointed to by the left has all
        # the qualifiers of the type pointed to by the right
        if (ctype.arg.weak_compat(il_value.ctype.arg) and
             (not il_value.ctype.arg.const or ctype.arg.const)):
            return

        # Cast between void pointer and pointer to object type okay
        elif (ctype.arg.is_void() and il_value.ctype.arg.is_object() and
              (not il_value.ctype.arg.const or ctype.arg.const)):
            return

        elif (ctype.arg.is_object() and il_value.ctype.arg.is_void() and
              (not il_value.ctype.arg.const or ctype.arg.const)):
            return

        # error on any other kind of pointer cast - TODO: better errors
        else:
            with report_err():
                err = "conversion from incompatible pointer type"
                raise CompilerError(err, range)
            return

    # Cast from null pointer constant to pointer okay
    elif ctype.is_pointer() and getattr(il_value.literal, "val", None) == 0:
        return

    # Cast from pointer to boolean okay
    elif ctype.is_bool() and il_value.ctype.is_pointer():
        return

    else:
        err = "invalid conversion between types"
        raise CompilerError(err, range)
Exemple #5
0
    def _nonarith(self, left, right, il_code):
        """Check equality of non-arithmetic expressions."""

        # If either operand is a null pointer constant, cast it to the
        # other's pointer type.
        if left.ctype.is_pointer() and right.null_ptr_const:
            right = set_type(right, left.ctype, il_code)
        elif right.ctype.is_pointer() and left.null_ptr_const:
            left = set_type(left, right.ctype, il_code)

        # If both operands are not pointer types, quit now
        if not left.ctype.is_pointer() or not right.ctype.is_pointer():
            with report_err():
                err = "comparison between incomparable types"
                raise CompilerError(err, self.op.r)

        # If one side is pointer to void, cast the other to same.
        elif left.ctype.arg.is_void():
            check_cast(right, left.ctype, self.op.r)
            right = set_type(right, left.ctype, il_code)
        elif right.ctype.arg.is_void():
            check_cast(left, right.ctype, self.op.r)
            left = set_type(left, right.ctype, il_code)

        # If both types are still incompatible, warn!
        elif not left.ctype.compatible(right.ctype):
            with report_err():
                err = "comparison between distinct pointer types"
                raise CompilerError(err, self.op.r)

        # Now, we can do comparison
        out = ILValue(ctypes.integer)
        il_code.add(self.eq_il_cmd(out, left, right))
        return out
Exemple #6
0
    def make_il(self, il_code, symbol_table, c):
        """Make code for this node."""
        lval = self.expr.lvalue(il_code, symbol_table, c)

        if not lval or not lval.modable():
            err = "operand of {} operator not a modifiable lvalue"
            raise CompilerError(err.format(self.descrip), self.expr.r)

        val = self.expr.make_il(il_code, symbol_table, c)
        one = ILValue(val.ctype)
        if val.ctype.is_arith():
            il_code.register_literal_var(one, 1)
        elif val.ctype.is_pointer() and val.ctype.arg.is_complete():
            il_code.register_literal_var(one, val.ctype.arg.size)
        elif val.ctype.is_pointer() and not val.ctype.arg.is_complete():
            err = "invalid arithmetic on pointer to incomplete type"
            raise CompilerError(err, self.op.r)
        else:
            err = "invalid type for {} operator"
            raise CompilerError(err.format(self.descrip), self.expr.r)

        new_val = ILValue(val.ctype)

        if self.return_new:
            il_code.add(self.cmd(new_val, val, one))
            lval.set_to(new_val, il_code, self.expr.r)
            return new_val
        else:
            old_val = ILValue(val.ctype)
            il_code.add(value_cmds.Set(old_val, val))
            il_code.add(self.cmd(new_val, val, one))
            lval.set_to(new_val, il_code, self.expr.r)
            return old_val
Exemple #7
0
    def make_il(self, il_code, symbol_table, c):
        # ILValue for storing the output of this boolean operation
        out = ILValue(ctypes.integer)

        # ILValue for initial value of output variable.
        init = ILValue(ctypes.integer)
        il_code.register_literal_var(init, self.initial_value)

        # ILValue for other value of output variable.
        other = ILValue(ctypes.integer)
        il_code.register_literal_var(other, 1 - self.initial_value)

        # Label which immediately precedes the line which sets out to 0 or 1.
        set_out = il_code.get_label()

        # Label which skips the line which sets out to 0 or 1.
        end = il_code.get_label()

        err = "'{}' operator requires scalar operands".format(str(self.op))
        left = self.left.make_il(il_code, symbol_table, c)
        if not left.ctype.is_scalar():
            raise CompilerError(err, self.left.r)

        il_code.add(value_cmds.Set(out, init))
        il_code.add(self.jump_cmd(left, set_out))
        right = self.right.make_il(il_code, symbol_table, c)
        if not right.ctype.is_scalar():
            raise CompilerError(err, self.right.r)

        il_code.add(self.jump_cmd(right, set_out))
        il_code.add(control_cmds.Jump(end))
        il_code.add(control_cmds.Label(set_out))
        il_code.add(value_cmds.Set(out, other))
        il_code.add(control_cmds.Label(end))
        return out
Exemple #8
0
    def check_main_type(self):
        """Check if function signature matches signature expected of main.

        Raises an exception if this function signature does not match the
        function signature expected of the main function.
        """
        if not self.ctype.ret.compatible(ctypes.integer):
            err = "'main' function must have integer return type"
            raise CompilerError(err, self.range)
        if len(self.ctype.args) not in {0, 2}:
            err = "'main' function must have 0 or 2 arguments"
            raise CompilerError(err, self.range)
        if self.ctype.args:
            first = self.ctype.args[0]
            second = self.ctype.args[1]

            if not first.compatible(ctypes.integer):
                err = "first parameter of 'main' must be of integer type"
                raise CompilerError(err, self.range)

            is_ptr_array = (second.is_pointer() and
                            (second.arg.is_pointer() or second.arg.is_array()))

            if not is_ptr_array or not second.arg.arg.compatible(ctypes.char):
                err = "second parameter of 'main' must be like char**"
                raise CompilerError(err, self.range)
Exemple #9
0
def read_include_filename(line, start):
    """Read a filename that follows a #include directive.

    Expects line[start] to be one of `<` or `"`, then reads characters until a
    matching symbol is reached. Then, returns as a string the characters
    read including the initial and final symbol markers. The index returned
    is that of the closing token in the filename.
    """
    if start < len(line) and line[start].c == '"':
        end = '"'
    elif start < len(line) and line[start].c == "<":
        end = ">"
    else:
        descrip = "expected \"FILENAME\" or <FILENAME> after include directive"
        if start < len(line):
            char = line[start]
        else:
            char = line[-1]

        raise CompilerError(descrip, char.r)

    i = start + 1
    try:
        while line[i].c != end:
            i += 1
    except IndexError:
        descrip = "missing terminating character for include filename"
        raise CompilerError(descrip, line[start].r)

    return chunk_to_str(line[start:i + 1]), i
Exemple #10
0
    def _check_struct_member_decl_info(self, decl_info, kind, members):
        """Check whether given decl_info object is a valid struct member."""

        if decl_info.identifier is None:
            # someone snuck an abstract declarator into here!
            err = f"missing name of {kind} member"
            raise CompilerError(err, decl_info.range)

        if decl_info.storage is not None:
            err = f"cannot have storage specifier on {kind} member"
            raise CompilerError(err, decl_info.range)

        if decl_info.ctype.is_function():
            err = f"cannot have function type as {kind} member"
            raise CompilerError(err, decl_info.range)

        # TODO: 6.7.2.1.18 (allow flexible array members)
        if not decl_info.ctype.is_complete():
            err = f"cannot have incomplete type as {kind} member"
            raise CompilerError(err, decl_info.range)

        # TODO: 6.7.2.1.13 (anonymous structs)
        if decl_info.identifier.content in members:
            err = f"duplicate member '{decl_info.identifier.content}'"
            raise CompilerError(err, decl_info.identifier.r)
Exemple #11
0
    def _get_args_with_prototype(self, func_ctype, il_code, symbol_table, c):
        """Return list of argument ILValues for function this represents.

        Use _get_args_with_prototype when the function this represents
        has a prototype. This function converts all passed arguments to
        expected types.
        """
        # if only parameter is of type void, expect no arguments
        if (len(func_ctype.args) == 1 and
             func_ctype.args[0].is_void()):
            arg_types = []
        else:
            arg_types = func_ctype.args
        if len(arg_types) != len(self.args):
            err = ("incorrect number of arguments for function call" +
                   " (expected {}, have {})").format(len(arg_types),
                                                     len(self.args))

            if self.args:
                raise CompilerError(err, self.args[-1].r)
            else:
                raise CompilerError(err, self.tok.r)

        final_args = []
        for arg_given, arg_type in zip(self.args, arg_types):
            arg = arg_given.make_il(il_code, symbol_table, c)
            check_cast(arg, arg_type, arg_given.r)
            final_args.append(set_type(arg, arg_type, il_code))
        return final_args
Exemple #12
0
    def process(self, decl_info, il_code, symbol_table, c):
        """Process given DeclInfo object.

        This includes error checking, adding the variable to the symbol
        table, and registering it with the IL.
        """
        if not decl_info.identifier:
            err = "missing identifier name in declaration"
            raise CompilerError(err, decl_info.range)

        # TODO: prohibit all declarations of incomplete types?
        if decl_info.ctype == ctypes.void:
            err = "variable of void type declared"
            raise CompilerError(err, decl_info.range)

        var = symbol_table.add(decl_info.identifier, decl_info.ctype)

        # Variables declared to be EXTERN
        if decl_info.storage == DeclInfo.EXTERN:
            il_code.register_extern_var(var, decl_info.identifier.content)

            # Extern variable should not have initializer
            if decl_info.init:
                err = "extern variable has initializer"
                raise CompilerError(err, decl_info.range)

        # Variables declared to be static
        elif decl_info.storage == DeclInfo.STATIC:
            # These should be in .data section, but not global
            raise NotImplementedError("static variables unsupported")

        # Global variables
        elif c.is_global:
            # Global functions are extern by default
            if decl_info.ctype.is_function():
                il_code.register_extern_var(var, decl_info.identifier.content)
            else:
                # These should be common if uninitialized, or data if
                # initialized
                raise NotImplementedError(
                    "non-extern global variables unsupported")

        # Local variables
        else:
            il_code.register_local_var(var)

        # Initialize variable if needed
        if decl_info.init:
            init_val = decl_info.init.make_il(il_code, symbol_table, c)
            lval = DirectLValue(var)

            if lval.ctype().is_arith() or lval.ctype().is_pointer():
                lval.set_to(init_val, il_code, decl_info.identifier.r)
            else:
                err = "declared variable is not of assignable type"
                raise CompilerError(err, decl_info.range)
Exemple #13
0
    def add_variable(self, identifier, ctype, defined, linkage, storage):
        """Add an identifier with the given name and type to the symbol table.

        identifier (Token) - Identifier to add, for error purposes.
        ctype (CType) - C type of the identifier we're adding.
        defined - one of DEFINED, UNDEFINED, or TENTATIVE
        linkage - one of INTERNAL, EXTERNAL, or None
        storage - STATIC, AUTOMATIC, or None

        return (ILValue) - the ILValue added
        """
        name = identifier.content

        # if it's already declared in this scope
        if name in self.tables[-1].vars:
            var = self.tables[-1].vars[name]
            if isinstance(var, CType):
                err = f"redeclared type definition '{name}' as variable"
                raise CompilerError(err, identifier.r)
            if defined == self.def_state[var] == self.DEFINED:
                raise CompilerError(f"redefinition of '{name}'", identifier.r)
            if linkage != self.linkage_type.get(var, None):
                err = f"redeclared '{name}' with different linkage"
                raise CompilerError(err, identifier.r)
        elif linkage and name in self.linkages[linkage]:
            var = self.linkages[linkage][name]
        else:
            var = ILValue(ctype)

        # Verify new type is compatible with previous type (if there was one)
        if not var.ctype.compatible(ctype):
            err = f"redeclared '{name}' with incompatible type"
            raise CompilerError(err, identifier.r)
        else:
            # Update type of stored variable (in case this declaration
            # completed an object type)
            var.ctype = ctype

        self.tables[-1].vars[name] = var

        # Set this variable's linkage if it has one
        if linkage:
            self.linkages[linkage][name] = var
            self.linkage_type[var] = linkage

        self.def_state[var] = max(self.def_state.get(var, 0), defined)

        # If this variable has not been assigned a storage duration, or the
        # previous storage duration was None, assign it this storage duration.
        if not self.storage.get(var, None):
            self.storage[var] = storage

        self.names[var] = name
        return var
Exemple #14
0
    def process_typedef(self, symbol_table):
        """Process type declarations."""

        if self.init:
            err = "typedef cannot have initializer"
            raise CompilerError(err, self.range)

        if self.body:
            err = "function definition cannot be a typedef"
            raise CompilerError(err, self.range)

        symbol_table.add_typedef(self.identifier, self.ctype)
Exemple #15
0
def process(tokens, this_file, defineDict={}, includeList=[]):
    """Process the given tokens and return the preprocessed token list."""
    #for token in tokens:
    #    print(token)
    processed = []
    i = 0
    while i < len(tokens) - 2:
        if (tokens[i].kind == token_kinds.pound
                and tokens[i + 1].kind == token_kinds.identifier
                and tokens[i + 1].content == "include"
                and tokens[i + 2].kind == token_kinds.include_file):

            # Replace tokens[i] -> tokens[i+2] with preprocessed contents of
            # the included file.
            try:
                file, filename = read_file(tokens[i + 2].content, this_file)
                if filename not in includeList:
                    includeList.append(filename)
                    lexTokens, _ = lexer.tokenize(file, filename)
                    new_tokens = process(lexTokens, filename, defineDict,
                                         includeList)
                    processed += new_tokens

            except IOError:
                error_collector.add(
                    CompilerError("unable to read included file",
                                  tokens[i + 2].r))

            i += 3

        # Ignore defines. Currently the value of the define is not in the token list
        elif (tokens[i].kind == token_kinds.pound
              and tokens[i + 1].kind == token_kinds.identifier
              and tokens[i + 1].content == "define"):

            i += 3

        else:
            # Here we apply the Define dictionary

            if str(tokens[i]) in defineDict:
                if defineDict[str(tokens[i])].isdigit():
                    tokens[i].kind = token_kinds.number
                else:
                    error_collector.add(
                        CompilerError("Define value is not a number",
                                      tokens[i].r))
                tokens[i].content = defineDict[str(tokens[i])]

            processed.append(tokens[i])
            i += 1

    return processed + tokens[i:]
Exemple #16
0
    def _nonarith(self, left, right, il_code):
        """Compare non-arithmetic expressions."""

        if not left.ctype.is_pointer() or not right.ctype.is_pointer():
            err = "comparison between incomparable types"
            raise CompilerError(err, self.op.r)
        elif not left.ctype.compatible(right.ctype):
            err = "comparison between distinct pointer types"
            raise CompilerError(err, self.op.r)

        out = ILValue(ctypes.integer)
        il_code.add(self.comp_cmd(out, left, right))
        return out
Exemple #17
0
    def sizeof_ctype(self, ctype, range, il_code):
        """Raise CompilerError if ctype is not valid as sizeof argument."""

        if ctype.is_function():
            err = "sizeof argument cannot have function type"
            raise CompilerError(err, range)

        if ctype.is_incomplete():
            err = "sizeof argument cannot have incomplete type"
            raise CompilerError(err, range)

        out = ILValue(ctypes.unsig_longint)
        il_code.register_literal_var(out, ctype.size)
        return out
Exemple #18
0
def add_chunk(chunk, tokens):
    """Convert chunk into a token if possible and add to tokens.

    If chunk is non-empty but cannot be made into a token, this function
    records a compiler error. We don't need to check for symbol kind tokens
    here because they are converted before they are shifted into the chunk.

    chunk - Chunk to convert into a token, as list of Tagged characters.
    tokens (List[Token]) - List of the tokens thusfar parsed.

    """
    if chunk:
        range = Range(chunk[0].p, chunk[-1].p)

        keyword_kind = match_keyword_kind(chunk)
        if keyword_kind:
            tokens.append(Token(keyword_kind, r=range))
            return

        number_string = match_number_string(chunk)
        if number_string:
            tokens.append(Token(token_kinds.number, number_string, r=range))
            return

        identifier_name = match_identifier_name(chunk)
        if identifier_name:
            tokens.append(Token(
                token_kinds.identifier, identifier_name, r=range))
            return

        descrip = "unrecognized token at '{}'".format(chunk_to_str(chunk))
        raise CompilerError(descrip, range)
Exemple #19
0
def process(tokens, this_file):
    """Process the given tokens and return the preprocessed token list."""

    processed = []
    i = 0
    while i < len(tokens) - 2:
        if (tokens[i].kind == token_kinds.pound
                and tokens[i + 1].kind == token_kinds.identifier
                and tokens[i + 1].content == "include"
                and tokens[i + 2].kind == token_kinds.include_file):

            # Replace tokens[i] -> tokens[i+2] with preprocessed contents of
            # the included file.
            file, filename = read_file(tokens[i + 2].content, this_file)
            if not file:
                error_collector.add(
                    CompilerError("unable to read included file",
                                  tokens[i + 2].r))
            else:
                new_tokens = process(lexer.tokenize(file, filename), filename)
                processed += new_tokens

            i += 3

        else:
            processed.append(tokens[i])
            i += 1

    return processed + tokens[i:]
Exemple #20
0
    def make_il(self, il_code, symbol_table, c):
        """Make IL code for returning this value."""

        if self.return_value and not c.return_type.is_void():
            il_value = self.return_value.make_il(il_code, symbol_table, c)
            check_cast(il_value, c.return_type, self.return_value.r)
            ret = set_type(il_value, c.return_type, il_code)
            il_code.add(control_cmds.Return(ret))
        elif self.return_value and c.return_type.is_void():
            err = "function with void return type cannot return value"
            raise CompilerError(err, self.r)
        elif not self.return_value and not c.return_type.is_void():
            err = "function with non-void return type must return value"
            raise CompilerError(err, self.r)
        else:
            il_code.add(control_cmds.Return())
Exemple #21
0
    def add_typedef(self, identifier, ctype):
        """Add a type definition to the symbol table."""

        name = identifier.content
        if name in self.tables[-1].vars:
            old_ctype = self.tables[-1].vars[name]
            if isinstance(old_ctype, ILValue):
                err = f"'{name}' redeclared as type definition in same scope"
                raise CompilerError(err, identifier.r)
            elif not old_ctype.compatible(ctype):
                err = f"'{name}' redeclared as incompatible type in same scope"
                raise CompilerError(err, identifier.r)
            else:
                return

        self.tables[-1].vars[name] = ctype
Exemple #22
0
    def extract_params(self, decl):
        """Return the parameter list for this function."""

        identifiers = []
        func_decl = None
        while decl and not isinstance(decl, decl_nodes.Identifier):
            if isinstance(decl, decl_nodes.Function):
                func_decl = decl
            decl = decl.child

        if not func_decl:
            # This condition is true for the following code:
            #
            # typedef int F(void);
            # F f { }
            #
            # See 6.9.1.2
            err = "function definition missing parameter list"
            raise CompilerError(err, self.r)

        for param in func_decl.args:
            decl_info = self.get_decl_infos(param)[0]
            identifiers.append(decl_info.identifier)

        return identifiers
Exemple #23
0
    def lookup_typedef(self, identifier):
        """Look up a typedef from the symbol table.

        If not found, raises an exception.
        """
        ctype = self._lookup_raw(identifier.content)
        if isinstance(ctype, CType):
            return ctype
        else:
            # This exception is only raised when the parser symbol table
            # makes an error, and this only happens when there is another
            # error in the source anyway. For example, consider this:
            #
            # int A;
            # {
            #   static typedef int A;
            #   A a;
            # }
            #
            # The parser symbol table will naively think that A is a
            # typedef on the line `A a`, when in fact the IL gen step will
            # still classify it as an integer because the `static
            # typedef int A;` is not a valid declaration. In this case,
            # we raise the error below.
            err = f"use of undeclared type definition '{identifier.content}'"
            raise CompilerError(err, identifier.r)
Exemple #24
0
    def _lvalue(self, il_code, symbol_table, c):
        """Return lvalue form of this node.

        We have two main cases here. The first case covers most simple
        situations, like `array[5]` or `array[x+3]`, and the second case
        covers more complex situations like `array[4][2]` or an array
        within a struct.

        In the first case, one of the two operands is a DirectLValue array
        (i.e. just a variable, not a lookup into another object). This
        means it will have a spot in memory assigned to it by the register
        allocator, and we can return a RelativeLValue object from this
        function. This case corresponds to `matched` being True. A
        RelativeLValue object usually becomes an assembly command like
        `[rbp-40+3*rax]`, which is more efficient than manually computing
        the address like happens in the second case.

        In the second case, neither operand is a DirectLValue array. This is
        the case for two-dimensional arrays, for example. Here, we proceed
        naively and get the address for the pointer side. This is a little
        bit less efficient. Ideally, in cases like `array[2][4]` where the
        lookup address could be computed at compile-time, we'd be able to do
        that, but this is not yet supported (TODO).

        """

        # One operand should be pointer to complete object type, and the
        # other should be any integer type.

        head_lv = self.head.lvalue(il_code, symbol_table, c)
        arg_lv = self.arg.lvalue(il_code, symbol_table, c)

        matched = False
        if isinstance(head_lv, DirectLValue) and head_lv.ctype().is_array():
            array, arith = self.head, self.arg
            matched = True
        elif isinstance(arg_lv, DirectLValue) and arg_lv.ctype().is_array():
            array, arith = self.arg, self.head
            matched = True

        if matched:
            # If one operand was a DirectLValue array
            array_val = array.make_il_raw(il_code, symbol_table, c)
            arith_val = arith.make_il(il_code, symbol_table, c)

            if arith_val.ctype.is_integral():
                return self.array_subsc(array_val, arith_val)

        else:
            # Neither operand was a DirectLValue array
            head_val = self.head.make_il(il_code, symbol_table, c)
            arg_val = self.arg.make_il(il_code, symbol_table, c)

            if head_val.ctype.is_pointer() and arg_val.ctype.is_integral():
                return self.pointer_subsc(head_val, arg_val, il_code)
            elif arg_val.ctype.is_pointer() and head_val.ctype.is_integral():
                return self.pointer_subsc(head_val, arg_val, il_code)

        descrip = "invalid operand types for array subscriping"
        raise CompilerError(descrip, self.r)
Exemple #25
0
    def make_il(self, il_code, symbol_table, c):
        """Make code for this node."""

        expr = self.expr.make_il(il_code, symbol_table, c)
        if not expr.ctype.is_scalar():
            err = "'!' operator requires scalar operand"
            raise CompilerError(err, self.r)

        # ILValue for storing the output
        out = ILValue(ctypes.integer)

        # ILValue for zero.
        zero = ILValue(ctypes.integer)
        il_code.register_literal_var(zero, "0")

        # ILValue for one.
        one = ILValue(ctypes.integer)
        il_code.register_literal_var(one, "1")

        # Label which skips the line which sets out to 0.
        end = il_code.get_label()

        il_code.add(value_cmds.Set(out, one))
        il_code.add(control_cmds.JumpZero(expr, end))
        il_code.add(value_cmds.Set(out, zero))
        il_code.add(control_cmds.Label(end))

        return out
Exemple #26
0
    def _generate_func_ctype(self, decl, prev_ctype):
        """Generate a function ctype from a given a decl_node."""

        # Prohibit storage class specifiers in parameters.
        for param in decl.args:
            decl_info = self.get_decl_infos(param)[0]
            if decl_info.storage:
                err = "storage class specified for function parameter"
                raise CompilerError(err, decl_info.range)

        # Create a new scope because if we create a new struct type inside
        # the function parameters, it should be local to those parameters.
        self.symbol_table.new_scope()
        args = [
            self.get_decl_infos(decl)[0].ctype
            for decl in decl.args
        ]
        self.symbol_table.end_scope()

        # adjust array and function parameters
        has_void = False
        for i in range(len(args)):
            ctype = args[i]
            if ctype.is_array():
                args[i] = PointerCType(ctype.el)
            elif ctype.is_function():
                args[i] = PointerCType(ctype)
            elif ctype.is_void():
                has_void = True
        if has_void and len(args) > 1:
            decl_info = self.get_decl_infos(decl.args[0])[0]
            err = "'void' must be the only parameter"
            raise CompilerError(err, decl_info.range)
        if prev_ctype.is_function():
            err = "function cannot return function type"
            raise CompilerError(err, self.r)
        if prev_ctype.is_array():
            err = "function cannot return array type"
            raise CompilerError(err, self.r)

        if not args and not self.body:
            new_ctype = FunctionCType([], prev_ctype, True)
        elif has_void:
            new_ctype = FunctionCType([], prev_ctype, False)
        else:
            new_ctype = FunctionCType(args, prev_ctype, False)
        return new_ctype
Exemple #27
0
def read_file(file):
    """Return the contents of the given file."""
    try:
        with open(file) as c_file:
            return c_file.read()
    except IOError as e:
        descrip = f"could not read file: '{file}'"
        error_collector.add(CompilerError(descrip))
Exemple #28
0
    def _lvalue(self, il_code, symbol_table, c):
        addr = self.expr.make_il(il_code, symbol_table, c)

        if not addr.ctype.is_pointer():
            err = "operand of unary '*' must have pointer type"
            raise CompilerError(err, self.expr.r)

        return IndirectLValue(addr)
Exemple #29
0
 def make_il(self, il_code, symbol_table, c):
     """Make code for this node."""
     lvalue = self.expr.lvalue(il_code, symbol_table, c)
     if lvalue:
         return lvalue.addr(il_code)
     else:
         err = "operand of unary '&' must be lvalue"
         raise CompilerError(err, self.expr.r)
Exemple #30
0
    def make_il(self, il_code, symbol_table, c):
        """Make IL for this cast operation."""

        self.set_self_vars(il_code, symbol_table, c)
        base_type, _ = self.make_specs_ctype(self.node.specs, False)
        ctype, _ = self.make_ctype(self.node.decls[0], base_type)

        if not ctype.is_void() and not ctype.is_scalar():
            err = "can only cast to scalar or void type"
            raise CompilerError(err, self.node.decls[0].r)

        il_value = self.expr.make_il(il_code, symbol_table, c)
        if not il_value.ctype.is_scalar():
            err = "can only cast from scalar type"
            raise CompilerError(err, self.r)

        return set_type(il_value, ctype, il_code)