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,436 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* basic_detokenizer.cpp - Applesoft and Integer BASIC program detokenizer
|
|
3
|
+
*
|
|
4
|
+
* Written by
|
|
5
|
+
* Mike Daley <michael_daley@icloud.com>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#include "basic_detokenizer.hpp"
|
|
9
|
+
#include "basic_tokens.hpp"
|
|
10
|
+
#include <cstdio>
|
|
11
|
+
#include <cstring>
|
|
12
|
+
|
|
13
|
+
namespace a2e {
|
|
14
|
+
|
|
15
|
+
char BasicDetokenizer::outputBuffer_[MAX_OUTPUT];
|
|
16
|
+
int BasicDetokenizer::outputLen_ = 0;
|
|
17
|
+
|
|
18
|
+
void BasicDetokenizer::appendChar(char c) {
|
|
19
|
+
if (outputLen_ < MAX_OUTPUT - 1) {
|
|
20
|
+
outputBuffer_[outputLen_++] = c;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
void BasicDetokenizer::appendStr(const char* s) {
|
|
25
|
+
while (*s && outputLen_ < MAX_OUTPUT - 1) {
|
|
26
|
+
outputBuffer_[outputLen_++] = *s++;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
void BasicDetokenizer::appendInt(int n) {
|
|
31
|
+
char buf[12];
|
|
32
|
+
snprintf(buf, sizeof(buf), "%d", n);
|
|
33
|
+
appendStr(buf);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
void BasicDetokenizer::appendPaddedLineNum(int n) {
|
|
37
|
+
char buf[8];
|
|
38
|
+
snprintf(buf, sizeof(buf), "%5d", n);
|
|
39
|
+
appendStr(buf);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const char* BasicDetokenizer::detokenizeApplesoft(const uint8_t* data, int size,
|
|
43
|
+
bool hasLengthHeader) {
|
|
44
|
+
outputLen_ = 0;
|
|
45
|
+
|
|
46
|
+
// DOS 3.3 files have a 2-byte file length header, ProDOS files do not
|
|
47
|
+
int offset = hasLengthHeader ? 2 : 0;
|
|
48
|
+
int prevLineNum = -1;
|
|
49
|
+
bool firstLine = true;
|
|
50
|
+
|
|
51
|
+
// Track indentation
|
|
52
|
+
int indentLevel = 0;
|
|
53
|
+
|
|
54
|
+
// Collect lines first, then format with indentation
|
|
55
|
+
struct LineInfo {
|
|
56
|
+
int lineNum;
|
|
57
|
+
int contentStart; // offset into outputBuffer_
|
|
58
|
+
int contentLen;
|
|
59
|
+
int indent;
|
|
60
|
+
};
|
|
61
|
+
LineInfo lines[4096];
|
|
62
|
+
int lineCount = 0;
|
|
63
|
+
|
|
64
|
+
// Temporary buffer for line content
|
|
65
|
+
char lineBuf[8192];
|
|
66
|
+
int lineBufLen = 0;
|
|
67
|
+
|
|
68
|
+
auto lineAppendChar = [&](char c) {
|
|
69
|
+
if (lineBufLen < (int)sizeof(lineBuf) - 1)
|
|
70
|
+
lineBuf[lineBufLen++] = c;
|
|
71
|
+
};
|
|
72
|
+
auto lineAppendStr = [&](const char* s) {
|
|
73
|
+
while (*s && lineBufLen < (int)sizeof(lineBuf) - 1)
|
|
74
|
+
lineBuf[lineBufLen++] = *s++;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
while (offset < size - 4) {
|
|
78
|
+
// Read next line pointer (2 bytes)
|
|
79
|
+
int nextLine = data[offset] | (data[offset + 1] << 8);
|
|
80
|
+
if (nextLine == 0) break;
|
|
81
|
+
|
|
82
|
+
// Read line number (2 bytes)
|
|
83
|
+
int lineNum = data[offset + 2] | (data[offset + 3] << 8);
|
|
84
|
+
|
|
85
|
+
// Sanity checks
|
|
86
|
+
if (lineNum > 63999) break;
|
|
87
|
+
if (lineNum <= prevLineNum && prevLineNum >= 0) break;
|
|
88
|
+
prevLineNum = lineNum;
|
|
89
|
+
|
|
90
|
+
offset += 4;
|
|
91
|
+
|
|
92
|
+
// Track keywords for indentation
|
|
93
|
+
int forCount = 0;
|
|
94
|
+
int nextCount = 0;
|
|
95
|
+
|
|
96
|
+
lineBufLen = 0;
|
|
97
|
+
bool inString = false;
|
|
98
|
+
bool inRem = false;
|
|
99
|
+
bool inData = false;
|
|
100
|
+
const char* lastType = "start";
|
|
101
|
+
|
|
102
|
+
while (offset < size && data[offset] != 0x00) {
|
|
103
|
+
uint8_t byte = data[offset++];
|
|
104
|
+
|
|
105
|
+
if (inRem) {
|
|
106
|
+
char ch = byte & 0x7F;
|
|
107
|
+
if (ch >= 0x20 && ch < 0x7F) {
|
|
108
|
+
lineAppendChar(ch);
|
|
109
|
+
}
|
|
110
|
+
} else if (inString) {
|
|
111
|
+
if (byte == 0x22) {
|
|
112
|
+
lineAppendChar('"');
|
|
113
|
+
inString = false;
|
|
114
|
+
lastType = "string";
|
|
115
|
+
} else {
|
|
116
|
+
char ch = byte & 0x7F;
|
|
117
|
+
if (ch >= 0x20 && ch < 0x7F) {
|
|
118
|
+
lineAppendChar(ch);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
} else if (inData) {
|
|
122
|
+
if (byte == 0x3A) {
|
|
123
|
+
lineAppendStr(" : ");
|
|
124
|
+
inData = false;
|
|
125
|
+
lastType = "punct";
|
|
126
|
+
} else {
|
|
127
|
+
char ch = byte & 0x7F;
|
|
128
|
+
if (ch >= 0x20 && ch < 0x7F) {
|
|
129
|
+
lineAppendChar(ch);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
} else if (byte >= 0x80) {
|
|
133
|
+
int tokenIdx = byte - 0x80;
|
|
134
|
+
if (tokenIdx >= APPLESOFT_TOKEN_COUNT) continue;
|
|
135
|
+
const char* token = APPLESOFT_TOKENS[tokenIdx];
|
|
136
|
+
if (!token) continue;
|
|
137
|
+
|
|
138
|
+
// Track FOR/NEXT
|
|
139
|
+
if (strcmp(token, "FOR") == 0) forCount++;
|
|
140
|
+
if (strcmp(token, "NEXT") == 0) nextCount++;
|
|
141
|
+
|
|
142
|
+
// Add space before keyword if needed
|
|
143
|
+
if (needsSpaceBefore(token) &&
|
|
144
|
+
strcmp(lastType, "start") != 0 &&
|
|
145
|
+
strcmp(lastType, "punct") != 0) {
|
|
146
|
+
lineAppendChar(' ');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (strcmp(token, "REM") == 0) {
|
|
150
|
+
lineAppendStr(token);
|
|
151
|
+
inRem = true;
|
|
152
|
+
lastType = "keyword";
|
|
153
|
+
} else if (strcmp(token, "DATA") == 0) {
|
|
154
|
+
lineAppendStr(token);
|
|
155
|
+
inData = true;
|
|
156
|
+
lastType = "keyword";
|
|
157
|
+
} else if (strlen(token) == 1 && strchr("+-*/^=<>", token[0])) {
|
|
158
|
+
// Operator tokens
|
|
159
|
+
lineAppendChar(' ');
|
|
160
|
+
lineAppendStr(token);
|
|
161
|
+
lineAppendChar(' ');
|
|
162
|
+
lastType = "operator";
|
|
163
|
+
} else {
|
|
164
|
+
lineAppendStr(token);
|
|
165
|
+
lastType = "keyword";
|
|
166
|
+
|
|
167
|
+
if (needsSpaceAfter(token)) {
|
|
168
|
+
lineAppendChar(' ');
|
|
169
|
+
lastType = "space";
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
} else if (byte == 0x22) {
|
|
173
|
+
lineAppendChar('"');
|
|
174
|
+
inString = true;
|
|
175
|
+
} else if (byte == 0x3A) {
|
|
176
|
+
lineAppendStr(" : ");
|
|
177
|
+
lastType = "punct";
|
|
178
|
+
} else if (byte >= 0x30 && byte <= 0x39) {
|
|
179
|
+
// Number
|
|
180
|
+
lineAppendChar((char)byte);
|
|
181
|
+
while (offset < size && data[offset] != 0x00 &&
|
|
182
|
+
data[offset] >= 0x30 && data[offset] <= 0x39) {
|
|
183
|
+
lineAppendChar((char)data[offset++]);
|
|
184
|
+
}
|
|
185
|
+
// Decimal point
|
|
186
|
+
if (offset < size && data[offset] == 0x2E) {
|
|
187
|
+
lineAppendChar('.');
|
|
188
|
+
offset++;
|
|
189
|
+
while (offset < size && data[offset] != 0x00 &&
|
|
190
|
+
data[offset] >= 0x30 && data[offset] <= 0x39) {
|
|
191
|
+
lineAppendChar((char)data[offset++]);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
lastType = "number";
|
|
195
|
+
} else if ((byte >= 0x41 && byte <= 0x5A) ||
|
|
196
|
+
(byte >= 0x61 && byte <= 0x7A)) {
|
|
197
|
+
// Variable name
|
|
198
|
+
lineAppendChar((char)byte);
|
|
199
|
+
while (offset < size && data[offset] != 0x00) {
|
|
200
|
+
uint8_t next = data[offset];
|
|
201
|
+
if ((next >= 0x41 && next <= 0x5A) ||
|
|
202
|
+
(next >= 0x61 && next <= 0x7A) ||
|
|
203
|
+
(next >= 0x30 && next <= 0x39) ||
|
|
204
|
+
next == 0x24 || next == 0x25) {
|
|
205
|
+
lineAppendChar((char)next);
|
|
206
|
+
offset++;
|
|
207
|
+
} else {
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
lastType = "variable";
|
|
212
|
+
} else if (byte == 0x20) {
|
|
213
|
+
if (strcmp(lastType, "space") != 0 &&
|
|
214
|
+
strcmp(lastType, "punct") != 0 &&
|
|
215
|
+
strcmp(lastType, "start") != 0) {
|
|
216
|
+
lineAppendChar(' ');
|
|
217
|
+
lastType = "space";
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
char ch = (char)byte;
|
|
221
|
+
if (strchr("+-*/^=<>", ch)) {
|
|
222
|
+
lineAppendChar(' ');
|
|
223
|
+
lineAppendChar(ch);
|
|
224
|
+
lineAppendChar(' ');
|
|
225
|
+
lastType = "operator";
|
|
226
|
+
} else if (strchr("(),;", ch)) {
|
|
227
|
+
lineAppendChar(ch);
|
|
228
|
+
lastType = "punct";
|
|
229
|
+
} else if (byte >= 0x20 && byte < 0x7F) {
|
|
230
|
+
lineAppendChar(ch);
|
|
231
|
+
lastType = "text";
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Flush remaining
|
|
237
|
+
if (inString) {
|
|
238
|
+
// Unterminated string - already in buffer
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (offset < size) offset++; // Skip end-of-line marker
|
|
242
|
+
|
|
243
|
+
// Strip leading whitespace
|
|
244
|
+
int contentStart = 0;
|
|
245
|
+
while (contentStart < lineBufLen && lineBuf[contentStart] == ' ')
|
|
246
|
+
contentStart++;
|
|
247
|
+
|
|
248
|
+
// Adjust indentation
|
|
249
|
+
if (nextCount > 0) {
|
|
250
|
+
indentLevel -= nextCount;
|
|
251
|
+
if (indentLevel < 0) indentLevel = 0;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (lineCount < 4096) {
|
|
255
|
+
lines[lineCount].lineNum = lineNum;
|
|
256
|
+
lines[lineCount].contentStart = contentStart;
|
|
257
|
+
lines[lineCount].contentLen = lineBufLen - contentStart;
|
|
258
|
+
lines[lineCount].indent = indentLevel;
|
|
259
|
+
|
|
260
|
+
// Now append to output buffer
|
|
261
|
+
if (!firstLine) appendChar('\n');
|
|
262
|
+
firstLine = false;
|
|
263
|
+
|
|
264
|
+
appendPaddedLineNum(lineNum);
|
|
265
|
+
appendChar(' ');
|
|
266
|
+
|
|
267
|
+
// Indent
|
|
268
|
+
int indentChars = indentLevel * 3;
|
|
269
|
+
for (int i = 0; i < indentChars && outputLen_ < MAX_OUTPUT - 1; i++)
|
|
270
|
+
appendChar(' ');
|
|
271
|
+
|
|
272
|
+
// Content
|
|
273
|
+
for (int i = contentStart; i < lineBufLen && outputLen_ < MAX_OUTPUT - 1; i++)
|
|
274
|
+
appendChar(lineBuf[i]);
|
|
275
|
+
|
|
276
|
+
lineCount++;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (forCount > 0) {
|
|
280
|
+
indentLevel += forCount;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
outputBuffer_[outputLen_] = '\0';
|
|
285
|
+
return outputBuffer_;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const char* BasicDetokenizer::detokenizeIntegerBasic(const uint8_t* data, int size,
|
|
289
|
+
bool hasLengthHeader) {
|
|
290
|
+
outputLen_ = 0;
|
|
291
|
+
|
|
292
|
+
// DOS 3.3 files have a 2-byte program length header, ProDOS files do not
|
|
293
|
+
int offset = hasLengthHeader ? 2 : 0;
|
|
294
|
+
bool firstLine = true;
|
|
295
|
+
int indentLevel = 0;
|
|
296
|
+
|
|
297
|
+
while (offset < size) {
|
|
298
|
+
int lineLength = data[offset];
|
|
299
|
+
if (lineLength == 0 || lineLength < 4 || offset + lineLength > size) break;
|
|
300
|
+
|
|
301
|
+
int lineNum = data[offset + 1] | (data[offset + 2] << 8);
|
|
302
|
+
if (lineNum > 32767) break;
|
|
303
|
+
|
|
304
|
+
int pos = offset + 3;
|
|
305
|
+
int lineEnd = offset + lineLength;
|
|
306
|
+
|
|
307
|
+
// Temporary line buffer
|
|
308
|
+
char lineBuf[4096];
|
|
309
|
+
int lineBufLen = 0;
|
|
310
|
+
|
|
311
|
+
auto lineAppendChar = [&](char c) {
|
|
312
|
+
if (lineBufLen < (int)sizeof(lineBuf) - 1)
|
|
313
|
+
lineBuf[lineBufLen++] = c;
|
|
314
|
+
};
|
|
315
|
+
auto lineAppendStr = [&](const char* s) {
|
|
316
|
+
while (*s && lineBufLen < (int)sizeof(lineBuf) - 1)
|
|
317
|
+
lineBuf[lineBufLen++] = *s++;
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
bool inRem = false;
|
|
321
|
+
bool inQuote = false;
|
|
322
|
+
int forCount = 0;
|
|
323
|
+
int nextCount = 0;
|
|
324
|
+
|
|
325
|
+
while (pos < lineEnd) {
|
|
326
|
+
uint8_t byte = data[pos++];
|
|
327
|
+
|
|
328
|
+
if (byte == 0x01) {
|
|
329
|
+
break; // End of line
|
|
330
|
+
} else if (inRem) {
|
|
331
|
+
char ch = byte >= 0x80 ? (byte & 0x7F) : byte;
|
|
332
|
+
if (ch >= 0x20 && ch < 0x7F) {
|
|
333
|
+
lineAppendChar(ch);
|
|
334
|
+
}
|
|
335
|
+
} else if (inQuote) {
|
|
336
|
+
if (byte == 0x29) {
|
|
337
|
+
// End quote token
|
|
338
|
+
lineAppendChar('"');
|
|
339
|
+
inQuote = false;
|
|
340
|
+
} else {
|
|
341
|
+
char ch = byte >= 0x80 ? (byte & 0x7F) : byte;
|
|
342
|
+
if (ch >= 0x20 && ch < 0x7F) {
|
|
343
|
+
lineAppendChar(ch);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
} else if (byte >= 0xB0 && byte <= 0xB9) {
|
|
347
|
+
// Numeric constant: $B0-$B9 followed by 2-byte integer
|
|
348
|
+
if (pos + 1 < lineEnd) {
|
|
349
|
+
int num = data[pos] | (data[pos + 1] << 8);
|
|
350
|
+
int value = num > 32767 ? num - 65536 : num;
|
|
351
|
+
char numBuf[12];
|
|
352
|
+
snprintf(numBuf, sizeof(numBuf), "%d", value);
|
|
353
|
+
lineAppendStr(numBuf);
|
|
354
|
+
pos += 2;
|
|
355
|
+
}
|
|
356
|
+
} else if (byte == 0x28) {
|
|
357
|
+
// Start quote
|
|
358
|
+
lineAppendChar('"');
|
|
359
|
+
inQuote = true;
|
|
360
|
+
} else if (byte == 0x5D) {
|
|
361
|
+
// REM token
|
|
362
|
+
lineAppendStr(" REM ");
|
|
363
|
+
inRem = true;
|
|
364
|
+
} else if (byte < INTEGER_BASIC_TOKEN_COUNT && INTEGER_BASIC_TOKENS[byte]) {
|
|
365
|
+
const char* token = INTEGER_BASIC_TOKENS[byte];
|
|
366
|
+
const char* trimmed = token;
|
|
367
|
+
// Skip leading spaces for identification
|
|
368
|
+
while (*trimmed == ' ') trimmed++;
|
|
369
|
+
|
|
370
|
+
if (strcmp(trimmed, "FOR ") == 0 || strncmp(trimmed, "FOR", 3) == 0) {
|
|
371
|
+
// Check if it's FOR (token $55)
|
|
372
|
+
if (byte == 0x55) forCount++;
|
|
373
|
+
}
|
|
374
|
+
if (byte == 0x59) nextCount++; // NEXT
|
|
375
|
+
|
|
376
|
+
lineAppendStr(token);
|
|
377
|
+
} else if (byte >= 0x80) {
|
|
378
|
+
// High-bit ASCII character (variable name)
|
|
379
|
+
char ch = byte & 0x7F;
|
|
380
|
+
lineAppendChar(ch);
|
|
381
|
+
while (pos < lineEnd) {
|
|
382
|
+
uint8_t next = data[pos];
|
|
383
|
+
if (next >= 0x80) {
|
|
384
|
+
char nc = next & 0x7F;
|
|
385
|
+
if ((nc >= 'A' && nc <= 'Z') || (nc >= 'a' && nc <= 'z') ||
|
|
386
|
+
(nc >= '0' && nc <= '9')) {
|
|
387
|
+
lineAppendChar(nc);
|
|
388
|
+
pos++;
|
|
389
|
+
} else {
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
} else {
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
} else if (byte >= 0x20 && byte < 0x80) {
|
|
397
|
+
lineAppendChar((char)byte);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Strip leading whitespace
|
|
402
|
+
int contentStart = 0;
|
|
403
|
+
while (contentStart < lineBufLen && lineBuf[contentStart] == ' ')
|
|
404
|
+
contentStart++;
|
|
405
|
+
|
|
406
|
+
// Adjust indentation
|
|
407
|
+
if (nextCount > 0) {
|
|
408
|
+
indentLevel -= nextCount;
|
|
409
|
+
if (indentLevel < 0) indentLevel = 0;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (!firstLine) appendChar('\n');
|
|
413
|
+
firstLine = false;
|
|
414
|
+
|
|
415
|
+
appendPaddedLineNum(lineNum);
|
|
416
|
+
appendChar(' ');
|
|
417
|
+
|
|
418
|
+
int indentChars = indentLevel * 2;
|
|
419
|
+
for (int i = 0; i < indentChars && outputLen_ < MAX_OUTPUT - 1; i++)
|
|
420
|
+
appendChar(' ');
|
|
421
|
+
|
|
422
|
+
for (int i = contentStart; i < lineBufLen && outputLen_ < MAX_OUTPUT - 1; i++)
|
|
423
|
+
appendChar(lineBuf[i]);
|
|
424
|
+
|
|
425
|
+
if (forCount > 0) {
|
|
426
|
+
indentLevel += forCount;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
offset += lineLength;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
outputBuffer_[outputLen_] = '\0';
|
|
433
|
+
return outputBuffer_;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
} // namespace a2e
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* basic_detokenizer.hpp - Applesoft and Integer BASIC program detokenizer
|
|
3
|
+
*
|
|
4
|
+
* Written by
|
|
5
|
+
* Mike Daley <michael_daley@icloud.com>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#include <cstdint>
|
|
11
|
+
#include <cstddef>
|
|
12
|
+
|
|
13
|
+
namespace a2e {
|
|
14
|
+
|
|
15
|
+
class BasicDetokenizer {
|
|
16
|
+
public:
|
|
17
|
+
/**
|
|
18
|
+
* Detokenize Applesoft BASIC program to plain text.
|
|
19
|
+
* Output includes line numbers and proper keyword spacing.
|
|
20
|
+
* Returns pointer to internal static buffer (valid until next call).
|
|
21
|
+
*/
|
|
22
|
+
static const char* detokenizeApplesoft(const uint8_t* data, int size, bool hasLengthHeader);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Detokenize Integer BASIC program to plain text.
|
|
26
|
+
* Returns pointer to internal static buffer (valid until next call).
|
|
27
|
+
*/
|
|
28
|
+
static const char* detokenizeIntegerBasic(const uint8_t* data, int size, bool hasLengthHeader);
|
|
29
|
+
|
|
30
|
+
private:
|
|
31
|
+
static constexpr int MAX_OUTPUT = 256 * 1024; // 256KB max output
|
|
32
|
+
static char outputBuffer_[MAX_OUTPUT];
|
|
33
|
+
static int outputLen_;
|
|
34
|
+
|
|
35
|
+
static void appendChar(char c);
|
|
36
|
+
static void appendStr(const char* s);
|
|
37
|
+
static void appendInt(int n);
|
|
38
|
+
static void appendPaddedLineNum(int n);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
} // namespace a2e
|