Some checks failed
akbasic CI Build / cmake_build (push) Successful in 3m0s
akbasic CI Build / sanitizers (push) Successful in 3m45s
akbasic CI Build / coverage (push) Failing after 3m22s
akbasic CI Build / akgl_build (push) Failing after 21s
akbasic CI Build / mutation_test (push) Successful in 11m26s
Organised the way the C128 Programmer's Reference Guide is: the language first, then each hardware area, then the reference sections. One markdown file per chapter. The verb and function references are generated from the interpreter's own dispatch table, with an assertion that every row is described, so they cannot drift out of step with what the program accepts. 98 verbs and 30 functions. Every example was run before it was written down, which caught three claims that were wrong: a whole FOR loop on one line prints nothing rather than looping once, MID and INSTR count from zero where a C128 counts from one, and a multi-line DEF returns a value the caller has to assign away. Chapter 13 is the list a BASIC 7.0 programmer needs -- roughly sixty documented differences, including the two known FOR defects and the fact that drawing does not survive a frame. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
114 lines
4.1 KiB
Markdown
114 lines
4.1 KiB
Markdown
# 10. Embedding
|
|
|
|
The whole design of this interpreter is shaped by one requirement: a game must be able
|
|
to embed it as a scripting engine without giving up control. That produces four rules,
|
|
and they explain most of what looks unusual elsewhere in this guide.
|
|
|
|
1. **Nothing in the library terminates the process.** Errors come back as
|
|
`akerr_ErrorContext *` for you to handle.
|
|
2. **The interpreter owns no window, no renderer and no event loop.** It draws through
|
|
whatever you already created.
|
|
3. **It never blocks.** `SLEEP`, `GETKEY`, `PLAY` and `WAIT` hold the program without
|
|
holding your frame rate.
|
|
4. **You can bound it.** A script with `10 GOTO 10` cannot take your game with it.
|
|
|
|
## Linking
|
|
|
|
```cmake
|
|
add_subdirectory(deps/akbasic EXCLUDE_FROM_ALL)
|
|
target_link_libraries(YOUR_GAME PRIVATE akbasic::akbasic)
|
|
```
|
|
|
|
| Target | What it is | Link it? |
|
|
|---|---|---|
|
|
| `akbasic` | The interpreter. No SDL, nothing that exits. | Always |
|
|
| `akbasic_akgl` | The graphics, sound, input and sprite backends, drawing through *your* renderer | If you want them |
|
|
| `akbasic_frontend` | The standalone program's host: creates the window, owns the loop | **No.** You are the host |
|
|
|
|
## The shortest useful host
|
|
|
|
```c
|
|
#include <akbasic/runtime.h>
|
|
#include <akbasic/sink.h>
|
|
|
|
static akbasic_Runtime RUNTIME; /* too big for a stack */
|
|
static akbasic_TextSink SINK;
|
|
static akbasic_StdioSink SINKSTATE;
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *run_script(const char *source)
|
|
{
|
|
PREPARE_ERROR(e);
|
|
|
|
PASS(e, akbasic_sink_init_stdio(&SINK, &SINKSTATE, stdout, NULL));
|
|
PASS(e, akbasic_runtime_init(&RUNTIME, &SINK));
|
|
PASS(e, akbasic_runtime_load(&RUNTIME, source));
|
|
PASS(e, akbasic_runtime_start(&RUNTIME, AKBASIC_MODE_RUN));
|
|
|
|
while ( RUNTIME.mode != AKBASIC_MODE_QUIT ) {
|
|
PASS(e, akbasic_runtime_settime(&RUNTIME, your_clock_ms()));
|
|
PASS(e, akbasic_runtime_run(&RUNTIME, 256)); /* 256 steps, then return */
|
|
your_draw_a_frame();
|
|
}
|
|
SUCCEED_RETURN(e);
|
|
}
|
|
```
|
|
|
|
`akbasic_runtime_run(rt, n)` runs at most `n` steps and returns. That bound is what
|
|
keeps a runaway script from owning your process.
|
|
|
|
`akbasic_runtime_settime()` is how the interpreter knows what time it is. It reads no
|
|
clock of its own, because it owns no loop. If you never call it, every duration expires
|
|
immediately — audible, but never a hang.
|
|
|
|
## Exchanging variables with a script
|
|
|
|
Use `akbasic_runtime_global()`. It finds or creates the variable in the script's
|
|
outermost scope, which is the only place both of you can reliably see:
|
|
|
|
```c
|
|
akbasic_Variable *health = NULL;
|
|
int64_t subscript[1] = { 0 };
|
|
|
|
PASS(e, akbasic_runtime_global(&RUNTIME, "HEALTH#", &health));
|
|
PASS(e, akbasic_variable_set_integer(health, 100, subscript, 1));
|
|
```
|
|
|
|
Do not reach for `akbasic_environment_get()`. A script suspended part-way through a
|
|
bounded run is usually inside a `FOR` or `GOSUB` body, and a variable created there
|
|
dies when the body pops — silently, with the script reading it correctly right up until
|
|
it stops.
|
|
|
|
## Lending devices
|
|
|
|
```c
|
|
PASS(e, akbasic_runtime_set_devices(&RUNTIME, &graphics, &audio, &input, &sprites));
|
|
```
|
|
|
|
Any of them may be `NULL`, and that is how you withhold a capability: a script given no
|
|
audio backend gets an error from `SOUND` rather than silence. Each is a record of
|
|
function pointers, so you can supply your own and never link the graphics library at
|
|
all.
|
|
|
|
`akbasic_akgl` provides implementations that draw through a renderer *you* created:
|
|
|
|
```c
|
|
PASS(e, akbasic_graphics_init_akgl(&graphics, &gstate, my_renderer));
|
|
PASS(e, akbasic_sprite_init_akgl(&sprites, &sstate, my_renderer, &gstate));
|
|
```
|
|
|
|
Sprites become real actors in your registry, so your game can see them.
|
|
|
|
## Where a script's errors go
|
|
|
|
A BASIC-level error is reported through the sink and stops the script; it does not come
|
|
back to you as a failure. What comes back to you is an error in the *interpreter* —
|
|
pool exhaustion, a NULL argument — which is yours to handle.
|
|
|
|
That is the split to hold on to: a script's mistakes are the script's problem, and your
|
|
program keeps running.
|
|
|
|
## Reading it all
|
|
|
|
`README.md` in the repository root carries the full API surface, the pool limits, and
|
|
the exact list of what each target pulls in.
|