import unittest from h8536.pseudocode import PseudocodeOptions, generate_pseudocode def _instruction( address, mnemonic, operands="", *, kind="normal", targets=None, text=None, ): return { "address": address, "text": text or f"{mnemonic} {operands}".strip(), "mnemonic": mnemonic, "operands": operands, "kind": kind, "targets": list(targets or []), "references": [], "comment": "", } def _payload(instructions): start = min(ins["address"] for ins in instructions) end = max(ins["address"] for ins in instructions) return { "vectors": [], "call_graph": { "nodes": [ { "start": start, "end": end, "label": f"loc_{start:04X}", "sources": [], "instruction_count": len(instructions), "calls": [], } ], "edges": [], }, "instructions": instructions, } def _options(**overrides): values = { "include_asm": False, "include_addresses": False, "emit_declarations": False, } values.update(overrides) return PseudocodeOptions(**values) class PseudocodeStructuringTest(unittest.TestCase): def test_backward_conditional_branch_becomes_do_while(self): payload = _payload( [ _instruction(0x0100, "MOV.B", "#H'00, R0"), _instruction(0x0102, "ADD.B", "#H'01, R0"), _instruction(0x0104, "CMP.B", "#H'03, R0"), _instruction(0x0106, "BNE", "loc_0102", kind="branch", targets=[0x0102]), _instruction(0x0108, "RTS", kind="return"), ] ) text = generate_pseudocode(payload, options=_options()) self.assertIn("do {", text) self.assertIn("} while (!Z);", text) self.assertNotIn("goto loc_0102;", text) self.assertNotIn("loc_0102:", text) def test_forward_conditional_branch_over_small_span_becomes_if(self): payload = _payload( [ _instruction(0x0100, "CMP.B", "#H'00, R0"), _instruction(0x0102, "BEQ", "loc_0108", kind="branch", targets=[0x0108]), _instruction(0x0104, "MOV.B", "#H'01, R1"), _instruction(0x0106, "ADD.B", "#H'02, R1"), _instruction(0x0108, "RTS", kind="return"), ] ) text = generate_pseudocode(payload, options=_options()) self.assertIn("if (!Z) {", text) self.assertIn("R1 = (uint8_t)(0x01);", text) self.assertIn("R1 += (uint8_t)(0x02);", text) self.assertNotIn("goto loc_0108;", text) self.assertNotIn("loc_0108:", text) def test_structuring_can_be_disabled(self): payload = _payload( [ _instruction(0x0100, "CMP.B", "#H'00, R0"), _instruction(0x0102, "BEQ", "loc_0108", kind="branch", targets=[0x0108]), _instruction(0x0104, "MOV.B", "#H'01, R1"), _instruction(0x0108, "RTS", kind="return"), ] ) text = generate_pseudocode(payload, options=_options(structured=False)) self.assertIn("if (Z) goto loc_0108;", text) self.assertIn("loc_0108:", text) self.assertNotIn("if (!Z) {", text) def test_ambiguous_forward_branch_keeps_goto_fallback(self): payload = _payload( [ _instruction(0x0100, "BEQ", "loc_0108", kind="branch", targets=[0x0108]), _instruction(0x0102, "MOV.B", "#H'01, R1"), _instruction(0x0104, "BRA", "loc_0108", kind="jump", targets=[0x0108]), _instruction(0x0108, "RTS", kind="return"), ] ) text = generate_pseudocode(payload, options=_options()) self.assertIn("if (Z) goto loc_0108;", text) self.assertIn("goto loc_0108;", text) self.assertIn("loc_0108:", text) self.assertNotIn("if (!Z) {", text) if __name__ == "__main__": unittest.main()