From 79e48829da50e0103e81684896b8287bfe23cc96 Mon Sep 17 00:00:00 2001 From: ehofman <ehofman> Date: Mon, 1 Dec 2003 14:35:49 +0000 Subject: [PATCH] Add Nasal Vs. 1.5 --- src/Scripting/Makefile.am | 2 +- src/Scripting/NasalSys.cxx | 142 ++++++++++++++++----- src/Scripting/NasalSys.hxx | 16 +++ src/Scripting/nasal-props.cxx | 230 ++++++++++++++++++++++++++++++++++ 4 files changed, 357 insertions(+), 33 deletions(-) create mode 100644 src/Scripting/nasal-props.cxx diff --git a/src/Scripting/Makefile.am b/src/Scripting/Makefile.am index 4eb53621f..468e969d7 100644 --- a/src/Scripting/Makefile.am +++ b/src/Scripting/Makefile.am @@ -1,6 +1,6 @@ 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 diff --git a/src/Scripting/NasalSys.cxx b/src/Scripting/NasalSys.cxx index e481ac510..80adc19ea 100644 --- a/src/Scripting/NasalSys.cxx +++ b/src/Scripting/NasalSys.cxx @@ -34,7 +34,8 @@ static char* readfile(const char* file, int* lenOut) // 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; } @@ -60,6 +61,20 @@ FGNasalSys::~FGNasalSys() _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) @@ -176,13 +191,11 @@ static naRef f_fgcommand(naContext c, naRef args) { 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 @@ -194,18 +207,34 @@ static naRef f_settimer(naContext c, naRef args) 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 @@ -220,10 +249,13 @@ void FGNasalSys::init() 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); @@ -241,6 +273,42 @@ void FGNasalSys::init() 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 @@ -260,27 +328,47 @@ void FGNasalSys::logError() // 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) @@ -310,23 +398,13 @@ bool FGNasalSys::handleCommand(const SGPropertyNode* arg) 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; diff --git a/src/Scripting/NasalSys.hxx b/src/Scripting/NasalSys.hxx index 36473ed36..c1dfa92ad 100644 --- a/src/Scripting/NasalSys.hxx +++ b/src/Scripting/NasalSys.hxx @@ -15,9 +15,18 @@ public: 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. @@ -31,15 +40,22 @@ private: 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: diff --git a/src/Scripting/nasal-props.cxx b/src/Scripting/nasal-props.cxx new file mode 100644 index 000000000..de0efca33 --- /dev/null +++ b/src/Scripting/nasal-props.cxx @@ -0,0 +1,230 @@ +#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; +}