4006 lines
110 KiB
C++
4006 lines
110 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-2014 Null Team
|
|
*
|
|
* This software is distributed under multiple licenses;
|
|
* see the COPYING file in the main directory for licensing
|
|
* information for this specific distribution.
|
|
*
|
|
* This use of this software may be subject to additional restrictions.
|
|
* See the LEGAL file in the main directory for details.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include "yatescript.h"
|
|
#include <yatengine.h>
|
|
|
|
//#define STATS_TRACE "jstrace"
|
|
|
|
using namespace TelEngine;
|
|
|
|
namespace { // anonymous
|
|
|
|
class ParseNested;
|
|
class JsRunner;
|
|
class JsCodeStats;
|
|
|
|
class JsContext : public JsObject, public ScriptMutex
|
|
{
|
|
YCLASS(JsContext,JsObject)
|
|
public:
|
|
inline JsContext(unsigned int instIdx = 0, unsigned int maxInst = 1)
|
|
: JsObject("Context",0), ScriptMutex(true,"JsContext"),
|
|
m_trackObjs(0), m_trackObjsMtx(false,"JsObjTrack")
|
|
{
|
|
setMutex(this);
|
|
params().addParam(new ExpFunction("isNaN"));
|
|
params().addParam(new ExpFunction("parseInt"));
|
|
params().addParam(new ExpOperation(ExpOperation::nonInteger(),"NaN"));
|
|
m_objTrack = !!m_trackObjs;
|
|
setInstance(instIdx,maxInst);
|
|
DDebug(DebugAll,"JsContext::JsContext() [%p]",this);
|
|
}
|
|
virtual void destroyed();
|
|
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);
|
|
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);
|
|
void objCreated(GenObject* obj)
|
|
{ createdObj(obj); };
|
|
void objDeleted(GenObject* obj)
|
|
{ deletedObj(obj); };
|
|
void createdObj(GenObject* obj);
|
|
void deletedObj(GenObject* obj);
|
|
void trackObjs(unsigned int track = 0);
|
|
ObjList* countAllocations();
|
|
private:
|
|
GenObject* resolveTop(ObjList& stack, const String& name, GenObject* context);
|
|
HashList* m_trackObjs;
|
|
Mutex m_trackObjsMtx;
|
|
};
|
|
|
|
class JsNull : public JsObject
|
|
{
|
|
public:
|
|
inline JsNull()
|
|
: JsObject(0,"null",0,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); }
|
|
virtual ExpOperation* copy(ScriptMutex* mtx) const
|
|
{ return clone(name()); }
|
|
protected:
|
|
inline ExpNull(JsNull* obj, const char* name)
|
|
: ExpWrapper(obj,name)
|
|
{ obj->ref(); }
|
|
};
|
|
|
|
class JsCodeFile : public String
|
|
{
|
|
public:
|
|
inline JsCodeFile(const String& file)
|
|
: String(file), m_fileTime(0), m_shortName(file)
|
|
{ File::getFileTime(file,m_fileTime); setShortName(); }
|
|
inline JsCodeFile(const String& file, unsigned int fTime)
|
|
: String(file), m_fileTime(fTime), m_shortName(file)
|
|
{ setShortName(); }
|
|
inline unsigned int fileTime() const
|
|
{ return m_fileTime; }
|
|
inline bool fileChanged() const
|
|
{
|
|
if (!m_fileTime)
|
|
return false;
|
|
unsigned int t = 0;
|
|
File::getFileTime(c_str(),t);
|
|
return t != m_fileTime;
|
|
}
|
|
inline const String& shortName() const
|
|
{ return m_shortName; }
|
|
private:
|
|
inline void setShortName()
|
|
{
|
|
int pos = rfind('/');
|
|
#ifdef _WINDOWS
|
|
int backPos = rfind('\\');
|
|
pos = pos > backPos ? pos : backPos;
|
|
#endif
|
|
if (pos >= 0 && (pos < (int)length() - 1))
|
|
m_shortName = substr(pos + 1);
|
|
}
|
|
unsigned int m_fileTime;
|
|
String m_shortName;
|
|
};
|
|
|
|
struct JsEntry
|
|
{
|
|
long int number;
|
|
unsigned int index;
|
|
};
|
|
|
|
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,
|
|
OpcDelete,
|
|
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,
|
|
OpcPragma,
|
|
};
|
|
inline JsCode()
|
|
: ExpEvaluator(C),
|
|
m_pragmas(""), m_label(0), m_depth(0), m_entries(0), m_traceable(false)
|
|
{ debugName("JsCode"); }
|
|
~JsCode();
|
|
virtual void* getObject(const String& name) const
|
|
{
|
|
if (name == YATOM("JsCode"))
|
|
return const_cast<JsCode*>(this);
|
|
if (name == YATOM("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, const char* title);
|
|
virtual bool null() const;
|
|
virtual void dump(String& res, bool loneNo = false) const;
|
|
virtual void getFileLine(unsigned int line, String& fileName, unsigned int& fileLine, bool wholePath = true)
|
|
{
|
|
fileName = getFileName(line,wholePath);
|
|
fileLine = getLineNo(line);
|
|
}
|
|
bool link();
|
|
inline bool traceable() const
|
|
{ return m_traceable; }
|
|
JsObject* parseArray(ParsePoint& expr, bool constOnly, ScriptMutex* mtx);
|
|
JsObject* parseObject(ParsePoint& expr, bool constOnly, ScriptMutex* mtx);
|
|
inline const NamedList& pragmas() const
|
|
{ return m_pragmas; }
|
|
inline static unsigned int getLineNo(unsigned int line)
|
|
{ return line & 0xffffff; }
|
|
inline static unsigned int getFileNo(unsigned int line)
|
|
{ return (line >> 24) & 0xff; }
|
|
inline unsigned int getFileCount() const
|
|
{ return m_included.length(); }
|
|
const String& getFileAt(unsigned int index, bool wholePath = true) const;
|
|
inline const String& getFileName(unsigned int line, bool wholePath = true) const
|
|
{ return getFileAt(getFileNo(line),wholePath); }
|
|
bool scriptChanged() const;
|
|
protected:
|
|
inline void trace(bool allowed)
|
|
{ m_traceable = allowed; }
|
|
void setBaseFile(const String& file);
|
|
virtual void formatLineNo(String& buf, unsigned int line) const;
|
|
virtual bool getString(ParsePoint& expr);
|
|
virtual bool getEscape(const char*& expr, String& str, char sep);
|
|
virtual bool keywordLetter(char c) const;
|
|
virtual int getKeyword(const char* str) const;
|
|
virtual char skipComments(ParsePoint& expr, GenObject* context = 0);
|
|
virtual int preProcess(ParsePoint& expr, GenObject* context = 0);
|
|
virtual bool getInstruction(ParsePoint& expr, char stop, GenObject* nested);
|
|
virtual bool getSimple(ParsePoint& expr, bool constOnly = false);
|
|
virtual Opcode getOperator(ParsePoint& expr);
|
|
virtual Opcode getUnaryOperator(ParsePoint& expr);
|
|
virtual Opcode getPostfixOperator(ParsePoint& expr, int precedence);
|
|
virtual const char* getOperator(Opcode oper) const;
|
|
virtual int getPrecedence(ExpEvaluator::Opcode oper) const;
|
|
virtual bool getSeparator(ParsePoint& 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;
|
|
NamedList m_pragmas;
|
|
bool preProcessInclude(ParsePoint& expr, bool once, GenObject* context);
|
|
bool preProcessPragma(ParsePoint& expr, GenObject* context);
|
|
bool getOneInstruction(ParsePoint& expr, GenObject* nested);
|
|
bool parseInner(ParsePoint& expr, JsOpcode opcode, ParseNested* nested);
|
|
bool parseIf(ParsePoint& expr, GenObject* nested);
|
|
bool parseSwitch(ParsePoint& expr, GenObject* nested);
|
|
bool parseFor(ParsePoint& expr, GenObject* nested);
|
|
bool parseWhile(ParsePoint& expr, GenObject* nested);
|
|
bool parseVar(ParsePoint& expr);
|
|
bool parseTry(ParsePoint& expr, GenObject* nested);
|
|
bool parseFuncDef(ParsePoint& expr, bool publish);
|
|
bool parseSimple(ParsePoint& expr, bool constOnly, ScriptMutex* mtx = 0);
|
|
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, JsObject* thisObj = 0) const;
|
|
bool callFunction(ObjList& stack, const ExpOperation& oper, GenObject* context,
|
|
long int retIndex, JsFunction* func, ObjList& args,
|
|
JsObject* thisObj, JsObject* scopeObj) const;
|
|
void resolveObjectParams(JsObject* obj, ObjList& stack, GenObject* context) const;
|
|
void resolveObjectParams(JsObject* object, ObjList& stack, GenObject* context, JsContext* ctxt,
|
|
JsObject* objProto, JsArray* arrayProto) const;
|
|
inline JsFunction* getGlobalFunction(const String& name) const
|
|
{ return YOBJECT(JsFunction,m_globals[name]); }
|
|
long int m_label;
|
|
int m_depth;
|
|
JsEntry* m_entries;
|
|
bool m_traceable;
|
|
};
|
|
|
|
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 JsLineStats : public GenObject
|
|
{
|
|
public:
|
|
inline JsLineStats(unsigned int lineNo, unsigned int instr, u_int64_t usec)
|
|
: lineNumber(lineNo), operations(instr), microseconds(usec), isCall(false)
|
|
{ }
|
|
unsigned int lineNumber;
|
|
unsigned int operations;
|
|
u_int64_t microseconds;
|
|
bool isCall;
|
|
};
|
|
|
|
class JsCallStats : public JsLineStats
|
|
{
|
|
public:
|
|
inline JsCallStats(const char* name, unsigned int caller, unsigned int called, unsigned int instr, u_int64_t usec)
|
|
: JsLineStats(caller,instr,usec),
|
|
funcName(name), callsCount(1), calledLine(called)
|
|
{ isCall = true; }
|
|
String funcName;
|
|
unsigned int callsCount;
|
|
unsigned int calledLine;
|
|
};
|
|
|
|
class JsFuncStats : public String
|
|
{
|
|
public:
|
|
inline JsFuncStats(const char* name, unsigned int lineNo)
|
|
: String(name), lineNumber(lineNo)
|
|
{ }
|
|
unsigned int lineNumber;
|
|
ObjList funcLines;
|
|
void updateLine(unsigned int lineNo, u_int64_t usec);
|
|
void updateCall(const char* name, unsigned int caller, unsigned int called, unsigned int instr, u_int64_t usec);
|
|
};
|
|
|
|
class JsCodeStats : public Mutex, public RefObject
|
|
{
|
|
YCLASS(JsCodeStats,RefObject)
|
|
public:
|
|
JsCodeStats(JsCode* code, const char* file = 0);
|
|
~JsCodeStats();
|
|
virtual const String& toString() const
|
|
{ return m_fileName; }
|
|
JsFuncStats* getFuncStats(const char* name, unsigned int lineNo);
|
|
void dump();
|
|
void dump(const char* file);
|
|
void dump(Stream& file);
|
|
private:
|
|
RefPointer<JsCode> m_code;
|
|
String m_fileName;
|
|
ObjList m_funcStats;
|
|
};
|
|
|
|
class JsCallInfo : public String
|
|
{
|
|
friend class JsRunner;
|
|
private:
|
|
inline JsCallInfo(JsFuncStats* stats, const char* name,
|
|
unsigned int caller, unsigned int called, unsigned int instr, u_int64_t time)
|
|
: String(name),
|
|
funcStats(stats), callerLine(caller), calledLine(called), startInstr(instr), startTime(time)
|
|
{ }
|
|
inline void traceLine(unsigned int line, u_int64_t time)
|
|
{ if (funcStats) funcStats->updateLine(line,time); }
|
|
inline void traceCall(const JsCallInfo* call, unsigned int instr, u_int64_t time)
|
|
{ if (call && funcStats) funcStats->updateCall(call->c_str(),call->callerLine,call->calledLine,instr,time); }
|
|
|
|
JsFuncStats* funcStats;
|
|
unsigned int callerLine;
|
|
unsigned int calledLine;
|
|
unsigned int startInstr;
|
|
u_int64_t startTime;
|
|
};
|
|
|
|
class JsRunner : public ScriptRun
|
|
{
|
|
YCLASS(JsRunner,ScriptRun)
|
|
friend class JsCode;
|
|
public:
|
|
inline JsRunner(ScriptCode* code, ScriptContext* context, const char* title)
|
|
: ScriptRun(code,context),
|
|
m_paused(false), m_tracing(false), m_opcode(0), m_index(0),
|
|
m_instr(0), m_lastLine(0), m_lastTime(0), m_totalTime(0), m_callInfo(0)
|
|
{ traceCheck(title); }
|
|
virtual ~JsRunner()
|
|
{ if (m_tracing) traceDump(); }
|
|
inline bool tracing() const
|
|
{ return m_tracing; }
|
|
virtual Status reset(bool init);
|
|
virtual bool pause();
|
|
virtual Status call(const String& name, ObjList& args, ExpOperation* thisObj = 0, ExpOperation* scopeObj = 0);
|
|
virtual bool callable(const String& name);
|
|
void traceStart(const char* title, const char* file = 0);
|
|
void tracePrep(const ExpOperation& oper);
|
|
void tracePost(const ExpOperation& oper);
|
|
void traceCall(const ExpOperation& oper, const JsFunction& func);
|
|
void traceReturn();
|
|
const ExpOperation* getCurrentOpCode() const;
|
|
virtual unsigned int currentLineNo() const;
|
|
virtual const String& currentFileName(bool wholePath = false) const;
|
|
protected:
|
|
virtual Status resume();
|
|
void traceDump();
|
|
void traceCheck(const char* title);
|
|
void traceStart(const char* title, JsCodeStats* stats);
|
|
private:
|
|
bool m_paused;
|
|
bool m_tracing;
|
|
const ObjList* m_opcode;
|
|
unsigned int m_index;
|
|
unsigned int m_instr;
|
|
unsigned int m_lastLine;
|
|
u_int64_t m_lastTime;
|
|
u_int64_t m_totalTime;
|
|
JsCallInfo* m_callInfo;
|
|
ObjList m_traceStack;
|
|
RefPointer<JsCodeStats> m_stats;
|
|
};
|
|
|
|
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, ParsePoint& expr)
|
|
{ ParseNested* inner = findMatch(nested,opcode);
|
|
return inner && inner->parseInner(expr,opcode); }
|
|
protected:
|
|
virtual bool isMatch(JsCode::JsOpcode opcode)
|
|
{ return false; }
|
|
inline bool parseInner(ParsePoint& 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;
|
|
};
|
|
|
|
class NativeFields : public ObjList
|
|
{
|
|
public:
|
|
inline NativeFields()
|
|
{
|
|
append(new String("length"));
|
|
append(new String("charAt"));
|
|
append(new String("charCodeAt"));
|
|
append(new String("indexOf"));
|
|
append(new String("includes"));
|
|
append(new String("substr"));
|
|
append(new String("match"));
|
|
append(new String("toLowerCase"));
|
|
append(new String("toUpperCase"));
|
|
append(new String("trim"));
|
|
append(new String("sqlEscape"));
|
|
append(new String("startsWith"));
|
|
append(new String("endsWith"));
|
|
append(new String("split"));
|
|
append(new String("toString"));
|
|
append(new String("isNaN"));
|
|
append(new String("parseInt"));
|
|
append(new String("hashCode"));
|
|
}
|
|
};
|
|
|
|
#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),
|
|
MAKEOP("delete", Delete),
|
|
{ 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),
|
|
MAKEOP("#pragma", Pragma),
|
|
{ 0, 0 }
|
|
};
|
|
#undef MAKEOP
|
|
|
|
#define MAKEOP(o) { "[" #o "]", JsCode::Opc ## o }
|
|
static const TokenDict s_internals[] =
|
|
{
|
|
MAKEOP(Field),
|
|
MAKEOP(Func),
|
|
MAKEOP(Push),
|
|
MAKEOP(Label),
|
|
MAKEOP(Begin),
|
|
MAKEOP(End),
|
|
MAKEOP(Flush),
|
|
MAKEOP(Jump),
|
|
MAKEOP(JumpTrue),
|
|
MAKEOP(JumpFalse),
|
|
MAKEOP(JRel),
|
|
MAKEOP(JRelTrue),
|
|
MAKEOP(JRelFalse),
|
|
{ 0, 0 }
|
|
};
|
|
#undef MAKEOP
|
|
|
|
static const ExpNull s_null;
|
|
static const String s_noFile = "[no file]";
|
|
static const NativeFields s_nativeFields;
|
|
|
|
void JsContext::destroyed()
|
|
{
|
|
params().clearParams();
|
|
TelEngine::destruct(m_trackObjs);
|
|
setMutex(0);
|
|
JsObject::destroyed();
|
|
}
|
|
|
|
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->toString() == YSTRING("()") && 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);
|
|
name.clear();
|
|
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);
|
|
name.append(*s,".");
|
|
if (!l2)
|
|
break;
|
|
ExpExtender* ext = YOBJECT(ExpExtender,obj);
|
|
if (ext) {
|
|
GenObject* adv = ext->getField(stack,name,context);
|
|
XDebug(DebugAll,"JsContext::resolve advanced to '%s' of %p for '%s'",
|
|
(adv ? adv->toString().c_str() : 0),ext,s->c_str());
|
|
if (adv) {
|
|
if (YOBJECT(ExpExtender,adv)) {
|
|
obj = adv;
|
|
name.clear();
|
|
}
|
|
else if (l->count() == 2) { // there is only one other field after this one
|
|
s = static_cast<const String*>(l2->get());
|
|
if (!TelEngine::null(s) && s_nativeFields.find(*s)) {
|
|
obj = adv;
|
|
name.clear();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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' line=0x%08x [%p]",oper.name().c_str(),oper.lineNumber(),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")) {
|
|
int64_t val = ExpOperation::nonInteger();
|
|
ObjList args;
|
|
extractArgs(stack,oper,context,args);
|
|
ExpOperation* op1 = static_cast<ExpOperation*>(args[0]);
|
|
if (op1) {
|
|
int base = 0;
|
|
ExpOperation* op2 = static_cast<ExpOperation*>(args[1]);
|
|
if (op2) {
|
|
base = (int)op2->valInteger();
|
|
if (base < 2 || base > 36)
|
|
base = 0;
|
|
}
|
|
val = op1->trimSpaces().toInt64(val,base);
|
|
}
|
|
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 = (int)op->number();
|
|
}
|
|
ExpEvaluator::pushOne(stack,new ExpOperation(String(str->at(idx))));
|
|
return true;
|
|
}
|
|
if (name == YSTRING("charCodeAt")) {
|
|
int idx = 0;
|
|
ObjList args;
|
|
if (extractArgs(stack,oper,context,args)) {
|
|
ExpOperation* op = static_cast<ExpOperation*>(args[0]);
|
|
if (op && op->isInteger())
|
|
idx = (int)op->number();
|
|
}
|
|
ExpEvaluator::pushOne(stack,new ExpOperation((int64_t)(uint8_t)str->at(idx)));
|
|
return true;
|
|
}
|
|
if ((name == YSTRING("indexOf")) || (name == YSTRING("includes"))) {
|
|
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()) ? (int)from->number() : 0;
|
|
if (offs < 0)
|
|
offs = 0;
|
|
idx = str->find(*what,offs);
|
|
}
|
|
}
|
|
if (name.length() == 7)
|
|
ExpEvaluator::pushOne(stack,new ExpOperation((int64_t)idx));
|
|
else
|
|
ExpEvaluator::pushOne(stack,new ExpOperation(idx >= 0));
|
|
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 = (int)op->number();
|
|
op = static_cast<ExpOperation*>(args[1]);
|
|
if (op && op->isInteger()) {
|
|
len = (int)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(context,oper.lineNumber(),mutex());
|
|
for (int i = 0; i <= buf.matchCount(); i++)
|
|
jsa->push(new ExpOperation(buf.matchString(i)));
|
|
jsa->params().addParam(new ExpOperation((int64_t)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;
|
|
}
|
|
#define NO_PARAM_STRING_METHOD(method) \
|
|
{ \
|
|
ObjList args; \
|
|
extractArgs(stack,oper,context,args); \
|
|
String s(*str); \
|
|
ExpEvaluator::pushOne(stack,new ExpOperation(s.method())); \
|
|
}
|
|
|
|
if (name == YSTRING("toLowerCase")) {
|
|
NO_PARAM_STRING_METHOD(toLower);
|
|
return true;
|
|
}
|
|
if (name == YSTRING("toUpperCase")) {
|
|
NO_PARAM_STRING_METHOD(toUpper);
|
|
return true;
|
|
}
|
|
if (name == YSTRING("trim")) {
|
|
NO_PARAM_STRING_METHOD(trimSpaces);
|
|
return true;
|
|
}
|
|
if (name == YSTRING("sqlEscape")) {
|
|
NO_PARAM_STRING_METHOD(sqlEscape);
|
|
return true;
|
|
}
|
|
#undef NO_PARAM_STRING_METHOD
|
|
|
|
#define MAKE_WITH_METHOD \
|
|
ObjList args; \
|
|
const char* what = 0; \
|
|
int pos = 0; \
|
|
if (extractArgs(stack,oper,context,args)) { \
|
|
if (args.skipNull()) { \
|
|
String* tmp = static_cast<String*>(args.skipNull()->get()); \
|
|
if (tmp) \
|
|
what = tmp->c_str(); \
|
|
} \
|
|
if (args.count() >= 2) { \
|
|
String* tmp = static_cast<String*>(args[1]); \
|
|
if (tmp) \
|
|
pos = tmp->toInteger(0); \
|
|
} \
|
|
} \
|
|
String s(*str);
|
|
|
|
if (name == YSTRING("startsWith")) {
|
|
MAKE_WITH_METHOD;
|
|
if (pos > 0)
|
|
s = s.substr(pos);
|
|
ExpEvaluator::pushOne(stack,new ExpOperation(s.startsWith(what)));
|
|
return true;
|
|
}
|
|
if (name == YSTRING("endsWith")) {
|
|
MAKE_WITH_METHOD;
|
|
if (pos > 0)
|
|
s = s.substr(0,pos);
|
|
ExpEvaluator::pushOne(stack,new ExpOperation(s.endsWith(what)));
|
|
return true;
|
|
}
|
|
#undef MAKE_WITH_METHOD
|
|
#define SPLIT_EMPTY() do { \
|
|
array->push(new ExpOperation(*str)); \
|
|
ExpEvaluator::pushOne(stack,new ExpWrapper(array,0)); \
|
|
return true; \
|
|
} while (false);
|
|
if (name == YSTRING("split")) {
|
|
ObjList args;
|
|
JsArray* array = new JsArray(context,oper.lineNumber(),mutex());
|
|
if (!(extractArgs(stack,oper,context,args) && args.skipNull()))
|
|
SPLIT_EMPTY();
|
|
GenObject* gen = args[0];
|
|
if (!gen)
|
|
SPLIT_EMPTY();
|
|
JsRegExp* jsReg = YOBJECT(JsRegExp,gen);
|
|
ObjList* splits = jsReg ? str->split(jsReg->regexp()) :
|
|
str->split(static_cast<String*>(gen)->at(0));
|
|
unsigned int limit = 0;
|
|
if (args.count() >= 2) {
|
|
String* l = static_cast<String*>(args[1]);
|
|
if (l)
|
|
limit = l->toInteger(0xffffffff);
|
|
}
|
|
if (!limit)
|
|
limit = 0xffffffff;
|
|
for (ObjList* o = splits->skipNull(); o && limit; o = o->skipNext(), limit--)
|
|
array->push(new ExpOperation(*static_cast<String*>(o->get())));
|
|
ExpEvaluator::pushOne(stack,new ExpWrapper(array,0));
|
|
TelEngine::destruct(splits);
|
|
return true;
|
|
}
|
|
#undef SPLIT_EMPTY
|
|
if (name == YSTRING("toString")) {
|
|
ObjList args;
|
|
extractArgs(stack,oper,context,args);
|
|
ExpOperation* op = YOBJECT(ExpOperation,str);
|
|
if (op && op->isInteger()) {
|
|
if (op->isBoolean()) {
|
|
ExpEvaluator::pushOne(stack,new ExpOperation(String::boolText(op->valBoolean())));
|
|
return true;
|
|
}
|
|
ExpOperation* tmp = static_cast<ExpOperation*>(args[0]);
|
|
int radix = tmp ? (int)tmp->valInteger() : 0;
|
|
if (radix < 2 || radix > 36)
|
|
radix = 10;
|
|
static const char s_base[] = "0123456789abcdefghijklmnopqrstuvwxyz";
|
|
int64_t n = op->valInteger();
|
|
bool neg = false;
|
|
if (n < 0) {
|
|
n = -n;
|
|
neg = true;
|
|
}
|
|
String s;
|
|
char buf[2];
|
|
buf[1] = '\0';
|
|
do {
|
|
buf[0] = s_base[n % radix];
|
|
s = buf + s;
|
|
} while ((n = n / radix));
|
|
tmp = static_cast<ExpOperation*>(args[1]);
|
|
int len = tmp ? (int)tmp->valInteger() : 0;
|
|
if (len > 1) {
|
|
if (neg)
|
|
len--;
|
|
while (len > (int)s.length())
|
|
s = "0" + s;
|
|
}
|
|
if (neg)
|
|
s = "-" + s;
|
|
ExpEvaluator::pushOne(stack,new ExpOperation(s));
|
|
return true;
|
|
}
|
|
ExpEvaluator::pushOne(stack,new ExpOperation(*str));
|
|
return true;
|
|
}
|
|
if (name == YSTRING("hashCode")) {
|
|
ExpEvaluator::pushOne(stack,new ExpOperation((int64_t)str->hash()));
|
|
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")) {
|
|
int64_t len = s->lenUtf8();
|
|
if (len < 0)
|
|
len = s->length();
|
|
ExpEvaluator::pushOne(stack,new ExpOperation(len));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool JsContext::runAssign(ObjList& stack, const ExpOperation& oper, GenObject* context)
|
|
{
|
|
XDebug(DebugAll,"JsContext::runAssign '%s'='%s' (%s) [%p]",
|
|
oper.name().c_str(),oper.c_str(),oper.typeOf(),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);
|
|
}
|
|
|
|
void JsContext::createdObj(GenObject* obj)
|
|
{
|
|
if (!m_trackObjs)
|
|
return;
|
|
JsObject* jso = YOBJECT(JsObject,obj);
|
|
if (!(jso && jso != static_cast<JsObject*>(this)))
|
|
return;
|
|
XDebug(DebugInfo,"Adding object=%p created at line=%u(0x%08x)",jso,jso->lineNo(),jso->lineNo());
|
|
Lock l(m_trackObjsMtx);
|
|
ObjList* o = m_trackObjs->append(jso,hashPtr(obj));
|
|
if (o)
|
|
o->setDelete(false);
|
|
}
|
|
|
|
void JsContext::deletedObj(GenObject* obj)
|
|
{
|
|
if (!m_trackObjs)
|
|
return;
|
|
JsObject* jso = YOBJECT(JsObject,obj);
|
|
if (!jso)
|
|
return;
|
|
XDebug(DebugInfo,"Removing object=%p created at line=%u(0x%08x)",jso,jso->lineNo(),jso->lineNo());
|
|
Lock l(m_trackObjsMtx);
|
|
m_trackObjs->remove(jso,hashPtr(obj),false);
|
|
}
|
|
|
|
void JsContext::trackObjs(unsigned int track)
|
|
{
|
|
// once tracking is set per script, it cannot be reset
|
|
if (!track || m_trackObjs)
|
|
return;
|
|
m_trackObjs = new HashList(track);
|
|
m_objTrack = true;
|
|
}
|
|
|
|
ObjList* JsContext::countAllocations()
|
|
{
|
|
if (!m_trackObjs)
|
|
return 0;
|
|
ObjList* counters = new ObjList();
|
|
for (unsigned int i = 0; i < m_trackObjs->length(); i++) {
|
|
m_trackObjsMtx.lock();
|
|
ObjList* l = m_trackObjs->getList(i);
|
|
if (!l) {
|
|
m_trackObjsMtx.unlock();
|
|
continue;
|
|
}
|
|
for (ObjList* n = l->skipNull(); n; n = n->skipNext()) {
|
|
JsObject* jso = YOBJECT(JsObject,n->get());
|
|
if (!jso)
|
|
continue;
|
|
String id(jso->lineNo());
|
|
NamedCounter* count = static_cast<NamedCounter*>(counters->operator[](id));
|
|
if (!count)
|
|
counters->insert(count = new NamedCounter(id));
|
|
count->inc();
|
|
}
|
|
m_trackObjsMtx.unlock();
|
|
}
|
|
if (!counters->skipNull()) {
|
|
TelEngine::destruct(counters);
|
|
return 0;
|
|
}
|
|
return counters;
|
|
}
|
|
|
|
JsCode::~JsCode()
|
|
{
|
|
delete[] m_entries;
|
|
}
|
|
|
|
// 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()))
|
|
continue;
|
|
const JsFunction* jf = YOBJECT(JsFunction,op);
|
|
if (jf) {
|
|
JsObject* nf = jf->copy(context->mutex(),jf->getFunc()->name(),*op);
|
|
context->params().setParam(new ExpWrapper(nf,op->name(),op->barrier()));
|
|
}
|
|
else
|
|
context->params().setParam(op->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.skipNull())
|
|
return false;
|
|
m_linked.assign(m_opcodes);
|
|
delete[] m_entries;
|
|
m_entries = 0;
|
|
unsigned int n = m_linked.count();
|
|
if (!n)
|
|
return false;
|
|
unsigned int entries = 0;
|
|
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 = (long int)l->number();
|
|
if (lbl >= 0 && l->barrier())
|
|
entries++;
|
|
for (unsigned int j = 0; j < n; j++) {
|
|
const ExpOperation* jmp = static_cast<const ExpOperation*>(m_linked[j]);
|
|
if (!jmp || jmp->number() != lbl)
|
|
continue;
|
|
Opcode op = OpcNone;
|
|
switch ((int)jmp->opcode()) {
|
|
case OpcJump:
|
|
op = (Opcode)OpcJRel;
|
|
break;
|
|
case OpcJumpTrue:
|
|
op = (Opcode)OpcJRelTrue;
|
|
break;
|
|
case OpcJumpFalse:
|
|
op = (Opcode)OpcJRelFalse;
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
long int offs = (long int)i - j;
|
|
ExpOperation* newJump = new ExpOperation(op,0,offs,jmp->barrier());
|
|
newJump->lineNumber(jmp->lineNumber());
|
|
m_linked.set(newJump,j);
|
|
}
|
|
}
|
|
if (entries) {
|
|
m_entries = new JsEntry[entries+1];
|
|
unsigned int e = 0;
|
|
for (unsigned int j = 0; j < n; j++) {
|
|
const ExpOperation* l = static_cast<const ExpOperation*>(m_linked[j]);
|
|
if (l && l->barrier() && l->opcode() == OpcLabel && l->number() >= 0) {
|
|
m_entries[e].number = (long int)l->number();
|
|
m_entries[e++].index = j;
|
|
}
|
|
}
|
|
m_entries[entries].number = -1;
|
|
m_entries[entries].index = 0;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const String& JsCode::getFileAt(unsigned int index, bool wholePath) const
|
|
{
|
|
if (!index)
|
|
return s_noFile;
|
|
const JsCodeFile* file = static_cast<JsCodeFile*>(m_included[index - 1]);
|
|
return file ? (wholePath ? file->toString() : file->shortName()) : s_noFile;
|
|
}
|
|
|
|
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(ParsePoint& 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,lineNumber(),str,insensitive,extended);
|
|
addOpcode(new ExpWrapper(ExpEvaluator::OpcCopy,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;
|
|
}
|
|
switch (*expr) {
|
|
case 'u':
|
|
{
|
|
DataBlock bin;
|
|
if (bin.unHexify(expr + 1,4,'\0')) {
|
|
str = UChar((bin.at(0) << 8) + bin.at(1));
|
|
expr += 5;
|
|
return true;
|
|
}
|
|
}
|
|
break;
|
|
case 'x':
|
|
{
|
|
DataBlock bin;
|
|
if (bin.unHexify(expr + 1,2,'\0')) {
|
|
str = UChar(bin.at(0));
|
|
expr += 3;
|
|
return true;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return ExpEvaluator::getEscape(expr,str,sep);
|
|
}
|
|
|
|
bool JsCode::keywordLetter(char c) const
|
|
{
|
|
return ExpEvaluator::keywordLetter(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 (!len && keywordDigit(c))
|
|
return 0;
|
|
if (keywordChar(c))
|
|
continue;
|
|
if (len && (c == '.')) {
|
|
if (!keywordLetter(s[0]))
|
|
return 0;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
if (len > 1 && (s[-2] == '.'))
|
|
len--;
|
|
if (len && ExpEvaluator::getOperator(str,s_instr) != OpcNone)
|
|
return 0;
|
|
return len;
|
|
}
|
|
|
|
char JsCode::skipComments(ParsePoint& 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;
|
|
}
|
|
|
|
void JsCode::setBaseFile(const String& file)
|
|
{
|
|
if (file.null() || m_depth || m_included.find(file))
|
|
return;
|
|
m_included.append(new JsCodeFile(file));
|
|
int idx = m_included.index(file);
|
|
m_lineNo = ((idx + 1) << 24) | 1;
|
|
}
|
|
|
|
bool JsCode::scriptChanged() const
|
|
{
|
|
for (const ObjList* l = m_included.skipNull(); l; l = l->skipNext())
|
|
if (static_cast<const JsCodeFile*>(l->get())->fileChanged())
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool JsCode::preProcessInclude(ParsePoint& 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,true);
|
|
str.trimSpaces();
|
|
bool ok = !str.null();
|
|
if (ok) {
|
|
int idx = m_included.index(str);
|
|
if (!(once && (idx >= 0))) {
|
|
if (idx < 0) {
|
|
String* s = new JsCodeFile(str);
|
|
m_included.append(s);
|
|
idx = m_included.index(s);
|
|
}
|
|
// use the upper bits of line # for file index
|
|
unsigned int savedLine = expr.m_lineNo;
|
|
expr.m_lineNo = m_lineNo = ((idx + 1) << 24) | 1;
|
|
m_depth++;
|
|
ok = parser->parseFile(str,true);
|
|
m_depth--;
|
|
expr.m_lineNo = m_lineNo = savedLine;
|
|
}
|
|
}
|
|
return ok || gotError("Failed to include " + str);
|
|
}
|
|
return false;
|
|
}
|
|
return gotError("Expecting include file",expr);
|
|
}
|
|
|
|
bool JsCode::preProcessPragma(ParsePoint& expr, GenObject* context)
|
|
{
|
|
skipComments(expr);
|
|
int len = ExpEvaluator::getKeyword(expr);
|
|
if (len <= 0)
|
|
return gotError("Expecting pragma code",expr);
|
|
ParsePoint str = expr;
|
|
str += len;
|
|
char c = skipComments(str);
|
|
if (c == '"' || c == '\'') {
|
|
String val;
|
|
if (ExpEvaluator::getString(str,val)) {
|
|
m_pragmas.setParam(String(expr,len),val);
|
|
expr = str;
|
|
return true;
|
|
}
|
|
return gotError("Expecting pragma value",expr);
|
|
}
|
|
return gotError("Expecting pragma string",expr);
|
|
}
|
|
|
|
int JsCode::preProcess(ParsePoint& 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;
|
|
case OpcPragma:
|
|
if (!preProcessPragma(expr,context))
|
|
return -1;
|
|
break;
|
|
default:
|
|
return rval;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool JsCode::getOneInstruction(ParsePoint& expr, GenObject* nested)
|
|
{
|
|
if (inError())
|
|
return false;
|
|
XDebug(this,DebugAll,"JsCode::getOneInstruction %p '%.30s'",nested,(const char*)expr);
|
|
const char* savedSeps = expr.m_searchedSeps;
|
|
unsigned int count = expr.m_count;
|
|
if (skipComments(expr) == '{') {
|
|
expr.m_searchedSeps = "}";
|
|
if (!getInstruction(expr,0,nested))
|
|
return false;
|
|
}
|
|
else {
|
|
expr.m_searchedSeps = ";}";
|
|
expr.m_count = 0;
|
|
if (!runCompile(expr,";}",nested))
|
|
return false;
|
|
if (skipComments(expr) == ';') {
|
|
expr.m_foundSep = ';';
|
|
expr++;
|
|
}
|
|
}
|
|
expr.m_searchedSeps = savedSeps;
|
|
if (!expr.m_searchedSeps || expr.m_count)
|
|
expr.m_foundSep = 0;
|
|
expr.m_count = count;
|
|
return true;
|
|
}
|
|
|
|
bool JsCode::getInstruction(ParsePoint& expr, char stop, GenObject* nested)
|
|
{
|
|
if (inError())
|
|
return false;
|
|
XDebug(this,DebugAll,"JsCode::getInstruction %p '%.1s' 'separators=%s' 'count=%u' '%.30s'",nested,&stop,expr.m_searchedSeps,
|
|
expr.m_count,(const char*)expr);
|
|
if (skipComments(expr) == '{') {
|
|
if (stop == ')')
|
|
return false;
|
|
expr++;
|
|
expr.m_count++;
|
|
for (;;) {
|
|
if (!runCompile(expr,'}',nested))
|
|
return false;
|
|
bool sep = false;
|
|
while (skipComments(expr) && getSeparator(expr,true))
|
|
sep = true;
|
|
if (*expr.m_expr == '}' || !sep)
|
|
break;
|
|
}
|
|
if (*expr != '}')
|
|
return gotError("Expecting '}'",expr);
|
|
expr++;
|
|
expr.m_foundSep = '}';
|
|
if (expr.m_count > 0)
|
|
expr.m_count--;
|
|
return true;
|
|
}
|
|
else if (*expr == ';') {
|
|
expr++;
|
|
expr.m_foundSep = ';';
|
|
return true;
|
|
}
|
|
expr.m_foundSep = 0;
|
|
ParsePoint saved = expr;
|
|
Opcode op = ExpEvaluator::getOperator(expr,s_instr);
|
|
switch ((int)op) {
|
|
case OpcNone:
|
|
return false;
|
|
case OpcThrow:
|
|
if (!runCompile(expr))
|
|
return false;
|
|
addOpcode(op);
|
|
break;
|
|
case OpcReturn:
|
|
{
|
|
int64_t pop = ExpOperation::nonInteger();
|
|
switch (skipComments(expr)) {
|
|
case ';':
|
|
case '}':
|
|
break;
|
|
case '{':
|
|
{
|
|
saved = expr;
|
|
JsObject* jso = parseObject(expr,false,0);
|
|
if (!jso)
|
|
return gotError("Expecting valid object",saved);
|
|
if (skipComments(expr) != ';') {
|
|
TelEngine::destruct(jso);
|
|
return gotError("Expecting ';'",expr);
|
|
}
|
|
addOpcode(new ExpWrapper(ExpEvaluator::OpcCopy,jso));
|
|
pop = 1;
|
|
}
|
|
break;
|
|
default:
|
|
if (!runCompile(expr,';'))
|
|
return false;
|
|
pop = 1;
|
|
if ((skipComments(expr) != ';') && (*expr != '}'))
|
|
return gotError("Expecting ';' or '}'",expr);
|
|
}
|
|
addOpcode(op,pop);
|
|
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)) {
|
|
expr.m_lineNo = saved.m_lineNo;
|
|
return gotError("case not inside switch",saved);
|
|
}
|
|
if (skipComments(expr) != ':')
|
|
return gotError("Expecting ':'",expr);
|
|
expr++;
|
|
break;
|
|
case OpcDefault:
|
|
if (!ParseNested::parseInner(nested,OpcDefault,expr)) {
|
|
expr.m_lineNo = saved.m_lineNo;
|
|
return gotError("Unexpected default instruction",saved);
|
|
}
|
|
if (skipComments(expr) != ':')
|
|
return gotError("Expecting ':'",expr);
|
|
expr++;
|
|
break;
|
|
case OpcBreak:
|
|
if (!ParseNested::parseInner(nested,OpcBreak,expr)) {
|
|
expr.m_lineNo = saved.m_lineNo;
|
|
return gotError("Unexpected break instruction",saved);
|
|
}
|
|
if (skipComments(expr) != ';')
|
|
return gotError("Expecting ';'",expr);
|
|
break;
|
|
case OpcCont:
|
|
if (!ParseNested::parseInner(nested,OpcCont,expr)) {
|
|
expr.m_lineNo = saved.m_lineNo;
|
|
return gotError("Unexpected continue instruction",saved);
|
|
}
|
|
if (skipComments(expr) != ';')
|
|
return gotError("Expecting ';'",expr);
|
|
break;
|
|
case OpcVar:
|
|
return parseVar(expr);
|
|
case OpcTry:
|
|
return parseTry(expr,nested);
|
|
case OpcFuncDef:
|
|
return parseFuncDef(expr,!nested);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
class ParseLoop : public ParseNested
|
|
{
|
|
friend class JsCode;
|
|
public:
|
|
inline ParseLoop(JsCode* code, GenObject* nested, JsCode::JsOpcode oper, int64_t lblCont, int64_t 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:
|
|
int64_t m_lblCont;
|
|
int64_t 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:
|
|
int64_t m_lblBreak;
|
|
int64_t m_lblDefault;
|
|
SwitchState m_state;
|
|
ObjList m_cases;
|
|
};
|
|
|
|
// Parse keywords inner to specific instructions
|
|
bool JsCode::parseInner(ParsePoint& 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'",(const char*)expr);
|
|
addOpcode((Opcode)OpcJump,block->m_lblBreak);
|
|
break;
|
|
case OpcCont:
|
|
XDebug(this,DebugAll,"Parsing loop:continue '%.30s'",(const char*)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'",(const char*)expr);
|
|
block->m_state = ParseSwitch::InCase;
|
|
block->m_cases.append(popOpcode());
|
|
addOpcode(OpcLabel,(int64_t)++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'",(const char*)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'",(const char*)expr);
|
|
addOpcode((Opcode)OpcJump,static_cast<ParseSwitch*>(nested)->m_lblBreak);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool JsCode::parseIf(ParsePoint& expr, GenObject* nested)
|
|
{
|
|
XDebug(this,DebugAll,"JsCode::parseIf() '%.30s'",(const char*)expr);
|
|
if (skipComments(expr) != '(')
|
|
return gotError("Expecting '('",expr);
|
|
if (!runCompile(++expr,')'))
|
|
return false;
|
|
if (skipComments(expr) != ')')
|
|
return gotError("Expecting ')'",expr);
|
|
ExpOperation* cond = addOpcode((Opcode)OpcJumpFalse,(int64_t)++m_label);
|
|
expr++;
|
|
if (!getOneInstruction(expr,nested))
|
|
return false;
|
|
skipComments(expr);
|
|
ParsePoint save = expr;
|
|
if ((JsOpcode)ExpEvaluator::getOperator(expr,s_instr) == OpcElse) {
|
|
ExpOperation* jump = addOpcode((Opcode)OpcJump,(int64_t)++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(ParsePoint& 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++;
|
|
const char* savedSeps = expr.m_searchedSeps;
|
|
expr.m_searchedSeps = "";
|
|
ExpOperation* jump = addOpcode((Opcode)OpcJump,(int64_t)++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++;
|
|
expr.m_searchedSeps = savedSeps;
|
|
if (!expr.m_searchedSeps || expr.m_count)
|
|
expr.m_foundSep = 0;
|
|
// 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(ParsePoint& expr, GenObject* nested)
|
|
{
|
|
if (skipComments(expr) != '(')
|
|
return gotError("Expecting '('",expr);
|
|
addOpcode((Opcode)OpcBegin);
|
|
if ((skipComments(++expr) != ';') && !runCompile(expr,')'))
|
|
return false;
|
|
bool iter = false;
|
|
int64_t cont = 0;
|
|
int64_t jump = ++m_label;
|
|
int64_t body = ++m_label;
|
|
// parse initializer
|
|
if (skipComments(expr) == ';') {
|
|
int64_t 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 {
|
|
iter = true;
|
|
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 (!iter) {
|
|
// non-iterative version needs to avoid stack build-up
|
|
addOpcode((Opcode)OpcFlush);
|
|
addOpcode((Opcode)OpcBegin);
|
|
}
|
|
if (!getOneInstruction(++expr,parseStack))
|
|
return false;
|
|
addOpcode((Opcode)OpcJump,cont);
|
|
addOpcode(OpcLabel,jump);
|
|
addOpcode((Opcode)OpcFlush);
|
|
return true;
|
|
}
|
|
|
|
bool JsCode::parseWhile(ParsePoint& expr, GenObject* nested)
|
|
{
|
|
if (skipComments(expr) != '(')
|
|
return gotError("Expecting '('",expr);
|
|
addOpcode((Opcode)OpcBegin);
|
|
int64_t cont = ++m_label;
|
|
addOpcode(OpcLabel,cont);
|
|
if (!runCompile(++expr,')'))
|
|
return false;
|
|
if (skipComments(expr) != ')')
|
|
return gotError("Expecting ')'",expr);
|
|
int64_t jump = ++m_label;
|
|
addOpcode((Opcode)OpcJumpFalse,jump);
|
|
ParseLoop parseStack(this,nested,OpcWhile,cont,jump);
|
|
addOpcode((Opcode)OpcFlush);
|
|
addOpcode((Opcode)OpcBegin);
|
|
if (!getOneInstruction(++expr,parseStack))
|
|
return false;
|
|
addOpcode((Opcode)OpcJump,cont);
|
|
addOpcode(OpcLabel,jump);
|
|
addOpcode((Opcode)OpcFlush);
|
|
return true;
|
|
}
|
|
|
|
bool JsCode::parseVar(ParsePoint& expr)
|
|
{
|
|
if (inError())
|
|
return false;
|
|
XDebug(this,DebugAll,"parseVar '%.30s'",(const char*)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(ParsePoint& expr, GenObject* nested)
|
|
{
|
|
addOpcode((Opcode)OpcTry);
|
|
ParseNested parseStack(this,nested,OpcTry);
|
|
if (!runCompile(expr,(const char*)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(ParsePoint& expr, bool publish)
|
|
{
|
|
XDebug(this,DebugAll,"JsCode::parseFuncDef '%.30s'",(const char*)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,(int64_t)++m_label);
|
|
ExpOperation* lbl = addOpcode(OpcLabel,(int64_t)++m_label,true);
|
|
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++;
|
|
// Add the implicit "return undefined" at end of function
|
|
addOpcode((Opcode)OpcReturn);
|
|
addOpcode(OpcLabel,jump->number());
|
|
JsFunction* obj = new JsFunction(0,name,lineNumber(),&args,(long int)lbl->number(),this);
|
|
ExpWrapper* exp = new ExpWrapper(obj,name);
|
|
exp->lineNumber(lineNumber());
|
|
addOpcode(exp);
|
|
if (publish && name && obj->ref()) {
|
|
exp = new ExpWrapper(obj,name);
|
|
exp->lineNumber(lineNumber());
|
|
m_globals.append(exp);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
ExpEvaluator::Opcode JsCode::getOperator(ParsePoint& expr)
|
|
{
|
|
if (inError())
|
|
return OpcNone;
|
|
XDebug(this,DebugAll,"JsCode::getOperator line=0x%X '%.30s'",lineNumber(),(const char*)expr);
|
|
skipComments(expr);
|
|
Opcode op = ExpEvaluator::getOperator(expr,s_operators);
|
|
if (OpcNone != op)
|
|
return op;
|
|
return ExpEvaluator::getOperator(expr);
|
|
}
|
|
|
|
ExpEvaluator::Opcode JsCode::getUnaryOperator(ParsePoint& expr)
|
|
{
|
|
if (inError())
|
|
return OpcNone;
|
|
XDebug(this,DebugAll,"JsCode::getUnaryOperator line=0x%X '%.30s'",lineNumber(),(const char*)expr);
|
|
skipComments(expr);
|
|
Opcode op = ExpEvaluator::getOperator(expr,s_unaryOps);
|
|
if (OpcNone != op)
|
|
return op;
|
|
return ExpEvaluator::getUnaryOperator(expr);
|
|
}
|
|
|
|
ExpEvaluator::Opcode JsCode::getPostfixOperator(ParsePoint& expr, int precedence)
|
|
{
|
|
if (inError())
|
|
return OpcNone;
|
|
XDebug(this,DebugAll,"JsCode::getPostfixOperator line=0x%X '%.30s'",lineNumber(),(const char*)expr);
|
|
if (skipComments(expr) == '[') {
|
|
// The Indexing operator has maximum priority!
|
|
// No need to check it.
|
|
if (!runCompile(++expr,']'))
|
|
return OpcNone;
|
|
if (skipComments(expr) != ']') {
|
|
gotError("Expecting ']'",expr);
|
|
return OpcNone;
|
|
}
|
|
expr++;
|
|
return (Opcode)OpcIndex;
|
|
}
|
|
skipComments(expr);
|
|
ParsePoint save = expr;
|
|
Opcode op = ExpEvaluator::getOperator(expr,s_postfixOps);
|
|
if (OpcNone != op) {
|
|
if (getPrecedence(op) >= precedence)
|
|
return op;
|
|
expr = save;
|
|
return OpcNone;
|
|
}
|
|
return ExpEvaluator::getPostfixOperator(expr,precedence);
|
|
}
|
|
|
|
const char* JsCode::getOperator(Opcode oper) const
|
|
{
|
|
if ((int)oper == (int)OpcIndex)
|
|
return "[]";
|
|
const char* tmp = ExpEvaluator::getOperator(oper);
|
|
if (!tmp) {
|
|
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);
|
|
if (!tmp)
|
|
tmp = lookup(oper,s_internals);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return tmp;
|
|
}
|
|
|
|
int JsCode::getPrecedence(ExpEvaluator::Opcode oper) const
|
|
{
|
|
switch ((int)oper) {
|
|
case OpcEqIdentity:
|
|
case OpcNeIdentity:
|
|
return 110;
|
|
case OpcDelete:
|
|
case OpcNew:
|
|
case OpcTypeof:
|
|
return 170;
|
|
case OpcFieldOf:
|
|
case OpcIndex:
|
|
return 200;
|
|
default:
|
|
return ExpEvaluator::getPrecedence(oper);
|
|
}
|
|
}
|
|
|
|
bool JsCode::getSeparator(ParsePoint& expr, bool remove)
|
|
{
|
|
if (inError())
|
|
return false;
|
|
switch (skipComments(expr)) {
|
|
case ';':
|
|
expr.m_foundSep =';';
|
|
case ']':
|
|
if (remove)
|
|
expr++;
|
|
return true;
|
|
}
|
|
return ExpEvaluator::getSeparator(expr,remove);
|
|
}
|
|
|
|
bool JsCode::getSimple(ParsePoint& expr, bool constOnly)
|
|
{
|
|
return parseSimple(expr,constOnly);
|
|
}
|
|
|
|
bool JsCode::parseSimple(ParsePoint& expr, bool constOnly, ScriptMutex* mtx)
|
|
{
|
|
if (inError())
|
|
return false;
|
|
XDebug(this,DebugAll,"JsCode::parseSimple(%s) '%.30s'",String::boolText(constOnly),(const char*)expr);
|
|
skipComments(expr);
|
|
ParsePoint 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,mtx);
|
|
if (!jso)
|
|
jso = parseObject(expr,constOnly,mtx);
|
|
if (!jso)
|
|
return ExpEvaluator::getSimple(expr,constOnly);
|
|
addOpcode(new ExpWrapper(ExpEvaluator::OpcCopy,jso));
|
|
return true;
|
|
}
|
|
|
|
// Parse an inline Javascript Array: [ item1, item2, ... ]
|
|
JsObject* JsCode::parseArray(ParsePoint& expr, bool constOnly, ScriptMutex* mtx)
|
|
{
|
|
if (skipComments(expr) != '[')
|
|
return 0;
|
|
expr++;
|
|
JsArray* jsa = new JsArray(mtx,"[object Array]",lineNumber());
|
|
for (bool first = true; ; first = false) {
|
|
if (skipComments(expr) == ']') {
|
|
expr++;
|
|
break;
|
|
}
|
|
if (first) {
|
|
if (*expr == ',') {
|
|
ParsePoint next = expr;
|
|
next++;
|
|
// A construct like [,] creates an empty Array
|
|
if (skipComments(next) == ']') {
|
|
next++;
|
|
expr = next;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (*expr != ',') {
|
|
TelEngine::destruct(jsa);
|
|
break;
|
|
}
|
|
expr++;
|
|
}
|
|
// Swallow the single comma allowed after last item
|
|
if (skipComments(expr) == ']') {
|
|
expr++;
|
|
break;
|
|
}
|
|
// Successive commas insert an undefined between them
|
|
if (skipComments(expr) == ',') {
|
|
jsa->push(new ExpWrapper(0,"undefined"));
|
|
continue;
|
|
}
|
|
bool ok = constOnly ? parseSimple(expr,true,mtx) : getOperand(expr,false);
|
|
if (!ok) {
|
|
TelEngine::destruct(jsa);
|
|
break;
|
|
}
|
|
ExpOperation* oper = popOpcode();
|
|
if (oper && oper->opcode() == OpcField)
|
|
oper->assign(oper->name());
|
|
jsa->push(oper);
|
|
}
|
|
return jsa;
|
|
}
|
|
|
|
|
|
// Parse an inline Javascript Object: { prop1: value1, "prop 2": value2, ... }
|
|
JsObject* JsCode::parseObject(ParsePoint& expr, bool constOnly, ScriptMutex* mtx)
|
|
{
|
|
if (skipComments(expr) != '{')
|
|
return 0;
|
|
expr++;
|
|
JsObject* jso = new JsObject(mtx,"[object Object]",lineNumber());
|
|
for (bool first = true; ; first = false) {
|
|
if (skipComments(expr) == '}') {
|
|
expr++;
|
|
break;
|
|
}
|
|
if (!first) {
|
|
if (*expr != ',') {
|
|
TelEngine::destruct(jso);
|
|
break;
|
|
}
|
|
expr++;
|
|
// A single comma is allowed after last property
|
|
if (skipComments(expr) == '}') {
|
|
expr++;
|
|
break;
|
|
}
|
|
}
|
|
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 ? parseSimple(expr,true,mtx) : getOperand(expr,false);
|
|
if (!ok) {
|
|
TelEngine::destruct(jso);
|
|
break;
|
|
}
|
|
ExpOperation* op = popOpcode();
|
|
if (!op) {
|
|
TelEngine::destruct(jso);
|
|
break;
|
|
}
|
|
if (op->opcode() == OpcField)
|
|
op->assign(op->name());
|
|
const_cast<String&>(op->name()) = name;
|
|
jso->params().setParam(op);
|
|
}
|
|
return jso;
|
|
}
|
|
|
|
bool JsCode::runOperation(ObjList& stack, const ExpOperation& oper, GenObject* context) const
|
|
{
|
|
DDebug(this,DebugAll,"JsCode::runOperation(%p,%u,%p) %s line=0x%08x",&stack,oper.opcode(),context,
|
|
getOperator(oper.opcode()),oper.lineNumber());
|
|
JsRunner* sr = static_cast<JsRunner*>(context);
|
|
if (sr && sr->tracing())
|
|
sr->tracePrep(oper);
|
|
switch ((int)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) {
|
|
// try to obtain an object on which to run the field
|
|
ScriptContext* ctx = YOBJECT(ScriptContext,op1);
|
|
if (ctx && ctx->runField(stack,*op2,context)) {
|
|
TelEngine::destruct(op1);
|
|
TelEngine::destruct(op2);
|
|
break;
|
|
}
|
|
else {
|
|
// op1 is not an object, it's a string
|
|
JsContext* jsCtx = 0;
|
|
if (sr)
|
|
jsCtx = static_cast<JsContext*>(sr->context());
|
|
if (jsCtx && jsCtx->runStringField(op1,op2->name(),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());
|
|
pushOne(stack,new ExpOperation(op->typeOf()));
|
|
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);
|
|
op = 0;
|
|
break;
|
|
}
|
|
}
|
|
// fall through
|
|
default:
|
|
TelEngine::destruct(op);
|
|
return gotError("Expecting class name",oper.lineNumber());
|
|
}
|
|
if (!op)
|
|
break;
|
|
ExpFunction ctr(op->name(),(long int)op->number());
|
|
ctr.lineNumber(oper.lineNumber());
|
|
TelEngine::destruct(op);
|
|
if (!runOperation(stack,ctr,context))
|
|
return false;
|
|
}
|
|
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 = oper.valInteger() ? popValue(stack,context) : new ExpWrapper(0,"undefined");
|
|
ExpOperation* thisObj = 0;
|
|
bool ok = false;
|
|
while (ExpOperation* drop = popAny(stack)) {
|
|
ok = drop->barrier() && (drop->opcode() == OpcFunc);
|
|
long int lbl = (long int)drop->number();
|
|
if (ok && (lbl < -1)) {
|
|
lbl = -lbl;
|
|
XDebug(this,DebugInfo,"Returning this=%p from constructor '%s'",
|
|
thisObj,drop->name().c_str());
|
|
if (thisObj) {
|
|
TelEngine::destruct(op);
|
|
op = thisObj;
|
|
thisObj = 0;
|
|
}
|
|
}
|
|
if (drop->opcode() == OpcPush) {
|
|
ExpWrapper* wrap = YOBJECT(ExpWrapper,drop);
|
|
if (wrap && wrap->name() == YSTRING("()")) {
|
|
JsObject* jso = YOBJECT(JsObject,wrap->object());
|
|
if (jso) {
|
|
wrap = YOBJECT(ExpWrapper,jso->params().getParam(YSTRING("this")));
|
|
if (wrap) {
|
|
TelEngine::destruct(thisObj);
|
|
thisObj = wrap->clone(wrap->name());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
TelEngine::destruct(drop);
|
|
if (ok) {
|
|
ok = jumpAbsolute(lbl,context);
|
|
break;
|
|
}
|
|
}
|
|
TelEngine::destruct(thisObj);
|
|
if (!ok) {
|
|
TelEngine::destruct(op);
|
|
return gotError("Return outside function call",oper.lineNumber());
|
|
}
|
|
if (op)
|
|
pushOne(stack,op);
|
|
if (sr && sr->tracing())
|
|
sr->traceReturn();
|
|
}
|
|
break;
|
|
case OpcIn:
|
|
case OpcOf:
|
|
{
|
|
ExpOperation* obj = popOne(stack);
|
|
ExpOperation* fld = popOne(stack);
|
|
String name;
|
|
if (obj && obj->opcode() == OpcField) {
|
|
name = obj->name();
|
|
bool ok = runField(stack,*obj,context);
|
|
TelEngine::destruct(obj);
|
|
obj = ok ? popOne(stack) : 0;
|
|
}
|
|
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(name ? 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());
|
|
ExpOperation assign(OpcAssign);
|
|
assign.lineNumber(oper.lineNumber());
|
|
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,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);
|
|
TelEngine::destruct(expr);
|
|
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:
|
|
val = !val;
|
|
default:
|
|
break;
|
|
}
|
|
if (val)
|
|
break;
|
|
}
|
|
// fall through
|
|
case OpcJump:
|
|
case OpcJRel:
|
|
switch ((JsOpcode)oper.opcode()) {
|
|
case OpcJump:
|
|
case OpcJumpTrue:
|
|
case OpcJumpFalse:
|
|
if (!jumpToLabel((long int)oper.number(),context))
|
|
return gotError("Label not found",oper.lineNumber());
|
|
break;
|
|
case OpcJRel:
|
|
case OpcJRelTrue:
|
|
case OpcJRelFalse:
|
|
if (!jumpRelative((long int)oper.number(),context))
|
|
return gotError("Relative jump failed",oper.lineNumber());
|
|
break;
|
|
default:
|
|
return gotError("Internal error",oper.lineNumber());
|
|
}
|
|
break;
|
|
case OpcDelete:
|
|
{
|
|
ExpOperation* op = popOne(stack);
|
|
if (!op)
|
|
return gotError("Stack underflow",oper.lineNumber());
|
|
if (op->opcode() != OpcField) {
|
|
pushOne(stack,new ExpOperation(true));
|
|
TelEngine::destruct(op);
|
|
break;
|
|
}
|
|
JsObject* obj = 0;
|
|
String name = op->name();
|
|
TelEngine::destruct(op);
|
|
JsContext* ctx = YOBJECT(JsContext,sr->context());
|
|
if (ctx)
|
|
obj = YOBJECT(JsObject,ctx->resolve(stack,name,context));
|
|
bool ret = false;
|
|
if (obj && (!obj->frozen() || !obj->hasField(stack,name,context))
|
|
&& obj->toString() != YSTRING("()")) {
|
|
obj->clearField(name);
|
|
ret = true;
|
|
}
|
|
DDebug(DebugAll,"Deleted '%s' : %s",name.c_str(),String::boolText(ret));
|
|
pushOne(stack,new ExpOperation(ret));
|
|
}
|
|
break;
|
|
case OpcCopy:
|
|
if (!ExpEvaluator::runOperation(stack,oper,context))
|
|
return false;
|
|
resolveObjectParams(YOBJECT(JsObject,stack.get()),stack,context);
|
|
break;
|
|
default:
|
|
if (!ExpEvaluator::runOperation(stack,oper,context))
|
|
return false;
|
|
}
|
|
if (sr && sr->tracing())
|
|
sr->tracePost(oper);
|
|
return true;
|
|
}
|
|
|
|
void JsCode::resolveObjectParams(JsObject* object, ObjList& stack, GenObject* context, JsContext* ctxt, JsObject* objProto, JsArray* arrayProto) const
|
|
{
|
|
DDebug(this,DebugAll,"JsCode::resolveObjectParams(%p,%p,%p,%p,%p,%p)",
|
|
object,&stack,context,ctxt,objProto,arrayProto);
|
|
for (unsigned int i = 0;i < object->params().length();i++) {
|
|
String* param = object->params().getParam(i);
|
|
JsObject* tmpObj = YOBJECT(JsObject,param);
|
|
if (tmpObj) {
|
|
resolveObjectParams(tmpObj,stack,context,ctxt,objProto,arrayProto);
|
|
continue;
|
|
}
|
|
ExpOperation* op = YOBJECT(ExpOperation,param);
|
|
if (!op || op->opcode() != OpcField)
|
|
continue;
|
|
String name = *op;
|
|
JsObject* jsobj = YOBJECT(JsObject,ctxt->resolve(stack,name,context));
|
|
if (!jsobj) {
|
|
object->params().setParam(new ExpWrapper(0,op->name()));
|
|
continue;
|
|
}
|
|
NamedString* ns = jsobj->getField(stack,name,context);
|
|
if (!ns) {
|
|
object->params().setParam(new ExpWrapper(0,op->name()));
|
|
continue;
|
|
}
|
|
ExpOperation* objOper = YOBJECT(ExpOperation,ns);
|
|
NamedString* temp = 0;
|
|
if (objOper)
|
|
temp = objOper->clone(op->name());
|
|
else
|
|
temp = new NamedString(op->name(),*ns);
|
|
object->params().setParam(temp);
|
|
}
|
|
if (object->frozen() || object->params().getParam(JsObject::protoName()))
|
|
return;
|
|
JsArray* arr = YOBJECT(JsArray,object);
|
|
if (arr) {
|
|
if (arrayProto && arrayProto->ref())
|
|
object->params().addParam(new ExpWrapper(arrayProto,JsObject::protoName()));
|
|
}
|
|
else if (objProto && objProto->ref())
|
|
object->params().addParam(new ExpWrapper(objProto,JsObject::protoName()));
|
|
}
|
|
|
|
void JsCode::resolveObjectParams(JsObject* object, ObjList& stack, GenObject* context) const
|
|
{
|
|
if (!(object && context))
|
|
return;
|
|
ScriptRun* sr = static_cast<ScriptRun*>(context);
|
|
JsContext* ctx = YOBJECT(JsContext,sr->context());
|
|
if (!ctx)
|
|
return;
|
|
JsFunction* objCtr = 0;
|
|
// check first for regexp built /expr/ syntax
|
|
JsRegExp* reg = YOBJECT(JsRegExp,object);
|
|
if (reg) {
|
|
objCtr = YOBJECT(JsFunction,ctx->params().getParam(YSTRING("RegExp")));
|
|
if (objCtr) {
|
|
JsRegExp* regexpProto = YOBJECT(JsRegExp,objCtr->params().getParam(YSTRING("prototype")));
|
|
if (regexpProto && regexpProto->ref())
|
|
object->params().addParam(new ExpWrapper(regexpProto,JsObject::protoName()));
|
|
}
|
|
return;
|
|
}
|
|
|
|
JsObject* objProto = 0;
|
|
objCtr = YOBJECT(JsFunction,ctx->params().getParam(YSTRING("Object")));
|
|
if (objCtr)
|
|
objProto = YOBJECT(JsObject,objCtr->params().getParam(YSTRING("prototype")));
|
|
|
|
JsArray* arrayProto = 0;
|
|
objCtr = YOBJECT(JsFunction,ctx->params().getParam(YSTRING("Array")));
|
|
if (objCtr)
|
|
arrayProto = YOBJECT(JsArray,objCtr->params().getParam(YSTRING("prototype")));
|
|
|
|
resolveObjectParams(object,stack,context,ctx,objProto,arrayProto);
|
|
}
|
|
|
|
bool JsCode::runFunction(ObjList& stack, const ExpOperation& oper, GenObject* context) const
|
|
{
|
|
DDebug(this,DebugAll,"JsCode::runFunction(%p,'%s' " FMT64 ", %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);
|
|
if (m_opcodes.skipNull()) {
|
|
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->skipNext();
|
|
XDebug(this,DebugInfo,"Jumped to label %ld",label);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (m_entries) {
|
|
for (const JsEntry* e = m_entries; e->number >= 0; e++) {
|
|
if (e->number == label) {
|
|
runner->m_index = e->index;
|
|
XDebug(this,DebugInfo,"Fast jumped to index %u",e->index);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
unsigned int n = m_linked.length();
|
|
if (!n)
|
|
return false;
|
|
for (unsigned int i = 0; i < n; i++) {
|
|
const ExpOperation* o = static_cast<const ExpOperation*>(m_linked[i]);
|
|
if (o && o->opcode() == OpcLabel && o->number() == label) {
|
|
runner->m_index = i;
|
|
XDebug(this,DebugInfo,"Jumped to index %u",i);
|
|
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 > (long int)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, JsObject* thisObj) const
|
|
{
|
|
if (!(func && context))
|
|
return false;
|
|
XDebug(this,DebugInfo,"JsCode::callFunction(%p," FMT64 ",%p) in %s'%s' this=%p",
|
|
&stack,oper.number(),context,(constr ? "constructor " : ""),
|
|
func->toString().c_str(),thisObj);
|
|
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 = 0;
|
|
if (constr) {
|
|
index = -index;
|
|
op = popOne(stack);
|
|
if (op && !thisObj)
|
|
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
|
|
{
|
|
ExpOperation* ret = new ExpOperation(OpcFunc,oper.name(),retIndex,true);
|
|
ret->lineNumber(oper.lineNumber());
|
|
pushOne(stack,ret);
|
|
if (scopeObj)
|
|
pushOne(stack,new ExpWrapper(scopeObj,"()"));
|
|
JsObject* arguments = new JsObject("Arguments",func->mutex());
|
|
JsObject* ctxt = JsObject::buildCallContext(func->mutex(),thisObj);
|
|
int64_t cnt = 0;
|
|
for (unsigned int idx = 0; ; idx++) {
|
|
const String* name = func->formalName(idx);
|
|
if (!(name || arguments))
|
|
break;
|
|
ExpOperation* param = static_cast<ExpOperation*>(args.remove(false));
|
|
if (name) {
|
|
if (param)
|
|
ctxt->params().setParam(param->clone(*name));
|
|
else
|
|
ctxt->params().setParam(new ExpWrapper(0,*name));
|
|
// don't overwrite an explicit "arguments" argument
|
|
if (*name == YSTRING("arguments"))
|
|
TelEngine::destruct(arguments);
|
|
}
|
|
else if (!param)
|
|
break;
|
|
if (arguments && param)
|
|
arguments->params().setParam(param->clone(String(cnt++)));
|
|
TelEngine::destruct(param);
|
|
}
|
|
if (arguments) {
|
|
arguments->params().setParam(new ExpOperation(cnt,"length"));
|
|
arguments->params().setParam(new ExpWrapper(func,"callee"));
|
|
func->ref();
|
|
ctxt->params().setParam(new ExpWrapper(arguments,"arguments"));
|
|
}
|
|
pushOne(stack,new ExpWrapper(ctxt,ctxt->toString(),true));
|
|
if (!jumpToLabel(func->label(),context))
|
|
return false;
|
|
JsRunner* jsr = static_cast<JsRunner*>(context);
|
|
if (jsr && jsr->tracing())
|
|
jsr->traceCall(oper,*func);
|
|
return true;
|
|
}
|
|
|
|
ScriptRun* JsCode::createRunner(ScriptContext* context, const char* title)
|
|
{
|
|
if (!context)
|
|
return 0;
|
|
return new JsRunner(this,context,title);
|
|
}
|
|
|
|
bool JsCode::null() const
|
|
{
|
|
return m_linked.null() && !m_opcodes.skipNull();
|
|
}
|
|
|
|
void JsCode::dump(String& res, bool lineNo) const
|
|
{
|
|
if (m_linked.null())
|
|
return ExpEvaluator::dump(res,lineNo);
|
|
for (unsigned int i = 0; i < m_linked.length(); i++) {
|
|
const ExpOperation* o = static_cast<const ExpOperation*>(m_linked[i]);
|
|
if (!o)
|
|
continue;
|
|
if (res)
|
|
res << " ";
|
|
ExpEvaluator::dump(*o,res,lineNo);
|
|
}
|
|
}
|
|
|
|
|
|
ScriptRun::Status JsRunner::reset(bool init)
|
|
{
|
|
Status s = ScriptRun::reset(init);
|
|
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();
|
|
mylock.acquire(context()->mutex());
|
|
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) {
|
|
JsContext* ctx = YOBJECT(JsContext,context());
|
|
if (ctx)
|
|
func = YOBJECT(JsFunction,ctx->getField(stack(),name,this));
|
|
}
|
|
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(false);
|
|
// 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));
|
|
}
|
|
|
|
void JsRunner::traceStart(const char* title, const char* file)
|
|
{
|
|
if (m_tracing)
|
|
return;
|
|
m_tracing = true;
|
|
if (TelEngine::null(file) || !code())
|
|
return;
|
|
Debug(DebugInfo,"Preparing Javascript trace file '%s'",file);
|
|
JsCodeStats* stats = new JsCodeStats(static_cast<JsCode*>(code()),file);
|
|
traceStart(title,stats);
|
|
TelEngine::destruct(stats);
|
|
}
|
|
|
|
void JsRunner::traceStart(const char* title, JsCodeStats* stats)
|
|
{
|
|
m_stats = stats;
|
|
if (m_stats) {
|
|
m_tracing = true;
|
|
if (!m_callInfo) {
|
|
if (TelEngine::null(title))
|
|
title = "[main flow]";
|
|
m_stats->lock();
|
|
JsFuncStats* fs = m_stats->getFuncStats(title,0);
|
|
m_stats->unlock();
|
|
m_traceStack.insert(m_callInfo = new JsCallInfo(fs,title,0,0,0,0));
|
|
}
|
|
}
|
|
}
|
|
|
|
void JsRunner::traceDump()
|
|
{
|
|
if (!m_stats)
|
|
Debug(DebugNote,"Executed %u operations in " FMT64U " usec",m_instr,m_totalTime);
|
|
}
|
|
|
|
void JsRunner::traceCheck(const char* title)
|
|
{
|
|
if (!(code()))
|
|
return;
|
|
JsCode* c = static_cast<JsCode*>(code());
|
|
if (!c->traceable())
|
|
return;
|
|
static const String s_tracingPragma = "trace";
|
|
const NamedString* ns = c->pragmas().getParam(s_tracingPragma);
|
|
if (!(ns && ns->toBoolean(true)))
|
|
return;
|
|
if (ns->toBoolean(false) || !context()) {
|
|
traceStart(title);
|
|
return;
|
|
}
|
|
static const String s_tracingObj = "__trace__";
|
|
NamedString* obj = context()->params().getParam(s_tracingObj);
|
|
ExpWrapper* w = YOBJECT(ExpWrapper,obj);
|
|
if (w) {
|
|
JsCodeStats* stats = YOBJECT(JsCodeStats,w->object());
|
|
if (stats) {
|
|
DDebug(DebugInfo,"Using shared trace file '%s'",stats->toString().c_str());
|
|
traceStart(title,stats);
|
|
}
|
|
return;
|
|
}
|
|
else if (obj) {
|
|
traceStart(title);
|
|
return;
|
|
}
|
|
|
|
String filename = ns->c_str();
|
|
if (context()->instanceIndex())
|
|
filename << "_" << context()->instanceIndex();
|
|
traceStart(title,filename);
|
|
if (m_stats) {
|
|
m_stats->ref();
|
|
context()->params().setParam(new ExpWrapper(m_stats,s_tracingObj));
|
|
}
|
|
}
|
|
|
|
void JsRunner::tracePrep(const ExpOperation& oper)
|
|
{
|
|
if (!m_lastTime)
|
|
m_lastTime = Time::now();
|
|
m_lastLine = oper.lineNumber();
|
|
m_instr++;
|
|
}
|
|
|
|
void JsRunner::tracePost(const ExpOperation& oper)
|
|
{
|
|
u_int64_t time = Time::now();
|
|
u_int64_t diff = 0;
|
|
if (m_lastTime) {
|
|
diff = time - m_lastTime;
|
|
m_totalTime += diff;
|
|
}
|
|
m_lastTime = m_paused ? 0 : time;
|
|
|
|
if (diff && m_callInfo && m_stats) {
|
|
#ifdef STATS_TRACE
|
|
String line;
|
|
static_cast<const JsCode*>(code())->formatLineNo(line,m_lastLine);
|
|
Debug(STATS_TRACE,DebugNote,"Operation %u %s @ %s %s took " FMT64U " usec",
|
|
oper.opcode(),static_cast<const JsCode*>(code())->getOperator(oper.opcode()),
|
|
line.c_str(),m_callInfo->c_str(),diff);
|
|
#endif
|
|
m_stats->lock();
|
|
m_callInfo->traceLine(m_lastLine,diff);
|
|
m_stats->unlock();
|
|
}
|
|
}
|
|
|
|
void JsRunner::traceCall(const ExpOperation& oper, const JsFunction& func)
|
|
{
|
|
const ExpOperation* o = 0;
|
|
if (m_opcode)
|
|
o = static_cast<const ExpOperation*>(m_opcode->get());
|
|
if (!o)
|
|
o = static_cast<const ExpOperation*>(static_cast<const JsCode*>(code())->m_linked[m_index]);
|
|
if (!o) {
|
|
String str;
|
|
static_cast<const JsCode*>(code())->formatLineNo(str,m_lastLine);
|
|
Debug(DebugWarn,"Current operation unavailable in %s [%p]",str.c_str(),this);
|
|
return;
|
|
}
|
|
|
|
const String& name = func.getFunc()->name();
|
|
JsFuncStats* fs = 0;
|
|
if (m_stats) {
|
|
m_stats->lock();
|
|
if (m_lastTime) {
|
|
u_int64_t diff = Time::now() - m_lastTime;
|
|
m_totalTime += diff;
|
|
m_lastTime = 0;
|
|
if (m_callInfo)
|
|
m_callInfo->traceLine(m_lastLine,diff);
|
|
}
|
|
fs = m_stats->getFuncStats(name,o->lineNumber());
|
|
m_stats->unlock();
|
|
}
|
|
#ifdef STATS_TRACE
|
|
String caller,called;
|
|
static_cast<const JsCode*>(code())->formatLineNo(caller,m_lastLine);
|
|
static_cast<const JsCode*>(code())->formatLineNo(called,o->lineNumber());
|
|
Debug(STATS_TRACE,DebugCall,"Call %s %s -> %s, instr=%u",
|
|
name.c_str(),caller.c_str(),called.c_str(),m_instr);
|
|
#endif
|
|
m_traceStack.insert(m_callInfo = new JsCallInfo(fs,name,m_lastLine,o->lineNumber(),m_instr,m_totalTime));
|
|
}
|
|
|
|
void JsRunner::traceReturn()
|
|
{
|
|
JsCallInfo* info = static_cast<JsCallInfo*>(m_traceStack.remove(false));
|
|
if (!info) {
|
|
String str;
|
|
static_cast<const JsCode*>(code())->formatLineNo(str,m_lastLine);
|
|
Debug(DebugWarn,"Stats stack underflow in %s [%p]",str.c_str(),this);
|
|
return;
|
|
}
|
|
m_callInfo = static_cast<JsCallInfo*>(m_traceStack.get());
|
|
|
|
unsigned int instr = m_instr - info->startInstr;
|
|
u_int64_t time = Time::now();
|
|
u_int64_t timeInstr = 0;
|
|
if (m_lastTime) {
|
|
timeInstr = time - m_lastTime;
|
|
m_totalTime += timeInstr;
|
|
m_lastTime = 0;
|
|
}
|
|
u_int64_t timeCall = m_totalTime - info->startTime;
|
|
|
|
if (m_stats && timeInstr) {
|
|
m_stats->lock();
|
|
info->traceLine(m_lastLine,timeInstr);
|
|
m_stats->unlock();
|
|
}
|
|
if (m_callInfo && m_stats) {
|
|
#ifdef STATS_TRACE
|
|
String caller,called;
|
|
static_cast<const JsCode*>(code())->formatLineNo(caller,info->callerLine);
|
|
static_cast<const JsCode*>(code())->formatLineNo(called,info->calledLine);
|
|
Debug(STATS_TRACE,DebugCall,"Ret %s %s -> %s, %u oper / " FMT64U " usec",
|
|
info->c_str(),caller.c_str(),called.c_str(),instr,timeCall);
|
|
#endif
|
|
m_stats->lock();
|
|
m_callInfo->traceCall(info,instr,timeCall);
|
|
m_stats->unlock();
|
|
}
|
|
else {
|
|
String caller,called;
|
|
static_cast<const JsCode*>(code())->formatLineNo(caller,info->callerLine);
|
|
static_cast<const JsCode*>(code())->formatLineNo(called,info->calledLine);
|
|
Debug(DebugNote,"Function '%s' %s -> %s took %u operations / " FMT64U " usec",
|
|
info->c_str(),caller.c_str(),called.c_str(),instr,timeCall);
|
|
}
|
|
TelEngine::destruct(info);
|
|
}
|
|
|
|
|
|
void JsFuncStats::updateLine(unsigned int lineNo, u_int64_t usec)
|
|
{
|
|
if (!lineNumber)
|
|
lineNumber = lineNo;
|
|
#ifdef STATS_TRACE
|
|
Debug(STATS_TRACE,DebugAll,"Updating %u:%u in %s [%u:%u] with " FMT64U " usec",
|
|
JsCode::getFileNo(lineNo),JsCode::getLineNo(lineNo),c_str(),
|
|
JsCode::getFileNo(lineNumber),JsCode::getLineNo(lineNumber),usec);
|
|
#endif
|
|
ObjList* l = &funcLines;
|
|
while (l) {
|
|
JsLineStats* s = static_cast<JsLineStats*>(l->get());
|
|
if (s) {
|
|
if (s->lineNumber == lineNo && !s->isCall) {
|
|
s->operations++;
|
|
s->microseconds += usec;
|
|
return;
|
|
}
|
|
if (s->lineNumber > lineNo)
|
|
break;
|
|
}
|
|
ObjList* ln = l->next();
|
|
if (ln) {
|
|
l = ln;
|
|
continue;
|
|
}
|
|
l->append(new JsLineStats(lineNo,1,usec));
|
|
return;
|
|
}
|
|
l->insert(new JsLineStats(lineNo,1,usec));
|
|
}
|
|
|
|
void JsFuncStats::updateCall(const char* name, unsigned int caller, unsigned int called, unsigned int instr, u_int64_t usec)
|
|
{
|
|
ObjList* l = &funcLines;
|
|
while (l) {
|
|
JsLineStats* s = static_cast<JsLineStats*>(l->get());
|
|
if (s) {
|
|
if (s->lineNumber == caller && s->isCall) {
|
|
JsCallStats* cs = static_cast<JsCallStats*>(s);
|
|
if (cs->calledLine == called) {
|
|
cs->operations += instr;
|
|
cs->microseconds += usec;
|
|
cs->callsCount++;
|
|
return;
|
|
}
|
|
}
|
|
if (s->lineNumber > caller)
|
|
break;
|
|
}
|
|
ObjList* ln = l->next();
|
|
if (ln) {
|
|
l = ln;
|
|
continue;
|
|
}
|
|
#ifndef TRACE_RAW_NAME
|
|
String tmp(name);
|
|
if (called)
|
|
tmp << " [" << JsCode::getFileNo(called) << ":" << JsCode::getLineNo(called) << "]";
|
|
name = tmp;
|
|
#endif
|
|
l->append(new JsCallStats(name,caller,called,instr,usec));
|
|
return;
|
|
}
|
|
#ifndef TRACE_RAW_NAME
|
|
String tmp(name);
|
|
if (called)
|
|
tmp << " [" << JsCode::getFileNo(called) << ":" << JsCode::getLineNo(called) << "]";
|
|
name = tmp;
|
|
#endif
|
|
l->insert(new JsCallStats(name,caller,called,instr,usec));
|
|
}
|
|
|
|
const ExpOperation* JsRunner::getCurrentOpCode() const
|
|
{
|
|
const ExpOperation* o = 0;
|
|
if (m_opcode)
|
|
o = static_cast<const ExpOperation*>(m_opcode->get());
|
|
if (!o)
|
|
o = static_cast<const ExpOperation*>(static_cast<const JsCode*>(code())->m_linked[m_index]);
|
|
if (!o) {
|
|
String str;
|
|
static_cast<const JsCode*>(code())->formatLineNo(str,m_lastLine);
|
|
Debug(DebugWarn,"Current operation unavailable in %s [%p]",str.c_str(),this);
|
|
return 0;
|
|
}
|
|
return o;
|
|
}
|
|
|
|
unsigned int JsRunner::currentLineNo() const
|
|
{
|
|
const ExpOperation* o = getCurrentOpCode();
|
|
return o ? JsCode::getLineNo(o->lineNumber()) : 0;
|
|
}
|
|
|
|
const String& JsRunner::currentFileName(bool wholePath) const
|
|
{
|
|
static const String s_unk("???");
|
|
|
|
const ExpOperation* o = getCurrentOpCode();
|
|
if (!(o && code()))
|
|
return s_unk;
|
|
return (static_cast<const JsCode*>(code()))->getFileName(o->lineNumber(),wholePath);
|
|
}
|
|
|
|
JsCodeStats::JsCodeStats(JsCode* code, const char* file)
|
|
: Mutex(false,"JsCodeStats"),
|
|
m_fileName(file)
|
|
{
|
|
m_code = code;
|
|
if (!code)
|
|
return;
|
|
}
|
|
|
|
JsCodeStats::~JsCodeStats()
|
|
{
|
|
dump();
|
|
}
|
|
|
|
JsFuncStats* JsCodeStats::getFuncStats(const char* name, unsigned int lineNo)
|
|
{
|
|
String tmp(name);
|
|
#ifndef TRACE_RAW_NAME
|
|
if (lineNo)
|
|
tmp << " [" << JsCode::getFileNo(lineNo) << ":" << JsCode::getLineNo(lineNo) << "]";
|
|
#endif
|
|
ObjList* l = &m_funcStats;
|
|
while (l) {
|
|
JsFuncStats* s = static_cast<JsFuncStats*>(l->get());
|
|
if (s) {
|
|
if (s->lineNumber == lineNo && tmp == *s)
|
|
return s;
|
|
if (s->lineNumber > lineNo)
|
|
break;
|
|
}
|
|
ObjList* ln = l->next();
|
|
if (ln) {
|
|
l = ln;
|
|
continue;
|
|
}
|
|
s = new JsFuncStats(tmp,lineNo);
|
|
l->append(s);
|
|
return s;
|
|
}
|
|
JsFuncStats* s = new JsFuncStats(tmp,lineNo);
|
|
l->insert(s);
|
|
return s;
|
|
}
|
|
|
|
void JsCodeStats::dump()
|
|
{
|
|
dump(m_fileName);
|
|
m_fileName.clear();
|
|
}
|
|
|
|
void JsCodeStats::dump(const char* file)
|
|
{
|
|
File f;
|
|
if (file && m_code && f.openPath(file,true,false,true)) {
|
|
Debug(DebugInfo,"Writing trace file '%s'",file);
|
|
dump(f);
|
|
}
|
|
}
|
|
|
|
void JsCodeStats::dump(Stream& file)
|
|
{
|
|
if (!m_code)
|
|
return;
|
|
String fl,fn,cfn,cfl;
|
|
NamedList lMap(""), nMap("");
|
|
int ifl = 1, ifn = 1;
|
|
file.writeData("events: Operations Microseconds\n");
|
|
for (ObjList* f = m_funcStats.skipNull(); f; f = f->skipNext()) {
|
|
String str("\n");
|
|
const JsFuncStats* fs = static_cast<const JsFuncStats*>(f->get());
|
|
String tmp = m_code->getFileName(fs->lineNumber);
|
|
if (fl != tmp) {
|
|
fl = tmp;
|
|
tmp = lMap[fl];
|
|
if (tmp.null()) {
|
|
tmp << "(" << ifl++ << ")";
|
|
lMap.addParam(fl,tmp);
|
|
tmp << " " << fl;
|
|
}
|
|
str << "fl=" << tmp << "\n";
|
|
}
|
|
if (fn != *fs) {
|
|
fn = *fs;
|
|
tmp = nMap[fn];
|
|
if (tmp.null()) {
|
|
tmp << "(" << ifn++ << ")";
|
|
nMap.addParam(fn,tmp);
|
|
tmp << " " << fn;
|
|
}
|
|
str << "fn=" << tmp << "\n";
|
|
}
|
|
for (ObjList* l = fs->funcLines.skipNull(); l; l = l->skipNext()) {
|
|
const JsLineStats* ls = static_cast<const JsLineStats*>(l->get());
|
|
tmp = m_code->getFileName(ls->lineNumber);
|
|
if (fl != tmp) {
|
|
fl = tmp;
|
|
tmp = lMap[fl];
|
|
if (tmp.null()) {
|
|
tmp << "(" << ifl++ << ")";
|
|
lMap.addParam(fl,tmp);
|
|
tmp << " " << fl;
|
|
}
|
|
str << "fl=" << tmp << "\n";
|
|
}
|
|
if (ls->isCall) {
|
|
const JsCallStats* cs = static_cast<const JsCallStats*>(ls);
|
|
tmp = m_code->getFileName(cs->calledLine);
|
|
if (cfl != tmp) {
|
|
cfl = tmp;
|
|
tmp = lMap[cfl];
|
|
if (tmp.null()) {
|
|
tmp << "(" << ifl++ << ")";
|
|
lMap.addParam(cfl,tmp);
|
|
tmp << " " << cfl;
|
|
}
|
|
str << "cfl=" << tmp << "\n";
|
|
}
|
|
if (cfn != cs->funcName) {
|
|
cfn = cs->funcName;
|
|
tmp = nMap[cfn];
|
|
if (tmp.null()) {
|
|
tmp << "(" << ifn++ << ")";
|
|
nMap.addParam(cfn,tmp);
|
|
tmp << " " << cfn;
|
|
}
|
|
str << "cfn=" << tmp << "\n";
|
|
}
|
|
str << "calls=" << cs->callsCount << " "
|
|
<< JsCode::getLineNo(cs->calledLine) << "\n";
|
|
}
|
|
str << JsCode::getLineNo(ls->lineNumber) << " " << ls->operations << " "
|
|
<< ls->microseconds << "\n";
|
|
}
|
|
file.writeData(str);
|
|
}
|
|
}
|
|
|
|
}; // anonymous namespace
|
|
|
|
|
|
JsFunction::JsFunction(ScriptMutex* mtx)
|
|
: JsObject("Function",mtx,true),
|
|
m_label(0), m_code(0), m_func("")
|
|
{
|
|
init();
|
|
}
|
|
|
|
JsFunction::JsFunction(ScriptMutex* mtx, const char* name, unsigned int line, ObjList* args, long int lbl, ScriptCode* code)
|
|
: JsObject(mtx,String("[function ") + name + "()]",line,false),
|
|
m_label(lbl), m_code(code), m_func(name)
|
|
{
|
|
init();
|
|
if (args) {
|
|
while (GenObject* arg = args->remove(false))
|
|
m_formal.append(arg);
|
|
}
|
|
unsigned int argc = m_formal.count();
|
|
static_cast<ExpOperation&>(m_func) = argc;
|
|
if (name)
|
|
params().addParam("name",name);
|
|
params().addParam("length",String(argc));
|
|
}
|
|
|
|
JsObject* JsFunction::copy(ScriptMutex* mtx, const char* name, const ExpOperation& oper) const
|
|
{
|
|
ObjList args;
|
|
for (ObjList* l = m_formal.skipNull(); l; l = l->skipNext())
|
|
args.append(new String(l->get()->toString()));
|
|
return new JsFunction(mtx,name,oper.lineNumber(),&args,label(),m_code);
|
|
}
|
|
|
|
JsFunction* JsFunction::cloneFunction(const ExpOperation& oper, ScriptMutex* mtx)
|
|
{
|
|
XDebug(DebugInfo,"JsFunction::cloneFunction(mtx=%p) '%s'='%s' (%s) in '%s' [%p]",
|
|
mtx,oper.name().c_str(),oper.c_str(),oper.typeOf(),toString().c_str(),this);
|
|
if (mutex() && ref()) {
|
|
XDebug(DebugAll,"JsFunction::cloneFunction() Function '%s' already cloned [%p]",toString().c_str(),this);
|
|
return this;
|
|
}
|
|
return static_cast<JsFunction*>(copy(mtx,m_func.toString(),oper));
|
|
}
|
|
|
|
void JsFunction::init()
|
|
{
|
|
params().addParam(new ExpFunction("apply"));
|
|
params().addParam(new ExpFunction("call"));
|
|
}
|
|
|
|
void JsFunction::initConstructor(JsFunction* construct)
|
|
{
|
|
construct->params().addParam(new ExpFunction("context"));
|
|
construct->params().addParam(new ExpFunction("stack"));
|
|
}
|
|
|
|
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)
|
|
// func.apply(new_this,["array","of","params",...])
|
|
switch (oper.number()) {
|
|
case 1:
|
|
case 2:
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
ObjList args;
|
|
extractArgs(this,stack,oper,context,args);
|
|
JsObject* thisObj = YOBJECT(JsObject,args[0]);
|
|
JsArray* callArgs = YOBJECT(JsArray,args[1]);
|
|
int argc = 0;
|
|
if (callArgs) {
|
|
int32_t len = callArgs->length();
|
|
for (int32_t i = 0; i < len; i++)
|
|
if (callArgs->runField(stack,ExpOperation((int64_t)i,String(i)),context))
|
|
argc++;
|
|
}
|
|
ExpFunction func(toString(),argc);
|
|
return runDefined(stack,func,context,thisObj);
|
|
}
|
|
else if (oper.name() == YSTRING("call")) {
|
|
// func.call(new_this)
|
|
// func.call(new_this,param1,param2,...)
|
|
if (!oper.number())
|
|
return false;
|
|
ObjList args;
|
|
extractArgs(this,stack,oper,context,args);
|
|
JsObject* thisObj = YOBJECT(JsObject,args[0]);
|
|
int argc = 0;
|
|
ObjList* l = args.next();
|
|
if (l) {
|
|
while (ExpOperation* op = static_cast<ExpOperation*>(l->remove(false))) {
|
|
ExpEvaluator::pushOne(stack,op);
|
|
argc++;
|
|
}
|
|
}
|
|
ExpFunction func(toString(),argc);
|
|
return runDefined(stack,func,context,thisObj);
|
|
}
|
|
else if (oper.name() == YSTRING("context")) {
|
|
// Function.context([skipContexts])
|
|
// return the current function context
|
|
int skip = 0;
|
|
ObjList args;
|
|
switch (extractArgs(this,stack,oper,context,args)) {
|
|
case 1:
|
|
skip = static_cast<ExpOperation*>(args[0])->valInteger();
|
|
if (skip < 0)
|
|
skip = 0;
|
|
break;
|
|
case 0:
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
for (const ObjList* l = stack.skipNull(); l; l = l->skipNext()) {
|
|
GenObject* o = l->get();
|
|
if (static_cast<ExpOperation*>(o)->opcode() == ExpEvaluator::OpcFunc) {
|
|
if (skip--)
|
|
continue;
|
|
break;
|
|
}
|
|
if (skip)
|
|
continue;
|
|
ExpWrapper* wrap = YOBJECT(ExpWrapper,o);
|
|
if (!(wrap && wrap->name() == YSTRING("()")))
|
|
continue;
|
|
JsObject* obj = YOBJECT(JsObject,wrap->object());
|
|
if (!obj)
|
|
continue;
|
|
if (!obj->ref())
|
|
break;
|
|
ExpEvaluator::pushOne(stack,new ExpWrapper(obj,oper.name()));
|
|
return true;
|
|
}
|
|
ExpEvaluator::pushOne(stack,JsParser::nullClone());
|
|
}
|
|
else if (oper.name() == YSTRING("stack")) {
|
|
// Function.stack([fullPath[,stringSeparator]])
|
|
// return the function call stack
|
|
bool full = false;
|
|
String sep;
|
|
ObjList args;
|
|
switch (extractArgs(this,stack,oper,context,args)) {
|
|
case 2:
|
|
sep = *static_cast<ExpOperation*>(args[1]);
|
|
// fall through
|
|
case 1:
|
|
full = static_cast<ExpOperation*>(args[0])->valBoolean(full);
|
|
break;
|
|
case 0:
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
ScriptRun* runner = YOBJECT(ScriptRun,context);
|
|
JsArray* jsa = new JsArray(context,oper.lineNumber(),mutex());
|
|
if (runner) {
|
|
String file;
|
|
unsigned int line;
|
|
runner->code()->getFileLine(oper.lineNumber(),file,line,full);
|
|
file << ":" << line;
|
|
ExpOperation* last = new ExpOperation(file);
|
|
jsa->push(last);
|
|
for (const ObjList* l = stack.skipNull(); l; l = l->skipNext()) {
|
|
GenObject* o = l->get();
|
|
const ExpOperation* op = static_cast<const ExpOperation*>(o);
|
|
if ((op->barrier() && op->opcode() == ExpEvaluator::OpcFunc)) {
|
|
runner->code()->getFileLine(op->lineNumber(),file,line,full);
|
|
file << ":" << line;
|
|
last = new ExpOperation(file);
|
|
jsa->push(last);
|
|
}
|
|
else if (last) {
|
|
ExpWrapper* wrap = YOBJECT(ExpWrapper,o);
|
|
if (!(wrap && wrap->name() == YSTRING("()")))
|
|
continue;
|
|
JsObject* obj = YOBJECT(JsObject,wrap->object());
|
|
if (!obj)
|
|
continue;
|
|
wrap = YOBJECT(ExpWrapper,obj->params().getParam(YSTRING("arguments")));
|
|
if (!wrap)
|
|
continue;
|
|
obj = YOBJECT(JsObject,wrap->object());
|
|
if (!obj)
|
|
continue;
|
|
wrap = YOBJECT(ExpWrapper,obj->params().getParam(YSTRING("callee")));
|
|
if (!wrap)
|
|
continue;
|
|
obj = YOBJECT(JsObject,wrap->object());
|
|
if (!obj)
|
|
continue;
|
|
*last << " " << obj->params();
|
|
last = 0;
|
|
}
|
|
}
|
|
}
|
|
if (sep) {
|
|
String res;
|
|
for (int32_t i = 0; i < jsa->length(); i++)
|
|
res.append(jsa->params()[String(i)],sep);
|
|
TelEngine::destruct(jsa);
|
|
ExpEvaluator::pushOne(stack,new ExpOperation(res,oper.name()));
|
|
}
|
|
else
|
|
ExpEvaluator::pushOne(stack,new ExpWrapper(jsa,oper.name()));
|
|
}
|
|
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, JsObject* thisObj)
|
|
{
|
|
XDebug(DebugAll,"JsFunction::runDefined() in '%s' this=%p [%p]",
|
|
toString().c_str(),thisObj,this);
|
|
JsObject* newObj = 0;
|
|
JsObject* proto = YOBJECT(JsObject,getField(stack,"prototype",context));
|
|
if (proto) {
|
|
// found prototype, build object
|
|
newObj = proto->runConstructor(stack,oper,context);
|
|
if (!newObj)
|
|
return false;
|
|
ExpEvaluator::pushOne(stack,new ExpWrapper(newObj,oper.name()));
|
|
thisObj = newObj;
|
|
}
|
|
JsCode* code = YOBJECT(JsCode,m_code);
|
|
XDebug(DebugAll,"JsFunction::runDefined code=%p proto=%p %s=%p [%p]",
|
|
code,proto,(newObj ? "new" : "this"),thisObj,this);
|
|
if (code) {
|
|
if (!code->callFunction(stack,oper,context,this,(proto != 0),thisObj))
|
|
return false;
|
|
if (newObj && newObj->ref())
|
|
ExpEvaluator::pushOne(stack,new ExpWrapper(newObj,oper.name()));
|
|
return true;
|
|
}
|
|
return proto || runNative(stack,oper,context);
|
|
}
|
|
|
|
|
|
// Adjust a script file include path
|
|
void JsParser::adjustPath(String& script, bool extraInc) const
|
|
{
|
|
if (script.null() || script.startsWith(Engine::pathSeparator()))
|
|
return;
|
|
if (extraInc && m_includePath && File::exists(m_includePath + script))
|
|
script = m_includePath + script;
|
|
else
|
|
script = m_basePath + script;
|
|
}
|
|
|
|
// Create Javascript context
|
|
ScriptContext* JsParser::createContext(unsigned int instIdx, unsigned int maxInst) const
|
|
{
|
|
return new JsContext(instIdx,maxInst);
|
|
}
|
|
|
|
ScriptRun* JsParser::createRunner(ScriptCode* code, ScriptContext* context, const char* title,
|
|
unsigned int instIdx, unsigned int maxInst) const
|
|
{
|
|
if (!code)
|
|
return 0;
|
|
ScriptContext* ctxt = 0;
|
|
if (!context)
|
|
context = ctxt = createContext(instIdx,maxInst);
|
|
ScriptRun* runner = new JsRunner(code,context,title);
|
|
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, const char* file, int len)
|
|
{
|
|
if (TelEngine::null(text))
|
|
return false;
|
|
String::stripBOM(text);
|
|
JsCode* jsc = static_cast<JsCode*>(code());
|
|
ParsePoint expr(text,0,(jsc ? jsc->lineNumber() : 0),file);
|
|
if (fragment)
|
|
return jsc && jsc->compile(expr,this);
|
|
m_parsedFile.clear();
|
|
jsc = new JsCode;
|
|
setCode(jsc);
|
|
jsc->deref();
|
|
expr.m_eval = jsc;
|
|
if (!TelEngine::null(file)) {
|
|
jsc->setBaseFile(file);
|
|
expr.m_fileName = file;
|
|
expr.m_lineNo = jsc->lineNumber();
|
|
}
|
|
if (!jsc->compile(expr,this)) {
|
|
setCode(0);
|
|
return false;
|
|
}
|
|
m_parsedFile = file;
|
|
DDebug(DebugAll,"Compiled: %s",jsc->ExpEvaluator::dump().c_str());
|
|
jsc->simplify();
|
|
DDebug(DebugAll,"Simplified: %s",jsc->ExpEvaluator::dump().c_str());
|
|
if (m_allowLink) {
|
|
jsc->link();
|
|
#ifdef DEBUG
|
|
#ifdef XDEBUG
|
|
Debug(DebugAll,"Linked: %s",jsc->ExpEvaluator::dump(true).c_str());
|
|
#else
|
|
Debug(DebugAll,"Linked: %s",jsc->ExpEvaluator::dump(false).c_str());
|
|
#endif
|
|
#endif
|
|
}
|
|
jsc->trace(m_allowTrace);
|
|
return true;
|
|
}
|
|
|
|
// Check if the script, path or any included files have changed
|
|
bool JsParser::scriptChanged(const char* file) const
|
|
{
|
|
if (TelEngine::null(file))
|
|
return true;
|
|
const JsCode* c = static_cast<const JsCode*>(code());
|
|
if (!c)
|
|
return true;
|
|
String tmp(file);
|
|
adjustPath(tmp);
|
|
return (parsedFile() != tmp) || c->scriptChanged();
|
|
}
|
|
|
|
// 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
|
|
ExpOperation* JsParser::parseJSON(const char* text, ScriptMutex* mtx, ObjList* stack, GenObject* context, const ExpOperation* op)
|
|
{
|
|
if (!text)
|
|
return 0;
|
|
ExpOperation* ret = 0;
|
|
JsCode* code = new JsCode;
|
|
ParsePoint pp(text,code);
|
|
if (code->parseSimple(pp,true,mtx)) {
|
|
ret = code->popOpcode();
|
|
if (code->skipComments(pp,context))
|
|
TelEngine::destruct(ret);
|
|
}
|
|
if (stack && ret) {
|
|
JsObject* obj = YOBJECT(JsObject,ret);
|
|
code->resolveObjectParams(obj,*stack,context);
|
|
if (op)
|
|
JsObject::setLineForObj(obj,op->lineNumber(),true);
|
|
}
|
|
TelEngine::destruct(code);
|
|
return ret;
|
|
}
|
|
|
|
// Return a "null" object wrapper
|
|
ExpOperation* JsParser::nullClone(const char* name)
|
|
{
|
|
return TelEngine::null(name) ? s_null.ExpOperation::clone() : s_null.clone(name);
|
|
}
|
|
|
|
// Return the "null" object
|
|
JsObject* JsParser::nullObject()
|
|
{
|
|
JsObject* n = YOBJECT(JsObject,s_null.object());
|
|
return (n && n->ref()) ? n : 0;
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
|
|
// Check if an operation is null or undefined
|
|
bool JsParser::isMissing(const ExpOperation& oper)
|
|
{
|
|
ExpWrapper* w = YOBJECT(ExpWrapper,&oper);
|
|
return w && (!w->object() || (w->object() == s_null.object()));
|
|
}
|
|
|
|
/* vi: set ts=8 sw=4 sts=4 noet: */
|