Map sorting and usecode
This commit is contained in:
parent
589bfc31ef
commit
af5b77ea13
7 changed files with 1497 additions and 39 deletions
|
|
@ -2544,6 +2544,110 @@ def render_selector_chain(
|
|||
return rendered, label_to_index[join_label]
|
||||
|
||||
|
||||
def render_loop_construct(
|
||||
blocks: list[tuple[str, list[str]]],
|
||||
label_to_index: dict[str, int],
|
||||
index: int,
|
||||
end_index: int,
|
||||
return_labels: set[str],
|
||||
active_regions: set[tuple[int, int, tuple[str, ...]]] | None = None,
|
||||
render_cache: dict[tuple[int, int, tuple[str, ...]], tuple[list[str], bool] | None] | None = None,
|
||||
) -> tuple[list[str], int] | None:
|
||||
_, statements = blocks[index]
|
||||
if not statements:
|
||||
return None
|
||||
|
||||
terminal = parse_terminal_statement(statements[-1])
|
||||
if terminal is None or terminal.kind != "if":
|
||||
return None
|
||||
|
||||
target_label = terminal.target or ""
|
||||
target_index = label_to_index.get(target_label)
|
||||
if target_index is None or target_index <= index or target_index > end_index:
|
||||
return None
|
||||
|
||||
loop_tail_index = last_nonempty_block_index(blocks, index + 1, target_index)
|
||||
if loop_tail_index is None:
|
||||
return None
|
||||
|
||||
loop_tail_terminal = parse_terminal_statement(blocks[loop_tail_index][1][-1])
|
||||
if loop_tail_terminal is None or loop_tail_terminal.kind != "goto" or loop_tail_terminal.target != blocks[index][0]:
|
||||
return None
|
||||
|
||||
loop_body = render_structured_region(
|
||||
blocks,
|
||||
label_to_index,
|
||||
index + 1,
|
||||
target_index,
|
||||
return_labels,
|
||||
{blocks[index][0]},
|
||||
active_regions,
|
||||
render_cache,
|
||||
)
|
||||
if loop_body is None:
|
||||
return None
|
||||
|
||||
loop_lines, _ = loop_body
|
||||
loop_selector = None
|
||||
if index > 0 and is_loop_selector_only_block(blocks[index - 1][1]):
|
||||
loop_selector = parse_loop_selector_statement(blocks[index - 1][1][0])
|
||||
|
||||
rendered: list[str] = []
|
||||
if loop_selector is not None:
|
||||
rendered.append(f"for {loop_selector} {{")
|
||||
else:
|
||||
rendered.append(f"while ({invert_condition_text(terminal.condition or 'condition')}) {{")
|
||||
rendered.extend(indent_lines(loop_lines))
|
||||
rendered.append("}")
|
||||
return rendered, target_index
|
||||
|
||||
|
||||
def render_infinite_loop_construct(
|
||||
blocks: list[tuple[str, list[str]]],
|
||||
label_to_index: dict[str, int],
|
||||
index: int,
|
||||
end_index: int,
|
||||
return_labels: set[str],
|
||||
active_regions: set[tuple[int, int, tuple[str, ...]]] | None = None,
|
||||
render_cache: dict[tuple[int, int, tuple[str, ...]], tuple[list[str], bool] | None] | None = None,
|
||||
) -> tuple[list[str], int] | None:
|
||||
if index + 1 >= end_index:
|
||||
return None
|
||||
|
||||
loop_label = blocks[index][0]
|
||||
loop_tail_index: int | None = None
|
||||
for candidate in range(end_index - 1, index, -1):
|
||||
statements = blocks[candidate][1]
|
||||
if not statements:
|
||||
continue
|
||||
terminal = parse_terminal_statement(statements[-1])
|
||||
if terminal is not None and terminal.kind == "goto" and terminal.target == loop_label:
|
||||
loop_tail_index = candidate
|
||||
break
|
||||
|
||||
if loop_tail_index is None:
|
||||
return None
|
||||
|
||||
loop_body = render_structured_region(
|
||||
blocks,
|
||||
label_to_index,
|
||||
index,
|
||||
loop_tail_index + 1,
|
||||
return_labels,
|
||||
{loop_label},
|
||||
active_regions,
|
||||
render_cache,
|
||||
)
|
||||
if loop_body is None:
|
||||
return None
|
||||
|
||||
loop_lines, _ = loop_body
|
||||
rendered = ["while (true) {"]
|
||||
rendered.extend(indent_lines(loop_lines))
|
||||
rendered.append("}")
|
||||
return rendered, loop_tail_index + 1
|
||||
|
||||
|
||||
def render_structured_region(
|
||||
blocks: list[tuple[str, list[str]]],
|
||||
label_to_index: dict[str, int],
|
||||
|
|
@ -2635,34 +2739,20 @@ def render_structured_region(
|
|||
index = selector_join_index
|
||||
continue
|
||||
|
||||
if target_index <= end_index:
|
||||
loop_tail_index = last_nonempty_block_index(blocks, index + 1, target_index)
|
||||
if loop_tail_index is not None:
|
||||
loop_tail_terminal = parse_terminal_statement(blocks[loop_tail_index][1][-1])
|
||||
if loop_tail_terminal is not None and loop_tail_terminal.kind == "goto" and loop_tail_terminal.target == blocks[index][0]:
|
||||
loop_body = render_structured_region(
|
||||
blocks,
|
||||
label_to_index,
|
||||
index + 1,
|
||||
target_index,
|
||||
return_labels,
|
||||
{blocks[index][0]},
|
||||
active_regions,
|
||||
render_cache,
|
||||
)
|
||||
if loop_body is not None:
|
||||
loop_lines, _ = loop_body
|
||||
loop_selector = None
|
||||
if index > start_index:
|
||||
loop_selector = parse_loop_selector_statement(blocks[index - 1][1][0]) if is_loop_selector_only_block(blocks[index - 1][1]) else None
|
||||
if loop_selector is not None:
|
||||
lines.append(f"for {loop_selector} {{")
|
||||
else:
|
||||
lines.append(f"while ({invert_condition_text(terminal.condition or 'condition')}) {{")
|
||||
lines.extend(indent_lines(loop_lines))
|
||||
lines.append("}")
|
||||
index = target_index
|
||||
continue
|
||||
loop_construct = render_loop_construct(
|
||||
blocks,
|
||||
label_to_index,
|
||||
index,
|
||||
end_index,
|
||||
return_labels,
|
||||
active_regions,
|
||||
render_cache,
|
||||
)
|
||||
if loop_construct is not None:
|
||||
loop_lines, loop_join_index = loop_construct
|
||||
lines.extend(loop_lines)
|
||||
index = loop_join_index
|
||||
continue
|
||||
|
||||
true_tail_index = last_nonempty_block_index(blocks, index + 1, target_index)
|
||||
if true_tail_index is not None:
|
||||
|
|
@ -2817,6 +2907,38 @@ def render_partially_structured_blocks(blocks: list[tuple[str, list[str]]]) -> l
|
|||
index = selector_join_index
|
||||
continue
|
||||
|
||||
loop_construct = render_loop_construct(
|
||||
blocks,
|
||||
label_to_index,
|
||||
index,
|
||||
len(blocks),
|
||||
return_labels,
|
||||
)
|
||||
if loop_construct is not None:
|
||||
loop_lines, loop_join_index = loop_construct
|
||||
lines.append(f" {label}:")
|
||||
for statement in loop_lines:
|
||||
lines.append(f" {statement}" if statement else "")
|
||||
lines.append("")
|
||||
index = loop_join_index
|
||||
continue
|
||||
|
||||
infinite_loop_construct = render_infinite_loop_construct(
|
||||
blocks,
|
||||
label_to_index,
|
||||
index,
|
||||
len(blocks),
|
||||
return_labels,
|
||||
)
|
||||
if infinite_loop_construct is not None:
|
||||
loop_lines, loop_join_index = infinite_loop_construct
|
||||
lines.append(f" {label}:")
|
||||
for statement in loop_lines:
|
||||
lines.append(f" {statement}" if statement else "")
|
||||
lines.append("")
|
||||
index = loop_join_index
|
||||
continue
|
||||
|
||||
lines.append(f" {label}:")
|
||||
for statement in statements:
|
||||
lines.append(f" {statement}")
|
||||
|
|
@ -2855,6 +2977,47 @@ def render_pseudocode(ir: dict[str, Any], shape_catalog: ShapeCatalog | None = N
|
|||
return apply_shape_catalog_to_pseudocode("\n".join(lines) + "\n", shape_catalog)
|
||||
|
||||
|
||||
def validate_pseudocode_text(text: str) -> list[str]:
|
||||
errors: list[str] = []
|
||||
label_lines: dict[str, int] = {}
|
||||
goto_targets: list[tuple[str, int]] = []
|
||||
brace_depth = 0
|
||||
|
||||
for line_number, raw_line in enumerate(text.splitlines(), start=1):
|
||||
stripped = raw_line.strip()
|
||||
if not stripped:
|
||||
continue
|
||||
|
||||
if stripped.endswith("{"):
|
||||
brace_depth += 1
|
||||
if stripped == "}":
|
||||
brace_depth -= 1
|
||||
if brace_depth < 0:
|
||||
errors.append(f"line {line_number}: unexpected closing brace")
|
||||
brace_depth = 0
|
||||
|
||||
label_match = re.fullmatch(r"([A-Za-z_][A-Za-z0-9_]*):", stripped)
|
||||
if label_match is not None:
|
||||
label = label_match.group(1)
|
||||
previous_line = label_lines.get(label)
|
||||
if previous_line is not None:
|
||||
errors.append(f"line {line_number}: duplicate label {label} (first at line {previous_line})")
|
||||
else:
|
||||
label_lines[label] = line_number
|
||||
|
||||
for match in re.finditer(r"\bgoto ([A-Za-z_][A-Za-z0-9_]*)\s*;", stripped):
|
||||
goto_targets.append((match.group(1), line_number))
|
||||
|
||||
if brace_depth != 0:
|
||||
errors.append(f"unbalanced braces: final depth {brace_depth}")
|
||||
|
||||
for target, line_number in goto_targets:
|
||||
if target not in label_lines:
|
||||
errors.append(f"line {line_number}: goto target {target} has no label")
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def render_text(ir: dict[str, Any]) -> str:
|
||||
labels = build_listing_labels(ir)
|
||||
|
||||
|
|
|
|||
1000
tools/render_crusader_map.py
Normal file
1000
tools/render_crusader_map.py
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -9,6 +9,7 @@ from tools.poc_crusader_usecode_parser import (
|
|||
render_partially_structured_blocks,
|
||||
render_structured_pseudocode,
|
||||
try_decode_loop_selector,
|
||||
validate_pseudocode_text,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -222,6 +223,58 @@ class UsecodeStructuringTests(unittest.TestCase):
|
|||
self.assertNotIn("block_0358:", text)
|
||||
self.assertNotIn("goto block_0469;", text)
|
||||
|
||||
def test_generic_loop_renders_in_partial_fallback(self) -> None:
|
||||
blocks = [
|
||||
("entry", ["goto block_01E2;"]),
|
||||
("block_01E2", ["counter = 0;"]),
|
||||
("block_025C", ["if (counter <= rndNum) goto block_0315;"]),
|
||||
("block_0267", ["counter2 = 1;"]),
|
||||
("block_026E", ["if (counter2 <= 7) goto block_02B6;"]),
|
||||
("block_0276", ["spawn FREE.waitNTimerTicks(pid, 10, 0x00000000);", "suspend;", "counter2 = (1 + counter2);", "goto block_026E;"]),
|
||||
("block_02B6", ["counter = (1 + counter);", "goto block_025C;"]),
|
||||
("block_0315", ["goto block_01E2;"]),
|
||||
]
|
||||
|
||||
rendered = render_partially_structured_blocks(blocks)
|
||||
|
||||
text = "\n".join(rendered)
|
||||
self.assertIn("while (true) {", text)
|
||||
self.assertIn("while (counter > rndNum) {", text)
|
||||
self.assertIn("while (counter2 > 7) {", text)
|
||||
self.assertNotIn("block_026E:", text)
|
||||
self.assertNotIn("goto block_025C;", text)
|
||||
|
||||
def test_infinite_loop_region_renders_as_while_true(self) -> None:
|
||||
blocks = [
|
||||
("entry", ["set_info(0x021B, *(arg_06));"]),
|
||||
("block_01E2", ["suspend;", "FREE.slot_20(100);", "if (retval > 50) goto block_0318;"]),
|
||||
("block_0205", ["FREE.slot_20(pid, 120);", "goto block_046D;"]),
|
||||
("block_0318", ["FREE.slot_20(pid, 60);"]),
|
||||
("block_046D", ["goto block_01E2;"]),
|
||||
("block_0470", ["return;"]),
|
||||
]
|
||||
|
||||
rendered = render_partially_structured_blocks(blocks)
|
||||
|
||||
text = "\n".join(rendered)
|
||||
self.assertIn("while (true) {", text)
|
||||
self.assertNotIn("goto block_01E2;", text)
|
||||
self.assertNotIn("block_046D:", text)
|
||||
|
||||
def test_pseudocode_validator_reports_missing_label(self) -> None:
|
||||
errors = validate_pseudocode_text(
|
||||
"function sample()\n{\n entry:\n goto missing;\n}\n"
|
||||
)
|
||||
|
||||
self.assertEqual(errors, ["line 4: goto target missing has no label"])
|
||||
|
||||
def test_pseudocode_validator_accepts_balanced_text(self) -> None:
|
||||
errors = validate_pseudocode_text(
|
||||
"function sample()\n{\n entry:\n while (true) {\n goto entry;\n }\n}\n"
|
||||
)
|
||||
|
||||
self.assertEqual(errors, [])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue