dasmos 0.1.2__tar.gz

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.
Files changed (71) hide show
  1. dasmos-0.1.2/LICENSE +21 -0
  2. dasmos-0.1.2/PKG-INFO +315 -0
  3. dasmos-0.1.2/README.md +285 -0
  4. dasmos-0.1.2/pyproject.toml +121 -0
  5. dasmos-0.1.2/setup.cfg +4 -0
  6. dasmos-0.1.2/src/dasmos/__init__.py +65 -0
  7. dasmos-0.1.2/src/dasmos/_text.py +57 -0
  8. dasmos-0.1.2/src/dasmos/cli.py +485 -0
  9. dasmos-0.1.2/src/dasmos/core/__init__.py +8 -0
  10. dasmos-0.1.2/src/dasmos/core/annotations.py +207 -0
  11. dasmos-0.1.2/src/dasmos/core/classification.py +223 -0
  12. dasmos-0.1.2/src/dasmos/core/config.py +74 -0
  13. dasmos-0.1.2/src/dasmos/core/cpu_state.py +46 -0
  14. dasmos-0.1.2/src/dasmos/core/disassembly.py +161 -0
  15. dasmos-0.1.2/src/dasmos/core/labels.py +373 -0
  16. dasmos-0.1.2/src/dasmos/core/markdown_asm.py +374 -0
  17. dasmos-0.1.2/src/dasmos/core/memory.py +318 -0
  18. dasmos-0.1.2/src/dasmos/core/move.py +301 -0
  19. dasmos-0.1.2/src/dasmos/cpu.py +388 -0
  20. dasmos-0.1.2/src/dasmos/disassembler.py +1350 -0
  21. dasmos-0.1.2/src/dasmos/environment.py +141 -0
  22. dasmos-0.1.2/src/dasmos/exceptions.py +6 -0
  23. dasmos-0.1.2/src/dasmos/ext/__init__.py +8 -0
  24. dasmos-0.1.2/src/dasmos/ext/cpus/__init__.py +10 -0
  25. dasmos-0.1.2/src/dasmos/ext/cpus/cmos65c02/__init__.py +22 -0
  26. dasmos-0.1.2/src/dasmos/ext/cpus/cmos65c02/cpu.py +148 -0
  27. dasmos-0.1.2/src/dasmos/ext/cpus/nmos6502/__init__.py +22 -0
  28. dasmos-0.1.2/src/dasmos/ext/cpus/nmos6502/cpu.py +550 -0
  29. dasmos-0.1.2/src/dasmos/ext/environments/__init__.py +0 -0
  30. dasmos-0.1.2/src/dasmos/ext/environments/acorn_bbc_hardware/__init__.py +13 -0
  31. dasmos-0.1.2/src/dasmos/ext/environments/acorn_bbc_hardware/environment.py +164 -0
  32. dasmos-0.1.2/src/dasmos/ext/environments/acorn_mos/__init__.py +13 -0
  33. dasmos-0.1.2/src/dasmos/ext/environments/acorn_mos/enums.py +299 -0
  34. dasmos-0.1.2/src/dasmos/ext/environments/acorn_mos/environment.py +182 -0
  35. dasmos-0.1.2/src/dasmos/ext/environments/acorn_mos/hooks.py +212 -0
  36. dasmos-0.1.2/src/dasmos/ext/environments/acorn_sideways_rom/__init__.py +13 -0
  37. dasmos-0.1.2/src/dasmos/ext/environments/acorn_sideways_rom/environment.py +183 -0
  38. dasmos-0.1.2/src/dasmos/ext/renderers/__init__.py +10 -0
  39. dasmos-0.1.2/src/dasmos/ext/renderers/beebasm/__init__.py +16 -0
  40. dasmos-0.1.2/src/dasmos/ext/renderers/beebasm/renderer.py +1743 -0
  41. dasmos-0.1.2/src/dasmos/ext/renderers/json/__init__.py +16 -0
  42. dasmos-0.1.2/src/dasmos/ext/renderers/json/renderer.py +718 -0
  43. dasmos-0.1.2/src/dasmos/extension.py +250 -0
  44. dasmos-0.1.2/src/dasmos/hooks.py +76 -0
  45. dasmos-0.1.2/src/dasmos/ir.py +117 -0
  46. dasmos-0.1.2/src/dasmos/output.py +82 -0
  47. dasmos-0.1.2/src/dasmos/py.typed +0 -0
  48. dasmos-0.1.2/src/dasmos/renderer.py +272 -0
  49. dasmos-0.1.2/src/dasmos.egg-info/PKG-INFO +315 -0
  50. dasmos-0.1.2/src/dasmos.egg-info/SOURCES.txt +69 -0
  51. dasmos-0.1.2/src/dasmos.egg-info/dependency_links.txt +1 -0
  52. dasmos-0.1.2/src/dasmos.egg-info/entry_points.txt +15 -0
  53. dasmos-0.1.2/src/dasmos.egg-info/requires.txt +4 -0
  54. dasmos-0.1.2/src/dasmos.egg-info/top_level.txt +1 -0
  55. dasmos-0.1.2/tests/test_beebasm.py +643 -0
  56. dasmos-0.1.2/tests/test_cli.py +305 -0
  57. dasmos-0.1.2/tests/test_cmos65c02.py +275 -0
  58. dasmos-0.1.2/tests/test_cpu.py +327 -0
  59. dasmos-0.1.2/tests/test_disassembler.py +451 -0
  60. dasmos-0.1.2/tests/test_driver_roundtrip.py +2444 -0
  61. dasmos-0.1.2/tests/test_econet_bridge_roundtrip.py +396 -0
  62. dasmos-0.1.2/tests/test_environment.py +550 -0
  63. dasmos-0.1.2/tests/test_json_renderer.py +290 -0
  64. dasmos-0.1.2/tests/test_nfs_roundtrip.py +496 -0
  65. dasmos-0.1.2/tests/test_nmos6502.py +401 -0
  66. dasmos-0.1.2/tests/test_output.py +96 -0
  67. dasmos-0.1.2/tests/test_py8dis2dasmos.py +463 -0
  68. dasmos-0.1.2/tests/test_renderer.py +204 -0
  69. dasmos-0.1.2/tests/test_smoke.py +60 -0
  70. dasmos-0.1.2/tests/test_trace.py +381 -0
  71. dasmos-0.1.2/tests/test_tube_client_roundtrip.py +232 -0
dasmos-0.1.2/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Robert Smallshire
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
dasmos-0.1.2/PKG-INFO ADDED
@@ -0,0 +1,315 @@
1
+ Metadata-Version: 2.4
2
+ Name: dasmos
3
+ Version: 0.1.2
4
+ Summary: Pluggable tracing disassembler with CPU, renderer and environment extension points.
5
+ Author-email: Robert Smallshire <rob@sixty-north.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/acornaeology/dasmos
8
+ Project-URL: Repository, https://github.com/acornaeology/dasmos
9
+ Project-URL: Issues, https://github.com/acornaeology/dasmos/issues
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development :: Disassemblers
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Requires-Python: >=3.11
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: asyoulikeit>=1.1
26
+ Requires-Dist: click>=8.0
27
+ Requires-Dist: mistletoe>=1.4
28
+ Requires-Dist: stevedore
29
+ Dynamic: license-file
30
+
31
+ # Dasmos
32
+
33
+ A pluggable tracing disassembler for retro CPUs, version `0.1.2`.
34
+
35
+ <p align="center">
36
+ <a href="https://pypi.org/project/dasmos/"><img src="https://img.shields.io/pypi/v/dasmos.svg" alt="PyPI"></a>
37
+ <a href="https://github.com/acornaeology/dasmos/actions/workflows/release.yml"><img src="https://img.shields.io/github/actions/workflow/status/acornaeology/dasmos/release.yml?label=release" alt="Release"></a>
38
+ <a href="https://github.com/acornaeology/dasmos/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/acornaeology/dasmos/ci.yml?branch=master&label=CI" alt="CI"></a>
39
+ <a href="https://pypi.org/project/dasmos/"><img src="https://img.shields.io/pypi/pyversions/dasmos.svg" alt="Python versions"></a>
40
+ </p>
41
+
42
+ > *From Ancient Greek* δασμός *(dasmós, "division"), from* δαίω
43
+ > *(daíō, "to divide, share").*
44
+
45
+ *Dasmos* is a ground-up rewrite and reimagining of a heavily modified
46
+ [fork][py8dis-fork] of [py8dis][py8dis-original] — Steven Flintham's
47
+ original programmable tracing disassembler for the 6502 family.
48
+ *Dasmos* owes the whole core idea to Steven and to the py8dis project;
49
+ this project organises a tracing disassembler as a core algorithm
50
+ customised through plug-in extensions which provide knowledge of CPUs,
51
+ different assembly syntaxes, and target environments. The core of the
52
+ essential design vocabulary — driver scripts, traced classification,
53
+ label/comment/banner annotations — is all inspired by py8dis.
54
+
55
+ Driver scripts written for py8dis can be ported automatically to
56
+ *Dasmos* with the bundled `scripts/py8dis2dasmos.py`.
57
+
58
+ [py8dis-original]: https://github.com/ZornsLemma/py8dis
59
+ [py8dis-fork]: https://github.com/acornaeology/py8dis
60
+ [acornaeology]: https://github.com/acornaeology
61
+
62
+ ## Install
63
+
64
+ > The `uv` and `uvx` commands shown below come from
65
+ > [Astral's uv](https://docs.astral.sh/uv/). If you don't have it
66
+ > yet, see the [uv installation guide](https://docs.astral.sh/uv/getting-started/installation/)
67
+ > — one-line installers are available for macOS, Linux, and Windows.
68
+
69
+ For one-shot CLI use, no install needed — `uvx` fetches and runs in
70
+ a transient environment:
71
+
72
+ ```sh
73
+ uvx dasmos disassemble myrom.bin --load-addr '&8000'
74
+ ```
75
+
76
+ To add `dasmos` to a project (required for driver scripts that
77
+ `import dasmos`):
78
+
79
+ ```sh
80
+ uv add dasmos
81
+ ```
82
+
83
+ Or with pip:
84
+
85
+ ```sh
86
+ pip install dasmos
87
+ ```
88
+
89
+ *Dasmos*'s round-trip / parity tests assemble back to bytes via
90
+ [beebasm](https://github.com/stardot/beebasm); `beebasm` on `PATH`
91
+ (or via the `BEEBASM` env var) activates them. CI builds beebasm
92
+ from source per matrix cell so the round-trip is part of the gate.
93
+
94
+ ## Programmatic API
95
+
96
+ Every CLI capability is also reachable through the package. The
97
+ typical driver-script flow is: pick a CPU plug-in, load a binary,
98
+ register entry points / labels / classifications / annotations,
99
+ disassemble, then render via a renderer plug-in.
100
+
101
+ ```python
102
+ from dasmos import Disassembler, Align
103
+
104
+ d = Disassembler.create(cpu="nmos6502")
105
+ d.load("rom.bin", 0x8000)
106
+ d.entry(0x8000, name="start")
107
+ d.label(0x8006, "show", description="Display routine")
108
+ d.comment(0x8000, "Entry point.")
109
+ d.comment(0x8000, "magic", align=Align.INLINE)
110
+
111
+ ir = d.disassemble()
112
+ print(str(ir.render("beebasm")))
113
+ ```
114
+
115
+ That produces beebasm-assemblable source. Re-assembling it via the
116
+ real `beebasm` binary yields a binary byte-identical to the input
117
+ — the load-bearing **round-trip property** *Dasmos*'s test suite
118
+ exercises against real Acorn ROMs (the 8 KB Econet Bridge and the
119
+ 2 KB-mapped 6502 Tube Client both round-trip end-to-end with full
120
+ py8dis annotation-content parity).
121
+
122
+ ## CLI
123
+
124
+ ```console
125
+ $ dasmos --help
126
+ Usage: dasmos [OPTIONS] COMMAND [ARGS]...
127
+
128
+ A pluggable tracing disassembler.
129
+
130
+ Options:
131
+ --version Show the version and exit.
132
+ --help Show this message and exit.
133
+
134
+ Commands:
135
+ describe-cpu Describe a specific CPU plug-in.
136
+ describe-environment Describe a specific environment plug-in.
137
+ describe-renderer Describe a specific renderer plug-in.
138
+ disassemble Disassemble ROM and write the rendered output.
139
+ init Scaffold a starter dasmos driver at DRIVER_PATH.
140
+ list-cpus List the available CPU plug-ins.
141
+ list-environments List the available environment plug-ins.
142
+ list-renderers List the available renderer plug-ins.
143
+ ```
144
+
145
+ The CLI commands inherit a uniform `--as display | tsv | json` story
146
+ (plus `--report`, `--header`, `--detailed`) from
147
+ [asyoulikeit](https://github.com/sixty-north/asyoulikeit), so any
148
+ command's structured output drives downstream tooling cleanly.
149
+
150
+ ### Discovering plug-ins
151
+
152
+ Two namespaces are populated by the bundled extensions; third-party
153
+ packages register additional entries the same way.
154
+
155
+ ```console
156
+ $ dasmos list-cpus
157
+ CPUs registered under 'dasmos.cpu'
158
+ ┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
159
+ ┃ Name ┃ Description ┃
160
+ ┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
161
+ │ cmos65c02 │ The CMOS 65C02 — NMOS 6502 superset with 8 new mnemonics, 2 new │
162
+ │ nmos6502 │ The classic NMOS 6502. │
163
+ └───────────┴─────────────────────────────────────────────────────────────────┘
164
+ ```
165
+
166
+ ```console
167
+ $ dasmos list-renderers
168
+ Renderers registered under 'dasmos.renderer'
169
+ ┏━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
170
+ ┃ Name ┃ Description ┃
171
+ ┡━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
172
+ │ beebasm │ Beebasm-syntax renderer. │
173
+ │ json │ JSON structured-output renderer. │
174
+ └─────────┴──────────────────────────────────┘
175
+ ```
176
+
177
+ ```console
178
+ $ dasmos list-environments
179
+ Environments registered under 'dasmos.environment'
180
+ ┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
181
+ ┃ Name ┃ Description ┃
182
+ ┡━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
183
+ │ acorn_bbc_hardware │ Acorn BBC Micro hardware-register Environment. │
184
+ │ acorn_mos │ Acorn MOS environment. │
185
+ │ acorn_sideways_rom │ Acorn sideways ROM environment. │
186
+ └────────────────────┴────────────────────────────────────────────────┘
187
+ ```
188
+
189
+ Environments layer onto a disassembler additively — a driver can
190
+ activate any number of them, in either the constructor's
191
+ ``environments=[…]`` kwarg or via repeated
192
+ ``d.use_environment(…)`` calls.
193
+
194
+ `describe-cpu` (and the matching `describe-renderer`) shows the full
195
+ docstring of a single plug-in:
196
+
197
+ ```console
198
+ $ dasmos describe-cpu nmos6502
199
+ The classic NMOS 6502.
200
+
201
+ 16-bit address space; the 56 documented mnemonics across 13
202
+ addressing modes; 151 documented opcodes (undocumented opcodes
203
+ deliberately omitted).
204
+ ```
205
+
206
+ ```console
207
+ $ dasmos list-cpus --help
208
+ Usage: dasmos list-cpus [OPTIONS]
209
+
210
+ List the available CPU plug-ins.
211
+
212
+ Produces reports:
213
+ cpus Registered CPU (processor) plug-ins with one-line descriptions.
214
+
215
+ Options:
216
+ Report Output Options:
217
+ --no-reports Suppress all report output. The handler still runs
218
+ (useful for action commands whose reports are
219
+ incidental); only rendering is skipped. Mutually
220
+ exclusive with --report and --all-reports.
221
+ --all-reports Render every report the handler returns,
222
+ regardless of the command's default_reports.
223
+ Useful for commands whose default is a subset (or
224
+ silent) but where you want the full picture this
225
+ time. Mutually exclusive with --report and --no-
226
+ reports.
227
+ --report [cpus] Report name(s) to display (can be specified
228
+ multiple times). Shows all if omitted. Valid
229
+ values: cpus.
230
+ --header / --no-header Include column headers in output. Overrides each
231
+ report's default. Format-specific: TSV prefixes
232
+ first cell with '#', display omits
233
+ headers/title/caption, JSON ignores this flag.
234
+ --detailed / --essential Include detailed columns or only essential
235
+ columns. Auto-detects based on output format if
236
+ not specified.
237
+ --as [display|json|tsv] Output format for tabular data. Defaults to
238
+ 'display' for terminals, 'tsv' for pipes.
239
+ --help Show this message and exit.
240
+ ```
241
+
242
+ ## Migrating a py8dis driver
243
+
244
+ `scripts/py8dis2dasmos.py` is an AST-based porter that translates a
245
+ py8dis driver script into the equivalent *Dasmos* call shape:
246
+
247
+ ```sh
248
+ uv run python scripts/py8dis2dasmos.py path/to/disasm_<rom>.py > ported.py
249
+ ```
250
+
251
+ It rewrites the wildcard import, swaps `load(addr, file, cpu)` order,
252
+ maps `move()` → `d.add_move(...)`, threads `inline=True` →
253
+ `align=Align.INLINE`, recognises `subroutine(..., is_entry_point=False)`
254
+ as a label-plus-banner pair, expands `hook_subroutine` and the bundled
255
+ hooks (`stringhi_hook`, …) through `dasmos.hooks`, and configures the
256
+ `render()` call so the output matches py8dis's defaults
257
+ (`boundary_label_prefix='pydis_'`, `byte_column=True`).
258
+
259
+ Every transformation is covered by unit tests, plus a load-bearing
260
+ end-to-end test that ports the unmodified Econet Bridge driver and
261
+ asserts the resulting beebasm source re-assembles to the original ROM
262
+ bytes.
263
+
264
+ ## Testing
265
+
266
+ `pytest -v` runs the suite. Tests marked `@pytest.mark.beebasm`
267
+ auto-skip when `beebasm` isn't on `PATH`; the rest run anywhere
268
+ Python and `uv` are installed. CI exercises the full matrix
269
+ (ubuntu/macos/windows × earliest+latest declared Python) **against
270
+ the installed wheel**, not the source tree, so packaging regressions
271
+ (missing entry points, omitted `py.typed` markers, unshipped
272
+ sub-packages) fail loud.
273
+
274
+ ## Layout
275
+
276
+ ```
277
+ src/dasmos/ the package
278
+ src/dasmos/cli.py Click entry point + asyoulikeit reports
279
+ src/dasmos/disassembler.py Disassembler (driver-script API)
280
+ src/dasmos/core/ Memory / labels / moves / classifications
281
+ src/dasmos/cpu.py Cpu base + Opcode shape
282
+ src/dasmos/renderer.py Renderer base
283
+ src/dasmos/ext/cpus/ Bundled CPU plug-ins (nmos6502, cmos65c02)
284
+ src/dasmos/ext/renderers/ Bundled renderer plug-ins (beebasm)
285
+ src/dasmos/hooks.py Subroutine hooks (stringhi_hook, …)
286
+ scripts/py8dis2dasmos.py py8dis → dasmos AST porter
287
+ scripts/generate_readme.py This README's generator
288
+ docs/design/ Architecture decisions & sweep memos
289
+ tests/ Unit + round-trip + py8dis-parity tests
290
+ tests/fixtures/ Vendored ROM + driver + reference output
291
+ ```
292
+
293
+ ## Related projects
294
+
295
+ - [py8dis (fork)][py8dis-fork] — the predecessor *Dasmos* is replacing.
296
+ Driver scripts written against this fork port via
297
+ `scripts/py8dis2dasmos.py`.
298
+ - The four sibling Acorn ROM disassembly repositories under the
299
+ [acornaeology][acornaeology] umbrella that drive *Dasmos*'s
300
+ round-trip / parity validation:
301
+ `acorn-econet-bridge`, `acorn-6502-tube-client`, `acorn-nfs`,
302
+ `acorn-adfs`.
303
+ - [beebasm](https://github.com/stardot/beebasm) — the BBC-Micro-style
304
+ assembler used as the round-trip oracle.
305
+ - [asyoulikeit](https://github.com/sixty-north/asyoulikeit) — the
306
+ CLI-output framework *Dasmos*'s reports are built on.
307
+
308
+ ---
309
+
310
+ This README is generated from `scripts/readme_template.md.j2` by
311
+ `scripts/generate_readme.py`. **Do not edit it directly** — edit the
312
+ template (or the generator, or the source files whose output it
313
+ captures) and re-run `uv run python scripts/generate_readme.py`. The
314
+ pre-commit hook and the `readme-check` CI job both run the
315
+ generator's `--check` mode and will refuse stale READMEs.
dasmos-0.1.2/README.md ADDED
@@ -0,0 +1,285 @@
1
+ # Dasmos
2
+
3
+ A pluggable tracing disassembler for retro CPUs, version `0.1.2`.
4
+
5
+ <p align="center">
6
+ <a href="https://pypi.org/project/dasmos/"><img src="https://img.shields.io/pypi/v/dasmos.svg" alt="PyPI"></a>
7
+ <a href="https://github.com/acornaeology/dasmos/actions/workflows/release.yml"><img src="https://img.shields.io/github/actions/workflow/status/acornaeology/dasmos/release.yml?label=release" alt="Release"></a>
8
+ <a href="https://github.com/acornaeology/dasmos/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/acornaeology/dasmos/ci.yml?branch=master&label=CI" alt="CI"></a>
9
+ <a href="https://pypi.org/project/dasmos/"><img src="https://img.shields.io/pypi/pyversions/dasmos.svg" alt="Python versions"></a>
10
+ </p>
11
+
12
+ > *From Ancient Greek* δασμός *(dasmós, "division"), from* δαίω
13
+ > *(daíō, "to divide, share").*
14
+
15
+ *Dasmos* is a ground-up rewrite and reimagining of a heavily modified
16
+ [fork][py8dis-fork] of [py8dis][py8dis-original] — Steven Flintham's
17
+ original programmable tracing disassembler for the 6502 family.
18
+ *Dasmos* owes the whole core idea to Steven and to the py8dis project;
19
+ this project organises a tracing disassembler as a core algorithm
20
+ customised through plug-in extensions which provide knowledge of CPUs,
21
+ different assembly syntaxes, and target environments. The core of the
22
+ essential design vocabulary — driver scripts, traced classification,
23
+ label/comment/banner annotations — is all inspired by py8dis.
24
+
25
+ Driver scripts written for py8dis can be ported automatically to
26
+ *Dasmos* with the bundled `scripts/py8dis2dasmos.py`.
27
+
28
+ [py8dis-original]: https://github.com/ZornsLemma/py8dis
29
+ [py8dis-fork]: https://github.com/acornaeology/py8dis
30
+ [acornaeology]: https://github.com/acornaeology
31
+
32
+ ## Install
33
+
34
+ > The `uv` and `uvx` commands shown below come from
35
+ > [Astral's uv](https://docs.astral.sh/uv/). If you don't have it
36
+ > yet, see the [uv installation guide](https://docs.astral.sh/uv/getting-started/installation/)
37
+ > — one-line installers are available for macOS, Linux, and Windows.
38
+
39
+ For one-shot CLI use, no install needed — `uvx` fetches and runs in
40
+ a transient environment:
41
+
42
+ ```sh
43
+ uvx dasmos disassemble myrom.bin --load-addr '&8000'
44
+ ```
45
+
46
+ To add `dasmos` to a project (required for driver scripts that
47
+ `import dasmos`):
48
+
49
+ ```sh
50
+ uv add dasmos
51
+ ```
52
+
53
+ Or with pip:
54
+
55
+ ```sh
56
+ pip install dasmos
57
+ ```
58
+
59
+ *Dasmos*'s round-trip / parity tests assemble back to bytes via
60
+ [beebasm](https://github.com/stardot/beebasm); `beebasm` on `PATH`
61
+ (or via the `BEEBASM` env var) activates them. CI builds beebasm
62
+ from source per matrix cell so the round-trip is part of the gate.
63
+
64
+ ## Programmatic API
65
+
66
+ Every CLI capability is also reachable through the package. The
67
+ typical driver-script flow is: pick a CPU plug-in, load a binary,
68
+ register entry points / labels / classifications / annotations,
69
+ disassemble, then render via a renderer plug-in.
70
+
71
+ ```python
72
+ from dasmos import Disassembler, Align
73
+
74
+ d = Disassembler.create(cpu="nmos6502")
75
+ d.load("rom.bin", 0x8000)
76
+ d.entry(0x8000, name="start")
77
+ d.label(0x8006, "show", description="Display routine")
78
+ d.comment(0x8000, "Entry point.")
79
+ d.comment(0x8000, "magic", align=Align.INLINE)
80
+
81
+ ir = d.disassemble()
82
+ print(str(ir.render("beebasm")))
83
+ ```
84
+
85
+ That produces beebasm-assemblable source. Re-assembling it via the
86
+ real `beebasm` binary yields a binary byte-identical to the input
87
+ — the load-bearing **round-trip property** *Dasmos*'s test suite
88
+ exercises against real Acorn ROMs (the 8 KB Econet Bridge and the
89
+ 2 KB-mapped 6502 Tube Client both round-trip end-to-end with full
90
+ py8dis annotation-content parity).
91
+
92
+ ## CLI
93
+
94
+ ```console
95
+ $ dasmos --help
96
+ Usage: dasmos [OPTIONS] COMMAND [ARGS]...
97
+
98
+ A pluggable tracing disassembler.
99
+
100
+ Options:
101
+ --version Show the version and exit.
102
+ --help Show this message and exit.
103
+
104
+ Commands:
105
+ describe-cpu Describe a specific CPU plug-in.
106
+ describe-environment Describe a specific environment plug-in.
107
+ describe-renderer Describe a specific renderer plug-in.
108
+ disassemble Disassemble ROM and write the rendered output.
109
+ init Scaffold a starter dasmos driver at DRIVER_PATH.
110
+ list-cpus List the available CPU plug-ins.
111
+ list-environments List the available environment plug-ins.
112
+ list-renderers List the available renderer plug-ins.
113
+ ```
114
+
115
+ The CLI commands inherit a uniform `--as display | tsv | json` story
116
+ (plus `--report`, `--header`, `--detailed`) from
117
+ [asyoulikeit](https://github.com/sixty-north/asyoulikeit), so any
118
+ command's structured output drives downstream tooling cleanly.
119
+
120
+ ### Discovering plug-ins
121
+
122
+ Two namespaces are populated by the bundled extensions; third-party
123
+ packages register additional entries the same way.
124
+
125
+ ```console
126
+ $ dasmos list-cpus
127
+ CPUs registered under 'dasmos.cpu'
128
+ ┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
129
+ ┃ Name ┃ Description ┃
130
+ ┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
131
+ │ cmos65c02 │ The CMOS 65C02 — NMOS 6502 superset with 8 new mnemonics, 2 new │
132
+ │ nmos6502 │ The classic NMOS 6502. │
133
+ └───────────┴─────────────────────────────────────────────────────────────────┘
134
+ ```
135
+
136
+ ```console
137
+ $ dasmos list-renderers
138
+ Renderers registered under 'dasmos.renderer'
139
+ ┏━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
140
+ ┃ Name ┃ Description ┃
141
+ ┡━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
142
+ │ beebasm │ Beebasm-syntax renderer. │
143
+ │ json │ JSON structured-output renderer. │
144
+ └─────────┴──────────────────────────────────┘
145
+ ```
146
+
147
+ ```console
148
+ $ dasmos list-environments
149
+ Environments registered under 'dasmos.environment'
150
+ ┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
151
+ ┃ Name ┃ Description ┃
152
+ ┡━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
153
+ │ acorn_bbc_hardware │ Acorn BBC Micro hardware-register Environment. │
154
+ │ acorn_mos │ Acorn MOS environment. │
155
+ │ acorn_sideways_rom │ Acorn sideways ROM environment. │
156
+ └────────────────────┴────────────────────────────────────────────────┘
157
+ ```
158
+
159
+ Environments layer onto a disassembler additively — a driver can
160
+ activate any number of them, in either the constructor's
161
+ ``environments=[…]`` kwarg or via repeated
162
+ ``d.use_environment(…)`` calls.
163
+
164
+ `describe-cpu` (and the matching `describe-renderer`) shows the full
165
+ docstring of a single plug-in:
166
+
167
+ ```console
168
+ $ dasmos describe-cpu nmos6502
169
+ The classic NMOS 6502.
170
+
171
+ 16-bit address space; the 56 documented mnemonics across 13
172
+ addressing modes; 151 documented opcodes (undocumented opcodes
173
+ deliberately omitted).
174
+ ```
175
+
176
+ ```console
177
+ $ dasmos list-cpus --help
178
+ Usage: dasmos list-cpus [OPTIONS]
179
+
180
+ List the available CPU plug-ins.
181
+
182
+ Produces reports:
183
+ cpus Registered CPU (processor) plug-ins with one-line descriptions.
184
+
185
+ Options:
186
+ Report Output Options:
187
+ --no-reports Suppress all report output. The handler still runs
188
+ (useful for action commands whose reports are
189
+ incidental); only rendering is skipped. Mutually
190
+ exclusive with --report and --all-reports.
191
+ --all-reports Render every report the handler returns,
192
+ regardless of the command's default_reports.
193
+ Useful for commands whose default is a subset (or
194
+ silent) but where you want the full picture this
195
+ time. Mutually exclusive with --report and --no-
196
+ reports.
197
+ --report [cpus] Report name(s) to display (can be specified
198
+ multiple times). Shows all if omitted. Valid
199
+ values: cpus.
200
+ --header / --no-header Include column headers in output. Overrides each
201
+ report's default. Format-specific: TSV prefixes
202
+ first cell with '#', display omits
203
+ headers/title/caption, JSON ignores this flag.
204
+ --detailed / --essential Include detailed columns or only essential
205
+ columns. Auto-detects based on output format if
206
+ not specified.
207
+ --as [display|json|tsv] Output format for tabular data. Defaults to
208
+ 'display' for terminals, 'tsv' for pipes.
209
+ --help Show this message and exit.
210
+ ```
211
+
212
+ ## Migrating a py8dis driver
213
+
214
+ `scripts/py8dis2dasmos.py` is an AST-based porter that translates a
215
+ py8dis driver script into the equivalent *Dasmos* call shape:
216
+
217
+ ```sh
218
+ uv run python scripts/py8dis2dasmos.py path/to/disasm_<rom>.py > ported.py
219
+ ```
220
+
221
+ It rewrites the wildcard import, swaps `load(addr, file, cpu)` order,
222
+ maps `move()` → `d.add_move(...)`, threads `inline=True` →
223
+ `align=Align.INLINE`, recognises `subroutine(..., is_entry_point=False)`
224
+ as a label-plus-banner pair, expands `hook_subroutine` and the bundled
225
+ hooks (`stringhi_hook`, …) through `dasmos.hooks`, and configures the
226
+ `render()` call so the output matches py8dis's defaults
227
+ (`boundary_label_prefix='pydis_'`, `byte_column=True`).
228
+
229
+ Every transformation is covered by unit tests, plus a load-bearing
230
+ end-to-end test that ports the unmodified Econet Bridge driver and
231
+ asserts the resulting beebasm source re-assembles to the original ROM
232
+ bytes.
233
+
234
+ ## Testing
235
+
236
+ `pytest -v` runs the suite. Tests marked `@pytest.mark.beebasm`
237
+ auto-skip when `beebasm` isn't on `PATH`; the rest run anywhere
238
+ Python and `uv` are installed. CI exercises the full matrix
239
+ (ubuntu/macos/windows × earliest+latest declared Python) **against
240
+ the installed wheel**, not the source tree, so packaging regressions
241
+ (missing entry points, omitted `py.typed` markers, unshipped
242
+ sub-packages) fail loud.
243
+
244
+ ## Layout
245
+
246
+ ```
247
+ src/dasmos/ the package
248
+ src/dasmos/cli.py Click entry point + asyoulikeit reports
249
+ src/dasmos/disassembler.py Disassembler (driver-script API)
250
+ src/dasmos/core/ Memory / labels / moves / classifications
251
+ src/dasmos/cpu.py Cpu base + Opcode shape
252
+ src/dasmos/renderer.py Renderer base
253
+ src/dasmos/ext/cpus/ Bundled CPU plug-ins (nmos6502, cmos65c02)
254
+ src/dasmos/ext/renderers/ Bundled renderer plug-ins (beebasm)
255
+ src/dasmos/hooks.py Subroutine hooks (stringhi_hook, …)
256
+ scripts/py8dis2dasmos.py py8dis → dasmos AST porter
257
+ scripts/generate_readme.py This README's generator
258
+ docs/design/ Architecture decisions & sweep memos
259
+ tests/ Unit + round-trip + py8dis-parity tests
260
+ tests/fixtures/ Vendored ROM + driver + reference output
261
+ ```
262
+
263
+ ## Related projects
264
+
265
+ - [py8dis (fork)][py8dis-fork] — the predecessor *Dasmos* is replacing.
266
+ Driver scripts written against this fork port via
267
+ `scripts/py8dis2dasmos.py`.
268
+ - The four sibling Acorn ROM disassembly repositories under the
269
+ [acornaeology][acornaeology] umbrella that drive *Dasmos*'s
270
+ round-trip / parity validation:
271
+ `acorn-econet-bridge`, `acorn-6502-tube-client`, `acorn-nfs`,
272
+ `acorn-adfs`.
273
+ - [beebasm](https://github.com/stardot/beebasm) — the BBC-Micro-style
274
+ assembler used as the round-trip oracle.
275
+ - [asyoulikeit](https://github.com/sixty-north/asyoulikeit) — the
276
+ CLI-output framework *Dasmos*'s reports are built on.
277
+
278
+ ---
279
+
280
+ This README is generated from `scripts/readme_template.md.j2` by
281
+ `scripts/generate_readme.py`. **Do not edit it directly** — edit the
282
+ template (or the generator, or the source files whose output it
283
+ captures) and re-run `uv run python scripts/generate_readme.py`. The
284
+ pre-commit hook and the `readme-check` CI job both run the
285
+ generator's `--check` mode and will refuse stale READMEs.