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

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