source: trunk/FACT++/src/InterpreterV8.cc@ 16827

Last change on this file since 16827 was 16802, checked in by tbretz, 12 years ago
Allow arguments to be given to the interrupt handler and added a possibility to trigger an interrupt also from within the script; some modification to the way code is executed so that return values are only printed really when some code is finished from the command line, but not for include or when the main program is run.
File size: 87.5 KB
Line 
1#include "InterpreterV8.h"
2
3#ifdef HAVE_V8
4
5#include <fstream>
6#include <sstream>
7#include <iomanip>
8
9#include <sys/stat.h>
10
11#include <boost/tokenizer.hpp>
12
13#ifdef HAVE_NOVA
14#include "externals/nova.h"
15#endif
16
17#ifdef HAVE_SQL
18#include "Database.h"
19#endif
20
21#include <v8.h>
22
23#include "dim.h"
24#include "tools.h"
25#include "Readline.h"
26#include "externals/izstream.h"
27
28#include "WindowLog.h"
29
30using namespace std;
31using namespace v8;
32
33v8::Handle<v8::FunctionTemplate> InterpreterV8::fTemplateLocal;
34v8::Handle<v8::FunctionTemplate> InterpreterV8::fTemplateSky;
35v8::Handle<v8::FunctionTemplate> InterpreterV8::fTemplateEvent;
36v8::Handle<v8::FunctionTemplate> InterpreterV8::fTemplateDescription;
37//v8::Handle<v8::FunctionTemplate> InterpreterV8::fTemplateDatabase;
38
39
40// ==========================================================================
41// Some documentation
42// ==========================================================================
43//
44// Threads:
45// --------
46// In most cases Js* and other calls to native C++ code could be wrapped
47// with an Unlocker to allow possible other JavaScipt 'threads' to run
48// during that time. However, all of these calls should take much less than
49// the preemption time of 10ms, so it would just be a waste of tim.
50//
51// Termination:
52// ------------
53// Each thread running V8 code needs to be signalled individually for
54// termination. Therefor a list of V8 thread ids is created.
55//
56// If termination has already be signalled, no thread should start running
57// anymore (thy could, e.g., wait for their locking). So after locking
58// it has to be checked if the thread was terminated already. Note
59// that all calls to Terminate() must be locked to ensure that fThreadId
60// is correct when it is checked.
61//
62// The current thread id must be added to fThreadIds _before_ any
63// function is called after Locking and before execution is given
64// back to JavaScript, e.g. in script->Run(). So until the thread
65// is added to the list Terminate will not be executed. If Terminate
66// is then executed, it is ensured that the current thread is
67// already in the list. If terminate has been called before
68// the Locking, the check for the validiy of fThreadId ensures that
69// nothing is executed.
70//
71// Empty handles:
72// --------------
73// If exceution is terminated, V8 calls might return with empty handles,
74// e.g. Date::New(). Therefore, the returned handles of these calls have to
75// be checked in all placed to avoid that V8 will core dump.
76//
77// HandleScope:
78// ------------
79// A handle scope is a garbage collector and collects all handles created
80// until it goes out of scope. Handles which are not needed anymore are
81// then deleted. To return a handle from a HandleScope you need to use
82// Close(). E.g., String::AsciiValue does not create a new handle and
83// hence does not need a HandleScope. Any ::New will need a handle scope.
84// Forgetting the HandleScope could in principle fill your memory,
85// but everything is properly deleted by the global HandleScope at
86// script termination.
87//
88
89// ==========================================================================
90// Simple interface
91// ==========================================================================
92
93Handle<Value> InterpreterV8::FuncExit(const Arguments &)
94{
95 V8::TerminateExecution(fThreadId);
96
97 // we have to throw an excption to make sure that the
98 // calling thread does not go on executing until it
99 // has realized that it should terminate
100 return ThrowException(Null());
101}
102
103Handle<Value> InterpreterV8::FuncSleep(const Arguments& args)
104{
105 if (args.Length()==0)
106 {
107 // Theoretically, the CPU usage can be reduced by maybe a factor
108 // of four using a larger value, but this also means that the
109 // JavaScript is locked for a longer time.
110 const Unlocker unlock;
111 usleep(1000);
112 return Undefined();
113 }
114
115 if (args.Length()!=1)
116 return ThrowException(String::New("Number of arguments must be exactly 1."));
117
118 if (!args[0]->IsUint32())
119 return ThrowException(String::New("Argument 1 must be an uint32."));
120
121 // Using a Javascript function has the advantage that it is fully
122 // interruptable without the need of C++ code
123 const string code =
124 "(function(){"
125 "var t=new Date();"
126 "while ((new Date()-t)<"+to_string(args[0]->Int32Value())+") v8.sleep();"
127 "})();";
128
129 return ExecuteInternal(code);
130}
131
132Handle<Value> InterpreterV8::FuncTimeout(const Arguments &args)
133{
134 if (args.Length()<2)
135 return ThrowException(String::New("Number of arguments must be at least two."));
136
137 if (!args[0]->IsNull() && !args[0]->IsInt32())
138 return ThrowException(String::New("Argument 0 not null and not an int32."));
139
140 if (!args[1]->IsFunction())
141 return ThrowException(String::New("Argument 1 not a function."));
142
143 if (args.Length()>2 && !args[2]->IsObject())
144 return ThrowException(String::New("Argument 2 not an object."));
145
146 const int32_t timeout = args[0]->IsNull() ? 0 : args[0]->Int32Value();
147 const bool null = args[0]->IsNull();
148
149 HandleScope handle_scope;
150
151 Handle<Function> func = Handle<Function>::Cast(args[1]);
152
153 const int nn = args.Length()==2 ? 0 : args.Length()-3;
154
155 Handle<Value> argv[nn];
156 for (int i=0; i<nn; i++)
157 argv[i] = args[i+3];
158
159 Time t;
160 while (1)
161 {
162 const Handle<Value> rc = args.Length()<3 ? func->Call(func, nn, argv) : func->Call(args[2]->ToObject(), nn, argv);
163 if (rc.IsEmpty())
164 return Undefined();
165
166 if (!rc->IsUndefined())
167 return handle_scope.Close(rc);
168
169 if (!null && Time()-t>=boost::posix_time::milliseconds(abs(timeout)))
170 break;
171
172 // Theoretically, the CPU usage can be reduced by maybe a factor
173 // of four using a larger value, but this also means that the
174 // JavaScript is locked for a longer time.
175 const Unlocker unlock;
176 usleep(1000);
177 }
178
179 if (timeout<0)
180 return Undefined();
181
182 const string str = "Waiting for func to return a defined value timed out.";
183 return ThrowException(String::New(str.c_str()));
184}
185
186void InterpreterV8::Thread(int &id, Persistent<Object> _this, Persistent<Function> func, uint32_t ms)
187{
188 const Locker lock;
189
190 if (fThreadId<0)
191 {
192 id = -1;
193 return;
194 }
195
196 // Warning: As soon as id is set, the parent of this thread might terminate
197 // and hance the reference to id does not exist anymore. So, id
198 // is just a kind of return value and must not be used at all
199 // otherwise.
200
201 const int id_local = V8::GetCurrentThreadId();
202 id = id_local;
203 fThreadIds.insert(id_local);
204
205 const HandleScope handle_scope;
206
207 func->CreationContext()->Enter();
208
209 TryCatch exception;
210
211 const bool rc = ms==0 || !ExecuteInternal("v8.sleep("+to_string(ms)+");").IsEmpty();
212 if (rc)
213 {
214 if (_this.IsEmpty())
215 func->Call(func, 0, NULL);
216 else
217 func->Call(_this, 0, NULL);
218 }
219
220 func.Dispose();
221 _this.Dispose();
222
223 fThreadIds.erase(id_local);
224
225 if (!HandleException(exception, "thread"))
226 V8::TerminateExecution(fThreadId);
227}
228
229Handle<Value> InterpreterV8::FuncThread(const Arguments& args)
230{
231 if (!args.IsConstructCall())
232 return ThrowException(String::New("Thread must be called as constructor."));
233
234 if (args.Length()!=2 && args.Length()!=3)
235 return ThrowException(String::New("Number of arguments must be two or three."));
236
237 if (!args[0]->IsUint32())
238 return ThrowException(String::New("Argument 0 not an uint32."));
239
240 if (!args[1]->IsFunction())
241 return ThrowException(String::New("Argument 1 not a function."));
242
243 if (args.Length()==3 && !args[2]->IsObject())
244 return ThrowException(String::New("Argument 2 not an object."));
245
246 //if (!args.IsConstructCall())
247 // return Constructor(args);
248
249 const HandleScope handle_scope;
250
251 Handle<Function> handle = Handle<Function>::Cast(args[1]);
252
253 Persistent<Function> func = Persistent<Function>::New(handle);
254 Persistent<Object> _this;
255 if (args.Length()==3)
256 _this = Persistent<Object>::New(args[2]->ToObject());
257
258 const uint32_t ms = args[0]->Uint32Value();
259
260 int id=-2;
261 fThreads.push_back(thread(bind(&InterpreterV8::Thread, this, ref(id), _this, func, ms)));
262 {
263 // Allow the thread to lock, so we can get the thread id.
264 const Unlocker unlock;
265 while (id==-2)
266 usleep(1);
267 }
268
269 Handle<Object> self = args.This();
270
271 self->Set(String::New("id"), Integer::NewFromUnsigned(id), ReadOnly);
272 self->Set(String::New("kill"), FunctionTemplate::New(WrapKill)->GetFunction(), ReadOnly);
273
274 return Undefined();
275}
276
277Handle<Value> InterpreterV8::FuncKill(const Arguments& args)
278{
279 const uint32_t id = args.This()->Get(String::New("id"))->Uint32Value();
280
281 V8::TerminateExecution(id);
282
283 return Boolean::New(fThreadIds.erase(id));
284}
285
286Handle<Value> InterpreterV8::FuncSend(const Arguments& args)
287{
288 if (args.Length()==0)
289 return ThrowException(String::New("Number of arguments must be at least 1."));
290
291 if (!args[0]->IsString())
292 return ThrowException(String::New("Argument 1 must be a string."));
293
294 const String::AsciiValue str(args[0]);
295
296 string command = *str;
297
298 if (command.length()==0)
299 return ThrowException(String::New("Server name empty."));
300
301 if (args.Length()==0)
302 {
303 if (command.find_first_of('/')==string::npos)
304 command += "/";
305 }
306
307 // Escape all string arguments. All others can be kept as they are.
308 for (int i=1; i<args.Length(); i++)
309 {
310 string arg = *String::AsciiValue(args[i]);
311
312 // Escape string
313 if (args[i]->IsString())
314 {
315 boost::replace_all(arg, "\\", "\\\\");
316 boost::replace_all(arg, "'", "\\'");
317 boost::replace_all(arg, "\"", "\\\"");
318 }
319
320 command += " "+arg;
321 }
322
323 try
324 {
325 return Boolean::New(JsSend(command));
326 }
327 catch (const runtime_error &e)
328 {
329 return ThrowException(String::New(e.what()));
330 }
331}
332
333// ==========================================================================
334// State control
335// ==========================================================================
336
337Handle<Value> InterpreterV8::FuncWait(const Arguments& args)
338{
339 if (args.Length()!=2 && args.Length()!=3)
340 return ThrowException(String::New("Number of arguments must be 2 or 3."));
341
342 if (!args[0]->IsString())
343 return ThrowException(String::New("Argument 1 not a string."));
344
345 if (!args[1]->IsInt32() && !args[1]->IsString())
346 return ThrowException(String::New("Argument 2 not an int32 and not a string."));
347
348 if (args.Length()==3 && !args[2]->IsInt32() && !args[2]->IsUndefined())
349 return ThrowException(String::New("Argument 3 not an int32 and not undefined."));
350
351 // Using a Javascript function has the advantage that it is fully
352 // interruptable without the need of C++ code
353
354 const string index = args[1]->IsInt32() ? "s.index" : "s.name";
355 const bool timeout = args.Length()==3 && !args[2]->IsUndefined();
356 const string arg0 = *String::AsciiValue(args[0]);
357 const string state = args[1]->IsString() ? *String::AsciiValue(args[1]) : "";
358 const string arg1 = args[1]->IsString() ? ("\""+state+"\"") : to_string(args[1]->Int32Value());
359
360 if (arg0.find_first_of("\"'")!=string::npos)
361 return ThrowException(String::New("Server name must not contain quotation marks."));
362
363 if (args[1]->IsString())
364 if (state.find_first_of("\"'")!=string::npos)
365 return ThrowException(String::New("State name must not contain quotation marks."));
366
367 string code = "(function(name,state,ms)"
368 "{";
369 if (timeout)
370 code += "var t = new Date();";
371 code += "while (1)"
372 "{"
373 "var s = dim.state(name);"
374 "if(!s)throw new Error('Waiting for state "+arg1+" of server "+arg0+" failed.');"
375 "if(state=="+index+")return true;";
376 if (timeout)
377 code += "if((new Date()-t)>Math.abs(ms))break;";
378
379 code += "v8.sleep();"
380 "}";
381 if (timeout)
382 code += "if(ms>0)throw new Error('Waiting for state "+arg1+" of server "+arg0+" timed out.');";
383 code += "return false;"
384 "})('"+arg0+"',"+arg1;
385 if (timeout)
386 code += "," + (args[2]->IsUndefined()?"undefined":to_string(args[2]->Int32Value()));
387 code += ");";
388
389 return ExecuteInternal(code);
390}
391
392Handle<Value> InterpreterV8::FuncState(const Arguments& args)
393{
394 if (args.Length()!=1)
395 return ThrowException(String::New("Number of arguments must be exactly 1."));
396
397 if (!args[0]->IsString())
398 return ThrowException(String::New("Argument 1 must be a string."));
399
400 // Return state.name/state.index
401
402 const String::AsciiValue str(args[0]);
403
404 const State rc = JsState(*str);
405 if (rc.index<=-256)
406 return Undefined();
407
408 HandleScope handle_scope;
409
410 Handle<Object> obj = Object::New();
411
412 obj->Set(String::New("server"), String::New(*str), ReadOnly);
413 obj->Set(String::New("index"), Integer::New(rc.index), ReadOnly);
414 obj->Set(String::New("name"), String::New(rc.name.c_str()), ReadOnly);
415
416 const Local<Value> date = Date::New(rc.time.JavaDate());
417 if (rc.index>-256 && !date.IsEmpty())
418 obj->Set(String::New("time"), date);
419
420 return handle_scope.Close(obj);
421}
422
423Handle<Value> InterpreterV8::FuncNewState(const Arguments& args)
424{
425 if (args.Length()<1 || args.Length()>3)
426 return ThrowException(String::New("Number of arguments must be 1, 2 or 3."));
427
428 if (!args[0]->IsUint32())
429 return ThrowException(String::New("Argument 1 must be an uint32."));
430 if (args.Length()>1 && !args[1]->IsString())
431 return ThrowException(String::New("Argument 2 must be a string."));
432 if (args.Length()>2 && !args[2]->IsString())
433 return ThrowException(String::New("Argument 3 must be a string."));
434
435 const uint32_t index = args[0]->Int32Value();
436 const string name = *String::AsciiValue(args[1]);
437 const string comment = *String::AsciiValue(args[2]);
438
439 if (index<10 || index>255)
440 return ThrowException(String::New("State must be in the range [10, 255]."));
441
442 if (name.empty())
443 return ThrowException(String::New("State name must not be empty."));
444
445 if (name.find_first_of(':')!=string::npos || name.find_first_of('=')!=string::npos)
446 return ThrowException(String::New("State name must not contain : or =."));
447
448 struct Find : State
449 {
450 Find(int idx, const string &n) : State(idx, n) { }
451 bool operator()(const pair<int, string> &p) { return index==p.first || name==p.second; }
452 };
453
454 if (find_if(fStates.begin(), fStates.end(), Find(index, name))!=fStates.end())
455 {
456 const string what =
457 "State index ["+to_string(index)+"] or name ["+name+"] already defined.";
458
459 return ThrowException(String::New(what.c_str()));
460 }
461
462 return Boolean::New(JsNewState(index, name, comment));
463}
464
465Handle<Value> InterpreterV8::FuncSetState(const Arguments& args)
466{
467 if (args.Length()!=1)
468 return ThrowException(String::New("Number of arguments must be exactly 1."));
469
470 if (!args[0]->IsUint32() && !args[0]->IsString())
471 return ThrowException(String::New("Argument must be an unint32 or a string."));
472
473 int index = -2;
474 if (args[0]->IsUint32())
475 {
476 index = args[0]->Int32Value();
477 }
478 else
479 {
480 const string name = *String::AsciiValue(args[0]);
481 index = JsGetState(name);
482 if (index==-2)
483 return ThrowException(String::New(("State '"+name+"' not found.").c_str()));
484 }
485
486 if (index<10 || index>255)
487 return ThrowException(String::New("State must be in the range [10, 255]."));
488
489 return Boolean::New(JsSetState(index));
490}
491
492Handle<Value> InterpreterV8::FuncGetState(const Arguments& args)
493{
494 if (args.Length()>0)
495 return ThrowException(String::New("getState must not take arguments."));
496
497 const State state = JsGetCurrentState();
498
499 HandleScope handle_scope;
500
501 Handle<Object> rc = Object::New();
502 if (rc.IsEmpty())
503 return Undefined();
504
505 rc->Set(String::New("index"), Integer::New(state.index), ReadOnly);
506 rc->Set(String::New("name"), String::New(state.name.c_str()), ReadOnly);
507 rc->Set(String::New("description"), String::New(state.comment.c_str()), ReadOnly);
508
509 return handle_scope.Close(rc);
510}
511
512Handle<Value> InterpreterV8::FuncGetStates(const Arguments& args)
513{
514 if (args.Length()>1)
515 return ThrowException(String::New("getStates must not take more than one arguments."));
516
517 if (args.Length()==1 && !args[0]->IsString())
518 return ThrowException(String::New("Argument must be a string."));
519
520 const string server = args.Length()==1 ? *String::AsciiValue(args[0]) : "DIM_CONTROL";
521
522 const vector<State> states = JsGetStates(server);
523
524 HandleScope handle_scope;
525
526 Handle<Object> list = Object::New();
527 if (list.IsEmpty())
528 return Undefined();
529
530 for (auto it=states.begin(); it!=states.end(); it++)
531 {
532 Handle<Value> entry = StringObject::New(String::New(it->name.c_str()));
533 if (entry.IsEmpty())
534 return Undefined();
535
536 StringObject::Cast(*entry)->Set(String::New("description"), String::New(it->comment.c_str()), ReadOnly);
537 list->Set(Integer::New(it->index), entry, ReadOnly);
538 }
539
540 return handle_scope.Close(list);
541}
542
543Handle<Value> InterpreterV8::FuncGetDescription(const Arguments& args)
544{
545 if (args.Length()!=1)
546 return ThrowException(String::New("getDescription must take exactly one argument."));
547
548 if (args.Length()==1 && !args[0]->IsString())
549 return ThrowException(String::New("Argument must be a string."));
550
551 const string service = *String::AsciiValue(args[0]);
552
553 const vector<Description> descriptions = JsGetDescription(service);
554 const set<Service> services = JsGetServices();
555
556 auto is=services.begin();
557 for (; is!=services.end(); is++)
558 if (is->name==service)
559 break;
560
561 if (is==services.end())
562 return Undefined();
563
564 HandleScope handle_scope;
565
566 Handle<Object> arr = fTemplateDescription->GetFunction()->NewInstance();//Object::New();
567 if (arr.IsEmpty())
568 return Undefined();
569
570 auto it=descriptions.begin();
571 arr->Set(String::New("name"), String::New(it->name.c_str()), ReadOnly);
572 if (!it->comment.empty())
573 arr->Set(String::New("description"), String::New(it->comment.c_str()), ReadOnly);
574 if (is!=services.end())
575 {
576 arr->Set(String::New("server"), String::New(is->server.c_str()), ReadOnly);
577 arr->Set(String::New("service"), String::New(is->service.c_str()), ReadOnly);
578 arr->Set(String::New("isCommand"), Boolean::New(is->iscmd), ReadOnly);
579 if (!is->format.empty())
580 arr->Set(String::New("format"), String::New(is->format.c_str()), ReadOnly);
581 }
582
583 uint32_t i=0;
584 for (it++; it!=descriptions.end(); it++)
585 {
586 Handle<Object> obj = Object::New();
587 if (obj.IsEmpty())
588 return Undefined();
589
590 if (!it->name.empty())
591 obj->Set(String::New("name"), String::New(it->name.c_str()), ReadOnly);
592 if (!it->comment.empty())
593 obj->Set(String::New("description"), String::New(it->comment.c_str()), ReadOnly);
594 if (!it->unit.empty())
595 obj->Set(String::New("unit"), String::New(it->unit.c_str()), ReadOnly);
596
597 arr->Set(i++, obj);
598 }
599
600 return handle_scope.Close(arr);
601}
602
603Handle<Value> InterpreterV8::FuncGetServices(const Arguments& args)
604{
605 if (args.Length()>2)
606 return ThrowException(String::New("getServices must not take more than two argument."));
607
608 if (args.Length()>=1 && !args[0]->IsString())
609 return ThrowException(String::New("First argument must be a string."));
610
611 if (args.Length()==2 && !args[1]->IsBoolean())
612 return ThrowException(String::New("Second argument must be a boolean."));
613
614 string arg0 = args.Length() ? *String::AsciiValue(args[0]) : "";
615 if (arg0=="*")
616 arg0=="";
617
618 const set<Service> services = JsGetServices();
619
620 HandleScope handle_scope;
621
622 Handle<Array> arr = Array::New();
623 if (arr.IsEmpty())
624 return Undefined();
625
626 uint32_t i=0;
627 for (auto is=services.begin(); is!=services.end(); is++)
628 {
629 if (!arg0.empty() && is->name.find(arg0)!=0)
630 continue;
631
632 if (args.Length()==2 && args[1]->BooleanValue()!=is->iscmd)
633 continue;
634
635 Handle<Object> obj = Object::New();
636 if (obj.IsEmpty())
637 return Undefined();
638
639 obj->Set(String::New("name"), String::New(is->name.c_str()), ReadOnly);
640 obj->Set(String::New("server"), String::New(is->server.c_str()), ReadOnly);
641 obj->Set(String::New("service"), String::New(is->service.c_str()), ReadOnly);
642 obj->Set(String::New("isCommand"), Boolean::New(is->iscmd), ReadOnly);
643 if (!is->format.empty())
644 obj->Set(String::New("format"), String::New(is->format.c_str()), ReadOnly);
645
646 arr->Set(i++, obj);
647 }
648
649 return handle_scope.Close(arr);
650}
651
652// ==========================================================================
653// Internal functions
654// ==========================================================================
655
656
657// The callback that is invoked by v8 whenever the JavaScript 'print'
658// function is called. Prints its arguments on stdout separated by
659// spaces and ending with a newline.
660Handle<Value> InterpreterV8::FuncLog(const Arguments& args)
661{
662 for (int i=0; i<args.Length(); i++)
663 {
664 const String::AsciiValue str(args[i]);
665 if (*str)
666 JsPrint(*str);
667 }
668
669 if (args.Length()==0)
670 JsPrint();
671
672 return Undefined();
673}
674
675Handle<Value> InterpreterV8::FuncAlarm(const Arguments& args)
676{
677 for (int i=0; i<args.Length(); i++)
678 {
679 const String::AsciiValue str(args[i]);
680 if (*str)
681 JsAlarm(*str);
682 }
683
684 if (args.Length()==0)
685 JsAlarm();
686
687 return Undefined();
688}
689
690Handle<Value> InterpreterV8::FuncOut(const Arguments& args)
691{
692 for (int i=0; i<args.Length(); i++)
693 {
694 const String::AsciiValue str(args[i]);
695 if (*str)
696 JsOut(*str);
697 }
698 return Undefined();
699}
700
701Handle<Value> InterpreterV8::FuncWarn(const Arguments& args)
702{
703 for (int i=0; i<args.Length(); i++)
704 {
705 const String::AsciiValue str(args[i]);
706 if (*str)
707 JsWarn(*str);
708 }
709 return Undefined();
710}
711
712// The callback that is invoked by v8 whenever the JavaScript 'load'
713// function is called. Loads, compiles and executes its argument
714// JavaScript file.
715Handle<Value> InterpreterV8::FuncInclude(const Arguments& args)
716{
717 if (args.Length()!=1)
718 return ThrowException(String::New("Number of arguments must be one."));
719
720 if (!args[0]->IsString())
721 return ThrowException(String::New("Argument must be a string."));
722
723 const String::AsciiValue file(args[0]);
724 if (*file == NULL)
725 return ThrowException(String::New("File name missing."));
726
727 if (strlen(*file)==0)
728 return ThrowException(String::New("File name empty."));
729
730 izstream fin(*file);
731 if (!fin)
732 return ThrowException(String::New(errno!=0?strerror(errno):"Insufficient memory for decompression"));
733
734 string buffer;
735 getline(fin, buffer, '\0');
736
737 if ((fin.fail() && !fin.eof()) || fin.bad())
738 return ThrowException(String::New(strerror(errno)));
739
740 if (buffer.length()>1 && buffer[0]=='#' && buffer[1]=='!')
741 buffer.insert(0, "//");
742
743 return ExecuteCode(buffer, *file);
744}
745
746Handle<Value> InterpreterV8::FuncFile(const Arguments& args)
747{
748 if (args.Length()!=1 && args.Length()!=2)
749 return ThrowException(String::New("Number of arguments must be one or two."));
750
751 const String::AsciiValue file(args[0]);
752 if (*file == NULL)
753 return ThrowException(String::New("File name missing"));
754
755 if (args.Length()==2 && !args[1]->IsString())
756 return ThrowException(String::New("Second argument must be a string."));
757
758 const string delim = args.Length()==2 ? *String::AsciiValue(args[1]) : "";
759
760 if (args.Length()==2 && delim.size()!=1)
761 return ThrowException(String::New("Second argument must be a string of length 1."));
762
763 HandleScope handle_scope;
764
765 izstream fin(*file);
766 if (!fin)
767 return ThrowException(String::New(errno!=0?strerror(errno):"Insufficient memory for decompression"));
768
769 if (args.Length()==1)
770 {
771 string buffer;
772 getline(fin, buffer, '\0');
773 if ((fin.fail() && !fin.eof()) || fin.bad())
774 return ThrowException(String::New(strerror(errno)));
775
776 Handle<Value> str = StringObject::New(String::New(buffer.c_str()));
777 StringObject::Cast(*str)->Set(String::New("name"), String::New(*file));
778 return handle_scope.Close(str);
779 }
780
781 Handle<Array> arr = Array::New();
782 if (arr.IsEmpty())
783 return Undefined();
784
785 int i=0;
786 string buffer;
787 while (getline(fin, buffer, delim[0]))
788 arr->Set(i++, String::New(buffer.c_str()));
789
790 if ((fin.fail() && !fin.eof()) || fin.bad())
791 return ThrowException(String::New(strerror(errno)));
792
793 arr->Set(String::New("name"), String::New(*file));
794 arr->Set(String::New("delim"), String::New(delim.c_str(), 1));
795
796 return handle_scope.Close(arr);
797}
798
799// ==========================================================================
800// Mail
801// ==========================================================================
802
803Handle<Value> InterpreterV8::ConstructorMail(const Arguments &args)
804{
805 if (!args.IsConstructCall())
806 return ThrowException(String::New("Mail must be called as constructor"));
807
808 if (args.Length()!=1 || !args[0]->IsString())
809 return ThrowException(String::New("Constructor must be called with a single string as argument"));
810
811 HandleScope handle_scope;
812
813 Handle<Array> rec = Array::New();
814 Handle<Array> att = Array::New();
815 Handle<Array> bcc = Array::New();
816 Handle<Array> cc = Array::New();
817 Handle<Array> txt = Array::New();
818 if (rec.IsEmpty() || att.IsEmpty() || bcc.IsEmpty() || cc.IsEmpty() || txt.IsEmpty())
819 return Undefined();
820
821 Handle<Object> self = args.This();
822
823 self->Set(String::New("subject"), args[0]->ToString(), ReadOnly);
824 self->Set(String::New("recipients"), rec, ReadOnly);
825 self->Set(String::New("attachments"), att, ReadOnly);
826 self->Set(String::New("bcc"), bcc, ReadOnly);
827 self->Set(String::New("cc"), cc, ReadOnly);
828 self->Set(String::New("text"), txt, ReadOnly);
829
830 self->Set(String::New("send"), FunctionTemplate::New(WrapSendMail)->GetFunction(), ReadOnly);
831
832 return handle_scope.Close(self);
833}
834
835vector<string> InterpreterV8::ValueToArray(const Handle<Value> &val, bool only)
836{
837 vector<string> rc;
838
839 Handle<Array> arr = Handle<Array>::Cast(val);
840 for (uint32_t i=0; i<arr->Length(); i++)
841 {
842 Handle<Value> obj = arr->Get(i);
843 if (obj.IsEmpty())
844 continue;
845
846 if (obj->IsNull() || obj->IsUndefined())
847 continue;
848
849 if (only && !obj->IsString())
850 continue;
851
852 rc.push_back(*String::AsciiValue(obj->ToString()));
853 }
854
855 return rc;
856}
857
858Handle<Value> InterpreterV8::FuncSendMail(const Arguments& args)
859{
860 HandleScope handle_scope;
861
862 if (args.Length()>1)
863 return ThrowException(String::New("Only one argument allowed."));
864
865 if (args.Length()==1 && !args[0]->IsBoolean())
866 return ThrowException(String::New("Argument must be a boolean."));
867
868 const bool block = args.Length()==0 || args[0]->BooleanValue();
869
870 const Handle<Value> sub = args.This()->Get(String::New("subject"));
871 const Handle<Value> rec = args.This()->Get(String::New("recipients"));
872 const Handle<Value> txt = args.This()->Get(String::New("text"));
873 const Handle<Value> att = args.This()->Get(String::New("attachments"));
874 const Handle<Value> bcc = args.This()->Get(String::New("bcc"));
875 const Handle<Value> cc = args.This()->Get(String::New("cc"));
876
877 const vector<string> vrec = ValueToArray(rec);
878 const vector<string> vtxt = ValueToArray(txt, false);
879 const vector<string> vatt = ValueToArray(att);
880 const vector<string> vbcc = ValueToArray(bcc);
881 const vector<string> vcc = ValueToArray(cc);
882
883 if (vrec.size()==0)
884 return ThrowException(String::New("At least one valid string is required in 'recipients'."));
885 if (vtxt.size()==0)
886 return ThrowException(String::New("At least one valid string is required in 'text'."));
887
888 const string subject = *String::AsciiValue(sub->ToString());
889
890 FILE *pipe = popen(("from=no-reply@fact-project.org mailx -~ "+vrec[0]).c_str(), "w");
891 if (!pipe)
892 return ThrowException(String::New(strerror(errno)));
893
894 fprintf(pipe, "%s", ("~s"+subject+"\n").c_str());
895 for (auto it=vrec.begin()+1; it<vrec.end(); it++)
896 fprintf(pipe, "%s", ("~t"+*it+"\n").c_str());
897 for (auto it=vbcc.begin(); it<vbcc.end(); it++)
898 fprintf(pipe, "%s", ("~b"+*it+"\n").c_str());
899 for (auto it=vcc.begin(); it<vcc.end(); it++)
900 fprintf(pipe, "%s", ("~c"+*it+"\n").c_str());
901 for (auto it=vatt.begin(); it<vatt.end(); it++)
902 fprintf(pipe, "%s", ("~@"+*it+"\n").c_str()); // Must not contain white spaces
903
904 for (auto it=vtxt.begin(); it<vtxt.end(); it++)
905 fwrite((*it+"\n").c_str(), it->length()+1, 1, pipe);
906
907 fprintf(pipe, "\n---\nsent by dimctrl");
908
909 if (!block)
910 return Undefined();
911
912 const int rc = pclose(pipe);
913
914 const Locker lock;
915 return handle_scope.Close(Integer::New(WEXITSTATUS(rc)));
916}
917
918// ==========================================================================
919// Database
920// ==========================================================================
921
922Handle<Value> InterpreterV8::FuncDbClose(const Arguments &args)
923{
924 void *ptr = External::Unwrap(args.This()->GetInternalField(0));
925 if (!ptr)
926 return Boolean::New(false);
927
928#ifdef HAVE_SQL
929 Database *db = reinterpret_cast<Database*>(ptr);
930 auto it = find(fDatabases.begin(), fDatabases.end(), db);
931 fDatabases.erase(it);
932 delete db;
933#endif
934
935 HandleScope handle_scope;
936
937 args.This()->SetInternalField(0, External::New(0));
938
939 return handle_scope.Close(Boolean::New(true));
940}
941
942Handle<Value> InterpreterV8::FuncDbQuery(const Arguments &args)
943{
944 if (args.Length()==0)
945 return ThrowException(String::New("Arguments expected."));
946
947 void *ptr = External::Unwrap(args.This()->GetInternalField(0));
948 if (!ptr)
949 return Undefined();
950
951 string query;
952 for (int i=0; i<args.Length(); i++)
953 query += string(" ") + *String::AsciiValue(args[i]);
954 query.erase(0, 1);
955
956#ifdef HAVE_SQL
957 try
958 {
959 HandleScope handle_scope;
960
961 Database *db = reinterpret_cast<Database*>(ptr);
962
963 const mysqlpp::StoreQueryResult res = db->query(query).store();
964
965 Handle<Array> ret = Array::New();
966 if (ret.IsEmpty())
967 return Undefined();
968
969 ret->Set(String::New("table"), String::New(res.table()), ReadOnly);
970 ret->Set(String::New("query"), String::New(query.c_str()), ReadOnly);
971
972 Handle<Array> cols = Array::New();
973 if (cols.IsEmpty())
974 return Undefined();
975
976 int irow=0;
977 for (vector<mysqlpp::Row>::const_iterator it=res.begin(); it<res.end(); it++)
978 {
979 Handle<Object> row = Object::New();
980 if (row.IsEmpty())
981 return Undefined();
982
983 const mysqlpp::FieldNames *list = it->field_list().list;
984
985 for (size_t i=0; i<it->size(); i++)
986 {
987 const Handle<Value> name = String::New((*list)[i].c_str());
988 if (irow==0)
989 cols->Set(i, name);
990
991 if ((*it)[i].is_null())
992 {
993 row->Set(name, Undefined(), ReadOnly);
994 continue;
995 }
996
997 const string sql_type = (*it)[i].type().sql_name();
998
999 const bool uns = sql_type.find("UNSIGNED")==string::npos;
1000
1001 if (sql_type.find("BIGINT")!=string::npos)
1002 {
1003 if (uns)
1004 {
1005 const uint64_t val = (uint64_t)(*it)[i];
1006 if (val>UINT32_MAX)
1007 row->Set(name, Number::New(val), ReadOnly);
1008 else
1009 row->Set(name, Integer::NewFromUnsigned(val), ReadOnly);
1010 }
1011 else
1012 {
1013 const int64_t val = (int64_t)(*it)[i];
1014 if (val<INT32_MIN || val>INT32_MAX)
1015 row->Set(name, Number::New(val), ReadOnly);
1016 else
1017 row->Set(name, Integer::NewFromUnsigned(val), ReadOnly);
1018 }
1019 continue;
1020 }
1021
1022 // 32 bit
1023 if (sql_type.find("INT")!=string::npos)
1024 {
1025 if (uns)
1026 row->Set(name, Integer::NewFromUnsigned((uint32_t)(*it)[i]), ReadOnly);
1027 else
1028 row->Set(name, Integer::New((int32_t)(*it)[i]), ReadOnly);
1029 continue;
1030 }
1031
1032 if (sql_type.find("BOOL")!=string::npos )
1033 {
1034 row->Set(name, Boolean::New((bool)(*it)[i]), ReadOnly);
1035 continue;
1036 }
1037
1038 if (sql_type.find("FLOAT")!=string::npos)
1039 {
1040 ostringstream val;
1041 val << setprecision(7) << (float)(*it)[i];
1042 row->Set(name, Number::New(stod(val.str())), ReadOnly);
1043 continue;
1044
1045 }
1046 if (sql_type.find("DOUBLE")!=string::npos)
1047 {
1048 row->Set(name, Number::New((double)(*it)[i]), ReadOnly);
1049 continue;
1050 }
1051
1052 if (sql_type.find("CHAR")!=string::npos ||
1053 sql_type.find("TEXT")!=string::npos)
1054 {
1055 row->Set(name, String::New((const char*)(*it)[i]), ReadOnly);
1056 continue;
1057 }
1058
1059 time_t date = 0;
1060 if (sql_type.find("TIMESTAMP")!=string::npos)
1061 date = mysqlpp::Time((*it)[i]);
1062
1063 if (sql_type.find("DATETIME")!=string::npos)
1064 date = mysqlpp::DateTime((*it)[i]);
1065
1066 if (sql_type.find(" DATE ")!=string::npos)
1067 date = mysqlpp::Date((*it)[i]);
1068
1069 if (date>0)
1070 {
1071 // It is important to catch the exception thrown
1072 // by Date::New in case of thread termination!
1073 const Local<Value> val = Date::New(date*1000);
1074 if (val.IsEmpty())
1075 return Undefined();
1076
1077 row->Set(name, val, ReadOnly);
1078 }
1079 }
1080
1081 ret->Set(irow++, row);
1082 }
1083
1084 if (irow>0)
1085 ret->Set(String::New("cols"), cols, ReadOnly);
1086
1087 return handle_scope.Close(ret);
1088 }
1089 catch (const exception &e)
1090 {
1091 return ThrowException(String::New(e.what()));
1092 }
1093#endif
1094}
1095
1096Handle<Value> InterpreterV8::FuncDatabase(const Arguments &args)
1097{
1098 if (!args.IsConstructCall())
1099 return ThrowException(String::New("Database must be called as constructor."));
1100
1101 if (args.Length()!=1)
1102 return ThrowException(String::New("Number of arguments must be 1."));
1103
1104 if (!args[0]->IsString())
1105 return ThrowException(String::New("Argument 1 not a string."));
1106
1107#ifdef HAVE_SQL
1108 try
1109 {
1110 HandleScope handle_scope;
1111
1112 //if (!args.IsConstructCall())
1113 // return Constructor(fTemplateDatabase, args);
1114
1115 Database *db = new Database(*String::AsciiValue(args[0]));
1116 fDatabases.push_back(db);
1117
1118 Handle<Object> self = args.This();
1119 self->Set(String::New("user"), String::New(db->user.c_str()), ReadOnly);
1120 self->Set(String::New("server"), String::New(db->server.c_str()), ReadOnly);
1121 self->Set(String::New("database"), String::New(db->db.c_str()), ReadOnly);
1122 self->Set(String::New("port"), db->port==0?Undefined():Integer::NewFromUnsigned(db->port), ReadOnly);
1123 self->Set(String::New("query"), FunctionTemplate::New(WrapDbQuery)->GetFunction(), ReadOnly);
1124 self->Set(String::New("close"), FunctionTemplate::New(WrapDbClose)->GetFunction(), ReadOnly);
1125 self->SetInternalField(0, External::New(db));
1126
1127 return handle_scope.Close(self);
1128 }
1129 catch (const exception &e)
1130 {
1131 return ThrowException(String::New(e.what()));
1132 }
1133#endif
1134}
1135
1136// ==========================================================================
1137// Services
1138// ==========================================================================
1139
1140Handle<Value> InterpreterV8::Convert(char type, const char* &ptr)
1141{
1142 // Dim values are always unsigned per (FACT++) definition
1143 switch (type)
1144 {
1145 case 'F':
1146 {
1147 // Remove the "imprecision" effect coming from casting a float to
1148 // a double and then showing it with double precision
1149 ostringstream val;
1150 val << setprecision(7) << *reinterpret_cast<const float*>(ptr);
1151 ptr += 4;
1152 return Number::New(stod(val.str()));
1153 }
1154 case 'D': { Handle<Value> v=Number::New(*reinterpret_cast<const double*>(ptr)); ptr+=8; return v; }
1155 case 'I':
1156 case 'L': { Handle<Value> v=Integer::NewFromUnsigned(*reinterpret_cast<const uint32_t*>(ptr)); ptr += 4; return v; }
1157 case 'X':
1158 {
1159 const int64_t val = *reinterpret_cast<const int64_t*>(ptr);
1160 ptr += 8;
1161 if (val>=0 && val<=UINT32_MAX)
1162 return Integer::NewFromUnsigned(val);
1163 if (val>=INT32_MIN && val<0)
1164 return Integer::New(val);
1165 return Number::New(val);
1166 }
1167 case 'S': { Handle<Value> v=Integer::NewFromUnsigned(*reinterpret_cast<const uint16_t*>(ptr)); ptr += 2; return v; }
1168 case 'C': { Handle<Value> v=Integer::NewFromUnsigned((uint16_t)*reinterpret_cast<const uint8_t*>(ptr)); ptr += 1; return v; }
1169 }
1170 return Undefined();
1171}
1172
1173Handle<Value> InterpreterV8::FuncClose(const Arguments &args)
1174{
1175 HandleScope handle_scope;
1176
1177 //const void *ptr = Local<External>::Cast(args.Holder()->GetInternalField(0))->Value();
1178
1179 const String::AsciiValue str(args.This()->Get(String::New("name")));
1180
1181 const auto it = fReverseMap.find(*str);
1182 if (it!=fReverseMap.end())
1183 {
1184 it->second.Dispose();
1185 fReverseMap.erase(it);
1186 }
1187
1188 args.This()->Set(String::New("isOpen"), Boolean::New(false), ReadOnly);
1189
1190 return handle_scope.Close(Boolean::New(JsUnsubscribe(*str)));
1191}
1192
1193Handle<Value> InterpreterV8::ConvertEvent(const EventImp *evt, uint64_t counter, const char *str)
1194{
1195 const vector<Description> vec = JsDescription(str);
1196
1197 Handle<Object> ret = fTemplateEvent->GetFunction()->NewInstance();//Object::New();
1198 if (ret.IsEmpty())
1199 return Undefined();
1200
1201 const Local<Value> date = Date::New(evt->GetJavaDate());
1202 if (date.IsEmpty())
1203 return Undefined();
1204
1205 ret->Set(String::New("name"), String::New(str), ReadOnly);
1206 ret->Set(String::New("format"), String::New(evt->GetFormat().c_str()), ReadOnly);
1207 ret->Set(String::New("qos"), Integer::New(evt->GetQoS()), ReadOnly);
1208 ret->Set(String::New("size"), Integer::New(evt->GetSize()), ReadOnly);
1209 ret->Set(String::New("counter"), Integer::New(counter), ReadOnly);
1210 if (evt->GetJavaDate()>0)
1211 ret->Set(String::New("time"), date, ReadOnly);
1212
1213 // If names are available data will also be provided as an
1214 // object. If an empty event was received, but names are available,
1215 // the object will be empty. Otherwise 'obj' will be undefined.
1216 // obj===undefined: no data received
1217 // obj!==undefined, length==0: names for event available
1218 // obj!==undefined, obj.length>0: names available, data received
1219 Handle<Object> named = Object::New();
1220 if (vec.size()>0)
1221 ret->Set(String::New("obj"), named, ReadOnly);
1222
1223 // If no event was received (usually a disconnection event in
1224 // the context of FACT++), no data is returned
1225 if (evt->IsEmpty())
1226 return ret;
1227
1228 // If valid data was received, but the size was zero, then
1229 // null is returned as data
1230 // data===undefined: no data received
1231 // data===null: event received, but no data
1232 // data.length>0: event received, contains data
1233 if (evt->GetSize()==0 || evt->GetFormat().empty())
1234 {
1235 ret->Set(String::New("data"), Null(), ReadOnly);
1236 return ret;
1237 }
1238
1239 typedef boost::char_separator<char> separator;
1240 const boost::tokenizer<separator> tokenizer(evt->GetFormat(), separator(";:"));
1241
1242 const vector<string> tok(tokenizer.begin(), tokenizer.end());
1243
1244 Handle<Object> arr = tok.size()>1 ? Array::New() : ret;
1245 if (arr.IsEmpty())
1246 return Undefined();
1247
1248 const char *ptr = evt->GetText();
1249 const char *end = evt->GetText()+evt->GetSize();
1250
1251 try
1252 {
1253 size_t pos = 1;
1254 for (auto it=tok.begin(); it<tok.end() && ptr<end; it++, pos++)
1255 {
1256 char type = (*it)[0];
1257 it++;
1258
1259 string name = pos<vec.size() ? vec[pos].name : "";
1260 if (tok.size()==1)
1261 name = "data";
1262
1263 // Get element size
1264 uint32_t sz = 1;
1265 switch (type)
1266 {
1267 case 'X':
1268 case 'D': sz = 8; break;
1269 case 'F':
1270 case 'I':
1271 case 'L': sz = 4; break;
1272 case 'S': sz = 2; break;
1273 case 'C': sz = 1; break;
1274 }
1275
1276 // Check if no number is attached if the size of the
1277 // received data is consistent with the format string
1278 if (it==tok.end() && (end-ptr)%sz>0)
1279 return Exception::Error(String::New(("Number of received bytes ["+to_string(evt->GetSize())+"] does not match format ["+evt->GetFormat()+"]").c_str()));
1280
1281 // Check if format has a number attached.
1282 // If no number is attached calculate number of elements
1283 const uint32_t cnt = it==tok.end() ? (end-ptr)/sz : stoi(it->c_str());
1284
1285 // is_str: Array of type C but unknown size (String)
1286 // is_one: Array of known size, but size is 1 (I:1)
1287 const bool is_str = type=='C' && it==tok.end();
1288 const bool is_one = cnt==1 && it!=tok.end();
1289
1290 Handle<Value> v;
1291
1292 if (is_str)
1293 v = String::New(ptr);
1294 if (is_one)
1295 v = Convert(type, ptr);
1296
1297 // Array of known (I:5) or unknown size (I), but no string
1298 if (!is_str && !is_one)
1299 {
1300 Handle<Object> a = Array::New(cnt);
1301 if (a.IsEmpty())
1302 return Undefined();
1303
1304 for (uint32_t i=0; i<cnt; i++)
1305 a->Set(i, Convert(type, ptr));
1306
1307 v = a;
1308 }
1309
1310 if (tok.size()>1)
1311 arr->Set(pos-1, v);
1312 else
1313 ret->Set(String::New("data"), v, ReadOnly);
1314
1315 if (!name.empty())
1316 {
1317 const Handle<String> n = String::New(name.c_str());
1318 named->Set(n, v);
1319 }
1320 }
1321
1322 if (tok.size()>1)
1323 ret->Set(String::New("data"), arr, ReadOnly);
1324
1325 return ret;
1326 }
1327 catch (...)
1328 {
1329 return Exception::Error(String::New(("Format string conversion '"+evt->GetFormat()+"' failed.").c_str()));
1330 }
1331}
1332/*
1333Handle<Value> InterpreterV8::FuncGetData(const Arguments &args)
1334{
1335 HandleScope handle_scope;
1336
1337 const String::AsciiValue str(args.Holder()->Get(String::New("name")));
1338
1339 const pair<uint64_t, EventImp *> p = JsGetEvent(*str);
1340
1341 const EventImp *evt = p.second;
1342 if (!evt)
1343 return Undefined();
1344
1345 //if (counter==cnt)
1346 // return info.Holder();//Holder()->Get(String::New("data"));
1347
1348 Handle<Value> ret = ConvertEvent(evt, p.first, *str);
1349 return ret->IsNativeError() ? ThrowException(ret) : handle_scope.Close(ret);
1350}
1351*/
1352Handle<Value> InterpreterV8::FuncGetData(const Arguments &args)
1353{
1354 if (args.Length()>2)
1355 return ThrowException(String::New("Number of arguments must not be greater than 2."));
1356
1357 if (args.Length()>=1 && !args[0]->IsInt32() && !args[0]->IsNull())
1358 return ThrowException(String::New("Argument 1 not an uint32."));
1359
1360 if (args.Length()==2 && !args[1]->IsBoolean())
1361 return ThrowException(String::New("Argument 2 not a boolean."));
1362
1363 // Using a Javascript function has the advantage that it is fully
1364 // interruptable without the need of C++ code
1365 const bool null = args.Length()>=1 && args[0]->IsNull();
1366 const int32_t timeout = args.Length()>=1 ? args[0]->Int32Value() : 0;
1367 const bool named = args.Length()<2 || args[1]->BooleanValue();
1368
1369 HandleScope handle_scope;
1370
1371 const Handle<String> data = String::New("data");
1372 const Handle<String> object = String::New("obj");
1373
1374 const String::AsciiValue name(args.Holder()->Get(String::New("name")));
1375
1376 TryCatch exception;
1377
1378 Time t;
1379 while (!exception.HasCaught())
1380 {
1381 const pair<uint64_t, EventImp *> p = JsGetEvent(*name);
1382
1383 const EventImp *evt = p.second;
1384 if (evt)
1385 {
1386 const Handle<Value> val = ConvertEvent(evt, p.first, *name);
1387 if (val->IsNativeError())
1388 return ThrowException(val);
1389
1390 // Protect against the return of an exception
1391 if (val->IsObject())
1392 {
1393 const Handle<Object> event = val->ToObject();
1394 const Handle<Value> obj = event->Get(named?object:data);
1395 if (!obj.IsEmpty())
1396 {
1397 if (!named)
1398 {
1399 // No names (no 'obj'), but 'data'
1400 if (!obj->IsUndefined())
1401 return handle_scope.Close(val);
1402 }
1403 else
1404 {
1405 // Has names and data was received?
1406 if (obj->IsObject() && obj->ToObject()->GetOwnPropertyNames()->Length()>0)
1407 return handle_scope.Close(val);
1408 }
1409 }
1410 }
1411 }
1412
1413 if (args.Length()==0)
1414 break;
1415
1416 if (!null && Time()-t>=boost::posix_time::milliseconds(abs(timeout)))
1417 break;
1418
1419 // Theoretically, the CPU usage can be reduced by maybe a factor
1420 // of four using a larger value, but this also means that the
1421 // JavaScript is locked for a longer time.
1422 const Unlocker unlock;
1423 usleep(1000);
1424 }
1425
1426 // This hides the location of the exception, which is wanted.
1427 if (exception.HasCaught())
1428 return exception.ReThrow();
1429
1430 if (timeout<0)
1431 return Undefined();
1432
1433 const string str = "Waiting for a valid event of "+string(*name)+" timed out.";
1434 return ThrowException(String::New(str.c_str()));
1435}
1436
1437
1438// This is a callback from the RemoteControl piping event handling
1439// to the java script ---> in test phase!
1440void InterpreterV8::JsHandleEvent(const EventImp &evt, uint64_t cnt, const string &service)
1441{
1442 const Locker locker;
1443
1444 if (fThreadId<0)
1445 return;
1446
1447 const auto it = fReverseMap.find(service);
1448 if (it==fReverseMap.end())
1449 return;
1450
1451 const HandleScope handle_scope;
1452
1453 Handle<Object> obj = it->second;
1454
1455 obj->CreationContext()->Enter();
1456
1457 const Handle<String> onchange = String::New("onchange");
1458 if (!obj->Has(onchange))
1459 return;
1460
1461 const Handle<Value> val = obj->Get(onchange);
1462 if (!val->IsFunction())
1463 return;
1464
1465 // -------------------------------------------------------------------
1466
1467 TryCatch exception;
1468
1469 const int id = V8::GetCurrentThreadId();
1470 fThreadIds.insert(id);
1471
1472 Handle<Value> ret = ConvertEvent(&evt, cnt, service.c_str());
1473 if (ret->IsObject())
1474 Handle<Function>::Cast(val)->Call(obj, 1, &ret);
1475
1476 fThreadIds.erase(id);
1477
1478 if (!HandleException(exception, "Service.onchange"))
1479 V8::TerminateExecution(fThreadId);
1480
1481 if (ret->IsNativeError())
1482 {
1483 JsException(service+".onchange callback - "+*String::AsciiValue(ret));
1484 V8::TerminateExecution(fThreadId);
1485 }
1486}
1487
1488Handle<Value> InterpreterV8::OnChangeSet(Local<String> prop, Local<Value> value, const AccessorInfo &)
1489{
1490 // Returns the value if the setter intercepts the request. Otherwise, returns an empty handle.
1491 const string server = *String::AsciiValue(prop);
1492 auto it = fStateCallbacks.find(server);
1493
1494 if (it!=fStateCallbacks.end())
1495 {
1496 it->second.Dispose();
1497 fStateCallbacks.erase(it);
1498 }
1499
1500 if (value->IsFunction())
1501 fStateCallbacks[server] = Persistent<Object>::New(value->ToObject());
1502
1503 return Handle<Value>();
1504}
1505
1506void InterpreterV8::JsHandleState(const std::string &server, const State &state)
1507{
1508 const Locker locker;
1509
1510 if (fThreadId<0)
1511 return;
1512
1513 auto it = fStateCallbacks.find(server);
1514 if (it==fStateCallbacks.end())
1515 {
1516 it = fStateCallbacks.find("*");
1517 if (it==fStateCallbacks.end())
1518 return;
1519 }
1520
1521 const HandleScope handle_scope;
1522
1523 it->second->CreationContext()->Enter();
1524
1525 // -------------------------------------------------------------------
1526
1527 Handle<ObjectTemplate> obj = ObjectTemplate::New();
1528 obj->Set(String::New("server"), String::New(server.c_str()), ReadOnly);
1529
1530 if (state.index>-256)
1531 {
1532 obj->Set(String::New("index"), Integer::New(state.index), ReadOnly);
1533 obj->Set(String::New("name"), String::New(state.name.c_str()), ReadOnly);
1534 obj->Set(String::New("comment"), String::New(state.comment.c_str()), ReadOnly);
1535 const Local<Value> date = Date::New(state.time.JavaDate());
1536 if (!date.IsEmpty())
1537 obj->Set(String::New("time"), date);
1538 }
1539
1540 // -------------------------------------------------------------------
1541
1542 TryCatch exception;
1543
1544 const int id = V8::GetCurrentThreadId();
1545 fThreadIds.insert(id);
1546
1547 Handle<Value> args[] = { obj->NewInstance() };
1548 Handle<Function> fun = Handle<Function>(Function::Cast(*it->second));
1549 fun->Call(fun, 1, args);
1550
1551 fThreadIds.erase(id);
1552
1553 if (!HandleException(exception, "dim.onchange"))
1554 V8::TerminateExecution(fThreadId);
1555}
1556
1557// ==========================================================================
1558// Interrupt handling
1559// ==========================================================================
1560
1561Handle<Value> InterpreterV8::FuncSetInterrupt(const Arguments &args)
1562{
1563 if (args.Length()!=1)
1564 return ThrowException(String::New("Number of arguments must be 1."));
1565
1566 if (!args[0]->IsNull() && !args[0]->IsUndefined() && !args[0]->IsFunction())
1567 return ThrowException(String::New("Argument not a function, null or undefined."));
1568
1569 if (args[0]->IsNull() || args[0]->IsUndefined())
1570 {
1571 fInterruptCallback.Dispose();
1572 return Undefined();
1573 }
1574
1575 // Returns the value if the setter intercepts the request. Otherwise, returns an empty handle.
1576 fInterruptCallback = Persistent<Object>::New(args[0]->ToObject());
1577 return Undefined();
1578}
1579
1580Handle<Value> InterpreterV8::HandleInterruptImp(string str, uint64_t time)
1581{
1582 if (fInterruptCallback.IsEmpty())
1583 return Handle<Value>();
1584
1585 const size_t p = str.find_last_of('\n');
1586
1587 const string usr = p==string::npos?"":str.substr(p+1);
1588
1589 string irq = p==string::npos?str:str.substr(0, p);
1590 const map<string,string> data = Tools::Split(irq, true);
1591
1592 Local<Value> irq_str = String::New(irq.c_str());
1593 Local<Value> usr_str = String::New(usr.c_str());
1594 Local<Value> date = Date::New(time);
1595 Handle<Object> arr = Array::New(data.size());
1596
1597 if (date.IsEmpty() || arr.IsEmpty())
1598 return Handle<Value>();
1599
1600 for (auto it=data.begin(); it!=data.end(); it++)
1601 {
1602 cout << "|" << it->first << "=" << it->second << "|" << endl;
1603 arr->Set(String::New(it->first.c_str()), String::New(it->second.c_str()));
1604 }
1605
1606 Handle<Value> args[] = { irq_str, arr, date, usr_str };
1607 Handle<Function> fun = Handle<Function>(Function::Cast(*fInterruptCallback));
1608
1609 return fun->Call(fun, 4, args);
1610}
1611
1612int InterpreterV8::JsHandleInterrupt(const EventImp &evt)
1613{
1614 const Locker locker;
1615
1616 if (fThreadId<0)
1617 return -42;
1618
1619 const HandleScope handle_scope;
1620
1621 fInterruptCallback->CreationContext()->Enter();
1622
1623 // -------------------------------------------------------------------
1624
1625 TryCatch exception;
1626
1627 const int id = V8::GetCurrentThreadId();
1628 fThreadIds.insert(id);
1629
1630 const Handle<Value> val = HandleInterruptImp(evt.GetString(), evt.GetJavaDate());
1631
1632 fThreadIds.erase(id);
1633
1634 const int rc = !val.IsEmpty() && val->IsInt32() ? val->Int32Value() : 0;
1635
1636 if (!HandleException(exception, "interrupt"))
1637 V8::TerminateExecution(fThreadId);
1638
1639 return rc<10 || rc>255 ? -42 : rc;
1640}
1641
1642Handle<Value> InterpreterV8::FuncTriggerInterrupt(const Arguments &args)
1643{
1644 string data;
1645 for (int i=0; i<args.Length(); i++)
1646 {
1647 const String::AsciiValue str(args[i]);
1648
1649 if (string(*str).find_first_of('\n')!=string::npos)
1650 return ThrowException(String::New("No argument must contain line breaks."));
1651
1652 if (!*str)
1653 continue;
1654
1655 data += *str;
1656 data += ' ';
1657 }
1658
1659 HandleScope handle_scope;
1660
1661 const Handle<Value> rc = HandleInterruptImp(Tools::Trim(data), Time().JavaDate());
1662 return handle_scope.Close(rc);
1663}
1664
1665// ==========================================================================
1666// Class 'Subscription'
1667// ==========================================================================
1668
1669Handle<Value> InterpreterV8::FuncSubscription(const Arguments &args)
1670{
1671 if (args.Length()!=1 && args.Length()!=2)
1672 return ThrowException(String::New("Number of arguments must be one or two."));
1673
1674 if (!args[0]->IsString())
1675 return ThrowException(String::New("Argument 1 must be a string."));
1676
1677 if (args.Length()==2 && !args[1]->IsFunction())
1678 return ThrowException(String::New("Argument 2 must be a function."));
1679
1680 const String::AsciiValue str(args[0]);
1681
1682 if (!args.IsConstructCall())
1683 {
1684 const auto it = fReverseMap.find(*str);
1685 if (it!=fReverseMap.end())
1686 return it->second;
1687
1688 return Undefined();
1689 }
1690
1691 const HandleScope handle_scope;
1692
1693 Handle<Object> self = args.This();
1694 self->Set(String::New("get"), FunctionTemplate::New(WrapGetData)->GetFunction(), ReadOnly);
1695 self->Set(String::New("close"), FunctionTemplate::New(WrapClose)->GetFunction(), ReadOnly);
1696 self->Set(String::New("name"), String::New(*str), ReadOnly);
1697 self->Set(String::New("isOpen"), Boolean::New(true));
1698
1699 if (args.Length()==2)
1700 self->Set(String::New("onchange"), args[1]);
1701
1702 fReverseMap[*str] = Persistent<Object>::New(self);
1703
1704 void *ptr = JsSubscribe(*str);
1705 if (ptr==0)
1706 return ThrowException(String::New(("Subscription to '"+string(*str)+"' already exists.").c_str()));
1707
1708 self->SetInternalField(0, External::New(ptr));
1709
1710 return Undefined();
1711
1712 // Persistent<Object> p = Persistent<Object>::New(obj->NewInstance());
1713 // obj.MakeWeak((void*)1, Cleanup);
1714 // return obj;
1715}
1716
1717// ==========================================================================
1718// Astrometry
1719// ==========================================================================
1720#ifdef HAVE_NOVA
1721
1722double InterpreterV8::GetDataMember(const Arguments &args, const char *name)
1723{
1724 return args.This()->Get(String::New(name))->NumberValue();
1725}
1726
1727Handle<Value> InterpreterV8::CalcDist(const Arguments &args, const bool local)
1728{
1729 if (args.Length()!=2)
1730 return ThrowException(String::New("dist must not be called with two arguments."));
1731
1732 if (!args[0]->IsObject() || !args[1]->IsObject())
1733 return ThrowException(String::New("at least one argument not an object."));
1734
1735 HandleScope handle_scope;
1736
1737 Handle<Object> obj[2] =
1738 {
1739 Handle<Object>::Cast(args[0]),
1740 Handle<Object>::Cast(args[1])
1741 };
1742
1743 const Handle<String> s_theta = String::New(local?"zd":"dec"); // was: zd
1744 const Handle<String> s_phi = String::New(local?"az":"ra"); // was: az
1745
1746 const double conv_t = M_PI/180;
1747 const double conv_p = local ? -M_PI/180 : M_PI/12;
1748 const double offset = local ? 0 : M_PI;
1749
1750 const double theta0 = offset - obj[0]->Get(s_theta)->NumberValue() * conv_t;
1751 const double phi0 = obj[0]->Get(s_phi )->NumberValue() * conv_p;
1752 const double theta1 = offset - obj[1]->Get(s_theta)->NumberValue() * conv_t;
1753 const double phi1 = obj[1]->Get(s_phi )->NumberValue() * conv_p;
1754
1755 if (!finite(theta0) || !finite(theta1) || !finite(phi0) || !finite(phi1))
1756 return ThrowException(String::New("some values not valid or not finite."));
1757
1758 /*
1759 const double x0 = sin(zd0) * cos(az0); // az0 -= az0
1760 const double y0 = sin(zd0) * sin(az0); // az0 -= az0
1761 const double z0 = cos(zd0);
1762
1763 const double x1 = sin(zd1) * cos(az1); // az1 -= az0
1764 const double y1 = sin(zd1) * sin(az1); // az1 -= az0
1765 const double z1 = cos(zd1);
1766
1767 const double res = acos(x0*x1 + y0*y1 + z0*z1) * 180/M_PI;
1768 */
1769
1770 // cos(az1-az0) = cos(az1)*cos(az0) + sin(az1)*sin(az0)
1771
1772 const double x = sin(theta0) * sin(theta1) * cos(phi1-phi0);
1773 const double y = cos(theta0) * cos(theta1);
1774
1775 const double res = acos(x + y) * 180/M_PI;
1776
1777 return handle_scope.Close(Number::New(res));
1778}
1779
1780Handle<Value> InterpreterV8::LocalDist(const Arguments &args)
1781{
1782 return CalcDist(args, true);
1783}
1784
1785Handle<Value> InterpreterV8::SkyDist(const Arguments &args)
1786{
1787 return CalcDist(args, false);
1788}
1789
1790Handle<Value> InterpreterV8::MoonDisk(const Arguments &args)
1791{
1792 if (args.Length()>1)
1793 return ThrowException(String::New("disk must not be called with more than one argument."));
1794
1795 const uint64_t v = uint64_t(args[0]->NumberValue());
1796 const Time utc = args.Length()==0 ? Time() : Time(v/1000, v%1000);
1797
1798 return Number::New(Nova::GetLunarDisk(utc.JD()));
1799}
1800
1801Handle<Value> InterpreterV8::LocalToSky(const Arguments &args)
1802{
1803 if (args.Length()>1)
1804 return ThrowException(String::New("toSky must not be called with more than one argument."));
1805
1806 if (args.Length()==1 && !args[0]->IsDate())
1807 return ThrowException(String::New("Argument must be a Date"));
1808
1809 Nova::ZdAzPosn hrz;
1810 hrz.zd = GetDataMember(args, "zd");
1811 hrz.az = GetDataMember(args, "az");
1812
1813 if (!finite(hrz.zd) || !finite(hrz.az))
1814 return ThrowException(String::New("zd and az must be finite."));
1815
1816 HandleScope handle_scope;
1817
1818 const Local<Value> date =
1819 args.Length()==0 ? Date::New(Time().JavaDate()) : args[0];
1820 if (date.IsEmpty())
1821 return Undefined();
1822
1823 const uint64_t v = uint64_t(date->NumberValue());
1824 const Time utc(v/1000, v%1000);
1825
1826 const Nova::EquPosn equ = Nova::GetEquFromHrz(hrz, utc.JD());
1827
1828 // -----------------------------
1829
1830 Handle<Value> arg[] = { Number::New(equ.ra/15), Number::New(equ.dec), date };
1831 return handle_scope.Close(fTemplateSky->GetFunction()->NewInstance(3, arg));
1832}
1833
1834Handle<Value> InterpreterV8::SkyToLocal(const Arguments &args)
1835{
1836 if (args.Length()>1)
1837 return ThrowException(String::New("toLocal must not be called with more than one argument."));
1838
1839 if (args.Length()==1 && !args[0]->IsDate())
1840 return ThrowException(String::New("Argument must be a Date"));
1841
1842 Nova::EquPosn equ;
1843 equ.ra = GetDataMember(args, "ra")*15;
1844 equ.dec = GetDataMember(args, "dec");
1845
1846 if (!finite(equ.ra) || !finite(equ.dec))
1847 return ThrowException(String::New("Ra and dec must be finite."));
1848
1849 HandleScope handle_scope;
1850
1851 const Local<Value> date =
1852 args.Length()==0 ? Date::New(Time().JavaDate()) : args[0];
1853 if (date.IsEmpty())
1854 return Undefined();
1855
1856 const uint64_t v = uint64_t(date->NumberValue());
1857 const Time utc(v/1000, v%1000);
1858
1859 const Nova::ZdAzPosn hrz = Nova::GetHrzFromEqu(equ, utc.JD());
1860
1861 Handle<Value> arg[] = { Number::New(hrz.zd), Number::New(hrz.az), date };
1862 return handle_scope.Close(fTemplateLocal->GetFunction()->NewInstance(3, arg));
1863}
1864
1865Handle<Value> InterpreterV8::MoonToLocal(const Arguments &args)
1866{
1867 if (args.Length()>0)
1868 return ThrowException(String::New("toLocal must not be called with arguments."));
1869
1870 Nova::EquPosn equ;
1871 equ.ra = GetDataMember(args, "ra")*15;
1872 equ.dec = GetDataMember(args, "dec");
1873
1874 if (!finite(equ.ra) || !finite(equ.dec))
1875 return ThrowException(String::New("ra and dec must be finite."));
1876
1877 HandleScope handle_scope;
1878
1879 const Local<Value> date = args.This()->Get(String::New("time"));
1880 if (date.IsEmpty() || date->IsUndefined() )
1881 return Undefined();
1882
1883 const uint64_t v = uint64_t(date->NumberValue());
1884 const Time utc(v/1000, v%1000);
1885
1886 const Nova::ZdAzPosn hrz = Nova::GetHrzFromEqu(equ, utc.JD());
1887
1888 Handle<Value> arg[] = { Number::New(hrz.zd), Number::New(hrz.az), date };
1889 return handle_scope.Close(fTemplateLocal->GetFunction()->NewInstance(3, arg));
1890}
1891
1892Handle<Value> InterpreterV8::ConstructorMoon(const Arguments &args)
1893{
1894 if (args.Length()>1)
1895 return ThrowException(String::New("Moon constructor must not be called with more than one argument."));
1896
1897 if (args.Length()==1 && !args[0]->IsDate())
1898 return ThrowException(String::New("Argument must be a Date"));
1899
1900 HandleScope handle_scope;
1901
1902 const Local<Value> date =
1903 args.Length()==0 ? Date::New(Time().JavaDate()) : args[0];
1904 if (date.IsEmpty())
1905 return Undefined();
1906
1907 const uint64_t v = uint64_t(date->NumberValue());
1908 const Time utc(v/1000, v%1000);
1909
1910 const Nova::EquPosn equ = Nova::GetLunarEquCoords(utc.JD(), 0.01);
1911
1912 // ----------------------------
1913
1914 if (!args.IsConstructCall())
1915 return handle_scope.Close(Constructor(args));
1916
1917 Handle<Function> function =
1918 FunctionTemplate::New(MoonToLocal)->GetFunction();
1919 if (function.IsEmpty())
1920 return Undefined();
1921
1922 Handle<Object> self = args.This();
1923 self->Set(String::New("ra"), Number::New(equ.ra/15), ReadOnly);
1924 self->Set(String::New("dec"), Number::New(equ.dec), ReadOnly);
1925 self->Set(String::New("toLocal"), function, ReadOnly);
1926 self->Set(String::New("time"), date, ReadOnly);
1927
1928 return handle_scope.Close(self);
1929}
1930
1931Handle<Value> InterpreterV8::ConstructorSky(const Arguments &args)
1932{
1933 if (args.Length()<2 || args.Length()>3)
1934 return ThrowException(String::New("Sky constructor takes two or three arguments."));
1935
1936 if (args.Length()==3 && !args[2]->IsDate())
1937 return ThrowException(String::New("Third argument must be a Date."));
1938
1939 const double ra = args[0]->NumberValue();
1940 const double dec = args[1]->NumberValue();
1941
1942 if (!finite(ra) || !finite(dec))
1943 return ThrowException(String::New("Both arguments to Sky must be valid numbers."));
1944
1945 // ----------------------------
1946
1947 HandleScope handle_scope;
1948
1949 if (!args.IsConstructCall())
1950 return handle_scope.Close(Constructor(args));
1951
1952 Handle<Function> function =
1953 FunctionTemplate::New(SkyToLocal)->GetFunction();
1954 if (function.IsEmpty())
1955 return Undefined();
1956
1957 Handle<Object> self = args.This();
1958 self->Set(String::New("ra"), Number::New(ra), ReadOnly);
1959 self->Set(String::New("dec"), Number::New(dec), ReadOnly);
1960 self->Set(String::New("toLocal"), function, ReadOnly);
1961 if (args.Length()==3)
1962 self->Set(String::New("time"), args[2], ReadOnly);
1963
1964 return handle_scope.Close(self);
1965}
1966
1967Handle<Value> InterpreterV8::ConstructorLocal(const Arguments &args)
1968{
1969 if (args.Length()<2 || args.Length()>3)
1970 return ThrowException(String::New("Local constructor takes two or three arguments."));
1971
1972 if (args.Length()==3 && !args[2]->IsDate())
1973 return ThrowException(String::New("Third argument must be a Date."));
1974
1975 const double zd = args[0]->NumberValue();
1976 const double az = args[1]->NumberValue();
1977
1978 if (!finite(zd) || !finite(az))
1979 return ThrowException(String::New("Both arguments to Local must be valid numbers."));
1980
1981 // --------------------
1982
1983 HandleScope handle_scope;
1984
1985 if (!args.IsConstructCall())
1986 return handle_scope.Close(Constructor(args));
1987
1988 Handle<Function> function =
1989 FunctionTemplate::New(LocalToSky)->GetFunction();
1990 if (function.IsEmpty())
1991 return Undefined();
1992
1993 Handle<Object> self = args.This();
1994 self->Set(String::New("zd"), Number::New(zd), ReadOnly);
1995 self->Set(String::New("az"), Number::New(az), ReadOnly);
1996 self->Set(String::New("toSky"), function, ReadOnly);
1997 if (args.Length()==3)
1998 self->Set(String::New("time"), args[2], ReadOnly);
1999
2000 return handle_scope.Close(self);
2001}
2002
2003Handle<Object> InterpreterV8::ConstructRiseSet(const Handle<Value> time, const Nova::RstTime &rst, const bool &rc)
2004{
2005 Handle<Object> obj = Object::New();
2006 obj->Set(String::New("time"), time, ReadOnly);
2007
2008 const uint64_t v = uint64_t(time->NumberValue());
2009 const double jd = Time(v/1000, v%1000).JD();
2010
2011 const bool isUp = rc>0 ||
2012 (rst.rise<rst.set && (jd>rst.rise && jd<rst.set)) ||
2013 (rst.rise>rst.set && (jd<rst.set || jd>rst.rise));
2014
2015 obj->Set(String::New("isUp"), Boolean::New(rc>=0 && isUp), ReadOnly);
2016
2017 if (rc!=0)
2018 return obj;
2019
2020 Handle<Value> rise = Date::New(Time(rst.rise).JavaDate());
2021 Handle<Value> set = Date::New(Time(rst.set).JavaDate());
2022 Handle<Value> trans = Date::New(Time(rst.transit).JavaDate());
2023 if (rise.IsEmpty() || set.IsEmpty() || trans.IsEmpty())
2024 return Handle<Object>();
2025
2026 obj->Set(String::New("rise"), rise, ReadOnly);
2027 obj->Set(String::New("set"), set, ReadOnly);
2028 obj->Set(String::New("transit"), trans, ReadOnly);
2029
2030 return obj;
2031}
2032
2033Handle<Value> InterpreterV8::SunHorizon(const Arguments &args)
2034{
2035 if (args.Length()>2)
2036 return ThrowException(String::New("Sun.horizon must not be called with one or two arguments."));
2037
2038 if (args.Length()==2 && !args[1]->IsDate())
2039 return ThrowException(String::New("Second argument must be a Date"));
2040
2041 HandleScope handle_scope;
2042
2043 double hrz = NAN;
2044 if (args.Length()==0 || args[0]->IsNull())
2045 hrz = LN_SOLAR_STANDART_HORIZON;
2046 if (args.Length()>0 && args[0]->IsNumber())
2047 hrz = args[0]->NumberValue();
2048 if (args.Length()>0 && args[0]->IsString())
2049 {
2050 string arg(Tools::Trim(*String::AsciiValue(args[0])));
2051 transform(arg.begin(), arg.end(), arg.begin(), ::tolower);
2052
2053 if (arg==string("horizon").substr(0, arg.length()))
2054 hrz = LN_SOLAR_STANDART_HORIZON;
2055 if (arg==string("civil").substr(0, arg.length()))
2056 hrz = LN_SOLAR_CIVIL_HORIZON;
2057 if (arg==string("nautical").substr(0, arg.length()))
2058 hrz = LN_SOLAR_NAUTIC_HORIZON;
2059 if (arg==string("fact").substr(0, arg.length()))
2060 hrz = -13;
2061 if (arg==string("astronomical").substr(0, arg.length()))
2062 hrz = LN_SOLAR_ASTRONOMICAL_HORIZON;
2063 }
2064
2065 if (!finite(hrz))
2066 return ThrowException(String::New("Second argument did not yield a valid number."));
2067
2068 const Local<Value> date =
2069 args.Length()<2 ? Date::New(Time().JavaDate()) : args[1];
2070 if (date.IsEmpty())
2071 return Undefined();
2072
2073 const uint64_t v = uint64_t(date->NumberValue());
2074 const Time utc(v/1000, v%1000);
2075
2076 Nova::LnLatPosn obs = Nova::ORM();
2077
2078 ln_rst_time sun;
2079 const int rc = ln_get_solar_rst_horizon(utc.JD()-0.5, &obs, hrz, &sun);
2080 Handle<Object> rst = ConstructRiseSet(date, sun, rc);
2081 rst->Set(String::New("horizon"), Number::New(hrz));
2082 return handle_scope.Close(rst);
2083};
2084
2085Handle<Value> InterpreterV8::MoonHorizon(const Arguments &args)
2086{
2087 if (args.Length()>1)
2088 return ThrowException(String::New("Moon.horizon must not be called with one argument."));
2089
2090 if (args.Length()==1 && !args[0]->IsDate())
2091 return ThrowException(String::New("Argument must be a Date"));
2092
2093 HandleScope handle_scope;
2094
2095 const Local<Value> date =
2096 args.Length()==0 ? Date::New(Time().JavaDate()) : args[0];
2097 if (date.IsEmpty())
2098 return Undefined();
2099
2100 const uint64_t v = uint64_t(date->NumberValue());
2101 const Time utc(v/1000, v%1000);
2102
2103 Nova::LnLatPosn obs = Nova::ORM();
2104
2105 ln_rst_time moon;
2106 const int rc = ln_get_lunar_rst(utc.JD()-0.5, &obs, &moon);
2107 Handle<Object> rst = ConstructRiseSet(date, moon, rc);
2108 return handle_scope.Close(rst);
2109};
2110#endif
2111
2112// ==========================================================================
2113// Process control
2114// ==========================================================================
2115
2116bool InterpreterV8::HandleException(TryCatch& try_catch, const char *where)
2117{
2118 if (!try_catch.HasCaught() || !try_catch.CanContinue())
2119 return true;
2120
2121 const HandleScope handle_scope;
2122
2123 Handle<Value> except = try_catch.Exception();
2124 if (except.IsEmpty() || except->IsNull())
2125 return true;
2126
2127 const String::AsciiValue exception(except);
2128
2129 const Handle<Message> message = try_catch.Message();
2130 if (message.IsEmpty())
2131 return false;
2132
2133 ostringstream out;
2134
2135 if (!message->GetScriptResourceName()->IsUndefined())
2136 {
2137 // Print (filename):(line number): (message).
2138 const String::AsciiValue filename(message->GetScriptResourceName());
2139 if (filename.length()>0)
2140 {
2141 out << *filename;
2142 if (message->GetLineNumber()>0)
2143 out << ": l." << message->GetLineNumber();
2144 if (*exception)
2145 out << ": ";
2146 }
2147 }
2148
2149 if (*exception)
2150 out << *exception;
2151
2152 out << " [" << where << "]";
2153
2154 JsException(out.str());
2155
2156 // Print line of source code.
2157 const String::AsciiValue sourceline(message->GetSourceLine());
2158 if (*sourceline)
2159 JsException(*sourceline);
2160
2161 // Print wavy underline (GetUnderline is deprecated).
2162 const int start = message->GetStartColumn();
2163 const int end = message->GetEndColumn();
2164
2165 out.str("");
2166 if (start>0)
2167 out << setfill(' ') << setw(start) << ' ';
2168 out << setfill('^') << setw(end-start) << '^';
2169
2170 JsException(out.str());
2171
2172 const String::AsciiValue stack_trace(try_catch.StackTrace());
2173 if (stack_trace.length()<=0)
2174 return false;
2175
2176 if (!*stack_trace)
2177 return false;
2178
2179 const string trace(*stack_trace);
2180
2181 typedef boost::char_separator<char> separator;
2182 const boost::tokenizer<separator> tokenizer(trace, separator("\n"));
2183
2184 // maybe skip: " at internal:"
2185 // maybe skip: " at unknown source:"
2186
2187 auto it = tokenizer.begin();
2188 JsException("");
2189 while (it!=tokenizer.end())
2190 JsException(*it++);
2191
2192 return false;
2193}
2194
2195Handle<Value> InterpreterV8::ExecuteInternal(const string &code)
2196{
2197 // Try/catch and re-throw hides our internal code from
2198 // the displayed exception showing the origin and shows
2199 // the user function instead.
2200 TryCatch exception;
2201
2202 const Handle<Value> result = ExecuteCode(code);
2203
2204 // This hides the location of the exception in the internal code,
2205 // which is wanted.
2206 if (exception.HasCaught())
2207 exception.ReThrow();
2208
2209 return result;
2210}
2211
2212Handle<Value> InterpreterV8::ExecuteCode(const string &code, const string &file)
2213{
2214 HandleScope handle_scope;
2215
2216 const Handle<String> source = String::New(code.c_str(), code.size());
2217 const Handle<String> origin = String::New(file.c_str());
2218 if (source.IsEmpty())
2219 return Undefined();
2220
2221 const Handle<Script> script = Script::Compile(source, origin);
2222 if (script.IsEmpty())
2223 return Undefined();
2224
2225 const Handle<String> __date__ = String::New("__DATE__");
2226 const Handle<String> __file__ = String::New("__FILE__");
2227
2228 Handle<Value> save_date;
2229 Handle<Value> save_file;
2230
2231 Handle<Object> global = Context::GetCurrent()->Global();
2232 if (!global.IsEmpty())
2233 {
2234 struct stat attrib;
2235 if (stat(file.c_str(), &attrib)==0)
2236 {
2237 save_date = global->Get(__date__);
2238 save_file = global->Get(__file__);
2239
2240 global->Set(__file__, String::New(file.c_str()));
2241
2242 const Local<Value> date = Date::New(attrib.st_mtime*1000);
2243 if (!date.IsEmpty())
2244 global->Set(__date__, date);
2245 }
2246 }
2247
2248 const Handle<Value> rc = script->Run();
2249 if (rc.IsEmpty())
2250 return Undefined();
2251
2252 if (!global.IsEmpty() && !save_date.IsEmpty())
2253 {
2254 global->ForceSet(__date__, save_date);
2255 global->ForceSet(__file__, save_file);
2256 }
2257
2258 return handle_scope.Close(rc);
2259}
2260
2261void InterpreterV8::ExecuteConsole()
2262{
2263 JsSetState(3);
2264
2265 WindowLog lout;
2266 lout << "\n " << kUnderline << " JavaScript interpreter " << kReset << " (enter '.q' to quit)\n" << endl;
2267
2268 Readline::StaticPushHistory("java.his");
2269
2270 string command;
2271 while (1)
2272 {
2273 // Create a local handle scope so that left-overs from single
2274 // console inputs will not fill up the memory
2275 const HandleScope handle_scope;
2276
2277 // Unlocking is necessary for the preemption to work
2278 const Unlocker global_unlock;
2279
2280 const string buffer = Tools::Trim(Readline::StaticPrompt(command.empty() ? "JS> " : " \\> "));
2281 if (buffer==".q")
2282 break;
2283
2284 // buffer empty, do nothing
2285 if (buffer.empty())
2286 continue;
2287
2288 // Compose command
2289 if (!command.empty())
2290 command += ' ';
2291 command += buffer;
2292
2293 // If line ends with a backslash, allow addition of next line
2294 auto back = command.rbegin();
2295 if (*back=='\\')
2296 {
2297 *back = ' ';
2298 command = Tools::Trim(command);
2299 continue;
2300 }
2301
2302 // Locking is necessary to be able to execute java script code
2303 const Locker lock;
2304
2305 // Catch exceptions during code compilation
2306 TryCatch exception;
2307
2308 // Execute code which was entered
2309 const Handle<Value> rc = ExecuteCode(command, "console");
2310
2311 // If all went well and the result wasn't undefined then print
2312 // the returned value.
2313 if (!rc->IsUndefined() && !rc->IsFunction())
2314 JsResult(*String::AsciiValue(rc));
2315
2316 if (!HandleException(exception, "console"))
2317 lout << endl;
2318
2319 // Stop all other threads
2320 for (auto it=fThreadIds.begin(); it!=fThreadIds.end(); it++)
2321 V8::TerminateExecution(*it);
2322
2323 // Allow the java scripts (threads) to run and hence to terminate
2324 const Unlocker unlock;
2325
2326 // Wait until all threads are terminated
2327 while (!fThreadIds.empty())
2328 usleep(1000);
2329
2330 // command has been executed, collect new command
2331 command = "";
2332 }
2333
2334 lout << endl;
2335
2336 Readline::StaticPopHistory("java.his");
2337}
2338
2339// ==========================================================================
2340// CORE
2341// ==========================================================================
2342
2343InterpreterV8::InterpreterV8() : fThreadId(-1)
2344{
2345 const string ver(V8::GetVersion());
2346
2347 typedef boost::char_separator<char> separator;
2348 const boost::tokenizer<separator> tokenizer(ver, separator("."));
2349
2350 const vector<string> tok(tokenizer.begin(), tokenizer.end());
2351
2352 const int major = tok.size()>0 ? stol(tok[0]) : -1;
2353 const int minor = tok.size()>1 ? stol(tok[1]) : -1;
2354 const int build = tok.size()>2 ? stol(tok[2]) : -1;
2355
2356 if (major>3 || (major==3 && minor>9) || (major==3 && minor==9 && build>10))
2357 {
2358 const string argv = "--use_strict";
2359 V8::SetFlagsFromString(argv.c_str(), argv.size());
2360 }
2361
2362 This = this;
2363}
2364
2365Handle<Value> InterpreterV8::Constructor(/*Handle<FunctionTemplate> T,*/ const Arguments &args)
2366{
2367 Handle<Value> argv[args.Length()];
2368
2369 for (int i=0; i<args.Length(); i++)
2370 argv[i] = args[i];
2371
2372 return args.Callee()->NewInstance(args.Length(), argv);
2373}
2374
2375
2376void InterpreterV8::AddFormatToGlobal()// const
2377{
2378 const string code =
2379 "String.form = function(str, arr)"
2380 "{"
2381 "var i = -1;"
2382 "function callback(exp, p0, p1, p2, p3, p4/*, pos, str*/)"
2383 "{"
2384 "if (exp=='%%')"
2385 "return '%';"
2386 ""
2387 "if (arr[++i]===undefined)"
2388 "return undefined;"
2389 ""
2390 "var exp = p2 ? parseInt(p2.substr(1)) : undefined;"
2391 "var base = p3 ? parseInt(p3.substr(1)) : undefined;"
2392 ""
2393 "var val;"
2394 "switch (p4)"
2395 "{"
2396 "case 's': val = arr[i]; break;"
2397 "case 'c': val = arr[i][0]; break;"
2398 "case 'f': val = parseFloat(arr[i]).toFixed(exp); break;"
2399 "case 'p': val = parseFloat(arr[i]).toPrecision(exp); break;"
2400 "case 'e': val = parseFloat(arr[i]).toExponential(exp); break;"
2401 "case 'x': val = parseInt(arr[i]).toString(base?base:16); break;"
2402 "case 'd': val = parseFloat(parseInt(arr[i], base?base:10).toPrecision(exp)).toFixed(0); break;"
2403 //"default:\n"
2404 //" throw new SyntaxError('Conversion specifier '+p4+' unknown.');\n"
2405 "}"
2406 ""
2407 "val = typeof(val)=='object' ? JSON.stringify(val) : val.toString(base);"
2408 ""
2409 "var sz = parseInt(p1); /* padding size */"
2410 "var ch = p1 && p1[0]=='0' ? '0' : ' '; /* isnull? */"
2411 "while (val.length<sz)"
2412 "val = p0 !== undefined ? val+ch : ch+val; /* isminus? */"
2413 ""
2414 "return val;"
2415 "}"
2416 ""
2417 "var regex = /%(-)?(0?[0-9]+)?([.][0-9]+)?([#][0-9]+)?([scfpexd])/g;"
2418 "return str.replace(regex, callback);"
2419 "}"
2420 "\n"
2421 "String.prototype.$ = function()"
2422 "{"
2423 "return String.form(this, Array.prototype.slice.call(arguments));"
2424 "}"
2425 "\n"
2426 "String.prototype.count = function(c,i)"
2427 "{"
2428 "return (this.match(new RegExp(c,i?'gi':'g'))||[]).length;"
2429 "}"/*
2430 "\n"
2431 "var format = function()"
2432 "{"
2433 "return dim.format(arguments[0], Array.prototype.slice.call(arguments,1));"
2434 "}"*/;
2435
2436 // ExcuteInternal does not work properly here...
2437 // If suring compilation an exception is thrown, it will not work
2438 Handle<Script> script = Script::New(String::New(code.c_str()), String::New("internal"));
2439 if (!script.IsEmpty())
2440 script->Run();
2441}
2442
2443void InterpreterV8::JsLoad(const std::string &)
2444{
2445 Readline::SetScriptDepth(1);
2446}
2447
2448void InterpreterV8::JsEnd(const std::string &)
2449{
2450 Readline::SetScriptDepth(0);
2451}
2452
2453bool InterpreterV8::JsRun(const string &filename, const map<string, string> &map)
2454{
2455 const Locker locker;
2456 fThreadId = V8::GetCurrentThreadId();
2457
2458 JsPrint(string("JavaScript Engine V8 ")+V8::GetVersion());
2459
2460 JsLoad(filename);
2461
2462 const HandleScope handle_scope;
2463
2464 // Create a template for the global object.
2465 Handle<ObjectTemplate> dim = ObjectTemplate::New();
2466 dim->Set(String::New("log"), FunctionTemplate::New(WrapLog), ReadOnly);
2467 dim->Set(String::New("alarm"), FunctionTemplate::New(WrapAlarm), ReadOnly);
2468 dim->Set(String::New("wait"), FunctionTemplate::New(WrapWait), ReadOnly);
2469 dim->Set(String::New("send"), FunctionTemplate::New(WrapSend), ReadOnly);
2470 dim->Set(String::New("state"), FunctionTemplate::New(WrapState), ReadOnly);
2471 dim->Set(String::New("version"), Integer::New(DIM_VERSION_NUMBER), ReadOnly);
2472 dim->Set(String::New("getStates"), FunctionTemplate::New(WrapGetStates), ReadOnly);
2473 dim->Set(String::New("getDescription"), FunctionTemplate::New(WrapGetDescription), ReadOnly);
2474 dim->Set(String::New("getServices"), FunctionTemplate::New(WrapGetServices), ReadOnly);
2475
2476 Handle<ObjectTemplate> dimctrl = ObjectTemplate::New();
2477 dimctrl->Set(String::New("defineState"), FunctionTemplate::New(WrapNewState), ReadOnly);
2478 dimctrl->Set(String::New("setState"), FunctionTemplate::New(WrapSetState), ReadOnly);
2479 dimctrl->Set(String::New("getState"), FunctionTemplate::New(WrapGetState), ReadOnly);
2480 dimctrl->Set(String::New("setInterruptHandler"), FunctionTemplate::New(WrapSetInterrupt), ReadOnly);
2481 dimctrl->Set(String::New("triggerInterrupt"), FunctionTemplate::New(WrapTriggerInterrupt), ReadOnly);
2482
2483 Handle<ObjectTemplate> v8 = ObjectTemplate::New();
2484 v8->Set(String::New("sleep"), FunctionTemplate::New(WrapSleep), ReadOnly);
2485 v8->Set(String::New("timeout"), FunctionTemplate::New(WrapTimeout), ReadOnly);
2486 v8->Set(String::New("version"), String::New(V8::GetVersion()), ReadOnly);
2487
2488 Handle<ObjectTemplate> console = ObjectTemplate::New();
2489 console->Set(String::New("out"), FunctionTemplate::New(WrapOut), ReadOnly);
2490 console->Set(String::New("warn"), FunctionTemplate::New(WrapWarn), ReadOnly);
2491
2492 Handle<ObjectTemplate> onchange = ObjectTemplate::New();
2493 onchange->SetNamedPropertyHandler(OnChangeGet, WrapOnChangeSet);
2494 dim->Set(String::New("onchange"), onchange);
2495
2496 Handle<ObjectTemplate> global = ObjectTemplate::New();
2497 global->Set(String::New("v8"), v8, ReadOnly);
2498 global->Set(String::New("dim"), dim, ReadOnly);
2499 global->Set(String::New("dimctrl"), dimctrl, ReadOnly);
2500 global->Set(String::New("console"), console, ReadOnly);
2501 global->Set(String::New("include"), FunctionTemplate::New(WrapInclude), ReadOnly);
2502 global->Set(String::New("exit"), FunctionTemplate::New(WrapExit), ReadOnly);
2503
2504 Handle<FunctionTemplate> sub = FunctionTemplate::New(WrapSubscription);
2505 sub->SetClassName(String::New("Subscription"));
2506 sub->InstanceTemplate()->SetInternalFieldCount(1);
2507 global->Set(String::New("Subscription"), sub, ReadOnly);
2508
2509#ifdef HAVE_SQL
2510 Handle<FunctionTemplate> db = FunctionTemplate::New(WrapDatabase);
2511 db->SetClassName(String::New("Database"));
2512 db->InstanceTemplate()->SetInternalFieldCount(1);
2513 global->Set(String::New("Database"), db, ReadOnly);
2514#endif
2515
2516 Handle<FunctionTemplate> thread = FunctionTemplate::New(WrapThread);
2517 thread->SetClassName(String::New("Thread"));
2518 global->Set(String::New("Thread"), thread, ReadOnly);
2519
2520 Handle<FunctionTemplate> file = FunctionTemplate::New(WrapFile);
2521 file->SetClassName(String::New("File"));
2522 global->Set(String::New("File"), file, ReadOnly);
2523
2524 Handle<FunctionTemplate> evt = FunctionTemplate::New();
2525 evt->SetClassName(String::New("Event"));
2526 global->Set(String::New("Event"), evt, ReadOnly);
2527
2528 Handle<FunctionTemplate> desc = FunctionTemplate::New();
2529 desc->SetClassName(String::New("Description"));
2530 global->Set(String::New("Description"), desc, ReadOnly);
2531
2532 fTemplateEvent = evt;
2533 fTemplateDescription = desc;
2534
2535#ifdef HAVE_MAILX
2536 Handle<FunctionTemplate> mail = FunctionTemplate::New(ConstructorMail);
2537 mail->SetClassName(String::New("Mail"));
2538 global->Set(String::New("Mail"), mail, ReadOnly);
2539#endif
2540
2541#ifdef HAVE_NOVA
2542 Handle<FunctionTemplate> sky = FunctionTemplate::New(ConstructorSky);
2543 sky->SetClassName(String::New("Sky"));
2544 sky->Set(String::New("dist"), FunctionTemplate::New(SkyDist), ReadOnly);
2545 global->Set(String::New("Sky"), sky, ReadOnly);
2546
2547 Handle<FunctionTemplate> loc = FunctionTemplate::New(ConstructorLocal);
2548 loc->SetClassName(String::New("Local"));
2549 loc->Set(String::New("dist"), FunctionTemplate::New(LocalDist), ReadOnly);
2550 global->Set(String::New("Local"), loc, ReadOnly);
2551
2552 Handle<FunctionTemplate> moon = FunctionTemplate::New(ConstructorMoon);
2553 moon->SetClassName(String::New("Moon"));
2554 moon->Set(String::New("disk"), FunctionTemplate::New(MoonDisk), ReadOnly);
2555 moon->Set(String::New("horizon"), FunctionTemplate::New(MoonHorizon), ReadOnly);
2556 global->Set(String::New("Moon"), moon, ReadOnly);
2557
2558 Handle<FunctionTemplate> sun = FunctionTemplate::New();
2559 sun->SetClassName(String::New("Sun"));
2560 sun->Set(String::New("horizon"), FunctionTemplate::New(SunHorizon), ReadOnly);
2561 global->Set(String::New("Sun"), sun, ReadOnly);
2562
2563 fTemplateLocal = loc;
2564 fTemplateSky = sky;
2565#endif
2566
2567 // Persistent
2568 Persistent<Context> context = Context::New(NULL, global);
2569 if (context.IsEmpty())
2570 {
2571 JsException("Creation of global context failed...");
2572 JsEnd(filename);
2573 return false;
2574 }
2575
2576 // Switch off eval(). It is not possible to track it's exceptions.
2577 context->AllowCodeGenerationFromStrings(false);
2578
2579 Context::Scope scope(context);
2580
2581 Handle<Array> args = Array::New(map.size());
2582 for (auto it=map.begin(); it!=map.end(); it++)
2583 args->Set(String::New(it->first.c_str()), String::New(it->second.c_str()));
2584 context->Global()->Set(String::New("$"), args, ReadOnly);
2585 context->Global()->Set(String::New("arg"), args, ReadOnly);
2586
2587 const Local<Value> starttime = Date::New(Time().JavaDate());
2588 if (!starttime.IsEmpty())
2589 context->Global()->Set(String::New("__START__"), starttime, ReadOnly);
2590
2591 //V8::ResumeProfiler();
2592
2593 TryCatch exception;
2594
2595 AddFormatToGlobal();
2596
2597 if (!exception.HasCaught())
2598 {
2599 JsStart(filename);
2600
2601 Locker::StartPreemption(10);
2602
2603 if (filename.empty())
2604 ExecuteConsole();
2605 else
2606 {
2607 // We call script->Run because it is the only way to
2608 // catch exceptions.
2609 const Handle<String> source = String::New(("include('"+filename+"');").c_str());
2610 const Handle<String> origin = String::New("main");
2611 const Handle<Script> script = Script::Compile(source, origin);
2612 if (!script.IsEmpty())
2613 {
2614 JsSetState(3);
2615 script->Run();
2616 }
2617 }
2618
2619 Locker::StopPreemption();
2620
2621 // Stop all other threads
2622 for (auto it=fThreadIds.begin(); it!=fThreadIds.end(); it++)
2623 V8::TerminateExecution(*it);
2624 fThreadIds.clear();
2625 }
2626
2627 // Handle an exception
2628 /*const bool rc =*/ HandleException(exception, "main");
2629
2630 // IsProfilerPaused()
2631 // V8::PauseProfiler();
2632
2633 // -----
2634 // This is how an exit handler could look like, but there is no way to interrupt it
2635 // -----
2636 // Handle<Object> obj = Handle<Object>::Cast(context->Global()->Get(String::New("dim")));
2637 // if (!obj.IsEmpty())
2638 // {
2639 // Handle<Value> onexit = obj->Get(String::New("onexit"));
2640 // if (!onexit->IsUndefined())
2641 // Handle<Function>::Cast(onexit)->NewInstance(0, NULL); // argc, argv
2642 // // Handle<Object> result = Handle<Function>::Cast(onexit)->NewInstance(0, NULL); // argc, argv
2643 // }
2644
2645 //context->Exit();
2646
2647 // The threads are started already and wait to get the lock
2648 // So we have to unlock (manual preemtion) so that they get
2649 // the signal to terminate.
2650 {
2651 const Unlocker unlock;
2652
2653 for (auto it=fThreads.begin(); it!=fThreads.end(); it++)
2654 it->join();
2655 fThreads.clear();
2656 }
2657
2658 // Now we can dispose all persistent handles from state callbacks
2659 for (auto it=fStateCallbacks.begin(); it!=fStateCallbacks.end(); it++)
2660 it->second.Dispose();
2661 fStateCallbacks.clear();
2662
2663 // Now we can dispose the persistent interrupt handler
2664 fInterruptCallback.Dispose();
2665
2666 // Now we can dispose all persistent handles from reverse maps
2667 for (auto it=fReverseMap.begin(); it!=fReverseMap.end(); it++)
2668 it->second.Dispose();
2669 fReverseMap.clear();
2670
2671#ifdef HAVE_SQL
2672 // ...and close all database handles
2673 for (auto it=fDatabases.begin(); it!=fDatabases.end(); it++)
2674 delete *it;
2675 fDatabases.clear();
2676#endif
2677
2678 fStates.clear();
2679
2680 context.Dispose();
2681
2682 JsEnd(filename);
2683
2684 return true;
2685}
2686
2687void InterpreterV8::JsStop()
2688{
2689 Locker locker;
2690 V8::TerminateExecution(This->fThreadId);
2691}
2692
2693#endif
2694
2695InterpreterV8 *InterpreterV8::This = 0;
Note: See TracBrowser for help on using the repository browser.