diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..8ed948b --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,44 @@ +name: Docs + +on: + push: + branches: [main] + paths: [docs/**] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.x" + + - run: pip install -r docs/requirements.txt + + - run: sphinx-build -b html docs docs/_build/html + + - uses: actions/upload-pages-artifact@v3 + with: + path: docs/_build/html + + deploy: + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deploy.outputs.page_url }} + steps: + - id: deploy + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 55b33cf..b5d1ae2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ build/ +docs/_build/ *.o *.d gwbasic_*.txt diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..260f93b --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,71 @@ +# Architecture + +## Pipeline + +``` +Source text → Tokenizer (CRUNCH) → Token stream + ↓ + Expression evaluator (FRMEVL) + ↓ + Statement dispatcher (NEWSTT) + ↓ + 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. + +## Module Map + +| Module | Source | Original Assembly | +|--------|--------|--------------------| +| Tokenizer (CRUNCH/LIST) | `tokenizer.c` | GWMAIN.ASM | +| Expression evaluator | `eval.c` | GWEVAL.ASM | +| Execution loop + control flow | `interp.c` | BINTRP.ASM | +| Graphics engine | `graphics.c` | — | +| Token/keyword tables | `tokens.c`, `tokens.h` | IBMRES.ASM | +| Error handling | `error.c` | GWDATA.ASM | +| Integer arithmetic | `math_int.c` | MATH1.ASM | +| Float ops + MBF conversion | `math_float.c` | MATH2.ASM | +| Transcendentals | `math_transcend.c` | MATH1.ASM | +| String functions | `strings.c` | BISTRS.ASM | +| PRINT statement | `print.c` | BINTRP.ASM | +| PRINT USING | `print_using.c` | BIPRTU.ASM | +| Variables + arrays | `vars.c`, `arrays.c` | GWMAIN.ASM | +| File I/O + random access | `fileio.c` | BIPTRG.ASM | +| Program I/O (SAVE/LOAD) | `program_io.c` | BIMISC.ASM | +| INPUT/LINE INPUT | `input.c` | BINTRP.ASM | +| Sound engine | `sound.c` | — | +| Platform abstraction | `hal_posix.c` | OEM*.ASM | + +## Source Layout + +``` +src/ — core interpreter (19 files) +include/ — headers (11 files) +platform/ — HAL backends (1 file) +tests/ — test programs (50 .BAS files) +``` + +~8,600 lines of C11. + +## Design Decisions + +### 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. + +### Key Differences from the Original + +- **IEEE 754 floating point** — MBF (Microsoft Binary Format) conversion is only + used for file I/O compatibility (CVI/CVS/CVD, MKI$/MKS$/MKD$) +- **Dynamic memory allocation** — `malloc`/`free` instead of a 64KB segment layout +- **malloc'd strings** — instead of a compacting garbage collector +- **`setjmp`/`longjmp`** — for error recovery, matching the original's stack reset + behavior diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..954f623 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,15 @@ +project = "gwbasic-c" +author = "Eremey Valetov" +copyright = "2026, Eremey Valetov" + +extensions = ["myst_parser"] +source_suffix = {".md": "markdown"} + +html_theme = "furo" +html_title = "gwbasic-c" + +html_theme_options = { + "source_repository": "https://github.com/evvaletov/gwbasic-c", + "source_branch": "main", + "source_directory": "docs/", +} diff --git a/docs/development.md b/docs/development.md new file mode 100644 index 0000000..322afc5 --- /dev/null +++ b/docs/development.md @@ -0,0 +1,30 @@ +# Development + +## Development History + +| Phase | Commit | Description | +|-------|--------|-------------| +| 1 | `d8e8375` | Expression calculator with direct mode | +| 2 | `6162595` | Variables, arrays, program storage, control flow | +| 3 | `c2d73e9` | File I/O, PRINT USING, SAVE/LOAD, MID$ assignment, graphics stubs | +| 4 | `df5c308` | CHAIN, RUN "file", random-access I/O (FIELD/PUT/GET), CVI/CVS/CVD/MKI$/MKS$/MKD$ | +| 5 | `1f4c460` | CI, terminal I/O (raw mode), Sixel graphics, 13 classic programs, AND/OR precedence fix | +| 5+ | `169f16d` | SOUND/BEEP/PLAY with PulseAudio backend | +| 5+ | `691031a` | Fix RESTORE with line number, 8 Rosetta Code test programs | + +## Tests + +50 test programs in `tests/programs/`. Run the full suite: + +```bash +bash tests/run_tests.sh +``` + +Each test has a 5-second timeout and compares output against expected results. + +## CI + +GitHub Actions runs on every push to `main` and on pull requests. The workflow +builds the project with PulseAudio support and runs all 50 test programs. + +See [`.github/workflows/ci.yml`](https://github.com/evvaletov/gwbasic-c/blob/main/.github/workflows/ci.yml). diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..0eedbd9 --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,71 @@ +# Getting Started + +## Dependencies + +- C11 compiler (GCC or Clang) +- CMake 3.10+ +- PulseAudio development library (`libpulse-simple`) — optional, for `SOUND`/`BEEP`/`PLAY` + +On Debian/Ubuntu: + +```bash +sudo apt-get install build-essential cmake libpulse-dev +``` + +On Fedora/RHEL: + +```bash +sudo dnf install gcc cmake pulseaudio-libs-devel +``` + +## Building + +```bash +git clone https://github.com/evvaletov/gwbasic-c.git +cd gwbasic-c +mkdir -p build && cd build +cmake .. && make +``` + +The binary is `build/gwbasic`. + +## Usage + +### Interactive Mode + +``` +$ ./gwbasic +GW-BASIC 0.5.0 +(C) Eremey Valetov 2026. MIT License. +Based on Microsoft GW-BASIC assembly source. +Ok +PRINT 2+2 + 4 +Ok +FOR I=1 TO 5:PRINT I;:NEXT + 1 2 3 4 5 +Ok +``` + +### Running a Program File + +```bash +./gwbasic tests/programs/prime_sieve.bas +``` + +### Piped Input + +```bash +echo '10 FOR I=1 TO 10:PRINT I*I;:NEXT' | ./gwbasic +``` + +### Direct Mode Expressions + +Type expressions and statements at the `Ok` prompt: + +``` +PRINT SIN(3.14159/2) + 1 +A$="HELLO WORLD":MID$(A$,7,5)="BASIC":PRINT A$ +HELLO BASIC +``` diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..a34ab5e --- /dev/null +++ b/docs/index.md @@ -0,0 +1,29 @@ +# gwbasic-c + +A portable C reimplementation of Microsoft GW-BASIC, using the +[original 8088 assembly source](https://github.com/microsoft/GW-BASIC) +(released by Microsoft in 2020) as the authoritative reference. + +This is not a transpilation — it reimplements the algorithms in clean C11 +with modern data structures while targeting bug-compatible behavior. +Unlike the original assembly (43,771 lines across 43 `.ASM` files), this +version is structured as modular C suitable for new feature development. + +## Highlights + +- **~8,600 lines of C11** with 50 test programs +- **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 +- **Terminal I/O** — raw mode for `INKEY$` and `INPUT$` +- **MIT License** + +```{toctree} +:maxdepth: 2 + +getting-started +language-reference +architecture +development +roadmap +``` diff --git a/docs/language-reference.md b/docs/language-reference.md new file mode 100644 index 0000000..a76f82b --- /dev/null +++ b/docs/language-reference.md @@ -0,0 +1,100 @@ +# Language Reference + +## Data Types + +| Type | Suffix | Description | +|------|--------|-------------| +| INTEGER | `%` | 16-bit signed | +| SINGLE | `!` | 32-bit float | +| DOUBLE | `#` | 64-bit float | +| STRING | `$` | Up to 255 bytes | + +## Operators + +`+`, `-`, `*`, `/`, `^`, `\` (integer div), `MOD`, `AND`, `OR`, `XOR`, `EQV`, +`IMP`, `NOT`, `<`, `=`, `>`, `<=`, `>=`, `<>` + +## Numeric Functions + +`SGN`, `INT`, `ABS`, `SQR`, `SIN`, `COS`, `TAN`, `ATN`, `LOG`, `EXP`, `RND`, +`FIX`, `CINT`, `CSNG`, `CDBL` + +## String Functions + +`LEN`, `ASC`, `CHR$`, `VAL`, `STR$`, `LEFT$`, `RIGHT$`, `MID$`, `SPACE$`, +`STRING$`, `HEX$`, `OCT$`, `INSTR`, `INPUT$` + +## File Functions + +`EOF`, `LOC`, `LOF` + +## Pseudo-variables + +`ERL`, `ERR`, `CSRLIN`, `INKEY$`, `DATE$`, `TIME$` + +## Literals + +Decimal, `&H` hex, `&O` octal, `D` exponent (double), `E` exponent (single), +type suffixes (`%`, `!`, `#`) + +## Statements + +| Category | Statements | +|----------|------------| +| Output | `PRINT`, `LPRINT`, `PRINT USING`, `WRITE`, `CLS` | +| Variables | `LET`, `DIM`, `ERASE`, `SWAP`, `DEFINT`, `DEFSNG`, `DEFDBL`, `DEFSTR` | +| Control flow | `GOTO`, `GOSUB`/`RETURN`, `FOR`/`NEXT`, `IF`/`THEN`/`ELSE`, `WHILE`/`WEND`, `ON...GOTO`, `ON...GOSUB` | +| Input | `INPUT`, `LINE INPUT`, `DATA`/`READ`/`RESTORE`, `INKEY$` | +| Program control | `RUN`, `RUN "file"`, `CONT`, `STOP`, `END`, `NEW`, `LIST`, `CLEAR` | +| 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` | +| Error handling | `ON ERROR GOTO`, `RESUME`, `RESUME NEXT`, `RESUME n`, `ERROR`, `ERR`, `ERL` | +| User functions | `DEF FN`, `RANDOMIZE` | +| File management | `KILL`, `NAME` | +| Screen | `LOCATE`, `COLOR`, `WIDTH`, `SCREEN` | +| Graphics | `PSET`, `PRESET`, `LINE`, `CIRCLE`, `DRAW`, `PAINT` | +| Sound | `SOUND`, `BEEP`, `PLAY` (MML parser, PulseAudio backend) | +| Misc | `POKE`, `KEY`, `TRON`/`TROFF`, `OPTION BASE`, `MID$` assignment, `COMMON` | +| System | `SYSTEM` | + +## Graphics + +Graphics mode is activated with `SCREEN 1` (320x200, 4 colors) or +`SCREEN 2` (640x200, monochrome). Drawing commands render to an internal +framebuffer and output via [Sixel graphics](https://en.wikipedia.org/wiki/Sixel), +which works in terminals like xterm, mlterm, foot, and WezTerm. + +### Drawing Commands + +- `PSET (x,y), color` / `PRESET (x,y)` — set/reset individual pixels +- `LINE (x1,y1)-(x2,y2), color [,B|BF]` — lines, boxes, filled boxes +- `CIRCLE (cx,cy), r [,color [,start, end [,aspect]]]` — circles and arcs +- `PAINT (x,y), fill, border` — flood fill +- `DRAW string` — turtle graphics mini-language (U/D/L/R/E/F/G/H, M, C, S, B, N) +- `POINT (x,y)` — read pixel color +- `COLOR fg, bg` — set foreground/background colors + +### Example + +``` +SCREEN 1 +LINE (0,0)-(319,199), 1 +CIRCLE (160,100), 80, 2 +PAINT (160,100), 3, 2 +``` + +## Sound + +- `SOUND frequency, duration` — play a tone (frequency in Hz, duration in clock ticks) +- `BEEP` — play the default beep +- `PLAY string` — Music Macro Language (MML) string for melodies + +Sound output uses PulseAudio when available; commands are silently ignored otherwise. + +## Terminal I/O + +When stdin is a terminal, the interpreter enters POSIX raw mode for real-time +keyboard polling with `INKEY$` and character-at-a-time input with `INPUT$(n)`. +Piped input is handled normally without raw mode. `INPUT` and `LINE INPUT` +temporarily exit raw mode for cooked-mode line editing. diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..600e05f --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,3 @@ +sphinx +furo +myst-parser diff --git a/docs/roadmap.md b/docs/roadmap.md new file mode 100644 index 0000000..23a1164 --- /dev/null +++ b/docs/roadmap.md @@ -0,0 +1,33 @@ +# Roadmap + +## Planned Features + +- **PRINT USING edge cases** — Thousands separator (`,`), `**` asterisk fill, + `**$` combined, `^^^^` scientific notation, `&` full-string format +- **Binary SAVE/LOAD** — Protected (,P) and tokenized binary formats +- **DEF SEG / PEEK / POKE** — Memory-mapped I/O emulation for common BIOS/screen + addresses +- **Compatibility testing** — Automated test suite comparing output against + original GWBASIC.EXE under DOSBox-X +- **GET/PUT** — Sprite capture and blit for graphics mode + +## IDE and Notebook Integration + +- **Jupyter kernel for GW-BASIC** — A Jupyter Notebook kernel that runs + GW-BASIC programs cell-by-cell, with rich output for `PRINT`, inline graphics + rendering for drawing commands, and interactive `INPUT` via notebook widgets. + Similar in spirit to [foxkernel](https://github.com/evvaletov/foxkernel). +- **JetBrains plugin (IntelliJ/CLion)** — Full-featured language plugin with + syntax highlighting, code completion, line number navigation, run + configurations, debugger integration (breakpoints via `STOP`, variable + inspection), structure view (line number outline), and error annotations. +- **VS Code extension** — Language extension providing syntax highlighting + (TextMate grammar), snippets, run/debug tasks, integrated terminal runner, + and Language Server Protocol support for diagnostics and hover info. + +## Known Limitations + +- No binary/protected file format support (ASCII only) +- `PEEK`/`POKE` are no-ops +- String garbage collection not implemented (uses `malloc`/`free` instead) +- Maximum 256 variables, 64 arrays, 16 FOR nesting, 24 GOSUB nesting