ppc2arm 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ppc2arm/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """PPC2ARM — PowerPC <-> ARM instruction picker."""
2
+
3
+ __version__ = "1.0.0"
ppc2arm/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from ppc2arm.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
ppc2arm/cli.py ADDED
@@ -0,0 +1,443 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ ppc2arm — PowerPC ↔ ARM / AArch64 Instruction Picker
4
+ =====================================================
5
+
6
+ Translate PowerPC mnemonics to their ARM/AArch64 equivalents and vice-versa,
7
+ or launch the retro ISA-museum GUI.
8
+
9
+ Usage examples
10
+ --------------
11
+ Single-shot lookup (PPC → ARM):
12
+ ppc2arm --from ppc --instr lwz
13
+ ppc2arm --from ppc --instr fmadd --mode 64
14
+
15
+ Single-shot lookup (ARM → PPC):
16
+ ppc2arm --from arm --instr LDR
17
+ ppc2arm --from arm --instr FMADD --mode 64
18
+
19
+ Interactive REPL (no arguments, or force with --repl):
20
+ ppc2arm
21
+ ppc2arm --repl
22
+
23
+ Launch the wxPython GUI:
24
+ ppc2arm --gui
25
+ ppc2arm --gui --mode 64
26
+
27
+ REPL commands
28
+ -------------
29
+ lookup <mnemonic> Look up the current source ISA mnemonic
30
+ search <query> Full-text search across all instructions
31
+ set dir ppc|arm Switch source ISA direction
32
+ set mode 32|64 Switch 32/64-bit mode
33
+ list categories List available instruction categories
34
+ info <isa> Show ISA profile card (arm, ppc, sparc, alpha, ...)
35
+ random fact [<isa>] Display a random ISA trivia fact
36
+ help Show this help text
37
+ quit / exit Exit the REPL
38
+ """
39
+
40
+ import argparse
41
+ import sys
42
+ import os
43
+
44
+ # ---------------------------------------------------------------------------
45
+ # Colour helpers (ANSI; disabled when not a TTY)
46
+ # ---------------------------------------------------------------------------
47
+
48
+ _USE_COLOR = sys.stdout.isatty()
49
+
50
+ _C = {
51
+ "reset": "\033[0m",
52
+ "bold": "\033[1m",
53
+ "green": "\033[32m",
54
+ "yellow": "\033[33m",
55
+ "cyan": "\033[36m",
56
+ "red": "\033[31m",
57
+ "blue": "\033[34m",
58
+ "dim": "\033[2m",
59
+ "magenta":"\033[35m",
60
+ }
61
+
62
+
63
+ def _c(name: str, text: str) -> str:
64
+ if not _USE_COLOR:
65
+ return text
66
+ return f"{_C[name]}{text}{_C['reset']}"
67
+
68
+
69
+ # ---------------------------------------------------------------------------
70
+ # Engine import (lazy — avoids import cost when --gui branches early)
71
+ # ---------------------------------------------------------------------------
72
+
73
+ def _get_engine():
74
+ """Import and return a shared InstructionEngine instance."""
75
+ from ppc2arm.core.engine import InstructionEngine
76
+ return InstructionEngine()
77
+
78
+
79
+ # ---------------------------------------------------------------------------
80
+ # Result formatting
81
+ # ---------------------------------------------------------------------------
82
+
83
+ _CONF_COLOR = {
84
+ "direct": "green",
85
+ "close": "cyan",
86
+ "suggestion": "yellow",
87
+ "none": "red",
88
+ "fuzzy": "dim",
89
+ }
90
+
91
+ _CONF_LABEL = {
92
+ "direct": "[DIRECT]",
93
+ "close": "[CLOSE]",
94
+ "suggestion": "[SUGGESTION]",
95
+ "none": "[NO MATCH]",
96
+ "fuzzy": "[FUZZY]",
97
+ }
98
+
99
+
100
+ def format_result(result, direction: str) -> str:
101
+ """Render a LookupResult to a multi-line terminal string."""
102
+ lines = []
103
+ conf = result.confidence
104
+ color = _CONF_COLOR.get(conf, "dim")
105
+ badge = _CONF_LABEL.get(conf, f"[{conf.upper()}]")
106
+
107
+ src = result.source_mnemonic
108
+ si = result.source_instr
109
+
110
+ lines.append("")
111
+ lines.append(_c("bold", f" {src.upper()}") + " " + _c(color, badge))
112
+ if si:
113
+ lines.append(_c("dim", f" {si.get('full_name', '')}"))
114
+ lines.append(f" Syntax : {si.get('syntax', 'n/a')}")
115
+ lines.append(f" {si.get('description', '')}")
116
+
117
+ if conf == "fuzzy":
118
+ lines.append("")
119
+ lines.append(_c("yellow", " No exact match found.") + " Did you mean:")
120
+ for mnem, score in result.alternatives:
121
+ lines.append(f" {_c('cyan', mnem):<20} score={score:.2f}")
122
+ lines.append("")
123
+ return "\n".join(lines)
124
+
125
+ if conf == "none":
126
+ lines.append("")
127
+ lines.append(_c("red", f" {result.notes}"))
128
+ lines.append("")
129
+ return "\n".join(lines)
130
+
131
+ arrow = "→" if direction == "ppc" else "←"
132
+ dest_isa = "ARM" if direction == "ppc" else "PPC"
133
+ lines.append("")
134
+ lines.append(_c("bold", f" {arrow} {dest_isa} analogues:"))
135
+
136
+ for i, tgt in enumerate(result.targets):
137
+ ti = result.target_instrs[i] if i < len(result.target_instrs) else None
138
+ fname = ti.get("full_name", "") if ti else ""
139
+ syn = ti.get("syntax", "") if ti else ""
140
+ desc = ti.get("description", "") if ti else ""
141
+ lines.append(f" {_c('bold', tgt):<22} {fname}")
142
+ if syn:
143
+ lines.append(f" {'':22} Syntax: {syn}")
144
+ if desc:
145
+ lines.append(f" {'':22} {_c('dim', desc[:80])}")
146
+
147
+ if result.notes:
148
+ lines.append("")
149
+ if conf == "suggestion":
150
+ lines.append(_c("yellow", " ⚠ " + result.notes))
151
+ else:
152
+ lines.append(_c("dim", " ℹ " + result.notes))
153
+
154
+ lines.append("")
155
+ return "\n".join(lines)
156
+
157
+
158
+ def format_isa_profile(profile: dict) -> str:
159
+ """Render an ISA profile dict as a museum-placard style string."""
160
+ import textwrap
161
+ lines = []
162
+ w = 60
163
+ lines.append(" " + "─" * w)
164
+ lines.append(f" {_c('bold', profile['name'])}")
165
+ lines.append(f" Vendor : {profile['vendor']}")
166
+ lines.append(f" Born : {profile['year']} "
167
+ f"Died: {profile.get('died') or 'still alive'}")
168
+ lines.append(f" Bits : {profile['bits']}-bit")
169
+ lines.append(f" Endian : {profile['endian']}")
170
+ lines.append(f" Regs : {profile['registers']} integer registers")
171
+ lines.append("")
172
+ desc = profile.get("description", "")
173
+ for line in textwrap.wrap(desc, width=58):
174
+ lines.append(f" {line}")
175
+ lines.append("")
176
+ lines.append(_c("dim", " Famous for:"))
177
+ for line in textwrap.wrap(profile.get("famous_for", ""), width=58):
178
+ lines.append(f" {line}")
179
+ lines.append(" " + "─" * w)
180
+ return "\n".join(lines)
181
+
182
+
183
+ # ---------------------------------------------------------------------------
184
+ # Argument parser
185
+ # ---------------------------------------------------------------------------
186
+
187
+ def build_parser() -> argparse.ArgumentParser:
188
+ parser = argparse.ArgumentParser(
189
+ prog="ppc2arm",
190
+ description=(
191
+ "PPC2ARM — PowerPC ↔ ARM/AArch64 Instruction Picker.\n\n"
192
+ "Translates PowerPC mnemonics to ARM/AArch64 equivalents (and vice-versa). "
193
+ "Run without arguments to enter the interactive REPL."
194
+ ),
195
+ formatter_class=argparse.RawDescriptionHelpFormatter,
196
+ epilog=__doc__,
197
+ )
198
+
199
+ parser.add_argument(
200
+ "--gui", action="store_true",
201
+ help="Launch the wxPython GUI (requires wxPython>=4.2).",
202
+ )
203
+ parser.add_argument(
204
+ "--from",
205
+ dest="source_isa",
206
+ choices=["ppc", "arm"],
207
+ metavar="{ppc,arm}",
208
+ help="Source ISA for a single-shot lookup.",
209
+ )
210
+ parser.add_argument(
211
+ "--instr",
212
+ dest="mnemonic",
213
+ metavar="MNEMONIC",
214
+ help="Instruction mnemonic to look up (e.g. lwz, LDR).",
215
+ )
216
+ parser.add_argument(
217
+ "--mode",
218
+ choices=["32", "64"],
219
+ default="32",
220
+ help="Target bit-mode: 32 (default) or 64.",
221
+ )
222
+ parser.add_argument(
223
+ "--repl",
224
+ action="store_true",
225
+ help="Force interactive REPL.",
226
+ )
227
+
228
+ return parser
229
+
230
+
231
+ # ---------------------------------------------------------------------------
232
+ # Single-shot
233
+ # ---------------------------------------------------------------------------
234
+
235
+ def run_single_shot(args: argparse.Namespace) -> None:
236
+ engine = _get_engine()
237
+ result = engine.lookup(args.mnemonic, args.source_isa, args.mode)
238
+ print(format_result(result, args.source_isa))
239
+ sys.exit(0)
240
+
241
+
242
+ # ---------------------------------------------------------------------------
243
+ # REPL
244
+ # ---------------------------------------------------------------------------
245
+
246
+ _REPL_HELP = """\
247
+ Commands
248
+ --------
249
+ lookup <mnemonic> Translate mnemonic (uses current dir/mode)
250
+ search <query> Full-text search instructions
251
+ set dir ppc|arm Switch direction
252
+ set mode 32|64 Switch bit mode
253
+ list categories List instruction categories
254
+ info <isa> Show ISA profile (arm, ppc, sparc, alpha, mips,
255
+ pa-risc, itanium, 68k, vax, riscv)
256
+ random fact [<isa>] Random ISA trivia fact
257
+ help Show this help
258
+ quit / exit Exit
259
+ """
260
+
261
+
262
+ def _repl_banner() -> str:
263
+ return (
264
+ _c("bold", "\n PPC2ARM — ISA Translation Station ")
265
+ + _c("magenta", "PowerPC ↔ ARM/AArch64")
266
+ + _c("dim", " v1.0.0\n")
267
+ )
268
+
269
+
270
+ def run_repl(args: argparse.Namespace) -> None:
271
+ engine = _get_engine()
272
+
273
+ try:
274
+ import readline # noqa: F401
275
+ except ImportError:
276
+ try:
277
+ import pyreadline3 as readline # noqa: F401
278
+ except ImportError:
279
+ pass
280
+
281
+ from ppc2arm.data.isa_trivia import COMPILER_QUOTES
282
+ import random
283
+
284
+ direction = "ppc"
285
+ mode = getattr(args, "mode", "32")
286
+
287
+ print(_repl_banner())
288
+ print(_c("dim", f" Direction: {direction.upper()} → {'ARM' if direction == 'ppc' else 'PPC'} "
289
+ f"Mode: {mode}-bit"))
290
+ print(_c("dim", " Type 'help' for commands, 'quit' to exit.\n"))
291
+
292
+ print(_c("cyan", f" \"{random.choice(COMPILER_QUOTES)}\"\n"))
293
+
294
+ try:
295
+ while True:
296
+ try:
297
+ prompt = _c("green", f"ppc2arm[{direction}/{mode}]> ")
298
+ line = input(prompt).strip()
299
+ except EOFError:
300
+ print()
301
+ break
302
+ if not line:
303
+ continue
304
+
305
+ parts = line.split(None, 1)
306
+ cmd = parts[0].lower()
307
+ rest = parts[1] if len(parts) > 1 else ""
308
+
309
+ if cmd in ("quit", "exit", "q"):
310
+ break
311
+
312
+ elif cmd == "help":
313
+ print(_REPL_HELP)
314
+
315
+ elif cmd == "lookup" and rest:
316
+ result = engine.lookup(rest.strip(), direction, mode)
317
+ print(format_result(result, direction))
318
+
319
+ elif cmd == "search" and rest:
320
+ isa = direction
321
+ matches = engine.search(rest.strip(), isa, mode)
322
+ if matches:
323
+ print(f"\n {len(matches)} matches in {isa.upper()}:")
324
+ for m in matches:
325
+ print(f" {_c('cyan', m)}")
326
+ else:
327
+ print(_c("yellow", f" No matches for '{rest.strip()}'."))
328
+ print()
329
+
330
+ elif cmd == "set":
331
+ sub_parts = rest.lower().split()
332
+ if len(sub_parts) == 2 and sub_parts[0] == "dir":
333
+ if sub_parts[1] in ("ppc", "arm"):
334
+ direction = sub_parts[1]
335
+ dest = "ARM" if direction == "ppc" else "PPC"
336
+ print(_c("cyan", f" Direction set to {direction.upper()} → {dest}\n"))
337
+ else:
338
+ print(_c("red", " Usage: set dir ppc|arm\n"))
339
+ elif len(sub_parts) == 2 and sub_parts[0] == "mode":
340
+ if sub_parts[1] in ("32", "64"):
341
+ mode = sub_parts[1]
342
+ print(_c("cyan", f" Mode set to {mode}-bit\n"))
343
+ else:
344
+ print(_c("red", " Usage: set mode 32|64\n"))
345
+ else:
346
+ print(_c("red", " Usage: set dir ppc|arm OR set mode 32|64\n"))
347
+
348
+ elif cmd == "list" and rest.lower().startswith("cat"):
349
+ cats = engine.list_categories(direction)
350
+ print(f"\n Categories ({direction.upper()}):")
351
+ for cat in cats:
352
+ print(f" {_c('cyan', cat)}")
353
+ print()
354
+
355
+ elif cmd == "info" and rest:
356
+ isa_name = rest.strip().lower()
357
+ profile = engine.get_isa_profile(isa_name)
358
+ if profile:
359
+ print(format_isa_profile(profile))
360
+ else:
361
+ avail = ", ".join(sorted(
362
+ ["arm", "ppc", "sparc", "alpha", "mips", "pa-risc",
363
+ "itanium", "68k", "vax", "riscv", "x86"]
364
+ ))
365
+ print(_c("yellow", f" Unknown ISA '{isa_name}'."))
366
+ print(_c("dim", f" Available: {avail}\n"))
367
+
368
+ elif cmd == "random" and rest.lower().startswith("fact"):
369
+ parts2 = rest.split(None, 1)
370
+ isa_filter = parts2[1].strip() if len(parts2) > 1 else None
371
+ fact = engine.random_fact(isa_filter)
372
+ if fact:
373
+ label = fact.get("isa", "").upper()
374
+ cat = fact.get("category", "")
375
+ print(f"\n {_c('bold', label)} {_c('dim', '[' + cat + ']')}")
376
+ import textwrap
377
+ for fl in textwrap.wrap(fact["text"], width=70):
378
+ print(f" {fl}")
379
+ print()
380
+ else:
381
+ print(_c("yellow", f" No facts found for '{isa_filter}'.\n"))
382
+
383
+ else:
384
+ # Interpret whole line as mnemonic lookup
385
+ result = engine.lookup(line, direction, mode)
386
+ print(format_result(result, direction))
387
+
388
+ except KeyboardInterrupt:
389
+ print()
390
+
391
+ sys.exit(0)
392
+
393
+
394
+ # ---------------------------------------------------------------------------
395
+ # GUI
396
+ # ---------------------------------------------------------------------------
397
+
398
+ def run_gui(args: argparse.Namespace) -> None:
399
+ """Launch the wxPython GUI."""
400
+ try:
401
+ import wx # noqa: F401
402
+ except ImportError:
403
+ print(
404
+ "wxPython is not installed. Install it with:\n"
405
+ " pipx inject ppc2arm wxPython (if installed with pipx)\n"
406
+ " pip install wxPython (pip / virtualenv)\n"
407
+ "or visit https://wxpython.org/pages/downloads/"
408
+ )
409
+ sys.exit(1)
410
+ try:
411
+ from ppc2arm.gui.app import PPC2ARMApp
412
+ except ImportError:
413
+ print("GUI not yet fully implemented.")
414
+ sys.exit(0)
415
+ app = PPC2ARMApp(initial_mode=args.mode)
416
+ app.MainLoop()
417
+ sys.exit(0)
418
+
419
+
420
+ # ---------------------------------------------------------------------------
421
+ # Entry point
422
+ # ---------------------------------------------------------------------------
423
+
424
+ def main() -> None:
425
+ parser = build_parser()
426
+ args = parser.parse_args()
427
+
428
+ if args.gui:
429
+ run_gui(args)
430
+ elif args.repl:
431
+ run_repl(args)
432
+ elif args.source_isa and args.mnemonic:
433
+ run_single_shot(args)
434
+ elif args.source_isa and not args.mnemonic:
435
+ parser.error("--from requires --instr MNEMONIC.")
436
+ elif args.mnemonic and not args.source_isa:
437
+ parser.error("--instr requires --from {ppc,arm}.")
438
+ else:
439
+ run_repl(args)
440
+
441
+
442
+ if __name__ == "__main__":
443
+ main()
File without changes