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-program-parser.js - Parse BASIC program from memory for debugger display
|
|
3
|
+
*
|
|
4
|
+
* Written by
|
|
5
|
+
* Mike Daley <michael_daley@icloud.com>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Applesoft BASIC Program Memory Layout:
|
|
10
|
+
* - TXTTAB ($67-$68): Start of BASIC program text
|
|
11
|
+
* - Program lines: [next-ptr:2][line-num:2][tokenized-text...][00]
|
|
12
|
+
* - End marker: [00][00] (null next pointer)
|
|
13
|
+
*
|
|
14
|
+
* Execution State:
|
|
15
|
+
* - CURLIN ($75-$76): Current line number being executed (CURLIN+1=$FF = direct mode)
|
|
16
|
+
* - TXTPTR ($7A-$7B): Pointer to current position in program text
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { APPLESOFT_TOKENS } from "../utils/basic-tokens.js";
|
|
20
|
+
import { peek, readWord } from "../utils/wasm-memory.js";
|
|
21
|
+
|
|
22
|
+
export class BasicProgramParser {
|
|
23
|
+
constructor(wasmModule) {
|
|
24
|
+
this.wasmModule = wasmModule;
|
|
25
|
+
this.lineCache = null;
|
|
26
|
+
this.lastTxttab = 0;
|
|
27
|
+
this.lastVartab = 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get all program lines
|
|
32
|
+
* @returns {Array<{lineNumber: number, address: number, text: string}>}
|
|
33
|
+
*/
|
|
34
|
+
getLines() {
|
|
35
|
+
const txttab = readWord(this.wasmModule,0x67);
|
|
36
|
+
const vartab = readWord(this.wasmModule,0x69);
|
|
37
|
+
|
|
38
|
+
// Check cache validity
|
|
39
|
+
if (
|
|
40
|
+
this.lineCache &&
|
|
41
|
+
txttab === this.lastTxttab &&
|
|
42
|
+
vartab === this.lastVartab
|
|
43
|
+
) {
|
|
44
|
+
return this.lineCache;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const lines = [];
|
|
48
|
+
|
|
49
|
+
if (txttab === 0 || vartab === 0 || txttab >= vartab) {
|
|
50
|
+
this.lineCache = lines;
|
|
51
|
+
this.lastTxttab = txttab;
|
|
52
|
+
this.lastVartab = vartab;
|
|
53
|
+
return lines;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let addr = txttab;
|
|
57
|
+
const endAddr = vartab;
|
|
58
|
+
let safetyCount = 0;
|
|
59
|
+
const maxLines = 10000;
|
|
60
|
+
|
|
61
|
+
while (addr < endAddr && safetyCount < maxLines) {
|
|
62
|
+
const nextPtr = readWord(this.wasmModule,addr);
|
|
63
|
+
|
|
64
|
+
// End of program
|
|
65
|
+
if (nextPtr === 0) break;
|
|
66
|
+
|
|
67
|
+
const lineNumber = readWord(this.wasmModule,addr + 2);
|
|
68
|
+
const textStart = addr + 4;
|
|
69
|
+
|
|
70
|
+
// Find end of line (null terminator)
|
|
71
|
+
let textEnd = textStart;
|
|
72
|
+
while (peek(this.wasmModule,textEnd) !== 0 && textEnd < nextPtr) {
|
|
73
|
+
textEnd++;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Read tokenized bytes
|
|
77
|
+
const tokenBytes = new Uint8Array(textEnd - textStart);
|
|
78
|
+
for (let i = 0; i < tokenBytes.length; i++) {
|
|
79
|
+
tokenBytes[i] = peek(this.wasmModule,textStart + i);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Detokenize
|
|
83
|
+
const text = this._detokenize(tokenBytes);
|
|
84
|
+
|
|
85
|
+
lines.push({
|
|
86
|
+
lineNumber,
|
|
87
|
+
address: addr,
|
|
88
|
+
text,
|
|
89
|
+
tokenAddress: textStart,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
addr = nextPtr;
|
|
93
|
+
safetyCount++;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
this.lineCache = lines;
|
|
97
|
+
this.lastTxttab = txttab;
|
|
98
|
+
this.lastVartab = vartab;
|
|
99
|
+
|
|
100
|
+
return lines;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Invalidate the line cache (call when program may have changed)
|
|
105
|
+
*/
|
|
106
|
+
invalidateCache() {
|
|
107
|
+
this.lineCache = null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Get a map of line numbers to addresses for breakpoint setting
|
|
112
|
+
* @returns {Map<number, number>}
|
|
113
|
+
*/
|
|
114
|
+
getLineAddressMap() {
|
|
115
|
+
const lines = this.getLines();
|
|
116
|
+
const map = new Map();
|
|
117
|
+
for (const line of lines) {
|
|
118
|
+
map.set(line.lineNumber, line.address);
|
|
119
|
+
}
|
|
120
|
+
return map;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get execution state
|
|
125
|
+
* @returns {{running: boolean, currentLine: number, txtptr: number}}
|
|
126
|
+
*/
|
|
127
|
+
getExecutionState() {
|
|
128
|
+
const curlin = readWord(this.wasmModule,0x75);
|
|
129
|
+
const txtptr = readWord(this.wasmModule,0xb8);
|
|
130
|
+
|
|
131
|
+
// Use the C++ emulator's ROM-based tracking: PC at $D912 (RUN) = started,
|
|
132
|
+
// PC at $D43C (RESTART/] prompt) = stopped. This is definitive because it
|
|
133
|
+
// hooks directly into Applesoft's own execution flow.
|
|
134
|
+
const running = this.wasmModule._isBasicProgramRunning
|
|
135
|
+
? this.wasmModule._isBasicProgramRunning()
|
|
136
|
+
: false;
|
|
137
|
+
// Direct mode check: only CURLIN+1 ($76) matters (ROM checks INX on $76)
|
|
138
|
+
const curlinHi = peek(this.wasmModule, 0x76);
|
|
139
|
+
const directMode = curlinHi === 0xff;
|
|
140
|
+
return {
|
|
141
|
+
running,
|
|
142
|
+
currentLine: !directMode ? curlin : null,
|
|
143
|
+
txtptr,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Check if BASIC is running (CURLIN+1 != $FF, matching ROM check)
|
|
149
|
+
*/
|
|
150
|
+
isRunning() {
|
|
151
|
+
return peek(this.wasmModule, 0x76) !== 0xff;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Get current line number being executed
|
|
156
|
+
* Returns null if in direct mode or not running
|
|
157
|
+
*/
|
|
158
|
+
getCurrentLine() {
|
|
159
|
+
// Direct mode check: only CURLIN+1 ($76) matters (ROM checks INX on $76)
|
|
160
|
+
if (peek(this.wasmModule, 0x76) === 0xff) return null;
|
|
161
|
+
return readWord(this.wasmModule, 0x75);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Get current text pointer position
|
|
166
|
+
*/
|
|
167
|
+
getTxtptr() {
|
|
168
|
+
return readWord(this.wasmModule,0x7a);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Find line info by line number
|
|
173
|
+
*/
|
|
174
|
+
findLine(lineNumber) {
|
|
175
|
+
const lines = this.getLines();
|
|
176
|
+
return lines.find((l) => l.lineNumber === lineNumber) || null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Find the line containing the given address
|
|
181
|
+
*/
|
|
182
|
+
findLineByAddress(addr) {
|
|
183
|
+
const lines = this.getLines();
|
|
184
|
+
for (let i = 0; i < lines.length; i++) {
|
|
185
|
+
const line = lines[i];
|
|
186
|
+
const nextLine = lines[i + 1];
|
|
187
|
+
const endAddr = nextLine ? nextLine.address : readWord(this.wasmModule,0x69);
|
|
188
|
+
|
|
189
|
+
if (addr >= line.address && addr < endAddr) {
|
|
190
|
+
return line;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Get current statement info for the given line and TXTPTR
|
|
198
|
+
* Returns {statementIndex, statementCount, statementStart, statementEnd}
|
|
199
|
+
* where statementStart/End are character offsets in the detokenized text
|
|
200
|
+
*/
|
|
201
|
+
getCurrentStatementInfo(lineNumber, txtptr) {
|
|
202
|
+
const line = this.findLine(lineNumber);
|
|
203
|
+
if (!line) return null;
|
|
204
|
+
|
|
205
|
+
// Find end of this line's tokens
|
|
206
|
+
const nextPtr = readWord(this.wasmModule,line.address);
|
|
207
|
+
const tokenStart = line.tokenAddress;
|
|
208
|
+
const tokenEnd = nextPtr - 1; // -1 for null terminator
|
|
209
|
+
|
|
210
|
+
// If TXTPTR is outside this line, return null
|
|
211
|
+
if (txtptr < tokenStart || txtptr > tokenEnd) {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Parse tokenized bytes to find statement boundaries (colons not in quotes/REM/DATA)
|
|
216
|
+
const statementBoundaries = [0]; // Start positions in detokenized text
|
|
217
|
+
let inQuote = false;
|
|
218
|
+
let inRem = false;
|
|
219
|
+
let inData = false;
|
|
220
|
+
let detokenizedPos = 0;
|
|
221
|
+
let currentStatementIndex = 0;
|
|
222
|
+
|
|
223
|
+
for (let addr = tokenStart; addr < tokenEnd; addr++) {
|
|
224
|
+
const byte = peek(this.wasmModule,addr);
|
|
225
|
+
|
|
226
|
+
// Track if we've passed TXTPTR
|
|
227
|
+
if (addr === txtptr) {
|
|
228
|
+
currentStatementIndex = statementBoundaries.length - 1;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Handle quotes
|
|
232
|
+
if (byte === 0x22) { // Quote
|
|
233
|
+
inQuote = !inQuote;
|
|
234
|
+
detokenizedPos++;
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Inside quote or REM - just count characters
|
|
239
|
+
if (inQuote || inRem) {
|
|
240
|
+
detokenizedPos++;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Token range: $80-$EA
|
|
245
|
+
if (byte >= 0x80 && byte <= 0xea) {
|
|
246
|
+
const token = this._getTokenString(byte);
|
|
247
|
+
if (token) {
|
|
248
|
+
detokenizedPos += token.length;
|
|
249
|
+
if (byte === 0xb2) inRem = true; // REM
|
|
250
|
+
if (byte === 0x83) inData = true; // DATA
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Colon outside quotes/REM marks statement boundary
|
|
256
|
+
if (byte === 0x3a && !inData) { // Colon
|
|
257
|
+
detokenizedPos++;
|
|
258
|
+
statementBoundaries.push(detokenizedPos);
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Inside DATA - colons end DATA mode
|
|
263
|
+
if (inData && byte === 0x3a) {
|
|
264
|
+
inData = false;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
detokenizedPos++;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Add end position
|
|
271
|
+
statementBoundaries.push(line.text.length);
|
|
272
|
+
|
|
273
|
+
// Handle case where TXTPTR is at or past the last checked position
|
|
274
|
+
if (txtptr >= tokenEnd) {
|
|
275
|
+
currentStatementIndex = statementBoundaries.length - 2;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
statementIndex: currentStatementIndex,
|
|
280
|
+
statementCount: statementBoundaries.length - 1,
|
|
281
|
+
statementStart: statementBoundaries[currentStatementIndex] || 0,
|
|
282
|
+
statementEnd: statementBoundaries[currentStatementIndex + 1] || line.text.length,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Get the number of statements in a given line (colons + 1, respecting quotes/REM/DATA)
|
|
288
|
+
* @param {number} lineNumber
|
|
289
|
+
* @returns {number} statement count (1 if no colons)
|
|
290
|
+
*/
|
|
291
|
+
getStatementCount(lineNumber) {
|
|
292
|
+
const line = this.findLine(lineNumber);
|
|
293
|
+
if (!line) return 1;
|
|
294
|
+
|
|
295
|
+
const nextPtr = readWord(this.wasmModule, line.address);
|
|
296
|
+
const tokenStart = line.tokenAddress;
|
|
297
|
+
const tokenEnd = nextPtr - 1;
|
|
298
|
+
|
|
299
|
+
let colonCount = 0;
|
|
300
|
+
let inQuote = false;
|
|
301
|
+
let inRem = false;
|
|
302
|
+
|
|
303
|
+
for (let addr = tokenStart; addr < tokenEnd; addr++) {
|
|
304
|
+
const byte = peek(this.wasmModule, addr);
|
|
305
|
+
if (byte === 0) break;
|
|
306
|
+
if (inRem) continue;
|
|
307
|
+
if (byte === 0x22) { inQuote = !inQuote; continue; }
|
|
308
|
+
if (inQuote) continue;
|
|
309
|
+
if (byte === 0xB2) { inRem = true; continue; }
|
|
310
|
+
if (byte === 0x3A) colonCount++;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return colonCount + 1;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Get token string for a token byte
|
|
318
|
+
*/
|
|
319
|
+
_getTokenString(byte) {
|
|
320
|
+
if (byte >= 0x80 && byte <= 0xea) {
|
|
321
|
+
return APPLESOFT_TOKENS[byte - 0x80] || null;
|
|
322
|
+
}
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Detokenize Applesoft BASIC tokens with proper spacing
|
|
328
|
+
*/
|
|
329
|
+
_detokenize(bytes) {
|
|
330
|
+
let result = "";
|
|
331
|
+
let inQuote = false;
|
|
332
|
+
let inRem = false;
|
|
333
|
+
let inData = false;
|
|
334
|
+
|
|
335
|
+
// Helper to check if character is alphanumeric
|
|
336
|
+
const isAlphaNum = (c) => /[A-Za-z0-9]/.test(c);
|
|
337
|
+
|
|
338
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
339
|
+
const byte = bytes[i];
|
|
340
|
+
|
|
341
|
+
// Inside quote or REM/DATA - output as character (no spacing changes)
|
|
342
|
+
if (inQuote || inRem) {
|
|
343
|
+
if (byte === 0x22) inQuote = false; // End quote
|
|
344
|
+
result += String.fromCharCode(byte & 0x7f);
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Check for quote start
|
|
349
|
+
if (byte === 0x22) {
|
|
350
|
+
inQuote = true;
|
|
351
|
+
result += '"';
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Token range: $80-$EA
|
|
356
|
+
if (byte >= 0x80 && byte <= 0xea) {
|
|
357
|
+
const token = APPLESOFT_TOKENS[byte - 0x80];
|
|
358
|
+
if (token) {
|
|
359
|
+
// Add space before token if last char is alphanumeric
|
|
360
|
+
const lastChar = result.length > 0 ? result[result.length - 1] : "";
|
|
361
|
+
if (isAlphaNum(lastChar)) {
|
|
362
|
+
result += " ";
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
result += token;
|
|
366
|
+
|
|
367
|
+
// Check for REM or DATA
|
|
368
|
+
if (byte === 0xb2) inRem = true; // REM
|
|
369
|
+
if (byte === 0x83) inData = true; // DATA
|
|
370
|
+
|
|
371
|
+
// Add space after token if next byte isn't a space and token ends with a letter
|
|
372
|
+
const nextByte = i + 1 < bytes.length ? bytes[i + 1] : 0;
|
|
373
|
+
if (nextByte !== 0x20 && nextByte !== 0 && isAlphaNum(token[token.length - 1])) {
|
|
374
|
+
result += " ";
|
|
375
|
+
}
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Inside DATA - colons end DATA mode
|
|
381
|
+
if (inData && byte === 0x3a) {
|
|
382
|
+
inData = false;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Regular character
|
|
386
|
+
const char = String.fromCharCode(byte & 0x7f);
|
|
387
|
+
|
|
388
|
+
// Consume contiguous number (digits + optional decimal point)
|
|
389
|
+
if (byte >= 0x30 && byte <= 0x39) {
|
|
390
|
+
const lastChar = result.length > 0 ? result[result.length - 1] : "";
|
|
391
|
+
if (isAlphaNum(lastChar)) {
|
|
392
|
+
result += " ";
|
|
393
|
+
}
|
|
394
|
+
result += char;
|
|
395
|
+
while (i + 1 < bytes.length) {
|
|
396
|
+
const next = bytes[i + 1];
|
|
397
|
+
if ((next >= 0x30 && next <= 0x39) || next === 0x2e) {
|
|
398
|
+
result += String.fromCharCode(next);
|
|
399
|
+
i++;
|
|
400
|
+
} else {
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Consume contiguous variable name (letters + digits + $ + %)
|
|
408
|
+
const isLetter = (byte >= 0x41 && byte <= 0x5a) || (byte >= 0x61 && byte <= 0x7a);
|
|
409
|
+
if (isLetter) {
|
|
410
|
+
const lastChar = result.length > 0 ? result[result.length - 1] : "";
|
|
411
|
+
if (isAlphaNum(lastChar)) {
|
|
412
|
+
result += " ";
|
|
413
|
+
}
|
|
414
|
+
result += char;
|
|
415
|
+
while (i + 1 < bytes.length) {
|
|
416
|
+
const next = bytes[i + 1];
|
|
417
|
+
const nextIsLetter =
|
|
418
|
+
(next >= 0x41 && next <= 0x5a) || (next >= 0x61 && next <= 0x7a);
|
|
419
|
+
const nextIsDigit = next >= 0x30 && next <= 0x39;
|
|
420
|
+
if (nextIsLetter || nextIsDigit || next === 0x24 || next === 0x25) {
|
|
421
|
+
result += String.fromCharCode(next);
|
|
422
|
+
i++;
|
|
423
|
+
} else {
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
result += char;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return result;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
}
|