GameBoy Emulator 1
Game Boy emulator core and tooling
Loading...
Searching...
No Matches
opcode_progress.py
1#!/usr/bin/env python3
2
3import argparse
4import os
5import re
6import sys
7from pathlib import Path
8
9
10RESET = "\033[0m"
11BOLD = "\033[1m"
12DIM = "\033[2m"
13GREEN = "\033[32m"
14YELLOW = "\033[33m"
15CYAN = "\033[36m"
16
17FULL_BLOCK = "█"
18EMPTY_BLOCK = "░"
19
20
21def parse_opcode_declarations(header_path: Path) -> list[tuple[str, str]]:
22 entries: list[tuple[str, str]] = []
23 pattern = re.compile(
24 r"^\s*int\s+(op_[A-Za-z0-9_]+)\s*\‍([^;]*\‍)\s*;\s*//\s*(0x[0-9A-Fa-f]+)\s*$"
25 )
26
27 for line in header_path.read_text(encoding="utf-8").splitlines():
28 match = pattern.match(line)
29 if match:
30 entries.append((match.group(1), match.group(2).upper()))
31
32 return entries
33
34
35def collect_dummy_symbols(source_dir: Path) -> set[str]:
36 pattern = re.compile(r"DUMMY\‍(\s*(op_[A-Za-z0-9_]+)\s*\‍)")
37 dummies: set[str] = set()
38
39 for cpp_file in sorted(source_dir.glob("*.cpp")):
40 content = cpp_file.read_text(encoding="utf-8")
41 for symbol in pattern.findall(content):
42 dummies.add(symbol)
43
44 return dummies
45
46
47def use_color(args: argparse.Namespace) -> bool:
48 if args.no_color:
49 return False
50 if args.force_color:
51 return True
52 if os.environ.get("NO_COLOR") is not None:
53 return False
54 return sys.stdout.isatty()
55
56
57def color_text(text: str, color: str, enabled: bool) -> str:
58 if not enabled or not text:
59 return text
60 return f"{color}{text}{RESET}"
61
62
63def progress_bar(
64 done: int, total: int, width: int, unicode_bar: bool, colored: bool, fill_color: str
65) -> str:
66 if total <= 0:
67 empty_char = EMPTY_BLOCK if unicode_bar else "-"
68 return "[" + (empty_char * width) + "]"
69
70 filled = int((done * width) / total)
71 filled = max(0, min(width, filled))
72 empty = width - filled
73
74 full_char = FULL_BLOCK if unicode_bar else "#"
75 empty_char = EMPTY_BLOCK if unicode_bar else "-"
76
77 fill_text = full_char * filled
78 empty_text = empty_char * empty
79
80 if colored:
81 fill_text = color_text(fill_text, fill_color, True)
82 empty_text = color_text(empty_text, DIM, True)
83
84 return f"[{fill_text}{empty_text}]"
85
86
87def print_section(
88 label: str,
89 done: int,
90 total: int,
91 width: int,
92 unicode_bar: bool,
93 colored: bool,
94 fill_color: str,
95 label_width: int,
96) -> None:
97 pct = 0.0 if total == 0 else (done / total) * 100.0
98 bar = progress_bar(done, total, width, unicode_bar, colored, fill_color)
99 spacer = " " * (label_width - len(label) + 1)
100 pct_text = f"{pct:.1f}%"
101 if colored:
102 pct_text = color_text(pct_text, BOLD, True)
103 print(f"- {label}:{spacer}{bar} {pct_text} ({done}/{total})")
104
105
106def main() -> int:
107 parser = argparse.ArgumentParser(
108 description="Show opcode implementation progress based on DUMMY placeholders"
109 )
110 parser.add_argument(
111 "--width",
112 type=int,
113 default=20,
114 help="Progress bar width (default: 20)",
115 )
116 parser.add_argument(
117 "--show-missing",
118 action="store_true",
119 help="Print missing opcode symbols",
120 )
121 parser.add_argument(
122 "--ascii",
123 action="store_true",
124 help="Use ASCII bars (# and -) instead of block bars",
125 )
126 parser.add_argument(
127 "--no-color",
128 action="store_true",
129 help="Disable ANSI colors",
130 )
131 parser.add_argument(
132 "--force-color",
133 action="store_true",
134 help="Force ANSI colors even when not in a TTY",
135 )
136 args = parser.parse_args()
137
138 project_root = Path(__file__).resolve().parent.parent
139
140 base_header = project_root / "include" / "opcodes.hpp"
141 cb_header = project_root / "include" / "cb_opcodes.hpp"
142 base_src = project_root / "src" / "core" / "cpu" / "instructions" / "opcodes"
143 cb_src = project_root / "src" / "core" / "cpu" / "instructions" / "cb_opcodes"
144
145 base_entries = parse_opcode_declarations(base_header)
146 cb_entries = parse_opcode_declarations(cb_header)
147
148 base_dummies = collect_dummy_symbols(base_src)
149 cb_dummies = collect_dummy_symbols(cb_src)
150
151 base_missing = [
152 (name, opcode) for name, opcode in base_entries if name in base_dummies
153 ]
154 cb_missing = [(name, opcode) for name, opcode in cb_entries if name in cb_dummies]
155
156 base_total = len(base_entries)
157 cb_total = len(cb_entries)
158 base_done = base_total - len(base_missing)
159 cb_done = cb_total - len(cb_missing)
160
161 total_all = base_total + cb_total
162 done_all = base_done + cb_done
163
164 colored = use_color(args)
165 unicode_bar = not args.ascii
166 label_width = len("Base Opcodes")
167
168 print_section(
169 "Base Opcodes",
170 base_done,
171 base_total,
172 args.width,
173 unicode_bar,
174 colored,
175 YELLOW,
176 label_width,
177 )
178 print_section(
179 "CB Opcodes",
180 cb_done,
181 cb_total,
182 args.width,
183 unicode_bar,
184 colored,
185 GREEN,
186 label_width,
187 )
188 print_section(
189 "Combined",
190 done_all,
191 total_all,
192 args.width,
193 unicode_bar,
194 colored,
195 CYAN,
196 label_width,
197 )
198
199 if args.show_missing:
200 if base_missing:
201 print("\nMissing Base Opcodes")
202 for name, opcode in base_missing:
203 print(f"- {opcode}: {name}")
204 if cb_missing:
205 print("\nMissing CB Opcodes")
206 for name, opcode in cb_missing:
207 print(f"- {opcode}: {name}")
208 if not base_missing and not cb_missing:
209 print("\nNo missing opcode implementations detected.")
210
211 return 0
212
213
214if __name__ == "__main__":
215 raise SystemExit(main())