Forth Lesson 21
This lesson is based on a transcript of a conversation in #olpc-devel with Mitch Bradley.
Word lookup and Vocabularies
A vocabulary is an ordered list of words, searched from most-recently-created to least-recently created. The search order is a stack of vocabularies. Executing the name of a vocabulary replaces the top of the search order stack with that vocabulary.
Useful commands:
order
displays the search order stack (context) and the vocabulary for new definitions (current),also
is like "dup" for the search order stack,previous
is like "drop" for the search order stack,only
clears the search order stack and then primes it with a default set of vocabularies that contain at least the root vocabulary, which contains the words necessary to manipulate the search order,definitions
arranges for new definitions to go into the vocabulary that is the top of the search order stack,- the
forth
vocabulary contains the words that one normally uses for Forth programming.
This rather odd scheme was for compatibility with an older arrangement in which one one vocabulary could be searched at a time - essentially the historical search order stack had only one entry. The phrase "only forth also definitions
" is the canonical way to get things back to the usual state. But for most coding, you want to use a reversible technique:
vocabulary foo also foo definitions <add words to foo> previous definitions
The initial vocabulary foo
creates a new wordlist. The name of that wordlist "foo" exists within whatever wordlist was the "current" vocabulary, i.e. the wordlist that was on top of the search order when "definitions" was last executed. The defining word "vocabulary" does not in and of itself change the search order, it just creates a container into which new words could be added. Then, when you say also
, that "dup"s the top of the search order, so that the following foo
does not overwrite the top of the search order.
Suppose the search order currently contains "root forth" (forth on top). Then, if you say "foo", the search order would be "root foo". But if you said "also foo", the search order would be "root forth forth" just after the "also", then "root forth foo" after the final "foo".
The preceding describes the conventional way of controlling the search order within source files. There are some primitives that are much more useful within code:
The "get-order" word pushes the search order stack contents onto the data stack:
get-order ( -- vocn .. voc1 n )
And there's a corresponding "set-order":
set-order ( vocn .. voc1 n -- )
To create an empty unnamed wordlist:
wordlist ( -- voc )
You can also manually search through a vocabulary, even if it's not currently in the search order:
search-wordlist ( adr len voc -- false | xt +1 )
The search-wordlist
word is a primitive that you can use to search a specific vocabulary; for example, it can be used for string-associative tasks. The search-wordlist
word does not execute anything, it just does the lookup and tells you what it found.
However, search-wordlist
is also invoked by the forth machinery when it is parsing a word, at the very bottom of things. So it could be hooked to implement other ways of looking up forth words; however, you probably want to hook at a higher level.
Device Tree lookup
The OFW device tree is weakly object-oriented. Each device node contains a set of methods. That method list is implemented as a vocabulary - but the vocabulary need not be in the search order to be used at runtime. Instead, you call the method via an "instance handle". You must explicitly use $call-method
or its variants $call-self
and $call-parent
to invoke a method.
For example, a proper device method invocation looks like:
0 value disk-ih " /pci/scsi/disk" open-dev to disk-ih
Note that disk-ih
here pushes a pointer to the instance handle on the stack. Another example:
h# 1000 buffer: my-buf my-buf h# 1000 " read" disk-ih $call-method
Again, disk-ih
does nothing but push (a pointer to) the instance handle on the stack, and then $call-method
takes an instance handle and a string naming a method and does the actual dispatch. It is reasonably fast, since the number of methods in a typical device node is modest and the lookup is implemented efficiently.
There is also a debugging mechanism for the device tree which uses vocabularies. dev /pci
is really a debugging tool, not a runtime thing, but it does push the device node vocabulary on the search order. It is equivalent to
also <pci_device_tree_vocabulary> definitions
The corresponding device-end
word pops that vocabulary.
Instance Handle Details
The instance handle (ihandle) is the address of a data structure that contains a reference to a method vocabulary, a reference to a similar data structure for the parent device's ihandle, and private data specific to that instance. There is a global value my-self
that dynamically points to the ihandle for the device instance whose method is currently executing.
The $call-method
word pushes the current value of my-self
on the return stack, sets my-self
to the argument ihandle
, executes the named method, then pops the return stack into my-self
.
The open-dev
word opens all of the device nodes in the path from the root to the leaf devices, linking them together in an "instance chain". In that chain, each instance record (referent of an ihandle) contains a link to its parent's instance record, so a device method can use $call-parent
to invoke services from its parent bus.
Thus, for example, the disk methods can call scsi methods which can call pci methods which can call root bus methods. It's not quite inheritance, though. You must explicitly call up the chain. The namespaces aren't commingled.
When a device node in a chain needs methods from a package, the device node opens the package it needs when the device is opened. The device node, when its children call device node methods, sometimes calls the package methods. Support packages must be opened explictly and called explicitly, with $call-method
.