noinst_LIBRARIES = libScripting.a
-libScripting_a_SOURCES = NasalSys.cxx NasalSys.hxx
+libScripting_a_SOURCES = NasalSys.cxx NasalSys.hxx nasal-props.cxx
# libScripting_a_SOURCES = scriptmgr.cxx scriptmgr.hxx NasalSys.cxx NasalSys.hxx
INCLUDES = -I$(top_srcdir) -I$(top_srcdir)/src
// etc...)
SG_LOG(SG_NASAL, SG_ALERT,
"ERROR in Nasal initialization: " <<
- "short count returned from fread(). Check your C library!");
+ "short count returned from fread() of " << file <<
+ ". Check your C library!");
delete[] buf;
return 0;
}
_globals = naNil();
}
+bool FGNasalSys::parseAndRun(const char* sourceCode)
+{
+ naRef code = parse("FGNasalSys::parseAndRun()", sourceCode,
+ strlen(sourceCode));
+ if(naIsNil(code))
+ return false;
+
+ naCall(_context, code, naNil(), naNil(), naNil());
+
+ if(!naGetError(_context)) return true;
+ logError();
+ return false;
+}
+
// 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 cmd = naVec_get(args, 0);
naRef props = naVec_get(args, 1);
- if(!naIsString(cmd) || !naIsString(props)) return naNil();
-
- SGPropertyNode* pnode =
- globals->get_props()->getNode(naStr_data(props));
- if(pnode)
- globals->get_commands()->execute(naStr_data(cmd), pnode);
+ if(!naIsString(cmd) || !naIsGhost(props)) return naNil();
+ SGPropertyNode_ptr* node = (SGPropertyNode_ptr*)naGhost_ptr(props);
+ globals->get_commands()->execute(naStr_data(cmd), *node);
return naNil();
+
}
// settimer(func, dt, simtime) extension function. Falls through to
return naNil();
}
+// Returns a ghost handle to the argument to the currently executing
+// command
+static naRef f_cmdarg(naContext c, naRef args)
+{
+ FGNasalSys* nasal = (FGNasalSys*)globals->get_subsystem("nasal");
+ return nasal->cmdArgGhost();
+}
+
// Table of extension functions. Terminate with zeros.
static struct { char* name; naCFunction func; } funcs[] = {
{ "getprop", f_getprop },
{ "setprop", f_setprop },
{ "print", f_print },
- { "fgcommand", f_fgcommand },
+ { "_fgcommand", f_fgcommand },
{ "settimer", f_settimer },
+ { "_cmdarg", f_cmdarg },
{ 0, 0 }
};
+naRef FGNasalSys::cmdArgGhost()
+{
+ return propNodeGhost(_cmdArg);
+}
+
void FGNasalSys::init()
{
+ int i;
+
_context = naNewContext();
// Start with globals. Add it to itself as a recursive
hashset(_globals, "math", naMathLib(_context));
// Add our custom extension functions:
- for(int i=0; funcs[i].name; i++)
+ for(i=0; funcs[i].name; i++)
hashset(_globals, funcs[i].name,
naNewFunc(_context, naNewCCode(_context, funcs[i].func)));
+ // And our SGPropertyNode wrapper
+ hashset(_globals, "props", genPropsModule());
+
// Make a "__timers" hash to hold the settimer() handlers (to
// protect them from begin garbage-collected).
_timerHash = naNewHash(_context);
if(file.extension() != "nas") continue;
readScriptFile(fullpath, file.base().c_str());
}
+
+ // Pull scripts out of the property tree, too
+ loadPropertyScripts();
+}
+
+// Loads the scripts found under /nasal in the global tree
+void FGNasalSys::loadPropertyScripts()
+{
+ SGPropertyNode* nasal = globals->get_props()->getNode("nasal");
+ if(!nasal) return;
+
+ for(int i=0; i<nasal->nChildren(); i++) {
+ SGPropertyNode* n = nasal->getChild(i);
+
+ const char* module = n->getName();
+ if(n->hasChild("module"))
+ module = n->getStringValue("module");
+
+ const char* file = n->getStringValue("file");
+ if(!n->hasChild("file")) file = 0; // Hrm...
+ if(file) {
+ SGPath p(globals->get_fg_root());
+ p.append(file);
+ readScriptFile(p, module);
+ }
+
+ const char* src = n->getStringValue("script");
+ if(!n->hasChild("script")) src = 0; // Hrm...
+ if(src)
+ initModule(module, n->getPath(), src, strlen(src));
+
+ if(!file && !src)
+ SG_LOG(SG_NASAL, SG_ALERT, "Nasal error: " <<
+ "no <file> or <script> defined in " <<
+ "/nasal/" << module);
+ }
}
// Logs a runtime error, with stack trace, to the FlightGear log stream
// Reads a script file, executes it, and places the resulting
// namespace into the global namespace under the specified module
// name.
-void FGNasalSys::readScriptFile(SGPath file, const char* lib)
+void FGNasalSys::readScriptFile(SGPath file, const char* module)
{
int len = 0;
char* buf = readfile(file.c_str(), &len);
- if(!buf) return;
+ if(!buf) {
+ SG_LOG(SG_NASAL, SG_ALERT,
+ "Nasal error: could not read script file " << file.c_str()
+ << " into module " << module);
+ return;
+ }
- // Parse and run. Save the local variables namespace, as it will
- // become a sub-object of globals.
- naRef code = parse(file.c_str(), buf, len);
+ initModule(module, file.c_str(), buf, len);
delete[] buf;
+}
+
+// Parse and run. Save the local variables namespace, as it will
+// become a sub-object of globals.
+void FGNasalSys::initModule(const char* moduleName, const char* fileName,
+ const char* src, int len)
+{
+ if(len == 0) len = strlen(src);
+
+ naRef code = parse(fileName, src, len);
if(naIsNil(code))
return;
- naRef locals = naNewHash(_context);
+ // 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;
+ naRef modname = naNewString(_context);
+ naStr_fromdata(modname, (char*)moduleName, strlen(moduleName));
+ if(!naHash_get(_globals, modname, &locals))
+ locals = naNewHash(_context);
+
naCall(_context, code, naNil(), naNil(), locals);
if(naGetError(_context)) {
logError();
return;
}
-
- hashset(_globals, lib, locals);
+ hashset(_globals, moduleName, locals);
}
naRef FGNasalSys::parse(const char* filename, const char* buf, int len)
naRef code = parse("<command>", nasal, strlen(nasal));
if(naIsNil(code)) return false;
- // FIXME: Cache the just-created code object somewhere, but watch
- // for changes to the source in the property tree. Maybe store an
- // integer index into a Nasal vector in the original property
- // location?
-
- // Extract the "value" or "offset" arguments if present
- naRef locals = naNil();
- if(arg->hasValue("value")) {
- locals = naNewHash(_context);
- hashset(locals, "value", naNum(arg->getDoubleValue("value")));
- } else if(arg->hasValue("offset")) {
- locals = naNewHash(_context);
- hashset(locals, "offset", naNum(arg->getDoubleValue("offset")));
- }
+ // Cache the command argument for inspection via cmdarg(). For
+ // performance reasons, we won't bother with it if the invoked
+ // code doesn't need it.
+ _cmdArg = (SGPropertyNode*)arg;
// Call it!
- naRef result = naCall(_context, code, naNil(), naNil(), locals);
+ naRef result = naCall(_context, code, naNil(), naNil(), naNil());
if(!naGetError(_context)) return true;
logError();
return false;
virtual bool handleCommand(const SGPropertyNode* arg);
+ // Simple hook to run arbitrary source code. Returns a bool to
+ // indicate successful execution. Does *not* return any Nasal
+ // values, because handling garbage-collected objects from C space
+ // is deep voodoo and violates the "simple hook" idea.
+ bool parseAndRun(const char* sourceCode);
+
// Implementation of the settimer extension function
void setTimer(naRef args);
+ // Returns a ghost wrapper for the current _cmdArg
+ naRef cmdArgGhost();
+
private:
//
// FGTimer subclass for handling Nasal timer callbacks.
FGNasalSys* nasal;
};
+ void loadPropertyScripts();
+ void initModule(const char* moduleName, const char* fileName,
+ const char* src, int len);
void readScriptFile(SGPath file, const char* lib);
void hashset(naRef hash, const char* key, naRef val);
void logError();
naRef parse(const char* filename, const char* buf, int len);
+ naRef genPropsModule();
+ naRef propNodeGhost(SGPropertyNode* handle);
naContext _context;
naRef _globals;
naRef _timerHash;
+ SGPropertyNode* _cmdArg;
+
int _nextTimerHashKey;
public:
--- /dev/null
+#include <simgear/nasal/nasal.h>
+#include <simgear/props/props.hxx>
+
+#include <Main/globals.hxx>
+
+#include "NasalSys.hxx"
+
+// Implementation of a Nasal wrapper for the SGPropertyNode class,
+// using the Nasal "ghost" (er... Garbage collection Handle for
+// OutSide Thingy) facility.
+//
+// Note that these functions appear in Nasal with prepended
+// underscores. They work on the low-level "ghost" objects and aren't
+// intended for use from user code, but from Nasal code you will find
+// in props.nas. That is where the Nasal props.Node class is defined,
+// which provides a saner interface along the lines of SGPropertyNode.
+
+static void propNodeGhostDestroy(void* ghost)
+{
+ SGPropertyNode_ptr* prop = (SGPropertyNode_ptr*)ghost;
+ delete prop;
+}
+
+naGhostType PropNodeGhostType = { propNodeGhostDestroy };
+
+static naRef propNodeGhostCreate(naContext c, SGPropertyNode* n)
+{
+ SGPropertyNode_ptr* ghost = new SGPropertyNode_ptr(n);
+ return naNewGhost(c, &PropNodeGhostType, ghost);
+}
+
+naRef FGNasalSys::propNodeGhost(SGPropertyNode* handle)
+{
+ return propNodeGhostCreate(_context, handle);
+}
+
+#define NASTR(s) s ? naStr_fromdata(naNewString(c),(char*)(s),strlen(s)) : naNil()
+
+//
+// Standard header for the extension functions. It turns the "ghost"
+// found in arg[0] into a SGPropertyNode_ptr*, and then "unwraps" the
+// vector found in the second argument into a normal-looking args
+// array. This allows the Nasal handlers to do things like:
+// Node.getChild = func { _getChild(me.ghost, arg) }
+//
+#define NODEARG() \
+ naRef ghost = naVec_get(args, 0); \
+ SGPropertyNode_ptr* node = (SGPropertyNode_ptr*)naGhost_ptr(ghost); \
+ if(!node || naGhost_type(ghost) != &PropNodeGhostType) \
+ return naNil(); \
+ if(naVec_size(args) > 1) { \
+ args = naVec_get(args, 1); \
+ if(!naIsVector(args)) return naNil(); \
+ } else { args = naNil(); }
+
+static naRef f_getType(naContext c, naRef args)
+{
+ NODEARG();
+ char* t = "unknown";
+ switch((*node)->getType()) {
+ case SGPropertyNode::NONE: t = "NONE"; break;
+ case SGPropertyNode::ALIAS: t = "ALIAS"; break;
+ case SGPropertyNode::BOOL: t = "BOOL"; break;
+ case SGPropertyNode::INT: t = "INT"; break;
+ case SGPropertyNode::LONG: t = "LONG"; break;
+ case SGPropertyNode::FLOAT: t = "FLOAT"; break;
+ case SGPropertyNode::DOUBLE: t = "DOUBLE"; break;
+ case SGPropertyNode::STRING: t = "STRING"; break;
+ case SGPropertyNode::UNSPECIFIED: t = "UNSPECIFIED"; break;
+ }
+ return NASTR(t);
+}
+
+static naRef f_getName(naContext c, naRef args)
+{
+ NODEARG();
+ return NASTR((*node)->getName());
+}
+
+static naRef f_getIndex(naContext c, naRef args)
+{
+ NODEARG();
+ return naNum((*node)->getIndex());
+}
+
+static naRef f_getValue(naContext c, naRef args)
+{
+ NODEARG();
+ switch((*node)->getType()) {
+ case SGPropertyNode::BOOL: case SGPropertyNode::INT:
+ case SGPropertyNode::LONG: case SGPropertyNode::FLOAT:
+ case SGPropertyNode::DOUBLE:
+ return naNum((*node)->getDoubleValue());
+ case SGPropertyNode::STRING:
+ case SGPropertyNode::UNSPECIFIED:
+ return NASTR((*node)->getStringValue());
+ }
+ return naNil();
+}
+
+static naRef f_setValue(naContext c, naRef args)
+{
+ NODEARG();
+ naRef val = naVec_get(args, 0);
+ if(naIsString(val)) (*node)->setStringValue(naStr_data(val));
+ else (*node)->setDoubleValue(naNumValue(val).num);
+ return naNil();
+}
+
+static naRef f_setIntValue(naContext c, naRef args)
+{
+ NODEARG();
+ int iv = (int)naNumValue(naVec_get(args, 0)).num;
+ (*node)->setIntValue(iv);
+ return naNil();
+}
+
+static naRef f_setBoolValue(naContext c, naRef args)
+{
+ NODEARG();
+ naRef val = naVec_get(args, 0);
+ (*node)->setBoolValue(naTrue(val) ? true : false);
+ return naNil();
+}
+
+static naRef f_setDoubleValue(naContext c, naRef args)
+{
+ NODEARG();
+ (*node)->setDoubleValue(naNumValue(naVec_get(args, 0)).num);
+ return naNil();
+}
+
+static naRef f_getParent(naContext c, naRef args)
+{
+ NODEARG();
+ SGPropertyNode* n = (*node)->getParent();
+ if(!n) return naNil();
+ return propNodeGhostCreate(c, n);
+}
+
+static naRef f_getChild(naContext c, naRef args)
+{
+ NODEARG();
+ naRef child = naVec_get(args, 0);
+ if(!naIsString(child)) return naNil();
+ SGPropertyNode* n = (*node)->getChild(naStr_data(child));
+ if(!n) return naNil();
+ return propNodeGhostCreate(c, n);
+}
+
+static naRef f_getChildren(naContext c, naRef args)
+{
+ NODEARG();
+ naRef result = naNewVector(c);
+ if(naIsNil(args) || naVec_size(args) == 0) {
+ // Get all children
+ for(int i=0; i<(*node)->nChildren(); i++)
+ naVec_append(result, propNodeGhostCreate(c, (*node)->getChild(i)));
+ } else {
+ // Get all children of a specified name
+ naRef name = naVec_get(args, 0);
+ if(!naIsString(name)) return naNil();
+ vector<SGPropertyNode_ptr> children
+ = (*node)->getChildren(naStr_data(name));
+ for(int i=0; i<children.size(); i++)
+ naVec_append(result, propNodeGhostCreate(c, children[i]));
+ }
+ return result;
+}
+
+static naRef f_removeChild(naContext c, naRef args)
+{
+ NODEARG();
+ naRef child = naVec_get(args, 0);
+ naRef index = naVec_get(args, 1);
+ if(!naIsString(child) || !naIsNum(index)) return naNil();
+ (*node)->removeChild(naStr_data(child), (int)index.num);
+ return naNil();
+}
+
+static naRef f_getNode(naContext c, naRef args)
+{
+ NODEARG();
+ naRef path = naVec_get(args, 0);
+ bool create = naTrue(naVec_get(args, 1));
+ if(!naIsString(path)) return naNil();
+ SGPropertyNode* n = (*node)->getNode(naStr_data(path), create);
+ return propNodeGhostCreate(c, n);
+}
+
+static naRef f_new(naContext c, naRef args)
+{
+ return propNodeGhostCreate(c, new SGPropertyNode());
+}
+
+static naRef f_globals(naContext c, naRef args)
+{
+ return propNodeGhostCreate(c, globals->get_props());
+}
+
+struct {
+ naCFunction func;
+ char* name;
+} propfuncs[] = {
+ { f_getType, "_getType" },
+ { f_getName, "_getName" },
+ { f_getIndex, "_getIndex" },
+ { f_getValue, "_getValue" },
+ { f_setValue, "_setValue" },
+ { f_setIntValue, "_setIntValue" },
+ { f_setBoolValue, "_setBoolValue" },
+ { f_setDoubleValue, "_setDoubleValue" },
+ { f_getParent, "_getParent" },
+ { f_getChild, "_getChild" },
+ { f_getChildren, "_getChildren" },
+ { f_removeChild, "_removeChild" },
+ { f_getNode, "_getNode" },
+ { f_new, "_new" },
+ { f_globals, "_globals" },
+ { 0, 0 }
+};
+
+naRef FGNasalSys::genPropsModule()
+{
+ naRef namespc = naNewHash(_context);
+ for(int i=0; propfuncs[i].name; i++)
+ hashset(namespc, propfuncs[i].name,
+ naNewFunc(_context, naNewCCode(_context, propfuncs[i].func)));
+ return namespc;
+}