TkScript |
|
reference guide | Classes |
Table of Contents:
-
1. Introduction
- About script classes
-
2. Declaration
- About class declarations and namespaces
-
3. Polymorphy
- About base classes and multiple inheritance
-
4. Members
- About class members / fields and permissions
-
5. Methods
- About methods, constructors, vtables and multi-thread synchronization
-
6. Constants
- How to define constants in the scope of a class
-
7. Exceptions
- How to define exceptions in the scope of a class
-
8. Delegates
- Callback methods in the scope of a class
1. Introduction
A class is a structure which is composed of the scalars
int
,
float
, and
Object
, i.e. instances of C++ resp. user defined script classes.
Classes are used to extend applications by new datatypes.
The elements of a class are called members, fields or properties. Also see
Members
.
Similar to basic datatypes like e.g.
int
or
float
, classes first need to be instantiated before their members and methods can actually be accessed.
Class instances are called objects (see
Object
,
TkScript reference guide / Objects
).
A
method
is basically a function that can operate on the instance of a script class. Methods can
see the script class members / fields and read/modify them. Also see
Methods
.
Class functions (
static methods) and constants can be used without a class instance (
Object
). The class is just used as a
scope in this case.
Further script class features include
Please notice that this section will mainly focus on
script classes. For more information about objects in general (and C++ classes), see
TkScript reference guide / Objects
.
2. Declaration
The
class
statement is used to declare a script class (see
The class statement
).
A class declaration is placed in the currently active namespace. The class name must be
unique in the active namespace.
Example:
// Declare class in default namespace
class C {
exec() { print "C::exec()"; }
}
namespace test; // create/select namespace "test"
// Declare class in namespace "test"
class C {
exec() { print "test::C::exec()"; }
}
namespace default; // select default namespace again
C c1;
c1.exec(); // => call "exec" method of class "C" in default namespace
test::C c2;
c2.exec(); // => call "exec" method of class "C" in "test" namespace
3. Polymorphy
A class may have up to 32 base classes from which it inherits the respective base class' methods, members, constants and exceptions.
A reference to a class can be cast down to any of its base class types.
The
Object
class is an implicit base class of every script class.
Example:
class A {
String
a_member;
}
class B {
float b_member;
}
class C extends A, B { // "C" has base classes "A" and "B"
int c_member;
public method printMembers() {
trace "a_member = " + a_member;
trace "b_member = " + b_member;
trace "c_member = " + c_member;
}
}
C c;
c.a_member = "hello, world.";
c.b_member = PI;
c.c_member = 42;
c.printMembers();
3.1. Base class order
The
base class order in a class declaration affects the class'
vtable (see
Method vtables
) setup:
class A {
test() { print "A::test()"; }
}
class B {
test() { print "B::test()"; }
}
class C extends A, B {
}
class D extends B, A {
}
C c;
c.test(); // => call "A::test()"
D d;
d.test(); // => call "B::test()"
3.2. Run time type checks
The class type of an
Object
reference can be tested at runtime by either using the
instanceof
expression or the
yacInstanceOf
() and
yacMetaClassInstanceOf
()
Object
class methods.
Example:
Boolean
b;
b = File
instanceof Stream
;
print b; // => "true"
File
f;
b = f.yacInstanceOf(Stream
);
print b; // => "true"
Also see
The instanceof expression
.
3.2.1. Automatic type checks
Run time type checks are also performed when invoking class methods or when reading/writing class members.
If a check fails, a
ClassTypeMismatch
or
InvalidPointer
exception is thrown by the runtime:
class A { int i=23; test() { } }
class B { int i=42; test() { } }
A a <= new B(); // invalid assignment but still possible
// Call method
try {
a.test(); // raises exception since "a" actually has type "B"
}
catch(ClassTypeMismatch e) {
trace "caught ClassTypeMismatch exception (OK).";
}
// Access member
try {
trace a.i; // raises exception since "a" actually has type "B"
}
catch(ClassTypeMismatch e) {
trace "caught ClassTypeMismatch exception (OK).";
}
4. Members
A
member is a named value defined in the scope of a class -
instance.
Each time a class is instantiated, its non-static members are instantiated, too.
Members are visible in class methods; they can also be accessed from outside script code using the
objname.member
syntax.
Member declarations may also contain initializers (e.g.
int i=42;
or
int ia2[16];
)
Example:
class MyClass {
int i;
float f;
String
s;
int ia[]; // same as IntArray ia; . initial size=0.
int ia2[16]; // initial size=16
FloatArray
fa; // same as float fa[]; . initial size=0.
float fa2[32]; // initial size=32
HashTable
ht; // initial size=57
}
MyClass obj; // instantiate class "MyClass"
4.1. Static members
A
static member is basically a variable (or named value) defined in the scope of a class.
The main difference to a regular member is that it is
not instantiated per
instance but rather per
class, i.e. all instances of a class "see" the same data.
Static member have to be prefixed with the
static
keyword.
Example:
class Configuration
{
static int debug_level;
}
Configuration.debug_level = 10;
4.2. Initializers
Class members can be initialized during the instantiation of a class.
The
=
assignment operator is used to copy the result value of an initializer expression.
Initializers are called in the order in which they have been declared.
Initializers may refer to the result of previous initializers
Initializer expressions do not need to be constant, you may also call functions, for example.
The
<=
assignment operator is used to "grab" a reference to the (
Object
) result of an initializer expression.
Example:
class C {
int i = 42; // initialize with integer "42"
float f = PI; // initialize with float "3.1415"
String
s = "i="+i+" f="+f; // s => "i=42 f=3.1415"
IntArray
ia <= [1, 2, 3];
}
C c;
4.3. Object members
Object class member instances are usually created automatically when a new instance of the respective class is created or, if the class member is static, during startup.
However, there is one exception to this rule: When the class type of an object member is equal or derived from the current class, a
null
reference will be created, instead. This is done to avoid infinite recursion.
Example:
class MyListNode {
MyListNode next; // not instantiated to avoid recursion
Value
data;
}
MyListNode ln;
trace #(ln.next); // => null
4.4. Permissions
The scope of a class member can be limited
- not at all ("public")
- to the current module ("module")
- to the current class ("private")
- to the current class and all derived classes ("protected")
This
information hiding mechanism is especially useful when creating libraries: Certain members (and methods..) can be
hidden from the application that uses the class and thus force the application to use the designated (method) interface of the respective class.
If no specific permission attribute is used, a script class member (or method) uses the "public" scope.
Example:
class MyListNode {
protected MyListNode next; // not visible outside "MyListNode"
protected Value
data;
public method initValue(local var v) {
data = deref v;
}
public method appendValue(local var v) : MyListNode {
explain "Append a new listnode holding the given value and return the new node.";
local MyListNode ln;
ln.initValue(deref v); // Copy value to "data" member
ln.next <= deref next; // Old "next" becomes "next" of new node
next <= deref ln; // Insert new listnode directly after this node
return ln; // Return reference to new node
}
public method getString() : String
{
MyListNode ln <= this;
local String
r = "{";
int i = 0;
while(ln != null)
{
if(i++ > 0)
{
r.append(", ");
}
r.append(ln.data.string);
ln <= ln.next;
}
return deref r; // Return string representation of this list(-node)
}
}
MyListNode ln, t <= null;
ln.initValue(42); // Init "head" node
t <= ln.appendValue(PI); // Append new node
t.appendValue("hello, world."); // Append another node to new node
trace ln.getString(); // Print string representation of list to stdout
The previous example can also be implemented using the native
List
and
ListNode
classes:
List
l;
l.addLast(#(42)); // Init "head" node
l.addLast(#(PI)); // Append new node
l.addLast(#("hello, world.")); // Append another node to new node
trace l.string; // Print string representation of list to stdout
Respectively using the listnode expression (see
The {} list expression
) :
trace {42, PI, "hello, world."};
4.5. Tags
A script class member must be tagged using the
tag
keyword in order to allow it to be serialized when a class is written to a
Stream
.
Example:
class Person {
tag String
forename; // can be serialized
tag String
surname; // can be serialized
String
hash; // will never be serialized
}
Person p;
p.forename = "Peter";
p.surname = "Parker";
Stream
o <= StdOutStream
;
o << p; // Serialize class instance into stream
Also see
The stream operator
.
4.6. How to explicitely address a class member
Class members can be addressed by prepending the member name with the
this
keyword.
This is mostly useful when a variable or function parameter
shadows the script class member in a class method.
Example:
class C {
String
s;
init(String
s) {
this.s = s;
}
}
C c;
c.init("hello, world.");
However, I recommend to simply prefix the argument names with the
_
character to avoid this situation:
class C {
String
s;
init(String
_s) {
s = _s;
}
}
C c;
c.init("hello, world.");
A static class member can be addressed by prepending the member name with its parent class name:
class C {
static String
s;
}
C.s = "hello, world.";
trace C.s;
5. Methods
A
method is basically a function defined in the scope of a class.
A method that is prefixed by the
static
keyword is accessible without a class instance.
Methods can also be decorated with the
public
,
module
,
protected
and
private
attributes to limit the scope of the respective method.
Methods can be synchronized per-instance, per-method, per global mutex or using manually written synchronization code. See
Synchronization
.
For more details about the function / method syntax, see
TkScript reference guide / Functions
.
5.1. Method vtables
TkScript creates a so-called
vtable for each non-static class method.
The virtual table (vtable) is used to call methods dependent on the actual object class type. This mechanism is also called
late-binding.
Example:
class MyLog {
puts(String
s) { } // no-op interface declaration
}
class MyStringLog : MyLog {
protected String
log;
puts(String
s) {
log.append(s);
}
}
class MyStdOutLog : MyLog {
puts(String
s) {
print s;
}
}
MyStringLog stringLog;
MyStdOutLog stdoutLog;
MyLog log <= stringLog;
log.puts("hello, world."); // => call MyStringLog::puts method
log <= stdoutLog;
log.puts("hello, world."); // => call MyStdOutLog::puts method
5.1.1. How to explicitely address methods of base classes
Base class methods can be addressed by prepending the method name with the respective base-class method name.
This is especially useful to call a
super class method:
class MyBaseClass {
public method init() {
print "MyBaseClass::init()";
}
}
class MyDerivedClass : MyBaseClass {
public method init() {
MyBaseClass::init(); // Call super class method
print "MyDerivedClass::init()";
}
}
MyDerivedClass c;
c.init();
5.2. Forward declarations
Forward declarations of class methods can be used to separate the declaration from the actual implementation.
The permission attributes, return type declaration, argument list or constraints are
not repeated in the implementation.
Example:
class MyVector {
public method init(float x,y,z); // Forward declaration of method "init"
}
MyVector::init { // Implementation of method "init"
trace "("+x+"; "+y+"; "+z+")";
}
MyVector v;
v.init(1,2,3);
5.3. Constructors
TkScript supports constructors and destructors
BUT in the current runtime it cannot be guaranteed that these will actually be called.
The reason for this is that the c'tors and d'tors need the correct
script context to run. This context is currently not available everywhere a new object can be created.
Example:
class MyClass {
MyClass() {
trace "construct MyClass";
}
~MyClass() {
trace "destruct MyClass";
}
}
MyClass c;
Please do not rely on constructors or deconstructors and use simple
init
/
free
methods instead:
class MyClass {
init() {
trace "construct MyClass";
}
free() {
trace "free MyClass";
}
static New() : MyClass {
local MyClass c;
c.init();
return deref c;
}
}
MyClass c <= MyClass.New();
c.free();
6. Constants
Constants can be declared in the scope of a script class.
In contrary to
module constants, class constants may use complex initializer expressions.
Example:
class C {
define int INT_CONST = 2 * 21;
define float FLOAT_CONST = PI;
define String
STRING_CONST = "hello, world.";
}
trace " INT_CONST=" + C.INT_CONST;
trace " FLOAT_CONST=" + C.FLOAT_CONST;
trace "STRING_CONST=" + C.STRING_CONST;
Also see
User defined constants
.
7. Exceptions
Exceptions can be declared in the scope of a script class.
A
class exception is inherited by all derived classes.
Class exceptions can be derived from
global exceptions.
A specific base class exception type can be selected by prepending the exception name with the respective class and/or namespace name (separated by
::
).
A global exception type can be addressed by prepending the exception name with the
::
keyword.
For more about exceptions, see
TkScript reference guide / Exceptions
,
The exception statement
.
Example:
define exception MyGlobalException; // declare global exception
class MyBaseClass {
define exception MyBaseException; // declare class exception
}
class MyClass extends MyBaseClass {
define exception MyException;
define exception MyDerivedException extends MyBaseException;
define exception MyDerivedException2 : MyBaseClass::MyBaseException;
define exception MyDerivedException3 : ::MyGlobalException;
static ThrowIt() {
throw MyDerivedException2 "something went terribly wrong";
}
}
try {
MyClass.ThrowIt();
}
catch(MyBaseClass::MyBaseException e) {
trace "caught " + e.fullName + " e.message=\"" + e.message + "\".";
}
8. Delegates
A
delegate is basically a method that can run in the scope/context of a class instance although it has not actually been declared within the respective class.
This means that the delegate method can access all members and methods of a class, just like it were a regular class method:
class C {
define String
CMD = "my_cmd_callback";
int i = 42;
public method exec() {
// Delegate call to the method that is currently bound to "my_cmd_callback"
delegate (CMD) ();
}
}
delegate C:print_i() {
// Delegate method is allowed to access class member "i"
print "i="+i;
}
C c; // Instantiate class
// Assign delegate method "print_i" to delegate slot "my_cmd_callback"
delegate c : C.CMD = print_i;
c.exec();
This mechanism is mainly useful to implement callbacks and object-specific behaviours without deriving a new class type.
Example:
class AbstractAction {
getActionName() { return "click"; }
}
class Button {
consumeAction(AbstractAction _ac) returns boolean {
// Delegate method call
return delegate "xfm__"+_ac.getActionName()(_ac);
}
callPrintTextHandler() {
// Delegate method call to callback "print"
delegate "print"();
}
}
Button xfm1;
// ---- Declare method that can be plugged into script object
delegate Button:Print_HelloWorld() {
print "hello, world.";
}
delegate Button:xfm1__click(AbstractAction _ac) {
print "delegate \"xfm1__click\" called.";
return true; // consumed..
}
delegate xfm1 : "xfm__click" = xfm1__click; // add method delegate
delegate xfm1 : "print" = Print_HelloWorld; // add method delegate
xfm1.callPrintTextHandler();
xfm1.consumeAction(new AbstractAction);
Also see
The delegate statement
.
auto-generated by "DOG", the TkScript document generator. Wed, 31/Dec/2008 15:53:35