A brief introduction to the TKScript language.

This document was written to teach you the basics of the TKScript language.

TKScript is mostly based on the "C"/"C++" language (by Dennis Richie and Bjarne Stroustrup) with influences from Java (by James Gosling) and Perl (by Larry Wall).

TKScript is not a full-fledged programming language but..

In contrary to system-level, pre-compiled languages..

What is it used for?

In general, TKScript can be used for text-processing tools or as a glue language for native APIs, like for example OpenGL, SDL, MySQL or the FOX toolkit. The language can easily be extended by new scripted and native datatypes ("classes") and functions. You may find that the JIT compiler makes the language attractive for video games and similar "real-time" applications.

Is it portable?

TKScript has successfully been ported to and tested on Intel/AMD/.. 0x86 (Win32, Linux), Motorola 68K (Amiga OS 3.0), IBM/.. PPC/G3 (MacOSX).

Is it free?

TKScript is distributed under the terms of the GNU General Public License. The YAC "object model" interface is distributed under the terms of the GNU Lesser General Public license. In practice this means that you do not have to publish your custom extensions (but your are encouraged to do so!) as long as they are located in shared objects (DLLs) which are handled by the TKS/YAC plugin loader.

Values, atomic and abstract datatypes

The script language knows about 4 basic ("atomic") datatypes:

Example:
int i=42; i=i*3+i*i; print i;

A 5th datatype String is used to speed up string operations but it is actually an abstract datatype and not supported by the JIT compiler (well, to be precise: The JIT compiler does not support statements/expressions which spawn new objects and/or use dynamically typed values). Example:

String s; s="hello, world."; int i=42; stdout s+" i="+i+".\n";

Scripted abstract datatypes are formed by one or many "atomic", scripted or builtin resp. dynamically loaded C++ datatypes.

Builtin abstract datatypes

A number of native C++ datatypes are directly built into the barebone tkscript runtime. Along with the int and atomic datatypes and the native datatypes added by YAC plugins they can be used to form new scripted datatypes (classes). See here for a comprehensive list of builtin language ADTs.

Variables

Values can be stored at "named" memory locations called "variables". Variables may be declared whereever a statement is expected. Example:

int i;
You can directly initialize variables:
int i=42;
You can declare multiple variables of the same type at once:
float x,y,z;

TKScript supports static and local variables. The local qualifier may only be used in functions resp. methods. If the local qualifier is not used in a variable declaration, the variable is declared as static. Static function variables preserve their state between two function calls. Local variables are initialized each time a function is called and they are automatically deleted once the function call finishes (unless the Object pointer has been unlinked using the deref expression). Example:

function Test_Static() {
     String s;
     print s;           // when this is called for the first time, the string is empty
     s="hello, world."; 
}
loop 2 Test_Static();

Static variables can be initialized exactly once by using the prepare statement. Example:

function Test_Static_Init() {
    String s;
    prepare { s="hello, "; }
    print s;
    s="world.\n";
}
Test_Static_Init();
Test_Static_Init();

Please notice that static variables can also be initialized each time a function is called if you simply write
function f() { String s="test"; return s;} print f(); // print a copy of the constant string object "test"
Bear in mind that if you change the object of a variable, all operations will have effect on the changed object:
function f() { String s; s<="test"; return s; } print f(); // will print the constant string object "test"

In contrary to static variables, local variables are placed on a dedicated stack. The lifetime of a local variable starts when a function is being called and ends when the function returns. Example:

function Test_Local() {
    local String s="hello, world."; // alloc/init new String object each time the function is called
    return deref s;                 // unlink object from variable so it is not deleted when the function returns
}
print Test_Local();                 // the String object returned by Test_Local() is deleted after the print statement
Please notice that the JIT compiler uses the CPU stack to manage local variables (reward is a slight increase in execution speed); the "interpreter" engine uses a different stack (which is basically an array of YAC_Values).

Abstract datatypes (scripted)

A set of named variables that represent a higher level data type is called a "structure" or class. Example:

class CPerson {
   String name;
   String surname;
   Time   birthdate;
}
CPerson p; p.birthdate.now(); p.name="Jack"; p.surname="Carver"; 

Memory managment

TKScript uses a simple but effective memory managment system which stores a "deleteme" flag along with an object pointer in a script value: (excerpt from the yac.h C++ plugin SDK)

    class YAC_Value {
        union __value {	
	    sF32        float_val; 
	    sSI         int_val; 
	    YAC_Object *object_val; 
	    YAC_String *string_val; 
	} value; 
	sUI deleteme; // ---- this flag is used to decide whether it is safe to delete the object
	sUI type;     // ---- 0=void, 1=int, 2=float, 3=Object, 4=String
    }; 

In consequence, the script language allows you to have direct control over object pointers: Example:

    String  s;                     // declare a String variable

            s<=tcstring("hello");  // delete previous content of variable "s" and assign a new String object which is a copy of the constant "hello"

    String  r<=new String;         // declare a String variable and assign a new String object

    Pointer t<=s;                  // declare an "untyped" Object pointer variable which stores a *reference* to s (valid as long as "s" exists)

    String  u<=deref s;            // declare a String variable, unlink object pointer from variable "s" and make "u" take care of object pointer deletion

    String  z=t;                   // declare a String variable and assign value of "t" (string copy)

            u<=null;               // force instant deletion of String object, "t" and "s" become invalid

                                   // String objects "r" and "z" will be automatically deleted at script exit.

Constant objects

Although object pointers are marked "read-only" when passing them with the <= operator, their actual value is not protected. This has the consequence that "constant" objects obtained from array/hashtable/list initializers can be altered afterwards. String constants can also be changed. Nevertheless, only a single occurrance of a constant is subject to that change.
Please keep in mind that constant objects are not shared by all occurances of the value(s) that was/were used to initialize the constant object. Last but not least, what you can not do is delete a constant object. Example:

String t<="world"; // assign the pointer to the occurrence of the constant string object "world" to the variable t
t="hello, ";       // change the value of the occurrence of the constant string object "world"
print t;           // print the value of the changed constant string object
print "world";     // print the next occurrance of the constant string object "world" 

Conversion between datatypes

The script engine automatically converts between the atomic datatypes int,float,Object and String. Example:

int i=3.14; float f=4+i; String s=23; i=s; print i;

If an integer or float is used in a double arg expression in combination with an Object it is converted to a Float object. Example:

int i=42; Double d,d2=PI; d=i*d2*0.01232435; print d.printf("%4.24g");

Control structures

Computer programming is based on the idea that you have memory to store values (like ints, floats and complex datatypes like e.g. Strings) and program instructions which read and combine one or many memory locations and write to one or many others. Program instructions are usually also located in memory and processed in linear order. Some instructions are used to compare one or many memory values and branch to a new instruction memory address if the result is "true".
Note: the diassembly output was produced by placing the example code in a compile { /*...*/ } statement and then calling "tks -da <scriptfile>"

Example:
int i=42; if(i==42) print "true";
 Disassembly:
(#0000)00000000: movvc i 42 (0.000000f);
(#0001)00000003: jivic i != 42 000d;
(#0002)00000007: pushc 9569208 (0.000000f);
(#0003)00000009: loadc 4624960 (0.000000f);
(#0004)0000000b: apicall;
(#0005)0000000c: incstp;
(#0006)0000000d: halt;

Example:

int i; 
   for(i=1; i<11; i++) 
       print i;
 Disassembly:
(#0000)00000000: movvc i 1 (0.000000f);
(#0001)00000003: jivic i >= 11 0011;
(#0002)00000007: pushc 9569184 (0.000000f);
(#0003)00000009: loadc 4624960 (0.000000f);
(#0004)0000000b: apicall;
(#0005)0000000c: incstp;
(#0006)0000000d: inciv i;
(#0007)0000000f: bra 0003;
(#0008)00000011: halt;

Example:

int i=1;
   while(i<11)
       print i++;

 Disassembly:
(#0000)00000000: movvc i 1 (0.000000f);
(#0001)00000003: jivic i >= 11 000f;
(#0002)00000007: pushc 9569216 (0.000000f);
(#0003)00000009: loadc 4624960 (0.000000f);
(#0004)0000000b: apicall;
(#0005)0000000c: incstp;
(#0006)0000000d: bra 0003;
(#0007)0000000f: halt;

Example:

int i=1; 
   loop(10)
       print i++;
 Disassembly:
(#0000)00000000: movvc i 1 (0.000000f);
(#0001)00000003: pushc 10 (0.000000f);
(#0002)00000005: sitestzp 000f;
(#0003)00000007: pushc 9569168 (0.000000f);
(#0004)00000009: loadc 4624960 (0.000000f);
(#0005)0000000b: apicall;
(#0006)0000000c: incstp;
(#0007)0000000d: siloop 0007;
(#0008)0000000f: incstp;
(#0009)00000010: halt;

Example:

int i; 
   foreach i in [1,2,3,4,5,6,7,8,9,10]  // iterate an IntArray
       print i;
 Disassembly:
(#0000)00000000: pushc 9891912 (0.000000f);
(#0001)00000002: loadc 4624960 (0.000000f);
(#0002)00000004: apicall;
(#0003)00000005: incstp;
(#0004)00000006: halt;

Example:

ListNode l; 
   foreach l in {1,2,3,4,5,6,7,8,9,10}  // iterate a List
       print l.intValue;
 Disassembly:
(#0000)00000000: pushc 9569544 (0.000000f);
(#0001)00000002: loadc 4624960 (0.000000f);
(#0002)00000004: apicall;
(#0003)00000005: incstp;
(#0004)00000006: halt;

Example:

int i=0;
    loop 10 switch(i++) {
      case 0: print "1";  break;
      case 1: print "2";  break;
      case 2: print "3";  break;
      case 3: print "4";  break;
      case 4: print "5";  break;
      case 5: print "6";  break;
      case 6: print "7";  break;
      case 7: print "8";  break;
      case 8: print "9";  break;
      case 9: print "10"; break;
    }
 Disassembly:
(#0000)00000000: movvc i 0 (0.000000f);
(#0001)00000003: pushc 10 (0.000000f);
(#0002)00000005: sitestzp 000f;
(#0003)00000007: pushc 9569800 (0.000000f);
(#0004)00000009: loadc 4624960 (0.000000f);
(#0005)0000000b: apicall;
(#0006)0000000c: incstp;
(#0007)0000000d: siloop 0007;
(#0008)0000000f: incstp;
(#0009)00000010: halt;

Example:

var i=0;
    loop 10 switch(i++) {
      case i-1: print i;  break;
    }
 Disassembly:
<can not be compiled>

Note: the JIT compiler was designed to process small, CPU intensive "inner loops"; it can not handle all aspects of the tkscript language. Also see here.

Example:

var i;
HashTable ht<=#["0"=1, "1"=2, "2"=3, "3"=4, "4"=5, "5"=6, "6"=7, "7"=8, "8"=9, "9"=10];
foreach i in ht {
   print ht[i];
}
 Disassembly:
(#0000)00000000: pushc 9852344 (0.000000f);
(#0001)00000002: pushc 9568880 (0.000000f);
(#0002)00000004: loadc 4624768 (0.000000f);
(#0003)00000006: apicall;
(#0004)00000007: incstp;
(#0005)00000008: incstp;
(#0006)00000009: pushc 9851784 (0.000000f);
(#0007)0000000b: loadc 4624960 (0.000000f);
(#0008)0000000d: apicall;
(#0009)0000000e: incstp;
(#0010)0000000f: halt;

Functions

..are used to "name" frequently used instruction sequences. Functions can be parametrized by up to 255 arguments and can return nothing or a single value (which may be an abstract datatype instance holding several values).
Please notice that tkscript does not make a difference between procedures (which return nothing) and functions (which usually return something). If the return type is not specified using the returns resp. : keyword, it defaults to variant; i.e. the type of the returned value is dynamic. Example:

function Get1_to_10() {
    return [1,2,3,4,5,6,7,8,9,10];
}
print Get1_to_10()[9];

Example:

#define TEN 10
int nine=9;
function GetList() {
    return {1,"2",3.3,"four",0x5,$6,0b111,#8,nine,TEN};
}
ListNode l; foreach l in GetList() print l.stringValue;
print GetList()[9].intValue+1;

Functions (as well as methods) can hold local variables which will be created resp. deleted each time a function is called. Also see fibonacci.tks which demonstrates local variables and recursive function calls.

Methods

A method is basically a function associated with an abstract datatype. All variables ("members") of the respective instance of the abstract datatype are visible within a method:

class CPerson {
    String name;
    String surname;
    Time   birthdate;

    create(String _name) { 
      String words[]<=_name.splitSpace(true);
      name   =words[0];
      surname=words[1];
      birthdate.now();
      print "\""+name+" "+surname+"\" says hello =).";
    }

}

CPerson p; p.create("Jack Carver");

(File-)Streams

Serialization

Accessing XML files using the builtin mini-XML parser

API extensions (plugins)


 

The future

I hate talking about the future :). Let's just say this project is targeted at the client-side of things which means a possible improvement of the UI extension. I will also try to create/adapt to some kind of IDE which I personally miss most when working with this system.
 
Greetings,
       --Bastian Spiegel <bs@tkscript.de>

last changed: 01.November.2005