commit 9d0d5210813171c49cac98c732cb55285341f5c2
parent 96c3897cb8e3677a694e4e155f172ef11c86fdfb
Author: Virgil Dupras <hsoft@hardcoded.net>
Date: Sat, 7 Jan 2023 09:39:59 -0500
Consolidate and document structures
Move the "meta" part of structs (for now, only the Struct and Field definitions
which are not actually used anywhere) in a new lib/struct unit. Move structure
documentation in its own doc/struct page and augment that documentation.
Diffstat:
7 files changed, 255 insertions(+), 156 deletions(-)
diff --git a/fs/doc/struct.txt b/fs/doc/struct.txt
@@ -0,0 +1,219 @@
+# Structures
+
+Structures are an effective way to address offsets from base addresses while
+keeping the general namespace clean. Structures have a name and a list of fields
+and are declared thus:
+
+ struct[ Foo
+ sfield bar
+ sfield baz
+ smethod :bleh
+ ]struct
+
+This describes an 12 byte wide struct with 3 fields.
+
+## Namespace
+
+Anything goes inside of a struct. Whatever word you define there will be
+included in the struct's namespace. That is, a sub dictionary inside the system
+dictionary. Those words will not be present in the system dictionary. To access
+a word in a namespace, you begin by calling the struct name and write the name
+of the word to call inside the namespace. This also works when compiling:
+
+ Foo bar
+ : myword Foo :bleh ;
+ : err Foo ; \ error! ";" doesn't exist in Foo namespace
+
+While inside a struct definition, however, you can access words inside the
+struct directly.
+
+ : hello 42 . ;
+ struct[ Bar
+ : hello 54 . ;
+ hello \ prints 54
+ : hey hello ;
+ hey \ prints 54
+ ]struct
+
+## Fields and methods
+
+"sfield" and "smethod" have a special struct-specific behavior as they
+automatically place themselves inside the struct at the correct offset and
+increase the struct's size.
+
+A struct size can be obtained with the "SZ" word automatically added to every
+struct. It returns the size, in bytes, of the fields included in the struct. It
+can also be used inside a struct definition to get the struct size "up until
+now". This can be useful for initialization methods:
+
+ struct[ Foo
+ sfield bar
+ sfield baz
+ smethod :bleh
+ : :new ( -- 'foo ) here SZ allot0 ;
+ ]struct
+
+A struct holds no data by itself and can't be used directly to access fields
+from memory. You refer to fields in a struct by supplying it with a source
+pointer, like this:
+
+ create data1 1 , 2 , ' mybleh ,
+ create data2 3 , 4 , ' mybleh ,
+ data1 Foo bar . \ prints 1
+ data2 Foo baz . \ prints 4
+
+Fields obey "to" semantics:
+
+ 42 to+ data1 Foo bar
+ data1 Foo bar . --> prints 43
+
+Field access can be compiled:
+
+ : foobar data2 Foo baz ;
+ foobar . \ prints 4
+
+A method is an alias to a word reference inside a struct. When the method is
+invoked, it dereferences the alias and calls it, but it also pushes a reference
+of the data structure on top of PS so that the method can work with its data.
+By convention, method names start with ":", but nothing forces you to have it.
+Example of a method that adds bar to baz:
+
+ : mybleh ( 'data -- n ) dup Foo bar swap Foo baz + ;
+
+defined before the previous examples, then you could do:
+
+ data1 Foo :bleh . \ prints 3
+ data2 Foo :bleh . \ prints 7
+
+## Structure binds
+
+You will often want to bind data to structs. You can do so with "structbind":
+
+ data1 structbind Foo MyData1
+ MyData1 :bleh . \ prints 3
+ : someword MyData1 bar ;
+ someword . \ prints 2
+
+A structbind is compiled with a level of indirection that allows it to be
+rebound. So, you can rebind structbinds with the word "rebind" (we can't use
+"to" because that applies to the original bind's field):
+
+ data2 ' MyData1 rebind
+ MyData1 :bleh . \ prints 7
+ someword . \ prints 4
+
+All structs have a ":self" method which is a noop, and thus returns a reference
+to the associated data. This can be used to get a structbind's data reference:
+
+ data2 :self \ MyData1 is on PS TOS
+
+## Other field types
+
+There are several kinds of fields:
+
+* sfield a 4 byte field
+* sfieldw a 2 byte field
+* sfieldb a 1 byte field
+* sconst a 4 byte field that doesn't obey "to" semantics
+* sfield' a field that yields its address instead of a value. Useful for
+ buffers. It must be called with a size argument.
+* smethod A 4 byte pointer to a word that behaves as described above.
+* ssmethod A "static" method. Like a method, but we don't copy the struct
+ address to PS.
+
+You can also create gaps in the struct with "sallot":
+
+struct[ Gaps
+ sfield foo
+ 42 sallot
+ sfield bar
+]struct
+
+In this struct, foo's offset is 0 and bar's is 46.
+
+## Extending a structure
+
+You can also extend a previous struct with a new struct:
+
+ extends Foo struct[ Bar
+ sfield bazooka
+ ]struct
+ create data3 1 , 2 , ' mybleh , 1234
+ data3 Bar bazooka . \ prints 1234
+ data3 Bar bar . \ prints 1
+
+Extended structs will have their "running size" pick up where the extended
+struct left. They also inherit their whole namespace, which means that any word
+in the extended namespace can be accessed directly without prefixing it with
+the struct's name. Extending a struct does not modify the extended struct.
+
+If, instead of extending a struct, you want to "augment it", that is, to
+supplement a struct that was defined earlier with new elements, you can use
+struct+[ in this fashion:
+
+ struct+[ Foo
+ sfield bazooka
+ ]struct
+
+This does not create a new struct, but rather adds a new field to the existing
+struct. It is useful for struct that require partial declaration because of
+inter-dependency with other pieces of code.
+
+Warning: do not augment a struct with new fields if it has already been extended
+by another struct because this will generate slot conflicts. You can augment a
+struct that has been extended, that will work, but only with non-field words.
+
+## Name conventions
+
+A structure begins with an uppercase letter and a word in a namespace that is
+intended to be called from the outside (so, not only methods) begin with a ":".
+
+## API
+
+struct[ <name> -- Create a struct named "name" and enter its definition
+]struct -- Exit the definition of the current structure
+struct+[ <name> -- Re-open the definition of existing struct "name"
+extends <name> -- Make the next struct definition extend struct "name"
+
+structbind <struct> <name> ( 'data -- )
+ Create a new struct bind named "name" binding "'data" to the struct "struct".
+rebind ( 'data 'bind -- )
+ Rebind struct binding "'bind" to data "'data".
+
+The following words give meta information on a struct from the basis of its word
+address, named "w" below. For a given struct "Foo", you get "w" with "' Foo".
+
+structdict' w -- a Address of struct namespace dictionary.
+structsz w -- sz Size of the fields and methods in the struct.
+structsz' w -- 'sz Address of the size field cell.
+structlastfield' w -- a Address of the last field of the struct.
+
+Fields words, described above, only work inside struct[ definitions and must be
+followed by their name:
+
+sfield
+sfieldw
+sfieldb
+sconst
+smethod
+ssmethod
+
+These words (also described above) have a slightly different signature:
+
+sfield' <name> ( size -- )
+sallot ( size -- )
+
+## Inspecting structures
+
+The unit lib/struct allows to go meta on structs and inspect them. It defines
+the Struct and Field structures.
+
+Struct
+ dict Structure namespace dictionary
+ size Total size in bytes of struct fields
+ lastfield Link to last field of struct (a Field)
+
+Field
+ next Next field (this is a linked list)
+ offset Offset, in bytes, of that field
+ size Size, in bytes, of that field (1, 2 or 4)
diff --git a/fs/doc/usage.txt b/fs/doc/usage.txt
@@ -333,141 +333,8 @@ this way, we can avoid crashes, making the system a bit easier to debug.
## Structures
-Structures are an effective way to address offsets from base addresses while
-keeping the general namespace clean. Structures have a name and a list of fields
-and are declared thus:
-
- struct[ Foo
- sfield bar
- sfield baz
- smethod :bleh
- ]struct
-
-This describes an 12 byte wide struct with 3 fields.
-
-Anything goes inside of a struct. Whatever word you define there will be
-included in the struct's namespace. Those words will not be present in the
-system dictionary. While inside a struct definition, however, you can access
-words inside the struct directly.
-
-"sfield" and "smethod" have a special struct-specific behavior as they
-automatically place themselves inside the struct at the correct offset and
-increase the struct's size.
-
-A struct size can be obtained with the "SZ" word automatically added to every
-struct. It returns the size, in bytes, of the fields included in the struct. It
-can also be used inside a struct definition to get the struct size "up until
-now". This can be useful for initialization methods:
-
- struct[ Foo
- sfield bar
- sfield baz
- smethod :bleh
- : :new ( -- 'foo ) here SZ allot0 ;
- ]struct
-
-A struct holds no data by itself and can't be used directly to access fields
-from memory. You refer to fields in a struct by supplying it with a source
-pointer, like this:
-
- create data1 1 , 2 , ' mybleh ,
- create data2 3 , 4 , ' mybleh ,
- data1 Foo bar . \ prints 1
- data2 Foo baz . \ prints 4
-
-Fields obey "to" semantics:
-
- 42 to+ data1 Foo bar
- data1 Foo bar . --> prints 43
-
-Field access can be compiled:
-
- : foobar data2 Foo baz ;
- foobar . \ prints 4
-
-A method is an alias to a word reference inside a struct. When the method is
-invoked, it dereferences the alias and calls it, but it also pushes a reference
-of the data structure on top of PS so that the method can work with its data.
-By convention, method names start with ":", but nothing forces you to have it.
-Example of a method that adds bar to baz:
-
- : mybleh ( 'data -- n ) dup Foo bar swap Foo baz + ;
-
-defined before the previous examples, then you could do:
-
- data1 Foo :bleh . \ prints 3
- data2 Foo :bleh . \ prints 7
-
-You will often want to bind data to structs. You can do so with "structbind":
-
- data1 structbind Foo MyData1
- MyData1 :bleh . \ prints 3
- : someword MyData1 bar ;
- someword . \ prints 2
-
-A structbind is compiled with a level of indirection that allows it to be
-rebound. So, you can rebind structbinds with the word "rebind" (we can't use
-"to" because that applies to the original bind's field):
-
- data2 ' MyData1 rebind
- MyData1 :bleh . \ prints 7
- someword . \ prints 4
-
-All structs have a ":self" method which is a noop, and thus returns a reference
-to the associated data. This can be used to get a structbind's data reference:
-
- data2 :self \ MyData1 is on PS TOS
-
-There are several kinds of fields:
-
-* sfield a 4 byte field
-* sfieldw a 2 byte field
-* sfieldb a 1 byte field
-* sconst a 4 byte field that doesn't obey "to" semantics
-* sfield' a field that yields its address instead of a value. Useful for
- buffers. It must be called with a size argument.
-* smethod A 4 byte pointer to a word that behaves as described above.
-* ssmethod A "static" method. Like a method, but we don't copy the struct
- address to PS.
-
-You can also create gaps in the struct with "sallot":
-
-struct[ Gaps
- sfield foo
- 42 sallot
- sfield bar
-]struct
-
-In this struct, foo's offset is 0 and bar's is 46. You can also extend a
-previous struct with a new struct:
-
- extends Foo struct[ Bar
- sfield bazooka
- ]struct
- create data3 1 , 2 , ' mybleh , 1234
- data3 Bar bazooka . \ prints 1234
- data3 Bar bar . \ prints 1
-
-Extended structs will have their "running size" pick up where the extended
-struct left. They also inherit their whole namespace, which means that any word
-in the extended namespace can be accessed directly without prefixing it with
-the struct's name. Extending a struct does not modify the extended struct.
-
-If, instead of extending a struct, you want to "augment it", that is, to
-supplement a struct that was defined earlier with new elements, you can use
-struct+[ in this fashion:
-
- struct+[ Foo
- sfield bazooka
- ]struct
-
-This does not create a new struct, but rather adds a new field to the existing
-struct. It is useful for struct that require partial declaration because of
-inter-dependency with other pieces of code.
-
-Warning: do not augment a struct with new fields if it has already been extended
-by another struct because this will generate slot conflicts. You can augment a
-struct that has been extended, that will work, but only with non-field words.
+Structures are a wide subject and have their own documentation at doc/struct.
+They're used everywhere and you should know about them.
## Input/Output
diff --git a/fs/lib/struct.fs b/fs/lib/struct.fs
@@ -0,0 +1,14 @@
+\ Utilities around structures. see doc/struct
+
+struct[ Struct
+ sfield dict
+ 1 sallot \ 1b that is always zero after dict link. See doc/arch
+ sfield size
+ sfield lastfield \ pointer to field *word*
+]struct
+
+struct[ Field
+ sfield next
+ sfield offset
+ sfield size \ 1, 2 or 4
+]struct
diff --git a/fs/tests/kernel.fs b/fs/tests/kernel.fs
@@ -130,11 +130,4 @@ extends Foo struct[ Bazooka
create data3 7 , 9 c, ' mybleh , 999 ,
data3 Bazooka bling 999 #eq
data3 Bazooka baz 9 #eq
-
-\ we can iterate fields of a struct
-' Bazooka does' Struct lastfield
-S" bling" over wordname[] s[]= #
-does' Field next S" baz" over wordname[] s[]= #
-does' Field next S" bar" over wordname[] s[]= #
-does' Field next 0 #eq
testend
diff --git a/fs/tests/lib/all.fs b/fs/tests/lib/all.fs
@@ -2,10 +2,11 @@
f<< /tests/lib/core.fs
f<< /tests/lib/bit.fs
f<< /tests/lib/str.fs
-f<< /tests/lib/crc.fs
+f<< /tests/lib/struct.fs
f<< /tests/lib/meta.fs
f<< /tests/lib/arena.fs
f<< /tests/lib/math.fs
f<< /tests/lib/stack.fs
f<< /tests/lib/tree.fs
f<< /tests/lib/fmt.fs
+f<< /tests/lib/crc.fs
diff --git a/fs/tests/lib/struct.fs b/fs/tests/lib/struct.fs
@@ -0,0 +1,18 @@
+?f<< /tests/harness.fs
+?f<< /lib/meta.fs
+?f<< /lib/struct.fs
+testbegin
+struct[ Foo
+ sfield bar
+ sfieldb baz
+ smethod :bleh
+ sfield bling
+]struct
+
+\ we can iterate fields of a struct
+' Foo does' Struct lastfield
+S" bling" over wordname[] s[]= #
+does' Field next S" baz" over wordname[] s[]= #
+does' Field next S" bar" over wordname[] s[]= #
+does' Field next 0 #eq
+testend
diff --git a/fs/xcomp/bootlo.fs b/fs/xcomp/bootlo.fs
@@ -244,19 +244,6 @@ does> ( 'struct )
: smethod doer _cur e>w structsz , CELLSZ sallot does> @ over + @ execute ;
: ssmethod doer _cur e>w structsz , CELLSZ sallot does> @ swap + @ execute ;
-struct[ Struct
- sfield dict
- 1 sallot \ 1b that is always zero after dict link. See doc/arch
- sfield size
- sfield lastfield \ pointer to field *word*
-]struct
-
-struct[ Field
- sfield next
- sfield offset
- sfield size \ 1, 2 or 4
-]struct
-
\ 4b link to struct
\ 4b link to data
: structbind ( 'data -- ) ' doer , , immediate does> ( 'bind -- *to* )