def write_var(self, name): # writes to name (value to be written should already be on the stack) # tries write to locals first then globals # args are not writable because they're passed on the stack if name in self.locals.keys(): offset = self.locals[name] return helpers.num(offset) + OPCODES.HEAP_WRITE elif name in self.globals.keys(): offset = self.globals[name] return helpers.num(offset) + OPCODES.HEAP_WRITE else: raise TranslationError( "Cannot write to unknown variable {}".format(name))
def read_var(self, name): # return opcodes for retrieving value of variable "name" # tries to look in order: local variables, args, globals if name in self.locals.keys(): offset = self.locals[name] return helpers.num(offset) + OPCODES.HEAP_READ elif name in self.globals.keys(): # name is in local variable list, retrieve from heap offset = self.globals[name] return helpers.num(offset) + OPCODES.HEAP_READ else: raise TranslationError( "Cannot read from unknown variable {}".format(name))
def accept_settings(): update_baud_rate() GlobalElements.Config["parity"] = GlobalElements.SettingsDialog.findChild(QComboBox, "comboBoxParity").currentText() GlobalElements.Config["port"] = GlobalElements.SettingsDialog.findChild(QComboBox, "comboBoxPort").currentText() GlobalElements.Config["bytesize"] = int(GlobalElements.SettingsDialog.findChild(QButtonGroup, "buttonGroupByteSize") \ .checkedButton().text()) GlobalElements.Config["stopbits"] = num(GlobalElements.SettingsDialog.findChild(QButtonGroup, "buttonGroupStopBits") \ .checkedButton().text()) setup_serial_port() GlobalElements.StatusBar.showMessage(u"Ustawienia zaakceptowane") GlobalElements.SettingsDialog.accept()
def translate_function(self, node): # builds a function from a ast node opcodes = "" self.is_in_func = True func_name = node.name self.functions[func_name] = {"offset": self.funcs_len} # load name of args args = [] for arg in node.args.args: args.append(arg.arg) self.alloc_local(arg.arg) self.logger.info("Arg {} at heap[{}]".format( arg.arg, self.locals[arg.arg])) # transfer value of args from the stack to the heap for arg in args[::-1]: opcodes += helpers.num(self.locals[arg]) opcodes += OPCODES.HEAP_WRITE self.logger.info("Building function {} with args {}".format( func_name, ", ".join(args))) self.logger.debug(ast.dump(node)) # scan through to identify all the variables # that are assigned to counter = VariableCounter(node) # store the heap address for local vars for local_var, size in counter.variables.items(): # check if the variable is actually an argument if local_var in args: continue if size > 1: raise TranslationUnknown( "Arrays should be declared globally, not inside a function" ) self.alloc_local(local_var) self.logger.info("Local {} at heap[{}]".format( local_var, self.locals[local_var])) self.logger.info("Variables used: {}".format( ["{} at heap[{}]".format(k, v) for k, v in self.locals.items()])) # handle the actual code in the function opcodes += self.translate_nodes(node.body) opcodes += OPCODES.RETURN # remember to destroy variables on the stack if any extra are left self.locals.clear() self.functions[func_name]["opcodes"] = opcodes self.funcs_len += len(opcodes) self.is_in_func = False
def translate_node(self, node): # translates a python node into a series of lscvm instructions opcode_change = "" node_name = helpers.class_name(node) if node_name == "Name": opcode_change += self.read_var(node.id) elif node_name == "Num": opcode_change += helpers.num(node.n) # a[1] elif node_name == "Subscript": # resolve array index position on the heap opcode_change += helpers.num(self.arrays[node.value.id]["offset"]) opcode_change += self.translate_node(node.slice.value) opcode_change += OPCODES.STACK_ADD ctx_name = helpers.class_name(node.ctx) if ctx_name == "Store": opcode_change += OPCODES.HEAP_WRITE elif ctx_name == "Load": opcode_change += OPCODES.HEAP_READ else: self.logger.info(ast.dump(node)) raise TranslationUnknown( "Unknown Subscript.ctx {}".format(ctx_name)) elif node_name == "BinOp": # load left value onto stack first, then right opcode_change += self.translate_node(node.left) opcode_change += self.translate_node(node.right) op_name = helpers.class_name(node.op) if op_name == "Add": opcode_change += OPCODES.STACK_ADD elif op_name == "Sub": opcode_change += OPCODES.STACK_SUBTRACT elif op_name == "Mult": opcode_change += OPCODES.STACK_MULTIPLY elif op_name == "Div": opcode_change += OPCODES.STACK_DIVIDE else: self.logger.info(ast.dump(node)) raise TranslationUnknown( "Missing handler for BinOp.op {}".format(op_name)) elif node_name == "BoolOp": # load all the Compares onto the stack opcode_change += self.translate_nodes(node.values) op_name = helpers.class_name(node.op) if op_name == "And": # multiply all the conditions together # will result in 0 if any are 0 opcode_change += OPCODES.STACK_MULTIPLY * (len(node.values) - 1) elif op_name == "Or": # add all the conditions together # will result in 1 if at least one is 1 opcode_change += OPCODES.STACK_ADD * (len(node.values) - 1) else: self.logger.info(ast.dump(node)) raise TranslationUnknown( "Missing handler for BoolOp.op {}".format(op_name)) elif node_name == "Assign": # sanity check: cannot assign to more than one variable at a time # should be simple to do though if len(node.targets) > 1: self.logger.info(ast.dump(node)) raise TranslationUnknown( "Cannot assign to more than one variable at a time") # this handles a = [1, 2, 3] if helpers.class_name(node.value) == "List": arr_name = node.targets[0].id if arr_name not in self.arrays: raise TranslationFail( "Tried to write to unknown array {}".format(arr_name)) for i, val in enumerate(node.value.elts): opcode_change += self.translate_node(val) opcode_change += helpers.num( self.arrays[arr_name]["offset"] + i) opcode_change += OPCODES.HEAP_WRITE elif helpers.class_name(node.targets[0]) == "Subscript": opcode_change += self.translate_node(node.value) opcode_change += self.translate_node(node.targets[0]) else: # evaluate node.value and leave it on the stack opcode_change += self.translate_node(node.value) opcode_change += self.write_var(node.targets[0].id) elif node_name == "AugAssign": # a += b opcode_change += self.read_var(node.target.id) opcode_change += self.translate_node(node.value) op_name = helpers.class_name(node.op) if op_name == "Add": opcode_change += OPCODES.STACK_ADD elif op_name == "Sub": opcode_change += OPCODES.STACK_SUBTRACT elif op_name == "Mult": opcode_change += OPCODES.STACK_MULTIPLY elif op_name == "Div": opcode_change += OPCODES.STACK_DIVIDE else: self.logger.info(ast.dump(node)) raise TranslationUnknown( "Missing handler for AugAssign.op {}".format(op_name)) opcode_change += self.write_var(node.target.id) elif node_name == "If": test = self.translate_node(node.test) body = self.translate_nodes(node.body) orelse = self.translate_nodes(node.orelse) body += helpers.num(len(orelse)) body += OPCODES.GO opcode_change += test opcode_change += helpers.num(len(body)) opcode_change += OPCODES.CONDITIONAL_JUMP opcode_change += body opcode_change += orelse elif node_name == "Compare": # compare will leave a 1 on the stack if true, else 0 opcode_change += self.translate_node(node.left) if len(node.comparators) > 1: self.logger.info(ast.dump(node)) raise TranslationUnknown("More than one comparator present") opcode_change += self.translate_node(node.comparators[0]) if len(node.ops) > 1: self.logger.info(ast.dump(node)) raise TranslationUnknown("More than one operator provided") op_name = helpers.class_name(node.ops[0]) if op_name in ["NotEq"]: if op_name == "NotEq": opcode_change += OPCODES.STACK_COMPARE # 1 or -1 on stack if true # changes 0 to 0, non zero to 1 opcode_change += OPCODES.STACK_3 + OPCODES.CONDITIONAL_JUMP # if 0, jump to STACK_1 opcode_change += OPCODES.STACK_1 opcode_change += OPCODES.STACK_1 + OPCODES.GO # then skip past the next instruction opcode_change += OPCODES.STACK_0 elif op_name in ["Eq", "Gt", "Lt"]: # the blocks here are responsible for leaving # a 0 on the stack if true if op_name == "Eq": opcode_change += OPCODES.STACK_COMPARE elif op_name == "Gt": opcode_change += OPCODES.STACK_COMPARE # should be a 1 on the stack if true opcode_change += OPCODES.STACK_1 + OPCODES.STACK_SUBTRACT # subtract 1 so becomes 0 elif op_name == "Lt": opcode_change += OPCODES.STACK_COMPARE # should be a -1 on the stack if true opcode_change += OPCODES.STACK_1 + OPCODES.STACK_ADD # add 1 so becomes 0 # this changes 0 to 1, and non zero to 0 opcode_change += OPCODES.STACK_3 + OPCODES.CONDITIONAL_JUMP # if 0, jump to STACK_1 opcode_change += OPCODES.STACK_0 # here because not zero so add a zero opcode_change += OPCODES.STACK_1 + OPCODES.GO # then skip past the next instruction opcode_change += OPCODES.STACK_1 elif op_name in ["GtE", "LtE"]: if op_name == "GtE": # need to return 1 if the compare result is 1 or 0 # so test twice opcode_change += OPCODES.STACK_COMPARE opcode_change += OPCODES.STACK_0 + OPCODES.STACK_FIND # copy the compare result opcode_change += OPCODES.STACK_1 + OPCODES.STACK_SUBTRACT # subtract 1 so become 0 elif op_name == "LtE": opcode_change += OPCODES.STACK_COMPARE opcode_change += OPCODES.STACK_0 + OPCODES.STACK_FIND # copy the compare result opcode_change += OPCODES.STACK_1 + OPCODES.STACK_ADD # add 1 so become 0 # this tests the top two values on the stack and # returns 1 if either of them are 0 opcode_change += OPCODES.STACK_5 + OPCODES.CONDITIONAL_JUMP # if gt, jump to STACK_DROP opcode_change += OPCODES.STACK_4 + OPCODES.CONDITIONAL_JUMP # if =, jump to STACK_1 opcode_change += OPCODES.STACK_0 # add a zero as the return value opcode_change += OPCODES.STACK_2 + OPCODES.GO # skip next 2 instructions opcode_change += OPCODES.STACK_DROP # destroy that extra value that we cloned since we never compared = opcode_change += OPCODES.STACK_1 else: self.logger.info(ast.dump(node)) raise TranslationUnknown( "Missing handler for operator {}".format(op_name)) elif node_name == "Call": if helpers.class_name(node.func) != "Name": raise TranslationUnknown( "Calling functions this way is not supported") func_name = node.func.id if func_name not in [ "putchar", "putint", "puts", "stack_push", "stack_find" ] and func_name not in self.functions: raise TranslationError( "Tried to call undefined function {}".format(func_name)) if func_name in [ "putchar", "putint", "puts", "stack_push", "stack_find" ]: for arg in node.args: opcode_change += self.translate_node(arg) if func_name == "putchar": opcode_change += OPCODES.PRINT_ASCII elif func_name == "putint": opcode_change += OPCODES.PRINT_NUM elif func_name == "puts": pass elif func_name == "stack_push": # the value would already have been pushed to the stack # as an arg pass elif func_name == "stack_find": opcode_change += OPCODES.STACK_FIND else: if self.is_in_func: # save all locals to the stack for var in self.locals.keys(): opcode_change += self.read_var(var) for arg in node.args: opcode_change += self.translate_node(arg) opcode_change += helpers.num( self.functions[func_name]["offset"]) opcode_change += OPCODES.CALL if self.is_in_func: # restore all locals from the stack # expect the function to leave ONE value on the stack # so the saved variables are at -1 position for var in list(self.locals.keys())[::-1]: opcode_change += OPCODES.STACK_1 + OPCODES.STACK_FIND_REMOVE + self.write_var( var) elif node_name == "While": if node.orelse: self.logger.info(ast.dump(node)) raise TranslationUnknown( "Handling of While.orelse is not implemented") # build something like: # 1: [load -len(everything below)] # 2: [copy previous value] (this is so that i can calculate len(everything below) # without including the length of the actual pushing) # 3: [compare] # 4: [load len(everything below)] # 5: JZ # 6: [body] # 7: JMP (this jumps using -len(everything below) loaded at the start) # 5 to 7 body = OPCODES.CONDITIONAL_JUMP + self.translate_nodes( node.body) + OPCODES.GO # 2 to 7 opcode_change = OPCODES.STACK_0 + OPCODES.STACK_FIND opcode_change += self.translate_node( node.test) + helpers.num(len(body) - 1) + body # 1 to 7 opcode_change = helpers.num(-len(opcode_change)) + opcode_change # remove the two extra jump lengths left on the stack opcode_change += OPCODES.STACK_DROP * 2 elif node_name == "Expr": opcode_change += self.translate_node(node.value) elif node_name == "Return": opcode_change += self.translate_node(node.value) else: raise TranslationUnknown( "Missing handler for node type {}: {}".format( node_name, ast.dump(node))) self.logger.debug("{}: {}".format(ast.dump(node), opcode_change)) return opcode_change
def translate(self, code): # translates code to lscvm tree = ast.parse(code) self.logger.info("Identifying globals...") counter = VariableCounter(tree) for global_var, size in counter.variables.items(): # standard int if size == 1: self.alloc_global(global_var) self.logger.info("Global {} at heap[{}]".format( global_var, self.globals[global_var])) # array else: self.alloc_array(global_var, size) arr = self.arrays[global_var] self.logger.info("Array {} at heap[{}:{}]".format( global_var, arr["offset"], arr["offset"] + arr["size"] - 1)) self.logger.info("Allocated {} global variables".format( len(self.globals))) self.logger.info("Looking for functions...") for node in ast.iter_child_nodes(tree): try: node_name = helpers.class_name(node) if node_name == "FunctionDef": self.translate_function(node) except TranslationError as e: self.logger.error( "Failed to translate function ({}) at line {}: {}".format( helpers.class_name(e), node.lineno, e)) raise e if len(self.functions): self.logger.info("Concantenating functions...") total_func_length = sum( [len(func["opcodes"]) for func in self.functions.values()]) self.logger.info( "Total function length: {}".format(total_func_length)) jump_functions = helpers.num(total_func_length).ljust( FUNCTION_OFFSET_START - 1, " ") + OPCODES.GO if len(jump_functions) > FUNCTION_OFFSET_START: raise TranslationFail( "JMP instruction too long. Increase FUNCTION_OFFSET_START") self.opcodes += jump_functions else: self.logger.info( "No user defined functions, skipping function block") for func_name in self.functions: func = self.functions[func_name] self.opcodes += func["opcodes"] for node in ast.iter_child_nodes(tree): try: node_name = helpers.class_name(node) # silently skip "from stubs import *" because this is used # for testing scripts before compiling if node_name == "ImportFrom" and node.module == "stubs": continue if node_name != "FunctionDef": self.opcodes += self.translate_node(node) except TranslationError as e: self.logger.info(ast.dump(node)) self.logger.error("Failed to translate at line {}: {}".format( node.lineno, e)) raise e except Exception as e: self.logger.info(ast.dump(node)) self.logger.error("Error parsing {}:".format(ast.dump(node))) raise e return self.opcodes