TkScript |
|
reference guide | Functions |
1. Functions
Functions are used to group and parametrize frequently used statement sequences.
The basic idea of a function (in TkScript) is that
n values go in and
one value comes out (even if it is
void
):
function Add(var a, var b) {
return a + b;
}
trace Add(42, 23);
trace Add(1.1, 2.2);
Functions support modularization of programs and help to split a problem into
sub problems (
Divide and Conquer):
function LI(String
r) {
r.append("<li>");
}
function A(String
href, String
caption, String
r) compile {
r.append("<a href=\"");
r.append(href);
r.append("\">");
r.append(caption);
r.append("</a>");
}
// Add some random links to "document"
String
doc = "<html><body>\n";
doc.append("<ul>");
LI(doc); A("http://www.tkscript.de", "The TkScript home page", doc);
LI(doc); A("http://www.contactor.se/~dast/frexxed/", "The FrexxEd home page", doc);
LI(doc); A("http://scene.org", "Demo or Die :-)", doc);
doc.append("</ul>");
doc.append("\n</body></html>\n");
doc.saveLocal("test.html");
TkScript has two different parser codepaths for module functions and class functions:
- It is recommended to use class functions since
- they support more features
- constraints
- strongly typed return values
- argument declaration lists (i.e. the arg type may be omitted if subsequent arguments share the same type)
- the order of declaration does not matter
- class function declarations are already handled in the first (scanning) parser pass.
1.1. Function signatures
The function signature is simply its name.
This means that it is not allowed to declare two functions (or methods) that only differ by argument list or return type within the same scope.
In other words: TkScript does not support Overloading.
2. Native functions
The
core
API and additional plugins (e.g.
tkopengl
) export a number of
native code functions that can be used like regular script functions.
There is one important difference between script and native functions:
Object
references are always passed
without the ownership flag in calls to native functions and methods.
Also see
Value wrapper objects
,
The #() value expression
.
The number of arguments to a native code function is currently limited to 16.
Please see
→yac for more information about the native code interface.
The following example calls the native code function
milliSeconds
to calculate the execution time for a (
rather pointless) loop:
int t = milliSeconds();
int i = 0; loop(10000000) i++;
t = (milliSeconds() - t);
print "ms=" + t;
3. Module functions
A module function is a function that is declared within the scope of a single source module.
Example:
String
my_string;
// Declare and implement function
function MyFunction(int i, float f, String
s) {
print "i=" + i + " f=" + f + " s=\"" + s + "\" my_string =\"" + my_string + "\".";
}
// Call function
MyFunction(42, PI, "hello, world.");
In general, a module function is only visible within the module it has been declared in.
There are ways to address functions in other modules or use
Function
pointers to pass the function object to other modules
but this should be avoided, if possible. Please use class functions and methods, instead.
Addressing a function in another script module:
MMyModule.MyFunction(42, PI, "hello, world.");
Module functions are detected not until the second parser pass which means that an
Unresolved
node will be created when a function is used before it has been declared. These nodes will be resolved after parsing and before the script is executed.
Also see
Function/method calls
.
4. Static methods
A special case of a function is a
static method which basically is a function attached to a class.
A static method can access all static members of the respective class (and its
base classes).
Example:
class MyClass {
static String
my_string = "hello, world.";
static public MyStaticMethod() {
print my_string;
}
static AnotherStaticMethod() {
print my_string;
}
function YetAnotherStaticMethod() {
print my_string;
}
}
MyClass.MyStaticMethod();
MyClass.AnotherStaticMethod();
MyClass.YetAnotherStaticMethod();
Also see
TkScript reference guide / Classes
.
4.1. Scope
The scope of a static method can be limited to the current module (
module
), the current class (
private
), the current class and all derived classes (
protected
) or not at all (
public
,
default):
class C {
public function FPublic () { }
private function FPrivate () { }
protected function FProtected () { }
module function FModule () { }
function FPublic2 () { }
static FPublic3 () { }
}
Note: It may be tempting to use the shortest possible declaration syntax for a (static) method but I recommend to prepend methods with the
method
keyword simply because it becomes easier to find them in a text editor.
5. Arguments
Functions may be parametrized by up to 255 (script) resp. 16 (native) arguments.
Function argument declarations look a lot like variable declarations. In fact,
static function arguments are simply placed in the variable space of the respective function.
Local function arguments are placed in a function
stackframe that is created and destroyed every time a function is called / returns.
The argument type in the
formal argument list of a class function may be omitted if two or more subsequent arguments have the same type:
class C {
static PrintVector(float x, y, z) {
print "(x="+x+"; y="+y+"; z="+z+")";
}
}
C.PrintVector(1.1, 2.2, 3.3);
6. Recursion
Function arguments (and variables) can either be
local
or
static
(
default).
Example:
function Test(int i, local String
s) {
String
t;
prepare { t= "hello, world.."; }
trace "i="+i+" s="+#(deref s);
trace "t=\""+t+"\".";
t.append("..");
}
Test(42, "hello, world.");
Test(23, "hello, world.");
Test(64, "hello, world.");
All function arguments (and also variables) that are used re-entrant or recursively must be prefixed with the
local
keyword.
A
re-entrant function is a function that is called from more than one execution context (e.g. from multiple
Thread
s)
A
recursive function is a function that calls itself (or calls other code that calls the function).
Example for a recursive function:
function Fib(local int _i) {
if(_i == 0)
return 0;
else
if(_i == 1)
return 1;
else
return Fib(_i - 1) + Fib(_i - 2);
}
print "Fib(9)=" + Fib(9);
6.1. Limits
The default recursion depth is 1024. This can be changed using the
-fss
,
--functionstacksize
commandline arguments.
The default stack size for all function stackframes is 1024. This can be changed using the
-fcs
,
--functioncallstacksize
commandline arguments (also see
Command line interface
)
7. Return value
A function returns nothing (
void
) or a single value which may be a value container holding several values.
The return type of a function is usually
dynamic, i.e. it depends on the currently returned value:
function GetHashVal(HashTable
ht, String
key) {
return ht[key];
}
print #(GetHashVal(#[a=42], "a"));
print #(GetHashVal(#[b=PI], "b"));
print #(GetHashVal(#[c="hello, world."], "c"));
print #(GetHashVal(#[d=null], "d"));
The following example simply casts the dynamically typed argument
v to another type (specified by
type):
function MyFunction(int type, var v) {
switch(type)
{
case YAC_TYPE_INT:
return int(v);
case YAC_TYPE_FLOAT:
return float(v);
case YAC_TYPE_OBJECT:
return Object
(v);
case YAC_TYPE_STRING:
return String
(v);
}
// default: return "void"
}
trace #(MyFunction(YAC_TYPE_INT, "42"));
trace #(MyFunction(YAC_TYPE_FLOAT, "3.1415"));
trace #(MyFunction(YAC_TYPE_OBJECT, 42));
trace #(MyFunction(YAC_TYPE_STRING, 3.1415));
This example uses an array to return multiple values:
function MyFunction() : Object
{
return [42, PI, "hello, world."];
}
Object
r <= MyFunction();
int i = r[0];
float f = r[1];
String
s = r[2];
7.1. The return statement
The
return
statement is used to return from a function before execution reaches the end of the function.
The
return
statement can also be used to simply set the default return value for a function call without leaving the function.
Example:
function MyFunction() {
return 42;
// this line is never reached
print "hello, world.";
}
MyFunction();
The expression after the
return
keyword is optional:
class C {
function MyFunction() {
return; // return the default return value (void)
// this line is never reached
print "hello, world.";
}
}
C.MyFunction();
7.1.1. Setting the return value
The return value of a function can be set without returning from a function call:
function MyFunction(int b) {
return = "my default return value";
if(b)
return "true return value";
else
return "false return value";
}
print MyFunction(true);
print MyFunction(false);
print MyFunction(maybe);
7.2. Return type declarations
Class functions (and methods) support the declaration of a return type.
The value returned by a function call is cast to the specified return type if the types are not compatible.
Example:
class C {
static ToInt (var v) : int { return v; }
static ToFloat (var v) : float { return v; }
static ToObject (var v) : Object
{ return v; }
static ToString (var v) : String
{ return v; }
}
trace #(C.ToInt("42"));
trace #(C.ToFloat("3.1415"));
trace #(C.ToObject(42));
trace #(C.ToString(3.1415));
8. Constraints
Function arguments and return values can be assigned a
constraint expression.
Argument constraints are checked after the
actual argument list has been evaluated. A runtime error is raised when a constraint violation is detected.
Return value constraints are checked shortly before a function call returns and a runtime error is raised in case of a constraint violation.
The magic variable
_
is used to refer to the current argument or return value in a constraint expression.
The predefined
notnull
constraint can be used to make sure that an
Object
reference is not
null
(or an
invalid Object
).
Example:
constraint csneg _<0 "< 0";
constraint cspos _>=0 ">= 0";
constraint csarr [0,2,4,6,8].contains(_) "value must be one of [0,2,4,6,8]";
class C {
static Test(int negI.csneg, posI.cspos, int iArr.csarr,
Object
o.notnull, Object
p) returns Object.notnull
{
return p;
}
}
C.Test(-1, 1, 8, "hello, world.", 42); // OK
//C.Test( 1, -1, 8, "hello, world.", 42); // violates constraint "cspos"
//C.Test(-1, 1, 10, "hello, world.", 42); // violates constraint "csarr"
//C.Test(-1, 1, 8, null, 42); // violates constraint "notnull"
//C.Test(-1, 1, 8, "hello, world", null); // violates (return) constraint "notnull"
To be honest, this feature was added as a
proof-of-concept implementation after a discussion with a colleague some years ago.
Currently, I recommend to NOT use it since a) the constraint violations are currently not exceptions (and therefore not catchable) and b) the script engine does not always shut down properly if a constraint violation occurs.
However, the constraint checks can be entirely disabled using the
-ncs
,
--noconstraints
command line option so it cannot hurt to decorate your methods with e.g.
notnull
constraints..
9. Function objects
A
Function
object is simply the
Object
representation of a script function or static script class method.
Example:
function MyFunction() {
return "hello, world.";
}
Function
fo <= MyFunction;
trace #(fo);
print fo.eval({}); // evaluate with empty argument list
Example:
function MyCallBack(String
line) {
explain "This callback function will be invoked everytime a new line has
been read from stdin.";
trace "MyCallBack: line=\""+line+"\".";
switch(line)
{
case "?":
case "h":
case "help":
print "\n\ntype 'q' to quit";
break;
case "q":
case "quit":
print "[...] quitting..";
exit(0);
}
}
function ProcessInput(Function
cbk) {
String
line; line.alloc(1024);
line.empty();
while(StdInStream.readLine(line, 1024))
{
cbk.eval({line});
}
}
ProcessInput(MyCallBack);
9.1. Thread example
The following example uses a
Function
Object
to pass the reference to a static script class method to a native C++ method (
Thread.create()
) :
class C {
private Thread
thread;
static MyThreadEntry(local Thread
t) {
C c <= t.userdata;
c.test();
}
public method run() {
thread.userdata = this;
// C.MyThreadEntry returns a Function Object for
// the static method "MyThreadEntry"
thread.create(C.MyThreadEntry);
thread.wait();
}
protected method test() {
trace "hello, world.";
}
}
C c;
c.run();
9.2. Other ways to implement callbacks
The use of function objects can usually be avoided by using an
object-oriented design approach and virtual methods (see
Method vtables
).
See
Delegates
for a different, yet similar way to implement callback functions.
10. Synchronization
When using multi-threading, it is required to synchronize methods that are used in more than one script context (
Thread
).
TkScript supports the following synchronization
short-cuts :
- Synchronize method calls per method (=)
- Synchronize method calls per class instance (==)
- Synchronize method calls per named mutex (=name=)
Example:
=mymutex= // declare global mutex
Mutex
mtx; // declare Mutex object
class C {
= public method myGloballySynchronizedMethod() { }
== public method myPerInstanceSynchronizedMethod() { }
=mymutex= public method myNamedMutexSynchronizedMethod() { }
public method myTraditionalSynchronizedMethod() {
mtx.lock();
/* ... */
mtx.unlock();
}
}
Note: the synchronization tags
=
,
==
and
=mymutex=
are currently only supported in the class parser !
auto-generated by "DOG", the TkScript document generator. Wed, 31/Dec/2008 15:53:35