In which we have to reimplement create and does>.
Having implemented create
and does>
as described previously, there are two problems. Consider the following extract from my core tests:
t(": DOES1 DOES> @ 1 + ;", expected: "")
t(": DOES2 DOES> @ 2 + ;", expected: "")
t("CREATE CR1", expected: "")
t("CR1 here =", expected: "<TRUE>")
t("' CR1 >BODY", expected: "HERE ") // 1
t("1 ,", expected: "")
t("CR1 @", expected: "1 ")
t("DOES1", expected: "")
t("CR1", expected: "2 ")
t("DOES2", expected: "") // 2
t("CR1", expected: "3 ")
The line commented with “1” puts the data field of cr1
on the stack. This means we can’t just compile here
into the code of the definition for the created word.
The line commented with “2” and its successor show that, if we run does>
a second time on the same word, we must replace the old appended code. This means we must be more clever than just ticking a goto on the end of the existing definition. We must get rid of the old goto, if it exists.
The fix for the first issue is relatively simple. We’ll create a data field property for every word that is defined and have a primitive to store it and retrieve it.
The second problem is a bit more tricky. We can’t just examine the last two words to see if they form a goto because the last but one word might be the argument of a .pushnext
that just happens to have the same value as the .goto
primitive.
Step one is to define >body
. It did occur to me that the definition of >body
for a created word is just the same as executing the created word. However, if we just make >body
a synonym for execute
it could get confusing if we called >body
on a word defined by other means. So, instead, we will create an optional dataField
property, and assign it during create
. >body
will be a new primitive. The code for this change is tagged blog-1294-1.
The second problem is a bit more tricky. I have the choice of either assuming the last defined word is a created word, in which case, I can predict where the .goto
must go, or I can put an explicit exit
on the end of every defined word and replace it with a .goto
when we execute does>
. Then, if we execute does>
again for the same word, we can tell it’s the second time , because the last word won’t be an explicit exit
. The latter idea seems more reasonable because it gives less chance of screwing up a word that wasn’t created (as opposed to defined). On the other hand, adding an explicit exit
to every word seem fraught with danger. For example, postpone
will need to be modified to stop it from inlining the word’s exit
.
The design I have settled on is as follow:
- There’s a new word called
.nop
that does nothing. - The word created by
create
is padded with two instances of .nop at the end >does
becomes a primitive>does
first checks that the current word is acreate
defined word and throws an exception if not- The last two words in the current word’s definition – which are either two
.nop
s or a.goto
are replaced with a.goto
to the current word index in the current word number (the index will already have been incremented to the word after the>does
). >does
does an explicitexit
to miss out all the words in the definition following>does
.
The code for the above is tagged blog-1294-2