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,894 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* basic-program-tools.js - BASIC program window tools
|
|
3
|
+
*
|
|
4
|
+
* Written by
|
|
5
|
+
* Shawn Bullock <shawn@agenticexpert.ai>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { BasicProgramParser } from "../debug/basic-program-parser.js";
|
|
9
|
+
import { tokenizeProgram } from "../utils/basic-tokenizer.js";
|
|
10
|
+
|
|
11
|
+
export const basicProgramTools = {
|
|
12
|
+
/**
|
|
13
|
+
* Direct read of BASIC program from memory (no UI interaction required)
|
|
14
|
+
*/
|
|
15
|
+
directReadBasic: async (args) => {
|
|
16
|
+
const wasmModule = window.emulator?.wasmModule;
|
|
17
|
+
if (!wasmModule) {
|
|
18
|
+
throw new Error("WASM module not available");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Create parser instance to read from memory
|
|
22
|
+
const parser = new BasicProgramParser(wasmModule);
|
|
23
|
+
const lines = parser.getLines();
|
|
24
|
+
|
|
25
|
+
if (lines.length === 0) {
|
|
26
|
+
return {
|
|
27
|
+
success: true,
|
|
28
|
+
program: "",
|
|
29
|
+
lines: 0,
|
|
30
|
+
message: "No BASIC program in memory",
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Format as program text (line number + text)
|
|
35
|
+
const programText = lines
|
|
36
|
+
.map((line) => `${line.lineNumber} ${line.text}`)
|
|
37
|
+
.join("\n");
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
success: true,
|
|
41
|
+
program: programText,
|
|
42
|
+
lines: lines.length,
|
|
43
|
+
message: `Read ${lines.length} lines from memory`,
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Direct write of BASIC program to memory (no UI interaction required)
|
|
49
|
+
*/
|
|
50
|
+
directWriteBasic: async (args) => {
|
|
51
|
+
const { program } = args;
|
|
52
|
+
|
|
53
|
+
if (program === undefined) {
|
|
54
|
+
throw new Error("program parameter is required");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const wasmModule = window.emulator?.wasmModule;
|
|
58
|
+
if (!wasmModule) {
|
|
59
|
+
throw new Error("WASM module not available");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const emulator = window.emulator;
|
|
63
|
+
if (!emulator || !emulator.running) {
|
|
64
|
+
throw new Error("Emulator must be powered on");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Parse program text into lines (matches window's parseProgram method)
|
|
68
|
+
let text = program.trim();
|
|
69
|
+
if (!text) {
|
|
70
|
+
throw new Error("No program text provided");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Sanitize to pure ASCII: convert to ASCII and strip non-ASCII characters
|
|
74
|
+
// Replace any Unicode spaces with regular ASCII space (0x20)
|
|
75
|
+
text = text
|
|
76
|
+
.replace(/[\u00A0\u1680\u2000-\u200B\u202F\u205F\u3000]/g, ' ') // Unicode spaces → ASCII space
|
|
77
|
+
.replace(/[^\x00-\x7F]/g, ''); // Remove any non-ASCII characters
|
|
78
|
+
|
|
79
|
+
// Debug: Log the character codes to detect encoding issues
|
|
80
|
+
console.log("[directWriteBasic] Input text:", text);
|
|
81
|
+
console.log("[directWriteBasic] First 50 char codes:",
|
|
82
|
+
text.substring(0, 50).split('').map((c, i) =>
|
|
83
|
+
`[${i}]=${c}:0x${c.charCodeAt(0).toString(16)}`
|
|
84
|
+
).join(' ')
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const lines = [];
|
|
88
|
+
const rawLines = text.split(/\r?\n/);
|
|
89
|
+
|
|
90
|
+
for (const rawLine of rawLines) {
|
|
91
|
+
const trimmed = rawLine.trim().toUpperCase();
|
|
92
|
+
if (!trimmed) continue;
|
|
93
|
+
|
|
94
|
+
const match = trimmed.match(/^(\d+)\s*(.*)/);
|
|
95
|
+
if (!match) {
|
|
96
|
+
console.warn("Skipping line without line number:", rawLine);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const lineNum = parseInt(match[1], 10);
|
|
101
|
+
if (lineNum < 0 || lineNum > 63999) {
|
|
102
|
+
console.warn("Invalid line number:", lineNum);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Normalize whitespace: replace multiple spaces with single space
|
|
107
|
+
// This matches ROM tokenizer behavior
|
|
108
|
+
const normalizedContent = (match[2] || "").replace(/\s+/g, " ");
|
|
109
|
+
|
|
110
|
+
lines.push({
|
|
111
|
+
lineNumber: lineNum,
|
|
112
|
+
content: normalizedContent,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Sort lines by line number (critical for proper BASIC program order)
|
|
117
|
+
lines.sort((a, b) => a.lineNumber - b.lineNumber);
|
|
118
|
+
|
|
119
|
+
if (lines.length === 0) {
|
|
120
|
+
throw new Error("No valid BASIC lines found");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Tokenize the program
|
|
124
|
+
const txttab = 0x0801;
|
|
125
|
+
const { bytes, endAddr } = tokenizeProgram(lines, txttab);
|
|
126
|
+
|
|
127
|
+
// Write tokenized program bytes into emulator memory
|
|
128
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
129
|
+
wasmModule._writeMemory(txttab + i, bytes[i]);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Helper to write a 16-bit little-endian pointer to zero page
|
|
133
|
+
const writePtr = (zpAddr, value) => {
|
|
134
|
+
wasmModule._writeMemory(zpAddr, value & 0xff);
|
|
135
|
+
wasmModule._writeMemory(zpAddr + 1, (value >> 8) & 0xff);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// Read MEMSIZE ($73) - the ROM sets FRETOP to this on CLR/NEW
|
|
139
|
+
const memsizeLo = wasmModule._readMemory(0x73);
|
|
140
|
+
const memsizeHi = wasmModule._readMemory(0x74);
|
|
141
|
+
const memsize = memsizeLo | (memsizeHi << 8);
|
|
142
|
+
|
|
143
|
+
// Update Applesoft zero page pointers
|
|
144
|
+
writePtr(0x67, txttab); // TXTTAB - start of program
|
|
145
|
+
writePtr(0x69, endAddr); // VARTAB - start of variable space
|
|
146
|
+
writePtr(0x6b, endAddr); // ARYTAB - start of array space
|
|
147
|
+
writePtr(0x6d, endAddr); // STREND - end of numeric storage
|
|
148
|
+
writePtr(0x6f, memsize); // FRETOP - end of string storage
|
|
149
|
+
writePtr(0xaf, endAddr); // PRGEND - end of program
|
|
150
|
+
writePtr(0xb8, txttab - 1); // TXTPTR - interpreter text pointer
|
|
151
|
+
wasmModule._writeMemory(0x76, 0xff); // CURLIN+1 = $FF (direct mode)
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
success: true,
|
|
155
|
+
lines: lines.length,
|
|
156
|
+
bytes: bytes.length,
|
|
157
|
+
message: `Wrote ${lines.length} lines (${bytes.length} bytes) to memory`,
|
|
158
|
+
};
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Direct run of BASIC program (no UI interaction required)
|
|
163
|
+
*/
|
|
164
|
+
directRunBasic: async (args) => {
|
|
165
|
+
const wasmModule = window.emulator?.wasmModule;
|
|
166
|
+
if (!wasmModule) {
|
|
167
|
+
throw new Error("WASM module not available");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const emulator = window.emulator;
|
|
171
|
+
if (!emulator || !emulator.running) {
|
|
172
|
+
throw new Error("Emulator must be powered on");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const inputHandler = window.emulator?.inputHandler;
|
|
176
|
+
if (!inputHandler) {
|
|
177
|
+
throw new Error("Input handler not available");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Ensure emulator is not paused
|
|
181
|
+
wasmModule._setPaused(false);
|
|
182
|
+
|
|
183
|
+
// Clear BASIC breakpoint hit flag if available
|
|
184
|
+
if (wasmModule._clearBasicBreakpointHit) {
|
|
185
|
+
wasmModule._clearBasicBreakpointHit();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Queue RUN command to input handler
|
|
189
|
+
inputHandler.queueTextInput("RUN\r");
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
success: true,
|
|
193
|
+
message: "BASIC program execution started",
|
|
194
|
+
};
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Direct NEW command - clears BASIC program buffer (no UI interaction required)
|
|
199
|
+
*/
|
|
200
|
+
directNewBasic: async (args) => {
|
|
201
|
+
const wasmModule = window.emulator?.wasmModule;
|
|
202
|
+
if (!wasmModule) {
|
|
203
|
+
throw new Error("WASM module not available");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const emulator = window.emulator;
|
|
207
|
+
if (!emulator || !emulator.running) {
|
|
208
|
+
throw new Error("Emulator must be powered on");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Helper to write a 16-bit little-endian pointer to zero page
|
|
212
|
+
const writePtr = (zpAddr, value) => {
|
|
213
|
+
wasmModule._writeMemory(zpAddr, value & 0xff);
|
|
214
|
+
wasmModule._writeMemory(zpAddr + 1, (value >> 8) & 0xff);
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// Read MEMSIZE ($73)
|
|
218
|
+
const memsizeLo = wasmModule._readMemory(0x73);
|
|
219
|
+
const memsizeHi = wasmModule._readMemory(0x74);
|
|
220
|
+
const memsize = memsizeLo | (memsizeHi << 8);
|
|
221
|
+
|
|
222
|
+
// TXTTAB - start of BASIC program area
|
|
223
|
+
const txttab = 0x0801;
|
|
224
|
+
|
|
225
|
+
// Write end-of-program marker (0x00, 0x00) at start
|
|
226
|
+
wasmModule._writeMemory(txttab, 0x00);
|
|
227
|
+
wasmModule._writeMemory(txttab + 1, 0x00);
|
|
228
|
+
|
|
229
|
+
// Reset all BASIC pointers to empty program state
|
|
230
|
+
writePtr(0x67, txttab); // TXTTAB - start of program
|
|
231
|
+
writePtr(0x69, txttab); // VARTAB - start of variables (same as program start = empty)
|
|
232
|
+
writePtr(0x6b, txttab); // ARYTAB - start of arrays
|
|
233
|
+
writePtr(0x6d, txttab); // STREND - end of arrays
|
|
234
|
+
writePtr(0x6f, memsize); // FRETOP - top of free memory
|
|
235
|
+
writePtr(0xaf, txttab); // PRGEND - end of program
|
|
236
|
+
writePtr(0xb8, txttab - 1); // TXTPTR - interpreter text pointer
|
|
237
|
+
wasmModule._writeMemory(0x76, 0xff); // CURLIN+1 = $FF (direct mode)
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
success: true,
|
|
241
|
+
message: "BASIC program buffer cleared",
|
|
242
|
+
};
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Get BASIC program content from editor (used with save_basic_file MCP tool)
|
|
247
|
+
*/
|
|
248
|
+
saveBasicInEditorToLocal: async (args) => {
|
|
249
|
+
const windowManager = window.emulator?.windowManager;
|
|
250
|
+
if (!windowManager) {
|
|
251
|
+
throw new Error("Window manager not available");
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const basicWindow = windowManager.getWindow("basic-program");
|
|
255
|
+
if (!basicWindow) {
|
|
256
|
+
throw new Error("BASIC program window not found");
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const content = basicWindow.textarea ? basicWindow.textarea.value : "";
|
|
260
|
+
if (!content.trim()) {
|
|
261
|
+
throw new Error("No program in editor to save");
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
success: true,
|
|
266
|
+
content: content,
|
|
267
|
+
lines: content.split("\n").length,
|
|
268
|
+
message: "BASIC program content retrieved from editor",
|
|
269
|
+
};
|
|
270
|
+
},
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Save BASIC program from emulator memory to local file
|
|
274
|
+
* Combines directReadBasic + save to filesystem
|
|
275
|
+
*/
|
|
276
|
+
directSaveBasicInMemoryToLocal: async (args) => {
|
|
277
|
+
const { path } = args;
|
|
278
|
+
|
|
279
|
+
if (!path) {
|
|
280
|
+
throw new Error("path parameter is required");
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const wasmModule = window.emulator?.wasmModule;
|
|
284
|
+
if (!wasmModule) {
|
|
285
|
+
throw new Error("WASM module not available");
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Read BASIC program from memory
|
|
289
|
+
const parser = new BasicProgramParser(wasmModule);
|
|
290
|
+
const lines = parser.getLines();
|
|
291
|
+
|
|
292
|
+
if (lines.length === 0) {
|
|
293
|
+
throw new Error("No BASIC program in memory to save");
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Format as program text (line number + text)
|
|
297
|
+
const programText = lines
|
|
298
|
+
.map((line) => `${line.lineNumber} ${line.text}`)
|
|
299
|
+
.join("\n");
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
success: true,
|
|
303
|
+
content: programText,
|
|
304
|
+
lines: lines.length,
|
|
305
|
+
path: path,
|
|
306
|
+
message: `BASIC program read from memory (${lines.length} lines), ready to save to ${path}`,
|
|
307
|
+
};
|
|
308
|
+
},
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Load BASIC program from emulator memory into editor
|
|
312
|
+
*/
|
|
313
|
+
basicProgramLoadFromMemory: async (args) => {
|
|
314
|
+
const windowManager = window.emulator?.windowManager;
|
|
315
|
+
if (!windowManager) {
|
|
316
|
+
throw new Error("Window manager not available");
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const basicWindow = windowManager.getWindow("basic-program");
|
|
320
|
+
if (!basicWindow) {
|
|
321
|
+
throw new Error("BASIC program window not found");
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
basicWindow.loadFromMemory();
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
success: true,
|
|
328
|
+
message: "BASIC program loaded from memory into editor",
|
|
329
|
+
};
|
|
330
|
+
},
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Load BASIC program from editor into emulator memory
|
|
334
|
+
*/
|
|
335
|
+
basicProgramLoadIntoEmulator: async (args) => {
|
|
336
|
+
const windowManager = window.emulator?.windowManager;
|
|
337
|
+
if (!windowManager) {
|
|
338
|
+
throw new Error("Window manager not available");
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const basicWindow = windowManager.getWindow("basic-program");
|
|
342
|
+
if (!basicWindow) {
|
|
343
|
+
throw new Error("BASIC program window not found");
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
basicWindow.loadIntoMemory();
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
success: true,
|
|
350
|
+
message: "BASIC program loaded from editor into emulator memory",
|
|
351
|
+
};
|
|
352
|
+
},
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Run BASIC program
|
|
356
|
+
*/
|
|
357
|
+
basicProgramRun: async (args) => {
|
|
358
|
+
const windowManager = window.emulator?.windowManager;
|
|
359
|
+
if (!windowManager) {
|
|
360
|
+
throw new Error("Window manager not available");
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const basicWindow = windowManager.getWindow("basic-program");
|
|
364
|
+
if (!basicWindow) {
|
|
365
|
+
throw new Error("BASIC program window not found");
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
basicWindow.handleRun();
|
|
369
|
+
|
|
370
|
+
return {
|
|
371
|
+
success: true,
|
|
372
|
+
message: "BASIC program execution started",
|
|
373
|
+
};
|
|
374
|
+
},
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Pause BASIC program execution
|
|
378
|
+
*/
|
|
379
|
+
basicProgramPause: async (args) => {
|
|
380
|
+
const windowManager = window.emulator?.windowManager;
|
|
381
|
+
if (!windowManager) {
|
|
382
|
+
throw new Error("Window manager not available");
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const basicWindow = windowManager.getWindow("basic-program");
|
|
386
|
+
if (!basicWindow) {
|
|
387
|
+
throw new Error("BASIC program window not found");
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
basicWindow.handlePause();
|
|
391
|
+
|
|
392
|
+
return {
|
|
393
|
+
success: true,
|
|
394
|
+
message: "BASIC program execution paused",
|
|
395
|
+
};
|
|
396
|
+
},
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Clear BASIC program editor and start new program
|
|
400
|
+
* Emulates the newFile() method without confirmation dialog
|
|
401
|
+
*/
|
|
402
|
+
basicProgramNew: async (args) => {
|
|
403
|
+
const windowManager = window.emulator?.windowManager;
|
|
404
|
+
if (!windowManager) {
|
|
405
|
+
throw new Error("Window manager not available");
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const basicWindow = windowManager.getWindow("basic-program");
|
|
409
|
+
if (!basicWindow) {
|
|
410
|
+
throw new Error("BASIC program window not found");
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Emulate newFile() behavior without confirmation
|
|
414
|
+
if (basicWindow.textarea) {
|
|
415
|
+
basicWindow.textarea.value = "";
|
|
416
|
+
basicWindow._fileHandle = null;
|
|
417
|
+
basicWindow.updateGutter();
|
|
418
|
+
basicWindow.updateHighlighting();
|
|
419
|
+
basicWindow.updateStats();
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return {
|
|
423
|
+
success: true,
|
|
424
|
+
message: "BASIC program editor cleared",
|
|
425
|
+
};
|
|
426
|
+
},
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Renumber BASIC program lines
|
|
430
|
+
*/
|
|
431
|
+
basicProgramRenumber: async (args) => {
|
|
432
|
+
const windowManager = window.emulator?.windowManager;
|
|
433
|
+
if (!windowManager) {
|
|
434
|
+
throw new Error("Window manager not available");
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const basicWindow = windowManager.getWindow("basic-program");
|
|
438
|
+
if (!basicWindow) {
|
|
439
|
+
throw new Error("BASIC program window not found");
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
basicWindow.renumberProgram();
|
|
443
|
+
|
|
444
|
+
return {
|
|
445
|
+
success: true,
|
|
446
|
+
message: "BASIC program renumbered",
|
|
447
|
+
};
|
|
448
|
+
},
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Format BASIC program code
|
|
452
|
+
*/
|
|
453
|
+
basicProgramFormat: async (args) => {
|
|
454
|
+
const windowManager = window.emulator?.windowManager;
|
|
455
|
+
if (!windowManager) {
|
|
456
|
+
throw new Error("Window manager not available");
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const basicWindow = windowManager.getWindow("basic-program");
|
|
460
|
+
if (!basicWindow) {
|
|
461
|
+
throw new Error("BASIC program window not found");
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
basicWindow.autoFormatCode();
|
|
465
|
+
|
|
466
|
+
return {
|
|
467
|
+
success: true,
|
|
468
|
+
message: "BASIC program formatted",
|
|
469
|
+
};
|
|
470
|
+
},
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Get BASIC program line and character count
|
|
474
|
+
*/
|
|
475
|
+
basicProgramLineCount: async (args) => {
|
|
476
|
+
const windowManager = window.emulator?.windowManager;
|
|
477
|
+
if (!windowManager) {
|
|
478
|
+
throw new Error("Window manager not available");
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const basicWindow = windowManager.getWindow("basic-program");
|
|
482
|
+
if (!basicWindow) {
|
|
483
|
+
throw new Error("BASIC program window not found");
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const text = basicWindow.textarea ? basicWindow.textarea.value : "";
|
|
487
|
+
const lines = text ? text.split(/\r?\n/).filter((l) => l.trim()).length : 0;
|
|
488
|
+
const chars = text.length;
|
|
489
|
+
|
|
490
|
+
return {
|
|
491
|
+
success: true,
|
|
492
|
+
lines: lines,
|
|
493
|
+
chars: chars,
|
|
494
|
+
message: `${lines} lines, ${chars} characters`,
|
|
495
|
+
};
|
|
496
|
+
},
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Get current BASIC program text
|
|
500
|
+
*/
|
|
501
|
+
basicProgramGet: async (args) => {
|
|
502
|
+
const windowManager = window.emulator?.windowManager;
|
|
503
|
+
if (!windowManager) {
|
|
504
|
+
throw new Error("Window manager not available");
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const basicWindow = windowManager.getWindow("basic-program");
|
|
508
|
+
if (!basicWindow) {
|
|
509
|
+
throw new Error("BASIC program window not found");
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const text = basicWindow.textarea ? basicWindow.textarea.value : "";
|
|
513
|
+
|
|
514
|
+
return {
|
|
515
|
+
success: true,
|
|
516
|
+
program: text,
|
|
517
|
+
message: "BASIC program retrieved",
|
|
518
|
+
};
|
|
519
|
+
},
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Set BASIC program (replace entire content)
|
|
523
|
+
*/
|
|
524
|
+
basicProgramSet: async (args) => {
|
|
525
|
+
const { program } = args;
|
|
526
|
+
|
|
527
|
+
if (program === undefined) {
|
|
528
|
+
throw new Error("program parameter is required");
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const windowManager = window.emulator?.windowManager;
|
|
532
|
+
if (!windowManager) {
|
|
533
|
+
throw new Error("Window manager not available");
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const basicWindow = windowManager.getWindow("basic-program");
|
|
537
|
+
if (!basicWindow) {
|
|
538
|
+
throw new Error("BASIC program window not found");
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (basicWindow.textarea) {
|
|
542
|
+
basicWindow.textarea.value = program;
|
|
543
|
+
basicWindow.updateGutter();
|
|
544
|
+
basicWindow.updateHighlighting();
|
|
545
|
+
basicWindow.updateStats();
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return {
|
|
549
|
+
success: true,
|
|
550
|
+
message: "BASIC program set",
|
|
551
|
+
};
|
|
552
|
+
},
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* List all BASIC breakpoints
|
|
556
|
+
*/
|
|
557
|
+
basicProgramListBreakpoints: async (args) => {
|
|
558
|
+
const windowManager = window.emulator?.windowManager;
|
|
559
|
+
if (!windowManager) {
|
|
560
|
+
throw new Error("Window manager not available");
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const basicWindow = windowManager.getWindow("basic-program");
|
|
564
|
+
if (!basicWindow) {
|
|
565
|
+
throw new Error("BASIC program window not found");
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
const breakpointManager = basicWindow.getBreakpointManager();
|
|
569
|
+
if (!breakpointManager) {
|
|
570
|
+
throw new Error("Breakpoint manager not available");
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const entries = breakpointManager.getAllEntries();
|
|
574
|
+
const breakpoints = entries.map((entry) => ({
|
|
575
|
+
lineNumber: entry.lineNumber,
|
|
576
|
+
statementIndex: entry.statementIndex,
|
|
577
|
+
enabled: entry.enabled,
|
|
578
|
+
type: entry.statementIndex === -1 ? "line" : "statement",
|
|
579
|
+
}));
|
|
580
|
+
|
|
581
|
+
return {
|
|
582
|
+
success: true,
|
|
583
|
+
breakpoints: breakpoints,
|
|
584
|
+
count: breakpoints.length,
|
|
585
|
+
message: `${breakpoints.length} breakpoint(s) set`,
|
|
586
|
+
};
|
|
587
|
+
},
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Set a BASIC breakpoint on a line or statement
|
|
591
|
+
* @param {number} lineNumber - BASIC line number
|
|
592
|
+
* @param {number} statementIndex - Optional: -1 for whole line (default), 0+ for specific statement
|
|
593
|
+
*/
|
|
594
|
+
basicProgramSetBreakpoint: async (args) => {
|
|
595
|
+
const { lineNumber, statementIndex = -1 } = args;
|
|
596
|
+
|
|
597
|
+
if (lineNumber === undefined) {
|
|
598
|
+
throw new Error("lineNumber parameter is required");
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const windowManager = window.emulator?.windowManager;
|
|
602
|
+
if (!windowManager) {
|
|
603
|
+
throw new Error("Window manager not available");
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
const basicWindow = windowManager.getWindow("basic-program");
|
|
607
|
+
if (!basicWindow) {
|
|
608
|
+
throw new Error("BASIC program window not found");
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const breakpointManager = basicWindow.getBreakpointManager();
|
|
612
|
+
if (!breakpointManager) {
|
|
613
|
+
throw new Error("Breakpoint manager not available");
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
breakpointManager.add(lineNumber, statementIndex);
|
|
617
|
+
|
|
618
|
+
const type = statementIndex === -1 ? "line" : `statement ${statementIndex}`;
|
|
619
|
+
return {
|
|
620
|
+
success: true,
|
|
621
|
+
lineNumber: lineNumber,
|
|
622
|
+
statementIndex: statementIndex,
|
|
623
|
+
message: `Breakpoint set on line ${lineNumber} (${type})`,
|
|
624
|
+
};
|
|
625
|
+
},
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Remove a BASIC breakpoint from a line or statement
|
|
629
|
+
* @param {number} lineNumber - BASIC line number
|
|
630
|
+
* @param {number} statementIndex - Optional: -1 for whole line (default), 0+ for specific statement
|
|
631
|
+
*/
|
|
632
|
+
basicProgramUnsetBreakpoint: async (args) => {
|
|
633
|
+
const { lineNumber, statementIndex = -1 } = args;
|
|
634
|
+
|
|
635
|
+
if (lineNumber === undefined) {
|
|
636
|
+
throw new Error("lineNumber parameter is required");
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const windowManager = window.emulator?.windowManager;
|
|
640
|
+
if (!windowManager) {
|
|
641
|
+
throw new Error("Window manager not available");
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const basicWindow = windowManager.getWindow("basic-program");
|
|
645
|
+
if (!basicWindow) {
|
|
646
|
+
throw new Error("BASIC program window not found");
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
const breakpointManager = basicWindow.getBreakpointManager();
|
|
650
|
+
if (!breakpointManager) {
|
|
651
|
+
throw new Error("Breakpoint manager not available");
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
breakpointManager.remove(lineNumber, statementIndex);
|
|
655
|
+
|
|
656
|
+
const type = statementIndex === -1 ? "line" : `statement ${statementIndex}`;
|
|
657
|
+
return {
|
|
658
|
+
success: true,
|
|
659
|
+
lineNumber: lineNumber,
|
|
660
|
+
statementIndex: statementIndex,
|
|
661
|
+
message: `Breakpoint removed from line ${lineNumber} (${type})`,
|
|
662
|
+
};
|
|
663
|
+
},
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Get current BASIC line number
|
|
667
|
+
* Returns undefined if not stopped at a breakpoint
|
|
668
|
+
*/
|
|
669
|
+
basicProgramGetCurrentLine: async (args) => {
|
|
670
|
+
const wasmModule = window.emulator?.wasmModule;
|
|
671
|
+
if (!wasmModule) {
|
|
672
|
+
throw new Error("WASM module not available");
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const emulator = window.emulator;
|
|
676
|
+
if (!emulator || !emulator.running) {
|
|
677
|
+
return {
|
|
678
|
+
success: true,
|
|
679
|
+
lineNumber: undefined,
|
|
680
|
+
message: "Emulator not running",
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// Check if paused at a BASIC breakpoint
|
|
685
|
+
const isPaused = wasmModule._isPaused();
|
|
686
|
+
const isBasicBreakpointHit = wasmModule._isBasicBreakpointHit
|
|
687
|
+
? wasmModule._isBasicBreakpointHit()
|
|
688
|
+
: false;
|
|
689
|
+
|
|
690
|
+
if (!isPaused || !isBasicBreakpointHit) {
|
|
691
|
+
return {
|
|
692
|
+
success: true,
|
|
693
|
+
lineNumber: undefined,
|
|
694
|
+
message: "Not stopped at a breakpoint",
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// Read CURLIN from zero page $75-$76
|
|
699
|
+
const lo = wasmModule._readMemory(0x75);
|
|
700
|
+
const hi = wasmModule._readMemory(0x76);
|
|
701
|
+
const lineNumber = lo | (hi << 8);
|
|
702
|
+
|
|
703
|
+
return {
|
|
704
|
+
success: true,
|
|
705
|
+
lineNumber: lineNumber,
|
|
706
|
+
message: `Stopped at line ${lineNumber}`,
|
|
707
|
+
};
|
|
708
|
+
},
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Get all BASIC variables (simple and arrays)
|
|
712
|
+
*/
|
|
713
|
+
basicProgramGetVariables: async (args) => {
|
|
714
|
+
const windowManager = window.emulator?.windowManager;
|
|
715
|
+
if (!windowManager) {
|
|
716
|
+
throw new Error("Window manager not available");
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
const basicWindow = windowManager.getWindow("basic-program");
|
|
720
|
+
if (!basicWindow) {
|
|
721
|
+
throw new Error("BASIC program window not found");
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
const inspector = basicWindow.variableInspector;
|
|
725
|
+
if (!inspector) {
|
|
726
|
+
throw new Error("Variable inspector not available");
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
const simpleVars = inspector.getSimpleVariables();
|
|
730
|
+
const arrayVars = inspector.getArrayVariables();
|
|
731
|
+
|
|
732
|
+
// Format simple variables
|
|
733
|
+
const variables = simpleVars.map((v) => ({
|
|
734
|
+
name: v.name,
|
|
735
|
+
type: v.type,
|
|
736
|
+
value: v.value,
|
|
737
|
+
formattedValue: inspector.formatValue(v),
|
|
738
|
+
}));
|
|
739
|
+
|
|
740
|
+
// Format array variables
|
|
741
|
+
const arrays = arrayVars.map((arr) => ({
|
|
742
|
+
name: arr.name,
|
|
743
|
+
type: arr.type,
|
|
744
|
+
dimensions: arr.dimensions,
|
|
745
|
+
totalElements: arr.totalElements,
|
|
746
|
+
values: arr.values,
|
|
747
|
+
}));
|
|
748
|
+
|
|
749
|
+
return {
|
|
750
|
+
success: true,
|
|
751
|
+
variables: variables,
|
|
752
|
+
arrays: arrays,
|
|
753
|
+
totalVariables: variables.length,
|
|
754
|
+
totalArrays: arrays.length,
|
|
755
|
+
message: `${variables.length} variable(s), ${arrays.length} array(s)`,
|
|
756
|
+
};
|
|
757
|
+
},
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* Set a BASIC variable value
|
|
761
|
+
* @param {string} name - Variable name (e.g., "X", "A$", "COUNT%")
|
|
762
|
+
* @param {string|number} value - New value (converted to string for parsing)
|
|
763
|
+
*/
|
|
764
|
+
basicProgramSetVariable: async (args) => {
|
|
765
|
+
const { name, value } = args;
|
|
766
|
+
|
|
767
|
+
if (name === undefined) {
|
|
768
|
+
throw new Error("name parameter is required");
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
if (value === undefined) {
|
|
772
|
+
throw new Error("value parameter is required");
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
const windowManager = window.emulator?.windowManager;
|
|
776
|
+
if (!windowManager) {
|
|
777
|
+
throw new Error("Window manager not available");
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
const basicWindow = windowManager.getWindow("basic-program");
|
|
781
|
+
if (!basicWindow) {
|
|
782
|
+
throw new Error("BASIC program window not found");
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
const inspector = basicWindow.variableInspector;
|
|
786
|
+
if (!inspector) {
|
|
787
|
+
throw new Error("Variable inspector not available");
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// Get all variables to find the one we want
|
|
791
|
+
const simpleVars = inspector.getSimpleVariables();
|
|
792
|
+
const varInfo = simpleVars.find((v) => v.name === name);
|
|
793
|
+
|
|
794
|
+
if (!varInfo) {
|
|
795
|
+
throw new Error(`Variable "${name}" not found`);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// Convert value to string for the inspector's setVariableValue method
|
|
799
|
+
const valueStr = typeof value === "string" ? value : String(value);
|
|
800
|
+
|
|
801
|
+
// Set the variable value
|
|
802
|
+
const success = inspector.setVariableValue(varInfo, valueStr);
|
|
803
|
+
|
|
804
|
+
if (!success) {
|
|
805
|
+
throw new Error(`Failed to set variable "${name}" to "${valueStr}"`);
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// Refresh the UI to show the change immediately
|
|
809
|
+
if (basicWindow.renderVariables) {
|
|
810
|
+
basicWindow.renderVariables();
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// Get the new value to confirm
|
|
814
|
+
const updatedVars = inspector.getSimpleVariables();
|
|
815
|
+
const updatedVar = updatedVars.find((v) => v.name === name);
|
|
816
|
+
|
|
817
|
+
return {
|
|
818
|
+
success: true,
|
|
819
|
+
name: name,
|
|
820
|
+
oldValue: varInfo.value,
|
|
821
|
+
newValue: updatedVar ? updatedVar.value : value,
|
|
822
|
+
type: varInfo.type,
|
|
823
|
+
message: `Variable ${name} set to ${inspector.formatValue(updatedVar || varInfo)}`,
|
|
824
|
+
};
|
|
825
|
+
},
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* Step to next BASIC line when paused at a breakpoint
|
|
829
|
+
*/
|
|
830
|
+
basicProgramStepNext: async (args) => {
|
|
831
|
+
const wasmModule = window.emulator?.wasmModule;
|
|
832
|
+
if (!wasmModule) {
|
|
833
|
+
throw new Error("WASM module not available");
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
const emulator = window.emulator;
|
|
837
|
+
if (!emulator || !emulator.running) {
|
|
838
|
+
throw new Error("Emulator must be powered on");
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// Check if emulator is paused
|
|
842
|
+
const isPaused = wasmModule._isPaused();
|
|
843
|
+
if (!isPaused) {
|
|
844
|
+
throw new Error("Emulator must be paused at a breakpoint to step");
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// Check if we're at a BASIC breakpoint or in BASIC program
|
|
848
|
+
const isBasicBreakpointHit = wasmModule._isBasicBreakpointHit
|
|
849
|
+
? wasmModule._isBasicBreakpointHit()
|
|
850
|
+
: false;
|
|
851
|
+
const isBasicRunning = wasmModule._isBasicProgramRunning
|
|
852
|
+
? wasmModule._isBasicProgramRunning()
|
|
853
|
+
: false;
|
|
854
|
+
|
|
855
|
+
if (!isBasicBreakpointHit && !isBasicRunning) {
|
|
856
|
+
throw new Error("Not currently at a BASIC breakpoint");
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// Helper to read CURLIN (current line number) from zero page $75-$76
|
|
860
|
+
const readCurlin = () => {
|
|
861
|
+
const lo = wasmModule._readMemory(0x75);
|
|
862
|
+
const hi = wasmModule._readMemory(0x76);
|
|
863
|
+
return lo | (hi << 8);
|
|
864
|
+
};
|
|
865
|
+
|
|
866
|
+
// Get current line before stepping (read CURLIN from zero page)
|
|
867
|
+
const previousLine = readCurlin();
|
|
868
|
+
|
|
869
|
+
// Clear breakpoint hit flag
|
|
870
|
+
if (wasmModule._clearBasicBreakpointHit) {
|
|
871
|
+
wasmModule._clearBasicBreakpointHit();
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// Step to next BASIC line
|
|
875
|
+
if (wasmModule._stepBasicLine) {
|
|
876
|
+
wasmModule._stepBasicLine();
|
|
877
|
+
} else {
|
|
878
|
+
throw new Error("BASIC line stepping not supported");
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// Wait a brief moment for step to complete, then read new line
|
|
882
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
883
|
+
|
|
884
|
+
// Get new line after stepping (read CURLIN again)
|
|
885
|
+
const currentLine = readCurlin();
|
|
886
|
+
|
|
887
|
+
return {
|
|
888
|
+
success: true,
|
|
889
|
+
previousLine: previousLine,
|
|
890
|
+
currentLine: currentLine,
|
|
891
|
+
message: `Stepped from line ${previousLine} to line ${currentLine}`,
|
|
892
|
+
};
|
|
893
|
+
},
|
|
894
|
+
};
|