diff --git a/libs/yscript/javascript.cpp b/libs/yscript/javascript.cpp index 323076ec..9a75ce76 100644 --- a/libs/yscript/javascript.cpp +++ b/libs/yscript/javascript.cpp @@ -140,6 +140,7 @@ public: } virtual bool initialize(ScriptContext* context) const; virtual bool evaluate(ScriptRun& runner, ObjList& results) const; + virtual ScriptRun* createRunner(ScriptContext* context); bool link(); JsObject* parseArray(const char*& expr, bool constOnly); JsObject* parseObject(const char*& expr, bool constOnly); @@ -184,7 +185,8 @@ private: bool callFunction(ObjList& stack, const ExpOperation& oper, GenObject* context, JsFunction* func, bool constr) const; bool callFunction(ObjList& stack, const ExpOperation& oper, GenObject* context, - long int retIndex, JsFunction* func, ObjList& args, ExpOperation* thisObj) const; + long int retIndex, JsFunction* func, ObjList& args, + ExpOperation* thisObj, ExpOperation* scopeObj) const; inline JsFunction* getGlobalFunction(const String& name) const { return YOBJECT(JsFunction,m_globals[name]); } long int m_label; @@ -228,7 +230,7 @@ public: m_paused(false), m_opcode(0), m_index(0) { } virtual Status reset(); - virtual Status call(const String& name, ObjList& args, ExpOperation* thisObj = 0); + virtual Status call(const String& name, ObjList& args, ExpOperation* thisObj = 0, ExpOperation* scopeObj = 0); virtual bool callable(const String& name); protected: virtual Status resume(); @@ -2026,13 +2028,16 @@ bool JsCode::callFunction(ObjList& stack, const ExpOperation& oper, GenObject* c ExpOperation* thisObj = constr ? popOne(stack) : 0; ObjList args; JsObject::extractArgs(func,stack,oper,context,args); - return callFunction(stack,oper,context,index,func,args,thisObj); + return callFunction(stack,oper,context,index,func,args,thisObj,0); } bool JsCode::callFunction(ObjList& stack, const ExpOperation& oper, GenObject* context, - long int retIndex, JsFunction* func, ObjList& args, ExpOperation* thisObj) const + long int retIndex, JsFunction* func, ObjList& args, + ExpOperation* thisObj, ExpOperation* scopeObj) const { pushOne(stack,new ExpOperation(OpcFunc,0,retIndex,true)); + if (scopeObj) + pushOne(stack,new ExpWrapper(scopeObj,scopeObj->name())); JsObject* ctxt = JsObject::buildCallContext(func->mutex(),thisObj); for (unsigned int idx = 0; ; idx++) { const String* name = func->formalName(idx); @@ -2049,6 +2054,14 @@ bool JsCode::callFunction(ObjList& stack, const ExpOperation& oper, GenObject* c return jumpToLabel(func->label(),context); } +ScriptRun* JsCode::createRunner(ScriptContext* context) +{ + if (!context) + return 0; + return new JsRunner(this,context); +} + + ScriptRun::Status JsRunner::reset() { @@ -2073,27 +2086,31 @@ ScriptRun::Status JsRunner::resume() return m_paused ? Incomplete : Succeeded; } -ScriptRun::Status JsRunner::call(const String& name, ObjList& args, ExpOperation* thisObj) +ScriptRun::Status JsRunner::call(const String& name, ObjList& args, + ExpOperation* thisObj, ExpOperation* scopeObj) { Lock mylock(this); if (Invalid == state()) { TelEngine::destruct(thisObj); + TelEngine::destruct(scopeObj); return Invalid; } const JsCode* c = static_cast(code()); if (!(c && context())) { TelEngine::destruct(thisObj); + TelEngine::destruct(scopeObj); return Invalid; } JsFunction* func = c->getGlobalFunction(name); if (!func) { TelEngine::destruct(thisObj); + TelEngine::destruct(scopeObj); return Failed; } reset(); // prepare a function call stack ExpOperation oper(ExpEvaluator::OpcFunc,name,args.count()); - if (!c->callFunction(stack(),oper,this,-1,func,args,thisObj)) + if (!c->callFunction(stack(),oper,this,-1,func,args,thisObj,scopeObj)) return Failed; mylock.drop(); // continue normal execution like in run() @@ -2154,8 +2171,10 @@ bool JsFunction::runNative(ObjList& stack, const ExpOperation& oper, GenObject* if (!oper.number()) return false; } - else - return JsObject::runNative(stack,oper,context); + else { + JsObject* obj = YOBJECT(JsObject,params().getParam(YSTRING("prototype"))); + return obj ? obj->runNative(stack,oper,context) : JsObject::runNative(stack,oper,context); + } return true; } @@ -2165,13 +2184,16 @@ bool JsFunction::runDefined(ObjList& stack, const ExpOperation& oper, GenObject* JsObject* proto = YOBJECT(JsObject,getField(stack,"prototype",context)); if (proto) { // found prototype, build object - JsObject* obj = proto->clone(); - obj->copyFields(stack,*proto,context); - obj->runConstructor(stack,oper,context); + JsObject* obj = proto->runConstructor(stack,oper,context); + if (!obj) + return false; ExpEvaluator::pushOne(stack,new ExpWrapper(obj,oper.name())); } JsCode* code = YOBJECT(JsCode,m_code); - return code && code->callFunction(stack,oper,context,this,(proto != 0)); + XDebug(DebugAll,"JsFunction::runDefined code=%p proto=%p [%p]",code,proto,this); + if (code) + return code->callFunction(stack,oper,context,this,(proto != 0)); + return proto || runNative(stack,oper,context); } diff --git a/libs/yscript/jsobjects.cpp b/libs/yscript/jsobjects.cpp index 063bbdf0..66cd1cd6 100644 --- a/libs/yscript/jsobjects.cpp +++ b/libs/yscript/jsobjects.cpp @@ -226,6 +226,13 @@ JsObject* JsObject::buildCallContext(Mutex* mtx, ExpOperation* thisObj) return ctxt; } +JsObject* JsObject::runConstructor(ObjList& stack, const ExpOperation& oper, GenObject* context) +{ + JsObject* obj = clone(); + obj->copyFields(stack,*this,context); + return obj; +} + bool JsObject::runFunction(ObjList& stack, const ExpOperation& oper, GenObject* context) { XDebug(DebugInfo,"JsObject::runFunction() '%s' in '%s' [%p]", @@ -344,6 +351,7 @@ void JsObject::addConstructor(NamedList& params, const char* name, JsObject* obj { JsFunction* ctr = new JsFunction(obj->mutex(),name); ctr->params().addParam(new NamedPointer("prototype",obj,obj->toString())); + obj->initConstructor(ctr); params.addParam(new NamedPointer(name,ctr,ctr->toString())); } diff --git a/libs/yscript/script.cpp b/libs/yscript/script.cpp index 72593087..53eb817f 100644 --- a/libs/yscript/script.cpp +++ b/libs/yscript/script.cpp @@ -277,9 +277,11 @@ ScriptRun::Status ScriptRun::run() } // Execute a function or method call -ScriptRun::Status ScriptRun::call(const String& name, ObjList& args, ExpOperation* thisObj) +ScriptRun::Status ScriptRun::call(const String& name, ObjList& args, + ExpOperation* thisObj, ExpOperation* scopeObj) { TelEngine::destruct(thisObj); + TelEngine::destruct(scopeObj); return Failed; } diff --git a/libs/yscript/yatescript.h b/libs/yscript/yatescript.h index 8f2f16aa..6d142c62 100644 --- a/libs/yscript/yatescript.h +++ b/libs/yscript/yatescript.h @@ -1270,6 +1270,14 @@ public: * @param results List to fill with expression results */ virtual bool evaluate(ScriptRun& runner, ObjList& results) const = 0; + + /** + * Create a runner adequate for this block of parsed code + * @param context Script context, must not be NULL + * @return A new script runner, NULL if context is NULL or feature is not supported + */ + virtual ScriptRun* createRunner(ScriptContext* context) + { return 0; } }; /** @@ -1410,9 +1418,11 @@ public: * @param name Name of the function to call * @param args Values to pass as actual function arguments * @param thisObj Object to pass as "this" if applicable + * @param scopeObj Optional object to be used for scope resolution inside the call * @return Final status of the runtime after function call */ - virtual Status call(const String& name, ObjList& args, ExpOperation* thisObj = 0); + virtual Status call(const String& name, ObjList& args, + ExpOperation* thisObj = 0, ExpOperation* scopeObj = 0); /** * Check if a script has a certain function or method @@ -1532,12 +1542,15 @@ private: ScriptCode* m_code; }; +class JsFunction; + /** * Javascript Object class, base for all JS objects * @short Javascript Object */ class YSCRIPT_API JsObject : public ScriptContext { + friend class JsFunction; YCLASS(JsObject,ScriptContext) public: /** @@ -1576,13 +1589,20 @@ public: { return clone(toString()); } /** - * Native object constructor + * Native constructor initialization, called by addConstructor on the prototype + * @param construct Function that has this object as prototype + */ + virtual void initConstructor(JsFunction* construct) + { } + + /** + * Native object constructor, it's run on the prototype * @param stack Evaluation stack in use * @param oper Constructor function to evaluate * @param context Pointer to arbitrary object passed from evaluation methods + * @return New created and populated Javascript object */ - virtual void runConstructor(ObjList& stack, const ExpOperation& oper, GenObject* context) - { } + virtual JsObject* runConstructor(ObjList& stack, const ExpOperation& oper, GenObject* context); /** * Try to evaluate a single method diff --git a/modules/javascript.cpp b/modules/javascript.cpp index fd82959f..f2a7e0b3 100644 --- a/modules/javascript.cpp +++ b/modules/javascript.cpp @@ -51,6 +51,8 @@ private: JsParser m_assistCode; }; +INIT_PLUGIN(JsModule); + class JsAssist : public ChanAssist { public: @@ -139,33 +141,64 @@ public: inline JsMessage(Mutex* mtx) : JsObject("Message",mtx,true), m_message(0), m_owned(false) { - XDebug(DebugAll,"JsMessage::JsMessage() [%p]",this); - params().addParam(new ExpFunction("constructor")); + XDebug(&__plugin,DebugAll,"JsMessage::JsMessage() [%p]",this); } inline JsMessage(Message* message, Mutex* mtx, bool owned) : JsObject("Message",mtx), m_message(message), m_owned(owned) { - XDebug(DebugAll,"JsMessage::JsMessage(%p) [%p]",message,this); + XDebug(&__plugin,DebugAll,"JsMessage::JsMessage(%p) [%p]",message,this); params().addParam(new ExpFunction("enqueue")); params().addParam(new ExpFunction("dispatch")); params().addParam(new ExpFunction("broadcast")); } virtual ~JsMessage() { - XDebug(DebugAll,"JsMessage::~JsMessage() [%p]",this); + XDebug(&__plugin,DebugAll,"JsMessage::~JsMessage() [%p]",this); if (m_owned) TelEngine::destruct(m_message); } - virtual void runConstructor(ObjList& stack, const ExpOperation& oper, GenObject* context); + virtual JsObject* runConstructor(ObjList& stack, const ExpOperation& oper, GenObject* context); + virtual void initConstructor(JsFunction* construct) + { + construct->params().addParam(new ExpFunction("install")); + } inline void clearMsg() { m_message = 0; m_owned = false; } static void initialize(ScriptContext* context); protected: bool runNative(ObjList& stack, const ExpOperation& oper, GenObject* context); + ObjList m_handlers; Message* m_message; bool m_owned; }; +class JsHandler : public MessageHandler +{ + YCLASS(JsHandler,MessageHandler) +public: + inline JsHandler(const char* name, unsigned priority, const ExpFunction& func, GenObject* context) + : MessageHandler(name,priority,__plugin.name()), + m_function(func.name(),1) + { + XDebug(&__plugin,DebugAll,"JsHandler::JsHandler('%s',%u,'%s') [%p]", + name,priority,func.name().c_str(),this); + ScriptRun* runner = YOBJECT(ScriptRun,context); + if (runner) { + m_context = runner->context(); + m_code = runner->code(); + } + } + virtual ~JsHandler() + { + XDebug(&__plugin,DebugAll,"JsHandler::~JsHandler() '%s' [%p]",c_str(),this); + } + virtual bool received(Message& msg); +private: + ExpFunction m_function; + RefPointer m_context; + RefPointer m_code; +}; + class JsFile : public JsObject { YCLASS(JsFile,JsObject) @@ -210,8 +243,6 @@ protected: static String s_basePath; -INIT_PLUGIN(JsModule); - UNLOAD_PLUGIN(unloadNow) { if (unloadNow) { @@ -319,6 +350,7 @@ void JsEngine::initialize(ScriptContext* context) bool JsMessage::runNative(ObjList& stack, const ExpOperation& oper, GenObject* context) { + XDebug(DebugAll,"JsMessage::runNative '%s'(%ld)",oper.name().c_str(),oper.number()); if (oper.name() == YSTRING("broadcast")) { if (oper.number() != 0) return false; @@ -350,21 +382,50 @@ bool JsMessage::runNative(ObjList& stack, const ExpOperation& oper, GenObject* c } ExpEvaluator::pushOne(stack,new ExpOperation(ok)); } + else if (oper.name() == YSTRING("install")) { + ObjList args; + if (extractArgs(stack,oper,context,args) < 2) + return false; + ExpFunction* func = YOBJECT(ExpFunction,args[0]); + if (!func) + return false; + ExpOperation* name = static_cast(args[1]); + ExpOperation* prio = static_cast(args[2]); + if (!name) + return false; + unsigned int priority = 100; + if (prio) { + if (prio->isInteger() && (prio->number() >= 0)) + priority = prio->number(); + else + return false; + } + JsHandler* h = new JsHandler(*name,priority,*func,context); + m_handlers.append(h); + Engine::install(h); + } else return JsObject::runNative(stack,oper,context); return true; } -void JsMessage::runConstructor(ObjList& stack, const ExpOperation& oper, GenObject* context) +JsObject* JsMessage::runConstructor(ObjList& stack, const ExpOperation& oper, GenObject* context) { - if (oper.number() != 1) - return; - ExpOperation* op = popValue(stack,context); - if (!op) - return; - Message* m = new Message(*op); - ExpEvaluator::pushOne(stack,new ExpWrapper(new JsMessage(m,mutex(),true))); - TelEngine::destruct(op); + XDebug(DebugAll,"JsMessage::runConstructor '%s'(%ld)",oper.name().c_str(),oper.number()); + ObjList args; + switch (extractArgs(stack,oper,context,args)) { + case 1: + case 2: + break; + default: + return 0; + } + ExpOperation* name = static_cast(args[0]); + ExpOperation* broad = static_cast(args[1]); + if (!name) + return 0; + Message* m = new Message(*name,0,broad && broad->valBoolean()); + return new JsMessage(m,mutex(),true); } void JsMessage::initialize(ScriptContext* context) @@ -375,12 +436,33 @@ void JsMessage::initialize(ScriptContext* context) Lock mylock(mtx); NamedList& params = context->params(); if (!params.getParam(YSTRING("Message"))) - addObject(params,"Message",new JsMessage(mtx)); + addConstructor(params,"Message",new JsMessage(mtx)); +} + + +bool JsHandler::received(Message& msg) +{ + DDebug(&__plugin,DebugAll,"JsHandler::received '%s'",c_str()); + if (!m_code) + return false; + ScriptRun* runner = m_code->createRunner(m_context); + if (!runner) + return false; + JsMessage* jm = new JsMessage(&msg,runner->context()->mutex(),false); + jm->ref(); + ObjList args; + args.append(new ExpWrapper(jm,"message")); + ScriptRun::Status rval = runner->call(m_function.name(),args); + jm->clearMsg(); + TelEngine::destruct(jm); + TelEngine::destruct(runner); + return (ScriptRun::Succeeded == rval); } bool JsFile::runNative(ObjList& stack, const ExpOperation& oper, GenObject* context) { + XDebug(DebugAll,"JsFile::runNative '%s'(%ld)",oper.name().c_str(),oper.number()); if (oper.name() == YSTRING("exists")) { if (oper.number() != 1) return false; @@ -479,6 +561,7 @@ void JsFile::initialize(ScriptContext* context) bool JsChannel::runNative(ObjList& stack, const ExpOperation& oper, GenObject* context) { + XDebug(DebugAll,"JsChannel::runNative '%s'(%ld)",oper.name().c_str(),oper.number()); if (oper.name() == YSTRING("id")) { if (oper.number()) return false; @@ -536,7 +619,20 @@ void JsChannel::initialize(ScriptContext* context, JsAssist* assist) JsAssist::~JsAssist() { - TelEngine::destruct(m_runner); + if (m_runner) { + ScriptContext* context = m_runner->context(); + if (m_runner->callable("onUnload")) { + ScriptRun* runner = m_runner->code()->createRunner(context); + if (runner) { + ObjList args; + runner->call("onUnload",args); + TelEngine::destruct(runner); + } + } + if (context) + context->params().clearParams(); + TelEngine::destruct(m_runner); + } } bool JsAssist::init() @@ -643,6 +739,8 @@ JsGlobal::~JsGlobal() TelEngine::destruct(runner); } } + if (m_context) + m_context->params().clearParams(); } bool JsGlobal::fileChanged(const char* fileName) const