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;
+}