Skip to content

Language 03 Data Objects

Chloë Strainge edited this page Oct 2, 2023 · 8 revisions

Data Objects

All of the data objects are treated slightly differently the the previously mentioned data types.

Beyond Strings and Numbers

When you use a boolean, string, or number type. It's value is copied when pushed onto the stack. It's copied when it is popped off and written to a variable. Most operations with these values involve a copy at some point.

The other supported data types are a bit different. When you create an instance of one of these you are actually creating a reference to an object in memory. It is the reference that is copied around instead of the data object itself. The references are counted, so if a data object is refereed in multiple places, both references point to the same object. So, if you change a data object in one place it is updated in both.

In fact, the data object is not freed until all references to the object are lost.

Arrays

Starting with arrays. An array is a simple list of values. Each slot of the array can refer to an entirely different data type. Let's start by creating an array that has 3 slots and is stored in the variable my_array

>> 3 [].new variable! my_array
ok

>>

Now lets we read the first value out of the array as it is now.

>> my_array @ [ 0 ]@ .cr
0
ok

>>

Notice how we used [ ]@ syntax to pass the index and ask for a read? Also notice how we had to do a variable read to get the array itself onto the stack before it could be accessed.

There is a simpler syntax to make this kind of variable access easier. [ ]@@. The double @@ indicates read from a variable and then read from the array. So the previous example could have been written as:

>> my_array [ 0 ]@@ .cr
0
ok

>>

Choosing between [ ]@ and [ ]@@ is a matter of taste and circumstance. If through the effects of other words in your statement you know that an array will be left on the top of the stack without a variable access then you definitely need to use [ ]@.

Writes are similar, and they involve [ ]!, and [ ]!! much like read. Given that, let's write 2048 to the second slot.

>> 2048 my_array [ 1 ]!!
ok

>> my_array [ 1 ]@@
2048
ok

>>

You can also display the whole array at once:

>> my_array @ .cr
[ 0 , 2048 , 0 ]
ok

>>

Instead of filling in an array one entry at a time, you can also define the whole array upfront. Notice in this example the spacing used. Spaces are required before and after the brackets [ ] and the commas , otherwise the syntax element is not recognized by the interpreter.

>> [ 1024 , 2048 , 4096 ] my_array !
ok

>> my_array @ .cr
[ 1024 , 2048 , 4096 ]
ok

>> my_array [ 2 ]@@ .cr
4096
ok

>>

Array words

Word Description
[].new Create a new array of n elements.
[].size@ Get the current size of an array.
[].size! Grow or shrink the array to a new size.
[].size@@ Get the current size of an array variable.
[].size!! Grow or shrink the array variable to a new size.
[].size++! Grow the array by one item.
[].size++!! Grow the array by one item.
[].+ Append a second array onto the end of the first.
array [ index ]@ Read from an array at index position.
variable [ index ]@@ Read from an array variable at index position.
array [ index ]! Write to an array at index position.
variable [ index ]!! Write to an array variable at index postion.

Hash-Tables

Hash tables allow you to associate keys with values. The overall syntax structure is very similar to that of arrays except instead of a numeric index you can use any value type as a key.

Creating a hash table you do not need to specify the initial size. The initial size is always 0.

>> {}.new variable! my_hash
ok

>>

With this hash table we can add some values.

>> "world" my_hash { "hello" }!!
ok

>> "bar" my_hash { "foo" }!!
ok

>>

We can also query the table and read values back out of it.

>> my_hash { "hello" }@@ .cr
world
ok

>>

Like with arrays you can also print out the entire hash table.

>> my_hash @ .cr
{
    hello -> world
    foo -> bar
}
ok

>>

The expression before the -> is the key. The expression after is the value.

Structures can also be constructed one shot just like with the array syntax. So, to recreate the previous hash table you use the -> operator to separate key from value and you also use space separated commas to keep the entries distinct from each other.

>> { "Hello" -> "world" , "foo" -> "bar" } variable! my_hash
ok

>> my_hash @ .cr
{
    hello -> world
    foo -> bar
}
ok

>>

Hash table words

Word Description
{}.new Create a new and empty hash table.
{}? Is a kew in the hash table?
{}?? Is a key in the hash table variable?
{}.+ Append a hash table into another one.
word {}.iterate Call a word for every entry in the hash table. Value and key is pushed for each entry.
table { key }@ Read a value from the given hash table.
variable { key }@@ Read a value from the hash table variable.
table { key }! Write a value to the the hash table.
variable { key }!! Write a value to the hash table variable.

Structures

Structures are a custom data object where you get to define your own custom fields and override the default constructor. You define a structure by giving the structure a name and it's fields their own names.

So, to define a structure named foo and fields a, b, and c:

>> # foo a b c ;
ok

>>

This defines a few words automatically.

Word Description
foo.new Create a default instance of the structure.
foo.a Push the index of filed a onto the stack.
foo.b Push the index of filed b onto the stack.
foo.c Push the index of filed c onto the stack.

Structure read and write words.

The words #@, #!, #@@, and #!! exist to read and write values to/from the structure. So to create a new strure and write to field b we write:

>> foo.new variable! my_foo
ok

>> 50 foo.a my_foo #!!
ok

>> foo.a my_foo #@@ .cr
50
ok

>>

Note that it is under consideration to expand the number of words created to include words that will read and write from fields directly. <struct>.<field>@, <struct>.<field>@@, <struct>.<field>!, <struct>.<field>!! For example to read and write field a using these words:

>> 22 my_foo foo.a!!
ok

>> my_foo foo.a@@
22
ok

>>

There is also a structure creation syntax just like with arrays and hash tables. So to init all 3 fields in foo:

>> #.new foo { a -> 1 , b -> 2 , c -> 3 } variable! my_foo
ok

>> my_foo @ .cr
# foo a: 1 b: 2 c: 3 ;
ok

>>

Structure words

Word Description
#@ Read a field from a structure.
#@@ Read a field from a structure variable.
#! Write a value to a structure field.
#!! Write a value to a structure variable's field.
<word> #.iterate Given a word call it for each structure member.
#.new <name> { <initializers> } Create a new instance of a structure with specific values.

Byte-Buffers

Byte buffers are a bit different than the other data structures. They exist to facilitate reading and writing byte accurate data structures, either through some form of IPC/networking or binary data file.

As such, you create them with a specific size in mind and read and write values of various bit/bite sizes to them. In the end you writ4 or transmit the entire buffer as a block of bytes. The buffer maintains a read/write pointer, so values are automatically written sequentially.

To create a new buffer you specify the size and a buffer of that many bytes will be created.

>> 20 buffer.new variable! my_buffer
ok

>> my_buffer @ .cr
          00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
00000000  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000010  00 00 00 00                                      ....
ok

>>

Once created you can use the buffer with the standard read and write operations. (Discussed further in the I/O chapter.) Values can also be written or read from the buffer itself. For example, here we take a 16-bit unsigned value, write it to the buffer and read it back out again.

>> 65535 my_buffer buffer.i16!!
ok

>> 0 my_buffer @ buffer.position!
ok

>> my_buffer buffer.u16@@ .cr
65535
ok

>> my_buffer @ .cr
          00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
00000000  ff ff 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000010  00 00 00 00                                      ....
ok

>>

In this example we used buffer.i16!! to write our value and buffer.u16@@ to read the value back. This is because the writes only exist in i versions. u versions are not needed because the write is a truncating operation and the signed reads will perform a proper sign extension when converting to the language's native numeric data types.

The line 0 my_buffer @ buffer.position! moves the read/write pointer back to the beginning of the buffer so that we can read back the value we originally wrote. Otherwise the w/r pointer would be sitting at the position just after the first value when we attempted to read and we would only get a 0 value.

Strings can be written to the buffer as well. But in these cases you specify the number of bytes the string may occupy within the buffer. If the string is longer than that byte size on write it is padded with 0s. If the string is larger, then it is truncated to the number of bytes. A trailing 0 is not written in that case. If a trailing 0 is needed then it is advised to manually write a 0 u8 value at the end of the string section being written.

So, to write a string to our buffer:

>> "Hello world." my_buffer @ 14 buffer.string!
ok

>> my_buffer @ .cr
          00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
00000000  ff ff 48 65 6c 6c 6f 20 77 6f 72 6c 64 2e 00 00  ..Hello world...
00000010  00 00 00 00                                      ....
ok

>>

As you can see, our string was written in the position we left the r/w pointer at. Just after our last read. If we readjust and read our string back we should get back what we wrote.

>> 2 my_buffer @ buffer.position!
ok

>> my_buffer @ 14 buffer.string@ .cr
Hello world.
ok

>>

Buffer words

Word Description
buffer.new Create a new byte buffer with a specified size.
buffer.position@ Read the position of the the r/w pointer.
buffer.position! Write the position of the r/w pointer.
buffer.position@@ With a buffer variable, get the r/w pointer position.
buffer.position!! With a buffer variable, set the r/w pointer position.
buffer.string@ Read a string from the byte buffer.
buffer.string! Write a string to the byte buffer.
buffer.string@@ Read a string from the byte buffer variable.
buffer.string!! Write a string to a byte buffer variable.
buffer.i8!! Write an 8-bit integer value to the buffer variable.
buffer.i16!! Write a 16-bit integer value to the buffer variable.
buffer.i32!! Write a 32-bit integer value to the buffer variable.
buffer.i64!! Write a 64-bit integer value to the buffer variable.
buffer.i8@@ Read a signed 8-bit value from the buffer variable.
buffer.i16@@ Read a signed 16-bit value from the buffer variable.
buffer.i32@@ Read a signed 32-bit value from the buffer variable.
buffer.i64@@ Read a signed 64-bit value from the buffer variable.
buffer.u8@@ Read an unsigned 8-bit value from the buffer variable.
buffer.u16@@ Read an unsigned 16-bit value from the buffer variable.
buffer.u32@@ Read an unsigned 32-bit value from the buffer variable.
buffer.u64@@ Read an unsigned 64-bit value from the buffer variable.
buffer.f32!! Write a 32-bit floating point value to the buffer variable.
buffer.f64!! Write a 64-bit floating point value to the buffer variable.
buffer.f32@@ Read a 32-bit floating point value from the buffer variable.
buffer.f64@@ Read a 64-bit floating point value from the buffer variable.