Contact: zeng  @  zegraph.com      Last update: 20 January 2010

Application Programming Interface of ZeScript

As demonstrated in main.cpp, the following API functions are for building an executable program:

void* api_parse_file(const char* fname, char *error)
      -- Parses script in the file and returns a pointer to module or NULL in case of error.
         The second input variable must point to string of at least 256 bytes for error message.
void* api_parse_file2(const char* fname, char *error, const char *path)
      -- Parses script in the path+file and returns a pointer to module or NULL in case of error.
         The second input variable must point to string of at least 256 bytes for error message.

int api_exec_module(void *module, char *error)
      -- Executes the module and return a negative integer if failed.
         The second input variable must point to string of at least 256 bytes for error message.

void api_delete_module(void *module)
      -- Delete the module and release resources allocated by the module.

A module manages memory of its own and of imported modules and may be executed many times. In applications that need to evaluate script code in a string, as in embedding script in HTML document:

void* api_parse_string(void *string, char *error)
      -- Parses script in the string and returns a pointer to module or NULL in case of error.

It is possible to pass variable between modules using the these two functions:

void *api_get_context(void *module);
      -- Gets the pointer the module's variable context.

void api_set_object(void *ctx, const char *vname, void *object);
      -- Sets an object to a variable in the variable context.

You may build dynamic link libraries (DLL) of primitive functions by including api.h (and scan.h if you want to redefine operators for you objects) in your C or C++ source code and linking to the library zs.lib. A primitive function must have the prototype of

void *func(void *ctx, int nargs, **args);

Where the ctx is the variable context that an expression is using to call the function with given number (nargs) of arguments (args). A primitive function may be registered to ZeScript by the function

void api_add_primitive(const char* name, int type, void* func)

You should assign a distinguished type ID to your object. ZeScript uses the ID to prevent name clash of primitive functions. That is that the registered name of a primitive function will be formed with the type ID attached to the given name. And when the primitive is called as

obj.func(...);

ZeScript will look for the function with the name of func+ID, e.g. func4835.

A primitive function may return NULL or an object create by one of the API functions:

void* api_create_null(void *ctx, caller)
      -- creates a null object. 

void* api_create_integer(void *ctx, int value)
      -- creates a integer type object. 

void* api_create_real(void *ctx, double value)
      -- creates a real type object. 

void* api_create_string(void *ctx, const char* str)
      -- creates a string type object. 

void* api_create_array(void *ctx, size)
      -- creates a array type object with the given hash table size. 

void* api_create_user(void *ctx, void* ptr, void* opfunc, void* destroy, int type)
      -- creates a user type object. 

The ctx may be NULL. The that case, the created object must be set to an array or context immediately. The opfunc pointer in api_create_user() may be NULL or the address of a primitive function that will be called when a user object is an operand of a operator. The first argument passed to opfunc will be the user object, the second will be the operator ID (ref. scan.h), and the third will be null for unary operators or another object for binary operators. The destroy function pointer may point to a function of the prototype

void destroy(void *ptr)

that will be called when ZeScript is about to delete the user object.

In a primitive function, you may use these functions to query the object type of an argument:

bool api_is_null(void *object)
     -- returns true is the object is null; returns false otherwise.

bool api_is_integer(void* object) 
     -- returns true is the object is integer; returns false otherwise.

bool api_is_real(void* object)
     -- returns true is the object is real (double); returns false otherwise.

bool api_is_number(void *object)
     -- returns true is the object is integer or real; returns false otherwise.

bool api_is_string(void *object)
     -- returns true is the object is string; returns false otherwise.

bool api_is_array(void *object) 
     -- returns true is the object is array; returns false otherwise.

bool api_is_user(void *object)
     -- returns true is the object is user type; returns false otherwise.

int  api_get_type(void *object)
     -- returns the object type ID.

If you are expecting a certain type of object, you may just use the following functions to extract object values. They will throw an error when the object type is not what your are asking for.

int api_get_integer(void *ctx, void* object)
     -- asserts that the object is integer and returns its value.

double api_get_real(void *ctx, void* object)
     -- asserts that the object is real and returns its value.

double api_get_number(void *ctx, void* object)
     -- asserts that the object is integer or real and returns its value.

const char* api_get_string(void *ctx, void* object)
     -- asserts that the object is string and returns the pointer to the string.

void* api_get_ptr(void *ctx, void *user)
     -- asserts that the object is user type and return the pointer
        that is passed to api_create_user function.

int  api_get_array_size(void *ctx, void *object)
     -- returns the size of the array object.

void* api_get_array_object(void *ctx, void* array, const char* key)
void* api_get_array_object2(void *ctx, void* array, int key)
     -- asserts that the array type is correct
        and returns the object corresponding to the key.

void api_set_array_object(void *ctx, void* array, const char* key, void* value)
void api_set_array_object2(void *ctx, void* array, int key, void* value)
     -- asserts that the array type is correct, removes the key/value first,
        and then sets the key/value in the array.

Sometime, you may wish to check whether a variable has been defined before creating it; then you can use:

void* api_peek_variable(void *ctx, const char *name);
     -- returns the pointer to the named variable; returns NULL if the variable is not defined.

Calling script function from your primitive functions may be achieved through these functions:

void* api_get_func(void *ctx, const char* name)
     -- returns the pointer to the named script function;
        throw error is the function is not declared in script.

void* api_call_func(void *func, int nargs, void** args)
     -- call script function with arguments

Although a script function executes in its own context, it is strongly recommended to implement mutex locking in a mutiple-thread application because the function may modify module-level global variables or may call another function that modifies global variables.

And finally, you should let ZeScript to handle critical error using this function:

void api_input_error(void *ctx)
     -- throw error message indicating incorrect input in function call.

void api_runtime_error(void *ctx, const char* msg)
     -- throw error message.

void api_runtime_error2(void *ctx, const char* msg, const char* msg2)
     -- throw error message.

For a user object, if a primitive function is registered with the name "__get ", it will be called in an member access expression (e.g., u.member) or array access (e.g., u[a, b, c...]). In the former case, the function will get two arguments with the first being the user object and the second being a string object whose value is "member"; and in the latter, the number of arguments and their types depend on expressions inside [], but the first argument is the user object. Similarly, if a primitive function is registered with the name "__set ", it will be called in an assignment expression, e.g., u.member = expr or u[a, b, c...] = expr and the last argument is the object to be assigned to u. And if a primitive function is registered with the name "__copy ", it will be called in an assignment expression, e.g., user2 = user1, and will get user1 as the only argument; otherwise user2 will be an alias of user1. The following C++ code demonstrates how to define primitive functions for a simple 3D point object.

#include "api.h"
#include 
#include 

#pragma warning(disable: 4244)

#define P3D_TYPE		'P3D'

///////////////////////////////////////////////////////
// 3D point class
//
class zsP3D
{
public:
	zsP3D() : x(0), y(0), z(0) { }
	~zsP3D() { }
	double x, y, z;
};

///////////////////////////////////////////////////////
// This will be called when reference counts to
// a zsP3D object are reduced to zero.
//
void p3d_destroy(void* ptr) { delete (zsP3D*)ptr; }

///////////////////////////////////////////////////////
// Define + operator for zsP3D 
//
void* p3d_opfunc(void *ctx, int nargs, void** args)
{
	if (nargs != 3) api_input_error(ctx);
	zsP3D *l = (zsP3D*)api_get_user(ctx, args[0], P3D_TYPE);
	int op = api_get_integer(ctx, args[1]);
	switch (op) {
	case '+':
		if (api_is_number(args[2])) {
			// p3d + number
			real_t v = api_get_number(ctx, args[2]);
			zsP3D *o = new zsP3D;
			o->x = l->x + v;
			o->y = l->x + v;
			o->z = l->x + v;
			return api_create_user(ctx, o, p3d_opfunc, p3d_destroy, P3D_TYPE);
		}
		else {
			// p3d + p3d
			zsP3D *r = (zsP3D*)api_get_user(ctx, args[2], P3D_TYPE);
			zsP3D *o = new zsP3D;
			o->x = l->x + r->x;
			o->y = l->x + r->y;
			o->z = l->x + r->z;
			return api_create_user(ctx, o, p3d_opfunc, p3d_destroy, P3D_TYPE);
		}
		break;
		// more operator definitions here...
	}
	api_runtime_error(ctx, "undefined operation for P3D object");
	return 0;
}

///////////////////////////////////////////////////////
// Create a zsP3D object. This may be called as
// a = p3p();
// a = p3p(0.1);
// a = p3p(0.1, 0.2);
// a = p3p(0.1, 0.2, 3);
//
void* p3d_create(void *ctx, int nargs, void** args)
{
	zsP3D *o = new zsP3D;
	if (nargs > 0) o->x = api_get_number(ctx, args[0]);
	if (nargs > 1) o->y = api_get_number(ctx, args[1]);
	if (nargs > 2) o->z = api_get_number(ctx, args[2]);
	return api_create_user(ctx, o, p3d_opfunc, p3d_destroy, P3D_TYPE);
}

///////////////////////////////////////////////////////
// Define ___get to be called by such a expression as
// a = p.name;
// a = p[idx];
//
void* p3d_get(void *ctx, int nargs, void** args)
{
	if (nargs < 2) api_input_error(ctx);
	zsP3D *o = (zsP3D*)api_get_user(ctx, args[0], P3D_TYPE);
	if (api_is_string(args[1])) {
		// p.x
		const char *name = api_get_string(ctx, args[1]);
		if (strcmp(name, "x") == 0) return api_create_real(ctx, o->x);
		if (strcmp(name, "y") == 0) return api_create_real(ctx, o->y);
		if (strcmp(name, "z") == 0) return api_create_real(ctx, o->z);
		api_runtime_error(ctx, "bad P3D member access");
	}
	// p[idx]
	int idx = api_get_integer(ctx, args[1]);
	if (idx == 0) return api_create_real(ctx, o->x);
	if (idx == 1) return api_create_real(ctx, o->y);
	if (idx == 2) return api_create_real(ctx, o->z);
	api_runtime_error(ctx, "bad P3D member access");
	return 0;
}

///////////////////////////////////////////////////////
// Define ___set to be called by such a expression as
// p.name = v;
// p[idx] = v;
//
void* p3d_set(void *ctx, int nargs, void** args)
{
	if (nargs < 3) api_input_error(ctx);
	zsP3D *o = (zsP3D*)api_get_user(ctx, args[0], P3D_TYPE);
	real_t v = api_get_number(ctx, args[2]);
	if (api_is_string(args[1])) {
		// p.name = v
		const char *name = api_get_string(ctx, args[1]);
		if      (strcmp(name, "x") == 0) o->x = v;
		else if (strcmp(name, "y") == 0) o->y = v;
		else if (strcmp(name, "z") == 0) o->z = v;
		else api_runtime_error(ctx, "bad P3D member assignment");
		return 0;
	}
	// p[idx] = v
	int idx = api_get_integer(ctx, args[1]);
	if      (idx == 0) o->x = v;
	else if (idx == 1) o->y = v;
	else if (idx == 2) o->z = v;
	else api_runtime_error(ctx, "bad P3D member assignment2");
	return 0;
}

///////////////////////////////////////////////////////
// Define ___copy to be called by such a expression as
// p2 = p1;
//
void* p3d_copy(void *ctx, int nargs, void** args)
{
	if (nargs < 1) api_input_error(ctx);
	zsP3D *o = (zsP3D*)api_get_user(ctx, args[0], P3D_TYPE);
	zsP3D *ret = new zsP3D;
	ret->x = o->x;
	ret->y = o->y;
	ret->z = o->z;
	return api_create_user(ctx, ret, p3d_opfunc, p3d_destroy, P3D_TYPE);
}

///////////////////////////////////////////////////////
// Display class members
//
void* p3d_print(void *ctx, int nargs, void** args)
{
	if (nargs < 1) api_input_error(ctx);
	zsP3D *o = (zsP3D*)api_get_user(ctx, args[0], P3D_TYPE);
	printf("x=%f, y=%f, z=%f\n", o->x, o->y, o->z);
	return 0;
}

///////////////////////////////////////////////////////
// Class for registering primitive function
//
class zsRegPrimitive
{
public:
	zsRegPrimitive()
	{
		api_add_primitive("p3d",		0,			p3d_create);
		api_add_primitive("__get",		P3D_TYPE,	p3d_get);
		api_add_primitive("__set",		P3D_TYPE,	p3d_set);
		api_add_primitive("__copy",		P3D_TYPE,	p3d_copy);
		api_add_primitive("print",		P3D_TYPE,	p3d_print);
	}

	~zsRegPrimitive() { }
};

zsRegPrimitive p3d_register;

Alfter linking to ZeScript library, you can manipulate  a 3D point object as shown in the following script code

// load primitive functions in the library
load("point3d.dll");

// create 3D point object
p1 = p3d(1, 2, 3);

// show its members
p1.print();

// copy by assignment
p2 = p1;

// set members
p2.x = 10;
p2[1] = 20;

p2.print();

// get members of p1 and show them using buildin csv function
csv(p1.x, p1.y, p1[2]);

// + operator
p1 = p1 + 0.1;
p1.print();
p1 = p1 + p2;
p1.print();