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.
orderdisplays the search order stack (context) and the vocabulary for new definitions (current),
alsois like "dup" for the search order stack,
previousis like "drop" for the search order stack,
onlyclears 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,
definitionsarranges for new definitions to go into the vocabulary that is the top of the search order stack,
forthvocabulary 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
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 )
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.
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-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
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
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
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.
$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
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