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,447 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* basic-variable-inspector.js - Parse and display Applesoft BASIC variables
|
|
3
|
+
*
|
|
4
|
+
* Written by
|
|
5
|
+
* Mike Daley <michael_daley@icloud.com>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Applesoft BASIC Variable Memory Layout:
|
|
10
|
+
* - Simple variables: VARTAB ($69-$6A) to ARYTAB ($6B-$6C)
|
|
11
|
+
* - Arrays: ARYTAB ($6B-$6C) to STREND ($6D-$6E)
|
|
12
|
+
*
|
|
13
|
+
* Variable name format (2 bytes):
|
|
14
|
+
* - First byte: First char (A-Z), bit 7 = integer type if set on BOTH bytes
|
|
15
|
+
* - Second byte: Second char (0-9, A-Z, or null), high bit set for string type
|
|
16
|
+
*
|
|
17
|
+
* Value format:
|
|
18
|
+
* - Real (5 bytes): Applesoft floating point
|
|
19
|
+
* - Integer (2 bytes): Signed 16-bit (high byte, low byte)
|
|
20
|
+
* - String (3 bytes): Length byte + 2-byte pointer to string data
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { peek, readWord } from "../utils/wasm-memory.js";
|
|
24
|
+
|
|
25
|
+
export class BasicVariableInspector {
|
|
26
|
+
constructor(wasmModule) {
|
|
27
|
+
this.wasmModule = wasmModule;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get all simple variables from memory
|
|
32
|
+
* @returns {Array<{name: string, type: string, value: any, rawValue: Uint8Array}>}
|
|
33
|
+
*/
|
|
34
|
+
getSimpleVariables() {
|
|
35
|
+
const variables = [];
|
|
36
|
+
|
|
37
|
+
const vartab = readWord(this.wasmModule,0x69);
|
|
38
|
+
const arytab = readWord(this.wasmModule,0x6b);
|
|
39
|
+
|
|
40
|
+
// No variables if pointers are invalid or equal (empty variable area)
|
|
41
|
+
if (vartab === 0 || arytab === 0 || vartab >= arytab) {
|
|
42
|
+
return variables;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Sanity check - variable area should be in reasonable range
|
|
46
|
+
if (vartab < 0x800 || arytab > 0xC000) {
|
|
47
|
+
return variables;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let addr = vartab;
|
|
51
|
+
while (addr < arytab) {
|
|
52
|
+
const varInfo = this._parseVariable(addr);
|
|
53
|
+
if (!varInfo) break;
|
|
54
|
+
|
|
55
|
+
variables.push(varInfo);
|
|
56
|
+
addr += varInfo.size;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return variables;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get all array variables from memory
|
|
64
|
+
* @returns {Array<{name: string, type: string, dimensions: number[], values: any[]}>}
|
|
65
|
+
*/
|
|
66
|
+
getArrayVariables() {
|
|
67
|
+
const arrays = [];
|
|
68
|
+
|
|
69
|
+
const arytab = readWord(this.wasmModule,0x6b);
|
|
70
|
+
const strend = readWord(this.wasmModule,0x6d);
|
|
71
|
+
|
|
72
|
+
if (arytab === 0 || strend === 0 || arytab >= strend) {
|
|
73
|
+
return arrays;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let addr = arytab;
|
|
77
|
+
while (addr < strend) {
|
|
78
|
+
const arrayInfo = this._parseArray(addr);
|
|
79
|
+
if (!arrayInfo) break;
|
|
80
|
+
|
|
81
|
+
arrays.push(arrayInfo);
|
|
82
|
+
addr += arrayInfo.totalSize;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return arrays;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Parse a single variable at the given address
|
|
90
|
+
*/
|
|
91
|
+
_parseVariable(addr) {
|
|
92
|
+
const byte1 = peek(this.wasmModule,addr);
|
|
93
|
+
const byte2 = peek(this.wasmModule,addr + 1);
|
|
94
|
+
|
|
95
|
+
if (byte1 === 0) return null;
|
|
96
|
+
|
|
97
|
+
const { name, type } = this._parseVariableName(byte1, byte2);
|
|
98
|
+
let value;
|
|
99
|
+
let rawValue;
|
|
100
|
+
let size;
|
|
101
|
+
|
|
102
|
+
if (type === "integer") {
|
|
103
|
+
// Integer: 2 bytes (high, low)
|
|
104
|
+
const high = peek(this.wasmModule,addr + 2);
|
|
105
|
+
const low = peek(this.wasmModule,addr + 3);
|
|
106
|
+
value = (high << 8) | low;
|
|
107
|
+
// Convert to signed
|
|
108
|
+
if (value >= 0x8000) value -= 0x10000;
|
|
109
|
+
rawValue = new Uint8Array([high, low]);
|
|
110
|
+
size = 7; // 2 name + 5 value (padded to match real size)
|
|
111
|
+
} else if (type === "string") {
|
|
112
|
+
// String: length + 2-byte pointer
|
|
113
|
+
const len = peek(this.wasmModule,addr + 2);
|
|
114
|
+
const ptrLow = peek(this.wasmModule,addr + 3);
|
|
115
|
+
const ptrHigh = peek(this.wasmModule,addr + 4);
|
|
116
|
+
const ptr = (ptrHigh << 8) | ptrLow;
|
|
117
|
+
value = this._readString(ptr, len);
|
|
118
|
+
rawValue = new Uint8Array([len, ptrLow, ptrHigh]);
|
|
119
|
+
size = 7; // 2 name + 3 value + 2 padding
|
|
120
|
+
} else {
|
|
121
|
+
// Real: 5-byte Applesoft float
|
|
122
|
+
const floatBytes = new Uint8Array(5);
|
|
123
|
+
for (let i = 0; i < 5; i++) {
|
|
124
|
+
floatBytes[i] = peek(this.wasmModule,addr + 2 + i);
|
|
125
|
+
}
|
|
126
|
+
value = this._decodeApplesoftFloat(floatBytes);
|
|
127
|
+
rawValue = floatBytes;
|
|
128
|
+
size = 7; // 2 name + 5 value
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return { name, type, value, rawValue, size, addr };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Parse an array variable header
|
|
136
|
+
*/
|
|
137
|
+
_parseArray(addr) {
|
|
138
|
+
const byte1 = peek(this.wasmModule,addr);
|
|
139
|
+
const byte2 = peek(this.wasmModule,addr + 1);
|
|
140
|
+
|
|
141
|
+
if (byte1 === 0) return null;
|
|
142
|
+
|
|
143
|
+
const { name, type } = this._parseVariableName(byte1, byte2);
|
|
144
|
+
|
|
145
|
+
// Total size of array entry (including header) - stored little-endian
|
|
146
|
+
const sizeLow = peek(this.wasmModule,addr + 2);
|
|
147
|
+
const sizeHigh = peek(this.wasmModule,addr + 3);
|
|
148
|
+
const totalSize = (sizeHigh << 8) | sizeLow;
|
|
149
|
+
|
|
150
|
+
// Number of dimensions
|
|
151
|
+
const numDims = peek(this.wasmModule,addr + 4);
|
|
152
|
+
|
|
153
|
+
// Read dimension sizes (2 bytes each, stored high-low)
|
|
154
|
+
const dimensions = [];
|
|
155
|
+
let dimAddr = addr + 5;
|
|
156
|
+
for (let i = 0; i < numDims; i++) {
|
|
157
|
+
const dimHigh = peek(this.wasmModule,dimAddr);
|
|
158
|
+
const dimLow = peek(this.wasmModule,dimAddr + 1);
|
|
159
|
+
dimensions.push((dimHigh << 8) | dimLow);
|
|
160
|
+
dimAddr += 2;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Calculate total elements
|
|
164
|
+
let totalElements = 1;
|
|
165
|
+
for (const dim of dimensions) {
|
|
166
|
+
totalElements *= dim;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Read values
|
|
170
|
+
const values = [];
|
|
171
|
+
let valueAddr = dimAddr;
|
|
172
|
+
const elementSize = type === "integer" ? 2 : type === "string" ? 3 : 5;
|
|
173
|
+
|
|
174
|
+
// Read all elements (Applesoft arrays are limited by memory anyway)
|
|
175
|
+
for (let i = 0; i < totalElements && i < 10000; i++) {
|
|
176
|
+
let elemValue;
|
|
177
|
+
if (type === "integer") {
|
|
178
|
+
const high = peek(this.wasmModule,valueAddr);
|
|
179
|
+
const low = peek(this.wasmModule,valueAddr + 1);
|
|
180
|
+
elemValue = (high << 8) | low;
|
|
181
|
+
if (elemValue >= 0x8000) elemValue -= 0x10000;
|
|
182
|
+
} else if (type === "string") {
|
|
183
|
+
const len = peek(this.wasmModule,valueAddr);
|
|
184
|
+
const ptrLow = peek(this.wasmModule,valueAddr + 1);
|
|
185
|
+
const ptrHigh = peek(this.wasmModule,valueAddr + 2);
|
|
186
|
+
const ptr = (ptrHigh << 8) | ptrLow;
|
|
187
|
+
elemValue = this._readString(ptr, len);
|
|
188
|
+
} else {
|
|
189
|
+
const floatBytes = new Uint8Array(5);
|
|
190
|
+
for (let j = 0; j < 5; j++) {
|
|
191
|
+
floatBytes[j] = peek(this.wasmModule,valueAddr + j);
|
|
192
|
+
}
|
|
193
|
+
elemValue = this._decodeApplesoftFloat(floatBytes);
|
|
194
|
+
}
|
|
195
|
+
values.push(elemValue);
|
|
196
|
+
valueAddr += elementSize;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
name,
|
|
201
|
+
type,
|
|
202
|
+
dimensions,
|
|
203
|
+
values,
|
|
204
|
+
totalSize,
|
|
205
|
+
totalElements,
|
|
206
|
+
addr,
|
|
207
|
+
numDims,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Parse variable name from two bytes
|
|
213
|
+
*/
|
|
214
|
+
_parseVariableName(byte1, byte2) {
|
|
215
|
+
// Extract characters (mask off high bits for char value)
|
|
216
|
+
const char1 = String.fromCharCode(byte1 & 0x7f);
|
|
217
|
+
const char2Raw = byte2 & 0x7f;
|
|
218
|
+
const char2 = char2Raw ? String.fromCharCode(char2Raw) : "";
|
|
219
|
+
|
|
220
|
+
// Determine type from high bits
|
|
221
|
+
// Integer: both high bits set
|
|
222
|
+
// String: second byte high bit set (but not both)
|
|
223
|
+
const isInteger = (byte1 & 0x80) !== 0 && (byte2 & 0x80) !== 0;
|
|
224
|
+
const isString = !isInteger && (byte2 & 0x80) !== 0;
|
|
225
|
+
|
|
226
|
+
let type = "real";
|
|
227
|
+
let suffix = "";
|
|
228
|
+
if (isInteger) {
|
|
229
|
+
type = "integer";
|
|
230
|
+
suffix = "%";
|
|
231
|
+
} else if (isString) {
|
|
232
|
+
type = "string";
|
|
233
|
+
suffix = "$";
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
name: char1 + char2 + suffix,
|
|
238
|
+
type,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Decode Applesoft floating point format
|
|
244
|
+
* Format: exponent (1 byte) + mantissa (4 bytes)
|
|
245
|
+
* Exponent: excess-128 (0 = zero value)
|
|
246
|
+
* Mantissa: normalized with implied leading 1, sign in bit 7 of first mantissa byte
|
|
247
|
+
*/
|
|
248
|
+
_decodeApplesoftFloat(bytes) {
|
|
249
|
+
const exp = bytes[0];
|
|
250
|
+
|
|
251
|
+
// Zero check
|
|
252
|
+
if (exp === 0) return 0;
|
|
253
|
+
|
|
254
|
+
// Sign is in bit 7 of mantissa byte 1
|
|
255
|
+
const sign = bytes[1] & 0x80 ? -1 : 1;
|
|
256
|
+
|
|
257
|
+
// Build mantissa as a number between 1 and 2 (normalized 1.xxxxx form)
|
|
258
|
+
// The implied leading 1 is not stored, so we start with 1.0
|
|
259
|
+
// Then add the fractional bits from the mantissa bytes
|
|
260
|
+
let mantissa = 1.0;
|
|
261
|
+
mantissa += (bytes[1] & 0x7F) / 128.0; // 7 bits: values 0.5, 0.25, 0.125, etc.
|
|
262
|
+
mantissa += bytes[2] / 32768.0; // next 8 bits
|
|
263
|
+
mantissa += bytes[3] / 8388608.0; // next 8 bits
|
|
264
|
+
mantissa += bytes[4] / 2147483648.0; // last 8 bits
|
|
265
|
+
|
|
266
|
+
// Applesoft uses excess-129 notation (the implied 1 is at position 2^-1, not 2^0)
|
|
267
|
+
// So the actual exponent is exp - 129
|
|
268
|
+
const actualExp = exp - 129;
|
|
269
|
+
const value = sign * mantissa * Math.pow(2, actualExp);
|
|
270
|
+
|
|
271
|
+
return value;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Read a string from memory
|
|
276
|
+
*/
|
|
277
|
+
_readString(ptr, len) {
|
|
278
|
+
if (len === 0 || ptr === 0) return "";
|
|
279
|
+
|
|
280
|
+
let str = "";
|
|
281
|
+
for (let i = 0; i < len; i++) {
|
|
282
|
+
const char = peek(this.wasmModule,ptr + i) & 0x7f;
|
|
283
|
+
str += String.fromCharCode(char);
|
|
284
|
+
}
|
|
285
|
+
return str;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Write a new value to a simple variable in memory
|
|
290
|
+
* @param {Object} varInfo - Variable info from getSimpleVariables() (must include addr and type)
|
|
291
|
+
* @param {string} newValueStr - New value as a string entered by the user
|
|
292
|
+
* @returns {boolean} true if the write succeeded
|
|
293
|
+
*/
|
|
294
|
+
setVariableValue(varInfo, newValueStr) {
|
|
295
|
+
const { addr, type } = varInfo;
|
|
296
|
+
const valueAddr = addr + 2; // skip 2-byte name
|
|
297
|
+
|
|
298
|
+
if (type === "integer") {
|
|
299
|
+
const parsed = parseInt(newValueStr, 10);
|
|
300
|
+
if (isNaN(parsed) || parsed < -32768 || parsed > 32767) return false;
|
|
301
|
+
const unsigned = parsed < 0 ? parsed + 0x10000 : parsed;
|
|
302
|
+
this.wasmModule._writeMemory(valueAddr, (unsigned >> 8) & 0xff);
|
|
303
|
+
this.wasmModule._writeMemory(valueAddr + 1, unsigned & 0xff);
|
|
304
|
+
return true;
|
|
305
|
+
} else if (type === "string") {
|
|
306
|
+
// String editing: write new characters into the existing string buffer
|
|
307
|
+
// We can only write up to the original allocated length
|
|
308
|
+
const origLen = peek(this.wasmModule, valueAddr);
|
|
309
|
+
const ptr = peek(this.wasmModule, valueAddr + 1) | (peek(this.wasmModule, valueAddr + 2) << 8);
|
|
310
|
+
// Strip surrounding quotes if present
|
|
311
|
+
let str = newValueStr;
|
|
312
|
+
if (str.startsWith('"') && str.endsWith('"')) str = str.slice(1, -1);
|
|
313
|
+
if (str.length > origLen) str = str.slice(0, origLen);
|
|
314
|
+
// Update length
|
|
315
|
+
this.wasmModule._writeMemory(valueAddr, str.length);
|
|
316
|
+
// Write characters
|
|
317
|
+
for (let i = 0; i < str.length; i++) {
|
|
318
|
+
this.wasmModule._writeMemory(ptr + i, str.charCodeAt(i) | 0x80);
|
|
319
|
+
}
|
|
320
|
+
return true;
|
|
321
|
+
} else {
|
|
322
|
+
// Real number
|
|
323
|
+
const parsed = parseFloat(newValueStr);
|
|
324
|
+
if (isNaN(parsed)) return false;
|
|
325
|
+
const bytes = this._encodeApplesoftFloat(parsed);
|
|
326
|
+
for (let i = 0; i < 5; i++) {
|
|
327
|
+
this.wasmModule._writeMemory(valueAddr + i, bytes[i]);
|
|
328
|
+
}
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Write a new value to an array element in memory
|
|
335
|
+
* @param {Object} info - { addr, type, numDims, elementIndex }
|
|
336
|
+
* addr: start address of the array entry in memory
|
|
337
|
+
* type: 'real', 'integer', or 'string'
|
|
338
|
+
* numDims: number of dimensions
|
|
339
|
+
* elementIndex: flat index of the element
|
|
340
|
+
* @param {string} newValueStr - New value as a string
|
|
341
|
+
* @returns {boolean} true if the write succeeded
|
|
342
|
+
*/
|
|
343
|
+
setArrayElementValue(info, newValueStr) {
|
|
344
|
+
const { addr, type, numDims, elementIndex } = info;
|
|
345
|
+
const elementSize = type === "integer" ? 2 : type === "string" ? 3 : 5;
|
|
346
|
+
const dataStart = addr + 5 + numDims * 2;
|
|
347
|
+
const elemAddr = dataStart + elementIndex * elementSize;
|
|
348
|
+
|
|
349
|
+
if (type === "integer") {
|
|
350
|
+
const parsed = parseInt(newValueStr, 10);
|
|
351
|
+
if (isNaN(parsed) || parsed < -32768 || parsed > 32767) return false;
|
|
352
|
+
const unsigned = parsed < 0 ? parsed + 0x10000 : parsed;
|
|
353
|
+
this.wasmModule._writeMemory(elemAddr, (unsigned >> 8) & 0xff);
|
|
354
|
+
this.wasmModule._writeMemory(elemAddr + 1, unsigned & 0xff);
|
|
355
|
+
return true;
|
|
356
|
+
} else if (type === "string") {
|
|
357
|
+
const origLen = peek(this.wasmModule, elemAddr);
|
|
358
|
+
const ptr = peek(this.wasmModule, elemAddr + 1) | (peek(this.wasmModule, elemAddr + 2) << 8);
|
|
359
|
+
let str = newValueStr;
|
|
360
|
+
if (str.startsWith('"') && str.endsWith('"')) str = str.slice(1, -1);
|
|
361
|
+
if (str.length > origLen) str = str.slice(0, origLen);
|
|
362
|
+
this.wasmModule._writeMemory(elemAddr, str.length);
|
|
363
|
+
for (let i = 0; i < str.length; i++) {
|
|
364
|
+
this.wasmModule._writeMemory(ptr + i, str.charCodeAt(i) | 0x80);
|
|
365
|
+
}
|
|
366
|
+
return true;
|
|
367
|
+
} else {
|
|
368
|
+
const parsed = parseFloat(newValueStr);
|
|
369
|
+
if (isNaN(parsed)) return false;
|
|
370
|
+
const bytes = this._encodeApplesoftFloat(parsed);
|
|
371
|
+
for (let i = 0; i < 5; i++) {
|
|
372
|
+
this.wasmModule._writeMemory(elemAddr + i, bytes[i]);
|
|
373
|
+
}
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Encode a JavaScript number into 5-byte Applesoft floating point format
|
|
380
|
+
*/
|
|
381
|
+
_encodeApplesoftFloat(value) {
|
|
382
|
+
const bytes = new Uint8Array(5);
|
|
383
|
+
if (value === 0) return bytes; // all zeros = 0.0
|
|
384
|
+
|
|
385
|
+
const sign = value < 0 ? 1 : 0;
|
|
386
|
+
let abs = Math.abs(value);
|
|
387
|
+
|
|
388
|
+
// Find exponent: normalize so 1.0 <= mantissa < 2.0
|
|
389
|
+
let exp = Math.floor(Math.log2(abs));
|
|
390
|
+
let mantissa = abs / Math.pow(2, exp);
|
|
391
|
+
|
|
392
|
+
// Adjust if mantissa is out of range due to floating point
|
|
393
|
+
if (mantissa < 1.0) { mantissa *= 2; exp--; }
|
|
394
|
+
if (mantissa >= 2.0) { mantissa /= 2; exp++; }
|
|
395
|
+
|
|
396
|
+
// Applesoft exponent is excess-129
|
|
397
|
+
const expByte = exp + 129;
|
|
398
|
+
if (expByte <= 0 || expByte > 255) return bytes; // underflow/overflow -> 0
|
|
399
|
+
|
|
400
|
+
bytes[0] = expByte;
|
|
401
|
+
|
|
402
|
+
// Remove the implicit leading 1
|
|
403
|
+
mantissa -= 1.0;
|
|
404
|
+
|
|
405
|
+
// Encode 31 bits of fractional mantissa across bytes[1..4]
|
|
406
|
+
// byte[1] has 7 fraction bits + sign in bit 7
|
|
407
|
+
mantissa *= 128; // 2^7
|
|
408
|
+
bytes[1] = (Math.floor(mantissa) & 0x7f) | (sign << 7);
|
|
409
|
+
mantissa -= Math.floor(mantissa);
|
|
410
|
+
|
|
411
|
+
mantissa *= 256;
|
|
412
|
+
bytes[2] = Math.floor(mantissa) & 0xff;
|
|
413
|
+
mantissa -= Math.floor(mantissa);
|
|
414
|
+
|
|
415
|
+
mantissa *= 256;
|
|
416
|
+
bytes[3] = Math.floor(mantissa) & 0xff;
|
|
417
|
+
mantissa -= Math.floor(mantissa);
|
|
418
|
+
|
|
419
|
+
mantissa *= 256;
|
|
420
|
+
bytes[4] = Math.round(mantissa) & 0xff;
|
|
421
|
+
|
|
422
|
+
return bytes;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Format a value for display
|
|
427
|
+
*/
|
|
428
|
+
formatValue(variable) {
|
|
429
|
+
if (variable.type === "string") {
|
|
430
|
+
return `"${variable.value}"`;
|
|
431
|
+
} else if (variable.type === "integer") {
|
|
432
|
+
return variable.value.toString();
|
|
433
|
+
} else {
|
|
434
|
+
// Real number
|
|
435
|
+
if (Number.isInteger(variable.value)) {
|
|
436
|
+
return variable.value.toString();
|
|
437
|
+
}
|
|
438
|
+
// Format with reasonable precision
|
|
439
|
+
const absVal = Math.abs(variable.value);
|
|
440
|
+
if (absVal === 0) return "0";
|
|
441
|
+
if (absVal >= 0.01 && absVal < 1e7) {
|
|
442
|
+
return variable.value.toPrecision(9).replace(/\.?0+$/, "");
|
|
443
|
+
}
|
|
444
|
+
return variable.value.toExponential(6);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|