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,473 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* basic-highlighting.js - BASIC syntax highlighting with smart formatting
|
|
3
|
+
*
|
|
4
|
+
* Written by
|
|
5
|
+
* Mike Daley <michael_daley@icloud.com>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { escapeHtml } from "./string-utils.js";
|
|
9
|
+
import { APPLESOFT_TOKENS } from "./basic-tokens.js";
|
|
10
|
+
|
|
11
|
+
// BASIC keyword categories for syntax highlighting
|
|
12
|
+
// Colors follow the Apple II rainbow theme (Green, Yellow, Orange, Red, Purple, Blue)
|
|
13
|
+
export const BASIC_CATEGORIES = {
|
|
14
|
+
// RED - Flow control that changes execution path
|
|
15
|
+
flow: [
|
|
16
|
+
"GOTO", "GOSUB", "RETURN", "IF", "THEN", "ON", "ONERR", "RESUME",
|
|
17
|
+
"END", "STOP", "RUN",
|
|
18
|
+
],
|
|
19
|
+
// PURPLE - Loop constructs
|
|
20
|
+
loop: ["FOR", "TO", "STEP", "NEXT"],
|
|
21
|
+
// BLUE - Input/Output operations
|
|
22
|
+
io: ["PRINT", "INPUT", "GET", "DATA", "READ", "RESTORE"],
|
|
23
|
+
// GREEN - Graphics and display
|
|
24
|
+
graphics: [
|
|
25
|
+
"GR", "HGR", "HGR2", "TEXT", "PLOT", "HPLOT", "HLIN", "VLIN",
|
|
26
|
+
"COLOR=", "HCOLOR=", "DRAW", "XDRAW", "ROT=", "SCALE=", "SCRN(",
|
|
27
|
+
"HOME", "HTAB", "VTAB", "NORMAL", "INVERSE", "FLASH",
|
|
28
|
+
],
|
|
29
|
+
// ORANGE - Memory and system
|
|
30
|
+
memory: ["PEEK", "POKE", "CALL", "HIMEM:", "LOMEM:", "USR", "DEF", "FN"],
|
|
31
|
+
// CYAN - Built-in functions
|
|
32
|
+
functions: [
|
|
33
|
+
"SGN", "INT", "ABS", "SQR", "RND", "LOG", "EXP", "COS", "SIN",
|
|
34
|
+
"TAN", "ATN", "LEN", "ASC", "VAL", "STR$", "CHR$", "LEFT$",
|
|
35
|
+
"RIGHT$", "MID$", "FRE", "PDL", "POS", "TAB(", "SPC(",
|
|
36
|
+
],
|
|
37
|
+
// YELLOW - Variable declarations
|
|
38
|
+
variable: ["DIM", "LET", "DEL", "NEW", "CLR", "CLEAR"],
|
|
39
|
+
// MUTED - Miscellaneous
|
|
40
|
+
misc: [
|
|
41
|
+
"REM", "LOAD", "SAVE", "SHLOAD", "STORE", "RECALL", "PR#", "IN#",
|
|
42
|
+
"WAIT", "CONT", "LIST", "TRACE", "NOTRACE", "SPEED=", "POP",
|
|
43
|
+
"NOT", "AND", "OR", "&",
|
|
44
|
+
],
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Keywords that increase indentation level
|
|
48
|
+
const INDENT_INCREASE = new Set(["FOR", "GOSUB"]);
|
|
49
|
+
// Keywords that decrease indentation level (before the line)
|
|
50
|
+
const INDENT_DECREASE = new Set(["NEXT", "RETURN"]);
|
|
51
|
+
|
|
52
|
+
// Build a set of all keywords for quick lookup
|
|
53
|
+
const ALL_KEYWORDS = new Set();
|
|
54
|
+
for (const category of Object.values(BASIC_CATEGORIES)) {
|
|
55
|
+
for (const keyword of category) {
|
|
56
|
+
ALL_KEYWORDS.add(keyword);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Also add any tokens not in categories
|
|
61
|
+
for (const token of APPLESOFT_TOKENS) {
|
|
62
|
+
if (token && token.length > 0) {
|
|
63
|
+
ALL_KEYWORDS.add(token);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get CSS class for a BASIC keyword
|
|
69
|
+
* @param {string} keyword - The keyword (uppercase)
|
|
70
|
+
* @returns {string} CSS class name
|
|
71
|
+
*/
|
|
72
|
+
export function getBasicKeywordClass(keyword) {
|
|
73
|
+
const kw = keyword.trim().toUpperCase();
|
|
74
|
+
if (BASIC_CATEGORIES.flow.includes(kw)) return "bas-flow";
|
|
75
|
+
if (BASIC_CATEGORIES.loop.includes(kw)) return "bas-loop";
|
|
76
|
+
if (BASIC_CATEGORIES.io.includes(kw)) return "bas-io";
|
|
77
|
+
if (BASIC_CATEGORIES.graphics.includes(kw)) return "bas-graphics";
|
|
78
|
+
if (BASIC_CATEGORIES.memory.includes(kw)) return "bas-memory";
|
|
79
|
+
if (BASIC_CATEGORIES.functions.includes(kw)) return "bas-func";
|
|
80
|
+
if (BASIC_CATEGORIES.variable.includes(kw)) return "bas-var";
|
|
81
|
+
if (BASIC_CATEGORIES.misc.includes(kw)) return "bas-misc";
|
|
82
|
+
return "bas-keyword";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Sort keywords by length (longest first) for matching
|
|
86
|
+
const SORTED_KEYWORDS = Array.from(ALL_KEYWORDS)
|
|
87
|
+
.filter(k => k.length > 1 || /[&]/.test(k)) // Include & but not single operators
|
|
88
|
+
.sort((a, b) => b.length - a.length);
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Calculate the indentation level for each line based on control structures
|
|
92
|
+
* Handles multiple FOR/NEXT on the same line (e.g., FOR A=1 TO 2:FOR B=1 TO 2)
|
|
93
|
+
* @param {string[]} lines - Array of BASIC lines
|
|
94
|
+
* @returns {number[]} Array of indentation levels (0-8)
|
|
95
|
+
*/
|
|
96
|
+
function calculateIndentLevels(lines) {
|
|
97
|
+
const levels = [];
|
|
98
|
+
let currentLevel = 0;
|
|
99
|
+
|
|
100
|
+
for (const line of lines) {
|
|
101
|
+
const upper = line.toUpperCase();
|
|
102
|
+
|
|
103
|
+
// Count all FOR and NEXT occurrences on this line
|
|
104
|
+
const forMatches = upper.match(/\bFOR\b/g);
|
|
105
|
+
const nextMatches = upper.match(/\bNEXT\b/g);
|
|
106
|
+
const forCount = forMatches ? forMatches.length : 0;
|
|
107
|
+
const nextCount = nextMatches ? nextMatches.length : 0;
|
|
108
|
+
|
|
109
|
+
// Decrease level for each NEXT (before rendering the line)
|
|
110
|
+
// Only decrease by NEXTs not paired with FORs on this same line
|
|
111
|
+
const unpaired = Math.max(0, nextCount - forCount);
|
|
112
|
+
if (unpaired > 0) {
|
|
113
|
+
currentLevel = Math.max(0, currentLevel - unpaired);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
levels.push(Math.min(currentLevel, 8)); // Cap at 8 levels
|
|
117
|
+
|
|
118
|
+
// Increase level for each FOR not closed by a NEXT on the same line
|
|
119
|
+
const netOpen = Math.max(0, forCount - nextCount);
|
|
120
|
+
if (netOpen > 0) {
|
|
121
|
+
currentLevel += netOpen;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return levels;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Highlight BASIC source code (text input, not tokenized)
|
|
130
|
+
* @param {string} source - BASIC source code
|
|
131
|
+
* @param {Object} options - Options
|
|
132
|
+
* @param {boolean} options.preserveCase - Keep original case (default: false, converts to uppercase)
|
|
133
|
+
* @returns {string} HTML with syntax highlighting spans
|
|
134
|
+
*/
|
|
135
|
+
export function highlightBasicSource(source, options = {}) {
|
|
136
|
+
const { preserveCase = false } = options;
|
|
137
|
+
const lines = source.split(/\r?\n/);
|
|
138
|
+
const highlightedLines = [];
|
|
139
|
+
|
|
140
|
+
// Calculate indent levels for all lines
|
|
141
|
+
const indentLevels = calculateIndentLevels(lines);
|
|
142
|
+
|
|
143
|
+
for (let i = 0; i < lines.length; i++) {
|
|
144
|
+
const highlighted = highlightBasicLine(lines[i], preserveCase);
|
|
145
|
+
const indentClass = indentLevels[i] > 0 ? ` indent-${indentLevels[i]}` : "";
|
|
146
|
+
// We don't wrap here - the wrapper will add indentation class if needed
|
|
147
|
+
highlightedLines.push({ html: highlighted, indent: indentLevels[i] });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Return just the HTML - caller will handle indent classes
|
|
151
|
+
return highlightedLines.map(l => l.html).join("\n");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Highlight BASIC source with indent information
|
|
156
|
+
* @param {string} source - BASIC source code
|
|
157
|
+
* @param {Object} options - Options
|
|
158
|
+
* @param {boolean} options.preserveCase - Keep original case (default: false)
|
|
159
|
+
* @returns {{html: string, indent: number}[]} Array of highlighted lines with indent levels
|
|
160
|
+
*/
|
|
161
|
+
export function highlightBasicSourceWithIndent(source, options = {}) {
|
|
162
|
+
const { preserveCase = false } = options;
|
|
163
|
+
const lines = source.split(/\r?\n/);
|
|
164
|
+
const indentLevels = calculateIndentLevels(lines);
|
|
165
|
+
const result = [];
|
|
166
|
+
|
|
167
|
+
for (let i = 0; i < lines.length; i++) {
|
|
168
|
+
result.push({
|
|
169
|
+
html: highlightBasicLine(lines[i], preserveCase),
|
|
170
|
+
indent: indentLevels[i],
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Auto-format BASIC source code
|
|
179
|
+
* - Sorts lines by line number (so changing a line number moves it to correct position)
|
|
180
|
+
* - Right-aligns line numbers to consistent width
|
|
181
|
+
* - Adds indentation for control structures (FOR/NEXT loops)
|
|
182
|
+
* @param {string} source - BASIC source code
|
|
183
|
+
* @returns {string} Formatted source code
|
|
184
|
+
*/
|
|
185
|
+
export function formatBasicSource(source) {
|
|
186
|
+
const rawLines = source.split(/\r?\n/);
|
|
187
|
+
|
|
188
|
+
// Parse lines and extract line numbers for sorting
|
|
189
|
+
const parsedLines = [];
|
|
190
|
+
for (const line of rawLines) {
|
|
191
|
+
const trimmed = line.trim();
|
|
192
|
+
if (!trimmed) continue; // Skip empty lines
|
|
193
|
+
|
|
194
|
+
const match = trimmed.match(/^(\d+)\s*(.*)/);
|
|
195
|
+
if (match) {
|
|
196
|
+
parsedLines.push({
|
|
197
|
+
lineNumber: parseInt(match[1], 10),
|
|
198
|
+
lineNumStr: match[1],
|
|
199
|
+
code: match[2] || "",
|
|
200
|
+
});
|
|
201
|
+
} else {
|
|
202
|
+
// Lines without line numbers are kept but won't be sorted
|
|
203
|
+
// (unusual in BASIC but handle gracefully)
|
|
204
|
+
parsedLines.push({
|
|
205
|
+
lineNumber: -1, // Sort to top
|
|
206
|
+
lineNumStr: null,
|
|
207
|
+
code: trimmed,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Sort by line number (lines without numbers stay at top)
|
|
213
|
+
parsedLines.sort((a, b) => a.lineNumber - b.lineNumber);
|
|
214
|
+
|
|
215
|
+
// Rebuild lines array for indent calculation
|
|
216
|
+
const sortedLines = parsedLines.map((p) =>
|
|
217
|
+
p.lineNumStr ? `${p.lineNumStr} ${p.code}` : p.code
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
// Calculate indentation on sorted lines
|
|
221
|
+
const indentLevels = calculateIndentLevels(sortedLines);
|
|
222
|
+
|
|
223
|
+
// Find max line number width for alignment
|
|
224
|
+
let maxLineNumWidth = 0;
|
|
225
|
+
for (const p of parsedLines) {
|
|
226
|
+
if (p.lineNumStr) {
|
|
227
|
+
maxLineNumWidth = Math.max(maxLineNumWidth, p.lineNumStr.length);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Format each line
|
|
232
|
+
const formattedLines = [];
|
|
233
|
+
for (let i = 0; i < sortedLines.length; i++) {
|
|
234
|
+
formattedLines.push(formatBasicLine(sortedLines[i], maxLineNumWidth, indentLevels[i]));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return formattedLines.join("\n");
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Indent string constant (2 spaces per level)
|
|
241
|
+
const INDENT_CHARS = " ";
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Convert BASIC code to uppercase while preserving string contents
|
|
245
|
+
* @param {string} code - BASIC code
|
|
246
|
+
* @returns {string} Code with keywords/variables uppercase, strings preserved
|
|
247
|
+
*/
|
|
248
|
+
function toUppercasePreservingStrings(code) {
|
|
249
|
+
let result = "";
|
|
250
|
+
let inString = false;
|
|
251
|
+
|
|
252
|
+
for (let i = 0; i < code.length; i++) {
|
|
253
|
+
const char = code[i];
|
|
254
|
+
|
|
255
|
+
if (char === '"') {
|
|
256
|
+
inString = !inString;
|
|
257
|
+
result += char;
|
|
258
|
+
} else if (inString) {
|
|
259
|
+
// Inside string - preserve case
|
|
260
|
+
result += char;
|
|
261
|
+
} else {
|
|
262
|
+
// Outside string - convert to uppercase
|
|
263
|
+
result += char.toUpperCase();
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return result;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Format a single line of BASIC source
|
|
272
|
+
* @param {string} line - Single line of BASIC
|
|
273
|
+
* @param {number} maxLineNumWidth - Width to pad line numbers to
|
|
274
|
+
* @param {number} indentLevel - Indentation level (0-8)
|
|
275
|
+
* @returns {string} Formatted line
|
|
276
|
+
*/
|
|
277
|
+
function formatBasicLine(line, maxLineNumWidth, indentLevel) {
|
|
278
|
+
const trimmed = line.trim();
|
|
279
|
+
if (!trimmed) {
|
|
280
|
+
return "";
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Parse line number and code
|
|
284
|
+
const match = trimmed.match(/^(\d+)\s*(.*)/);
|
|
285
|
+
|
|
286
|
+
if (match) {
|
|
287
|
+
const lineNum = match[1];
|
|
288
|
+
const code = toUppercasePreservingStrings(match[2]);
|
|
289
|
+
|
|
290
|
+
// Right-align line number by left-padding with spaces
|
|
291
|
+
const padding = " ".repeat(Math.max(0, maxLineNumWidth - lineNum.length));
|
|
292
|
+
|
|
293
|
+
// Add indentation
|
|
294
|
+
const indent = indentLevel > 0 ? INDENT_CHARS.repeat(indentLevel) : "";
|
|
295
|
+
|
|
296
|
+
return `${padding}${lineNum} ${indent}${code}`;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// No line number - just return trimmed with indent (also uppercase)
|
|
300
|
+
const indent = indentLevel > 0 ? INDENT_CHARS.repeat(indentLevel) : "";
|
|
301
|
+
return `${indent}${toUppercasePreservingStrings(trimmed)}`;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Highlight a single line of BASIC source
|
|
306
|
+
* @param {string} line - Single line of BASIC
|
|
307
|
+
* @param {boolean} preserveCase - Keep original case
|
|
308
|
+
* @returns {string} HTML with syntax highlighting
|
|
309
|
+
*/
|
|
310
|
+
function highlightBasicLine(line, preserveCase) {
|
|
311
|
+
if (!line.trim()) {
|
|
312
|
+
return escapeHtml(line);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const upper = line.toUpperCase();
|
|
316
|
+
let result = "";
|
|
317
|
+
let i = 0;
|
|
318
|
+
|
|
319
|
+
// Check for line number at start
|
|
320
|
+
const lineNumMatch = upper.match(/^(\s*)(\d+)(\s*)/);
|
|
321
|
+
if (lineNumMatch) {
|
|
322
|
+
result += escapeHtml(lineNumMatch[1]); // Leading whitespace
|
|
323
|
+
result += `<span class="bas-linenum">${lineNumMatch[2]}</span>`;
|
|
324
|
+
result += escapeHtml(lineNumMatch[3]); // Trailing whitespace
|
|
325
|
+
i = lineNumMatch[0].length;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
let inString = false;
|
|
329
|
+
let inRem = false;
|
|
330
|
+
let stringStart = -1;
|
|
331
|
+
let remStart = -1;
|
|
332
|
+
|
|
333
|
+
while (i < line.length) {
|
|
334
|
+
const char = line[i];
|
|
335
|
+
const upperChar = upper[i];
|
|
336
|
+
|
|
337
|
+
// Handle REM - rest of line is comment
|
|
338
|
+
if (inRem) {
|
|
339
|
+
if (remStart === -1) remStart = i;
|
|
340
|
+
i++;
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Handle strings
|
|
345
|
+
if (char === '"') {
|
|
346
|
+
if (inString) {
|
|
347
|
+
// End of string
|
|
348
|
+
const str = line.substring(stringStart, i + 1);
|
|
349
|
+
result += `<span class="bas-string">${escapeHtml(str)}</span>`;
|
|
350
|
+
inString = false;
|
|
351
|
+
stringStart = -1;
|
|
352
|
+
i++;
|
|
353
|
+
continue;
|
|
354
|
+
} else {
|
|
355
|
+
// Start of string
|
|
356
|
+
inString = true;
|
|
357
|
+
stringStart = i;
|
|
358
|
+
i++;
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (inString) {
|
|
364
|
+
i++;
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Try to match a keyword
|
|
369
|
+
const remaining = upper.substring(i);
|
|
370
|
+
let matched = false;
|
|
371
|
+
|
|
372
|
+
for (const keyword of SORTED_KEYWORDS) {
|
|
373
|
+
if (remaining.startsWith(keyword)) {
|
|
374
|
+
// Check it's not part of a longer identifier
|
|
375
|
+
const nextChar = remaining[keyword.length];
|
|
376
|
+
const isKeywordEnd = !nextChar || !/[A-Z0-9]/.test(nextChar);
|
|
377
|
+
const isSpecial = /[=(:&]$/.test(keyword) || keyword === "&";
|
|
378
|
+
|
|
379
|
+
if (isKeywordEnd || isSpecial) {
|
|
380
|
+
const originalCase = preserveCase ? line.substring(i, i + keyword.length) : keyword;
|
|
381
|
+
const kwClass = getBasicKeywordClass(keyword);
|
|
382
|
+
result += `<span class="${kwClass}">${escapeHtml(originalCase)}</span>`;
|
|
383
|
+
i += keyword.length;
|
|
384
|
+
matched = true;
|
|
385
|
+
|
|
386
|
+
// Check for REM
|
|
387
|
+
if (keyword === "REM") {
|
|
388
|
+
inRem = true;
|
|
389
|
+
remStart = i;
|
|
390
|
+
}
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (matched) continue;
|
|
397
|
+
|
|
398
|
+
// Check for numbers
|
|
399
|
+
if (/[0-9]/.test(upperChar)) {
|
|
400
|
+
let num = char;
|
|
401
|
+
let j = i + 1;
|
|
402
|
+
while (j < line.length && /[0-9.]/.test(line[j])) {
|
|
403
|
+
num += line[j];
|
|
404
|
+
j++;
|
|
405
|
+
}
|
|
406
|
+
// Check for E notation
|
|
407
|
+
if (j < line.length && /[Ee]/.test(line[j])) {
|
|
408
|
+
num += line[j];
|
|
409
|
+
j++;
|
|
410
|
+
if (j < line.length && /[+-]/.test(line[j])) {
|
|
411
|
+
num += line[j];
|
|
412
|
+
j++;
|
|
413
|
+
}
|
|
414
|
+
while (j < line.length && /[0-9]/.test(line[j])) {
|
|
415
|
+
num += line[j];
|
|
416
|
+
j++;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
result += `<span class="bas-number">${escapeHtml(num)}</span>`;
|
|
420
|
+
i = j;
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Check for variable names
|
|
425
|
+
if (/[A-Za-z]/.test(char)) {
|
|
426
|
+
let varName = char;
|
|
427
|
+
let j = i + 1;
|
|
428
|
+
while (j < line.length && /[A-Za-z0-9]/.test(line[j])) {
|
|
429
|
+
varName += line[j];
|
|
430
|
+
j++;
|
|
431
|
+
}
|
|
432
|
+
// Check for type suffix
|
|
433
|
+
if (j < line.length && /[$%]/.test(line[j])) {
|
|
434
|
+
varName += line[j];
|
|
435
|
+
j++;
|
|
436
|
+
}
|
|
437
|
+
const displayName = preserveCase ? varName : varName.toUpperCase();
|
|
438
|
+
result += `<span class="bas-variable">${escapeHtml(displayName)}</span>`;
|
|
439
|
+
i = j;
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Check for operators
|
|
444
|
+
if ("+-*/^=<>".includes(char)) {
|
|
445
|
+
result += `<span class="bas-operator">${escapeHtml(char)}</span>`;
|
|
446
|
+
i++;
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Check for punctuation
|
|
451
|
+
if ("(),;:".includes(char)) {
|
|
452
|
+
result += `<span class="bas-punct">${escapeHtml(char)}</span>`;
|
|
453
|
+
i++;
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Default: just output the character
|
|
458
|
+
result += escapeHtml(char);
|
|
459
|
+
i++;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Flush remaining content
|
|
463
|
+
if (inString && stringStart !== -1) {
|
|
464
|
+
const str = line.substring(stringStart);
|
|
465
|
+
result += `<span class="bas-string">${escapeHtml(str)}</span>`;
|
|
466
|
+
}
|
|
467
|
+
if (inRem && remStart !== -1) {
|
|
468
|
+
const rem = line.substring(remStart);
|
|
469
|
+
result += `<span class="bas-comment">${escapeHtml(rem)}</span>`;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return result;
|
|
473
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* basic-tokenizer.js - Applesoft BASIC tokenizer for direct memory insertion
|
|
3
|
+
*
|
|
4
|
+
* Written by
|
|
5
|
+
* Mike Daley <michael_daley@icloud.com>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { APPLESOFT_TOKENS } from './basic-tokens.js';
|
|
9
|
+
|
|
10
|
+
// Build a lookup from keyword string to token byte value.
|
|
11
|
+
// Sort by length descending for greedy longest-match.
|
|
12
|
+
const KEYWORD_LIST = APPLESOFT_TOKENS
|
|
13
|
+
.map((kw, i) => ({ keyword: kw, token: 0x80 + i }))
|
|
14
|
+
.sort((a, b) => b.keyword.length - a.keyword.length);
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Tokenize a single BASIC line's content (without the line number).
|
|
18
|
+
* @param {string} text - The line content (e.g. "PRINT \"HELLO\":GOTO 10")
|
|
19
|
+
* @returns {Uint8Array} Tokenized bytes
|
|
20
|
+
*/
|
|
21
|
+
export function tokenizeLine(text) {
|
|
22
|
+
const bytes = [];
|
|
23
|
+
const upper = text.toUpperCase();
|
|
24
|
+
let i = 0;
|
|
25
|
+
let inRem = false;
|
|
26
|
+
let inData = false;
|
|
27
|
+
let inQuote = false;
|
|
28
|
+
|
|
29
|
+
while (i < upper.length) {
|
|
30
|
+
const ch = upper[i];
|
|
31
|
+
|
|
32
|
+
// Inside a quoted string - emit as-is until closing quote
|
|
33
|
+
if (inQuote) {
|
|
34
|
+
bytes.push(text.charCodeAt(i));
|
|
35
|
+
if (ch === '"') {
|
|
36
|
+
inQuote = false;
|
|
37
|
+
}
|
|
38
|
+
i++;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// After REM token - emit rest of line as raw ASCII
|
|
43
|
+
if (inRem) {
|
|
44
|
+
bytes.push(text.charCodeAt(i));
|
|
45
|
+
i++;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// After DATA token - emit as-is until colon
|
|
50
|
+
if (inData) {
|
|
51
|
+
if (ch === ':') {
|
|
52
|
+
inData = false;
|
|
53
|
+
// Fall through to normal processing for the colon
|
|
54
|
+
} else {
|
|
55
|
+
bytes.push(text.charCodeAt(i));
|
|
56
|
+
i++;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Opening quote
|
|
62
|
+
if (ch === '"') {
|
|
63
|
+
inQuote = true;
|
|
64
|
+
bytes.push(text.charCodeAt(i));
|
|
65
|
+
i++;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ? is shorthand for PRINT
|
|
70
|
+
if (ch === '?') {
|
|
71
|
+
bytes.push(0xBA); // PRINT token
|
|
72
|
+
i++;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Try greedy longest-match against keywords
|
|
77
|
+
let matched = false;
|
|
78
|
+
const remaining = upper.substring(i);
|
|
79
|
+
|
|
80
|
+
for (const { keyword, token } of KEYWORD_LIST) {
|
|
81
|
+
if (remaining.startsWith(keyword)) {
|
|
82
|
+
bytes.push(token);
|
|
83
|
+
i += keyword.length;
|
|
84
|
+
matched = true;
|
|
85
|
+
|
|
86
|
+
if (token === 0xB2) { // REM
|
|
87
|
+
inRem = true;
|
|
88
|
+
} else if (token === 0x83) { // DATA
|
|
89
|
+
inData = true;
|
|
90
|
+
}
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!matched) {
|
|
96
|
+
// Emit character as ASCII byte
|
|
97
|
+
bytes.push(text.charCodeAt(i));
|
|
98
|
+
i++;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return new Uint8Array(bytes);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Build a full tokenized Applesoft BASIC program in memory format.
|
|
107
|
+
* @param {Array<{lineNumber: number, content: string}>} lines - Parsed program lines
|
|
108
|
+
* @param {number} [txttab=0x0801] - Starting address of the program
|
|
109
|
+
* @returns {{ bytes: Uint8Array, endAddr: number }}
|
|
110
|
+
*/
|
|
111
|
+
export function tokenizeProgram(lines, txttab = 0x0801) {
|
|
112
|
+
// First pass: tokenize all lines and compute sizes
|
|
113
|
+
const tokenizedLines = lines.map(line => {
|
|
114
|
+
const tokenBytes = tokenizeLine(line.content);
|
|
115
|
+
// Each line: [next-ptr:2] [line-num:2] [token-bytes...] [0x00]
|
|
116
|
+
const lineSize = 2 + 2 + tokenBytes.length + 1;
|
|
117
|
+
return { lineNumber: line.lineNumber, tokenBytes, lineSize };
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Calculate total size: all lines + end marker (0x00, 0x00)
|
|
121
|
+
const totalSize = tokenizedLines.reduce((sum, l) => sum + l.lineSize, 0) + 2;
|
|
122
|
+
const bytes = new Uint8Array(totalSize);
|
|
123
|
+
let offset = 0;
|
|
124
|
+
let addr = txttab;
|
|
125
|
+
|
|
126
|
+
// Second pass: write bytes with correct next-pointers
|
|
127
|
+
for (const line of tokenizedLines) {
|
|
128
|
+
const nextAddr = addr + line.lineSize;
|
|
129
|
+
|
|
130
|
+
// Next-pointer (little-endian)
|
|
131
|
+
bytes[offset] = nextAddr & 0xFF;
|
|
132
|
+
bytes[offset + 1] = (nextAddr >> 8) & 0xFF;
|
|
133
|
+
|
|
134
|
+
// Line number (little-endian)
|
|
135
|
+
bytes[offset + 2] = line.lineNumber & 0xFF;
|
|
136
|
+
bytes[offset + 3] = (line.lineNumber >> 8) & 0xFF;
|
|
137
|
+
|
|
138
|
+
// Token bytes
|
|
139
|
+
bytes.set(line.tokenBytes, offset + 4);
|
|
140
|
+
|
|
141
|
+
// Line terminator
|
|
142
|
+
bytes[offset + 4 + line.tokenBytes.length] = 0x00;
|
|
143
|
+
|
|
144
|
+
offset += line.lineSize;
|
|
145
|
+
addr = nextAddr;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// End-of-program marker
|
|
149
|
+
bytes[offset] = 0x00;
|
|
150
|
+
bytes[offset + 1] = 0x00;
|
|
151
|
+
|
|
152
|
+
return { bytes, endAddr: addr + 2 };
|
|
153
|
+
}
|