OK, I had some time over the weekend to carry out my threat of hacking
the Nasal interpreter into FlightGear.  So now we have another
language to flame about.  :)

  http://www.plausible.org/andy/fg-nasal-1.0.tar.gz

  [This touches SimGear, FlightGear and the base package, so you'll
   see source, data and SimGear directories inside the tarball.]

Obviously people are going to have differing opinions on whether this
language is worth pursuing.  I've chosen to fight the other
alternatives via relentless integration.  This code actually does
useful things.  (I said "integrated", by the way, not "debugged". :)

+ Like the existing PSL engine, it can be used to define command
  bindings and get and set properties in the global tree.

And there are a few new features:

+ You can call globally defined FlightGear commands, allowing
  scriptable control over the GUI, for example.  You can even call a
  PSL command from a Nasal script if you want.  (Amusingly, you
  *can't* call a "nasal" command from a Nasal script -- there's only
  one context object in the current implementation and you'll corrupt
  your call stack.  Obviously no one would want to do this, though.)

+ You can write whole files of script code by dropping ".nas" files
  into the Nasal directory of the base backage.  These are parsed and
  interpreted at startup, and can hold any code or data you like.

And becasue Nasal pretends to be a Real Language, it has a few and
advantages (IMHO) over the PSL stuff:

+ It is much richer, semantically.  You have vectors and hashes, OOP
  syntax, inheritance, functional programming, etc...

+ You can write your code in whole source files, store data in
  persistent variables and organize it into modules with real
  namespaces.  Emacs users even have access to a mostly-working
  nasal-mode.el that I hacked together (don't laugh; I basically
  blind-copied awk-mode.el and tried to avoid changing the things I
  didn't understand).

+ The flip side is that because of the ability to abstract complicated
  stuff out into modules, the XML-side configuration can be much
  cleaner.  The existing keyboard "C" binding looks like this when
  written in PSL:
 
   <binding>
   <command>script</command>
    <script>
     int main ()
     {
       print("Longitude: ", get_property("/position/longitude-deg"), "
deg\n");
       print("Latitude: ", get_property("/position/latitude-deg"), "
deg\n");
       print("Altitude: ", get_property("/position/altitude-ft"), " ft\n");
     }
    </script>
   </binding>

  But shortens to this when you put your code into a test.nas module
  (and you don't need the silly main() declaration because Nasal
  expressions and statments are grammatically identical; but that's
  only a mild PSL misfeature):

   <binding>
     <command>nasal</command>
     <script> test.handleTestKeyEvent() </script>
   </binding>  

The rest of the world looks very similar.  The way that Nasal is
integrated mirrors the PSL engine almost exactly.  Neither touches the
core of FlightGear much at all; they just hook a new subsystem into
the global list.  SimGear is affected only in that it gets a new
directory and a new library to build.

There is some (admittedly terse) documentation on the language
available at:

  http://www.plausible.org/nasal/
  http://www.plausible.org/nasal/doc.html
  http://www.plausible.org/nasal/sample.nas

Nasal scripts have access to a small standard library of useful data
structure and math functions plus the following FlightGear-specific
ones:

+ print() - Concatenates and writes out its arguments via SG_LOG()

+ getprop() - Fetches a property from the global tree.  Will accept a
    variable-length list of arguments and append them together to form
    a property name.  This is so that you can do stuff like:
      getprop("/sim/mode/space-wars");
      getprop(CurrentGalaxy, "size-parsecs");

+ setprop() - Ditto.

+ fgcommand() - First argument specifies a FlightGear "command" name,
    second is the path of a property sub-node where you have placed
    the arguments to the command.

Over the next few days, I'm going to try to write some non-trivial
example code (the existing test.nas module is attached to this
message) and maybe code up a few enhancements.  Useful future stuff
might be:

+ A timer API, so you can register callbacks for a specified time
  and/or interval.  Almost the whole ATC subsystem could be based on
  such a mechanism, for example.

+ A "threading" API, where a script could run in a loop forever making
  blocking calls (waitfor(), or somesuch).  This kind of programming
  is wasteful and fraught with race conditions, but still a good fit
  for some problems (AI, for example).

+ Hooks into more FlightGear subsystems.  The GUI module doesn't work
  in the main property tree, so it will require just a little bit of
  work to integrate.  But raw PUI bindings might not be a bad idea
  either.

+ A "wrapped object" library for SGPropertyNode, so you can use the
  property system more like you do in C++.

+ Lots of new library functions.  What's there now is pretty basic:
  just core data structures and math functions that can't be otherwise
  done in user code.

So anyway, take a look and see what you think.

Andy

#
# Spam the console, just to wake up the user. :)
#
for(i=0; i<1000; i=i+1) {
    print("Nasal module 'test' initializing... [", i, "]");
}

#
# Inspect the globals, just to be sure they're all there
#
print("These are the symbols found in the global namespace:");
foreach(i; keys(globals)) { print("  ", i); }

#
# Define a handler for the "C" key (bound in keyboard.xml)
#
handleTestKeyEvent = func {
    handler = TestHandlers[TestHandlerIdx];
    TestHandlerIdx = TestHandlerIdx + 1;
    if(TestHandlerIdx >= size(TestHandlers)) {
        TestHandlerIdx = 0;
    }
    handler();
}

#
# ... which calls each of these functions in rotation
#
WhereAmI = func {
    print("Where I am:");
    print(" Longitude: ", getprop("/position/longitude-deg"), " deg");
    print(" Latitude: ", getprop("/position/latitude-deg"), " deg");
    print(" Altitude: ", getprop("/position/altitude-ft"), " ft");
}

PopUp = func {
    print("Popping up 'autopilot' dialog");
    setprop("/nasal/dialog-args/dialog-name", "autopilot");
    fgcommand("dialog-show", "/nasal/dialog-args");
}

## This one doesn't work.  I *think* it's a bug with dialog-close...
PopDown = func {
    print("Popping down 'autopilot' dialog");
    setprop("/nasal/dialog-args/dialog-name", "autopilot");
    fgcommand("dialog-close", "/nasal/dialog-args");
}

Pause = func {
    print("Pausing FlightGear via Nasal code...");
    setprop("/sim/freeze/master", 0);
    setprop("/sim/freeze/clock", 0);
    setprop("/sim/freeze/replay", 0);
}

Unpause = func {
    print("Un-pausing FlightGear via Nasal code...");
    setprop("/sim/freeze/master", 1);
    setprop("/sim/freeze/clock", 1);
    setprop("/sim/freeze/replay", 1);
}

TestHandlerIdx = 0;
TestHandlers = [WhereAmI, Pause, PopUp, PopDown, Unpause];
_______________________________________________
Flightgear-devel mailing list
[EMAIL PROTECTED]
http://mail.flightgear.org/mailman/listinfo/flightgear-devel

Reply via email to