diff --git a/docs/13-differences.md b/docs/13-differences.md index eff113d..139a409 100644 --- a/docs/13-differences.md +++ b/docs/13-differences.md @@ -217,7 +217,7 @@ interpreter's error code, which bears no relation to a Commodore error number. P | Line length | 255 | | String length | 255 | | Variables | 128 | -| Array elements | 1024 per array, 4096 in total | +| Array elements | 1024 per array, 4096 across every array and structure | | Scopes | 32 | | Labels | 64 | | `DATA` items | 512 | @@ -226,3 +226,10 @@ interpreter's error code, which bears no relation to a Commodore error number. P Every one is a fixed pool. Nothing in the interpreter calls `malloc`, which is what makes it safe to embed in a game that cannot afford a surprise allocation. + +**A scalar does not come out of the 4096.** It lives in the variable itself, so creating +one inside a `GOSUB` or a `FOR` — including the loop counter — costs nothing and can be +done for as long as the program runs. An *array* declared inside a scope does come out of +it and is not given back: the pool never frees, which is what lets a pointer into a +record outlive the scope that declared it. In practice that means `DIM` at the top rather +than in a loop, which is where you would have put it anyway. diff --git a/docs/14-architecture.md b/docs/14-architecture.md index 2a18c7a..c64cf97 100644 --- a/docs/14-architecture.md +++ b/docs/14-architecture.md @@ -366,12 +366,21 @@ error naming the pool, not a crash and not a slow leak. | `AKBASIC_MAX_VARIABLES` | 128 | runtime | `Maximum runtime variables reached` | | `AKBASIC_MAX_FUNCTIONS` | 64 | runtime | `Maximum function definitions reached` | | `AKBASIC_MAX_ARRAY_VALUES` | 4096 | runtime (`valuepool`) | `Array of N elements does not fit in the M remaining value slots` | +| — a scalar is not in that pool | 1 | **the variable** (`inlinevalue`) | — | | `AKBASIC_MAX_TOKENS` | 32 | **environment** | `Line N has more than 32 tokens` | | `AKBASIC_MAX_LEAVES` | 32 | **environment** | `No more leaves available` | | `AKBASIC_MAX_VALUES` | 64 | **environment** | `Maximum values per line reached` | The numbers are in `include/akbasic/types.h`, transcribed from the reference's `main.go` plus three the Go version did not need because it called `make()`. + +**The value pool is the one that needs a second sentence.** It is a bump allocator with +no free, so anything drawn from it is spent for the life of the run — and scope exit +returns a variable's *slot* without returning its storage. A scalar therefore does not +draw from it at all: `akbasic_variable_init()` points a one-element non-`@` variable at +its own `inlinevalue`, which is what makes a local, a `FOR` counter and a `DEF` parameter +free. Arrays and structures still spend, deliberately, because a pointer into a record is +allowed to outlive the scope that DIMmed it. [Chapter 13](13-differences.md) states the same budget from a BASIC programmer's side. The per-environment three are reset at the top of every line, which is what makes diff --git a/docs/16-structures.md b/docs/16-structures.md index 5fbdcb2..0343040 100644 --- a/docs/16-structures.md +++ b/docs/16-structures.md @@ -365,4 +365,7 @@ Three things a host should know: An instance's fields come out of the same value pool arrays use, so the 4096-element budget in Chapter 13 covers both. **Nothing is reclaimed** — a structure lasts until -`CLR` or `NEW`, exactly as an array does. +`CLR` or `NEW`, exactly as an array does, and that is what lets a pointer into a record +stay sound after the scope that declared it has gone. Scalars are the exception and are +not in the pool at all; they live in the variable, so creating one inside a scope costs +nothing.