#include "InterpreterV8.h" #ifdef HAVE_V8 #include #include #include #include #include #include #include #ifdef HAVE_NOVA #include "nova.h" #endif #ifdef HAVE_SQL #include "Database.h" #endif #include #include #include "dim.h" #include "tools.h" #include "Readline.h" #include "izstream.h" #include "WindowLog.h" using namespace std; using namespace v8; v8::Handle InterpreterV8::fTemplateLocal; v8::Handle InterpreterV8::fTemplateSky; v8::Handle InterpreterV8::fTemplateEvent; v8::Handle InterpreterV8::fTemplateDescription; //v8::Handle InterpreterV8::fTemplateDatabase; // ========================================================================== // Some documentation // ========================================================================== // // Threads: // -------- // In most cases Js* and other calls to native C++ code could be wrapped // with an Unlocker to allow possible other JavaScipt 'threads' to run // during that time. However, all of these calls should take much less than // the preemption time of 10ms, so it would just be a waste of tim. // // Termination: // ------------ // Each thread running V8 code needs to be signalled individually for // termination. Therefor a list of V8 thread ids is created. // // If termination has already be signalled, no thread should start running // anymore (thy could, e.g., wait for their locking). So after locking // it has to be checked if the thread was terminated already. Note // that all calls to Terminate() must be locked to ensure that fThreadId // is correct when it is checked. // // The current thread id must be added to fThreadIds _before_ any // function is called after Locking and before execution is given // back to JavaScript, e.g. in script->Run(). So until the thread // is added to the list Terminate will not be executed. If Terminate // is then executed, it is ensured that the current thread is // already in the list. If terminate has been called before // the Locking, the check for the validiy of fThreadId ensures that // nothing is executed. // // Empty handles: // -------------- // If exceution is terminated, V8 calls might return with empty handles, // e.g. Date::New(isolate). Therefore, the returned handles of these calls have to // be checked in all placed to avoid that V8 will core dump. // // HandleScope: // ------------ // A handle scope is a garbage collector and collects all handles created // until it goes out of scope. Handles which are not needed anymore are // then deleted. To return a handle from a HandleScope you need to use // Close(). E.g., String::Utf8Value does not create a new handle and // hence does not need a HandleScope. Any ::New will need a handle scope. // Forgetting the HandleScope could in principle fill your memory, // but everything is properly deleted by the global HandleScope at // script termination. // // Here is another good reference for v8, also containing some // good explanations for the meaning of handles, persistent handles // and weak handles: http://create.tpsitulsa.com/wiki/V8_Cookbook // // ========================================================================== // Simple interface // ========================================================================== typedef FunctionCallbackInfo Arguments; void InterpreterV8::FuncExit(const Arguments &) { fMainThread->TerminateExecution(); // we have to throw an excption to make sure that the // calling thread does not go on executing until it // has realized that it should terminate //*NEw* FIXME: Which Thread??? Isolate* isolate = Isolate::GetCurrent(); isolate->ThrowException(Null(isolate)); } void InterpreterV8::FuncSleep(const Arguments& args) { Isolate* isolate = Isolate::GetCurrent(); if (args.Length()==0) { // Theoretically, the CPU usage can be reduced by maybe a factor // of four using a larger value, but this also means that the // JavaScript is locked for a longer time. const Unlocker unlock(isolate); usleep(1000); return; } if (args.Length()!=1) { isolate->ThrowException(String::NewFromUtf8(isolate, "Number of arguments must be exactly 1.")); return; } if (!args[0]->IsUint32()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Argument 1 must be an uint32.")); return; } // Using a Javascript function has the advantage that it is fully // interruptable without the need of C++ code const string code = "(function(){" "var t=new Date();" "while ((new Date()-t)<"+to_string(args[0]->Int32Value())+") v8.sleep();" "})();"; args.GetReturnValue().Set(ExecuteInternal(code)); } void InterpreterV8::FuncTimeout(const Arguments &args) { Isolate* isolate = Isolate::GetCurrent(); if (args.Length()<2) { isolate->ThrowException(String::NewFromUtf8(isolate, "Number of arguments must be at least two.")); return; } if (!args[0]->IsNull() && !args[0]->IsInt32()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Argument 0 not null and not an int32.")); return; } if (!args[1]->IsFunction()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Argument 1 not a function.")); return; } if (args.Length()>2 && !args[2]->IsObject()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Argument 2 not an object.")); return; } const int32_t timeout = args[0]->IsNull() ? 0 : args[0]->Int32Value(); const bool null = args[0]->IsNull(); HandleScope handle_scope(isolate); Handle func = Handle::Cast(args[1]); const int nn = args.Length()==2 ? 0 : args.Length()-3; vector> argv(nn); for (int i=0; i rc = args.Length()<3 ? func->Call(func, nn, argv.data()) : func->Call(args[2]->ToObject(), nn, argv.data()); if (rc.IsEmpty()) return; if (!rc->IsUndefined()) { args.GetReturnValue().Set(rc); return; } if (!null && Time()-t>=boost::posix_time::milliseconds(abs(timeout))) break; // Theoretically, the CPU usage can be reduced by maybe a factor // of four using a larger value, but this also means that the // JavaScript is locked for a longer time. const Unlocker unlock(isolate); usleep(1000); } if (timeout<0) return; isolate->ThrowException(String::NewFromUtf8(isolate, "Waiting for func to return a defined value timed out.")); } void InterpreterV8::Thread(v8::UniquePersistent &_this, v8::UniquePersistent &func, uint32_t ms) { Isolate *isolate = fMainThread; if (!isolate) return; // We want to run as an interrupt in the main thread const Locker locker(isolate); if (!fMainThread) return; const Isolate::Scope isolate_scope(isolate); const HandleScope handle_scope(isolate); Handle fun = func.Get(isolate); const Context::Scope scope(fun->CreationContext()); TryCatch exception(isolate); fun->CreationContext()->Enter(); const bool rc = ms==0 || !ExecuteInternal("v8.sleep("+to_string(ms)+");").IsEmpty(); if (rc) { if (_this.IsEmpty()) Handle::Cast(fun)->Call(fun->CreationContext(), fun, 0, NULL).IsEmpty(); else Handle::Cast(fun)->Call(fun->CreationContext(), _this.Get(isolate), 0, NULL).IsEmpty(); } if (!HandleException(exception, "Thread")) isolate->AddBeforeCallEnteredCallback(TerminateExecution); fun->CreationContext()->Exit(); } void InterpreterV8::FuncThread(const Arguments& args) { Isolate *isolate = Isolate::GetCurrent(); if (!args.IsConstructCall()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Thread must be called as constructor.")); return; } if (args.Length()!=2 && args.Length()!=3) { isolate->ThrowException(String::NewFromUtf8(isolate, "Number of arguments must be two or three.")); return; } if (!args[0]->IsUint32()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Argument 0 not an uint32.")); return; } if (!args[1]->IsFunction()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Argument 1 not a function.")); return; } if (args.Length()==3 && !args[2]->IsObject()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Argument 2 not an object.")); return; } const HandleScope handle_scope(isolate); UniquePersistent func = UniquePersistent(isolate, args[1]->ToObject()); UniquePersistent _this; if (args.Length()==3) _this = UniquePersistent(isolate, args[2]->ToObject()); const uint32_t ms = args[0]->Uint32Value(); //int id=__id; fThreads.push_back(thread(bind(&InterpreterV8::Thread, this, /*ref(id),*/ std::move(_this), std::move(func), ms))); /* { // Allow the thread to lock, so we can get the thread id. const Unlocker unlock(isolate); while (id==-2) usleep(1); } Handle self = args.This(); self->Set(String::NewFromUtf8(isolate, "id"), Integer::NewFromUnsigned(isolate, id)); self->Set(String::NewFromUtf8(isolate, "kill"), FunctionTemplate::New(isolate, WrapKill)->GetFunction()); args.GetReturnValue().Set(self); */ } void InterpreterV8::FuncKill(const Arguments& args) { /* Isolate *isolate = Isolate::GetCurrent(); const uint32_t id = args.This()->Get(String::NewFromUtf8(isolate, "id"))->Uint32Value(); V8::TerminateExecution(id); // DISPOSE? return Boolean::New(fThreadIds.erase(id)); */ } void InterpreterV8::FuncSend(const Arguments& args) { Isolate *isolate = Isolate::GetCurrent(); if (args.Length()==0) { isolate->ThrowException(String::NewFromUtf8(isolate, "Number of arguments must be at least 1.")); return; } if (!args[0]->IsString()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Argument 1 must be a string.")); return; } string command = *String::Utf8Value(args[0]); if (command.length()==0) { isolate->ThrowException(String::NewFromUtf8(isolate, "Server name empty.")); return; } if (args.Length()==0) { if (command.find_first_of('/')==string::npos) command += "/"; } // Escape all string arguments. All others can be kept as they are. for (int i=1; iIsString()) { boost::replace_all(arg, "\\", "\\\\"); boost::replace_all(arg, "'", "\\'"); boost::replace_all(arg, "\"", "\\\""); } command += " "+arg; } try { args.GetReturnValue().Set(JsSend(command)); } catch (const runtime_error &e) { isolate->ThrowException(String::NewFromUtf8(isolate, e.what())); } } // ========================================================================== // State control // ========================================================================== void InterpreterV8::FuncWait(const Arguments& args) { Isolate *isolate = Isolate::GetCurrent(); if (args.Length()!=2 && args.Length()!=3) { isolate->ThrowException(String::NewFromUtf8(isolate, "Number of arguments must be 2 or 3.")); return; } if (!args[0]->IsString()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Argument 1 not a string.")); return; } if (!args[1]->IsInt32() && !args[1]->IsString()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Argument 2 not an int32 and not a string.")); return; } if (args.Length()==3 && !args[2]->IsInt32() && !args[2]->IsUndefined()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Argument 3 not an int32 and not undefined.")); return; } // Using a Javascript function has the advantage that it is fully // interruptable without the need of C++ code const string index = args[1]->IsInt32() ? "s.index" : "s.name"; const bool timeout = args.Length()==3 && !args[2]->IsUndefined(); const string arg0 = *String::Utf8Value(args[0]); const string state = args[1]->IsString() ? *String::Utf8Value(args[1]) : ""; const string arg1 = args[1]->IsString() ? ("\""+state+"\"") : to_string(args[1]->Int32Value()); const bool isNot = arg0[0]=='!'; const string name = isNot ? arg0.substr(1) : arg0; if (arg0.find_first_of("\"'")!=string::npos) { isolate->ThrowException(String::NewFromUtf8(isolate, "Server name must not contain quotation marks.")); return; } if (args[1]->IsString()) if (state.find_first_of("\"'")!=string::npos) { isolate->ThrowException(String::NewFromUtf8(isolate, "State name must not contain quotation marks.")); return; } string code = "(function(name,state,ms)" "{"; if (timeout) code += "var t = new Date();"; code += "var s;" "while (1)" "{" "s = dim.state(name);" "if(!s)throw new Error('Waiting for state "+arg1+" of server "+arg0+" failed.');"; if (isNot) code += "if(state!="+index+")return true;"; else code += "if(state=="+index+")return true;"; if (timeout) code += "if((new Date()-t)>Math.abs(ms))break;"; code += "v8.sleep();" "}"; if (timeout) code += "if(ms>0)throw new Error('Waiting for state "+arg1+" of server "+arg0+" ['+"+index+"+'] timed out.');"; code += "return false;" "})('"+name+"',"+arg1; if (timeout) code += "," + (args[2]->IsUndefined()?"undefined":to_string(args[2]->Int32Value())); code += ");"; args.GetReturnValue().Set(ExecuteInternal(code)); } void InterpreterV8::GetterCallback(Local< Name > name, const PropertyCallbackInfo< Value > &info) { //const string server = *String::Utf8Value(name); info.GetReturnValue().Set(info.Data()); } void InterpreterV8::SetProperty(Handle &obj, const char *name, const Local &val) { // My answer on: https://bugs.chromium.org/p/v8/issues/detail?id=4997 obj->SetNativeDataProperty(String::NewFromUtf8(Isolate::GetCurrent(), name), GetterCallback, nullptr, val, ReadOnly, Local(), ALL_CAN_READ); } void InterpreterV8::FuncState(const Arguments& args) { Isolate *isolate = Isolate::GetCurrent(); if (args.Length()!=1) { isolate->ThrowException(String::NewFromUtf8(isolate, "Number of arguments must be exactly 1.")); return; } if (!args[0]->IsString()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Argument 1 must be a string.")); return; } // Return state.name/state.index const String::Utf8Value str(args[0]); const State rc = JsState(*str); if (rc.index<=-256) return; const HandleScope handle_scope(isolate); Handle obj = ObjectTemplate::New(isolate); if (obj.IsEmpty()) return; obj->Set(String::NewFromUtf8(isolate, "server"), String::NewFromUtf8(isolate, *str), ReadOnly); obj->Set(String::NewFromUtf8(isolate, "index"), Integer::New(isolate, rc.index), ReadOnly); obj->Set(String::NewFromUtf8(isolate, "name"), String::NewFromUtf8(isolate, rc.name.c_str()), ReadOnly); MaybeLocal date = Date::New(isolate->GetCurrentContext(), rc.time.JavaDate()); if (rc.index>-256 && !date.IsEmpty()) SetProperty(obj, "time", date.ToLocalChecked()); args.GetReturnValue().Set(obj->NewInstance()); } void InterpreterV8::FuncNewState(const Arguments& args) { Isolate *isolate = Isolate::GetCurrent(); if (args.Length()<1 || args.Length()>3) { isolate->ThrowException(String::NewFromUtf8(isolate, "Number of arguments must be 1, 2 or 3.")); return; } if (!args[0]->IsUint32()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Argument 1 must be an uint32.")); return; } if (args.Length()>1 && !args[1]->IsString()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Argument 2 must be a string.")); return; } if (args.Length()>2 && !args[2]->IsString()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Argument 3 must be a string.")); return; } const uint32_t index = args[0]->Int32Value(); const string name = *String::Utf8Value(args[1]); const string comment = *String::Utf8Value(args[2]); if (index<10 || index>255) { isolate->ThrowException(String::NewFromUtf8(isolate, "State must be in the range [10, 255].")); return; } if (name.empty()) { isolate->ThrowException(String::NewFromUtf8(isolate, "State name must not be empty.")); return; } if (name.find_first_of(':')!=string::npos || name.find_first_of('=')!=string::npos) { isolate->ThrowException(String::NewFromUtf8(isolate, "State name must not contain : or =.")); return; } struct Find : State { Find(int idx, const string &n) : State(idx, n) { } bool operator()(const pair &p) { return index==p.first || name==p.second; } }; if (find_if(fStates.begin(), fStates.end(), Find(index, name))!=fStates.end()) { const string what = "State index ["+to_string(index)+"] or name ["+name+"] already defined."; isolate->ThrowException(String::NewFromUtf8(isolate, what.c_str())); return; } args.GetReturnValue().Set(JsNewState(index, name, comment)); } void InterpreterV8::FuncSetState(const Arguments& args) { Isolate *isolate = Isolate::GetCurrent(); if (args.Length()!=1) { isolate->ThrowException(String::NewFromUtf8(isolate, "Number of arguments must be exactly 1.")); return; } if (!args[0]->IsUint32() && !args[0]->IsString()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Argument must be an uint32 or a string.")); return; } int index = -2; if (args[0]->IsUint32()) { index = args[0]->Int32Value(); } else { const string name = *String::Utf8Value(args[0]); index = JsGetState(name); if (index==-2) { isolate->ThrowException(String::NewFromUtf8(isolate, ("State '"+name+"' not found.").c_str())); return; } } if (index<10 || index>255) { isolate->ThrowException(String::NewFromUtf8(isolate, "State must be in the range [10, 255].")); return; } args.GetReturnValue().Set(JsSetState(index)); } void InterpreterV8::FuncGetState(const Arguments& args) { Isolate *isolate = Isolate::GetCurrent(); if (args.Length()>0) { isolate->ThrowException(String::NewFromUtf8(isolate, "getState must not take arguments.")); return; } const State state = JsGetCurrentState(); const HandleScope handle_scope(isolate); Handle obj = ObjectTemplate::New(isolate); if (obj.IsEmpty()) return; obj->Set(String::NewFromUtf8(isolate, "index"), Integer::New(isolate, state.index), ReadOnly); obj->Set(String::NewFromUtf8(isolate, "name"), String::NewFromUtf8(isolate, state.name.c_str()), ReadOnly); obj->Set(String::NewFromUtf8(isolate, "description"), String::NewFromUtf8(isolate, state.comment.c_str()), ReadOnly); args.GetReturnValue().Set(obj->NewInstance()); } void InterpreterV8::FuncGetStates(const Arguments& args) { Isolate *isolate = Isolate::GetCurrent(); if (args.Length()>1) { isolate->ThrowException(String::NewFromUtf8(isolate, "getStates must not take more than one arguments.")); return; } if (args.Length()==1 && !args[0]->IsString()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Argument must be a string.")); return; } const string server = args.Length()==1 ? *String::Utf8Value(args[0]) : "DIM_CONTROL"; const vector states = JsGetStates(server); const HandleScope handle_scope(isolate); Handle obj = Object::New(isolate); if (obj.IsEmpty()) return; for (auto it=states.begin(); it!=states.end(); it++) { Handle entry = StringObject::New(String::NewFromUtf8(isolate, it->name.c_str())); if (entry.IsEmpty()) return; StringObject::Cast(*entry)->Set(String::NewFromUtf8(isolate, "description"), String::NewFromUtf8(isolate, it->comment.c_str())/*, ReadOnly*/); obj->Set(Integer::New(isolate, it->index), entry/*, ReadOnly*/); } args.GetReturnValue().Set(obj); } void InterpreterV8::FuncGetDescription(const Arguments& args) { Isolate *isolate = Isolate::GetCurrent(); if (args.Length()!=1) { isolate->ThrowException(String::NewFromUtf8(isolate, "getDescription must take exactly one argument.")); return; } if (args.Length()==1 && !args[0]->IsString()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Argument must be a string.")); return; } const string service = *String::Utf8Value(args[0]); const vector descriptions = JsGetDescription(service); const set services = JsGetServices(); auto is=services.begin(); for (; is!=services.end(); is++) if (is->name==service) break; if (is==services.end()) return; const HandleScope handle_scope(isolate); Local context = isolate->GetCurrentContext(); MaybeLocal marr = fTemplateDescription->GetFunction()->NewInstance(context); if (marr.IsEmpty()) return; Local arr = marr.ToLocalChecked(); auto it=descriptions.begin(); arr->Set(String::NewFromUtf8(isolate, "name"), String::NewFromUtf8(isolate, it->name.c_str())/*, ReadOnly*/); if (!it->comment.empty()) arr->Set(String::NewFromUtf8(isolate, "description"), String::NewFromUtf8(isolate, it->comment.c_str())/*, ReadOnly*/); if (is!=services.end()) { arr->Set(String::NewFromUtf8(isolate, "server"), String::NewFromUtf8(isolate, is->server.c_str())/*, ReadOnly*/); arr->Set(String::NewFromUtf8(isolate, "service"), String::NewFromUtf8(isolate, is->service.c_str())/*, ReadOnly*/); arr->Set(String::NewFromUtf8(isolate, "isCommand"), Boolean::New(isolate, is->iscmd)/*, ReadOnly*/); if (!is->format.empty()) arr->Set(String::NewFromUtf8(isolate, "format"), String::NewFromUtf8(isolate, is->format.c_str())/*, ReadOnly*/); } uint32_t i=0; for (it++; it!=descriptions.end(); it++) { Handle obj = Object::New(isolate); if (obj.IsEmpty()) return; if (!it->name.empty()) obj->Set(String::NewFromUtf8(isolate, "name"), String::NewFromUtf8(isolate, it->name.c_str())/*, ReadOnly*/); if (!it->comment.empty()) obj->Set(String::NewFromUtf8(isolate, "description"), String::NewFromUtf8(isolate, it->comment.c_str())/*, ReadOnly*/); if (!it->unit.empty()) obj->Set(String::NewFromUtf8(isolate, "unit"), String::NewFromUtf8(isolate, it->unit.c_str())/*, ReadOnly*/); arr->Set(i++, obj); } args.GetReturnValue().Set(arr); } void InterpreterV8::FuncGetServices(const Arguments& args) { Isolate *isolate = Isolate::GetCurrent(); if (args.Length()>2) { isolate->ThrowException(String::NewFromUtf8(isolate, "getServices must not take more than two argument.")); return; } if (args.Length()>=1 && !args[0]->IsString()) { isolate->ThrowException(String::NewFromUtf8(isolate, "First argument must be a string.")); return; } if (args.Length()==2 && !args[1]->IsBoolean()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Second argument must be a boolean.")); return; } string arg0 = args.Length() ? *String::Utf8Value(args[0]) : ""; if (arg0=="*") arg0=""; const set services = JsGetServices(); const HandleScope handle_scope(isolate); Handle arr = Array::New(isolate); if (arr.IsEmpty()) return; uint32_t i=0; for (auto is=services.begin(); is!=services.end(); is++) { if (!arg0.empty() && is->name.find(arg0)!=0) continue; if (args.Length()==2 && args[1]->BooleanValue()!=is->iscmd) continue; Handle obj = Object::New(isolate); if (obj.IsEmpty()) return; obj->Set(String::NewFromUtf8(isolate, "name"), String::NewFromUtf8(isolate, is->name.c_str())/*, ReadOnly*/); obj->Set(String::NewFromUtf8(isolate, "server"), String::NewFromUtf8(isolate, is->server.c_str())/*, ReadOnly*/); obj->Set(String::NewFromUtf8(isolate, "service"), String::NewFromUtf8(isolate, is->service.c_str())/*, ReadOnly*/); obj->Set(String::NewFromUtf8(isolate, "isCommand"), Boolean::New(isolate, is->iscmd)/*, ReadOnly*/); if (!is->format.empty()) obj->Set(String::NewFromUtf8(isolate, "format"), String::NewFromUtf8(isolate, is->format.c_str())/*, ReadOnly*/); arr->Set(i++, obj); } args.GetReturnValue().Set(arr); } // ========================================================================== // Internal functions // ========================================================================== // The callback that is invoked by v8 whenever the JavaScript 'print' // function is called. Prints its arguments on stdout separated by // spaces and ending with a newline. void InterpreterV8::FuncLog(const Arguments& args) { for (int i=0; iThrowException(String::NewFromUtf8(isolate, "Number of arguments must be one.")); return; } if (!args[0]->IsString()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Argument must be a string.")); return; } const String::Utf8Value file(args[0]); if (*file == NULL) { isolate->ThrowException(String::NewFromUtf8(isolate, "File name missing.")); return; } if (strlen(*file)==0) { isolate->ThrowException(String::NewFromUtf8(isolate, "File name empty.")); return; } const auto path = boost::filesystem::path(*file); const auto f = path.is_absolute() ? path : boost::filesystem::path(fIncludePath)/path; izstream fin(*file);//f.string().c_str()); if (!fin) { isolate->ThrowException(String::NewFromUtf8(isolate, errno!=0?strerror(errno):"Insufficient memory for decompression")); return; } string buffer; getline(fin, buffer, '\0'); if ((fin.fail() && !fin.eof()) || fin.bad()) { isolate->ThrowException(String::NewFromUtf8(isolate, strerror(errno))); return; } if (buffer.length()>1 && buffer[0]=='#' && buffer[1]=='!') buffer.insert(0, "//"); args.GetReturnValue().Set(ExecuteCode(buffer, *file)); } void InterpreterV8::FuncFile(const Arguments& args) { Isolate *isolate = Isolate::GetCurrent(); if (args.Length()!=1 && args.Length()!=2) { isolate->ThrowException(String::NewFromUtf8(isolate, "Number of arguments must be one or two.")); return; } const String::Utf8Value file(args[0]); if (*file == NULL) { isolate->ThrowException(String::NewFromUtf8(isolate, "File name missing")); return; } if (args.Length()==2 && !args[1]->IsString()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Second argument must be a string.")); return; } const string delim = args.Length()==2 ? *String::Utf8Value(args[1]) : ""; if (args.Length()==2 && delim.size()!=1) { isolate->ThrowException(String::NewFromUtf8(isolate, "Second argument must be a string of length 1.")); return; } const HandleScope handle_scope(isolate); const auto path = boost::filesystem::path(*file); const auto f = path.is_absolute() ? path : boost::filesystem::path(fIncludePath)/path; izstream fin(*file);//f.string().c_str()); if (!fin) { isolate->ThrowException(String::NewFromUtf8(isolate, errno!=0?strerror(errno):"Insufficient memory for decompression")); return; } if (args.Length()==1) { string buffer; getline(fin, buffer, '\0'); if ((fin.fail() && !fin.eof()) || fin.bad()) { isolate->ThrowException(String::NewFromUtf8(isolate, strerror(errno))); return; } Handle str = StringObject::New(String::NewFromUtf8(isolate, buffer.c_str())); StringObject::Cast(*str)->Set(String::NewFromUtf8(isolate, "name"), String::NewFromUtf8(isolate, *file)); args.GetReturnValue().Set(str); return; } Handle arr = Array::New(isolate); if (arr.IsEmpty()) return; int i=0; string buffer; while (getline(fin, buffer, delim[0])) arr->Set(i++, String::NewFromUtf8(isolate, buffer.c_str())); if ((fin.fail() && !fin.eof()) || fin.bad()) { isolate->ThrowException(String::NewFromUtf8(isolate, strerror(errno))); return; } arr->Set(String::NewFromUtf8(isolate, "name"), String::NewFromUtf8(isolate, *file)); arr->Set(String::NewFromUtf8(isolate, "delim"), String::NewFromUtf8(isolate, delim.c_str(), NewStringType::kNormal, 1).ToLocalChecked()); args.GetReturnValue().Set(arr); } // ========================================================================== // Mail // ========================================================================== void InterpreterV8::ConstructorMail(const Arguments &args) { Isolate *isolate = Isolate::GetCurrent(); if (!args.IsConstructCall()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Mail must be called as constructor")); return; } if (args.Length()!=1 || !args[0]->IsString()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Constructor must be called with a single string as argument")); return; } const HandleScope handle_scope(isolate); Handle rec = Array::New(isolate); Handle att = Array::New(isolate); Handle bcc = Array::New(isolate); Handle cc = Array::New(isolate); Handle txt = Array::New(isolate); if (rec.IsEmpty() || att.IsEmpty() || bcc.IsEmpty() || cc.IsEmpty() || txt.IsEmpty()) return; Handle self = args.This(); self->Set(String::NewFromUtf8(isolate, "subject"), args[0]->ToString()/*, ReadOnly*/); self->Set(String::NewFromUtf8(isolate, "recipients"), rec/*, ReadOnly*/); self->Set(String::NewFromUtf8(isolate, "attachments"), att/*, ReadOnly*/); self->Set(String::NewFromUtf8(isolate, "bcc"), bcc/*, ReadOnly*/); self->Set(String::NewFromUtf8(isolate, "cc"), cc/*, ReadOnly*/); self->Set(String::NewFromUtf8(isolate, "text"), txt/*, ReadOnly*/); self->Set(String::NewFromUtf8(isolate, "send"), FunctionTemplate::New(isolate, WrapSendMail)->GetFunction()/*, ReadOnly*/); args.GetReturnValue().Set(self); } vector InterpreterV8::ValueToArray(const Handle &val, bool only) { vector rc; Handle arr = Handle::Cast(val); for (uint32_t i=0; iLength(); i++) { Handle obj = arr->Get(i); if (obj.IsEmpty()) continue; if (obj->IsNull() || obj->IsUndefined()) continue; if (only && !obj->IsString()) continue; rc.push_back(*String::Utf8Value(obj->ToString())); } return rc; } void InterpreterV8::FuncSendMail(const Arguments& args) { Isolate *isolate = Isolate::GetCurrent(); const HandleScope handle_scope(isolate); if (args.Length()>1) { isolate->ThrowException(String::NewFromUtf8(isolate, "Only one argument allowed.")); return; } if (args.Length()==1 && !args[0]->IsBoolean()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Argument must be a boolean.")); return; } const bool block = args.Length()==0 || args[0]->BooleanValue(); const Handle sub = args.This()->Get(String::NewFromUtf8(isolate, "subject")); const Handle rec = args.This()->Get(String::NewFromUtf8(isolate, "recipients")); const Handle txt = args.This()->Get(String::NewFromUtf8(isolate, "text")); const Handle att = args.This()->Get(String::NewFromUtf8(isolate, "attachments")); const Handle bcc = args.This()->Get(String::NewFromUtf8(isolate, "bcc")); const Handle cc = args.This()->Get(String::NewFromUtf8(isolate, "cc")); const vector vrec = ValueToArray(rec); const vector vtxt = ValueToArray(txt, false); const vector vatt = ValueToArray(att); const vector vbcc = ValueToArray(bcc); const vector vcc = ValueToArray(cc); if (vrec.size()==0) { isolate->ThrowException(String::NewFromUtf8(isolate, "At least one valid string is required in 'recipients'.")); return; } if (vtxt.size()==0) { isolate->ThrowException(String::NewFromUtf8(isolate, "At least one valid string is required in 'text'.")); return; } const string subject = *String::Utf8Value(sub->ToString()); FILE *pipe = popen(("from=no-reply@fact-project.org mailx -~ "+vrec[0]).c_str(), "w"); if (!pipe) { isolate->ThrowException(String::NewFromUtf8(isolate, strerror(errno))); return; } fprintf(pipe, "%s", ("~s"+subject+"\n").c_str()); for (auto it=vrec.begin()+1; itlength()+1, 1, pipe); fprintf(pipe, "\n---\nsent by dimctrl"); if (!block) return; const int rc = pclose(pipe); const Locker lock(isolate); args.GetReturnValue().Set(WEXITSTATUS(rc)); } // ========================================================================= // Curl // ========================================================================== void InterpreterV8::ConstructorCurl(const Arguments &args) { Isolate *isolate = Isolate::GetCurrent(); if (!args.IsConstructCall()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Curl must be called as constructor")); return; } if (args.Length()!=1 || !args[0]->IsString()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Constructor must be called with a single string as argument")); return; } const HandleScope handle_scope(isolate); Handle data = Array::New(isolate); if (data.IsEmpty()) return; Handle self = args.This(); self->Set(String::NewFromUtf8(isolate, "url"), args[0]->ToString()/*, ReadOnly*/); self->Set(String::NewFromUtf8(isolate, "data"), data/*, ReadOnly*/); self->Set(String::NewFromUtf8(isolate, "send"), FunctionTemplate::New(isolate, WrapSendCurl)->GetFunction()/*, ReadOnly*/); args.GetReturnValue().Set(self); } void InterpreterV8::FuncSendCurl(const Arguments& args) { Isolate *isolate = Isolate::GetCurrent(); if (args.Length()>1) { isolate->ThrowException(String::NewFromUtf8(isolate, "Only one argument allowed.")); return; } if (args.Length()==1 && !args[0]->IsBoolean()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Argument must be a boolean.")); return; } const HandleScope handle_scope(isolate); const bool block = args.Length()==0 || args[0]->BooleanValue(); const Handle url = args.This()->Get(String::NewFromUtf8(isolate, "url")); const Handle data = args.This()->Get(String::NewFromUtf8(isolate, "data")); const vector vdata = ValueToArray(data); const string sdata = boost::algorithm::join(vdata, "&"); const string surl = *String::Utf8Value(url->ToString()); string cmd = "curl -sSf "; if (!sdata.empty()) cmd += "--data '"+sdata+"' "; cmd += "'http://"+surl+"' 2>&1 "; FILE *pipe = popen(cmd.c_str(), "r"); if (!pipe) { isolate->ThrowException(String::NewFromUtf8(isolate, strerror(errno))); return; } if (!block) return; string txt; while (!feof(pipe)) { char buf[1025]; if (fgets(buf, 1024, pipe)==NULL) break; txt += buf; } const int rc = pclose(pipe); Handle obj = Object::New(isolate); obj->Set(String::NewFromUtf8(isolate, "cmd"), String::NewFromUtf8(isolate, cmd.c_str())); obj->Set(String::NewFromUtf8(isolate, "data"), String::NewFromUtf8(isolate, txt.c_str())); obj->Set(String::NewFromUtf8(isolate, "rc"), Integer::NewFromUnsigned(isolate, WEXITSTATUS(rc))); const Locker lock(isolate); args.GetReturnValue().Set(obj); } // ========================================================================== // Database // ========================================================================== void InterpreterV8::FuncDbClose(const Arguments &args) { Isolate* isolate = Isolate::GetCurrent(); void *ptr = args.This()->GetInternalField(0).As()->Value(); if (!ptr) { args.GetReturnValue().Set(false); return; } #ifdef HAVE_SQL Database *db = reinterpret_cast(ptr); auto it = find(fDatabases.begin(), fDatabases.end(), db); fDatabases.erase(it); delete db; #endif const HandleScope handle_scope(isolate); args.This()->SetInternalField(0, External::New(isolate, 0)); args.GetReturnValue().Set(true); } void InterpreterV8::FuncDbQuery(const Arguments &args) { Isolate* isolate = Isolate::GetCurrent(); if (args.Length()==0) { isolate->ThrowException(String::NewFromUtf8(isolate, "Arguments expected.")); return; } void *ptr = args.This()->GetInternalField(0).As()->Value(); if (!ptr) return; string query; for (int i=0; i(ptr); const mysqlpp::StoreQueryResult res = db->query(query).store(); Handle ret = Array::New(isolate); if (ret.IsEmpty()) return; ret->Set(String::NewFromUtf8(isolate, "table"), String::NewFromUtf8(isolate, res.table())/*, ReadOnly*/); ret->Set(String::NewFromUtf8(isolate, "query"), String::NewFromUtf8(isolate, query.c_str())/*, ReadOnly*/); Handle cols = Array::New(isolate); if (cols.IsEmpty()) return; int irow=0; for (vector::const_iterator it=res.begin(); it row = Object::New(isolate); if (row.IsEmpty()) return; const mysqlpp::FieldNames *list = it->field_list().list; for (size_t i=0; isize(); i++) { const Handle name = String::NewFromUtf8(isolate, (*list)[i].c_str()); if (irow==0) cols->Set(i, name); if ((*it)[i].is_null()) { row->Set(name, Undefined(isolate)/*, ReadOnly*/); continue; } const string sql_type = (*it)[i].type().sql_name(); const bool uns = sql_type.find("UNSIGNED")==string::npos; if (sql_type.find("BIGINT")!=string::npos) { if (uns) { const uint64_t val = (uint64_t)(*it)[i]; if (val>UINT32_MAX) row->Set(name, Number::New(isolate, val)/*, ReadOnly*/); else row->Set(name, Integer::NewFromUnsigned(isolate, val)/*, ReadOnly*/); } else { const int64_t val = (int64_t)(*it)[i]; if (valINT32_MAX) row->Set(name, Number::New(isolate, val)/*, ReadOnly*/); else row->Set(name, Integer::NewFromUnsigned(isolate, val)/*, ReadOnly*/); } continue; } // 32 bit if (sql_type.find("INT")!=string::npos) { if (uns) row->Set(name, Integer::NewFromUnsigned(isolate, (uint32_t)(*it)[i])/*, ReadOnly*/); else row->Set(name, Integer::New(isolate, (int32_t)(*it)[i])/*, ReadOnly*/); continue; } if (sql_type.find("BOOL")!=string::npos ) { row->Set(name, Boolean::New(isolate, (bool)(*it)[i])/*, ReadOnly*/); continue; } if (sql_type.find("FLOAT")!=string::npos) { ostringstream val; val << setprecision(7) << (float)(*it)[i]; row->Set(name, Number::New(isolate, stod(val.str()))/*, ReadOnly*/); continue; } if (sql_type.find("DOUBLE")!=string::npos) { row->Set(name, Number::New(isolate, (double)(*it)[i])/*, ReadOnly*/); continue; } if (sql_type.find("CHAR")!=string::npos || sql_type.find("TEXT")!=string::npos) { row->Set(name, String::NewFromUtf8(isolate, (const char*)(*it)[i])/*, ReadOnly*/); continue; } time_t date = 0; if (sql_type.find("TIMESTAMP")!=string::npos) date = mysqlpp::Time((*it)[i]); if (sql_type.find("DATETIME")!=string::npos) date = mysqlpp::DateTime((*it)[i]); if (sql_type.find(" DATE ")!=string::npos) date = mysqlpp::Date((*it)[i]); if (date>0) { // It is important to catch the exception thrown // by Date::New in case of thread termination! const Local val = Date::New(isolate, date*1000); if (val.IsEmpty()) return; row->Set(name, val/*, ReadOnly*/); } } ret->Set(irow++, row); } if (irow>0) ret->Set(String::NewFromUtf8(isolate, "cols"), cols/*, ReadOnly*/); args.GetReturnValue().Set(ret); } catch (const exception &e) { isolate->ThrowException(String::NewFromUtf8(isolate, e.what())); } #endif } void InterpreterV8::FuncDatabase(const Arguments &args) { Isolate *isolate = Isolate::GetCurrent(); if (!args.IsConstructCall()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Database must be called as constructor.")); return; } if (args.Length()!=1) { isolate->ThrowException(String::NewFromUtf8(isolate, "Number of arguments must be 1.")); return; } if (!args[0]->IsString()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Argument 1 not a string.")); return; } #ifdef HAVE_SQL try { const HandleScope handle_scope(isolate); // if (!args.IsConstructCall()) // return Constructor(fTemplateDatabase, args); Database *db = new Database(*String::Utf8Value(args[0])); fDatabases.push_back(db); Handle self = args.This(); self->Set(String::NewFromUtf8(isolate, "user"), String::NewFromUtf8(isolate, db->user.c_str())/*, ReadOnly*/); self->Set(String::NewFromUtf8(isolate, "server"), String::NewFromUtf8(isolate, db->server.c_str())/*, ReadOnly*/); self->Set(String::NewFromUtf8(isolate, "database"), String::NewFromUtf8(isolate, db->db.c_str())/*, ReadOnly*/); if (db->port) self->Set(String::NewFromUtf8(isolate, "port"), Integer::NewFromUnsigned(isolate, db->port)); self->Set(String::NewFromUtf8(isolate, "query"), FunctionTemplate::New(isolate, WrapDbQuery)->GetFunction()/*, ReadOnly*/); self->Set(String::NewFromUtf8(isolate, "close"), FunctionTemplate::New(isolate, WrapDbClose)->GetFunction()/*, ReadOnly*/); self->SetInternalField(0, External::New(isolate, db)); args.GetReturnValue().Set(self); } catch (const exception &e) { isolate->ThrowException(String::NewFromUtf8(isolate, e.what())); } #endif } // ========================================================================== // Services // ========================================================================== Handle InterpreterV8::Convert(char type, const char* &ptr) { Isolate *isolate = Isolate::GetCurrent(); // Dim values are always unsigned per (FACT++) definition switch (type) { case 'F': { // Remove the "imprecision" effect coming from casting a float to // a double and then showing it with double precision ostringstream val; val << setprecision(7) << *reinterpret_cast(ptr); ptr += 4; return Number::New(isolate, stod(val.str())); } case 'D': { Handle v=Number::New(isolate, *reinterpret_cast(ptr)); ptr+=8; return v; } case 'I': case 'L': { Handle v=Integer::NewFromUnsigned(isolate, *reinterpret_cast(ptr)); ptr += 4; return v; } case 'X': { const int64_t val = *reinterpret_cast(ptr); ptr += 8; if (val>=0 && val<=UINT32_MAX) return Integer::NewFromUnsigned(isolate, val); if (val>=INT32_MIN && val<0) return Integer::New(isolate, val); return Number::New(isolate, val); } case 'S': { Handle v=Integer::NewFromUnsigned(isolate, *reinterpret_cast(ptr)); ptr += 2; return v; } case 'C': { Handle v=Integer::NewFromUnsigned(isolate, (uint16_t)*reinterpret_cast(ptr)); ptr += 1; return v; } } return Undefined(isolate); } void InterpreterV8::FuncClose(const Arguments &args) { Isolate *isolate = Isolate::GetCurrent(); const HandleScope handle_scope(isolate); //const void *ptr = Local::Cast(args.Holder()->GetInternalField(0))->Value(); const String::Utf8Value str(args.This()->Get(String::NewFromUtf8(isolate, "name"))); const auto it = fReverseMap.find(*str); if (it!=fReverseMap.end()) fReverseMap.erase(it); args.This()->Set(String::NewFromUtf8(isolate, "isOpen"), Boolean::New(isolate, false)/*, ReadOnly*/); args.GetReturnValue().Set(JsUnsubscribe(*str)); } Handle InterpreterV8::ConvertEvent(const EventImp *evt, uint64_t counter, const char *str) { Isolate *isolate = Isolate::GetCurrent(); const vector vec = JsDescription(str); Local context = isolate->GetCurrentContext(); MaybeLocal mret = fTemplateEvent->GetFunction()->NewInstance(context); if (mret.IsEmpty()) return Undefined(isolate); const Local date = Date::New(isolate, evt->GetJavaDate()); if (date.IsEmpty()) return Undefined(isolate); Local ret = mret.ToLocalChecked(); ret->Set(String::NewFromUtf8(isolate, "name"), String::NewFromUtf8(isolate, str)/*, ReadOnly*/); ret->Set(String::NewFromUtf8(isolate, "format"), String::NewFromUtf8(isolate, evt->GetFormat().c_str())/*, ReadOnly*/); ret->Set(String::NewFromUtf8(isolate, "qos"), Integer::New(isolate, evt->GetQoS())/*, ReadOnly*/); ret->Set(String::NewFromUtf8(isolate, "size"), Integer::New(isolate, evt->GetSize())/*, ReadOnly*/); ret->Set(String::NewFromUtf8(isolate, "counter"), Integer::New(isolate, counter)/*, ReadOnly*/); if (evt->GetJavaDate()>0) ret->Set(String::NewFromUtf8(isolate, "time"), date/*, ReadOnly*/); // If names are available data will also be provided as an // object. If an empty event was received, but names are available, // the object will be empty. Otherwise 'obj' will be undefined. // obj===undefined: no data received // obj!==undefined, length==0: names for event available // obj!==undefined, obj.length>0: names available, data received Handle named = Object::New(isolate); if (vec.size()>0) ret->Set(String::NewFromUtf8(isolate, "obj"), named/*, ReadOnly*/); // If no event was received (usually a disconnection event in // the context of FACT++), no data is returned if (evt->IsEmpty()) return ret; // If valid data was received, but the size was zero, then // null is returned as data // data===undefined: no data received // data===null: event received, but no data // data.length>0: event received, contains data if (evt->GetSize()==0 || evt->GetFormat().empty()) { ret->Set(String::NewFromUtf8(isolate, "data"), Null(isolate)/*, ReadOnly*/); return ret; } // It seems a copy is required either in the boost which comes with // Ubuntu 16.04 or in gcc5 ?! const string fmt = evt->GetFormat(); typedef boost::char_separator separator; const boost::tokenizer tokenizer(fmt, separator(";:")); const vector tok(tokenizer.begin(), tokenizer.end()); //Handle arr = tok.size()>1 ? Array::New(isolate) : ret; Handle arr; if (tok.size()>1) arr = Array::New(isolate); else arr = ret; if (arr.IsEmpty()) return Undefined(isolate); const char *ptr = evt->GetText(); const char *end = evt->GetText()+evt->GetSize(); try { size_t pos = 1; for (auto it=tok.begin(); it0) return Exception::Error(String::NewFromUtf8(isolate, ("Number of received bytes ["+to_string(evt->GetSize())+"] does not match format ["+evt->GetFormat()+"]").c_str())); // Check if format has a number attached. // If no number is attached calculate number of elements const uint32_t cnt = it==tok.end() ? (end-ptr)/sz : stoi(it->c_str()); // is_str: Array of type C but unknown size (String) // is_one: Array of known size, but size is 1 (I:1) const bool is_str = type=='C' && it==tok.end(); const bool is_one = cnt==1 && it!=tok.end(); Handle v; if (is_str) v = String::NewFromUtf8(isolate, ptr); if (is_one) v = Convert(type, ptr); // Array of known (I:5) or unknown size (I), but no string if (!is_str && !is_one) { Handle a = Array::New(isolate, cnt); if (a.IsEmpty()) return Undefined(isolate); for (uint32_t i=0; iSet(i, Convert(type, ptr)); v = a; } if (tok.size()>1) arr->Set(pos-1, v); else ret->Set(String::NewFromUtf8(isolate, "data"), v/*, ReadOnly*/); if (!name.empty()) { const Handle n = String::NewFromUtf8(isolate, name.c_str()); named->Set(n, v); } } if (tok.size()>1) ret->Set(String::NewFromUtf8(isolate, "data"), arr/*, ReadOnly*/); return ret; } catch (...) { return Exception::Error(String::NewFromUtf8(isolate, ("Format string conversion '"+evt->GetFormat()+"' failed.").c_str())); } } /* Handle InterpreterV8::FuncGetData(const Arguments &args) { HandleScope handle_scope; const String::Utf8Value str(args.Holder()->Get(String::NewFromUtf8(isolate, "name"))); const pair p = JsGetEvent(*str); const EventImp *evt = p.second; if (!evt) return Undefined(isolate); //if (counter==cnt) // return info.Holder();//Holder()->Get(String::NewFromUtf8(isolate, "data")); Handle ret = ConvertEvent(evt, p.first, *str); return ret->IsNativeError() ? isolate->ThrowException(ret) : handle_scope.Escape(ret); } */ void InterpreterV8::FuncGetData(const Arguments &args) { Isolate *isolate = Isolate::GetCurrent(); if (args.Length()>2) { isolate->ThrowException(String::NewFromUtf8(isolate, "Number of arguments must not be greater than 2.")); return; } if (args.Length()>=1 && !args[0]->IsInt32() && !args[0]->IsNull()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Argument 1 not an uint32.")); return; } if (args.Length()==2 && !args[1]->IsBoolean()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Argument 2 not a boolean.")); return; } // Using a Javascript function has the advantage that it is fully // interruptable without the need of C++ code const bool null = args.Length()>=1 && args[0]->IsNull(); const int32_t timeout = args.Length()>=1 ? args[0]->Int32Value() : 0; const bool named = args.Length()<2 || args[1]->BooleanValue(); const HandleScope handle_scope(isolate); const Handle data = String::NewFromUtf8(isolate, "data"); const Handle object = String::NewFromUtf8(isolate, "obj"); const String::Utf8Value name(args.Holder()->Get(String::NewFromUtf8(isolate, "name"))); TryCatch exception(isolate); Time t; while (!exception.HasCaught()) { const pair p = JsGetEvent(*name); const EventImp *evt = p.second; if (evt) { const Handle val = ConvertEvent(evt, p.first, *name); if (val->IsNativeError()) { isolate->ThrowException(val); return; } // Protect against the return of an exception if (val->IsObject()) { const Handle event = val->ToObject(); const Handle obj = event->Get(named?object:data); if (!obj.IsEmpty()) { if (!named) { // No names (no 'obj'), but 'data' if (!obj->IsUndefined()) { args.GetReturnValue().Set(val); return; } } else { // Has names and data was received? if (obj->IsObject() && obj->ToObject()->GetOwnPropertyNames()->Length()>0) { args.GetReturnValue().Set(val); return; } } } } } if (args.Length()==0) break; if (!null && Time()-t>=boost::posix_time::milliseconds(abs(timeout))) break; // Theoretically, the CPU usage can be reduced by maybe a factor // of four using a larger value, but this also means that the // JavaScript is locked for a longer time. const Unlocker unlock(isolate); usleep(1000); } // This hides the location of the exception, which is wanted. if (exception.HasCaught()) { exception.ReThrow(); return; } if (timeout<0) return; const string str = "Waiting for a valid event of "+string(*name)+" timed out."; isolate->ThrowException(String::NewFromUtf8(isolate, str.c_str())); } void InterpreterV8::TerminateExecution(Isolate* isolate) { // This makes all threads/contexts sooner or later terminate isolate->TerminateExecution(); } // This is a callback from the RemoteControl piping event handling // to the java script ---> in test phase! void InterpreterV8::JsHandleEvent(const EventImp &evt, uint64_t cnt, const string &service) { // FIXME: This is not thread safe! // This is copied as fMainThread=0 is used to signal // that no execution should take place anymore Isolate *isolate = fMainThread; if (!isolate) return; //{ // We want to run as an interrupt in the main thread const Locker locker(isolate); if (!fMainThread) return; const Isolate::Scope isolate_scope(isolate); const HandleScope handle_scope(isolate); const auto it = fReverseMap.find(service); if (it==fReverseMap.end()) return; Handle fun = it->second.Get(isolate); const Context::Scope scope(fun->CreationContext()); const Handle onchange = String::NewFromUtf8(isolate, "onchange"); if (!fun->Has(onchange)) return; const Handle val = fun->Get(onchange); if (!val->IsFunction()) return; TryCatch exception(isolate); Handle ret = ConvertEvent(&evt, cnt, service.c_str()); if (ret->IsObject()) Handle::Cast(val)->Call(fun->CreationContext(), fun, 1, &ret).IsEmpty(); if (!HandleException(exception, "Service.onchange")) isolate->AddBeforeCallEnteredCallback(TerminateExecution); if (ret->IsNativeError()) { JsException(service+".onchange callback - "+*String::Utf8Value(ret)); isolate->AddBeforeCallEnteredCallback(TerminateExecution); } // isolate->DiscardThreadSpecificMetadata(); /* // The Isolation context isolates (keeps local) interrupts and termination requests! // Terminating execution doesn't work while the JS is still runnning in this script if (term) { // There is no point in calling TerminateExecution before (after releasing // the lock) the scheduler has entered the main thread again, as the // TerminateExecution would just be lost. const Unlocker unlock(isolate); for (int i=0; i<10 && !fMainThread->InContext(); i++) usleep(50); isolate->TerminateExecution(); } */ } void InterpreterV8::OnChangeSet(Local name, Local value, const PropertyCallbackInfo &) { Isolate *isolate = Isolate::GetCurrent(); // Returns the value if the setter intercepts the request. // Otherwise, returns an empty handle. const string server = *String::Utf8Value(name); auto it = fStateCallbacks.find(server); if (it!=fStateCallbacks.end()) fStateCallbacks.erase(it); if (value->IsFunction()) fStateCallbacks[server] = UniquePersistent(isolate, value->ToObject()); } void InterpreterV8::JsHandleState(const std::string &server, const State &state) { // FIXME: This is not thread safe! // This is copied as fMainThread=0 is used to signal // that no execution should take place anymore Isolate *isolate = fMainThread; if (!isolate) return; //{ // We want to run as an interrupt in the main thread const Locker locker(isolate); if (!fMainThread) return; const Isolate::Scope isolate_scope(isolate); const HandleScope handle_scope(isolate); auto it = fStateCallbacks.find(server); if (it==fStateCallbacks.end()) { it = fStateCallbacks.find("*"); if (it==fStateCallbacks.end()) return; } Handle fun = it->second.Get(isolate); const Context::Scope scope(fun->CreationContext()); // ------------------------------------------------------------------- Handle obj = ObjectTemplate::New(isolate); obj->Set(String::NewFromUtf8(isolate, "server"), String::NewFromUtf8(isolate, server.c_str()), ReadOnly); if (state.index>-256) { obj->Set(String::NewFromUtf8(isolate, "index"), Integer::New(isolate, state.index), ReadOnly); obj->Set(String::NewFromUtf8(isolate, "name"), String::NewFromUtf8(isolate, state.name.c_str()), ReadOnly); obj->Set(String::NewFromUtf8(isolate, "comment"), String::NewFromUtf8(isolate, state.comment.c_str()), ReadOnly); MaybeLocal date = Date::New(isolate->GetCurrentContext(), state.time.JavaDate()); if (!date.IsEmpty()) SetProperty(obj, "time", date.ToLocalChecked()); /* { obj->Set(String::NewFromUtf8(isolate, "time"), date.ToLocalChecked()->ToString(), ReadOnly); // This crahshes when called a secont time. I don't know why. // obj->Set(String::NewFromUtf8(isolate, "time"), date.ToLocalChecked(), ReadOnly); }*/ } // ------------------------------------------------------------------- TryCatch exception(isolate); Handle args[] = { obj->NewInstance() }; Handle::Cast(fun)->Call(fun->CreationContext(), fun, 1, args).IsEmpty(); if (!HandleException(exception, "dim.onchange")) isolate->AddBeforeCallEnteredCallback(TerminateExecution); // isolate->DiscardThreadSpecificMetadata(); } // ========================================================================== // Interrupt handling // ========================================================================== void InterpreterV8::FuncSetInterrupt(const Arguments &args) { Isolate *isolate = Isolate::GetCurrent(); if (args.Length()!=1) { isolate->ThrowException(String::NewFromUtf8(isolate, "Number of arguments must be 1.")); return; } if (!args[0]->IsNull() && !args[0]->IsUndefined() && !args[0]->IsFunction()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Argument not a function, null or undefined.")); return; } if (args[0]->IsNull() || args[0]->IsUndefined()) { fInterruptCallback.Reset(); return; } // Returns the value if the setter intercepts the request. Otherwise, returns an empty handle. fInterruptCallback = UniquePersistent(isolate, args[0]->ToObject()); } Handle InterpreterV8::HandleInterruptImp(string str, uint64_t time) { Isolate *isolate = Isolate::GetCurrent(); if (fInterruptCallback.IsEmpty()) return Handle(); const size_t p = str.find_last_of('\n'); const string usr = p==string::npos?"":str.substr(p+1); string irq = p==string::npos?str:str.substr(0, p); map data; try { data = Tools::Split(irq, true); } catch (const exception &e) { irq = "ERROR"; data["0"] = e.what(); JsWarn("Couldn't parse interrupt: "+irq+" ["+string(e.what())+"]"); } Local irq_str = String::NewFromUtf8(isolate, irq.c_str()); Local usr_str = String::NewFromUtf8(isolate, usr.c_str()); Local date = Date::New(isolate, time); Handle arr = Array::New(isolate, data.size()); if (date.IsEmpty() || arr.IsEmpty()) return Handle(); for (auto it=data.begin(); it!=data.end(); it++) arr->Set(String::NewFromUtf8(isolate, it->first.c_str()), String::NewFromUtf8(isolate, it->second.c_str())); Handle args[] = { irq_str, arr, date, usr_str }; Handle fun = fInterruptCallback.Get(isolate); //const Context::Scope scope(obj->CreationContext()); MaybeLocal ret = Handle::Cast(fun)->Call(fun->CreationContext(), fun, 4, args); return ret.IsEmpty() ? Handle() : ret.ToLocalChecked(); } int InterpreterV8::JsHandleInterrupt(const EventImp &evt) { // FIXME: This is not thread safe! // This is copied as fMainThread=0 is used to signal // that no execution should take place anymore Isolate *isolate = fMainThread; if (!isolate) return -42; //{ // We want to run as an interrupt in the main thread const Locker locker(isolate); if (!fMainThread) return -42; const Isolate::Scope isolate_scope(isolate); const HandleScope handle_scope(isolate); if (fInterruptCallback.IsEmpty()) return -42; Handle fun = fInterruptCallback.Get(isolate); const Context::Scope scope(fun->CreationContext()); // ------------------------------------------------------------------- TryCatch exception(isolate); const Handle val = HandleInterruptImp(evt.GetString(), evt.GetJavaDate()); const int rc = !val.IsEmpty() && val->IsInt32() ? val->Int32Value() : 0; if (!HandleException(exception, "interrupt")) isolate->AddBeforeCallEnteredCallback(TerminateExecution); return rc<10 || rc>255 ? -42 : rc; } void InterpreterV8::FuncTriggerInterrupt(const Arguments &args) { Isolate *isolate = Isolate::GetCurrent(); string data; for (int i=0; iThrowException(String::NewFromUtf8(isolate, "No argument must contain line breaks.")); return; } if (!*str) continue; data += *str; data += ' '; } const HandleScope handle_scope(isolate); const Handle rc = HandleInterruptImp(Tools::Trim(data), Time().JavaDate()); args.GetReturnValue().Set(rc); } // ========================================================================== // Class 'Subscription' // ========================================================================== void InterpreterV8::FuncSubscription(const Arguments &args) { Isolate *isolate = Isolate::GetCurrent(); if (args.Length()!=1 && args.Length()!=2) { isolate->ThrowException(String::NewFromUtf8(isolate, "Number of arguments must be one or two.")); return; } if (!args[0]->IsString()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Argument 1 must be a string.")); return; } if (args.Length()==2 && !args[1]->IsFunction()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Argument 2 must be a function.")); return; } const String::Utf8Value str(args[0]); if (!args.IsConstructCall()) { const auto it = fReverseMap.find(*str); if (it!=fReverseMap.end()) args.GetReturnValue().Set(it->second.Get(isolate)/*Local::New(isolate, it->second)*/); return; } const HandleScope handle_scope(isolate); Handle self = args.This(); self->Set(String::NewFromUtf8(isolate, "get"), FunctionTemplate::New(isolate, WrapGetData)->GetFunction()/*, ReadOnly*/); self->Set(String::NewFromUtf8(isolate, "close"), FunctionTemplate::New(isolate, WrapClose)->GetFunction()/*, ReadOnly*/); self->Set(String::NewFromUtf8(isolate, "name"), String::NewFromUtf8(isolate, *str)/*, ReadOnly*/); self->Set(String::NewFromUtf8(isolate, "isOpen"), Boolean::New(isolate, true)); if (args.Length()==2) self->Set(String::NewFromUtf8(isolate, "onchange"), args[1]); fReverseMap[*str] = UniquePersistent(isolate, self); void *ptr = JsSubscribe(*str); if (ptr==0) { isolate->ThrowException(String::NewFromUtf8(isolate, ("Subscription to '"+string(*str)+"' already exists.").c_str())); return; } self->SetInternalField(0, External::New(isolate, ptr)); // Persistent p = Persistent::New(obj->NewInstance()); // obj.MakeWeak((void*)1, Cleanup); // return obj; } // ========================================================================== // Astrometry // ========================================================================== #ifdef HAVE_NOVA double InterpreterV8::GetDataMember(const Arguments &args, const char *name) { Isolate *isolate = Isolate::GetCurrent(); return args.This()->Get(String::NewFromUtf8(isolate, name))->NumberValue(); } void InterpreterV8::CalcDist(const Arguments &args, const bool local) { Isolate *isolate = Isolate::GetCurrent(); if (args.Length()!=2) { isolate->ThrowException(String::NewFromUtf8(isolate, "dist must be called with exactly two arguments.")); return; } if (!args[0]->IsObject() || !args[1]->IsObject()) { isolate->ThrowException(String::NewFromUtf8(isolate, "At least one argument not an object.")); return; } // FiXME: Add a check for the argument type const HandleScope handle_scope(isolate); Handle obj[2] = { Handle::Cast(args[0]), Handle::Cast(args[1]) }; const Handle s_theta = String::NewFromUtf8(isolate, local?"zd":"dec"); // was: zd const Handle s_phi = String::NewFromUtf8(isolate, local?"az":"ra"); // was: az const double conv_t = M_PI/180; const double conv_p = local ? -M_PI/180 : M_PI/12; const double offset = local ? 0 : M_PI; const double theta0 = offset - obj[0]->Get(s_theta)->NumberValue() * conv_t; const double phi0 = obj[0]->Get(s_phi )->NumberValue() * conv_p; const double theta1 = offset - obj[1]->Get(s_theta)->NumberValue() * conv_t; const double phi1 = obj[1]->Get(s_phi )->NumberValue() * conv_p; if (!finite(theta0) || !finite(theta1) || !finite(phi0) || !finite(phi1)) { isolate->ThrowException(String::NewFromUtf8(isolate, "some values not valid or not finite.")); return; } /* const double x0 = sin(zd0) * cos(az0); // az0 -= az0 const double y0 = sin(zd0) * sin(az0); // az0 -= az0 const double z0 = cos(zd0); const double x1 = sin(zd1) * cos(az1); // az1 -= az0 const double y1 = sin(zd1) * sin(az1); // az1 -= az0 const double z1 = cos(zd1); const double res = acos(x0*x1 + y0*y1 + z0*z1) * 180/M_PI; */ // cos(az1-az0) = cos(az1)*cos(az0) + sin(az1)*sin(az0) const double x = sin(theta0) * sin(theta1) * cos(phi1-phi0); const double y = cos(theta0) * cos(theta1); const double res = acos(x + y) * 180/M_PI; args.GetReturnValue().Set(res); } void InterpreterV8::LocalDist(const Arguments &args) { CalcDist(args, true); } void InterpreterV8::SkyDist(const Arguments &args) { CalcDist(args, false); } void InterpreterV8::MoonDisk(const Arguments &args) { Isolate *isolate = Isolate::GetCurrent(); if (args.Length()>1) { isolate->ThrowException(String::NewFromUtf8(isolate, "disk must not be called with more than one argument.")); return; } const uint64_t v = uint64_t(args[0]->NumberValue()); const Time utc = args.Length()==0 ? Time() : Time(v/1000, v%1000); args.GetReturnValue().Set(Nova::GetLunarDisk(utc.JD())); } struct AstroArgs { string obs; Nova::LnLatPosn posn; double jd; uint64_t jsdate; AstroArgs() : jsdate(0) { } }; AstroArgs EvalAstroArgs(int offset, const Arguments &args, int8_t type=2) { const uint8_t max = abs(type); if (args.Length()>offset+max) throw runtime_error("Number of arguments must not exceed "+to_string(offset+max)+"."); if (type==1 && args.Length()==offset+1 && !args[offset]->IsString()) throw runtime_error("Argument "+to_string(offset+1)+" must be a string."); if (type==-1 && args.Length()==offset+1 && !args[offset]->IsDate()) throw runtime_error("Argument "+to_string(offset+1)+" must be a date."); if (args.Length()==offset+1 && !(args[offset]->IsDate() || args[offset]->IsString())) throw runtime_error("Argument "+to_string(offset+1)+" must be a string or Date."); if (args.Length()==offset+2 && !(args[offset+0]->IsDate() && args[offset+1]->IsString()) && !(args[offset+1]->IsDate() && args[offset+0]->IsString())) throw runtime_error("Arguments "+to_string(offset+1)+" and "+to_string(offset+2)+" must be a string/Date or Date/string."); Isolate *isolate = Isolate::GetCurrent(); const HandleScope handle_scope(isolate); Local obs = args.This()->Get(String::NewFromUtf8(isolate, "observatory")); if (args.Length()>offset && args[offset]->IsString()) obs = args[offset]; if (args.Length()>offset+1 && args[offset+1]->IsString()) obs = args[offset+1]; AstroArgs rc; // For constructors, observatory can stay empty if not explicitly given if (offset<2) rc.obs = Nova::LnLatPosn::preset(); if (!obs.IsEmpty() && !obs->IsUndefined()) rc.obs = *String::Utf8Value(obs); rc.posn = rc.obs; if ((!rc.obs.empty() || offset==0) && !rc.posn.isValid()) throw runtime_error("Observatory "+rc.obs+" unknown."); Local date = args.This()->Get(String::NewFromUtf8(isolate, "time")); if (args.Length()>offset && args[offset]->IsDate()) date = args[offset]; if (args.Length()>offset+1 && args[offset+1]->IsDate()) date = args[offset+1]; // For constructors, time can stay empty if not explicitly given if (offset<2) rc.jsdate = Time().JavaDate(); if (!date.IsEmpty() && !date->IsUndefined()) rc.jsdate = uint64_t(date->NumberValue()); rc.jd = Time(rc.jsdate/1000, rc.jsdate%1000).JD(); return rc; } void InterpreterV8::LocalToSky(const Arguments &args) { Isolate *isolate = Isolate::GetCurrent(); AstroArgs local; try { local = EvalAstroArgs(0, args, 2); } catch (const exception &e) { isolate->ThrowException(String::NewFromUtf8(isolate, e.what())); return; } Nova::ZdAzPosn hrz; hrz.zd = GetDataMember(args, "zd"); hrz.az = GetDataMember(args, "az"); if (!finite(hrz.zd) || !finite(hrz.az)) { isolate->ThrowException(String::NewFromUtf8(isolate, "Zd and az must be finite.")); return; } const Nova::EquPosn equ = Nova::GetEquFromHrz(hrz, local.posn, local.jd); const HandleScope handle_scope(isolate); Local context = isolate->GetCurrentContext(); Handle arg_loc[] = { Number::New(isolate, hrz.zd), Number::New(isolate, hrz.az), String::NewFromUtf8(isolate, local.obs.c_str()), Date::New(isolate, local.jsdate) }; MaybeLocal loc = fTemplateLocal->GetFunction()->NewInstance(context, 4, arg_loc); Handle arg_sky[] = { Number::New(isolate, equ.ra/15), Number::New(isolate, equ.dec), loc.ToLocalChecked() }; args.GetReturnValue().Set(fTemplateSky->GetFunction()->NewInstance(context, 3, arg_sky).ToLocalChecked()); } void InterpreterV8::SkyToLocal(const Arguments &args) { Isolate *isolate = Isolate::GetCurrent(); AstroArgs local; try { local = EvalAstroArgs(0, args, 2); } catch (const exception &e) { isolate->ThrowException(String::NewFromUtf8(isolate, e.what())); return; } Nova::EquPosn equ; equ.ra = GetDataMember(args, "ra")*15; equ.dec = GetDataMember(args, "dec"); if (!finite(equ.ra) || !finite(equ.dec)) { isolate->ThrowException(String::NewFromUtf8(isolate, "Ra and dec must be finite.")); return; } const HandleScope handle_scope(isolate); Local context = isolate->GetCurrentContext(); const Nova::ZdAzPosn hrz = Nova::GetHrzFromEqu(equ, local.posn, local.jd); Handle arg[] = { Number::New(isolate, hrz.zd), Number::New(isolate, hrz.az), String::NewFromUtf8(isolate, local.obs.c_str()), Date::New(isolate, local.jsdate) }; args.GetReturnValue().Set(fTemplateLocal->GetFunction()->NewInstance(context, 4, arg).ToLocalChecked()); } void InterpreterV8::MoonToLocal(const Arguments &args) { Isolate *isolate = Isolate::GetCurrent(); AstroArgs local; try { local = EvalAstroArgs(0, args, 1); } catch (const exception &e) { isolate->ThrowException(String::NewFromUtf8(isolate, e.what())); return; } Nova::EquPosn equ; equ.ra = GetDataMember(args, "ra")*15; equ.dec = GetDataMember(args, "dec"); if (!finite(equ.ra) || !finite(equ.dec)) { isolate->ThrowException(String::NewFromUtf8(isolate, "Ra and dec must be finite.")); return; } const HandleScope handle_scope(isolate); Local context = isolate->GetCurrentContext(); const Nova::ZdAzPosn hrz = Nova::GetHrzFromEqu(equ, local.posn, local.jd); Handle arg[] = { Number::New(isolate, hrz.zd), Number::New(isolate, hrz.az), String::NewFromUtf8(isolate, local.obs.c_str()), Date::New(isolate, local.jsdate) }; args.GetReturnValue().Set(fTemplateLocal->GetFunction()->NewInstance(context, 4, arg).ToLocalChecked()); } void InterpreterV8::ConstructorMoon(const Arguments &args) { Isolate *isolate = Isolate::GetCurrent(); AstroArgs local; try { local = EvalAstroArgs(0, args, -1); } catch (const exception &e) { isolate->ThrowException(String::NewFromUtf8(isolate, e.what())); return; } const Nova::EquPosn equ = Nova::GetLunarEquCoords(local.jd, 0.01); const HandleScope handle_scope(isolate); // ---------------------------- if (!args.IsConstructCall()) { args.GetReturnValue().Set(Constructor(args)); return; } Handle function = FunctionTemplate::New(isolate, MoonToLocal)->GetFunction(); if (function.IsEmpty()) return; Handle self = args.This(); self->Set(String::NewFromUtf8(isolate, "ra"), Number::New(isolate, equ.ra/15)/*, ReadOnly*/); self->Set(String::NewFromUtf8(isolate, "dec"), Number::New(isolate, equ.dec)/*, ReadOnly*/); self->Set(String::NewFromUtf8(isolate, "toLocal"), function/*, ReadOnly*/); self->Set(String::NewFromUtf8(isolate, "time"), Date::New(isolate, local.jsdate)/*, ReadOnly*/); args.GetReturnValue().Set(self); } void InterpreterV8::ConstructorSky(const Arguments &args) { Isolate *isolate = Isolate::GetCurrent(); if (args.Length()<2 || args.Length()>3) { isolate->ThrowException(String::NewFromUtf8(isolate, "Sky constructor takes two or three arguments.")); return; } if (args.Length()>2 && !args[2]->IsObject()) { const string n = *String::Utf8Value(args[2]->ToObject()->GetConstructorName()); if (n!="Local") { isolate->ThrowException(String::NewFromUtf8(isolate, "Third argument must be of type Local.")); return; } } const double ra = args[0]->NumberValue(); const double dec = args[1]->NumberValue(); if (!finite(ra) || !finite(dec)) { isolate->ThrowException(String::NewFromUtf8(isolate, "The first two arguments to Sky must be valid numbers.")); return; } // ---------------------------- const HandleScope handle_scope(isolate); if (!args.IsConstructCall()) { args.GetReturnValue().Set(Constructor(args)); return; } Handle function = FunctionTemplate::New(isolate, SkyToLocal)->GetFunction(); if (function.IsEmpty()) return; Handle self = args.This(); self->Set(String::NewFromUtf8(isolate, "ra"), Number::New(isolate, ra)/*, ReadOnly*/); self->Set(String::NewFromUtf8(isolate, "dec"), Number::New(isolate, dec)/*, ReadOnly*/); self->Set(String::NewFromUtf8(isolate, "toLocal"), function/*, ReadOnly*/); if (args.Length()==3) self->Set(String::NewFromUtf8(isolate, "local"), args[2]/*, ReadOnly*/); args.GetReturnValue().Set(self); } void InterpreterV8::ConstructorLocal(const Arguments &args) { Isolate *isolate = Isolate::GetCurrent(); AstroArgs local; try { local = EvalAstroArgs(2, args, 2); } catch (const exception &e) { isolate->ThrowException(String::NewFromUtf8(isolate, e.what())); return; } const double zd = args[0]->NumberValue(); const double az = args[1]->NumberValue(); if (!finite(zd) || !finite(az)) { isolate->ThrowException(String::NewFromUtf8(isolate, "The first two arguments to Local must be valid numbers.")); return; } // -------------------- const HandleScope handle_scope(isolate); if (!args.IsConstructCall()) { args.GetReturnValue().Set(Constructor(args)); return; } Handle function = FunctionTemplate::New(isolate, LocalToSky)->GetFunction(); if (function.IsEmpty()) return; Handle self = args.This(); self->Set(String::NewFromUtf8(isolate, "zd"), Number::New(isolate, zd)/*, ReadOnly*/); self->Set(String::NewFromUtf8(isolate, "az"), Number::New(isolate, az)/*, ReadOnly*/); self->Set(String::NewFromUtf8(isolate, "toSky"), function/*, ReadOnly*/); if (!local.obs.empty()) self->Set(String::NewFromUtf8(isolate, "observatory"), String::NewFromUtf8(isolate, local.obs.c_str())/*, ReadOnly*/); if (local.jsdate>0) self->Set(String::NewFromUtf8(isolate, "time"), Date::New(isolate, local.jsdate)/*, ReadOnly*/); args.GetReturnValue().Set(self); } Handle ConstructRiseSet(const AstroArgs &args, const Nova::RstTime &rst, const bool &rc) { Isolate *isolate = Isolate::GetCurrent(); Handle obj = Object::New(isolate); obj->Set(String::NewFromUtf8(isolate, "time"), Date::New(isolate, args.jsdate)/*, ReadOnly*/); obj->Set(String::NewFromUtf8(isolate, "observatory"), String::NewFromUtf8(isolate, args.obs.c_str())/*, ReadOnly*/); const bool isUp = (rst.riserst.rise && args.jdrst.set && (args.jdrst.rise)); obj->Set(String::NewFromUtf8(isolate, "isUp"), Boolean::New(isolate, isUp)/*, ReadOnly*/); if (!rc) // circumpolar return obj; Handle rise = Date::New(isolate, Time(rst.rise).JavaDate()); Handle set = Date::New(isolate, Time(rst.set).JavaDate()); Handle trans = Date::New(isolate, Time(rst.transit).JavaDate()); if (rise.IsEmpty() || set.IsEmpty() || trans.IsEmpty()) return Handle(); obj->Set(String::NewFromUtf8(isolate, "rise"), rise/*, ReadOnly*/); obj->Set(String::NewFromUtf8(isolate, "set"), set/*, ReadOnly*/); obj->Set(String::NewFromUtf8(isolate, "transit"), trans/*, ReadOnly*/); return obj; } void InterpreterV8::SunHorizon(const Arguments &args) { Isolate *isolate = Isolate::GetCurrent(); AstroArgs local; try { local = EvalAstroArgs(1, args, 2); } catch (const exception &e) { isolate->ThrowException(String::NewFromUtf8(isolate, e.what())); return; } const HandleScope handle_scope(isolate); double hrz = NAN; if (args.Length()==0 || args[0]->IsNull()) hrz = LN_SOLAR_STANDART_HORIZON; if (args.Length()>0 && args[0]->IsNumber()) hrz = args[0]->NumberValue(); if (args.Length()>0 && args[0]->IsString()) { string arg(Tools::Trim(*String::Utf8Value(args[0]))); transform(arg.begin(), arg.end(), arg.begin(), ::tolower); if (arg==string("horizon").substr(0, arg.length())) hrz = LN_SOLAR_STANDART_HORIZON; if (arg==string("civil").substr(0, arg.length())) hrz = LN_SOLAR_CIVIL_HORIZON; if (arg==string("nautical").substr(0, arg.length())) hrz = LN_SOLAR_NAUTIC_HORIZON; if (arg==string("fact").substr(0, arg.length())) hrz = -13; if (arg==string("astronomical").substr(0, arg.length())) hrz = LN_SOLAR_ASTRONOMICAL_HORIZON; } if (!finite(hrz)) { isolate->ThrowException(String::NewFromUtf8(isolate, "First argument did not yield a valid number.")); return; } ln_rst_time sun; const bool rc = ln_get_solar_rst_horizon(local.jd-0.5, &local.posn, hrz, &sun)==0; Handle rst = ConstructRiseSet(local, sun, rc); rst->Set(String::NewFromUtf8(isolate, "horizon"), Number::New(isolate, hrz)); args.GetReturnValue().Set(rst); }; void InterpreterV8::MoonHorizon(const Arguments &args) { Isolate *isolate = Isolate::GetCurrent(); AstroArgs local; try { local = EvalAstroArgs(0, args, 2); } catch (const exception &e) { isolate->ThrowException(String::NewFromUtf8(isolate, e.what())); return; } const HandleScope handle_scope(isolate); ln_rst_time moon; const bool rc = ln_get_lunar_rst(local.jd-0.5, &local.posn, &moon)==0; Handle rst = ConstructRiseSet(local, moon, rc); args.GetReturnValue().Set(rst); }; #endif // ========================================================================== // Process control // ========================================================================== bool InterpreterV8::HandleException(TryCatch& try_catch, const char *where) { if (!try_catch.HasCaught() || !try_catch.CanContinue()) return true; Isolate *isolate = Isolate::GetCurrent(); const HandleScope handle_scope(isolate); Handle except = try_catch.Exception(); if (except.IsEmpty() || except->IsNull()) return true; const String::Utf8Value exception(except); const Handle message = try_catch.Message(); if (message.IsEmpty()) return false; ostringstream out; if (!message->GetScriptResourceName()->IsUndefined()) { // Print (filename):(line number): (message). const String::Utf8Value filename(message->GetScriptResourceName()); if (filename.length()>0) { out << *filename; if (message->GetLineNumber()>0) out << ": l." << message->GetLineNumber(); if (*exception) out << ": "; } } if (*exception) out << *exception; out << " [" << where << "]"; JsException(out.str()); // Print line of source code. const String::Utf8Value sourceline(message->GetSourceLine()); if (*sourceline) JsException(*sourceline); // Print wavy underline (GetUnderline is deprecated). const int start = message->GetStartColumn(); const int end = message->GetEndColumn(); out.str(""); if (start>0) out << setfill(' ') << setw(start) << ' '; out << setfill('^') << setw(end-start) << '^'; JsException(out.str()); const String::Utf8Value stack_trace(try_catch.StackTrace()); if (stack_trace.length()<=0) return false; if (!*stack_trace) return false; const string trace(*stack_trace); typedef boost::char_separator separator; const boost::tokenizer tokenizer(trace, separator("\n")); // maybe skip: " at internal:" // maybe skip: " at unknown source:" auto it = tokenizer.begin(); JsException(""); while (it!=tokenizer.end()) JsException(*it++); return false; } Handle InterpreterV8::ExecuteInternal(const string &code) { Isolate *isolate = Isolate::GetCurrent(); // Try/catch and re-throw hides our internal code from // the displayed exception showing the origin and shows // the user function instead. TryCatch exception(isolate); const Handle result = ExecuteCode(code); // This hides the location of the exception in the internal code, // which is wanted. if (exception.HasCaught()) exception.ReThrow(); return result; } Handle InterpreterV8::ExecuteCode(const string &code, const string &file) { Isolate *isolate = Isolate::GetCurrent(); EscapableHandleScope handle_scope(isolate); MaybeLocal source = String::NewFromUtf8(isolate, code.c_str(), NewStringType::kNormal, code.size()); const Handle origin = String::NewFromUtf8(isolate, file.c_str()); if (source.IsEmpty()) return Undefined(isolate); ScriptOrigin sorigin(origin); Local context = isolate->GetCurrentContext(); MaybeLocal