The MIPS disassembler (arch/mips/disasm.fs) contains many words for disassembling instructions, that follow a very repetetive scheme:
:noname disasm-operands s" inst-name" type ; entry-num cells table + !
Of course, this inspires the idea to factor out the commonalities to allow a definition like
disasm-operands entry-num table define-inst inst-name
The parameters disasm-operands and table are usually correlated. Moreover, before I wrote the disassembler, there already existed code that defines instructions like this:
entry-num inst-format inst-name
This code comes from the assembler and resides in arch/mips/insts.fs.
So I had to define the inst-format words that performed the scheme above when executed. At first I chose to use run-time code-generation:
: inst-format ( entry-num "name" -- ; compiled code: addr w -- ) :noname Postpone disasm-operands name Postpone sliteral Postpone type Postpone ; swap cells table + ! ;
Note that this supplies the other two parameters of the scheme above.
An alternative would have been to write this using
: inst-format ( entry-num "name" -- ) here name string, ( entry-num c-addr ) \ parse and save "name" noname create , ( entry-num ) latestxt swap cells table + ! does> ( addr w -- ) \ disassemble instruction w at addr @ >r disasm-operands r> count type ;
Somehow the first solution is simpler, mainly because it’s simpler to
shift a string from definition-time to use-time with
string, and friends.
I wrote a lot of words following this scheme and soon thought about factoring out the commonalities among them. Note that this uses a two-level defining word, i.e., a word that defines ordinary defining words.
This time a solution involving
postpone and friends seemed more
difficult (try it as an exercise), so I decided to use a
does> word; since I was already at it, I also used
does> for the lower level (try using
postpone etc. as an exercise), resulting in the following
: define-format ( disasm-xt table-xt -- ) \ define an instruction format that uses disasm-xt for \ disassembling and enters the defined instructions into table \ table-xt create 2, does> ( u "inst" -- ) \ defines an anonymous word for disassembling instruction inst, \ and enters it as u-th entry into table-xt 2@ swap here name string, ( u table-xt disasm-xt c-addr ) \ remember string noname create 2, \ define anonymous word execute latestxt swap ! \ enter xt of defined word into table-xt does> ( addr w -- ) \ disassemble instruction w at addr 2@ >r ( addr w disasm-xt R: c-addr ) execute ( R: c-addr ) \ disassemble operands r> count type ; \ print name
Note that the tables here (in contrast to above) do the
by themselves (that’s why you have to pass an xt). This word is used in
the following way:
' disasm-operands ' table define-format inst-format
As shown above, the defined instruction format is then used like this:
entry-num inst-format inst-name
In terms of currying, this kind of two-level defining word provides the
parameters in three stages: first disasm-operands and table,
then entry-num and inst-name, finally
addr w, i.e.,
the instruction to be disassembled.
Of course this did not quite fit all the instruction format names used in insts.fs, so I had to define a few wrappers that conditioned the parameters into the right form.
If you have trouble following this section, don’t worry. First, this is
involved and takes time (and probably some playing around) to
understand; second, this is the first two-level
does> word I have written in seventeen years of
Forth; and if I did not have insts.fs to start with, I may well
have elected to use just a one-level defining word (with some repeating
of parameters when using the defining word). So it is not necessary to
understand this, but it may improve your understanding of Forth.