The PSL Application Programmer's Guide. |
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
#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
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
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
You can pass to the compiler either:
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 ) ) ;
prog->getProgName()
member function.
pslExtension
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 ) ; } ;
{ NULL, 0, NULL}
entry.
The C++ function has to look like this:
pslValue my_func ( int argc, pslValue *argv, pslProgram *p )
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
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.
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:
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.
pslProgram::reset()
function that
restarts the PSL program from the beginning having first
reset all of its internal variables.
You can also call pslProgram::dump()
to print out
all of the byte-code and the PSL symbol table for the program.
pslScriptPath ( "directory" ) ;