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
.nopthat does nothing. - The word created by
createis padded with two instances of .nop at the end >doesbecomes a primitive>doesfirst checks that the current word is acreatedefined word and throws an exception if not- The last two words in the current word’s definition – which are either two
.nops or a.gotoare replaced with a.gototo the current word index in the current word number (the index will already have been incremented to the word after the>does). >doesdoes an explicitexitto miss out all the words in the definition following>does.
The code for the above is tagged blog-1294-2
