Programming Forth?
The Robot World Repo on GitHub
The Forth Repo on GitHub
I feel that I’ve shown that I can program A Forth, but I don’t really know how to program IN Forth. Time to begin to learn. #StopTheCoup!
My alleged purpose for this Forth is to provide a simple but useful “programming language” that imaginary users could use to program the bots in the Robot World program. The idea is that there would be some simple conventions for controlling your bots, and a simple interface that you’d implement, all in Forth, and then you could run the Robot World and INCLUDE your code and observe your bots botting around.
I have some tentative ideas about how this would work. Here are a few:
- The player must implement a word, say RUN-BOT, that will be called repeatedly for each of the player’s bots.
- Some predefined words will allow the player to control the currently running bot, such as motion words like LEFT, RIGHT, AROUND, EAST, WEST and so on, world-inspection words like LOOK or SNIFF, action words like TAKE or DROP, and so on.
- I think we would limit the bots to one actual MOVE per turn, which would cause the bot to try to move in the direction it is currently facing.
- On each call to RUN-BOT, various words would allow the player’s code to access the variables containing details of the bot’s status, maybe BOT.X, BOT.HOLDING, and so on.
- I assume that we’d need some pre-defined constants to inform the player about just what it is looking at, so that you could say something like LOOK BLOCK? IF TAKE ELSE TURN THEN.
What I emphatically do not have is a good sense of what a decent Forth program to control a bit might look like. I have read a lot about Forth, but not much about what a good program in Forth is like.
There is another issue somewhat askew to the issue of “How do we program a bot?”, namely “How can we write a program that runs a bot well enough to help us learn Forth?”
One possibility is to pause here and explore how to interface PyGame and Forth. One relatively awful possibility would be to finish up the client-server mode and have two programs running, RobotWorld standing alone, with a separate Forth program talking to it. I know that my friend Bryan would not consider that possibility to be awful, because he has multiple programs talking to each other in just that fashion. But I think we really want a single stand-alone game with a text pane and a game display. Sooner or later, I do have to do that.
But right now, I’m in a Forth frame of mind, so while I’m as fresh as I’ve been on Forth in recent memory, I’d like to do some work on programming a bot in Forth. Nothing for it but to start.
I type in this sequence, edited a bit for clarity:
Forth>variable bot 3 allot
ok
Forth>: b.x bot ;
ok
Forth>: b.y bot 1 + ;
ok
Forth>: b.dir bot 2 + ;
ok
Forth>20 30 b.y ! b.x !
ok
Forth>b.y @ .
30 ok
Forth>: left b.dir @ 1 + 4 mod b.dir ! ;
ok
Forth>b.dir @ .
0 ok
Forth>left
ok
Forth>b.dir @ .
1 ok
Forth>left left
ok
Forth>b.dir @ .
3 ok
Forth>left
ok
Forth>b.dir @ .
0 ok
Forth>
THe first four commands define a variable ‘bot’ with 3 cells, and three words bot.x, bot.y, bot.dir that return the addresses of cells 0, 1 and 2 of bot. I mean those to be the bot’s x, y, and direction, of course.
In my head I defined directions to go from 0 to 3, counter-clockwise, so if you are facing 0 and turn left you are facing 1 and so on until when you are facing 3 and turn left, there you are back at 0.
So left is:
: left b.dir @ 1 + 4 mod b.dir ! ;
Get the contents of b.dir with @, add 1 to it take that mod 4 and store it back into b.dir.
Then I checked to see that it worked, and it did.
If I liked those definitions, I could put them into an INCLUDE file and load them up thereafter.
Another way to have done the layout of the bot record might be this:
variable bot variable b.x 1 allot variable b.y 1 allot variable b.dir 1 allot
Note that variable
does not allocate any space. So in the line above, b.x has the same address as bot, which is what we intend. And the other two allocate space. Note that b.y is in the right place because b.x did the allot … and the final allot ensures that the next variable we define will not stomp on b.dir. It’s odd, isn’t it, but such is Forth.
While doing all that I noticed something bad. When Forth sees an error and since I’m typing it will see plenty, it goes to abend
and that seems to clear the heap.
# noinspection PyAttributeOutsideInit
def abend(self):
self.active_words = Stack()
self.compile_stack = Stack()
self.compilation_state = False
self.c_stack_top = None
self.heap = Heap()
self.provider = StringProvider('')
self.return_stack = Stack()
self.stack = Stack()
self.word_list = []
self.stack.clear()
Sure enough, there it is. That’s not good, because it means that after a typo, all your current variables remain defined, but new variables will step on them. In addition, I suspect you could generate an error condition pretty easily:
Forth>42 constant answer
ok
Forth>answer .
42 ok
Forth>foo
Syntax error: "FOO" unrecognized ?
Forth>answer .
0 ok
We see above that answer
has been wiped out by the error. I think we can do worse.
Forth>variable a 1 allot
ok
Forth>22 a !
ok
Forth>a @ .
22 ok
Forth>foo
Syntax error: "FOO" unrecognized ?
Forth>a @ .
pop from empty list ?
Forth>
OK, we just need not to clear the heap except at init. Commit: init Heap only once so that errors don’t lose all the variables.
- Side Note:
- A more classical way of managing variables and ALLOT is to allocate space in the lexicon for the variables and their contents. There would be no “heap” per se, or you could view the lexicon as a heap containing the word definitions. I don’t think we’ll do that, but you never know what I might do.
Reflection
This little exercise has found a defect for us, and it has clarified my thinking a bit. In particular, I think we want the access words like b.x and b.dir to act like constants, not variables. We don’t want the player to be able to store new values directly into the bot’s details. But we do want our own code to do that.
Let me try to type in a sequence that would allow that.
Forth>variable _b.dir 1 allot
ok
Forth>: b.dir _b.dir @ ;
ok
Forth>2 _b.dir !
ok
Forth>b.dir .
2 ok
Forth>55 b.dir !
list assignment index out of range ?
I define a “secret” variable ‘_b.dir’ and then a word ‘b.dir’ that returns the contents of ‘_b.dir’. We would publish b.dir and not mention the secret one. So when you say ‘b.dir’ you get the value and when you try to store into it you get your hand slapped.
However. This, too, teaches me a lesson. The way the heap works now, the ‘@’ and ‘!’ words just access the word at that index in the heap:
self.pw('@', lambda f: f.stack.push(f.heap.at(f.stack.pop())))
self.pw('!', lambda f: f.heap.put(f.stack.pop(), f.stack.pop()))
There is no checking to see whether your particular access is within the range of your word’s allocation. If that many words are in the heap, we’ll get or set the corresponding word!
That’s pretty much what Forth would do, although with allocated words in the lexicon there could be a bit more checking. We might have to look into that after all. Anyway, I’ve made a note of this concern. In a classical Forth, a lot of mistakes were somewhat fatal. We probably don’t want quite that same level of sensitivity to mistakes in our game.
Summary
This is a good place to stop. I’ve done some experiments, firmed up some ideas, gained some experience in Forth thinking, and identified some areas where our Forth needs some improvement.
When learning a new language, I’ll often write little programs, just a few lines, to do things. With Forth, we can build things up a bit more dynamically than we could with a more conventional language, but the process will be much the same. But there is one exception that is quite interesting: since we are writing the Forth as well as using it, we can modify the language as well as build up our application. And with Forth in Python we can improve the language by defining new words in Forth and using INCLUDE, or we can define behavior in Python and recompile.
I expect that this will lead to some sequences where we do something in Forth, try a few different definitions, then get an idea for something that should be more built in, and build it in. But if things go as I think they “should”, we’ll do more and more of our Forth enhancement in Forth language, rather than the Python code.
I think that’s going to be quite an interesting thing to watch. I hope you’ll join me on the journey.
#StopTheCoup! See you next time!