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
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
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
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)
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
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
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
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)
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
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)
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
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)
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
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)
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:]
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
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
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)
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:]
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())
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
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
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)
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)
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
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
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))
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)
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)
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)