Forth Lesson 21

From OLPC
Revision as of 20:45, 14 April 2011 by CScott (talk | contribs) (More edits of the transcript into a proper lesson.)
Jump to navigation Jump to search
Mitch Bradley's Forth and
Open Firmware Lessons:

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:

  • 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 -- )

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.