]> git.mxchange.org Git - flightgear.git/blob - src/Scripting/NasalSys.cxx
Nasal: expose md5 function.
[flightgear.git] / src / Scripting / NasalSys.cxx
1
2 #ifdef HAVE_CONFIG_H
3 #  include "config.h"
4 #endif
5
6 #ifdef HAVE_WINDOWS_H
7 #include <windows.h>
8 #endif
9
10 #ifdef HAVE_SYS_TIME_H
11 #  include <sys/time.h>  // gettimeofday
12 #endif
13
14 #include <string.h>
15 #include <stdio.h>
16 #include <sys/types.h>
17 #include <sys/stat.h>
18 #include <fstream>
19 #include <sstream>
20
21 #include <simgear/nasal/nasal.h>
22 #include <simgear/props/props.hxx>
23 #include <simgear/math/sg_random.h>
24 #include <simgear/misc/sg_path.hxx>
25 #include <simgear/misc/sg_dir.hxx>
26 #include <simgear/misc/SimpleMarkdown.hxx>
27 #include <simgear/structure/commands.hxx>
28 #include <simgear/math/sg_geodesy.hxx>
29 #include <simgear/structure/event_mgr.hxx>
30 #include <simgear/debug/BufferedLogCallback.hxx>
31 #include <simgear/package/md5.h>
32
33 #include <simgear/nasal/cppbind/from_nasal.hxx>
34 #include <simgear/nasal/cppbind/to_nasal.hxx>
35 #include <simgear/nasal/cppbind/Ghost.hxx>
36 #include <simgear/nasal/cppbind/NasalHash.hxx>
37
38 #include "NasalSGPath.hxx"
39 #include "NasalSys.hxx"
40 #include "NasalSys_private.hxx"
41 #include "NasalAircraft.hxx"
42 #include "NasalModelData.hxx"
43 #include "NasalPositioned.hxx"
44 #include "NasalCanvas.hxx"
45 #include "NasalClipboard.hxx"
46 #include "NasalCondition.hxx"
47 #include "NasalHTTP.hxx"
48 #include "NasalString.hxx"
49
50 #include <Main/globals.hxx>
51 #include <Main/util.hxx>
52 #include <Main/fg_props.hxx>
53
54 using std::map;
55 using std::string;
56 using std::vector;
57
58 void postinitNasalGUI(naRef globals, naContext c);
59
60 static FGNasalSys* nasalSys = 0;
61
62 // Listener class for loading Nasal modules on demand
63 class FGNasalModuleListener : public SGPropertyChangeListener
64 {
65 public:
66     FGNasalModuleListener(SGPropertyNode* node);
67
68     virtual void valueChanged(SGPropertyNode* node);
69
70 private:
71     SGPropertyNode_ptr _node;
72 };
73
74 FGNasalModuleListener::FGNasalModuleListener(SGPropertyNode* node) : _node(node)
75 {
76 }
77
78 void FGNasalModuleListener::valueChanged(SGPropertyNode*)
79 {
80     if (_node->getBoolValue("enabled",false)&&
81         !_node->getBoolValue("loaded",true))
82     {
83         nasalSys->loadPropertyScripts(_node);
84     }
85 }
86
87 //////////////////////////////////////////////////////////////////////////
88
89
90 class TimerObj : public SGReferenced
91 {
92 public:
93   TimerObj(FGNasalSys* sys, naRef f, naRef self, double interval) :
94     _sys(sys),
95     _func(f),
96     _self(self),
97     _isRunning(false),
98     _interval(interval),
99     _singleShot(false)
100   {
101     char nm[128];
102     snprintf(nm, 128, "nasal-timer-%p", this);
103     _name = nm;
104     _gcRoot =  sys->gcSave(f);
105     _gcSelf = sys->gcSave(self);
106   }
107   
108   virtual ~TimerObj()
109   {
110     stop();
111     _sys->gcRelease(_gcRoot);
112     _sys->gcRelease(_gcSelf);
113   }
114   
115   bool isRunning() const { return _isRunning; }
116     
117   void stop()
118   {
119     if (_isRunning) {
120       globals->get_event_mgr()->removeTask(_name);
121       _isRunning = false;
122     }
123   }
124   
125   void start()
126   {
127     if (_isRunning) {
128       return;
129     }
130     
131     _isRunning = true;
132     if (_singleShot) {
133       globals->get_event_mgr()->addEvent(_name, this, &TimerObj::invoke, _interval);
134     } else {
135       globals->get_event_mgr()->addTask(_name, this, &TimerObj::invoke,
136                                         _interval, _interval /* delay */);
137     }
138   }
139   
140   // stop and then start -
141   void restart(double newInterval)
142   {
143     _interval = newInterval;
144     stop();
145     start();
146   }
147   
148   void invoke()
149   {
150     naRef *args = NULL;
151     _sys->callMethod(_func, _self, 0, args, naNil() /* locals */);
152     if (_singleShot) {
153       _isRunning = false;
154     }
155   }
156   
157   void setSingleShot(bool aSingleShot)
158   {
159     _singleShot = aSingleShot;
160   }
161   
162   bool isSingleShot() const
163   { return _singleShot; }
164 private:
165   std::string _name;
166   FGNasalSys* _sys;
167   naRef _func, _self;
168   int _gcRoot, _gcSelf;
169   bool _isRunning;
170   double _interval;
171   bool _singleShot;
172 };
173
174 typedef SGSharedPtr<TimerObj> TimerObjRef;
175 typedef nasal::Ghost<TimerObjRef> NasalTimerObj;
176
177 ///////////////////////////////////////////////////////////////////////////
178
179 // Read and return file contents in a single buffer.  Note use of
180 // stat() to get the file size.  This is a win32 function, believe it
181 // or not. :) Note the REALLY IMPORTANT use of the "b" flag to fopen.
182 // Text mode brain damage will kill us if we're trying to do bytewise
183 // I/O.
184 static char* readfile(const char* file, int* lenOut)
185 {
186     struct stat data;
187     if(stat(file, &data) != 0) return 0;
188     FILE* f = fopen(file, "rb");
189     if(!f) return 0;
190     char* buf = new char[data.st_size];
191     *lenOut = fread(buf, 1, data.st_size, f);
192     fclose(f);
193     if(*lenOut != data.st_size) {
194         // Shouldn't happen, but warn anyway since it represents a
195         // platform bug and not a typical runtime error (missing file,
196         // etc...)
197         SG_LOG(SG_NASAL, SG_ALERT,
198                "ERROR in Nasal initialization: " <<
199                "short count returned from fread() of " << file <<
200                ".  Check your C library!");
201         delete[] buf;
202         return 0;
203     }
204     return buf;
205 }
206
207 FGNasalSys::FGNasalSys() :
208     _inited(false)
209 {
210     nasalSys = this;
211     _context = 0;
212     _globals = naNil();
213     _string = naNil();
214     _wrappedNodeFunc = naNil();
215     
216     _log = new simgear::BufferedLogCallback(SG_NASAL, SG_INFO);
217     _log->truncateAt(255);
218     sglog().addCallback(_log);
219
220     naSetErrorHandler(&logError);
221 }
222
223 // Utility.  Sets a named key in a hash by C string, rather than nasal
224 // string object.
225 void FGNasalSys::hashset(naRef hash, const char* key, naRef val)
226 {
227     naRef s = naNewString(_context);
228     naStr_fromdata(s, (char*)key, strlen(key));
229     naHash_set(hash, s, val);
230 }
231
232 void FGNasalSys::globalsSet(const char* key, naRef val)
233 {
234   hashset(_globals, key, val);
235 }
236
237 naRef FGNasalSys::call(naRef code, int argc, naRef* args, naRef locals)
238 {
239   return callMethod(code, naNil(), argc, args, locals);
240 }
241
242 naRef FGNasalSys::callWithContext(naContext ctx, naRef code, int argc, naRef* args, naRef locals)
243 {
244   return callMethodWithContext(ctx, code, naNil(), argc, args, locals);
245 }
246
247 // Does a naCall() in a new context.  Wrapped here to make lock
248 // tracking easier.  Extension functions are called with the lock, but
249 // we have to release it before making a new naCall().  So rather than
250 // drop the lock in every extension function that might call back into
251 // Nasal, we keep a stack depth counter here and only unlock/lock
252 // around the naCall if it isn't the first one.
253
254 naRef FGNasalSys::callMethod(naRef code, naRef self, int argc, naRef* args, naRef locals)
255 {
256   return naCallMethod(code, self, argc, args, locals);
257 }
258
259 naRef FGNasalSys::callMethodWithContext(naContext ctx, naRef code, naRef self, int argc, naRef* args, naRef locals)
260 {
261   return naCallMethodCtx(ctx, code, self, argc, args, locals);
262 }
263
264 FGNasalSys::~FGNasalSys()
265 {
266     if (_inited) {
267         SG_LOG(SG_GENERAL, SG_ALERT, "Nasal was not shutdown");
268     }
269     nasalSys = 0;
270 }
271
272 bool FGNasalSys::parseAndRun(const char* sourceCode)
273 {
274     naContext ctx = naNewContext();
275     naRef code = parse(ctx, "FGNasalSys::parseAndRun()", sourceCode,
276                        strlen(sourceCode));
277     if(naIsNil(code)) {
278         naFreeContext(ctx);
279         return false;
280     }
281     callWithContext(ctx, code, 0, 0, naNil());
282     naFreeContext(ctx);
283     return true;
284 }
285
286 #if 0
287 FGNasalScript* FGNasalSys::parseScript(const char* src, const char* name)
288 {
289     FGNasalScript* script = new FGNasalScript();
290     script->_gcKey = -1; // important, if we delete it on a parse
291     script->_nas = this; // error, don't clobber a real handle!
292
293     char buf[256];
294     if(!name) {
295         sprintf(buf, "FGNasalScript@%p", (void *)script);
296         name = buf;
297     }
298
299     script->_code = parse(name, src, strlen(src));
300     if(naIsNil(script->_code)) {
301         delete script;
302         return 0;
303     }
304
305     script->_gcKey = gcSave(script->_code);
306     return script;
307 }
308 #endif
309
310 // The get/setprop functions accept a *list* of strings and walk
311 // through the property tree with them to find the appropriate node.
312 // This allows a Nasal object to hold onto a property path and use it
313 // like a node object, e.g. setprop(ObjRoot, "size-parsecs", 2.02).  This
314 // is the utility function that walks the property tree.
315 static SGPropertyNode* findnode(naContext c, naRef* vec, int len, bool create=false)
316 {
317     SGPropertyNode* p = globals->get_props();
318     try {
319         for(int i=0; i<len; i++) {
320             naRef a = vec[i];
321             if(!naIsString(a)) {
322                 naRuntimeError(c, "bad argument to setprop/getprop path: expected a string");
323             }
324             naRef b = i < len-1 ? naNumValue(vec[i+1]) : naNil();
325             if (!naIsNil(b)) {
326                 p = p->getNode(naStr_data(a), (int)b.num, create);
327                 i++;
328             } else {
329                 p = p->getNode(naStr_data(a), create);
330             }
331             if(p == 0) return 0;
332         }
333     } catch (const string& err) {
334         naRuntimeError(c, (char *)err.c_str());
335     }
336     return p;
337 }
338
339 // getprop() extension function.  Concatenates its string arguments as
340 // property names and returns the value of the specified property.  Or
341 // nil if it doesn't exist.
342 static naRef f_getprop(naContext c, naRef me, int argc, naRef* args)
343 {
344     using namespace simgear;
345     if (argc < 1) {
346         naRuntimeError(c, "getprop() expects at least 1 argument");
347     }
348     const SGPropertyNode* p = findnode(c, args, argc, false);
349     if(!p) return naNil();
350
351     switch(p->getType()) {
352     case props::BOOL:   case props::INT:
353     case props::LONG:   case props::FLOAT:
354     case props::DOUBLE:
355         {
356         double dv = p->getDoubleValue();
357         if (SGMisc<double>::isNaN(dv)) {
358           SG_LOG(SG_NASAL, SG_ALERT, "Nasal getprop: property " << p->getPath() << " is NaN");
359           return naNil();
360         }
361         
362         return naNum(dv);
363         }
364         
365     case props::STRING:
366     case props::UNSPECIFIED:
367         {
368             naRef nastr = naNewString(c);
369             const char* val = p->getStringValue();
370             naStr_fromdata(nastr, (char*)val, strlen(val));
371             return nastr;
372         }
373     case props::ALIAS: // <--- FIXME, recurse?
374     default:
375         return naNil();
376     }
377 }
378
379 // setprop() extension function.  Concatenates its string arguments as
380 // property names and sets the value of the specified property to the
381 // final argument.
382 static naRef f_setprop(naContext c, naRef me, int argc, naRef* args)
383 {
384     if (argc < 2) {
385         naRuntimeError(c, "setprop() expects at least 2 arguments");
386     }
387     naRef val = args[argc - 1];
388     SGPropertyNode* p = findnode(c, args, argc-1, true);
389
390     bool result = false;
391     try {
392         if(naIsString(val)) result = p->setStringValue(naStr_data(val));
393         else {
394             if(!naIsNum(val))
395                 naRuntimeError(c, "setprop() value is not string or number");
396                 
397             if (SGMisc<double>::isNaN(val.num)) {
398                 naRuntimeError(c, "setprop() passed a NaN");
399             }
400             
401             result = p->setDoubleValue(val.num);
402         }
403     } catch (const string& err) {
404         naRuntimeError(c, (char *)err.c_str());
405     }
406     return naNum(result);
407 }
408
409 // print() extension function.  Concatenates and prints its arguments
410 // to the FlightGear log.  Uses the highest log level (SG_ALERT), to
411 // make sure it appears.  Is there better way to do this?
412 static naRef f_print(naContext c, naRef me, int argc, naRef* args)
413 {
414     string buf;
415     int n = argc;
416     for(int i=0; i<n; i++) {
417         naRef s = naStringValue(c, args[i]);
418         if(naIsNil(s)) continue;
419         buf += naStr_data(s);
420     }
421     SG_LOG(SG_NASAL, SG_ALERT, buf);
422     return naNum(buf.length());
423 }
424
425 // logprint() extension function.  Same as above, all arguments after the
426 // first argument are concatenated. Argument 0 is the log-level, matching
427 // sgDebugPriority.
428 static naRef f_logprint(naContext c, naRef me, int argc, naRef* args)
429 {
430   if (argc < 1)
431     naRuntimeError(c, "no prioirty argument to logprint()");
432   
433   naRef priority = args[0];
434   string buf;
435   int n = argc;
436   for(int i=1; i<n; i++) {
437     naRef s = naStringValue(c, args[i]);
438     if(naIsNil(s)) continue;
439     buf += naStr_data(s);
440   }
441 // use the nasal source file and line for the message location, since
442 // that's more useful than the location here!
443   sglog().log(SG_NASAL, (sgDebugPriority)(int) priority.num,
444                naStr_data(naGetSourceFile(c, 0)),
445                naGetLine(c, 0), buf);
446   return naNum(buf.length());
447 }
448
449
450 // fgcommand() extension function.  Executes a named command via the
451 // FlightGear command manager.  Takes a single property node name as
452 // an argument.
453 static naRef f_fgcommand(naContext c, naRef me, int argc, naRef* args)
454 {
455     naRef cmd = argc > 0 ? args[0] : naNil();
456     naRef props = argc > 1 ? args[1] : naNil();
457     if(!naIsString(cmd) || (!naIsNil(props) && !naIsGhost(props)))
458         naRuntimeError(c, "bad arguments to fgcommand()");
459     SGPropertyNode_ptr tmp, *node;
460     if(!naIsNil(props))
461         node = (SGPropertyNode_ptr*)naGhost_ptr(props);
462     else {
463         tmp = new SGPropertyNode();
464         node = &tmp;
465     }
466     return naNum(globals->get_commands()->execute(naStr_data(cmd), *node));
467 }
468
469 // settimer(func, dt, simtime) extension function.  Falls through to
470 // FGNasalSys::setTimer().  See there for docs.
471 static naRef f_settimer(naContext c, naRef me, int argc, naRef* args)
472 {
473     nasalSys->setTimer(c, argc, args);
474     return naNil();
475 }
476
477 static naRef f_makeTimer(naContext c, naRef me, int argc, naRef* args)
478 {
479   if (!naIsNum(args[0])) {
480     naRuntimeError(c, "bad interval argument to maketimer");
481   }
482     
483   naRef func, self = naNil();
484   if (naIsFunc(args[1])) {
485     func = args[1];
486   } else if ((argc == 3) && naIsFunc(args[2])) {
487     self = args[1];
488     func = args[2];
489   }
490   
491   TimerObj* timerObj = new TimerObj(nasalSys, func, self, args[0].num);
492   return nasal::to_nasal(c, timerObj);
493 }
494
495 // setlistener(func, property, bool) extension function.  Falls through to
496 // FGNasalSys::setListener().  See there for docs.
497 static naRef f_setlistener(naContext c, naRef me, int argc, naRef* args)
498 {
499     return nasalSys->setListener(c, argc, args);
500 }
501
502 // removelistener(int) extension function. Falls through to
503 // FGNasalSys::removeListener(). See there for docs.
504 static naRef f_removelistener(naContext c, naRef me, int argc, naRef* args)
505 {
506     return nasalSys->removeListener(c, argc, args);
507 }
508
509 // Returns a ghost handle to the argument to the currently executing
510 // command
511 static naRef f_cmdarg(naContext c, naRef me, int argc, naRef* args)
512 {
513     return nasalSys->cmdArgGhost();
514 }
515
516 // Sets up a property interpolation.  The first argument is either a
517 // ghost (SGPropertyNode_ptr*) or a string (global property path) to
518 // interpolate.  The second argument is a vector of pairs of
519 // value/delta numbers.
520 static naRef f_interpolate(naContext c, naRef me, int argc, naRef* args)
521 {
522   SGPropertyNode* node;
523   naRef prop = argc > 0 ? args[0] : naNil();
524   if(naIsString(prop)) node = fgGetNode(naStr_data(prop), true);
525   else if(naIsGhost(prop)) node = *(SGPropertyNode_ptr*)naGhost_ptr(prop);
526   else return naNil();
527
528   naRef curve = argc > 1 ? args[1] : naNil();
529   if(!naIsVector(curve)) return naNil();
530   int nPoints = naVec_size(curve) / 2;
531
532   simgear::PropertyList value_nodes;
533   value_nodes.reserve(nPoints);
534   double_list deltas;
535   deltas.reserve(nPoints);
536
537   for( int i = 0; i < nPoints; ++i )
538   {
539     SGPropertyNode* val = new SGPropertyNode;
540     val->setDoubleValue(naNumValue(naVec_get(curve, 2*i)).num);
541     value_nodes.push_back(val);
542     deltas.push_back(naNumValue(naVec_get(curve, 2*i+1)).num);
543   }
544
545   node->interpolate("numeric", value_nodes, deltas, "linear");
546   return naNil();
547 }
548
549 // This is a better RNG than the one in the default Nasal distribution
550 // (which is based on the C library rand() implementation). It will
551 // override.
552 static naRef f_rand(naContext c, naRef me, int argc, naRef* args)
553 {
554     return naNum(sg_random());
555 }
556
557 static naRef f_srand(naContext c, naRef me, int argc, naRef* args)
558 {
559     sg_srandom_time();
560     return naNum(0);
561 }
562
563 static naRef f_abort(naContext c, naRef me, int argc, naRef* args)
564 {
565     abort();
566     return naNil();
567 }
568
569 // Return an array listing of all files in a directory
570 static naRef f_directory(naContext c, naRef me, int argc, naRef* args)
571 {
572     if(argc != 1 || !naIsString(args[0]))
573         naRuntimeError(c, "bad arguments to directory()");
574     
575     simgear::Dir d(SGPath(naStr_data(args[0])));
576     if(!d.exists()) return naNil();
577     naRef result = naNewVector(c);
578
579     simgear::PathList paths = d.children(simgear::Dir::TYPE_FILE | simgear::Dir::TYPE_DIR);
580     for (unsigned int i=0; i<paths.size(); ++i) {
581       std::string p = paths[i].file();
582       naVec_append(result, naStr_fromdata(naNewString(c), p.c_str(), p.size()));
583     }
584     
585     return result;
586 }
587
588 /**
589  * Given a data path, resolve it in FG_ROOT or an FG_AIRCRFT directory
590  */
591 static naRef f_resolveDataPath(naContext c, naRef me, int argc, naRef* args)
592 {
593     if(argc != 1 || !naIsString(args[0]))
594         naRuntimeError(c, "bad arguments to resolveDataPath()");
595
596     SGPath p = globals->resolve_maybe_aircraft_path(naStr_data(args[0]));
597     const char* pdata = p.c_str();
598     return naStr_fromdata(naNewString(c), const_cast<char*>(pdata), strlen(pdata));
599 }
600
601 static naRef f_findDataDir(naContext c, naRef me, int argc, naRef* args)
602 {
603     if(argc != 1 || !naIsString(args[0]))
604         naRuntimeError(c, "bad arguments to findDataDir()");
605     
606     SGPath p = globals->find_data_dir(naStr_data(args[0]));
607     const char* pdata = p.c_str();
608     return naStr_fromdata(naNewString(c), const_cast<char*>(pdata), strlen(pdata));
609 }
610
611 class NasalCommand : public SGCommandMgr::Command
612 {
613 public:
614     NasalCommand(FGNasalSys* sys, naRef f, const std::string& name) :
615         _sys(sys),
616         _func(f),
617         _name(name)
618     {
619         globals->get_commands()->addCommandObject(_name, this);
620         _gcRoot =  sys->gcSave(f);
621     }
622     
623     virtual ~NasalCommand()
624     {
625         _sys->gcRelease(_gcRoot);
626     }
627     
628     virtual bool operator()(const SGPropertyNode* aNode)
629     {
630         _sys->setCmdArg(const_cast<SGPropertyNode*>(aNode));
631         naRef args[1];
632         args[0] = _sys->wrappedPropsNode(const_cast<SGPropertyNode*>(aNode));
633     
634         _sys->callMethod(_func, naNil(), 1, args, naNil() /* locals */);
635
636         return true;
637     }
638     
639 private:
640     FGNasalSys* _sys;
641     naRef _func;
642     int _gcRoot;
643     std::string _name;
644 };
645
646 static naRef f_addCommand(naContext c, naRef me, int argc, naRef* args)
647 {
648     if(argc != 2 || !naIsString(args[0]) || !naIsFunc(args[1]))
649         naRuntimeError(c, "bad arguments to addcommand()");
650     
651     nasalSys->addCommand(args[1], naStr_data(args[0]));
652     return naNil();
653 }
654
655 static naRef f_removeCommand(naContext c, naRef me, int argc, naRef* args)
656 {
657     if ((argc < 1) || !naIsString(args[0]))
658         naRuntimeError(c, "bad argument to removecommand()");
659     
660     globals->get_commands()->removeCommand(naStr_data(args[0]));
661     return naNil();
662 }
663
664 // Parse XML file.
665 //     parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
666 //
667 // <path>      ... absolute path to an XML file
668 // <start-tag> ... callback function with two args: tag name, attribute hash
669 // <end-tag>   ... callback function with one arg:  tag name
670 // <data>      ... callback function with one arg:  data
671 // <pi>        ... callback function with two args: target, data
672 //                 (pi = "processing instruction")
673 // All four callback functions are optional and default to nil.
674 // The function returns nil on error, or the validated file name otherwise.
675 static naRef f_parsexml(naContext c, naRef me, int argc, naRef* args)
676 {
677     if(argc < 1 || !naIsString(args[0]))
678         naRuntimeError(c, "parsexml(): path argument missing or not a string");
679     if(argc > 5) argc = 5;
680     for(int i=1; i<argc; i++)
681         if(!(naIsNil(args[i]) || naIsFunc(args[i])))
682             naRuntimeError(c, "parsexml(): callback argument not a function");
683
684     const char* file = fgValidatePath(naStr_data(args[0]), false);
685     if(!file) {
686         naRuntimeError(c, "parsexml(): reading '%s' denied "
687                 "(unauthorized access)", naStr_data(args[0]));
688         return naNil();
689     }
690     std::ifstream input(file);
691     NasalXMLVisitor visitor(c, argc, args);
692     try {
693         readXML(input, visitor);
694     } catch (const sg_exception& e) {
695         naRuntimeError(c, "parsexml(): file '%s' %s",
696                 file, e.getFormattedMessage().c_str());
697         return naNil();
698     }
699     return naStr_fromdata(naNewString(c), const_cast<char*>(file), strlen(file));
700 }
701
702 /**
703  * Parse very simple and small subset of markdown
704  *
705  * parse_markdown(src)
706  */
707 static naRef f_parse_markdown(naContext c, naRef me, int argc, naRef* args)
708 {
709   nasal::CallContext ctx(c, argc, args);
710   return ctx.to_nasal(
711     simgear::SimpleMarkdown::parse(ctx.requireArg<std::string>(0))
712   );
713 }
714
715 /**
716  * Create md5 hash from given string
717  *
718  * md5(str)
719  */
720 static naRef f_md5(naContext c, naRef me, int argc, naRef* args)
721 {
722   nasal::CallContext ctx(c, argc, args);
723   std::string const str = ctx.requireArg<std::string>(0);
724
725   SG_MD5_CTX md5_ctx;
726   SG_MD5Init(&md5_ctx);
727   SG_MD5Update(&md5_ctx, (unsigned char*)str.c_str(), str.size());
728
729   unsigned char digest[MD5_DIGEST_LENGTH];
730   SG_MD5Final(digest, &md5_ctx);
731
732   // TODO make something more generic
733   // convert final sum to hex
734   const char hexChar[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
735   std::stringstream hexMd5;
736   for (int i=0; i<MD5_DIGEST_LENGTH;++i) {
737       hexMd5 << hexChar[digest[i] >> 4];
738       hexMd5 << hexChar[digest[i] & 0x0f];
739   }
740
741   return ctx.to_nasal(hexMd5.str());
742 }
743
744 // Return UNIX epoch time in seconds.
745 static naRef f_systime(naContext c, naRef me, int argc, naRef* args)
746 {
747 #ifdef _WIN32
748     FILETIME ft;
749     GetSystemTimeAsFileTime(&ft);
750     double t = (4294967296.0 * ft.dwHighDateTime + ft.dwLowDateTime);
751     // Converts from 100ns units in 1601 epoch to unix epoch in sec
752     return naNum((t * 1e-7) - 11644473600.0);
753 #else
754     struct timeval td;
755     gettimeofday(&td, 0);
756     return naNum(td.tv_sec + 1e-6 * td.tv_usec);
757 #endif
758 }
759
760 // Table of extension functions.  Terminate with zeros.
761 static struct { const char* name; naCFunction func; } funcs[] = {
762     { "getprop",   f_getprop },
763     { "setprop",   f_setprop },
764     { "print",     f_print },
765     { "logprint",  f_logprint },
766     { "_fgcommand", f_fgcommand },
767     { "settimer",  f_settimer },
768     { "maketimer", f_makeTimer },
769     { "_setlistener", f_setlistener },
770     { "removelistener", f_removelistener },
771     { "addcommand", f_addCommand },
772     { "removecommand", f_removeCommand },
773     { "_cmdarg",  f_cmdarg },
774     { "_interpolate",  f_interpolate },
775     { "rand",  f_rand },
776     { "srand",  f_srand },
777     { "abort", f_abort },
778     { "directory", f_directory },
779     { "resolvepath", f_resolveDataPath },
780     { "finddata", f_findDataDir },
781     { "parsexml", f_parsexml },
782     { "parse_markdown", f_parse_markdown },
783     { "md5", f_md5 },
784     { "systime", f_systime },
785     { 0, 0 }
786 };
787
788 naRef FGNasalSys::cmdArgGhost()
789 {
790     return propNodeGhost(_cmdArg);
791 }
792
793 void FGNasalSys::setCmdArg(SGPropertyNode* aNode)
794 {
795     _cmdArg = aNode;
796 }
797
798 void FGNasalSys::init()
799 {
800     if (_inited) {
801         SG_LOG(SG_GENERAL, SG_ALERT, "duplicate init of Nasal");
802     }
803     int i;
804
805     _context = naNewContext();
806
807     // Start with globals.  Add it to itself as a recursive
808     // sub-reference under the name "globals".  This gives client-code
809     // write access to the namespace if someone wants to do something
810     // fancy.
811     _globals = naInit_std(_context);
812     naSave(_context, _globals);
813     hashset(_globals, "globals", _globals);
814
815     hashset(_globals, "math", naInit_math(_context));
816     hashset(_globals, "bits", naInit_bits(_context));
817     hashset(_globals, "io", naInit_io(_context));
818     hashset(_globals, "thread", naInit_thread(_context));
819     hashset(_globals, "utf8", naInit_utf8(_context));
820
821     // Add our custom extension functions:
822     for(i=0; funcs[i].name; i++)
823         hashset(_globals, funcs[i].name,
824                 naNewFunc(_context, naNewCCode(_context, funcs[i].func)));
825
826     // And our SGPropertyNode wrapper
827     hashset(_globals, "props", genPropsModule());
828
829     // Add string methods
830     _string = naInit_string(_context);
831     naSave(_context, _string);
832     initNasalString(_globals, _string, _context);
833
834     initNasalPositioned(_globals, _context);
835     initNasalPositioned_cppbind(_globals, _context);
836     initNasalAircraft(_globals, _context);
837     NasalClipboard::init(this);
838     initNasalCanvas(_globals, _context);
839     initNasalCondition(_globals, _context);
840     initNasalHTTP(_globals, _context);
841     initNasalSGPath(_globals, _context);
842   
843     NasalTimerObj::init("Timer")
844       .method("start", &TimerObj::start)
845       .method("stop", &TimerObj::stop)
846       .method("restart", &TimerObj::restart)
847       .member("singleShot", &TimerObj::isSingleShot, &TimerObj::setSingleShot)
848       .member("isRunning", &TimerObj::isRunning);
849
850     // Now load the various source files in the Nasal directory
851     simgear::Dir nasalDir(SGPath(globals->get_fg_root(), "Nasal"));
852     loadScriptDirectory(nasalDir);
853
854     // Add modules in Nasal subdirectories to property tree
855     simgear::PathList directories = nasalDir.children(simgear::Dir::TYPE_DIR+
856             simgear::Dir::NO_DOT_OR_DOTDOT, "");
857     for (unsigned int i=0; i<directories.size(); ++i) {
858         simgear::Dir dir(directories[i]);
859         simgear::PathList scripts = dir.children(simgear::Dir::TYPE_FILE, ".nas");
860         addModule(directories[i].file(), scripts);
861     }
862
863     // set signal and remove node to avoid restoring at reinit
864     const char *s = "nasal-dir-initialized";
865     SGPropertyNode *signal = fgGetNode("/sim/signals", true);
866     signal->setBoolValue(s, true);
867     signal->removeChildren(s);
868
869     // Pull scripts out of the property tree, too
870     loadPropertyScripts();
871   
872     // now Nasal modules are loaded, we can do some delayed work
873     postinitNasalPositioned(_globals, _context);
874     postinitNasalGUI(_globals, _context);
875     
876     _inited = true;
877 }
878
879 void FGNasalSys::shutdown()
880 {
881     if (!_inited) {
882         return;
883     }
884     
885     shutdownNasalPositioned();
886     
887     map<int, FGNasalListener *>::iterator it, end = _listener.end();
888     for(it = _listener.begin(); it != end; ++it)
889         delete it->second;
890     _listener.clear();
891     
892     NasalCommandDict::iterator j = _commands.begin();
893     for (; j != _commands.end(); ++j) {
894         globals->get_commands()->removeCommand(j->first);
895     }
896     _commands.clear();
897     
898     std::vector<FGNasalModuleListener*>::iterator k = _moduleListeners.begin();
899     for(; k!= _moduleListeners.end(); ++k)
900         delete *k;
901     _moduleListeners.clear();
902     
903     naClearSaved();
904     
905     _string = naNil(); // will be freed by _context
906     naFreeContext(_context);
907    
908     //setWatchedRef(_globals);
909     
910     // remove the recursive reference in globals
911     hashset(_globals, "globals", naNil());
912     _globals = naNil();    
913     
914     naGC();
915     _inited = false;
916 }
917
918 naRef FGNasalSys::wrappedPropsNode(SGPropertyNode* aProps)
919 {
920     if (naIsNil(_wrappedNodeFunc)) {
921         nasal::Hash props = getGlobals().get<nasal::Hash>("props");
922         _wrappedNodeFunc = props.get("wrapNode");
923     }
924     
925     naRef args[1];
926     args[0] = propNodeGhost(aProps);
927     naContext ctx = naNewContext();
928     naRef wrapped = naCall(ctx, _wrappedNodeFunc, 1, args, naNil(), naNil());
929     naFreeContext(ctx);
930     return wrapped;
931 }
932
933 void FGNasalSys::update(double)
934 {
935     if( NasalClipboard::getInstance() )
936         NasalClipboard::getInstance()->update();
937
938     if(!_dead_listener.empty()) {
939         vector<FGNasalListener *>::iterator it, end = _dead_listener.end();
940         for(it = _dead_listener.begin(); it != end; ++it) delete *it;
941         _dead_listener.clear();
942     }
943
944     if (!_loadList.empty())
945     {
946         if( _delay_load )
947           _delay_load = false;
948         else
949           // process Nasal load hook (only one per update loop to avoid excessive lags)
950           _loadList.pop()->load();
951     }
952     else
953     if (!_unloadList.empty())
954     {
955         // process pending Nasal unload hooks after _all_ load hooks were processed
956         // (only unload one per update loop to avoid excessive lags)
957         _unloadList.pop()->unload();
958     }
959
960     // Destroy all queued ghosts
961     nasal::ghostProcessDestroyList();
962
963     // The global context is a legacy thing.  We use dynamically
964     // created contexts for naCall() now, so that we can call them
965     // recursively.  But there are still spots that want to use it for
966     // naNew*() calls, which end up leaking memory because the context
967     // only clears out its temporary vector when it's *used*.  So just
968     // junk it and fetch a new/reinitialized one every frame.  This is
969     // clumsy: the right solution would use the dynamic context in all
970     // cases and eliminate _context entirely.  But that's more work,
971     // and this works fine (yes, they say "New" and "Free", but
972     // they're very fast, just trust me). -Andy
973     naFreeContext(_context);
974     _context = naNewContext();
975 }
976
977 bool pathSortPredicate(const SGPath& p1, const SGPath& p2)
978 {
979   return p1.file() < p2.file();
980 }
981
982 // Loads all scripts in given directory 
983 void FGNasalSys::loadScriptDirectory(simgear::Dir nasalDir)
984 {
985     simgear::PathList scripts = nasalDir.children(simgear::Dir::TYPE_FILE, ".nas");
986     // Note: simgear::Dir already reports file entries in a deterministic order,
987     // so a fixed loading sequence is guaranteed (same for every user)
988     for (unsigned int i=0; i<scripts.size(); ++i) {
989       SGPath fullpath(scripts[i]);
990       SGPath file = fullpath.file();
991       loadModule(fullpath, file.base().c_str());
992     }
993 }
994
995 // Create module with list of scripts
996 void FGNasalSys::addModule(string moduleName, simgear::PathList scripts)
997 {
998     if (! scripts.empty())
999     {
1000         SGPropertyNode* nasal = globals->get_props()->getNode("nasal");
1001         SGPropertyNode* module_node = nasal->getChild(moduleName,0,true);
1002         for (unsigned int i=0; i<scripts.size(); ++i) {
1003             SGPropertyNode* pFileNode = module_node->getChild("file",i,true);
1004             pFileNode->setStringValue(scripts[i].c_str());
1005         }
1006         if (!module_node->hasChild("enabled",0))
1007         {
1008             SGPropertyNode* node = module_node->getChild("enabled",0,true);
1009             node->setBoolValue(true);
1010             node->setAttribute(SGPropertyNode::USERARCHIVE,true);
1011         }
1012     }
1013 }
1014
1015 // Loads the scripts found under /nasal in the global tree
1016 void FGNasalSys::loadPropertyScripts()
1017 {
1018     SGPropertyNode* nasal = globals->get_props()->getNode("nasal");
1019     if(!nasal) return;
1020
1021     for(int i=0; i<nasal->nChildren(); i++)
1022     {
1023         SGPropertyNode* n = nasal->getChild(i);
1024         loadPropertyScripts(n);
1025     }
1026 }
1027
1028 // Loads the scripts found under /nasal in the global tree
1029 void FGNasalSys::loadPropertyScripts(SGPropertyNode* n)
1030 {
1031     bool is_loaded = false;
1032
1033     const char* module = n->getName();
1034     if(n->hasChild("module"))
1035         module = n->getStringValue("module");
1036     if (n->getBoolValue("enabled",true))
1037     {
1038         // allow multiple files to be specified within a single
1039         // Nasal module tag
1040         int j = 0;
1041         SGPropertyNode *fn;
1042         bool file_specified = false;
1043         bool ok=true;
1044         while((fn = n->getChild("file", j)) != NULL) {
1045             file_specified = true;
1046             const char* file = fn->getStringValue();
1047             SGPath p(file);
1048             if (!p.isAbsolute() || !p.exists())
1049             {
1050                 p = globals->resolve_maybe_aircraft_path(file);
1051                 if (p.isNull())
1052                 {
1053                     SG_LOG(SG_NASAL, SG_ALERT, "Cannot find Nasal script '" <<
1054                             file << "' for module '" << module << "'.");
1055                 }
1056             }
1057             ok &= p.isNull() ? false : loadModule(p, module);
1058             j++;
1059         }
1060
1061         const char* src = n->getStringValue("script");
1062         if(!n->hasChild("script")) src = 0; // Hrm...
1063         if(src)
1064             createModule(module, n->getPath().c_str(), src, strlen(src));
1065
1066         if(!file_specified && !src)
1067         {
1068             // module no longer exists - clear the archived "enable" flag
1069             n->setAttribute(SGPropertyNode::USERARCHIVE,false);
1070             SGPropertyNode* node = n->getChild("enabled",0,false);
1071             if (node)
1072                 node->setAttribute(SGPropertyNode::USERARCHIVE,false);
1073
1074             SG_LOG(SG_NASAL, SG_ALERT, "Nasal error: " <<
1075                     "no <file> or <script> defined in " <<
1076                     "/nasal/" << module);
1077         }
1078         else
1079             is_loaded = ok;
1080     }
1081     else
1082     {
1083         SGPropertyNode* enable = n->getChild("enabled");
1084         if (enable)
1085         {
1086             FGNasalModuleListener* listener = new FGNasalModuleListener(n);
1087             _moduleListeners.push_back(listener);
1088             enable->addChangeListener(listener, false);
1089         }
1090     }
1091     SGPropertyNode* loaded = n->getChild("loaded",0,true);
1092     loaded->setAttribute(SGPropertyNode::PRESERVE,true);
1093     loaded->setBoolValue(is_loaded);
1094 }
1095
1096 // Logs a runtime error, with stack trace, to the FlightGear log stream
1097 void FGNasalSys::logError(naContext context)
1098 {
1099     SG_LOG(SG_NASAL, SG_ALERT, "Nasal runtime error: " << naGetError(context));
1100     int stack_depth = naStackDepth(context);
1101     if( stack_depth < 1 )
1102       return;
1103     SG_LOG(SG_NASAL, SG_ALERT,
1104            "  at " << naStr_data(naGetSourceFile(context, 0)) <<
1105            ", line " << naGetLine(context, 0));
1106     for(int i=1; i<stack_depth; i++)
1107         SG_LOG(SG_NASAL, SG_ALERT,
1108                "  called from: " << naStr_data(naGetSourceFile(context, i)) <<
1109                ", line " << naGetLine(context, i));
1110 }
1111
1112 // Reads a script file, executes it, and places the resulting
1113 // namespace into the global namespace under the specified module
1114 // name.
1115 bool FGNasalSys::loadModule(SGPath file, const char* module)
1116 {
1117     int len = 0;
1118     char* buf = readfile(file.c_str(), &len);
1119     if(!buf) {
1120         SG_LOG(SG_NASAL, SG_ALERT,
1121                "Nasal error: could not read script file " << file.c_str()
1122                << " into module " << module);
1123         return false;
1124     }
1125
1126     bool ok = createModule(module, file.c_str(), buf, len);
1127     delete[] buf;
1128     return ok;
1129 }
1130
1131 // Parse and run.  Save the local variables namespace, as it will
1132 // become a sub-object of globals.  The optional "arg" argument can be
1133 // used to pass an associated property node to the module, which can then
1134 // be accessed via cmdarg().  (This is, for example, used by XML dialogs.)
1135 bool FGNasalSys::createModule(const char* moduleName, const char* fileName,
1136                               const char* src, int len,
1137                               const SGPropertyNode* cmdarg,
1138                               int argc, naRef* args)
1139 {
1140     naContext ctx = naNewContext();
1141     naRef code = parse(ctx, fileName, src, len);
1142     if(naIsNil(code)) {
1143         naFreeContext(ctx);
1144         return false;
1145     }
1146
1147     
1148     // See if we already have a module hash to use.  This allows the
1149     // user to, for example, add functions to the built-in math
1150     // module.  Make a new one if necessary.
1151     naRef locals;
1152     naRef modname = naNewString(ctx);
1153     naStr_fromdata(modname, (char*)moduleName, strlen(moduleName));
1154     if(!naHash_get(_globals, modname, &locals))
1155         locals = naNewHash(ctx);
1156
1157     _cmdArg = (SGPropertyNode*)cmdarg;
1158
1159     callWithContext(ctx, code, argc, args, locals);
1160     hashset(_globals, moduleName, locals);
1161     
1162     naFreeContext(ctx);
1163     return true;
1164 }
1165
1166 void FGNasalSys::deleteModule(const char* moduleName)
1167 {
1168     if (!_inited) {
1169         // can occur on shutdown due to us being shutdown first, but other
1170         // subsystems having Nasal objects.
1171         return;
1172     }
1173     
1174     naContext ctx = naNewContext();
1175     naRef modname = naNewString(ctx);
1176     naStr_fromdata(modname, (char*)moduleName, strlen(moduleName));
1177     naHash_delete(_globals, modname);
1178     naFreeContext(ctx);
1179 }
1180
1181 naRef FGNasalSys::parse(naContext ctx, const char* filename, const char* buf, int len)
1182 {
1183     int errLine = -1;
1184     naRef srcfile = naNewString(ctx);
1185     naStr_fromdata(srcfile, (char*)filename, strlen(filename));
1186     naRef code = naParseCode(ctx, srcfile, 1, (char*)buf, len, &errLine);
1187     if(naIsNil(code)) {
1188         SG_LOG(SG_NASAL, SG_ALERT,
1189                "Nasal parse error: " << naGetError(ctx) <<
1190                " in "<< filename <<", line " << errLine);
1191         return naNil();
1192     }
1193
1194     // Bind to the global namespace before returning
1195     return naBindFunction(ctx, code, _globals);
1196 }
1197
1198 bool FGNasalSys::handleCommand( const char* moduleName,
1199                                 const char* fileName,
1200                                 const char* src,
1201                                 const SGPropertyNode* arg )
1202 {
1203     naContext ctx = naNewContext();
1204     naRef code = parse(ctx, fileName, src, strlen(src));
1205     if(naIsNil(code)) {
1206         naFreeContext(ctx);
1207         return false;
1208     }
1209
1210     // Commands can be run "in" a module.  Make sure that module
1211     // exists, and set it up as the local variables hash for the
1212     // command.
1213     naRef locals = naNil();
1214     if(moduleName[0]) {
1215         naRef modname = naNewString(ctx);
1216         naStr_fromdata(modname, (char*)moduleName, strlen(moduleName));
1217         if(!naHash_get(_globals, modname, &locals)) {
1218             locals = naNewHash(ctx);
1219             naHash_set(_globals, modname, locals);
1220         }
1221     }
1222
1223     // Cache this command's argument for inspection via cmdarg().  For
1224     // performance reasons, we won't bother with it if the invoked
1225     // code doesn't need it.
1226     _cmdArg = (SGPropertyNode*)arg;
1227
1228     callWithContext(ctx, code, 0, 0, locals);
1229     naFreeContext(ctx);
1230     return true;
1231 }
1232
1233 bool FGNasalSys::handleCommand(const SGPropertyNode* arg)
1234 {
1235   const char* src = arg->getStringValue("script");
1236   const char* moduleName = arg->getStringValue("module");
1237
1238   return handleCommand( moduleName,
1239                         arg->getPath(true).c_str(),
1240                         src,
1241                         arg );
1242 }
1243
1244 // settimer(func, dt, simtime) extension function.  The first argument
1245 // is a Nasal function to call, the second is a delta time (from now),
1246 // in seconds.  The third, if present, is a boolean value indicating
1247 // that "real world" time (rather than simulator time) is to be used.
1248 //
1249 // Implementation note: the FGTimer objects don't live inside the
1250 // garbage collector, so the Nasal handler functions have to be
1251 // "saved" somehow lest they be inadvertently cleaned.  In this case,
1252 // they are inserted into a globals.__gcsave hash and removed on
1253 // expiration.
1254 void FGNasalSys::setTimer(naContext c, int argc, naRef* args)
1255 {
1256     // Extract the handler, delta, and simtime arguments:
1257     naRef handler = argc > 0 ? args[0] : naNil();
1258     if(!(naIsCode(handler) || naIsCCode(handler) || naIsFunc(handler))) {
1259         naRuntimeError(c, "settimer() with invalid function argument");
1260         return;
1261     }
1262
1263     naRef delta = argc > 1 ? args[1] : naNil();
1264     if(naIsNil(delta)) {
1265         naRuntimeError(c, "settimer() with invalid time argument");
1266         return;
1267     }
1268
1269     bool simtime = (argc > 2 && naTrue(args[2])) ? false : true;
1270
1271     // Generate and register a C++ timer handler
1272     NasalTimer* t = new NasalTimer;
1273     t->handler = handler;
1274     t->gcKey = gcSave(handler);
1275     t->nasal = this;
1276
1277     globals->get_event_mgr()->addEvent("NasalTimer",
1278                                        t, &NasalTimer::timerExpired,
1279                                        delta.num, simtime);
1280 }
1281
1282 void FGNasalSys::handleTimer(NasalTimer* t)
1283 {
1284     call(t->handler, 0, 0, naNil());
1285     gcRelease(t->gcKey);
1286 }
1287
1288 int FGNasalSys::gcSave(naRef r)
1289 {
1290     return naGCSave(r);
1291 }
1292
1293 void FGNasalSys::gcRelease(int key)
1294 {
1295     naGCRelease(key);
1296 }
1297
1298 void FGNasalSys::NasalTimer::timerExpired()
1299 {
1300     nasal->handleTimer(this);
1301     delete this;
1302 }
1303
1304 int FGNasalSys::_listenerId = 0;
1305
1306 // setlistener(<property>, <func> [, <initial=0> [, <persistent=1>]])
1307 // Attaches a callback function to a property (specified as a global
1308 // property path string or a SGPropertyNode_ptr* ghost). If the third,
1309 // optional argument (default=0) is set to 1, then the function is also
1310 // called initially. If the fourth, optional argument is set to 0, then the
1311 // function is only called when the property node value actually changes.
1312 // Otherwise it's called independent of the value whenever the node is
1313 // written to (default). The setlistener() function returns a unique
1314 // id number, which is to be used as argument to the removelistener()
1315 // function.
1316 naRef FGNasalSys::setListener(naContext c, int argc, naRef* args)
1317 {
1318     SGPropertyNode_ptr node;
1319     naRef prop = argc > 0 ? args[0] : naNil();
1320     if(naIsString(prop)) node = fgGetNode(naStr_data(prop), true);
1321     else if(naIsGhost(prop)) node = *(SGPropertyNode_ptr*)naGhost_ptr(prop);
1322     else {
1323         naRuntimeError(c, "setlistener() with invalid property argument");
1324         return naNil();
1325     }
1326
1327     if(node->isTied())
1328         SG_LOG(SG_NASAL, SG_DEBUG, "Attaching listener to tied property " <<
1329                 node->getPath());
1330
1331     naRef code = argc > 1 ? args[1] : naNil();
1332     if(!(naIsCode(code) || naIsCCode(code) || naIsFunc(code))) {
1333         naRuntimeError(c, "setlistener() with invalid function argument");
1334         return naNil();
1335     }
1336
1337     int init = argc > 2 && naIsNum(args[2]) ? int(args[2].num) : 0;
1338     int type = argc > 3 && naIsNum(args[3]) ? int(args[3].num) : 1;
1339     FGNasalListener *nl = new FGNasalListener(node, code, this,
1340             gcSave(code), _listenerId, init, type);
1341
1342     node->addChangeListener(nl, init != 0);
1343
1344     _listener[_listenerId] = nl;
1345     return naNum(_listenerId++);
1346 }
1347
1348 // removelistener(int) extension function. The argument is the id of
1349 // a listener as returned by the setlistener() function.
1350 naRef FGNasalSys::removeListener(naContext c, int argc, naRef* args)
1351 {
1352     naRef id = argc > 0 ? args[0] : naNil();
1353     map<int, FGNasalListener *>::iterator it = _listener.find(int(id.num));
1354
1355     if(!naIsNum(id) || it == _listener.end() || it->second->_dead) {
1356         naRuntimeError(c, "removelistener() with invalid listener id");
1357         return naNil();
1358     }
1359
1360     it->second->_dead = true;
1361     _dead_listener.push_back(it->second);
1362     _listener.erase(it);
1363     return naNum(_listener.size());
1364 }
1365
1366 void FGNasalSys::registerToLoad(FGNasalModelData *data)
1367 {
1368   if( _loadList.empty() )
1369     _delay_load = true;
1370   _loadList.push(data);
1371 }
1372
1373 void FGNasalSys::registerToUnload(FGNasalModelData *data)
1374 {
1375     _unloadList.push(data);
1376 }
1377
1378 void FGNasalSys::addCommand(naRef func, const std::string& name)
1379 {
1380     if (_commands.find(name) != _commands.end()) {
1381         SG_LOG(SG_NASAL, SG_WARN, "duplicate add of command:" << name);
1382         return;
1383     }
1384     
1385     NasalCommand* cmd = new NasalCommand(this, func, name);
1386     _commands[name] = cmd;
1387 }
1388
1389 void FGNasalSys::removeCommand(const std::string& name)
1390 {
1391     NasalCommandDict::iterator it = _commands.find(name);
1392     if (it == _commands.end()) {
1393         SG_LOG(SG_NASAL, SG_WARN, "remove of unknwon command:" << name);
1394         return;
1395     }
1396
1397     // will delete the NasalCommand instance
1398     globals->get_commands()->removeCommand(name);
1399     _commands.erase(it);
1400 }
1401
1402 //////////////////////////////////////////////////////////////////////////
1403 // FGNasalListener class.
1404
1405 FGNasalListener::FGNasalListener(SGPropertyNode *node, naRef code,
1406                                  FGNasalSys* nasal, int key, int id,
1407                                  int init, int type) :
1408     _node(node),
1409     _code(code),
1410     _gcKey(key),
1411     _id(id),
1412     _nas(nasal),
1413     _init(init),
1414     _type(type),
1415     _active(0),
1416     _dead(false),
1417     _last_int(0L),
1418     _last_float(0.0)
1419 {
1420     if(_type == 0 && !_init)
1421         changed(node);
1422 }
1423
1424 FGNasalListener::~FGNasalListener()
1425 {
1426     _node->removeChangeListener(this);
1427     _nas->gcRelease(_gcKey);
1428 }
1429
1430 void FGNasalListener::call(SGPropertyNode* which, naRef mode)
1431 {
1432     if(_active || _dead) return;
1433     _active++;
1434     naRef arg[4];
1435     arg[0] = _nas->propNodeGhost(which);
1436     arg[1] = _nas->propNodeGhost(_node);
1437     arg[2] = mode;                  // value changed, child added/removed
1438     arg[3] = naNum(_node != which); // child event?
1439     _nas->call(_code, 4, arg, naNil());
1440     _active--;
1441 }
1442
1443 void FGNasalListener::valueChanged(SGPropertyNode* node)
1444 {
1445     if(_type < 2 && node != _node) return;   // skip child events
1446     if(_type > 0 || changed(_node) || _init)
1447         call(node, naNum(0));
1448
1449     _init = 0;
1450 }
1451
1452 void FGNasalListener::childAdded(SGPropertyNode*, SGPropertyNode* child)
1453 {
1454     if(_type == 2) call(child, naNum(1));
1455 }
1456
1457 void FGNasalListener::childRemoved(SGPropertyNode*, SGPropertyNode* child)
1458 {
1459     if(_type == 2) call(child, naNum(-1));
1460 }
1461
1462 bool FGNasalListener::changed(SGPropertyNode* node)
1463 {
1464     using namespace simgear;
1465     props::Type type = node->getType();
1466     if(type == props::NONE) return false;
1467     if(type == props::UNSPECIFIED) return true;
1468
1469     bool result;
1470     switch(type) {
1471     case props::BOOL:
1472     case props::INT:
1473     case props::LONG:
1474         {
1475             long l = node->getLongValue();
1476             result = l != _last_int;
1477             _last_int = l;
1478             return result;
1479         }
1480     case props::FLOAT:
1481     case props::DOUBLE:
1482         {
1483             double d = node->getDoubleValue();
1484             result = d != _last_float;
1485             _last_float = d;
1486             return result;
1487         }
1488     default:
1489         {
1490             string s = node->getStringValue();
1491             result = s != _last_string;
1492             _last_string = s;
1493             return result;
1494         }
1495     }
1496 }
1497
1498 // NasalXMLVisitor class: handles EasyXML visitor callback for parsexml()
1499 //
1500 NasalXMLVisitor::NasalXMLVisitor(naContext c, int argc, naRef* args) :
1501     _c(naSubContext(c)),
1502     _start_element(argc > 1 ? args[1] : naNil()),
1503     _end_element(argc > 2 ? args[2] : naNil()),
1504     _data(argc > 3 ? args[3] : naNil()),
1505     _pi(argc > 4 ? args[4] : naNil())
1506 {
1507 }
1508
1509 void NasalXMLVisitor::startElement(const char* tag, const XMLAttributes& a)
1510 {
1511     if(naIsNil(_start_element)) return;
1512     naRef attr = naNewHash(_c);
1513     for(int i=0; i<a.size(); i++) {
1514         naRef name = make_string(a.getName(i));
1515         naRef value = make_string(a.getValue(i));
1516         naHash_set(attr, name, value);
1517     }
1518     call(_start_element, 2, make_string(tag), attr);
1519 }
1520
1521 void NasalXMLVisitor::endElement(const char* tag)
1522 {
1523     if(!naIsNil(_end_element)) call(_end_element, 1, make_string(tag));
1524 }
1525
1526 void NasalXMLVisitor::data(const char* str, int len)
1527 {
1528     if(!naIsNil(_data)) call(_data, 1, make_string(str, len));
1529 }
1530
1531 void NasalXMLVisitor::pi(const char* target, const char* data)
1532 {
1533     if(!naIsNil(_pi)) call(_pi, 2, make_string(target), make_string(data));
1534 }
1535
1536 void NasalXMLVisitor::call(naRef func, int num, naRef a, naRef b)
1537 {
1538     naRef args[2];
1539     args[0] = a;
1540     args[1] = b;
1541     naCall(_c, func, num, args, naNil(), naNil());
1542     if(naGetError(_c))
1543         naRethrowError(_c);
1544 }
1545
1546 naRef NasalXMLVisitor::make_string(const char* s, int n)
1547 {
1548     return naStr_fromdata(naNewString(_c), const_cast<char *>(s),
1549                           n < 0 ? strlen(s) : n);
1550 }
1551
1552