# akbasic

A BASIC interpreter written in C, styled after [Commodore BASIC 7.0](http://www.jbrain.com/pub/cbm/manuals/128/C128PRG.pdf)
and the [Dartmouth BASIC of 1964](https://www.dartmouth.edu/basicfifty/basic.html). It runs a
`.bas` file, it gives you a prompt, and — the point of the exercise — it links into a C program
as a scripting engine.

It is a rewrite of [basicinterpreter](https://source.starfort.tech/andrew/basicinterpreter), a Go
implementation that started from the Java Lox instructions in
[craftinginterpreters.com](https://craftinginterpreters.com) and then struck off on its own. That
project is deprecated. It is vendored here as the behavioural spec to read when a question about
semantics comes up, and its acceptance corpus is checked in at
[`tests/language/`](tests/language/README.md) and runs on every build — so nothing about
building or testing this project needs it.

## Quickstart

```sh norun
git submodule update --init --recursive
cmake -S . -B build
cmake --build build --parallel
ctest --test-dir build --output-on-failure
```

```sh norun
./build/basic                                          # the REPL
./build/basic tests/language/functions.bas             # run a program
```

```basic
10 FOR I# = 1 TO 3
20   PRINT "HELLO " + I#
30 NEXT I#
```

```output
HELLO 1
HELLO 2
HELLO 3
```

Two things in that program are not Commodore BASIC and will catch you out immediately: variables
carry a **type suffix** (`I#` is an integer), and **`+` concatenates a string with a number**.
[Chapter 3](docs/03-the-language.md) explains both; [Chapter 13](docs/13-differences.md) is the
whole list of what differs from a C128.

Graphics, sound and sprites need the SDL build, which is off by default because the interpreter
and its entire test suite build on a machine with no SDL installed at all:

```sh norun
cmake -S . -B build-akgl -DAKBASIC_WITH_AKGL=ON
cmake --build build-akgl --parallel
```

That `basic` is a different program: it opens a window, draws BASIC output into it in the
Commodore font, and still puts every byte on stdout.

## Why rewrite it in C?

Three reasons, in the order they matter.

The interpreter is meant to end up *inside*
[libakgl](https://source.starfort.tech/andrew/libakgl) as a scripting engine for game authors,
and libakgl is C. Embedding a Go runtime in a C game is not a thing anybody should do to
themselves.

The Go version was already written against static pools and explicit state structs — a fixed
source table, a fixed variable pool, a 32-leaf ceiling per line — so it ports across almost
directly. It reads like C that happens to be spelled in Go.

And the port is a good excuse to find out what the original actually does, as opposed to what it
looks like it does. It found five defects nobody knew about.

## Design philosophy

A game engine cannot tolerate a scripting language that surprises it. Five rules follow from
that, and between them they explain most of what looks unusual in this interpreter:

* **Nothing in the library terminates the process.** No `exit()`, no `abort()`, no `panic`.
  Errors come back as `akerr_ErrorContext *` for the host to handle. `FINISH_NORETURN` appears
  only in a `main()`.
* **Nothing calls `malloc`.** Every object comes from a fixed pool inside `akbasic_Runtime`.
  Exhausting one is a diagnosable error, not a crash and not a slow leak.
* **No file-scope mutable state.** Interpreter state lives in an `akbasic_Runtime` you own. Two
  of them in one process do not interfere.
* **The host owns the loop.** `akbasic_runtime_run(rt, n)` executes at most `n` steps and
  returns. A script containing `10 GOTO 10` costs you `n` steps per frame and nothing else.
* **Hardware is a record of function pointers.** Graphics, audio, input and sprites attach as
  backends, and any of them may be `NULL` — that is how a host withholds a capability, and how a
  host that renders some other way never links libakgl at all.

Nothing is silently ignored, either. A verb that needs a device it was not given, or a capability
nothing underneath can supply, refuses by name and says why.

## Two ways to use it

**As a program.** `basic` is a REPL and a script runner, and the standalone driver owns the
things a library has no business owning: argv, `QUIT`, and the window in the SDL build.
[The guide](docs/README.md) is written for this reader.

**As a library.** A host links `akbasic::akbasic`, hands the interpreter a script, and steps it a
frame at a time:

```cmake
add_subdirectory(deps/akbasic EXCLUDE_FROM_ALL)
target_link_libraries(YOUR_GAME PRIVATE akbasic::akbasic)
```

Host and script exchange variables through the same pool the script itself uses — no marshalling
layer and no copy. `PRINT` goes through an `akbasic_TextSink` the host supplies, so a game draws
BASIC output into its own text layer. [`examples/embed.c`](examples/embed.c) and
[`examples/hostvars.c`](examples/hostvars.c) are complete and runnable, and both are built and
run by every build, so they cannot rot. [Chapter 10](docs/10-embedding.md) walks through the API.

## What state it is in

Everything the Go version does, and by now a good deal more. All 41 `.bas` files of the
reference's corpus produce the expected output, including error messages, each as a separate
CTest case so a failure names the file.

What is still missing, what is refused deliberately, and the eleven defects inherited from the Go
version are catalogued in [`TODO.md`](TODO.md) and summarised for a BASIC programmer in
[Chapter 13](docs/13-differences.md).

## Where everything else lives

| | |
|---|---|
| [`docs/`](docs/README.md) | The guide: twenty-one chapters, the language then each hardware area then a reference section for every verb and function, [Chapter 14](docs/14-architecture.md) on the interpreter's own architecture, [Chapter 15](docs/15-error-codes.md) listing every error code, [Chapters 17](docs/17-tutorial-breakout.md) and [18](docs/18-tutorial-breakout-artwork.md) building a whole game twice, and [Chapters 20](docs/20-tutorial-galaga.md) and [21](docs/21-tutorial-galaga-enemies.md) building a C game that embeds the interpreter |
| [`MAINTENANCE.md`](MAINTENANCE.md) | For contributors and maintainers: the documentation-example harness, the three test lists, mutation testing, error-code allocation, style |
| [`TODO.md`](TODO.md) | Outstanding defects, with file, line and consequence |
| [`tests/language/README.md`](tests/language/README.md) | The editable language corpus and the rule for changing it |

API documentation builds with `doxygen Doxyfile`, into `build/docs/html`.

## Dependencies

Everything is a submodule; `git submodule update --init --recursive` gets all of it. There is
nothing to install first.

* [libakerror](https://source.starfort.tech/andrew/libakerror) 2.0.2 — TRY/CATCH-style error
  contexts. Every function that can fail returns one. 2.0.0 made it thread safe and broke the
  ABI; anything built against a 1.x header must be rebuilt rather than relinked.
* [libakstdlib](https://source.starfort.tech/andrew/libakstdlib) 0.2.0 — libc wrappers that
  report through `libakerror`. String-to-number conversion goes straight to it, which is why
  `VAL("garbage")` is an error rather than a silent `0`. Its public header now also pulls in
  `<dirent.h>` and `<sys/stat.h>` for the directory and file-metadata wrappers.
* [libakgl](https://source.starfort.tech/andrew/libakgl) 0.9.0 — **optional**, only for
  `-DAKBASIC_WITH_AKGL=ON`. Pulls in SDL3. Its soname carries `MAJOR.MINOR` while the major is 0,
  so rebuild rather than relink.
* [basicinterpret](https://source.starfort.tech/andrew/basicinterpret) — the Go original.
  Not linked, not built, and safe to omit.
