def compile_test_function(self, call_node: ast.Call, equals=None): if call_node.keywords: raise EsdSyntaxError(call_node.lineno, "You cannot use keyword arguments in test functions (yet).") try: f_id = TEST_FUNCTIONS_ID_BY_TYPE_NAME[self.ESD.esd_type, call_node.func.id] except KeyError: try: f_id = int(self._TEST_DEFAULT_RE.match(call_node.func.id).group(1)) except AttributeError: raise EsdValueError(call_node.lineno, f"Invalid ESD function name: '{call_node.func.id}'.") args = self.parse_args(call_node.args) call = (f_id, *args) if call in self.registers: # Load from register. return struct.pack('B', self.registers.index(call) + 175) compiled = self.compile_number(f_id) + b''.join(self.compile_ezl(arg) for arg in args) compiled += FUNCTION_ARG_BYTES_BY_COUNT[len(args)] if call in self.current_to_write: compiled += self.save_into_next_available_register(call) self.current_to_write.remove(call) if equals is not None: if equals == 0: compiled += b'\x40\x95' elif equals == 1: compiled += b'\x41\x95' else: raise ValueError("Internal error: 'equals' arg should only be 0 or 1 (or None).") return compiled
def build_command(self, node): """Pass in the body of a function def, or a list of nodes before 'return' in a test block.""" if self.is_state_machine_call(node): bank = 6 # TODO: True in every game/file? f_id = 0x80000000 - node.value.func.slice.value.n if not isinstance(f_id, int): raise EsdValueError( node.lineno, "State machine call must have an integer index.") elif self.is_call(node): try: bank, f_id = COMMANDS_BANK_ID_BY_TYPE_NAME[self.ESD.esd_type, node.value.func.id] except KeyError: command_match = self._COMMAND_DEFAULT_RE.match( node.value.func.id) if not command_match: raise EsdError( node.lineno, f"Invalid enter/exit/ongoing command: {node.value.func.id}" ) bank, f_id = command_match.group(1, 2) bank, f_id = int(bank), int(f_id) else: raise EsdSyntaxError( node.lineno, f"Expected only function calls, not node type {type(node)}.") # TODO: Check arg count against canonical function, once available, and order keyword args. args = node.value.args + [ keyword.value for keyword in node.value.keywords ] command_args = [self.compile_ezl(arg) + b"\xa1" for arg in args] return self.ESD.Command(self.ESD.esd_type, bank, f_id, command_args)
def parse_args(arg_nodes): args = [] for node in arg_nodes: if isinstance(node, ast.Num): args.append(node.n) elif isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.USub): args.append(-node.operand.n) elif isinstance(node, (ast.Subscript, ast.Name)): args.append(node) else: raise EsdValueError(node.lineno, "Function arguments must be numeric literals.") return tuple(args)
def get_ast_sequence(node): """ List/tuple can only contain literals. """ if isinstance(node, (ast.Tuple, ast.List)): t = [] for e in node.elts: if isinstance(e, ast.Num): t.append(e.n) elif isinstance(e, ast.Str): t.append(e.s) else: raise EsdValueError(node.lineno, f"Sequences must contain only numeric/string literals.") return t raise EsdSyntaxError(node.lineno, f"Expected a list or tuple node, but found: {type(node)}")