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

Last change on this file since 15264 was 15253, checked in by tbretz, 12 years ago
The problem with the izsream is solved. It was the allocation of the large chunk of stack memory in izstream - now dynamic
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 izstream fin(*file);
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.