2392 lines
64 KiB
C++
2392 lines
64 KiB
C++
/**
|
|
* javascript.cpp
|
|
* Yet Another (Java)script library
|
|
* This file is part of the YATE Project http://YATE.null.ro
|
|
*
|
|
* Yet Another Telephony Engine - a fully featured software PBX and IVR
|
|
* Copyright (C) 2011 Null Team
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "yatescript.h"
|
|
#include <yatengine.h>
|
|
|
|
using namespace TelEngine;
|
|
|
|
namespace { // anonymous
|
|
|
|
class ParseNested;
|
|
class JsRunner;
|
|
|
|
class JsContext : public JsObject, public Mutex
|
|
{
|
|
YCLASS(JsContext,JsObject)
|
|
public:
|
|
inline JsContext()
|
|
: JsObject("Context",this), Mutex(true,"JsContext")
|
|
{
|
|
params().addParam(new ExpFunction("isNaN"));
|
|
params().addParam(new ExpFunction("parseInt"));
|
|
params().addParam(new ExpOperation(ExpOperation::nonInteger(),"NaN"));
|
|
}
|
|
virtual bool runFunction(ObjList& stack, const ExpOperation& oper, GenObject* context);
|
|
virtual bool runField(ObjList& stack, const ExpOperation& oper, GenObject* context);
|
|
virtual bool runAssign(ObjList& stack, const ExpOperation& oper, GenObject* context);
|
|
private:
|
|
GenObject* resolveTop(ObjList& stack, const String& name, GenObject* context);
|
|
GenObject* resolve(ObjList& stack, String& name, GenObject* context);
|
|
bool runStringFunction(GenObject* obj, const String& name, ObjList& stack, const ExpOperation& oper, GenObject* context);
|
|
bool runStringField(GenObject* obj, const String& name, ObjList& stack, const ExpOperation& oper, GenObject* context);
|
|
};
|
|
|
|
class JsNull : public JsObject
|
|
{
|
|
public:
|
|
inline JsNull()
|
|
: JsObject(0,"null",true)
|
|
{ }
|
|
};
|
|
|
|
class ExpNull : public ExpWrapper
|
|
{
|
|
public:
|
|
inline ExpNull()
|
|
: ExpWrapper(new JsNull,"null")
|
|
{ }
|
|
virtual bool valBoolean() const
|
|
{ return false; }
|
|
virtual ExpOperation* clone(const char* name) const
|
|
{ return new ExpNull(static_cast<JsNull*>(object()),name); }
|
|
protected:
|
|
inline ExpNull(JsNull* obj, const char* name)
|
|
: ExpWrapper(obj,name)
|
|
{ obj->ref(); }
|
|
};
|
|
|
|
class JsCode : public ScriptCode, public ExpEvaluator
|
|
{
|
|
friend class TelEngine::JsFunction;
|
|
friend class TelEngine::JsParser;
|
|
friend class ParseNested;
|
|
friend class JsRunner;
|
|
public:
|
|
enum JsOpcode {
|
|
OpcBegin = OpcPrivate + 1,
|
|
OpcEnd,
|
|
OpcFlush,
|
|
OpcIndex,
|
|
OpcEqIdentity,
|
|
OpcNeIdentity,
|
|
OpcFieldOf,
|
|
OpcTypeof,
|
|
OpcNew,
|
|
OpcFor,
|
|
OpcWhile,
|
|
OpcIf,
|
|
OpcElse,
|
|
OpcSwitch,
|
|
OpcCase,
|
|
OpcDefault,
|
|
OpcBreak,
|
|
OpcCont,
|
|
OpcIn,
|
|
OpcOf,
|
|
OpcNext,
|
|
OpcVar,
|
|
OpcWith,
|
|
OpcTry,
|
|
OpcCatch,
|
|
OpcFinally,
|
|
OpcThrow,
|
|
OpcFuncDef,
|
|
OpcReturn,
|
|
OpcJump,
|
|
OpcJumpTrue,
|
|
OpcJumpFalse,
|
|
OpcJRel,
|
|
OpcJRelTrue,
|
|
OpcJRelFalse,
|
|
OpcTrue,
|
|
OpcFalse,
|
|
OpcNull,
|
|
OpcUndefined,
|
|
OpcInclude,
|
|
OpcRequire,
|
|
};
|
|
inline JsCode()
|
|
: ExpEvaluator(C), m_label(0), m_depth(0)
|
|
{ debugName("JsCode"); }
|
|
|
|
virtual void* getObject(const String& name) const
|
|
{
|
|
if (name == YSTRING("JsCode"))
|
|
return const_cast<JsCode*>(this);
|
|
if (name == YSTRING("ExpEvaluator"))
|
|
return const_cast<ExpEvaluator*>((const ExpEvaluator*)this);
|
|
return ScriptCode::getObject(name);
|
|
}
|
|
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);
|
|
protected:
|
|
virtual void formatLineNo(String& buf, unsigned int line) const;
|
|
virtual bool getString(const char*& expr);
|
|
virtual bool getEscape(const char*& expr, String& str, char sep);
|
|
virtual bool keywordChar(char c) const;
|
|
virtual int getKeyword(const char* str) const;
|
|
virtual char skipComments(const char*& expr, GenObject* context = 0);
|
|
virtual int preProcess(const char*& expr, GenObject* context = 0);
|
|
virtual bool getInstruction(const char*& expr, char stop, GenObject* nested);
|
|
virtual bool getSimple(const char*& expr, bool constOnly = false);
|
|
virtual Opcode getOperator(const char*& expr);
|
|
virtual Opcode getUnaryOperator(const char*& expr);
|
|
virtual Opcode getPostfixOperator(const char*& expr);
|
|
virtual const char* getOperator(Opcode oper) const;
|
|
virtual int getPrecedence(ExpEvaluator::Opcode oper) const;
|
|
virtual bool getSeparator(const char*& expr, bool remove);
|
|
virtual bool runOperation(ObjList& stack, const ExpOperation& oper, GenObject* context) const;
|
|
virtual bool runFunction(ObjList& stack, const ExpOperation& oper, GenObject* context) const;
|
|
virtual bool runField(ObjList& stack, const ExpOperation& oper, GenObject* context) const;
|
|
virtual bool runAssign(ObjList& stack, const ExpOperation& oper, GenObject* context) const;
|
|
private:
|
|
ObjVector m_linked;
|
|
ObjList m_included;
|
|
ObjList m_globals;
|
|
bool preProcessInclude(const char*& expr, bool once, GenObject* context);
|
|
bool getOneInstruction(const char*& expr, GenObject* nested);
|
|
bool parseInner(const char*& expr, JsOpcode opcode, ParseNested* nested);
|
|
bool parseIf(const char*& expr, GenObject* nested);
|
|
bool parseSwitch(const char*& expr, GenObject* nested);
|
|
bool parseFor(const char*& expr, GenObject* nested);
|
|
bool parseWhile(const char*& expr, GenObject* nested);
|
|
bool parseVar(const char*& expr);
|
|
bool parseTry(const char*& expr, GenObject* nested);
|
|
bool parseFuncDef(const char*& expr, bool publish);
|
|
bool evalList(ObjList& stack, GenObject* context) const;
|
|
bool evalVector(ObjList& stack, GenObject* context) const;
|
|
bool jumpToLabel(long int label, GenObject* context) const;
|
|
bool jumpRelative(long int offset, GenObject* context) const;
|
|
bool jumpAbsolute(long int index, GenObject* context) const;
|
|
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,
|
|
JsObject* thisObj, JsObject* scopeObj) const;
|
|
inline JsFunction* getGlobalFunction(const String& name) const
|
|
{ return YOBJECT(JsFunction,m_globals[name]); }
|
|
long int m_label;
|
|
int m_depth;
|
|
};
|
|
|
|
class JsIterator : public RefObject
|
|
{
|
|
YCLASS(JsIterator,RefObject);
|
|
public:
|
|
inline JsIterator(const ExpOperation& field, JsObject* obj)
|
|
: m_field(field.clone()), m_obj(obj)
|
|
{ obj->fillFieldNames(m_keys); }
|
|
inline JsIterator(const ExpOperation& field, NamedList* lst)
|
|
: m_field(field.clone())
|
|
{ ScriptContext::fillFieldNames(m_keys,*lst); }
|
|
virtual ~JsIterator()
|
|
{ TelEngine::destruct(m_field); }
|
|
inline ExpOperation& field() const
|
|
{ return *m_field; }
|
|
inline String* get()
|
|
{ return static_cast<String*>(m_keys.remove(false)); }
|
|
inline const String& name() const
|
|
{ return m_name; }
|
|
inline void name(const char* objName)
|
|
{ m_name = objName; }
|
|
private:
|
|
ExpOperation* m_field;
|
|
RefPointer<JsObject> m_obj;
|
|
ObjList m_keys;
|
|
String m_name;
|
|
};
|
|
|
|
class JsRunner : public ScriptRun
|
|
{
|
|
friend class JsCode;
|
|
public:
|
|
inline JsRunner(ScriptCode* code, ScriptContext* context)
|
|
: ScriptRun(code,context),
|
|
m_paused(false), m_opcode(0), m_index(0)
|
|
{ }
|
|
virtual Status reset();
|
|
virtual bool pause();
|
|
virtual Status call(const String& name, ObjList& args, ExpOperation* thisObj = 0, ExpOperation* scopeObj = 0);
|
|
virtual bool callable(const String& name);
|
|
protected:
|
|
virtual Status resume();
|
|
private:
|
|
bool m_paused;
|
|
const ObjList* m_opcode;
|
|
unsigned int m_index;
|
|
};
|
|
|
|
class ParseNested : public GenObject
|
|
{
|
|
YCLASS(ParseNested,GenObject)
|
|
public:
|
|
inline explicit ParseNested(JsCode* code, GenObject* nested,
|
|
JsCode::JsOpcode oper = (JsCode::JsOpcode)ExpEvaluator::OpcNone)
|
|
: m_code(code), m_nested(static_cast<ParseNested*>(nested)), m_opcode(oper)
|
|
{ }
|
|
inline operator GenObject*()
|
|
{ return this; }
|
|
inline operator JsCode::JsOpcode() const
|
|
{ return m_opcode; }
|
|
inline static JsCode::JsOpcode code(GenObject* nested)
|
|
{ return nested ? *static_cast<ParseNested*>(nested) :
|
|
(JsCode::JsOpcode)ExpEvaluator::OpcNone; }
|
|
inline static ParseNested* find(GenObject* nested, JsCode::JsOpcode opcode)
|
|
{ return nested ? static_cast<ParseNested*>(nested)->find(opcode) : 0; }
|
|
inline static ParseNested* findMatch(GenObject* nested, JsCode::JsOpcode opcode)
|
|
{ return nested ? static_cast<ParseNested*>(nested)->findMatch(opcode) : 0; }
|
|
static bool parseInner(GenObject* nested, JsCode::JsOpcode opcode, const char*& expr)
|
|
{ ParseNested* inner = findMatch(nested,opcode);
|
|
return inner && inner->parseInner(expr,opcode); }
|
|
protected:
|
|
virtual bool isMatch(JsCode::JsOpcode opcode)
|
|
{ return false; }
|
|
inline bool parseInner(const char*& expr, JsCode::JsOpcode opcode)
|
|
{ return m_code->parseInner(expr,opcode,this); }
|
|
inline ParseNested* find(JsCode::JsOpcode opcode)
|
|
{ return (opcode == m_opcode) ? this :
|
|
(m_nested ? m_nested->find(opcode) : 0); }
|
|
inline ParseNested* findMatch(JsCode::JsOpcode opcode)
|
|
{ return isMatch(opcode) ? this :
|
|
(m_nested ? m_nested->findMatch(opcode) : 0); }
|
|
private:
|
|
JsCode* m_code;
|
|
ParseNested* m_nested;
|
|
JsCode::JsOpcode m_opcode;
|
|
};
|
|
|
|
#define MAKEOP(s,o) { s, JsCode::Opc ## o }
|
|
static const TokenDict s_operators[] =
|
|
{
|
|
MAKEOP("===", EqIdentity),
|
|
MAKEOP("!==", NeIdentity),
|
|
MAKEOP(".", FieldOf),
|
|
MAKEOP("in", In),
|
|
MAKEOP("of", Of),
|
|
{ 0, 0 }
|
|
};
|
|
|
|
static const TokenDict s_unaryOps[] =
|
|
{
|
|
MAKEOP("new", New),
|
|
MAKEOP("typeof", Typeof),
|
|
{ 0, 0 }
|
|
};
|
|
|
|
static const TokenDict s_postfixOps[] =
|
|
{
|
|
MAKEOP("++", IncPost),
|
|
MAKEOP("--", DecPost),
|
|
{ 0, 0 }
|
|
};
|
|
|
|
static const TokenDict s_instr[] =
|
|
{
|
|
MAKEOP("function", FuncDef),
|
|
MAKEOP("for", For),
|
|
MAKEOP("while", While),
|
|
MAKEOP("if", If),
|
|
MAKEOP("else", Else),
|
|
MAKEOP("switch", Switch),
|
|
MAKEOP("case", Case),
|
|
MAKEOP("default", Default),
|
|
MAKEOP("break", Break),
|
|
MAKEOP("continue", Cont),
|
|
MAKEOP("var", Var),
|
|
MAKEOP("with", With),
|
|
MAKEOP("try", Try),
|
|
MAKEOP("catch", Catch),
|
|
MAKEOP("finally", Finally),
|
|
MAKEOP("throw", Throw),
|
|
MAKEOP("return", Return),
|
|
{ 0, 0 }
|
|
};
|
|
|
|
static const TokenDict s_constants[] =
|
|
{
|
|
MAKEOP("false", False),
|
|
MAKEOP("true", True),
|
|
MAKEOP("null", Null),
|
|
MAKEOP("undefined", Undefined),
|
|
MAKEOP("function", FuncDef),
|
|
{ 0, 0 }
|
|
};
|
|
|
|
static const TokenDict s_preProc[] =
|
|
{
|
|
MAKEOP("#include", Include),
|
|
MAKEOP("#require", Require),
|
|
{ 0, 0 }
|
|
};
|
|
#undef MAKEOP
|
|
|
|
static const ExpNull s_null;
|
|
|
|
|
|
GenObject* JsContext::resolveTop(ObjList& stack, const String& name, GenObject* context)
|
|
{
|
|
XDebug(DebugAll,"JsContext::resolveTop '%s'",name.c_str());
|
|
for (ObjList* l = stack.skipNull(); l; l = l->skipNext()) {
|
|
JsObject* jso = YOBJECT(JsObject,l->get());
|
|
if (jso && jso->hasField(stack,name,context))
|
|
return jso;
|
|
}
|
|
return this;
|
|
}
|
|
|
|
GenObject* JsContext::resolve(ObjList& stack, String& name, GenObject* context)
|
|
{
|
|
GenObject* obj = 0;
|
|
if (name.find('.') < 0)
|
|
obj = resolveTop(stack,name,context);
|
|
else {
|
|
ObjList* list = name.split('.',true);
|
|
for (ObjList* l = list->skipNull(); l; ) {
|
|
const String* s = static_cast<const String*>(l->get());
|
|
ObjList* l2 = l->skipNext();
|
|
if (TelEngine::null(s)) {
|
|
// consecutive dots - not good
|
|
obj = 0;
|
|
break;
|
|
}
|
|
if (!obj)
|
|
obj = resolveTop(stack,*s,context);
|
|
if (!l2) {
|
|
name = *s;
|
|
break;
|
|
}
|
|
ExpExtender* ext = YOBJECT(ExpExtender,obj);
|
|
if (ext) {
|
|
GenObject* adv = ext->getField(stack,*s,context);
|
|
XDebug(DebugAll,"JsContext::resolve advanced to '%s' of %p for '%s'",
|
|
(adv ? adv->toString().c_str() : 0),ext,s->c_str());
|
|
if (adv)
|
|
obj = adv;
|
|
else {
|
|
name.clear();
|
|
for (; l; l = l->skipNext())
|
|
name.append(l->get()->toString(),".");
|
|
break;
|
|
}
|
|
}
|
|
l = l2;
|
|
}
|
|
TelEngine::destruct(list);
|
|
}
|
|
DDebug(DebugAll,"JsContext::resolve got '%s' %p for '%s'",
|
|
(obj ? obj->toString().c_str() : 0),obj,name.c_str());
|
|
return obj;
|
|
}
|
|
|
|
bool JsContext::runFunction(ObjList& stack, const ExpOperation& oper, GenObject* context)
|
|
{
|
|
XDebug(DebugAll,"JsContext::runFunction '%s' [%p]",oper.name().c_str(),this);
|
|
String name = oper.name();
|
|
GenObject* o = resolve(stack,name,context);
|
|
if (o && o != this) {
|
|
ExpExtender* ext = YOBJECT(ExpExtender,o);
|
|
if (ext) {
|
|
ExpOperation op(oper,name);
|
|
return ext->runFunction(stack,op,context);
|
|
}
|
|
if (runStringFunction(o,name,stack,oper,context))
|
|
return true;
|
|
}
|
|
if (name == YSTRING("isNaN")) {
|
|
bool nan = true;
|
|
ExpOperation* op = popValue(stack,context);
|
|
if (op)
|
|
nan = !op->isInteger();
|
|
TelEngine::destruct(op);
|
|
ExpEvaluator::pushOne(stack,new ExpOperation(nan));
|
|
return true;
|
|
}
|
|
if (name == YSTRING("parseInt")) {
|
|
long int val = ExpOperation::nonInteger();
|
|
ExpOperation* op1 = popValue(stack,context);
|
|
if (op1) {
|
|
ExpOperation* op2 = popValue(stack,context);
|
|
if (op2) {
|
|
int base = op1->number();
|
|
if (base >= 0)
|
|
val = op2->trimSpaces().toLong(val,base);
|
|
}
|
|
else
|
|
val = op1->trimSpaces().toLong(val);
|
|
TelEngine::destruct(op2);
|
|
}
|
|
TelEngine::destruct(op1);
|
|
ExpEvaluator::pushOne(stack,new ExpOperation(val));
|
|
return true;
|
|
}
|
|
return JsObject::runFunction(stack,oper,context);
|
|
}
|
|
|
|
bool JsContext::runField(ObjList& stack, const ExpOperation& oper, GenObject* context)
|
|
{
|
|
XDebug(DebugAll,"JsContext::runField '%s' [%p]",oper.name().c_str(),this);
|
|
String name = oper.name();
|
|
GenObject* o = resolve(stack,name,context);
|
|
if (o && o != this) {
|
|
ExpExtender* ext = YOBJECT(ExpExtender,o);
|
|
if (ext) {
|
|
ExpOperation op(oper,name);
|
|
return ext->runField(stack,op,context);
|
|
}
|
|
if (runStringField(o,name,stack,oper,context))
|
|
return true;
|
|
}
|
|
return JsObject::runField(stack,oper,context);
|
|
}
|
|
|
|
bool JsContext::runStringFunction(GenObject* obj, const String& name, ObjList& stack, const ExpOperation& oper, GenObject* context)
|
|
{
|
|
const String* str = YOBJECT(String,obj);
|
|
if (!str)
|
|
return false;
|
|
if (name == YSTRING("charAt")) {
|
|
int idx = 0;
|
|
ObjList args;
|
|
if (extractArgs(stack,oper,context,args)) {
|
|
ExpOperation* op = static_cast<ExpOperation*>(args[0]);
|
|
if (op && op->isInteger())
|
|
idx = op->number();
|
|
}
|
|
ExpEvaluator::pushOne(stack,new ExpOperation(String(str->at(idx))));
|
|
return true;
|
|
}
|
|
if (name == YSTRING("indexOf")) {
|
|
int idx = -1;
|
|
ObjList args;
|
|
if (extractArgs(stack,oper,context,args)) {
|
|
const String* what = static_cast<String*>(args[0]);
|
|
if (what) {
|
|
ExpOperation* from = static_cast<ExpOperation*>(args[1]);
|
|
int offs = (from && from->isInteger()) ? from->number() : 0;
|
|
if (offs < 0)
|
|
offs = 0;
|
|
idx = str->find(*what,offs);
|
|
}
|
|
}
|
|
ExpEvaluator::pushOne(stack,new ExpOperation((long int)idx));
|
|
return true;
|
|
}
|
|
if (name == YSTRING("substr")) {
|
|
ObjList args;
|
|
int offs = 0;
|
|
int len = -1;
|
|
if (extractArgs(stack,oper,context,args)) {
|
|
ExpOperation* op = static_cast<ExpOperation*>(args[0]);
|
|
if (op && op->isInteger())
|
|
offs = op->number();
|
|
op = static_cast<ExpOperation*>(args[1]);
|
|
if (op && op->isInteger()) {
|
|
len = op->number();
|
|
if (len < 0)
|
|
len = 0;
|
|
}
|
|
}
|
|
ExpEvaluator::pushOne(stack,new ExpOperation(str->substr(offs,len)));
|
|
return true;
|
|
}
|
|
if (name == YSTRING("match")) {
|
|
ObjList args;
|
|
String buf(*str);
|
|
if (extractArgs(stack,oper,context,args)) {
|
|
ExpOperation* op = static_cast<ExpOperation*>(args[0]);
|
|
ExpWrapper* wrap = YOBJECT(ExpWrapper,op);
|
|
JsRegExp* rexp = YOBJECT(JsRegExp,wrap);
|
|
bool ok = false;
|
|
if (rexp)
|
|
ok = buf.matches(rexp->regexp());
|
|
else if (!wrap) {
|
|
Regexp r(*static_cast<String*>(op),true);
|
|
ok = buf.matches(r);
|
|
}
|
|
if (ok) {
|
|
JsArray* jsa = new JsArray(mutex());
|
|
for (int i = 0; i <= buf.matchCount(); i++)
|
|
jsa->push(new ExpOperation(buf.matchString(i)));
|
|
jsa->params().addParam(new ExpOperation((long int)buf.matchOffset(),"index"));
|
|
if (rexp)
|
|
jsa->params().addParam(wrap->clone("input"));
|
|
ExpEvaluator::pushOne(stack,new ExpWrapper(jsa));
|
|
return true;
|
|
}
|
|
}
|
|
ExpEvaluator::pushOne(stack,s_null.ExpOperation::clone());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool JsContext::runStringField(GenObject* obj, const String& name, ObjList& stack, const ExpOperation& oper, GenObject* context)
|
|
{
|
|
const String* s = YOBJECT(String,obj);
|
|
if (!s)
|
|
return false;
|
|
if (name == YSTRING("length")) {
|
|
ExpEvaluator::pushOne(stack,new ExpOperation((long int)s->length()));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool JsContext::runAssign(ObjList& stack, const ExpOperation& oper, GenObject* context)
|
|
{
|
|
XDebug(DebugAll,"JsContext::runAssign '%s'='%s' [%p]",oper.name().c_str(),oper.c_str(),this);
|
|
String name = oper.name();
|
|
GenObject* o = resolve(stack,name,context);
|
|
if (o && o != this) {
|
|
ExpExtender* ext = YOBJECT(ExpExtender,o);
|
|
if (ext) {
|
|
ExpOperation* op = oper.clone(name);
|
|
bool ok = ext->runAssign(stack,*op,context);
|
|
TelEngine::destruct(op);
|
|
return ok;
|
|
}
|
|
}
|
|
return JsObject::runAssign(stack,oper,context);
|
|
}
|
|
|
|
|
|
// Initialize standard globals in the execution context
|
|
bool JsCode::initialize(ScriptContext* context) const
|
|
{
|
|
if (!context)
|
|
return false;
|
|
JsObject::initialize(context);
|
|
for (ObjList* l = m_globals.skipNull(); l; l = l->skipNext()) {
|
|
ExpOperation* op = static_cast<ExpOperation*>(l->get());
|
|
if (!context->params().getParam(op->name()))
|
|
context->params().setParam(static_cast<ExpOperation*>(l->get())->clone());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool JsCode::evaluate(ScriptRun& runner, ObjList& results) const
|
|
{
|
|
if (null())
|
|
return false;
|
|
bool ok = m_linked.length() ? evalVector(results,&runner) : evalList(results,&runner);
|
|
if (!ok)
|
|
return false;
|
|
if (static_cast<JsRunner&>(runner).m_paused)
|
|
return true;
|
|
if (!runAllFields(results,&runner))
|
|
return gotError("Could not evaluate all fields");
|
|
return true;
|
|
}
|
|
|
|
// Convert list to vector and fix label relocations
|
|
bool JsCode::link()
|
|
{
|
|
if (!m_opcodes.count())
|
|
return false;
|
|
m_linked.assign(m_opcodes);
|
|
unsigned int n = m_linked.count();
|
|
if (!n)
|
|
return false;
|
|
for (unsigned int i = 0; i < n; i++) {
|
|
const ExpOperation* l = static_cast<const ExpOperation*>(m_linked[i]);
|
|
if (!l || l->opcode() != OpcLabel)
|
|
continue;
|
|
long int lbl = l->number();
|
|
for (unsigned int j = 0; j < n; i++) {
|
|
const ExpOperation* jmp = static_cast<const ExpOperation*>(m_linked[j]);
|
|
if (!jmp || jmp->number() != lbl)
|
|
continue;
|
|
Opcode op = OpcNone;
|
|
switch (jmp->opcode()) {
|
|
case (Opcode)OpcJump:
|
|
op = (Opcode)OpcJRel;
|
|
break;
|
|
case (Opcode)OpcJumpTrue:
|
|
op = (Opcode)OpcJRelTrue;
|
|
break;
|
|
case (Opcode)OpcJumpFalse:
|
|
op = (Opcode)OpcJRelFalse;
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
long int offs = i - j;
|
|
m_linked.set(new ExpOperation(op,0,offs,jmp->barrier()),j);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void JsCode::formatLineNo(String& buf, unsigned int line) const
|
|
{
|
|
unsigned int fnum = (line >> 24) & 0xff;
|
|
if (!fnum)
|
|
return ExpEvaluator::formatLineNo(buf,line);
|
|
buf.clear();
|
|
const GenObject* file = m_included[fnum - 1];
|
|
buf << (file ? file->toString().c_str() : "???") << ":" << (line & 0xffffff);
|
|
}
|
|
|
|
bool JsCode::getString(const char*& expr)
|
|
{
|
|
if (inError())
|
|
return false;
|
|
char c = skipComments(expr);
|
|
if (c != '/' && c != '%')
|
|
return ExpEvaluator::getString(expr);
|
|
String str;
|
|
if (!ExpEvaluator::getString(expr,str))
|
|
return false;
|
|
bool extended = true;
|
|
bool insensitive = false;
|
|
if (c == '%') {
|
|
// dialplan pattern - turn it into a regular expression
|
|
insensitive = true;
|
|
String tmp = str;
|
|
tmp.toUpper();
|
|
str = "^";
|
|
char last = '\0';
|
|
int count = 0;
|
|
bool esc = false;
|
|
for (unsigned int i = 0; ; i++) {
|
|
c = tmp.at(i);
|
|
if (last && c != last) {
|
|
switch (last) {
|
|
case 'X':
|
|
str << "[0-9]";
|
|
break;
|
|
case 'Z':
|
|
str << "[1-9]";
|
|
break;
|
|
case 'N':
|
|
str << "[2-9]";
|
|
break;
|
|
case '.':
|
|
str << ".+";
|
|
count = 1;
|
|
break;
|
|
}
|
|
if (count > 1)
|
|
str << "{" << count << "}";
|
|
last = '\0';
|
|
count = 0;
|
|
}
|
|
if (!c) {
|
|
str << "$";
|
|
break;
|
|
}
|
|
switch (c) {
|
|
case '.':
|
|
if (esc) {
|
|
str << c;
|
|
break;
|
|
}
|
|
// fall through
|
|
case 'X':
|
|
case 'Z':
|
|
case 'N':
|
|
last = c;
|
|
count++;
|
|
break;
|
|
case '+':
|
|
case '*':
|
|
str << "\\";
|
|
// fall through
|
|
default:
|
|
str << c;
|
|
}
|
|
esc = (c == '\\');
|
|
}
|
|
}
|
|
else {
|
|
// regexp - check for flags
|
|
do {
|
|
c = *expr;
|
|
switch (c) {
|
|
case 'i':
|
|
expr++;
|
|
insensitive = true;
|
|
break;
|
|
case 'b':
|
|
expr++;
|
|
extended = false;
|
|
break;
|
|
default:
|
|
c = 0;
|
|
}
|
|
} while (c);
|
|
}
|
|
XDebug(this,DebugInfo,"Regexp '%s' flags '%s%s'",str.c_str(),
|
|
(insensitive ? "i" : ""),(extended ? "" : "b"));
|
|
JsRegExp* obj = new JsRegExp(0,str,str,insensitive,extended);
|
|
addOpcode(new ExpWrapper(obj));
|
|
return true;
|
|
}
|
|
|
|
bool JsCode::getEscape(const char*& expr, String& str, char sep)
|
|
{
|
|
if (sep != '\'' && sep != '"') {
|
|
// this is not a string but a regexp or dialplan template
|
|
char c = *expr++;
|
|
if (!c)
|
|
return false;
|
|
if (c != '\\' && c != sep)
|
|
str << '\\';
|
|
str << c;
|
|
return true;
|
|
}
|
|
return ExpEvaluator::getEscape(expr,str,sep);
|
|
}
|
|
|
|
bool JsCode::keywordChar(char c) const
|
|
{
|
|
return ExpEvaluator::keywordChar(c) || (c == '$');
|
|
}
|
|
|
|
int JsCode::getKeyword(const char* str) const
|
|
{
|
|
int len = 0;
|
|
const char*s = str;
|
|
for (;; len++) {
|
|
char c = *s++;
|
|
if (c <= ' ')
|
|
break;
|
|
if (keywordChar(c) || (len && (c == '.')))
|
|
continue;
|
|
break;
|
|
}
|
|
if (len > 1 && (s[-2] == '.'))
|
|
len--;
|
|
if (len && ExpEvaluator::getOperator(str,s_instr) != OpcNone)
|
|
return 0;
|
|
return len;
|
|
}
|
|
|
|
char JsCode::skipComments(const char*& expr, GenObject* context)
|
|
{
|
|
char c = skipWhites(expr);
|
|
while (c == '/') {
|
|
if (expr[1] == '/') {
|
|
// comment to end of line
|
|
expr+=2;
|
|
while ((c = *expr) && (c != '\r') && (c != '\n'))
|
|
expr++;
|
|
c = skipWhites(expr);
|
|
}
|
|
else if (expr[1] == '*') {
|
|
/* comment to close */
|
|
expr++;
|
|
while ((c = skipWhites(expr)) && (c != '*' || expr[1] != '/'))
|
|
expr++;
|
|
if (c) {
|
|
expr+=2;
|
|
c = skipWhites(expr);
|
|
}
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
return c;
|
|
}
|
|
|
|
bool JsCode::preProcessInclude(const char*& expr, bool once, GenObject* context)
|
|
{
|
|
if (m_depth > 5)
|
|
return gotError("Possible recursive include");
|
|
JsParser* parser = YOBJECT(JsParser,context);
|
|
if (!parser)
|
|
return false;
|
|
char c = skipComments(expr);
|
|
if (c == '"' || c == '\'') {
|
|
String str;
|
|
if (ExpEvaluator::getString(expr,str)) {
|
|
DDebug(this,DebugAll,"Found include '%s'",str.safe());
|
|
parser->adjustPath(str);
|
|
str.trimSpaces();
|
|
bool ok = !str.null();
|
|
if (ok) {
|
|
int idx = m_included.index(str);
|
|
if (!(once && (idx >= 0))) {
|
|
if (idx < 0) {
|
|
String* s = new String(str);
|
|
m_included.append(s);
|
|
idx = m_included.index(s);
|
|
}
|
|
// use the upper bits of line # for file index
|
|
unsigned int savedLine = m_lineNo;
|
|
m_lineNo = ((idx + 1) << 24) | 1;
|
|
m_depth++;
|
|
ok = parser->parseFile(str,true);
|
|
m_depth--;
|
|
m_lineNo = savedLine;
|
|
}
|
|
}
|
|
return ok || gotError("Failed to include " + str);
|
|
}
|
|
return false;
|
|
}
|
|
return gotError("Expecting include file",expr);
|
|
}
|
|
|
|
int JsCode::preProcess(const char*& expr, GenObject* context)
|
|
{
|
|
int rval = -1;
|
|
for (;;) {
|
|
skipComments(expr);
|
|
JsOpcode opc = (JsOpcode)ExpEvaluator::getOperator(expr,s_preProc);
|
|
switch (opc) {
|
|
case OpcInclude:
|
|
case OpcRequire:
|
|
if (preProcessInclude(expr,(OpcRequire == opc),context)) {
|
|
if (rval < 0)
|
|
rval = 1;
|
|
else
|
|
rval++;
|
|
}
|
|
else
|
|
return -1;
|
|
break;
|
|
default:
|
|
return rval;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool JsCode::getOneInstruction(const char*& expr, GenObject* nested)
|
|
{
|
|
if (inError())
|
|
return false;
|
|
XDebug(this,DebugAll,"JsCode::getOneInstruction %p '%.30s'",nested,expr);
|
|
if (skipComments(expr) == '{') {
|
|
if (!getInstruction(expr,0,nested))
|
|
return false;
|
|
}
|
|
else {
|
|
if (!runCompile(expr,';',nested))
|
|
return false;
|
|
if (skipComments(expr) == ';')
|
|
expr++;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool JsCode::getInstruction(const char*& expr, char stop, GenObject* nested)
|
|
{
|
|
if (inError())
|
|
return false;
|
|
XDebug(this,DebugAll,"JsCode::getInstruction %p '%.1s' '%.30s'",nested,&stop,expr);
|
|
if (skipComments(expr) == '{') {
|
|
if (stop == ')')
|
|
return false;
|
|
expr++;
|
|
for (;;) {
|
|
if (!runCompile(expr,'}',nested))
|
|
return false;
|
|
bool sep = false;
|
|
while (skipComments(expr) && getSeparator(expr,true))
|
|
sep = true;
|
|
if (*expr == '}' || !sep)
|
|
break;
|
|
}
|
|
if (*expr != '}')
|
|
return gotError("Expecting '}'",expr);
|
|
expr++;
|
|
return true;
|
|
}
|
|
else if (*expr == ';') {
|
|
expr++;
|
|
return true;
|
|
}
|
|
const char* saved = expr;
|
|
Opcode op = ExpEvaluator::getOperator(expr,s_instr);
|
|
switch ((JsOpcode)op) {
|
|
case (JsOpcode)OpcNone:
|
|
return false;
|
|
case OpcThrow:
|
|
if (!runCompile(expr))
|
|
return false;
|
|
addOpcode(op);
|
|
break;
|
|
case OpcReturn:
|
|
switch (skipComments(expr)) {
|
|
case ';':
|
|
case '}':
|
|
break;
|
|
default:
|
|
if (!runCompile(expr,';'))
|
|
return false;
|
|
if ((skipComments(expr) != ';') && (*expr != '}'))
|
|
return gotError("Expecting ';' or '}'",expr);
|
|
}
|
|
addOpcode(op);
|
|
break;
|
|
case OpcIf:
|
|
return parseIf(expr,nested);
|
|
case OpcElse:
|
|
expr = saved;
|
|
return false;
|
|
case OpcSwitch:
|
|
return parseSwitch(expr,nested);
|
|
case OpcFor:
|
|
return parseFor(expr,nested);
|
|
case OpcWhile:
|
|
return parseWhile(expr,nested);
|
|
case OpcCase:
|
|
if (!ParseNested::parseInner(nested,OpcCase,expr))
|
|
return gotError("case not inside switch",saved);
|
|
if (skipComments(expr) != ':')
|
|
return gotError("Expecting ':'",expr);
|
|
expr++;
|
|
break;
|
|
case OpcDefault:
|
|
if (!ParseNested::parseInner(nested,OpcDefault,expr))
|
|
return gotError("Unexpected default instruction",saved);
|
|
if (skipComments(expr) != ':')
|
|
return gotError("Expecting ':'",expr);
|
|
expr++;
|
|
break;
|
|
case OpcBreak:
|
|
if (!ParseNested::parseInner(nested,OpcBreak,expr))
|
|
return gotError("Unexpected break instruction",saved);
|
|
if (skipComments(expr) != ';')
|
|
return gotError("Expecting ';'",expr);
|
|
expr++;
|
|
break;
|
|
case OpcCont:
|
|
if (!ParseNested::parseInner(nested,OpcCont,expr))
|
|
return gotError("Unexpected continue instruction",saved);
|
|
if (skipComments(expr) != ';')
|
|
return gotError("Expecting ';'",expr);
|
|
expr++;
|
|
break;
|
|
case OpcVar:
|
|
return parseVar(expr);
|
|
case OpcTry:
|
|
return parseTry(expr,nested);
|
|
case OpcFuncDef:
|
|
return parseFuncDef(expr,!nested);
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
class ParseLoop : public ParseNested
|
|
{
|
|
friend class JsCode;
|
|
public:
|
|
inline ParseLoop(JsCode* code, GenObject* nested, JsCode::JsOpcode oper, long int lblCont, long int lblBreak)
|
|
: ParseNested(code,nested,oper),
|
|
m_lblCont(lblCont), m_lblBreak(lblBreak)
|
|
{ }
|
|
protected:
|
|
virtual bool isMatch(JsCode::JsOpcode opcode)
|
|
{ return JsCode::OpcBreak == opcode || JsCode::OpcCont == opcode; }
|
|
private:
|
|
long int m_lblCont;
|
|
long int m_lblBreak;
|
|
};
|
|
|
|
class ParseSwitch : public ParseNested
|
|
{
|
|
friend class JsCode;
|
|
public:
|
|
enum SwitchState {
|
|
Before,
|
|
InCase,
|
|
InDefault
|
|
};
|
|
inline ParseSwitch(JsCode* code, GenObject* nested, long int lblBreak)
|
|
: ParseNested(code,nested,JsCode::OpcSwitch),
|
|
m_lblBreak(lblBreak), m_lblDefault(0), m_state(Before)
|
|
{ }
|
|
inline SwitchState state() const
|
|
{ return m_state; }
|
|
protected:
|
|
virtual bool isMatch(JsCode::JsOpcode opcode)
|
|
{ return JsCode::OpcCase == opcode || JsCode::OpcDefault == opcode ||
|
|
JsCode::OpcBreak == opcode; }
|
|
private:
|
|
long int m_lblBreak;
|
|
long int m_lblDefault;
|
|
SwitchState m_state;
|
|
ObjList m_cases;
|
|
};
|
|
|
|
// Parse keywords inner to specific instructions
|
|
bool JsCode::parseInner(const char*& expr, JsOpcode opcode, ParseNested* nested)
|
|
{
|
|
switch (*nested) {
|
|
case OpcFor:
|
|
case OpcWhile:
|
|
{
|
|
ParseLoop* block = static_cast<ParseLoop*>(nested);
|
|
switch (opcode) {
|
|
case OpcBreak:
|
|
XDebug(this,DebugAll,"Parsing loop:break '%.30s'",expr);
|
|
addOpcode((Opcode)OpcJump,block->m_lblBreak);
|
|
break;
|
|
case OpcCont:
|
|
XDebug(this,DebugAll,"Parsing loop:continue '%.30s'",expr);
|
|
addOpcode((Opcode)OpcJump,block->m_lblCont);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
break;
|
|
case OpcSwitch:
|
|
{
|
|
ParseSwitch* block = static_cast<ParseSwitch*>(nested);
|
|
switch (opcode) {
|
|
case OpcCase:
|
|
if (block->state() == ParseSwitch::InDefault)
|
|
return gotError("Encountered case after default",expr);
|
|
if (!getSimple(expr,true))
|
|
return gotError("Expecting case constant",expr);
|
|
XDebug(this,DebugAll,"Parsing switch:case: '%.30s'",expr);
|
|
block->m_state = ParseSwitch::InCase;
|
|
block->m_cases.append(popOpcode());
|
|
addOpcode(OpcLabel,++m_label);
|
|
block->m_cases.append(new ExpOperation((Opcode)OpcJumpTrue,0,m_label));
|
|
break;
|
|
case OpcDefault:
|
|
if (block->state() == ParseSwitch::InDefault)
|
|
return gotError("Duplicate default case",expr);
|
|
XDebug(this,DebugAll,"Parsing switch:default: '%.30s'",expr);
|
|
block->m_state = ParseSwitch::InDefault;
|
|
block->m_lblDefault = ++m_label;
|
|
addOpcode(OpcLabel,block->m_lblDefault);
|
|
break;
|
|
case OpcBreak:
|
|
XDebug(this,DebugAll,"Parsing switch:break '%.30s'",expr);
|
|
addOpcode((Opcode)OpcJump,static_cast<ParseSwitch*>(nested)->m_lblBreak);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool JsCode::parseIf(const char*& expr, GenObject* nested)
|
|
{
|
|
if (skipComments(expr) != '(')
|
|
return gotError("Expecting '('",expr);
|
|
if (!runCompile(++expr,')'))
|
|
return false;
|
|
if (skipComments(expr) != ')')
|
|
return gotError("Expecting ')'",expr);
|
|
ExpOperation* cond = addOpcode((Opcode)OpcJumpFalse,++m_label);
|
|
expr++;
|
|
if (!getOneInstruction(++expr,nested))
|
|
return false;
|
|
const char* save = expr;
|
|
skipComments(expr);
|
|
if ((JsOpcode)ExpEvaluator::getOperator(expr,s_instr) == OpcElse) {
|
|
ExpOperation* jump = addOpcode((Opcode)OpcJump,++m_label);
|
|
addOpcode(OpcLabel,cond->number());
|
|
if (!getOneInstruction(expr,nested))
|
|
return false;
|
|
addOpcode(OpcLabel,jump->number());
|
|
}
|
|
else {
|
|
expr = save;
|
|
addOpcode(OpcLabel,cond->number());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool JsCode::parseSwitch(const char*& expr, GenObject* nested)
|
|
{
|
|
if (skipComments(expr) != '(')
|
|
return gotError("Expecting '('",expr);
|
|
addOpcode((Opcode)OpcBegin);
|
|
if (!runCompile(++expr,')'))
|
|
return false;
|
|
if (skipComments(expr) != ')')
|
|
return gotError("Expecting ')'",expr);
|
|
if (skipComments(++expr) != '{')
|
|
return gotError("Expecting '{'",expr);
|
|
expr++;
|
|
ExpOperation* jump = addOpcode((Opcode)OpcJump,++m_label);
|
|
ParseSwitch parseStack(this,nested,++m_label);
|
|
for (;;) {
|
|
if (!runCompile(expr,'}',parseStack))
|
|
return false;
|
|
bool sep = false;
|
|
while (skipComments(expr) && getSeparator(expr,true))
|
|
sep = true;
|
|
if (*expr == '}' || !sep)
|
|
break;
|
|
}
|
|
if (*expr != '}')
|
|
return gotError("Expecting '}'",expr);
|
|
expr++;
|
|
// implicit break at end
|
|
addOpcode((Opcode)OpcJump,parseStack.m_lblBreak);
|
|
addOpcode(OpcLabel,jump->number());
|
|
while (ExpOperation* c = static_cast<ExpOperation*>(parseStack.m_cases.remove(false))) {
|
|
ExpOperation* j = static_cast<ExpOperation*>(parseStack.m_cases.remove(false));
|
|
if (!j)
|
|
break;
|
|
addOpcode(c,c->lineNumber());
|
|
addOpcode((Opcode)OpcCase);
|
|
addOpcode(j,c->lineNumber());
|
|
}
|
|
// if no case matched drop the expression
|
|
addOpcode(OpcDrop);
|
|
if (parseStack.m_lblDefault)
|
|
addOpcode((Opcode)OpcJump,parseStack.m_lblDefault);
|
|
addOpcode(OpcLabel,parseStack.m_lblBreak);
|
|
addOpcode((Opcode)OpcFlush);
|
|
return true;
|
|
}
|
|
|
|
bool JsCode::parseFor(const char*& expr, GenObject* nested)
|
|
{
|
|
if (skipComments(expr) != '(')
|
|
return gotError("Expecting '('",expr);
|
|
addOpcode((Opcode)OpcBegin);
|
|
if ((skipComments(++expr) != ';') && !runCompile(expr,')'))
|
|
return false;
|
|
long int cont = 0;
|
|
long int jump = ++m_label;
|
|
long int body = ++m_label;
|
|
// parse initializer
|
|
if (skipComments(expr) == ';') {
|
|
long int check = body;
|
|
if (skipComments(++expr) != ';') {
|
|
check = ++m_label;
|
|
addOpcode(OpcLabel,check);
|
|
addOpcode((Opcode)OpcBegin);
|
|
// parse condition
|
|
if (!runCompile(expr))
|
|
return false;
|
|
if (skipComments(expr) != ';')
|
|
return gotError("Expecting ';'",expr);
|
|
addOpcode((Opcode)OpcEnd);
|
|
addOpcode((Opcode)OpcJumpFalse,jump);
|
|
}
|
|
addOpcode((Opcode)OpcJump,body);
|
|
if (skipComments(++expr) == ')')
|
|
cont = check;
|
|
else {
|
|
cont = ++m_label;
|
|
addOpcode(OpcLabel,cont);
|
|
addOpcode((Opcode)OpcBegin);
|
|
// parse increment
|
|
if (!runCompile(expr,')'))
|
|
return false;
|
|
addOpcode((Opcode)OpcFlush);
|
|
addOpcode((Opcode)OpcJump,check);
|
|
}
|
|
}
|
|
else {
|
|
cont = ++m_label;
|
|
addOpcode(OpcLabel,cont);
|
|
addOpcode((Opcode)OpcNext);
|
|
addOpcode((Opcode)OpcJumpFalse,jump);
|
|
}
|
|
if (skipComments(expr) != ')')
|
|
return gotError("Expecting ')'",expr);
|
|
ParseLoop parseStack(this,nested,OpcFor,cont,jump);
|
|
addOpcode(OpcLabel,body);
|
|
if (!getOneInstruction(++expr,parseStack))
|
|
return false;
|
|
addOpcode((Opcode)OpcJump,cont);
|
|
addOpcode(OpcLabel,jump);
|
|
addOpcode((Opcode)OpcFlush);
|
|
return true;
|
|
}
|
|
|
|
bool JsCode::parseWhile(const char*& expr, GenObject* nested)
|
|
{
|
|
if (skipComments(expr) != '(')
|
|
return gotError("Expecting '('",expr);
|
|
addOpcode((Opcode)OpcBegin);
|
|
long int cont = ++m_label;
|
|
addOpcode(OpcLabel,cont);
|
|
if (!runCompile(++expr,')'))
|
|
return false;
|
|
if (skipComments(expr) != ')')
|
|
return gotError("Expecting ')'",expr);
|
|
long int jump = ++m_label;
|
|
addOpcode((Opcode)OpcJumpFalse,jump);
|
|
ParseLoop parseStack(this,nested,OpcWhile,cont,jump);
|
|
if (!getOneInstruction(++expr,parseStack))
|
|
return false;
|
|
addOpcode((Opcode)OpcJump,cont);
|
|
addOpcode(OpcLabel,jump);
|
|
addOpcode((Opcode)OpcFlush);
|
|
return true;
|
|
}
|
|
|
|
bool JsCode::parseVar(const char*& expr)
|
|
{
|
|
if (inError())
|
|
return false;
|
|
XDebug(this,DebugAll,"parseVar '%.30s'",expr);
|
|
skipComments(expr);
|
|
int len = ExpEvaluator::getKeyword(expr);
|
|
if (len <= 0 || expr[len] == '(')
|
|
return gotError("Expecting variable name",expr);
|
|
String str(expr,len);
|
|
if (str.toInteger(s_instr,-1) >= 0 || str.toInteger(s_constants,-1) >= 0)
|
|
return gotError("Not a valid variable name",expr);
|
|
DDebug(this,DebugAll,"Found variable '%s'",str.safe());
|
|
addOpcode((Opcode)OpcVar,str);
|
|
return true;
|
|
}
|
|
|
|
bool JsCode::parseTry(const char*& expr, GenObject* nested)
|
|
{
|
|
addOpcode((Opcode)OpcTry);
|
|
ParseNested parseStack(this,nested,OpcTry);
|
|
if (!runCompile(expr,0,parseStack))
|
|
return false;
|
|
skipComments(expr);
|
|
if ((JsOpcode)ExpEvaluator::getOperator(expr,s_instr) == OpcCatch) {
|
|
if (skipComments(expr) != '(')
|
|
return gotError("Expecting '('",expr);
|
|
if (!getField(++expr))
|
|
return gotError("Expecting formal argument",expr);
|
|
if (skipComments(expr) != ')')
|
|
return gotError("Expecting ')'",expr);
|
|
if (!runCompile(++expr))
|
|
return false;
|
|
}
|
|
skipComments(expr);
|
|
if ((JsOpcode)ExpEvaluator::getOperator(expr,s_instr) == OpcFinally) {
|
|
if (!runCompile(expr))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool JsCode::parseFuncDef(const char*& expr, bool publish)
|
|
{
|
|
XDebug(this,DebugAll,"JsCode::parseFuncDef '%.30s'",expr);
|
|
skipComments(expr);
|
|
int len = getKeyword(expr);
|
|
String name;
|
|
if (len > 0) {
|
|
name.assign(expr,len);
|
|
expr += len;
|
|
}
|
|
if (skipComments(expr) != '(')
|
|
return gotError("Expecting '('",expr);
|
|
expr++;
|
|
ObjList args;
|
|
while (skipComments(expr) != ')') {
|
|
len = getKeyword(expr);
|
|
if (len > 0) {
|
|
args.append(new String(expr,len));
|
|
expr += len;
|
|
}
|
|
else
|
|
return gotError("Expecting formal argument",expr);
|
|
if ((skipComments(expr) == ',') && (skipComments(++expr) == ')'))
|
|
return gotError("Expecting formal argument",expr);
|
|
}
|
|
if (skipComments(++expr) != '{')
|
|
return gotError("Expecting '{'",expr);
|
|
expr++;
|
|
ExpOperation* jump = addOpcode((Opcode)OpcJump,++m_label);
|
|
ExpOperation* lbl = addOpcode(OpcLabel,++m_label);
|
|
for (;;) {
|
|
if (!runCompile(expr,'}'))
|
|
return false;
|
|
bool sep = false;
|
|
while (skipComments(expr) && getSeparator(expr,true))
|
|
sep = true;
|
|
if (*expr == '}' || !sep)
|
|
break;
|
|
}
|
|
if (*expr != '}')
|
|
return gotError("Expecting '}'",expr);
|
|
expr++;
|
|
addOpcode((Opcode)OpcReturn);
|
|
addOpcode(OpcLabel,jump->number());
|
|
JsFunction* obj = new JsFunction(0,name,&args,lbl->number(),this);
|
|
addOpcode(new ExpWrapper(obj,name));
|
|
if (publish && name && obj->ref())
|
|
m_globals.append(new ExpWrapper(obj,name));
|
|
return true;
|
|
}
|
|
|
|
ExpEvaluator::Opcode JsCode::getOperator(const char*& expr)
|
|
{
|
|
if (inError())
|
|
return OpcNone;
|
|
XDebug(this,DebugAll,"JsCode::getOperator '%.30s'",expr);
|
|
skipComments(expr);
|
|
Opcode op = ExpEvaluator::getOperator(expr,s_operators);
|
|
if (OpcNone != op)
|
|
return op;
|
|
return ExpEvaluator::getOperator(expr);
|
|
}
|
|
|
|
ExpEvaluator::Opcode JsCode::getUnaryOperator(const char*& expr)
|
|
{
|
|
if (inError())
|
|
return OpcNone;
|
|
XDebug(this,DebugAll,"JsCode::getUnaryOperator '%.30s'",expr);
|
|
skipComments(expr);
|
|
Opcode op = ExpEvaluator::getOperator(expr,s_unaryOps);
|
|
if (OpcNone != op)
|
|
return op;
|
|
return ExpEvaluator::getUnaryOperator(expr);
|
|
}
|
|
|
|
ExpEvaluator::Opcode JsCode::getPostfixOperator(const char*& expr)
|
|
{
|
|
if (inError())
|
|
return OpcNone;
|
|
XDebug(this,DebugAll,"JsCode::getPostfixOperator '%.30s'",expr);
|
|
if (skipComments(expr) == '[') {
|
|
if (!runCompile(++expr,']'))
|
|
return OpcNone;
|
|
if (skipComments(expr) != ']') {
|
|
gotError("Expecting ']'",expr);
|
|
return OpcNone;
|
|
}
|
|
expr++;
|
|
return (Opcode)OpcIndex;
|
|
}
|
|
skipComments(expr);
|
|
Opcode op = ExpEvaluator::getOperator(expr,s_postfixOps);
|
|
if (OpcNone != op)
|
|
return op;
|
|
return ExpEvaluator::getPostfixOperator(expr);
|
|
}
|
|
|
|
const char* JsCode::getOperator(Opcode oper) const
|
|
{
|
|
if (oper < OpcPrivate)
|
|
return ExpEvaluator::getOperator(oper);
|
|
if ((int)oper == (int)OpcIndex)
|
|
return "[]";
|
|
const char* tmp = lookup(oper,s_operators);
|
|
if (!tmp) {
|
|
tmp = lookup(oper,s_unaryOps);
|
|
if (!tmp) {
|
|
tmp = lookup(oper,s_postfixOps);
|
|
if (!tmp)
|
|
tmp = lookup(oper,s_instr);
|
|
}
|
|
}
|
|
return tmp;
|
|
}
|
|
|
|
int JsCode::getPrecedence(ExpEvaluator::Opcode oper) const
|
|
{
|
|
switch (oper) {
|
|
case OpcEqIdentity:
|
|
case OpcNeIdentity:
|
|
return 4;
|
|
case OpcNew:
|
|
case OpcIndex:
|
|
return 12;
|
|
case OpcFieldOf:
|
|
return 13;
|
|
default:
|
|
return ExpEvaluator::getPrecedence(oper);
|
|
}
|
|
}
|
|
|
|
bool JsCode::getSeparator(const char*& expr, bool remove)
|
|
{
|
|
if (inError())
|
|
return false;
|
|
switch (skipComments(expr)) {
|
|
case ']':
|
|
case ';':
|
|
if (remove)
|
|
expr++;
|
|
return true;
|
|
}
|
|
return ExpEvaluator::getSeparator(expr,remove);
|
|
}
|
|
|
|
bool JsCode::getSimple(const char*& expr, bool constOnly)
|
|
{
|
|
if (inError())
|
|
return false;
|
|
XDebug(this,DebugAll,"JsCode::getSimple(%s) '%.30s'",String::boolText(constOnly),expr);
|
|
skipComments(expr);
|
|
const char* save = expr;
|
|
switch ((JsOpcode)ExpEvaluator::getOperator(expr,s_constants)) {
|
|
case OpcFalse:
|
|
addOpcode(false);
|
|
return true;
|
|
case OpcTrue:
|
|
addOpcode(true);
|
|
return true;
|
|
case OpcNull:
|
|
addOpcode(s_null.ExpOperation::clone());
|
|
return true;
|
|
case OpcUndefined:
|
|
addOpcode(new ExpWrapper(0,"undefined"));
|
|
return true;
|
|
case OpcFuncDef:
|
|
if (constOnly) {
|
|
expr = save;
|
|
return false;
|
|
}
|
|
return parseFuncDef(expr,false);
|
|
default:
|
|
break;
|
|
}
|
|
JsObject* jso = parseArray(expr,constOnly);
|
|
if (!jso)
|
|
jso = parseObject(expr,constOnly);
|
|
if (!jso)
|
|
return ExpEvaluator::getSimple(expr,constOnly);
|
|
addOpcode(new ExpWrapper(jso));
|
|
return true;
|
|
}
|
|
|
|
// Parse an inline Javascript Array: [ item1, item2, ... ]
|
|
JsObject* JsCode::parseArray(const char*& expr, bool constOnly)
|
|
{
|
|
if (skipComments(expr) != '[')
|
|
return 0;
|
|
expr++;
|
|
JsArray* jsa = new JsArray;
|
|
for (bool first = true; ; first = false) {
|
|
if (skipComments(expr) == ']') {
|
|
expr++;
|
|
break;
|
|
}
|
|
if (!first) {
|
|
if (*expr != ',') {
|
|
TelEngine::destruct(jsa);
|
|
break;
|
|
}
|
|
expr++;
|
|
}
|
|
bool ok = constOnly ? getSimple(expr,true) : getOperand(expr,false);
|
|
if (!ok) {
|
|
TelEngine::destruct(jsa);
|
|
break;
|
|
}
|
|
jsa->push(popOpcode());
|
|
}
|
|
return jsa;
|
|
}
|
|
|
|
|
|
// Parse an inline Javascript Object: { prop1: value1, "prop 2": value2, ... }
|
|
JsObject* JsCode::parseObject(const char*& expr, bool constOnly)
|
|
{
|
|
if (skipComments(expr) != '{')
|
|
return 0;
|
|
expr++;
|
|
JsObject* jso = new JsObject;
|
|
for (bool first = true; ; first = false) {
|
|
if (skipComments(expr) == '}') {
|
|
expr++;
|
|
break;
|
|
}
|
|
if (!first) {
|
|
if (*expr != ',') {
|
|
TelEngine::destruct(jso);
|
|
break;
|
|
}
|
|
expr++;
|
|
}
|
|
char c = skipComments(expr);
|
|
String name;
|
|
int len = getKeyword(expr);
|
|
if (len > 0) {
|
|
name.assign(expr,len);
|
|
expr += len;
|
|
}
|
|
else if ((c != '"' && c != '\'') || !ExpEvaluator::getString(expr,name)) {
|
|
TelEngine::destruct(jso);
|
|
break;
|
|
}
|
|
if (skipComments(expr) != ':') {
|
|
TelEngine::destruct(jso);
|
|
break;
|
|
}
|
|
expr++;
|
|
bool ok = constOnly ? getSimple(expr,true) : getOperand(expr,false);
|
|
if (!ok) {
|
|
TelEngine::destruct(jso);
|
|
break;
|
|
}
|
|
ExpOperation* op = popOpcode();
|
|
if (!op) {
|
|
TelEngine::destruct(jso);
|
|
break;
|
|
}
|
|
const_cast<String&>(op->name()) = name;
|
|
jso->params().setParam(op);
|
|
}
|
|
return jso;
|
|
}
|
|
|
|
bool JsCode::runOperation(ObjList& stack, const ExpOperation& oper, GenObject* context) const
|
|
{
|
|
switch ((JsOpcode)oper.opcode()) {
|
|
case OpcEqIdentity:
|
|
case OpcNeIdentity:
|
|
{
|
|
ExpOperation* op2 = popValue(stack,context);
|
|
ExpOperation* op1 = popValue(stack,context);
|
|
if (!op1 || !op2) {
|
|
TelEngine::destruct(op1);
|
|
TelEngine::destruct(op2);
|
|
return gotError("ExpEvaluator stack underflow",oper.lineNumber());
|
|
}
|
|
bool eq = (op1->opcode() == op2->opcode());
|
|
if (eq) {
|
|
ExpWrapper* w1 = YOBJECT(ExpWrapper,op1);
|
|
ExpWrapper* w2 = YOBJECT(ExpWrapper,op2);
|
|
if (w1 || w2)
|
|
eq = w1 && w2 && w1->object() == w2->object();
|
|
else
|
|
eq = (op1->number() == op2->number()) && (*op1 == *op2);
|
|
}
|
|
TelEngine::destruct(op1);
|
|
TelEngine::destruct(op2);
|
|
if ((JsOpcode)oper.opcode() == OpcNeIdentity)
|
|
eq = !eq;
|
|
pushOne(stack,new ExpOperation(eq));
|
|
}
|
|
break;
|
|
case OpcBegin:
|
|
pushOne(stack,new ExpOperation((Opcode)OpcBegin));
|
|
break;
|
|
case OpcEnd:
|
|
case OpcFlush:
|
|
{
|
|
ExpOperation* op = 0;
|
|
if ((JsOpcode)oper.opcode() == OpcEnd) {
|
|
op = popOne(stack);
|
|
if (op && (op->opcode() == (Opcode)OpcBegin)) {
|
|
TelEngine::destruct(op);
|
|
break;
|
|
}
|
|
}
|
|
bool done = false;
|
|
ExpOperation* o;
|
|
while ((o = static_cast<ExpOperation*>(stack.remove(false)))) {
|
|
done = (o->opcode() == (Opcode)OpcBegin);
|
|
TelEngine::destruct(o);
|
|
if (done)
|
|
break;
|
|
}
|
|
if (!done)
|
|
return gotError("ExpEvaluator stack underflow",oper.lineNumber());
|
|
if (op)
|
|
pushOne(stack,op);
|
|
}
|
|
break;
|
|
case OpcIndex:
|
|
{
|
|
ExpOperation* op2 = popValue(stack,context);
|
|
ExpOperation* op1 = popOne(stack);
|
|
if (!op1 || !op2) {
|
|
TelEngine::destruct(op1);
|
|
TelEngine::destruct(op2);
|
|
return gotError("Stack underflow",oper.lineNumber());
|
|
}
|
|
if (op1->opcode() != OpcField) {
|
|
ScriptContext* ctx = YOBJECT(ScriptContext,op1);
|
|
if (ctx) {
|
|
ExpOperation fld(OpcField,*op2);
|
|
if (ctx->runField(stack,fld,context)) {
|
|
TelEngine::destruct(op1);
|
|
TelEngine::destruct(op2);
|
|
break;
|
|
}
|
|
}
|
|
TelEngine::destruct(op1);
|
|
TelEngine::destruct(op2);
|
|
return gotError("Expecting field name",oper.lineNumber());
|
|
}
|
|
pushOne(stack,new ExpOperation(OpcField,op1->name() + "." + *op2));
|
|
TelEngine::destruct(op1);
|
|
TelEngine::destruct(op2);
|
|
}
|
|
break;
|
|
case OpcFieldOf:
|
|
{
|
|
ExpOperation* op2 = popOne(stack);
|
|
ExpOperation* op1 = popOne(stack);
|
|
if (!op1 || !op2) {
|
|
TelEngine::destruct(op1);
|
|
TelEngine::destruct(op2);
|
|
return gotError("Stack underflow",oper.lineNumber());
|
|
}
|
|
if (op2->opcode() != OpcField) {
|
|
TelEngine::destruct(op1);
|
|
TelEngine::destruct(op2);
|
|
return gotError("Expecting field names",oper.lineNumber());
|
|
}
|
|
if (op1->opcode() != OpcField) {
|
|
ScriptContext* ctx = YOBJECT(ScriptContext,op1);
|
|
if (ctx && ctx->runField(stack,*op2,context)) {
|
|
TelEngine::destruct(op1);
|
|
TelEngine::destruct(op2);
|
|
break;
|
|
}
|
|
TelEngine::destruct(op1);
|
|
TelEngine::destruct(op2);
|
|
return gotError("Expecting field names",oper.lineNumber());
|
|
}
|
|
pushOne(stack,new ExpOperation(OpcField,op1->name() + "." + op2->name()));
|
|
TelEngine::destruct(op1);
|
|
TelEngine::destruct(op2);
|
|
}
|
|
break;
|
|
case OpcTypeof:
|
|
{
|
|
ExpOperation* op = popValue(stack,context);
|
|
if (!op)
|
|
return gotError("Stack underflow",oper.lineNumber());
|
|
switch (op->opcode()) {
|
|
case OpcPush:
|
|
{
|
|
const char* txt = "string";
|
|
ExpWrapper* w = YOBJECT(ExpWrapper,op);
|
|
if (w)
|
|
txt = w->object() ? "object" : "undefined";
|
|
else if (op->isInteger())
|
|
txt = "number";
|
|
pushOne(stack,new ExpOperation(txt));
|
|
}
|
|
break;
|
|
case OpcFunc:
|
|
pushOne(stack,new ExpOperation("function"));
|
|
break;
|
|
default:
|
|
pushOne(stack,new ExpOperation("internal"));
|
|
}
|
|
TelEngine::destruct(op);
|
|
}
|
|
break;
|
|
case OpcVar:
|
|
{
|
|
for (ObjList* l = stack.skipNull(); l; l = l->skipNext()) {
|
|
JsObject* jso = YOBJECT(JsObject,l->get());
|
|
if (jso && jso->toString() == YSTRING("()")) {
|
|
if (!jso->hasField(stack,oper.name(),context)) {
|
|
XDebug(this,DebugInfo,"Creating variable '%s' in scope",
|
|
oper.name().c_str());
|
|
jso->params().setParam(new ExpWrapper(0,oper.name()));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case OpcNew:
|
|
{
|
|
ExpOperation* op = popOne(stack);
|
|
if (!op)
|
|
return gotError("Stack underflow",oper.lineNumber());
|
|
switch (op->opcode()) {
|
|
case OpcField:
|
|
break;
|
|
case OpcPush:
|
|
{
|
|
ExpWrapper* w = YOBJECT(ExpWrapper,op);
|
|
if (w && w->object()) {
|
|
pushOne(stack,op);
|
|
return true;
|
|
}
|
|
}
|
|
// fall through
|
|
default:
|
|
TelEngine::destruct(op);
|
|
return gotError("Expecting class name",oper.lineNumber());
|
|
}
|
|
ExpFunction ctr(op->name(),op->number());
|
|
TelEngine::destruct(op);
|
|
return runOperation(stack,ctr,context);
|
|
}
|
|
break;
|
|
case OpcThrow:
|
|
{
|
|
ExpOperation* op = popOne(stack);
|
|
if (!op)
|
|
return gotError("Stack underflow",oper.lineNumber());
|
|
bool ok = false;
|
|
while (ExpOperation* drop = popAny(stack)) {
|
|
JsOpcode c = (JsOpcode)drop->opcode();
|
|
TelEngine::destruct(drop);
|
|
if (c == OpcTry) {
|
|
ok = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!ok)
|
|
return gotError("Uncaught exception: " + *op,oper.lineNumber());
|
|
pushOne(stack,op);
|
|
}
|
|
break;
|
|
case OpcReturn:
|
|
{
|
|
ExpOperation* op = popValue(stack,context);
|
|
bool ok = false;
|
|
while (ExpOperation* drop = popAny(stack)) {
|
|
ok = drop->opcode() == OpcFunc;
|
|
long int lbl = drop->number();
|
|
TelEngine::destruct(drop);
|
|
if (ok) {
|
|
ok = jumpAbsolute(lbl,context);
|
|
break;
|
|
}
|
|
}
|
|
if (!ok) {
|
|
TelEngine::destruct(op);
|
|
return gotError("Return outside function call",oper.lineNumber());
|
|
}
|
|
if (op)
|
|
pushOne(stack,op);
|
|
}
|
|
break;
|
|
case OpcIn:
|
|
case OpcOf:
|
|
{
|
|
ExpOperation* obj = popValue(stack,context);
|
|
ExpOperation* fld = popOne(stack);
|
|
if (!fld || !obj) {
|
|
TelEngine::destruct(fld);
|
|
TelEngine::destruct(obj);
|
|
return gotError("Stack underflow",oper.lineNumber());
|
|
}
|
|
if (fld->opcode() != OpcField) {
|
|
TelEngine::destruct(fld);
|
|
TelEngine::destruct(obj);
|
|
return gotError("Expecting field name",oper.lineNumber());
|
|
}
|
|
bool isOf = ((JsOpcode)oper.opcode() == OpcOf);
|
|
JsIterator* iter = 0;
|
|
JsObject* jso = YOBJECT(JsObject,obj);
|
|
if (jso)
|
|
iter = new JsIterator(*fld,jso);
|
|
else {
|
|
NamedList* lst = YOBJECT(NamedList,obj);
|
|
if (lst)
|
|
iter = new JsIterator(*fld,lst);
|
|
}
|
|
ExpWrapper* wrap = 0;
|
|
if (iter) {
|
|
if (isOf)
|
|
iter->name(obj->name());
|
|
wrap = new ExpWrapper(iter);
|
|
#ifdef DEBUG
|
|
*wrap << fld->name() << (isOf ? " of " : " in ") << obj->name();
|
|
Debug(this,DebugInfo,"Created iterator: '%s'",wrap->c_str());
|
|
#endif
|
|
}
|
|
TelEngine::destruct(fld);
|
|
TelEngine::destruct(obj);
|
|
if (wrap)
|
|
pushOne(stack,wrap);
|
|
else
|
|
return gotError("Expecting iterable object",oper.lineNumber());
|
|
}
|
|
break;
|
|
case OpcNext:
|
|
{
|
|
JsIterator* iter = 0;
|
|
ExpOperation* op;
|
|
while (!iter) {
|
|
op = popValue(stack,context);
|
|
if (!op)
|
|
return gotError("Stack underflow",oper.lineNumber());
|
|
iter = YOBJECT(JsIterator,op);
|
|
if (!iter)
|
|
TelEngine::destruct(op);
|
|
}
|
|
bool ok = false;
|
|
String* n = iter->get();
|
|
if (n) {
|
|
XDebug(DebugInfo,"Iterator got item: '%s'",n->c_str());
|
|
static const ExpOperation s_assign(OpcAssign);
|
|
pushOne(stack,iter->field().clone());
|
|
if (iter->name())
|
|
pushOne(stack,new ExpOperation(OpcField,iter->name() + "." + *n));
|
|
else
|
|
pushOne(stack,new ExpOperation(*n));
|
|
TelEngine::destruct(n);
|
|
ok = runOperation(stack,s_assign,context);
|
|
}
|
|
if (ok) {
|
|
// assign pushes the value back on stack
|
|
TelEngine::destruct(popOne(stack));
|
|
pushOne(stack,op);
|
|
}
|
|
else
|
|
TelEngine::destruct(op);
|
|
pushOne(stack,new ExpOperation(ok));
|
|
}
|
|
break;
|
|
case OpcCase:
|
|
{
|
|
ExpOperation* cons = popValue(stack,context);
|
|
ExpOperation* expr = popValue(stack,context);
|
|
if (!cons || !expr) {
|
|
TelEngine::destruct(cons);
|
|
TelEngine::destruct(expr);
|
|
return gotError("ExpEvaluator stack underflow",oper.lineNumber());
|
|
}
|
|
bool eq = false;
|
|
JsRegExp* rex = YOBJECT(JsRegExp,cons);
|
|
if (rex)
|
|
eq = rex->regexp().matches(*expr);
|
|
else if (expr->opcode() == cons->opcode()) {
|
|
ExpWrapper* w1 = YOBJECT(ExpWrapper,expr);
|
|
ExpWrapper* w2 = YOBJECT(ExpWrapper,cons);
|
|
if (w1 || w2)
|
|
eq = w1 && w2 && w1->object() == w2->object();
|
|
else
|
|
eq = (expr->number() == cons->number()) && (*expr == *cons);
|
|
}
|
|
if (!eq) {
|
|
// put expression back on stack, we'll need it later
|
|
pushOne(stack,expr);
|
|
expr = 0;
|
|
}
|
|
TelEngine::destruct(cons);
|
|
pushOne(stack,new ExpOperation(eq));
|
|
}
|
|
break;
|
|
case OpcJumpTrue:
|
|
case OpcJumpFalse:
|
|
case OpcJRelTrue:
|
|
case OpcJRelFalse:
|
|
{
|
|
ExpOperation* op = popValue(stack,context);
|
|
if (!op)
|
|
return gotError("Stack underflow",oper.lineNumber());
|
|
bool val = op->valBoolean();
|
|
TelEngine::destruct(op);
|
|
switch ((JsOpcode)oper.opcode()) {
|
|
case OpcJumpTrue:
|
|
case OpcJRelTrue:
|
|
if (!val)
|
|
return true;
|
|
break;
|
|
case OpcJumpFalse:
|
|
case OpcJRelFalse:
|
|
if (val)
|
|
return true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
// fall through
|
|
case OpcJump:
|
|
case OpcJRel:
|
|
switch ((JsOpcode)oper.opcode()) {
|
|
case OpcJump:
|
|
case OpcJumpTrue:
|
|
case OpcJumpFalse:
|
|
return jumpToLabel(oper.number(),context) || gotError("Label not found",oper.lineNumber());
|
|
case OpcJRel:
|
|
case OpcJRelTrue:
|
|
case OpcJRelFalse:
|
|
return jumpRelative(oper.number(),context) || gotError("Relative jump failed",oper.lineNumber());
|
|
default:
|
|
return false;
|
|
}
|
|
break;
|
|
default:
|
|
return ExpEvaluator::runOperation(stack,oper,context);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool JsCode::runFunction(ObjList& stack, const ExpOperation& oper, GenObject* context) const
|
|
{
|
|
DDebug(this,DebugAll,"JsCode::runFunction(%p,'%s' %ld, %p) ext=%p",
|
|
&stack,oper.name().c_str(),oper.number(),context,extender());
|
|
if (context) {
|
|
ScriptRun* sr = static_cast<ScriptRun*>(context);
|
|
if (sr->context()->runFunction(stack,oper,context))
|
|
return true;
|
|
}
|
|
return extender() && extender()->runFunction(stack,oper,context);
|
|
}
|
|
|
|
bool JsCode::runField(ObjList& stack, const ExpOperation& oper, GenObject* context) const
|
|
{
|
|
DDebug(this,DebugAll,"JsCode::runField(%p,'%s',%p) ext=%p",
|
|
&stack,oper.name().c_str(),context,extender());
|
|
if (context) {
|
|
ScriptRun* sr = static_cast<ScriptRun*>(context);
|
|
if (sr->context()->runField(stack,oper,context))
|
|
return true;
|
|
}
|
|
return extender() && extender()->runField(stack,oper,context);
|
|
}
|
|
|
|
bool JsCode::runAssign(ObjList& stack, const ExpOperation& oper, GenObject* context) const
|
|
{
|
|
DDebug(this,DebugAll,"JsCode::runAssign('%s'='%s',%p) ext=%p",
|
|
oper.name().c_str(),oper.c_str(),context,extender());
|
|
if (context) {
|
|
ScriptRun* sr = static_cast<ScriptRun*>(context);
|
|
if (sr->context()->runAssign(stack,oper,context))
|
|
return true;
|
|
}
|
|
return extender() && extender()->runAssign(stack,oper,context);
|
|
}
|
|
|
|
bool JsCode::evalList(ObjList& stack, GenObject* context) const
|
|
{
|
|
XDebug(this,DebugInfo,"JsCode::evalList(%p,%p)",&stack,context);
|
|
JsRunner* runner = static_cast<JsRunner*>(context);
|
|
const ObjList* (& opcode) = runner->m_opcode;
|
|
while (opcode) {
|
|
const ExpOperation* o = static_cast<const ExpOperation*>(opcode->get());
|
|
opcode = opcode->skipNext();
|
|
if (!runOperation(stack,*o,context))
|
|
return false;
|
|
if (runner->m_paused)
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool JsCode::evalVector(ObjList& stack, GenObject* context) const
|
|
{
|
|
XDebug(this,DebugInfo,"JsCode::evalVector(%p,%p)",&stack,context);
|
|
JsRunner* runner = static_cast<JsRunner*>(context);
|
|
unsigned int& index = runner->m_index;
|
|
while (index < m_linked.length()) {
|
|
const ExpOperation* o = static_cast<const ExpOperation*>(m_linked[index++]);
|
|
if (o && !runOperation(stack,*o,context))
|
|
return false;
|
|
if (runner->m_paused)
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool JsCode::jumpToLabel(long int label, GenObject* context) const
|
|
{
|
|
if (!context)
|
|
return false;
|
|
JsRunner* runner = static_cast<JsRunner*>(context);
|
|
for (ObjList* l = m_opcodes.skipNull(); l; l = l->skipNext()) {
|
|
const ExpOperation* o = static_cast<const ExpOperation*>(l->get());
|
|
if (o->opcode() == OpcLabel && o->number() == label) {
|
|
runner->m_opcode = l;
|
|
XDebug(this,DebugInfo,"Jumped to label %ld",label);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool JsCode::jumpRelative(long int offset, GenObject* context) const
|
|
{
|
|
if (!context)
|
|
return false;
|
|
unsigned int& index = static_cast<JsRunner*>(context)->m_index;
|
|
long int i = index + offset;
|
|
if (i < 0 || i > (long int)m_linked.length())
|
|
return false;
|
|
index = i;
|
|
XDebug(this,DebugInfo,"Jumped relative %+ld to index %ld",offset,i);
|
|
return true;
|
|
}
|
|
|
|
bool JsCode::jumpAbsolute(long int index, GenObject* context) const
|
|
{
|
|
if (!context)
|
|
return false;
|
|
JsRunner* runner = static_cast<JsRunner*>(context);
|
|
if (m_linked.length()) {
|
|
if (index < 0) {
|
|
runner->m_index = m_linked.length();
|
|
return true;
|
|
}
|
|
if (index > m_linked.length())
|
|
return false;
|
|
runner->m_index = index;
|
|
}
|
|
else {
|
|
if (index < 0) {
|
|
runner->m_opcode = 0;
|
|
return true;
|
|
}
|
|
long int i = 0;
|
|
for (const ObjList* l = &m_opcodes; ; l = l->next(), i++) {
|
|
if (i == index) {
|
|
runner->m_opcode = l;
|
|
break;
|
|
}
|
|
if (!l)
|
|
break;
|
|
}
|
|
if (i != index)
|
|
return false;
|
|
}
|
|
XDebug(this,DebugInfo,"Jumped absolute to index %ld",index);
|
|
return true;
|
|
}
|
|
|
|
bool JsCode::callFunction(ObjList& stack, const ExpOperation& oper, GenObject* context, JsFunction* func, bool constr) const
|
|
{
|
|
if (!(func && context))
|
|
return false;
|
|
XDebug(this,DebugInfo,"JsCode::callFunction(%p,%lu,%p) in %s'%s'",
|
|
&stack,oper.number(),context,(constr ? "constructor " : ""),func->toString().c_str());
|
|
JsRunner* runner = static_cast<JsRunner*>(context);
|
|
long int index = runner->m_index;
|
|
if (!m_linked.length()) {
|
|
const ObjList* o = runner->m_opcode;
|
|
index = -1;
|
|
long int i = 0;
|
|
for (const ObjList* l = &m_opcodes; ; l = l->next(), i++) {
|
|
if (l == o) {
|
|
index = i;
|
|
break;
|
|
}
|
|
if (!l)
|
|
break;
|
|
}
|
|
}
|
|
if (index < 0) {
|
|
Debug(this,DebugWarn,"Oops! Could not find return point!");
|
|
return false;
|
|
}
|
|
ExpOperation* op = constr ? popOne(stack) : 0;
|
|
JsObject* thisObj = YOBJECT(JsObject,op);
|
|
if (thisObj && !thisObj->ref())
|
|
thisObj = 0;
|
|
TelEngine::destruct(op);
|
|
ObjList args;
|
|
JsObject::extractArgs(func,stack,oper,context,args);
|
|
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,
|
|
JsObject* thisObj, JsObject* scopeObj) const
|
|
{
|
|
pushOne(stack,new ExpOperation(OpcFunc,0,retIndex,true));
|
|
if (scopeObj)
|
|
pushOne(stack,new ExpWrapper(scopeObj,"()"));
|
|
JsObject* ctxt = JsObject::buildCallContext(func->mutex(),thisObj);
|
|
for (unsigned int idx = 0; ; idx++) {
|
|
const String* name = func->formalName(idx);
|
|
if (!name)
|
|
break;
|
|
ExpOperation* param = static_cast<ExpOperation*>(args.remove(false));
|
|
if (param)
|
|
ctxt->params().setParam(param->clone(*name));
|
|
else
|
|
ctxt->params().setParam(new ExpWrapper(0,*name));
|
|
TelEngine::destruct(param);
|
|
}
|
|
pushOne(stack,new ExpWrapper(ctxt,ctxt->toString(),true));
|
|
return jumpToLabel(func->label(),context);
|
|
}
|
|
|
|
ScriptRun* JsCode::createRunner(ScriptContext* context)
|
|
{
|
|
if (!context)
|
|
return 0;
|
|
return new JsRunner(this,context);
|
|
}
|
|
|
|
|
|
|
|
ScriptRun::Status JsRunner::reset()
|
|
{
|
|
Status s = ScriptRun::reset();
|
|
m_opcode = code() ? static_cast<const JsCode*>(code())->m_opcodes.skipNull() : 0;
|
|
m_index = 0;
|
|
return s;
|
|
}
|
|
|
|
ScriptRun::Status JsRunner::resume()
|
|
{
|
|
Lock mylock(this);
|
|
if (Running != state())
|
|
return state();
|
|
RefPointer<ScriptCode> c = code();
|
|
if (!(c && context()))
|
|
return Invalid;
|
|
m_paused = false;
|
|
mylock.drop();
|
|
if (!c->evaluate(*this,stack()))
|
|
return Failed;
|
|
return m_paused ? Incomplete : Succeeded;
|
|
}
|
|
|
|
bool JsRunner::pause()
|
|
{
|
|
Lock mylock(this);
|
|
if (m_paused)
|
|
return true;
|
|
switch (state()) {
|
|
case Running:
|
|
case Incomplete:
|
|
DDebug(DebugAll,"Pausing Javascript runner [%p]",this);
|
|
m_paused = true;
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
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<const JsCode*>(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;
|
|
}
|
|
JsObject* jsThis = YOBJECT(JsObject,thisObj);
|
|
if (jsThis && !jsThis->ref())
|
|
jsThis = 0;
|
|
JsObject* jsScope = YOBJECT(JsObject,scopeObj);
|
|
if (jsScope && !jsScope->ref())
|
|
jsScope = 0;
|
|
TelEngine::destruct(thisObj);
|
|
TelEngine::destruct(scopeObj);
|
|
reset();
|
|
// prepare a function call stack
|
|
ExpOperation oper(ExpEvaluator::OpcFunc,name,args.count());
|
|
if (!c->callFunction(stack(),oper,this,-1,func,args,jsThis,jsScope))
|
|
return Failed;
|
|
mylock.drop();
|
|
// continue normal execution like in run()
|
|
ScriptRun::Status s = state();
|
|
while (Incomplete == s)
|
|
s = execute();
|
|
return s;
|
|
}
|
|
|
|
bool JsRunner::callable(const String& name)
|
|
{
|
|
Lock mylock(this);
|
|
if (Invalid == state())
|
|
return false;
|
|
const JsCode* c = static_cast<const JsCode*>(code());
|
|
return (c && context() && c->getGlobalFunction(name));
|
|
}
|
|
|
|
}; // anonymous namespace
|
|
|
|
|
|
JsFunction::JsFunction(Mutex* mtx)
|
|
: JsObject("Function",mtx,true),
|
|
m_label(0), m_code(0)
|
|
{
|
|
init();
|
|
}
|
|
|
|
JsFunction::JsFunction(Mutex* mtx, const char* name, ObjList* args, long int lbl, ScriptCode* code)
|
|
: JsObject(mtx,String("[function ") + name + "()]",true),
|
|
m_label(lbl), m_code(code)
|
|
{
|
|
init();
|
|
if (args) {
|
|
while (GenObject* arg = args->remove(false))
|
|
m_formal.append(arg);
|
|
}
|
|
params().addParam("length",String(m_formal.count()));
|
|
}
|
|
|
|
void JsFunction::init()
|
|
{
|
|
params().addParam(new ExpFunction("apply"));
|
|
params().addParam(new ExpFunction("call"));
|
|
}
|
|
|
|
bool JsFunction::runNative(ObjList& stack, const ExpOperation& oper, GenObject* context)
|
|
{
|
|
XDebug(DebugAll,"JsFunction::runNative() '%s' in '%s' [%p]",
|
|
oper.name().c_str(),toString().c_str(),this);
|
|
if (oper.name() == YSTRING("apply")) {
|
|
// func.apply(new_this,["array","of","params",...])
|
|
if (oper.number() != 2)
|
|
return false;
|
|
}
|
|
else if (oper.name() == YSTRING("call")) {
|
|
// func.call(new_this,param1,param2,...)
|
|
if (!oper.number())
|
|
return false;
|
|
}
|
|
else {
|
|
JsObject* obj = YOBJECT(JsObject,params().getParam(YSTRING("prototype")));
|
|
return obj ? obj->runNative(stack,oper,context) : JsObject::runNative(stack,oper,context);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool JsFunction::runDefined(ObjList& stack, const ExpOperation& oper, GenObject* context)
|
|
{
|
|
XDebug(DebugAll,"JsFunction::runDefined() in '%s' [%p]",toString().c_str(),this);
|
|
JsObject* proto = YOBJECT(JsObject,getField(stack,"prototype",context));
|
|
if (proto) {
|
|
// found prototype, build object
|
|
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);
|
|
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);
|
|
}
|
|
|
|
|
|
// Adjust a script file include path
|
|
void JsParser::adjustPath(String& script) const
|
|
{
|
|
if (script.null() || script.startsWith(Engine::pathSeparator()))
|
|
return;
|
|
script = m_basePath + script;
|
|
}
|
|
|
|
// Create Javascript context
|
|
ScriptContext* JsParser::createContext() const
|
|
{
|
|
return new JsContext;
|
|
}
|
|
|
|
ScriptRun* JsParser::createRunner(ScriptCode* code, ScriptContext* context) const
|
|
{
|
|
if (!code)
|
|
return 0;
|
|
ScriptContext* ctxt = 0;
|
|
if (!context)
|
|
context = ctxt = createContext();
|
|
ScriptRun* runner = new JsRunner(code,context);
|
|
TelEngine::destruct(ctxt);
|
|
return runner;
|
|
}
|
|
|
|
// Check if function or method exists
|
|
bool JsParser::callable(const String& name)
|
|
{
|
|
const JsCode* c = static_cast<const JsCode*>(code());
|
|
return (c && c->getGlobalFunction(name));
|
|
}
|
|
|
|
// Parse a piece of Javascript text
|
|
bool JsParser::parse(const char* text, bool fragment)
|
|
{
|
|
if (TelEngine::null(text))
|
|
return false;
|
|
if (fragment)
|
|
return code() && static_cast<JsCode*>(code())->compile(text,this);
|
|
JsCode* code = new JsCode;
|
|
setCode(code);
|
|
code->deref();
|
|
if (!code->compile(text,this)) {
|
|
setCode(0);
|
|
return false;
|
|
}
|
|
DDebug(DebugAll,"Compiled: %s",code->dump().c_str());
|
|
code->simplify();
|
|
DDebug(DebugAll,"Simplified: %s",code->dump().c_str());
|
|
return true;
|
|
}
|
|
|
|
// Evaluate a string as expression or statement
|
|
ScriptRun::Status JsParser::eval(const String& text, ExpOperation** result, ScriptContext* context)
|
|
{
|
|
if (TelEngine::null(text))
|
|
return ScriptRun::Invalid;
|
|
JsParser parser;
|
|
if (!parser.parse(text))
|
|
return ScriptRun::Invalid;
|
|
ScriptRun* runner = parser.createRunner(context);
|
|
ScriptRun::Status rval = runner->run();
|
|
if (result && (ScriptRun::Succeeded == rval))
|
|
*result = ExpEvaluator::popOne(runner->stack());
|
|
TelEngine::destruct(runner);
|
|
return rval;
|
|
}
|
|
|
|
// Parse JSON using native methods
|
|
JsObject* JsParser::parseJSON(const char* text)
|
|
{
|
|
JsCode* code = new JsCode;
|
|
JsObject* jso = code->parseObject(text,true);
|
|
TelEngine::destruct(code);
|
|
return jso;
|
|
}
|
|
|
|
// Return a "null" object wrapper
|
|
ExpOperation* JsParser::nullClone()
|
|
{
|
|
return s_null.ExpOperation::clone();
|
|
}
|
|
|
|
// Check if an object is identic to null
|
|
bool JsParser::isNull(const ExpOperation& oper)
|
|
{
|
|
ExpWrapper* w = YOBJECT(ExpWrapper,&oper);
|
|
return w && (w->object() == s_null.object());
|
|
}
|
|
|
|
// Check if an operation is undefined
|
|
bool JsParser::isUndefined(const ExpOperation& oper)
|
|
{
|
|
ExpWrapper* w = YOBJECT(ExpWrapper,&oper);
|
|
return w && !w->object();
|
|
}
|
|
|
|
/* vi: set ts=8 sw=4 sts=4 noet: */
|