In the middle this room a small shaft descends into darkness below. Above the shaft is a metal framework to which a heavy iron chain is attached. There are exits to the west and north. A foul odor can be detected from the latter direction.
From the chain is suspended a basket.
The basket contains:
A torch (providing light)
> put the coal in the basket
> lower the basket
The basket is lowered to the bottom of the shaft.
> go north
> go east
> go north
> go south
> go down
> drop all
clove of garlic: Dropped.
pair of candles: Dropped.
brass lantern: Dropped.
> go west
Code for this episode can be found at https://github.com/ericlippert/flathead/tree/blog20
Recall that every object has a set of properties, that properties can have multiple bytes of value, and that they have default values if omitted. There are five instructions that manipulate properties:
get_prop_len instruction is a bit odd in that it takes the address of a property rather than an object number and property number, as all the rest do. Note that the address associated with a property is the address of the property data itself; this will cause us some small difficulties, as we’ll see.
The instruction handlers are all just straightforward little methods that call helpers in the object module, so I won’t bother to list them here.
Let’s start with the default values of properties. That’s pretty straightforward. I’m going to make yet another wrapper type, this one to represent a property number:
type property_number = Property of int
And we can fetch the default value of a particular property easily enough:
let default_property_table_base story = let (Object_base base)= Story.object_table_base story in Property_defaults_table base let default_property_value story (Property n) = if n < 1 || n > (default_property_table_size story) then failwith "invalid index into default property table" else let (Property_defaults_table base) = default_property_table_base story in let addr = Word_address ((base + (n - 1) * default_property_table_entry_size)) in Story.read_word story addr
So if an object does not have a property, we can fetch its default value. What if it does have a property?
We already know that the last two bytes in the descriptor for an object is a pointer to a block of property data. We also know already that it begins with a length-prefixed zstring-encoded string giving the “short name” of the object. The following data block logically very straightforward; it consists of:
- the property number
- the length of the data for that property
- that many bytes of data
and then another number / length / data, and so on. A zero byte terminates the table. So we can do a linear search of the table looking for the property we want. Easy, right?
Unfortunately the actual implementation is a little less straightforward.
In version 3 it’s not bad. The length and property number are encoded in a single byte. If zero, then there are no more properties. Otherwise, we treat the top three bits as a three-bit integer, add one, and that’s the length. The bottom five bits are the property number. There are only 31 possible properties, and the length is anything from 1 to 8 bytes.
In version 4 and above though things get weird. The version 4 format is:
- if bit 7 of the first byte is clear then bit 6 indicates whether the property is one or two bytes, and the bottom six bits give the property number
- if bit 7 is set then the bottom six bits of the following byte give the length, and if the length is 0 then it is treated as though it were 64, and the high bit of the following byte must be set. We’ll see why next time, though perhaps you can guess.
What a mess.
Once again, to ensure that the type system is helping me prevent bugs, I’m going to give a different type to each kind of integer:
type property_address = Property_address of int type property_data_address = Property_data of int
Let’s implement some logic for the rules I’ve just laid out:
let decode_property_data story (Property_address address) = let b = Story.read_byte story (Byte_address address) in if b = 0 then (0, 0, invalid_property) else if Story.v3_or_lower (Story.version story) then (1, (fetch_bits bit7 size3 b) + 1, Property (fetch_bits bit4 size5 b)) else let prop = Property (fetch_bits bit5 size6 b) in if fetch_bit bit7 b then let b2 = Story.read_byte story (Byte_address (address + 1)) in let len = fetch_bits bit5 size6 b2 in (2, (if len = 0 then 64 else len), prop) else (1, (if fetch_bit bit6 b then 2 else 1), prop)
This thing takes the address of a property block and returns a 3-tuple containing first, the length of the header, second, the length of the data, and third, the number of the property.
Next time on FAIC: We’ll use this information to make a list of all the properties for a given object.