The PSL Application Programmer's Guide.

By Steve Baker

Introduction

This document is to help people writing C++ applications to include scripting abilities using the PSL interpreter.

To include PSL scripts into your application requires that you:


   #include <plib/psl.h>

...and link with '-lplibpsl'

Then, sometime before you call any other PSL functions, you must call:


   pslInit () ;

Each PSL script is represented by an object of class pslProgram

Example Application

This is the simplest possible PSL application, it loads a script called "test.psl", compiles and runs it.

  #include <plib/psl.h>

  void main ()
  {
    pslInit () ;
    pslExtension extensions [] = { { NULL, 0, NULL } } ;
    pslProgram *prog = new pslProgram ( extensions, "Program1" ) ;
    prog -> compile ( "test.psl" ) ;
    while ( prog -> step () != PSL_PROGRAM_END ) /* Nothing */ ;
  }

Here is a line-by-line explanation:

pslInit () ; Initialise PSL - this must be the first thing you do.
pslExtension extensions [] ... Make a list of PSL extension functions.
This program doesn't have any.
pslProgram *prog = new pslProgram... Declare a program (called "MyProgram").
prog -> compile ( "test.psl" ) ; Compile the program to bytecode.
while ( prog -> step () != PSL_PROGRAM_END )... Tell the PSL program to execute one 'step'
Keep doing that until we reach the end of the program.

Easy!

Now let's look at class pslProgram in more detail.

class pslProgram

Each PSL script is represented by an object of class pslProgram.

  class pslProgram
  {
  public:

    pslProgram ( const pslExtension *extns, const char *name ) ;

    pslProgram ( pslProgram         *prog,  const char *name ) ;


    void  setUserData ( void *data ) ;
    void *getUserData () const ;

    void  setProgName ( const char *name ) ;
    char *getProgName () const ;

    void      dump  () const ;
    void      reset () ;
    pslResult step  () ;
    pslResult trace () ;

    int compile ( const char *memptr, const char *fname ) ;
    int compile ( const char *fname ) ;
    int compile ( FILE *fd ) ;
  } ;

pslProgram::pslProgram

There are two constructor functions to choose from. The first takes an array of 'extension functions' and the name by which this program will be known (for error messages and such). The second constructor takes an existing, compiled PSL program and makes a copy of it - it too needs a program name.

The second version of the constructor is especially efficient because this enables the script to be compiled just once - and run multiple times in parallel. The second and subsequent copies of the program consume much less RAM than the first copy because they share the 'code' part of the script.

For example, you can run two copies of a script in parallel like this:


  pslInit () ;
 
  pslProgram *prog_1 = new pslProgram ( extensions, "code1" ) ;
  prog_1 -> compile ( "data/test.psl" ) ;
 
  pslProgram *prog_2 = new pslProgram ( prog_1, "code2" ) ;
 
  while ( prog_1 -> step () != PSL_PROGRAM_END &&
          prog_2 -> step () != PSL_PROGRAM_END )
    /* NOTHING */ ;

pslProgram::compile

When you compile a PSL program, any errors or warnings are reported to stderr (by default) and the number of fatal compilation errors is returned as the result of the 'compile' function. Programs may choose to ignore any compilation errors - but executing the resulting program will immediately produce a PSL_PROGRAM_END.

You can pass to the compiler either:

  1. The filename of the file containing the PSL source code...or...
  2. A 'FILE *' descriptor for the file containing the PSL source...or...
  3. The address of a null terminated string containing the program source PLUS a name to use for the program when reporting errors, etc.
If you pass the filename (1) or the address of the source with a name (3), then error messages from PSL will refer to that name. But if you pass a file destriptor then the program name that you passed to the constructor function will be reported.

Applications that would like to report scripting errors in a more elegant way, may register a callback function that will be called whenever there is a problem within PSL:


  void pslSetErrorCallback ( void (*CB) ( pslProgram *, int, char *,
                                                        int, char * ) ) ;         
Your function will be called with five parameters:

  void myErrorCB ( pslProgram *prog, int severity,
                   char *progname, int lineno, char *message ) ) ;         
Notice that at compiletime, the 'progname' parameter is the name of the file or string being compiled (if that's known to the compiler). Since it's possible for one PSL source file to '#include' another, you should always use the 'progName' parameter in your error messages in preference to prog->getProgName() member function.

pslExtension

We have not yet talked about this mysterious 'extensions' array that's passed into the pslProgram constructor function.

It's important that your PSL scripts are able to interact with your C++ program - and this is done by creating a number of C++ functions that can be called by the PSL program as it executes. These are called 'extensions' because they extend the functionality of PSL.

The extension parameter to the pslProgram constructor is an array of pslExtension structures:


  class pslExtension
  {
  public:

    const char *symbol ;
    int   argc ;
    pslValue (*func) ( int, pslValue *, pslProgram *p ) ;
  } ;
                                                                               
The list of pslExtensions is terminated by a { NULL, 0, NULL} entry.

The C++ function has to look like this:


  pslValue my_func ( int argc, pslValue *argv, pslProgram *p )

Example Extension Function

This C++ function prints it's arguments to stdout and returns the value 123.456.
 
  pslValue print ( int argc, pslValue *argv, pslProgram *p )
  {
    for ( int i = 0 ; i < argc ; i++ )
    {
      switch ( argv[i].getType () )
      {
	case PSL_INT    : printf ( "%d ", argv[i].getInt    () ) ; break ;
	case PSL_FLOAT  : printf ( "%f ", argv[i].getFloat  () ) ; break ;
	case PSL_STRING : printf ( "%s ", argv[i].getString () ) ; break ;
	case PSL_VOID   : printf ( "(void) " ) ; break ;
	default : printf ( "Illegal parameter passed to 'print'." ) ; break ;
      }
    }
   
    pslValue ret ;
    ret.set ( 123.456f ) ;
    return ret ;
  }

Adding this to the example program above, requires only that you change the declaration of 'extensions' as follows:

  pslExtension extensions [] =
  {
    { "print", -1, print },
    { NULL, 0, NULL }
  } ;

Now, now you can write "Hello World" in PSL script:

  int main ()
  {
    print ( "Hello World.\n" ) ;
  }

...and your C++ function 'print' will be called with one parameter that'll contain the string value "Hello World.\n".

pslValue

The 'pslValue' class is used to pass numbers into and out of extension functions. It contains the type and value of a number or string in PSL and it looks like this:

  class pslValue
  {
  public:

    pslType getType   () const ;

    int     getInt    () const ;
    float   getFloat  () const ;
    char   *getString () const ;

    void set () ;
    void set ( int   v ) ;
    void set ( float v ) ;
    void set ( const char      *v ) ;
    void set ( const pslNumber *v ) ;
  } ;                                                                            

Setting nothing into your pslValue ("my_value->set()") is used to return a 'void' result from your extension function - that is the default type for a pslValue.

The 'getType' call returns the type of this value - currently, it can be: PSL_INT, PSL_FLOAT, PSL_STRING or PSL_VOID. If you 'set' the pslValue, it automatically changes it's 'getType' result to match.

Doing a 'get' for a type that DOESN'T match the 'getType' of the pslValue causes it to try to convert to that type. However, a 'getString' on a non-string pslValue will return NULL. Doing a 'getInt' or 'getFloat' on a PSL_STRING will perform an atoi() or atof() (respectively) in an attempt to get a number from the string.

Running, Tracing, Debugging.

You run the PSL program one byte-code instruction at a time by calling the pslProgram::step() function. A byte-code instruction is rather like the 'machine code' of a physical computer and it typically takes several byte-code instructions to implement each line of PSL source code.

The 'step' function returns one of three possible results:

The PSL_PROGRAM_PAUSE return is intended to cope with the specific case when PSL is being used in an interactive graphical application. Typically, such applications will not want to run PSL scripts to completion every frame - but instead run them up to the next 'pause' statement.

A typical game might have dozens of PSL scripts running in parallel and wish to run each of them until the script 'pause's.

So, your application's main loop might look something like this:


  read_the_joystick () ;

  for ( int i = 0 ; i < num_scripts ; i++ )
    while ( program [ i ] -> step () == PSL_PROGRAM_CONTINUE )
      /* Do Nothing */ ;

  render_the_graphics () ;
  swap_the_doublebuffer () ;

Then, one of those scripts (to move a monster for example) might look like this:

   int main ()
   {
     int i = getMyMonster () ;

     while ( 1 )
     {
       moveMonster ( i ) ;
       pause ;
     }
   }

The 'pause' command indicates that this script has completed it's work for this frame.

Alternatively, some applications may wish to run the scripts for fixed amounts of time, fixed numbers of byte-codes - or until some other criterion is satisfied.

Resetting a Script

There is a pslProgram::reset() function that restarts the PSL program from the beginning having first reset all of its internal variables.

Debugging PSL scripts

For debugging PSL scripts, you may replace the 'step' call with 'trace' - which causes the byte-code for each instruction to be printed to stderr as it's executed.

You can also call pslProgram::dump() to print out all of the byte-code and the PSL symbol table for the program.

Include paths.

By default, PSL searches for files with relative pathnames in the current directory - but you can override this by setting:


   pslScriptPath ( "directory" ) ;


Steve J. Baker. <sjbaker1@airmail.net>