]> git.mxchange.org Git - flightgear.git/blob - src/Scripting/NasalSys.cxx
80adc19eaa63dac153fbd020f9ad6260d0096d72
[flightgear.git] / src / Scripting / NasalSys.cxx
1 #include <string.h>
2 #include <stdio.h>
3 #include <sys/types.h>
4 #include <sys/stat.h>
5
6 #include <plib/ul.h>
7
8 #include <simgear/nasal/nasal.h>
9 #include <simgear/props/props.hxx>
10 #include <simgear/misc/sg_path.hxx>
11 #include <simgear/structure/commands.hxx>
12
13 #include <Main/globals.hxx>
14
15 #include "NasalSys.hxx"
16
17 // Read and return file contents in a single buffer.  Note use of
18 // stat() to get the file size.  This is a win32 function, believe it
19 // or not. :) Note the REALLY IMPORTANT use of the "b" flag to fopen.
20 // Text mode brain damage will kill us if we're trying to do bytewise
21 // I/O.
22 static char* readfile(const char* file, int* lenOut)
23 {
24     struct stat data;
25     if(stat(file, &data) != 0) return 0;
26     FILE* f = fopen(file, "rb");
27     if(!f) return 0;
28     char* buf = new char[data.st_size];
29     *lenOut = fread(buf, 1, data.st_size, f);
30     fclose(f);
31     if(*lenOut != data.st_size) {
32         // Shouldn't happen, but warn anyway since it represents a
33         // platform bug and not a typical runtime error (missing file,
34         // etc...)
35         SG_LOG(SG_NASAL, SG_ALERT,
36                "ERROR in Nasal initialization: " <<
37                "short count returned from fread() of " << file <<
38                ".  Check your C library!");
39         delete[] buf;
40         return 0;
41     }
42     return buf;
43 }
44
45 FGNasalSys::FGNasalSys()
46 {
47     _context = 0;
48     _globals = naNil();
49     _timerHash = naNil();
50     _nextTimerHashKey = 0; // Any value will do
51 }
52
53 FGNasalSys::~FGNasalSys()
54 {
55     // Nasal doesn't have a "destroy context" API yet. :(
56     // Not a problem for a global subsystem that will never be
57     // destroyed.  And the context is actually a global, so no memory
58     // is technically leaked (although the GC pool memory obviously
59     // won't be freed).
60     _context = 0;
61     _globals = naNil();
62 }
63
64 bool FGNasalSys::parseAndRun(const char* sourceCode)
65 {
66     naRef code = parse("FGNasalSys::parseAndRun()", sourceCode,
67                        strlen(sourceCode));
68     if(naIsNil(code))
69         return false;
70
71     naCall(_context, code, naNil(), naNil(), naNil());
72
73     if(!naGetError(_context)) return true;
74     logError();
75     return false;
76 }
77
78 // Utility.  Sets a named key in a hash by C string, rather than nasal
79 // string object.
80 void FGNasalSys::hashset(naRef hash, const char* key, naRef val)
81 {
82     naRef s = naNewString(_context);
83     naStr_fromdata(s, (char*)key, strlen(key));
84     naHash_set(hash, s, val);
85 }
86
87 // The get/setprop functions accept a *list* of strings and walk
88 // through the property tree with them to find the appropriate node.
89 // This allows a Nasal object to hold onto a property path and use it
90 // like a node object, e.g. setprop(ObjRoot, "size-parsecs", 2.02).  This
91 // is the utility function that walks the property tree.
92 // Future enhancement: support integer arguments to specify array
93 // elements.
94 static SGPropertyNode* findnode(naContext c, naRef vec, int len)
95 {
96     SGPropertyNode* p = globals->get_props();
97     for(int i=0; i<len; i++) {
98         naRef a = naVec_get(vec, i);
99         if(!naIsString(a)) return 0;
100         p = p->getNode(naStr_data(a));
101         if(p == 0) return 0;
102     }
103     return p;
104 }
105
106 // getprop() extension function.  Concatenates its string arguments as
107 // property names and returns the value of the specified property.  Or
108 // nil if it doesn't exist.
109 static naRef f_getprop(naContext c, naRef args)
110 {
111     const SGPropertyNode* p = findnode(c, args, naVec_size(args));
112     if(!p) return naNil();
113
114     switch(p->getType()) {
115     case SGPropertyNode::BOOL:   case SGPropertyNode::INT:
116     case SGPropertyNode::LONG:   case SGPropertyNode::FLOAT:
117     case SGPropertyNode::DOUBLE:
118         return naNum(p->getDoubleValue());
119
120     case SGPropertyNode::STRING:
121         {
122             naRef nastr = naNewString(c);
123             const char* val = p->getStringValue();
124             naStr_fromdata(nastr, (char*)val, strlen(val));
125             return nastr;
126         }
127     default:
128         return naNil();
129     }
130 }
131
132 // setprop() extension function.  Concatenates its string arguments as
133 // property names and sets the value of the specified property to the
134 // final argument.
135 static naRef f_setprop(naContext c, naRef args)
136 {
137 #define BUFLEN 1024
138     int argc = naVec_size(args);
139     char buf[BUFLEN + 1];
140     buf[BUFLEN] = 0;
141     char* p = buf;
142     int buflen = BUFLEN;
143     for(int i=0; i<argc-1; i++) {
144         naRef s = naStringValue(c, naVec_get(args, i));
145         if(naIsNil(s)) return naNil();
146         strncpy(p, naStr_data(s), buflen);
147         p += naStr_len(s);
148         buflen = BUFLEN - (p - buf);
149         if(i < (argc-2) && buflen > 0) {
150             *p++ = '/';
151             buflen--;
152         }
153     }
154
155     SGPropertyNode* props = globals->get_props();
156     naRef val = naVec_get(args, argc-1);
157     if(naIsString(val)) props->setStringValue(buf, naStr_data(val));
158     else                props->setDoubleValue(buf, naNumValue(val).num);
159     return naNil();
160 #undef BUFLEN
161 }
162
163 // print() extension function.  Concatenates and prints its arguments
164 // to the FlightGear log.  Uses the highest log level (SG_ALERT), to
165 // make sure it appears.  Is there better way to do this?
166 static naRef f_print(naContext c, naRef args)
167 {
168 #define BUFLEN 1024
169     char buf[BUFLEN + 1];
170     buf[BUFLEN] = 0; // extra nul to handle strncpy brain damage
171     char* p = buf;
172     int buflen = BUFLEN;
173     int n = naVec_size(args);
174     for(int i=0; i<n; i++) {
175         naRef s = naStringValue(c, naVec_get(args, i));
176         if(naIsNil(s)) continue;
177         strncpy(p, naStr_data(s), buflen);
178         p += naStr_len(s);
179         buflen = BUFLEN - (p - buf);
180         if(buflen <= 0) break;
181     }
182     SG_LOG(SG_GENERAL, SG_ALERT, buf);
183     return naNil();
184 #undef BUFLEN
185 }
186
187 // fgcommand() extension function.  Executes a named command via the
188 // FlightGear command manager.  Takes a single property node name as
189 // an argument.
190 static naRef f_fgcommand(naContext c, naRef args)
191 {
192     naRef cmd = naVec_get(args, 0);
193     naRef props = naVec_get(args, 1);
194     if(!naIsString(cmd) || !naIsGhost(props)) return naNil();
195     SGPropertyNode_ptr* node = (SGPropertyNode_ptr*)naGhost_ptr(props);
196     globals->get_commands()->execute(naStr_data(cmd), *node);
197     return naNil();
198
199 }
200
201 // settimer(func, dt, simtime) extension function.  Falls through to
202 // FGNasalSys::setTimer().  See there for docs.
203 static naRef f_settimer(naContext c, naRef args)
204 {
205     FGNasalSys* nasal = (FGNasalSys*)globals->get_subsystem("nasal");
206     nasal->setTimer(args);
207     return naNil();
208 }
209
210 // Returns a ghost handle to the argument to the currently executing
211 // command
212 static naRef f_cmdarg(naContext c, naRef args)
213 {
214     FGNasalSys* nasal = (FGNasalSys*)globals->get_subsystem("nasal");
215     return nasal->cmdArgGhost();
216 }
217
218 // Table of extension functions.  Terminate with zeros.
219 static struct { char* name; naCFunction func; } funcs[] = {
220     { "getprop",   f_getprop },
221     { "setprop",   f_setprop },
222     { "print",     f_print },
223     { "_fgcommand", f_fgcommand },
224     { "settimer",  f_settimer },
225     { "_cmdarg",  f_cmdarg },
226     { 0, 0 }
227 };
228
229 naRef FGNasalSys::cmdArgGhost()
230 {
231     return propNodeGhost(_cmdArg);
232 }
233
234 void FGNasalSys::init()
235 {
236     int i;
237
238     _context = naNewContext();
239
240     // Start with globals.  Add it to itself as a recursive
241     // sub-reference under the name "globals".  This gives client-code
242     // write access to the namespace if someone wants to do something
243     // fancy.
244     _globals = naStdLib(_context);
245     naSave(_context, _globals);
246     hashset(_globals, "globals", _globals);
247
248     // Add in the math library under "math"
249     hashset(_globals, "math", naMathLib(_context));
250
251     // Add our custom extension functions:
252     for(i=0; funcs[i].name; i++)
253         hashset(_globals, funcs[i].name,
254                 naNewFunc(_context, naNewCCode(_context, funcs[i].func)));
255
256     // And our SGPropertyNode wrapper
257     hashset(_globals, "props", genPropsModule());
258
259     // Make a "__timers" hash to hold the settimer() handlers (to
260     // protect them from begin garbage-collected).
261     _timerHash = naNewHash(_context);
262     hashset(_globals, "__timers", _timerHash);
263
264     // Now load the various source files in the Nasal directory
265     SGPath p(globals->get_fg_root());
266     p.append("Nasal");
267     ulDirEnt* dent;
268     ulDir* dir = ulOpenDir(p.c_str());
269     while(dir && (dent = ulReadDir(dir)) != 0) {
270         SGPath fullpath(p);
271         fullpath.append(dent->d_name);
272         SGPath file(dent->d_name);
273         if(file.extension() != "nas") continue;
274         readScriptFile(fullpath, file.base().c_str());
275     }
276
277     // Pull scripts out of the property tree, too
278     loadPropertyScripts();
279 }
280
281 // Loads the scripts found under /nasal in the global tree
282 void FGNasalSys::loadPropertyScripts()
283 {
284     SGPropertyNode* nasal = globals->get_props()->getNode("nasal");
285     if(!nasal) return;
286
287     for(int i=0; i<nasal->nChildren(); i++) {
288         SGPropertyNode* n = nasal->getChild(i);
289
290         const char* module = n->getName();
291         if(n->hasChild("module"))
292             module = n->getStringValue("module");
293
294         const char* file = n->getStringValue("file");
295         if(!n->hasChild("file")) file = 0; // Hrm...
296         if(file) {
297             SGPath p(globals->get_fg_root());
298             p.append(file);
299             readScriptFile(p, module);
300         }
301         
302         const char* src = n->getStringValue("script");
303         if(!n->hasChild("script")) src = 0; // Hrm...
304         if(src)
305             initModule(module, n->getPath(), src, strlen(src));
306
307         if(!file && !src)
308             SG_LOG(SG_NASAL, SG_ALERT, "Nasal error: " <<
309                    "no <file> or <script> defined in " <<
310                    "/nasal/" << module);
311     }
312 }
313
314 // Logs a runtime error, with stack trace, to the FlightGear log stream
315 void FGNasalSys::logError()
316 {
317     SG_LOG(SG_NASAL, SG_ALERT,
318            "Nasal runtime error: " << naGetError(_context));
319     SG_LOG(SG_NASAL, SG_ALERT,
320            "  at " << naStr_data(naGetSourceFile(_context, 0)) <<
321            ", line " << naGetLine(_context, 0));
322     for(int i=1; i<naStackDepth(_context); i++)
323         SG_LOG(SG_NASAL, SG_ALERT,
324                "  called from: " << naStr_data(naGetSourceFile(_context, i)) <<
325                ", line " << naGetLine(_context, i));
326 }
327
328 // Reads a script file, executes it, and places the resulting
329 // namespace into the global namespace under the specified module
330 // name.
331 void FGNasalSys::readScriptFile(SGPath file, const char* module)
332 {
333     int len = 0;
334     char* buf = readfile(file.c_str(), &len);
335     if(!buf) {
336         SG_LOG(SG_NASAL, SG_ALERT,
337                "Nasal error: could not read script file " << file.c_str()
338                << " into module " << module);
339         return;
340     }
341
342     initModule(module, file.c_str(), buf, len);
343     delete[] buf;
344 }
345
346 // Parse and run.  Save the local variables namespace, as it will
347 // become a sub-object of globals.
348 void FGNasalSys::initModule(const char* moduleName, const char* fileName,
349                             const char* src, int len)
350 {
351     if(len == 0) len = strlen(src);
352
353     naRef code = parse(fileName, src, len);
354     if(naIsNil(code))
355         return;
356
357     // See if we already have a module hash to use.  This allows the
358     // user to, for example, add functions to the built-in math
359     // module.  Make a new one if necessary.
360     naRef locals;
361     naRef modname = naNewString(_context);
362     naStr_fromdata(modname, (char*)moduleName, strlen(moduleName));
363     if(!naHash_get(_globals, modname, &locals))
364         locals = naNewHash(_context);
365
366     naCall(_context, code, naNil(), naNil(), locals);
367     if(naGetError(_context)) {
368         logError();
369         return;
370     }
371     hashset(_globals, moduleName, locals);
372 }
373
374 naRef FGNasalSys::parse(const char* filename, const char* buf, int len)
375 {
376     int errLine = -1;
377     naRef srcfile = naNewString(_context);
378     naStr_fromdata(srcfile, (char*)filename, strlen(filename));
379     naRef code = naParseCode(_context, srcfile, 1, (char*)buf, len, &errLine);
380     if(naIsNil(code)) {
381         SG_LOG(SG_NASAL, SG_ALERT,
382                "Nasal parse error: " << naGetError(_context) <<
383                " in "<< filename <<", line " << errLine);
384         return naNil();
385     }
386
387     // Bind to the global namespace before returning
388     return naBindFunction(_context, code, _globals);
389 }
390
391 bool FGNasalSys::handleCommand(const SGPropertyNode* arg)
392 {
393     // Parse the Nasal source.  I'd love to use the property name of
394     // the argument, but it's actually a *clone* of the original
395     // location in the property tree.  arg->getPath() returns an empty
396     // string.
397     const char* nasal = arg->getStringValue("script");
398     naRef code = parse("<command>", nasal, strlen(nasal));
399     if(naIsNil(code)) return false;
400     
401     // Cache the command argument for inspection via cmdarg().  For
402     // performance reasons, we won't bother with it if the invoked
403     // code doesn't need it.
404     _cmdArg = (SGPropertyNode*)arg;
405
406     // Call it!
407     naRef result = naCall(_context, code, naNil(), naNil(), naNil());
408     if(!naGetError(_context)) return true;
409     logError();
410     return false;
411 }
412
413 // settimer(func, dt, simtime) extension function.  The first argument
414 // is a Nasal function to call, the second is a delta time (from now),
415 // in seconds.  The third, if present, is a boolean value indicating
416 // that "simulator" time (rather than real time) is to be used.
417 //
418 // Implementation note: the FGTimer objects don't live inside the
419 // garbage collector, so the Nasal handler functions have to be
420 // "saved" somehow lest they be inadvertently cleaned.  In this case,
421 // they are inserted into a globals._timers hash and removed on
422 // expiration.
423 void FGNasalSys::setTimer(naRef args)
424 {
425     // Extract the handler, delta, and simtime arguments:
426     naRef handler = naVec_get(args, 0);
427     if(!(naIsCode(handler) || naIsCCode(handler) || naIsFunc(handler)))
428         return;
429
430     naRef delta = naNumValue(naVec_get(args, 1));
431     if(naIsNil(delta)) return;
432     
433     bool simtime = naTrue(naVec_get(args, 2)) ? true : false;
434
435     // Generate and register a C++ timer handler
436     NasalTimer* t = new NasalTimer;
437     t->handler = handler;
438     t->hashKey = _nextTimerHashKey++;
439     t->nasal = this;
440
441     globals->get_event_mgr()->addEvent("NasalTimer",
442                                        t, &NasalTimer::timerExpired,
443                                        delta.num, simtime);
444
445
446     // Save the handler in the globals.__timers hash to prevent
447     // garbage collection.
448     naHash_set(_timerHash, naNum(t->hashKey), handler);
449 }
450
451 void FGNasalSys::handleTimer(NasalTimer* t)
452 {
453     naCall(_context, t->handler, naNil(), naNil(), naNil());
454     naHash_delete(_timerHash, naNum(t->hashKey));
455 }
456
457 void FGNasalSys::NasalTimer::timerExpired()
458 {
459     nasal->handleTimer(this);
460     delete this;
461 }