FlightGear next
NasalSys.cxx
Go to the documentation of this file.
1// Copyright (C) 2013 James Turner
2//
3// This program is free software; you can redistribute it and/or
4// modify it under the terms of the GNU General Public License as
5// published by the Free Software Foundation; either version 2 of the
6// License, or (at your option) any later version.
7//
8// This program is distributed in the hope that it will be useful, but
9// WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11// General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with this program; if not, write to the Free Software
15// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16
17#include "config.h"
18
19#ifdef HAVE_WINDOWS_H
20#include <windows.h>
21#endif
22
23#ifdef HAVE_SYS_TIME_H
24# include <sys/time.h> // gettimeofday
25#endif
26
27#include <algorithm>
28#include <errno.h>
29#include <stdio.h>
30#include <string.h>
31#include <sys/types.h>
32#include <sys/stat.h>
33#include <fstream>
34#include <sstream>
35#include <vector>
36
37#include <simgear/debug/ErrorReportingCallback.hxx>
38#include <simgear/io/iostreams/sgstream.hxx>
39#include <simgear/math/sg_geodesy.hxx>
40#include <simgear/math/sg_random.hxx>
41#include <simgear/misc/sg_dir.hxx>
42#include <simgear/misc/sg_path.hxx>
43#include <simgear/misc/strutils.hxx>
44#include <simgear/nasal/iolib.h>
45#include <simgear/nasal/nasal.h>
46#include <simgear/props/props.hxx>
47#include <simgear/structure/commands.hxx>
48#include <simgear/structure/event_mgr.hxx>
49#include <simgear/io/sg_mmap.hxx>
50
51#include <simgear/nasal/cppbind/from_nasal.hxx>
52#include <simgear/nasal/cppbind/to_nasal.hxx>
53#include <simgear/nasal/cppbind/Ghost.hxx>
54#include <simgear/nasal/cppbind/NasalHash.hxx>
55#include <simgear/timing/timestamp.hxx>
56
57#include "NasalAddons.hxx"
58#include "NasalAircraft.hxx"
59#include "NasalCanvas.hxx"
60#include "NasalClipboard.hxx"
61#include "NasalCondition.hxx"
62#include "NasalFlightPlan.hxx"
63#include "NasalHTTP.hxx"
64#include "NasalPositioned.hxx"
65#include "NasalSGPath.hxx"
66#include "NasalString.hxx"
67#include "NasalSys.hxx"
68#include "NasalSys_private.hxx"
69#include "NasalTranslations.hxx"
70#include "NasalUnitTesting.hxx"
71
72#include <Main/globals.hxx>
73#include <Main/fg_props.hxx>
75
76using std::map;
77using std::string;
78using std::vector;
79
80void postinitNasalGUI(naRef globals, naContext c);
81
82static FGNasalSys* nasalSys = nullptr;
83
84// this is used by the test-suite to simplify
85// how much Nasal modules we load by default
87
88
89// Listener class for loading Nasal modules on demand
90class FGNasalModuleListener : public SGPropertyChangeListener
91{
92public:
93 FGNasalModuleListener(SGPropertyNode* node);
94
95 virtual void valueChanged(SGPropertyNode* node);
96
97private:
98 SGPropertyNode_ptr _node;
99};
100
101FGNasalModuleListener::FGNasalModuleListener(SGPropertyNode* node) : _node(node)
102{
103}
104
106{
107 const auto enabled = _node->getBoolValue("enabled", false);
108 const auto loaded = _node->getBoolValue("loaded", true);
109 if (enabled && !loaded) {
110 nasalSys->loadPropertyScripts(_node);
111 } else if (!enabled && loaded) {
112 // delete the module
113 std::string module = _node->getNameString();
114 if (_node->hasChild("module")) {
115 module = _node->getStringValue("module");
116 }
117
118 nasalSys->deleteModule(module.c_str());
119 }
120}
121
123
124class TimerObj : public SGReferenced
125{
126public:
127 TimerObj(Context *c, FGNasalSys* sys, naRef f, naRef self, double interval) :
128 _sys(sys),
129 _func(f),
130 _self(self),
131 _interval(interval)
132 {
133 char nm[256];
134 if (c) {
135 snprintf(nm, 128, "maketimer-[%p]-%s:%d", (void*)this, naStr_data(naGetSourceFile(c, 0)), naGetLine(c, 0));
136 }
137 else {
138 snprintf(nm, 128, "maketimer-%p", this);
139 }
140 _name = nm;
141 _gcRoot = naGCSave(f);
142 _gcSelf = naGCSave(self);
143 sys->addPersistentTimer(this);
144 }
145
146 virtual ~TimerObj()
147 {
148 stop();
149 naGCRelease(_gcRoot);
150 naGCRelease(_gcSelf);
151 _sys->removePersistentTimer(this);
152 }
153
154 bool isRunning() const { return _isRunning; }
155
156 void stop()
157 {
158 if (_isRunning) {
159 globals->get_event_mgr()->removeTask(_name);
160 _isRunning = false;
161 }
162 }
163
164 bool isSimTime() const { return _isSimTime; }
165
166 void setSimTime(bool value)
167 {
168 if (_isRunning) {
169 SG_LOG(SG_NASAL, SG_WARN, "can't change type of running timer!");
170 return;
171 }
172
173 _isSimTime = value;
174 }
175
176 void start()
177 {
178 if (_isRunning) {
179 return;
180 }
181
182 _isRunning = true;
183 if (_singleShot) {
184 globals->get_event_mgr()->addEvent(_name, [this](){ this->invoke(); }, _interval, _isSimTime);
185 } else {
186 globals->get_event_mgr()->addTask(_name, [this](){ this->invoke(); },
187 _interval, _interval /* delay */,
188 _isSimTime);
189 }
190 }
191
192 // stop and then start -
193 void restart(double newInterval)
194 {
195 _interval = newInterval;
196 stop();
197 start();
198 }
199
200 void invoke()
201 {
202 if( _singleShot )
203 // Callback may restart the timer, so update status before callback is
204 // called (Prevent warnings of deleting not existing tasks from the
205 // event manager).
206 _isRunning = false;
207
208 naRef *args = nullptr;
209 _sys->callMethod(_func, _self, 0, args, naNil() /* locals */);
210 }
211
212 void setSingleShot(bool aSingleShot)
213 {
214 _singleShot = aSingleShot;
215 }
216
217 bool isSingleShot() const
218 { return _singleShot; }
219
220 const std::string& name() const
221 { return _name; }
222private:
223 std::string _name;
224 FGNasalSys* _sys;
225 naRef _func, _self;
226 int _gcRoot, _gcSelf;
227 bool _isRunning = false;
228 double _interval;
229 bool _singleShot = false;
230 bool _isSimTime = false;
231};
232
233typedef SGSharedPtr<TimerObj> TimerObjRef;
234typedef nasal::Ghost<TimerObjRef> NasalTimerObj;
235
236static void f_timerObj_setSimTime(TimerObj& timer, naContext c, naRef value)
237{
238 if (timer.isRunning()) {
239 naRuntimeError(c, "Timer is running, cannot change type between real/sim time");
240 return;
241 }
242
243 timer.setSimTime(nasal::from_nasal<bool>(c, value));
244}
245
249class TimeStampObj : public SGReferenced
250{
251public:
252 TimeStampObj(Context *c)
253 {
254 timestamp.stamp();
255 }
256
257 virtual ~TimeStampObj() = default;
258
259 void stamp()
260 {
261 timestamp.stamp();
262 }
263 double elapsedMSec()
264 {
265 return timestamp.elapsedMSec();
266 }
267 double elapsedUSec()
268 {
269 return timestamp.elapsedUSec();
270 }
271private:
272 SGTimeStamp timestamp;
273};
274
275typedef SGSharedPtr<TimeStampObj> TimeStampObjRef;
276typedef nasal::Ghost<TimeStampObjRef> NasalTimeStampObj;
277
279
283
285
287{
288 nasalSys = this;
289 d->_globals = naNil();
290 d->_string = naNil();
291 d->_wrappedNodeFunc = naNil();
292
293 d->_log.reset(new simgear::BufferedLogCallback(SG_NASAL, SG_INFO));
294 d->_log->truncateAt(255);
295 sglog().addCallback(d->_log.get());
296
297 naSetErrorHandler(&logError);
298}
299
300// Utility. Sets a named key in a hash by C string, rather than nasal
301// string object.
302void FGNasalSys::hashset(naRef hash, const char* key, naRef val)
303{
304 naRef s = naNewString(d->_context);
305 naStr_fromdata(s, (char*)key, strlen(key));
306 naHash_set(hash, s, val);
307}
308
309void FGNasalSys::globalsSet(const char* key, naRef val)
310{
311 hashset(d->_globals, key, val);
312}
313
314naRef FGNasalSys::call(naRef code, int argc, naRef* args, naRef locals)
315{
316 return callMethod(code, naNil(), argc, args, locals);
317}
318
319naRef FGNasalSys::callWithContext(naContext ctx, naRef code, int argc, naRef* args, naRef locals)
320{
321 return callMethodWithContext(ctx, code, naNil(), argc, args, locals);
322}
323
324// Does a naCall() in a new context. Wrapped here to make lock
325// tracking easier. Extension functions are called with the lock, but
326// we have to release it before making a new naCall(). So rather than
327// drop the lock in every extension function that might call back into
328// Nasal, we keep a stack depth counter here and only unlock/lock
329// around the naCall if it isn't the first one.
330
331naRef FGNasalSys::callMethod(naRef code, naRef self, int argc, naRef* args, naRef locals)
332{
333 try {
334 return naCallMethod(code, self, argc, args, locals);
335 } catch (sg_exception& e) {
336 SG_LOG(SG_NASAL, SG_DEV_ALERT, "caught exception invoking nasal method:" << e.what());
337 return naNil();
338 }
339}
340
341naRef FGNasalSys::callMethodWithContext(naContext ctx, naRef code, naRef self, int argc, naRef* args, naRef locals)
342{
343 try {
344 return naCallMethodCtx(ctx, code, self, argc, args, locals);
345 } catch (sg_exception& e) {
346 SG_LOG(SG_NASAL, SG_DEV_ALERT, "caught exception invoking nasal method:" << e.what());
347 string_list nasalStack;
348 logNasalStack(ctx, nasalStack);
349 flightgear::sentryReportNasalError(string{"Exception invoking nasal method:"} + e.what(), nasalStack);
350 return naNil();
351 }
352}
353
355{
356 if (d->_inited) {
357 SG_LOG(SG_GENERAL, SG_ALERT, "Nasal was not shutdown");
358 }
359 sglog().removeCallback(d->_log.get());
360
361 nasalSys = nullptr;
362}
363
364bool FGNasalSys::parseAndRunWithOutput(const std::string& source, std::string& output, std::string& errors)
365{
366 naContext ctx = naNewContext();
367 naRef code = parse(ctx, "FGNasalSys::parseAndRun()", source.c_str(),
368 source.size(), errors);
369 if(naIsNil(code)) {
370 naFreeContext(ctx);
371 return false;
372 }
373 naRef result = callWithContext(ctx, code, 0, 0, naNil());
374
375 // if there was a result value, try to convert it to a string
376 // value.
377 if (!naIsNil(result)) {
378 naRef s = naStringValue(ctx, result);
379 if (!naIsNil(s)) {
380 output = naStr_data(s);
381 }
382 }
383
384 naFreeContext(ctx);
385 return true;
386}
387
388bool FGNasalSys::parseAndRun(const std::string& source)
389{
390 std::string errors;
391 std::string output;
392 return parseAndRunWithOutput(source, output, errors);
393}
394
395#if 0
396FGNasalScript* FGNasalSys::parseScript(const char* src, const char* name)
397{
398 FGNasalScript* script = new FGNasalScript();
399 script->_gcKey = -1; // important, if we delete it on a parse
400 script->_nas = this; // error, don't clobber a real handle!
401
402 char buf[256];
403 if(!name) {
404 snprintf(buf, 256, "FGNasalScript@%p", (void *)script);
405 name = buf;
406 }
407
408 script->_code = parse(name, src, strlen(src));
409 if(naIsNil(script->_code)) {
410 delete script;
411 return 0;
412 }
413
414 script->_gcKey = gcSave(script->_code);
415 return script;
416}
417#endif
418
419// The get/setprop functions accept a *list* of strings and walk
420// through the property tree with them to find the appropriate node.
421// This allows a Nasal object to hold onto a property path and use it
422// like a node object, e.g. setprop(ObjRoot, "size-parsecs", 2.02). This
423// is the utility function that walks the property tree.
424static SGPropertyNode* findnode(naContext c, naRef* vec, int len, bool create=false)
425{
426 SGPropertyNode* p = globals->get_props();
427 try {
428 for(int i=0; i<len; i++) {
429 naRef a = vec[i];
430 if(!naIsString(a)) {
431 naRuntimeError(c, "bad argument to setprop/getprop path: expected a string");
432 }
433 naRef b = i < len-1 ? naNumValue(vec[i+1]) : naNil();
434 if (!naIsNil(b)) {
435 p = p->getNode(naStr_data(a), (int)b.num, create);
436 i++;
437 } else {
438 p = p->getNode(naStr_data(a), create);
439 }
440 if(p == 0) return 0;
441 }
442 } catch (const string& err) {
443 naRuntimeError(c, (char *)err.c_str());
444 }
445 return p;
446}
447
448// getprop() extension function. Concatenates its string arguments as
449// property names and returns the value of the specified property. Or
450// nil if it doesn't exist.
451static naRef f_getprop(naContext c, naRef me, int argc, naRef* args)
452{
453 using namespace simgear;
454 if (argc < 1) {
455 naRuntimeError(c, "getprop() expects at least 1 argument");
456 }
457 const SGPropertyNode* p = findnode(c, args, argc, false);
458 if(!p) return naNil();
459
460 switch(p->getType()) {
461 case props::BOOL: case props::INT:
462 case props::LONG: case props::FLOAT:
463 case props::DOUBLE:
464 {
465 double dv = p->getDoubleValue();
466 if (SGMisc<double>::isNaN(dv)) {
467 SG_LOG(SG_NASAL, SG_ALERT, "Nasal getprop: property " << p->getPath() << " is NaN");
468 return naNil();
469 }
470
471 return naNum(dv);
472 }
473
474 case props::STRING:
475 case props::UNSPECIFIED:
476 {
477 naRef nastr = naNewString(c);
478 std::string val = p->getStringValue();
479 naStr_fromdata(nastr, val.c_str(), val.length());
480 return nastr;
481 }
482 case props::ALIAS: // <--- FIXME, recurse?
483 default:
484 return naNil();
485 }
486}
487
488// setprop() extension function. Concatenates its string arguments as
489// property names and sets the value of the specified property to the
490// final argument.
491static naRef f_setprop(naContext c, naRef me, int argc, naRef* args)
492{
493 if (argc < 2) {
494 naRuntimeError(c, "setprop() expects at least 2 arguments");
495 }
496 naRef val = args[argc - 1];
497 SGPropertyNode* p = findnode(c, args, argc-1, true);
498
499 bool result = false;
500 try {
501 if(naIsString(val)) result = p->setStringValue(naStr_data(val));
502 else {
503 if(!naIsNum(val))
504 naRuntimeError(c, "setprop() value is not string or number");
505
506 if (SGMisc<double>::isNaN(val.num)) {
507 naRuntimeError(c, "setprop() passed a NaN");
508 }
509
510 result = p->setDoubleValue(val.num);
511 }
512 } catch (const string& err) {
513 naRuntimeError(c, (char *)err.c_str());
514 }
515 return naNum(result);
516}
517
518
519struct SimNasalLogFileLine : SGPropertyChangeListener
520{
522 SGPropertyNode* node = fgGetNode("/sim/nasal-log-file-line", true /*create*/);
523 node->addChangeListener(this, true /*initial*/);
524 }
525 void valueChanged(SGPropertyNode* node) override {
526 _file_line = node->getIntValue();
527 }
528
529 static bool _file_line;
530};
531
533
534// print() extension function. Concatenates and prints its arguments
535// to the FlightGear log. Uses the mandatory log level (SG_MANDATORY_INFO), to
536// make sure it appears. Users should be converted to use logprint() instead,
537// and specify a level explicitly.
538static naRef f_print(naContext c, naRef me, int argc, naRef* args)
539{
540 static SimNasalLogFileLine snlfl;
541 string buf;
542 int n = argc;
543 for(int i=0; i<n; i++) {
544 naRef s = naStringValue(c, args[i]);
545 if(naIsNil(s)) continue;
546 buf += naStr_data(s);
547 }
548 if (snlfl._file_line) {
549 /* Copy what SG_LOG() does, but use nasal file:line instead of
550 our own __FILE__ and __LINE__. We don't (yet) attempt to find
551 the nasal function name. */
552 int frame = 0;
553 const char* file = naStr_data(naGetSourceFile(c, 0));
554 if (simgear::strutils::ends_with( file, "/globals.nas")) {
555 /* This generally means have been called by globals.nas's
556 printf function; go one step up the stack so we give the
557 file:line of the caller of printf, which is generally more
558 useful. */
559 frame += 1;
560 file = naStr_data(naGetSourceFile(c, frame));
561 }
562 int line = naGetLine(c, frame);
563 const char* function = "";
564 if (sglog().would_log(SG_NASAL, SG_MANDATORY_INFO, file, line, function)) {
565 sglog().logCopyingFilename(SG_NASAL, SG_MANDATORY_INFO, file, line, function, buf);
566 }
567 } else {
568 SG_LOG(SG_NASAL, SG_MANDATORY_INFO, buf);
569 }
570 return naNum(buf.length());
571}
572
573// logprint() extension function. Same as above, all arguments after the
574// first argument are concatenated. Argument 0 is the log-level, matching
575// sgDebugPriority.
576static naRef f_logprint(naContext c, naRef me, int argc, naRef* args)
577{
578 if (argc < 1)
579 naRuntimeError(c, "no prioirty argument to logprint()");
580
581 naRef priority = args[0];
582 string buf;
583 int n = argc;
584 for(int i=1; i<n; i++) {
585 naRef s = naStringValue(c, args[i]);
586 if(naIsNil(s)) continue;
587 buf += naStr_data(s);
588 }
589 // use the nasal source file and line for the message location, since
590 // that's more useful than the location here!
591 sglog().logCopyingFilename(SG_NASAL, (sgDebugPriority)(int) priority.num,
592 naStr_data(naGetSourceFile(c, 0)),
593 naGetLine(c, 0), "" /*function*/, buf);
594 return naNum(buf.length());
595}
596
597
598// fgcommand() extension function. Executes a named command via the
599// FlightGear command manager. Takes a single property node name as
600// an argument.
601static naRef f_fgcommand(naContext c, naRef me, int argc, naRef* args)
602{
603 naRef cmd = argc > 0 ? args[0] : naNil();
604 naRef props = argc > 1 ? args[1] : naNil();
605 if(!naIsString(cmd) || (!naIsNil(props) && !naIsGhost(props)))
606 naRuntimeError(c, "bad arguments to fgcommand()");
607 SGPropertyNode_ptr node;
608 if(!naIsNil(props))
609 node = static_cast<SGPropertyNode*>(naGhost_ptr(props));
610 else
611 node = new SGPropertyNode;
612
613 return naNum(globals->get_commands()->execute(naStr_data(cmd), node, nullptr));
614}
615
616// settimer(func, dt, simtime) extension function. Falls through to
617// FGNasalSys::setTimer(). See there for docs.
618static naRef f_settimer(naContext c, naRef me, int argc, naRef* args)
619{
620 nasalSys->setTimer(c, argc, args);
621 return naNil();
622}
623
624static naRef f_makeTimer(naContext c, naRef me, int argc, naRef* args)
625{
626 if (!naIsNum(args[0])) {
627 naRuntimeError(c, "bad interval argument to maketimer");
628 }
629
630 naRef func, self = naNil();
631 if (naIsFunc(args[1])) {
632 func = args[1];
633 } else if ((argc == 3) && naIsFunc(args[2])) {
634 self = args[1];
635 func = args[2];
636 } else {
637 naRuntimeError(c, "bad function/self arguments to maketimer");
638 }
639
640 TimerObj* timerObj = new TimerObj(c, nasalSys, func, self, args[0].num);
641 return nasal::to_nasal(c, timerObj);
642}
643
644static naRef f_makeSingleShot(naContext c, naRef me, int argc, naRef* args)
645{
646 if (!naIsNum(args[0])) {
647 naRuntimeError(c, "bad interval argument to makesingleshot");
648 }
649
650 naRef func, self = naNil();
651 if (naIsFunc(args[1])) {
652 func = args[1];
653 } else if ((argc == 3) && naIsFunc(args[2])) {
654 self = args[1];
655 func = args[2];
656 } else {
657 naRuntimeError(c, "bad function/self arguments to makesingleshot");
658 }
659
660 auto timerObj = new TimerObj(c, nasalSys, func, self, args[0].num);
661 timerObj->setSingleShot(true);
662 timerObj->start();
663 return nasal::to_nasal(c, timerObj);
664}
665
666static naRef f_maketimeStamp(naContext c, naRef me, int argc, naRef* args)
667{
668 TimeStampObj* timeStampObj = new TimeStampObj(c);
669 return nasal::to_nasal(c, timeStampObj);
670}
671
672// setlistener(func, property, bool) extension function. Falls through to
673// FGNasalSys::setListener(). See there for docs.
674static naRef f_setlistener(naContext c, naRef me, int argc, naRef* args)
675{
676 return nasalSys->setListener(c, argc, args);
677}
678
679// removelistener(int) extension function. Falls through to
680// FGNasalSys::removeListener(). See there for docs.
681static naRef f_removelistener(naContext c, naRef me, int argc, naRef* args)
682{
683 return nasalSys->removeListener(c, argc, args);
684}
685
686// Returns a ghost handle to the argument to the currently executing
687// command
688static naRef f_cmdarg(naContext c, naRef me, int argc, naRef* args)
689{
690 return nasalSys->cmdArgGhost();
691}
692
693// Sets up a property interpolation. The first argument is either a
694// ghost (SGPropertyNode*) or a string (global property path) to
695// interpolate. The second argument is a vector of pairs of
696// value/delta numbers.
697static naRef f_interpolate(naContext c, naRef me, int argc, naRef* args)
698{
699 SGPropertyNode* node;
700 naRef prop = argc > 0 ? args[0] : naNil();
701 if(naIsString(prop)) node = fgGetNode(naStr_data(prop), true);
702 else if(naIsGhost(prop)) node = static_cast<SGPropertyNode*>(naGhost_ptr(prop));
703 else return naNil();
704
705 naRef curve = argc > 1 ? args[1] : naNil();
706 if(!naIsVector(curve)) return naNil();
707 int nPoints = naVec_size(curve) / 2;
708
709 simgear::PropertyList value_nodes;
710 value_nodes.reserve(nPoints);
711 std::vector<double> deltas;
712 deltas.reserve(nPoints);
713
714 for( int i = 0; i < nPoints; ++i )
715 {
716 SGPropertyNode* val = new SGPropertyNode;
717 val->setDoubleValue(naNumValue(naVec_get(curve, 2*i)).num);
718 value_nodes.push_back(val);
719 deltas.push_back(naNumValue(naVec_get(curve, 2*i+1)).num);
720 }
721
722 node->interpolate("numeric", value_nodes, deltas, "linear");
723 return naNil();
724}
725
726// This is a better RNG than the one in the default Nasal distribution
727// (which is based on the C library rand() implementation). It will
728// override.
729static naRef f_rand(naContext c, naRef me, int argc, naRef* args)
730{
731 return naNum(sg_random());
732}
733
734static naRef f_srand(naContext c, naRef me, int argc, naRef* args)
735{
736 sg_srandom_time();
737 return naNum(0);
738}
739
740static naRef f_abort(naContext c, naRef me, int argc, naRef* args)
741{
742 abort();
743 return naNil();
744}
745
746// Return an array listing of all files in a directory
747static naRef f_directory(naContext c, naRef me, int argc, naRef* args)
748{
749 if(argc != 1 || !naIsString(args[0]))
750 naRuntimeError(c, "bad arguments to directory()");
751
752 SGPath dirname = SGPath::fromUtf8(naStr_data(args[0])).validate(false);
753 if(dirname.isNull()) {
754 SG_LOG(SG_NASAL, SG_ALERT, "directory(): listing '" <<
755 naStr_data(args[0]) << "' denied (unauthorized directory - authorization"
756 " no longer follows symlinks; to authorize reading additional "
757 "directories, pass them to --allow-nasal-read)");
758 // to avoid breaking dialogs, pretend it doesn't exist rather than erroring out
759 return naNil();
760 }
761
762 simgear::Dir d(dirname);
763 if(!d.exists()) return naNil();
764 naRef result = naNewVector(c);
765
766 simgear::PathList paths = d.children(simgear::Dir::TYPE_FILE | simgear::Dir::TYPE_DIR);
767 for (unsigned int i=0; i<paths.size(); ++i) {
768 std::string p = paths[i].file();
769 naVec_append(result, naStr_fromdata(naNewString(c), p.c_str(), p.size()));
770 }
771
772 return result;
773}
774
778static naRef f_resolveDataPath(naContext c, naRef me, int argc, naRef* args)
779{
780 if(argc != 1 || !naIsString(args[0]))
781 naRuntimeError(c, "bad arguments to resolveDataPath()");
782
783 SGPath p = globals->resolve_maybe_aircraft_path(naStr_data(args[0]));
784 std::string pdata = p.utf8Str();
785 return naStr_fromdata(naNewString(c), const_cast<char*>(pdata.c_str()), pdata.length());
786}
787
788static naRef f_findDataDir(naContext c, naRef me, int argc, naRef* args)
789{
790 if(argc != 1 || !naIsString(args[0]))
791 naRuntimeError(c, "bad arguments to findDataDir()");
792
793 SGPath p = globals->findDataPath(naStr_data(args[0]));
794 std::string pdata = p.utf8Str();
795 return naStr_fromdata(naNewString(c), const_cast<char*>(pdata.c_str()), pdata.length());
796}
797
798class NasalCommand : public SGCommandMgr::Command
799{
800public:
801 NasalCommand(FGNasalSys* sys, naRef f, const std::string& name) :
802 _sys(sys),
803 _func(f),
804 _name(name)
805 {
806 globals->get_commands()->addCommandObject(_name, this);
807 _gcRoot = sys->gcSave(f);
808 }
809
811 {
812 _sys->gcRelease(_gcRoot);
813 }
814
815 bool operator()(const SGPropertyNode* aNode, SGPropertyNode* root) override
816 {
817 _sys->setCmdArg(const_cast<SGPropertyNode*>(aNode));
818 naRef args[1];
819 args[0] = _sys->wrappedPropsNode(const_cast<SGPropertyNode*>(aNode));
820
821 _sys->callMethod(_func, naNil(), 1, args, naNil() /* locals */);
822
823 return true;
824 }
825
826private:
827 FGNasalSys* _sys;
828 naRef _func;
829 int _gcRoot;
830 std::string _name;
831};
832
833static naRef f_addCommand(naContext c, naRef me, int argc, naRef* args)
834{
835 if(argc != 2 || !naIsString(args[0]) || !naIsFunc(args[1]))
836 naRuntimeError(c, "bad arguments to addcommand()");
837
838 const string commandName(naStr_data(args[0]));
839 bool ok = nasalSys->addCommand(args[1], commandName);
840 if (!ok) {
841 naRuntimeError(c, "Failed to add command:%s : likely a duplicate name ", commandName.c_str());
842 }
843
844 return naNil();
845}
846
847static naRef f_removeCommand(naContext c, naRef me, int argc, naRef* args)
848{
849 if ((argc < 1) || !naIsString(args[0]))
850 naRuntimeError(c, "bad argument to removecommand()");
851
852 const string commandName(naStr_data(args[0]));
853 bool ok = nasalSys->removeCommand(commandName);
854 if (!ok) {
855 return naNum(0);
856 }
857 return naNum(1);
858}
859
860static naRef f_open(naContext c, naRef me, int argc, naRef* args)
861{
862 FILE* f;
863 naRef file = argc > 0 ? naStringValue(c, args[0]) : naNil();
864 naRef mode = argc > 1 ? naStringValue(c, args[1]) : naNil();
865 if(!naStr_data(file)) naRuntimeError(c, "bad argument to open()");
866 const char* modestr = naStr_data(mode) ? naStr_data(mode) : "rb";
867 const SGPath filename = SGPath::fromUtf8(naStr_data(file)).validate(
868 strcmp(modestr, "rb") && strcmp(modestr, "r"));
869 if(filename.isNull()) {
870 SG_LOG(SG_NASAL, SG_ALERT, "open(): reading/writing '" <<
871 naStr_data(file) << "' denied (unauthorized directory - authorization"
872 " no longer follows symlinks; to authorize reading additional "
873 "directories, pass them to --allow-nasal-read)");
874 naRuntimeError(c, "open(): access denied (unauthorized directory)");
875 return naNil();
876 }
877
878#if defined(SG_WINDOWS)
879 std::wstring fp = filename.wstr();
880 std::wstring wmodestr = simgear::strutils::convertUtf8ToWString(modestr);
881 f = _wfopen(fp.c_str(), wmodestr.c_str());
882#else
883 std::string fp = filename.utf8Str();
884 f = fopen(fp.c_str(), modestr);
885#endif
886 if(!f) naRuntimeError(c, strerror(errno));
887 return naIOGhost(c, f);
888}
889
890static naRef ftype(naContext ctx, const SGPath& f)
891{
892 const char* t = "unk";
893 if (f.isFile()) t = "reg";
894 else if(f.isDir()) t = "dir";
895 return naStr_fromdata(naNewString(ctx), t, strlen(t));
896}
897
898// io.stat with UTF-8 path support, replaces the default one in
899// Nasal iolib.c which does not support UTF-8 paths
900static naRef f_custom_stat(naContext ctx, naRef me, int argc, naRef* args)
901{
902 naRef pathArg = argc > 0 ? naStringValue(ctx, args[0]) : naNil();
903 if (!naIsString(pathArg))
904 naRuntimeError(ctx, "bad argument to stat()");
905
906 const auto path = SGPath::fromUtf8(naStr_data(pathArg));
907 if (!path.exists()) {
908 return naNil();
909 }
910
911 const SGPath filename = SGPath(path).validate(false);
912 if (filename.isNull()) {
913 SG_LOG(SG_NASAL, SG_ALERT, "stat(): reading '" <<
914 naStr_data(pathArg) << "' denied (unauthorized directory - authorization"
915 " no longer follows symlinks; to authorize reading additional "
916 "directories, pass them to --allow-nasal-read)");
917 naRuntimeError(ctx, "stat(): access denied (unauthorized directory)");
918 return naNil();
919 }
920
921 naRef result = naNewVector(ctx);
922 naVec_setsize(ctx, result, 12);
923
924 // every use in fgdata/Nasal only uses stat() to test existence of
925 // files, not to check any of this information.
926 int n = 0;
927 naVec_set(result, n++, naNum(0)); // device
928 naVec_set(result, n++, naNum(0)); // inode
929 naVec_set(result, n++, naNum(0)); // mode
930 naVec_set(result, n++, naNum(0)); // nlink
931 naVec_set(result, n++, naNum(0)); // uid
932 naVec_set(result, n++, naNum(0)); // guid
933 naVec_set(result, n++, naNum(0)); // rdev
934 naVec_set(result, n++, naNum(filename.sizeInBytes())); // size
935 naVec_set(result, n++, naNum(0)); // atime
936 naVec_set(result, n++, naNum(0)); // mtime
937 naVec_set(result, n++, naNum(0)); // ctime
938 naVec_set(result, n++, ftype(ctx, filename));
939 return result;
940}
941
942// Parse XML file.
943// parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
944//
945// <path> ... absolute path to an XML file
946// <start-tag> ... callback function with two args: tag name, attribute hash
947// <end-tag> ... callback function with one arg: tag name
948// <data> ... callback function with one arg: data
949// <pi> ... callback function with two args: target, data
950// (pi = "processing instruction")
951// All four callback functions are optional and default to nil.
952// The function returns nil on error, or the validated file name otherwise.
953static naRef f_parsexml(naContext c, naRef me, int argc, naRef* args)
954{
955 if(argc < 1 || !naIsString(args[0]))
956 naRuntimeError(c, "parsexml(): path argument missing or not a string");
957 if(argc > 5) argc = 5;
958 for(int i=1; i<argc; i++)
959 if(!(naIsNil(args[i]) || naIsFunc(args[i])))
960 naRuntimeError(c, "parsexml(): callback argument not a function");
961
962 const SGPath file = SGPath::fromUtf8(naStr_data(args[0])).validate(false);
963 if(file.isNull()) {
964 SG_LOG(SG_NASAL, SG_ALERT, "parsexml(): reading '" <<
965 naStr_data(args[0]) << "' denied (unauthorized directory - authorization"
966 " no longer follows symlinks; to authorize reading additional "
967 "directories, pass them to --allow-nasal-read)");
968 naRuntimeError(c, "parsexml(): access denied (unauthorized directory)");
969 return naNil();
970 }
971
972 NasalXMLVisitor visitor(c, argc, args);
973 try {
974 readXML(file, visitor);
975 } catch (const sg_exception& e) {
976 std::string fp = file.utf8Str();
977 naRuntimeError(c, "parsexml(): file '%s' %s", fp.c_str(), e.getFormattedMessage().c_str());
978 return naNil();
979 }
980
981 std::string fs = file.utf8Str();
982 return naStr_fromdata(naNewString(c), fs.c_str(), fs.length());
983}
984
990static naRef f_md5(naContext c, naRef me, int argc, naRef* args)
991{
992 if( argc != 1 || !naIsString(args[0]) )
993 naRuntimeError(c, "md5(): wrong type or number of arguments");
994
995 return nasal::to_nasal(
996 c,
997 simgear::strutils::md5(naStr_data(args[0]), naStr_len(args[0]))
998 );
999}
1000
1001// Return UNIX epoch time in seconds.
1002static naRef f_systime(naContext c, naRef me, int argc, naRef* args)
1003{
1004#ifdef _WIN32
1005 FILETIME ft;
1006 GetSystemTimeAsFileTime(&ft);
1007 double t = (4294967296.0 * ft.dwHighDateTime + ft.dwLowDateTime);
1008 // Converts from 100ns units in 1601 epoch to unix epoch in sec
1009 return naNum((t * 1e-7) - 11644473600.0);
1010#else
1011 struct timeval td;
1012 gettimeofday(&td, 0);
1013 return naNum(td.tv_sec + 1e-6 * td.tv_usec);
1014#endif
1015}
1016
1017// Table of extension functions. Terminate with zeros.
1018static struct { const char* name; naCFunction func;
1019} funcs[] = {
1020 {"getprop", f_getprop},
1021 {"setprop", f_setprop},
1022 {"print", f_print},
1023 {"logprint", f_logprint},
1024 {"_fgcommand", f_fgcommand},
1025 {"settimer", f_settimer},
1026 {"maketimer", f_makeTimer},
1027 {"makesingleshot", f_makeSingleShot},
1028 {"_setlistener", f_setlistener},
1029 {"removelistener", f_removelistener},
1030 {"addcommand", f_addCommand},
1031 {"removecommand", f_removeCommand},
1032 {"_cmdarg", f_cmdarg},
1033 {"_interpolate", f_interpolate},
1034 {"rand", f_rand},
1035 {"srand", f_srand},
1036 {"abort", f_abort},
1037 {"directory", f_directory},
1038 {"resolvepath", f_resolveDataPath},
1039 {"finddata", f_findDataDir},
1040 {"parsexml", f_parsexml},
1041 {"md5", f_md5},
1042 {"systime", f_systime},
1043 {"maketimestamp", f_maketimeStamp},
1044 {0, 0}};
1045
1047{
1048 return propNodeGhost(d->_cmdArg);
1049}
1050
1051void FGNasalSys::initLogLevelConstants()
1052{
1053 hashset(d->_globals, "LOG_BULK", naNum(SG_BULK));
1054 hashset(d->_globals, "LOG_WARN", naNum(SG_WARN));
1055 hashset(d->_globals, "LOG_DEBUG", naNum(SG_DEBUG));
1056 hashset(d->_globals, "LOG_INFO", naNum(SG_INFO));
1057 hashset(d->_globals, "LOG_ALERT", naNum(SG_ALERT));
1058 hashset(d->_globals, "DEV_WARN", naNum(SG_DEV_WARN));
1059 hashset(d->_globals, "DEV_ALERT", naNum(SG_DEV_ALERT));
1060 hashset(d->_globals, "MANDATORY_INFO", naNum(SG_MANDATORY_INFO));
1061}
1062
1063void FGNasalSys::setCmdArg(SGPropertyNode* aNode)
1064{
1065 d->_cmdArg = aNode;
1066}
1067
1069{
1070 if (d->_inited) {
1071 SG_LOG(SG_GENERAL, SG_ALERT, "duplicate init of Nasal");
1072 }
1073 int i;
1074
1075 d->_context = naNewContext();
1076
1077 // Start with globals. Add it to itself as a recursive
1078 // sub-reference under the name "globals". This gives client-code
1079 // write access to the namespace if someone wants to do something
1080 // fancy.
1081 d->_globals = naInit_std(d->_context);
1082 naSave(d->_context, d->_globals);
1083 hashset(d->_globals, "globals", d->_globals);
1084
1085 hashset(d->_globals, "math", naInit_math(d->_context));
1086 hashset(d->_globals, "bits", naInit_bits(d->_context));
1087 hashset(d->_globals, "io", naInit_io(d->_context));
1088 hashset(d->_globals, "thread", naInit_thread(d->_context));
1089 hashset(d->_globals, "utf8", naInit_utf8(d->_context));
1090 hashset(d->_globals, "sqlite", naInit_sqlite(d->_context));
1091
1092 initLogLevelConstants();
1093
1094 // Add our custom extension functions:
1095 for(i=0; funcs[i].name; i++)
1096 hashset(d->_globals, funcs[i].name,
1097 naNewFunc(d->_context, naNewCCode(d->_context, funcs[i].func)));
1098 nasal::Hash io_module = getGlobals().get<nasal::Hash>("io");
1099 io_module.set("open", f_open);
1100 io_module.set("stat", f_custom_stat);
1101
1102 // And our SGPropertyNode wrapper
1103 hashset(d->_globals, "props", genPropsModule());
1104
1105 // Add string methods
1106 d->_string = naInit_string(d->_context);
1107 naSave(d->_context, d->_string);
1108 initNasalString(d->_globals, d->_string, d->_context);
1109
1110#if defined (BUILDING_TESTSUITE)
1111 initNasalUnitTestCppUnit(d->_globals, d->_context);
1112#else
1113 initNasalUnitTestInSim(d->_globals, d->_context);
1114#endif
1115
1117 initNasalPositioned(d->_globals, d->_context);
1118 initNasalFlightPlan(d->_globals, d->_context);
1119 initNasalPositioned_cppbind(d->_globals, d->_context);
1120 initNasalAircraft(d->_globals, d->_context);
1121
1122#if !defined (BUILDING_TESTSUITE)
1123 // on Linux, clipboard init crashes in headless mode (DISPLAY no set)
1124 // so don't do this for testing.
1126#endif
1127 initNasalCanvas(d->_globals, d->_context);
1128 initNasalCondition(d->_globals, d->_context);
1129 initNasalHTTP(d->_globals, d->_context);
1130 initNasalSGPath(d->_globals, d->_context);
1131 }
1132
1133 NasalTimerObj::init("Timer")
1134 .method("start", &TimerObj::start)
1135 .method("stop", &TimerObj::stop)
1136 .method("restart", &TimerObj::restart)
1137 .member("singleShot", &TimerObj::isSingleShot, &TimerObj::setSingleShot)
1138 .member("simulatedTime", &TimerObj::isSimTime, &f_timerObj_setSimTime)
1139 .member("isRunning", &TimerObj::isRunning);
1140
1141 NasalTimeStampObj::init("TimeStamp")
1142 .method("stamp", &TimeStampObj::stamp)
1143 .method("elapsedMSec", &TimeStampObj::elapsedMSec)
1144 .method("elapsedUSec", &TimeStampObj::elapsedUSec)
1145 ;
1146
1147 // everything after here, skip if we're doing minimal init, so
1148 // we don'tload FG_DATA/Nasal or add-ons
1150 d->_inited = true;
1151 return;
1152 }
1153
1154 flightgear::initNasalTranslations(d->_globals, d->_context);
1155 flightgear::addons::initAddonClassesForNasal(d->_globals, d->_context);
1156
1157 // Now load the various source files in the Nasal directory
1158 simgear::Dir nasalDir(SGPath(globals->get_fg_root(), "Nasal"));
1159
1160 // load core Nasal scripts. In GUI startup mode, we restrict to a limited
1161 // set of modules deliberately
1162 loadScriptDirectory(nasalDir,
1163 globals->get_props()->getNode("/sim/nasal-load-priority"),
1164 fgGetBool("/sim/gui/startup"));
1165
1166 // Add modules in Nasal subdirectories to property tree
1167 simgear::PathList directories = nasalDir.children(simgear::Dir::TYPE_DIR+
1168 simgear::Dir::NO_DOT_OR_DOTDOT, "");
1169 for (unsigned int i=0; i<directories.size(); ++i) {
1170 simgear::Dir dir(directories[i]);
1171 simgear::PathList scripts = dir.children(simgear::Dir::TYPE_FILE, ".nas");
1172 addModule(directories[i].file(), scripts);
1173 }
1174
1175 // set signal and remove node to avoid restoring at reinit
1176 const char *s = "nasal-dir-initialized";
1177 SGPropertyNode *signal = fgGetNode("/sim/signals", true);
1178 signal->setBoolValue(s, true);
1179 signal->removeChildren(s);
1180
1181 // Pull scripts out of the property tree, too
1182 loadPropertyScripts();
1183
1184 // now Nasal modules are loaded, we can do some delayed work
1185 postinitNasalPositioned(d->_globals, d->_context);
1186 postinitNasalGUI(d->_globals, d->_context);
1187
1188 d->_inited = true;
1189}
1190
1192{
1193 if (!d->_inited) {
1194 return;
1195 }
1196
1200
1201 for (auto l : d->_listener)
1202 delete l.second;
1203 d->_listener.clear();
1204
1205 for (auto c : d->_commands) {
1206 globals->get_commands()->removeCommand(c.first);
1207 }
1208 d->_commands.clear();
1209
1210 for (auto ml : d->_moduleListeners)
1211 delete ml;
1212 d->_moduleListeners.clear();
1213
1214 for (auto t : d->_nasalTimers) {
1215 delete t;
1216 }
1217 d->_nasalTimers.clear();
1218
1219 naClearSaved();
1220
1221 d->_string = naNil(); // will be freed by _context
1222 naFreeContext(d->_context);
1223
1224 //setWatchedRef(d->_globals);
1225
1226 // remove the recursive reference in globals
1227 hashset(d->_globals, "globals", naNil());
1228 d->_globals = naNil();
1229
1230 naGC();
1231
1232 // Destroy all queued ghosts : important to ensure persistent timers are
1233 // destroyed now.
1234 nasal::ghostProcessDestroyList();
1235
1236 if (!d->_persistentTimers.empty()) {
1237 SG_LOG(SG_NASAL, SG_DEV_WARN, "Extant persistent timer count:" << d->_persistentTimers.size());
1238
1239 for (auto pt : d->_persistentTimers) {
1240 SG_LOG(SG_NASAL, SG_DEV_WARN, "Extant:" << pt << " : " << pt->name());
1241 }
1242 }
1243
1244 d->_inited = false;
1245}
1246
1247naRef FGNasalSys::wrappedPropsNode(SGPropertyNode* aProps)
1248{
1249 if (naIsNil(d->_wrappedNodeFunc)) {
1250 nasal::Hash props = getGlobals().get<nasal::Hash>("props");
1251 d->_wrappedNodeFunc = props.get("wrapNode");
1252 }
1253
1254 naRef args[1];
1255 args[0] = propNodeGhost(aProps);
1256 naContext ctx = naNewContext();
1257 naRef wrapped = naCallMethodCtx(ctx, d->_wrappedNodeFunc, naNil(), 1, args, naNil());
1258 naFreeContext(ctx);
1259 return wrapped;
1260}
1261
1263{
1265 NasalClipboard::getInstance()->update();
1266
1267 std::for_each(d->_dead_listener.begin(), d->_dead_listener.end(),
1268 [](FGNasalListener* l) { delete l; });
1269 d->_dead_listener.clear();
1270
1271 if (!d->_loadList.empty()) {
1272 if (d->_delay_load)
1273 d->_delay_load = false;
1274 else
1275 // process Nasal load hook (only one per update loop to avoid excessive lags)
1276 d->_loadList.pop()->load();
1277 } else if (!d->_unloadList.empty()) {
1278 // process pending Nasal unload hooks after _all_ load hooks were processed
1279 // (only unload one per update loop to avoid excessive lags)
1280 d->_unloadList.pop()->unload();
1281 }
1282 // Destroy all queued ghosts
1283 nasal::ghostProcessDestroyList();
1284
1285 // The global context is a legacy thing. We use dynamically
1286 // created contexts for naCall() now, so that we can call them
1287 // recursively. But there are still spots that want to use it for
1288 // naNew*() calls, which end up leaking memory because the context
1289 // only clears out its temporary vector when it's *used*. So just
1290 // junk it and fetch a new/reinitialized one every frame. This is
1291 // clumsy: the right solution would use the dynamic context in all
1292 // cases and eliminate _context entirely. But that's more work,
1293 // and this works fine (yes, they say "New" and "Free", but
1294 // they're very fast, just trust me). -Andy
1295 naFreeContext(d->_context);
1296 d->_context = naNewContext();
1297}
1298
1299bool pathSortPredicate(const SGPath& p1, const SGPath& p2)
1300{
1301 return p1.file() < p2.file();
1302}
1303
1304// Loads all scripts in given directory, with an optional partial ordering of
1305// files defined in loadorder.
1306void FGNasalSys::loadScriptDirectory(simgear::Dir nasalDir, SGPropertyNode* loadorder,
1307 bool excludeUnspecifiedInLoadOrder)
1308{
1309 simgear::PathList scripts = nasalDir.children(simgear::Dir::TYPE_FILE, ".nas");
1310
1311 if (loadorder != nullptr && loadorder->hasChild("file")) {
1312 // Load any scripts defined in the loadorder in order, removing them from
1313 // the list so they don't get loaded twice.
1314 simgear::PropertyList files = loadorder->getChildren("file");
1315
1316 auto loadAndErase = [ &scripts, &nasalDir, this ] (SGPropertyNode_ptr n) {
1317 SGPath p = SGPath(nasalDir.path(), n->getStringValue());
1318 auto script = std::find(scripts.begin(), scripts.end(), p);
1319 if (script != scripts.end()) {
1320 this->loadModule(p, p.file_base().c_str());
1321 scripts.erase(script);
1322 }
1323 };
1324
1325 std::for_each(files.begin(), files.end(), loadAndErase);
1326 }
1327
1328 if (excludeUnspecifiedInLoadOrder) {
1329 return;
1330 }
1331
1332 // Load any remaining scripts.
1333 // Note: simgear::Dir already reports file entries in a deterministic order,
1334 // so a fixed loading sequence is guaranteed (same for every user)
1335 std::for_each(scripts.begin(), scripts.end(), [this](SGPath p) { this->loadModule(p, p.file_base().c_str()); });
1336}
1337
1338// Create module with list of scripts
1339void FGNasalSys::addModule(string moduleName, simgear::PathList scripts)
1340{
1341 if (! scripts.empty())
1342 {
1343 SGPropertyNode* nasal = globals->get_props()->getNode("nasal", true);
1344 SGPropertyNode* module_node = nasal->getChild(moduleName,0,true);
1345 for (unsigned int i=0; i<scripts.size(); ++i) {
1346 SGPropertyNode* pFileNode = module_node->getChild("file",i,true);
1347 pFileNode->setStringValue(scripts[i].utf8Str());
1348 }
1349 if (!module_node->hasChild("enabled",0))
1350 {
1351 SGPropertyNode* node = module_node->getChild("enabled",0,true);
1352 node->setBoolValue(false);
1353 node->setAttribute(SGPropertyNode::USERARCHIVE,false);
1354 #ifndef BUILDING_TESTSUITE
1355 SG_LOG(SG_NASAL, SG_ALERT, "Nasal module " << moduleName
1356 << " present in FGDATA/Nasal but not configured in defaults.xml. "
1357 << " Please add an entry to defaults.xml, and set " << node->getPath()
1358 << "=true to load the module on-demand at runtime when required."
1359 );
1360 #endif
1361 }
1362 }
1363}
1364
1365// Loads the scripts found under /nasal in the global tree
1366void FGNasalSys::loadPropertyScripts()
1367{
1368 SGPropertyNode* nasal = globals->get_props()->getNode("nasal");
1369 if(!nasal) return;
1370
1371 for(int i=0; i<nasal->nChildren(); i++)
1372 {
1373 SGPropertyNode* n = nasal->getChild(i);
1374 loadPropertyScripts(n);
1375 }
1376}
1377
1378// Loads the scripts found under /nasal in the global tree
1379void FGNasalSys::loadPropertyScripts(SGPropertyNode* n)
1380{
1381 bool is_loaded = false;
1382
1383 std::string module = n->getNameString();
1384 if(n->hasChild("module"))
1385 module = n->getStringValue("module");
1386 if (n->getBoolValue("enabled",true))
1387 {
1388 // allow multiple files to be specified within a single
1389 // Nasal module tag
1390 int j = 0;
1391 SGPropertyNode *fn;
1392 bool file_specified = false;
1393 bool ok=true;
1394 while((fn = n->getChild("file", j)) != NULL) {
1395 file_specified = true;
1396 std::string file = fn->getStringValue();
1397 SGPath p(file);
1398 if (!p.isAbsolute() || !p.exists())
1399 {
1401 if (p.isNull()) {
1402 simgear::reportFailure(simgear::LoadFailure::NotFound, simgear::ErrorCode::AircraftSystems,
1403 string{"Missing nasal file for module:"} + module, sg_location{file});
1404 }
1405 }
1406 ok &= p.isNull() ? false : loadModule(p, module.c_str());
1407 j++;
1408 }
1409
1410 std::string src = n->getStringValue("script");
1411 if(!n->hasChild("script")) src = ""; // Hrm...
1412 if(!src.empty())
1413 createModule(module.c_str(), n->getPath().c_str(), src.c_str(), src.length());
1414
1415 if(!file_specified && src.empty())
1416 {
1417 // module no longer exists - clear the archived "enable" flag
1418 n->setAttribute(SGPropertyNode::USERARCHIVE,false);
1419 SGPropertyNode* node = n->getChild("enabled",0,false);
1420 if (node)
1421 node->setAttribute(SGPropertyNode::USERARCHIVE,false);
1422
1423 SG_LOG(SG_NASAL, SG_ALERT, "Nasal error: " <<
1424 "no <file> or <script> defined in " <<
1425 "/nasal/" << module);
1426 }
1427 else
1428 is_loaded = ok;
1429 }
1430 else
1431 {
1432 SGPropertyNode* enable = n->getChild("enabled");
1433 if (enable)
1434 {
1435 FGNasalModuleListener* listener = new FGNasalModuleListener(n);
1436 d->_moduleListeners.push_back(listener);
1437 enable->addChangeListener(listener, false);
1438 }
1439 }
1440 SGPropertyNode* loaded = n->getChild("loaded",0,true);
1441 loaded->setAttribute(SGPropertyNode::PRESERVE,true);
1442 loaded->setBoolValue(is_loaded);
1443}
1444
1445#if defined(BUILDING_TESTSUITE)
1446
1447static string_list global_nasalErrors;
1448
1450{
1451 string_list r;
1452 global_nasalErrors.swap(r);
1453 return r;
1454}
1455#endif
1456
1457// Logs a runtime error, with stack trace, to the FlightGear log stream
1458void FGNasalSys::logError(naContext context)
1459{
1460 string errorMessage = naGetError(context);
1461#if defined(BUILDING_TESTSUITE)
1462 global_nasalErrors.push_back(errorMessage);
1463#else
1464 SG_LOG(SG_NASAL, SG_ALERT, "Nasal runtime error: " << errorMessage);
1465
1466 string_list nasalStack;
1467 logNasalStack(context, nasalStack);
1468 flightgear::sentryReportNasalError(errorMessage, nasalStack);
1469#endif
1470}
1471
1472
1473void FGNasalSys::logNasalStack(naContext context, string_list& stack)
1474{
1475 const int stack_depth = naStackDepth(context);
1476 for (int i=0; i<stack_depth; ++i) {
1477 std::string text = std::string(naStr_data(naGetSourceFile(context, i)))
1478 + ", line " + std::to_string(naGetLine(context, i));
1479 stack.push_back(text);
1480 SG_LOG(SG_NASAL, SG_ALERT, ((i) ? " called from: " : " at ") << text);
1481 }
1482 return;
1483}
1484
1485// Reads a script file, executes it, and places the resulting
1486// namespace into the global namespace under the specified module
1487// name.
1488bool FGNasalSys::loadModule(SGPath file, const char* module)
1489{
1490 if (!file.exists()) {
1491 SG_LOG(SG_NASAL, SG_ALERT, "Cannot load module, missing file:" << file);
1492 return false;
1493 }
1494
1495 SGMMapFile mmap(file);
1496 mmap.open(SG_IO_IN);
1497
1498 auto pathStr = file.utf8Str();
1499 return createModule(module, pathStr.c_str(), mmap.get(), mmap.get_size());
1500}
1501
1502// Parse and run. Save the local variables namespace, as it will
1503// become a sub-object of globals. The optional "arg" argument can be
1504// used to pass an associated property node to the module, which can then
1505// be accessed via cmdarg(). (This is, for example, used by XML dialogs.)
1506bool FGNasalSys::createModule(const char* moduleName, const char* fileName,
1507 const char* src, int len,
1508 const SGPropertyNode* cmdarg,
1509 int argc, naRef* args)
1510{
1511 naContext ctx = naNewContext();
1512 std::string errors;
1513 naRef code = parse(ctx, fileName, src, len, errors);
1514 if(naIsNil(code)) {
1515 naFreeContext(ctx);
1516 return false;
1517 }
1518
1519
1520 // See if we already have a module hash to use. This allows the
1521 // user to, for example, add functions to the built-in math
1522 // module. Make a new one if necessary.
1523 naRef locals;
1524 naRef modname = naNewString(ctx);
1525 naStr_fromdata(modname, (char*)moduleName, strlen(moduleName));
1526 if (naIsNil(d->_globals))
1527 return false;
1528
1529 if (!naHash_get(d->_globals, modname, &locals)) {
1530 // if we are re-creating the module for canvas, ensure the C++
1531 // pieces are re-defined first. As far as I can see, Canvas is the only
1532 // hybrid module where C++ pieces and Nasal code are combined.
1533 const auto isCanvas = strcmp(moduleName, "canvas") == 0;
1534 if (isCanvas) {
1535 initNasalCanvas(d->_globals, d->_context);
1536 naHash_get(d->_globals, modname, &locals);
1537 } else {
1538 locals = naNewHash(ctx);
1539 }
1540 }
1541
1542 // store the filename in the module hash, so we could reload it
1543 // this is only needed for 'top-level' single file modules; for
1544 // 'directory' modules we use the file path nodes defined by
1545 // FGNasalSys::addModule
1546 naRef modFilePath = naNewString(ctx);
1547 naStr_fromdata(modFilePath, (char*)fileName, strlen(fileName));
1548 hashset(locals, "__moduleFilePath", modFilePath);
1549
1550 d->_cmdArg = (SGPropertyNode*)cmdarg;
1551 callWithContext(ctx, code, argc, args, locals);
1552 hashset(d->_globals, moduleName, locals);
1553
1554 naFreeContext(ctx);
1555 return true;
1556}
1557
1558void FGNasalSys::deleteModule(const char* moduleName)
1559{
1560 if (!d->_inited || naIsNil(d->_globals)) {
1561 // can occur on shutdown due to us being shutdown first, but other
1562 // subsystems having Nasal objects.
1563 return;
1564 }
1565
1566 auto nasalNode = globals->get_props()->getNode("nasal", true);
1567 auto moduleNode = nasalNode->getChild(moduleName, 0);
1568 if (moduleNode) {
1569 // modules can use this value going false, to trigger unload
1570 // behaviours
1571 moduleNode->setBoolValue("loaded", false);
1572 }
1573
1574 naContext ctx = naNewContext();
1575 naRef modname = naNewString(ctx);
1576 naStr_fromdata(modname, (char*)moduleName, strlen(moduleName));
1577
1578 naRef locals;
1579 if (naHash_get(d->_globals, modname, &locals)) {
1580 naRef unloadFunc = naHash_cget(locals, (char*)"unload");
1581 if (naIsFunc(unloadFunc)) {
1582 callWithContext(ctx, unloadFunc, 0, nullptr, locals);
1583 }
1584
1585 // now delete the module hash
1586 naHash_delete(d->_globals, modname);
1587 }
1588
1589 naFreeContext(ctx);
1590}
1591
1592bool FGNasalSys::reloadModuleFromFile(const std::string& moduleName)
1593{
1594 if (!d->_inited || naIsNil(d->_globals)) {
1595 return false;
1596 }
1597
1598 naRef locals = naHash_cget(d->_globals, (char*)moduleName.c_str());
1599 if (naIsNil(locals)) {
1600 // no such module
1601 return false;
1602 }
1603
1604 // check if we have a module entry under /nasal/ - if so, use
1605 // this to determine the list of files. We don't (yet) re-run
1606 // addModule here so adding new .nas files isn't possible, but
1607 // in principle it could be done
1608 auto nasalNode = globals->get_props()->getNode("nasal", true);
1609 auto moduleNode = nasalNode->getChild(moduleName, 0);
1610 if (moduleNode) {
1611 deleteModule(moduleName.c_str());
1612 loadPropertyScripts(moduleNode);
1613 return true;
1614 } else {
1615 // assume it's a single-file module for now
1616 naRef filePath = naHash_cget(locals, (char*)"__moduleFilePath");
1617 if (naIsNil(filePath)) {
1618 return false;
1619 }
1620
1621 SGPath path = SGPath::fromUtf8(naStr_data(filePath));
1622 deleteModule(moduleName.c_str());
1623 return loadModule(path, moduleName.c_str());
1624 }
1625}
1626
1627naRef FGNasalSys::getModule(const std::string& moduleName) const
1628{
1629 naRef mod = naHash_cget(d->_globals, (char*)moduleName.c_str());
1630 return mod;
1631}
1632
1633naRef FGNasalSys::getModule(const char* moduleName)
1634{
1635 naRef mod = naHash_cget(d->_globals, (char*)moduleName);
1636 return mod;
1637}
1638
1639naRef FGNasalSys::parse(naContext ctx, const char* filename,
1640 const char* buf, int len,
1641 std::string& errors)
1642{
1643 int errLine = -1;
1644 naRef srcfile = naNewString(ctx);
1645 naStr_fromdata(srcfile, (char*)filename, strlen(filename));
1646 naRef code = naParseCode(ctx, srcfile, 1, (char*)buf, len, &errLine);
1647 if(naIsNil(code)) {
1648 std::ostringstream errorMessageStream;
1649 errorMessageStream << "Nasal parse error: " << naGetError(ctx) <<
1650 " in "<< filename <<", line " << errLine;
1651 errors = errorMessageStream.str();
1652 SG_LOG(SG_NASAL, SG_ALERT, errors);
1653
1654 // Show the line, in case <filename> isn't a real file, e.g. nasal code
1655 // is in an .xml file.
1656 const char* line_begin = buf;
1657 const char* line_end = nullptr;
1658 int line_num = 1;
1659 for(;;) {
1660 line_end = strchr(line_begin, '\n');
1661 if (!line_end) {
1662 line_end = line_begin + strlen(line_begin);
1663 break;
1664 }
1665 if (line_num == errLine) break;
1666 line_begin = line_end + 1;
1667 line_num += 1;
1668 }
1669 if (line_num == errLine) {
1670 SG_LOG(SG_NASAL, SG_ALERT, std::string(line_begin, line_end) << "\n");
1671 }
1672 else {
1673 SG_LOG(SG_NASAL, SG_ALERT, "[Could not find line " << errLine << " - only " << line_num << " lines.");
1674 }
1675
1676 return naNil();
1677 }
1678
1679 // Bind to the global namespace before returning
1680 return naBindFunction(ctx, code, d->_globals);
1681}
1682
1683bool FGNasalSys::handleCommand( const char* moduleName,
1684 const char* fileName,
1685 const char* src,
1686 const SGPropertyNode* arg,
1687 SGPropertyNode* root)
1688{
1689 naContext ctx = naNewContext();
1690 std::string errorMessage;
1691 naRef code = parse(ctx, fileName, src, strlen(src), errorMessage);
1692 if(naIsNil(code)) {
1693 naFreeContext(ctx);
1694 return false;
1695 }
1696
1697 // Commands can be run "in" a module. Make sure that module
1698 // exists, and set it up as the local variables hash for the
1699 // command.
1700 naRef locals = naNil();
1701 if(moduleName[0]) {
1702 naRef modname = naNewString(ctx);
1703 naStr_fromdata(modname, (char*)moduleName, strlen(moduleName));
1704 if (!naHash_get(d->_globals, modname, &locals)) {
1705 locals = naNewHash(ctx);
1706 naHash_set(d->_globals, modname, locals);
1707 }
1708 }
1709
1710 // Cache this command's argument for inspection via cmdarg(). For
1711 // performance reasons, we won't bother with it if the invoked
1712 // code doesn't need it.
1713 d->_cmdArg = (SGPropertyNode*)arg;
1714
1715 callWithContext(ctx, code, 0, 0, locals);
1716 naFreeContext(ctx);
1717 return true;
1718}
1719
1720bool FGNasalSys::handleCommand(const SGPropertyNode * arg, SGPropertyNode * root)
1721{
1722 std::string src = arg->getStringValue("script");
1723 std::string moduleName = arg->getStringValue("module");
1724
1725 return handleCommand( moduleName.c_str(),
1726 arg->getPath(true).c_str(),
1727 src.c_str(),
1728 arg,
1729 root);
1730}
1731
1732// settimer(func, dt, simtime) extension function. The first argument
1733// is a Nasal function to call, the second is a delta time (from now),
1734// in seconds. The third, if present, is a boolean value indicating
1735// that "real world" time (rather than simulator time) is to be used.
1736//
1737// Implementation note: the FGTimer objects don't live inside the
1738// garbage collector, so the Nasal handler functions have to be
1739// "saved" somehow lest they be inadvertently cleaned. In this case,
1740// they are inserted into a globals.__gcsave hash and removed on
1741// expiration.
1742void FGNasalSys::setTimer(naContext c, int argc, naRef* args)
1743{
1744 // Extract the handler, delta, and simtime arguments:
1745 naRef handler = argc > 0 ? args[0] : naNil();
1746 if(!(naIsCode(handler) || naIsCCode(handler) || naIsFunc(handler))) {
1747 naRuntimeError(c, "settimer() with invalid function argument");
1748 return;
1749 }
1750
1751 naRef delta = argc > 1 ? args[1] : naNil();
1752 if(naIsNil(delta)) {
1753 naRuntimeError(c, "settimer() with invalid time argument");
1754 return;
1755 }
1756
1757 bool simtime = (argc > 2 && naTrue(args[2])) ? false : true;
1758
1759 // A unique name for the timer based on the file name and line number of the function.
1760 std::string name = "settimer-";
1761 name.append(naStr_data(naGetSourceFile(c, 0)));
1762 name.append(":");
1763 name.append(std::to_string(naGetLine(c, 0)));
1764
1765 // Generate and register a C++ timer handler
1766 NasalTimer* t = new NasalTimer(handler, this);
1767 d->_nasalTimers.push_back(t);
1768 globals->get_event_mgr()->addEvent(name,
1769 [t](){ t->timerExpired(); },
1770 delta.num, simtime);
1771}
1772
1773void FGNasalSys::handleTimer(NasalTimer* t)
1774{
1775 call(t->handler, 0, 0, naNil());
1776 auto it = std::find(d->_nasalTimers.begin(), d->_nasalTimers.end(), t);
1777 assert(it != d->_nasalTimers.end());
1778 d->_nasalTimers.erase(it);
1779 delete t;
1780}
1781
1783{
1784 return naGCSave(r);
1785}
1786
1788{
1789 naGCRelease(key);
1790}
1791
1792
1793//------------------------------------------------------------------------------
1794
1796 handler(h), nasal(sys)
1797{
1798 assert(sys);
1799 gcKey = naGCSave(handler);
1800}
1801
1803{
1804 naGCRelease(gcKey);
1805}
1806
1808{
1809 nasal->handleTimer(this);
1810 // note handleTimer calls delete on us, don't do anything
1811 // which requires 'this' to be valid here
1812}
1813
1814
1816
1817// setlistener(<property>, <func> [, <initial=0> [, <persistent=1>]])
1818// Attaches a callback function to a property (specified as a global
1819// property path string or a SGPropertyNode* ghost). If the third,
1820// optional argument (default=0) is set to 1, then the function is also
1821// called initially. If the fourth, optional argument is set to 0, then the
1822// function is only called when the property node value actually changes.
1823// Otherwise it's called independent of the value whenever the node is
1824// written to (default). The setlistener() function returns a unique
1825// id number, which is to be used as argument to the removelistener()
1826// function.
1827naRef FGNasalSys::setListener(naContext c, int argc, naRef* args)
1828{
1829 SGPropertyNode_ptr node;
1830 naRef prop = argc > 0 ? args[0] : naNil();
1831 if(naIsString(prop)) node = fgGetNode(naStr_data(prop), true);
1832 else if(naIsGhost(prop)) node = static_cast<SGPropertyNode*>(naGhost_ptr(prop));
1833 else {
1834 naRuntimeError(c, "setlistener() with invalid property argument");
1835 return naNil();
1836 }
1837
1838 if (node->isTied() || node->isAlias()) {
1839 const auto isSafe = node->getAttribute(SGPropertyNode::LISTENER_SAFE);
1840 if (!isSafe) {
1841 SG_LOG(SG_NASAL, SG_DEV_ALERT, "ERROR: Cannot add listener to tied property " <<
1842 node->getPath());
1843 }
1844 }
1845
1846 naRef code = argc > 1 ? args[1] : naNil();
1847 if(!(naIsCode(code) || naIsCCode(code) || naIsFunc(code))) {
1848 naRuntimeError(c, "setlistener() with invalid function argument");
1849 return naNil();
1850 }
1851
1852 int init = argc > 2 && naIsNum(args[2]) ? int(args[2].num) : 0; // do not trigger when created
1853 int type = argc > 3 && naIsNum(args[3]) ? int(args[3].num) : 1; // trigger will always be triggered when the property is written
1854 FGNasalListener* nl = new FGNasalListener(node, code, this,
1855 gcSave(code), d->_listenerId, init, type);
1856
1857 node->addChangeListener(nl, init != 0);
1858
1859 d->_listener[d->_listenerId] = nl;
1860 return naNum(d->_listenerId++);
1861}
1862
1863// removelistener(int) extension function. The argument is the id of
1864// a listener as returned by the setlistener() function.
1865naRef FGNasalSys::removeListener(naContext c, int argc, naRef* args)
1866{
1867 naRef id = argc > 0 ? args[0] : naNil();
1868 auto it = d->_listener.find(int(id.num));
1869 if (!naIsNum(id) || it == d->_listener.end() || it->second->_dead) {
1870 naRuntimeError(c, "removelistener() with invalid listener id");
1871 return naNil();
1872 }
1873
1874 it->second->_dead = true;
1875 d->_dead_listener.push_back(it->second);
1876 d->_listener.erase(it);
1877 return naNum(d->_listener.size());
1878}
1879
1881{
1882 if (d->_loadList.empty())
1883 d->_delay_load = true;
1884 d->_loadList.push(data);
1885}
1886
1888{
1889 d->_unloadList.push(data);
1890}
1891
1892bool FGNasalSys::addCommand(naRef func, const std::string& name)
1893{
1894 if (d->_commands.find(name) != d->_commands.end()) {
1895 SG_LOG(SG_NASAL, SG_WARN, "duplicate add of command:" << name);
1896 return false;
1897 }
1898
1899 NasalCommand* cmd = new NasalCommand(this, func, name);
1900 d->_commands[name] = cmd;
1901 return true;
1902}
1903
1904bool FGNasalSys::removeCommand(const std::string& name)
1905{
1906 auto it = d->_commands.find(name);
1907 if (it == d->_commands.end()) {
1908 SG_LOG(SG_NASAL, SG_WARN, "remove of unknwon command:" << name);
1909 return false;
1910 }
1911
1912 // will delete the NasalCommand instance
1913 bool ok = globals->get_commands()->removeCommand(name);
1914 d->_commands.erase(it);
1915 return ok;
1916}
1917
1918void FGNasalSys::addPersistentTimer(TimerObj* pto)
1919{
1920 d->_persistentTimers.push_back(pto);
1921}
1922
1923void FGNasalSys::removePersistentTimer(TimerObj* obj)
1924{
1925 auto it = std::find(d->_persistentTimers.begin(), d->_persistentTimers.end(), obj);
1926 assert(it != d->_persistentTimers.end());
1927 d->_persistentTimers.erase(it);
1928}
1929
1930// Register the subsystem.
1931SGSubsystemMgr::Registrant<FGNasalSys> registrantFGNasalSys(
1932 SGSubsystemMgr::INIT);
1933
1934
1936// FGNasalListener class.
1937
1938FGNasalListener::FGNasalListener(SGPropertyNode *node, naRef code,
1939 FGNasalSys* nasal, int key, int id,
1940 int init, int type) :
1941 _node(node),
1942 _code(code),
1943 _gcKey(key),
1944 _id(id),
1945 _nas(nasal),
1946 _init(init),
1947 _type(type),
1948 _active(0),
1949 _dead(false),
1950 _last_int(0L),
1951 _last_float(0.0)
1952{
1953 if(_type == 0 && !_init)
1954 changed(node);
1955}
1956
1958{
1959 _node->removeChangeListener(this);
1960 _nas->gcRelease(_gcKey);
1961}
1962
1963void FGNasalListener::call(SGPropertyNode* which, naRef mode)
1964{
1965 if(_active || _dead) return;
1966 _active++;
1967 naRef arg[4];
1968 arg[0] = _nas->propNodeGhost(which);
1969 arg[1] = _nas->propNodeGhost(_node);
1970 arg[2] = mode; // value changed, child added/removed
1971 arg[3] = naNum(_node != which); // child event?
1972 _nas->call(_code, 4, arg, naNil());
1973 _active--;
1974}
1975
1976void FGNasalListener::valueChanged(SGPropertyNode* node)
1977{
1978 if(_type < 2 && node != _node) return; // skip child events
1979 if(_type > 0 || changed(_node) || _init)
1980 call(node, naNum(0));
1981
1982 _init = 0;
1983}
1984
1985void FGNasalListener::childAdded(SGPropertyNode*, SGPropertyNode* child)
1986{
1987 if(_type == 2) call(child, naNum(1));
1988}
1989
1990void FGNasalListener::childRemoved(SGPropertyNode*, SGPropertyNode* child)
1991{
1992 if(_type == 2) call(child, naNum(-1));
1993}
1994
1995bool FGNasalListener::changed(SGPropertyNode* node)
1996{
1997 using namespace simgear;
1998 props::Type type = node->getType();
1999 if(type == props::NONE) return false;
2000 if(type == props::UNSPECIFIED) return true;
2001
2002 bool result;
2003 switch(type) {
2004 case props::BOOL:
2005 case props::INT:
2006 case props::LONG:
2007 {
2008 long l = node->getLongValue();
2009 result = l != _last_int;
2010 _last_int = l;
2011 return result;
2012 }
2013 case props::FLOAT:
2014 case props::DOUBLE:
2015 {
2016 double d = node->getDoubleValue();
2017 result = d != _last_float;
2018 _last_float = d;
2019 return result;
2020 }
2021 default:
2022 {
2023 string s = node->getStringValue();
2024 result = s != _last_string;
2025 _last_string = s;
2026 return result;
2027 }
2028 }
2029}
2030
2031// NasalXMLVisitor class: handles EasyXML visitor callback for parsexml()
2032//
2033NasalXMLVisitor::NasalXMLVisitor(naContext c, int argc, naRef* args) :
2034 _c(naSubContext(c)),
2035 _start_element(argc > 1 ? args[1] : naNil()),
2036 _end_element(argc > 2 ? args[2] : naNil()),
2037 _data(argc > 3 ? args[3] : naNil()),
2038 _pi(argc > 4 ? args[4] : naNil())
2039{
2040}
2041
2042void NasalXMLVisitor::startElement(const char* tag, const XMLAttributes& a)
2043{
2044 if(naIsNil(_start_element)) return;
2045 naRef attr = naNewHash(_c);
2046 for(int i=0; i<a.size(); i++) {
2047 naRef name = make_string(a.getName(i));
2048 naRef value = make_string(a.getValue(i));
2049 naHash_set(attr, name, value);
2050 }
2051 call(_start_element, 2, make_string(tag), attr);
2052}
2053
2054void NasalXMLVisitor::endElement(const char* tag)
2055{
2056 if(!naIsNil(_end_element)) call(_end_element, 1, make_string(tag));
2057}
2058
2059void NasalXMLVisitor::data(const char* str, int len)
2060{
2061 if(!naIsNil(_data)) call(_data, 1, make_string(str, len));
2062}
2063
2064void NasalXMLVisitor::pi(const char* target, const char* data)
2065{
2066 if(!naIsNil(_pi)) call(_pi, 2, make_string(target), make_string(data));
2067}
2068
2069void NasalXMLVisitor::call(naRef func, int num, naRef a, naRef b)
2070{
2071 naRef args[2];
2072 args[0] = a;
2073 args[1] = b;
2074 naCall(_c, func, num, args, naNil(), naNil());
2075 if(naGetError(_c))
2076 naRethrowError(_c);
2077}
2078
2079naRef NasalXMLVisitor::make_string(const char* s, int n)
2080{
2081 return naStr_fromdata(naNewString(_c), const_cast<char *>(s),
2082 n < 0 ? strlen(s) : n);
2083}
2084
2085// like naEqual, but checks vector/hash recursively
2086// note this will not tolerate a recursively defined Nasal structure
2087// (such as globals.)
2088int nasalStructEqual(naContext ctx, naRef a, naRef b)
2089{
2090 if (naIsVector(a) && naIsVector(b)) {
2091 const int aSz = naVec_size(a),
2092 bSz = naVec_size(b);
2093
2094 if (aSz != bSz)
2095 return 0;
2096
2097 for (int i = 0; i < aSz; ++i) {
2098 int eq = nasalStructEqual(ctx, naVec_get(a, i), naVec_get(b, i));
2099 if (!eq)
2100 return 0;
2101 }
2102
2103 // all elements equal, we're done
2104 return 1;
2105 }
2106
2107 if (naIsHash(a) && naIsHash(b)) {
2108 naRef keysVec = naNewVector(ctx);
2109 naHash_keys(keysVec, a);
2110 const auto aSz = naVec_size(keysVec);
2111
2112 // first check key count, that's fast
2113 if (aSz != naHash_size(b))
2114 return 0;
2115
2116 for (int i = 0; i < aSz; i++) {
2117 naRef key = naVec_get(keysVec, i);
2118 naRef aValue, bValue;
2119 if (!naHash_get(a, key, &aValue) || !naHash_get(b, key, &bValue)) {
2120 return 0;
2121 }
2122
2123 int eq = nasalStructEqual(ctx, aValue, bValue);
2124 if (!eq) {
2125 return 0;
2126 }
2127 }
2128
2129 // all values matched, we're good
2130 return 1;
2131 }
2132
2133 return naEqual(a, b);
2134}
2135
2136simgear::BufferedLogCallback* FGNasalSys::log() const
2137{
2138 return d->_log.get();
2139}
2140
2142{
2143 return d->_globals;
2144}
2145
2146nasal::Hash FGNasalSys::getGlobals() const
2147{
2148 return nasal::Hash(d->_globals, d->_context);
2149}
#define p2(x, y)
#define p(x)
void postinitNasalGUI(naRef globals, naContext c)
void initNasalAircraft(naRef globals, naContext c)
naRef initNasalCanvas(naRef globals, naContext c)
naRef initNasalCondition(naRef globals, naContext c)
static struct @032150236342374176343243244364035346052374146336 funcs[]
naRef initNasalFlightPlan(naRef globals, naContext c)
void shutdownNasalFlightPlan()
naRef initNasalHTTP(naRef globals, naContext c)
Definition NasalHTTP.cxx:88
void shutdownNasalPositioned()
naRef initNasalPositioned(naRef globals, naContext c)
void postinitNasalPositioned(naRef globals, naContext c)
naRef initNasalPositioned_cppbind(naRef globals, naContext c)
naRef initNasalSGPath(naRef globals, naContext c)
naRef initNasalString(naRef globals, naRef string, naContext c)
static void f_timerObj_setSimTime(TimerObj &timer, naContext c, naRef value)
Definition NasalSys.cxx:236
static naRef f_custom_stat(naContext ctx, naRef me, int argc, naRef *args)
Definition NasalSys.cxx:900
static FGNasalSys * nasalSys
Definition NasalSys.cxx:82
nasal::Ghost< TimeStampObjRef > NasalTimeStampObj
Definition NasalSys.cxx:276
bool pathSortPredicate(const SGPath &p1, const SGPath &p2)
nasal::Ghost< TimerObjRef > NasalTimerObj
Definition NasalSys.cxx:234
static naRef f_settimer(naContext c, naRef me, int argc, naRef *args)
Definition NasalSys.cxx:618
static naRef f_getprop(naContext c, naRef me, int argc, naRef *args)
Definition NasalSys.cxx:451
static naRef f_fgcommand(naContext c, naRef me, int argc, naRef *args)
Definition NasalSys.cxx:601
static naRef f_parsexml(naContext c, naRef me, int argc, naRef *args)
Definition NasalSys.cxx:953
static naRef f_print(naContext c, naRef me, int argc, naRef *args)
Definition NasalSys.cxx:538
SGSubsystemMgr::Registrant< FGNasalSys > registrantFGNasalSys(SGSubsystemMgr::INIT)
SGSharedPtr< TimerObj > TimerObjRef
Definition NasalSys.cxx:233
static naRef f_systime(naContext c, naRef me, int argc, naRef *args)
static naRef f_srand(naContext c, naRef me, int argc, naRef *args)
Definition NasalSys.cxx:734
static naRef f_cmdarg(naContext c, naRef me, int argc, naRef *args)
Definition NasalSys.cxx:688
static SGPropertyNode * findnode(naContext c, naRef *vec, int len, bool create=false)
Definition NasalSys.cxx:424
static naRef f_setlistener(naContext c, naRef me, int argc, naRef *args)
Definition NasalSys.cxx:674
static naRef ftype(naContext ctx, const SGPath &f)
Definition NasalSys.cxx:890
static naRef f_makeTimer(naContext c, naRef me, int argc, naRef *args)
Definition NasalSys.cxx:624
static naRef f_setprop(naContext c, naRef me, int argc, naRef *args)
Definition NasalSys.cxx:491
static naRef f_logprint(naContext c, naRef me, int argc, naRef *args)
Definition NasalSys.cxx:576
static naRef f_addCommand(naContext c, naRef me, int argc, naRef *args)
Definition NasalSys.cxx:833
static naRef f_open(naContext c, naRef me, int argc, naRef *args)
Definition NasalSys.cxx:860
const char * name
static naRef f_md5(naContext c, naRef me, int argc, naRef *args)
Create md5 hash from given string.
Definition NasalSys.cxx:990
static naRef f_abort(naContext c, naRef me, int argc, naRef *args)
Definition NasalSys.cxx:740
void postinitNasalGUI(naRef globals, naContext c)
static naRef f_removelistener(naContext c, naRef me, int argc, naRef *args)
Definition NasalSys.cxx:681
static naRef f_rand(naContext c, naRef me, int argc, naRef *args)
Definition NasalSys.cxx:729
static naRef f_findDataDir(naContext c, naRef me, int argc, naRef *args)
Definition NasalSys.cxx:788
static naRef f_maketimeStamp(naContext c, naRef me, int argc, naRef *args)
Definition NasalSys.cxx:666
static naRef f_removeCommand(naContext c, naRef me, int argc, naRef *args)
Definition NasalSys.cxx:847
int nasalStructEqual(naContext ctx, naRef a, naRef b)
@breif wrapper for naEqual which recursively checks vec/hash equality Probably not very performant.
static naRef f_resolveDataPath(naContext c, naRef me, int argc, naRef *args)
Given a data path, resolve it in FG_ROOT or an FG_AIRCRFT directory.
Definition NasalSys.cxx:778
static naRef f_directory(naContext c, naRef me, int argc, naRef *args)
Definition NasalSys.cxx:747
SGSharedPtr< TimeStampObj > TimeStampObjRef
Definition NasalSys.cxx:275
bool global_nasalMinimalInit
Definition NasalSys.cxx:86
static naRef f_interpolate(naContext c, naRef me, int argc, naRef *args)
Definition NasalSys.cxx:697
static naRef f_makeSingleShot(naContext c, naRef me, int argc, naRef *args)
Definition NasalSys.cxx:644
naRef initNasalUnitTestCppUnit(naRef globals, naContext c)
naRef initNasalUnitTestInSim(naRef globals, naContext c)
Translations: Nasal interface to FGTranslate and related classes (header file)
void shutdownNasalUnitTestInSim()
#define i(x)
SGPath resolve_maybe_aircraft_path(const std::string &branch) const
Same as above, but test for non 'Aircraft/' branch paths, and always resolve them against fg_root.
Definition globals.cxx:560
SGPropertyNode * get_props()
Definition globals.hxx:320
virtual ~FGNasalListener()
FGNasalListener(SGPropertyNode *node, naRef code, FGNasalSys *nasal, int key, int id, int init, int type)
virtual void childRemoved(SGPropertyNode *parent, SGPropertyNode *child)
virtual void valueChanged(SGPropertyNode *node)
friend class FGNasalSys
virtual void childAdded(SGPropertyNode *parent, SGPropertyNode *child)
Nasal model data container.
virtual void valueChanged(SGPropertyNode *node)
Definition NasalSys.cxx:105
FGNasalModuleListener(SGPropertyNode *node)
Definition NasalSys.cxx:101
simgear::BufferedLogCallback * log() const
retrive the associated log object, for displaying log output somewhere (a UI, presumably)
bool removeCommand(const std::string &name)
naRef removeListener(naContext c, int argc, naRef *args)
void setCmdArg(SGPropertyNode *aNode)
void update(double dt) override
bool createModule(const char *moduleName, const char *fileName, const char *src, int len, const SGPropertyNode *cmdarg=0, int argc=0, naRef *args=0)
void setTimer(naContext c, int argc, naRef *args)
void registerToUnload(FGNasalModelData *data)
naRef cmdArgGhost()
naRef call(naRef code, int argc, naRef *args, naRef locals)
Definition NasalSys.cxx:314
naRef callMethod(naRef code, naRef self, int argc, naRef *args, naRef locals)
Definition NasalSys.cxx:331
void deleteModule(const char *moduleName)
int gcSave(naRef r)
void registerToLoad(FGNasalModelData *data)
void hashset(naRef hash, const char *key, naRef val)
Set member of specified hash to given value.
Definition NasalSys.cxx:302
naRef callWithContext(naContext ctx, naRef code, int argc, naRef *args, naRef locals)
Definition NasalSys.cxx:319
bool addCommand(naRef func, const std::string &name)
void init() override
bool reloadModuleFromFile(const std::string &moduleName)
void shutdown() override
naRef nasalGlobals() const
virtual ~FGNasalSys()
Definition NasalSys.cxx:354
naRef callMethodWithContext(naContext ctx, naRef code, naRef self, int argc, naRef *args, naRef locals)
Definition NasalSys.cxx:341
naRef setListener(naContext c, int argc, naRef *args)
string_list getAndClearErrorList()
naRef getModule(const std::string &moduleName) const
void gcRelease(int key)
nasal::Hash getGlobals() const
virtual bool handleCommand(const char *moduleName, const char *fileName, const char *src, const SGPropertyNode *arg=0, SGPropertyNode *root=0)
void globalsSet(const char *key, naRef val)
Set member of globals hash to given value.
Definition NasalSys.cxx:309
bool parseAndRunWithOutput(const std::string &source, std::string &output, std::string &errors)
Definition NasalSys.cxx:364
naRef propNodeGhost(SGPropertyNode *handle)
naRef wrappedPropsNode(SGPropertyNode *aProps)
create Nasal props.Node for an SGPropertyNode* This is the actual ghost, wrapped in a Nasal sugar cla...
bool parseAndRun(const std::string &source)
Definition NasalSys.cxx:388
bool loadModule(SGPath file, const char *moduleName)
static Ptr getInstance()
Get clipboard platform specific instance.
static void init(FGNasalSys *nasal)
Sets up the clipboard and puts all the extension functions into a new "clipboard" namespace.
bool operator()(const SGPropertyNode *aNode, SGPropertyNode *root) override
Definition NasalSys.cxx:815
NasalCommand(FGNasalSys *sys, naRef f, const std::string &name)
Definition NasalSys.cxx:801
virtual ~NasalCommand()
Definition NasalSys.cxx:810
virtual void endElement(const char *tag)
virtual void startElement(const char *tag, const XMLAttributes &a)
virtual void pi(const char *target, const char *data)
virtual void data(const char *str, int len)
NasalXMLVisitor(naContext c, int argc, naRef *args)
Timestamp - used to provide millisecond based timing of operations. See SGTimeStamp / 0....
Definition NasalSys.cxx:250
TimeStampObj(Context *c)
Definition NasalSys.cxx:252
virtual ~TimeStampObj()=default
double elapsedMSec()
Definition NasalSys.cxx:263
double elapsedUSec()
Definition NasalSys.cxx:267
void stop()
Definition NasalSys.cxx:156
void invoke()
Definition NasalSys.cxx:200
TimerObj(Context *c, FGNasalSys *sys, naRef f, naRef self, double interval)
Definition NasalSys.cxx:127
bool isRunning() const
Definition NasalSys.cxx:154
void start()
Definition NasalSys.cxx:176
void restart(double newInterval)
Definition NasalSys.cxx:193
void setSingleShot(bool aSingleShot)
Definition NasalSys.cxx:212
bool isSimTime() const
Definition NasalSys.cxx:164
virtual ~TimerObj()
Definition NasalSys.cxx:146
const std::string & name() const
Definition NasalSys.cxx:220
void setSimTime(bool value)
Definition NasalSys.cxx:166
bool isSingleShot() const
Definition NasalSys.cxx:217
const char * name
FGGlobals * globals
Definition globals.cxx:142
std::vector< std::string > string_list
Definition globals.hxx:36
void initAddonClassesForNasal(naRef globals, naContext c)
void initNasalTranslations(naRef globals, naContext c)
void sentryReportNasalError(const std::string &, const string_list &)
naCFunction func
bool fgGetBool(char const *name, bool def)
Get a bool value for a property.
Definition proptest.cpp:25
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
Definition proptest.cpp:27
naRef naInit_sqlite(naContext c)
void timerExpired()
NasalTimer(naRef handler, FGNasalSys *sys)
void valueChanged(SGPropertyNode *node) override
Definition NasalSys.cxx:525
static bool _file_line
Definition NasalSys.cxx:529