27.16Adding inline macros. 4tH 4tH Forth compiler
광고
광고
CHAPTER 27. MODIFYING 4TH
27.16
Adding inline macros
532
We’ve already seen how we can add new words to 4tH. We add a token and write the runtime. But this approach has a few disadvantages. First, the number of tokens is limited.
You can use them, but once you run out of them, that’s it. Second, writing a runtime is a little complex if you only have limited knowledge of C.
You can add as many inline macros as you want. From a user point of view there is not much difference between an ordinary 4tH word and an inline macro. The word is recognized by the compiler and works as expected.
Inline macros are simply sequences of existing tokens. As we’ve seen before, ’NIP’ is implemented as an inline macro, so this source:
1 2 nip drop
Will compile to:
[0]
[1]
[2]
[3]
[4] literal literal swap drop drop
(1)
(2)
(0)
(0)
(0)
There are disadvantages to inline macros as well. Every time you use ’NIP’ it will expand to two words, so your Hcode will become a little bigger. We recommend to limit inline macros to three words. Second, an implementation using inline macros will make the compiler less compact compared to an implementation using tokens.
On the other hand, you only need to change the compiler when you use an inline macro.
No changes to the interpreter or the decompiler will be necessary. Existing HX files will still run, although some 4tH sources will need modification.
Let’s go to business. How can we implement an inline macro. Let’s take ’NIP’. First we have to make an entry in the ImmedList[] table.
{ 2, 1, -1, "TO",
{ 2, 1, -1, "IS",
{ 2, 1, -1, "AS",
{ 2, 0, 0, "IF",
{ 2, 0, 0, "DO",
{ 2, 0, 0, "->",
{ 3, 0, 1, "2R>",
{ 3, 0, 1, "2>R",
{ 3, 0, 3, "2R@",
"",
"",
"",
"",
"",
"",
"",
"",
"",
DoValue },
DoIs },
DoValue },
DoIf },
DoDo },
DoDummy },
DoTwoRGet },
DoTwoRPut },
DoTwoRCopy },
Since ’NIP’ is defined by:
: nip swap drop ;
It will compile to: swap (0) drop (0)
Which is one token more than the parser would expect. So the value of the fourth field is "1". But this will only reserve space for ’NIP’. The word won’t be recognized by the compiler yet. In order to do that we have to make a word that compiles the tokens for
’NIP’. You can only do that with an "immediate" word:
CHAPTER 27. MODIFYING 4TH 533
#ifndef ARCHAIC static void DoNip (void)
#else
#endif static void DoNip ()
{
CompileWord (SWAP, 0L);
CompileWord (DROP, 0L);
}
This will compile ’SWAP’ and ’DROP’ into the compilant. Now we have to make an entry in ImmedList[] to link this function to the name "NIP":
{ 2, 1, -1, "TO",
{ 2, 1, -1, "IS",
{ 2, 1, -1, "AS",
{ 2, 0, 0, "IF",
{ 2, 0, 0, "DO",
{ 2, 0, 0, "->",
{ 3, 0, 1, "2R>",
{ 3, 0, 1, "2>R",
{ 3, 0, 3, "2R@",
{ 3, 0, 1, "NIP",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
DoValue },
DoIs },
DoValue },
DoIf },
DoDo },
DoDummy },
DoTwoRGet },
DoTwoRPut },
DoTwoRCopy },
DoNip },
That’s all! Since ’NIP’ uses existing tokens, the compiler can handle it all by itself. There is no need to write runtime code or define tokens. All the burden is put on the compiler.
27.17
Adding string words
We’ve already seen that some words in 4tH have a name attached to them, like ’STRING’,
’CREATE’ or ’VARIABLE’. Since all these names are delimited by whitespace (like any other 4tH word), there is no need for special code.
Some words have special strings attached to them, like ’."’, ’(’ or ’\’. These string are not delimited by whitespace, so they need special treatment. It’s even more complex: each word has a different delimiter. ’."’ is delimited by ’"’, ’(’ is delimited by ’)’, and ’\’ is delimited by an end-of-line marker.
In this section we’re going to explain how we added ’."’, since it’s the most complex string word. Other string words like ’(’ are handled by the compiler only.
The first step to adding a string word is letting the compiler know, what delimiter is used.
We do that by modifying ImmedList[]:
{ 2, 0, -2, "#!",
{ 2, 0, -1, ",\"",
{ 2, 0, -1, ",|",
{ 2, 0, -1, ".\"",
{ 2, 0, -1, ".(",
{ 2, 0, 1, ">=",
{ 2, 0, 1, "<=",
{ 2, 0, -1, "S\"",
{ 2, 0, -1, "S|",
EOL, DoComment },
"\"", DoCommaQuote },
"|", DoCommaQuote },
"\"", DoDotQuote },
")", DoDotQuote },
"",
"",
DoGreaterEqual },
DoLessEqual },
"\"", DoSQuote },
"|", DoSQuote },
The fifth field tells the parser whether this word requires a special delimiter. Yes, ’."’ is delimited by ’"’, so we enter a quote in the fifth field. If you enter an empty string, the parser assumes the word isn’t a string word at all.
CHAPTER 27. MODIFYING 4TH 534
Now, what will the parser do when it encounters ’."’? It will find an entry in ImmedList[] for ’."’. It will see that this is a string word. Then it will make a call to ParseString() to find the delimiter and flag everything in between as a word. Which means that if you call GetNextWord(), you will get the entire string and not just the next word, e.g.:
: hello ." Hello world" cr ;
Will be parsed as:
GetNextWord ():
GetNextWord ():
GetNextWord ():
GetNextWord ():
GetNextWord ():
GetNextWord ():
: hello
."
Hello world cr
;
You will notice that there are some words that are delimited by whitespace, e.g. ’CHAR’.
Why is that? Isn’t every word delimited by whitespace? Yes, it is. But note that every word, which is parsed by ParseText() is also checked by ParseDirectives().
This expression would cause problems:
CHAR (
After ’CHAR’ is parsed by ParseText(), ’(’ follows and is recognized by ParseDirectives() to be the start of a comment. To prevent this, we let the string following ’CHAR’ be parsed by ParseString(). By the time ParseText() regains control, the character following ’CHAR’ is already parsed and can cause no more problems. In short, if an expression like: word ( or word \ is valid, let it be parsed by ParseString() by making an entry in the delimiter field of the ImmedList[] table. If not, don’t.
Next, we have to develop a word that compiles ’."’. Now, how can we compile ’."’? First, we have to get the string and move it to the String Segment. We can do that by calling
GetNextWord() manually, but then we have to check for NULL-pointers. It is much easier to call DecodeWord() which sets the ErrNo member automatically when an error occurs.
DecodeWord() takes one argument, which is the error code it should set the ErrNo member to. It returns TRUE if GetNextWord() was called successfully. "CurrentWord" now points to the string after ’."’.
Then we have to move the string to the String Segment. MoveString() does just that.
It expects "CurrentWord" to point to the string that has to be moved. It returns a number.
We’ll need that when we design the runtime code.
There is no token or combination of tokens for printing strings. So this one will need a token of its own. We’ll call it "PRINT" for the time being. Now, we got all components.
다운로드
광고