]> git.mxchange.org Git - flightgear.git/blob - src/Scripting/NasalSys.cxx
0a221fc6ebe6ae4c357ac853d35b1979fe4460e7
[flightgear.git] / src / Scripting / NasalSys.cxx
1
2 #ifdef HAVE_CONFIG_H
3 #  include "config.h"
4 #endif
5
6 #ifdef HAVE_SYS_TIME_H
7 #  include <sys/time.h>  // gettimeofday
8 #endif
9
10 #include <string.h>
11 #include <stdio.h>
12 #include <sys/types.h>
13 #include <sys/stat.h>
14 #include <fstream>
15 #include <sstream>
16
17 #include <plib/ul.h>
18
19 #include <simgear/nasal/nasal.h>
20 #include <simgear/props/props.hxx>
21 #include <simgear/math/sg_random.h>
22 #include <simgear/misc/sg_path.hxx>
23 #include <simgear/misc/interpolator.hxx>
24 #include <simgear/scene/material/mat.hxx>
25 #include <simgear/structure/commands.hxx>
26 #include <simgear/math/sg_geodesy.hxx>
27 #include <simgear/structure/event_mgr.hxx>
28
29 #include <Airports/runways.hxx>
30 #include <Airports/simple.hxx>
31 #include <Main/globals.hxx>
32 #include <Main/fg_props.hxx>
33 #include <Main/util.hxx>
34 #include <Scenery/scenery.hxx>
35 #include <Navaids/navrecord.hxx>
36
37 #include "NasalSys.hxx"
38
39 static FGNasalSys* nasalSys = 0;
40
41
42 // Read and return file contents in a single buffer.  Note use of
43 // stat() to get the file size.  This is a win32 function, believe it
44 // or not. :) Note the REALLY IMPORTANT use of the "b" flag to fopen.
45 // Text mode brain damage will kill us if we're trying to do bytewise
46 // I/O.
47 static char* readfile(const char* file, int* lenOut)
48 {
49     struct stat data;
50     if(stat(file, &data) != 0) return 0;
51     FILE* f = fopen(file, "rb");
52     if(!f) return 0;
53     char* buf = new char[data.st_size];
54     *lenOut = fread(buf, 1, data.st_size, f);
55     fclose(f);
56     if(*lenOut != data.st_size) {
57         // Shouldn't happen, but warn anyway since it represents a
58         // platform bug and not a typical runtime error (missing file,
59         // etc...)
60         SG_LOG(SG_NASAL, SG_ALERT,
61                "ERROR in Nasal initialization: " <<
62                "short count returned from fread() of " << file <<
63                ".  Check your C library!");
64         delete[] buf;
65         return 0;
66     }
67     return buf;
68 }
69
70 FGNasalSys::FGNasalSys()
71 {
72     nasalSys = this;
73     _context = 0;
74     _globals = naNil();
75     _gcHash = naNil();
76     _nextGCKey = 0; // Any value will do
77     _callCount = 0;
78 }
79
80 // Does a naCall() in a new context.  Wrapped here to make lock
81 // tracking easier.  Extension functions are called with the lock, but
82 // we have to release it before making a new naCall().  So rather than
83 // drop the lock in every extension function that might call back into
84 // Nasal, we keep a stack depth counter here and only unlock/lock
85 // around the naCall if it isn't the first one.
86 naRef FGNasalSys::call(naRef code, int argc, naRef* args, naRef locals)
87 {
88     naContext ctx = naNewContext();
89     if(_callCount) naModUnlock();
90     _callCount++;
91     naRef result = naCall(ctx, code, argc, args, naNil(), locals);
92     if(naGetError(ctx))
93         logError(ctx);
94     _callCount--;
95     if(_callCount) naModLock();
96     naFreeContext(ctx);
97     return result;
98 }
99
100 FGNasalSys::~FGNasalSys()
101 {
102     nasalSys = 0;
103     map<int, FGNasalListener *>::iterator it, end = _listener.end();
104     for(it = _listener.begin(); it != end; ++it)
105         delete it->second;
106
107     naFreeContext(_context);
108     _globals = naNil();
109 }
110
111 bool FGNasalSys::parseAndRun(const char* sourceCode)
112 {
113     naRef code = parse("FGNasalSys::parseAndRun()", sourceCode,
114                        strlen(sourceCode));
115     if(naIsNil(code))
116         return false;
117     call(code, 0, 0, naNil());
118     return true;
119 }
120
121 FGNasalScript* FGNasalSys::parseScript(const char* src, const char* name)
122 {
123     FGNasalScript* script = new FGNasalScript();
124     script->_gcKey = -1; // important, if we delete it on a parse
125     script->_nas = this; // error, don't clobber a real handle!
126
127     char buf[256];
128     if(!name) {
129         sprintf(buf, "FGNasalScript@%p", (void *)script);
130         name = buf;
131     }
132
133     script->_code = parse(name, src, strlen(src));
134     if(naIsNil(script->_code)) {
135         delete script;
136         return 0;
137     }
138
139     script->_gcKey = gcSave(script->_code);
140     return script;
141 }
142
143 // Utility.  Sets a named key in a hash by C string, rather than nasal
144 // string object.
145 void FGNasalSys::hashset(naRef hash, const char* key, naRef val)
146 {
147     naRef s = naNewString(_context);
148     naStr_fromdata(s, (char*)key, strlen(key));
149     naHash_set(hash, s, val);
150 }
151
152 // The get/setprop functions accept a *list* of strings and walk
153 // through the property tree with them to find the appropriate node.
154 // This allows a Nasal object to hold onto a property path and use it
155 // like a node object, e.g. setprop(ObjRoot, "size-parsecs", 2.02).  This
156 // is the utility function that walks the property tree.
157 // Future enhancement: support integer arguments to specify array
158 // elements.
159 static SGPropertyNode* findnode(naContext c, naRef* vec, int len)
160 {
161     SGPropertyNode* p = globals->get_props();
162     try {
163         for(int i=0; i<len; i++) {
164             naRef a = vec[i];
165             if(!naIsString(a)) return 0;
166             p = p->getNode(naStr_data(a));
167             if(p == 0) return 0;
168         }
169     } catch (const string& err) {
170         naRuntimeError(c, (char *)err.c_str());
171         return 0;
172     }
173     return p;
174 }
175
176 // getprop() extension function.  Concatenates its string arguments as
177 // property names and returns the value of the specified property.  Or
178 // nil if it doesn't exist.
179 static naRef f_getprop(naContext c, naRef me, int argc, naRef* args)
180 {
181     using namespace simgear;
182     const SGPropertyNode* p = findnode(c, args, argc);
183     if(!p) return naNil();
184
185     switch(p->getType()) {
186     case props::BOOL:   case props::INT:
187     case props::LONG:   case props::FLOAT:
188     case props::DOUBLE:
189         return naNum(p->getDoubleValue());
190
191     case props::STRING:
192     case props::UNSPECIFIED:
193         {
194             naRef nastr = naNewString(c);
195             const char* val = p->getStringValue();
196             naStr_fromdata(nastr, (char*)val, strlen(val));
197             return nastr;
198         }
199     case props::ALIAS: // <--- FIXME, recurse?
200     default:
201         return naNil();
202     }
203 }
204
205 // setprop() extension function.  Concatenates its string arguments as
206 // property names and sets the value of the specified property to the
207 // final argument.
208 static naRef f_setprop(naContext c, naRef me, int argc, naRef* args)
209 {
210 #define BUFLEN 1024
211     char buf[BUFLEN + 1];
212     buf[BUFLEN] = 0;
213     char* p = buf;
214     int buflen = BUFLEN;
215     for(int i=0; i<argc-1; i++) {
216         naRef s = naStringValue(c, args[i]);
217         if(naIsNil(s)) return naNil();
218         strncpy(p, naStr_data(s), buflen);
219         p += naStr_len(s);
220         buflen = BUFLEN - (p - buf);
221         if(i < (argc-2) && buflen > 0) {
222             *p++ = '/';
223             buflen--;
224         }
225     }
226
227     SGPropertyNode* props = globals->get_props();
228     naRef val = args[argc-1];
229     bool result = false;
230     try {
231         if(naIsString(val)) result = props->setStringValue(buf, naStr_data(val));
232         else {
233             naRef n = naNumValue(val);
234             if(naIsNil(n))
235                 naRuntimeError(c, "setprop() value is not string or number");
236             result = props->setDoubleValue(buf, n.num);
237         }
238     } catch (const string& err) {
239         naRuntimeError(c, (char *)err.c_str());
240     }
241     return naNum(result);
242 #undef BUFLEN
243 }
244
245 // print() extension function.  Concatenates and prints its arguments
246 // to the FlightGear log.  Uses the highest log level (SG_ALERT), to
247 // make sure it appears.  Is there better way to do this?
248 static naRef f_print(naContext c, naRef me, int argc, naRef* args)
249 {
250     string buf;
251     int n = argc;
252     for(int i=0; i<n; i++) {
253         naRef s = naStringValue(c, args[i]);
254         if(naIsNil(s)) continue;
255         buf += naStr_data(s);
256     }
257     SG_LOG(SG_GENERAL, SG_ALERT, buf);
258     return naNum(buf.length());
259 }
260
261 // fgcommand() extension function.  Executes a named command via the
262 // FlightGear command manager.  Takes a single property node name as
263 // an argument.
264 static naRef f_fgcommand(naContext c, naRef me, int argc, naRef* args)
265 {
266     naRef cmd = argc > 0 ? args[0] : naNil();
267     naRef props = argc > 1 ? args[1] : naNil();
268     if(!naIsString(cmd) || (!naIsNil(props) && !naIsGhost(props)))
269         naRuntimeError(c, "bad arguments to fgcommand()");
270     SGPropertyNode_ptr tmp, *node;
271     if(!naIsNil(props))
272         node = (SGPropertyNode_ptr*)naGhost_ptr(props);
273     else {
274         tmp = new SGPropertyNode();
275         node = &tmp;
276     }
277     return naNum(globals->get_commands()->execute(naStr_data(cmd), *node));
278 }
279
280 // settimer(func, dt, simtime) extension function.  Falls through to
281 // FGNasalSys::setTimer().  See there for docs.
282 static naRef f_settimer(naContext c, naRef me, int argc, naRef* args)
283 {
284     nasalSys->setTimer(c, argc, args);
285     return naNil();
286 }
287
288 // setlistener(func, property, bool) extension function.  Falls through to
289 // FGNasalSys::setListener().  See there for docs.
290 static naRef f_setlistener(naContext c, naRef me, int argc, naRef* args)
291 {
292     return nasalSys->setListener(c, argc, args);
293 }
294
295 // removelistener(int) extension function. Falls through to
296 // FGNasalSys::removeListener(). See there for docs.
297 static naRef f_removelistener(naContext c, naRef me, int argc, naRef* args)
298 {
299     return nasalSys->removeListener(c, argc, args);
300 }
301
302 // Returns a ghost handle to the argument to the currently executing
303 // command
304 static naRef f_cmdarg(naContext c, naRef me, int argc, naRef* args)
305 {
306     return nasalSys->cmdArgGhost();
307 }
308
309 // Sets up a property interpolation.  The first argument is either a
310 // ghost (SGPropertyNode_ptr*) or a string (global property path) to
311 // interpolate.  The second argument is a vector of pairs of
312 // value/delta numbers.
313 static naRef f_interpolate(naContext c, naRef me, int argc, naRef* args)
314 {
315     SGPropertyNode* node;
316     naRef prop = argc > 0 ? args[0] : naNil();
317     if(naIsString(prop)) node = fgGetNode(naStr_data(prop), true);
318     else if(naIsGhost(prop)) node = *(SGPropertyNode_ptr*)naGhost_ptr(prop);
319     else return naNil();
320
321     naRef curve = argc > 1 ? args[1] : naNil();
322     if(!naIsVector(curve)) return naNil();
323     int nPoints = naVec_size(curve) / 2;
324     double* values = new double[nPoints];
325     double* deltas = new double[nPoints];
326     for(int i=0; i<nPoints; i++) {
327         values[i] = naNumValue(naVec_get(curve, 2*i)).num;
328         deltas[i] = naNumValue(naVec_get(curve, 2*i+1)).num;
329     }
330
331     ((SGInterpolator*)globals->get_subsystem_mgr()
332         ->get_group(SGSubsystemMgr::INIT)->get_subsystem("interpolator"))
333         ->interpolate(node, nPoints, values, deltas);
334
335     delete[] values;
336     delete[] deltas;
337     return naNil();
338 }
339
340 // This is a better RNG than the one in the default Nasal distribution
341 // (which is based on the C library rand() implementation). It will
342 // override.
343 static naRef f_rand(naContext c, naRef me, int argc, naRef* args)
344 {
345     return naNum(sg_random());
346 }
347
348 static naRef f_srand(naContext c, naRef me, int argc, naRef* args)
349 {
350     sg_srandom_time();
351     return naNum(0);
352 }
353
354 static naRef f_abort(naContext c, naRef me, int argc, naRef* args)
355 {
356     abort();
357     return naNil();
358 }
359
360 // Return an array listing of all files in a directory
361 static naRef f_directory(naContext c, naRef me, int argc, naRef* args)
362 {
363     if(argc != 1 || !naIsString(args[0]))
364         naRuntimeError(c, "bad arguments to directory()");
365     naRef ldir = args[0];
366     ulDir* dir = ulOpenDir(naStr_data(args[0]));
367     if(!dir) return naNil();
368     naRef result = naNewVector(c);
369     ulDirEnt* dent;
370     while((dent = ulReadDir(dir)))
371         naVec_append(result, naStr_fromdata(naNewString(c), dent->d_name,
372                                             strlen(dent->d_name)));
373     ulCloseDir(dir);
374     return result;
375 }
376
377 /**
378  * Given a data path, resolve it in FG_ROOT or an FG_AIRCRFT directory
379  */
380 static naRef f_resolveDataPath(naContext c, naRef me, int argc, naRef* args)
381 {
382     if(argc != 1 || !naIsString(args[0]))
383         naRuntimeError(c, "bad arguments to resolveDataPath()");
384
385     SGPath p = globals->resolve_maybe_aircraft_path(naStr_data(args[0]));
386     const char* pdata = p.c_str();
387     return naStr_fromdata(naNewString(c), const_cast<char*>(pdata), strlen(pdata));
388 }
389
390 // Parse XML file.
391 //     parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
392 //
393 // <path>      ... absolute path to an XML file
394 // <start-tag> ... callback function with two args: tag name, attribute hash
395 // <end-tag>   ... callback function with one arg:  tag name
396 // <data>      ... callback function with one arg:  data
397 // <pi>        ... callback function with two args: target, data
398 //                 (pi = "processing instruction")
399 // All four callback functions are optional and default to nil.
400 // The function returns nil on error, or the validated file name otherwise.
401 static naRef f_parsexml(naContext c, naRef me, int argc, naRef* args)
402 {
403     if(argc < 1 || !naIsString(args[0]))
404         naRuntimeError(c, "parsexml(): path argument missing or not a string");
405     if(argc > 5) argc = 5;
406     for(int i=1; i<argc; i++)
407         if(!(naIsNil(args[i]) || naIsFunc(args[i])))
408             naRuntimeError(c, "parsexml(): callback argument not a function");
409
410     const char* file = fgValidatePath(naStr_data(args[0]), false);
411     if(!file) {
412         naRuntimeError(c, "parsexml(): reading '%s' denied "
413                 "(unauthorized access)", naStr_data(args[0]));
414         return naNil();
415     }
416     std::ifstream input(file);
417     NasalXMLVisitor visitor(c, argc, args);
418     try {
419         readXML(input, visitor);
420     } catch (const sg_exception& e) {
421         naRuntimeError(c, "parsexml(): file '%s' %s",
422                 file, e.getFormattedMessage().c_str());
423         return naNil();
424     }
425     return naStr_fromdata(naNewString(c), const_cast<char*>(file), strlen(file));
426 }
427
428 // Return UNIX epoch time in seconds.
429 static naRef f_systime(naContext c, naRef me, int argc, naRef* args)
430 {
431 #ifdef _WIN32
432     FILETIME ft;
433     GetSystemTimeAsFileTime(&ft);
434     double t = (4294967296.0 * ft.dwHighDateTime + ft.dwLowDateTime);
435     // Converts from 100ns units in 1601 epoch to unix epoch in sec
436     return naNum((t * 1e-7) - 11644473600.0);
437 #else
438     time_t t;
439     struct timeval td;
440     do { t = time(0); gettimeofday(&td, 0); } while(t != time(0));
441     return naNum(t + 1e-6 * td.tv_usec);
442 #endif
443 }
444
445 // Convert a cartesian point to a geodetic lat/lon/altitude.
446 static naRef f_carttogeod(naContext c, naRef me, int argc, naRef* args)
447 {
448     double lat, lon, alt, xyz[3];
449     if(argc != 3) naRuntimeError(c, "carttogeod() expects 3 arguments");
450     for(int i=0; i<3; i++)
451         xyz[i] = naNumValue(args[i]).num;
452     sgCartToGeod(xyz, &lat, &lon, &alt);
453     lat *= SG_RADIANS_TO_DEGREES;
454     lon *= SG_RADIANS_TO_DEGREES;
455     naRef vec = naNewVector(c);
456     naVec_append(vec, naNum(lat));
457     naVec_append(vec, naNum(lon));
458     naVec_append(vec, naNum(alt));
459     return vec;
460 }
461
462 // Convert a geodetic lat/lon/altitude to a cartesian point.
463 static naRef f_geodtocart(naContext c, naRef me, int argc, naRef* args)
464 {
465     if(argc != 3) naRuntimeError(c, "geodtocart() expects 3 arguments");
466     double lat = naNumValue(args[0]).num * SG_DEGREES_TO_RADIANS;
467     double lon = naNumValue(args[1]).num * SG_DEGREES_TO_RADIANS;
468     double alt = naNumValue(args[2]).num;
469     double xyz[3];
470     sgGeodToCart(lat, lon, alt, xyz);
471     naRef vec = naNewVector(c);
472     naVec_append(vec, naNum(xyz[0]));
473     naVec_append(vec, naNum(xyz[1]));
474     naVec_append(vec, naNum(xyz[2]));
475     return vec;
476 }
477
478 // For given geodetic point return array with elevation, and a material data
479 // hash, or nil if there's no information available (tile not loaded). If
480 // information about the material isn't available, then nil is returned instead
481 // of the hash.
482 static naRef f_geodinfo(naContext c, naRef me, int argc, naRef* args)
483 {
484 #define HASHSET(s,l,n) naHash_set(matdata, naStr_fromdata(naNewString(c),s,l),n)
485     if(argc < 2 || argc > 3)
486         naRuntimeError(c, "geodinfo() expects 2 or 3 arguments: lat, lon [, maxalt]");
487     double lat = naNumValue(args[0]).num;
488     double lon = naNumValue(args[1]).num;
489     double elev = argc == 3 ? naNumValue(args[2]).num : 10000;
490     const SGMaterial *mat;
491     SGGeod geod = SGGeod::fromDegM(lon, lat, elev);
492     if(!globals->get_scenery()->get_elevation_m(geod, elev, &mat))
493         return naNil();
494     naRef vec = naNewVector(c);
495     naVec_append(vec, naNum(elev));
496     naRef matdata = naNil();
497     if(mat) {
498         matdata = naNewHash(c);
499         naRef names = naNewVector(c);
500         const vector<string> n = mat->get_names();
501         for(unsigned int i=0; i<n.size(); i++)
502             naVec_append(names, naStr_fromdata(naNewString(c),
503                     const_cast<char*>(n[i].c_str()), n[i].size()));
504         HASHSET("names", 5, names);
505         HASHSET("solid", 5, naNum(mat->get_solid()));
506         HASHSET("friction_factor", 15, naNum(mat->get_friction_factor()));
507         HASHSET("rolling_friction", 16, naNum(mat->get_rolling_friction()));
508         HASHSET("load_resistance", 15, naNum(mat->get_load_resistance()));
509         HASHSET("bumpiness", 9, naNum(mat->get_bumpiness()));
510         HASHSET("light_coverage", 14, naNum(mat->get_light_coverage()));
511     }
512     naVec_append(vec, matdata);
513     return vec;
514 #undef HASHSET
515 }
516
517
518 class AirportInfoFilter : public FGAirport::AirportFilter
519 {
520 public:
521     AirportInfoFilter() : type(FGPositioned::AIRPORT) {
522     }
523
524     virtual FGPositioned::Type minType() const {
525         return type;
526     }
527
528     virtual FGPositioned::Type maxType() const {
529         return type;
530     }
531
532     FGPositioned::Type type;
533 };
534
535 // Returns data hash for particular or nearest airport of a <type>, or nil
536 // on error.
537 //
538 // airportinfo(<id>);                   e.g. "KSFO"
539 // airportinfo(<type>);                 type := ("airport"|"seaport"|"heliport")
540 // airportinfo()                        same as  airportinfo("airport")
541 // airportinfo(<lat>, <lon> [, <type>]);
542 static naRef f_airportinfo(naContext c, naRef me, int argc, naRef* args)
543 {
544     static SGConstPropertyNode_ptr latn = fgGetNode("/position/latitude-deg", true);
545     static SGConstPropertyNode_ptr lonn = fgGetNode("/position/longitude-deg", true);
546     SGGeod pos;
547     FGAirport* apt = NULL;
548
549     if(argc >= 2 && naIsNum(args[0]) && naIsNum(args[1])) {
550         pos = SGGeod::fromDeg(args[1].num, args[0].num);
551         args += 2;
552         argc -= 2;
553     } else {
554         pos = SGGeod::fromDeg(lonn->getDoubleValue(), latn->getDoubleValue());
555     }
556
557     double maxRange = 10000.0; // expose this? or pick a smaller value?
558
559     AirportInfoFilter filter; // defaults to airports only
560
561     if(argc == 0) {
562         // fall through and use AIRPORT
563     } else if(argc == 1 && naIsString(args[0])) {
564         const char *s = naStr_data(args[0]);
565         if(!strcmp(s, "airport")) filter.type = FGPositioned::AIRPORT;
566         else if(!strcmp(s, "seaport")) filter.type = FGPositioned::SEAPORT;
567         else if(!strcmp(s, "heliport")) filter.type = FGPositioned::HELIPORT;
568         else {
569             // user provided an <id>, hopefully
570             apt = FGAirport::findByIdent(s);
571             if (!apt) {
572                 // return nil here, but don't raise a runtime error; this is a
573                 // legitamate way to validate an ICAO code, for example in a
574                 // dialog box or similar.
575                 return naNil();
576             }
577         }
578     } else {
579         naRuntimeError(c, "airportinfo() with invalid function arguments");
580         return naNil();
581     }
582
583     if(!apt) {
584         apt = FGAirport::findClosest(pos, maxRange, &filter);
585         if(!apt) return naNil();
586     }
587
588     string id = apt->ident();
589     string name = apt->name();
590
591     // set runway hash
592     naRef rwys = naNewHash(c);
593     for(unsigned int r=0; r<apt->numRunways(); ++r) {
594         FGRunway* rwy(apt->getRunwayByIndex(r));
595
596         naRef rwyid = naStr_fromdata(naNewString(c),
597                       const_cast<char *>(rwy->ident().c_str()),
598                       rwy->ident().length());
599
600         naRef rwydata = naNewHash(c);
601 #define HASHSET(s,l,n) naHash_set(rwydata, naStr_fromdata(naNewString(c),s,l),n)
602         HASHSET("id", 2, rwyid);
603         HASHSET("lat", 3, naNum(rwy->latitude()));
604         HASHSET("lon", 3, naNum(rwy->longitude()));
605         HASHSET("heading", 7, naNum(rwy->headingDeg()));
606         HASHSET("length", 6, naNum(rwy->lengthM()));
607         HASHSET("width", 5, naNum(rwy->widthM()));
608         HASHSET("threshold", 9, naNum(rwy->displacedThresholdM()));
609         HASHSET("stopway", 7, naNum(rwy->stopwayM()));
610         
611         if (rwy->ILS()) {
612           HASHSET("ils_frequency_mhz", 17, naNum(rwy->ILS()->get_freq() / 100.0));
613         }
614         
615 #undef HASHSET
616         naHash_set(rwys, rwyid, rwydata);
617     }
618
619     // set airport hash
620     naRef aptdata = naNewHash(c);
621 #define HASHSET(s,l,n) naHash_set(aptdata, naStr_fromdata(naNewString(c),s,l),n)
622     HASHSET("id", 2, naStr_fromdata(naNewString(c),
623             const_cast<char *>(id.c_str()), id.length()));
624     HASHSET("name", 4, naStr_fromdata(naNewString(c),
625             const_cast<char *>(name.c_str()), name.length()));
626     HASHSET("lat", 3, naNum(apt->getLatitude()));
627     HASHSET("lon", 3, naNum(apt->getLongitude()));
628     HASHSET("elevation", 9, naNum(apt->getElevation() * SG_FEET_TO_METER));
629     HASHSET("has_metar", 9, naNum(apt->getMetar()));
630     HASHSET("runways", 7, rwys);
631 #undef HASHSET
632     return aptdata;
633 }
634
635
636 // Table of extension functions.  Terminate with zeros.
637 static struct { const char* name; naCFunction func; } funcs[] = {
638     { "getprop",   f_getprop },
639     { "setprop",   f_setprop },
640     { "print",     f_print },
641     { "_fgcommand", f_fgcommand },
642     { "settimer",  f_settimer },
643     { "_setlistener", f_setlistener },
644     { "removelistener", f_removelistener },
645     { "_cmdarg",  f_cmdarg },
646     { "_interpolate",  f_interpolate },
647     { "rand",  f_rand },
648     { "srand",  f_srand },
649     { "abort", f_abort },
650     { "directory", f_directory },
651     { "resolvepath", f_resolveDataPath },
652     { "parsexml", f_parsexml },
653     { "systime", f_systime },
654     { "carttogeod", f_carttogeod },
655     { "geodtocart", f_geodtocart },
656     { "geodinfo", f_geodinfo },
657     { "airportinfo", f_airportinfo },
658     { 0, 0 }
659 };
660
661 naRef FGNasalSys::cmdArgGhost()
662 {
663     return propNodeGhost(_cmdArg);
664 }
665
666 void FGNasalSys::init()
667 {
668     int i;
669
670     _context = naNewContext();
671
672     // Start with globals.  Add it to itself as a recursive
673     // sub-reference under the name "globals".  This gives client-code
674     // write access to the namespace if someone wants to do something
675     // fancy.
676     _globals = naInit_std(_context);
677     naSave(_context, _globals);
678     hashset(_globals, "globals", _globals);
679
680     hashset(_globals, "math", naInit_math(_context));
681     hashset(_globals, "bits", naInit_bits(_context));
682     hashset(_globals, "io", naInit_io(_context));
683     hashset(_globals, "thread", naInit_thread(_context));
684     hashset(_globals, "utf8", naInit_utf8(_context));
685
686     // Add our custom extension functions:
687     for(i=0; funcs[i].name; i++)
688         hashset(_globals, funcs[i].name,
689                 naNewFunc(_context, naNewCCode(_context, funcs[i].func)));
690
691     // And our SGPropertyNode wrapper
692     hashset(_globals, "props", genPropsModule());
693
694     // Make a "__gcsave" hash to hold the naRef objects which get
695     // passed to handles outside the interpreter (to protect them from
696     // begin garbage-collected).
697     _gcHash = naNewHash(_context);
698     hashset(_globals, "__gcsave", _gcHash);
699
700     // Now load the various source files in the Nasal directory
701     SGPath p(globals->get_fg_root());
702     p.append("Nasal");
703     ulDirEnt* dent;
704     ulDir* dir = ulOpenDir(p.c_str());
705     while(dir && (dent = ulReadDir(dir)) != 0) {
706         SGPath fullpath(p);
707         fullpath.append(dent->d_name);
708         SGPath file(dent->d_name);
709         if(file.extension() != "nas") continue;
710         loadModule(fullpath, file.base().c_str());
711     }
712     ulCloseDir(dir);
713
714     // set signal and remove node to avoid restoring at reinit
715     const char *s = "nasal-dir-initialized";
716     SGPropertyNode *signal = fgGetNode("/sim/signals", true);
717     signal->setBoolValue(s, true);
718     signal->removeChildren(s, false);
719
720     // Pull scripts out of the property tree, too
721     loadPropertyScripts();
722 }
723
724 void FGNasalSys::update(double)
725 {
726     if(!_dead_listener.empty()) {
727         vector<FGNasalListener *>::iterator it, end = _dead_listener.end();
728         for(it = _dead_listener.begin(); it != end; ++it) delete *it;
729         _dead_listener.clear();
730     }
731
732     // The global context is a legacy thing.  We use dynamically
733     // created contexts for naCall() now, so that we can call them
734     // recursively.  But there are still spots that want to use it for
735     // naNew*() calls, which end up leaking memory because the context
736     // only clears out its temporary vector when it's *used*.  So just
737     // junk it and fetch a new/reinitialized one every frame.  This is
738     // clumsy: the right solution would use the dynamic context in all
739     // cases and eliminate _context entirely.  But that's more work,
740     // and this works fine (yes, they say "New" and "Free", but
741     // they're very fast, just trust me). -Andy
742     naFreeContext(_context);
743     _context = naNewContext();
744 }
745
746 // Loads the scripts found under /nasal in the global tree
747 void FGNasalSys::loadPropertyScripts()
748 {
749     SGPropertyNode* nasal = globals->get_props()->getNode("nasal");
750     if(!nasal) return;
751
752     for(int i=0; i<nasal->nChildren(); i++) {
753         SGPropertyNode* n = nasal->getChild(i);
754
755         const char* module = n->getName();
756         if(n->hasChild("module"))
757             module = n->getStringValue("module");
758
759         // allow multiple files to be specified within a single
760         // Nasal module tag
761         int j = 0;
762         SGPropertyNode *fn;
763         bool file_specified = false;
764         while((fn = n->getChild("file", j)) != NULL) {
765             file_specified = true;
766             const char* file = fn->getStringValue();
767             SGPath p = globals->resolve_maybe_aircraft_path(file);
768             loadModule(p, module);
769             j++;
770         }
771
772         const char* src = n->getStringValue("script");
773         if(!n->hasChild("script")) src = 0; // Hrm...
774         if(src)
775             createModule(module, n->getPath().c_str(), src, strlen(src));
776
777         if(!file_specified && !src)
778             SG_LOG(SG_NASAL, SG_ALERT, "Nasal error: " <<
779                    "no <file> or <script> defined in " <<
780                    "/nasal/" << module);
781     }
782 }
783
784 // Logs a runtime error, with stack trace, to the FlightGear log stream
785 void FGNasalSys::logError(naContext context)
786 {
787     SG_LOG(SG_NASAL, SG_ALERT,
788            "Nasal runtime error: " << naGetError(context));
789     SG_LOG(SG_NASAL, SG_ALERT,
790            "  at " << naStr_data(naGetSourceFile(context, 0)) <<
791            ", line " << naGetLine(context, 0));
792     for(int i=1; i<naStackDepth(context); i++)
793         SG_LOG(SG_NASAL, SG_ALERT,
794                "  called from: " << naStr_data(naGetSourceFile(context, i)) <<
795                ", line " << naGetLine(context, i));
796 }
797
798 // Reads a script file, executes it, and places the resulting
799 // namespace into the global namespace under the specified module
800 // name.
801 void FGNasalSys::loadModule(SGPath file, const char* module)
802 {
803     int len = 0;
804     char* buf = readfile(file.c_str(), &len);
805     if(!buf) {
806         SG_LOG(SG_NASAL, SG_ALERT,
807                "Nasal error: could not read script file " << file.c_str()
808                << " into module " << module);
809         return;
810     }
811
812     createModule(module, file.c_str(), buf, len);
813     delete[] buf;
814 }
815
816 // Parse and run.  Save the local variables namespace, as it will
817 // become a sub-object of globals.  The optional "arg" argument can be
818 // used to pass an associated property node to the module, which can then
819 // be accessed via cmdarg().  (This is, for example, used by XML dialogs.)
820 void FGNasalSys::createModule(const char* moduleName, const char* fileName,
821                               const char* src, int len,
822                               const SGPropertyNode* cmdarg,
823                               int argc, naRef* args)
824 {
825     naRef code = parse(fileName, src, len);
826     if(naIsNil(code))
827         return;
828
829     // See if we already have a module hash to use.  This allows the
830     // user to, for example, add functions to the built-in math
831     // module.  Make a new one if necessary.
832     naRef locals;
833     naRef modname = naNewString(_context);
834     naStr_fromdata(modname, (char*)moduleName, strlen(moduleName));
835     if(!naHash_get(_globals, modname, &locals))
836         locals = naNewHash(_context);
837
838     _cmdArg = (SGPropertyNode*)cmdarg;
839
840     call(code, argc, args, locals);
841     hashset(_globals, moduleName, locals);
842 }
843
844 void FGNasalSys::deleteModule(const char* moduleName)
845 {
846     naRef modname = naNewString(_context);
847     naStr_fromdata(modname, (char*)moduleName, strlen(moduleName));
848     naHash_delete(_globals, modname);
849 }
850
851 naRef FGNasalSys::parse(const char* filename, const char* buf, int len)
852 {
853     int errLine = -1;
854     naRef srcfile = naNewString(_context);
855     naStr_fromdata(srcfile, (char*)filename, strlen(filename));
856     naRef code = naParseCode(_context, srcfile, 1, (char*)buf, len, &errLine);
857     if(naIsNil(code)) {
858         SG_LOG(SG_NASAL, SG_ALERT,
859                "Nasal parse error: " << naGetError(_context) <<
860                " in "<< filename <<", line " << errLine);
861         return naNil();
862     }
863
864     // Bind to the global namespace before returning
865     return naBindFunction(_context, code, _globals);
866 }
867
868 bool FGNasalSys::handleCommand(const SGPropertyNode* arg)
869 {
870     const char* nasal = arg->getStringValue("script");
871     const char* moduleName = arg->getStringValue("module");
872     naRef code = parse(arg->getPath(true).c_str(), nasal, strlen(nasal));
873     if(naIsNil(code)) return false;
874
875     // Commands can be run "in" a module.  Make sure that module
876     // exists, and set it up as the local variables hash for the
877     // command.
878     naRef locals = naNil();
879     if(moduleName[0]) {
880         naRef modname = naNewString(_context);
881         naStr_fromdata(modname, (char*)moduleName, strlen(moduleName));
882         if(!naHash_get(_globals, modname, &locals)) {
883             locals = naNewHash(_context);
884             naHash_set(_globals, modname, locals);
885         }
886     }
887
888     // Cache this command's argument for inspection via cmdarg().  For
889     // performance reasons, we won't bother with it if the invoked
890     // code doesn't need it.
891     _cmdArg = (SGPropertyNode*)arg;
892
893     call(code, 0, 0, locals);
894     return true;
895 }
896
897 // settimer(func, dt, simtime) extension function.  The first argument
898 // is a Nasal function to call, the second is a delta time (from now),
899 // in seconds.  The third, if present, is a boolean value indicating
900 // that "real world" time (rather than simulator time) is to be used.
901 //
902 // Implementation note: the FGTimer objects don't live inside the
903 // garbage collector, so the Nasal handler functions have to be
904 // "saved" somehow lest they be inadvertently cleaned.  In this case,
905 // they are inserted into a globals.__gcsave hash and removed on
906 // expiration.
907 void FGNasalSys::setTimer(naContext c, int argc, naRef* args)
908 {
909     // Extract the handler, delta, and simtime arguments:
910     naRef handler = argc > 0 ? args[0] : naNil();
911     if(!(naIsCode(handler) || naIsCCode(handler) || naIsFunc(handler))) {
912         naRuntimeError(c, "settimer() with invalid function argument");
913         return;
914     }
915
916     naRef delta = argc > 1 ? args[1] : naNil();
917     if(naIsNil(delta)) {
918         naRuntimeError(c, "settimer() with invalid time argument");
919         return;
920     }
921
922     bool simtime = (argc > 2 && naTrue(args[2])) ? false : true;
923
924     // Generate and register a C++ timer handler
925     NasalTimer* t = new NasalTimer;
926     t->handler = handler;
927     t->gcKey = gcSave(handler);
928     t->nasal = this;
929
930     globals->get_event_mgr()->addEvent("NasalTimer",
931                                        t, &NasalTimer::timerExpired,
932                                        delta.num, simtime);
933 }
934
935 void FGNasalSys::handleTimer(NasalTimer* t)
936 {
937     call(t->handler, 0, 0, naNil());
938     gcRelease(t->gcKey);
939 }
940
941 int FGNasalSys::gcSave(naRef r)
942 {
943     int key = _nextGCKey++;
944     naHash_set(_gcHash, naNum(key), r);
945     return key;
946 }
947
948 void FGNasalSys::gcRelease(int key)
949 {
950     naHash_delete(_gcHash, naNum(key));
951 }
952
953 void FGNasalSys::NasalTimer::timerExpired()
954 {
955     nasal->handleTimer(this);
956     delete this;
957 }
958
959 int FGNasalSys::_listenerId = 0;
960
961 // setlistener(<property>, <func> [, <initial=0> [, <persistent=1>]])
962 // Attaches a callback function to a property (specified as a global
963 // property path string or a SGPropertyNode_ptr* ghost). If the third,
964 // optional argument (default=0) is set to 1, then the function is also
965 // called initially. If the fourth, optional argument is set to 0, then the
966 // function is only called when the property node value actually changes.
967 // Otherwise it's called independent of the value whenever the node is
968 // written to (default). The setlistener() function returns a unique
969 // id number, which is to be used as argument to the removelistener()
970 // function.
971 naRef FGNasalSys::setListener(naContext c, int argc, naRef* args)
972 {
973     SGPropertyNode_ptr node;
974     naRef prop = argc > 0 ? args[0] : naNil();
975     if(naIsString(prop)) node = fgGetNode(naStr_data(prop), true);
976     else if(naIsGhost(prop)) node = *(SGPropertyNode_ptr*)naGhost_ptr(prop);
977     else {
978         naRuntimeError(c, "setlistener() with invalid property argument");
979         return naNil();
980     }
981
982     if(node->isTied())
983         SG_LOG(SG_NASAL, SG_DEBUG, "Attaching listener to tied property " <<
984                 node->getPath());
985
986     naRef code = argc > 1 ? args[1] : naNil();
987     if(!(naIsCode(code) || naIsCCode(code) || naIsFunc(code))) {
988         naRuntimeError(c, "setlistener() with invalid function argument");
989         return naNil();
990     }
991
992     int init = argc > 2 && naIsNum(args[2]) ? int(args[2].num) : 0;
993     int type = argc > 3 && naIsNum(args[3]) ? int(args[3].num) : 1;
994     FGNasalListener *nl = new FGNasalListener(node, code, this,
995             gcSave(code), _listenerId, init, type);
996
997     node->addChangeListener(nl, init);
998
999     _listener[_listenerId] = nl;
1000     return naNum(_listenerId++);
1001 }
1002
1003 // removelistener(int) extension function. The argument is the id of
1004 // a listener as returned by the setlistener() function.
1005 naRef FGNasalSys::removeListener(naContext c, int argc, naRef* args)
1006 {
1007     naRef id = argc > 0 ? args[0] : naNil();
1008     map<int, FGNasalListener *>::iterator it = _listener.find(int(id.num));
1009
1010     if(!naIsNum(id) || it == _listener.end() || it->second->_dead) {
1011         naRuntimeError(c, "removelistener() with invalid listener id");
1012         return naNil();
1013     }
1014
1015     it->second->_dead = true;
1016     _dead_listener.push_back(it->second);
1017     _listener.erase(it);
1018     return naNum(_listener.size());
1019 }
1020
1021
1022
1023 // FGNasalListener class.
1024
1025 FGNasalListener::FGNasalListener(SGPropertyNode *node, naRef code,
1026                                  FGNasalSys* nasal, int key, int id,
1027                                  int init, int type) :
1028     _node(node),
1029     _code(code),
1030     _gcKey(key),
1031     _id(id),
1032     _nas(nasal),
1033     _init(init),
1034     _type(type),
1035     _active(0),
1036     _dead(false),
1037     _last_int(0L),
1038     _last_float(0.0)
1039 {
1040     if(_type == 0 && !_init)
1041         changed(node);
1042 }
1043
1044 FGNasalListener::~FGNasalListener()
1045 {
1046     _node->removeChangeListener(this);
1047     _nas->gcRelease(_gcKey);
1048 }
1049
1050 void FGNasalListener::call(SGPropertyNode* which, naRef mode)
1051 {
1052     if(_active || _dead) return;
1053     SG_LOG(SG_NASAL, SG_DEBUG, "trigger listener #" << _id);
1054     _active++;
1055     naRef arg[4];
1056     arg[0] = _nas->propNodeGhost(which);
1057     arg[1] = _nas->propNodeGhost(_node);
1058     arg[2] = mode;                  // value changed, child added/removed
1059     arg[3] = naNum(_node != which); // child event?
1060     _nas->call(_code, 4, arg, naNil());
1061     _active--;
1062 }
1063
1064 void FGNasalListener::valueChanged(SGPropertyNode* node)
1065 {
1066     if(_type < 2 && node != _node) return;   // skip child events
1067     if(_type > 0 || changed(_node) || _init)
1068         call(node, naNum(0));
1069
1070     _init = 0;
1071 }
1072
1073 void FGNasalListener::childAdded(SGPropertyNode*, SGPropertyNode* child)
1074 {
1075     if(_type == 2) call(child, naNum(1));
1076 }
1077
1078 void FGNasalListener::childRemoved(SGPropertyNode*, SGPropertyNode* child)
1079 {
1080     if(_type == 2) call(child, naNum(-1));
1081 }
1082
1083 bool FGNasalListener::changed(SGPropertyNode* node)
1084 {
1085     using namespace simgear;
1086     props::Type type = node->getType();
1087     if(type == props::NONE) return false;
1088     if(type == props::UNSPECIFIED) return true;
1089
1090     bool result;
1091     switch(type) {
1092     case props::BOOL:
1093     case props::INT:
1094     case props::LONG:
1095         {
1096             long l = node->getLongValue();
1097             result = l != _last_int;
1098             _last_int = l;
1099             return result;
1100         }
1101     case props::FLOAT:
1102     case props::DOUBLE:
1103         {
1104             double d = node->getDoubleValue();
1105             result = d != _last_float;
1106             _last_float = d;
1107             return result;
1108         }
1109     default:
1110         {
1111             string s = node->getStringValue();
1112             result = s != _last_string;
1113             _last_string = s;
1114             return result;
1115         }
1116     }
1117 }
1118
1119
1120
1121 // FGNasalModelData class.  If sgLoad3DModel() is called with a pointer to
1122 // such a class, then it lets modelLoaded() run the <load> script, and the
1123 // destructor the <unload> script. The latter happens when the model branch
1124 // is removed from the scene graph.
1125
1126 unsigned int FGNasalModelData::_module_id = 0;
1127
1128 void FGNasalModelData::modelLoaded(const string& path, SGPropertyNode *prop,
1129                                    osg::Node *)
1130 {
1131     if(!prop)
1132         return;
1133     SGPropertyNode *nasal = prop->getNode("nasal");
1134     if(!nasal)
1135         return;
1136
1137     SGPropertyNode *load = nasal->getNode("load");
1138     _unload = nasal->getNode("unload");
1139     if(!load && !_unload)
1140         return;
1141
1142     std::stringstream m;
1143     m << "__model" << _module_id++;
1144     _module = m.str();
1145
1146     const char *s = load ? load->getStringValue() : "";
1147
1148     naRef arg[2];
1149     arg[0] = nasalSys->propNodeGhost(_root);
1150     arg[1] = nasalSys->propNodeGhost(prop);
1151     nasalSys->createModule(_module.c_str(), path.c_str(), s, strlen(s),
1152                            _root, 2, arg);
1153 }
1154
1155 FGNasalModelData::~FGNasalModelData()
1156 {
1157     if(_module.empty())
1158         return;
1159
1160     if(!nasalSys) {
1161         SG_LOG(SG_NASAL, SG_WARN, "Trying to run an <unload> script "
1162                 "without Nasal subsystem present.");
1163         return;
1164     }
1165
1166     if(_unload) {
1167         const char *s = _unload->getStringValue();
1168         nasalSys->createModule(_module.c_str(), _module.c_str(), s, strlen(s), _root);
1169     }
1170     nasalSys->deleteModule(_module.c_str());
1171 }
1172
1173
1174
1175 // NasalXMLVisitor class: handles EasyXML visitor callback for parsexml()
1176 //
1177 NasalXMLVisitor::NasalXMLVisitor(naContext c, int argc, naRef* args) :
1178     _c(naSubContext(c)),
1179     _start_element(argc > 1 ? args[1] : naNil()),
1180     _end_element(argc > 2 ? args[2] : naNil()),
1181     _data(argc > 3 ? args[3] : naNil()),
1182     _pi(argc > 4 ? args[4] : naNil())
1183 {
1184 }
1185
1186 void NasalXMLVisitor::startElement(const char* tag, const XMLAttributes& a)
1187 {
1188     if(naIsNil(_start_element)) return;
1189     naRef attr = naNewHash(_c);
1190     for(int i=0; i<a.size(); i++) {
1191         naRef name = make_string(a.getName(i));
1192         naRef value = make_string(a.getValue(i));
1193         naHash_set(attr, name, value);
1194     }
1195     call(_start_element, 2, make_string(tag), attr);
1196 }
1197
1198 void NasalXMLVisitor::endElement(const char* tag)
1199 {
1200     if(!naIsNil(_end_element)) call(_end_element, 1, make_string(tag));
1201 }
1202
1203 void NasalXMLVisitor::data(const char* str, int len)
1204 {
1205     if(!naIsNil(_data)) call(_data, 1, make_string(str, len));
1206 }
1207
1208 void NasalXMLVisitor::pi(const char* target, const char* data)
1209 {
1210     if(!naIsNil(_pi)) call(_pi, 2, make_string(target), make_string(data));
1211 }
1212
1213 void NasalXMLVisitor::call(naRef func, int num, naRef a, naRef b)
1214 {
1215     naRef args[2];
1216     args[0] = a;
1217     args[1] = b;
1218     naCall(_c, func, num, args, naNil(), naNil());
1219     if(naGetError(_c))
1220         naRethrowError(_c);
1221 }
1222
1223 naRef NasalXMLVisitor::make_string(const char* s, int n)
1224 {
1225     return naStr_fromdata(naNewString(_c), const_cast<char *>(s),
1226                           n < 0 ? strlen(s) : n);
1227 }
1228
1229