JX9 Scripting Engine

An Embeddable Scripting Engine



Introduction To The Constant Expansion Mechansim.

The following quick guide is what you do to start experimenting with the JX9 constant expansion mechanism without having to do a lot of tedious reading.

A constant is an identifier (name) for a simple value. As the name suggests, that value cannot change during the execution of the script.


A valid constant name starts with a letter or underscore, followed by any number of letters, numbers, or underscores. Constants name are case-sensitive.

The constant expansion mechanism under JX9 is extremely powerful yet simple and work as follows:


Each registered constant have a C procedure associated with it. This procedure is known as the constant expansion callback which is responsible of expanding the invoked constant to the desired value. For example the C procedure associated with the “__PI__” constant expands to 3.14 (the value of PI), the “__OS__” constant expands to the name of the host Operating Systems (Windows, Linux, etc.) and so on.


Constants and their associated callbacks are registered via a single interface: jx9_create_constant(). All of the built-in constants (over 139) such as __JX9__, __DATE__, PATH_SEPARATOR and so forth are registered using exactly this interface.


We will start our tutorial by creating a simple constant named  __PI__. That is, when the __PI__ identifier is seen in the running script, it's associated procedure (see below for the implementation) gets called by the underlying JX9 virtual machine. This procedure is responsible of expanding the PI  identifier to it's value (3.14 in our case).

The JX9 scripting engine require that the constant expansion callback signature must correspond to this:


void (*xExpand)(jx9_value *,void *)


That is, a procedure that takes two arguments. The first argument is a pointer to a jx9_value that the procedure must fill with the desired value for example 3.14 (value of PI) is stored in this pointer. Use the following interfaces to populate the jx9_value with the desired value:


jx9_value_int()

jx9_value_int64()

jx9_value_bool()

jx9_value_null()

jx9_value_double()

jx9_value_string()

jx9_value_string_format()

jx9_value_resource()


The second and last argument is a copy of the fourth argument to the jx9_create_constant() function which is forwarded verbatim by engine.

Continuing with our __PI__ constant, here is the C procedure associated with it:


void PI_Constant(

     jx9_value *pValue, /* Store expanded value here */

      void *pUserData /* User private data (unused in our case) */

  ){

    /* Expand the value of PI */

     jx9_value_double(pValue,3.1415926535898);

}


As you can see, the PI_Constant() is a simple C procedure that expand the value of PI using the jx9_value_double() interface.


Another more complex constant is the __TIME__ constant which expands to the current local time, here is the C procedure associated with it:


#include <time.h>

void TIME_Constant(jx9_value *pValue,void *pUserData)

{

   struct tm *pLocal;

   time_t tt;

   /* Get the current local time */

   time(&tt);

   pLocal = localtime(&tt);

   /* Expand the current time */

   jx9_value_string_format(pValue,

"%02d:%02d:%02d",

                   pLocal->tm_hour,

                   pLocal->tm_min,

                   pLocal->tm_sec

              );

}


We get the current local time using the libc localtime() routine, then we expand a printf() like formatted string holding the current time using the jx9_value_string_format() interface.


A final example with the __OS__ constant which expands to the name of the host Operating System.


void OS_Constant(jx9_value *pValue,void *pUserData)

{

  #ifdef __WINNT__

     jx9_value_string(pValue,"Windows",-1 /*Compute input length automatically */);

  #else

    /* Assume UNIX */

    jx9_value_string(pValue,"UNIX",-1 /*Compute input length automatically */);

  #endif /* __WINNT__ */

}

We use jx9_value_string() to expand the name of the host OS. Of course a more serious implementation would call uname() rather than using #ifdef macros. This is what the real built-in JX9_OS constant does.

Test The Constant Expansion Mechanism

Now, we have implemented our constants expansion procedures, it's time to test the expansion mechanism. For that we will create a simple JX9 program that invokes each of the created constants (__PI__, __TIME__ and __OS__) and displays their expanded values. Here is, the JX9 program:


    print '__PI__ value: ' .. /* '..' is the concatenation operator  */ __PI__ .. JX9_EOL;
    print
'__TIME__ value: ' .. __TIME__ .. JX9_EOL;

    print
'__OS__ value: ' .. __OS__ .. JX9_EOL;


When running, you should see something like that:


__PI__ value: 3.1415926535898

__TIME__ value: 15:02:27

__OS__ value: UNIX


Now, the main program. Note that you can get a working version of this program here


  1. int main(void)

  2. {

  3.    jx9 *pEngine;    /* JX9 engine handle */

  4.    jx9_vm *pVm;  /* Compiled JX9 program */

  5.    int rc;


  6.    /* Allocate a new JX9 engine instance */

  7.   rc = jx9_init(&pEngine);


  8.  if( rc != JX9_OK ){

  9.     Fatal("Error while allocating a new JX9 engine instance");

  10.   }



  11.   /* Compile the JX9 test program defined above */

  12.   rc = jx9_compile(

  13.            pEngine,       /* JX9 engine */

  14.            JX9_PROG,   /* JX9 test program defined above */

  15.            -1                /* Compute input length automatically*/,

  16.            &pVm           /* OUT: Compiled JX9 program */

  17.        );




  18.   if( rc != JX9_OK ){

  19.       if( rc == JX9_COMPILE_ERR ){

  20.         const char *zErrLog;

  21.         int nLen;

  22.         /* Extract error log */

  23.        jx9_config(pEngine,

  24.           JX9_CONFIG_ERR_LOG,

  25.           &zErrLog,

  26.           &nLen

  27.        );

  28.       if( nLen > 0 ){

  29.          /* zErrLog is null terminated */

  30.          puts(zErrLog);

  31.     }

  32.   }

  33.    /* Exit */

  34.    Fatal("Compile error");

  35.   }




  36.   /* Now register our constants and their associated C procedure */

  37.   rc = jx9_create_constant(pVm,"__PI__",PI_Constant,0);

  38.   if( rc != JX9_OK ){

  39.      Fatal("Error while installing the __PI__ constant");

  40.    }

  41.  

  42.   rc = jx9_create_constant(pVm,"__TIME__",TIME_Constant,0);

  43.   if( rc != JX9_OK ){

  44.      Fatal("Error while installing the __TIME__ constant");

  45.    }


  46.   rc = jx9_create_constant(pVm,"__OS__",OS_Constant,0);

  47.   if( rc != JX9_OK ){

  48.      Fatal("Error while installing the __OS__ constant");

  49.    }



  50.   /*

  51.    * Configure our VM:

  52.    * Install the VM output consumer callback defined above.

  53.    */

  54.    rc = jx9_vm_config(pVm,

  55.     JX9_VM_CONFIG_OUTPUT,

  56.      Output_Consumer, /* Output Consumer callback */

  57.       0 /* Callback private data */

  58.    );

  59.   if( rc != JX9_OK ){

  60.      Fatal("Error while installing the VM output consumer callback");

  61.   }

  62.   


  63. /*

  64.    * And finally, execute our program. Note that your output

  65.   * (STDOUT in our case) should display the result.

  66.    */

  67.    jx9_vm_exec(pVm,0);



  68.    /* All done, cleanup the mess left behind.

  69.    */

  70.   jx9_vm_release(pVm);

  71.   jx9_release(pEngine);


  72.   return 0;

  73.  }

Download the C file.

We create a new JX9 engine instance using a call to jx9_init() on line 8. This is often the first JX9 API call that an application makes and is a prerequisite in order to compile JX9 code using one of the compile interfaces.


We compile our JX9 test program on line 16 using the jx9_compile() interface.


We register our defined constants (__PI__, __TIME__ and __OS__) and their associated C procedures (PI_Constant(), TIME_Constant() and OS_Constant()) respectively on line 47, 52 and 57 using the jx9_create_constant() interface.


We configure our Virtual Machine on line 67 by setting a VM output consumer callback named Output_Consumer() (Download the C file to see the implementation) which redirect the VM output to STDOUT.


And finally we execute our JX9 program on line 81 using a call to jx9_vm_exec(). Your VM output should look like this:


__PI__ value: 3.1415926535898

__TIME__ value: 15:02:27

__OS__ value: UNIX


Clean-up is done on line 86 and 87 respectively via calls to jx9_vm_release() and jx9_release().

What to do next

As you can see, the constant expansion mechanism under JX9 is extremely powerful yet simple and it involves only a single call to jx9_create_constant().

The developer who understands this mechanism will have a good foundation on using the in-process extending JX9 interfaces. Now it's time to learn the more complex foreign functions creation mechanism.

Other useful links

Check out the Introduction To The JX9 C/C++ Interface for an introductory overview and roadmap to the dozens of JX9 interface functions.


A separate document, The JX9 C/C++ Interface, provides detailed specifications for all of the various C/C++ APIs for JX9. Once the reader understands the basic principles of operation for JX9, that document should be used as a reference guide.


Any questions, check the Frequently Asked Questions page or visit the Support Page for more information.


Symisc Systems
Copyright © Symisc Systems