Howto:Extend Nasal

Revision as of 02:26, 16 May 2009 by MILSTD (talk | contribs) (even more Nasal docs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
This article is a stub. You can help the wiki by expanding it.

This article is dedicated to describing how to write custom extension functions in order to extend the Nasal interpreter in FlightGear, for example in order to expose new FlightGear APIs to Nasal, so that scripts can access more FlightGear internals.

Intro

The simplest way to do this is to look at the existing functions in $FG_SRC/Scripting/NasalSys.cxx (src/Scripting/NasalSys.cxx).

There is a static table of function pointers referencing extension functions:

 // Table of extension functions.  Terminate with zeros.
 static struct { const char* name; naCFunction func; } funcs[] = {
   { "getprop",   f_getprop },
   { "setprop",   f_setprop },
   { "print",     f_print },
   { "_fgcommand", f_fgcommand },
   { "settimer",  f_settimer },
   { "_setlistener", f_setlistener },
   { "removelistener", f_removelistener },
   { "_cmdarg",  f_cmdarg },
   { "_interpolate",  f_interpolate },
   { "rand",  f_rand },
   { "srand",  f_srand },
   { "abort", f_abort },
   { "directory", f_directory },
   { "parsexml", f_parsexml },
   { "systime", f_systime },
   { "carttogeod", f_carttogeod },
   { "geodtocart", f_geodtocart },
   { "geodinfo", f_geodinfo },
   { "airportinfo", f_airportinfo },
   { 0, 0 }
 };

Extension Function Signature

These extension functions look like:

 static naRef f_your_function(naContext c, naRef me, int argc, naRef* args)
 {
     // ...
 }

If you don't have anything else to return, you can just return naNil().

Argument Processing

The arguments to the function are passed in in the args array. The "me" reference is set if the function was called as a method call on an object (e.g. object.your_function() instead of just your_function(), in which case "me" would be set to the object).

The naRef objects can be manipulated using the functions in nasal.h.

Basically, you can check the type of the reference with the following naIs*() functions:

int naIsNil(naRef r);
int naIsNum(naRef r);
int naIsString(naRef r);
int naIsScalar(naRef r);
int naIsVector(naRef r);
int naIsHash(naRef r);
int naIsCode(naRef r);
int naIsFunc(naRef r);
int naIsCCode(naRef r);

And then get the appropriate value out with things like naNumValue(), naStr_data(), etc...

Creating Nasal Data Structures

There are also functions to create Nasal data structures (hash tables, vectors, etc...) which you can return to the caller simply by returning the resulting naRef:

naRef naNil();
naRef naNum(double num);
naRef naNewString(naContext c);
naRef naNewVector(naContext c);
naRef naNewHash(naContext c);
naRef naNewFunc(naContext c, naRef code);
naRef naNewCCode(naContext c, naCFunction fptr);

String Manipulation API

// String utilities:
int naStr_len(naRef s);
char* naStr_data(naRef s);
naRef naStr_fromdata(naRef dst, const char* data, int len);
naRef naStr_concat(naRef dest, naRef s1, naRef s2);
naRef naStr_substr(naRef dest, naRef str, int start, int len);
naRef naInternSymbol(naRef sym);


Vector Manipulation API

// Vector utilities:
int naVec_size(naRef v);
naRef naVec_get(naRef v, int i);
void naVec_set(naRef vec, int i, naRef o);
int naVec_append(naRef vec, naRef o);
naRef naVec_removelast(naRef vec);
void naVec_setsize(naRef vec, int sz);


Hash Manipulation API

// Hash utilities:
int naHash_size(naRef h);
int naHash_get(naRef hash, naRef key, naRef* out);
naRef naHash_cget(naRef hash, char* key);
void naHash_set(naRef hash, naRef key, naRef val);
void naHash_cset(naRef hash, char* key, naRef val);
void naHash_delete(naRef hash, naRef key);
void naHash_keys(naRef dst, naRef hash);


Conversion & Comparison routines

Some useful conversion/comparison routines include:

int naEqual(naRef a, naRef b);
int naStrEqual(naRef a, naRef b);
int naTrue(naRef b);
naRef naNumValue(naRef n);
naRef naStringValue(naContext c, naRef n);

Passing C++ handles to Nasal scripts

Things get more complicated if you need to pass a handle to a C/C++ object into a Nasal script. There, you need to use a wrapped handle type called a ghost ("Garbage-collectable Handle to an OutSide Thing"), which has a callback that you need to implement to deal with what happens when the Nasal interpreter garbage collects your object.

Ghost utilities:

typedef struct naGhostType {
    void(*destroy)(void*);
    const char* name;
} naGhostType;
naRef        naNewGhost(naContext c, naGhostType* t, void* ghost);
naGhostType* naGhost_type(naRef ghost);
void*        naGhost_ptr(naRef ghost);
int          naIsGhost(naRef r);

Things to avoid

And just for completeness: things get *really* complicated if you need to *store* a naRef you got from a Nasal script in a place where the interpreter can't see it. There is a minimal API to handle this (naSave()), but it doesn't have a corrresponding call to release a saved object. Just don't do this, basically. Always keep your Nasal data in Nasal space.