Document structures: a chapter, the architecture, and the differences
docs/16-structures.md is the feature: records, nesting, copy-on-assign, strict
pointers, lists, what is checked and what is not, and how a host shares its own
C structs. Every example in it is executed by docs_examples and byte-compared,
including the refusals -- so a message that changes fails the suite rather than
quietly making the chapter wrong.
The chapter makes one contrast explicitly, because it is the question a reader
will actually have: a misspelled *field* is refused and a misspelled *variable*
still prints zero. The rule underneath is that what the program declared gets
checked and what it did not gets shrugged at -- a variable's name is never
declared, a TYPE's field list is. Structures end up the strictest thing in the
language, not from a higher standard but because they are the only named thing
whose valid spellings are written down.
Chapter 14 gains the layout: an instance is a contiguous run of value slots with
a diagram of where the fields sit, the three-pass prescan and why each pass
exists, why the copy cannot live in akbasic_value_clone(), and why the render
depth bound is four rather than eight. Chapter 3 gains the @ suffix, chapter 13
records that all of this is an addition BASIC 7.0 has nothing like, and the verb
reference gains TYPE, POINT and DIM ... AS.
MAINTENANCE.md gains the two rules that are on a maintainer rather than on a
test: a structure copy must not go through clone, and a field chain gets its own
leaf field. TODO.md section 5 records what was invented and the three limits
that are ours, and section 8 records the two defects the work exposed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 12:03:02 -04:00
|
|
|
# 16. Structures
|
|
|
|
|
|
|
|
|
|
A structure groups values that belong together. Commodore BASIC 7.0 has nothing like
|
|
|
|
|
it — this is entirely an addition, and Chapter 13 lists it with the other differences.
|
|
|
|
|
|
|
|
|
|
A structure variable's name ends in **`@`**, the fourth type suffix:
|
|
|
|
|
|
|
|
|
|
| Suffix | Type |
|
|
|
|
|
|---|---|
|
|
|
|
|
| `#` | integer |
|
|
|
|
|
| `%` | floating point |
|
|
|
|
|
| `$` | string |
|
|
|
|
|
| `@` | structure |
|
|
|
|
|
|
|
|
|
|
## Declaring a type
|
|
|
|
|
|
|
|
|
|
`TYPE` … `END TYPE` names a record and lists its fields, one per line. **Each field takes
|
|
|
|
|
its type from its own suffix**, the same rule every other name in this language follows,
|
|
|
|
|
so a field list needs no type column:
|
|
|
|
|
|
|
|
|
|
```basic
|
|
|
|
|
10 TYPE RECT
|
|
|
|
|
20 W#
|
|
|
|
|
30 H#
|
|
|
|
|
40 END TYPE
|
|
|
|
|
50 DIM R@ AS RECT
|
|
|
|
|
60 R@.W# = 3
|
|
|
|
|
70 R@.H# = 4
|
|
|
|
|
80 PRINT R@.W# * R@.H#
|
|
|
|
|
90 PRINT R@
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```output
|
|
|
|
|
12
|
|
|
|
|
RECT(W#=3, H#=4)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`DIM name@ AS TYPE` is how a variable gets storage, and it is required — unlike an
|
|
|
|
|
ordinary variable, a structure cannot spring into existence on first use, because
|
|
|
|
|
nothing would say which type it is.
|
|
|
|
|
|
|
|
|
|
**A type name is a bare word, and so is every verb**, so the two share a namespace.
|
|
|
|
|
`TYPE POINT` is refused, because `POINT` is a verb:
|
|
|
|
|
|
|
|
|
|
```basic
|
|
|
|
|
10 TYPE POINT
|
|
|
|
|
20 X#
|
|
|
|
|
30 END TYPE
|
|
|
|
|
40 PRINT 1
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```output
|
|
|
|
|
? 40 : PARSE ERROR TYPE POINT: POINT is a reserved word and cannot name a type
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The same applies to field names, for the same reason a variable cannot be called `TO#`.
|
|
|
|
|
|
|
|
|
|
## Nesting
|
|
|
|
|
|
|
|
|
|
A field may be another structure, named with `AS`:
|
|
|
|
|
|
|
|
|
|
```basic
|
|
|
|
|
10 TYPE COORD
|
|
|
|
|
20 X#
|
|
|
|
|
30 Y#
|
|
|
|
|
40 END TYPE
|
|
|
|
|
50 TYPE SHAPE
|
|
|
|
|
60 NAME$
|
|
|
|
|
70 ORIGIN@ AS COORD
|
|
|
|
|
80 END TYPE
|
|
|
|
|
90 DIM S@ AS SHAPE
|
|
|
|
|
100 S@.NAME$ = "BOX"
|
|
|
|
|
110 S@.ORIGIN@.X# = 10
|
|
|
|
|
120 PRINT S@
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```output
|
|
|
|
|
SHAPE(NAME$=BOX, ORIGIN@=COORD(X#=10, Y#=0))
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`@` on its own says "a structure" but not *which*, which is why a structure field has to
|
|
|
|
|
name its type where `W#` does not. Three primitive types fit in three suffix characters;
|
|
|
|
|
ten declared types do not fit in one.
|
|
|
|
|
|
|
|
|
|
## Assignment copies
|
|
|
|
|
|
|
|
|
|
**This is the rule to remember.** A structure behaves like every other value here:
|
|
|
|
|
|
|
|
|
|
```basic
|
|
|
|
|
10 TYPE RECT
|
|
|
|
|
20 W#
|
|
|
|
|
30 END TYPE
|
|
|
|
|
40 DIM A@ AS RECT
|
|
|
|
|
50 DIM B@ AS RECT
|
|
|
|
|
60 A@.W# = 1
|
|
|
|
|
70 B@ = A@
|
|
|
|
|
80 A@.W# = 99
|
|
|
|
|
90 PRINT B@.W#
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```output
|
|
|
|
|
1
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`B@` is its own record from line 70 onward. Nesting copies too — a whole record, however
|
|
|
|
|
deep, moves as a unit.
|
|
|
|
|
|
|
|
|
|
## Pointers
|
|
|
|
|
|
|
|
|
|
When you want two names for *one* record, say so. A pointer is a distinct declared kind:
|
|
|
|
|
|
|
|
|
|
```basic
|
|
|
|
|
10 TYPE RECT
|
|
|
|
|
20 W#
|
|
|
|
|
30 END TYPE
|
|
|
|
|
40 DIM A@ AS RECT
|
|
|
|
|
50 DIM P@ AS PTR TO RECT
|
|
|
|
|
60 A@.W# = 1
|
|
|
|
|
70 POINT P@ AT A@
|
|
|
|
|
80 P@->W# = 99
|
|
|
|
|
90 PRINT A@.W#
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```output
|
|
|
|
|
99
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Three things are deliberate:
|
|
|
|
|
|
|
|
|
|
- **`POINT` is the only way to share.** A program that never writes it can never be
|
|
|
|
|
surprised by aliasing.
|
|
|
|
|
- **`.` reaches a field of a structure and `->` reaches one through a pointer.** They do
|
|
|
|
|
not stand in for one another, and using the wrong one is an error that names the other.
|
|
|
|
|
So a reader always knows from the spelling whether the thing on the left is their own
|
|
|
|
|
copy or somebody else's data.
|
|
|
|
|
- **A pointer with nothing behind it is `NOTHING`**, and dereferencing it is refused
|
|
|
|
|
rather than being a crash.
|
|
|
|
|
|
|
|
|
|
```basic
|
|
|
|
|
10 TYPE RECT
|
|
|
|
|
20 W#
|
|
|
|
|
30 END TYPE
|
|
|
|
|
40 DIM P@ AS PTR TO RECT
|
|
|
|
|
50 PRINT P@
|
|
|
|
|
60 PRINT P@->W#
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```output
|
|
|
|
|
NOTHING
|
|
|
|
|
? 60 : RUNTIME ERROR This pointer is not pointing at anything yet; POINT it AT a structure first
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Lists and trees
|
|
|
|
|
|
|
|
|
|
A `TYPE` may refer to **itself only through a pointer** — by value it would have no
|
|
|
|
|
finite size, and that is refused at declaration. Which is exactly what makes a list
|
|
|
|
|
possible:
|
|
|
|
|
|
|
|
|
|
```basic
|
|
|
|
|
10 TYPE NODE
|
|
|
|
|
20 COUNT#
|
|
|
|
|
30 TAIL@ AS PTR TO NODE
|
|
|
|
|
40 END TYPE
|
|
|
|
|
50 DIM N1@ AS NODE
|
|
|
|
|
60 DIM N2@ AS NODE
|
|
|
|
|
70 N1@.COUNT# = 10
|
|
|
|
|
80 N2@.COUNT# = 20
|
|
|
|
|
90 POINT N1@.TAIL@ AT N2@
|
|
|
|
|
100 DIM WALK@ AS PTR TO NODE
|
|
|
|
|
110 POINT WALK@ AT N1@
|
|
|
|
|
120 PRINT WALK@->COUNT#
|
|
|
|
|
130 WALK@ = WALK@->TAIL@
|
|
|
|
|
140 PRINT WALK@->COUNT#
|
|
|
|
|
150 PRINT N1@
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```output
|
|
|
|
|
10
|
|
|
|
|
20
|
|
|
|
|
NODE(COUNT#=10, TAIL@=NODE(COUNT#=20, TAIL@=NOTHING))
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Note line 130: assigning one *pointer* to another copies the reference, not the record.
|
|
|
|
|
That is the one place assignment does not deep-copy, and it is why pointers are declared
|
|
|
|
|
separately rather than being a mode a structure can be in.
|
|
|
|
|
|
Let DEF take structure parameters, and give call scopes back
DEF AREA(S@ AS RECT) = S@.W# * S@.H#
DEF POKEIT(P@ AS PTR TO RECT)
A parameter names its type, exactly as DIM does. A bare `DEF F(S@)` is refused:
@ says "a structure" without saying which, so it does not state a contract the
way S$ does, and accepting it would mean checking fields at the call rather than
at the declaration -- which is the hole naming the type closes. The cost is that
there are no generic functions, and that is a real loss rather than an oversight.
Passing is by value, because a parameter is bound by assignment and assignment
copies; a pointer parameter copies its reference and lets a function change its
caller's record on purpose. Neither is a special rule. What a structure
parameter does need is its storage prepared before the copy, since a structure
variable is a run of slots and there is nothing to copy into until the run
exists.
A DEF parameter list is no longer parsed as an argument list, because a
parameter is a declaration rather than an expression: `S@ AS RECT` stopped that
parser dead with "Unbalanced parenthesis".
akbasic_value_is_truthy() learned that a pointer is true when it points at
something, which had to come with this. Without it there is no way to test for
the end of a list at all -- comparing a pointer to 0 reads a numeric field it
does not carry and answers whatever that field held. A structure is deliberately
given no truth value: it always exists, so the question has no answer worth
guessing at.
And a regression I introduced last commit, plus the older one underneath it.
prev_environment() released a scope but not the variables the scope created, so
a call leaked one slot per parameter and two hundred calls exhausted the
128-slot pool. Giving each DEF call its own scope made that reachable; it was
there for GOSUB all along, measured on a stashed build -- a subroutine with a
local of its own failed after about 128 calls before any of this work. The
release is safe because a scope's table holds only what it created, and it is
the variable slot that comes back rather than its storage, so a pointer into a
record DIMmed in that scope stays sound.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 13:05:32 -04:00
|
|
|
**A pointer is true when it points at something**, which is how a walk knows where the
|
|
|
|
|
list ends — and a recursive `DEF` can do the walking:
|
|
|
|
|
|
|
|
|
|
```basic
|
|
|
|
|
10 TYPE NODE
|
|
|
|
|
20 COUNT#
|
|
|
|
|
30 TAIL@ AS PTR TO NODE
|
|
|
|
|
40 END TYPE
|
|
|
|
|
50 DIM N1@ AS NODE
|
|
|
|
|
60 DIM N2@ AS NODE
|
|
|
|
|
70 N1@.COUNT# = 10
|
|
|
|
|
80 N2@.COUNT# = 20
|
|
|
|
|
90 POINT N1@.TAIL@ AT N2@
|
|
|
|
|
100 DEF TOTAL(P@ AS PTR TO NODE)
|
|
|
|
|
110 IF P@->TAIL@ THEN RETURN P@->COUNT# + TOTAL(P@->TAIL@)
|
|
|
|
|
120 RETURN P@->COUNT#
|
|
|
|
|
130 DIM W@ AS PTR TO NODE
|
|
|
|
|
140 POINT W@ AT N1@
|
|
|
|
|
150 PRINT TOTAL(W@)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```output
|
|
|
|
|
30
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`NOT` is the bitwise operator here, so write the test the positive way round as line 110
|
|
|
|
|
does. Recursion is bounded by the scope pool at 32 deep, the same bound `GOSUB` has.
|
Document structures: a chapter, the architecture, and the differences
docs/16-structures.md is the feature: records, nesting, copy-on-assign, strict
pointers, lists, what is checked and what is not, and how a host shares its own
C structs. Every example in it is executed by docs_examples and byte-compared,
including the refusals -- so a message that changes fails the suite rather than
quietly making the chapter wrong.
The chapter makes one contrast explicitly, because it is the question a reader
will actually have: a misspelled *field* is refused and a misspelled *variable*
still prints zero. The rule underneath is that what the program declared gets
checked and what it did not gets shrugged at -- a variable's name is never
declared, a TYPE's field list is. Structures end up the strictest thing in the
language, not from a higher standard but because they are the only named thing
whose valid spellings are written down.
Chapter 14 gains the layout: an instance is a contiguous run of value slots with
a diagram of where the fields sit, the three-pass prescan and why each pass
exists, why the copy cannot live in akbasic_value_clone(), and why the render
depth bound is four rather than eight. Chapter 3 gains the @ suffix, chapter 13
records that all of this is an addition BASIC 7.0 has nothing like, and the verb
reference gains TYPE, POINT and DIM ... AS.
MAINTENANCE.md gains the two rules that are on a maintainer rather than on a
test: a structure copy must not go through clone, and a field chain gets its own
leaf field. TODO.md section 5 records what was invented and the three limits
that are ours, and section 8 records the two defects the work exposed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 12:03:02 -04:00
|
|
|
|
|
|
|
|
`PRINT` follows pointers, so a cycle would not come back — it stops after four levels and
|
|
|
|
|
prints `(...)`.
|
|
|
|
|
|
Let DEF take structure parameters, and give call scopes back
DEF AREA(S@ AS RECT) = S@.W# * S@.H#
DEF POKEIT(P@ AS PTR TO RECT)
A parameter names its type, exactly as DIM does. A bare `DEF F(S@)` is refused:
@ says "a structure" without saying which, so it does not state a contract the
way S$ does, and accepting it would mean checking fields at the call rather than
at the declaration -- which is the hole naming the type closes. The cost is that
there are no generic functions, and that is a real loss rather than an oversight.
Passing is by value, because a parameter is bound by assignment and assignment
copies; a pointer parameter copies its reference and lets a function change its
caller's record on purpose. Neither is a special rule. What a structure
parameter does need is its storage prepared before the copy, since a structure
variable is a run of slots and there is nothing to copy into until the run
exists.
A DEF parameter list is no longer parsed as an argument list, because a
parameter is a declaration rather than an expression: `S@ AS RECT` stopped that
parser dead with "Unbalanced parenthesis".
akbasic_value_is_truthy() learned that a pointer is true when it points at
something, which had to come with this. Without it there is no way to test for
the end of a list at all -- comparing a pointer to 0 reads a numeric field it
does not carry and answers whatever that field held. A structure is deliberately
given no truth value: it always exists, so the question has no answer worth
guessing at.
And a regression I introduced last commit, plus the older one underneath it.
prev_environment() released a scope but not the variables the scope created, so
a call leaked one slot per parameter and two hundred calls exhausted the
128-slot pool. Giving each DEF call its own scope made that reachable; it was
there for GOSUB all along, measured on a stashed build -- a subroutine with a
local of its own failed after about 128 calls before any of this work. The
release is safe because a scope's table holds only what it created, and it is
the variable slot that comes back rather than its storage, so a pointer into a
record DIMmed in that scope stays sound.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 13:05:32 -04:00
|
|
|
## Passing a structure to a function
|
|
|
|
|
|
|
|
|
|
A parameter names its type, exactly as `DIM` does:
|
|
|
|
|
|
|
|
|
|
```basic
|
|
|
|
|
10 TYPE CRATE
|
|
|
|
|
20 W#
|
|
|
|
|
30 H#
|
|
|
|
|
40 END TYPE
|
|
|
|
|
50 DIM A@ AS CRATE
|
|
|
|
|
60 A@.W# = 5
|
|
|
|
|
70 A@.H# = 3
|
|
|
|
|
80 DEF AREA(B@ AS CRATE) = B@.W# * B@.H#
|
|
|
|
|
90 PRINT AREA(A@)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```output
|
|
|
|
|
15
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**A bare `B@` is refused.** `@` says "a structure" without saying which, so it does not
|
|
|
|
|
state a contract the way `B$` does — and accepting it would mean checking fields at the
|
|
|
|
|
call instead of at the declaration, which is the hole naming the type closes. The cost is
|
|
|
|
|
that there are no generic functions: one `AREA` cannot serve `CRATE` and `BOXY`.
|
|
|
|
|
|
|
|
|
|
**Passing is by value**, because a parameter is bound by assignment and assignment copies:
|
|
|
|
|
|
|
|
|
|
```basic
|
|
|
|
|
10 TYPE CRATE
|
|
|
|
|
20 W#
|
|
|
|
|
30 END TYPE
|
|
|
|
|
40 DIM A@ AS CRATE
|
|
|
|
|
50 A@.W# = 5
|
|
|
|
|
60 DEF WIDEN(B@ AS CRATE)
|
|
|
|
|
70 B@.W# = 99
|
|
|
|
|
80 RETURN B@.W#
|
|
|
|
|
90 PRINT WIDEN(A@)
|
|
|
|
|
100 PRINT A@.W#
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```output
|
|
|
|
|
99
|
|
|
|
|
5
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The function saw 99; the caller still has 5. To change a caller's record on purpose, pass
|
|
|
|
|
a pointer — `DEF POKEIT(P@ AS PTR TO CRATE)` — and reach through it with `->`. Neither is
|
|
|
|
|
a special rule: both fall out of the parameter being assigned like any other variable.
|
|
|
|
|
|
Document structures: a chapter, the architecture, and the differences
docs/16-structures.md is the feature: records, nesting, copy-on-assign, strict
pointers, lists, what is checked and what is not, and how a host shares its own
C structs. Every example in it is executed by docs_examples and byte-compared,
including the refusals -- so a message that changes fails the suite rather than
quietly making the chapter wrong.
The chapter makes one contrast explicitly, because it is the question a reader
will actually have: a misspelled *field* is refused and a misspelled *variable*
still prints zero. The rule underneath is that what the program declared gets
checked and what it did not gets shrugged at -- a variable's name is never
declared, a TYPE's field list is. Structures end up the strictest thing in the
language, not from a higher standard but because they are the only named thing
whose valid spellings are written down.
Chapter 14 gains the layout: an instance is a contiguous run of value slots with
a diagram of where the fields sit, the three-pass prescan and why each pass
exists, why the copy cannot live in akbasic_value_clone(), and why the render
depth bound is four rather than eight. Chapter 3 gains the @ suffix, chapter 13
records that all of this is an addition BASIC 7.0 has nothing like, and the verb
reference gains TYPE, POINT and DIM ... AS.
MAINTENANCE.md gains the two rules that are on a maintainer rather than on a
test: a structure copy must not go through clone, and a field chain gets its own
leaf field. TODO.md section 5 records what was invented and the three limits
that are ours, and section 8 records the two defects the work exposed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 12:03:02 -04:00
|
|
|
## What is checked, and what is not
|
|
|
|
|
|
|
|
|
|
A field name is checked against the set the type declared, and the refusal lists the
|
|
|
|
|
fields that do exist:
|
|
|
|
|
|
|
|
|
|
```basic
|
|
|
|
|
10 TYPE RECT
|
|
|
|
|
20 W#
|
|
|
|
|
30 H#
|
|
|
|
|
40 END TYPE
|
|
|
|
|
50 DIM R@ AS RECT
|
|
|
|
|
60 PRINT TOTLA#
|
|
|
|
|
70 PRINT R@.NOPE#
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```output
|
|
|
|
|
0
|
|
|
|
|
? 70 : RUNTIME ERROR RECT has no field NOPE# (W#, H#)
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Line 60 is the contrast worth understanding. **A misspelled variable is still silent** —
|
|
|
|
|
`TOTLA#` prints zero, as it does in every BASIC ever written. A misspelled *field* is
|
|
|
|
|
not, because the set of fields is closed and the program wrote it down.
|
|
|
|
|
|
|
|
|
|
That is the rule underneath both: **what the program declared gets checked, and what it
|
|
|
|
|
did not gets shrugged at.** A variable's name is never declared, so it cannot be checked.
|
|
|
|
|
A `TYPE`'s field list is, so it can be. Structures end up the strictest thing in the
|
|
|
|
|
language, not because they are held to a higher standard but because they are the only
|
|
|
|
|
named thing whose valid spellings are written down.
|
|
|
|
|
|
|
|
|
|
## Sharing a structure with a host
|
|
|
|
|
|
|
|
|
|
If you are embedding the interpreter in a game, a script can read and write **the game's
|
|
|
|
|
own C structs** — not a copy of them. The host describes its struct once:
|
|
|
|
|
|
|
|
|
|
```c norun
|
|
|
|
|
typedef struct
|
|
|
|
|
{
|
|
|
|
|
char name[32];
|
|
|
|
|
int32_t hp;
|
|
|
|
|
float x;
|
|
|
|
|
bool hostile;
|
|
|
|
|
} game_Enemy;
|
|
|
|
|
|
|
|
|
|
static const akbasic_HostField ENEMY_FIELDS[] = {
|
|
|
|
|
/* struct member BASIC name C representation */
|
|
|
|
|
AKBASIC_HOST_FIELD( game_Enemy, name, "NAME$", AKBASIC_HOSTFIELD_CSTRING ),
|
|
|
|
|
AKBASIC_HOST_FIELD( game_Enemy, hp, "HP#", AKBASIC_HOSTFIELD_INT32 ),
|
|
|
|
|
AKBASIC_HOST_FIELD( game_Enemy, x, "X%", AKBASIC_HOSTFIELD_FLOAT ),
|
|
|
|
|
AKBASIC_HOST_FIELD( game_Enemy, hostile, "HOSTILE#", AKBASIC_HOSTFIELD_BOOL )
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const akbasic_HostType ENEMY_TYPE = {
|
|
|
|
|
"ENEMY", sizeof(game_Enemy), ENEMY_FIELDS, 4
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
akbasic_host_register_type(&SCRIPT, &ENEMY_TYPE);
|
|
|
|
|
akbasic_host_bind(&SCRIPT, "FOE@", "ENEMY", &GOBLIN);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
and the script then works on `FOE@` like any other structure:
|
|
|
|
|
|
|
|
|
|
```basic norun
|
|
|
|
|
10 PRINT FOE@.NAME$ + " HAS " + FOE@.HP#
|
|
|
|
|
20 FOE@.HP# = FOE@.HP# - 10
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Line 20 decrements `GOBLIN.hp` in place. There is no marshalling step.
|
|
|
|
|
|
|
|
|
|
`examples/hoststruct.c` is a complete working host, built and run by every build.
|
|
|
|
|
Chapter 10 covers the rest of the embedding API; the parts specific to structures:
|
|
|
|
|
|
|
|
|
|
| Call | Does |
|
|
|
|
|
|---|---|
|
|
|
|
|
| `akbasic_host_register_type` | Makes a C struct available as a BASIC type |
|
|
|
|
|
| `akbasic_host_bind` | Binds one instance to a script variable |
|
|
|
|
|
| `akbasic_host_rebind` | Points that name at a different instance — the per-frame call |
|
|
|
|
|
| `akbasic_host_unbind` | Breaks the binding before the storage goes away |
|
|
|
|
|
|
|
|
|
|
Three things a host should know:
|
|
|
|
|
|
|
|
|
|
- **Conversion refuses rather than truncates.** Assigning 70000 to an `int16_t` field, or
|
|
|
|
|
forty characters to a `char[32]`, is an error naming the field.
|
|
|
|
|
- **A host `float` round-trips lossily**, because BASIC floats are doubles. A `char[]` has
|
|
|
|
|
a width that BASIC strings do not.
|
|
|
|
|
- **A bound instance must outlive the binding.** It is the only pointer this interpreter
|
|
|
|
|
holds that it did not allocate; `akbasic_host_unbind()` exists for exactly that.
|
|
|
|
|
|
|
|
|
|
## Limits
|
|
|
|
|
|
|
|
|
|
| | |
|
|
|
|
|
|---|---|
|
|
|
|
|
| Types | 16 |
|
|
|
|
|
| Fields per type | 16 |
|
|
|
|
|
| Nesting shown by `PRINT` | 4 levels |
|
|
|
|
|
|
|
|
|
|
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.
|