Loading...
 
Skip to main content
OO forth example.

Object example

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.


Created by eddie. Last Modification: Monday 17 of March, 2008 10:35:15 AEDT by eddie.

Main Index

Switch Theme

Switch Theme

Shoutbox

eddie, 18:52 AEST, Wed 11 of Sep, 2024: Most pages should be working again.
System Administrator, 08:03 AEST, Sat 10 of Aug, 2024: Lots of images are still broken. I'm working on it. Maybe 1/2 way through.
admin2, 14:05 AEST, Mon 05 of Aug, 2024: running tiki 27
admin2, 16:01 AEST, Sun 09 of Jun, 2024: Wiki running tikiwiki version 27alpha on a raspberry pi-3. About 1/2 the images are missing and most thumbnails not working. Slow manual rebuild. About %20 done.
eddie, 20:23 AEST, Sun 19 of May, 2024: Images moved from wiki_up to file galleries and wiki pages fixed.

Last-Visited Pages

Online Users

194 online users