Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ corpus runs reuse the verified `.cache/` checkout.
`go-basic` CLI.
2. Make the smallest product-code change that passes the test.
3. Refactor only while the tests remain green.
4. Update `README.md` and the compatibility documentation when behavior or
supported syntax changes.
4. Update the relevant user guide, language reference, and compatibility
documentation when behavior or supported syntax changes. Keep `README.md`
focused on project orientation and links into those documents.
5. Run the appropriate validation gates before opening a pull request.

Keep interpreter behavior in `pkg/interpreter/`. The command under
Expand Down Expand Up @@ -58,6 +59,19 @@ The full test suite uses the race detector and enforces at least 80% total
statement coverage. Fuzzing is deliberately time-bounded; preserve useful
regression seeds when a fuzzer finds a defect.

## Releases

Tags matching `v*` trigger `.github/workflows/release.yml`. The workflow repeats
the quality, fuzz, compatibility, and vulnerability gates, then publishes
checksummed Linux (`amd64`, `arm64`), macOS (`amd64`, `arm64`), and Windows
(`amd64`) archives.

Build and verify the same artifact set locally before tagging:

```bash
make release-check VERSION=v0.1.0
```

## Pull requests

Use a concise, imperative, sentence-case title. Keep each change focused and
Expand Down
113 changes: 32 additions & 81 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,28 @@
[![Go version](https://img.shields.io/github/go-mod/go-version/scottdensmore/go-basic)](go.mod)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

![A vintage computer running BASIC, connected to an abstract software pipeline](docs/assets/go-basic-hero.jpg)

A cross-platform interpreter for classic, line-numbered BASIC, written in Go.
It targets Microsoft 8K BASIC behavior and is continuously exercised against
all 112 byte-distinct original BASIC programs in the pinned
[BASIC Computer Games](https://github.com/coding-horror/basic-computer-games)
corpus.

## Why go-basic?

- Run classic BASIC programs on Linux, macOS, and Windows.
- Get actionable source, BASIC-line, and runtime diagnostics.
- Reproduce programs with controlled random seeds and statement limits.
- Depend on a small Go codebase with no third-party runtime dependencies.
- Verify compatibility against every distinct source in a pinned historical
corpus.

## Quick start

go-basic requires Go 1.26.6 or newer. Download a checksummed archive from the
Download a checksummed archive from the
[latest release](https://github.com/scottdensmore/go-basic/releases/latest), or
build it from source:
build from source with Go 1.26.6 or newer:

```bash
git clone https://github.com/scottdensmore/go-basic.git
Expand All @@ -36,87 +47,27 @@ Hello World
5 25
```

On Windows, run `bin\go-basic.exe test\scripts\test.bas` after building with
`go build -o bin\go-basic.exe ./cmd/go-basic`.

## Usage
## Documentation

```text
go-basic [-version] [-seed number] [-max-statements number] source.bas
```

| Option | Purpose |
| Guide | What it covers |
| --- | --- |
| `-version` | Print build version information and exit. |
| `-seed number` | Seed `RND` for reproducible runs. |
| `-max-statements number` | Stop before executing more than this many statements; `0` is unlimited. |

Programs read `INPUT` values from standard input and write program output to
standard output. Parse and runtime failures are written to standard error with
the source file and BASIC line context.

## Language support

| Area | Supported behavior |
| --- | --- |
| Values | Numeric and string scalars; explicitly or implicitly dimensioned numeric and string arrays |
| Arithmetic | `+`, `-`, `*`, `/`, right-associative `^` |
| Logic | Comparisons plus Microsoft-style 16-bit `NOT`, `AND`, and `OR` |
| Control flow | `IF...THEN`, `FOR...NEXT`, `GOTO`, `GOSUB...RETURN`, computed `ON...GOTO` and `ON...GOSUB`, `END`, `STOP` |
| Input/output | `INPUT`, `PRINT`, comma print zones, semicolon suppression, `TAB` |
| Data | `DIM`, `DATA`, `READ`, `RESTORE` |
| Numeric functions | `ABS`, `SGN`, `INT`, `SIN`, `COS`, `TAN`, `ATN`, `SQR`, `LOG`, `EXP`, `RND` |
| String functions | `LEFT$`, `RIGHT$`, `MID$`, `LEN`, `STR$`, `VAL`, `CHR$`, `ASC` |
| Other | `LET`, `DEF FN`, `REM`, colon-separated statements, and the `SLEEP` extension |

The corpus methodology, acceptance criteria, and known upstream exception are
documented in [docs/compatibility.md](docs/compatibility.md).

## Development

The repository has no third-party runtime dependencies. Common workflows are:

```bash
make fmt # format Go packages
make test # race-enabled unit and black-box CLI tests
make check # formatting, vet, coverage, and lint
make fuzz # bounded lexer and parser fuzzing
make vuln # pinned govulncheck scan
make build # build bin/go-basic
make corpus-smoke # all 112 pinned byte-distinct corpus variants
make corpus-playable # complete deterministic CLI gameplay suite
```

`make check` enforces an 80% total statement-coverage minimum. Tool binaries
are pinned by the Makefile and installed under the ignored `.tools/` directory;
the downloaded corpus is pinned by commit and cached under ignored `.cache/`.

See [CONTRIBUTING.md](CONTRIBUTING.md) for the change workflow and validation
expectations.

## Project layout

```text
cmd/go-basic/ CLI entry point
cmd/corpus-*/ pinned corpus tooling
internal/corpus/ corpus acquisition, discovery, and bounded execution
pkg/interpreter/ lexer, parser, AST, structured lowering, and evaluator
test/ black-box CLI tests and BASIC fixtures
.github/workflows/ CI and release automation
```

## Releases

Tags matching `v*` trigger the release workflow. It runs the complete quality,
fuzz, compatibility, and vulnerability gates, then publishes archives for Linux
(`amd64`, `arm64`), macOS (`amd64`, `arm64`), and Windows (`amd64`) with
`SHA256SUMS`.

Maintainers can build and verify the same artifact set locally:

```bash
make release-check VERSION=v0.1.0
```
| [Getting started](docs/getting-started.md) | Installation, CLI options, input, reproducibility, and errors |
| [How it works](docs/how-it-works.md) | Source preparation, lexing, parsing, evaluation, and package boundaries |
| [Language reference](docs/language-reference.md) | Supported syntax, statements, operators, functions, and extensions |
| [Compatibility](docs/compatibility.md) | Pinned corpus, acceptance tiers, results, and known upstream exception |
| [Contributing](CONTRIBUTING.md) | Development workflow, testing expectations, and pull requests |

## Supported at a glance

go-basic supports numeric and string values, arrays, arithmetic and 16-bit
logical operators, subroutines and loops, interactive input and formatted
printing, `DATA`/`READ`, user-defined numeric functions, and the common
Microsoft BASIC numeric and string function set.

The [language reference](docs/language-reference.md) is the source of truth for
the implemented language surface. Passing the external corpus is strong
compatibility evidence, not a claim of universal support for every BASIC
dialect or hardware-specific feature.

## License

Expand Down
Binary file added docs/assets/go-basic-hero.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions docs/compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,6 @@ vulnerability gates before publishing artifacts.

Passing the corpus is strong evidence for the supported language surface, but
it is not a claim that every Microsoft BASIC dialect or hardware-specific
feature is implemented. Supported behavior is summarized in the
[README](../README.md) and enforced by the product and compatibility tests.
feature is implemented. Supported behavior is documented in the
[language reference](language-reference.md) and enforced by the product and
compatibility tests.
98 changes: 98 additions & 0 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Getting started

go-basic executes BASIC source files from a terminal. Prebuilt releases do not
require a Go installation; building the project from source requires Go 1.26.6
or newer.

## Install a release

Open the [latest release](https://github.com/scottdensmore/go-basic/releases/latest)
and download the archive for your platform:

| Platform | Architectures | Archive format |
| --- | --- | --- |
| Linux | `amd64`, `arm64` | `.tar.gz` |
| macOS | `amd64`, `arm64` | `.tar.gz` |
| Windows | `amd64` | `.zip` |

Each release includes `SHA256SUMS`. Verify the downloaded archive against that
file before extracting it, then place `go-basic` or `go-basic.exe` somewhere on
your `PATH` if you want to invoke it from any directory.

## Build from source

Clone the repository and build the command:

```bash
git clone https://github.com/scottdensmore/go-basic.git
cd go-basic
make build
```

The resulting executable is `bin/go-basic`. On Windows, build an `.exe` with:

```text
go build -o bin\go-basic.exe ./cmd/go-basic
```

## Run a program

The command accepts one BASIC source file:

```text
go-basic [-version] [-seed number] [-max-statements number] source.bas
```

From a source checkout, run the included example:

```bash
./bin/go-basic test/scripts/test.bas
```

On Windows, use:

```text
bin\go-basic.exe test\scripts\test.bas
```

## Command options

| Option | Purpose |
| --- | --- |
| `-version` | Print build version information and exit. |
| `-seed number` | Seed `RND` so a run can be reproduced. |
| `-max-statements number` | Stop before executing more than this many statements; `0` is unlimited. |

The seed changes behavior only when it is explicitly supplied. A statement
limit is useful for historical programs that intentionally loop forever or
expect the user to interrupt them.

## Input and output

`INPUT` reads comma-separated values from standard input. Numeric variables
require numbers; names ending in `$` receive strings. Programs write to
standard output, so input and output can be redirected normally:

```bash
./bin/go-basic game.bas < answers.txt > transcript.txt
```

An `INPUT` prompt defaults to `? `. Invalid input prints
`?REDO FROM START` and asks again, following classic BASIC behavior.

## Diagnostics and exit status

Read, source-preparation, parse, and runtime failures are written to standard
error. Parse errors identify the source position and BASIC line where possible;
runtime errors include the active BASIC line. Undefined jump targets remain
errors rather than being silently ignored.

| Exit status | Meaning |
| --- | --- |
| `0` | The program completed successfully or `-version` was requested. |
| `1` | A file, preparation, parse, runtime, input, or output error occurred. |
| `2` | Command-line arguments were invalid. |

For supported syntax, continue with the
[language reference](language-reference.md). To understand the execution
pipeline, see [how it works](how-it-works.md).
110 changes: 110 additions & 0 deletions docs/how-it-works.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# How go-basic works

go-basic keeps the command-line interface thin and puts language behavior in
`pkg/interpreter`. A source file moves through preparation, lexing, parsing,
and evaluation before its output reaches the terminal.

```mermaid
flowchart LR
A["BASIC source file"] --> B["PrepareSource"]
B --> C["Lexer"]
C --> D["Parser"]
D --> E["Program AST"]
E --> F["Evaluator"]
G["stdin"] --> F
H["random source"] --> F
I["statement limit"] --> F
F --> J["stdout or a line-aware error"]
```

## 1. Command-line boundary

`cmd/go-basic` owns operating-system concerns: flags, the source-file read,
standard streams, version output, and process exit status. It passes injected
input, output, randomness, and execution limits to the interpreter rather than
implementing BASIC behavior itself.

This separation lets unit tests and the black-box CLI suite exercise the same
product implementation with deterministic dependencies.

## 2. Source preparation

`interpreter.PrepareSource` normally returns line-numbered Microsoft BASIC
unchanged. That preserves strict parsing and useful diagnostics for ordinary
programs.

The pinned corpus also contains an explicitly annotated Checkers source. A
standalone `Sub_Start` marker opts that source into a small compatibility
lowering pass. The pass converts its `LOOP`/`ENDLOOP`, block
`IF`/`ENDIF`, `BREAK`, `THEN BREAK`, numeric labels, `#` comments, and `==`
equality notation into strict numbered BASIC. It then follows the same lexer,
parser, and evaluator path as every other program.

Malformed or unclosed structured blocks fail during preparation. Merely using
the text `Sub_Start` inside a string does not enable the extension.

## 3. Lexing

The lexer reads the prepared source one byte at a time and emits tokens with
source line and column information. It recognizes BASIC keywords without
requiring whitespace between every token, preserves string contents, supports
numeric exponent forms, and reports illegal or unterminated input instead of
panicking.

## 4. Parsing

The parser builds an abstract syntax tree (AST): a `Program` containing a map
of BASIC line numbers to statements plus a sorted execution order. Duplicate
line numbers and unsupported syntax are diagnostics.

Statements use dedicated parsing methods. Expressions use a Pratt parser so
operator precedence and right-associative exponentiation are encoded in one
place. Colon-separated statements become a sequence attached to their BASIC
line.

## 5. Evaluation

Before execution, the evaluator collects literal `DATA` values and flattens
statement sequences into an instruction list for each sorted BASIC line. It
then advances using a BASIC-line index and an index within that line.

Runtime state includes:

- case-insensitive scalar variables and typed arrays;
- a stack for nested `FOR`/`NEXT` loops;
- a return stack for `GOSUB`/`RETURN`;
- the current `DATA` read position;
- user-defined `DEF FN` functions;
- output-column tracking for comma zones, semicolons, and `TAB`; and
- injected input, random, sleep, line-observer, and statement-limit behavior.

Control-flow statements update the execution position explicitly. A jump to a
missing line, a mismatched loop, invalid input type, out-of-range subscript, or
other runtime violation returns an error annotated with the active BASIC line.

## Deterministic acceptance

The ordinary test suite covers the lexer, parser, evaluator, and real CLI. Two
external-corpus tiers add broader evidence:

- `make corpus-smoke` parses and executes all 112 pinned byte-distinct source
variants under deterministic bounds.
- `make corpus-playable` drives complete repeatable CLI scenarios and verifies
transcripts or meaningful gameplay milestones.

The corpus is an acceptance suite rather than a runtime dependency. Its pinned
commit, inventory, termination rules, and known exception are documented in
[compatibility](compatibility.md).

## Package map

| Path | Responsibility |
| --- | --- |
| `cmd/go-basic/` | CLI adapter and exit behavior |
| `pkg/interpreter/` | Tokens, lexer, AST, parser, structured lowering, and evaluator |
| `cmd/corpus-*/` | Commands for fetching and running the pinned corpus |
| `internal/corpus/` | Corpus verification, discovery, and bounded execution |
| `test/` | Black-box CLI tests and BASIC fixtures |

See the [language reference](language-reference.md) for the supported syntax
that flows through this pipeline.
Loading