class IdleInterRepr: def __init__(self): self.__temporals = Temporal() self.__operands_stack = Stack() self.__operators_stack = Stack() self.__quads = [] self.__temp_quads = [] self.__jump_stack = Stack() self.__func_calls_stack = Stack() self.__param_counter_stack = Stack() @property def quads(self): return self.__quads def constant_quads(self): const_quads = [] for const_dict in CompilationMemory.CONSTANTS.values(): for name, var in const_dict.items(): const_quads.append( (OperationCode.ASSIGN, name, None, var.address)) return const_quads def get_last_var(self): """Returns top of operands stack. Expects to be called only when calling function or variable within object.""" return self.__operands_stack.pop() def add_goto_main(self): self.quads.append((OperationCode.GOTO, None, None, None)) def set_main(self, main_func: Func): start = list(self.__quads[0]) start[3] = main_func.func_start self.__quads[0] = tuple(start) def add_var(self, var): self.__operands_stack.push(var) def add_access_instance_var(self, class_ref: Variable, obj_var: Variable): temp = self.__temporals.next(obj_var.var_type) self.__quads.append((OperationCode.ASSIGN, class_ref.address, obj_var.address, temp.address)) self.__operands_stack.push(temp) def array_access(self, var): index_var = self.__operands_stack.pop() if index_var.var_type != DataType.INT: return False result = self.__temporals.next(DataType.POINTER) result.make_pointer(var.array_type) self.__quads.append((OperationCode.ARRINDEXCHECK.to_code(), 0, var.array_size - 1, index_var.address)) self.__quads.append((OperationCode.ARRACCESS.to_code(), var.address, index_var.address, result.address)) self.__operands_stack.push(result) self.__temporals.free_up_if_temp(index_var) return True def array_sort(self, var, direction): self.__quads.append((OperationCode.ARRSORT.to_code(), (0, var.array_size), direction, var.address)) def array_find(self, var): oper = self.__operands_stack.pop() result = self.__temporals.next(DataType.INT) if oper.var_type != var.array_type: return False self.__quads.append( (OperationCode.ARRFIND.to_code(), (0, var.array_size), (oper.address, var.address), result.address)) self.__operands_stack.push(result) temp_func = Func('temp') temp_func.return_type = DataType.INT self.__func_calls_stack.push(temp_func) return True def add_operator(self, operator): self.__operators_stack.push(OperationCode(operator)) def __resolve_oper(self) -> bool: right_oper = self.__operands_stack.pop() left_oper = self.__operands_stack.pop() operator = self.__operators_stack.pop() result_type = SemanticCube.check(operator, left_oper.var_type, right_oper.var_type) if result_type == DataType.ERROR: return False result = self.__temporals.next(result_type) self.__quads.append((operator.to_code(), left_oper.address, right_oper.address, result.address)) self.__operands_stack.push(result) self.__temporals.free_up_if_temp(left_oper) self.__temporals.free_up_if_temp(right_oper) return True def check_addsub(self) -> bool: if OperationCode.is_add_sub(self.__operators_stack.peek()): return self.__resolve_oper() return True def check_divmult(self) -> bool: if OperationCode.is_div_mult(self.__operators_stack.peek()): return self.__resolve_oper() return True def check_relop(self) -> bool: if OperationCode.is_relop(self.__operators_stack.peek()): return self.__resolve_oper() return True def open_parenthesis(self): self.__operators_stack.push(OperationCode.FAKEBOTTOM) def close_parenthesis(self): self.__operators_stack.pop() def assign(self) -> bool: oper = self.__operands_stack.pop() var = self.__operands_stack.pop() if oper.var_type != var.var_type: return False self.__temporals.free_up_if_temp(oper) self.__quads.append( (OperationCode.ASSIGN.to_code(), oper.address, None, var.address)) return True def short_var_decl_assign(self) -> DataType: oper = self.__operands_stack.pop() var = self.__operands_stack.pop() self.__temporals.free_up_if_temp(oper) self.__quads.append( (OperationCode.ASSIGN.to_code(), oper.address, None, var.address)) return oper.var_type def read(self, read_type): result = self.__temporals.next(read_type) if read_type == DataType.INT: self.__quads.append( (OperationCode.READINT.to_code(), None, None, result.address)) if read_type == DataType.FLOAT: self.__quads.append((OperationCode.READFLOAT.to_code(), None, None, result.address)) if read_type == DataType.STRING: self.__quads.append((OperationCode.READSTRING.to_code(), None, None, result.address)) self.__operands_stack.push(result) temp_func = Func('temp') temp_func.return_type = read_type self.__func_calls_stack.push(temp_func) def to_string(self): oper = self.__operands_stack.pop() result = self.__temporals.next(DataType.STRING) self.__quads.append((OperationCode.TOSTRING.to_code(), oper.address, None, result.address)) self.__operands_stack.push(result) temp_func = Func('temp') temp_func.return_type = DataType.STRING self.__func_calls_stack.push(temp_func) def print_st(self): oper = self.__operands_stack.pop() self.__quads.append( (OperationCode.PRINT.to_code(), oper.address, None, None)) def start_while(self): self.__jump_stack.push(len(self.__quads)) def end_while_expr(self) -> bool: expr_result = self.__operands_stack.pop() if expr_result.var_type != DataType.BOOL: return False self.__quads.append( (OperationCode.GOTOF.to_code(), expr_result.address, None, None)) self.__jump_stack.push(len(self.__quads) - 1) return True def end_while(self): expr_end = self.__jump_stack.pop() while_start = self.__jump_stack.pop() # Add GOTO to loop back to while start self.__quads.append( (OperationCode.GOTO.to_code(), None, None, while_start)) # Update GOTOF jump address after expression if expr_end: expr_end_quad = list(self.__quads[expr_end]) expr_end_quad[3] = len(self.__quads) self.__quads[expr_end] = tuple(expr_end_quad) def start_for_assign(self): # Save current quad list into temporal space and reset quads self.__temp_quads = self.__quads self.__quads = [] def end_for_assign(self): # Switch quad list with the temporal quads generated from the for assignment quads = self.__temp_quads self.__temp_quads = self.__quads self.__quads = quads def end_for_block(self): # Add temporal quads from assignment to end of block self.__quads.extend(self.__temp_quads) def end_if_expr(self) -> bool: expr_result = self.__operands_stack.pop() if expr_result.var_type != DataType.BOOL: return False self.__quads.append( (OperationCode.GOTOF.to_code(), expr_result.address, None, None)) false_jumps = Stack() false_jumps.push(len(self.__quads) - 1) self.__jump_stack.push(false_jumps) return True def fill_if_end_jumps(self): fill_jumps = self.__jump_stack.pop() while not fill_jumps.isEmpty(): expr_end = fill_jumps.pop() # Update GOTOF jump address after expression expr_end_quad = list(self.__quads[expr_end]) expr_end_quad[3] = len(self.__quads) self.__quads[expr_end] = tuple(expr_end_quad) def start_else_ifs(self): goto_end_jumps = Stack() goto_false_jumps = self.__jump_stack.pop() self.__jump_stack.push(goto_end_jumps) self.__jump_stack.push(goto_false_jumps) def add_else(self): self.__quads.append((OperationCode.GOTO.to_code(), None, None, None)) false_jumps = self.__jump_stack.pop() false_jump = false_jumps.pop() # Add quad to stack of quads that should jump to the end of if goto_end_jumps = self.__jump_stack.pop() goto_end_jumps.push(len(self.__quads) - 1) self.__jump_stack.push(goto_end_jumps) # Update GOTOF jump address from previous if / else if expr_false_jump = list(self.__quads[false_jump]) expr_false_jump[3] = len(self.__quads) self.__quads[false_jump] = tuple(expr_false_jump) def add_func_era(self, func_called: Func, obj_var: Variable = None): if obj_var != None: # If call belongs to object, add object reference to change instance contexts self.__quads.append((OperationCode.ERA, func_called.func_start, obj_var.address, None)) elif func_called.name == func_called.return_type: temp = self.__temporals.next(func_called.return_type) self.__quads.append((OperationCode.ERA, func_called.func_start, temp.address, None)) self.__operands_stack.push(temp) elif func_called.name == "__default_constructor__": temp = self.__temporals.next(func_called.return_type) self.__operands_stack.push(temp) else: self.__quads.append( (OperationCode.ERA, func_called.func_start, None, None)) self.__func_calls_stack.push(func_called) self.__param_counter_stack.push(0) def add_func_param(self): func_called = self.__func_calls_stack.peek() origin_var = self.__operands_stack.pop() param_counter = self.__param_counter_stack.pop() self.__param_counter_stack.push(param_counter + 1) # More arguments given than requested if param_counter >= len(func_called.arguments): return (True, None ) # Ignore, later will be caught on add_func_gosub # Check parameter type matching destination_var = func_called.arguments[param_counter] if origin_var.var_type != destination_var.var_type: return (False, destination_var.address, destination_var.var_type) if destination_var.is_param_by_ref: self.__quads.append((OperationCode.PARAMREF, origin_var.address, None, destination_var.address)) else: self.__quads.append((OperationCode.PARAM, origin_var.address, None, destination_var.address)) return (True, None) def add_func_gosub(self): func_called = self.__func_calls_stack.peek() if func_called: if func_called.name != "__default_constructor__": # Ignore default constructor # If function has return value, save return in temporal if func_called.return_type != None: if func_called.return_type == func_called.name: # Constructor call self.__quads.append( (OperationCode.GOSUB, func_called.name, None, self.__operands_stack.peek().address)) else: result = self.__temporals.next(func_called.return_type) self.__quads.append( (OperationCode.GOSUB, func_called.name, None, result.address)) self.__operands_stack.push(result) else: self.__quads.append( (OperationCode.GOSUB, func_called.name, None, None)) # Check number of parameters param_counter = self.__param_counter_stack.pop() if param_counter != len(func_called.arguments): return (False, func_called.name, len(func_called.arguments), param_counter) return (True, None) def check_not_void(self, check: bool): func_called = self.__func_calls_stack.pop() if check and func_called.return_type == None: return False return True def add_empty_return(self): self.__quads.append((OperationCode.ENDPROC, None, None, None)) def add_func_return(self, expected_return_type: DataType) -> bool: return_val = None return_type = None if not self.__operands_stack.isEmpty(): return_var = self.__operands_stack.pop() return_val = return_var.address return_type = return_var.var_type if expected_return_type != None: # Quad appended even on error to avoid also reporting 'missing return statement' on add_endproc self.__quads.append((OperationCode.RETURN, return_val, None, None)) if return_type != expected_return_type: return False return True def add_endproc(self, current_func: Func) -> bool: self.__quads.append((OperationCode.ENDPROC, None, None, None)) self.__temporals = Temporal() # Assumes last quad should be return statement if current_func.return_type != None: if current_func.return_type != current_func.name: # Not a constructor try: if self.__quads[-2][0] != OperationCode.RETURN: return False except IndexError: # Happens when method is blank return False return True
class IdleVirtualMachine(): def __init__(self, const_quadruples, quadruples, debug=False): self.__const_quadruples = const_quadruples self.__quadruples = quadruples self.__memory_stack = Stack() self.__next_class_stack = Stack() self.__debug = debug curr_class = ClassMemory() curr_class.era_func(self.__quadruples[0][3]) curr_class.goto_next_func() self.__memory_stack.push(curr_class) if self.__debug: for i in range(0, (len(self.__quadruples))): print(i, self.__quadruples[i]) @property def current_memory(self): return self.__memory_stack.peek() def run(self): """ Executes all of the quads generated during compilation. """ instruction_set = { OperationCode.GOTO: self.run_goto, OperationCode.GOTOF: self.run_gotof, OperationCode.GOTOT: self.run_gotot, OperationCode.ASSIGN: self.run_assign, OperationCode.ERA: self.run_era, OperationCode.PARAM: self.run_param, OperationCode.PARAMREF: self.run_paramref, OperationCode.GOSUB: self.run_gosub, OperationCode.RETURN: self.run_return, OperationCode.ENDPROC: self.run_endproc, OperationCode.ADD: self.run_add, OperationCode.SUB: self.run_sub, OperationCode.MULT: self.run_mult, OperationCode.DIV: self.run_div, OperationCode.GT: self.run_gt, OperationCode.LT: self.run_lt, OperationCode.GE: self.run_ge, OperationCode.LE: self.run_le, OperationCode.EQUAL: self.run_equal, OperationCode.NOTEQUAL: self.run_not_equal, OperationCode.AND: self.run_and, OperationCode.OR: self.run_or, OperationCode.PRINT: self.run_print, OperationCode.READFLOAT: self.run_read_float, OperationCode.READINT: self.run_read_int, OperationCode.READSTRING: self.run_read_string, OperationCode.TOSTRING: self.run_to_string, OperationCode.ARRACCESS: self.run_arr_access, OperationCode.ARRINDEXCHECK: self.run_arr_index_check, OperationCode.ARRSORT: self.run_arr_sort, OperationCode.ARRFIND: self.run_arr_find } # Add variables for constants to memory self.init_consts() # Execute all quads next_quad = self.next_instruction() while next_quad != None: instruction = instruction_set[OperationCode(next_quad[0])] instruction(next_quad) if self.__debug: print("===== EXECUTED: " + str(next_quad) + " =========") print(self.current_memory) next_quad = self.next_instruction() def init_consts(self): temp = Memory() for quad in self.__const_quadruples: temp.set_value(quad[1], quad[3]) def next_instruction(self): """ Gets the next instruction from memory. """ counter = self.current_memory.next_instruction() if counter != None: return self.__quadruples[counter] elif self.__memory_stack.size() > 1: self.__memory_stack.pop() return self.next_instruction() return None # INSTRUCTION SET FUNCTIONS def run_goto(self, quad): self.current_memory.goto(quad[3]) def run_gotof(self, quad): op1 = self.current_memory.get_value(quad[1]) if not op1: self.current_memory.goto(quad[3]) def run_gotot(self, quad): op1 = self.current_memory.get_value(quad[1]) if op1: self.current_memory.goto(quad[3]) def run_assign(self, quad): if quad[2] == None: # Regular access value = self.current_memory.get_value(quad[1]) self.current_memory.set_value(value, quad[3]) else: # Instance var access obj_instance = self.current_memory.get_value(quad[1]) ref = obj_instance.get_reference(quad[2]) self.current_memory.set_reference(ref, quad[3]) def run_era(self, quad): if quad[2] != None: obj = self.current_memory.get_value(quad[2]) obj.era_func(quad[1]) self.__next_class_stack.push(obj) else: self.current_memory.era_func(quad[1]) def run_param(self, quad): value = self.current_memory.get_value(quad[1]) if not self.__next_class_stack.isEmpty(): self.__next_class_stack.peek().send_param(value, quad[3]) else: self.current_memory.send_param(value, quad[3]) def run_paramref(self, quad): reference = self.current_memory.get_reference(quad[1]) if not self.__next_class_stack.isEmpty(): self.__next_class_stack.peek().send_param_by_ref( reference, quad[3]) else: self.current_memory.send_param_by_ref(reference, quad[3]) def run_gosub(self, quad): if not self.__next_class_stack.isEmpty(): self.__memory_stack.push(self.__next_class_stack.pop()) self.current_memory.goto_next_func() def run_return(self, quad): value = self.current_memory.get_value(quad[1]) # If there is another function in stack, it means it will return to that function within class if self.current_memory.can_return(): counter = self.current_memory.prev_func_last_instruction() address = self.__quadruples[counter][3] self.current_memory.return_value(value, address) else: # Return of object function call prev_class = self.__memory_stack.peek_next_to_last() counter = prev_class.curr_func_last_instruction() address = self.__quadruples[counter][3] prev_class.set_value(value, address) self.run_endproc() def run_endproc(self, quad=None): self.current_memory.end_func() def run_add(self, quad): op1 = self.current_memory.get_value(quad[1]) op2 = self.current_memory.get_value(quad[2]) self.current_memory.set_value(op1 + op2, quad[3]) def run_sub(self, quad): op1 = self.current_memory.get_value(quad[1]) op2 = self.current_memory.get_value(quad[2]) self.current_memory.set_value(op1 - op2, quad[3]) def run_mult(self, quad): op1 = self.current_memory.get_value(quad[1]) op2 = self.current_memory.get_value(quad[2]) self.current_memory.set_value(op1 * op2, quad[3]) def run_div(self, quad): op1 = self.current_memory.get_value(quad[1]) op2 = self.current_memory.get_value(quad[2]) if op2 == 0: print("Runtime Error: Division by 0.") exit() if isinstance(op1, int) and isinstance(op2, int): self.current_memory.set_value(op1 // op2, quad[3]) else: self.current_memory.set_value(op1 / op2, quad[3]) def run_gt(self, quad): op1 = self.current_memory.get_value(quad[1]) op2 = self.current_memory.get_value(quad[2]) self.current_memory.set_value(op1 > op2, quad[3]) def run_lt(self, quad): op1 = self.current_memory.get_value(quad[1]) op2 = self.current_memory.get_value(quad[2]) self.current_memory.set_value(op1 < op2, quad[3]) def run_equal(self, quad): op1 = self.current_memory.get_value(quad[1]) op2 = self.current_memory.get_value(quad[2]) self.current_memory.set_value(op1 == op2, quad[3]) def run_ge(self, quad): op1 = self.current_memory.get_value(quad[1]) op2 = self.current_memory.get_value(quad[2]) self.current_memory.set_value(op1 >= op2, quad[3]) def run_le(self, quad): op1 = self.current_memory.get_value(quad[1]) op2 = self.current_memory.get_value(quad[2]) self.current_memory.set_value(op1 <= op2, quad[3]) def run_not_equal(self, quad): op1 = self.current_memory.get_value(quad[1]) op2 = self.current_memory.get_value(quad[2]) self.current_memory.set_value(op1 != op2, quad[3]) def run_and(self, quad): op1 = self.current_memory.get_value(quad[1]) op2 = self.current_memory.get_value(quad[2]) self.current_memory.set_value(op1 and op2, quad[3]) def run_or(self, quad): op1 = self.current_memory.get_value(quad[1]) op2 = self.current_memory.get_value(quad[2]) self.current_memory.set_value(op1 or op2, quad[3]) def run_print(self, quad): op1 = self.current_memory.get_value(quad[1]) if isinstance(op1, bool): op1 = str(op1).lower() print(op1) def run_read_float(self, quad): op1 = input() try: op1 = float(op1) except ValueError: print("Runtime Error: Expected float.") exit() self.current_memory.set_value(op1, quad[3]) def run_read_int(self, quad): op1 = input() try: op1 = int(op1) except ValueError: print("Runtime Error: Expected int.") exit() self.current_memory.set_value(op1, quad[3]) def run_read_string(self, quad): op1 = str(input()) self.current_memory.set_value(op1, quad[3]) def run_to_string(self, quad): value = self.current_memory.get_value(quad[1]) string_value = str(value) self.current_memory.set_value(string_value, quad[3]) def run_arr_access(self, quad): base_address = quad[1] arr_index = self.current_memory.get_value(quad[2]) address = base_address + arr_index * 100 # RUNTIME ERROR index out of bounds self.current_memory.set_pointer_address(quad[3], address) def run_arr_index_check(self, quad): lower_limit = quad[1] upper_limit = quad[2] index = self.current_memory.get_value(quad[3]) if index < lower_limit or index > upper_limit: print("Runtime Error: Array index out of bounds.") exit() def run_arr_sort(self, quad): base_address = quad[3] start_address = base_address + quad[1][0] * 100 end_address = base_address + quad[1][1] * 100 array = self.current_memory.get_memory_slice(start_address, end_address) array.sort() if quad[2] == "desc": array.reverse() self.current_memory.set_memory_slice(array, start_address, end_address) def run_arr_find(self, quad): base_address = quad[2][1] start_address = base_address + quad[1][0] * 100 end_address = base_address + quad[1][1] * 100 value = self.current_memory.get_value(quad[2][0]) array = self.current_memory.get_memory_slice(start_address, end_address) value_index = -1 for index, item in enumerate(array): if item == value: value_index = index break self.current_memory.set_value(value_index, quad[3])
class nintVM: """docstring for nintVM""" def __init__(self, filename: str): super().__init__() self.ip = 0 # Instruction pointer self.quads = [] self.ConstTable = dict() # Memcounts self._memcount_temp = 0 self._memcount_global = 0 self._returns_value = False self._newstack = None self.load_data(filename) self._total_quads = len(self.quads) assert self._memcount_global != 0 and self._memcount_temp != 0, "No data read for global or temp counts from bytecode" if debug_mode == 'debug': debug("========== QUADS ===========") for quad in self.quads: debug(quad) debug() debug() # TODO: Create memory sections here self.Temp = Memory(self._memcount_temp) self._GlobalMemory = Memory(self._memcount_global) self.CallStack = Stack() # Local memory self.CallStack.push(self._GlobalMemory) # Instruction set self.nintIS = { # Arithmetic Operator.ASSIGN: self.assign, Operator.ADD: self.add, Operator.SUB: self.sub, Operator.MULT: self.mult, Operator.DIV: self.div, # Relops Operator.GT: self.gt, Operator.GTE: self.gte, Operator.LT: self.lt, Operator.LTE: self.lte, Operator.EQUAL: self.equals, Operator.NEQ: self.neq, # Boolean comparisons Operator.AND: self.bool_and, Operator.OR: self.bool_or, # GOTOs Operator.GOTO: self.goto, Operator.GOTOF: self.gotoF, Operator.GOTOV: self.gotoV, # Functions Operator.GOSUB: self.gosub, Operator.PARAM: self.param, Operator.ERA: self.expand_active_record, Operator.ENDPROC: self.endproc, Operator.RETURN: self.return_proc, Operator.PRINT: self._print } def load_data(self, filename: str): '''Read the data into memory''' with open(filename, 'rb') as f: data = pickle.load(f) self.quads = data[0] # quads # TODO: I need to parse this table and get the actual values with their real types self.ConstTable = data[1] # consttable self.FunDir = data[2] self._memcount_global = data[3] self._memcount_temp = data[4] def run(self): '''Run the actual code''' quad = self.quads[self.ip] while quad is not None: instruction = Operator(quad[0]) method = self.nintIS[instruction] method(quad) quad = self.next() def next(self): self.ip += 1 if self.ip < self._total_quads: return self.quads[self.ip] return None def set_value(self, address: str, value): if is_local(address): self.CallStack.peek().set_value(address, value) elif is_temp(address): self.Temp.set_value(address, value) else: self.CallStack.peek().set_value(address, value) def get_value(self, address): if is_constant(address): debug(address, "is_constant") return self.ConstTable[address] elif is_temp(address): return self.Temp.get_val(address) elif is_global(address): # TODO: we could probably remove this now return self._GlobalMemory.get_val(address) return self.CallStack.peek().get_val(address) # return self.mem.get_val(address) # Operation functions # --------------------------------------------------------------- def assign(self, quad): debug("assign") debug(quad) if self._returns_value: # Second param is function self._returns_value = False func = self.FunDir[quad[1]] assert 'value' in func, "Function should have a value because it is non-void" value = func['value'] else: value = self.get_value(quad[1]) assert value is not None target_address = quad[3] self.set_value(target_address, value) debug() # Relational operators # ------------------------------------------------- def equals(self, quad): debug("equals") left_operand = self.get_value(quad[1]) right_operand = self.get_value(quad[2]) result = left_operand == right_operand self.set_value(quad[3], result) debug() def neq(self, quad): left_operand = self.get_value(quad[1]) right_operand = self.get_value(quad[2]) result = left_operand != right_operand self.set_value(quad[3], result) def gt(self, quad): left_operand = self.get_value(quad[1]) right_operand = self.get_value(quad[2]) result = left_operand > right_operand self.set_value(quad[3], result) def gte(self, quad): left_operand = self.get_value(quad[1]) right_operand = self.get_value(quad[2]) result = left_operand >= right_operand self.set_value(quad[3], result) def lt(self, quad): left_operand = self.get_value(quad[1]) right_operand = self.get_value(quad[2]) result = left_operand < right_operand self.set_value(quad[3], result) def lte(self, quad): left_operand = self.get_value(quad[1]) right_operand = self.get_value(quad[2]) result = left_operand <= right_operand self.set_value(quad[3], result) # Boolean operators # ------------------------------------------------- def bool_and(self, quad): left_operand = self.get_value(quad[1]) right_operand = self.get_value(quad[2]) result = left_operand and right_operand self.set_value(quad[3], result) def bool_or(self, quad): left_operand = self.get_value(quad[1]) right_operand = self.get_value(quad[2]) result = left_operand or right_operand self.set_value(quad[3], result) # Arithmetic # ------------------------------------------------- def add(self, quad): debug("add") debug(quad) left_operand = self.get_value(quad[1]) right_operand = self.get_value(quad[2]) result = left_operand + right_operand self.set_value(quad[3], result) debug() def sub(self, quad): left_operand = self.get_value(quad[1]) right_operand = self.get_value(quad[2]) result = left_operand - right_operand self.set_value(quad[3], result) def mult(self, quad): left_operand = self.get_value(quad[1]) right_operand = self.get_value(quad[2]) result = left_operand * right_operand self.set_value(quad[3], result) def div(self, quad): left_operand = self.get_value(quad[1]) right_operand = self.get_value(quad[2]) if right_operand == 0: raise Exception("Runtime Exception: Division by 0.") result = left_operand / right_operand self.set_value(quad[3], result) # GOTOs # ------------------------------------------------- def goto(self, quad): quad_addr = int(quad[3]) self.ip = quad_addr - 1 def gotoF(self, quad): expr_result = self.get_value(quad[1]) if not expr_result: self.ip = int(quad[3]) - 1 def gotoV(self, quad): raise Exception('Not implemented') # Functions # --------------------------------------------------------------- def expand_active_record(self, quad): debug("ERA") func_name = quad[1] assert func_name in self.FunDir, "No function" size_map = self.FunDir[func_name] # Create the AR sf = StackFrame(func_name, size_map) # Add it to the callstack # self.CallStack.push(sf) self._newstack = sf debug() def param(self, quad): debug('param') current_scope = self.CallStack.peek() assert current_scope is not None, "No callstack" param = self.get_value(quad[1]) address = quad[3] assert self._newstack is not None self._newstack.set_value(address, param) debug() def gosub(self, quad): debug('gosub') sf = self._newstack self.CallStack.push(sf) sf.set_return_addr(self.ip) self.ip = int(quad[3]) - 1 # minus 1 because next() adds one debug() def endproc(self, quad): debug('endproc') current_scope = self.CallStack.pop() self.ip = current_scope.return_addr self._newstack = None del current_scope # vacuous statement but makes me feel good debug() def return_proc(self, quad): is_empty_return = quad[1] is None if is_empty_return: return self.endproc(quad) current_scope = self.CallStack.peek() fname = current_scope.function_name retval = self.get_value(quad[1]) self.FunDir[fname]['value'] = retval self._returns_value = True return self.endproc(None) # Special functions # --------------------------------------------------------------- def _print(self, quad): arg = self.get_value(quad[3]) if isinstance(arg, bool): arg = str(arg).lower() print(arg)
class ClassMemory(Memory): def __init__(self): super().__init__() self.__func_memory_stack = Stack() self.__next_func = Stack() def set_reference(self, reference, address): if self.get_type(address) == DataType.POINTER: actual_address = self.__func_memory_stack.peek().get_value(address) self.set_reference(reference, actual_address) elif address % 10 == CompilationMemory.INSTANCE_ID: super().set_reference(reference, address) else: self.__func_memory_stack.peek().set_reference(reference, address) def get_reference(self, address): if self.get_type(address) == DataType.POINTER: actual_address = self.__func_memory_stack.peek().get_value(address) return self.get_reference(actual_address) elif address % 10 == CompilationMemory.INSTANCE_ID: return super().get_reference(address) else: return self.__func_memory_stack.peek().get_reference(address) def set_value(self, value, address): if self.get_type(address) == DataType.POINTER: actual_address = self.__func_memory_stack.peek().get_value(address) self.set_value(value, actual_address) elif address % 10 == CompilationMemory.INSTANCE_ID: super().set_value(value, address) else: self.__func_memory_stack.peek().set_value(value, address) def get_value(self, address): if self.get_type(address) == DataType.POINTER: actual_address = self.__func_memory_stack.peek().get_value(address) return self.get_value(actual_address) if address % 10 == CompilationMemory.INSTANCE_ID: return super().get_value(address) else: return self.__func_memory_stack.peek().get_value(address) def set_pointer_address(self, pointer_address, pointing_address): self.__func_memory_stack.peek().set_value(pointing_address, pointer_address) def era_func(self, func_start): self.__next_func.push(LocalMemory(func_start)) def send_param(self, value, address): self.__next_func.peek().set_value(value, address) def send_param_by_ref(self, reference, address): self.__next_func.peek().set_reference(reference, address) def can_return(self): return self.__func_memory_stack.size() > 1 def curr_func_last_instruction(self): return self.__func_memory_stack.peek().last_instruction def prev_func_last_instruction(self): return self.__func_memory_stack.peek_next_to_last().last_instruction def return_value(self, value, address): self.__func_memory_stack.peek_next_to_last().set_value(value, address) def goto_next_func(self): self.__func_memory_stack.push(self.__next_func.pop()) def end_func(self): self.__func_memory_stack.pop() def next_instruction(self): if self.__func_memory_stack.peek() != None: return self.__func_memory_stack.peek().next_instruction() return None def goto(self, counter): self.__func_memory_stack.peek().goto(counter) def __str__(self): representation = "----------------------\n" representation += "CLASS MEMORY:\n" representation += super().__str__() + "\n" representation += "\nFUNCTION MEMORY:\n" for fnc in self.__func_memory_stack.items: representation += fnc.__str__() + "\n" representation += "\nNEXT FUNCTIONS:\n" for fnc in self.__next_func.items: representation += fnc.__str__() + "\n" representation += "CONSTANTS:\n" representation += self.CONSTANTS.__str__() + "\n" representation += "----------------------\n" return representation
class nintCompiler: """docstring for nintCompiler""" def __init__(self): super().__init__() self.OperatorStack = Stack() self.OperandStack = Stack() self.TypeStack = Stack() self.JumpStack = Stack() self.GScope = Env(None, MemType.GLOBAL) # Global env? # Function helpers self._found_main = False self._print = False self._current_func = None self._call_proc = None self._func_returned = None # Check if the current function has returned something self._param_k = None # Generate the global scope and insert it into the functions directory # gscope = Function(GLOBAL, None, VOID) # self.FunDir.insert(gscope) # TODO: are objects passed by reference here? self.ScopeStack = Stack() # Keep track of the current Scope self.ScopeStack.push(self.GScope) # Temporal memory self._Temporal = Temp() self._TempStack = Stack() self._TempStack.push(self._Temporal) self.quads = [] # Add GOTO main self.quads.append([Operator.GOTO.value, None, None, None]) def __getattribute__(self, attr): method = object.__getattribute__(self, attr) # if not method: # raise Exception("Method %s not implemented" % attr) if callable(method): name = method.__code__.co_name if name not in no_check and not name.startswith('procedure'): self.__check_main() return method def __check_main(self): if not self._found_main and self._current_func is None: self._found_main = True self.quads[0][3] = len(self.quads) def serialize(self, filename="out.nint.bytecode"): '''Serialize the quads and the quads into an intermediate obj file to be read by the VM.''' const_map = dict() for const_dict in Memory.CONST_TABLE.values(): for const, var in const_dict.items(): const_map[var.address] = utils.parseVar(var) temp_counters = self._Temporal._counters global_counters = self.GScope.memory._counters fun_dir = self.functionDirectory() data = [self.quads, const_map, fun_dir, global_counters, temp_counters] with open(filename, 'wb') as f: pickle.dump(data, f, pickle.HIGHEST_PROTOCOL) def functionDirectory(self): result = dict() for func in self.GScope.functions: result.update({func.name: func.size_map}) return result def intercode(self): '''Print the quadruples''' for i, quad in enumerate(self.quads): if debug_mode == 'debug': print("{}) ".format(i), end='') print('\t'.join(map(printable, quad))) def add_var_declaration(self, type_str: str, identifier: str): '''Add variable to the varsTable of the current context''' debug("add_var_declaration") current_scope = self.ScopeStack.peek() # Check if it's not already been defined if current_scope.exists(identifier): raise Exception( 'Double declaration. {} has already been declared in this context.' .format(identifier)) dtype = mapType(type_str) address = current_scope.memory.next_address(dtype) var = Variable(identifier, dtype, address) current_scope.insert(var) def add_var(self, token: str): '''Add variable to the operand stack''' debug("add_var") current_scope = self.ScopeStack.peek() variable = current_scope.get(token) # Check that the variable has already been declared _somewhere_ (could be current, could be upper scopes) assert variable is not None, "Name {} has not been declared.".format( token) debug("add_var: OperandStack.push({})".format(variable.name)) debug("add_var: TypeStack.push({})".format(variable.dtype)) self.OperandStack.push(variable) self.TypeStack.push(variable.dtype) debug() def add_constant(self, token, dtype: DType): '''Adds a constant to the operand stack''' debug("add_constant") debug("Operand.push({})".format(token)) debug("TypeStack.push({})".format(dtype)) debug() self.OperandStack.push(Memory.constant( dtype, token)) # TODO: should we parse? self.TypeStack.push(dtype) debug() def add_operator(self, op: str): ''' Adds operator op to the OperatorStack''' debug("add_operator") debug("Operator.push({})".format(op)) operator = Operator(op) self.OperatorStack.push(operator) # TODO: change this to an enum debug() # TODO: refactor the check_* functions def check_relop(self): debug("check_relop") top = self.OperatorStack.peek() debug('top: {}'.format(top)) if top not in RELOPS: return right_operand = self.OperandStack.pop() right_type = self.TypeStack.pop() left_operand = self.OperandStack.pop() left_type = self.TypeStack.pop() operator = self.OperatorStack.pop() debug((operator, left_type, right_type)) result_type = SemanticCube.check(operator, left_type, right_type) # Relational operators *always* return a boolean assert result_type is DType.BOOL result = self._TempStack.peek().next(result_type) debug("Adds quad") self.quads.append((operator.value, left_operand.address, right_operand.address, result.address)) self.OperandStack.push(result) self.TypeStack.push(result_type) debug() def check_and(self): debug("check_and") top = self.OperatorStack.peek() debug('top: {}'.format(top)) if top is not Operator.AND: return right_operand = self.OperandStack.pop() right_type = self.TypeStack.pop() left_operand = self.OperandStack.pop() left_type = self.TypeStack.pop() operator = self.OperatorStack.pop() debug((operator, left_type, right_type)) result_type = SemanticCube.check(operator, left_type, right_type) # Relational operators *always* return a boolean assert result_type is DType.BOOL result = self._TempStack.peek().next(result_type) debug("Adds quad") self.quads.append((operator.value, left_operand.address, right_operand.address, result.address)) self.OperandStack.push(result) self.TypeStack.push(result_type) debug() def check_or(self): debug("check_or") top = self.OperatorStack.peek() debug('top: {}'.format(top)) if top is not Operator.OR: return right_operand = self.OperandStack.pop() right_type = self.TypeStack.pop() left_operand = self.OperandStack.pop() left_type = self.TypeStack.pop() operator = self.OperatorStack.pop() debug((operator, left_type, right_type)) result_type = SemanticCube.check(operator, left_type, right_type) # Relational operators *always* return a boolean assert result_type is DType.BOOL result = self._TempStack.peek().next(result_type) debug("Adds quad") self.quads.append((operator.value, left_operand.address, right_operand.address, result.address)) self.OperandStack.push(result) self.TypeStack.push(result_type) debug() def check_eqop(self): debug("check_eqop") # TODO: implement this top = self.OperatorStack.peek() if top is not Operator.EQUAL and top is not Operator.NEQ: return right_operand = self.OperandStack.pop() right_type = self.TypeStack.pop() left_operand = self.OperandStack.pop() left_type = self.TypeStack.pop() operator = self.OperatorStack.pop() debug((operator, left_type, right_type)) result_type = DType.BOOL # result_type = SemanticCube.check(operator, left_type, right_type) # TODO: rn we allow comparison of everything vs everything, is this correct? # TODO: what if we compare a function name to a variable? (e.g. uno == 2) result = self._TempStack.peek().next(result_type) debug("Adds quad") self.quads.append((operator.value, left_operand.address, right_operand.address, result.address)) self.OperandStack.push(result) self.TypeStack.push(result_type) debug() def check_addsub(self): debug('check_addsub') top = self.OperatorStack.peek() if not (top == Operator.ADD or top == Operator.SUB): return right_operand = self.OperandStack.pop() right_type = self.TypeStack.pop() left_operand = self.OperandStack.pop() left_type = self.TypeStack.pop() operator = self.OperatorStack.pop() debug((operator, left_operand.name, right_operand.name)) result_type = SemanticCube.check(operator, left_type, right_type) result = self._TempStack.peek().next(result_type) self.OperandStack.push(result) self.TypeStack.push(result_type) debug("Adds quad") self.quads.append((operator.value, left_operand.address, right_operand.address, result.address)) debug() def check_multdiv(self): debug('check_multdiv') top = self.OperatorStack.peek() if not (top == Operator.MULT or top == Operator.DIV): return right_operand = self.OperandStack.pop() right_type = self.TypeStack.pop() left_operand = self.OperandStack.pop() left_type = self.TypeStack.pop() operator = self.OperatorStack.pop() debug((operator, left_type, right_type)) result_type = SemanticCube.check(operator, left_type, right_type) result = self._TempStack.peek().next(result_type) self.OperandStack.push(result) self.TypeStack.push(result_type) debug("Adds quad") self.quads.append((operator.value, left_operand.address, right_operand.address, result.address)) debug() # If-Else # -------------------------------------------- def ifelse_start_jump(self): debug("ifelse_start_jump") expression_type = self.TypeStack.pop() if expression_type != DType.BOOL: raise Exception( "Type mismatch on line {}".format('SOMELINE TODO FIX THIS')) # TODO: maybe make a static function for this? result = self.OperandStack.pop() self.quads.append([Operator.GOTOF.value, result.address, None, None]) self.JumpStack.push( len(self.quads) - 1 ) # TODO: definitely change this. There has to be a better way to do this debug() def ifelse_end_jump(self): debug("ifelse_end_jump") counter = len(self.quads) # TODO: change this debug('counter: {}'.format(counter)) self.fill(self.JumpStack.pop(), counter) debug() def fill(self, pending_jump_pos, jump_location): debug("fill") self.quads[pending_jump_pos][ 3] = jump_location # TODO: definitely make a class for this def ifelse_start_else(self): debug("ifelse_start_else") debug("ADD ELSE QUAD GOTO") self.quads.append([Operator.GOTO.value, None, None, None]) # Fill the false condition jump of the `if` if_false_jump = self.JumpStack.pop() counter = len(self.quads) # TODO: counter self.JumpStack.push(counter - 1) self.fill(if_false_jump, counter) def assignment_quad(self): debug("assignment_quad") operator = self.OperatorStack.pop() assert operator == Operator.ASSIGN right_operand = self.OperandStack.pop() right_type = self.TypeStack.pop() left_operand = self.OperandStack.pop() left_type = self.TypeStack.pop() debug("<{}> = <{}>".format(left_type, right_type)) # TODO: probably change this # TODO: these don't need to be *exactly* the same, they just need to be compatible # example: float a = 10 assert right_type == left_type, "Type mismatch: assignment does not match" self.quads.append((operator.value, right_operand.address, None, left_operand.address)) debug() # While # -------------------------------------------- def while_condition_start(self): debug("while_condition_start") counter = len(self.quads) # TODO: counter self.JumpStack.push(counter) def while_block_start(self): debug("while_block_start") expression_type = self.TypeStack.pop() if expression_type != DType.BOOL: raise Exception( "Type mismatch on line {}".format("SOMELINE FIX THIS TODO")) result = self.OperandStack.pop() debug("ADD QUAD: while block_start GOTOF") self.quads.append([Operator.GOTOF.value, result.address, None, None]) counter = len(self.quads) self.JumpStack.push(counter - 1) # TODO: counter def while_end(self): debug("while_end") pending_while_end_jump = self.JumpStack.pop() return_pos = self.JumpStack.pop() debug("ADD QUAD: while_end GOTO return") self.quads.append([Operator.GOTO.value, None, None, return_pos]) counter = len(self.quads) # TODO: change this self.fill(pending_while_end_jump, counter) # Function definitions # -------------------------------------------- def procedure_start(self, name: str): '''Insert procedure name into dirfunc table, verify semantics''' debug('procedure_start') current_scope = self.ScopeStack.peek() # Check that it's not already defined in the **current** scope if current_scope.exists(name) or name in special_functions: raise Exception('The name {} is already defined'.format(name)) func = Function(name, current_scope) current_scope.insert(func) self._current_func = func self._func_returned = False self.ScopeStack.push(func.varsTable) self._TempStack.push(func.varsTable.memory) def procedure_add_params(self, params): '''Add the total params to the current function''' debug('procedure_add_params') for param in params: self.procedure_add_param(param['type'], param['id']) def procedure_add_param(self, type_str: str, pname: str): '''Call function.add_param()''' debug('procedure_add_param') assert self._current_func is not None current_scope = self.ScopeStack.peek() if current_scope.exists(pname): raise Exception( 'Redefinition of parameter {} in function signature'.format( pname)) data_type = mapType(type_str) address = current_scope.memory.next_address(data_type) var = Variable(pname, data_type, address) self._current_func.add_param(var) def procedure_mark_start(self): '''Mark the current quadruple counter as the start of this function''' debug('procedure_mark_start') self._current_func.start_pos = len(self.quads) def procedure_set_type(self, type_str: str): '''Set the return type of this function''' self._current_func.update_type(mapType(type_str)) def procedure_update_size(self): '''Once we know the number of temps, and local variables defined, we can update the size''' # TODO: define this debug('procedure_update_size') def procedure_return(self, returns_expresion=False): '''Generate a RETURN command''' debug('procedure_return') retval = None if returns_expresion: retval = self.OperandStack.pop() retval_type = self.TypeStack.pop() assert self._current_func.dtype == retval_type, 'Type mismatch: value returned does not match function signature.' self._func_returned = True retval = retval.address else: assert self._current_func.is_void, 'Type mismatch: no value returned in non-void function.' self.quads.append((Operator.RETURN.value, retval, None, None)) debug() def procedure_end(self): '''Generate an ENDPROC''' debug('procedure_end') # TODO: release the current vartable? # TODO: resolve any ERAs here # self.procedure_update_size() # if function is non-void, it returned something if not self._current_func.is_void and not self._func_returned: raise Exception("Non-void function must return a valid value.") self.quads.append((Operator.ENDPROC.value, None, None, None)) debug(('FUNCTION TYPE:', self._current_func.dtype)) self._current_func = None self.ScopeStack.pop() self._TempStack.pop() debug() # Function calls # -------------------------------------------- def method_call_start(self, method_name): '''Verify that the procedure exists in DirFunc''' debug('method_call_start') if not self.GScope.exists(method_name): raise Exception( 'Method {} has not been defined'.format(method_name)) call_proc = self.GScope.find( method_name ) # Assumes all functions are defined in the global scope assert call_proc is not None self._call_proc = call_proc debug() def method_call_param_start(self): '''Start reading parameters for function call''' debug('method_call_param_start') debug("Start param k counter at 0") self._param_k = 0 fname = self._call_proc.name # TODO: add stack/list to keep track of these self.quads.append([Operator.ERA.value, fname, None, None]) # ActivationRecord expansion debug() def method_call_param(self): '''Get the kth parameter for the function call and perform semantic validations''' param = self.OperandStack.pop() param_type = self.TypeStack.pop() assert self._param_k is not None assert self._param_k < len(self._call_proc.param_list) kth_param = self._call_proc.param_list[self._param_k] kth_param_type = kth_param.dtype # Check param_type # TODO: estos no **tienen** que ser iguales, solo compatibles assert param_type == kth_param_type # TODO: Aqui es donde entra el cubo semantico self.quads.append( (Operator.PARAM.value, param.address, None, kth_param.address)) def method_call_param_end(self): '''Verify the last parameter points to null''' # i.e. we consumed all of the parameters # i.e. the parameter call matches the function definition # NOTE: when the argument list ends, the k pointer should be pointing at the last elem (len-1) arglength = len(self._call_proc.param_list) if arglength == 0: assert self._param_k == 0, "Parameter count mismatch." else: assert self._param_k == arglength - 1, "Parameter count mismatch." def method_call_end(self): '''Generate a GOSUB to take control flow the procedure''' debug('method_call_end') func = self._call_proc name = func.name init_address = func.start_pos is_void = func.is_void return_type = func.dtype self._call_proc = None self._param_k = None self.quads.append(( Operator.GOSUB.value, name, None, init_address)) # TODO: migaja de pan pa saber donde voy a regresar # If the function returned something, we should assign it to a local temporary var if not is_void: result = self._TempStack.peek().next(return_type) self.OperandStack.push(result) self.TypeStack.push(return_type) self.quads.append(('=', name, None, result.address)) else: # TODO: test this thoroughly, not sure if this is going to work assert self.OperatorStack.peek( ) is not Operator.ASSIGN, 'Void function does not return anything. Cannot assign void value.' debug() def print_start(self): self._print = True def print_end(self): self._print = False def print_expression(self): debug("print_expression") assert self._print op = self.OperandStack.pop() self.TypeStack.pop() self.quads.append((Operator.PRINT.value, None, None, op.address)) debug() def paren_open(self): self.OperatorStack.push(Operator.FAKE) def paren_close(self): self.OperatorStack.pop()