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.
- package/.clangd +5 -0
- package/.mcp.json +12 -0
- package/CLAUDE.md +362 -0
- package/CMakeLists.txt +774 -0
- package/LICENSE +21 -0
- package/README.md +392 -0
- package/build-wasm/generated/roms.cpp +2447 -0
- package/docker-compose.staging.yml +9 -0
- package/docs/basic-rom-disassembly.md +6663 -0
- package/docs/softswitch-comparison.md +273 -0
- package/docs/thunderclock-debug.md +89 -0
- package/examples/cube.bas +72 -0
- package/examples/hello.s +55 -0
- package/examples/scroll.s +140 -0
- package/package.json +18 -0
- package/public/assets/apple-logo-old.png +0 -0
- package/public/assets/apple-logo.png +0 -0
- package/public/assets/drive-closed-light-on.png +0 -0
- package/public/assets/drive-closed.png +0 -0
- package/public/assets/drive-open-light-on.png +0 -0
- package/public/assets/drive-open.png +0 -0
- package/public/audio-worklet.js +82 -0
- package/public/disks/Apple DOS 3.3 January 1983.dsk +0 -0
- package/public/disks/ProDOS 2.4.3.po +0 -0
- package/public/disks/h32mb.2mg +0 -0
- package/public/disks/library.json +26 -0
- package/public/docs/llms/llm-assembler.txt +90 -0
- package/public/docs/llms/llm-basic-program.txt +256 -0
- package/public/docs/llms/llm-disk-drives.txt +72 -0
- package/public/docs/llms/llm-file-explorer.txt +50 -0
- package/public/docs/llms/llm-hard-drives.txt +80 -0
- package/public/docs/llms/llm-main.txt +51 -0
- package/public/docs/llms/llm-slot-configuration.txt +66 -0
- package/public/icons/icon-192.svg +4 -0
- package/public/icons/icon-512.svg +4 -0
- package/public/index.html +661 -0
- package/public/llms.txt +49 -0
- package/public/manifest.json +29 -0
- package/public/shaders/burnin.glsl +22 -0
- package/public/shaders/crt.glsl +706 -0
- package/public/shaders/edge.glsl +109 -0
- package/public/shaders/vertex.glsl +8 -0
- package/public/sw.js +186 -0
- package/roms/341-0027.bin +0 -0
- package/roms/341-0160-A-US-UK.bin +0 -0
- package/roms/341-0160-A.bin +0 -0
- package/roms/342-0273-A-US-UK.bin +0 -0
- package/roms/342-0349-B-C0-FF.bin +0 -0
- package/roms/Apple Mouse Interface Card ROM - 342-0270-C.bin +0 -0
- package/roms/Thunderclock Plus ROM.bin +0 -0
- package/scripts/generate_roms.sh +69 -0
- package/src/bindings/wasm_interface.cpp +1940 -0
- package/src/core/assembler/assembler.cpp +1239 -0
- package/src/core/assembler/assembler.hpp +115 -0
- package/src/core/audio/audio.cpp +160 -0
- package/src/core/audio/audio.hpp +81 -0
- package/src/core/basic/basic_detokenizer.cpp +436 -0
- package/src/core/basic/basic_detokenizer.hpp +41 -0
- package/src/core/basic/basic_tokenizer.cpp +286 -0
- package/src/core/basic/basic_tokenizer.hpp +26 -0
- package/src/core/basic/basic_tokens.hpp +295 -0
- package/src/core/cards/disk2_card.cpp +568 -0
- package/src/core/cards/disk2_card.hpp +316 -0
- package/src/core/cards/expansion_card.hpp +185 -0
- package/src/core/cards/mockingboard/ay8910.cpp +616 -0
- package/src/core/cards/mockingboard/ay8910.hpp +159 -0
- package/src/core/cards/mockingboard/via6522.cpp +530 -0
- package/src/core/cards/mockingboard/via6522.hpp +163 -0
- package/src/core/cards/mockingboard_card.cpp +312 -0
- package/src/core/cards/mockingboard_card.hpp +159 -0
- package/src/core/cards/mouse_card.cpp +654 -0
- package/src/core/cards/mouse_card.hpp +190 -0
- package/src/core/cards/smartport/block_device.cpp +202 -0
- package/src/core/cards/smartport/block_device.hpp +60 -0
- package/src/core/cards/smartport/smartport_card.cpp +603 -0
- package/src/core/cards/smartport/smartport_card.hpp +120 -0
- package/src/core/cards/thunderclock_card.cpp +237 -0
- package/src/core/cards/thunderclock_card.hpp +122 -0
- package/src/core/cpu/cpu6502.cpp +1609 -0
- package/src/core/cpu/cpu6502.hpp +203 -0
- package/src/core/debug/condition_evaluator.cpp +470 -0
- package/src/core/debug/condition_evaluator.hpp +87 -0
- package/src/core/disassembler/disassembler.cpp +552 -0
- package/src/core/disassembler/disassembler.hpp +171 -0
- package/src/core/disk-image/disk_image.hpp +267 -0
- package/src/core/disk-image/dsk_disk_image.cpp +827 -0
- package/src/core/disk-image/dsk_disk_image.hpp +204 -0
- package/src/core/disk-image/gcr_encoding.cpp +147 -0
- package/src/core/disk-image/gcr_encoding.hpp +78 -0
- package/src/core/disk-image/woz_disk_image.cpp +1049 -0
- package/src/core/disk-image/woz_disk_image.hpp +343 -0
- package/src/core/emulator.cpp +2126 -0
- package/src/core/emulator.hpp +434 -0
- package/src/core/filesystem/dos33.cpp +178 -0
- package/src/core/filesystem/dos33.hpp +66 -0
- package/src/core/filesystem/pascal.cpp +262 -0
- package/src/core/filesystem/pascal.hpp +87 -0
- package/src/core/filesystem/prodos.cpp +369 -0
- package/src/core/filesystem/prodos.hpp +119 -0
- package/src/core/input/keyboard.cpp +227 -0
- package/src/core/input/keyboard.hpp +111 -0
- package/src/core/mmu/mmu.cpp +1387 -0
- package/src/core/mmu/mmu.hpp +236 -0
- package/src/core/types.hpp +196 -0
- package/src/core/video/video.cpp +680 -0
- package/src/core/video/video.hpp +156 -0
- package/src/css/assembler-editor.css +1617 -0
- package/src/css/base.css +470 -0
- package/src/css/basic-debugger.css +791 -0
- package/src/css/basic-editor.css +792 -0
- package/src/css/controls.css +783 -0
- package/src/css/cpu-debugger.css +1413 -0
- package/src/css/debug-base.css +160 -0
- package/src/css/debug-windows.css +6455 -0
- package/src/css/disk-drives.css +406 -0
- package/src/css/documentation.css +392 -0
- package/src/css/file-explorer.css +867 -0
- package/src/css/hard-drive.css +180 -0
- package/src/css/layout.css +217 -0
- package/src/css/memory-windows.css +798 -0
- package/src/css/modals.css +510 -0
- package/src/css/monitor.css +425 -0
- package/src/css/release-notes.css +101 -0
- package/src/css/responsive.css +400 -0
- package/src/css/rule-builder.css +340 -0
- package/src/css/save-states.css +201 -0
- package/src/css/settings-windows.css +1231 -0
- package/src/css/window-switcher.css +150 -0
- package/src/js/agent/agent-manager.js +643 -0
- package/src/js/agent/agent-tools.js +293 -0
- package/src/js/agent/agent-version-tools.js +131 -0
- package/src/js/agent/assembler-tools.js +357 -0
- package/src/js/agent/basic-program-tools.js +894 -0
- package/src/js/agent/disk-tools.js +417 -0
- package/src/js/agent/file-explorer-tools.js +269 -0
- package/src/js/agent/index.js +13 -0
- package/src/js/agent/main-tools.js +222 -0
- package/src/js/agent/slot-tools.js +303 -0
- package/src/js/agent/smartport-tools.js +257 -0
- package/src/js/agent/window-tools.js +80 -0
- package/src/js/audio/audio-driver.js +417 -0
- package/src/js/audio/audio-worklet.js +85 -0
- package/src/js/audio/index.js +8 -0
- package/src/js/config/default-layout.js +34 -0
- package/src/js/config/version.js +8 -0
- package/src/js/data/apple2-rom-routines.js +577 -0
- package/src/js/debug/assembler-editor-window.js +2993 -0
- package/src/js/debug/basic-breakpoint-manager.js +529 -0
- package/src/js/debug/basic-program-parser.js +436 -0
- package/src/js/debug/basic-program-window.js +2594 -0
- package/src/js/debug/basic-variable-inspector.js +447 -0
- package/src/js/debug/breakpoint-manager.js +472 -0
- package/src/js/debug/cpu-debugger-window.js +2396 -0
- package/src/js/debug/index.js +22 -0
- package/src/js/debug/label-manager.js +238 -0
- package/src/js/debug/memory-browser-window.js +416 -0
- package/src/js/debug/memory-heat-map-window.js +481 -0
- package/src/js/debug/memory-map-window.js +206 -0
- package/src/js/debug/mockingboard-window.js +882 -0
- package/src/js/debug/mouse-card-window.js +355 -0
- package/src/js/debug/rule-builder-window.js +648 -0
- package/src/js/debug/soft-switch-window.js +458 -0
- package/src/js/debug/stack-viewer-window.js +221 -0
- package/src/js/debug/symbols.js +416 -0
- package/src/js/debug/trace-panel.js +291 -0
- package/src/js/debug/zero-page-watch-window.js +297 -0
- package/src/js/disk-manager/disk-drives-window.js +212 -0
- package/src/js/disk-manager/disk-operations.js +284 -0
- package/src/js/disk-manager/disk-persistence.js +301 -0
- package/src/js/disk-manager/disk-surface-renderer.js +388 -0
- package/src/js/disk-manager/drive-sounds.js +139 -0
- package/src/js/disk-manager/hard-drive-manager.js +481 -0
- package/src/js/disk-manager/hard-drive-persistence.js +187 -0
- package/src/js/disk-manager/hard-drive-window.js +57 -0
- package/src/js/disk-manager/index.js +890 -0
- package/src/js/display/display-settings-window.js +383 -0
- package/src/js/display/index.js +10 -0
- package/src/js/display/screen-window.js +342 -0
- package/src/js/display/webgl-renderer.js +705 -0
- package/src/js/file-explorer/disassembler.js +574 -0
- package/src/js/file-explorer/dos33.js +266 -0
- package/src/js/file-explorer/file-viewer.js +359 -0
- package/src/js/file-explorer/index.js +1261 -0
- package/src/js/file-explorer/prodos.js +549 -0
- package/src/js/file-explorer/utils.js +67 -0
- package/src/js/help/documentation-window.js +1096 -0
- package/src/js/help/index.js +10 -0
- package/src/js/help/release-notes-window.js +85 -0
- package/src/js/help/release-notes.js +612 -0
- package/src/js/input/gamepad-handler.js +176 -0
- package/src/js/input/index.js +12 -0
- package/src/js/input/input-handler.js +396 -0
- package/src/js/input/joystick-window.js +404 -0
- package/src/js/input/mouse-handler.js +99 -0
- package/src/js/input/text-selection.js +462 -0
- package/src/js/main.js +653 -0
- package/src/js/state/index.js +15 -0
- package/src/js/state/save-states-window.js +393 -0
- package/src/js/state/state-manager.js +409 -0
- package/src/js/state/state-persistence.js +218 -0
- package/src/js/ui/confirm.js +43 -0
- package/src/js/ui/disk-drive-positioner.js +347 -0
- package/src/js/ui/reminder-controller.js +129 -0
- package/src/js/ui/slot-configuration-window.js +560 -0
- package/src/js/ui/theme-manager.js +61 -0
- package/src/js/ui/toast.js +44 -0
- package/src/js/ui/ui-controller.js +897 -0
- package/src/js/ui/window-switcher.js +275 -0
- package/src/js/utils/basic-autocomplete.js +832 -0
- package/src/js/utils/basic-highlighting.js +473 -0
- package/src/js/utils/basic-tokenizer.js +153 -0
- package/src/js/utils/basic-tokens.js +117 -0
- package/src/js/utils/constants.js +28 -0
- package/src/js/utils/indexeddb-helper.js +225 -0
- package/src/js/utils/merlin-editor-support.js +905 -0
- package/src/js/utils/merlin-highlighting.js +551 -0
- package/src/js/utils/storage.js +125 -0
- package/src/js/utils/string-utils.js +19 -0
- package/src/js/utils/wasm-memory.js +54 -0
- package/src/js/windows/base-window.js +690 -0
- package/src/js/windows/index.js +9 -0
- package/src/js/windows/window-manager.js +375 -0
- package/tests/catch2/catch.hpp +17976 -0
- package/tests/common/basic_program_builder.cpp +119 -0
- package/tests/common/basic_program_builder.hpp +209 -0
- package/tests/common/disk_image_builder.cpp +444 -0
- package/tests/common/disk_image_builder.hpp +141 -0
- package/tests/common/test_helpers.hpp +118 -0
- package/tests/gcr/gcr-test.cpp +142 -0
- package/tests/integration/check-rom.js +70 -0
- package/tests/integration/compare-boot.js +239 -0
- package/tests/integration/crash-trace.js +102 -0
- package/tests/integration/disk-boot-test.js +264 -0
- package/tests/integration/memory-crash.js +108 -0
- package/tests/integration/nibble-read-test.js +249 -0
- package/tests/integration/phase-test.js +159 -0
- package/tests/integration/test_emulator.cpp +291 -0
- package/tests/integration/test_emulator_basic.cpp +91 -0
- package/tests/integration/test_emulator_debug.cpp +344 -0
- package/tests/integration/test_emulator_disk.cpp +153 -0
- package/tests/integration/test_emulator_state.cpp +163 -0
- package/tests/klaus/6502_functional_test.bin +0 -0
- package/tests/klaus/65C02_extended_opcodes_test.bin +0 -0
- package/tests/klaus/klaus_6502_test.cpp +184 -0
- package/tests/klaus/klaus_65c02_test.cpp +197 -0
- package/tests/thunderclock/thunderclock_mmu_test.cpp +304 -0
- package/tests/thunderclock/thunderclock_test.cpp +550 -0
- package/tests/unit/test_assembler.cpp +521 -0
- package/tests/unit/test_audio.cpp +196 -0
- package/tests/unit/test_ay8910.cpp +311 -0
- package/tests/unit/test_basic_detokenizer.cpp +265 -0
- package/tests/unit/test_basic_tokenizer.cpp +382 -0
- package/tests/unit/test_block_device.cpp +259 -0
- package/tests/unit/test_condition_evaluator.cpp +219 -0
- package/tests/unit/test_cpu6502.cpp +1301 -0
- package/tests/unit/test_cpu_addressing.cpp +361 -0
- package/tests/unit/test_cpu_cycle_counts.cpp +409 -0
- package/tests/unit/test_cpu_decimal.cpp +166 -0
- package/tests/unit/test_cpu_interrupts.cpp +285 -0
- package/tests/unit/test_disassembler.cpp +323 -0
- package/tests/unit/test_disk2_card.cpp +330 -0
- package/tests/unit/test_dos33.cpp +273 -0
- package/tests/unit/test_dsk_disk_image.cpp +315 -0
- package/tests/unit/test_expansion_card.cpp +178 -0
- package/tests/unit/test_gcr_encoding.cpp +232 -0
- package/tests/unit/test_keyboard.cpp +262 -0
- package/tests/unit/test_mmu.cpp +555 -0
- package/tests/unit/test_mmu_slots.cpp +323 -0
- package/tests/unit/test_mockingboard.cpp +352 -0
- package/tests/unit/test_mouse_card.cpp +386 -0
- package/tests/unit/test_pascal.cpp +248 -0
- package/tests/unit/test_prodos.cpp +259 -0
- package/tests/unit/test_smartport_card.cpp +321 -0
- package/tests/unit/test_thunderclock.cpp +354 -0
- package/tests/unit/test_via6522.cpp +323 -0
- package/tests/unit/test_video.cpp +319 -0
- package/tests/unit/test_woz_disk_image.cpp +257 -0
- package/vite.config.js +96 -0
- package/wiki/AI-Agent.md +372 -0
- package/wiki/Architecture-Overview.md +303 -0
- package/wiki/Audio-System.md +449 -0
- package/wiki/CPU-Emulation.md +477 -0
- package/wiki/Debugger.md +516 -0
- package/wiki/Disk-Drives.md +161 -0
- package/wiki/Disk-System-Internals.md +547 -0
- package/wiki/Display-Settings.md +88 -0
- package/wiki/Expansion-Slots.md +187 -0
- package/wiki/File-Explorer.md +259 -0
- package/wiki/Getting-Started.md +156 -0
- package/wiki/Home.md +69 -0
- package/wiki/Input-Devices.md +183 -0
- package/wiki/Keyboard-Shortcuts.md +158 -0
- package/wiki/Memory-System.md +364 -0
- package/wiki/Save-States.md +172 -0
- 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
|
+
}
|