My Object FORTH
Intro
EMForth has OO (object oriented) extensions. Porting OO to SAM7 emforth is mostly complete but not well tested. It is based on code I wrote 20 years ago for the transputer and later ported to the 68hc11 and 68hc16.
Like most OO systems it sends messages to objects. Unlike most systems this can be either in compiled code or interactive. Unlike most systems objects are capable of accepting multiple messages.
Like most systems we can define classes. The definition contains data fields and method definitions.
Every class has a parent or superclass and inherits data fields and methods from the parent. The base classes have a null parent called "Nil".
Classes aren't used directly - instances of classes are created and these are used to do useful work.
A little like pascal records or "C" structures, you can define a data structure to suit your needs. The difference with objects is you also define the actions to perform on the data and generally don't use the data directly.
Messages
Before we can use messages we have to define them. For things to work properly they must be defined into the messages dictionary.
MESSAGES DEFINITIONS MESSAGE getX MESSAGE setX MESSAGE getY MESSAGE setY MESSAGE getZ MESSAGE setZ
Class definition.
Here is a trivial example of a base class which has two data fields and four methods to read and write data into the data area.
super Nil var Int X // these are instance variables, every instance get its own data area. var Int Y begin class Definition Test_Class getX begin Method X @ end Method setX begin Method X ! end Method getY begin Method Y @ end Method setY begin Method Y ! end Method end class Definition
This class has two data fields - X and Y. If we wanted another class with X Y and Z we can reuse the data and methods of the above class by making it the superclass of our new class.
Like this..
super Test_Class var Int Z begin class Definition Test2_Class getZ begin Method Z @ end Method setZ begin Method Z ! end Method end class Definition
A real world example might be to have a base class called LineClass which has data fields X1,Y1,X2 and Y2 which define the line end points in two dimensions. We might define another class called StrokeClass which inherits the properties of LineClass then adds extra properties such as line width and colour.
To make instances of this classes we do this.
instance Test_Class Test instance Test2_Class Test2
You will usually have many instances of a class not just one.
To interactively set then retrieve the data in these instance we can do this.
1 setX Test 2 setY Test 3 setZ Test2 getX Test . CR // this would print "1". getY Test . CR getZ Test2 . CR
These commands can also be compiled.
BTW, You can't send a message like setX to the class - you can only send it to an instance.
Messages sent to a class actually get redirected to an internal object called [Class] this understands things like "instance" and "super" (and more).
The self method.
Sometimes when you are defining methods for an object you would like to call one of the methods you have already defined for it.
For example you might be writing the methods for a graphical object called DRAWCLASS to as a display window on a LCD.
You may already have defined a "clear" methods to erase the window.
Now you are defining method to clear the window and draw a frame around it. To call the existing "clear" method from within the "frame" method you simply write
"clear self Method".
BTW - This generates code which sets the interpret flag, duplicates the top of the object stack and calls the method. If the interpret flag wasn't set the method code would attempt to load the object address as a literal - this isn't possible. Instead we force it to act on the address on the object stack (at runtime).
The super method.
Sometimes when defining methods, you want the call a method of the parent or super class.
If you were defining a child class of the DRAWCLASS and wanted to call "clear" you would write.
"clear super Method".
BTW - this would add an offset to the value on the object stack then push that value onto the O-stack.
At runtime this is done by the word [SUPER]
The fields in the data space have the child data fields first followed by the fields on the super class(es).
This may seem wrong but there is method to the madness - in many processors is it faster and more compact to use short offsets to access the data so the complication in accessing super-class methods is offset by faster access most of the time. This was particularly true for the transputer where four bit constants were coded in a one byte instruction. It is currently not useful for the ARM unless extra code is added to optimism access to the first part of the data area.
So a method of Test2_Class which prints X Y Z looks like this.
showXYZ begin Method getX super Method . getY super Method . getZ self Method . CR end Method
Dynamic instances.
One of the most interesting possibilities is the creation of dynamic objects.
What I mean by this is - objects which are created "on the fly" at runtime.
In the PC world an example of this is - most, if not all, the windows on yours computer screen.
These will be instances of some sort of "form" or "window" class which were created as needed.
In the embedded world an example might be a game like the classic "space invaders". I'm sure the original version would not have been OO but what if we wrote space invaders now as an exercise.
All the dozens of invaders could be instances of the class "Invader_Class". The instances could be created at play time. The class might have properties (instance variables) like position and state.
Position would be X,Y display coordinates and states might be - live, dead, explosion1,..., explosionN
Methods might be things like - step right, step left, step down, test collision.
The main code would simply send the messages (or call the methods) for every invader in a loop.
Each invader would test for a collision of itself against the missiles and set the state to exploding if the positions overlap.
The missiles might also be instances of a missile_class and so on.
We might use a variation of the finite state machine support to animate the explosion.
You could also go the whole hog and make the invaders cellular automata.
There is nothing special about the dynamic objects classes - they are defined like any other class and any class could have both static and dynamic instances of it.
To create a dynamic instance of an invader you would simply type..
"dynamic instance Invader_Class"
The memory allocator would grab enough memory - the instance is built into the space and the address of the object returned on the data stack. The word before the object is set to the length of the allocated block so it can be deallocated if the object is destroyed.
The address of the object might be stored in an array or perhaps used to create a linked list.
The object can be created interpretively or by compiled code.
The former might be used for debugging or running a script.
The later would be used for a demo space invader program.
To complicate matters the methods can be called in at least three distinct ways.
Firstly - They can be called interpretively by placing the messages onto the Mstack and executing the code at the object address.
Secondly - If the class of the object is known at compile time then the methods can be determined at compile time. Inside a forth definition you place the address of the object on the data stack and use a syntax like.
"stepleft dynamic Method Invader_Class"
Thirdly if the class is not known we can place the messages on the message stack and execute the code at the object address. This is almost identical to interpretive mode.
Using the Test2_Class class defined above - this is how to create and use a dynamic instance.
// In the interactive mode.. VARIABLE OBJADD // somewhere to remember the location. instance dynamic Test2_Class // create the instance. OBJADD ! // remember the address for later. // You can also write forth code to create it - example : CREATEDEMO // mt - object address; instance dynamic Test2_Class // create the instance. ; // to call a method interactively ABCD OBJADD @ setZ EXECUTE // this sets the Z field of the object to hex ADCD. // or there are two way to call a method in a compiled word. : TSTDI OBJADD @ showXYZ method dynamic Test2_Class OBJADD @ showXYZ EXECUTE ; // these two lines of code achieve the same thing // the first line is much faster because the method lookup happens at compile time.
Q - Why use "showXYZ EXECUTE" when "showXYZ method dynamic Test2_Class" is faster?
A - Because you might not know the class of object you are talking to.
Example - You might have a list of objects which need to be drawn on a display.
There might be a mix of windows, icons, a cursor, etc.
Using late binding (the ADDR message EXECUTE format) you could send the "draw" message to every object in your list despite the fact that they have different types and were created after the code was compiled.
a memory dump of the created dynamic object looks like this.
20BA14 0 40 2D E9 FC F6 FF EB F8 AB 20 0 CD AB 0 0 20BA24 55 55 0 0 34 12
The first three words are - a LR push, a call to [CODEOB] and a pointer to the class.
The next three words are the data.
A dynamic object is three words longer than the data size plus one extra word is used by the memory allocator to store the length.
SHOWFREE - shows the free memory list....
Address - 20BA2C Size - 3A40
Address - 20F210 Size - 25C
To destroy the object I say.
OBJADD @ FREEMEMORY
The free list now shows hex 1C bytes more memory than before.
Address - 20BA10 Size - 3A5C
Address - 20F210 Size - 25C
Published fields.
Normally the data is only accessed via methods and is hidden from the rest of the world.
I have provided the option to publish a data field so it can be accessed directly.
Physically this means a symbol table is added the class structure.
At present there are no words defined to use this in any practical way except to view the field offsets.
There is no plan the write the code unless I need to use it.
Above I've shown how to declare a normal data field.
For example..
var Int X
The published version is simply
var published Int X
More info on this is in the next section.
Examining an object.
We can ask the system to tell us about an object. What sort of object is it (class, instance etc) and what message does it understand.
If I type.... methods Of Test Forth answers... it's an instance of the class Test_Class it understands the following messages :- getX setX getY setY SUPER CLASS --> Nil
If I asked about Test2 it will tell me not only about getZ and setZ but also the methods of the superclass(s).
methods Of Test2 it's an instance of the class Test2_Class it understands the following messages :- getZ setZ showXYZ SUPER CLASS --> Test_Class getX setX getY setY SUPER CLASS --> Nil
Some have no class.
It seemed a little silly to me to have a class with only one instance so I implemented object which don't belong to a class and don't use the ostack to point to a data area.
You have already seen some of these in action. The words "Definition","Method" and "Of" and examples of classless objects.
Querying the methods of Definition shows us this object has a method hierarchy and accepts multiple messages.
methods Of Definition it's an object it understands the following messages :- class begin end object begin end compiler object begin end SUPER CLASS -->
You've already seen the usage in the earlier definitions.
When you say "begin class Definition" the messages "begin" and "class" are placed on the message stack and looked up in the method list for "Definition" - "class" is on top of the stack and is found to have "begin" as a sub-method so the target code can be located.
All method lists can have sub-methods so this applies to classy objects as well.
Published fields.
Published field are mentioned in the previous section.
For this section assume all the data fields in the above examples have been declared as published.
If I say to Forth - "fields Of Test_Class"
It will reply ....
it's a class
it has the following published data fields :-
Field :- X OFFSET = 0
Field :- Y OFFSET = 4
SUPER CLASS --> Nil
The offsets are in bytes so this makes sense.
fields Of Test2_Class
it's a class
it has the following published data fields :-
Field :- Z OFFSET = 0
SUPER CLASS --> Test_Class
Field :- X OFFSET = 4
Field :- Y OFFSET = 8
SUPER CLASS --> Nil
Because the Z field of Test2_Class sit at the beginning of the data field the offsets of X and Y have changed.
As stated elsewhere this is largely a hangover from the original transputer forth. This make less sense on the ARM and I may convert to putting the newer fields at the end one day.
fields Of Test2
it's an instance of the class Test2_Class
it has the following published data fields :-
Field :- Z OFFSET = 0
SUPER CLASS --> Test_Class
Field :- X OFFSET = 4
Field :- Y OFFSET = 8
SUPER CLASS --> Nil
Examining Test2 shows much the same info as Test2_Class. It would be possible to also show the absolute addresses of the fields.
Adding words to extract the offsets and absolute address of fields should not be too difficult.
Future object types.
So far objects can only live in RAM. At a minimum I need a "flash object" which can have the body in flash but a data area in ram - similar to how I do variables.
Remote objects.
Messages aren't just used to talk the virtual objects, I use them to talk the real objects too.
When using emserver, access to the keyboard, screen and file system is done by sending messages over the serial link.
For ease of routing a device ID is sent first then followed by 16 bit messages.
Because all devices have to use the same message code GMESSAGEs are used.
To open a file using the OO format (not defined for ARM yet) you say...
GETNAME read open File
This in turn sends a header, the messages "open" and "read" and the name string to the serial server.
A real world example.
Here is an example of what my object forth looks like.
The fragment is part of a simple GUI I wrote for a LCD on a hc16 micro.
This for doing clip regions. IE when you draw on the screen you want to remember what you drew over so you can restore it.
// We define the class "Region" ( Region Class ) super Nil // this is the parent var Int X1 // these are instance variables var Int X2 var Int Y1 var Int Y2 var Ptr *BMS var Ptr *CLIPSTORE VARIABLE BYTES VARIABLE WRAP VARIABLE STRTADDR // these are local variables : SETVARS // these are local forth words. X2 @ 8/ X1 @ 8/ - 1+ BYTES ! *BMS CALCWRAP DUP WRAP ! ( BUFFER WRAP ) Y1 @ * ( BUFFER WRAP*Y ) X1 @ 8/ + + ( BUFFER+WRAP*Y+X1/8 ) STRTADDR ! ; begin class Definition Region // now define the methods. setup ( X1 Y1 X2 Y2 *BM ) begin Method *BMS ! Y2 ! X2 ! Y1 ! X1 ! 0 *CLIPSTORE ! X1 @ X2 @ > IF X1 @ X2 @ X1 ! X2 ! THEN Y1 @ Y2 @ > IF Y1 @ Y2 @ Y1 ! Y2 ! THEN end Method clear begin Method X1 @ Y1 @ X2 @ Y2 @ *BMS @ box Draw end Method clip begin Method SETVARS BYTES @ Y2 @ Y1 @ - 1+ * *CLIPSTORE @ NOT IF GETMEM DUP *CLIPSTORE ! STRTADDR @ SWAP Y2 @ Y1 @ - 1+ 0 DO ( Once per line ) BYTES @ 0 DO ( STRTADDR *STORE -- once per byte ) OVER I + C@ ( STRTADDR *STORE CHAR -- ) OVER C! 1+ LOOP SWAP WRAP @ + SWAP ( adjust addr to next line ) LOOP DROP DROP ELSE DROP // THEN end Method restore begin Method SETVARS *CLIPSTORE @ DUP IF STRTADDR @ Y2 @ Y1 @ - 1+ 0 DO ( Once per line ) BYTES @ 0 DO ( *STORE STRTADDR -- once per byte ) OVER C@ OVER I + C! SWAP 1+ SWAP LOOP WRAP @ + ( adjust addr to next line ) LOOP DROP DROP *CLIPSTORE @ FREEMEM 0 *CLIPSTORE ! ELSE DROP THEN end Method end class Definition // the class is defined now make some instances of it. instance Region Bar1 instance Region Drop1 10 10 7F 78 LCD-BMS setup Drop1 // set it up. instance Region Helpline 0 75 EF 7F LCD-BMS setup Helpline
Each instance of the class gets its own data area. The words X1 etc know how to make the address of the variable based on the address on the object stack plus a literal offset.
The words "BYTES" etc are ordinary variables - there is only one copy of these but they are hidden from the rest of the system by discarding the headers.