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,265 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* test_basic_detokenizer.cpp - Unit tests for Applesoft and Integer BASIC detokenizer
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
#define CATCH_CONFIG_MAIN
|
|
6
|
+
#include "catch.hpp"
|
|
7
|
+
|
|
8
|
+
#include "basic_detokenizer.hpp"
|
|
9
|
+
#include "basic_program_builder.hpp"
|
|
10
|
+
|
|
11
|
+
#include <cstring>
|
|
12
|
+
#include <string>
|
|
13
|
+
#include <vector>
|
|
14
|
+
|
|
15
|
+
using namespace a2e;
|
|
16
|
+
using test::ApplesoftProgramBuilder;
|
|
17
|
+
using test::IntegerBasicProgramBuilder;
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Applesoft: simple PRINT line
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
TEST_CASE("detokenizeApplesoft simple PRINT line", "[basic][applesoft][print]") {
|
|
24
|
+
ApplesoftProgramBuilder builder;
|
|
25
|
+
|
|
26
|
+
// 10 PRINT "HELLO"
|
|
27
|
+
// Token stream: PRINT(0xBA) '"' 'H' 'E' 'L' 'L' 'O' '"'
|
|
28
|
+
builder.addLine(10, std::vector<uint8_t>{0xBA, 0x22, 'H', 'E', 'L', 'L', 'O', 0x22});
|
|
29
|
+
|
|
30
|
+
auto data = builder.build();
|
|
31
|
+
const char* result = BasicDetokenizer::detokenizeApplesoft(data.data(),
|
|
32
|
+
static_cast<int>(data.size()),
|
|
33
|
+
false);
|
|
34
|
+
|
|
35
|
+
std::string output(result);
|
|
36
|
+
// Should contain line number 10
|
|
37
|
+
CHECK(output.find("10") != std::string::npos);
|
|
38
|
+
// Should contain PRINT keyword
|
|
39
|
+
CHECK(output.find("PRINT") != std::string::npos);
|
|
40
|
+
// Should contain the string
|
|
41
|
+
CHECK(output.find("HELLO") != std::string::npos);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ============================================================================
|
|
45
|
+
// Applesoft: GOTO line
|
|
46
|
+
// ============================================================================
|
|
47
|
+
|
|
48
|
+
TEST_CASE("detokenizeApplesoft GOTO line", "[basic][applesoft][goto]") {
|
|
49
|
+
ApplesoftProgramBuilder builder;
|
|
50
|
+
|
|
51
|
+
// 20 GOTO 100
|
|
52
|
+
// Token stream: GOTO(0xAB) '1' '0' '0'
|
|
53
|
+
builder.addLine(20, std::vector<uint8_t>{0xAB, '1', '0', '0'});
|
|
54
|
+
|
|
55
|
+
auto data = builder.build();
|
|
56
|
+
const char* result = BasicDetokenizer::detokenizeApplesoft(data.data(),
|
|
57
|
+
static_cast<int>(data.size()),
|
|
58
|
+
false);
|
|
59
|
+
|
|
60
|
+
std::string output(result);
|
|
61
|
+
CHECK(output.find("20") != std::string::npos);
|
|
62
|
+
CHECK(output.find("GOTO") != std::string::npos);
|
|
63
|
+
CHECK(output.find("100") != std::string::npos);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ============================================================================
|
|
67
|
+
// Applesoft: multiple lines
|
|
68
|
+
// ============================================================================
|
|
69
|
+
|
|
70
|
+
TEST_CASE("detokenizeApplesoft multiple lines", "[basic][applesoft][multi]") {
|
|
71
|
+
ApplesoftProgramBuilder builder;
|
|
72
|
+
|
|
73
|
+
// 10 PRINT "HI"
|
|
74
|
+
builder.addLine(10, std::vector<uint8_t>{0xBA, 0x22, 'H', 'I', 0x22});
|
|
75
|
+
|
|
76
|
+
// 20 GOTO 10
|
|
77
|
+
builder.addLine(20, std::vector<uint8_t>{0xAB, '1', '0'});
|
|
78
|
+
|
|
79
|
+
auto data = builder.build();
|
|
80
|
+
const char* result = BasicDetokenizer::detokenizeApplesoft(data.data(),
|
|
81
|
+
static_cast<int>(data.size()),
|
|
82
|
+
false);
|
|
83
|
+
|
|
84
|
+
std::string output(result);
|
|
85
|
+
// Both line numbers should appear
|
|
86
|
+
CHECK(output.find("10") != std::string::npos);
|
|
87
|
+
CHECK(output.find("20") != std::string::npos);
|
|
88
|
+
CHECK(output.find("PRINT") != std::string::npos);
|
|
89
|
+
CHECK(output.find("GOTO") != std::string::npos);
|
|
90
|
+
|
|
91
|
+
// Should have a newline separating the lines
|
|
92
|
+
CHECK(output.find('\n') != std::string::npos);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ============================================================================
|
|
96
|
+
// Applesoft: with length header (hasLengthHeader=true)
|
|
97
|
+
// ============================================================================
|
|
98
|
+
|
|
99
|
+
TEST_CASE("detokenizeApplesoft with length header", "[basic][applesoft][header]") {
|
|
100
|
+
ApplesoftProgramBuilder builder;
|
|
101
|
+
|
|
102
|
+
// 10 END
|
|
103
|
+
builder.addLine(10, std::vector<uint8_t>{0x80}); // END token
|
|
104
|
+
|
|
105
|
+
auto data = builder.buildWithHeader();
|
|
106
|
+
const char* result = BasicDetokenizer::detokenizeApplesoft(data.data(),
|
|
107
|
+
static_cast<int>(data.size()),
|
|
108
|
+
true);
|
|
109
|
+
|
|
110
|
+
std::string output(result);
|
|
111
|
+
CHECK(output.find("10") != std::string::npos);
|
|
112
|
+
CHECK(output.find("END") != std::string::npos);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ============================================================================
|
|
116
|
+
// Applesoft: verify output contains line numbers
|
|
117
|
+
// ============================================================================
|
|
118
|
+
|
|
119
|
+
TEST_CASE("detokenizeApplesoft output contains padded line numbers", "[basic][applesoft][linenum]") {
|
|
120
|
+
ApplesoftProgramBuilder builder;
|
|
121
|
+
builder.addLine(100, std::vector<uint8_t>{0x80}); // END
|
|
122
|
+
|
|
123
|
+
auto data = builder.build();
|
|
124
|
+
const char* result = BasicDetokenizer::detokenizeApplesoft(data.data(),
|
|
125
|
+
static_cast<int>(data.size()),
|
|
126
|
+
false);
|
|
127
|
+
|
|
128
|
+
std::string output(result);
|
|
129
|
+
// Line number 100 should appear in the output, padded to 5 chars
|
|
130
|
+
CHECK(output.find("100") != std::string::npos);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ============================================================================
|
|
134
|
+
// Applesoft: HOME keyword (0x97)
|
|
135
|
+
// ============================================================================
|
|
136
|
+
|
|
137
|
+
TEST_CASE("detokenizeApplesoft HOME keyword", "[basic][applesoft][home]") {
|
|
138
|
+
ApplesoftProgramBuilder builder;
|
|
139
|
+
|
|
140
|
+
// 5 HOME
|
|
141
|
+
builder.addLine(5, std::vector<uint8_t>{0x97});
|
|
142
|
+
|
|
143
|
+
auto data = builder.build();
|
|
144
|
+
const char* result = BasicDetokenizer::detokenizeApplesoft(data.data(),
|
|
145
|
+
static_cast<int>(data.size()),
|
|
146
|
+
false);
|
|
147
|
+
|
|
148
|
+
std::string output(result);
|
|
149
|
+
CHECK(output.find("HOME") != std::string::npos);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ============================================================================
|
|
153
|
+
// Applesoft: FOR/NEXT
|
|
154
|
+
// ============================================================================
|
|
155
|
+
|
|
156
|
+
TEST_CASE("detokenizeApplesoft FOR/NEXT", "[basic][applesoft][for]") {
|
|
157
|
+
ApplesoftProgramBuilder builder;
|
|
158
|
+
|
|
159
|
+
// 10 FOR I = 1 TO 10
|
|
160
|
+
// FOR(0x81) 'I' '=' '1' TO(0xC1) '1' '0'
|
|
161
|
+
builder.addLine(10, std::vector<uint8_t>{0x81, 'I', 0xD0, '1', 0xC1, '1', '0'});
|
|
162
|
+
|
|
163
|
+
// 20 NEXT I
|
|
164
|
+
// NEXT(0x82) 'I'
|
|
165
|
+
builder.addLine(20, std::vector<uint8_t>{0x82, 'I'});
|
|
166
|
+
|
|
167
|
+
auto data = builder.build();
|
|
168
|
+
const char* result = BasicDetokenizer::detokenizeApplesoft(data.data(),
|
|
169
|
+
static_cast<int>(data.size()),
|
|
170
|
+
false);
|
|
171
|
+
|
|
172
|
+
std::string output(result);
|
|
173
|
+
CHECK(output.find("FOR") != std::string::npos);
|
|
174
|
+
CHECK(output.find("TO") != std::string::npos);
|
|
175
|
+
CHECK(output.find("NEXT") != std::string::npos);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ============================================================================
|
|
179
|
+
// Integer BASIC: simple program
|
|
180
|
+
// ============================================================================
|
|
181
|
+
|
|
182
|
+
TEST_CASE("detokenizeIntegerBasic simple PRINT program", "[basic][integer]") {
|
|
183
|
+
IntegerBasicProgramBuilder builder;
|
|
184
|
+
|
|
185
|
+
// Integer BASIC: 10 PRINT "HI"
|
|
186
|
+
// PRINT token = 0x61 in integer BASIC token table
|
|
187
|
+
// String: 0x28 = start quote, characters, 0x29 = end quote
|
|
188
|
+
builder.addLine(10, std::vector<uint8_t>{0x61, 0x28, 'H', 'I', 0x29});
|
|
189
|
+
|
|
190
|
+
auto data = builder.build();
|
|
191
|
+
const char* result = BasicDetokenizer::detokenizeIntegerBasic(data.data(),
|
|
192
|
+
static_cast<int>(data.size()),
|
|
193
|
+
false);
|
|
194
|
+
|
|
195
|
+
std::string output(result);
|
|
196
|
+
CHECK(output.find("10") != std::string::npos);
|
|
197
|
+
CHECK(output.find("PRINT") != std::string::npos);
|
|
198
|
+
CHECK(output.find("HI") != std::string::npos);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
TEST_CASE("detokenizeIntegerBasic with numeric constant", "[basic][integer][numeric]") {
|
|
202
|
+
IntegerBasicProgramBuilder builder;
|
|
203
|
+
|
|
204
|
+
// Integer BASIC: 10 PRINT 42
|
|
205
|
+
// PRINT token = 0x61
|
|
206
|
+
// Numeric: 0xB0 prefix + 2-byte LE value (42 = 0x002A)
|
|
207
|
+
builder.addLine(10, std::vector<uint8_t>{0x61, 0xB0, 0x2A, 0x00});
|
|
208
|
+
|
|
209
|
+
auto data = builder.build();
|
|
210
|
+
const char* result = BasicDetokenizer::detokenizeIntegerBasic(data.data(),
|
|
211
|
+
static_cast<int>(data.size()),
|
|
212
|
+
false);
|
|
213
|
+
|
|
214
|
+
std::string output(result);
|
|
215
|
+
CHECK(output.find("10") != std::string::npos);
|
|
216
|
+
CHECK(output.find("42") != std::string::npos);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
TEST_CASE("detokenizeIntegerBasic with length header", "[basic][integer][header]") {
|
|
220
|
+
IntegerBasicProgramBuilder builder;
|
|
221
|
+
|
|
222
|
+
// 10 END
|
|
223
|
+
builder.addLine(10, std::vector<uint8_t>{0x51}); // END token
|
|
224
|
+
|
|
225
|
+
auto data = builder.buildWithHeader();
|
|
226
|
+
const char* result = BasicDetokenizer::detokenizeIntegerBasic(data.data(),
|
|
227
|
+
static_cast<int>(data.size()),
|
|
228
|
+
true);
|
|
229
|
+
|
|
230
|
+
std::string output(result);
|
|
231
|
+
CHECK(output.find("10") != std::string::npos);
|
|
232
|
+
CHECK(output.find("END") != std::string::npos);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// ============================================================================
|
|
236
|
+
// Applesoft: REM preserves text
|
|
237
|
+
// ============================================================================
|
|
238
|
+
|
|
239
|
+
TEST_CASE("detokenizeApplesoft REM preserves text", "[basic][applesoft][rem]") {
|
|
240
|
+
ApplesoftProgramBuilder builder;
|
|
241
|
+
|
|
242
|
+
// 10 REM THIS IS A COMMENT
|
|
243
|
+
builder.addLine(10, std::vector<uint8_t>{0xB2, 'T', 'H', 'I', 'S', ' ', 'I', 'S',
|
|
244
|
+
' ', 'A', ' ', 'C', 'O', 'M', 'M', 'E', 'N', 'T'});
|
|
245
|
+
|
|
246
|
+
auto data = builder.build();
|
|
247
|
+
const char* result = BasicDetokenizer::detokenizeApplesoft(data.data(),
|
|
248
|
+
static_cast<int>(data.size()),
|
|
249
|
+
false);
|
|
250
|
+
|
|
251
|
+
std::string output(result);
|
|
252
|
+
CHECK(output.find("REM") != std::string::npos);
|
|
253
|
+
CHECK(output.find("THIS IS A COMMENT") != std::string::npos);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ============================================================================
|
|
257
|
+
// Empty program
|
|
258
|
+
// ============================================================================
|
|
259
|
+
|
|
260
|
+
TEST_CASE("detokenizeApplesoft empty program returns empty string", "[basic][applesoft][empty]") {
|
|
261
|
+
// Just the program terminator (0x00, 0x00)
|
|
262
|
+
uint8_t data[] = {0x00, 0x00};
|
|
263
|
+
const char* result = BasicDetokenizer::detokenizeApplesoft(data, sizeof(data), false);
|
|
264
|
+
CHECK(strlen(result) == 0);
|
|
265
|
+
}
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* test_basic_tokenizer.cpp - Unit tests for Applesoft BASIC tokenizer
|
|
3
|
+
*
|
|
4
|
+
* Tests the BASIC tokenizer including:
|
|
5
|
+
* - Single-line tokenization
|
|
6
|
+
* - Multi-line programs
|
|
7
|
+
* - Memory layout (next-addr, line-num, tokens, terminator)
|
|
8
|
+
* - Keyword token values (PRINT, GOTO, etc.)
|
|
9
|
+
* - Empty input handling
|
|
10
|
+
* - Round-trip tokenize/detokenize recovery
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
#define CATCH_CONFIG_MAIN
|
|
14
|
+
#include "catch.hpp"
|
|
15
|
+
|
|
16
|
+
#include "basic_tokenizer.hpp"
|
|
17
|
+
#include "basic_detokenizer.hpp"
|
|
18
|
+
|
|
19
|
+
#include <array>
|
|
20
|
+
#include <cstring>
|
|
21
|
+
#include <string>
|
|
22
|
+
#include <functional>
|
|
23
|
+
|
|
24
|
+
using namespace a2e;
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Test fixture: 64KB memory with zero-page pointers set up
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
struct BasicMemory {
|
|
31
|
+
std::array<uint8_t, 65536> mem{};
|
|
32
|
+
MemReadFn readMem;
|
|
33
|
+
MemWriteFn writeMem;
|
|
34
|
+
|
|
35
|
+
BasicMemory() {
|
|
36
|
+
mem.fill(0);
|
|
37
|
+
// Set up TXTTAB pointer at $67/$68 = $0801
|
|
38
|
+
mem[0x67] = 0x01;
|
|
39
|
+
mem[0x68] = 0x08;
|
|
40
|
+
// Set up HIMEM/MEMSIZE at $73/$74 = $9600
|
|
41
|
+
mem[0x73] = 0x00;
|
|
42
|
+
mem[0x74] = 0x96;
|
|
43
|
+
|
|
44
|
+
readMem = [this](uint16_t a) -> uint8_t { return mem[a]; };
|
|
45
|
+
writeMem = [this](uint16_t a, uint8_t v) { mem[a] = v; };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Read a 16-bit little-endian value from memory
|
|
49
|
+
uint16_t read16(uint16_t addr) const {
|
|
50
|
+
return mem[addr] | (mem[addr + 1] << 8);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// Simple PRINT tokenization
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
TEST_CASE("loadBasicProgram tokenizes simple PRINT line", "[basic][tokenizer]") {
|
|
59
|
+
BasicMemory m;
|
|
60
|
+
int count = loadBasicProgram("10 PRINT \"HELLO\"", m.readMem, m.writeMem);
|
|
61
|
+
REQUIRE(count == 1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
TEST_CASE("Tokenized memory has correct structure at $0801", "[basic][tokenizer]") {
|
|
65
|
+
BasicMemory m;
|
|
66
|
+
int count = loadBasicProgram("10 PRINT \"HELLO\"", m.readMem, m.writeMem);
|
|
67
|
+
REQUIRE(count == 1);
|
|
68
|
+
|
|
69
|
+
// At $0801: [next-addr:2][line-num:2][tokens...][0x00]
|
|
70
|
+
uint16_t nextAddr = m.read16(0x0801);
|
|
71
|
+
uint16_t lineNum = m.read16(0x0803);
|
|
72
|
+
|
|
73
|
+
CHECK(lineNum == 10);
|
|
74
|
+
CHECK(nextAddr > 0x0805); // Must point past this line
|
|
75
|
+
|
|
76
|
+
// The first token byte should be the PRINT token (0xBA)
|
|
77
|
+
CHECK(m.mem[0x0805] == 0xBA);
|
|
78
|
+
|
|
79
|
+
// The line should end with 0x00
|
|
80
|
+
uint16_t termPos = nextAddr - 1;
|
|
81
|
+
CHECK(m.mem[termPos] == 0x00);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
TEST_CASE("Program ends with double zero bytes", "[basic][tokenizer]") {
|
|
85
|
+
BasicMemory m;
|
|
86
|
+
int count = loadBasicProgram("10 PRINT \"HI\"", m.readMem, m.writeMem);
|
|
87
|
+
REQUIRE(count == 1);
|
|
88
|
+
|
|
89
|
+
uint16_t nextAddr = m.read16(0x0801);
|
|
90
|
+
// After the last line, there should be 0x00, 0x00
|
|
91
|
+
CHECK(m.mem[nextAddr] == 0x00);
|
|
92
|
+
CHECK(m.mem[nextAddr + 1] == 0x00);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
// Multiple lines
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
TEST_CASE("loadBasicProgram returns correct count for multiple lines", "[basic][tokenizer]") {
|
|
100
|
+
BasicMemory m;
|
|
101
|
+
int count = loadBasicProgram(
|
|
102
|
+
"10 PRINT \"A\"\n"
|
|
103
|
+
"20 PRINT \"B\"\n"
|
|
104
|
+
"30 PRINT \"C\"",
|
|
105
|
+
m.readMem, m.writeMem
|
|
106
|
+
);
|
|
107
|
+
REQUIRE(count == 3);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
TEST_CASE("Multiple lines are chained in memory", "[basic][tokenizer]") {
|
|
111
|
+
BasicMemory m;
|
|
112
|
+
int count = loadBasicProgram(
|
|
113
|
+
"10 PRINT \"A\"\n"
|
|
114
|
+
"20 GOTO 10",
|
|
115
|
+
m.readMem, m.writeMem
|
|
116
|
+
);
|
|
117
|
+
REQUIRE(count == 2);
|
|
118
|
+
|
|
119
|
+
// Line 1 starts at $0801
|
|
120
|
+
uint16_t line1Next = m.read16(0x0801);
|
|
121
|
+
uint16_t line1Num = m.read16(0x0803);
|
|
122
|
+
CHECK(line1Num == 10);
|
|
123
|
+
|
|
124
|
+
// Line 2 starts at line1Next
|
|
125
|
+
uint16_t line2Next = m.read16(line1Next);
|
|
126
|
+
uint16_t line2Num = m.read16(line1Next + 2);
|
|
127
|
+
CHECK(line2Num == 20);
|
|
128
|
+
|
|
129
|
+
// End of program
|
|
130
|
+
CHECK(m.mem[line2Next] == 0x00);
|
|
131
|
+
CHECK(m.mem[line2Next + 1] == 0x00);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
// Empty / null input
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
|
|
138
|
+
TEST_CASE("loadBasicProgram with empty string returns 0", "[basic][tokenizer]") {
|
|
139
|
+
BasicMemory m;
|
|
140
|
+
int count = loadBasicProgram("", m.readMem, m.writeMem);
|
|
141
|
+
CHECK(count == 0);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
TEST_CASE("loadBasicProgram with null pointer returns -1", "[basic][tokenizer]") {
|
|
145
|
+
BasicMemory m;
|
|
146
|
+
int count = loadBasicProgram(nullptr, m.readMem, m.writeMem);
|
|
147
|
+
CHECK(count == -1);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
TEST_CASE("loadBasicProgram with whitespace-only lines returns 0", "[basic][tokenizer]") {
|
|
151
|
+
BasicMemory m;
|
|
152
|
+
int count = loadBasicProgram(" \n \n", m.readMem, m.writeMem);
|
|
153
|
+
CHECK(count == 0);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
// Specific token values
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
|
|
160
|
+
TEST_CASE("GOTO keyword is tokenized as 0xAB", "[basic][tokenizer]") {
|
|
161
|
+
BasicMemory m;
|
|
162
|
+
int count = loadBasicProgram("10 GOTO 100", m.readMem, m.writeMem);
|
|
163
|
+
REQUIRE(count == 1);
|
|
164
|
+
|
|
165
|
+
// Scan token bytes at $0805+ for the GOTO token (0xAB)
|
|
166
|
+
uint16_t nextAddr = m.read16(0x0801);
|
|
167
|
+
bool foundGoto = false;
|
|
168
|
+
for (uint16_t addr = 0x0805; addr < nextAddr; addr++) {
|
|
169
|
+
if (m.mem[addr] == 0xAB) {
|
|
170
|
+
foundGoto = true;
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
CHECK(foundGoto);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
TEST_CASE("PRINT keyword is tokenized as 0xBA", "[basic][tokenizer]") {
|
|
178
|
+
BasicMemory m;
|
|
179
|
+
int count = loadBasicProgram("10 PRINT", m.readMem, m.writeMem);
|
|
180
|
+
REQUIRE(count == 1);
|
|
181
|
+
|
|
182
|
+
CHECK(m.mem[0x0805] == 0xBA);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
TEST_CASE("HOME keyword is tokenized as 0x97", "[basic][tokenizer]") {
|
|
186
|
+
BasicMemory m;
|
|
187
|
+
int count = loadBasicProgram("10 HOME", m.readMem, m.writeMem);
|
|
188
|
+
REQUIRE(count == 1);
|
|
189
|
+
|
|
190
|
+
CHECK(m.mem[0x0805] == 0x97);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
TEST_CASE("FOR keyword is tokenized as 0x81", "[basic][tokenizer]") {
|
|
194
|
+
BasicMemory m;
|
|
195
|
+
int count = loadBasicProgram("10 FOR I=1 TO 10", m.readMem, m.writeMem);
|
|
196
|
+
REQUIRE(count == 1);
|
|
197
|
+
|
|
198
|
+
CHECK(m.mem[0x0805] == 0x81); // FOR token
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
TEST_CASE("REM keyword is tokenized as 0xB2", "[basic][tokenizer]") {
|
|
202
|
+
BasicMemory m;
|
|
203
|
+
int count = loadBasicProgram("10 REM THIS IS A COMMENT", m.readMem, m.writeMem);
|
|
204
|
+
REQUIRE(count == 1);
|
|
205
|
+
|
|
206
|
+
CHECK(m.mem[0x0805] == 0xB2); // REM token
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
// String content in quotes preserved
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
|
|
213
|
+
TEST_CASE("Quoted string content is preserved verbatim", "[basic][tokenizer]") {
|
|
214
|
+
BasicMemory m;
|
|
215
|
+
int count = loadBasicProgram("10 PRINT \"AB\"", m.readMem, m.writeMem);
|
|
216
|
+
REQUIRE(count == 1);
|
|
217
|
+
|
|
218
|
+
// After PRINT token (0xBA) there should be: " A B "
|
|
219
|
+
// Find the quote in the token stream
|
|
220
|
+
uint16_t nextAddr = m.read16(0x0801);
|
|
221
|
+
bool foundQuotedAB = false;
|
|
222
|
+
for (uint16_t addr = 0x0805; addr + 3 < nextAddr; addr++) {
|
|
223
|
+
if (m.mem[addr] == '"' && m.mem[addr + 1] == 'A' &&
|
|
224
|
+
m.mem[addr + 2] == 'B' && m.mem[addr + 3] == '"') {
|
|
225
|
+
foundQuotedAB = true;
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
CHECK(foundQuotedAB);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
// Zero-page pointers are set correctly
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
|
|
236
|
+
TEST_CASE("TXTTAB pointer is set to $0801", "[basic][tokenizer][zeropage]") {
|
|
237
|
+
BasicMemory m;
|
|
238
|
+
loadBasicProgram("10 PRINT", m.readMem, m.writeMem);
|
|
239
|
+
|
|
240
|
+
CHECK(m.read16(0x67) == 0x0801);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
TEST_CASE("VARTAB and ARYTAB point past end of program", "[basic][tokenizer][zeropage]") {
|
|
244
|
+
BasicMemory m;
|
|
245
|
+
int count = loadBasicProgram("10 PRINT \"HI\"", m.readMem, m.writeMem);
|
|
246
|
+
REQUIRE(count == 1);
|
|
247
|
+
|
|
248
|
+
uint16_t vartab = m.read16(0x69); // VARTAB
|
|
249
|
+
uint16_t arytab = m.read16(0x6B); // ARYTAB
|
|
250
|
+
CHECK(vartab > 0x0801);
|
|
251
|
+
CHECK(arytab == vartab);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ---------------------------------------------------------------------------
|
|
255
|
+
// Round-trip: tokenize then detokenize recovers keywords
|
|
256
|
+
// ---------------------------------------------------------------------------
|
|
257
|
+
|
|
258
|
+
TEST_CASE("Round-trip tokenize/detokenize recovers PRINT keyword", "[basic][roundtrip]") {
|
|
259
|
+
BasicMemory m;
|
|
260
|
+
int count = loadBasicProgram("10 PRINT \"HELLO\"", m.readMem, m.writeMem);
|
|
261
|
+
REQUIRE(count == 1);
|
|
262
|
+
|
|
263
|
+
// Build a data buffer from memory starting at $0801
|
|
264
|
+
// Find program end (double zero)
|
|
265
|
+
uint16_t addr = 0x0801;
|
|
266
|
+
while (addr < 0x9600) {
|
|
267
|
+
uint16_t nextAddr = m.read16(addr);
|
|
268
|
+
if (nextAddr == 0x0000) break;
|
|
269
|
+
addr = nextAddr;
|
|
270
|
+
}
|
|
271
|
+
uint16_t progEnd = addr + 2; // Include the final 0x00, 0x00
|
|
272
|
+
|
|
273
|
+
int dataSize = progEnd - 0x0801;
|
|
274
|
+
REQUIRE(dataSize > 0);
|
|
275
|
+
REQUIRE(dataSize < 65536);
|
|
276
|
+
|
|
277
|
+
const char* listing = BasicDetokenizer::detokenizeApplesoft(
|
|
278
|
+
&m.mem[0x0801], dataSize, false
|
|
279
|
+
);
|
|
280
|
+
REQUIRE(listing != nullptr);
|
|
281
|
+
|
|
282
|
+
std::string result(listing);
|
|
283
|
+
// The listing should contain "PRINT" and "HELLO"
|
|
284
|
+
CHECK(result.find("PRINT") != std::string::npos);
|
|
285
|
+
CHECK(result.find("HELLO") != std::string::npos);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
TEST_CASE("Round-trip tokenize/detokenize recovers GOTO keyword", "[basic][roundtrip]") {
|
|
289
|
+
BasicMemory m;
|
|
290
|
+
int count = loadBasicProgram("10 GOTO 100", m.readMem, m.writeMem);
|
|
291
|
+
REQUIRE(count == 1);
|
|
292
|
+
|
|
293
|
+
uint16_t addr = 0x0801;
|
|
294
|
+
while (addr < 0x9600) {
|
|
295
|
+
uint16_t nextAddr = m.read16(addr);
|
|
296
|
+
if (nextAddr == 0x0000) break;
|
|
297
|
+
addr = nextAddr;
|
|
298
|
+
}
|
|
299
|
+
uint16_t progEnd = addr + 2;
|
|
300
|
+
int dataSize = progEnd - 0x0801;
|
|
301
|
+
|
|
302
|
+
const char* listing = BasicDetokenizer::detokenizeApplesoft(
|
|
303
|
+
&m.mem[0x0801], dataSize, false
|
|
304
|
+
);
|
|
305
|
+
REQUIRE(listing != nullptr);
|
|
306
|
+
|
|
307
|
+
std::string result(listing);
|
|
308
|
+
CHECK(result.find("GOTO") != std::string::npos);
|
|
309
|
+
CHECK(result.find("100") != std::string::npos);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
TEST_CASE("Round-trip multi-line program", "[basic][roundtrip]") {
|
|
313
|
+
BasicMemory m;
|
|
314
|
+
int count = loadBasicProgram(
|
|
315
|
+
"10 HOME\n"
|
|
316
|
+
"20 PRINT \"HELLO\"\n"
|
|
317
|
+
"30 GOTO 20",
|
|
318
|
+
m.readMem, m.writeMem
|
|
319
|
+
);
|
|
320
|
+
REQUIRE(count == 3);
|
|
321
|
+
|
|
322
|
+
uint16_t addr = 0x0801;
|
|
323
|
+
while (addr < 0x9600) {
|
|
324
|
+
uint16_t nextAddr = m.read16(addr);
|
|
325
|
+
if (nextAddr == 0x0000) break;
|
|
326
|
+
addr = nextAddr;
|
|
327
|
+
}
|
|
328
|
+
uint16_t progEnd = addr + 2;
|
|
329
|
+
int dataSize = progEnd - 0x0801;
|
|
330
|
+
|
|
331
|
+
const char* listing = BasicDetokenizer::detokenizeApplesoft(
|
|
332
|
+
&m.mem[0x0801], dataSize, false
|
|
333
|
+
);
|
|
334
|
+
REQUIRE(listing != nullptr);
|
|
335
|
+
|
|
336
|
+
std::string result(listing);
|
|
337
|
+
CHECK(result.find("HOME") != std::string::npos);
|
|
338
|
+
CHECK(result.find("PRINT") != std::string::npos);
|
|
339
|
+
CHECK(result.find("GOTO") != std::string::npos);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// ---------------------------------------------------------------------------
|
|
343
|
+
// Question mark shorthand for PRINT
|
|
344
|
+
// ---------------------------------------------------------------------------
|
|
345
|
+
|
|
346
|
+
TEST_CASE("Question mark is tokenized as PRINT", "[basic][tokenizer]") {
|
|
347
|
+
BasicMemory m;
|
|
348
|
+
int count = loadBasicProgram("10 ?\"HI\"", m.readMem, m.writeMem);
|
|
349
|
+
REQUIRE(count == 1);
|
|
350
|
+
|
|
351
|
+
// Should find PRINT token (0xBA)
|
|
352
|
+
CHECK(m.mem[0x0805] == 0xBA);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// ---------------------------------------------------------------------------
|
|
356
|
+
// Lines are sorted by line number
|
|
357
|
+
// ---------------------------------------------------------------------------
|
|
358
|
+
|
|
359
|
+
TEST_CASE("Lines provided out of order are sorted by line number", "[basic][tokenizer]") {
|
|
360
|
+
BasicMemory m;
|
|
361
|
+
int count = loadBasicProgram(
|
|
362
|
+
"30 END\n"
|
|
363
|
+
"10 PRINT\n"
|
|
364
|
+
"20 GOTO 10",
|
|
365
|
+
m.readMem, m.writeMem
|
|
366
|
+
);
|
|
367
|
+
REQUIRE(count == 3);
|
|
368
|
+
|
|
369
|
+
// Line 1 at $0801 should be line 10
|
|
370
|
+
uint16_t line1Num = m.read16(0x0803);
|
|
371
|
+
CHECK(line1Num == 10);
|
|
372
|
+
|
|
373
|
+
// Line 2 should be line 20
|
|
374
|
+
uint16_t line1Next = m.read16(0x0801);
|
|
375
|
+
uint16_t line2Num = m.read16(line1Next + 2);
|
|
376
|
+
CHECK(line2Num == 20);
|
|
377
|
+
|
|
378
|
+
// Line 3 should be line 30
|
|
379
|
+
uint16_t line2Next = m.read16(line1Next);
|
|
380
|
+
uint16_t line3Num = m.read16(line2Next + 2);
|
|
381
|
+
CHECK(line3Num == 30);
|
|
382
|
+
}
|