Howto:Extend Nasal: Difference between revisions

Jump to navigation Jump to search
m
Copyediting
(→‎Intro: updated)
m (Copyediting)
Line 1: Line 1:
{{Template:Nasal Internals}}
{{Template:Nasal Internals}}
This article is dedicated to describing how to write custom C/C++ extension functions in order to extend the [[Nasal]] scripting interpreter in FlightGear, for example in order to expose new or existing FlightGear APIs to the Nasal scripting engine, so that Nasal scripts can access additional FlightGear internals.
<!-- Some interesting ideas for extending Nasal this way have been collected at [[Proposals:Nasal related]].  Article was deleted 1 July 2014. /Johan G, 12 December 2014 -->


== Status (cppbind) ==
== Status (cppbind) ==
Line 23: Line 24:
For more technical Nasal questions (C API, internals etc), you'll probably want to refer to the forum, in particular: Philosopher, TheTom, Zakalawe or Hooray - TheTom and Zakalawe can also provide help on using cppbind, having both used it extensively during the last months.
For more technical Nasal questions (C API, internals etc), you'll probably want to refer to the forum, in particular: Philosopher, TheTom, Zakalawe or Hooray - TheTom and Zakalawe can also provide help on using cppbind, having both used it extensively during the last months.


== Scope ==
== Pre-requisites ==
This article is dedicated to describing how to write custom C/C++ extension functions in order to extend the [[Nasal]] scripting interpreter in FlightGear, for example in order to expose new or existing FlightGear APIs to the Nasal scripting engine, so that Nasal scripts can access additional FlightGear internals.
 
Some interesting ideas for extending Nasal this way have been collected at [[Proposals:Nasal related]].
 
= Pre-Requisites =
In order to work through this article, you will likely need to be interested in FlightGear core development, need to be somewhat familiar with C/C++, as well as with some basic Nasal (given that Nasal itself is implemented in ANSI C, basic C knowledge will mostly do for starters-C++ knowledge will only be required in order to understand the FlightGear side of things).
In order to work through this article, you will likely need to be interested in FlightGear core development, need to be somewhat familiar with C/C++, as well as with some basic Nasal (given that Nasal itself is implemented in ANSI C, basic C knowledge will mostly do for starters-C++ knowledge will only be required in order to understand the FlightGear side of things).


Line 35: Line 31:
In addition, you may want to check out the [http://www.plausible.org/nasal/flightgear.html FlightGear Integration Docs].
In addition, you may want to check out the [http://www.plausible.org/nasal/flightgear.html FlightGear Integration Docs].


= Nasal =
== Nasal ==
Introductory information on the Nasal programming language itself can be found at [[Nasal scripting language]]. Information on writing simple Nasal scripts can be found at [[Howto: Write simple scripts in Nasal]]. Useful Nasal snippets can be found at [[Nasal Snippets]]. A Nasal Style Guide is available at [[Nasal Style Guide]] (Note that as of 05/2009, this is work in progress).
Introductory information on the Nasal programming language itself can be found at [[Nasal scripting language]]. Information on writing simple Nasal scripts can be found at [[Howto: Write simple scripts in Nasal]]. Useful Nasal snippets can be found at [[Nasal Snippets]]. <!-- A Nasal Style Guide is available at [[Nasal Style Guide]] (Note that as of 05/2009, this is work in progress). Was deleted 9 April 2013 due to being a skeleton stub for years.  /Johan G, 13 dec 2014 -->


If you have any Nasal specific questions, you will want to check out the [[Nasal FAQ]], feel free to ask new questions or help answer and refine existing ones.
If you have any Nasal specific questions, you will want to check out the [[Nasal FAQ]], feel free to ask new questions or help answer and refine existing ones.
Line 42: Line 38:
All Nasal related articles can be found in the [http://wiki.flightgear.org/index.php/Category:Nasal Nasal category].
All Nasal related articles can be found in the [http://wiki.flightgear.org/index.php/Category:Nasal Nasal category].


'''Note:''' FlightGear's version of the Nasal interpreter is maintained in the [http://www.simgear.org SimGear] git repository, inside the {{Git file|gitorious|fg/simgear|next|simgear/nasal|pre=$SG_SRC/}} folder, the most important header file detailing the internal Nasal API is "[http://simgear.org/doxygen/nasal_8h-source.html nasal.h]", you will want to check this out for the latest changes and information.
{{Note|FlightGear's version of the Nasal interpreter is maintained in the [http://www.simgear.org SimGear] git repository, inside the {{Git file|gitorious|fg/simgear|next|simgear/nasal|pre=$SG_SRC/}} folder, the most important header file detailing the internal Nasal API is "[http://simgear.org/doxygen/nasal_8h-source.html nasal.h]", you will want to check this out for the latest changes and information.}}


You will probably also want to check out the {{Git file|gitorious|fg/simgear|next|simgear/nasal|pre=$SG_SRC/}} folder for specific examples on using the various Nasal APIs that are not yet covered here completely.
You will probably also want to check out the {{Git file|gitorious|fg/simgear|next|simgear/nasal|pre=$SG_SRC/}} folder for specific examples on using the various Nasal APIs that are not yet covered here completely.


'''Important:''' As of 05/2009, this article is work in progress, and none of the examples have so far been tested/compiled.  
{{caution|As of 05/2009, this article is work in progress, and ''none'' of the examples have so far been tested/compiled.  
Your help in improving and updating this article is appreciated, thanks!
Your help in improving and updating this article is appreciated, thanks!}}


Another, simpler, option to add new C++ code to FlightGear is illustrated here: [[Howto:Add new fgcommands to FlightGear]].
Another, simpler, option to add new C++ code to FlightGear is illustrated here: [[Howto:Add new fgcommands to FlightGear]].


= Intro =
== Intro ==
In FlightGear, the simplest way to add new extension functions is to look at the existing functions in {{Git file|gitorious|fg/flightgear|next|src/Scripting/NasalSys.cxx|747|pre=$FG_SRC/}}.
In FlightGear, the simplest way to add new extension functions is to look at the existing functions in {{Git file|gitorious|fg/flightgear|next|src/Scripting/NasalSys.cxx|747|pre=$FG_SRC/}}.


Line 91: Line 87:
So, the basic format is "name" (string), function_pointer - whereas "name" refers to the internal name used by Nasal and its scripts, and "function_pointer" has to use the right function signature and is a pointer to the implementation of the Nasal function in C/C++ code space:
So, the basic format is "name" (string), function_pointer - whereas "name" refers to the internal name used by Nasal and its scripts, and "function_pointer" has to use the right function signature and is a pointer to the implementation of the Nasal function in C/C++ code space:


<syntaxhighlight lang="c">
<syntaxhighlight lang="cpp">
  // The function signature for an extension function:
  // The function signature for an extension function:
  typedef naRef (*naCFunction)(naContext ctx, naRef me, int argc, naRef* args);
  typedef naRef (*naCFunction)(naContext ctx, naRef me, int argc, naRef* args);
Line 100: Line 96:
If your extension functions are likely to be fairly low level, and will thus be provided with a more abstract wrapper in Nasal space, these functions should use a prepended undercore ("_"), such as the _fgcommand, _setlistener, _cmdarg and _interpolate functions.
If your extension functions are likely to be fairly low level, and will thus be provided with a more abstract wrapper in Nasal space, these functions should use a prepended undercore ("_"), such as the _fgcommand, _setlistener, _cmdarg and _interpolate functions.


= Extension Function Signature =
== Extension function signature ==
These extension functions look like:
These extension functions look like:


<syntaxhighlight lang="c">
<syntaxhighlight lang="cpp">
   static naRef f_your_function(naContext c, naRef me, int argc, naRef* args)
   static naRef f_your_function(naContext c, naRef me, int argc, naRef* args)
   {
   {
Line 114: Line 110:
If you don't have anything else to return, you can just return naNil(), so that a function named "f_cool" looks like this:
If you don't have anything else to return, you can just return naNil(), so that a function named "f_cool" looks like this:


<syntaxhighlight lang="c">
<syntaxhighlight lang="cpp">
   static naRef f_cool (naContext c, naRef me, int argc, naRef* args)
   static naRef f_cool (naContext c, naRef me, int argc, naRef* args)
   {
   {
Line 124: Line 120:
In order to make this function known to the Nasal interpreter, you'll want to extend the previously mentioned list of Nasal extension functions (in src/Scripting/NasalSys.cxx), to read:
In order to make this function known to the Nasal interpreter, you'll want to extend the previously mentioned list of Nasal extension functions (in src/Scripting/NasalSys.cxx), to read:


<syntaxhighlight lang="cpp">
   // ....
   // ....
     { "airportinfo", f_airportinfo },
     { "airportinfo", f_airportinfo },
Line 129: Line 126:
     { 0, 0 }
     { 0, 0 }
   };
   };
</syntaxhighlight>


Once you have made these modifications and rebuilt FlightGear (running make in the Scripting sub folder and relinking FG should suffice), you can simply invoke your new function, of course it will not yet do anything useful, though:
Once you have made these modifications and rebuilt FlightGear (running make in the Scripting sub folder and relinking FG should suffice), you can simply invoke your new function, of course it will not yet do anything useful, though:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
  #cooltest.nas (to be saved in $FG_ROOT/Nasal or run via the Nasal console)
  #cooltest.nas (to be saved in $FG_ROOT/Nasal or run via the Nasal console)
  cool();
  cool();
Line 139: Line 137:
So, in order to make it slightly more interesting, we are going to change the return statement to return something else, instead of nil:
So, in order to make it slightly more interesting, we are going to change the return statement to return something else, instead of nil:


<syntaxhighlight lang="c">
<syntaxhighlight lang="cpp">
   static naRef f_cool (naContext c, naRef me, int argc, naRef* args)
   static naRef f_cool (naContext c, naRef me, int argc, naRef* args)
   {
   {
Line 151: Line 149:
So, after rebuilding the FlightGear binary, whenever you call the new "cool" API function, it will always return a "cool" string, which you could for example print out using a script like the following:
So, after rebuilding the FlightGear binary, whenever you call the new "cool" API function, it will always return a "cool" string, which you could for example print out using a script like the following:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
  #cooltest.nas (to be saved in $FG_ROOT/Nasal)
  #cooltest.nas (to be saved in $FG_ROOT/Nasal)
  var result=cool();
  var result=cool();
Line 157: Line 155:
</syntaxhighlight>
</syntaxhighlight>


= Argument Processing =
== Argument processing ==
Consider the callback signature:
Consider the callback signature:
<syntaxhighlight lang="c">
<syntaxhighlight lang="cpp">
  static naRef f_cool (naContext c, naRef me, int argc, naRef* args)
  static naRef f_cool (naContext c, naRef me, int argc, naRef* args)
</syntaxhighlight>
</syntaxhighlight>
Line 167: Line 165:
So, if you know that you require a certain number of arguments, you can also directly check argc to to match your requirements and show an error message, return nil, or throw an exception using naRuntimeError():
So, if you know that you require a certain number of arguments, you can also directly check argc to to match your requirements and show an error message, return nil, or throw an exception using naRuntimeError():


<syntaxhighlight lang="c">
<syntaxhighlight lang="cpp">
  // Throw an error from the current call stack.  This function makes a
  // Throw an error from the current call stack.  This function makes a
  // longjmp call to a handler in naCall() and DOES NOT RETURN.  It is
  // longjmp call to a handler in naCall() and DOES NOT RETURN.  It is
Line 184: Line 182:
Basically, you can check the type of the reference with the following naIs*() functions:
Basically, you can check the type of the reference with the following naIs*() functions:


<syntaxhighlight lang="c">
<syntaxhighlight lang="cpp">
  int naIsNil(naRef r);
  int naIsNil(naRef r);
  int naIsNum(naRef r);
  int naIsNum(naRef r);
Line 195: Line 193:
  int naIsCCode(naRef r);
  int naIsCCode(naRef r);
</syntaxhighlight>
</syntaxhighlight>
And then get the appropriate value out with things like naNumValue(), naStr_data(), etc...
And then get the appropriate value out with things like naNumValue(), naStr_data(), etc...


So, if we're now to change the interface of our earlier "cool" function in the example, to only return "cool" if we are passed at least one numerical parameter, and otherwise return "uncool", it could look like this:
So, if we're now to change the interface of our earlier "cool" function in the example, to only return "cool" if we are passed at least one numerical parameter, and otherwise return "uncool", it could look like this:


<syntaxhighlight lang="c">
<syntaxhighlight lang="cpp">
  static naRef f_cool (naContext c, naRef me, int argc, naRef* args)
  static naRef f_cool (naContext c, naRef me, int argc, naRef* args)
  {
  {
Line 216: Line 215:
You can test this, by running a simple Nasal script like the following:
You can test this, by running a simple Nasal script like the following:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
  # test new cool() function:
  # test new cool() function:
  print( cool("foo") );  
  print( cool("foo") );  
Line 224: Line 223:
A common way to evaluate parameters passed to Nasal extension functions in C, looks like this:
A common way to evaluate parameters passed to Nasal extension functions in C, looks like this:


<syntaxhighlight lang="c">
<syntaxhighlight lang="cpp">
  static naRef f_foo (naContext c, naRef me, int argc, naRef* args) {
  static naRef f_foo (naContext c, naRef me, int argc, naRef* args) {
     naRef param1 = argc > 0 ? args[0] : naNil();
     naRef param1 = argc > 0 ? args[0] : naNil();
Line 236: Line 235:
This will basically look for 3 parameters, and assign them accordingly - if they are not available, they will just be assigned nil using the naNil() calls (the next step would be to check if the parameters have the right types using the naIs* helpers mentioned above).
This will basically look for 3 parameters, and assign them accordingly - if they are not available, they will just be assigned nil using the naNil() calls (the next step would be to check if the parameters have the right types using the naIs* helpers mentioned above).


= Creating Nasal Data Structures =
== 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:
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:


<syntaxhighlight lang="c">
<syntaxhighlight lang="cpp">
  naRef naNil();
  naRef naNil();
  naRef naNum(double num);
  naRef naNum(double num);
Line 249: Line 248:
</syntaxhighlight>
</syntaxhighlight>


== String Manipulation API ==
=== String manipulation API ===
<syntaxhighlight lang="cpp">
  // String utilities:
  // String utilities:
  int naStr_len(naRef s);
  int naStr_len(naRef s);
Line 257: Line 257:
  naRef naStr_substr(naRef dest, naRef str, int start, int len);
  naRef naStr_substr(naRef dest, naRef str, int start, int len);
  naRef naInternSymbol(naRef sym);
  naRef naInternSymbol(naRef sym);
</syntaxhighlight>


 
=== Vector manipulation API ===
== Vector Manipulation API ==
<syntaxhighlight lang="cpp">
  // Vector utilities:
  // Vector utilities:
  int naVec_size(naRef v);
  int naVec_size(naRef v);
Line 267: Line 268:
  naRef naVec_removelast(naRef vec);
  naRef naVec_removelast(naRef vec);
  void naVec_setsize(naRef vec, int sz);
  void naVec_setsize(naRef vec, int sz);
</syntaxhighlight>


 
=== Hash manipulation API ===
== Hash Manipulation API ==
<syntaxhighlight lang="cpp">
  // Hash utilities:
  // Hash utilities:
  int naHash_size(naRef h);
  int naHash_size(naRef h);
Line 284: Line 286:
  int naMember_get(naRef obj, naRef field, naRef* out);
  int naMember_get(naRef obj, naRef field, naRef* out);
  int naMember_cget(naRef obj, const char* field, naRef* out);
  int naMember_cget(naRef obj, const char* field, naRef* out);
</syntaxhighlight>


A common way to set up hash field members easily, is using a simple macro like:
A common way to set up hash field members easily, is using a simple macro like:


<syntaxhighlight lang="C">
<syntaxhighlight lang="cpp">
#define HASHSET(s,l,n) naHash_set(naHash, naStr_fromdata(naNewString(c),s,l),n)
#define HASHSET(s,l,n) naHash_set(naHash, naStr_fromdata(naNewString(c),s,l),n)
//... do your hash setup here
//... do your hash setup here
Line 299: Line 302:
The macro can be even further simplified by automatically computing the length of the hash key using strlen() instead of the explicit length argument:
The macro can be even further simplified by automatically computing the length of the hash key using strlen() instead of the explicit length argument:


<syntaxhighlight lang="C">
<syntaxhighlight lang="cpp">
#define HASHSET(s,n) naHash_set(naHash, naStr_fromdata(naNewString(c),s,strlen(s)),n)
#define HASHSET(s,n) naHash_set(naHash, naStr_fromdata(naNewString(c),s,strlen(s)),n)
//... do your hash setup here
//... do your hash setup here
Line 305: Line 308:
</syntaxhighlight>
</syntaxhighlight>


= Conversion & Comparison routines =
== Conversion & comparison routines ==
Some useful conversion/comparison routines include:
Some useful conversion/comparison routines include:


<syntaxhighlight lang="cpp">
  int naEqual(naRef a, naRef b);
  int naEqual(naRef a, naRef b);
  int naStrEqual(naRef a, naRef b);
  int naStrEqual(naRef a, naRef b);
Line 313: Line 317:
  naRef naNumValue(naRef n);
  naRef naNumValue(naRef n);
  naRef naStringValue(naContext c, naRef n);
  naRef naStringValue(naContext c, naRef n);
</syntaxhighlight>


= Wrapping C++ Classes as Nasal Objects (cppbind) =
== Wrapping C++ classes as Nasal objects with cppbind ==
Please see: {{Git file|gitorious|fg/simgear|next|simgear/nasal/cppbind|pre=$SG_SRC/}}
Please see: {{Git file|gitorious|fg/simgear|next|simgear/nasal/cppbind|pre=$SG_SRC/}}
<!--
<!--
Line 322: Line 327:
Also, the implementation of the SGPropertyNode bindings in [https://gitorious.org/fg/flightgear/blobs/next/src/Scripting/nasal-props.cxx nasal-props.cxx] contains additional examples.
Also, the implementation of the SGPropertyNode bindings in [https://gitorious.org/fg/flightgear/blobs/next/src/Scripting/nasal-props.cxx nasal-props.cxx] contains additional examples.
-->
-->
= Passing Pointers to Nasal scripts =
== Passing pointers 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.
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:
Ghost utilities:


<syntaxhighlight lang="c">
<syntaxhighlight lang="cpp">
  typedef struct naGhostType {
  typedef struct naGhostType {
     void(*destroy)(void*);
     void(*destroy)(void*);
Line 342: Line 347:
For an example of how something like this is done in the FlightGear context, you may want to check out $FG_SRC/Scripting/nasal-props.cxx, which wraps the SGPropertyNode class ([[SimGear]]) inside a nasal ghost, so that the C++ class is exposed to Nasal scripts.
For an example of how something like this is done in the FlightGear context, you may want to check out $FG_SRC/Scripting/nasal-props.cxx, which wraps the SGPropertyNode class ([[SimGear]]) inside a nasal ghost, so that the C++ class is exposed to Nasal scripts.


= Locking a Nasal context (threading) =
== Locking a Nasal context (threading) ==
<syntaxhighlight lang="cpp">
  // Acquires a "modification lock" on a context, allowing the C code to
  // Acquires a "modification lock" on a context, allowing the C code to
  // modify Nasal data without fear that such data may be "lost" by the
  // modify Nasal data without fear that such data may be "lost" by the
Line 364: Line 370:
  void naModLock();
  void naModLock();
  void naModUnlock();
  void naModUnlock();
</syntaxhighlight>


= Things to avoid =
== 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.
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:
There is a minimal API to handle this:


<syntaxhighlight lang="cpp">
  // "Save" this object in the context, preventing it (and objects
  // "Save" this object in the context, preventing it (and objects
  // referenced by it) from being garbage collected.
  // referenced by it) from being garbage collected.
  void naSave(naContext ctx, naRef obj);
  void naSave(naContext ctx, naRef obj);
 
</syntaxhighlight>


But this API doesn't yet have a corresponding call to release a saved object. Just don't do this, basically. Always keep your Nasal data in Nasal space.
But this API doesn't yet have a corresponding call to release a saved object. Just don't do this, basically. Always keep your Nasal data in Nasal space.
Line 379: Line 387:
If you really need to do this, you may however want to check out the gcSave/gcRelease methods of the FGNasalSys class:
If you really need to do this, you may however want to check out the gcSave/gcRelease methods of the FGNasalSys class:


<syntaxhighlight lang="cpp">
     // This mechanism is here to allow naRefs to be passed to
     // This mechanism is here to allow naRefs to be passed to
     // locations "outside" the interpreter.  Normally, such a
     // locations "outside" the interpreter.  Normally, such a
Line 387: Line 396:
     int gcSave(naRef r);
     int gcSave(naRef r);
     void gcRelease(int key);
     void gcRelease(int key);
</syntaxhighlight>


[[Category:Core developer documentation]]
[[Category:Core developer documentation]]
[[Category:Nasal howto|Extend Nasal]]
[[Category:Nasal howto|Extend Nasal]]

Navigation menu