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

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