From e7f35c21ff00880fc99a259fe65f679608cf2201 Mon Sep 17 00:00:00 2001 From: Eremey Valetov Date: Sun, 1 Mar 2026 12:25:47 -0500 Subject: [PATCH] Implement binary SAVE/LOAD, INKEY$ extended keys, golden tests, update to v0.10.0 Binary SAVE/LOAD: SAVE now writes tokenized binary by default (0xFF header format), matching original GW-BASIC behavior. SAVE "file",A for ASCII. LOAD auto-detects binary vs ASCII from the first byte. Command-line file loading also auto-detects, so binary .BAS files just work. INKEY$ extended keys: arrow keys, Home/End/PgUp/PgDn, Insert/Delete, and F1-F10 now return the correct CHR$(0) + scan_code two-byte sequences per the IBM PC convention. Refactored event trap key parsing to use tui_read_key() instead of duplicating escape sequence parsing. Golden-file regression tests: generated .expected output files for 55 of 58 test programs (3 timing-dependent tests excluded). The test runner now reports compat match status alongside pass/fail. Classic programs: added Hamurabi, Lunar Lander, Gunner, and Diamond from David Ahl's BASIC Computer Games (1978) in tests/classic/ for manual compatibility testing. Docs updated with compiler roadmap item and hardware I/O simulator plan. --- README.md | 39 ++++- docs/architecture.md | 31 ++-- docs/development.md | 17 +- docs/getting-started.md | 2 +- docs/index.md | 9 +- docs/language-reference.md | 48 +++++- docs/roadmap.md | 43 +++-- include/gwbasic.h | 2 +- include/tui.h | 3 + src/eval.c | 28 +++- src/interp.c | 69 +-------- src/main.c | 47 ++++-- src/program_io.c | 198 +++++++++++++++++++----- src/tui.c | 29 ++++ tests/classic/diamond.bas | 27 ++++ tests/classic/gunner.bas | 50 ++++++ tests/classic/hamurabi.bas | 118 ++++++++++++++ tests/classic/lunar.bas | 49 ++++++ tests/expected/arrays.expected | 4 + tests/expected/bubble_sort.expected | 11 ++ tests/expected/caesar_cipher.expected | 3 + tests/expected/calendar.expected | 5 + tests/expected/chain_test.expected | 2 + tests/expected/common_test.expected | 4 + tests/expected/control_flow.expected | 5 + tests/expected/data_read.expected | 3 + tests/expected/def_fn.expected | 3 + tests/expected/def_types.expected | 1 + tests/expected/diamond.expected | 14 ++ tests/expected/error_handler.expected | 7 + tests/expected/fibonacci.expected | 21 +++ tests/expected/file_io.expected | 2 + tests/expected/filesystem.expected | 1 + tests/expected/for_next.expected | 2 + tests/expected/gosub.expected | 7 + tests/expected/graphics_stubs.expected | 1 + tests/expected/hailstone.expected | 1 + tests/expected/hanoi.expected | 1 + tests/expected/hello.expected | 1 + tests/expected/hundred_doors.expected | 1 + tests/expected/if_then_else.expected | 3 + tests/expected/inkey_ext.expected | 2 + tests/expected/invoice.expected | 12 ++ tests/expected/leibniz.expected | 1 + tests/expected/lprint.expected | 1 + tests/expected/luhn.expected | 1 + tests/expected/math_ops.expected | 19 +++ tests/expected/matrix_mult.expected | 4 + tests/expected/mid_assign.expected | 2 + tests/expected/mkicvi.expected | 3 + tests/expected/monte_carlo.expected | 3 + tests/expected/mult_table.expected | 15 ++ tests/expected/nested_for.expected | 5 + tests/expected/number_guess.expected | 2 + tests/expected/on_goto.expected | 3 + tests/expected/pascal_triangle.expected | 9 ++ tests/expected/play_music.expected | 1 + tests/expected/play_scale.expected | 1 + tests/expected/prime_sieve.expected | 2 + tests/expected/print_using.expected | 6 + tests/expected/random_access.expected | 1 + tests/expected/roman_numerals.expected | 7 + tests/expected/run_file.expected | 2 + tests/expected/save_binary.expected | 1 + tests/expected/save_load.expected | 1 + tests/expected/sound_test.expected | 1 + tests/expected/stats_calc.expected | 4 + tests/expected/string_ops.expected | 14 ++ tests/expected/temp_table.expected | 11 ++ tests/expected/text_adventure.expected | 8 + tests/expected/variables.expected | 3 + tests/expected/while_wend.expected | 2 + tests/expected/write_input.expected | 2 + tests/harness/hamurabi_input.txt | 40 +++++ tests/programs/inkey_ext.bas | 5 + tests/programs/save_binary.bas | 9 ++ 76 files changed, 952 insertions(+), 163 deletions(-) create mode 100644 tests/classic/diamond.bas create mode 100644 tests/classic/gunner.bas create mode 100644 tests/classic/hamurabi.bas create mode 100644 tests/classic/lunar.bas create mode 100644 tests/expected/arrays.expected create mode 100644 tests/expected/bubble_sort.expected create mode 100644 tests/expected/caesar_cipher.expected create mode 100644 tests/expected/calendar.expected create mode 100644 tests/expected/chain_test.expected create mode 100644 tests/expected/common_test.expected create mode 100644 tests/expected/control_flow.expected create mode 100644 tests/expected/data_read.expected create mode 100644 tests/expected/def_fn.expected create mode 100644 tests/expected/def_types.expected create mode 100644 tests/expected/diamond.expected create mode 100644 tests/expected/error_handler.expected create mode 100644 tests/expected/fibonacci.expected create mode 100644 tests/expected/file_io.expected create mode 100644 tests/expected/filesystem.expected create mode 100644 tests/expected/for_next.expected create mode 100644 tests/expected/gosub.expected create mode 100644 tests/expected/graphics_stubs.expected create mode 100644 tests/expected/hailstone.expected create mode 100644 tests/expected/hanoi.expected create mode 100644 tests/expected/hello.expected create mode 100644 tests/expected/hundred_doors.expected create mode 100644 tests/expected/if_then_else.expected create mode 100644 tests/expected/inkey_ext.expected create mode 100644 tests/expected/invoice.expected create mode 100644 tests/expected/leibniz.expected create mode 100644 tests/expected/lprint.expected create mode 100644 tests/expected/luhn.expected create mode 100644 tests/expected/math_ops.expected create mode 100644 tests/expected/matrix_mult.expected create mode 100644 tests/expected/mid_assign.expected create mode 100644 tests/expected/mkicvi.expected create mode 100644 tests/expected/monte_carlo.expected create mode 100644 tests/expected/mult_table.expected create mode 100644 tests/expected/nested_for.expected create mode 100644 tests/expected/number_guess.expected create mode 100644 tests/expected/on_goto.expected create mode 100644 tests/expected/pascal_triangle.expected create mode 100644 tests/expected/play_music.expected create mode 100644 tests/expected/play_scale.expected create mode 100644 tests/expected/prime_sieve.expected create mode 100644 tests/expected/print_using.expected create mode 100644 tests/expected/random_access.expected create mode 100644 tests/expected/roman_numerals.expected create mode 100644 tests/expected/run_file.expected create mode 100644 tests/expected/save_binary.expected create mode 100644 tests/expected/save_load.expected create mode 100644 tests/expected/sound_test.expected create mode 100644 tests/expected/stats_calc.expected create mode 100644 tests/expected/string_ops.expected create mode 100644 tests/expected/temp_table.expected create mode 100644 tests/expected/text_adventure.expected create mode 100644 tests/expected/variables.expected create mode 100644 tests/expected/while_wend.expected create mode 100644 tests/expected/write_input.expected create mode 100644 tests/harness/hamurabi_input.txt create mode 100644 tests/programs/inkey_ext.bas create mode 100644 tests/programs/save_binary.bas diff --git a/README.md b/README.md index 425a635..31ad6db 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Interactive mode launches the authentic GW-BASIC full-screen editor: ``` $ ./gwbasic -GW-BASIC 2026 0.9.0 +GW-BASIC 2026 0.10.0 (C) Eremey Valetov 2026. MIT License. Based on Microsoft GW-BASIC assembly source. Ok @@ -35,7 +35,7 @@ FOR I=1 TO 5:PRINT I;:NEXT Ok ``` -Run a program file: +Run a program file (ASCII or binary tokenized): ```bash ./gwbasic tests/programs/prime_sieve.bas @@ -70,7 +70,7 @@ SPACE$, STRING$, HEX$, OCT$, INSTR, INPUT$ | Program | RUN, RUN "file", CONT, STOP, END, NEW, LIST, CLEAR, AUTO, RENUM, DELETE, EDIT | | Sequential I/O | OPEN, CLOSE, PRINT#, WRITE#, INPUT#, LINE INPUT# | | Random-access I/O | FIELD, LSET, RSET, PUT, GET, CVI/CVS/CVD, MKI$/MKS$/MKD$ | -| Program I/O | SAVE, LOAD, MERGE, CHAIN, COMMON | +| Program I/O | SAVE (binary/ASCII), LOAD (auto-detects format), MERGE, CHAIN, COMMON | | Event trapping | ON TIMER(n) GOSUB, TIMER ON/OFF/STOP, ON KEY(n) GOSUB, KEY(n) ON/OFF/STOP | | Error handling | ON ERROR GOTO, RESUME, ERROR, ERR, ERL | | User functions | DEF FN, RANDOMIZE | @@ -80,6 +80,29 @@ SPACE$, STRING$, HEX$, OCT$, INSTR, INPUT$ | Graphics | PSET, PRESET, LINE, CIRCLE, DRAW, PAINT | | Sound | SOUND, BEEP, PLAY (MML) | +### Binary and ASCII File Formats + +`SAVE` writes tokenized binary by default (just like the original), or ASCII +with the `,A` flag. `LOAD` auto-detects the format — so you can load programs +saved in either format without any extra flags. + +``` +SAVE "myprog.bas" ' tokenized binary (compact, fast) +SAVE "myprog.bas",A ' ASCII text (human-readable) +LOAD "myprog.bas" ' auto-detects format +``` + +Binary files use the standard GW-BASIC 0xFF-header format. Command-line +loading (`./gwbasic file.bas`) also auto-detects, so binary `.BAS` files +just work. + +### INKEY$ Extended Keys + +`INKEY$` returns the classic GW-BASIC two-byte encoding for extended keys: +`CHR$(0) + CHR$(scan_code)`. Arrow keys, Home, End, PgUp/PgDn, Insert, +Delete, and F1-F10 all produce the correct IBM PC scan codes — so programs +that poll for arrow-key input work as expected. + ### Full-Screen Editor (TUI) When running interactively, GW-BASIC 2026 presents the authentic full-screen @@ -159,9 +182,17 @@ Key design differences from the original: - malloc'd strings instead of compacting garbage collector - setjmp/longjmp for error recovery +## Classic Programs + +The `tests/classic/` directory contains classic BASIC programs from David Ahl's +*BASIC Computer Games* (1978) — Hamurabi, Lunar Lander, Gunner, Diamond — for +manual compatibility testing. These are interactive programs that need keyboard +input, so they're not part of the automated test suite. + ## Tests -56 test programs in `tests/programs/`, with CI via GitHub Actions: +58 test programs in `tests/programs/`, with golden-file regression testing +and CI via GitHub Actions: ```bash bash tests/run_tests.sh diff --git a/docs/architecture.md b/docs/architecture.md index c57a4e1..3f3738e 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -14,15 +14,17 @@ Source text → Tokenizer (CRUNCH) → Token stream HAL (platform I/O) ``` -The interpreter follows the original GW-BASIC's internal structure. Source lines -are tokenized by CRUNCH into a compact token stream. The NEWSTT loop dispatches -each statement, calling FRMEVL for expression evaluation. All platform I/O goes -through a HAL vtable (`hal_ops_t`), keeping the core interpreter portable. +The interpreter mirrors the original GW-BASIC's internal pipeline, which — like +most Microsoft interpreters of the era — is a tight loop around three core +routines. CRUNCH tokenizes source lines into a compact byte stream. NEWSTT +dispatches each statement. FRMEVL evaluates expressions. All platform I/O +goes through a HAL vtable (`hal_ops_t`), so the core interpreter has no idea +whether it's talking to an ANSI terminal or a teletype from 1975. -When running interactively, the TUI layer intercepts HAL output calls -(`putch`, `puts`, `cls`, `locate`) and routes them through a dynamically -allocated screen buffer rendered via ANSI escape sequences. In piped mode the -TUI is not activated and the HAL writes directly to stdout. +In interactive mode, the TUI layer swaps in its own HAL function pointers and +redirects all output through a dynamically allocated screen buffer, displayed +via ANSI escape sequences. In piped mode the TUI stays out of the way and the +HAL writes straight to stdout. ## Module Map @@ -54,7 +56,7 @@ TUI is not activated and the HAL writes directly to stdout. src/ — core interpreter (20 files) include/ — headers (12 files) platform/ — HAL backends (1 file) -tests/ — test programs (54 .BAS files), compat test harness +tests/ — 58 automated test programs, 4 classic interactive programs, compat harness docs/ — Sphinx documentation ``` @@ -79,11 +81,12 @@ The TUI (`tui.c`) implements the classic GW-BASIC full-screen editor: ### Relation to Original Assembly -The original GW-BASIC source was -[released by Microsoft in 2020](https://github.com/microsoft/GW-BASIC) as 8088 -assembly (43,771 lines across 43 `.ASM` files). This reimplementation uses that -assembly as a reference but is not a transpilation — it reimplements the -algorithms in idiomatic C with modern data structures. +Microsoft [released the original GW-BASIC source](https://github.com/microsoft/GW-BASIC) +in 2020 — 43,771 lines of 8088 assembly spread across 43 `.ASM` files, complete +with Greg Whitten's comments and Neil Konzen's transcendental math routines +(which are, frankly, impressive for 16-bit fixed-point). This reimplementation +uses that assembly as a reference, not as input to a transliterator — the +algorithms are reimplemented in idiomatic C with modern data structures. ### Key Differences from the Original diff --git a/docs/development.md b/docs/development.md index fcbe115..065c6fd 100644 --- a/docs/development.md +++ b/docs/development.md @@ -12,19 +12,24 @@ | 0.6.0 | `ece018d` | DATE$/TIME$/TIMER, FILES, SHELL, CHDIR, MKDIR, RMDIR | | 0.7.0 | `da6b513` | AUTO, RENUM (with GOTO/GOSUB patching), DELETE, COMMON, LIST range fix | | 0.8.0 | `c68167c` | Dynamic TUI screen buffer, `--full` flag, LPRINT/LLIST with `--lpt` | -| 0.9.0 | | EDIT statement, ON TIMER/ON KEY event trapping, F-key escape parser fixes | +| 0.9.0 | `2a8f98b` | EDIT statement, ON TIMER/ON KEY event trapping, F-key escape parser fixes | +| 0.10.0 | | Binary tokenized SAVE/LOAD, INKEY$ extended key sequences, golden-file regression tests, classic BASIC programs | ## Tests -56 test programs in `tests/programs/`. Run the full suite: +58 test programs in `tests/programs/`, plus 4 classic interactive programs in +`tests/classic/` (Hamurabi, Lunar Lander, Gunner, Diamond from David Ahl's +*BASIC Computer Games*). + +Run the full automated suite: ```bash bash tests/run_tests.sh ``` -Each test has a 5-second timeout. When `.expected` files are present -(generated from real GWBASIC.EXE), the runner also reports compatibility -match status. +Each test has a 5-second timeout. 55 tests have `.expected` golden files +for output regression detection. Three timing-dependent tests (datetime, +on_timer, timer_stop) run without golden comparison. ### Compatibility Testing @@ -41,6 +46,6 @@ bash tests/run_compat.sh ## CI GitHub Actions runs on every push to `main` and on pull requests. The workflow -builds the project with PulseAudio support and runs all 56 test programs. +builds the project with PulseAudio support and runs all 58 test programs. See [`.github/workflows/ci.yml`](https://github.com/evvaletov/gw-basic-2026/blob/main/.github/workflows/ci.yml). diff --git a/docs/getting-started.md b/docs/getting-started.md index ec528ae..79b731b 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -37,7 +37,7 @@ Running `./gwbasic` with no arguments launches the full-screen editor: ``` $ ./gwbasic -GW-BASIC 2026 0.9.0 +GW-BASIC 2026 0.10.0 (C) Eremey Valetov 2026. MIT License. Based on Microsoft GW-BASIC assembly source. Ok diff --git a/docs/index.md b/docs/index.md index d2b0190..8476bea 100644 --- a/docs/index.md +++ b/docs/index.md @@ -14,11 +14,18 @@ version is structured as modular C suitable for new feature development. - **Authentic full-screen editor** — dynamically sized screen buffer (25×80 default, full terminal with `--full`), free cursor movement, Enter-on-any-line, F1-F10 function keys, Insert/Overwrite toggle +- **Binary and ASCII file formats** — `SAVE` writes tokenized binary by default, + `LOAD` auto-detects format (just like the real thing) +- **INKEY$ extended keys** — arrow keys, F-keys, and navigation keys return proper + `CHR$(0)` + scan code two-byte sequences - **Sixel graphics** — `SCREEN 1`/`SCREEN 2` rendering in compatible terminals - **Sound** — `SOUND`, `BEEP`, `PLAY` (MML) via PulseAudio - **Full file I/O** — sequential, random-access, SAVE/LOAD/MERGE/CHAIN/COMMON - **Printer output** — `LPRINT`/`LLIST` to file or real hardware via `--lpt` -- **54 test programs** with DOSBox-X compatibility testing against real GWBASIC.EXE +- **Classic programs** — Hamurabi, Lunar Lander, Gunner, and Diamond from + David Ahl's *BASIC Computer Games* (1978) run out of the box +- **58 test programs** with golden-file regression testing and DOSBox-X + compatibility testing against real GWBASIC.EXE - **MIT License** ```{toctree} diff --git a/docs/language-reference.md b/docs/language-reference.md index ef4d6fc..4073750 100644 --- a/docs/language-reference.md +++ b/docs/language-reference.md @@ -50,7 +50,7 @@ type suffixes (`%`, `!`, `#`) | Program control | `RUN`, `RUN "file"`, `CONT`, `STOP`, `END`, `NEW`, `LIST`, `CLEAR`, `AUTO`, `RENUM`, `DELETE`, `EDIT` | | Sequential I/O | `OPEN`, `CLOSE`, `PRINT#`, `WRITE#`, `INPUT#`, `LINE INPUT#` | | Random-access I/O | `FIELD`, `LSET`, `RSET`, `PUT`, `GET`, `CVI`/`CVS`/`CVD`, `MKI$`/`MKS$`/`MKD$` | -| Program I/O | `SAVE`, `LOAD`, `MERGE`, `CHAIN`, `COMMON` | +| Program I/O | `SAVE` (binary/ASCII), `LOAD` (auto-detects), `MERGE`, `CHAIN`, `COMMON` | | Event trapping | `ON TIMER(n) GOSUB`, `TIMER ON`/`OFF`/`STOP`, `ON KEY(n) GOSUB`, `KEY(n) ON`/`OFF`/`STOP` | | Error handling | `ON ERROR GOTO`, `RESUME`, `RESUME NEXT`, `RESUME n`, `ERROR`, `ERR`, `ERL` | | User functions | `DEF FN`, `RANDOMIZE` | @@ -62,6 +62,52 @@ type suffixes (`%`, `!`, `#`) | Misc | `POKE`, `KEY`, `TRON`/`TROFF`, `OPTION BASE`, `MID$` assignment | | System | `SYSTEM` | +## Program I/O (SAVE / LOAD) + +`SAVE` writes the current program to a file. The default format is tokenized +binary (compact, fast to load), matching the original GW-BASIC behavior: + +``` +SAVE "myprog.bas" ' tokenized binary (default) +SAVE "myprog.bas",A ' ASCII text (human-readable, editable) +``` + +`LOAD` reads a program file, auto-detecting the format from the first byte: + +``` +LOAD "myprog.bas" ' auto-detects binary or ASCII +LOAD "myprog.bas",R ' load and run immediately +``` + +Binary files use the standard GW-BASIC 0xFF header format. Command-line +loading (`./gwbasic file.bas`) also auto-detects format. + +`MERGE` loads an ASCII file without clearing the current program, overlaying +lines by number. `CHAIN` loads and runs a new program, optionally preserving +variables listed by `COMMON`. + +## INKEY$ Extended Keys + +`INKEY$` returns a zero-length string when no key is available, a one-byte +string for regular ASCII keys, or a two-byte string for extended keys: + +``` +K$ = INKEY$ +IF LEN(K$) = 2 THEN scan = ASC(MID$(K$, 2, 1)) +``` + +Extended keys return `CHR$(0)` as the first byte and the IBM PC scan code +as the second. Common scan codes: + +| Key | Scan | Key | Scan | +|-----|------|-----|------| +| F1-F10 | 59-68 | Home | 71 | +| Up | 72 | PgUp | 73 | +| Left | 75 | Right | 77 | +| End | 79 | Down | 80 | +| PgDn | 81 | Ins | 82 | +| Del | 83 | | | + ## Printer Output (LPRINT / LLIST) `LPRINT` works identically to `PRINT` but sends output to the printer: diff --git a/docs/roadmap.md b/docs/roadmap.md index 72c208e..05fe56d 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,16 +1,37 @@ # Roadmap +## The Big One + +- **GW-BASIC 2026 Compiler** — ahead-of-time compilation of BASIC programs to + native executables. Because nothing says "premature optimization" like + compiling a language designed for an interpreter running on a 4.77 MHz 8088. + But we've come this far, so why not? Likely approach: translate the token + stream to C and lean on GCC/Clang for the heavy lifting. + ## Planned Features +- **DEF SEG / PEEK / POKE emulation** — a virtual address space for the BIOS data + area and CGA screen buffer (B800:0000), so programs that directly twiddle + screen memory or read the keyboard buffer actually work. Not quite cycle- + accurate, but enough to run most "tricks" from the 1980s magazines. +- **Hardware I/O simulator** — an optional emulation layer for `OUT`, `INP`, + `WAIT`, `MOTOR`, and friends. The idea is to provide a virtual PC peripheral + bus so retro programs that talk to the speaker, joystick port, or parallel + interface can do *something* useful instead of silently no-oping. Think of it + as a tiny museum exhibit for vintage BASIC hardware hacking. - **BSAVE / BLOAD** — binary file save/load for screen buffers and data -- **DEF SEG** — memory segment declaration for PEEK/POKE/BSAVE/BLOAD -- **PRINT USING edge cases** — `**` asterisk fill, `**$` combined -- **Binary SAVE/LOAD** — Protected (,P) and tokenized binary formats -- **GET/PUT graphics** — sprite capture and blit for graphics mode +- **PRINT USING edge cases** — `**` asterisk fill, `**$` combined, thousands + separator with `,`, and `^^^^` scientific notation corner cases to match + the original output formatting exactly +- **GET/PUT graphics** — sprite capture and blit for graphics mode; the + framebuffer infrastructure is already in place, this is the missing piece + for any BASIC program that does animation - **TUI color support** — map GW-BASIC COLOR attributes to ANSI 16-color output -- **INKEY$ extended keys** — return CHR$(0) + scan code for arrow keys and - function keys, matching the original two-byte encoding -- **VIEW / WINDOW / PALETTE** — graphics viewport, coordinate mapping, and palette + in the TUI screen buffer +- **VIEW / WINDOW / PALETTE** — graphics viewport, coordinate mapping, and + palette remapping +- **MBF format support for binary LOAD** — convert Microsoft Binary Format + float constants when loading files saved by the original GWBASIC.EXE ## IDE and Notebook Integration @@ -28,8 +49,10 @@ ## Known Limitations -- No binary/protected file format support (ASCII only) - `PEEK`/`POKE` are stubs (POKE parses and discards, PEEK returns 0) +- Binary files use IEEE 754 floats, not MBF — files saved here are not + byte-compatible with original GWBASIC.EXE binary format - String garbage collection not implemented (uses `malloc`/`free` instead) -- Maximum 256 variables, 64 arrays, 16 FOR nesting, 24 GOSUB nesting, 16 WHILE nesting -- Hardware I/O (OUT, INP, WAIT, COM, MOTOR) not implemented — no modern equivalent +- Maximum 256 variables, 64 arrays, 16 FOR nesting, 24 GOSUB nesting, + 16 WHILE nesting +- Hardware I/O (`OUT`, `INP`, `WAIT`, `COM`, `MOTOR`) not yet implemented diff --git a/include/gwbasic.h b/include/gwbasic.h index b9225b1..1377479 100644 --- a/include/gwbasic.h +++ b/include/gwbasic.h @@ -10,7 +10,7 @@ #include "gw_math.h" #include "strings.h" -#define GW_VERSION "0.9.0" +#define GW_VERSION "0.10.0" #define GW_BANNER "GW-BASIC " GW_VERSION /* Tokenizer */ diff --git a/include/tui.h b/include/tui.h index 14262de..d239d46 100644 --- a/include/tui.h +++ b/include/tui.h @@ -107,6 +107,9 @@ void tui_push_key(int key); int tui_pop_key(void); bool tui_keybuf_empty(void); +/* Map TK_* key code to IBM PC scan code; returns -1 for ASCII keys */ +int tui_key_to_scancode(int key); + /* Ctrl+Break */ void tui_check_break(void); void tui_install_break_handler(void); diff --git a/src/eval.c b/src/eval.c index cbb2f64..f682bfa 100644 --- a/src/eval.c +++ b/src/eval.c @@ -880,17 +880,31 @@ static gw_value_t eval_atom(void) gw_chrget(); gw_value_t v; v.type = VT_STR; + + int key = -1; + /* Check key buffer first (keys pushed back by event trapping) */ if (!tui_keybuf_empty()) { - int ch = tui_pop_key(); - v.sval = gw_str_alloc(1); - v.sval.data[0] = (char)ch; + key = tui_pop_key(); + } else if (tui.active && gw_hal && gw_hal->kbhit()) { + key = tui_read_key(); } else if (gw_hal && gw_hal->kbhit()) { - int ch = gw_hal->getch(); - v.sval = gw_str_alloc(1); - v.sval.data[0] = ch; - } else { + key = gw_hal->getch(); + } + + if (key < 0) { v.sval = gw_str_alloc(0); + } else { + int scan = tui_key_to_scancode(key); + if (scan >= 0) { + /* Extended key: CHR$(0) + CHR$(scan_code) */ + v.sval = gw_str_alloc(2); + v.sval.data[0] = '\0'; + v.sval.data[1] = (char)scan; + } else { + v.sval = gw_str_alloc(1); + v.sval.data[0] = (char)key; + } } return v; } diff --git a/src/interp.c b/src/interp.c index 09ab33e..9d3c6c7 100644 --- a/src/interp.c +++ b/src/interp.c @@ -2645,67 +2645,14 @@ static void gw_check_events(void) } if (any_key_trap && gw_hal && gw_hal->kbhit()) { - int ch = gw_hal->getch(); + /* Use tui_read_key to get a properly parsed key code */ + int key = tui_read_key(); + if (key < 0) return; + + /* Check if it's an F-key for key trapping */ int fkey = -1; - if (ch == 27 && gw_hal->kbhit()) { - int seq1 = gw_hal->getch(); - if (seq1 == 'O') { - int seq2 = gw_hal->getch(); - switch (seq2) { - case 'P': fkey = 0; break; /* F1 */ - case 'Q': fkey = 1; break; /* F2 */ - case 'R': fkey = 2; break; /* F3 */ - case 'S': fkey = 3; break; /* F4 */ - default: - tui_push_key(27); - tui_push_key('O'); - tui_push_key(seq2); - return; - } - } else if (seq1 == '[') { - int seq2 = gw_hal->getch(); - if ((seq2 == '1' || seq2 == '2') && gw_hal->kbhit()) { - int seq3 = gw_hal->getch(); - if (gw_hal->kbhit()) { - int seq4 = gw_hal->getch(); - if (seq4 == '~') { - int code = (seq2 - '0') * 10 + (seq3 - '0'); - switch (code) { - case 15: fkey = 4; break; /* F5 */ - case 17: fkey = 5; break; /* F6 */ - case 18: fkey = 6; break; /* F7 */ - case 19: fkey = 7; break; /* F8 */ - case 20: fkey = 8; break; /* F9 */ - case 21: fkey = 9; break; /* F10 */ - } - } - if (fkey < 0) { - tui_push_key(27); - tui_push_key('['); - tui_push_key(seq2); - tui_push_key(seq3); - tui_push_key(seq4); - return; - } - } else { - tui_push_key(27); - tui_push_key('['); - tui_push_key(seq2); - tui_push_key(seq3); - return; - } - } else { - tui_push_key(27); - tui_push_key('['); - tui_push_key(seq2); - return; - } - } else { - tui_push_key(27); - tui_push_key(seq1); - return; - } - } + if (key >= TK_F1 && key <= TK_F10) + fkey = key - TK_F1; if (fkey >= 0 && fkey < 10) { event_trap_t *kt = &gw.key_traps[fkey]; @@ -2720,7 +2667,7 @@ static void gw_check_events(void) } /* Not an F-key: push into key buffer for INKEY$/input */ - tui_push_key(ch); + tui_push_key(key); } /* Check for pending key traps after KEY(n) ON */ diff --git a/src/main.c b/src/main.c index d5b2c82..d158dbd 100644 --- a/src/main.c +++ b/src/main.c @@ -202,27 +202,46 @@ int main(int argc, char **argv) } } - /* Load file: read lines, store numbered lines, then RUN */ + /* Load file: auto-detects binary vs ASCII, then RUN */ if (filename) { - FILE *f = fopen(filename, "r"); - if (!f) { + /* Peek at first byte to detect binary format */ + FILE *probe = fopen(filename, "rb"); + if (!probe) { fprintf(stderr, "File not found: %s\n", filename); return 1; } - char buf[256]; - while (fgets(buf, sizeof(buf), f)) { - int len = strlen(buf); - while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r')) - buf[--len] = '\0'; - if (buf[0] == '\0') continue; + int header = fgetc(probe); + fclose(probe); - if (setjmp(gw_error_jmp) != 0) - continue; - gw_exec_direct(buf); + if (header == 0xFF || header == 0xFE) { + /* Binary tokenized file — use LOAD machinery */ + if (setjmp(gw_error_jmp) != 0) { + fprintf(stderr, "Error loading %s\n", filename); + gw_lpt_close(); + snd_shutdown(); + if (gw_hal) gw_hal->shutdown(); + return 1; + } + gw_stmt_load_internal(filename, true); + } else { + /* ASCII file — process line by line (supports unnumbered lines) */ + FILE *f = fopen(filename, "r"); + if (!f) { + fprintf(stderr, "File not found: %s\n", filename); + return 1; + } + char buf[256]; + while (fgets(buf, sizeof(buf), f)) { + int len = strlen(buf); + while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r')) + buf[--len] = '\0'; + if (buf[0] == '\0') continue; + if (setjmp(gw_error_jmp) != 0) continue; + gw_exec_direct(buf); + } + fclose(f); } - fclose(f); - /* If program was loaded but RUN wasn't in the file, auto-run */ if (gw.prog_head && !gw.running) { if (setjmp(gw_error_jmp) != 0) { /* error during auto-run */ diff --git a/src/program_io.c b/src/program_io.c index 7e24ce8..dfde475 100644 --- a/src/program_io.c +++ b/src/program_io.c @@ -3,31 +3,67 @@ #include #include -/* SAVE "filename" [,A] - save program as ASCII text */ -void gw_stmt_save(void) +/* + * GW-BASIC binary file format: + * Header: 0xFF (tokenized) | 0xFE (protected) | 0x00 (ASCII) + * Lines: [2-byte next-ptr][2-byte linenum][tokens...][0x00] + * End: [0x00 0x00] [0x1A] + * + * The next-ptr is an absolute offset from the start of file data. For our + * purposes we compute it sequentially — the original GW-BASIC used memory + * addresses here, but only the line number and token data matter on load. + * + * Floating-point constants are stored in our native IEEE format, not MBF. + * This means files saved here are compatible with this interpreter but not + * directly with the original GWBASIC.EXE binary format. + */ + +#define BIN_HEADER_TOKEN 0xFF +#define BIN_HEADER_PROTECT 0xFE +#define BIN_EOF_MARKER 0x1A + +static void write16(FILE *fp, uint16_t val) { - gw_skip_spaces(); - gw_value_t fname_val = gw_eval_str(); - char *filename = gw_str_to_cstr(&fname_val.sval); - gw_str_free(&fname_val.sval); + fputc(val & 0xFF, fp); + fputc((val >> 8) & 0xFF, fp); +} - /* We only support ASCII saves (,A is optional/default) */ - gw_skip_spaces(); - if (gw_chrgot() == ',') { - gw_chrget(); - gw_skip_spaces(); - /* Skip A or P flag */ - if (gw_is_letter(gw_chrgot())) - gw_chrget(); +static uint16_t read16(FILE *fp) +{ + int lo = fgetc(fp); + int hi = fgetc(fp); + if (lo == EOF || hi == EOF) return 0; + return (uint16_t)(lo | (hi << 8)); +} + +/* Save program in tokenized binary format */ +static void save_binary(FILE *fp) +{ + fputc(BIN_HEADER_TOKEN, fp); + + /* First pass: compute the offset base (1 byte for header) */ + uint16_t offset = 1; + + program_line_t *p = gw.prog_head; + while (p) { + /* next-ptr(2) + linenum(2) + tokens(len) + null(1) */ + uint16_t line_size = 2 + 2 + p->len + 1; + offset += line_size; + write16(fp, offset); /* next-line pointer */ + write16(fp, p->num); /* line number */ + fwrite(p->tokens, 1, p->len, fp); + fputc(0x00, fp); /* null terminator */ + p = p->next; } - FILE *fp = fopen(filename, "w"); - if (!fp) { - free(filename); - gw_error(ERR_IO); - } - free(filename); + /* End-of-program marker */ + write16(fp, 0x0000); + fputc(BIN_EOF_MARKER, fp); +} +/* Save program in ASCII format */ +static void save_ascii(FILE *fp) +{ char listbuf[512]; program_line_t *p = gw.prog_head; while (p) { @@ -35,35 +71,68 @@ void gw_stmt_save(void) fprintf(fp, "%u %s\n", p->num, listbuf); p = p->next; } +} + +/* SAVE "filename" [,A] [,P] + * Default = binary tokenized. ,A = ASCII. ,P = protected (stub). */ +void gw_stmt_save(void) +{ + gw_skip_spaces(); + gw_value_t fname_val = gw_eval_str(); + char *filename = gw_str_to_cstr(&fname_val.sval); + gw_str_free(&fname_val.sval); + + char mode = 'B'; /* default: binary */ + gw_skip_spaces(); + if (gw_chrgot() == ',') { + gw_chrget(); + gw_skip_spaces(); + char flag = toupper(gw_chrgot()); + if (flag == 'A') mode = 'A'; + else if (flag == 'P') mode = 'P'; + if (gw_is_letter(gw_chrgot())) + gw_chrget(); + } + + FILE *fp = fopen(filename, mode == 'A' ? "w" : "wb"); + if (!fp) { + free(filename); + gw_error(ERR_IO); + } + free(filename); + + if (mode == 'A') + save_ascii(fp); + else + save_binary(fp); /* P treated same as B for now */ + fclose(fp); } -/* Helper: load lines from a file into the program, optionally clearing first */ -void gw_stmt_load_internal(const char *filename, bool clear); - -void gw_stmt_load_internal(const char *filename, bool clear) +/* Load binary tokenized file */ +static void load_binary(FILE *fp) { - FILE *fp = fopen(filename, "r"); - if (!fp) - gw_error(ERR_FF); + for (;;) { + uint16_t next_ptr = read16(fp); + if (next_ptr == 0) break; /* end of program */ - if (clear) { - gw_free_program(); - gw_vars_clear(); - gw_arrays_clear(); - gw_file_close_all(); - memset(gw.fn_defs, 0, sizeof(gw.fn_defs)); - gw.for_sp = 0; - gw.gosub_sp = 0; - gw.while_sp = 0; - gw.data_ptr = NULL; - gw.data_line_ptr = NULL; - gw.cont_text = NULL; - gw.cont_line = NULL; - gw.on_error_line = 0; - gw.in_error_handler = false; + uint16_t linenum = read16(fp); + + /* Read token data until null terminator */ + uint8_t tokbuf[300]; + int len = 0; + int ch; + while ((ch = fgetc(fp)) != EOF && ch != 0x00 && len < (int)sizeof(tokbuf) - 1) + tokbuf[len++] = (uint8_t)ch; + + if (len > 0) + gw_store_line(linenum, tokbuf, len); } +} +/* Load ASCII text file */ +static void load_ascii(FILE *fp) +{ char buf[256]; while (fgets(buf, sizeof(buf), fp)) { int len = strlen(buf); @@ -90,7 +159,7 @@ void gw_stmt_load_internal(const char *filename, bool clear) num = (uint16_t)(tp[1] | (tp[2] << 8)); skip = (tp - gw.kbuf) + 3; } else { - continue; /* skip non-numbered lines */ + continue; } int data_len = clen - skip; @@ -100,6 +169,49 @@ void gw_stmt_load_internal(const char *filename, bool clear) if (data_len > 0 && *data != 0) gw_store_line(num, data, data_len); } +} + +/* Clear interpreter state for a fresh LOAD */ +static void clear_state(void) +{ + gw_free_program(); + gw_vars_clear(); + gw_arrays_clear(); + gw_file_close_all(); + memset(gw.fn_defs, 0, sizeof(gw.fn_defs)); + gw.for_sp = 0; + gw.gosub_sp = 0; + gw.while_sp = 0; + gw.data_ptr = NULL; + gw.data_line_ptr = NULL; + gw.cont_text = NULL; + gw.cont_line = NULL; + gw.on_error_line = 0; + gw.in_error_handler = false; +} + +/* Auto-detect format and load. */ +void gw_stmt_load_internal(const char *filename, bool clear) +{ + FILE *fp = fopen(filename, "rb"); + if (!fp) + gw_error(ERR_FF); + + if (clear) + clear_state(); + + /* Peek at first byte to detect format */ + int header = fgetc(fp); + if (header == BIN_HEADER_TOKEN || header == BIN_HEADER_PROTECT) { + load_binary(fp); + } else { + /* ASCII: rewind and read as text */ + fclose(fp); + fp = fopen(filename, "r"); + if (!fp) gw_error(ERR_FF); + load_ascii(fp); + } + fclose(fp); } diff --git a/src/tui.c b/src/tui.c index 0a9a584..3d26daf 100644 --- a/src/tui.c +++ b/src/tui.c @@ -513,6 +513,35 @@ bool tui_keybuf_empty(void) return tui.keybuf_head == tui.keybuf_tail; } +int tui_key_to_scancode(int key) +{ + /* Map internal TK_* key codes to IBM PC scan codes for INKEY$. + Returns -1 for regular ASCII keys (no scan code). */ + switch (key) { + case TK_F1: return 59; + case TK_F2: return 60; + case TK_F3: return 61; + case TK_F4: return 62; + case TK_F5: return 63; + case TK_F6: return 64; + case TK_F7: return 65; + case TK_F8: return 66; + case TK_F9: return 67; + case TK_F10: return 68; + case TK_HOME: return 71; + case TK_UP: return 72; + case TK_PGUP: return 73; + case TK_LEFT: return 75; + case TK_RIGHT: return 77; + case TK_END: return 79; + case TK_DOWN: return 80; + case TK_PGDN: return 81; + case TK_INSERT: return 82; + case TK_DELETE: return 83; + default: return -1; + } +} + void tui_edit_line(const char *prefill) { /* Advance to new line */ diff --git a/tests/classic/diamond.bas b/tests/classic/diamond.bas new file mode 100644 index 0000000..5c94058 --- /dev/null +++ b/tests/classic/diamond.bas @@ -0,0 +1,27 @@ +1 PRINT TAB(33);"DIAMOND" +2 PRINT TAB(15);"CREATIVE COMPUTING MORRISTOWN, NEW JERSEY" +3 PRINT:PRINT:PRINT +4 PRINT "FOR A PRETTY DIAMOND PATTERN," +5 INPUT "TYPE IN AN ODD NUMBER BETWEEN 5 AND 21";R:PRINT +6 Q=INT(60/R):A$="CC" +8 FOR L=1 TO Q +10 X=1:Y=R:Z=2 +20 FOR N=X TO Y STEP Z +25 PRINT TAB((R-N)/2); +28 FOR M=1 TO Q +29 C=1 +30 FOR A=1 TO N +32 IF C>LEN(A$) THEN PRINT "!";:GOTO 50 +34 PRINT MID$(A$,C,1); +36 C=C+1 +50 NEXT A +53 IF M=Q THEN 60 +55 PRINT TAB(R*M+(R-N)/2); +56 NEXT M +60 PRINT +70 NEXT N +83 IF X<>1 THEN 95 +85 X=R-2:Y=1:Z=-2 +90 GOTO 20 +95 NEXT L +99 END diff --git a/tests/classic/gunner.bas b/tests/classic/gunner.bas new file mode 100644 index 0000000..4506f3e --- /dev/null +++ b/tests/classic/gunner.bas @@ -0,0 +1,50 @@ +10 PRINT TAB(30);"GUNNER" +20 PRINT TAB(15);"CREATIVE COMPUTING MORRISTOWN, NEW JERSEY" +30 PRINT:PRINT:PRINT +130 PRINT "YOU ARE THE OFFICER-IN-CHARGE, GIVING ORDERS TO A GUN" +140 PRINT "CREW, TELLING THEM THE DEGREES OF ELEVATION YOU ESTIMATE" +150 PRINT "WILL PLACE A PROJECTILE ON TARGET. A HIT WITHIN 100 YARDS" +160 PRINT "OF THE TARGET WILL DESTROY IT." : PRINT +170 R=INT(40000*RND(1)+20000) +180 PRINT "MAXIMUM RANGE OF YOUR GUN IS";R;" YARDS." +185 Z=0 +190 PRINT +195 S1=0 +200 T=INT(R*(.1+.8*RND(1))) +210 S=0 +220 GOTO 370 +230 PRINT "MINIMUM ELEVATION IS ONE DEGREE." +240 GOTO 390 +250 PRINT "MAXIMUM ELEVATION IS 89 DEGREES." +260 GOTO 390 +270 PRINT "OVER TARGET BY";ABS(E);"YARDS." +280 GOTO 390 +290 PRINT "SHORT OF TARGET BY";ABS(E);"YARDS." +300 GOTO 390 +320 PRINT "*** TARGET DESTROYED *** ";S;"ROUNDS OF AMMUNITION EXPENDED." +325 S1=S1+S +330 IF Z=4 THEN 490 +340 Z=Z+1 +345 PRINT +350 PRINT "THE FORWARD OBSERVER HAS SIGHTED MORE ENEMY ACTIVITY..." +360 GOTO 200 +370 PRINT "DISTANCE TO THE TARGET IS";T;"YARDS." +380 PRINT +390 PRINT +400 INPUT "ELEVATION";B +420 IF B>89 THEN 250 +430 IF B<1 THEN 230 +440 S=S+1 +442 IF S<6 THEN 450 +444 PRINT:PRINT "BOOM !!!! YOU HAVE JUST BEEN DESTROYED "; +446 PRINT "BY THE ENEMY." : PRINT : PRINT : PRINT : GOTO 495 +450 B2=2*B/57.3 : I=R*SIN(B2) : X=T-I : E=INT(X) +460 IF ABS(E)<100 THEN 320 +470 IF E>100 THEN 290 +480 GOTO 270 +490 PRINT : PRINT : PRINT "TOTAL ROUNDS EXPENDED WERE:";S1 +492 IF S1>18 THEN 495 +493 PRINT "NICE SHOOTING !!" : GOTO 500 +495 PRINT "BETTER GO BACK TO FORT SILL FOR REFRESHER TRAINING!" +500 PRINT +510 END diff --git a/tests/classic/hamurabi.bas b/tests/classic/hamurabi.bas new file mode 100644 index 0000000..b89aa80 --- /dev/null +++ b/tests/classic/hamurabi.bas @@ -0,0 +1,118 @@ +10 PRINT TAB(32);"HAMURABI" +20 PRINT TAB(15);"CREATIVE COMPUTING MORRISTOWN, NEW JERSEY" +30 PRINT:PRINT:PRINT +80 PRINT "TRY YOUR HAND AT GOVERNING ANCIENT SUMERIA" +90 PRINT "FOR A TEN-YEAR TERM OF OFFICE.":PRINT +95 D1=0: P1=0 +100 Z=0: P=95:S=2800: H=3000: E=H-S +110 Y=3: A=H/Y: I=5: Q=1 +210 D=0 +215 PRINT:PRINT:PRINT "HAMURABI: I BEG TO REPORT TO YOU,": Z=Z+1 +217 PRINT "IN YEAR";Z;",";D;"PEOPLE STARVED,";I;"CAME TO THE CITY," +218 P=P+I +227 IF Q>0 THEN 230 +228 P=INT(P/2) +229 PRINT "A HORRIBLE PLAGUE STRUCK! HALF THE PEOPLE DIED." +230 PRINT "POPULATION IS NOW";P +232 PRINT "THE CITY NOW OWNS ";A;"ACRES." +235 PRINT "YOU HARVESTED";Y;"BUSHELS PER ACRE." +250 PRINT "THE RATS ATE";E;"BUSHELS." +260 PRINT "YOU NOW HAVE ";S;"BUSHELS IN STORE.": PRINT +270 IF Z=11 THEN 860 +310 C=INT(10*RND(1)): Y=C+17 +312 PRINT "LAND IS TRADING AT";Y;"BUSHELS PER ACRE." +320 PRINT "HOW MANY ACRES DO YOU WISH TO BUY"; +321 INPUT Q: IF Q<0 THEN 850 +322 IF Y*Q<=S THEN 330 +323 GOSUB 710 +324 GOTO 320 +330 IF Q=0 THEN 340 +331 A=A+Q: S=S-Y*Q: C=0 +334 GOTO 400 +340 PRINT "HOW MANY ACRES DO YOU WISH TO SELL"; +341 INPUT Q: IF Q<0 THEN 850 +342 IF QC/2 THEN 530 +523 REM *** RATS ARE RUNNING WILD!! +525 E=INT(S/C) +530 S=S-E+H +531 GOSUB 800 +532 REM *** LET'S HAVE SOME BABIES +533 I=INT(C*(20*A+S)/P/100+1) +539 REM *** HOW MANY PEOPLE HAD FULL TUMMIES? +540 C=INT(Q/20) +541 REM *** HORROR, A 15% CHANCE OF PLAGUE +542 Q=INT(10*(2*RND(1)-.3)) +550 IF P.45*P THEN 560 +553 P1=((Z-1)*P1+D*100/P)/Z +555 P=C: D1=D1+D: GOTO 215 +560 PRINT: PRINT "YOU STARVED";D;"PEOPLE IN ONE YEAR!!!" +565 PRINT "DUE TO THIS EXTREME MISMANAGEMENT YOU HAVE NOT ONLY" +566 PRINT "BEEN IMPEACHED AND THROWN OUT OF OFFICE BUT YOU HAVE" +567 PRINT "ALSO BEEN DECLARED NATIONAL FINK!!!!": GOTO 990 +710 PRINT "HAMURABI: THINK AGAIN. YOU HAVE ONLY" +711 PRINT S;"BUSHELS OF GRAIN. NOW THEN," +712 RETURN +720 PRINT "HAMURABI: THINK AGAIN. YOU OWN ONLY";A;"ACRES. NOW THEN," +730 RETURN +800 C=INT(RND(1)*5)+1 +801 RETURN +850 PRINT: PRINT "HAMURABI: I CANNOT DO WHAT YOU WISH." +855 PRINT "GET YOURSELF ANOTHER STEWARD!!!!!" +857 GOTO 990 +860 PRINT "IN YOUR 10-YEAR TERM OF OFFICE,";P1;"PERCENT OF THE" +862 PRINT "POPULATION STARVED PER YEAR ON THE AVERAGE, I.E. A TOTAL OF" +865 PRINT D1;"PEOPLE DIED!!": L=A/P +870 PRINT "YOU STARTED WITH 10 ACRES PER PERSON AND ENDED WITH" +875 PRINT L;"ACRES PER PERSON.": PRINT +880 IF P1>33 THEN 565 +885 IF L<7 THEN 565 +890 IF P1>10 THEN 940 +892 IF L<9 THEN 940 +895 IF P1>3 THEN 960 +896 IF L<10 THEN 960 +900 PRINT "A FANTASTIC PERFORMANCE!!! CHARLEMANGE, DISRAELI, AND" +905 PRINT "JEFFERSON COMBINED COULD NOT HAVE DONE BETTER!":GOTO 990 +940 PRINT "YOUR HEAVY-HANDED PERFORMANCE SMACKS OF NERO AND IVAN IV." +945 PRINT "THE PEOPLE (REMIANING) FIND YOU AN UNPLEASANT RULER, AND," +950 PRINT "FRANKLY, HATE YOUR GUTS!!":GOTO 990 +960 PRINT "YOUR PERFORMANCE COULD HAVE BEEN SOMEWHAT BETTER, BUT" +965 PRINT "REALLY WASN'T TOO BAD AT ALL. ";INT(P*.8*RND(1));"PEOPLE" +970 PRINT "WOULD DEARLY LIKE TO SEE YOU ASSASSINATED BUT WE ALL HAVE OUR" +975 PRINT "TRIVIAL PROBLEMS." +990 PRINT: PRINT "SO LONG FOR NOW.": PRINT +999 END diff --git a/tests/classic/lunar.bas b/tests/classic/lunar.bas new file mode 100644 index 0000000..951664e --- /dev/null +++ b/tests/classic/lunar.bas @@ -0,0 +1,49 @@ +10 PRINT TAB(33);"LUNAR" +20 PRINT TAB(15);"CREATIVE COMPUTING MORRISTOWN, NEW JERSEY" +25 PRINT:PRINT:PRINT +30 PRINT "THIS IS A COMPUTER SIMULATION OF AN APOLLO LUNAR" +40 PRINT "LANDING CAPSULE.": PRINT: PRINT +50 PRINT "THE ON-BOARD COMPUTER HAS FAILED (IT WAS MADE BY" +60 PRINT "XEROX) SO YOU HAVE TO LAND THE CAPSULE MANUALLY." +70 PRINT: PRINT "SET BURN RATE OF RETRO ROCKETS TO ANY VALUE BETWEEN" +80 PRINT "0 (FREE FALL) AND 200 (MAXIMUM BURN) POUNDS PER SECOND." +90 PRINT "SET NEW BURN RATE EVERY 10 SECONDS.": PRINT +100 PRINT "CAPSULE WEIGHT 32,500 LBS; FUEL WEIGHT 16,000 LBS." +110 PRINT: PRINT: PRINT: PRINT "GOOD LUCK" +120 L=0 +130 PRINT: PRINT "SEC","MI + FT","MPH","LB FUEL","BURN RATE":PRINT +140 A=120:V=1:M=33000:N=16500:G=1E-03:Z=1.8 +150 PRINT L,INT(A);INT(5280*(A-INT(A))),3600*V,M-N,:INPUT K:T=10 +160 IF M-N<1E-03 THEN 240 +170 IF T<1E-03 THEN 150 +180 S=T: IF M>=N+S*K THEN 200 +190 S=(M-N)/K +200 GOSUB 420: IF I<=0 THEN 340 +210 IF V<=0 THEN 230 +220 IF J<0 THEN 370 +230 GOSUB 330: GOTO 160 +240 PRINT "FUEL OUT AT";L;"SECONDS":S=(-V+SQR(V*V+2*A*G))/G +250 V=V+G*S: L=L+S +260 W=3600*V: PRINT "ON MOON AT";L;"SECONDS - IMPACT VELOCITY";W;"MPH" +274 IF W<=1.2 THEN PRINT "PERFECT LANDING!": GOTO 440 +280 IF W<=10 THEN PRINT "GOOD LANDING (COULD BE BETTER)":GOTO 440 +282 IF W>60 THEN 300 +284 PRINT "CRAFT DAMAGE... YOU'RE STRANDED HERE UNTIL A RESCUE" +286 PRINT "PARTY ARRIVES. HOPE YOU HAVE ENOUGH OXYGEN!" +288 GOTO 440 +300 PRINT "SORRY THERE WERE NO SURVIVORS. YOU BLEW IT!" +310 PRINT "IN FACT, YOU BLASTED A NEW LUNAR CRATER";W*.227;"FEET DEEP!" +320 GOTO 440 +330 L=L+S: T=T-S: M=M-S*K: A=I: V=J: RETURN +340 IF S<5E-03 THEN 260 +350 D=V+SQR(V*V+2*A*(G-Z*K/M)):S=2*A/D +360 GOSUB 420: GOSUB 330: GOTO 340 +370 W=(1-M*G/(Z*K))/2: S=M*V/(Z*K*(W+SQR(W*W+V/Z)))+.05:GOSUB 420 +380 IF I<=0 THEN 340 +390 GOSUB 330: IF J>0 THEN 160 +400 IF V>0 THEN 370 +410 GOTO 160 +420 Q=S*K/M: J=V+G*S+Z*(-Q-Q*Q/2-Q^3/3-Q^4/4-Q^5/5) +430 I=A-G*S*S/2-V*S+Z*S*(Q/2+Q^2/6+Q^3/12+Q^4/20+Q^5/30):RETURN +440 PRINT:PRINT:PRINT:PRINT "TRY AGAIN??" +450 END diff --git a/tests/expected/arrays.expected b/tests/expected/arrays.expected new file mode 100644 index 0000000..26f65f0 --- /dev/null +++ b/tests/expected/arrays.expected @@ -0,0 +1,4 @@ + 0 1 4 9 16 25 36 49 64 81 100 +Zero +One +Two diff --git a/tests/expected/bubble_sort.expected b/tests/expected/bubble_sort.expected new file mode 100644 index 0000000..e38bceb --- /dev/null +++ b/tests/expected/bubble_sort.expected @@ -0,0 +1,11 @@ +apple +banana +cherry +date +elderberry +fig +grape +honeydew +kiwi +lemon +Bubble sort OK diff --git a/tests/expected/caesar_cipher.expected b/tests/expected/caesar_cipher.expected new file mode 100644 index 0000000..f44b471 --- /dev/null +++ b/tests/expected/caesar_cipher.expected @@ -0,0 +1,3 @@ +Encoded: URYYB JBEYQ +Decoded: HELLO WORLD +Caesar cipher OK diff --git a/tests/expected/calendar.expected b/tests/expected/calendar.expected new file mode 100644 index 0000000..b79a0e0 --- /dev/null +++ b/tests/expected/calendar.expected @@ -0,0 +1,5 @@ +2026 1 1 Thursday +2026 7 4 Saturday +2000 1 1 Saturday +1969 7 20 Sunday +Calendar OK diff --git a/tests/expected/chain_test.expected b/tests/expected/chain_test.expected new file mode 100644 index 0000000..3a467e4 --- /dev/null +++ b/tests/expected/chain_test.expected @@ -0,0 +1,2 @@ +Before CHAIN +Chained OK diff --git a/tests/expected/common_test.expected b/tests/expected/common_test.expected new file mode 100644 index 0000000..7bbac76 --- /dev/null +++ b/tests/expected/common_test.expected @@ -0,0 +1,4 @@ +X preserved +N$ preserved +Y cleared +COMMON test passed diff --git a/tests/expected/control_flow.expected b/tests/expected/control_flow.expected new file mode 100644 index 0000000..d28d5dc --- /dev/null +++ b/tests/expected/control_flow.expected @@ -0,0 +1,5 @@ +Start +I = 1 + (subroutine called)I = 2 +I = 3 +End diff --git a/tests/expected/data_read.expected b/tests/expected/data_read.expected new file mode 100644 index 0000000..321569c --- /dev/null +++ b/tests/expected/data_read.expected @@ -0,0 +1,3 @@ + 10 20 30 +hello world +After RESTORE: 10 diff --git a/tests/expected/def_fn.expected b/tests/expected/def_fn.expected new file mode 100644 index 0000000..a05d88b --- /dev/null +++ b/tests/expected/def_fn.expected @@ -0,0 +1,3 @@ +5 squared = 25 +3 cubed = 27 +Sum = 17 diff --git a/tests/expected/def_types.expected b/tests/expected/def_types.expected new file mode 100644 index 0000000..6f010d9 --- /dev/null +++ b/tests/expected/def_types.expected @@ -0,0 +1 @@ +Type declarations OK diff --git a/tests/expected/diamond.expected b/tests/expected/diamond.expected new file mode 100644 index 0000000..a588916 --- /dev/null +++ b/tests/expected/diamond.expected @@ -0,0 +1,14 @@ + * + *** + ***** + ******* + ********* + *********** +************* + *********** + ********* + ******* + ***** + *** + * +Diamond OK diff --git a/tests/expected/error_handler.expected b/tests/expected/error_handler.expected new file mode 100644 index 0000000..6675d29 --- /dev/null +++ b/tests/expected/error_handler.expected @@ -0,0 +1,7 @@ +Before error +Caught error 5 at line 40 +After resume +Triggering div by zero +Caught error 11 at line 70 +After second resume +Error handler OK diff --git a/tests/expected/fibonacci.expected b/tests/expected/fibonacci.expected new file mode 100644 index 0000000..def7f22 --- /dev/null +++ b/tests/expected/fibonacci.expected @@ -0,0 +1,21 @@ + 1. 0 + 2. 1 + 3. 1 + 4. 2 + 5. 3 + 6. 5 + 7. 8 + 8. 13 + 9. 21 +10. 34 +11. 55 +12. 89 +13. 144 +14. 233 +15. 377 +16. 610 +17. 987 +18. 1597 +19. 2584 +20. 4181 +Fibonacci OK diff --git a/tests/expected/file_io.expected b/tests/expected/file_io.expected new file mode 100644 index 0000000..fb6bd21 --- /dev/null +++ b/tests/expected/file_io.expected @@ -0,0 +1,2 @@ +Hello from GW-BASIC +Second line diff --git a/tests/expected/filesystem.expected b/tests/expected/filesystem.expected new file mode 100644 index 0000000..f8ac64b --- /dev/null +++ b/tests/expected/filesystem.expected @@ -0,0 +1 @@ +All filesystem tests passed diff --git a/tests/expected/for_next.expected b/tests/expected/for_next.expected new file mode 100644 index 0000000..69901a2 --- /dev/null +++ b/tests/expected/for_next.expected @@ -0,0 +1,2 @@ + 1 2 3 4 5 + 10 8 6 4 2 0 diff --git a/tests/expected/gosub.expected b/tests/expected/gosub.expected new file mode 100644 index 0000000..c9db801 --- /dev/null +++ b/tests/expected/gosub.expected @@ -0,0 +1,7 @@ +Main program +In first subroutine +In second subroutine +Back in first sub +Back from first sub +In second subroutine +Back from second sub diff --git a/tests/expected/graphics_stubs.expected b/tests/expected/graphics_stubs.expected new file mode 100644 index 0000000..a8e4eac --- /dev/null +++ b/tests/expected/graphics_stubs.expected @@ -0,0 +1 @@ +Pq#0;2;0;0;0#3;2;0;66;66#0!640~-#0!640~-#0!640~-#0!10~z!629~$#3!10?C!629?-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640B-\Pq#0;2;0;0;0#3;2;0;66;66#0!640~-#0!640~-#0!640~-#0!10~z!629~$#3!10?C!629?-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640B-\Pq#0;2;0;0;0#1;2;0;0;66#3;2;0;66;66#0}|zvn^!634~$#1@ACGO_!634?-#0!6~}|zvn^!628~$#1!6?@ACGO_!628?-#0!12~}|zvn^!622~$#1!12?@ACGO_!622?-#0!10~z!7~}|zvn^!616~$#1!18?@ACGO_!616?$#3!10?C!629?-#0!24~}|zvn^!610~$#1!24?@ACGO_!610?-#0!30~}|zvn^!604~$#1!30?@ACGO_!604?-#0!36~}|zvn^!598~$#1!36?@ACGO_!598?-#0!42~}|zvn^!592~$#1!42?@ACGO_!592?-#0!48~}|zvn^!586~$#1!48?@ACGO_!586?-#0!54~}|zvn^!580~$#1!54?@ACGO_!580?-#0!60~}|zvn^!574~$#1!60?@ACGO_!574?-#0!66~}|zvn^!568~$#1!66?@ACGO_!568?-#0!72~}|zvn^!562~$#1!72?@ACGO_!562?-#0!78~}|zvn^!556~$#1!78?@ACGO_!556?-#0!84~}|zvn^!550~$#1!84?@ACGO_!550?-#0!90~}|zvn^!544~$#1!90?@ACGO_!544?-#0!96~}|zvn!539~$#1!96?@ACGO!539?-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640B-\Pq#0;2;0;0;0#1;2;0;0;66#2;2;0;66;0#3;2;0;66;66#0}|zvn^!634~$#1@ACGO_!634?-#0!6~}|zvn^!628~$#1!6?@ACGO_!628?-#0!12~}|zvn^!622~$#1!12?@ACGO_!622?-#0!10~z!7~}|zvn^!616~$#1!18?@ACGO_!616?$#3!10?C!629?-#0!24~}|zvn^!610~$#1!24?@ACGO_!610?-#0!30~}|zvn^!604~$#1!30?@ACGO_!604?-#0!36~}|zvn^!598~$#1!36?@ACGO_!598?-#0!42~}|zvn^!592~$#1!42?@ACGO_!592?-#0!48~}|zvn^!91~^^^nnf!4v!11z!4vfnn^^^!464~$#1!48?@ACGO_!586?$#2!145?___OOW!4G!11C!4GWOO___!464?-#0!54~}|zvn^!76~^^nvrz||}}!29~}}||zrvn^^!455~$#1!54?@ACGO_!580?$#2!136?__OGKCAA@@!29?@@AACKGO__!455?-#0!60~}|zvn^!64~^nvzz{!49~{zzvn^!449~$#1!60?@ACGO_!574?$#2!130?_OGCCB!49?BCCGO_!449?-#0!66~}|zvn^!54~^nvx}!59~}xvn^!445~$#1!66?@ACGO_!568?$#2!126?_OGE@!59?@EGO_!445?-#0!72~}|zvn^!46~Np}!67~}pN!443~$#1!72?@ACGO_!562?$#2!124?oM@!67?@Mo!443?-#0!78~}|zvn^!37~Nr{!73~{rN!440~$#1!78?@ACGO_!556?$#2!121?oKB!73?BKo!440?-#0!84~}|zvn^!30~@}!77~}@!439~$#1!84?@ACGO_!550?$#2!120?}@!77?@}!439?-#0!90~}|zvn^!23~Fw!79~wF!438~$#1!90?@ACGO_!544?$#2!119?wF!79?Fw!438?-#0!96~}|zvn!18~?!81~?!438~$#1!96?@ACGO!539?$#2!119?~!81?~!438?-#0!119~?!81~?!438~$#2!119?~!81?~!438?-#0!120~?!79~?!439~$#2!120?~!79?~!439?-#0!120~{b^!75~^b{!439~$#2!120?B[_!75?_[B!439?-#0!122~}xf^!69~^fx}!441~$#2!122?@EW_!69?_WE@!441?-#0!125~{rn^!63~^nr{!444~$#2!125?BKO_!63?_OKB!444?-#0!129~{rn^!55~^nr{!448~$#2!129?BKO_!55?_OKB!448?-#0!133~}}xvvn^^!39~^^nvvx}}!452~$#2!133?@@EGGO__!39?__OGGE@@!452?-#0!140~}}||zrvvnnN!4^!11~!4^Nnnvvrz||}}!459~$#2!140?@@AACKGGOOo!4_!11?!4_oOOGGKCAA@@!459?-#0!155~!11}!474~$#2!155?!11@!474?-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640B-\Pq#0;2;0;0;0#1;2;0;0;66#2;2;0;66;0#3;2;0;66;66#0}|zvn^!634~$#1@ACGO_!634?-#0!6~}|zvn^!628~$#1!6?@ACGO_!628?-#0!12~}|zvn^!622~$#1!12?@ACGO_!622?-#0!10~z!7~}|zvn^!616~$#1!18?@ACGO_!616?$#3!10?C!629?-#0!24~}|zvn^!610~$#1!24?@ACGO_!610?-#0!30~}|zvn^!604~$#1!30?@ACGO_!604?-#0!36~}|zvn^!598~$#1!36?@ACGO_!598?-#0!42~}|zvn^!592~$#1!42?@ACGO_!592?-#0!48~}|zvn^!91~^^^nnf!4v!11z!4vfnn^^^!464~$#1!48?@ACGO_!586?$#2!145?___OOW!4G!11C!4GWOO___!464?-#0!54~}|zvn^!76~^^nvrz||}}!29~}}||zrvn^^!455~$#1!54?@ACGO_!580?$#2!136?__OGKCAA@@!29?@@AACKGO__!455?-#0!60~}|zvn^!64~^nvzz{!49~{zzvn^!449~$#1!60?@ACGO_!574?$#2!130?_OGCCB!49?BCCGO_!449?-#0!66~}|zvn^!54~^nvx}!59~}xvn^!445~$#1!66?@ACGO_!568?$#2!126?_OGE@!59?@EGO_!445?-#0!72~}|zvn^!46~Np}!67~}pN!443~$#1!72?@ACGO_!562?$#2!124?oM@!67?@Mo!443?-#0!78~}|zvn^!37~Nr{!73~{rN!440~$#1!78?@ACGO_!556?$#2!121?oKB!73?BKo!440?-#0!84~}|zvn^!30~@}!77~}@!439~$#1!84?@ACGO_!550?$#2!120?}@!77?@}!439?-#0!90~}|zvn^!23~Fw!39~?!9}?!29~wF!438~$#1!90?@ACGO_!64?~!9@~!469?$#2!119?wF!79?Fw!438?-#0!96~}|zvn!18~?!40~_!9n_!30~?!438~$#1!96?@ACGO!59?^!9O^!469?$#2!119?~!81?~!438?-#0!119~?!81~?!438~$#2!119?~!81?~!438?-#0!120~?!79~?!439~$#2!120?~!79?~!439?-#0!120~{b^!75~^b{!439~$#2!120?B[_!75?_[B!439?-#0!122~}xf^!69~^fx}!441~$#2!122?@EW_!69?_WE@!441?-#0!125~{rn^!63~^nr{!444~$#2!125?BKO_!63?_OKB!444?-#0!129~{rn^!55~^nr{!448~$#2!129?BKO_!55?_OKB!448?-#0!133~}}xvvn^^!39~^^nvvx}}!452~$#2!133?@@EGGO__!39?__OGGE@@!452?-#0!140~}}||zrvvnnN!4^!11~!4^Nnnvvrz||}}!459~$#2!140?@@AACKGGOOo!4_!11?!4_oOOGGKCAA@@!459?-#0!155~!11}!474~$#2!155?!11@!474?-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640B-\Pq#0;2;0;0;0#1;2;0;0;66#2;2;0;66;0#3;2;0;66;66#0}|zvn^!634~$#1@ACGO_!634?-#0!6~}|zvn^!628~$#1!6?@ACGO_!628?-#0!12~}|zvn^!622~$#1!12?@ACGO_!622?-#0!10~z!7~}|zvn^!616~$#1!18?@ACGO_!616?$#3!10?C!629?-#0!24~}|zvn^!610~$#1!24?@ACGO_!610?-#0!30~}|zvn^!604~$#1!30?@ACGO_!604?-#0!36~}|zvn^!598~$#1!36?@ACGO_!598?-#0!42~}|zvn^!592~$#1!42?@ACGO_!592?-#0!48~}|zvn^!91~^^^nnf!4v!11z!4vfnn^^^!464~$#1!48?@ACGO_!586?$#2!145?___OOW!4G!11C!4GWOO___!464?-#0!54~}|zvn^!76~^^nvrz||}}!29~}}||zrvn^^!455~$#1!54?@ACGO_!580?$#2!136?__OGKCAA@@!29?@@AACKGO__!455?-#0!60~}|zvn^!64~^nvzz{!49~{zzvn^!449~$#1!60?@ACGO_!574?$#2!130?_OGCCB!49?BCCGO_!449?-#0!66~}|zvn^!54~^nvx}!59~}xvn^!445~$#1!66?@ACGO_!568?$#2!126?_OGE@!59?@EGO_!445?-#0!72~}|zvn^!46~Np}!67~}pN!443~$#1!72?@ACGO_!562?$#2!124?oM@!67?@Mo!443?-#0!78~}|zvn^!37~Nr{!73~{rN!440~$#1!78?@ACGO_!556?$#2!121?oKB!73?BKo!440?-#0!84~}|zvn^!30~@}!77~}@!439~$#1!84?@ACGO_!550?$#2!120?}@!77?@}!439?-#0!90~}|zvn^!23~Fw!39~?!9}?!29~wF!438~$#1!90?@ACGO_!64?~!9@~!469?$#2!119?wF!79?Fw!438?-#0!96~}|zvn!18~?!40~_!9n_!30~?!438~$#1!96?@ACGO!59?^!9O^!469?$#2!119?~!81?~!438?-#0!119~?!81~?!438~$#2!119?~!81?~!438?-#0!120~?!79~?!439~$#2!120?~!79?~!439?-#0!120~{b^!75~^b{!439~$#2!120?B[_!75?_[B!439?-#0!122~}xf^!69~^fx}!441~$#2!122?@EW_!69?_WE@!441?-#0!125~{rn^!63~^nr{!444~$#2!125?BKO_!63?_OKB!444?-#0!129~{rn^!55~^nr{!448~$#2!129?BKO_!55?_OKB!448?-#0!133~}}xvvn^^!39~^^nvvx}}!452~$#2!133?@@EGGO__!39?__OGGE@@!452?-#0!140~}}||zrvvnnN!4^!11~!4^Nnnvvrz||}}!459~$#2!140?@@AACKGGOOo!4_!11?!4_oOOGGKCAA@@!459?-#0!155~!11}!474~$#2!155?!11@!474?-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640~-#0!640B-\Graphics stubs OK diff --git a/tests/expected/hailstone.expected b/tests/expected/hailstone.expected new file mode 100644 index 0000000..208e1e8 --- /dev/null +++ b/tests/expected/hailstone.expected @@ -0,0 +1 @@ +Hailstone OK: 27 -> 111 steps, max 9232 diff --git a/tests/expected/hanoi.expected b/tests/expected/hanoi.expected new file mode 100644 index 0000000..cc60238 --- /dev/null +++ b/tests/expected/hanoi.expected @@ -0,0 +1 @@ +Hanoi OK: 15 moves diff --git a/tests/expected/hello.expected b/tests/expected/hello.expected new file mode 100644 index 0000000..ee8cb8a --- /dev/null +++ b/tests/expected/hello.expected @@ -0,0 +1 @@ +HELLO, WORLD! diff --git a/tests/expected/hundred_doors.expected b/tests/expected/hundred_doors.expected new file mode 100644 index 0000000..ef8384a --- /dev/null +++ b/tests/expected/hundred_doors.expected @@ -0,0 +1 @@ +100 Doors OK: 10 open diff --git a/tests/expected/if_then_else.expected b/tests/expected/if_then_else.expected new file mode 100644 index 0000000..05fd4c5 --- /dev/null +++ b/tests/expected/if_then_else.expected @@ -0,0 +1,3 @@ +X > 3 +X >= 3 +Jumped to 80 diff --git a/tests/expected/inkey_ext.expected b/tests/expected/inkey_ext.expected new file mode 100644 index 0000000..bfd9702 --- /dev/null +++ b/tests/expected/inkey_ext.expected @@ -0,0 +1,2 @@ +Empty INKEY$ ok +INKEY$ extended keys test passed diff --git a/tests/expected/invoice.expected b/tests/expected/invoice.expected new file mode 100644 index 0000000..f24c534 --- /dev/null +++ b/tests/expected/invoice.expected @@ -0,0 +1,12 @@ +================================ + INVOICE +================================ +Item Qty Price Amount +------------- --- ------- --------- +Widget A 12 $4.99 $59.88 +Widget B 5 $12.50 $62.50 +Gizmo C 3 $29.99 $89.97 +Part D 100 $0.75 $75.00 + --------- + Total: $287.35 +Invoice OK diff --git a/tests/expected/leibniz.expected b/tests/expected/leibniz.expected new file mode 100644 index 0000000..6e37f33 --- /dev/null +++ b/tests/expected/leibniz.expected @@ -0,0 +1 @@ +Pi approx: 3.140593 diff --git a/tests/expected/lprint.expected b/tests/expected/lprint.expected new file mode 100644 index 0000000..bb1bbe2 --- /dev/null +++ b/tests/expected/lprint.expected @@ -0,0 +1 @@ +LPRINT test complete diff --git a/tests/expected/luhn.expected b/tests/expected/luhn.expected new file mode 100644 index 0000000..1e89f71 --- /dev/null +++ b/tests/expected/luhn.expected @@ -0,0 +1 @@ +Luhn OK diff --git a/tests/expected/math_ops.expected b/tests/expected/math_ops.expected new file mode 100644 index 0000000..27b4098 --- /dev/null +++ b/tests/expected/math_ops.expected @@ -0,0 +1,19 @@ +Arithmetic: + 4 + 7 + 42 + 33.33333 + 1024 + 2 + 3 +Functions: + 1 + 1 + 12 + 42 + 3 +-3 + 0 + 1 +-1 + 3.141593 diff --git a/tests/expected/matrix_mult.expected b/tests/expected/matrix_mult.expected new file mode 100644 index 0000000..c847c16 --- /dev/null +++ b/tests/expected/matrix_mult.expected @@ -0,0 +1,4 @@ + 30 24 18 + 84 69 54 + 138 114 90 +Matrix mult OK diff --git a/tests/expected/mid_assign.expected b/tests/expected/mid_assign.expected new file mode 100644 index 0000000..b2f6889 --- /dev/null +++ b/tests/expected/mid_assign.expected @@ -0,0 +1,2 @@ +HELLO BASIC +XYCDEF diff --git a/tests/expected/mkicvi.expected b/tests/expected/mkicvi.expected new file mode 100644 index 0000000..352437c --- /dev/null +++ b/tests/expected/mkicvi.expected @@ -0,0 +1,3 @@ + 12345 + 3.14 + 2.718281745910645 diff --git a/tests/expected/monte_carlo.expected b/tests/expected/monte_carlo.expected new file mode 100644 index 0000000..1f9ad59 --- /dev/null +++ b/tests/expected/monte_carlo.expected @@ -0,0 +1,3 @@ +Pi estimate: 3.1288 +Actual pi: 3.1416 +Monte Carlo OK diff --git a/tests/expected/mult_table.expected b/tests/expected/mult_table.expected new file mode 100644 index 0000000..9d501c6 --- /dev/null +++ b/tests/expected/mult_table.expected @@ -0,0 +1,15 @@ + X 1 2 3 4 5 6 7 8 9 10 11 12 +--------------------------------------------------- + 1 1 2 3 4 5 6 7 8 9 10 11 12 + 2 4 6 8 10 12 14 16 18 20 22 24 + 3 9 12 15 18 21 24 27 30 33 36 + 4 16 20 24 28 32 36 40 44 48 + 5 25 30 35 40 45 50 55 60 + 6 36 42 48 54 60 66 72 + 7 49 56 63 70 77 84 + 8 64 72 80 88 96 + 9 81 90 99 108 + 10 100 110 120 + 11 121 132 + 12 144 +Multiplication table OK diff --git a/tests/expected/nested_for.expected b/tests/expected/nested_for.expected new file mode 100644 index 0000000..7f7fc76 --- /dev/null +++ b/tests/expected/nested_for.expected @@ -0,0 +1,5 @@ + 1 2 3 4 5 + 2 4 6 8 10 + 3 6 9 12 15 + 4 8 12 16 20 + 5 10 15 20 25 diff --git a/tests/expected/number_guess.expected b/tests/expected/number_guess.expected new file mode 100644 index 0000000..4a17e3a --- /dev/null +++ b/tests/expected/number_guess.expected @@ -0,0 +1,2 @@ +Found 71 in 5 tries +Number guess OK diff --git a/tests/expected/on_goto.expected b/tests/expected/on_goto.expected new file mode 100644 index 0000000..fca7e33 --- /dev/null +++ b/tests/expected/on_goto.expected @@ -0,0 +1,3 @@ +Branch 1 +Branch 2 +Branch 3 diff --git a/tests/expected/pascal_triangle.expected b/tests/expected/pascal_triangle.expected new file mode 100644 index 0000000..67dfe7f --- /dev/null +++ b/tests/expected/pascal_triangle.expected @@ -0,0 +1,9 @@ + 1 + 1 1 + 1 2 1 + 1 3 3 1 + 1 4 6 4 1 + 1 5 10 10 5 1 + 1 6 15 20 15 6 1 + 1 7 21 35 35 21 7 1 +Pascal's triangle OK diff --git a/tests/expected/play_music.expected b/tests/expected/play_music.expected new file mode 100644 index 0000000..cc34be7 --- /dev/null +++ b/tests/expected/play_music.expected @@ -0,0 +1 @@ +play_music OK diff --git a/tests/expected/play_scale.expected b/tests/expected/play_scale.expected new file mode 100644 index 0000000..b009fe5 --- /dev/null +++ b/tests/expected/play_scale.expected @@ -0,0 +1 @@ +play_scale OK diff --git a/tests/expected/prime_sieve.expected b/tests/expected/prime_sieve.expected new file mode 100644 index 0000000..413bd87 --- /dev/null +++ b/tests/expected/prime_sieve.expected @@ -0,0 +1,2 @@ + 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 + 25 primes found diff --git a/tests/expected/print_using.expected b/tests/expected/print_using.expected new file mode 100644 index 0000000..6e6d434 --- /dev/null +++ b/tests/expected/print_using.expected @@ -0,0 +1,6 @@ + 3.14 +-42.50 ++ 99.10 +H +Hello +$1234.56 diff --git a/tests/expected/random_access.expected b/tests/expected/random_access.expected new file mode 100644 index 0000000..acf1f2a --- /dev/null +++ b/tests/expected/random_access.expected @@ -0,0 +1 @@ +Alice 25 Bob 30 diff --git a/tests/expected/roman_numerals.expected b/tests/expected/roman_numerals.expected new file mode 100644 index 0000000..e6a5e4a --- /dev/null +++ b/tests/expected/roman_numerals.expected @@ -0,0 +1,7 @@ + 1990 =MCMXC + 2008 =MMVIII + 1666 =MDCLXVI + 3999 =MMMCMXCIX + 42 =XLII + 14 =XIV +Roman numerals OK diff --git a/tests/expected/run_file.expected b/tests/expected/run_file.expected new file mode 100644 index 0000000..c45ef3f --- /dev/null +++ b/tests/expected/run_file.expected @@ -0,0 +1,2 @@ +Before RUN +Chained OK diff --git a/tests/expected/save_binary.expected b/tests/expected/save_binary.expected new file mode 100644 index 0000000..7b29dee --- /dev/null +++ b/tests/expected/save_binary.expected @@ -0,0 +1 @@ +Binary round-trip ok diff --git a/tests/expected/save_load.expected b/tests/expected/save_load.expected new file mode 100644 index 0000000..375bf3d --- /dev/null +++ b/tests/expected/save_load.expected @@ -0,0 +1 @@ +Program saved diff --git a/tests/expected/sound_test.expected b/tests/expected/sound_test.expected new file mode 100644 index 0000000..2692fc3 --- /dev/null +++ b/tests/expected/sound_test.expected @@ -0,0 +1 @@ +sound_test OK diff --git a/tests/expected/stats_calc.expected b/tests/expected/stats_calc.expected new file mode 100644 index 0000000..c73a523 --- /dev/null +++ b/tests/expected/stats_calc.expected @@ -0,0 +1,4 @@ +Mean: 50.50 +Variance: 820.25 +Std Dev: 28.64 +Stats calc OK diff --git a/tests/expected/string_ops.expected b/tests/expected/string_ops.expected new file mode 100644 index 0000000..643c819 --- /dev/null +++ b/tests/expected/string_ops.expected @@ -0,0 +1,14 @@ +HELLO +WORLD +WORLD + 4 +A + 65 + 3.14 + 42 + X +********** +FF +377 + 7 +ABCDEF diff --git a/tests/expected/temp_table.expected b/tests/expected/temp_table.expected new file mode 100644 index 0000000..c4193f6 --- /dev/null +++ b/tests/expected/temp_table.expected @@ -0,0 +1,11 @@ + Celsius Fahrenheit + ------- ---------- + -40.0 -40.0 + -20.0 -4.0 + 0.0 32.0 + 20.0 68.0 + 40.0 104.0 + 60.0 140.0 + 80.0 176.0 + 100.0 212.0 +Temp table OK diff --git a/tests/expected/text_adventure.expected b/tests/expected/text_adventure.expected new file mode 100644 index 0000000..7715e59 --- /dev/null +++ b/tests/expected/text_adventure.expected @@ -0,0 +1,8 @@ +Room: Dark cave +Room: Forest path +Room: Dark cave +Room: Forest path +Room: Treasure room +You found the treasure! +Turns taken: 4 +Text adventure OK diff --git a/tests/expected/variables.expected b/tests/expected/variables.expected new file mode 100644 index 0000000..f05c145 --- /dev/null +++ b/tests/expected/variables.expected @@ -0,0 +1,3 @@ + 42 3.14 2.718281745910645 +Hello + 8 diff --git a/tests/expected/while_wend.expected b/tests/expected/while_wend.expected new file mode 100644 index 0000000..5faff3c --- /dev/null +++ b/tests/expected/while_wend.expected @@ -0,0 +1,2 @@ + 2 4 6 8 10 +Done diff --git a/tests/expected/write_input.expected b/tests/expected/write_input.expected new file mode 100644 index 0000000..cdaf437 --- /dev/null +++ b/tests/expected/write_input.expected @@ -0,0 +1,2 @@ +Alice 25 +Bob 30 diff --git a/tests/harness/hamurabi_input.txt b/tests/harness/hamurabi_input.txt new file mode 100644 index 0000000..0594da6 --- /dev/null +++ b/tests/harness/hamurabi_input.txt @@ -0,0 +1,40 @@ +0 +0 +2000 +1000 +0 +0 +2000 +1000 +0 +0 +2000 +1000 +0 +0 +2000 +1000 +0 +0 +2000 +1000 +0 +0 +2000 +1000 +0 +0 +2000 +1000 +0 +0 +2000 +1000 +0 +0 +2000 +1000 +0 +0 +2000 +1000 diff --git a/tests/programs/inkey_ext.bas b/tests/programs/inkey_ext.bas new file mode 100644 index 0000000..4bce0c6 --- /dev/null +++ b/tests/programs/inkey_ext.bas @@ -0,0 +1,5 @@ +10 REM Test INKEY$ extended key support (non-interactive) +20 REM When no key is available, INKEY$ returns "" +30 K$ = INKEY$ +40 IF LEN(K$) = 0 THEN PRINT "Empty INKEY$ ok" ELSE PRINT "FAIL: expected empty" +50 PRINT "INKEY$ extended keys test passed" diff --git a/tests/programs/save_binary.bas b/tests/programs/save_binary.bas new file mode 100644 index 0000000..2a54138 --- /dev/null +++ b/tests/programs/save_binary.bas @@ -0,0 +1,9 @@ +10 REM Binary SAVE/LOAD round-trip test +20 REM Create a test program, save as binary default, load binary back +30 OPEN "gwbasic_binsrc.bas" FOR OUTPUT AS #1 +40 PRINT #1, "10 PRINT "+CHR$(34)+"Binary round-trip ok"+CHR$(34) +50 PRINT #1, "20 SAVE "+CHR$(34)+"gwbasic_roundtrip.bas"+CHR$(34) +60 PRINT #1, "30 KILL "+CHR$(34)+"gwbasic_binsrc.bas"+CHR$(34) +70 PRINT #1, "40 KILL "+CHR$(34)+"gwbasic_roundtrip.bas"+CHR$(34) +80 CLOSE #1 +90 LOAD "gwbasic_binsrc.bas",R