6.11.1 Standard Structures

The Forth 2012 standard defines a number of words for defining fields and structures.

A typical example of defining a structure with several fields is:

0 \ offset of first field, 0 in the usual case
  field: intlist-next ( intlist -- addr1 )
  field: intlist-val  ( intlist -- addr2 )
constant intlist ( -- u )

An equivalent alternative way of defining this structure is:

begin-structure intlist ( -- u )
  field: intlist-next ( intlist -- addr1 )
  field: intlist-val  ( intlist -- addr2 )
end-structure

Intlist returns the size of the structure. The convention for the field names here is to prepend the structure name, so that you don’t run into conflicts when several structures have next and val fields; in Forth, by default field names are in the same wordlist (i.e., the same name space) as the other words (including other field names), and trying to use the search order (see Word Lists) for avoiding conflicts is rather cumbersome (unless you use the scope recognizer !! pxref).

You can then use that to allocate an instance of that structure and then use the field words to access the fields of that instance:

intlist allocate throw constant my-intlist1
0 my-intlist1 intlist-next !
5 my-intlist1 intlist-val  !

intlist allocate throw constant my-intlist2
my-intlist1 my-intlist2 intlist-next !
7           my-intlist2 intlist-val !

: intlist-sum ( intlist -- n )
\ "intlist" is a pointer to the first element of a linked list
\ "n" is the sum of the intlist-val fields in the linked list
    0 BEGIN ( intlist1 n1 )
        over
    WHILE ( list1 n1 )
        over intlist-val @ +
        swap intlist-next @ swap
    REPEAT
    nip ;

my-intlist2 intlist-sum . \ prints "12"

In addition to field: for cell-aligned and cell-sized fields, you can define fields sized and aligned for various types with:

begin-structure ( "name" – struct-sys 0  ) facility-ext “begin-structure”
end-structure ( struct-sys +n –  ) facility-ext “end-structure”

end a structure started wioth begin-structure

cfield: ( u1 "name" – u2  ) facility-ext “c-field-colon”

Define a char-sized field

field: ( u1 "name" – u2  ) facility-ext “field-colon”

Define an aligned cell-sized field

2field: ( u1 "name" – u2  ) gforth-0.7 “two-field-colon”

Define an aligned double-cell-sized field

ffield: ( u1 "name" – u2  ) floating-ext “f-field-colon”

Define a faligned float-sized field

sffield: ( u1 "name" – u2  ) floating-ext “s-f-field-colon”

Define a sfaligned sfloat-sized field

dffield: ( u1 "name" – u2  ) floating-ext “d-f-field-colon”

Define a dfaligned dfloat-sized field

wfield: ( u1 "name" – u2  ) gforth-1.0 “w-field-colon”

Define a naturally aligned field for a 16-bit value.

lfield: ( u1 "name" – u2  ) gforth-1.0 “l-field-colon”

Define a naturally aligned field for a 32-bit value.

xfield: ( u1 "name" – u2  ) gforth-1.0 “x-field-colon”

Define a naturally aligned field for a 64-bit-value.

If you need something beyond these field types, you can use +field to define fields of arbitrary size. You have to ensure the correct alignment yourself in this case. E.g., if you want to put one struct inside another struct, you would do it with

0
  cfield:                nested-foo
  aligned intlist +field nested-bar
constant nested

In this example the field nested-bar contains an intlist structure, so the size of intlist is passed to +field. An intlist must be cell-aligned (it contains cell fields), and this is achieved by aligning the current field offset with aligned before the field definition. Our recommendation is to always precede the usage of +field with an appropriate alignment word (except if character-alignment is good enough for the field); this ensures that the field will stay correctly aligned even if other fields are later inserted before the +field-defined field.

+field ( noffset1 nsize "name" – noffset2  ) facility-ext “plus-field”

Defining word; defines name ( addr1 -- addr2 ), where addr2 is addr1+noffset1. noffset2 is noffset1+nsize.

The first field is at the base address of a structure and the word for this field (e.g., list-next) actually does not change the address on the stack. You may be tempted to leave it away in the interest of run-time and space efficiency. This is not necessary, because Gforth and other Forth systems optimize this case: If you compile a first-field word, no code is generated. So, in the interest of readability and maintainability you should include the word for the field when accessing the field.