Object scripting Object scripting overview |
Introduction |
The KVIrc scripting language is not object oriented in nature. Anyway, objects are a highlevel abstraction that allow to write complex code in a clean way. So I've added at least some pseudo-object support. |
Basic concepts |
Objects are arranged in tree structures.
Each object that you create is either toplevel object or a child
of another object. A toplevel object is a parentless one.
Obviously all objects can have child objects.
When an object is destroyed, all its child objects are also destroyed. The toplevel objects are automatically destroyed when KVIrc quits. The objects are global to the entire application (this is different from previous KVIrc releases where the objects were local to the current frame window and arranged in a single tree with a builtin root object). Each object is an instance of a class that defines its features. Each object has also a name, that is not necessary unique and is assigned to by the programmer; the name is just a mnemonic expedient, and you may also not need it. Each object is identified by an OPAQUE UNIQUE ID. The ID is assigned by KVIrc and can be held in any variable. You can think the object ID as a handle for the object or the object's pointer. Any action performed on the object will require its ID. |
Creation and destruction |
To create an object you must use the $new()
function. $new() requires three parameters: - The object class (more about object classes later in this document) - The ID of the parent object (this can be 0 for toplevel objects). - The object name (eventually empty) |
%myobject = $new(object,0,theName) |
$new() returns the ID of the newly created object, or the STRING 0 if the object creation fails (it is a string because the object ID's are generally strings, and 0 is an "invalid object ID"). In well written scripts it is not common that the object creation fails, anyway you can check if the creation has failed in the following way: |
if(%myobject) echo "Object created!" else echo "Object creation failed!" |
You can also test the object ID's for equality: |
if(%myobject == %anotherobject)[cmd] echo[/cmd] "This is the same object!"; |
The parent object ID is optional, if not specified it is assumed to be 0.
The object name is optional, but it may help you later in finding the object.
To destroy an object use the delete command. (In previous versions this command was named destroy and delete is currently aliased to that name too). |
delete %myobject |
If the destroyed object has child objects, these are destroyed too. |
Fields: objects as pseudo-structures |
All the objects can contain variable fields. You can set an object's field by using the object scope operator "->": |
%myobject->%fieldVariable = dataString |
To unset a field set it with empty data string (just like with a normal variable). To retrieve the field data use the object scope operator in the same way: |
echo %myobject->%fieldVariable |
The '->' operator has been stolen from the C language.
In the KVIrc scripting language it switches from the global namespace
to the object's one. So in the above example %fieldVariable is owned by the object. The first character of the variable name has no special meaning in the object namespace (in the global namespace the variables starting with an uppercase letter are global to the application, the other ones are local to the command sequence). The variable names are completely case insensitive. Any operator can be used with the object field variables: |
%myobject->%fieldVariable = 0 %myobject->%fieldVarialbe ++ if0(%myobject->%fieldVariable != 1) echo KVIrc is drunk, maybe a reboot will help? |
You can simulate C structures on the fly by using objects and fields: |
# Create a user description on the fly %myobj = $new(object,0,userDescription) # Set the fields %myobj->%nickname = Pragma %myobj->%username = daemon %myobj->%hostname = pippo.pragma.org %myobj->%info = Pragma goes always sleep too late %myobj->%info << and wakes up too late too! # Call an (user defined) alias that stores the data to a file storetofile %myobj # Destroy the object delete %myobj |
The field variables can be also dictionaries: |
%theobj->%field[key] = something |
Unlike in C, there is no need to declare object fields. If you have ever used other high level object-oriented languages, you may be used to declaring different types of variables: instance variables, which per definition define an object's state (at least partly) and local variables, which can be used in any function and will be only valid in the very current scope. This does and does not apply to KVI++. Local variables can be used as normal and the scope of those variables will (naturally) be limited to the scope of the function they are defined in. |
class(test,object) { test() { %test = "will this persist?" } anotherfunc() { echo "var: %test" } } %myObject = $new(test,0) %myObject->$test() # Behold! This will only print "var: "! %myObject->$anotherfunc() |
Instance variables, however, which are managed in the object's field can be accessed at any time by anyone.
Warning: every script or object is potentially able to change the values of your field variables!
They may also add or unset (empty) previously not used or used fields. As earlier said, there is no need to declare object fields, as KVIrc will keep track of them. Even more precisely said, you can not declare them in the class file itself (some later example will tell you otherwise, just keep in mind to ignore the pseudo code, as it does not reflect how KVI++ is really working in respect of fields.) However, there is one way to declare and define object fields: using the constructor (please see below, if you are interesting in learning about this function), it is possible to declare (really only for the human being reading the code) and more important initialize object fields. For more information, see the Constructor section below. Any object can have any field variable; an unset field is equivalent to an empty field. Note: The KVIrc scripting language is not typed. Any object class (be patient... I'll explain classes in a while) identifier can be stored in any KVIrc variable: it is not possible to find out the object features by examining its identifier. This may make the usage of objects a bit unclear; However, with some experience you will be able to use the objects in a very powerful way. The type-safety can be also simulated by a careful usage of object names; in the above example, the %myobj object was created with the userDescription name. The storetofile alias could check the passed object's name and refuse to work if that does not match "userDescription". A more complex use of fields will be described later in this document. |
Member functions |
Just like in C++, the objects have member functions. For example, the object class (again... read on) objects export the $name() and $classname() functions. |
%tmp = $new(object,0,myobject) echo The object's name is %tmp->$name(), the class name is %tmp->$classname() # Destroy the object delete %tmp |
Another cool function exported by the object class is the $children() function. It returns a comma separated list of child identifiers. |
%tmp = $new(object,0,myobject) %tmpchild = $new(object,%tmp,child1) %tmpchild = $new(object,%tmp,child2) %tmpchild = $new(object,%tmp,child3) echo The object's child list is: %tmp->$children() # Destroy the object and the child delete %tmp |
There are two special functions for each objects: the constructor and the destructor.
You will find more information on constructors and destructors later in this document,
for now it's enough that you know that these functions are called automatically by KVIrc:
the constructor is called when the object is created and the destructor is called when the
object is being destroyed with delete. The object functions can be reimplemented on-the-fly by using the privateimpl command: you can simply modify the behaviour of the function by writing your own function body. (This is an uncommon feature: unlike many other languages, you can reimplement object functions at run-time, when the object has been already created.) A more complex example |
%tmp = $new(object,0,myobject) foreach(%i,1,2,3) { %tmpchild = $new(object,%tmp,child%i) privateimpl(%tmpchild,destructor) { echo Object $this ($this->$name()) destroyed; } } privateimpl(%tmp,destructor) { %count = 0; foreach(%t,$this->$children()) { echo Children : %t->$name() with class %t->$class() %count++ } echo Just before destroying my %count child items. } # Destroy the object and it's child items delete %tmp |
In the example above four objects have been created.
A parent object named myobject, and three child objects.
The destructor has been reimplemented for each child object,
to make it say its name (Please note the usage of $this).
In the parent destructor the child objects have been counted and listed. Then the parent object is destroyed causing to: - trigger the parent destructor. - destroy all the child items (and consequently trigger all the individual destructors). Not all the object functions must return a value: If a function does not return a meaningful value, or you just want to ignore it, you can call it in the following way: |
%anyobject->$functionname() |
Classes |
As said before, all objects are instances of a specific class.
This concept is common to almost all object oriented languages.
A class is a collection of methods that define an object's behaviour.
It's not easy to explain it, so I'll try with an example: Please note, that this is pseudo code. KVI++ does by no mean employs a field directive as shown below! |
class HostAddress { field hostname function ipnumber() function isLocalhost() } |
The above class is a representation of a host address.
You create an instance of this class and set the hostname field, for example,
to www.kernel.org.
The object is now able to give you information about the hostname in a transparent way:
You can call the ipnumber() function, and the object will return you the
digits and dots representation of www.kernel.org.
The isLocalhost() function will return true if the hostname refers to the local machine
The object internal job is hidden from the user, but probably it will be a huge job.
To obtain the IP number from the hostname, the object will probably have to perform a DNS call (usually a complex task).
To check if the hostname references the local machine, the object will have to obtain the local hostname
from the system (in some unspecified way) and then compare it with the given hostname field.
The internal job of the object is defined by the "implementation of the class". Obviously, the programmer that creates the class has to write that implementation. |
class HostAddress { field hostname function ipnumber() { find the nearest DNS server make the DNS call wait for the response decode the response } function isLocalhost() { query the kernel for the local hostname compare the obtained hostname with the hostname field } } |
In the above example I have implemented the two functions in pseudo code.
Let's go back to the real world. KVirc contains a set of built-in ready-to-use classes. The basic class is object: all the other classes are derived from this (more about object inheritance later in this doc). Another available class is socket that is an interface to the real system sockets. An instance of the socket class can connect and communicate with other hosts on the net. The class definitions are GLOBAL to the entire application: all server windows share them. So now we can say that in KVIrc a CLASS is a collection of features that define the behaviour of an object. The user interface to the class are the member functions and the events. |
Inheritance |
Someone asked for derived classes? Here we go: The class command allows you to define new object classes. In KVI++, A new class must be always derived from some other class: the lowest possible level of inheritance is 1: deriving from class object. |
class(helloworld,object) { sayhello() { echo Hello world! } } |
The above class is named helloworld. It inherits the object class. This means that it acquires all the object functions: $name(), $class(), $children()... Additionally, it has the $sayhello() function, that echoes Hello world to the console. Now you can create an instance of this class: |
%instance = $new(helloworld) %instance->$sayhello() |
You should see Hello world printed in the console. Easy job... let's make the things a bit more complex now: derive another class from helloworld and make it say hello in two different languages: |
class(localizedhelloworld,helloworld) { # define the setlanguage function # note that <$0 = language> is just a programmer reminder setlanguage(<$0 = language>) { if(($0 == english) || ($0 == italian)) { $$->%lang = $0 return 1 } else { echo I don't know that language ($0) echo defaulting to English $$->%lang = english return 0 } } sayhello() { if($$->%lang == italian) echo Ciao mondo! else $$->$helloworld:sayhello() } } |
Now you can call: |
%m = $new(localizedhelloworld) %m->$setLanguage(italian) %m->$sayhello() %m->$setLanguage(english) %m->$sayhello() %m->$setLanguage(turkish) %m->$sayhello() delete %myobj |
The class defined above is inherited from the previously defined helloworld class: so it inherits the object class functions and events and the sayhello function from helloworld. In addition a setlanguage function is defined that stores in a variable the language name passed as a parameter (after checking its validity). ($0 evaluates to the first parameter passed) If the language is unknown the setlanguage function will return 0 (false). Now we want to be able to say hello world in Italian and English. So we override the inherited sayhello function. To override means to reimplement: if you call %object->$sayhello() and %object contains the ID of an instance of class localizedhelloworld, the new implementation of that function will be called (executed). The inherited sayhello was able to say hello world only in English, so we can still use it in the new implementation without rewriting its contents. So if the language set is not Italian we assume that it is English and call the base class implementation. |
$this->$helloworld:sayhello() # equivalent to $$->$helloworld:sayhello(), # to $this->$helloworld::sayhello(), # and to $$->$helloworld::sayhello() |
otherwise the language is Italian and we say hello in Italian :).
So, to call a base class implementation of a function we prepend the base class name before the function name in the call.
The base class name could be also object in this case, but the object class has no sayhello function defined
so it would result in an error. In the above example, all the values of $this->%language that are not equal to Italian are assumed to be English. This is not always true, for example, just after the object creation the %language variable field is effectively empty. The above class works correctly in this case, but we might want to have always a coherent state of the field variables, so we need another concept: the class constructor that will be discussed in the next paragraph. Note: multiple inheritance (inheritance from more than one base class) is not implemented, KVIrc is not a compiler. :) Objects are much more powerful... Do a clearobjects to cleanup the old class definitions and read on. |
Constructors and destructors |
The class constructor is a function that is called automatically just after the object
has been created internally by KVIrc and just before the $new
function returns. It should be used to setup the internal object state. The constructor can and should list and initialize all the necessary object fields. |
class(myObject,object) { constructor() { $this->%test = "This is a sample object field." } } %myObject = $new(myObject,object) echo %myObject->%test |
Will thus print "This is a sample object field." Unlike in C++, in KVIrc, the constructor CAN return a value: If it returns 0 it signals the object creation failure: the object is immediately destroyed and $new() returns 0 to the caller. Any other return value is treated as success, so the object is effectively created and $new() returns its ID to the caller. This said, KVI++ will automatically return a value of 1 and you should never return a value other than 0 if something bad happened (like a mandatory parameter was not given in the $new() call or the like.) KVIrc will also issue a warning message and remind you of this when a non-zero value is returned. All the builtin classes have a constructor defined that will almost never fail (only if we run out of memory), so you can avoid to check the $new() return value when creating the instances of the built-in classes. In derived classes you can override the constructor to setup your object's state. You should always call the base class constructor in your overridden one, to setup the base class state, and propagate its return value (eventually modified if the base class constructor is successful but your derived class initialization fails). This very basic example will illustrate how to do this (please read the paragraph about inheriting classes above first): |
class(baseObject,object) { constructor() { echo "baseObject or derived object created." } } class(derivedObject,baseObject) { constructor() { echo "derivedObject object created." $this->$baseObject::constructor() } } |
In practice, the builtin class constructors do nothing other than setting the return
value to 1 so you can even avoid to call them, but in any other case you must do it. This is different from C (for example), where the constructors are called (more or less) automatically. |
Signals and slots |
The signals and slots are a powerful mean of inter-object communication.
A signal is emitted by an object to notify a change in its state.
For example, the button class emits the
clicked signal when the user clicks the button. A signal is emitted by an object and can be received and handled by any other existing object (including the object that emits the signal). The handler function for a signal is called "slot". It is just a convention: in fact, a slot is a normal object function (and any object function can be a slot). More than one slot can be connected to a single signal, and more signals can be connected to a single slot. In this way, many objects can be notified of a change in a single object, as well as a single object can easily handle state-changes for many objects. The signal/slot behaviour could be easily implemented by a careful usage of object functions. |
So why signals and slots? |
Because signals are much more powerful in many situations.
The signals have no equivalent in C/C++... but they have been implemented in many high-level
C/C++ libraries and development kits (including the system-wide signal/handler mechanism implemented
by all the modern kernels and used in inter-process communication). |