web-a2e 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (295) hide show
  1. package/.clangd +5 -0
  2. package/.mcp.json +12 -0
  3. package/CLAUDE.md +362 -0
  4. package/CMakeLists.txt +774 -0
  5. package/LICENSE +21 -0
  6. package/README.md +392 -0
  7. package/build-wasm/generated/roms.cpp +2447 -0
  8. package/docker-compose.staging.yml +9 -0
  9. package/docs/basic-rom-disassembly.md +6663 -0
  10. package/docs/softswitch-comparison.md +273 -0
  11. package/docs/thunderclock-debug.md +89 -0
  12. package/examples/cube.bas +72 -0
  13. package/examples/hello.s +55 -0
  14. package/examples/scroll.s +140 -0
  15. package/package.json +18 -0
  16. package/public/assets/apple-logo-old.png +0 -0
  17. package/public/assets/apple-logo.png +0 -0
  18. package/public/assets/drive-closed-light-on.png +0 -0
  19. package/public/assets/drive-closed.png +0 -0
  20. package/public/assets/drive-open-light-on.png +0 -0
  21. package/public/assets/drive-open.png +0 -0
  22. package/public/audio-worklet.js +82 -0
  23. package/public/disks/Apple DOS 3.3 January 1983.dsk +0 -0
  24. package/public/disks/ProDOS 2.4.3.po +0 -0
  25. package/public/disks/h32mb.2mg +0 -0
  26. package/public/disks/library.json +26 -0
  27. package/public/docs/llms/llm-assembler.txt +90 -0
  28. package/public/docs/llms/llm-basic-program.txt +256 -0
  29. package/public/docs/llms/llm-disk-drives.txt +72 -0
  30. package/public/docs/llms/llm-file-explorer.txt +50 -0
  31. package/public/docs/llms/llm-hard-drives.txt +80 -0
  32. package/public/docs/llms/llm-main.txt +51 -0
  33. package/public/docs/llms/llm-slot-configuration.txt +66 -0
  34. package/public/icons/icon-192.svg +4 -0
  35. package/public/icons/icon-512.svg +4 -0
  36. package/public/index.html +661 -0
  37. package/public/llms.txt +49 -0
  38. package/public/manifest.json +29 -0
  39. package/public/shaders/burnin.glsl +22 -0
  40. package/public/shaders/crt.glsl +706 -0
  41. package/public/shaders/edge.glsl +109 -0
  42. package/public/shaders/vertex.glsl +8 -0
  43. package/public/sw.js +186 -0
  44. package/roms/341-0027.bin +0 -0
  45. package/roms/341-0160-A-US-UK.bin +0 -0
  46. package/roms/341-0160-A.bin +0 -0
  47. package/roms/342-0273-A-US-UK.bin +0 -0
  48. package/roms/342-0349-B-C0-FF.bin +0 -0
  49. package/roms/Apple Mouse Interface Card ROM - 342-0270-C.bin +0 -0
  50. package/roms/Thunderclock Plus ROM.bin +0 -0
  51. package/scripts/generate_roms.sh +69 -0
  52. package/src/bindings/wasm_interface.cpp +1940 -0
  53. package/src/core/assembler/assembler.cpp +1239 -0
  54. package/src/core/assembler/assembler.hpp +115 -0
  55. package/src/core/audio/audio.cpp +160 -0
  56. package/src/core/audio/audio.hpp +81 -0
  57. package/src/core/basic/basic_detokenizer.cpp +436 -0
  58. package/src/core/basic/basic_detokenizer.hpp +41 -0
  59. package/src/core/basic/basic_tokenizer.cpp +286 -0
  60. package/src/core/basic/basic_tokenizer.hpp +26 -0
  61. package/src/core/basic/basic_tokens.hpp +295 -0
  62. package/src/core/cards/disk2_card.cpp +568 -0
  63. package/src/core/cards/disk2_card.hpp +316 -0
  64. package/src/core/cards/expansion_card.hpp +185 -0
  65. package/src/core/cards/mockingboard/ay8910.cpp +616 -0
  66. package/src/core/cards/mockingboard/ay8910.hpp +159 -0
  67. package/src/core/cards/mockingboard/via6522.cpp +530 -0
  68. package/src/core/cards/mockingboard/via6522.hpp +163 -0
  69. package/src/core/cards/mockingboard_card.cpp +312 -0
  70. package/src/core/cards/mockingboard_card.hpp +159 -0
  71. package/src/core/cards/mouse_card.cpp +654 -0
  72. package/src/core/cards/mouse_card.hpp +190 -0
  73. package/src/core/cards/smartport/block_device.cpp +202 -0
  74. package/src/core/cards/smartport/block_device.hpp +60 -0
  75. package/src/core/cards/smartport/smartport_card.cpp +603 -0
  76. package/src/core/cards/smartport/smartport_card.hpp +120 -0
  77. package/src/core/cards/thunderclock_card.cpp +237 -0
  78. package/src/core/cards/thunderclock_card.hpp +122 -0
  79. package/src/core/cpu/cpu6502.cpp +1609 -0
  80. package/src/core/cpu/cpu6502.hpp +203 -0
  81. package/src/core/debug/condition_evaluator.cpp +470 -0
  82. package/src/core/debug/condition_evaluator.hpp +87 -0
  83. package/src/core/disassembler/disassembler.cpp +552 -0
  84. package/src/core/disassembler/disassembler.hpp +171 -0
  85. package/src/core/disk-image/disk_image.hpp +267 -0
  86. package/src/core/disk-image/dsk_disk_image.cpp +827 -0
  87. package/src/core/disk-image/dsk_disk_image.hpp +204 -0
  88. package/src/core/disk-image/gcr_encoding.cpp +147 -0
  89. package/src/core/disk-image/gcr_encoding.hpp +78 -0
  90. package/src/core/disk-image/woz_disk_image.cpp +1049 -0
  91. package/src/core/disk-image/woz_disk_image.hpp +343 -0
  92. package/src/core/emulator.cpp +2126 -0
  93. package/src/core/emulator.hpp +434 -0
  94. package/src/core/filesystem/dos33.cpp +178 -0
  95. package/src/core/filesystem/dos33.hpp +66 -0
  96. package/src/core/filesystem/pascal.cpp +262 -0
  97. package/src/core/filesystem/pascal.hpp +87 -0
  98. package/src/core/filesystem/prodos.cpp +369 -0
  99. package/src/core/filesystem/prodos.hpp +119 -0
  100. package/src/core/input/keyboard.cpp +227 -0
  101. package/src/core/input/keyboard.hpp +111 -0
  102. package/src/core/mmu/mmu.cpp +1387 -0
  103. package/src/core/mmu/mmu.hpp +236 -0
  104. package/src/core/types.hpp +196 -0
  105. package/src/core/video/video.cpp +680 -0
  106. package/src/core/video/video.hpp +156 -0
  107. package/src/css/assembler-editor.css +1617 -0
  108. package/src/css/base.css +470 -0
  109. package/src/css/basic-debugger.css +791 -0
  110. package/src/css/basic-editor.css +792 -0
  111. package/src/css/controls.css +783 -0
  112. package/src/css/cpu-debugger.css +1413 -0
  113. package/src/css/debug-base.css +160 -0
  114. package/src/css/debug-windows.css +6455 -0
  115. package/src/css/disk-drives.css +406 -0
  116. package/src/css/documentation.css +392 -0
  117. package/src/css/file-explorer.css +867 -0
  118. package/src/css/hard-drive.css +180 -0
  119. package/src/css/layout.css +217 -0
  120. package/src/css/memory-windows.css +798 -0
  121. package/src/css/modals.css +510 -0
  122. package/src/css/monitor.css +425 -0
  123. package/src/css/release-notes.css +101 -0
  124. package/src/css/responsive.css +400 -0
  125. package/src/css/rule-builder.css +340 -0
  126. package/src/css/save-states.css +201 -0
  127. package/src/css/settings-windows.css +1231 -0
  128. package/src/css/window-switcher.css +150 -0
  129. package/src/js/agent/agent-manager.js +643 -0
  130. package/src/js/agent/agent-tools.js +293 -0
  131. package/src/js/agent/agent-version-tools.js +131 -0
  132. package/src/js/agent/assembler-tools.js +357 -0
  133. package/src/js/agent/basic-program-tools.js +894 -0
  134. package/src/js/agent/disk-tools.js +417 -0
  135. package/src/js/agent/file-explorer-tools.js +269 -0
  136. package/src/js/agent/index.js +13 -0
  137. package/src/js/agent/main-tools.js +222 -0
  138. package/src/js/agent/slot-tools.js +303 -0
  139. package/src/js/agent/smartport-tools.js +257 -0
  140. package/src/js/agent/window-tools.js +80 -0
  141. package/src/js/audio/audio-driver.js +417 -0
  142. package/src/js/audio/audio-worklet.js +85 -0
  143. package/src/js/audio/index.js +8 -0
  144. package/src/js/config/default-layout.js +34 -0
  145. package/src/js/config/version.js +8 -0
  146. package/src/js/data/apple2-rom-routines.js +577 -0
  147. package/src/js/debug/assembler-editor-window.js +2993 -0
  148. package/src/js/debug/basic-breakpoint-manager.js +529 -0
  149. package/src/js/debug/basic-program-parser.js +436 -0
  150. package/src/js/debug/basic-program-window.js +2594 -0
  151. package/src/js/debug/basic-variable-inspector.js +447 -0
  152. package/src/js/debug/breakpoint-manager.js +472 -0
  153. package/src/js/debug/cpu-debugger-window.js +2396 -0
  154. package/src/js/debug/index.js +22 -0
  155. package/src/js/debug/label-manager.js +238 -0
  156. package/src/js/debug/memory-browser-window.js +416 -0
  157. package/src/js/debug/memory-heat-map-window.js +481 -0
  158. package/src/js/debug/memory-map-window.js +206 -0
  159. package/src/js/debug/mockingboard-window.js +882 -0
  160. package/src/js/debug/mouse-card-window.js +355 -0
  161. package/src/js/debug/rule-builder-window.js +648 -0
  162. package/src/js/debug/soft-switch-window.js +458 -0
  163. package/src/js/debug/stack-viewer-window.js +221 -0
  164. package/src/js/debug/symbols.js +416 -0
  165. package/src/js/debug/trace-panel.js +291 -0
  166. package/src/js/debug/zero-page-watch-window.js +297 -0
  167. package/src/js/disk-manager/disk-drives-window.js +212 -0
  168. package/src/js/disk-manager/disk-operations.js +284 -0
  169. package/src/js/disk-manager/disk-persistence.js +301 -0
  170. package/src/js/disk-manager/disk-surface-renderer.js +388 -0
  171. package/src/js/disk-manager/drive-sounds.js +139 -0
  172. package/src/js/disk-manager/hard-drive-manager.js +481 -0
  173. package/src/js/disk-manager/hard-drive-persistence.js +187 -0
  174. package/src/js/disk-manager/hard-drive-window.js +57 -0
  175. package/src/js/disk-manager/index.js +890 -0
  176. package/src/js/display/display-settings-window.js +383 -0
  177. package/src/js/display/index.js +10 -0
  178. package/src/js/display/screen-window.js +342 -0
  179. package/src/js/display/webgl-renderer.js +705 -0
  180. package/src/js/file-explorer/disassembler.js +574 -0
  181. package/src/js/file-explorer/dos33.js +266 -0
  182. package/src/js/file-explorer/file-viewer.js +359 -0
  183. package/src/js/file-explorer/index.js +1261 -0
  184. package/src/js/file-explorer/prodos.js +549 -0
  185. package/src/js/file-explorer/utils.js +67 -0
  186. package/src/js/help/documentation-window.js +1096 -0
  187. package/src/js/help/index.js +10 -0
  188. package/src/js/help/release-notes-window.js +85 -0
  189. package/src/js/help/release-notes.js +612 -0
  190. package/src/js/input/gamepad-handler.js +176 -0
  191. package/src/js/input/index.js +12 -0
  192. package/src/js/input/input-handler.js +396 -0
  193. package/src/js/input/joystick-window.js +404 -0
  194. package/src/js/input/mouse-handler.js +99 -0
  195. package/src/js/input/text-selection.js +462 -0
  196. package/src/js/main.js +653 -0
  197. package/src/js/state/index.js +15 -0
  198. package/src/js/state/save-states-window.js +393 -0
  199. package/src/js/state/state-manager.js +409 -0
  200. package/src/js/state/state-persistence.js +218 -0
  201. package/src/js/ui/confirm.js +43 -0
  202. package/src/js/ui/disk-drive-positioner.js +347 -0
  203. package/src/js/ui/reminder-controller.js +129 -0
  204. package/src/js/ui/slot-configuration-window.js +560 -0
  205. package/src/js/ui/theme-manager.js +61 -0
  206. package/src/js/ui/toast.js +44 -0
  207. package/src/js/ui/ui-controller.js +897 -0
  208. package/src/js/ui/window-switcher.js +275 -0
  209. package/src/js/utils/basic-autocomplete.js +832 -0
  210. package/src/js/utils/basic-highlighting.js +473 -0
  211. package/src/js/utils/basic-tokenizer.js +153 -0
  212. package/src/js/utils/basic-tokens.js +117 -0
  213. package/src/js/utils/constants.js +28 -0
  214. package/src/js/utils/indexeddb-helper.js +225 -0
  215. package/src/js/utils/merlin-editor-support.js +905 -0
  216. package/src/js/utils/merlin-highlighting.js +551 -0
  217. package/src/js/utils/storage.js +125 -0
  218. package/src/js/utils/string-utils.js +19 -0
  219. package/src/js/utils/wasm-memory.js +54 -0
  220. package/src/js/windows/base-window.js +690 -0
  221. package/src/js/windows/index.js +9 -0
  222. package/src/js/windows/window-manager.js +375 -0
  223. package/tests/catch2/catch.hpp +17976 -0
  224. package/tests/common/basic_program_builder.cpp +119 -0
  225. package/tests/common/basic_program_builder.hpp +209 -0
  226. package/tests/common/disk_image_builder.cpp +444 -0
  227. package/tests/common/disk_image_builder.hpp +141 -0
  228. package/tests/common/test_helpers.hpp +118 -0
  229. package/tests/gcr/gcr-test.cpp +142 -0
  230. package/tests/integration/check-rom.js +70 -0
  231. package/tests/integration/compare-boot.js +239 -0
  232. package/tests/integration/crash-trace.js +102 -0
  233. package/tests/integration/disk-boot-test.js +264 -0
  234. package/tests/integration/memory-crash.js +108 -0
  235. package/tests/integration/nibble-read-test.js +249 -0
  236. package/tests/integration/phase-test.js +159 -0
  237. package/tests/integration/test_emulator.cpp +291 -0
  238. package/tests/integration/test_emulator_basic.cpp +91 -0
  239. package/tests/integration/test_emulator_debug.cpp +344 -0
  240. package/tests/integration/test_emulator_disk.cpp +153 -0
  241. package/tests/integration/test_emulator_state.cpp +163 -0
  242. package/tests/klaus/6502_functional_test.bin +0 -0
  243. package/tests/klaus/65C02_extended_opcodes_test.bin +0 -0
  244. package/tests/klaus/klaus_6502_test.cpp +184 -0
  245. package/tests/klaus/klaus_65c02_test.cpp +197 -0
  246. package/tests/thunderclock/thunderclock_mmu_test.cpp +304 -0
  247. package/tests/thunderclock/thunderclock_test.cpp +550 -0
  248. package/tests/unit/test_assembler.cpp +521 -0
  249. package/tests/unit/test_audio.cpp +196 -0
  250. package/tests/unit/test_ay8910.cpp +311 -0
  251. package/tests/unit/test_basic_detokenizer.cpp +265 -0
  252. package/tests/unit/test_basic_tokenizer.cpp +382 -0
  253. package/tests/unit/test_block_device.cpp +259 -0
  254. package/tests/unit/test_condition_evaluator.cpp +219 -0
  255. package/tests/unit/test_cpu6502.cpp +1301 -0
  256. package/tests/unit/test_cpu_addressing.cpp +361 -0
  257. package/tests/unit/test_cpu_cycle_counts.cpp +409 -0
  258. package/tests/unit/test_cpu_decimal.cpp +166 -0
  259. package/tests/unit/test_cpu_interrupts.cpp +285 -0
  260. package/tests/unit/test_disassembler.cpp +323 -0
  261. package/tests/unit/test_disk2_card.cpp +330 -0
  262. package/tests/unit/test_dos33.cpp +273 -0
  263. package/tests/unit/test_dsk_disk_image.cpp +315 -0
  264. package/tests/unit/test_expansion_card.cpp +178 -0
  265. package/tests/unit/test_gcr_encoding.cpp +232 -0
  266. package/tests/unit/test_keyboard.cpp +262 -0
  267. package/tests/unit/test_mmu.cpp +555 -0
  268. package/tests/unit/test_mmu_slots.cpp +323 -0
  269. package/tests/unit/test_mockingboard.cpp +352 -0
  270. package/tests/unit/test_mouse_card.cpp +386 -0
  271. package/tests/unit/test_pascal.cpp +248 -0
  272. package/tests/unit/test_prodos.cpp +259 -0
  273. package/tests/unit/test_smartport_card.cpp +321 -0
  274. package/tests/unit/test_thunderclock.cpp +354 -0
  275. package/tests/unit/test_via6522.cpp +323 -0
  276. package/tests/unit/test_video.cpp +319 -0
  277. package/tests/unit/test_woz_disk_image.cpp +257 -0
  278. package/vite.config.js +96 -0
  279. package/wiki/AI-Agent.md +372 -0
  280. package/wiki/Architecture-Overview.md +303 -0
  281. package/wiki/Audio-System.md +449 -0
  282. package/wiki/CPU-Emulation.md +477 -0
  283. package/wiki/Debugger.md +516 -0
  284. package/wiki/Disk-Drives.md +161 -0
  285. package/wiki/Disk-System-Internals.md +547 -0
  286. package/wiki/Display-Settings.md +88 -0
  287. package/wiki/Expansion-Slots.md +187 -0
  288. package/wiki/File-Explorer.md +259 -0
  289. package/wiki/Getting-Started.md +156 -0
  290. package/wiki/Home.md +69 -0
  291. package/wiki/Input-Devices.md +183 -0
  292. package/wiki/Keyboard-Shortcuts.md +158 -0
  293. package/wiki/Memory-System.md +364 -0
  294. package/wiki/Save-States.md +172 -0
  295. package/wiki/Video-Rendering.md +658 -0
@@ -0,0 +1,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