web-a2e 1.0.0

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 (295) hide show
  1. package/.clangd +5 -0
  2. package/.mcp.json +12 -0
  3. package/CLAUDE.md +362 -0
  4. package/CMakeLists.txt +774 -0
  5. package/LICENSE +21 -0
  6. package/README.md +392 -0
  7. package/build-wasm/generated/roms.cpp +2447 -0
  8. package/docker-compose.staging.yml +9 -0
  9. package/docs/basic-rom-disassembly.md +6663 -0
  10. package/docs/softswitch-comparison.md +273 -0
  11. package/docs/thunderclock-debug.md +89 -0
  12. package/examples/cube.bas +72 -0
  13. package/examples/hello.s +55 -0
  14. package/examples/scroll.s +140 -0
  15. package/package.json +18 -0
  16. package/public/assets/apple-logo-old.png +0 -0
  17. package/public/assets/apple-logo.png +0 -0
  18. package/public/assets/drive-closed-light-on.png +0 -0
  19. package/public/assets/drive-closed.png +0 -0
  20. package/public/assets/drive-open-light-on.png +0 -0
  21. package/public/assets/drive-open.png +0 -0
  22. package/public/audio-worklet.js +82 -0
  23. package/public/disks/Apple DOS 3.3 January 1983.dsk +0 -0
  24. package/public/disks/ProDOS 2.4.3.po +0 -0
  25. package/public/disks/h32mb.2mg +0 -0
  26. package/public/disks/library.json +26 -0
  27. package/public/docs/llms/llm-assembler.txt +90 -0
  28. package/public/docs/llms/llm-basic-program.txt +256 -0
  29. package/public/docs/llms/llm-disk-drives.txt +72 -0
  30. package/public/docs/llms/llm-file-explorer.txt +50 -0
  31. package/public/docs/llms/llm-hard-drives.txt +80 -0
  32. package/public/docs/llms/llm-main.txt +51 -0
  33. package/public/docs/llms/llm-slot-configuration.txt +66 -0
  34. package/public/icons/icon-192.svg +4 -0
  35. package/public/icons/icon-512.svg +4 -0
  36. package/public/index.html +661 -0
  37. package/public/llms.txt +49 -0
  38. package/public/manifest.json +29 -0
  39. package/public/shaders/burnin.glsl +22 -0
  40. package/public/shaders/crt.glsl +706 -0
  41. package/public/shaders/edge.glsl +109 -0
  42. package/public/shaders/vertex.glsl +8 -0
  43. package/public/sw.js +186 -0
  44. package/roms/341-0027.bin +0 -0
  45. package/roms/341-0160-A-US-UK.bin +0 -0
  46. package/roms/341-0160-A.bin +0 -0
  47. package/roms/342-0273-A-US-UK.bin +0 -0
  48. package/roms/342-0349-B-C0-FF.bin +0 -0
  49. package/roms/Apple Mouse Interface Card ROM - 342-0270-C.bin +0 -0
  50. package/roms/Thunderclock Plus ROM.bin +0 -0
  51. package/scripts/generate_roms.sh +69 -0
  52. package/src/bindings/wasm_interface.cpp +1940 -0
  53. package/src/core/assembler/assembler.cpp +1239 -0
  54. package/src/core/assembler/assembler.hpp +115 -0
  55. package/src/core/audio/audio.cpp +160 -0
  56. package/src/core/audio/audio.hpp +81 -0
  57. package/src/core/basic/basic_detokenizer.cpp +436 -0
  58. package/src/core/basic/basic_detokenizer.hpp +41 -0
  59. package/src/core/basic/basic_tokenizer.cpp +286 -0
  60. package/src/core/basic/basic_tokenizer.hpp +26 -0
  61. package/src/core/basic/basic_tokens.hpp +295 -0
  62. package/src/core/cards/disk2_card.cpp +568 -0
  63. package/src/core/cards/disk2_card.hpp +316 -0
  64. package/src/core/cards/expansion_card.hpp +185 -0
  65. package/src/core/cards/mockingboard/ay8910.cpp +616 -0
  66. package/src/core/cards/mockingboard/ay8910.hpp +159 -0
  67. package/src/core/cards/mockingboard/via6522.cpp +530 -0
  68. package/src/core/cards/mockingboard/via6522.hpp +163 -0
  69. package/src/core/cards/mockingboard_card.cpp +312 -0
  70. package/src/core/cards/mockingboard_card.hpp +159 -0
  71. package/src/core/cards/mouse_card.cpp +654 -0
  72. package/src/core/cards/mouse_card.hpp +190 -0
  73. package/src/core/cards/smartport/block_device.cpp +202 -0
  74. package/src/core/cards/smartport/block_device.hpp +60 -0
  75. package/src/core/cards/smartport/smartport_card.cpp +603 -0
  76. package/src/core/cards/smartport/smartport_card.hpp +120 -0
  77. package/src/core/cards/thunderclock_card.cpp +237 -0
  78. package/src/core/cards/thunderclock_card.hpp +122 -0
  79. package/src/core/cpu/cpu6502.cpp +1609 -0
  80. package/src/core/cpu/cpu6502.hpp +203 -0
  81. package/src/core/debug/condition_evaluator.cpp +470 -0
  82. package/src/core/debug/condition_evaluator.hpp +87 -0
  83. package/src/core/disassembler/disassembler.cpp +552 -0
  84. package/src/core/disassembler/disassembler.hpp +171 -0
  85. package/src/core/disk-image/disk_image.hpp +267 -0
  86. package/src/core/disk-image/dsk_disk_image.cpp +827 -0
  87. package/src/core/disk-image/dsk_disk_image.hpp +204 -0
  88. package/src/core/disk-image/gcr_encoding.cpp +147 -0
  89. package/src/core/disk-image/gcr_encoding.hpp +78 -0
  90. package/src/core/disk-image/woz_disk_image.cpp +1049 -0
  91. package/src/core/disk-image/woz_disk_image.hpp +343 -0
  92. package/src/core/emulator.cpp +2126 -0
  93. package/src/core/emulator.hpp +434 -0
  94. package/src/core/filesystem/dos33.cpp +178 -0
  95. package/src/core/filesystem/dos33.hpp +66 -0
  96. package/src/core/filesystem/pascal.cpp +262 -0
  97. package/src/core/filesystem/pascal.hpp +87 -0
  98. package/src/core/filesystem/prodos.cpp +369 -0
  99. package/src/core/filesystem/prodos.hpp +119 -0
  100. package/src/core/input/keyboard.cpp +227 -0
  101. package/src/core/input/keyboard.hpp +111 -0
  102. package/src/core/mmu/mmu.cpp +1387 -0
  103. package/src/core/mmu/mmu.hpp +236 -0
  104. package/src/core/types.hpp +196 -0
  105. package/src/core/video/video.cpp +680 -0
  106. package/src/core/video/video.hpp +156 -0
  107. package/src/css/assembler-editor.css +1617 -0
  108. package/src/css/base.css +470 -0
  109. package/src/css/basic-debugger.css +791 -0
  110. package/src/css/basic-editor.css +792 -0
  111. package/src/css/controls.css +783 -0
  112. package/src/css/cpu-debugger.css +1413 -0
  113. package/src/css/debug-base.css +160 -0
  114. package/src/css/debug-windows.css +6455 -0
  115. package/src/css/disk-drives.css +406 -0
  116. package/src/css/documentation.css +392 -0
  117. package/src/css/file-explorer.css +867 -0
  118. package/src/css/hard-drive.css +180 -0
  119. package/src/css/layout.css +217 -0
  120. package/src/css/memory-windows.css +798 -0
  121. package/src/css/modals.css +510 -0
  122. package/src/css/monitor.css +425 -0
  123. package/src/css/release-notes.css +101 -0
  124. package/src/css/responsive.css +400 -0
  125. package/src/css/rule-builder.css +340 -0
  126. package/src/css/save-states.css +201 -0
  127. package/src/css/settings-windows.css +1231 -0
  128. package/src/css/window-switcher.css +150 -0
  129. package/src/js/agent/agent-manager.js +643 -0
  130. package/src/js/agent/agent-tools.js +293 -0
  131. package/src/js/agent/agent-version-tools.js +131 -0
  132. package/src/js/agent/assembler-tools.js +357 -0
  133. package/src/js/agent/basic-program-tools.js +894 -0
  134. package/src/js/agent/disk-tools.js +417 -0
  135. package/src/js/agent/file-explorer-tools.js +269 -0
  136. package/src/js/agent/index.js +13 -0
  137. package/src/js/agent/main-tools.js +222 -0
  138. package/src/js/agent/slot-tools.js +303 -0
  139. package/src/js/agent/smartport-tools.js +257 -0
  140. package/src/js/agent/window-tools.js +80 -0
  141. package/src/js/audio/audio-driver.js +417 -0
  142. package/src/js/audio/audio-worklet.js +85 -0
  143. package/src/js/audio/index.js +8 -0
  144. package/src/js/config/default-layout.js +34 -0
  145. package/src/js/config/version.js +8 -0
  146. package/src/js/data/apple2-rom-routines.js +577 -0
  147. package/src/js/debug/assembler-editor-window.js +2993 -0
  148. package/src/js/debug/basic-breakpoint-manager.js +529 -0
  149. package/src/js/debug/basic-program-parser.js +436 -0
  150. package/src/js/debug/basic-program-window.js +2594 -0
  151. package/src/js/debug/basic-variable-inspector.js +447 -0
  152. package/src/js/debug/breakpoint-manager.js +472 -0
  153. package/src/js/debug/cpu-debugger-window.js +2396 -0
  154. package/src/js/debug/index.js +22 -0
  155. package/src/js/debug/label-manager.js +238 -0
  156. package/src/js/debug/memory-browser-window.js +416 -0
  157. package/src/js/debug/memory-heat-map-window.js +481 -0
  158. package/src/js/debug/memory-map-window.js +206 -0
  159. package/src/js/debug/mockingboard-window.js +882 -0
  160. package/src/js/debug/mouse-card-window.js +355 -0
  161. package/src/js/debug/rule-builder-window.js +648 -0
  162. package/src/js/debug/soft-switch-window.js +458 -0
  163. package/src/js/debug/stack-viewer-window.js +221 -0
  164. package/src/js/debug/symbols.js +416 -0
  165. package/src/js/debug/trace-panel.js +291 -0
  166. package/src/js/debug/zero-page-watch-window.js +297 -0
  167. package/src/js/disk-manager/disk-drives-window.js +212 -0
  168. package/src/js/disk-manager/disk-operations.js +284 -0
  169. package/src/js/disk-manager/disk-persistence.js +301 -0
  170. package/src/js/disk-manager/disk-surface-renderer.js +388 -0
  171. package/src/js/disk-manager/drive-sounds.js +139 -0
  172. package/src/js/disk-manager/hard-drive-manager.js +481 -0
  173. package/src/js/disk-manager/hard-drive-persistence.js +187 -0
  174. package/src/js/disk-manager/hard-drive-window.js +57 -0
  175. package/src/js/disk-manager/index.js +890 -0
  176. package/src/js/display/display-settings-window.js +383 -0
  177. package/src/js/display/index.js +10 -0
  178. package/src/js/display/screen-window.js +342 -0
  179. package/src/js/display/webgl-renderer.js +705 -0
  180. package/src/js/file-explorer/disassembler.js +574 -0
  181. package/src/js/file-explorer/dos33.js +266 -0
  182. package/src/js/file-explorer/file-viewer.js +359 -0
  183. package/src/js/file-explorer/index.js +1261 -0
  184. package/src/js/file-explorer/prodos.js +549 -0
  185. package/src/js/file-explorer/utils.js +67 -0
  186. package/src/js/help/documentation-window.js +1096 -0
  187. package/src/js/help/index.js +10 -0
  188. package/src/js/help/release-notes-window.js +85 -0
  189. package/src/js/help/release-notes.js +612 -0
  190. package/src/js/input/gamepad-handler.js +176 -0
  191. package/src/js/input/index.js +12 -0
  192. package/src/js/input/input-handler.js +396 -0
  193. package/src/js/input/joystick-window.js +404 -0
  194. package/src/js/input/mouse-handler.js +99 -0
  195. package/src/js/input/text-selection.js +462 -0
  196. package/src/js/main.js +653 -0
  197. package/src/js/state/index.js +15 -0
  198. package/src/js/state/save-states-window.js +393 -0
  199. package/src/js/state/state-manager.js +409 -0
  200. package/src/js/state/state-persistence.js +218 -0
  201. package/src/js/ui/confirm.js +43 -0
  202. package/src/js/ui/disk-drive-positioner.js +347 -0
  203. package/src/js/ui/reminder-controller.js +129 -0
  204. package/src/js/ui/slot-configuration-window.js +560 -0
  205. package/src/js/ui/theme-manager.js +61 -0
  206. package/src/js/ui/toast.js +44 -0
  207. package/src/js/ui/ui-controller.js +897 -0
  208. package/src/js/ui/window-switcher.js +275 -0
  209. package/src/js/utils/basic-autocomplete.js +832 -0
  210. package/src/js/utils/basic-highlighting.js +473 -0
  211. package/src/js/utils/basic-tokenizer.js +153 -0
  212. package/src/js/utils/basic-tokens.js +117 -0
  213. package/src/js/utils/constants.js +28 -0
  214. package/src/js/utils/indexeddb-helper.js +225 -0
  215. package/src/js/utils/merlin-editor-support.js +905 -0
  216. package/src/js/utils/merlin-highlighting.js +551 -0
  217. package/src/js/utils/storage.js +125 -0
  218. package/src/js/utils/string-utils.js +19 -0
  219. package/src/js/utils/wasm-memory.js +54 -0
  220. package/src/js/windows/base-window.js +690 -0
  221. package/src/js/windows/index.js +9 -0
  222. package/src/js/windows/window-manager.js +375 -0
  223. package/tests/catch2/catch.hpp +17976 -0
  224. package/tests/common/basic_program_builder.cpp +119 -0
  225. package/tests/common/basic_program_builder.hpp +209 -0
  226. package/tests/common/disk_image_builder.cpp +444 -0
  227. package/tests/common/disk_image_builder.hpp +141 -0
  228. package/tests/common/test_helpers.hpp +118 -0
  229. package/tests/gcr/gcr-test.cpp +142 -0
  230. package/tests/integration/check-rom.js +70 -0
  231. package/tests/integration/compare-boot.js +239 -0
  232. package/tests/integration/crash-trace.js +102 -0
  233. package/tests/integration/disk-boot-test.js +264 -0
  234. package/tests/integration/memory-crash.js +108 -0
  235. package/tests/integration/nibble-read-test.js +249 -0
  236. package/tests/integration/phase-test.js +159 -0
  237. package/tests/integration/test_emulator.cpp +291 -0
  238. package/tests/integration/test_emulator_basic.cpp +91 -0
  239. package/tests/integration/test_emulator_debug.cpp +344 -0
  240. package/tests/integration/test_emulator_disk.cpp +153 -0
  241. package/tests/integration/test_emulator_state.cpp +163 -0
  242. package/tests/klaus/6502_functional_test.bin +0 -0
  243. package/tests/klaus/65C02_extended_opcodes_test.bin +0 -0
  244. package/tests/klaus/klaus_6502_test.cpp +184 -0
  245. package/tests/klaus/klaus_65c02_test.cpp +197 -0
  246. package/tests/thunderclock/thunderclock_mmu_test.cpp +304 -0
  247. package/tests/thunderclock/thunderclock_test.cpp +550 -0
  248. package/tests/unit/test_assembler.cpp +521 -0
  249. package/tests/unit/test_audio.cpp +196 -0
  250. package/tests/unit/test_ay8910.cpp +311 -0
  251. package/tests/unit/test_basic_detokenizer.cpp +265 -0
  252. package/tests/unit/test_basic_tokenizer.cpp +382 -0
  253. package/tests/unit/test_block_device.cpp +259 -0
  254. package/tests/unit/test_condition_evaluator.cpp +219 -0
  255. package/tests/unit/test_cpu6502.cpp +1301 -0
  256. package/tests/unit/test_cpu_addressing.cpp +361 -0
  257. package/tests/unit/test_cpu_cycle_counts.cpp +409 -0
  258. package/tests/unit/test_cpu_decimal.cpp +166 -0
  259. package/tests/unit/test_cpu_interrupts.cpp +285 -0
  260. package/tests/unit/test_disassembler.cpp +323 -0
  261. package/tests/unit/test_disk2_card.cpp +330 -0
  262. package/tests/unit/test_dos33.cpp +273 -0
  263. package/tests/unit/test_dsk_disk_image.cpp +315 -0
  264. package/tests/unit/test_expansion_card.cpp +178 -0
  265. package/tests/unit/test_gcr_encoding.cpp +232 -0
  266. package/tests/unit/test_keyboard.cpp +262 -0
  267. package/tests/unit/test_mmu.cpp +555 -0
  268. package/tests/unit/test_mmu_slots.cpp +323 -0
  269. package/tests/unit/test_mockingboard.cpp +352 -0
  270. package/tests/unit/test_mouse_card.cpp +386 -0
  271. package/tests/unit/test_pascal.cpp +248 -0
  272. package/tests/unit/test_prodos.cpp +259 -0
  273. package/tests/unit/test_smartport_card.cpp +321 -0
  274. package/tests/unit/test_thunderclock.cpp +354 -0
  275. package/tests/unit/test_via6522.cpp +323 -0
  276. package/tests/unit/test_video.cpp +319 -0
  277. package/tests/unit/test_woz_disk_image.cpp +257 -0
  278. package/vite.config.js +96 -0
  279. package/wiki/AI-Agent.md +372 -0
  280. package/wiki/Architecture-Overview.md +303 -0
  281. package/wiki/Audio-System.md +449 -0
  282. package/wiki/CPU-Emulation.md +477 -0
  283. package/wiki/Debugger.md +516 -0
  284. package/wiki/Disk-Drives.md +161 -0
  285. package/wiki/Disk-System-Internals.md +547 -0
  286. package/wiki/Display-Settings.md +88 -0
  287. package/wiki/Expansion-Slots.md +187 -0
  288. package/wiki/File-Explorer.md +259 -0
  289. package/wiki/Getting-Started.md +156 -0
  290. package/wiki/Home.md +69 -0
  291. package/wiki/Input-Devices.md +183 -0
  292. package/wiki/Keyboard-Shortcuts.md +158 -0
  293. package/wiki/Memory-System.md +364 -0
  294. package/wiki/Save-States.md +172 -0
  295. package/wiki/Video-Rendering.md +658 -0
@@ -0,0 +1,521 @@
1
+ /*
2
+ * test_assembler.cpp - Unit tests for 65C02 assembler
3
+ *
4
+ * Tests the assembler including:
5
+ * - Simple instructions (NOP, LDA variants)
6
+ * - Directives (ORG, DB/DFB, DW/DA, DS, ASC)
7
+ * - Labels and forward references
8
+ * - Branch instructions
9
+ * - Symbol table
10
+ * - Error reporting
11
+ * - All addressing modes
12
+ * - Multi-instruction programs
13
+ */
14
+
15
+ #define CATCH_CONFIG_MAIN
16
+ #include "catch.hpp"
17
+
18
+ #include "assembler.hpp"
19
+
20
+ #include <cstring>
21
+ #include <string>
22
+ #include <algorithm>
23
+
24
+ using namespace a2e;
25
+
26
+ // Helper to find a symbol by name in the result
27
+ static const AsmSymbol* findSymbol(const AsmResult& result, const char* name) {
28
+ std::string upper(name);
29
+ for (auto& c : upper) c = toupper(c);
30
+ for (const auto& sym : result.symbols) {
31
+ if (std::string(sym.name) == upper) return &sym;
32
+ }
33
+ return nullptr;
34
+ }
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // Simple instructions
38
+ // ---------------------------------------------------------------------------
39
+
40
+ TEST_CASE("Assembler NOP produces correct output", "[asm][instruction]") {
41
+ Assembler asm_;
42
+ auto result = asm_.assemble(" NOP");
43
+ REQUIRE(result.success);
44
+ REQUIRE(result.output.size() == 1);
45
+ CHECK(result.output[0] == 0xEA);
46
+ }
47
+
48
+ TEST_CASE("Assembler LDA immediate", "[asm][instruction]") {
49
+ Assembler asm_;
50
+ auto result = asm_.assemble(" LDA #$42");
51
+ REQUIRE(result.success);
52
+ REQUIRE(result.output.size() == 2);
53
+ CHECK(result.output[0] == 0xA9);
54
+ CHECK(result.output[1] == 0x42);
55
+ }
56
+
57
+ TEST_CASE("Assembler LDA absolute", "[asm][instruction]") {
58
+ Assembler asm_;
59
+ auto result = asm_.assemble(" LDA $1234");
60
+ REQUIRE(result.success);
61
+ REQUIRE(result.output.size() == 3);
62
+ CHECK(result.output[0] == 0xAD);
63
+ CHECK(result.output[1] == 0x34); // low byte
64
+ CHECK(result.output[2] == 0x12); // high byte
65
+ }
66
+
67
+ TEST_CASE("Assembler LDA zero page", "[asm][instruction]") {
68
+ Assembler asm_;
69
+ auto result = asm_.assemble(" LDA $42");
70
+ REQUIRE(result.success);
71
+ REQUIRE(result.output.size() == 2);
72
+ CHECK(result.output[0] == 0xA5);
73
+ CHECK(result.output[1] == 0x42);
74
+ }
75
+
76
+ TEST_CASE("Assembler STA absolute", "[asm][instruction]") {
77
+ Assembler asm_;
78
+ auto result = asm_.assemble(" STA $2000");
79
+ REQUIRE(result.success);
80
+ REQUIRE(result.output.size() == 3);
81
+ CHECK(result.output[0] == 0x8D);
82
+ CHECK(result.output[1] == 0x00);
83
+ CHECK(result.output[2] == 0x20);
84
+ }
85
+
86
+ TEST_CASE("Assembler RTS", "[asm][instruction]") {
87
+ Assembler asm_;
88
+ auto result = asm_.assemble(" RTS");
89
+ REQUIRE(result.success);
90
+ REQUIRE(result.output.size() == 1);
91
+ CHECK(result.output[0] == 0x60);
92
+ }
93
+
94
+ // ---------------------------------------------------------------------------
95
+ // Directives
96
+ // ---------------------------------------------------------------------------
97
+
98
+ TEST_CASE("Assembler ORG directive sets origin", "[asm][directive]") {
99
+ Assembler asm_;
100
+ auto result = asm_.assemble(" ORG $0800\n NOP");
101
+ REQUIRE(result.success);
102
+ CHECK(result.origin == 0x0800);
103
+ REQUIRE(result.output.size() == 1);
104
+ CHECK(result.output[0] == 0xEA);
105
+ }
106
+
107
+ TEST_CASE("Assembler ORG directive at different address", "[asm][directive]") {
108
+ Assembler asm_;
109
+ auto result = asm_.assemble(" ORG $2000\n NOP");
110
+ REQUIRE(result.success);
111
+ CHECK(result.origin == 0x2000);
112
+ }
113
+
114
+ TEST_CASE("Assembler DFB directive emits bytes", "[asm][directive]") {
115
+ Assembler asm_;
116
+ auto result = asm_.assemble(" DFB $01,$02,$03");
117
+ REQUIRE(result.success);
118
+ REQUIRE(result.output.size() == 3);
119
+ CHECK(result.output[0] == 0x01);
120
+ CHECK(result.output[1] == 0x02);
121
+ CHECK(result.output[2] == 0x03);
122
+ }
123
+
124
+ TEST_CASE("Assembler DB directive emits bytes (alias for DFB)", "[asm][directive]") {
125
+ Assembler asm_;
126
+ auto result = asm_.assemble(" DB $FF,$00,$AA");
127
+ REQUIRE(result.success);
128
+ REQUIRE(result.output.size() == 3);
129
+ CHECK(result.output[0] == 0xFF);
130
+ CHECK(result.output[1] == 0x00);
131
+ CHECK(result.output[2] == 0xAA);
132
+ }
133
+
134
+ TEST_CASE("Assembler DW directive emits little-endian word", "[asm][directive]") {
135
+ Assembler asm_;
136
+ auto result = asm_.assemble(" DW $1234");
137
+ REQUIRE(result.success);
138
+ REQUIRE(result.output.size() == 2);
139
+ CHECK(result.output[0] == 0x34); // low byte
140
+ CHECK(result.output[1] == 0x12); // high byte
141
+ }
142
+
143
+ TEST_CASE("Assembler DA directive emits little-endian word (alias for DW)", "[asm][directive]") {
144
+ Assembler asm_;
145
+ auto result = asm_.assemble(" DA $ABCD");
146
+ REQUIRE(result.success);
147
+ REQUIRE(result.output.size() == 2);
148
+ CHECK(result.output[0] == 0xCD);
149
+ CHECK(result.output[1] == 0xAB);
150
+ }
151
+
152
+ TEST_CASE("Assembler ASC directive emits characters", "[asm][directive]") {
153
+ Assembler asm_;
154
+ auto result = asm_.assemble(" ASC 'HI'");
155
+ REQUIRE(result.success);
156
+ REQUIRE(result.output.size() == 2);
157
+ CHECK(result.output[0] == 'H');
158
+ CHECK(result.output[1] == 'I');
159
+ }
160
+
161
+ TEST_CASE("Assembler DS directive emits zero-filled space", "[asm][directive]") {
162
+ Assembler asm_;
163
+ auto result = asm_.assemble(" DS 3");
164
+ REQUIRE(result.success);
165
+ REQUIRE(result.output.size() == 3);
166
+ CHECK(result.output[0] == 0x00);
167
+ CHECK(result.output[1] == 0x00);
168
+ CHECK(result.output[2] == 0x00);
169
+ }
170
+
171
+ TEST_CASE("Assembler HEX directive emits hex data", "[asm][directive]") {
172
+ Assembler asm_;
173
+ auto result = asm_.assemble(" HEX A0B0C0");
174
+ REQUIRE(result.success);
175
+ REQUIRE(result.output.size() == 3);
176
+ CHECK(result.output[0] == 0xA0);
177
+ CHECK(result.output[1] == 0xB0);
178
+ CHECK(result.output[2] == 0xC0);
179
+ }
180
+
181
+ // ---------------------------------------------------------------------------
182
+ // Labels and forward references
183
+ // ---------------------------------------------------------------------------
184
+
185
+ TEST_CASE("Assembler labels resolve to correct address", "[asm][labels]") {
186
+ Assembler asm_;
187
+ auto result = asm_.assemble("LOOP NOP\n JMP LOOP");
188
+ REQUIRE(result.success);
189
+ REQUIRE(result.output.size() == 4);
190
+
191
+ // NOP = 1 byte at origin (0x0800)
192
+ CHECK(result.output[0] == 0xEA);
193
+
194
+ // JMP LOOP = JMP $0800
195
+ CHECK(result.output[1] == 0x4C); // JMP absolute
196
+ CHECK(result.output[2] == 0x00); // low byte of $0800
197
+ CHECK(result.output[3] == 0x08); // high byte of $0800
198
+ }
199
+
200
+ TEST_CASE("Assembler forward reference resolves correctly", "[asm][labels]") {
201
+ Assembler asm_;
202
+ auto result = asm_.assemble(" JMP FWD\nFWD NOP");
203
+ REQUIRE(result.success);
204
+ REQUIRE(result.output.size() == 4);
205
+
206
+ // JMP FWD at $0800 (3 bytes), FWD at $0803
207
+ CHECK(result.output[0] == 0x4C); // JMP absolute
208
+ CHECK(result.output[1] == 0x03); // low byte of $0803
209
+ CHECK(result.output[2] == 0x08); // high byte of $0803
210
+ CHECK(result.output[3] == 0xEA); // NOP at FWD
211
+ }
212
+
213
+ TEST_CASE("Assembler label with colon syntax", "[asm][labels]") {
214
+ Assembler asm_;
215
+ auto result = asm_.assemble("START: NOP\n JMP START");
216
+ REQUIRE(result.success);
217
+ REQUIRE(result.output.size() == 4);
218
+ CHECK(result.output[1] == 0x4C);
219
+ CHECK(result.output[2] == 0x00);
220
+ CHECK(result.output[3] == 0x08);
221
+ }
222
+
223
+ // ---------------------------------------------------------------------------
224
+ // Branch instructions
225
+ // ---------------------------------------------------------------------------
226
+
227
+ TEST_CASE("Assembler short relative branch within range", "[asm][branch]") {
228
+ Assembler asm_;
229
+ auto result = asm_.assemble("LOOP NOP\n BNE LOOP");
230
+ REQUIRE(result.success);
231
+ REQUIRE(result.output.size() == 3);
232
+
233
+ // NOP at $0800
234
+ CHECK(result.output[0] == 0xEA);
235
+ // BNE LOOP: from $0801, offset = $0800 - ($0801 + 2) = -3 = 0xFD
236
+ CHECK(result.output[1] == 0xD0); // BNE
237
+ CHECK(result.output[2] == 0xFD); // -3 relative offset
238
+ }
239
+
240
+ TEST_CASE("Assembler forward branch", "[asm][branch]") {
241
+ Assembler asm_;
242
+ auto result = asm_.assemble(" BEQ SKIP\n NOP\nSKIP NOP");
243
+ REQUIRE(result.success);
244
+
245
+ // BEQ at $0800 (2 bytes), NOP at $0802 (1 byte), SKIP at $0803
246
+ // offset = $0803 - ($0800 + 2) = 1
247
+ CHECK(result.output[0] == 0xF0); // BEQ
248
+ CHECK(result.output[1] == 0x01); // +1 relative offset
249
+ }
250
+
251
+ TEST_CASE("Assembler BRA (65C02 unconditional branch)", "[asm][branch]") {
252
+ Assembler asm_;
253
+ auto result = asm_.assemble(" BRA DEST\nDEST NOP");
254
+ REQUIRE(result.success);
255
+ CHECK(result.output[0] == 0x80); // BRA opcode
256
+ }
257
+
258
+ // ---------------------------------------------------------------------------
259
+ // Symbols in result
260
+ // ---------------------------------------------------------------------------
261
+
262
+ TEST_CASE("Assembler symbols list contains defined labels", "[asm][symbols]") {
263
+ Assembler asm_;
264
+ auto result = asm_.assemble("START NOP\n RTS");
265
+ REQUIRE(result.success);
266
+
267
+ const AsmSymbol* sym = findSymbol(result, "START");
268
+ REQUIRE(sym != nullptr);
269
+ CHECK(sym->value == 0x0800); // Default origin
270
+ }
271
+
272
+ TEST_CASE("Assembler EQU creates symbol with specified value", "[asm][symbols]") {
273
+ Assembler asm_;
274
+ auto result = asm_.assemble("SCREEN EQU $2000\n LDA SCREEN");
275
+ REQUIRE(result.success);
276
+
277
+ const AsmSymbol* sym = findSymbol(result, "SCREEN");
278
+ REQUIRE(sym != nullptr);
279
+ CHECK(sym->value == 0x2000);
280
+ }
281
+
282
+ TEST_CASE("Assembler multiple labels appear in symbols", "[asm][symbols]") {
283
+ Assembler asm_;
284
+ auto result = asm_.assemble("ONE NOP\nTWO NOP\nTHREE NOP");
285
+ REQUIRE(result.success);
286
+
287
+ CHECK(findSymbol(result, "ONE") != nullptr);
288
+ CHECK(findSymbol(result, "TWO") != nullptr);
289
+ CHECK(findSymbol(result, "THREE") != nullptr);
290
+ }
291
+
292
+ // ---------------------------------------------------------------------------
293
+ // Error handling
294
+ // ---------------------------------------------------------------------------
295
+
296
+ TEST_CASE("Assembler error on invalid mnemonic", "[asm][error]") {
297
+ Assembler asm_;
298
+ auto result = asm_.assemble(" XYZ");
299
+ REQUIRE_FALSE(result.success);
300
+ REQUIRE(result.errors.size() > 0);
301
+ }
302
+
303
+ TEST_CASE("Assembler error on undefined symbol", "[asm][error]") {
304
+ Assembler asm_;
305
+ auto result = asm_.assemble(" LDA UNDEFINED");
306
+ REQUIRE_FALSE(result.success);
307
+ REQUIRE(result.errors.size() > 0);
308
+ }
309
+
310
+ TEST_CASE("Assembler empty source returns success with no output", "[asm][edge]") {
311
+ Assembler asm_;
312
+ auto result = asm_.assemble("");
313
+ REQUIRE(result.success);
314
+ CHECK(result.output.empty());
315
+ }
316
+
317
+ TEST_CASE("Assembler comment-only lines are ignored", "[asm][edge]") {
318
+ Assembler asm_;
319
+ auto result = asm_.assemble("; This is a comment\n* Another comment\n NOP");
320
+ REQUIRE(result.success);
321
+ REQUIRE(result.output.size() == 1);
322
+ CHECK(result.output[0] == 0xEA);
323
+ }
324
+
325
+ // ---------------------------------------------------------------------------
326
+ // Multiple instructions
327
+ // ---------------------------------------------------------------------------
328
+
329
+ TEST_CASE("Assembler multi-line program assembles correctly", "[asm][program]") {
330
+ Assembler asm_;
331
+ auto result = asm_.assemble(
332
+ " ORG $0300\n"
333
+ " LDA #$00\n" // A9 00
334
+ " STA $2000\n" // 8D 00 20
335
+ " RTS\n" // 60
336
+ );
337
+ REQUIRE(result.success);
338
+ CHECK(result.origin == 0x0300);
339
+ REQUIRE(result.output.size() == 6);
340
+ CHECK(result.output[0] == 0xA9); // LDA #
341
+ CHECK(result.output[1] == 0x00);
342
+ CHECK(result.output[2] == 0x8D); // STA abs
343
+ CHECK(result.output[3] == 0x00);
344
+ CHECK(result.output[4] == 0x20);
345
+ CHECK(result.output[5] == 0x60); // RTS
346
+ }
347
+
348
+ TEST_CASE("Assembler endAddress is set after last instruction", "[asm][program]") {
349
+ Assembler asm_;
350
+ auto result = asm_.assemble(" ORG $0300\n NOP\n NOP\n NOP");
351
+ REQUIRE(result.success);
352
+ CHECK(result.origin == 0x0300);
353
+ CHECK(result.endAddress == 0x0303);
354
+ }
355
+
356
+ // ---------------------------------------------------------------------------
357
+ // All addressing modes
358
+ // ---------------------------------------------------------------------------
359
+
360
+ TEST_CASE("Assembler immediate addressing mode", "[asm][addrmode]") {
361
+ Assembler asm_;
362
+ auto result = asm_.assemble(" LDX #$10");
363
+ REQUIRE(result.success);
364
+ CHECK(result.output[0] == 0xA2); // LDX #imm
365
+ CHECK(result.output[1] == 0x10);
366
+ }
367
+
368
+ TEST_CASE("Assembler zero page addressing mode", "[asm][addrmode]") {
369
+ Assembler asm_;
370
+ auto result = asm_.assemble(" LDA $10");
371
+ REQUIRE(result.success);
372
+ CHECK(result.output[0] == 0xA5);
373
+ CHECK(result.output[1] == 0x10);
374
+ }
375
+
376
+ TEST_CASE("Assembler zero page,X addressing mode", "[asm][addrmode]") {
377
+ Assembler asm_;
378
+ auto result = asm_.assemble(" LDA $10,X");
379
+ REQUIRE(result.success);
380
+ CHECK(result.output[0] == 0xB5); // LDA zp,X
381
+ CHECK(result.output[1] == 0x10);
382
+ }
383
+
384
+ TEST_CASE("Assembler zero page,Y addressing mode", "[asm][addrmode]") {
385
+ Assembler asm_;
386
+ auto result = asm_.assemble(" LDX $10,Y");
387
+ REQUIRE(result.success);
388
+ CHECK(result.output[0] == 0xB6); // LDX zp,Y
389
+ CHECK(result.output[1] == 0x10);
390
+ }
391
+
392
+ TEST_CASE("Assembler absolute addressing mode", "[asm][addrmode]") {
393
+ Assembler asm_;
394
+ auto result = asm_.assemble(" LDA $1000");
395
+ REQUIRE(result.success);
396
+ CHECK(result.output[0] == 0xAD);
397
+ CHECK(result.output[1] == 0x00);
398
+ CHECK(result.output[2] == 0x10);
399
+ }
400
+
401
+ TEST_CASE("Assembler absolute,X addressing mode", "[asm][addrmode]") {
402
+ Assembler asm_;
403
+ auto result = asm_.assemble(" LDA $1000,X");
404
+ REQUIRE(result.success);
405
+ CHECK(result.output[0] == 0xBD); // LDA abs,X
406
+ CHECK(result.output[1] == 0x00);
407
+ CHECK(result.output[2] == 0x10);
408
+ }
409
+
410
+ TEST_CASE("Assembler absolute,Y addressing mode", "[asm][addrmode]") {
411
+ Assembler asm_;
412
+ auto result = asm_.assemble(" LDA $1000,Y");
413
+ REQUIRE(result.success);
414
+ CHECK(result.output[0] == 0xB9); // LDA abs,Y
415
+ CHECK(result.output[1] == 0x00);
416
+ CHECK(result.output[2] == 0x10);
417
+ }
418
+
419
+ TEST_CASE("Assembler indexed indirect (ind,X) addressing mode", "[asm][addrmode]") {
420
+ Assembler asm_;
421
+ auto result = asm_.assemble(" LDA ($20,X)");
422
+ REQUIRE(result.success);
423
+ CHECK(result.output[0] == 0xA1); // LDA (zp,X)
424
+ CHECK(result.output[1] == 0x20);
425
+ }
426
+
427
+ TEST_CASE("Assembler indirect indexed (ind),Y addressing mode", "[asm][addrmode]") {
428
+ Assembler asm_;
429
+ auto result = asm_.assemble(" LDA ($20),Y");
430
+ REQUIRE(result.success);
431
+ CHECK(result.output[0] == 0xB1); // LDA (zp),Y
432
+ CHECK(result.output[1] == 0x20);
433
+ }
434
+
435
+ TEST_CASE("Assembler zero page indirect (65C02) addressing mode", "[asm][addrmode]") {
436
+ Assembler asm_;
437
+ auto result = asm_.assemble(" LDA ($20)");
438
+ REQUIRE(result.success);
439
+ CHECK(result.output[0] == 0xB2); // LDA (zp) - 65C02
440
+ CHECK(result.output[1] == 0x20);
441
+ }
442
+
443
+ TEST_CASE("Assembler relative addressing mode (branch)", "[asm][addrmode]") {
444
+ Assembler asm_;
445
+ auto result = asm_.assemble("HERE BEQ HERE");
446
+ REQUIRE(result.success);
447
+ CHECK(result.output[0] == 0xF0); // BEQ
448
+ CHECK(result.output[1] == 0xFE); // -2 (branch to self)
449
+ }
450
+
451
+ TEST_CASE("Assembler implied addressing mode", "[asm][addrmode]") {
452
+ Assembler asm_;
453
+ auto result = asm_.assemble(" INX");
454
+ REQUIRE(result.success);
455
+ REQUIRE(result.output.size() == 1);
456
+ CHECK(result.output[0] == 0xE8);
457
+ }
458
+
459
+ TEST_CASE("Assembler accumulator addressing mode", "[asm][addrmode]") {
460
+ Assembler asm_;
461
+
462
+ SECTION("ASL with no operand defaults to accumulator") {
463
+ auto result = asm_.assemble(" ASL");
464
+ REQUIRE(result.success);
465
+ REQUIRE(result.output.size() == 1);
466
+ CHECK(result.output[0] == 0x0A);
467
+ }
468
+
469
+ SECTION("ASL A explicit accumulator fails (operand evaluated as expression)") {
470
+ // The assembler evaluates operands as expressions before checking
471
+ // addressing modes, so "A" is treated as an undefined symbol.
472
+ // Use the no-operand form (ASL) for accumulator mode instead.
473
+ auto result = asm_.assemble(" ASL A");
474
+ REQUIRE_FALSE(result.success);
475
+ REQUIRE(result.errors.size() > 0);
476
+ }
477
+ }
478
+
479
+ TEST_CASE("Assembler indirect (JMP) addressing mode", "[asm][addrmode]") {
480
+ Assembler asm_;
481
+ auto result = asm_.assemble(" JMP ($1234)");
482
+ REQUIRE(result.success);
483
+ CHECK(result.output[0] == 0x6C); // JMP (abs)
484
+ CHECK(result.output[1] == 0x34);
485
+ CHECK(result.output[2] == 0x12);
486
+ }
487
+
488
+ TEST_CASE("Assembler JSR instruction", "[asm][instruction]") {
489
+ Assembler asm_;
490
+ auto result = asm_.assemble(" JSR $FFD2");
491
+ REQUIRE(result.success);
492
+ REQUIRE(result.output.size() == 3);
493
+ CHECK(result.output[0] == 0x20); // JSR
494
+ CHECK(result.output[1] == 0xD2);
495
+ CHECK(result.output[2] == 0xFF);
496
+ }
497
+
498
+ // ---------------------------------------------------------------------------
499
+ // Expression evaluation
500
+ // ---------------------------------------------------------------------------
501
+
502
+ TEST_CASE("Assembler handles decimal numbers", "[asm][expression]") {
503
+ Assembler asm_;
504
+ auto result = asm_.assemble(" LDA #65");
505
+ REQUIRE(result.success);
506
+ CHECK(result.output[1] == 65);
507
+ }
508
+
509
+ TEST_CASE("Assembler handles binary numbers", "[asm][expression]") {
510
+ Assembler asm_;
511
+ auto result = asm_.assemble(" LDA #%11001100");
512
+ REQUIRE(result.success);
513
+ CHECK(result.output[1] == 0xCC);
514
+ }
515
+
516
+ TEST_CASE("Assembler handles arithmetic in expressions", "[asm][expression]") {
517
+ Assembler asm_;
518
+ auto result = asm_.assemble(" LDA #$10+$20");
519
+ REQUIRE(result.success);
520
+ CHECK(result.output[1] == 0x30);
521
+ }