2020-04-27 08:50:38 +01:00
|
|
|
// Copyright (C) 2013 James Turner
|
|
|
|
//
|
|
|
|
// This program is free software; you can redistribute it and/or
|
|
|
|
// modify it under the terms of the GNU General Public License as
|
|
|
|
// published by the Free Software Foundation; either version 2 of the
|
|
|
|
// License, or (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful, but
|
|
|
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
// General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with this program; if not, write to the Free Software
|
|
|
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
2006-02-18 13:58:09 +00:00
|
|
|
|
2020-04-27 08:50:38 +01:00
|
|
|
#include "config.h"
|
2006-02-18 13:58:09 +00:00
|
|
|
|
2013-02-08 08:46:34 +01:00
|
|
|
#ifdef HAVE_WINDOWS_H
|
|
|
|
#include <windows.h>
|
|
|
|
#endif
|
|
|
|
|
2007-05-14 16:24:41 +00:00
|
|
|
#ifdef HAVE_SYS_TIME_H
|
|
|
|
# include <sys/time.h> // gettimeofday
|
|
|
|
#endif
|
|
|
|
|
2020-04-21 15:38:52 +01:00
|
|
|
#include <algorithm>
|
2015-03-13 18:02:46 +00:00
|
|
|
#include <errno.h>
|
2003-11-25 21:08:36 +00:00
|
|
|
#include <stdio.h>
|
2020-04-21 15:38:52 +01:00
|
|
|
#include <string.h>
|
2003-11-25 21:08:36 +00:00
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
add parsexml() function, which is a wrapper around the built-in easyxml
parser. Advantages over xml.nas: (reviewed and OK'ed by Andy)
- faster (33% ... only. I had hoped for more.)
- more standards compliant
- should support UTF
- I don't have to support it. ;-)
Usage: parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
<path> is an absolute file path, the rest are optional callback functions.
Example:
parsexml("/tmp/foo.xml", nil, nil, func(d) { print("DATA FOUND: ", d) });
2007-06-29 15:34:38 +00:00
|
|
|
#include <fstream>
|
2009-03-22 17:04:02 +00:00
|
|
|
#include <sstream>
|
2003-11-25 21:08:36 +00:00
|
|
|
|
2021-02-20 20:06:27 +00:00
|
|
|
#include <simgear/debug/BufferedLogCallback.hxx>
|
|
|
|
#include <simgear/debug/ErrorReportingCallback.hxx>
|
2017-02-09 19:48:33 +01:00
|
|
|
#include <simgear/io/iostreams/sgstream.hxx>
|
2021-02-20 20:06:27 +00:00
|
|
|
#include <simgear/math/sg_geodesy.hxx>
|
2021-05-25 12:57:07 +01:00
|
|
|
#include <simgear/math/sg_random.hxx>
|
2021-02-20 20:06:27 +00:00
|
|
|
#include <simgear/misc/SimpleMarkdown.hxx>
|
2010-10-24 07:09:05 +01:00
|
|
|
#include <simgear/misc/sg_dir.hxx>
|
2021-02-20 20:06:27 +00:00
|
|
|
#include <simgear/misc/sg_path.hxx>
|
2016-07-15 16:35:17 +01:00
|
|
|
#include <simgear/misc/strutils.hxx>
|
2021-02-20 20:06:27 +00:00
|
|
|
#include <simgear/nasal/iolib.h>
|
|
|
|
#include <simgear/nasal/nasal.h>
|
|
|
|
#include <simgear/props/props.hxx>
|
2003-11-25 21:08:36 +00:00
|
|
|
#include <simgear/structure/commands.hxx>
|
2008-07-31 12:04:32 +00:00
|
|
|
#include <simgear/structure/event_mgr.hxx>
|
2013-03-16 12:44:27 +00:00
|
|
|
|
|
|
|
#include <simgear/nasal/cppbind/from_nasal.hxx>
|
|
|
|
#include <simgear/nasal/cppbind/to_nasal.hxx>
|
|
|
|
#include <simgear/nasal/cppbind/Ghost.hxx>
|
2013-02-09 16:05:54 +00:00
|
|
|
#include <simgear/nasal/cppbind/NasalHash.hxx>
|
2019-05-02 07:30:32 +02:00
|
|
|
#include <simgear/timing/timestamp.hxx>
|
2003-11-25 21:08:36 +00:00
|
|
|
|
Improved infrastructure for add-ons: C++ classes, metadata file, Nasal interface
This commit adds C++ classes for add-on management, most notably
AddonManager, Addon and AddonVersion. The AddonManager is used to
register add-ons. It relies on an std::map<std::string, AddonRef> to
hold the metadata of each registered add-on (keys of the std::map are
add-on identifiers, and AddonRef is currently SGSharedPtr<Addon>).
Accessor methods are available for:
- retrieving the list of registered or loaded add-ons (terminology
explained in $FG_ROOT/Docs/README.add-ons);
- checking if a particular add-on has already been registered or
loaded;
- for each add-on, obtaining an Addon instance which can be queried
for its name, id, version, base path, the minimum and maximum
FlightGear versions it requires, its base node in the Property Tree,
its order in the load sequence, short and long description strings,
home page, etc.
The most important metadata is made accessible in the Property Tree
under /addons/by-id/<addon-id> and the property
/addons/by-id/<addon-id>/loaded can be checked or listened to, in
order to determine when a particular add-on is loaded. There is also a
Nasal interface to access add-on metadata in a convenient way.
In order to provide this metadata, each add-on must from now on have in
its base directory a file called 'addon-metadata.xml'.
All this is documented in much more detail in
$FG_ROOT/Docs/README.add-ons.
Mailing-list discussion:
https://sourceforge.net/p/flightgear/mailman/message/36146017/
2017-11-06 13:58:14 +01:00
|
|
|
#include "NasalAddons.hxx"
|
2014-05-10 10:52:16 +02:00
|
|
|
#include "NasalAircraft.hxx"
|
2012-08-02 12:18:38 +01:00
|
|
|
#include "NasalCanvas.hxx"
|
2012-08-04 00:24:26 +02:00
|
|
|
#include "NasalClipboard.hxx"
|
2012-08-19 21:13:31 +01:00
|
|
|
#include "NasalCondition.hxx"
|
2020-05-22 16:36:44 +01:00
|
|
|
#include "NasalFlightPlan.hxx"
|
2013-10-15 01:02:05 +02:00
|
|
|
#include "NasalHTTP.hxx"
|
2020-05-22 16:36:44 +01:00
|
|
|
#include "NasalModelData.hxx"
|
|
|
|
#include "NasalPositioned.hxx"
|
|
|
|
#include "NasalSGPath.hxx"
|
2013-01-31 19:14:14 +01:00
|
|
|
#include "NasalString.hxx"
|
2020-05-22 16:36:44 +01:00
|
|
|
#include "NasalSys.hxx"
|
|
|
|
#include "NasalSys_private.hxx"
|
2020-04-27 08:50:38 +01:00
|
|
|
#include "NasalUnitTesting.hxx"
|
2012-08-02 12:18:38 +01:00
|
|
|
|
2003-11-25 21:08:36 +00:00
|
|
|
#include <Main/globals.hxx>
|
2012-04-15 13:54:50 +01:00
|
|
|
#include <Main/fg_props.hxx>
|
2020-04-20 10:47:11 +01:00
|
|
|
#include <Main/sentryIntegration.hxx>
|
2012-08-04 00:24:26 +02:00
|
|
|
|
2012-03-31 18:19:14 +02:00
|
|
|
using std::map;
|
2013-11-11 19:55:54 +01:00
|
|
|
using std::string;
|
|
|
|
using std::vector;
|
2012-03-31 18:19:14 +02:00
|
|
|
|
2012-12-28 14:48:19 +00:00
|
|
|
void postinitNasalGUI(naRef globals, naContext c);
|
|
|
|
|
2020-04-23 10:11:28 +01:00
|
|
|
static FGNasalSys* nasalSys = nullptr;
|
|
|
|
|
|
|
|
// this is used by the test-suite to simplify
|
|
|
|
// how much Nasal modules we load by default
|
|
|
|
bool global_nasalMinimalInit = false;
|
|
|
|
|
2007-05-01 17:03:50 +00:00
|
|
|
|
2011-04-03 15:30:25 +02:00
|
|
|
// Listener class for loading Nasal modules on demand
|
|
|
|
class FGNasalModuleListener : public SGPropertyChangeListener
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
FGNasalModuleListener(SGPropertyNode* node);
|
|
|
|
|
|
|
|
virtual void valueChanged(SGPropertyNode* node);
|
|
|
|
|
|
|
|
private:
|
|
|
|
SGPropertyNode_ptr _node;
|
|
|
|
};
|
|
|
|
|
|
|
|
FGNasalModuleListener::FGNasalModuleListener(SGPropertyNode* node) : _node(node)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void FGNasalModuleListener::valueChanged(SGPropertyNode*)
|
|
|
|
{
|
2023-01-06 10:41:01 +00:00
|
|
|
const auto enabled = _node->getBoolValue("enabled", false);
|
|
|
|
const auto loaded = _node->getBoolValue("loaded", true);
|
|
|
|
if (enabled && !loaded) {
|
2011-04-03 15:30:25 +02:00
|
|
|
nasalSys->loadPropertyScripts(_node);
|
2023-01-06 10:41:01 +00:00
|
|
|
} else if (!enabled && loaded) {
|
|
|
|
// delete the module
|
|
|
|
std::string module = _node->getNameString();
|
|
|
|
if (_node->hasChild("module")) {
|
|
|
|
module = _node->getStringValue("module");
|
|
|
|
}
|
|
|
|
|
|
|
|
nasalSys->deleteModule(module.c_str());
|
2011-04-03 15:30:25 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-03-16 12:44:27 +00:00
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
class TimerObj : public SGReferenced
|
|
|
|
{
|
|
|
|
public:
|
2019-02-10 17:24:55 +01:00
|
|
|
TimerObj(Context *c, FGNasalSys* sys, naRef f, naRef self, double interval) :
|
2013-03-16 12:44:27 +00:00
|
|
|
_sys(sys),
|
|
|
|
_func(f),
|
|
|
|
_self(self),
|
2016-12-11 16:10:57 +00:00
|
|
|
_interval(interval)
|
2013-03-16 12:44:27 +00:00
|
|
|
{
|
2019-04-28 19:25:11 +02:00
|
|
|
char nm[256];
|
2019-02-10 17:24:55 +01:00
|
|
|
if (c) {
|
2019-04-28 19:25:11 +02:00
|
|
|
snprintf(nm, 128, "maketimer-[%p]-%s:%d", (void*)this, naStr_data(naGetSourceFile(c, 0)), naGetLine(c, 0));
|
2019-02-10 17:24:55 +01:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
snprintf(nm, 128, "maketimer-%p", this);
|
|
|
|
}
|
2013-03-16 12:44:27 +00:00
|
|
|
_name = nm;
|
2018-09-01 18:53:36 +01:00
|
|
|
_gcRoot = naGCSave(f);
|
|
|
|
_gcSelf = naGCSave(self);
|
|
|
|
sys->addPersistentTimer(this);
|
2013-03-16 12:44:27 +00:00
|
|
|
}
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2013-03-16 12:44:27 +00:00
|
|
|
virtual ~TimerObj()
|
|
|
|
{
|
|
|
|
stop();
|
2018-09-01 18:53:36 +01:00
|
|
|
naGCRelease(_gcRoot);
|
|
|
|
naGCRelease(_gcSelf);
|
|
|
|
_sys->removePersistentTimer(this);
|
2013-03-16 12:44:27 +00:00
|
|
|
}
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2013-03-20 18:10:27 +00:00
|
|
|
bool isRunning() const { return _isRunning; }
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2013-03-16 12:44:27 +00:00
|
|
|
void stop()
|
|
|
|
{
|
|
|
|
if (_isRunning) {
|
|
|
|
globals->get_event_mgr()->removeTask(_name);
|
|
|
|
_isRunning = false;
|
|
|
|
}
|
|
|
|
}
|
2016-12-11 16:10:57 +00:00
|
|
|
|
|
|
|
bool isSimTime() const { return _isSimTime; }
|
|
|
|
|
|
|
|
void setSimTime(bool value)
|
|
|
|
{
|
|
|
|
if (_isRunning) {
|
|
|
|
SG_LOG(SG_NASAL, SG_WARN, "can't change type of running timer!");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
_isSimTime = value;
|
|
|
|
}
|
|
|
|
|
2013-03-16 12:44:27 +00:00
|
|
|
void start()
|
|
|
|
{
|
|
|
|
if (_isRunning) {
|
|
|
|
return;
|
|
|
|
}
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2013-03-16 12:44:27 +00:00
|
|
|
_isRunning = true;
|
|
|
|
if (_singleShot) {
|
2022-05-24 21:52:41 +02:00
|
|
|
globals->get_event_mgr()->addEvent(_name, [this](){ this->invoke(); }, _interval, _isSimTime);
|
2013-03-16 12:44:27 +00:00
|
|
|
} else {
|
2022-05-24 21:52:41 +02:00
|
|
|
globals->get_event_mgr()->addTask(_name, [this](){ this->invoke(); },
|
2016-12-11 16:10:57 +00:00
|
|
|
_interval, _interval /* delay */,
|
|
|
|
_isSimTime);
|
2013-03-16 12:44:27 +00:00
|
|
|
}
|
|
|
|
}
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2013-03-16 12:44:27 +00:00
|
|
|
// stop and then start -
|
|
|
|
void restart(double newInterval)
|
|
|
|
{
|
|
|
|
_interval = newInterval;
|
|
|
|
stop();
|
|
|
|
start();
|
|
|
|
}
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2013-03-16 12:44:27 +00:00
|
|
|
void invoke()
|
|
|
|
{
|
2014-08-11 00:31:18 +02:00
|
|
|
if( _singleShot )
|
|
|
|
// Callback may restart the timer, so update status before callback is
|
|
|
|
// called (Prevent warnings of deleting not existing tasks from the
|
|
|
|
// event manager).
|
|
|
|
_isRunning = false;
|
|
|
|
|
2018-09-01 18:53:36 +01:00
|
|
|
naRef *args = nullptr;
|
2013-03-16 12:44:27 +00:00
|
|
|
_sys->callMethod(_func, _self, 0, args, naNil() /* locals */);
|
|
|
|
}
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2013-03-16 12:44:27 +00:00
|
|
|
void setSingleShot(bool aSingleShot)
|
|
|
|
{
|
|
|
|
_singleShot = aSingleShot;
|
|
|
|
}
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2013-03-16 12:44:27 +00:00
|
|
|
bool isSingleShot() const
|
|
|
|
{ return _singleShot; }
|
2020-02-19 20:32:22 +00:00
|
|
|
|
2019-09-18 14:46:40 +01:00
|
|
|
const std::string& name() const
|
|
|
|
{ return _name; }
|
2013-03-16 12:44:27 +00:00
|
|
|
private:
|
|
|
|
std::string _name;
|
|
|
|
FGNasalSys* _sys;
|
|
|
|
naRef _func, _self;
|
|
|
|
int _gcRoot, _gcSelf;
|
2016-12-11 16:10:57 +00:00
|
|
|
bool _isRunning = false;
|
2013-03-16 12:44:27 +00:00
|
|
|
double _interval;
|
2016-12-11 16:10:57 +00:00
|
|
|
bool _singleShot = false;
|
|
|
|
bool _isSimTime = false;
|
2013-03-16 12:44:27 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
typedef SGSharedPtr<TimerObj> TimerObjRef;
|
|
|
|
typedef nasal::Ghost<TimerObjRef> NasalTimerObj;
|
|
|
|
|
2016-12-11 16:10:57 +00:00
|
|
|
static void f_timerObj_setSimTime(TimerObj& timer, naContext c, naRef value)
|
|
|
|
{
|
|
|
|
if (timer.isRunning()) {
|
|
|
|
naRuntimeError(c, "Timer is running, cannot change type between real/sim time");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
timer.setSimTime(nasal::from_nasal<bool>(c, value));
|
|
|
|
}
|
2019-05-02 07:30:32 +02:00
|
|
|
////////////////////
|
|
|
|
/// Timestamp - used to provide millisecond based timing of operations. See SGTimeStamp
|
2019-05-07 05:16:21 +02:00
|
|
|
////
|
2020-02-19 20:32:22 +00:00
|
|
|
/// 0.373404us to perform elapsedUSec from Nasal : Tested i7-4790K, Win64
|
2019-05-02 07:30:32 +02:00
|
|
|
class TimeStampObj : public SGReferenced
|
|
|
|
{
|
|
|
|
public:
|
2020-03-10 17:02:08 +00:00
|
|
|
TimeStampObj(Context *c)
|
2019-05-02 07:30:32 +02:00
|
|
|
{
|
2019-05-07 05:16:21 +02:00
|
|
|
timestamp.stamp();
|
2019-05-02 07:30:32 +02:00
|
|
|
}
|
|
|
|
|
2020-03-10 17:02:08 +00:00
|
|
|
virtual ~TimeStampObj() = default;
|
2019-05-02 07:30:32 +02:00
|
|
|
|
|
|
|
void stamp()
|
|
|
|
{
|
|
|
|
timestamp.stamp();
|
|
|
|
}
|
2019-05-07 05:16:21 +02:00
|
|
|
double elapsedMSec()
|
2019-05-02 07:30:32 +02:00
|
|
|
{
|
|
|
|
return timestamp.elapsedMSec();
|
|
|
|
}
|
2019-05-07 05:16:21 +02:00
|
|
|
double elapsedUSec()
|
|
|
|
{
|
|
|
|
return timestamp.elapsedUSec();
|
|
|
|
}
|
2019-05-02 07:30:32 +02:00
|
|
|
private:
|
|
|
|
SGTimeStamp timestamp;
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef SGSharedPtr<TimeStampObj> TimeStampObjRef;
|
|
|
|
typedef nasal::Ghost<TimeStampObjRef> NasalTimeStampObj;
|
2016-12-11 16:10:57 +00:00
|
|
|
|
2013-03-16 12:44:27 +00:00
|
|
|
///////////////////////////////////////////////////////////////////////////
|
2007-05-01 17:03:50 +00:00
|
|
|
|
2014-03-16 22:52:55 +00:00
|
|
|
FGNasalSys::FGNasalSys() :
|
|
|
|
_inited(false)
|
2003-11-25 21:08:36 +00:00
|
|
|
{
|
2020-04-27 08:50:38 +01:00
|
|
|
nasalSys = this;
|
2003-11-25 21:08:36 +00:00
|
|
|
_context = 0;
|
|
|
|
_globals = naNil();
|
2013-01-31 19:14:14 +01:00
|
|
|
_string = naNil();
|
2013-11-22 22:40:50 +00:00
|
|
|
_wrappedNodeFunc = naNil();
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2018-12-20 16:07:44 +00:00
|
|
|
_log.reset(new simgear::BufferedLogCallback(SG_NASAL, SG_INFO));
|
2013-02-08 19:39:41 +00:00
|
|
|
_log->truncateAt(255);
|
2018-12-20 16:07:44 +00:00
|
|
|
sglog().addCallback(_log.get());
|
2013-10-15 17:48:13 +02:00
|
|
|
|
|
|
|
naSetErrorHandler(&logError);
|
2006-07-19 19:46:53 +00:00
|
|
|
}
|
|
|
|
|
2012-08-04 00:24:26 +02:00
|
|
|
// Utility. Sets a named key in a hash by C string, rather than nasal
|
|
|
|
// string object.
|
|
|
|
void FGNasalSys::hashset(naRef hash, const char* key, naRef val)
|
|
|
|
{
|
|
|
|
naRef s = naNewString(_context);
|
|
|
|
naStr_fromdata(s, (char*)key, strlen(key));
|
|
|
|
naHash_set(hash, s, val);
|
|
|
|
}
|
|
|
|
|
|
|
|
void FGNasalSys::globalsSet(const char* key, naRef val)
|
|
|
|
{
|
|
|
|
hashset(_globals, key, val);
|
|
|
|
}
|
|
|
|
|
2012-05-12 17:23:17 +01:00
|
|
|
naRef FGNasalSys::call(naRef code, int argc, naRef* args, naRef locals)
|
|
|
|
{
|
|
|
|
return callMethod(code, naNil(), argc, args, locals);
|
|
|
|
}
|
|
|
|
|
2014-04-15 14:13:46 +01:00
|
|
|
naRef FGNasalSys::callWithContext(naContext ctx, naRef code, int argc, naRef* args, naRef locals)
|
|
|
|
{
|
|
|
|
return callMethodWithContext(ctx, code, naNil(), argc, args, locals);
|
|
|
|
}
|
|
|
|
|
2006-07-19 19:46:53 +00:00
|
|
|
// Does a naCall() in a new context. Wrapped here to make lock
|
|
|
|
// tracking easier. Extension functions are called with the lock, but
|
|
|
|
// we have to release it before making a new naCall(). So rather than
|
|
|
|
// drop the lock in every extension function that might call back into
|
|
|
|
// Nasal, we keep a stack depth counter here and only unlock/lock
|
|
|
|
// around the naCall if it isn't the first one.
|
2012-05-12 17:23:17 +01:00
|
|
|
|
|
|
|
naRef FGNasalSys::callMethod(naRef code, naRef self, int argc, naRef* args, naRef locals)
|
2006-07-19 19:46:53 +00:00
|
|
|
{
|
2018-09-07 15:17:55 +01:00
|
|
|
try {
|
|
|
|
return naCallMethod(code, self, argc, args, locals);
|
|
|
|
} catch (sg_exception& e) {
|
|
|
|
SG_LOG(SG_NASAL, SG_DEV_ALERT, "caught exception invoking nasal method:" << e.what());
|
|
|
|
return naNil();
|
|
|
|
}
|
2003-11-25 21:08:36 +00:00
|
|
|
}
|
|
|
|
|
2014-04-15 14:13:46 +01:00
|
|
|
naRef FGNasalSys::callMethodWithContext(naContext ctx, naRef code, naRef self, int argc, naRef* args, naRef locals)
|
|
|
|
{
|
2018-09-07 15:17:55 +01:00
|
|
|
try {
|
|
|
|
return naCallMethodCtx(ctx, code, self, argc, args, locals);
|
|
|
|
} catch (sg_exception& e) {
|
|
|
|
SG_LOG(SG_NASAL, SG_DEV_ALERT, "caught exception invoking nasal method:" << e.what());
|
2020-04-20 10:47:11 +01:00
|
|
|
string_list nasalStack;
|
|
|
|
logNasalStack(ctx, nasalStack);
|
|
|
|
flightgear::sentryReportNasalError(string{"Exception invoking nasal method:"} + e.what(), nasalStack);
|
2018-09-07 15:17:55 +01:00
|
|
|
return naNil();
|
|
|
|
}
|
2014-04-15 14:13:46 +01:00
|
|
|
}
|
|
|
|
|
2003-11-25 21:08:36 +00:00
|
|
|
FGNasalSys::~FGNasalSys()
|
|
|
|
{
|
2014-03-16 22:52:55 +00:00
|
|
|
if (_inited) {
|
|
|
|
SG_LOG(SG_GENERAL, SG_ALERT, "Nasal was not shutdown");
|
|
|
|
}
|
2018-12-20 16:07:44 +00:00
|
|
|
sglog().removeCallback(_log.get());
|
2020-02-19 20:32:22 +00:00
|
|
|
|
2018-09-07 15:17:55 +01:00
|
|
|
nasalSys = nullptr;
|
2003-11-25 21:08:36 +00:00
|
|
|
}
|
|
|
|
|
2018-02-03 13:44:26 +00:00
|
|
|
bool FGNasalSys::parseAndRunWithOutput(const std::string& source, std::string& output, std::string& errors)
|
2003-12-01 14:35:49 +00:00
|
|
|
{
|
2014-04-15 14:13:46 +01:00
|
|
|
naContext ctx = naNewContext();
|
2018-02-03 13:44:26 +00:00
|
|
|
naRef code = parse(ctx, "FGNasalSys::parseAndRun()", source.c_str(),
|
|
|
|
source.size(), errors);
|
2014-04-15 14:13:46 +01:00
|
|
|
if(naIsNil(code)) {
|
|
|
|
naFreeContext(ctx);
|
2003-12-01 14:35:49 +00:00
|
|
|
return false;
|
2014-04-15 14:13:46 +01:00
|
|
|
}
|
2018-02-03 13:44:26 +00:00
|
|
|
naRef result = callWithContext(ctx, code, 0, 0, naNil());
|
2020-02-19 20:32:22 +00:00
|
|
|
|
2018-02-03 13:44:26 +00:00
|
|
|
// if there was a result value, try to convert it to a string
|
|
|
|
// value.
|
|
|
|
if (!naIsNil(result)) {
|
|
|
|
naRef s = naStringValue(ctx, result);
|
|
|
|
if (!naIsNil(s)) {
|
|
|
|
output = naStr_data(s);
|
|
|
|
}
|
|
|
|
}
|
2020-02-19 20:32:22 +00:00
|
|
|
|
2014-04-15 14:13:46 +01:00
|
|
|
naFreeContext(ctx);
|
2006-07-19 19:46:53 +00:00
|
|
|
return true;
|
2003-12-01 14:35:49 +00:00
|
|
|
}
|
|
|
|
|
2018-02-03 13:44:26 +00:00
|
|
|
bool FGNasalSys::parseAndRun(const std::string& source)
|
|
|
|
{
|
|
|
|
std::string errors;
|
|
|
|
std::string output;
|
|
|
|
return parseAndRunWithOutput(source, output, errors);
|
|
|
|
}
|
|
|
|
|
2013-02-09 15:33:05 +00:00
|
|
|
#if 0
|
2003-12-05 01:54:39 +00:00
|
|
|
FGNasalScript* FGNasalSys::parseScript(const char* src, const char* name)
|
|
|
|
{
|
|
|
|
FGNasalScript* script = new FGNasalScript();
|
|
|
|
script->_gcKey = -1; // important, if we delete it on a parse
|
|
|
|
script->_nas = this; // error, don't clobber a real handle!
|
|
|
|
|
|
|
|
char buf[256];
|
|
|
|
if(!name) {
|
2023-01-08 19:33:14 +00:00
|
|
|
snprintf(buf, 256, "FGNasalScript@%p", (void *)script);
|
2003-12-05 01:54:39 +00:00
|
|
|
name = buf;
|
|
|
|
}
|
|
|
|
|
2005-04-26 20:57:06 +00:00
|
|
|
script->_code = parse(name, src, strlen(src));
|
2003-12-05 01:54:39 +00:00
|
|
|
if(naIsNil(script->_code)) {
|
|
|
|
delete script;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
script->_gcKey = gcSave(script->_code);
|
|
|
|
return script;
|
|
|
|
}
|
2013-02-09 15:33:05 +00:00
|
|
|
#endif
|
2003-12-05 01:54:39 +00:00
|
|
|
|
2003-11-25 21:08:36 +00:00
|
|
|
// The get/setprop functions accept a *list* of strings and walk
|
|
|
|
// through the property tree with them to find the appropriate node.
|
|
|
|
// This allows a Nasal object to hold onto a property path and use it
|
|
|
|
// like a node object, e.g. setprop(ObjRoot, "size-parsecs", 2.02). This
|
|
|
|
// is the utility function that walks the property tree.
|
2014-02-18 20:07:29 -08:00
|
|
|
static SGPropertyNode* findnode(naContext c, naRef* vec, int len, bool create=false)
|
2003-11-25 21:08:36 +00:00
|
|
|
{
|
|
|
|
SGPropertyNode* p = globals->get_props();
|
2006-05-23 18:55:38 +00:00
|
|
|
try {
|
|
|
|
for(int i=0; i<len; i++) {
|
|
|
|
naRef a = vec[i];
|
2014-02-18 20:07:29 -08:00
|
|
|
if(!naIsString(a)) {
|
|
|
|
naRuntimeError(c, "bad argument to setprop/getprop path: expected a string");
|
|
|
|
}
|
|
|
|
naRef b = i < len-1 ? naNumValue(vec[i+1]) : naNil();
|
|
|
|
if (!naIsNil(b)) {
|
|
|
|
p = p->getNode(naStr_data(a), (int)b.num, create);
|
|
|
|
i++;
|
|
|
|
} else {
|
|
|
|
p = p->getNode(naStr_data(a), create);
|
|
|
|
}
|
2006-05-23 18:55:38 +00:00
|
|
|
if(p == 0) return 0;
|
|
|
|
}
|
|
|
|
} catch (const string& err) {
|
|
|
|
naRuntimeError(c, (char *)err.c_str());
|
2003-11-25 21:08:36 +00:00
|
|
|
}
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
|
|
|
// getprop() extension function. Concatenates its string arguments as
|
|
|
|
// property names and returns the value of the specified property. Or
|
|
|
|
// nil if it doesn't exist.
|
2005-04-18 19:49:13 +00:00
|
|
|
static naRef f_getprop(naContext c, naRef me, int argc, naRef* args)
|
2003-11-25 21:08:36 +00:00
|
|
|
{
|
2009-07-17 14:54:12 +02:00
|
|
|
using namespace simgear;
|
2014-02-18 20:07:29 -08:00
|
|
|
if (argc < 1) {
|
|
|
|
naRuntimeError(c, "getprop() expects at least 1 argument");
|
|
|
|
}
|
|
|
|
const SGPropertyNode* p = findnode(c, args, argc, false);
|
2003-11-25 21:08:36 +00:00
|
|
|
if(!p) return naNil();
|
|
|
|
|
|
|
|
switch(p->getType()) {
|
2009-07-17 14:54:12 +02:00
|
|
|
case props::BOOL: case props::INT:
|
|
|
|
case props::LONG: case props::FLOAT:
|
|
|
|
case props::DOUBLE:
|
2010-10-20 00:08:57 +01:00
|
|
|
{
|
|
|
|
double dv = p->getDoubleValue();
|
2012-11-11 17:22:42 +01:00
|
|
|
if (SGMisc<double>::isNaN(dv)) {
|
2011-12-11 13:55:56 +01:00
|
|
|
SG_LOG(SG_NASAL, SG_ALERT, "Nasal getprop: property " << p->getPath() << " is NaN");
|
2011-01-14 20:04:20 +00:00
|
|
|
return naNil();
|
2010-10-20 00:08:57 +01:00
|
|
|
}
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2010-10-20 00:08:57 +01:00
|
|
|
return naNum(dv);
|
|
|
|
}
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2009-07-17 14:54:12 +02:00
|
|
|
case props::STRING:
|
|
|
|
case props::UNSPECIFIED:
|
2003-11-25 21:08:36 +00:00
|
|
|
{
|
|
|
|
naRef nastr = naNewString(c);
|
2021-11-06 19:10:20 +01:00
|
|
|
std::string val = p->getStringValue();
|
|
|
|
naStr_fromdata(nastr, val.c_str(), val.length());
|
2003-11-25 21:08:36 +00:00
|
|
|
return nastr;
|
|
|
|
}
|
2009-07-17 14:54:12 +02:00
|
|
|
case props::ALIAS: // <--- FIXME, recurse?
|
2003-11-25 21:08:36 +00:00
|
|
|
default:
|
|
|
|
return naNil();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// setprop() extension function. Concatenates its string arguments as
|
|
|
|
// property names and sets the value of the specified property to the
|
|
|
|
// final argument.
|
2005-04-18 19:49:13 +00:00
|
|
|
static naRef f_setprop(naContext c, naRef me, int argc, naRef* args)
|
2003-11-25 21:08:36 +00:00
|
|
|
{
|
2014-02-18 20:07:29 -08:00
|
|
|
if (argc < 2) {
|
|
|
|
naRuntimeError(c, "setprop() expects at least 2 arguments");
|
2003-11-25 21:08:36 +00:00
|
|
|
}
|
2014-02-18 20:07:29 -08:00
|
|
|
naRef val = args[argc - 1];
|
|
|
|
SGPropertyNode* p = findnode(c, args, argc-1, true);
|
2003-11-25 21:08:36 +00:00
|
|
|
|
2009-02-07 23:52:12 +00:00
|
|
|
bool result = false;
|
2006-05-23 18:55:38 +00:00
|
|
|
try {
|
2014-02-18 20:07:29 -08:00
|
|
|
if(naIsString(val)) result = p->setStringValue(naStr_data(val));
|
2006-10-17 19:58:33 +00:00
|
|
|
else {
|
2014-02-18 20:07:29 -08:00
|
|
|
if(!naIsNum(val))
|
2006-10-17 19:58:33 +00:00
|
|
|
naRuntimeError(c, "setprop() value is not string or number");
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2014-02-18 20:07:29 -08:00
|
|
|
if (SGMisc<double>::isNaN(val.num)) {
|
2010-10-20 00:08:57 +01:00
|
|
|
naRuntimeError(c, "setprop() passed a NaN");
|
|
|
|
}
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2014-02-18 20:07:29 -08:00
|
|
|
result = p->setDoubleValue(val.num);
|
2006-10-17 19:58:33 +00:00
|
|
|
}
|
2006-05-23 18:55:38 +00:00
|
|
|
} catch (const string& err) {
|
|
|
|
naRuntimeError(c, (char *)err.c_str());
|
|
|
|
}
|
2009-02-07 23:52:12 +00:00
|
|
|
return naNum(result);
|
2003-11-25 21:08:36 +00:00
|
|
|
}
|
|
|
|
|
2019-09-17 21:06:39 +01:00
|
|
|
|
|
|
|
struct SimNasalLogFileLine : SGPropertyChangeListener
|
|
|
|
{
|
|
|
|
SimNasalLogFileLine() {
|
|
|
|
SGPropertyNode* node = fgGetNode("/sim/nasal-log-file-line", true /*create*/);
|
|
|
|
node->addChangeListener(this, true /*initial*/);
|
|
|
|
}
|
2020-06-20 19:07:24 +01:00
|
|
|
void valueChanged(SGPropertyNode* node) override {
|
2019-09-17 21:06:39 +01:00
|
|
|
_file_line = node->getIntValue();
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool _file_line;
|
|
|
|
};
|
|
|
|
|
|
|
|
bool SimNasalLogFileLine::_file_line = false;
|
|
|
|
|
2003-11-25 21:08:36 +00:00
|
|
|
// print() extension function. Concatenates and prints its arguments
|
2020-09-06 14:57:58 +01:00
|
|
|
// to the FlightGear log. Uses the mandatory log level (SG_MANDATORY_INFO), to
|
|
|
|
// make sure it appears. Users should be converted to use logprint() instead,
|
|
|
|
// and specify a level explicitly.
|
2005-04-18 19:49:13 +00:00
|
|
|
static naRef f_print(naContext c, naRef me, int argc, naRef* args)
|
2003-11-25 21:08:36 +00:00
|
|
|
{
|
2019-09-17 21:06:39 +01:00
|
|
|
static SimNasalLogFileLine snlfl;
|
2007-01-28 12:16:37 +00:00
|
|
|
string buf;
|
2005-04-18 19:49:13 +00:00
|
|
|
int n = argc;
|
2003-11-25 21:08:36 +00:00
|
|
|
for(int i=0; i<n; i++) {
|
2005-04-18 19:49:13 +00:00
|
|
|
naRef s = naStringValue(c, args[i]);
|
2003-11-25 21:08:36 +00:00
|
|
|
if(naIsNil(s)) continue;
|
2007-01-28 12:16:37 +00:00
|
|
|
buf += naStr_data(s);
|
2003-11-25 21:08:36 +00:00
|
|
|
}
|
2019-09-17 21:06:39 +01:00
|
|
|
if (snlfl._file_line) {
|
|
|
|
/* Copy what SG_LOG() does, but use nasal file:line instead of
|
2020-09-26 16:37:55 +01:00
|
|
|
our own __FILE__ and __LINE__. We don't (yet) attempt to find
|
|
|
|
the nasal function name. */
|
|
|
|
int frame = 0;
|
|
|
|
const char* file = naStr_data(naGetSourceFile(c, 0));
|
|
|
|
if (simgear::strutils::ends_with( file, "/globals.nas")) {
|
|
|
|
/* This generally means have been called by globals.nas's
|
|
|
|
printf function; go one step up the stack so we give the
|
|
|
|
file:line of the caller of printf, which is generally more
|
|
|
|
useful. */
|
|
|
|
frame += 1;
|
|
|
|
file = naStr_data(naGetSourceFile(c, frame));
|
|
|
|
}
|
|
|
|
int line = naGetLine(c, frame);
|
|
|
|
const char* function = "";
|
|
|
|
if (sglog().would_log(SG_NASAL, SG_MANDATORY_INFO, file, line, function)) {
|
|
|
|
sglog().logCopyingFilename(SG_NASAL, SG_MANDATORY_INFO, file, line, function, buf);
|
2019-09-17 21:06:39 +01:00
|
|
|
}
|
2020-09-06 14:57:58 +01:00
|
|
|
} else {
|
|
|
|
SG_LOG(SG_NASAL, SG_MANDATORY_INFO, buf);
|
2019-09-17 21:06:39 +01:00
|
|
|
}
|
2007-01-28 12:16:37 +00:00
|
|
|
return naNum(buf.length());
|
2003-11-25 21:08:36 +00:00
|
|
|
}
|
|
|
|
|
2013-02-07 16:44:24 +00:00
|
|
|
// logprint() extension function. Same as above, all arguments after the
|
|
|
|
// first argument are concatenated. Argument 0 is the log-level, matching
|
|
|
|
// sgDebugPriority.
|
|
|
|
static naRef f_logprint(naContext c, naRef me, int argc, naRef* args)
|
|
|
|
{
|
|
|
|
if (argc < 1)
|
|
|
|
naRuntimeError(c, "no prioirty argument to logprint()");
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2013-02-07 16:44:24 +00:00
|
|
|
naRef priority = args[0];
|
|
|
|
string buf;
|
|
|
|
int n = argc;
|
|
|
|
for(int i=1; i<n; i++) {
|
|
|
|
naRef s = naStringValue(c, args[i]);
|
|
|
|
if(naIsNil(s)) continue;
|
|
|
|
buf += naStr_data(s);
|
|
|
|
}
|
2020-09-26 16:37:55 +01:00
|
|
|
// use the nasal source file and line for the message location, since
|
|
|
|
// that's more useful than the location here!
|
2020-07-17 12:03:28 +01:00
|
|
|
sglog().logCopyingFilename(SG_NASAL, (sgDebugPriority)(int) priority.num,
|
2013-02-09 15:33:05 +00:00
|
|
|
naStr_data(naGetSourceFile(c, 0)),
|
2020-09-26 16:37:55 +01:00
|
|
|
naGetLine(c, 0), "" /*function*/, buf);
|
2013-02-07 16:44:24 +00:00
|
|
|
return naNum(buf.length());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2003-11-25 21:08:36 +00:00
|
|
|
// fgcommand() extension function. Executes a named command via the
|
|
|
|
// FlightGear command manager. Takes a single property node name as
|
|
|
|
// an argument.
|
2005-04-18 19:49:13 +00:00
|
|
|
static naRef f_fgcommand(naContext c, naRef me, int argc, naRef* args)
|
2003-11-25 21:08:36 +00:00
|
|
|
{
|
2007-06-07 16:17:48 +00:00
|
|
|
naRef cmd = argc > 0 ? args[0] : naNil();
|
|
|
|
naRef props = argc > 1 ? args[1] : naNil();
|
|
|
|
if(!naIsString(cmd) || (!naIsNil(props) && !naIsGhost(props)))
|
2005-04-18 19:49:13 +00:00
|
|
|
naRuntimeError(c, "bad arguments to fgcommand()");
|
2014-11-23 14:53:54 +01:00
|
|
|
SGPropertyNode_ptr node;
|
2007-06-07 16:17:48 +00:00
|
|
|
if(!naIsNil(props))
|
2014-11-23 14:53:54 +01:00
|
|
|
node = static_cast<SGPropertyNode*>(naGhost_ptr(props));
|
|
|
|
else
|
|
|
|
node = new SGPropertyNode;
|
|
|
|
|
2017-07-05 01:23:24 +02:00
|
|
|
return naNum(globals->get_commands()->execute(naStr_data(cmd), node, nullptr));
|
2003-11-25 21:08:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// settimer(func, dt, simtime) extension function. Falls through to
|
|
|
|
// FGNasalSys::setTimer(). See there for docs.
|
2005-04-18 19:49:13 +00:00
|
|
|
static naRef f_settimer(naContext c, naRef me, int argc, naRef* args)
|
2003-11-25 21:08:36 +00:00
|
|
|
{
|
2007-05-12 18:15:45 +00:00
|
|
|
nasalSys->setTimer(c, argc, args);
|
2003-11-25 21:08:36 +00:00
|
|
|
return naNil();
|
|
|
|
}
|
|
|
|
|
2013-03-16 12:44:27 +00:00
|
|
|
static naRef f_makeTimer(naContext c, naRef me, int argc, naRef* args)
|
|
|
|
{
|
|
|
|
if (!naIsNum(args[0])) {
|
|
|
|
naRuntimeError(c, "bad interval argument to maketimer");
|
|
|
|
}
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2013-03-16 12:44:27 +00:00
|
|
|
naRef func, self = naNil();
|
|
|
|
if (naIsFunc(args[1])) {
|
|
|
|
func = args[1];
|
|
|
|
} else if ((argc == 3) && naIsFunc(args[2])) {
|
|
|
|
self = args[1];
|
|
|
|
func = args[2];
|
2019-12-24 22:41:42 +00:00
|
|
|
} else {
|
|
|
|
naRuntimeError(c, "bad function/self arguments to maketimer");
|
2013-03-16 12:44:27 +00:00
|
|
|
}
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2019-02-10 17:24:55 +01:00
|
|
|
TimerObj* timerObj = new TimerObj(c, nasalSys, func, self, args[0].num);
|
2014-06-03 02:23:17 +02:00
|
|
|
return nasal::to_nasal(c, timerObj);
|
2013-03-16 12:44:27 +00:00
|
|
|
}
|
|
|
|
|
2020-06-01 11:16:37 +01:00
|
|
|
static naRef f_makeSingleShot(naContext c, naRef me, int argc, naRef* args)
|
|
|
|
{
|
|
|
|
if (!naIsNum(args[0])) {
|
|
|
|
naRuntimeError(c, "bad interval argument to makesingleshot");
|
|
|
|
}
|
|
|
|
|
|
|
|
naRef func, self = naNil();
|
|
|
|
if (naIsFunc(args[1])) {
|
|
|
|
func = args[1];
|
|
|
|
} else if ((argc == 3) && naIsFunc(args[2])) {
|
|
|
|
self = args[1];
|
|
|
|
func = args[2];
|
|
|
|
} else {
|
|
|
|
naRuntimeError(c, "bad function/self arguments to makesingleshot");
|
|
|
|
}
|
|
|
|
|
|
|
|
auto timerObj = new TimerObj(c, nasalSys, func, self, args[0].num);
|
|
|
|
timerObj->setSingleShot(true);
|
|
|
|
timerObj->start();
|
|
|
|
return nasal::to_nasal(c, timerObj);
|
|
|
|
}
|
|
|
|
|
2019-05-02 07:30:32 +02:00
|
|
|
static naRef f_maketimeStamp(naContext c, naRef me, int argc, naRef* args)
|
|
|
|
{
|
2020-03-10 17:02:08 +00:00
|
|
|
TimeStampObj* timeStampObj = new TimeStampObj(c);
|
2019-05-02 07:30:32 +02:00
|
|
|
return nasal::to_nasal(c, timeStampObj);
|
|
|
|
}
|
|
|
|
|
2006-02-28 14:55:37 +00:00
|
|
|
// setlistener(func, property, bool) extension function. Falls through to
|
2005-12-16 19:11:03 +00:00
|
|
|
// FGNasalSys::setListener(). See there for docs.
|
|
|
|
static naRef f_setlistener(naContext c, naRef me, int argc, naRef* args)
|
|
|
|
{
|
2007-05-01 17:03:50 +00:00
|
|
|
return nasalSys->setListener(c, argc, args);
|
2006-02-28 14:55:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// removelistener(int) extension function. Falls through to
|
|
|
|
// FGNasalSys::removeListener(). See there for docs.
|
|
|
|
static naRef f_removelistener(naContext c, naRef me, int argc, naRef* args)
|
|
|
|
{
|
2007-05-01 17:03:50 +00:00
|
|
|
return nasalSys->removeListener(c, argc, args);
|
2005-12-16 19:11:03 +00:00
|
|
|
}
|
|
|
|
|
2003-12-01 14:35:49 +00:00
|
|
|
// Returns a ghost handle to the argument to the currently executing
|
|
|
|
// command
|
2005-04-18 19:49:13 +00:00
|
|
|
static naRef f_cmdarg(naContext c, naRef me, int argc, naRef* args)
|
2003-12-01 14:35:49 +00:00
|
|
|
{
|
2007-05-01 17:03:50 +00:00
|
|
|
return nasalSys->cmdArgGhost();
|
2003-12-01 14:35:49 +00:00
|
|
|
}
|
|
|
|
|
2003-12-05 01:54:39 +00:00
|
|
|
// Sets up a property interpolation. The first argument is either a
|
2014-11-23 14:53:54 +01:00
|
|
|
// ghost (SGPropertyNode*) or a string (global property path) to
|
2003-12-05 01:54:39 +00:00
|
|
|
// interpolate. The second argument is a vector of pairs of
|
|
|
|
// value/delta numbers.
|
2005-04-18 19:49:13 +00:00
|
|
|
static naRef f_interpolate(naContext c, naRef me, int argc, naRef* args)
|
2003-12-05 01:54:39 +00:00
|
|
|
{
|
2013-03-16 16:43:55 +01:00
|
|
|
SGPropertyNode* node;
|
|
|
|
naRef prop = argc > 0 ? args[0] : naNil();
|
|
|
|
if(naIsString(prop)) node = fgGetNode(naStr_data(prop), true);
|
2014-11-23 14:53:54 +01:00
|
|
|
else if(naIsGhost(prop)) node = static_cast<SGPropertyNode*>(naGhost_ptr(prop));
|
2013-03-16 16:43:55 +01:00
|
|
|
else return naNil();
|
2004-11-15 18:15:33 +00:00
|
|
|
|
2013-03-16 16:43:55 +01:00
|
|
|
naRef curve = argc > 1 ? args[1] : naNil();
|
|
|
|
if(!naIsVector(curve)) return naNil();
|
|
|
|
int nPoints = naVec_size(curve) / 2;
|
|
|
|
|
|
|
|
simgear::PropertyList value_nodes;
|
|
|
|
value_nodes.reserve(nPoints);
|
|
|
|
double_list deltas;
|
|
|
|
deltas.reserve(nPoints);
|
|
|
|
|
|
|
|
for( int i = 0; i < nPoints; ++i )
|
|
|
|
{
|
|
|
|
SGPropertyNode* val = new SGPropertyNode;
|
|
|
|
val->setDoubleValue(naNumValue(naVec_get(curve, 2*i)).num);
|
|
|
|
value_nodes.push_back(val);
|
|
|
|
deltas.push_back(naNumValue(naVec_get(curve, 2*i+1)).num);
|
|
|
|
}
|
|
|
|
|
2013-03-17 23:48:42 +01:00
|
|
|
node->interpolate("numeric", value_nodes, deltas, "linear");
|
2013-03-16 16:43:55 +01:00
|
|
|
return naNil();
|
2003-12-05 01:54:39 +00:00
|
|
|
}
|
|
|
|
|
2005-04-18 19:49:13 +00:00
|
|
|
// This is a better RNG than the one in the default Nasal distribution
|
|
|
|
// (which is based on the C library rand() implementation). It will
|
|
|
|
// override.
|
|
|
|
static naRef f_rand(naContext c, naRef me, int argc, naRef* args)
|
2003-12-05 01:54:39 +00:00
|
|
|
{
|
|
|
|
return naNum(sg_random());
|
|
|
|
}
|
|
|
|
|
2006-01-09 15:29:24 +00:00
|
|
|
static naRef f_srand(naContext c, naRef me, int argc, naRef* args)
|
|
|
|
{
|
|
|
|
sg_srandom_time();
|
|
|
|
return naNum(0);
|
|
|
|
}
|
|
|
|
|
2008-06-19 17:18:42 +00:00
|
|
|
static naRef f_abort(naContext c, naRef me, int argc, naRef* args)
|
|
|
|
{
|
|
|
|
abort();
|
|
|
|
return naNil();
|
|
|
|
}
|
|
|
|
|
2006-01-09 03:48:14 +00:00
|
|
|
// Return an array listing of all files in a directory
|
|
|
|
static naRef f_directory(naContext c, naRef me, int argc, naRef* args)
|
|
|
|
{
|
|
|
|
if(argc != 1 || !naIsString(args[0]))
|
|
|
|
naRuntimeError(c, "bad arguments to directory()");
|
2016-02-06 21:26:05 +00:00
|
|
|
|
2022-08-22 13:28:51 +02:00
|
|
|
SGPath dirname = SGPath::fromUtf8(naStr_data(args[0])).validate(false);
|
2016-07-03 23:59:40 +01:00
|
|
|
if(dirname.isNull()) {
|
2016-02-06 21:26:05 +00:00
|
|
|
SG_LOG(SG_NASAL, SG_ALERT, "directory(): listing '" <<
|
|
|
|
naStr_data(args[0]) << "' denied (unauthorized directory - authorization"
|
|
|
|
" no longer follows symlinks; to authorize reading additional "
|
2016-10-09 15:21:09 +01:00
|
|
|
"directories, pass them to --allow-nasal-read)");
|
2016-02-09 23:17:41 +00:00
|
|
|
// to avoid breaking dialogs, pretend it doesn't exist rather than erroring out
|
2016-02-06 21:26:05 +00:00
|
|
|
return naNil();
|
|
|
|
}
|
|
|
|
|
2016-07-03 23:59:40 +01:00
|
|
|
simgear::Dir d(dirname);
|
2010-10-24 07:09:05 +01:00
|
|
|
if(!d.exists()) return naNil();
|
2006-01-09 03:48:14 +00:00
|
|
|
naRef result = naNewVector(c);
|
2010-10-24 07:09:05 +01:00
|
|
|
|
|
|
|
simgear::PathList paths = d.children(simgear::Dir::TYPE_FILE | simgear::Dir::TYPE_DIR);
|
|
|
|
for (unsigned int i=0; i<paths.size(); ++i) {
|
|
|
|
std::string p = paths[i].file();
|
|
|
|
naVec_append(result, naStr_fromdata(naNewString(c), p.c_str(), p.size()));
|
|
|
|
}
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2006-01-09 03:48:14 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2010-08-15 11:02:27 +01:00
|
|
|
/**
|
|
|
|
* Given a data path, resolve it in FG_ROOT or an FG_AIRCRFT directory
|
|
|
|
*/
|
|
|
|
static naRef f_resolveDataPath(naContext c, naRef me, int argc, naRef* args)
|
|
|
|
{
|
|
|
|
if(argc != 1 || !naIsString(args[0]))
|
|
|
|
naRuntimeError(c, "bad arguments to resolveDataPath()");
|
|
|
|
|
|
|
|
SGPath p = globals->resolve_maybe_aircraft_path(naStr_data(args[0]));
|
2016-06-23 14:26:34 +01:00
|
|
|
std::string pdata = p.utf8Str();
|
|
|
|
return naStr_fromdata(naNewString(c), const_cast<char*>(pdata.c_str()), pdata.length());
|
2010-08-15 11:02:27 +01:00
|
|
|
}
|
|
|
|
|
2013-06-27 09:37:53 +01:00
|
|
|
static naRef f_findDataDir(naContext c, naRef me, int argc, naRef* args)
|
|
|
|
{
|
|
|
|
if(argc != 1 || !naIsString(args[0]))
|
|
|
|
naRuntimeError(c, "bad arguments to findDataDir()");
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2020-07-03 12:34:37 +01:00
|
|
|
SGPath p = globals->findDataPath(naStr_data(args[0]));
|
2016-06-23 14:26:34 +01:00
|
|
|
std::string pdata = p.utf8Str();
|
|
|
|
return naStr_fromdata(naNewString(c), const_cast<char*>(pdata.c_str()), pdata.length());
|
2013-06-27 09:37:53 +01:00
|
|
|
}
|
|
|
|
|
2013-02-09 15:33:05 +00:00
|
|
|
class NasalCommand : public SGCommandMgr::Command
|
|
|
|
{
|
|
|
|
public:
|
2013-10-06 17:36:19 +01:00
|
|
|
NasalCommand(FGNasalSys* sys, naRef f, const std::string& name) :
|
2013-02-09 15:33:05 +00:00
|
|
|
_sys(sys),
|
2013-10-06 17:36:19 +01:00
|
|
|
_func(f),
|
|
|
|
_name(name)
|
2013-02-09 15:33:05 +00:00
|
|
|
{
|
2013-10-06 17:36:19 +01:00
|
|
|
globals->get_commands()->addCommandObject(_name, this);
|
2013-02-09 15:33:05 +00:00
|
|
|
_gcRoot = sys->gcSave(f);
|
|
|
|
}
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2013-02-09 15:33:05 +00:00
|
|
|
virtual ~NasalCommand()
|
|
|
|
{
|
|
|
|
_sys->gcRelease(_gcRoot);
|
|
|
|
}
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2022-11-06 11:44:09 +00:00
|
|
|
bool operator()(const SGPropertyNode* aNode, SGPropertyNode* root) override
|
2013-02-09 15:33:05 +00:00
|
|
|
{
|
|
|
|
_sys->setCmdArg(const_cast<SGPropertyNode*>(aNode));
|
|
|
|
naRef args[1];
|
2013-02-09 16:05:54 +00:00
|
|
|
args[0] = _sys->wrappedPropsNode(const_cast<SGPropertyNode*>(aNode));
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2013-02-09 15:33:05 +00:00
|
|
|
_sys->callMethod(_func, naNil(), 1, args, naNil() /* locals */);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2013-02-09 15:33:05 +00:00
|
|
|
private:
|
|
|
|
FGNasalSys* _sys;
|
|
|
|
naRef _func;
|
|
|
|
int _gcRoot;
|
2013-10-06 17:36:19 +01:00
|
|
|
std::string _name;
|
2013-02-09 15:33:05 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static naRef f_addCommand(naContext c, naRef me, int argc, naRef* args)
|
|
|
|
{
|
|
|
|
if(argc != 2 || !naIsString(args[0]) || !naIsFunc(args[1]))
|
|
|
|
naRuntimeError(c, "bad arguments to addcommand()");
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2020-08-26 17:20:58 +01:00
|
|
|
const string commandName(naStr_data(args[0]));
|
|
|
|
bool ok = nasalSys->addCommand(args[1], commandName);
|
|
|
|
if (!ok) {
|
|
|
|
naRuntimeError(c, "Failed to add command:%s : likely a duplicate name ", commandName.c_str());
|
|
|
|
}
|
|
|
|
|
2013-02-09 15:33:05 +00:00
|
|
|
return naNil();
|
|
|
|
}
|
|
|
|
|
|
|
|
static naRef f_removeCommand(naContext c, naRef me, int argc, naRef* args)
|
|
|
|
{
|
2013-10-06 17:36:19 +01:00
|
|
|
if ((argc < 1) || !naIsString(args[0]))
|
|
|
|
naRuntimeError(c, "bad argument to removecommand()");
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2020-08-25 19:23:43 +01:00
|
|
|
const string commandName(naStr_data(args[0]));
|
|
|
|
bool ok = nasalSys->removeCommand(commandName);
|
|
|
|
if (!ok) {
|
2020-09-04 10:56:18 +01:00
|
|
|
return naNum(0);
|
2020-08-25 19:23:43 +01:00
|
|
|
}
|
2020-09-04 10:56:18 +01:00
|
|
|
return naNum(1);
|
2013-02-09 15:33:05 +00:00
|
|
|
}
|
|
|
|
|
2015-03-13 18:02:46 +00:00
|
|
|
static naRef f_open(naContext c, naRef me, int argc, naRef* args)
|
|
|
|
{
|
|
|
|
FILE* f;
|
|
|
|
naRef file = argc > 0 ? naStringValue(c, args[0]) : naNil();
|
|
|
|
naRef mode = argc > 1 ? naStringValue(c, args[1]) : naNil();
|
|
|
|
if(!naStr_data(file)) naRuntimeError(c, "bad argument to open()");
|
|
|
|
const char* modestr = naStr_data(mode) ? naStr_data(mode) : "rb";
|
2022-08-22 13:28:51 +02:00
|
|
|
const SGPath filename = SGPath::fromUtf8(naStr_data(file)).validate(
|
2015-03-13 18:02:46 +00:00
|
|
|
strcmp(modestr, "rb") && strcmp(modestr, "r"));
|
2016-07-03 23:59:40 +01:00
|
|
|
if(filename.isNull()) {
|
2015-08-14 21:37:28 +01:00
|
|
|
SG_LOG(SG_NASAL, SG_ALERT, "open(): reading/writing '" <<
|
|
|
|
naStr_data(file) << "' denied (unauthorized directory - authorization"
|
|
|
|
" no longer follows symlinks; to authorize reading additional "
|
2016-10-09 15:21:09 +01:00
|
|
|
"directories, pass them to --allow-nasal-read)");
|
2015-08-14 21:37:28 +01:00
|
|
|
naRuntimeError(c, "open(): access denied (unauthorized directory)");
|
2015-03-13 18:02:46 +00:00
|
|
|
return naNil();
|
|
|
|
}
|
2016-07-03 23:59:40 +01:00
|
|
|
|
2016-07-15 16:35:17 +01:00
|
|
|
#if defined(SG_WINDOWS)
|
|
|
|
std::wstring fp = filename.wstr();
|
|
|
|
std::wstring wmodestr = simgear::strutils::convertUtf8ToWString(modestr);
|
|
|
|
f = _wfopen(fp.c_str(), wmodestr.c_str());
|
|
|
|
#else
|
2020-03-13 09:53:49 +00:00
|
|
|
std::string fp = filename.utf8Str();
|
2016-07-15 16:35:17 +01:00
|
|
|
f = fopen(fp.c_str(), modestr);
|
|
|
|
#endif
|
2015-03-13 18:02:46 +00:00
|
|
|
if(!f) naRuntimeError(c, strerror(errno));
|
|
|
|
return naIOGhost(c, f);
|
|
|
|
}
|
|
|
|
|
2020-03-14 21:01:36 +00:00
|
|
|
static naRef ftype(naContext ctx, const SGPath& f)
|
|
|
|
{
|
|
|
|
const char* t = "unk";
|
|
|
|
if (f.isFile()) t = "reg";
|
|
|
|
else if(f.isDir()) t = "dir";
|
|
|
|
return naStr_fromdata(naNewString(ctx), t, strlen(t));
|
|
|
|
}
|
|
|
|
|
|
|
|
// io.stat with UTF-8 path support, replaces the default one in
|
2020-10-08 09:16:00 +01:00
|
|
|
// Nasal iolib.c which does not support UTF-8 paths
|
2020-03-14 21:01:36 +00:00
|
|
|
static naRef f_custom_stat(naContext ctx, naRef me, int argc, naRef* args)
|
|
|
|
{
|
|
|
|
naRef pathArg = argc > 0 ? naStringValue(ctx, args[0]) : naNil();
|
|
|
|
if (!naIsString(pathArg))
|
|
|
|
naRuntimeError(ctx, "bad argument to stat()");
|
2020-05-25 19:45:40 +01:00
|
|
|
|
2020-03-14 21:01:36 +00:00
|
|
|
const auto path = SGPath::fromUtf8(naStr_data(pathArg));
|
|
|
|
if (!path.exists()) {
|
|
|
|
return naNil();
|
|
|
|
}
|
2020-05-25 19:45:40 +01:00
|
|
|
|
2022-08-22 13:28:51 +02:00
|
|
|
const SGPath filename = SGPath(path).validate(false);
|
2020-03-14 21:01:36 +00:00
|
|
|
if (filename.isNull()) {
|
|
|
|
SG_LOG(SG_NASAL, SG_ALERT, "stat(): reading '" <<
|
|
|
|
naStr_data(pathArg) << "' denied (unauthorized directory - authorization"
|
|
|
|
" no longer follows symlinks; to authorize reading additional "
|
|
|
|
"directories, pass them to --allow-nasal-read)");
|
|
|
|
naRuntimeError(ctx, "stat(): access denied (unauthorized directory)");
|
|
|
|
return naNil();
|
|
|
|
}
|
2020-05-25 19:45:40 +01:00
|
|
|
|
2020-03-14 21:01:36 +00:00
|
|
|
naRef result = naNewVector(ctx);
|
|
|
|
naVec_setsize(ctx, result, 12);
|
2020-05-25 19:45:40 +01:00
|
|
|
|
2020-03-14 21:01:36 +00:00
|
|
|
// every use in fgdata/Nasal only uses stat() to test existence of
|
|
|
|
// files, not to check any of this information.
|
|
|
|
int n = 0;
|
|
|
|
naVec_set(result, n++, naNum(0)); // device
|
|
|
|
naVec_set(result, n++, naNum(0)); // inode
|
|
|
|
naVec_set(result, n++, naNum(0)); // mode
|
|
|
|
naVec_set(result, n++, naNum(0)); // nlink
|
|
|
|
naVec_set(result, n++, naNum(0)); // uid
|
|
|
|
naVec_set(result, n++, naNum(0)); // guid
|
|
|
|
naVec_set(result, n++, naNum(0)); // rdev
|
|
|
|
naVec_set(result, n++, naNum(filename.sizeInBytes())); // size
|
|
|
|
naVec_set(result, n++, naNum(0)); // atime
|
|
|
|
naVec_set(result, n++, naNum(0)); // mtime
|
|
|
|
naVec_set(result, n++, naNum(0)); // ctime
|
|
|
|
naVec_set(result, n++, ftype(ctx, filename));
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
add parsexml() function, which is a wrapper around the built-in easyxml
parser. Advantages over xml.nas: (reviewed and OK'ed by Andy)
- faster (33% ... only. I had hoped for more.)
- more standards compliant
- should support UTF
- I don't have to support it. ;-)
Usage: parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
<path> is an absolute file path, the rest are optional callback functions.
Example:
parsexml("/tmp/foo.xml", nil, nil, func(d) { print("DATA FOUND: ", d) });
2007-06-29 15:34:38 +00:00
|
|
|
// Parse XML file.
|
|
|
|
// parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
|
|
|
|
//
|
2007-09-30 11:56:21 +00:00
|
|
|
// <path> ... absolute path to an XML file
|
add parsexml() function, which is a wrapper around the built-in easyxml
parser. Advantages over xml.nas: (reviewed and OK'ed by Andy)
- faster (33% ... only. I had hoped for more.)
- more standards compliant
- should support UTF
- I don't have to support it. ;-)
Usage: parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
<path> is an absolute file path, the rest are optional callback functions.
Example:
parsexml("/tmp/foo.xml", nil, nil, func(d) { print("DATA FOUND: ", d) });
2007-06-29 15:34:38 +00:00
|
|
|
// <start-tag> ... callback function with two args: tag name, attribute hash
|
|
|
|
// <end-tag> ... callback function with one arg: tag name
|
2007-07-01 11:07:53 +00:00
|
|
|
// <data> ... callback function with one arg: data
|
|
|
|
// <pi> ... callback function with two args: target, data
|
add parsexml() function, which is a wrapper around the built-in easyxml
parser. Advantages over xml.nas: (reviewed and OK'ed by Andy)
- faster (33% ... only. I had hoped for more.)
- more standards compliant
- should support UTF
- I don't have to support it. ;-)
Usage: parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
<path> is an absolute file path, the rest are optional callback functions.
Example:
parsexml("/tmp/foo.xml", nil, nil, func(d) { print("DATA FOUND: ", d) });
2007-06-29 15:34:38 +00:00
|
|
|
// (pi = "processing instruction")
|
|
|
|
// All four callback functions are optional and default to nil.
|
2008-07-22 20:26:17 +00:00
|
|
|
// The function returns nil on error, or the validated file name otherwise.
|
add parsexml() function, which is a wrapper around the built-in easyxml
parser. Advantages over xml.nas: (reviewed and OK'ed by Andy)
- faster (33% ... only. I had hoped for more.)
- more standards compliant
- should support UTF
- I don't have to support it. ;-)
Usage: parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
<path> is an absolute file path, the rest are optional callback functions.
Example:
parsexml("/tmp/foo.xml", nil, nil, func(d) { print("DATA FOUND: ", d) });
2007-06-29 15:34:38 +00:00
|
|
|
static naRef f_parsexml(naContext c, naRef me, int argc, naRef* args)
|
|
|
|
{
|
|
|
|
if(argc < 1 || !naIsString(args[0]))
|
2007-07-01 11:07:53 +00:00
|
|
|
naRuntimeError(c, "parsexml(): path argument missing or not a string");
|
|
|
|
if(argc > 5) argc = 5;
|
|
|
|
for(int i=1; i<argc; i++)
|
|
|
|
if(!(naIsNil(args[i]) || naIsFunc(args[i])))
|
|
|
|
naRuntimeError(c, "parsexml(): callback argument not a function");
|
add parsexml() function, which is a wrapper around the built-in easyxml
parser. Advantages over xml.nas: (reviewed and OK'ed by Andy)
- faster (33% ... only. I had hoped for more.)
- more standards compliant
- should support UTF
- I don't have to support it. ;-)
Usage: parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
<path> is an absolute file path, the rest are optional callback functions.
Example:
parsexml("/tmp/foo.xml", nil, nil, func(d) { print("DATA FOUND: ", d) });
2007-06-29 15:34:38 +00:00
|
|
|
|
2022-08-22 13:28:51 +02:00
|
|
|
const SGPath file = SGPath::fromUtf8(naStr_data(args[0])).validate(false);
|
2016-07-03 23:59:40 +01:00
|
|
|
if(file.isNull()) {
|
2015-08-14 21:37:28 +01:00
|
|
|
SG_LOG(SG_NASAL, SG_ALERT, "parsexml(): reading '" <<
|
|
|
|
naStr_data(args[0]) << "' denied (unauthorized directory - authorization"
|
|
|
|
" no longer follows symlinks; to authorize reading additional "
|
2016-10-09 15:21:09 +01:00
|
|
|
"directories, pass them to --allow-nasal-read)");
|
2015-08-14 21:37:28 +01:00
|
|
|
naRuntimeError(c, "parsexml(): access denied (unauthorized directory)");
|
2008-07-22 20:26:17 +00:00
|
|
|
return naNil();
|
|
|
|
}
|
2016-07-15 16:35:17 +01:00
|
|
|
|
add parsexml() function, which is a wrapper around the built-in easyxml
parser. Advantages over xml.nas: (reviewed and OK'ed by Andy)
- faster (33% ... only. I had hoped for more.)
- more standards compliant
- should support UTF
- I don't have to support it. ;-)
Usage: parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
<path> is an absolute file path, the rest are optional callback functions.
Example:
parsexml("/tmp/foo.xml", nil, nil, func(d) { print("DATA FOUND: ", d) });
2007-06-29 15:34:38 +00:00
|
|
|
NasalXMLVisitor visitor(c, argc, args);
|
|
|
|
try {
|
2016-07-15 16:35:17 +01:00
|
|
|
readXML(file, visitor);
|
add parsexml() function, which is a wrapper around the built-in easyxml
parser. Advantages over xml.nas: (reviewed and OK'ed by Andy)
- faster (33% ... only. I had hoped for more.)
- more standards compliant
- should support UTF
- I don't have to support it. ;-)
Usage: parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
<path> is an absolute file path, the rest are optional callback functions.
Example:
parsexml("/tmp/foo.xml", nil, nil, func(d) { print("DATA FOUND: ", d) });
2007-06-29 15:34:38 +00:00
|
|
|
} catch (const sg_exception& e) {
|
2016-07-15 16:35:17 +01:00
|
|
|
std::string fp = file.utf8Str();
|
|
|
|
naRuntimeError(c, "parsexml(): file '%s' %s", fp.c_str(), e.getFormattedMessage().c_str());
|
add parsexml() function, which is a wrapper around the built-in easyxml
parser. Advantages over xml.nas: (reviewed and OK'ed by Andy)
- faster (33% ... only. I had hoped for more.)
- more standards compliant
- should support UTF
- I don't have to support it. ;-)
Usage: parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
<path> is an absolute file path, the rest are optional callback functions.
Example:
parsexml("/tmp/foo.xml", nil, nil, func(d) { print("DATA FOUND: ", d) });
2007-06-29 15:34:38 +00:00
|
|
|
return naNil();
|
|
|
|
}
|
2016-07-03 23:59:40 +01:00
|
|
|
|
|
|
|
std::string fs = file.utf8Str();
|
|
|
|
return naStr_fromdata(naNewString(c), fs.c_str(), fs.length());
|
add parsexml() function, which is a wrapper around the built-in easyxml
parser. Advantages over xml.nas: (reviewed and OK'ed by Andy)
- faster (33% ... only. I had hoped for more.)
- more standards compliant
- should support UTF
- I don't have to support it. ;-)
Usage: parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
<path> is an absolute file path, the rest are optional callback functions.
Example:
parsexml("/tmp/foo.xml", nil, nil, func(d) { print("DATA FOUND: ", d) });
2007-06-29 15:34:38 +00:00
|
|
|
}
|
|
|
|
|
2014-06-13 19:16:26 +02:00
|
|
|
/**
|
|
|
|
* Parse very simple and small subset of markdown
|
|
|
|
*
|
|
|
|
* parse_markdown(src)
|
|
|
|
*/
|
|
|
|
static naRef f_parse_markdown(naContext c, naRef me, int argc, naRef* args)
|
|
|
|
{
|
2014-07-21 00:26:54 +02:00
|
|
|
nasal::CallContext ctx(c, me, argc, args);
|
2014-06-13 19:16:26 +02:00
|
|
|
return ctx.to_nasal(
|
|
|
|
simgear::SimpleMarkdown::parse(ctx.requireArg<std::string>(0))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2014-06-17 22:33:53 +02:00
|
|
|
/**
|
|
|
|
* Create md5 hash from given string
|
|
|
|
*
|
|
|
|
* md5(str)
|
|
|
|
*/
|
|
|
|
static naRef f_md5(naContext c, naRef me, int argc, naRef* args)
|
|
|
|
{
|
2014-06-18 15:59:41 +02:00
|
|
|
if( argc != 1 || !naIsString(args[0]) )
|
|
|
|
naRuntimeError(c, "md5(): wrong type or number of arguments");
|
2014-06-17 22:33:53 +02:00
|
|
|
|
2014-06-18 15:59:41 +02:00
|
|
|
return nasal::to_nasal(
|
|
|
|
c,
|
|
|
|
simgear::strutils::md5(naStr_data(args[0]), naStr_len(args[0]))
|
|
|
|
);
|
2014-06-17 22:33:53 +02:00
|
|
|
}
|
|
|
|
|
2007-05-14 16:24:41 +00:00
|
|
|
// Return UNIX epoch time in seconds.
|
|
|
|
static naRef f_systime(naContext c, naRef me, int argc, naRef* args)
|
|
|
|
{
|
2010-01-23 22:26:30 +00:00
|
|
|
#ifdef _WIN32
|
2007-05-14 16:24:41 +00:00
|
|
|
FILETIME ft;
|
|
|
|
GetSystemTimeAsFileTime(&ft);
|
|
|
|
double t = (4294967296.0 * ft.dwHighDateTime + ft.dwLowDateTime);
|
|
|
|
// Converts from 100ns units in 1601 epoch to unix epoch in sec
|
|
|
|
return naNum((t * 1e-7) - 11644473600.0);
|
|
|
|
#else
|
|
|
|
struct timeval td;
|
2011-06-02 23:53:42 +02:00
|
|
|
gettimeofday(&td, 0);
|
|
|
|
return naNum(td.tv_sec + 1e-6 * td.tv_usec);
|
2007-05-14 16:24:41 +00:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2003-11-25 21:08:36 +00:00
|
|
|
// Table of extension functions. Terminate with zeros.
|
2020-06-01 11:16:37 +01:00
|
|
|
static struct { const char* name; naCFunction func;
|
|
|
|
} funcs[] = {
|
|
|
|
{"getprop", f_getprop},
|
|
|
|
{"setprop", f_setprop},
|
|
|
|
{"print", f_print},
|
|
|
|
{"logprint", f_logprint},
|
|
|
|
{"_fgcommand", f_fgcommand},
|
|
|
|
{"settimer", f_settimer},
|
|
|
|
{"maketimer", f_makeTimer},
|
|
|
|
{"makesingleshot", f_makeSingleShot},
|
|
|
|
{"_setlistener", f_setlistener},
|
|
|
|
{"removelistener", f_removelistener},
|
|
|
|
{"addcommand", f_addCommand},
|
|
|
|
{"removecommand", f_removeCommand},
|
|
|
|
{"_cmdarg", f_cmdarg},
|
|
|
|
{"_interpolate", f_interpolate},
|
|
|
|
{"rand", f_rand},
|
|
|
|
{"srand", f_srand},
|
|
|
|
{"abort", f_abort},
|
|
|
|
{"directory", f_directory},
|
|
|
|
{"resolvepath", f_resolveDataPath},
|
|
|
|
{"finddata", f_findDataDir},
|
|
|
|
{"parsexml", f_parsexml},
|
|
|
|
{"parse_markdown", f_parse_markdown},
|
|
|
|
{"md5", f_md5},
|
|
|
|
{"systime", f_systime},
|
|
|
|
{"maketimestamp", f_maketimeStamp},
|
|
|
|
{0, 0}};
|
2003-11-25 21:08:36 +00:00
|
|
|
|
2003-12-01 14:35:49 +00:00
|
|
|
naRef FGNasalSys::cmdArgGhost()
|
|
|
|
{
|
|
|
|
return propNodeGhost(_cmdArg);
|
|
|
|
}
|
|
|
|
|
2020-04-09 13:59:37 +01:00
|
|
|
void FGNasalSys::initLogLevelConstants()
|
|
|
|
{
|
|
|
|
hashset(_globals, "LOG_BULK", naNum(SG_BULK));
|
|
|
|
hashset(_globals, "LOG_WARN", naNum(SG_WARN));
|
|
|
|
hashset(_globals, "LOG_DEBUG", naNum(SG_DEBUG));
|
|
|
|
hashset(_globals, "LOG_INFO", naNum(SG_INFO));
|
|
|
|
hashset(_globals, "LOG_ALERT", naNum(SG_ALERT));
|
|
|
|
hashset(_globals, "DEV_WARN", naNum(SG_DEV_WARN));
|
|
|
|
hashset(_globals, "DEV_ALERT", naNum(SG_DEV_ALERT));
|
2020-09-06 14:57:58 +01:00
|
|
|
hashset(_globals, "MANDATORY_INFO", naNum(SG_MANDATORY_INFO));
|
2020-04-09 13:59:37 +01:00
|
|
|
}
|
|
|
|
|
2013-02-09 15:33:05 +00:00
|
|
|
void FGNasalSys::setCmdArg(SGPropertyNode* aNode)
|
|
|
|
{
|
|
|
|
_cmdArg = aNode;
|
|
|
|
}
|
|
|
|
|
2003-11-25 21:08:36 +00:00
|
|
|
void FGNasalSys::init()
|
|
|
|
{
|
2014-03-16 22:52:55 +00:00
|
|
|
if (_inited) {
|
|
|
|
SG_LOG(SG_GENERAL, SG_ALERT, "duplicate init of Nasal");
|
|
|
|
}
|
2003-12-01 14:35:49 +00:00
|
|
|
int i;
|
|
|
|
|
2003-11-25 21:08:36 +00:00
|
|
|
_context = naNewContext();
|
|
|
|
|
|
|
|
// Start with globals. Add it to itself as a recursive
|
|
|
|
// sub-reference under the name "globals". This gives client-code
|
|
|
|
// write access to the namespace if someone wants to do something
|
|
|
|
// fancy.
|
2007-03-29 18:50:28 +00:00
|
|
|
_globals = naInit_std(_context);
|
2003-11-25 21:08:36 +00:00
|
|
|
naSave(_context, _globals);
|
|
|
|
hashset(_globals, "globals", _globals);
|
|
|
|
|
2007-03-29 18:50:28 +00:00
|
|
|
hashset(_globals, "math", naInit_math(_context));
|
|
|
|
hashset(_globals, "bits", naInit_bits(_context));
|
|
|
|
hashset(_globals, "io", naInit_io(_context));
|
|
|
|
hashset(_globals, "thread", naInit_thread(_context));
|
|
|
|
hashset(_globals, "utf8", naInit_utf8(_context));
|
2006-03-15 18:10:29 +00:00
|
|
|
|
2020-04-09 13:59:37 +01:00
|
|
|
initLogLevelConstants();
|
2020-05-25 19:45:40 +01:00
|
|
|
|
2003-11-25 21:08:36 +00:00
|
|
|
// Add our custom extension functions:
|
2003-12-01 14:35:49 +00:00
|
|
|
for(i=0; funcs[i].name; i++)
|
2003-11-25 21:08:36 +00:00
|
|
|
hashset(_globals, funcs[i].name,
|
|
|
|
naNewFunc(_context, naNewCCode(_context, funcs[i].func)));
|
Improved infrastructure for add-ons: C++ classes, metadata file, Nasal interface
This commit adds C++ classes for add-on management, most notably
AddonManager, Addon and AddonVersion. The AddonManager is used to
register add-ons. It relies on an std::map<std::string, AddonRef> to
hold the metadata of each registered add-on (keys of the std::map are
add-on identifiers, and AddonRef is currently SGSharedPtr<Addon>).
Accessor methods are available for:
- retrieving the list of registered or loaded add-ons (terminology
explained in $FG_ROOT/Docs/README.add-ons);
- checking if a particular add-on has already been registered or
loaded;
- for each add-on, obtaining an Addon instance which can be queried
for its name, id, version, base path, the minimum and maximum
FlightGear versions it requires, its base node in the Property Tree,
its order in the load sequence, short and long description strings,
home page, etc.
The most important metadata is made accessible in the Property Tree
under /addons/by-id/<addon-id> and the property
/addons/by-id/<addon-id>/loaded can be checked or listened to, in
order to determine when a particular add-on is loaded. There is also a
Nasal interface to access add-on metadata in a convenient way.
In order to provide this metadata, each add-on must from now on have in
its base directory a file called 'addon-metadata.xml'.
All this is documented in much more detail in
$FG_ROOT/Docs/README.add-ons.
Mailing-list discussion:
https://sourceforge.net/p/flightgear/mailman/message/36146017/
2017-11-06 13:58:14 +01:00
|
|
|
nasal::Hash io_module = getGlobals().get<nasal::Hash>("io");
|
2015-03-13 18:02:46 +00:00
|
|
|
io_module.set("open", f_open);
|
2020-03-14 21:01:36 +00:00
|
|
|
io_module.set("stat", f_custom_stat);
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2003-12-01 14:35:49 +00:00
|
|
|
// And our SGPropertyNode wrapper
|
|
|
|
hashset(_globals, "props", genPropsModule());
|
|
|
|
|
2013-01-31 19:14:14 +01:00
|
|
|
// Add string methods
|
|
|
|
_string = naInit_string(_context);
|
|
|
|
naSave(_context, _string);
|
2013-10-15 00:58:04 +02:00
|
|
|
initNasalString(_globals, _string, _context);
|
2013-01-31 19:14:14 +01:00
|
|
|
|
2020-04-27 08:50:38 +01:00
|
|
|
#if defined (BUILDING_TESTSUITE)
|
|
|
|
initNasalUnitTestCppUnit(_globals, _context);
|
|
|
|
#else
|
|
|
|
initNasalUnitTestInSim(_globals, _context);
|
|
|
|
#endif
|
|
|
|
|
2020-04-23 10:11:28 +01:00
|
|
|
if (!global_nasalMinimalInit) {
|
|
|
|
initNasalPositioned(_globals, _context);
|
2020-05-22 16:36:44 +01:00
|
|
|
initNasalFlightPlan(_globals, _context);
|
2020-04-23 10:11:28 +01:00
|
|
|
initNasalPositioned_cppbind(_globals, _context);
|
|
|
|
initNasalAircraft(_globals, _context);
|
2022-05-17 14:33:35 +01:00
|
|
|
|
|
|
|
#if !defined (BUILDING_TESTSUITE)
|
|
|
|
// on Linux, clipboard init crashes in headless mode (DISPLAY no set)
|
|
|
|
// so don't do this for testing.
|
2020-04-23 10:11:28 +01:00
|
|
|
NasalClipboard::init(this);
|
2022-05-17 14:33:35 +01:00
|
|
|
#endif
|
2020-04-23 10:11:28 +01:00
|
|
|
initNasalCanvas(_globals, _context);
|
|
|
|
initNasalCondition(_globals, _context);
|
|
|
|
initNasalHTTP(_globals, _context);
|
|
|
|
initNasalSGPath(_globals, _context);
|
|
|
|
}
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2013-11-23 20:08:55 +00:00
|
|
|
NasalTimerObj::init("Timer")
|
|
|
|
.method("start", &TimerObj::start)
|
|
|
|
.method("stop", &TimerObj::stop)
|
|
|
|
.method("restart", &TimerObj::restart)
|
|
|
|
.member("singleShot", &TimerObj::isSingleShot, &TimerObj::setSingleShot)
|
2016-12-11 16:10:57 +00:00
|
|
|
.member("simulatedTime", &TimerObj::isSimTime, &f_timerObj_setSimTime)
|
2013-11-23 20:08:55 +00:00
|
|
|
.member("isRunning", &TimerObj::isRunning);
|
|
|
|
|
2019-05-02 07:30:32 +02:00
|
|
|
NasalTimeStampObj::init("TimeStamp")
|
|
|
|
.method("stamp", &TimeStampObj::stamp)
|
|
|
|
.method("elapsedMSec", &TimeStampObj::elapsedMSec)
|
2019-05-07 05:16:21 +02:00
|
|
|
.method("elapsedUSec", &TimeStampObj::elapsedUSec)
|
2019-05-02 07:30:32 +02:00
|
|
|
;
|
|
|
|
|
2020-04-23 10:11:28 +01:00
|
|
|
// everything after here, skip if we're doing minimal init, so
|
|
|
|
// we don'tload FG_DATA/Nasal or add-ons
|
|
|
|
if (global_nasalMinimalInit) {
|
|
|
|
_inited = true;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-12-15 23:53:46 +01:00
|
|
|
flightgear::addons::initAddonClassesForNasal(_globals, _context);
|
Improved infrastructure for add-ons: C++ classes, metadata file, Nasal interface
This commit adds C++ classes for add-on management, most notably
AddonManager, Addon and AddonVersion. The AddonManager is used to
register add-ons. It relies on an std::map<std::string, AddonRef> to
hold the metadata of each registered add-on (keys of the std::map are
add-on identifiers, and AddonRef is currently SGSharedPtr<Addon>).
Accessor methods are available for:
- retrieving the list of registered or loaded add-ons (terminology
explained in $FG_ROOT/Docs/README.add-ons);
- checking if a particular add-on has already been registered or
loaded;
- for each add-on, obtaining an Addon instance which can be queried
for its name, id, version, base path, the minimum and maximum
FlightGear versions it requires, its base node in the Property Tree,
its order in the load sequence, short and long description strings,
home page, etc.
The most important metadata is made accessible in the Property Tree
under /addons/by-id/<addon-id> and the property
/addons/by-id/<addon-id>/loaded can be checked or listened to, in
order to determine when a particular add-on is loaded. There is also a
Nasal interface to access add-on metadata in a convenient way.
In order to provide this metadata, each add-on must from now on have in
its base directory a file called 'addon-metadata.xml'.
All this is documented in much more detail in
$FG_ROOT/Docs/README.add-ons.
Mailing-list discussion:
https://sourceforge.net/p/flightgear/mailman/message/36146017/
2017-11-06 13:58:14 +01:00
|
|
|
|
2003-11-25 21:08:36 +00:00
|
|
|
// Now load the various source files in the Nasal directory
|
2010-10-24 07:09:05 +01:00
|
|
|
simgear::Dir nasalDir(SGPath(globals->get_fg_root(), "Nasal"));
|
2022-03-09 16:47:12 +00:00
|
|
|
|
|
|
|
// load core Nasal scripts. In GUI startup mode, we restrict to a limited
|
|
|
|
// set of modules deliberately
|
|
|
|
loadScriptDirectory(nasalDir,
|
|
|
|
globals->get_props()->getNode("/sim/nasal-load-priority"),
|
|
|
|
fgGetBool("/sim/gui/startup"));
|
2011-04-02 15:13:29 +02:00
|
|
|
|
|
|
|
// Add modules in Nasal subdirectories to property tree
|
|
|
|
simgear::PathList directories = nasalDir.children(simgear::Dir::TYPE_DIR+
|
|
|
|
simgear::Dir::NO_DOT_OR_DOTDOT, "");
|
|
|
|
for (unsigned int i=0; i<directories.size(); ++i) {
|
|
|
|
simgear::Dir dir(directories[i]);
|
|
|
|
simgear::PathList scripts = dir.children(simgear::Dir::TYPE_FILE, ".nas");
|
|
|
|
addModule(directories[i].file(), scripts);
|
2003-11-25 21:08:36 +00:00
|
|
|
}
|
2007-01-23 15:53:04 +00:00
|
|
|
|
|
|
|
// set signal and remove node to avoid restoring at reinit
|
|
|
|
const char *s = "nasal-dir-initialized";
|
|
|
|
SGPropertyNode *signal = fgGetNode("/sim/signals", true);
|
|
|
|
signal->setBoolValue(s, true);
|
2014-03-06 00:41:41 +01:00
|
|
|
signal->removeChildren(s);
|
2003-12-01 14:35:49 +00:00
|
|
|
|
|
|
|
// Pull scripts out of the property tree, too
|
|
|
|
loadPropertyScripts();
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2012-04-25 16:54:40 +01:00
|
|
|
// now Nasal modules are loaded, we can do some delayed work
|
|
|
|
postinitNasalPositioned(_globals, _context);
|
2012-12-28 14:48:19 +00:00
|
|
|
postinitNasalGUI(_globals, _context);
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2014-03-16 22:52:55 +00:00
|
|
|
_inited = true;
|
2003-12-01 14:35:49 +00:00
|
|
|
}
|
|
|
|
|
2013-10-06 17:36:19 +01:00
|
|
|
void FGNasalSys::shutdown()
|
|
|
|
{
|
2014-03-16 22:52:55 +00:00
|
|
|
if (!_inited) {
|
|
|
|
return;
|
|
|
|
}
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2013-10-06 17:36:19 +01:00
|
|
|
shutdownNasalPositioned();
|
2020-05-22 16:36:44 +01:00
|
|
|
shutdownNasalFlightPlan();
|
2020-04-27 08:50:38 +01:00
|
|
|
shutdownNasalUnitTestInSim();
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2018-09-01 18:53:36 +01:00
|
|
|
for (auto l : _listener)
|
|
|
|
delete l.second;
|
2013-10-06 17:36:19 +01:00
|
|
|
_listener.clear();
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2018-09-01 18:53:36 +01:00
|
|
|
for (auto c : _commands) {
|
|
|
|
globals->get_commands()->removeCommand(c.first);
|
2013-10-06 17:36:19 +01:00
|
|
|
}
|
|
|
|
_commands.clear();
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2018-09-01 18:53:36 +01:00
|
|
|
for(auto ml : _moduleListeners)
|
|
|
|
delete ml;
|
2013-12-03 21:38:24 +00:00
|
|
|
_moduleListeners.clear();
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2018-09-01 18:53:36 +01:00
|
|
|
for (auto t : _nasalTimers) {
|
|
|
|
delete t;
|
|
|
|
}
|
|
|
|
_nasalTimers.clear();
|
2020-02-19 20:32:22 +00:00
|
|
|
|
2013-10-06 17:36:19 +01:00
|
|
|
naClearSaved();
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2013-10-06 17:36:19 +01:00
|
|
|
_string = naNil(); // will be freed by _context
|
|
|
|
naFreeContext(_context);
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2013-10-06 17:36:19 +01:00
|
|
|
//setWatchedRef(_globals);
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2013-10-06 17:36:19 +01:00
|
|
|
// remove the recursive reference in globals
|
|
|
|
hashset(_globals, "globals", naNil());
|
2017-03-21 21:43:42 +01:00
|
|
|
_globals = naNil();
|
|
|
|
|
2013-10-06 17:36:19 +01:00
|
|
|
naGC();
|
2020-02-19 20:32:22 +00:00
|
|
|
|
2018-09-01 18:53:36 +01:00
|
|
|
// Destroy all queued ghosts : important to ensure persistent timers are
|
|
|
|
// destroyed now.
|
|
|
|
nasal::ghostProcessDestroyList();
|
2020-02-19 20:32:22 +00:00
|
|
|
|
2018-09-01 18:53:36 +01:00
|
|
|
if (!_persistentTimers.empty()) {
|
|
|
|
SG_LOG(SG_NASAL, SG_DEV_WARN, "Extant persistent timer count:" << _persistentTimers.size());
|
2020-02-19 20:32:22 +00:00
|
|
|
|
2019-09-18 14:46:40 +01:00
|
|
|
for (auto pt : _persistentTimers) {
|
|
|
|
SG_LOG(SG_NASAL, SG_DEV_WARN, "Extant:" << pt << " : " << pt->name());
|
|
|
|
}
|
2018-09-01 18:53:36 +01:00
|
|
|
}
|
2020-05-25 19:45:40 +01:00
|
|
|
|
2014-03-16 22:52:55 +00:00
|
|
|
_inited = false;
|
2013-10-06 17:36:19 +01:00
|
|
|
}
|
|
|
|
|
2013-02-09 16:05:54 +00:00
|
|
|
naRef FGNasalSys::wrappedPropsNode(SGPropertyNode* aProps)
|
|
|
|
{
|
2013-11-22 22:40:50 +00:00
|
|
|
if (naIsNil(_wrappedNodeFunc)) {
|
2013-03-21 01:16:01 +01:00
|
|
|
nasal::Hash props = getGlobals().get<nasal::Hash>("props");
|
2013-11-22 22:40:50 +00:00
|
|
|
_wrappedNodeFunc = props.get("wrapNode");
|
2013-02-09 16:05:54 +00:00
|
|
|
}
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2013-02-09 16:05:54 +00:00
|
|
|
naRef args[1];
|
|
|
|
args[0] = propNodeGhost(aProps);
|
2013-11-22 22:40:50 +00:00
|
|
|
naContext ctx = naNewContext();
|
2015-01-18 22:23:21 +00:00
|
|
|
naRef wrapped = naCallMethodCtx(ctx, _wrappedNodeFunc, naNil(), 1, args, naNil());
|
2013-11-22 22:40:50 +00:00
|
|
|
naFreeContext(ctx);
|
|
|
|
return wrapped;
|
2013-02-09 16:05:54 +00:00
|
|
|
}
|
|
|
|
|
2007-01-23 16:47:04 +00:00
|
|
|
void FGNasalSys::update(double)
|
|
|
|
{
|
2012-08-05 11:19:24 +02:00
|
|
|
if( NasalClipboard::getInstance() )
|
|
|
|
NasalClipboard::getInstance()->update();
|
2020-05-25 19:45:40 +01:00
|
|
|
|
2020-04-27 08:50:38 +01:00
|
|
|
std::for_each(_dead_listener.begin(), _dead_listener.end(),
|
|
|
|
[]( FGNasalListener* l) { delete l; });
|
|
|
|
_dead_listener.clear();
|
2020-05-25 19:45:40 +01:00
|
|
|
|
2012-02-06 22:19:33 +01:00
|
|
|
if (!_loadList.empty())
|
|
|
|
{
|
2013-03-23 12:53:17 +01:00
|
|
|
if( _delay_load )
|
|
|
|
_delay_load = false;
|
|
|
|
else
|
|
|
|
// process Nasal load hook (only one per update loop to avoid excessive lags)
|
|
|
|
_loadList.pop()->load();
|
2012-02-06 22:19:33 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
if (!_unloadList.empty())
|
|
|
|
{
|
|
|
|
// process pending Nasal unload hooks after _all_ load hooks were processed
|
|
|
|
// (only unload one per update loop to avoid excessive lags)
|
|
|
|
_unloadList.pop()->unload();
|
|
|
|
}
|
2014-06-15 16:36:35 +02:00
|
|
|
// Destroy all queued ghosts
|
|
|
|
nasal::ghostProcessDestroyList();
|
|
|
|
|
2007-10-24 18:07:02 +00:00
|
|
|
// The global context is a legacy thing. We use dynamically
|
|
|
|
// created contexts for naCall() now, so that we can call them
|
|
|
|
// recursively. But there are still spots that want to use it for
|
|
|
|
// naNew*() calls, which end up leaking memory because the context
|
|
|
|
// only clears out its temporary vector when it's *used*. So just
|
|
|
|
// junk it and fetch a new/reinitialized one every frame. This is
|
|
|
|
// clumsy: the right solution would use the dynamic context in all
|
|
|
|
// cases and eliminate _context entirely. But that's more work,
|
|
|
|
// and this works fine (yes, they say "New" and "Free", but
|
|
|
|
// they're very fast, just trust me). -Andy
|
|
|
|
naFreeContext(_context);
|
|
|
|
_context = naNewContext();
|
2007-01-23 16:47:04 +00:00
|
|
|
}
|
|
|
|
|
2011-05-29 18:44:15 +02:00
|
|
|
bool pathSortPredicate(const SGPath& p1, const SGPath& p2)
|
|
|
|
{
|
|
|
|
return p1.file() < p2.file();
|
|
|
|
}
|
|
|
|
|
2020-05-25 19:45:40 +01:00
|
|
|
// Loads all scripts in given directory, with an optional partial ordering of
|
|
|
|
// files defined in loadorder.
|
2022-03-09 16:47:12 +00:00
|
|
|
void FGNasalSys::loadScriptDirectory(simgear::Dir nasalDir, SGPropertyNode* loadorder,
|
|
|
|
bool excludeUnspecifiedInLoadOrder)
|
2011-04-02 15:13:29 +02:00
|
|
|
{
|
|
|
|
simgear::PathList scripts = nasalDir.children(simgear::Dir::TYPE_FILE, ".nas");
|
2020-05-25 19:45:40 +01:00
|
|
|
|
|
|
|
if (loadorder != nullptr && loadorder->hasChild("file")) {
|
|
|
|
// Load any scripts defined in the loadorder in order, removing them from
|
|
|
|
// the list so they don't get loaded twice.
|
|
|
|
simgear::PropertyList files = loadorder->getChildren("file");
|
|
|
|
|
|
|
|
auto loadAndErase = [ &scripts, &nasalDir, this ] (SGPropertyNode_ptr n) {
|
|
|
|
SGPath p = SGPath(nasalDir.path(), n->getStringValue());
|
|
|
|
auto script = std::find(scripts.begin(), scripts.end(), p);
|
|
|
|
if (script != scripts.end()) {
|
|
|
|
this->loadModule(p, p.file_base().c_str());
|
|
|
|
scripts.erase(script);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
std::for_each(files.begin(), files.end(), loadAndErase);
|
|
|
|
}
|
|
|
|
|
2022-03-09 16:47:12 +00:00
|
|
|
if (excludeUnspecifiedInLoadOrder) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-05-25 19:45:40 +01:00
|
|
|
// Load any remaining scripts.
|
2012-10-13 12:42:48 +02:00
|
|
|
// Note: simgear::Dir already reports file entries in a deterministic order,
|
|
|
|
// so a fixed loading sequence is guaranteed (same for every user)
|
2020-05-25 19:45:40 +01:00
|
|
|
std::for_each(scripts.begin(), scripts.end(), [this](SGPath p) { this->loadModule(p, p.file_base().c_str()); });
|
2011-04-02 15:13:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Create module with list of scripts
|
|
|
|
void FGNasalSys::addModule(string moduleName, simgear::PathList scripts)
|
|
|
|
{
|
2013-03-21 19:43:14 -07:00
|
|
|
if (! scripts.empty())
|
2011-04-02 15:13:29 +02:00
|
|
|
{
|
2019-09-18 14:46:40 +01:00
|
|
|
SGPropertyNode* nasal = globals->get_props()->getNode("nasal", true);
|
2011-04-02 15:13:29 +02:00
|
|
|
SGPropertyNode* module_node = nasal->getChild(moduleName,0,true);
|
|
|
|
for (unsigned int i=0; i<scripts.size(); ++i) {
|
|
|
|
SGPropertyNode* pFileNode = module_node->getChild("file",i,true);
|
2016-06-23 14:26:34 +01:00
|
|
|
pFileNode->setStringValue(scripts[i].utf8Str());
|
2011-04-02 15:13:29 +02:00
|
|
|
}
|
|
|
|
if (!module_node->hasChild("enabled",0))
|
|
|
|
{
|
|
|
|
SGPropertyNode* node = module_node->getChild("enabled",0,true);
|
2020-02-19 20:32:22 +00:00
|
|
|
node->setBoolValue(false);
|
|
|
|
node->setAttribute(SGPropertyNode::USERARCHIVE,false);
|
2021-06-05 12:24:58 +01:00
|
|
|
#ifndef BUILDING_TESTSUITE
|
|
|
|
SG_LOG(SG_NASAL, SG_ALERT, "Nasal module " << moduleName
|
|
|
|
<< " present in FGDATA/Nasal but not configured in defaults.xml. "
|
|
|
|
<< " Please add an entry to defaults.xml, and set " << node->getPath()
|
|
|
|
<< "=true to load the module on-demand at runtime when required."
|
|
|
|
);
|
|
|
|
#endif
|
2011-04-02 15:13:29 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2003-12-01 14:35:49 +00:00
|
|
|
// Loads the scripts found under /nasal in the global tree
|
|
|
|
void FGNasalSys::loadPropertyScripts()
|
|
|
|
{
|
|
|
|
SGPropertyNode* nasal = globals->get_props()->getNode("nasal");
|
|
|
|
if(!nasal) return;
|
|
|
|
|
2011-04-03 15:30:25 +02:00
|
|
|
for(int i=0; i<nasal->nChildren(); i++)
|
|
|
|
{
|
2003-12-01 14:35:49 +00:00
|
|
|
SGPropertyNode* n = nasal->getChild(i);
|
2011-04-03 15:30:25 +02:00
|
|
|
loadPropertyScripts(n);
|
|
|
|
}
|
|
|
|
}
|
2003-12-01 14:35:49 +00:00
|
|
|
|
2011-04-03 15:30:25 +02:00
|
|
|
// Loads the scripts found under /nasal in the global tree
|
|
|
|
void FGNasalSys::loadPropertyScripts(SGPropertyNode* n)
|
|
|
|
{
|
|
|
|
bool is_loaded = false;
|
|
|
|
|
2021-11-06 19:10:20 +01:00
|
|
|
std::string module = n->getNameString();
|
2011-04-03 15:30:25 +02:00
|
|
|
if(n->hasChild("module"))
|
|
|
|
module = n->getStringValue("module");
|
|
|
|
if (n->getBoolValue("enabled",true))
|
|
|
|
{
|
|
|
|
// allow multiple files to be specified within a single
|
|
|
|
// Nasal module tag
|
|
|
|
int j = 0;
|
|
|
|
SGPropertyNode *fn;
|
|
|
|
bool file_specified = false;
|
|
|
|
bool ok=true;
|
|
|
|
while((fn = n->getChild("file", j)) != NULL) {
|
|
|
|
file_specified = true;
|
2021-11-06 19:10:20 +01:00
|
|
|
std::string file = fn->getStringValue();
|
2011-04-03 15:30:25 +02:00
|
|
|
SGPath p(file);
|
|
|
|
if (!p.isAbsolute() || !p.exists())
|
2011-04-02 15:13:29 +02:00
|
|
|
{
|
2011-04-03 15:30:25 +02:00
|
|
|
p = globals->resolve_maybe_aircraft_path(file);
|
2023-01-06 10:41:01 +00:00
|
|
|
if (p.isNull()) {
|
2021-07-29 08:49:24 +01:00
|
|
|
simgear::reportFailure(simgear::LoadFailure::NotFound, simgear::ErrorCode::AircraftSystems,
|
|
|
|
string{"Missing nasal file for module:"} + module, sg_location{file});
|
2011-07-03 13:06:41 +02:00
|
|
|
}
|
2011-04-02 15:13:29 +02:00
|
|
|
}
|
2021-11-06 19:10:20 +01:00
|
|
|
ok &= p.isNull() ? false : loadModule(p, module.c_str());
|
2011-04-03 15:30:25 +02:00
|
|
|
j++;
|
|
|
|
}
|
|
|
|
|
2021-11-06 19:10:20 +01:00
|
|
|
std::string src = n->getStringValue("script");
|
|
|
|
if(!n->hasChild("script")) src = ""; // Hrm...
|
|
|
|
if(!src.empty())
|
|
|
|
createModule(module.c_str(), n->getPath().c_str(), src.c_str(), src.length());
|
2011-04-03 15:30:25 +02:00
|
|
|
|
2021-11-06 19:10:20 +01:00
|
|
|
if(!file_specified && src.empty())
|
2011-04-03 15:30:25 +02:00
|
|
|
{
|
|
|
|
// module no longer exists - clear the archived "enable" flag
|
|
|
|
n->setAttribute(SGPropertyNode::USERARCHIVE,false);
|
|
|
|
SGPropertyNode* node = n->getChild("enabled",0,false);
|
|
|
|
if (node)
|
|
|
|
node->setAttribute(SGPropertyNode::USERARCHIVE,false);
|
|
|
|
|
|
|
|
SG_LOG(SG_NASAL, SG_ALERT, "Nasal error: " <<
|
|
|
|
"no <file> or <script> defined in " <<
|
|
|
|
"/nasal/" << module);
|
2005-03-16 21:36:55 +00:00
|
|
|
}
|
2011-04-03 15:30:25 +02:00
|
|
|
else
|
|
|
|
is_loaded = ok;
|
2003-12-01 14:35:49 +00:00
|
|
|
}
|
2011-04-03 15:30:25 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
SGPropertyNode* enable = n->getChild("enabled");
|
|
|
|
if (enable)
|
|
|
|
{
|
|
|
|
FGNasalModuleListener* listener = new FGNasalModuleListener(n);
|
2013-12-03 21:38:24 +00:00
|
|
|
_moduleListeners.push_back(listener);
|
2011-04-03 15:30:25 +02:00
|
|
|
enable->addChangeListener(listener, false);
|
|
|
|
}
|
|
|
|
}
|
2011-06-12 20:31:56 +02:00
|
|
|
SGPropertyNode* loaded = n->getChild("loaded",0,true);
|
|
|
|
loaded->setAttribute(SGPropertyNode::PRESERVE,true);
|
|
|
|
loaded->setBoolValue(is_loaded);
|
2003-11-25 21:08:36 +00:00
|
|
|
}
|
|
|
|
|
2020-08-26 17:20:58 +01:00
|
|
|
#if defined(BUILDING_TESTSUITE)
|
|
|
|
|
|
|
|
static string_list global_nasalErrors;
|
|
|
|
|
|
|
|
string_list FGNasalSys::getAndClearErrorList()
|
|
|
|
{
|
|
|
|
string_list r;
|
|
|
|
global_nasalErrors.swap(r);
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2003-11-25 21:08:36 +00:00
|
|
|
// Logs a runtime error, with stack trace, to the FlightGear log stream
|
2006-01-27 19:51:25 +00:00
|
|
|
void FGNasalSys::logError(naContext context)
|
2003-11-25 21:08:36 +00:00
|
|
|
{
|
2020-04-20 10:47:11 +01:00
|
|
|
string errorMessage = naGetError(context);
|
2020-08-26 17:20:58 +01:00
|
|
|
#if defined(BUILDING_TESTSUITE)
|
|
|
|
global_nasalErrors.push_back(errorMessage);
|
|
|
|
#else
|
2020-04-20 10:47:11 +01:00
|
|
|
SG_LOG(SG_NASAL, SG_ALERT, "Nasal runtime error: " << errorMessage);
|
2020-05-25 19:45:40 +01:00
|
|
|
|
2020-04-20 10:47:11 +01:00
|
|
|
string_list nasalStack;
|
|
|
|
logNasalStack(context, nasalStack);
|
|
|
|
flightgear::sentryReportNasalError(errorMessage, nasalStack);
|
2020-08-26 17:20:58 +01:00
|
|
|
#endif
|
2018-09-07 15:17:55 +01:00
|
|
|
}
|
|
|
|
|
2020-08-26 17:20:58 +01:00
|
|
|
|
2020-04-20 10:47:11 +01:00
|
|
|
void FGNasalSys::logNasalStack(naContext context, string_list& stack)
|
2018-09-07 15:17:55 +01:00
|
|
|
{
|
|
|
|
const int stack_depth = naStackDepth(context);
|
2021-08-24 22:44:37 +01:00
|
|
|
for (int i=0; i<stack_depth; ++i) {
|
|
|
|
std::string text = std::string(naStr_data(naGetSourceFile(context, i)))
|
|
|
|
+ ", line " + std::to_string(naGetLine(context, i));
|
|
|
|
stack.push_back(text);
|
|
|
|
SG_LOG(SG_NASAL, SG_ALERT, ((i) ? " called from: " : " at ") << text);
|
2018-09-07 15:17:55 +01:00
|
|
|
}
|
2021-08-24 22:44:37 +01:00
|
|
|
return;
|
2003-11-25 21:08:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Reads a script file, executes it, and places the resulting
|
|
|
|
// namespace into the global namespace under the specified module
|
|
|
|
// name.
|
2011-04-03 15:30:25 +02:00
|
|
|
bool FGNasalSys::loadModule(SGPath file, const char* module)
|
2003-11-25 21:08:36 +00:00
|
|
|
{
|
2020-03-12 10:51:49 +00:00
|
|
|
if (!file.exists()) {
|
|
|
|
SG_LOG(SG_NASAL, SG_ALERT, "Cannot load module, missing file:" << file);
|
2011-04-03 15:30:25 +02:00
|
|
|
return false;
|
2003-12-01 14:35:49 +00:00
|
|
|
}
|
2003-11-25 21:08:36 +00:00
|
|
|
|
2020-03-12 10:51:49 +00:00
|
|
|
sg_ifstream file_in(file);
|
|
|
|
string buf;
|
|
|
|
while (!file_in.eof()) {
|
|
|
|
char bytes[8192];
|
|
|
|
file_in.read(bytes, 8192);
|
|
|
|
buf.append(bytes, file_in.gcount());
|
|
|
|
}
|
|
|
|
file_in.close();
|
|
|
|
auto pathStr = file.utf8Str();
|
|
|
|
return createModule(module, pathStr.c_str(), buf.data(), buf.length());
|
2003-12-01 14:35:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Parse and run. Save the local variables namespace, as it will
|
2006-04-27 16:40:04 +00:00
|
|
|
// become a sub-object of globals. The optional "arg" argument can be
|
|
|
|
// used to pass an associated property node to the module, which can then
|
|
|
|
// be accessed via cmdarg(). (This is, for example, used by XML dialogs.)
|
2011-04-03 15:30:25 +02:00
|
|
|
bool FGNasalSys::createModule(const char* moduleName, const char* fileName,
|
2008-10-18 19:52:18 +00:00
|
|
|
const char* src, int len,
|
|
|
|
const SGPropertyNode* cmdarg,
|
|
|
|
int argc, naRef* args)
|
2003-12-01 14:35:49 +00:00
|
|
|
{
|
2014-04-15 14:13:46 +01:00
|
|
|
naContext ctx = naNewContext();
|
2018-02-03 13:44:26 +00:00
|
|
|
std::string errors;
|
|
|
|
naRef code = parse(ctx, fileName, src, len, errors);
|
2014-04-15 14:13:46 +01:00
|
|
|
if(naIsNil(code)) {
|
|
|
|
naFreeContext(ctx);
|
2011-04-03 15:30:25 +02:00
|
|
|
return false;
|
2014-04-15 14:13:46 +01:00
|
|
|
}
|
2003-11-25 21:08:36 +00:00
|
|
|
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2003-12-01 14:35:49 +00:00
|
|
|
// See if we already have a module hash to use. This allows the
|
|
|
|
// user to, for example, add functions to the built-in math
|
|
|
|
// module. Make a new one if necessary.
|
|
|
|
naRef locals;
|
2013-11-22 22:40:50 +00:00
|
|
|
naRef modname = naNewString(ctx);
|
2003-12-01 14:35:49 +00:00
|
|
|
naStr_fromdata(modname, (char*)moduleName, strlen(moduleName));
|
2015-11-29 19:15:24 +01:00
|
|
|
if (naIsNil(_globals))
|
|
|
|
return false;
|
2017-01-26 15:37:43 +01:00
|
|
|
|
2023-01-06 10:41:01 +00:00
|
|
|
if (!naHash_get(_globals, modname, &locals)) {
|
|
|
|
// if we are re-creating the module for canvas, ensure the C++
|
|
|
|
// pieces are re-defined first. As far as I can see, Canvas is the only
|
|
|
|
// hybrid module where C++ pieces and Nasal code are combined.
|
|
|
|
const auto isCanvas = strcmp(moduleName, "canvas") == 0;
|
|
|
|
if (isCanvas) {
|
|
|
|
initNasalCanvas(_globals, _context);
|
|
|
|
naHash_get(_globals, modname, &locals);
|
|
|
|
} else {
|
|
|
|
locals = naNewHash(ctx);
|
|
|
|
}
|
|
|
|
}
|
2003-12-01 14:35:49 +00:00
|
|
|
|
2022-11-06 11:44:09 +00:00
|
|
|
// store the filename in the module hash, so we could reload it
|
2023-01-06 10:41:01 +00:00
|
|
|
// this is only needed for 'top-level' single file modules; for
|
|
|
|
// 'directory' modules we use the file path nodes defined by
|
|
|
|
// FGNasalSys::addModule
|
2022-11-06 11:44:09 +00:00
|
|
|
naRef modFilePath = naNewString(ctx);
|
|
|
|
naStr_fromdata(modFilePath, (char*)fileName, strlen(fileName));
|
|
|
|
hashset(locals, "__moduleFilePath", modFilePath);
|
2006-04-27 15:56:51 +00:00
|
|
|
|
2022-11-06 11:44:09 +00:00
|
|
|
_cmdArg = (SGPropertyNode*)cmdarg;
|
2014-04-15 14:13:46 +01:00
|
|
|
callWithContext(ctx, code, argc, args, locals);
|
2003-12-01 14:35:49 +00:00
|
|
|
hashset(_globals, moduleName, locals);
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2013-11-22 22:40:50 +00:00
|
|
|
naFreeContext(ctx);
|
2011-04-03 15:30:25 +02:00
|
|
|
return true;
|
2003-11-25 21:08:36 +00:00
|
|
|
}
|
|
|
|
|
2006-03-08 10:35:20 +00:00
|
|
|
void FGNasalSys::deleteModule(const char* moduleName)
|
|
|
|
{
|
2022-03-09 16:47:12 +00:00
|
|
|
if (!_inited || naIsNil(_globals)) {
|
2014-03-16 22:52:55 +00:00
|
|
|
// can occur on shutdown due to us being shutdown first, but other
|
|
|
|
// subsystems having Nasal objects.
|
|
|
|
return;
|
|
|
|
}
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2023-01-06 10:41:01 +00:00
|
|
|
auto nasalNode = globals->get_props()->getNode("nasal", true);
|
|
|
|
auto moduleNode = nasalNode->getChild(moduleName, 0);
|
|
|
|
if (moduleNode) {
|
|
|
|
// modules can use this value going false, to trigger unload
|
|
|
|
// behaviours
|
|
|
|
moduleNode->setBoolValue("loaded", false);
|
|
|
|
}
|
|
|
|
|
2013-11-22 22:40:50 +00:00
|
|
|
naContext ctx = naNewContext();
|
|
|
|
naRef modname = naNewString(ctx);
|
2006-03-08 10:35:20 +00:00
|
|
|
naStr_fromdata(modname, (char*)moduleName, strlen(moduleName));
|
2023-01-06 10:41:01 +00:00
|
|
|
|
|
|
|
naRef locals;
|
|
|
|
if (naHash_get(_globals, modname, &locals)) {
|
|
|
|
naRef unloadFunc = naHash_cget(locals, (char*)"unload");
|
|
|
|
if (naIsFunc(unloadFunc)) {
|
|
|
|
callWithContext(ctx, unloadFunc, 0, nullptr, locals);
|
|
|
|
}
|
|
|
|
|
|
|
|
// now delete the module hash
|
|
|
|
naHash_delete(_globals, modname);
|
|
|
|
}
|
|
|
|
|
2013-11-22 22:40:50 +00:00
|
|
|
naFreeContext(ctx);
|
2006-03-08 10:35:20 +00:00
|
|
|
}
|
|
|
|
|
2022-11-06 11:44:09 +00:00
|
|
|
bool FGNasalSys::reloadModuleFromFile(const std::string& moduleName)
|
|
|
|
{
|
|
|
|
if (!_inited || naIsNil(_globals)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
naRef locals = naHash_cget(_globals, (char*)moduleName.c_str());
|
|
|
|
if (naIsNil(locals)) {
|
|
|
|
// no such module
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-01-06 10:41:01 +00:00
|
|
|
// check if we have a module entry under /nasal/ - if so, use
|
|
|
|
// this to determine the list of files. We don't (yet) re-run
|
|
|
|
// addModule here so adding new .nas files isn't possible, but
|
|
|
|
// in principle it could be done
|
|
|
|
auto nasalNode = globals->get_props()->getNode("nasal", true);
|
|
|
|
auto moduleNode = nasalNode->getChild(moduleName, 0);
|
|
|
|
if (moduleNode) {
|
|
|
|
deleteModule(moduleName.c_str());
|
|
|
|
loadPropertyScripts(moduleNode);
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
// assume it's a single-file module for now
|
|
|
|
naRef filePath = naHash_cget(locals, (char*)"__moduleFilePath");
|
|
|
|
if (naIsNil(filePath)) {
|
|
|
|
return false;
|
|
|
|
}
|
2022-11-06 11:44:09 +00:00
|
|
|
|
2023-01-06 10:41:01 +00:00
|
|
|
SGPath path = SGPath::fromUtf8(naStr_data(filePath));
|
|
|
|
deleteModule(moduleName.c_str());
|
|
|
|
return loadModule(path, moduleName.c_str());
|
|
|
|
}
|
2022-11-06 11:44:09 +00:00
|
|
|
}
|
|
|
|
|
2021-06-16 14:41:19 +01:00
|
|
|
naRef FGNasalSys::getModule(const std::string& moduleName) const
|
|
|
|
{
|
|
|
|
naRef mod = naHash_cget(_globals, (char*)moduleName.c_str());
|
|
|
|
return mod;
|
|
|
|
}
|
|
|
|
|
2016-03-19 14:44:38 +00:00
|
|
|
naRef FGNasalSys::getModule(const char* moduleName)
|
|
|
|
{
|
|
|
|
naRef mod = naHash_cget(_globals, (char*) moduleName);
|
|
|
|
return mod;
|
|
|
|
}
|
|
|
|
|
2018-02-03 13:44:26 +00:00
|
|
|
naRef FGNasalSys::parse(naContext ctx, const char* filename,
|
|
|
|
const char* buf, int len,
|
|
|
|
std::string& errors)
|
2003-11-25 21:08:36 +00:00
|
|
|
{
|
|
|
|
int errLine = -1;
|
2013-11-22 22:40:50 +00:00
|
|
|
naRef srcfile = naNewString(ctx);
|
2003-11-25 21:08:36 +00:00
|
|
|
naStr_fromdata(srcfile, (char*)filename, strlen(filename));
|
2013-11-22 22:40:50 +00:00
|
|
|
naRef code = naParseCode(ctx, srcfile, 1, (char*)buf, len, &errLine);
|
2003-11-25 21:08:36 +00:00
|
|
|
if(naIsNil(code)) {
|
2018-02-03 13:44:26 +00:00
|
|
|
std::ostringstream errorMessageStream;
|
|
|
|
errorMessageStream << "Nasal parse error: " << naGetError(ctx) <<
|
|
|
|
" in "<< filename <<", line " << errLine;
|
|
|
|
errors = errorMessageStream.str();
|
|
|
|
SG_LOG(SG_NASAL, SG_ALERT, errors);
|
2021-06-06 12:44:03 +01:00
|
|
|
|
|
|
|
// Show the line, in case <filename> isn't a real file, e.g. nasal code
|
|
|
|
// is in an .xml file.
|
|
|
|
const char* line_begin = buf;
|
|
|
|
const char* line_end = nullptr;
|
|
|
|
int line_num = 1;
|
|
|
|
for(;;) {
|
|
|
|
line_end = strchr(line_begin, '\n');
|
|
|
|
if (!line_end) {
|
|
|
|
line_end = line_begin + strlen(line_begin);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (line_num == errLine) break;
|
|
|
|
line_begin = line_end + 1;
|
|
|
|
line_num += 1;
|
|
|
|
}
|
|
|
|
if (line_num == errLine) {
|
|
|
|
SG_LOG(SG_NASAL, SG_ALERT, std::string(line_begin, line_end) << "\n");
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
SG_LOG(SG_NASAL, SG_ALERT, "[Could not find line " << errLine << " - only " << line_num << " lines.");
|
|
|
|
}
|
2020-02-19 20:32:22 +00:00
|
|
|
|
2003-11-25 21:08:36 +00:00
|
|
|
return naNil();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Bind to the global namespace before returning
|
2014-04-15 14:13:46 +01:00
|
|
|
return naBindFunction(ctx, code, _globals);
|
2003-11-25 21:08:36 +00:00
|
|
|
}
|
|
|
|
|
2012-07-04 13:15:12 +02:00
|
|
|
bool FGNasalSys::handleCommand( const char* moduleName,
|
|
|
|
const char* fileName,
|
|
|
|
const char* src,
|
2017-07-05 01:23:24 +02:00
|
|
|
const SGPropertyNode* arg,
|
|
|
|
SGPropertyNode* root)
|
2003-11-25 21:08:36 +00:00
|
|
|
{
|
2014-04-15 14:13:46 +01:00
|
|
|
naContext ctx = naNewContext();
|
2018-02-03 13:44:26 +00:00
|
|
|
std::string errorMessage;
|
|
|
|
naRef code = parse(ctx, fileName, src, strlen(src), errorMessage);
|
2014-04-15 14:13:46 +01:00
|
|
|
if(naIsNil(code)) {
|
|
|
|
naFreeContext(ctx);
|
|
|
|
return false;
|
|
|
|
}
|
2005-06-19 17:09:03 +00:00
|
|
|
|
2007-03-29 18:50:28 +00:00
|
|
|
// Commands can be run "in" a module. Make sure that module
|
|
|
|
// exists, and set it up as the local variables hash for the
|
|
|
|
// command.
|
2005-06-19 17:09:03 +00:00
|
|
|
naRef locals = naNil();
|
2006-03-08 16:06:32 +00:00
|
|
|
if(moduleName[0]) {
|
2013-11-22 22:40:50 +00:00
|
|
|
naRef modname = naNewString(ctx);
|
2005-06-19 17:09:03 +00:00
|
|
|
naStr_fromdata(modname, (char*)moduleName, strlen(moduleName));
|
2007-03-29 18:50:28 +00:00
|
|
|
if(!naHash_get(_globals, modname, &locals)) {
|
2013-11-22 22:40:50 +00:00
|
|
|
locals = naNewHash(ctx);
|
2007-03-29 18:50:28 +00:00
|
|
|
naHash_set(_globals, modname, locals);
|
|
|
|
}
|
2005-06-19 17:09:03 +00:00
|
|
|
}
|
2007-03-29 18:50:28 +00:00
|
|
|
|
|
|
|
// Cache this command's argument for inspection via cmdarg(). For
|
2003-12-01 14:35:49 +00:00
|
|
|
// performance reasons, we won't bother with it if the invoked
|
|
|
|
// code doesn't need it.
|
|
|
|
_cmdArg = (SGPropertyNode*)arg;
|
2003-11-25 21:08:36 +00:00
|
|
|
|
2014-04-15 14:13:46 +01:00
|
|
|
callWithContext(ctx, code, 0, 0, locals);
|
|
|
|
naFreeContext(ctx);
|
2006-07-19 19:46:53 +00:00
|
|
|
return true;
|
2003-11-25 21:08:36 +00:00
|
|
|
}
|
|
|
|
|
2017-07-05 01:23:24 +02:00
|
|
|
bool FGNasalSys::handleCommand(const SGPropertyNode * arg, SGPropertyNode * root)
|
2012-07-04 13:15:12 +02:00
|
|
|
{
|
2021-11-06 19:10:20 +01:00
|
|
|
std::string src = arg->getStringValue("script");
|
|
|
|
std::string moduleName = arg->getStringValue("module");
|
2012-07-04 13:15:12 +02:00
|
|
|
|
2021-11-06 19:10:20 +01:00
|
|
|
return handleCommand( moduleName.c_str(),
|
2014-03-12 22:42:51 +01:00
|
|
|
arg->getPath(true).c_str(),
|
2021-11-06 19:10:20 +01:00
|
|
|
src.c_str(),
|
2017-07-05 01:23:24 +02:00
|
|
|
arg,
|
|
|
|
root);
|
2012-07-04 13:15:12 +02:00
|
|
|
}
|
|
|
|
|
2003-11-25 21:08:36 +00:00
|
|
|
// settimer(func, dt, simtime) extension function. The first argument
|
|
|
|
// is a Nasal function to call, the second is a delta time (from now),
|
|
|
|
// in seconds. The third, if present, is a boolean value indicating
|
2005-11-09 20:34:46 +00:00
|
|
|
// that "real world" time (rather than simulator time) is to be used.
|
2003-11-25 21:08:36 +00:00
|
|
|
//
|
|
|
|
// Implementation note: the FGTimer objects don't live inside the
|
|
|
|
// garbage collector, so the Nasal handler functions have to be
|
|
|
|
// "saved" somehow lest they be inadvertently cleaned. In this case,
|
2003-12-05 01:54:39 +00:00
|
|
|
// they are inserted into a globals.__gcsave hash and removed on
|
2003-11-25 21:08:36 +00:00
|
|
|
// expiration.
|
2007-05-12 18:15:45 +00:00
|
|
|
void FGNasalSys::setTimer(naContext c, int argc, naRef* args)
|
2003-11-25 21:08:36 +00:00
|
|
|
{
|
|
|
|
// Extract the handler, delta, and simtime arguments:
|
2005-04-18 19:49:13 +00:00
|
|
|
naRef handler = argc > 0 ? args[0] : naNil();
|
2007-05-12 18:15:45 +00:00
|
|
|
if(!(naIsCode(handler) || naIsCCode(handler) || naIsFunc(handler))) {
|
|
|
|
naRuntimeError(c, "settimer() with invalid function argument");
|
2003-11-25 21:08:36 +00:00
|
|
|
return;
|
2007-05-12 18:15:45 +00:00
|
|
|
}
|
2003-11-25 21:08:36 +00:00
|
|
|
|
2005-04-18 19:49:13 +00:00
|
|
|
naRef delta = argc > 1 ? args[1] : naNil();
|
2007-05-12 18:15:45 +00:00
|
|
|
if(naIsNil(delta)) {
|
|
|
|
naRuntimeError(c, "settimer() with invalid time argument");
|
|
|
|
return;
|
|
|
|
}
|
2006-02-28 14:55:37 +00:00
|
|
|
|
2005-11-09 20:34:46 +00:00
|
|
|
bool simtime = (argc > 2 && naTrue(args[2])) ? false : true;
|
2003-11-25 21:08:36 +00:00
|
|
|
|
2018-03-21 10:21:45 +01:00
|
|
|
// A unique name for the timer based on the file name and line number of the function.
|
2019-02-10 17:24:55 +01:00
|
|
|
std::string name = "settimer-";
|
2018-03-21 10:21:45 +01:00
|
|
|
name.append(naStr_data(naGetSourceFile(c, 0)));
|
|
|
|
name.append(":");
|
|
|
|
name.append(std::to_string(naGetLine(c, 0)));
|
|
|
|
|
2003-11-25 21:08:36 +00:00
|
|
|
// Generate and register a C++ timer handler
|
2018-09-01 18:53:36 +01:00
|
|
|
NasalTimer* t = new NasalTimer(handler, this);
|
|
|
|
_nasalTimers.push_back(t);
|
2018-03-21 10:21:45 +01:00
|
|
|
globals->get_event_mgr()->addEvent(name,
|
2022-05-24 21:52:41 +02:00
|
|
|
[t](){ t->timerExpired(); },
|
2003-11-25 21:08:36 +00:00
|
|
|
delta.num, simtime);
|
|
|
|
}
|
|
|
|
|
|
|
|
void FGNasalSys::handleTimer(NasalTimer* t)
|
|
|
|
{
|
2007-10-15 16:28:40 +00:00
|
|
|
call(t->handler, 0, 0, naNil());
|
2018-09-01 18:53:36 +01:00
|
|
|
auto it = std::find(_nasalTimers.begin(), _nasalTimers.end(), t);
|
|
|
|
assert(it != _nasalTimers.end());
|
|
|
|
_nasalTimers.erase(it);
|
|
|
|
delete t;
|
2003-12-05 01:54:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int FGNasalSys::gcSave(naRef r)
|
|
|
|
{
|
2013-10-15 00:58:04 +02:00
|
|
|
return naGCSave(r);
|
2003-12-05 01:54:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void FGNasalSys::gcRelease(int key)
|
|
|
|
{
|
2013-10-15 00:58:04 +02:00
|
|
|
naGCRelease(key);
|
2003-11-25 21:08:36 +00:00
|
|
|
}
|
|
|
|
|
2014-09-19 18:21:42 +02:00
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
2018-09-01 18:53:36 +01:00
|
|
|
|
|
|
|
NasalTimer::NasalTimer(naRef h, FGNasalSys* sys) :
|
|
|
|
handler(h), nasal(sys)
|
|
|
|
{
|
|
|
|
assert(sys);
|
|
|
|
gcKey = naGCSave(handler);
|
|
|
|
}
|
|
|
|
|
|
|
|
NasalTimer::~NasalTimer()
|
|
|
|
{
|
|
|
|
naGCRelease(gcKey);
|
|
|
|
}
|
|
|
|
|
|
|
|
void NasalTimer::timerExpired()
|
2003-11-25 21:08:36 +00:00
|
|
|
{
|
|
|
|
nasal->handleTimer(this);
|
2018-09-01 18:53:36 +01:00
|
|
|
// note handleTimer calls delete on us, don't do anything
|
|
|
|
// which requires 'this' to be valid here
|
2003-11-25 21:08:36 +00:00
|
|
|
}
|
2005-12-16 19:11:03 +00:00
|
|
|
|
2018-09-01 18:53:36 +01:00
|
|
|
|
2006-02-28 14:55:37 +00:00
|
|
|
int FGNasalSys::_listenerId = 0;
|
|
|
|
|
2007-10-14 18:01:26 +00:00
|
|
|
// setlistener(<property>, <func> [, <initial=0> [, <persistent=1>]])
|
|
|
|
// Attaches a callback function to a property (specified as a global
|
2014-11-23 14:53:54 +01:00
|
|
|
// property path string or a SGPropertyNode* ghost). If the third,
|
2007-10-14 18:01:26 +00:00
|
|
|
// optional argument (default=0) is set to 1, then the function is also
|
|
|
|
// called initially. If the fourth, optional argument is set to 0, then the
|
|
|
|
// function is only called when the property node value actually changes.
|
|
|
|
// Otherwise it's called independent of the value whenever the node is
|
|
|
|
// written to (default). The setlistener() function returns a unique
|
|
|
|
// id number, which is to be used as argument to the removelistener()
|
|
|
|
// function.
|
2007-02-03 16:46:39 +00:00
|
|
|
naRef FGNasalSys::setListener(naContext c, int argc, naRef* args)
|
2005-12-16 19:11:03 +00:00
|
|
|
{
|
2006-02-28 14:55:37 +00:00
|
|
|
SGPropertyNode_ptr node;
|
2005-12-16 19:11:03 +00:00
|
|
|
naRef prop = argc > 0 ? args[0] : naNil();
|
|
|
|
if(naIsString(prop)) node = fgGetNode(naStr_data(prop), true);
|
2014-11-23 14:53:54 +01:00
|
|
|
else if(naIsGhost(prop)) node = static_cast<SGPropertyNode*>(naGhost_ptr(prop));
|
2007-02-03 16:46:39 +00:00
|
|
|
else {
|
|
|
|
naRuntimeError(c, "setlistener() with invalid property argument");
|
|
|
|
return naNil();
|
|
|
|
}
|
2005-12-16 19:11:03 +00:00
|
|
|
|
2019-10-20 13:08:17 +01:00
|
|
|
if (node->isTied()) {
|
|
|
|
const auto isSafe = node->getAttribute(SGPropertyNode::LISTENER_SAFE);
|
|
|
|
if (!isSafe) {
|
|
|
|
SG_LOG(SG_NASAL, SG_DEV_ALERT, "ERROR: Cannot add listener to tied property " <<
|
|
|
|
node->getPath());
|
|
|
|
}
|
|
|
|
}
|
2020-02-19 20:32:22 +00:00
|
|
|
|
2007-10-18 11:43:38 +00:00
|
|
|
naRef code = argc > 1 ? args[1] : naNil();
|
|
|
|
if(!(naIsCode(code) || naIsCCode(code) || naIsFunc(code))) {
|
2007-02-03 16:46:39 +00:00
|
|
|
naRuntimeError(c, "setlistener() with invalid function argument");
|
2006-02-28 14:55:37 +00:00
|
|
|
return naNil();
|
2007-02-03 16:46:39 +00:00
|
|
|
}
|
2005-12-16 19:11:03 +00:00
|
|
|
|
2019-05-29 20:25:28 +02:00
|
|
|
int init = argc > 2 && naIsNum(args[2]) ? int(args[2].num) : 0; // do not trigger when created
|
2020-02-19 20:32:22 +00:00
|
|
|
int type = argc > 3 && naIsNum(args[3]) ? int(args[3].num) : 1; // trigger will always be triggered when the property is written
|
2007-10-18 11:43:38 +00:00
|
|
|
FGNasalListener *nl = new FGNasalListener(node, code, this,
|
2009-03-07 17:57:20 +00:00
|
|
|
gcSave(code), _listenerId, init, type);
|
2007-10-14 18:01:26 +00:00
|
|
|
|
2011-07-22 12:37:41 +02:00
|
|
|
node->addChangeListener(nl, init != 0);
|
2006-03-02 10:41:48 +00:00
|
|
|
|
|
|
|
_listener[_listenerId] = nl;
|
2006-02-28 14:55:37 +00:00
|
|
|
return naNum(_listenerId++);
|
|
|
|
}
|
|
|
|
|
|
|
|
// removelistener(int) extension function. The argument is the id of
|
|
|
|
// a listener as returned by the setlistener() function.
|
2007-01-17 13:56:22 +00:00
|
|
|
naRef FGNasalSys::removeListener(naContext c, int argc, naRef* args)
|
2006-02-28 14:55:37 +00:00
|
|
|
{
|
|
|
|
naRef id = argc > 0 ? args[0] : naNil();
|
2020-04-27 08:50:38 +01:00
|
|
|
auto it = _listener.find(int(id.num));
|
2007-01-23 16:47:04 +00:00
|
|
|
if(!naIsNum(id) || it == _listener.end() || it->second->_dead) {
|
2007-01-17 13:56:22 +00:00
|
|
|
naRuntimeError(c, "removelistener() with invalid listener id");
|
2006-02-28 14:55:37 +00:00
|
|
|
return naNil();
|
2007-01-17 13:56:22 +00:00
|
|
|
}
|
2006-02-28 14:55:37 +00:00
|
|
|
|
2007-10-18 11:43:38 +00:00
|
|
|
it->second->_dead = true;
|
|
|
|
_dead_listener.push_back(it->second);
|
2007-01-23 16:47:04 +00:00
|
|
|
_listener.erase(it);
|
2006-03-02 10:41:48 +00:00
|
|
|
return naNum(_listener.size());
|
2005-12-16 19:11:03 +00:00
|
|
|
}
|
|
|
|
|
2013-02-09 15:33:05 +00:00
|
|
|
void FGNasalSys::registerToLoad(FGNasalModelData *data)
|
|
|
|
{
|
2013-03-23 12:53:17 +01:00
|
|
|
if( _loadList.empty() )
|
|
|
|
_delay_load = true;
|
|
|
|
_loadList.push(data);
|
2013-02-09 15:33:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void FGNasalSys::registerToUnload(FGNasalModelData *data)
|
|
|
|
{
|
|
|
|
_unloadList.push(data);
|
|
|
|
}
|
2006-03-09 09:04:03 +00:00
|
|
|
|
2020-08-26 17:20:58 +01:00
|
|
|
bool FGNasalSys::addCommand(naRef func, const std::string& name)
|
2013-10-06 17:36:19 +01:00
|
|
|
{
|
|
|
|
if (_commands.find(name) != _commands.end()) {
|
|
|
|
SG_LOG(SG_NASAL, SG_WARN, "duplicate add of command:" << name);
|
2020-08-26 17:20:58 +01:00
|
|
|
return false;
|
2013-10-06 17:36:19 +01:00
|
|
|
}
|
2017-03-21 21:43:42 +01:00
|
|
|
|
2013-10-06 17:36:19 +01:00
|
|
|
NasalCommand* cmd = new NasalCommand(this, func, name);
|
|
|
|
_commands[name] = cmd;
|
2020-08-26 17:20:58 +01:00
|
|
|
return true;
|
2013-10-06 17:36:19 +01:00
|
|
|
}
|
|
|
|
|
2020-08-25 19:23:43 +01:00
|
|
|
bool FGNasalSys::removeCommand(const std::string& name)
|
2013-10-06 17:36:19 +01:00
|
|
|
{
|
2020-08-25 19:23:43 +01:00
|
|
|
auto it = _commands.find(name);
|
2013-10-06 17:36:19 +01:00
|
|
|
if (it == _commands.end()) {
|
|
|
|
SG_LOG(SG_NASAL, SG_WARN, "remove of unknwon command:" << name);
|
2020-08-25 19:23:43 +01:00
|
|
|
return false;
|
2013-10-06 17:36:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// will delete the NasalCommand instance
|
2020-08-25 19:23:43 +01:00
|
|
|
bool ok = globals->get_commands()->removeCommand(name);
|
2013-10-06 17:36:19 +01:00
|
|
|
_commands.erase(it);
|
2020-08-25 19:23:43 +01:00
|
|
|
return ok;
|
2013-10-06 17:36:19 +01:00
|
|
|
}
|
|
|
|
|
2018-09-01 18:53:36 +01:00
|
|
|
void FGNasalSys::addPersistentTimer(TimerObj* pto)
|
|
|
|
{
|
|
|
|
_persistentTimers.push_back(pto);
|
|
|
|
}
|
|
|
|
|
|
|
|
void FGNasalSys::removePersistentTimer(TimerObj* obj)
|
|
|
|
{
|
|
|
|
auto it = std::find(_persistentTimers.begin(), _persistentTimers.end(), obj);
|
|
|
|
assert(it != _persistentTimers.end());
|
|
|
|
_persistentTimers.erase(it);
|
|
|
|
}
|
|
|
|
|
2018-05-07 08:46:44 +02:00
|
|
|
// Register the subsystem.
|
|
|
|
SGSubsystemMgr::Registrant<FGNasalSys> registrantFGNasalSys(
|
|
|
|
SGSubsystemMgr::INIT);
|
|
|
|
|
|
|
|
|
2013-03-16 12:44:27 +00:00
|
|
|
//////////////////////////////////////////////////////////////////////////
|
2006-03-20 07:13:10 +00:00
|
|
|
// FGNasalListener class.
|
|
|
|
|
2007-10-18 11:43:38 +00:00
|
|
|
FGNasalListener::FGNasalListener(SGPropertyNode *node, naRef code,
|
2009-03-07 17:57:20 +00:00
|
|
|
FGNasalSys* nasal, int key, int id,
|
|
|
|
int init, int type) :
|
2006-03-20 07:13:10 +00:00
|
|
|
_node(node),
|
2007-10-18 11:43:38 +00:00
|
|
|
_code(code),
|
2006-03-20 07:13:10 +00:00
|
|
|
_gcKey(key),
|
2007-04-27 14:30:05 +00:00
|
|
|
_id(id),
|
2006-03-20 07:13:10 +00:00
|
|
|
_nas(nasal),
|
2009-03-07 17:57:20 +00:00
|
|
|
_init(init),
|
2007-10-15 16:28:40 +00:00
|
|
|
_type(type),
|
2007-01-23 16:47:04 +00:00
|
|
|
_active(0),
|
2007-10-12 17:24:43 +00:00
|
|
|
_dead(false),
|
|
|
|
_last_int(0L),
|
|
|
|
_last_float(0.0)
|
2006-03-20 07:13:10 +00:00
|
|
|
{
|
2009-03-07 17:57:20 +00:00
|
|
|
if(_type == 0 && !_init)
|
|
|
|
changed(node);
|
2006-03-20 07:13:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
FGNasalListener::~FGNasalListener()
|
|
|
|
{
|
2006-06-10 22:21:22 +00:00
|
|
|
_node->removeChangeListener(this);
|
2006-03-20 07:13:10 +00:00
|
|
|
_nas->gcRelease(_gcKey);
|
|
|
|
}
|
|
|
|
|
2007-10-16 15:15:41 +00:00
|
|
|
void FGNasalListener::call(SGPropertyNode* which, naRef mode)
|
2006-03-20 07:13:10 +00:00
|
|
|
{
|
2007-10-16 15:15:41 +00:00
|
|
|
if(_active || _dead) return;
|
2007-10-15 16:28:40 +00:00
|
|
|
_active++;
|
2007-10-16 15:15:41 +00:00
|
|
|
naRef arg[4];
|
|
|
|
arg[0] = _nas->propNodeGhost(which);
|
|
|
|
arg[1] = _nas->propNodeGhost(_node);
|
|
|
|
arg[2] = mode; // value changed, child added/removed
|
|
|
|
arg[3] = naNum(_node != which); // child event?
|
2007-10-18 11:43:38 +00:00
|
|
|
_nas->call(_code, 4, arg, naNil());
|
2007-10-15 16:28:40 +00:00
|
|
|
_active--;
|
|
|
|
}
|
|
|
|
|
|
|
|
void FGNasalListener::valueChanged(SGPropertyNode* node)
|
|
|
|
{
|
2007-10-18 11:43:38 +00:00
|
|
|
if(_type < 2 && node != _node) return; // skip child events
|
2009-03-07 17:57:20 +00:00
|
|
|
if(_type > 0 || changed(_node) || _init)
|
2007-10-16 15:15:41 +00:00
|
|
|
call(node, naNum(0));
|
2007-10-15 16:28:40 +00:00
|
|
|
|
2009-03-07 17:57:20 +00:00
|
|
|
_init = 0;
|
2006-03-20 07:13:10 +00:00
|
|
|
}
|
|
|
|
|
2007-10-15 19:12:03 +00:00
|
|
|
void FGNasalListener::childAdded(SGPropertyNode*, SGPropertyNode* child)
|
2007-10-15 16:28:40 +00:00
|
|
|
{
|
2007-10-16 15:15:41 +00:00
|
|
|
if(_type == 2) call(child, naNum(1));
|
2007-10-15 16:28:40 +00:00
|
|
|
}
|
|
|
|
|
2007-10-15 19:12:03 +00:00
|
|
|
void FGNasalListener::childRemoved(SGPropertyNode*, SGPropertyNode* child)
|
2007-10-15 16:28:40 +00:00
|
|
|
{
|
2007-10-16 15:15:41 +00:00
|
|
|
if(_type == 2) call(child, naNum(-1));
|
2007-10-15 16:28:40 +00:00
|
|
|
}
|
|
|
|
|
2007-10-14 18:01:26 +00:00
|
|
|
bool FGNasalListener::changed(SGPropertyNode* node)
|
2007-10-12 17:24:43 +00:00
|
|
|
{
|
2009-07-17 14:54:12 +02:00
|
|
|
using namespace simgear;
|
|
|
|
props::Type type = node->getType();
|
|
|
|
if(type == props::NONE) return false;
|
|
|
|
if(type == props::UNSPECIFIED) return true;
|
2007-10-12 17:24:43 +00:00
|
|
|
|
|
|
|
bool result;
|
|
|
|
switch(type) {
|
2009-07-17 14:54:12 +02:00
|
|
|
case props::BOOL:
|
|
|
|
case props::INT:
|
|
|
|
case props::LONG:
|
2007-10-12 17:24:43 +00:00
|
|
|
{
|
|
|
|
long l = node->getLongValue();
|
2007-10-14 18:01:26 +00:00
|
|
|
result = l != _last_int;
|
2007-10-12 17:24:43 +00:00
|
|
|
_last_int = l;
|
|
|
|
return result;
|
|
|
|
}
|
2009-07-17 14:54:12 +02:00
|
|
|
case props::FLOAT:
|
|
|
|
case props::DOUBLE:
|
2007-10-12 17:24:43 +00:00
|
|
|
{
|
|
|
|
double d = node->getDoubleValue();
|
2007-10-14 18:01:26 +00:00
|
|
|
result = d != _last_float;
|
2007-10-12 17:24:43 +00:00
|
|
|
_last_float = d;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
{
|
|
|
|
string s = node->getStringValue();
|
2007-10-14 18:01:26 +00:00
|
|
|
result = s != _last_string;
|
2007-10-12 17:24:43 +00:00
|
|
|
_last_string = s;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2006-03-20 07:13:10 +00:00
|
|
|
|
add parsexml() function, which is a wrapper around the built-in easyxml
parser. Advantages over xml.nas: (reviewed and OK'ed by Andy)
- faster (33% ... only. I had hoped for more.)
- more standards compliant
- should support UTF
- I don't have to support it. ;-)
Usage: parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
<path> is an absolute file path, the rest are optional callback functions.
Example:
parsexml("/tmp/foo.xml", nil, nil, func(d) { print("DATA FOUND: ", d) });
2007-06-29 15:34:38 +00:00
|
|
|
// NasalXMLVisitor class: handles EasyXML visitor callback for parsexml()
|
|
|
|
//
|
|
|
|
NasalXMLVisitor::NasalXMLVisitor(naContext c, int argc, naRef* args) :
|
|
|
|
_c(naSubContext(c)),
|
2007-07-01 11:07:53 +00:00
|
|
|
_start_element(argc > 1 ? args[1] : naNil()),
|
|
|
|
_end_element(argc > 2 ? args[2] : naNil()),
|
|
|
|
_data(argc > 3 ? args[3] : naNil()),
|
|
|
|
_pi(argc > 4 ? args[4] : naNil())
|
add parsexml() function, which is a wrapper around the built-in easyxml
parser. Advantages over xml.nas: (reviewed and OK'ed by Andy)
- faster (33% ... only. I had hoped for more.)
- more standards compliant
- should support UTF
- I don't have to support it. ;-)
Usage: parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
<path> is an absolute file path, the rest are optional callback functions.
Example:
parsexml("/tmp/foo.xml", nil, nil, func(d) { print("DATA FOUND: ", d) });
2007-06-29 15:34:38 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void NasalXMLVisitor::startElement(const char* tag, const XMLAttributes& a)
|
|
|
|
{
|
|
|
|
if(naIsNil(_start_element)) return;
|
|
|
|
naRef attr = naNewHash(_c);
|
|
|
|
for(int i=0; i<a.size(); i++) {
|
|
|
|
naRef name = make_string(a.getName(i));
|
|
|
|
naRef value = make_string(a.getValue(i));
|
|
|
|
naHash_set(attr, name, value);
|
|
|
|
}
|
|
|
|
call(_start_element, 2, make_string(tag), attr);
|
|
|
|
}
|
|
|
|
|
|
|
|
void NasalXMLVisitor::endElement(const char* tag)
|
|
|
|
{
|
|
|
|
if(!naIsNil(_end_element)) call(_end_element, 1, make_string(tag));
|
|
|
|
}
|
|
|
|
|
|
|
|
void NasalXMLVisitor::data(const char* str, int len)
|
|
|
|
{
|
|
|
|
if(!naIsNil(_data)) call(_data, 1, make_string(str, len));
|
|
|
|
}
|
|
|
|
|
|
|
|
void NasalXMLVisitor::pi(const char* target, const char* data)
|
|
|
|
{
|
2007-09-30 11:56:21 +00:00
|
|
|
if(!naIsNil(_pi)) call(_pi, 2, make_string(target), make_string(data));
|
add parsexml() function, which is a wrapper around the built-in easyxml
parser. Advantages over xml.nas: (reviewed and OK'ed by Andy)
- faster (33% ... only. I had hoped for more.)
- more standards compliant
- should support UTF
- I don't have to support it. ;-)
Usage: parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
<path> is an absolute file path, the rest are optional callback functions.
Example:
parsexml("/tmp/foo.xml", nil, nil, func(d) { print("DATA FOUND: ", d) });
2007-06-29 15:34:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void NasalXMLVisitor::call(naRef func, int num, naRef a, naRef b)
|
|
|
|
{
|
2007-06-30 09:44:33 +00:00
|
|
|
naRef args[2];
|
|
|
|
args[0] = a;
|
|
|
|
args[1] = b;
|
|
|
|
naCall(_c, func, num, args, naNil(), naNil());
|
add parsexml() function, which is a wrapper around the built-in easyxml
parser. Advantages over xml.nas: (reviewed and OK'ed by Andy)
- faster (33% ... only. I had hoped for more.)
- more standards compliant
- should support UTF
- I don't have to support it. ;-)
Usage: parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
<path> is an absolute file path, the rest are optional callback functions.
Example:
parsexml("/tmp/foo.xml", nil, nil, func(d) { print("DATA FOUND: ", d) });
2007-06-29 15:34:38 +00:00
|
|
|
if(naGetError(_c))
|
|
|
|
naRethrowError(_c);
|
|
|
|
}
|
|
|
|
|
|
|
|
naRef NasalXMLVisitor::make_string(const char* s, int n)
|
|
|
|
{
|
|
|
|
return naStr_fromdata(naNewString(_c), const_cast<char *>(s),
|
|
|
|
n < 0 ? strlen(s) : n);
|
|
|
|
}
|
2020-07-08 15:19:47 +01:00
|
|
|
|
|
|
|
// like naEqual, but checks vector/hash recursively
|
|
|
|
// note this will not tolerate a recursively defined Nasal structure
|
|
|
|
// (such as globals.)
|
|
|
|
int nasalStructEqual(naContext ctx, naRef a, naRef b)
|
|
|
|
{
|
|
|
|
if (naIsVector(a) && naIsVector(b)) {
|
|
|
|
const int aSz = naVec_size(a),
|
|
|
|
bSz = naVec_size(b);
|
|
|
|
|
|
|
|
if (aSz != bSz)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
for (int i = 0; i < aSz; ++i) {
|
|
|
|
int eq = nasalStructEqual(ctx, naVec_get(a, i), naVec_get(b, i));
|
|
|
|
if (!eq)
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// all elements equal, we're done
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (naIsHash(a) && naIsHash(b)) {
|
|
|
|
naRef keysVec = naNewVector(ctx);
|
|
|
|
naHash_keys(keysVec, a);
|
|
|
|
const auto aSz = naVec_size(keysVec);
|
|
|
|
|
|
|
|
// first check key count, that's fast
|
|
|
|
if (aSz != naHash_size(b))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
for (int i = 0; i < aSz; i++) {
|
|
|
|
naRef key = naVec_get(keysVec, i);
|
|
|
|
naRef aValue, bValue;
|
|
|
|
if (!naHash_get(a, key, &aValue) || !naHash_get(b, key, &bValue)) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int eq = nasalStructEqual(ctx, aValue, bValue);
|
|
|
|
if (!eq) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// all values matched, we're good
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return naEqual(a, b);
|
|
|
|
}
|