# 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. A recursive `DEF` works too, and is bounded by the scope pool at 32 deep — the same bound `GOSUB` has. What a function cannot yet do is take a structure *parameter*: `DEF F(B@ AS NODE)` is not implemented, and a bare `DEF F(B@)` is refused because `@` alone does not say which type. Until it is, a function reaches a record by name, which the scoping already allows — a call can see the caller's variables. `PRINT` follows pointers, so a cycle would not come back — it stops after four levels and prints `(...)`. ## 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.