Code archives/Algorithms/BlitzClass

This code has been declared by its author to be Public Domain code.

Download source code

BlitzClass by Yasha2010
Another utterly pointless example.... hehe.

At the end of the day there is no easy way to use polymorphism in a purely procedural language without function pointers, but depending on how masochistic you are, you can come pretty close (A C preprocessor with macros would be a big bonus though). This system is a simplified version of the one I used (as a compiler target) in Objective-B3D. It's fairly readable, but still requires far too much typing to really be useful (and also isn't type-checked). I post it in the hope that it'll help procedural-BB users in the understanding of polymorphism, more than anything else.

Usage:

There are two include files. you'll need to Include "BlitzClass.bb" at the top of your project (the first codebox). BlitzClass.bb itself includes BlitzClass_User.bb (the second codebox). The files are separate because, as you might guess from the name, thie functions in BlitzClass_User.bb are intended to be edited by the end user; those in BlitzClass.bb aren't.

Classes need to be declared above code where they're used, as they're created at runtime and thus the code needs to run through them before they exist. Start a new class with the NewClass() function (I would assign this to a global, unless you specifically want it in local scope). Add data fields to it with the AddField function. Specifying a type is only really important for string fields.

Methods are a little more complex. Define the function normally, with the first parameter (the "self" parameter) of type BCObject. I would recommend positioning and naming the function with the rest of the class. Then (or if you prefer the style, right before it) call AddMethod as shown in the examples. This needs to be done in sequence so that the right method ID will be generated. Alternatively if you want to reuse a method in another class, you can supply the optional ID parameter yourself. Finally, you need to add a call to the function to BlitzClass_CallMethod__() in BlitzClass_User.bb, as shown in the example. If the function returns a non-integer, wrap it in a cast-to-int function (explained below); if any of the parameters are non-integers, they need to be unwrapped with cast-from-int functions.
Any method added with the name "new" will automatically be called when the class is instantiated.

The next three functions in BlitzClass_User.bb only need to have their number of arguments changed to suit the maximum number of arguments accepted by your constructors and methods. By default it's four and eight respectively. The other function is an error handler; since type errors will be noticed at runtime rather than compile-time with such a system, how to handle them is best left up to the user.

You can extend a base class with the Extend() function. Extend() applies an old class to a new class; you may as well just call NewClass() for the first parameter. Each class can only have one super.

To instantiate a class, call new_() with the class variable and any constructor arguments required. In order to make the main functions generic, new_() and msg_() accept only integer arguments (and msg_() returns only integers too). To pass a B3D type object, use Handle(); to pass floats and strings, use bf_() and bs_() respectively to "box" the value into an integer. Non-integer parameters need to be "unboxed" with the appropriate function in BlitzClass_CallMethod__() (uf_(), us_(), or Object.myType() ).

Once your object is created, send messages to it with msg_(). The first argument is the BCObject, and the second is a string containing the name of the method to call. The remianing arguments are passed as integers, same as to new_(), and the same rules apply. Additionally, if msg_() needs to return a non-integer, it'll need to be "unboxed" with uf_(), us_() or Object.myType().

get_() and set_() work in the same way as msg_(), but the string parameter is a data field rather than a method. These are implemented in BlitzClass.bb.

When you're done with your object, remember to delete it with delete_() (not the regular Delete command), as there isn't any reference counting.

The boxing of values into integers is only one way to do this; a more restricted system might go for methods that only have a certain type signature; or all the arguments could be boxed into a single value by an argument-compound-constructor function; or one could try a more flexible value type like strings (that are much slower). Or you could be sensible and use a different language, but that's never the fun way....


BlitzClass.bb:


BlitzClass_User.bb:
;BlitzClass example

Include "BlitzClass.bb"		;Needs to appear before first use of the classes


;Because classes are defined at runtime they also need to be defined before first use


;Let's copy Wikipedia's polymorphism example


; Class Animal:

Global Animal.BlitzClass = NewClass("Animal")	;You don't actually have to name them but it helps error messages

AddField Animal, "name", BC_STRING		;Strings absolutely need type declarations - other types don't really

Function Animal_New(self.BCObject, name$)
	set_ self, "name", bs_(name)
End Function											; Naming a method "new" will set it as a constructor, and it will be called
Global Animal_New_Ptr = AddMethod(Animal, "New")		; automatically at instantiation (after the super's constructor)

Function Animal_PrintName(self.BCObject)		;Name the actual functions as you like, so long as it's unique (obviously)
	Print "Name: " + us_(get_(self, "name"))
End Function
Global Animal_PrintName_Ptr = AddMethod(Animal, "PrintName")	; It is ++ABSOLUTELY ESSENTIAL++ that the function declarations
												; and method declarations are in the same order! Best to keep them
												; side by side like this.


; Class Cat extends Animal

Global Cat.BlitzClass = Extend(NewClass("Cat"), Animal)

Function Cat_Talk(self.BCObject)
	Print "Meowww!"
End Function
Global Cat_Talk_Ptr = AddMethod(Cat, "Talk")


; Class Dog extends Animal

Global Dog.BlitzClass = Extend(NewClass("Dog"), Animal)

Function Dog_Talk(self.BCObject)
	Print "Arf! Arf!"
End Function
Global Dog_Talk_Ptr = AddMethod(Dog, "Talk")


; Let's test it

Local myAnimal.BCObject[3], i

myAnimal[1] = new_(Cat, bs_("Missie"))
myAnimal[2] = new_(Cat, bs_("Mr. Mistoffelees"))
myAnimal[3] = new_(Dog, bs_("Lassie"))


For i = 1 To 3
	If BC_CheckType(myAnimal[i], Animal)	;Manually check type (totally unnecessary, shouldn't bother but this proves it works)
		
		msg_(myAnimal[i], "PrintName")
		msg_(myAnimal[i], "talk")
		
	EndIf
Next

Print "":Print "Press a key to end..."
WaitKey
End

Comments

_PJ_2010
Amazing!


Code Archives Forum