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,286 @@
1
+ /*
2
+ * basic_tokenizer.cpp - Applesoft BASIC tokenizer for direct memory insertion
3
+ *
4
+ * Written by
5
+ * Mike Daley <michael_daley@icloud.com>
6
+ */
7
+
8
+ #include "basic_tokenizer.hpp"
9
+ #include "basic_tokens.hpp"
10
+ #include <algorithm>
11
+ #include <cctype>
12
+ #include <cstring>
13
+ #include <string>
14
+ #include <vector>
15
+
16
+ namespace a2e {
17
+
18
+ // A keyword entry for greedy longest-match tokenization
19
+ struct KeywordEntry {
20
+ const char* keyword;
21
+ uint8_t token;
22
+ size_t length;
23
+ };
24
+
25
+ // Build sorted keyword list (longest first) for greedy matching
26
+ static std::vector<KeywordEntry> buildKeywordList() {
27
+ std::vector<KeywordEntry> list;
28
+ for (int i = 0; i < APPLESOFT_TOKEN_COUNT; i++) {
29
+ KeywordEntry e;
30
+ e.keyword = APPLESOFT_TOKENS[i];
31
+ e.token = static_cast<uint8_t>(0x80 + i);
32
+ e.length = strlen(APPLESOFT_TOKENS[i]);
33
+ list.push_back(e);
34
+ }
35
+ // Sort by length descending for greedy longest-match
36
+ std::sort(list.begin(), list.end(), [](const KeywordEntry& a, const KeywordEntry& b) {
37
+ return a.length > b.length;
38
+ });
39
+ return list;
40
+ }
41
+
42
+ // Parsed BASIC line
43
+ struct BasicLine {
44
+ int lineNumber;
45
+ std::string content; // uppercase content after line number
46
+ };
47
+
48
+ // Parse source into lines, extracting line numbers
49
+ static std::vector<BasicLine> parseSource(const char* source) {
50
+ std::vector<BasicLine> lines;
51
+ std::string src(source);
52
+
53
+ size_t pos = 0;
54
+ while (pos < src.size()) {
55
+ // Find end of line
56
+ size_t eol = src.find('\n', pos);
57
+ if (eol == std::string::npos) eol = src.size();
58
+
59
+ std::string rawLine = src.substr(pos, eol - pos);
60
+ pos = eol + 1;
61
+
62
+ // Trim leading/trailing whitespace
63
+ size_t start = rawLine.find_first_not_of(" \t\r");
64
+ if (start == std::string::npos) continue;
65
+ std::string trimmed = rawLine.substr(start);
66
+
67
+ // Extract line number
68
+ if (trimmed.empty() || !isdigit(static_cast<unsigned char>(trimmed[0]))) continue;
69
+
70
+ size_t numEnd = 0;
71
+ while (numEnd < trimmed.size() && isdigit(static_cast<unsigned char>(trimmed[numEnd]))) {
72
+ numEnd++;
73
+ }
74
+
75
+ int lineNum = std::stoi(trimmed.substr(0, numEnd));
76
+ if (lineNum < 0 || lineNum > 63999) continue;
77
+
78
+ // Get content after line number, skip leading spaces
79
+ std::string content = trimmed.substr(numEnd);
80
+ size_t contentStart = content.find_first_not_of(" \t");
81
+ if (contentStart != std::string::npos) {
82
+ content = content.substr(contentStart);
83
+ } else {
84
+ content = "";
85
+ }
86
+
87
+ // Skip lines with only a line number and no content
88
+ if (content.empty()) continue;
89
+
90
+ // Convert to uppercase, preserving case inside quoted strings
91
+ std::string upper;
92
+ upper.reserve(content.size());
93
+ bool inQuote = false;
94
+ for (char c : content) {
95
+ if (c == '"') {
96
+ inQuote = !inQuote;
97
+ upper += c;
98
+ } else if (inQuote) {
99
+ upper += c;
100
+ } else {
101
+ upper += static_cast<char>(toupper(static_cast<unsigned char>(c)));
102
+ }
103
+ }
104
+
105
+ lines.push_back({lineNum, upper});
106
+ }
107
+
108
+ // Sort by line number
109
+ std::sort(lines.begin(), lines.end(), [](const BasicLine& a, const BasicLine& b) {
110
+ return a.lineNumber < b.lineNumber;
111
+ });
112
+
113
+ return lines;
114
+ }
115
+
116
+ // Tokenize a single line's content
117
+ static std::vector<uint8_t> tokenizeLine(const std::string& text,
118
+ const std::vector<KeywordEntry>& keywords) {
119
+ std::vector<uint8_t> bytes;
120
+ size_t i = 0;
121
+ bool inRem = false;
122
+ bool inData = false;
123
+ bool inQuote = false;
124
+
125
+ while (i < text.size()) {
126
+ char ch = text[i];
127
+
128
+ // Inside a quoted string - emit as-is until closing quote
129
+ if (inQuote) {
130
+ bytes.push_back(static_cast<uint8_t>(text[i]));
131
+ if (ch == '"') inQuote = false;
132
+ i++;
133
+ continue;
134
+ }
135
+
136
+ // After REM token - emit rest of line as raw ASCII
137
+ if (inRem) {
138
+ bytes.push_back(static_cast<uint8_t>(text[i]));
139
+ i++;
140
+ continue;
141
+ }
142
+
143
+ // After DATA token - emit as-is until colon
144
+ if (inData) {
145
+ if (ch == ':') {
146
+ inData = false;
147
+ // Fall through to normal processing for the colon
148
+ } else {
149
+ bytes.push_back(static_cast<uint8_t>(text[i]));
150
+ i++;
151
+ continue;
152
+ }
153
+ }
154
+
155
+ // Opening quote
156
+ if (ch == '"') {
157
+ inQuote = true;
158
+ bytes.push_back(static_cast<uint8_t>(text[i]));
159
+ i++;
160
+ continue;
161
+ }
162
+
163
+ // ? is shorthand for PRINT
164
+ if (ch == '?') {
165
+ bytes.push_back(0xBA); // PRINT token
166
+ i++;
167
+ continue;
168
+ }
169
+
170
+ // Skip spaces outside strings (Apple II tokenizer ignores spaces)
171
+ if (ch == ' ') {
172
+ i++;
173
+ continue;
174
+ }
175
+
176
+ // Try greedy longest-match against keywords
177
+ bool matched = false;
178
+ const char* remaining = text.c_str() + i;
179
+ size_t remainingLen = text.size() - i;
180
+
181
+ for (const auto& kw : keywords) {
182
+ if (kw.length <= remainingLen &&
183
+ strncmp(remaining, kw.keyword, kw.length) == 0) {
184
+ bytes.push_back(kw.token);
185
+ i += kw.length;
186
+ matched = true;
187
+
188
+ if (kw.token == 0xB2) { // REM
189
+ inRem = true;
190
+ } else if (kw.token == 0x83) { // DATA
191
+ inData = true;
192
+ }
193
+ break;
194
+ }
195
+ }
196
+
197
+ if (!matched) {
198
+ bytes.push_back(static_cast<uint8_t>(text[i]));
199
+ i++;
200
+ }
201
+ }
202
+
203
+ return bytes;
204
+ }
205
+
206
+ int loadBasicProgram(const char* source, MemReadFn readMem, MemWriteFn writeMem) {
207
+ if (!source) return -1;
208
+
209
+ auto lines = parseSource(source);
210
+ if (lines.empty()) return 0;
211
+
212
+ auto keywords = buildKeywordList();
213
+
214
+ constexpr uint16_t txttab = 0x0801;
215
+ uint16_t addr = txttab;
216
+
217
+ // First pass: tokenize all lines and compute layout
218
+ struct TokenizedLine {
219
+ int lineNumber;
220
+ std::vector<uint8_t> tokens;
221
+ uint16_t lineSize; // 2 (next-ptr) + 2 (line-num) + tokens + 1 (terminator)
222
+ };
223
+
224
+ std::vector<TokenizedLine> tokenizedLines;
225
+ for (const auto& line : lines) {
226
+ auto tokens = tokenizeLine(line.content, keywords);
227
+ uint16_t lineSize = static_cast<uint16_t>(2 + 2 + tokens.size() + 1);
228
+ tokenizedLines.push_back({line.lineNumber, std::move(tokens), lineSize});
229
+ }
230
+
231
+ // Second pass: write into memory
232
+ for (const auto& line : tokenizedLines) {
233
+ uint16_t nextAddr = addr + line.lineSize;
234
+
235
+ // Bounds check
236
+ if (nextAddr >= 0xC000) return -1;
237
+
238
+ // Next-pointer (little-endian)
239
+ writeMem(addr, nextAddr & 0xFF);
240
+ writeMem(addr + 1, (nextAddr >> 8) & 0xFF);
241
+
242
+ // Line number (little-endian)
243
+ writeMem(addr + 2, line.lineNumber & 0xFF);
244
+ writeMem(addr + 3, (line.lineNumber >> 8) & 0xFF);
245
+
246
+ // Token bytes
247
+ for (size_t i = 0; i < line.tokens.size(); i++) {
248
+ writeMem(static_cast<uint16_t>(addr + 4 + i), line.tokens[i]);
249
+ }
250
+
251
+ // Line terminator
252
+ writeMem(static_cast<uint16_t>(addr + 4 + line.tokens.size()), 0x00);
253
+
254
+ addr = nextAddr;
255
+ }
256
+
257
+ // End-of-program marker
258
+ writeMem(addr, 0x00);
259
+ writeMem(addr + 1, 0x00);
260
+
261
+ uint16_t endAddr = addr + 2;
262
+
263
+ // Read MEMSIZE ($73/$74) from memory
264
+ uint16_t memsize = readMem(0x73) | (readMem(0x74) << 8);
265
+
266
+ // Set zero page pointers
267
+ auto writePtr = [&](uint16_t zpAddr, uint16_t value) {
268
+ writeMem(zpAddr, value & 0xFF);
269
+ writeMem(zpAddr + 1, (value >> 8) & 0xFF);
270
+ };
271
+
272
+ writePtr(0x67, txttab); // TXTTAB - start of program
273
+ writePtr(0x69, endAddr); // VARTAB - start of variable space
274
+ writePtr(0x6B, endAddr); // ARYTAB - start of array space
275
+ writePtr(0x6D, endAddr); // STREND - end of numeric storage
276
+ writePtr(0x6F, memsize); // FRETOP - end of string storage
277
+ writePtr(0xAF, endAddr); // PRGEND - end of program
278
+
279
+ // Set interpreter state for direct mode
280
+ writePtr(0xB8, txttab - 1); // TXTPTR
281
+ writeMem(0x76, 0xFF); // CURLIN+1 high byte = $FF (direct mode)
282
+
283
+ return static_cast<int>(tokenizedLines.size());
284
+ }
285
+
286
+ } // namespace a2e
@@ -0,0 +1,26 @@
1
+ /*
2
+ * basic_tokenizer.hpp - Applesoft BASIC tokenizer for direct memory insertion
3
+ *
4
+ * Written by
5
+ * Mike Daley <michael_daley@icloud.com>
6
+ */
7
+
8
+ #pragma once
9
+
10
+ #include <cstdint>
11
+ #include <cstddef>
12
+ #include <functional>
13
+
14
+ namespace a2e {
15
+
16
+ // Callback types for reading/writing emulator memory
17
+ using MemReadFn = std::function<uint8_t(uint16_t)>;
18
+ using MemWriteFn = std::function<void(uint16_t, uint8_t)>;
19
+
20
+ // Tokenize BASIC source text and load it into emulator memory.
21
+ // Writes the tokenized program at TXTTAB (0x0801), sets zero page pointers.
22
+ // Uses callbacks to read/write memory (main RAM, bypassing ALTZP).
23
+ // Returns the number of lines loaded, or -1 on error.
24
+ int loadBasicProgram(const char* source, MemReadFn readMem, MemWriteFn writeMem);
25
+
26
+ } // namespace a2e
@@ -0,0 +1,295 @@
1
+ /*
2
+ * basic_tokens.hpp - Applesoft and Integer BASIC token tables and keyword helpers
3
+ *
4
+ * Written by
5
+ * Mike Daley <michael_daley@icloud.com>
6
+ */
7
+
8
+ #pragma once
9
+
10
+ #include <cstring>
11
+
12
+ namespace a2e {
13
+
14
+ // Applesoft BASIC tokens - index = token byte - 0x80
15
+ static constexpr const char* APPLESOFT_TOKENS[] = {
16
+ "END", // $80
17
+ "FOR", // $81
18
+ "NEXT", // $82
19
+ "DATA", // $83
20
+ "INPUT", // $84
21
+ "DEL", // $85
22
+ "DIM", // $86
23
+ "READ", // $87
24
+ "GR", // $88
25
+ "TEXT", // $89
26
+ "PR#", // $8A
27
+ "IN#", // $8B
28
+ "CALL", // $8C
29
+ "PLOT", // $8D
30
+ "HLIN", // $8E
31
+ "VLIN", // $8F
32
+ "HGR2", // $90
33
+ "HGR", // $91
34
+ "HCOLOR=", // $92
35
+ "HPLOT", // $93
36
+ "DRAW", // $94
37
+ "XDRAW", // $95
38
+ "HTAB", // $96
39
+ "HOME", // $97
40
+ "ROT=", // $98
41
+ "SCALE=", // $99
42
+ "SHLOAD", // $9A
43
+ "TRACE", // $9B
44
+ "NOTRACE", // $9C
45
+ "NORMAL", // $9D
46
+ "INVERSE", // $9E
47
+ "FLASH", // $9F
48
+ "COLOR=", // $A0
49
+ "POP", // $A1
50
+ "VTAB", // $A2
51
+ "HIMEM:", // $A3
52
+ "LOMEM:", // $A4
53
+ "ONERR", // $A5
54
+ "RESUME", // $A6
55
+ "RECALL", // $A7
56
+ "STORE", // $A8
57
+ "SPEED=", // $A9
58
+ "LET", // $AA
59
+ "GOTO", // $AB
60
+ "RUN", // $AC
61
+ "IF", // $AD
62
+ "RESTORE", // $AE
63
+ "&", // $AF
64
+ "GOSUB", // $B0
65
+ "RETURN", // $B1
66
+ "REM", // $B2
67
+ "STOP", // $B3
68
+ "ON", // $B4
69
+ "WAIT", // $B5
70
+ "LOAD", // $B6
71
+ "SAVE", // $B7
72
+ "DEF", // $B8
73
+ "POKE", // $B9
74
+ "PRINT", // $BA
75
+ "CONT", // $BB
76
+ "LIST", // $BC
77
+ "CLEAR", // $BD
78
+ "GET", // $BE
79
+ "NEW", // $BF
80
+ "TAB(", // $C0
81
+ "TO", // $C1
82
+ "FN", // $C2
83
+ "SPC(", // $C3
84
+ "THEN", // $C4
85
+ "AT", // $C5
86
+ "NOT", // $C6
87
+ "STEP", // $C7
88
+ "+", // $C8
89
+ "-", // $C9
90
+ "*", // $CA
91
+ "/", // $CB
92
+ "^", // $CC
93
+ "AND", // $CD
94
+ "OR", // $CE
95
+ ">", // $CF
96
+ "=", // $D0
97
+ "<", // $D1
98
+ "SGN", // $D2
99
+ "INT", // $D3
100
+ "ABS", // $D4
101
+ "USR", // $D5
102
+ "FRE", // $D6
103
+ "SCRN(", // $D7
104
+ "PDL", // $D8
105
+ "POS", // $D9
106
+ "SQR", // $DA
107
+ "RND", // $DB
108
+ "LOG", // $DC
109
+ "EXP", // $DD
110
+ "COS", // $DE
111
+ "SIN", // $DF
112
+ "TAN", // $E0
113
+ "ATN", // $E1
114
+ "PEEK", // $E2
115
+ "LEN", // $E3
116
+ "STR$", // $E4
117
+ "VAL", // $E5
118
+ "ASC", // $E6
119
+ "CHR$", // $E7
120
+ "LEFT$", // $E8
121
+ "RIGHT$", // $E9
122
+ "MID$", // $EA
123
+ };
124
+
125
+ static constexpr int APPLESOFT_TOKEN_COUNT = sizeof(APPLESOFT_TOKENS) / sizeof(APPLESOFT_TOKENS[0]);
126
+
127
+ // Integer BASIC tokens ($00-$7F)
128
+ static constexpr const char* INTEGER_BASIC_TOKENS[] = {
129
+ " HIMEM: ", // $00
130
+ nullptr, // $01 (end of line)
131
+ "_", // $02
132
+ ":", // $03
133
+ " LOAD ", // $04
134
+ " SAVE ", // $05
135
+ " CON ", // $06
136
+ " RUN ", // $07
137
+ " RUN ", // $08
138
+ " DEL ", // $09
139
+ ",", // $0A
140
+ " NEW ", // $0B
141
+ " CLR ", // $0C
142
+ " AUTO ", // $0D
143
+ ",", // $0E
144
+ " MAN ", // $0F
145
+ " HIMEM: ", // $10
146
+ " LOMEM: ", // $11
147
+ "+", // $12
148
+ "-", // $13
149
+ "*", // $14
150
+ "/", // $15
151
+ "=", // $16
152
+ "#", // $17
153
+ ">=", // $18
154
+ ">", // $19
155
+ "<=", // $1A
156
+ "<>", // $1B
157
+ "<", // $1C
158
+ " AND ", // $1D
159
+ " OR ", // $1E
160
+ " MOD ", // $1F
161
+ "^", // $20
162
+ "+", // $21
163
+ "(", // $22
164
+ ",", // $23
165
+ " THEN ", // $24
166
+ " THEN ", // $25
167
+ ",", // $26
168
+ ",", // $27
169
+ "\"", // $28 (start quote)
170
+ "\"", // $29 (end quote)
171
+ "(", // $2A
172
+ "!", // $2B
173
+ "!", // $2C
174
+ "(", // $2D
175
+ "PEEK", // $2E
176
+ "RND", // $2F
177
+ "SGN", // $30
178
+ "ABS", // $31
179
+ "PDL", // $32
180
+ "RNDX", // $33
181
+ "(", // $34
182
+ "+", // $35
183
+ "-", // $36
184
+ " NOT ", // $37
185
+ "(", // $38
186
+ "=", // $39
187
+ "#", // $3A
188
+ "LEN(", // $3B
189
+ "ASC(", // $3C
190
+ "SCRN(", // $3D
191
+ ",", // $3E
192
+ "(", // $3F
193
+ "$", // $40
194
+ "$", // $41
195
+ "(", // $42
196
+ ",", // $43
197
+ ",", // $44
198
+ ";", // $45
199
+ ";", // $46
200
+ ";", // $47
201
+ ",", // $48
202
+ ",", // $49
203
+ ",", // $4A
204
+ " TEXT ", // $4B
205
+ " GR ", // $4C
206
+ " CALL ", // $4D
207
+ " DIM ", // $4E
208
+ " DIM ", // $4F
209
+ " TAB ", // $50
210
+ " END ", // $51
211
+ " INPUT ", // $52
212
+ " INPUT ", // $53
213
+ " INPUT ", // $54
214
+ " FOR ", // $55
215
+ "=", // $56
216
+ " TO ", // $57
217
+ " STEP ", // $58
218
+ " NEXT ", // $59
219
+ ",", // $5A
220
+ " RETURN ", // $5B
221
+ " GOSUB ", // $5C
222
+ " REM ", // $5D
223
+ " LET ", // $5E
224
+ " GOTO ", // $5F
225
+ " IF ", // $60
226
+ " PRINT ", // $61
227
+ " PRINT ", // $62
228
+ " PRINT ", // $63
229
+ " POKE ", // $64
230
+ ",", // $65
231
+ " COLOR= ", // $66
232
+ " PLOT ", // $67
233
+ ",", // $68
234
+ " HLIN ", // $69
235
+ ",", // $6A
236
+ " AT ", // $6B
237
+ " VLIN ", // $6C
238
+ ",", // $6D
239
+ " AT ", // $6E
240
+ " VTAB ", // $6F
241
+ "=", // $70
242
+ "=", // $71
243
+ ")", // $72
244
+ ")", // $73
245
+ " LIST ", // $74
246
+ ",", // $75
247
+ " LIST ", // $76
248
+ " POP ", // $77
249
+ " NODSP ", // $78
250
+ " DSP ", // $79
251
+ " NOTRACE ", // $7A
252
+ " DSP ", // $7B
253
+ " DSP ", // $7C
254
+ " TRACE ", // $7D
255
+ " PR# ", // $7E
256
+ " IN# ", // $7F
257
+ };
258
+
259
+ static constexpr int INTEGER_BASIC_TOKEN_COUNT = 0x80;
260
+
261
+ // Keywords that need space before them (Applesoft)
262
+ inline bool needsSpaceBefore(const char* token) {
263
+ // Check common keywords
264
+ const char* keywords[] = {
265
+ "FOR", "NEXT", "DATA", "INPUT", "DIM", "READ", "GR", "TEXT",
266
+ "CALL", "PLOT", "HLIN", "VLIN", "HGR2", "HGR", "HPLOT",
267
+ "DRAW", "XDRAW", "HTAB", "HOME", "SHLOAD", "TRACE", "NOTRACE",
268
+ "NORMAL", "INVERSE", "FLASH", "POP", "VTAB", "ONERR", "RESUME",
269
+ "RECALL", "STORE", "LET", "GOTO", "RUN", "IF", "RESTORE",
270
+ "GOSUB", "RETURN", "REM", "STOP", "ON", "WAIT", "LOAD", "SAVE",
271
+ "DEF", "POKE", "PRINT", "CONT", "LIST", "CLEAR", "GET", "NEW",
272
+ "TO", "FN", "THEN", "AT", "NOT", "STEP", "AND", "OR", "END",
273
+ };
274
+ for (const char* kw : keywords) {
275
+ if (strcmp(token, kw) == 0) return true;
276
+ }
277
+ return false;
278
+ }
279
+
280
+ // Keywords that need space after them (Applesoft)
281
+ inline bool needsSpaceAfter(const char* token) {
282
+ const char* keywords[] = {
283
+ "GOTO", "GOSUB", "THEN", "TO", "STEP", "AND", "OR", "NOT",
284
+ "IF", "ON", "LET", "FOR", "NEXT", "PRINT", "INPUT", "READ",
285
+ "DATA", "DIM", "DEF", "POKE", "CALL", "PLOT", "HLIN", "VLIN",
286
+ "HPLOT", "DRAW", "XDRAW", "HTAB", "VTAB", "ONERR", "WAIT",
287
+ "GET", "AT", "FN",
288
+ };
289
+ for (const char* kw : keywords) {
290
+ if (strcmp(token, kw) == 0) return true;
291
+ }
292
+ return false;
293
+ }
294
+
295
+ } // namespace a2e