Z-Script is a small, simple, embeddable, and thread-safe scripting language with C-like syntax. It is a by-product in the search for a scripting language for zeGraph. Of the many wonderful languages out there, Lua and C-Talk have been used. Both have their merit but lack features that make it natural to call functions of or to apply operators to user objects. For example, re-defining operators for user objects is very limited in Lua; and while more may be done in C-Talk, it does not has the mechanism of Lua to call functions like they are methods of a user object.
Z-Script is light and fast -- the whole executable, including the standard library, is about 100 K. The language is easy to learn too, especially for those with C experience.
Z-Script is easy to extend. The sources for dynamic link libraries of EXPAT(expat.cpp), SQLITE (sqlite.cpp), netCDF (netcdf.cpp), HDF (hdf.cpp), wxWidget (wdget.cpp), HTTPD (httpd.cpp), and MySQL (mysql.cpp) speak themselves. Not only can you add primitive functions that accept only certain type of user object, you can also assign a primitive function to define operator behaviors for any user objects. The source code for the matrix library shows how to make operators work on data array of char, short, integer, float, and double; and shows how to define __get and __set functions to access matrix values through array access expressions of Z-Script.
Z-Script supports primitive object types of null, boolean, integer, real, string, hash array, and user. Above those, a structured object type, i.e., class, may be defined to encapsulate variables of primitive types and functions. A class is similar to a module in many ways; for example, variables are shared by inside functions but protected from functions of other modules or classes. The difference is that each instance of a class has its own variable context while a module has only one variable context.
Z-Script uses re2c to generate its token scanner, resulting in very fast script compiling.
The syntax of Z-Script is very close to that of C language. But variables are dynamic and are created or updated by assignment, or by function arguments. The object that a variable represents may be null, boolean, integer, real, string, hash array, class, or user object that holds pointer created by user's primitive function.
/*********************************************** * Long comments may be extended to multiple * lines like this one. * * This example also shows the use of * one-line comments. ***********************************************/ a = 135; // integer a = 12.0; // a is now real a = "I love Z-Script."; // a is now string b = [1, 3, 5]; // b is an array with default integer keys a = b[0]; // access array by index; a is 1. b.key = "Hi!"; // adds a key-value pair to array b["key"] = "Hello!"; // another way to use array c = b.key; // c contains a copy of "Hello!" d = b["key"]; // d has the same contents as c
A string, i.e., text between a pair of double quotation marks, may contain C escape characters. A string may also occupy multi lines, but such a long string must not exceed 8 K.
An assignment statement set the right to the left or defines a new variable if the left does not exist. The left will get a copy of the right if the right is a type of null, boolean, integer, real, string, or array; and get the reference to the right for class and user types. A class or a user object should define its own copy function if necessary.
Z-Script array is implemented as hash table so that it may contain any types of objects. When an array is created without keys, the keys are the string form of integers of position indexes, e.g.,
a = ["Tokyo", "New York", "Beijing"]; // is equivalent to a = ["0"="Tokyo", "1"="New York", "2"="Beijing"];
And when such an array contains only numerical values, it will be treated like numbers by operators and functions, e.g.,
a = [1, 2.1, 3.5, 5]; a++; // a-array now contains [2, 3.1, 4.5, 6]; b = sin(a) // b-array contains [sin(2), sin(3.1), sin(4.5), sin(6)]
The array creation expression may be used as the acceptor in assignment to set array values to multiple variables:
[a, b, c] = [1, 2, 3]; // a=1, b=2, c=3 [a, b, c] = [1, 2]; // a=1, b=2, c=null [a, b, c] = [1, "a"=2, 3]; // a=2, b=null, c=3 [a, b, c] = 1; // a=1, b=null, c=null
This feature offers a convenient way to receive multiple values returned as an array object from a function. The expression a.key is equivalent to a["key"] and a[0] to a["0"]. Multiple keys are allowed to get and set values:
a = [10, 11, 12, 13, "hi"]; // create a array b = a[1,4]; // take a's values at index 1 and 4 csv(b); // show b's values, which are [11, "hi"] a[0,1] = [100,200]; // now a contains [100,200,12,13,"hi"]
In array access expression, if the variable is a user object, the expression tries to call the __get primitive function defined for that type of object to extract array values and call the __set primitive function to assign values to it. This feature allows user to add primitive functions to handle array access and assignment. Refer to the matrix library for implementation details.
Z-Script supports three loop structures (while, do, for):
n = 0;
while (n >= 0) {
n++;
if (n == 50) continue; // skip this number
if (n > 100) break; // jump out of the loop
csv(n);
}
/////////////////////////////////////
n = 0;
do {
n++;
if (n == 50) continue; // skip this number
if (n > 100) break; // jump out of the loop
csv(n);
} while (n >= 0);
/////////////////////////////////////
for (i = 0; i < 100; i++) {
if (i == 10) continue; // Skip the rest when i=0
csv(i);
}
And three control/redirect structures (if, switch, goto):
a = 1;
b = 2;
c = 3;
/////////////////////////////////////
if (a > 0) {
csv(a);
// more statements may follow.
}
/////////////////////////////////////
if (a > b) csv(a); // {} is not necessary if there is only one statement
else csv(b); // after "if" or "else".
/////////////////////////////////////
if (a > b) {
csv(b);
}
else if (a > c) {
csv(c);
}
else {
csv(a);
}
/////////////////////////////////////
switch (flag) {
case C1:
csv(C1);
break;
case C2:
csv(C2);
goto 100;
default:
csv("default");
}
case 100:
....
The switch argument and the expressions after case and goto must evaluate to integer or string. A case marks where switch starts execution according to the value of switch argument. A case may also be used in a function or module for goto redirection. That is the goto statement above will make execution jumping to case 100. Although goto is not recommended in common programming practice, it nevertheless provides convenient redirection in certain situations.
You may define functions in a module and import them to another. (Refer to Z-Script Usage for the meaning of module.)
////////// hello.zs //////////////////
function hello()
{
print("Hello!");
}
////////// main module ///////////////
import hello;
hello();
When a module is not in the current path or in subdirectories of lib, cls, or prog; or the module name contains operator symbols, e.g. "-"; the full module name must be composed as a string:
import "my-module/say-hello"; // import say-hello.zs in the my-module subdirectory
The import statement is like the include macro in C, but may be placed any where in a script.
In function declaration, an argument may have default value. When an argument's default value is not set, null is assumed implicitly. In a function call, parameters are passed to arguments at corresponding positions, but when an assignment expression is used, the object resulted from the right will be set to the argument that has the same name as the left.
function f1(a, b=1, c=2.1, d="hi!", e=[1, 2, 3])
{
csv(a, b, c, d, e);
}
f1(); // call f1 with no argument
// output: null, 1, 2.000000, hi!, [0=1, 1=2, 2=3]
f1(a=1, 2, e="Hi!", d=[1, 2, 3]); // call f1 with positional and assignment arguments
// output: 1, 2, 2.000000, [0=1, 1=2, 2=3], Hi!
Objects of null, boolean, integer, real, and string are passed to functions by value; and objects of array, class, and user are passed by reference.
Calling script function iteratively is allowed:
function Ack(m, n)
{
if (m == 0) {
return n + 1;
}
if (n == 0) {
return Ack(m - 1, 1);
}
return Ack(m-1, Ack(m, n - 1));
}
csv(Ack(3, 4));
A function may be declared inside another function. In that case, the inside function may access the private variables of the parent function.
Primitive functions in a dynamic link library may be loaded to the global primitive function table by any module. For example:
load("my.dll"); // load primitive functions in my.dll
hello(); // suppose hello() is a primitive function in my.dll
Calling a script function from a primitive function of your library may be achieved through api functions.
Like Lua, Z-Script support the special function call
caller:func(...);
The caller is passed to the function as the first argument. Combined with the feature of registering primitive functions according to name and type, you can use the same function name for different types of objects and make calling functions in Z-Script like calling methods in C++.
Primitive functions precede script functions in function call. That is when a loaded primitive has the same name as a script function, the primitive function will be used.
Z-Script has implemented two call-back mechanisms: calling script functions from a primitive function (see API reference) and use script function as the argument of another script function shown as follows:
function f1(x, y)
{
return [x, y];
}
function f2(x, y)
{
return [x*10, y*100];
}
function dynamic1(f, x, y)
{
// call is a buid-in function
csv(call(f, x, y));
}
function dynamic2(name, x, y)
{
// func is a buid-in function
f = func(name);
csv(call(f, x, y));
}
f = func("f1");
dynamic1(f, 1, 10);
f = func("f2");
dynamic1(f, 1, 10);
dynamic2("f1", 1, 10);
dynamic2("f2", 1, 10);
A class is a structured object declared at the module level and must be instantiated before being used. A very special feature of Z-Script's class is that operator functions may be defined to process such an expression as a + b. For example,
class Point {
cx = 0; // initialize class level variables
cy = 0;
cz = 0;
function set(x, y, z)
{
cx = x; cy = y; cz = z;
}
function add(x, y, z)
{
cx += x; cy += y; cz += z;
}
function csv()
{
csv(cx, cy, cz);
}
// this is a operator function for +
function __add(a, b)
{
c = new Point;
c->cx = a->cx + b->cx;
c->cy = a->cy + b->cy;
c->cz = a->cz + b->cz;
return c;
}
}
a = new Point; // create a point
a->set(10, 10, 10); // call a's function
b = new Point;
b->cx += 5; // access b's variable directly
c = a + b; // because a is a class, call a's
// operator function for "+".
// c is now a class object.
c->csv();
For binary operators, Z-script will calls the operator function of the higher rank in the order of null, boolean, integer, real, string, array, class, and user. In the above example, if a is number and b is a class, b's __add() function will be called with b as the first argument and a as the second. Redefinable operator functions are listed as follows:
__neg(a) <==> -a __not(a) <==> !a __cmpl(a) <==> ~a __incr(a) <==> a++ __decr(a) <==> a-- __mul(a,b) <==> a + b __add(a,b) <==> a + b __div(a,b) <==> a - b __div2(a,b) <==> b - a __mod(a,b) <==> a % b __mod2(a,b) <==> b % a __sub(a,b) <==> a - b __sub2(a,b) <==> b - a __le(a,b) <==> a <= b __lt(a,b) <==> a < b __ge(a,b) <==> a >= b __gt(a,b) <==> a > b __eq(a,b) <==> a == b __ne(a,b) <==> a != b __and(a,b) <==> a & b __or(a,b) <==> a | b __xor(a,b) <==> a ^ b __nn(a,b) <==> a && b __oo(a,b) <==> a || b __rsh(a,b) <==> a >> b __lsh(a,b) <==> a << b __mul_eq(a,b) <==> a *= b __div_eq(a,b) <==> a /= b __mod_eq(a,b) <==> a %= b __add_eq(a,b) <==> a += b __sub_eq(a,b) <==> a -= b __rsh_eq(a,b) <==> a >>= b __lsh_eq(a,b) <==> a <<= b __and_eq(a,b) <==> a &= b __or_eq(a,b) <==> a |= b __xor_eq(a,b) <==> a ^= b
All operators of Z-Script may be redefined to act on any types of user objects. Please refer to the API page and source code for matrix library on how to achieve that.
load("matrix.dll");
A = matrix("double", 10, 10); // 10x10 double matrix
A:fill(1, 1); // A contains numbers from 1 to 100
A *= 10; // A contains numbers from 10 to 1000;
A += 1; // A contains numbers from 11 to 1001;
A variable defined in a module is accessible only to functions and classes declared in the same module; a variable defined in a class is accessible only to functions declared in the class; and a variable defined in a function is only accessible to itself and to any functions declared in the function.
When an expression tries to get value from a variable, it starts searching the variable the function, class, or module that it belongs to and then, if failed, starts searching in the ancestors of function or class. But when the global: modifier is used, the initial search starts in the owner of function or class.
When an expression tries to set value to a variable and the variable does not exist in the function, class, or module that the expression belongs to, a new variable is defined locally. But when the global: modifier is used, the expression tries to find the ancestor of the function or class that defines the variable and then set the value to it.
The following example shows the concept of variable scope:
a = 0;
b = 0;
function f()
{
a = 10;
global:a = 100;
csv(a, b, global:a);
ff();
function ff()
{
a = 1;
global:b = -1;
csv(a, global:a, global:b);
}
}
csv(a, b);
f();
csv(a, b);
In case of using Z-Script in a server, allowing any Z-Script runtime error to interrupt server service may not be desirable. The try-catch feature of Z-Script may be used to catch and process the error message, e.g.,
try {
a++;
...
}
catch (error) {
cgi_error(error)
}
You can put csv(...) and return anywhere in a script to control execution to that point and check vaiable values. Alternatively, you can use a variable as flag and insert trace(flag) in your code. If flag=true, the trace function will display vaiable values and halt the execution until the enter key is pressed. Here is a script example that evaluate user input interactively:
/*********************************************
* Interactive program in z-script
*********************************************/
exec("cls");
csv("Z-Script version 1.4 by Jiye Zeng\n");
csv("Commands:");
csv(" @run -- execute script");
csv(" @who -- show variables");
csv(" @reset -- clear script and screen");
csv(" @clear -- clear screen");
csv(" @script -- show script");
csv("\nA input not starting with @ is treated as script.\n");
script = "";
while(1) {
try {
s = input("zs>");
// When control-C is used to interrupt
// the program, s will not be string.
// So checking s is necessary.
if (isstring(s)) {
switch(s) {
case "@run":
// execute script
ret = eval(script, false);
if (ret != null) csv(ret);
break;
case "@who":
// execute and show vaiables
ret = eval(script, true);
if (ret != null) csv(ret);
break;
case "@reset":
// reset script code and clear screen
script = "";
exec("cls");
break;
case "@clear":
// clear screen only
exec("cls");
break;
case "@script":
// show script code
csv(script);
break;
default:
// script code;
if (size(s) > 0) script += s;
break;
}
}
}
catch(msg) {
csv(msg);
}
}
Executing the scritp will bring up an interactive DOS window shown below.

break case catch class continue default do else false for function global goto if import new null return switch true try while
Use a++ instead of a = a+ 1, a *= b instead of a = a + b, and so on for efficiency.