Added native JSON parser and stringifier.

git-svn-id: http://voip.null.ro/svn/yate@5869 acf43c95-373e-0410-b603-e72c3f656dc1
This commit is contained in:
paulc 2014-07-14 08:06:41 +00:00
parent 08e8bec9e3
commit d92a383e0f
3 changed files with 244 additions and 16 deletions

View File

@ -174,8 +174,8 @@ public:
bool link();
inline bool traceable() const
{ return m_traceable; }
JsObject* parseArray(ParsePoint& expr, bool constOnly);
JsObject* parseObject(ParsePoint& expr, bool constOnly);
JsObject* parseArray(ParsePoint& expr, bool constOnly, Mutex* mtx);
JsObject* parseObject(ParsePoint& expr, bool constOnly, Mutex* mtx);
inline const NamedList& pragmas() const
{ return m_pragmas; }
inline static unsigned int getLineNo(unsigned int line)
@ -227,6 +227,7 @@ private:
bool parseVar(ParsePoint& expr);
bool parseTry(ParsePoint& expr, GenObject* nested);
bool parseFuncDef(ParsePoint& expr, bool publish);
bool parseSimple(ParsePoint& expr, bool constOnly, Mutex* mtx = 0);
bool evalList(ObjList& stack, GenObject* context) const;
bool evalVector(ObjList& stack, GenObject* context) const;
bool jumpToLabel(long int label, GenObject* context) const;
@ -1908,10 +1909,15 @@ bool JsCode::getSeparator(ParsePoint& expr, bool remove)
}
bool JsCode::getSimple(ParsePoint& expr, bool constOnly)
{
return parseSimple(expr,constOnly);
}
bool JsCode::parseSimple(ParsePoint& expr, bool constOnly, Mutex* mtx)
{
if (inError())
return false;
XDebug(this,DebugAll,"JsCode::getSimple(%s) '%.30s'",String::boolText(constOnly),(const char*)expr);
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)) {
@ -1936,9 +1942,9 @@ bool JsCode::getSimple(ParsePoint& expr, bool constOnly)
default:
break;
}
JsObject* jso = parseArray(expr,constOnly);
JsObject* jso = parseArray(expr,constOnly,mtx);
if (!jso)
jso = parseObject(expr,constOnly);
jso = parseObject(expr,constOnly,mtx);
if (!jso)
return ExpEvaluator::getSimple(expr,constOnly);
addOpcode(new ExpWrapper(ExpEvaluator::OpcCopy,jso));
@ -1946,12 +1952,12 @@ bool JsCode::getSimple(ParsePoint& expr, bool constOnly)
}
// Parse an inline Javascript Array: [ item1, item2, ... ]
JsObject* JsCode::parseArray(ParsePoint& expr, bool constOnly)
JsObject* JsCode::parseArray(ParsePoint& expr, bool constOnly, Mutex* mtx)
{
if (skipComments(expr) != '[')
return 0;
expr++;
JsArray* jsa = new JsArray(0,"[object Array]");
JsArray* jsa = new JsArray(mtx,"[object Array]");
for (bool first = true; ; first = false) {
if (skipComments(expr) == ']') {
expr++;
@ -1986,7 +1992,7 @@ JsObject* JsCode::parseArray(ParsePoint& expr, bool constOnly)
jsa->push(new ExpWrapper(0,"undefined"));
continue;
}
bool ok = constOnly ? getSimple(expr,true) : getOperand(expr,false);
bool ok = constOnly ? parseSimple(expr,true,mtx) : getOperand(expr,false);
if (!ok) {
TelEngine::destruct(jsa);
break;
@ -2001,12 +2007,12 @@ JsObject* JsCode::parseArray(ParsePoint& expr, bool constOnly)
// Parse an inline Javascript Object: { prop1: value1, "prop 2": value2, ... }
JsObject* JsCode::parseObject(ParsePoint& expr, bool constOnly)
JsObject* JsCode::parseObject(ParsePoint& expr, bool constOnly, Mutex* mtx)
{
if (skipComments(expr) != '{')
return 0;
expr++;
JsObject* jso = new JsObject(0,"[object Object]");
JsObject* jso = new JsObject(mtx,"[object Object]");
for (bool first = true; ; first = false) {
if (skipComments(expr) == '}') {
expr++;
@ -2040,7 +2046,7 @@ JsObject* JsCode::parseObject(ParsePoint& expr, bool constOnly)
break;
}
expr++;
bool ok = constOnly ? getSimple(expr,true) : getOperand(expr,false);
bool ok = constOnly ? parseSimple(expr,true,mtx) : getOperand(expr,false);
if (!ok) {
TelEngine::destruct(jso);
break;
@ -3505,13 +3511,17 @@ ScriptRun::Status JsParser::eval(const String& text, ExpOperation** result, Scri
}
// Parse JSON using native methods
JsObject* JsParser::parseJSON(const char* text)
ExpOperation* JsParser::parseJSON(const char* text, Mutex* mtx)
{
if (!text)
return 0;
ExpOperation* ret = 0;
JsCode* code = new JsCode;
ParsePoint pp(text,code);
JsObject* jso = code->parseObject(pp,true);
if (code->parseSimple(pp,true,mtx))
ret = code->popOpcode();
TelEngine::destruct(code);
return jso;
return ret;
}
// Return a "null" object wrapper

View File

@ -2545,9 +2545,10 @@ public:
/**
* Parse a complete block of JSON text
* @param text JSON text to parse
* @return JsObject holding the content of JSON, must be dereferenced after use, NULL if parse error
* @param mtx Pointer to the mutex that serializes this object
* @return ExpOperation holding the content of JSON, must be dereferenced after use, NULL if parse error
*/
static JsObject* parseJSON(const char* text);
static ExpOperation* parseJSON(const char* text, Mutex* mtx = 0);
/**
* Get a "null" object wrapper that will identity match another "null"

View File

@ -575,6 +575,26 @@ private:
Hasher* m_hasher;
};
class JsJSON : public JsObject
{
YCLASS(JsJSON,JsObject)
public:
inline JsJSON(Mutex* mtx)
: JsObject("JSON",mtx,true)
{
params().addParam(new ExpFunction("parse"));
params().addParam(new ExpFunction("stringify"));
params().addParam(new ExpFunction("loadFile"));
params().addParam(new ExpFunction("saveFile"));
}
static void initialize(ScriptContext* context);
protected:
bool runNative(ObjList& stack, const ExpOperation& oper, GenObject* context);
static ExpOperation* stringify(const ExpOperation* oper, int spaces);
static void stringify(const NamedString* ns, String& buf, int spaces, int indent = 0);
static String strEscape(const char* str);
};
class JsChannel : public JsObject
{
YCLASS(JsChannel,JsObject)
@ -2545,6 +2565,200 @@ void JsXML::initialize(ScriptContext* context)
addConstructor(params,"XML",new JsXML(mtx));
}
bool JsJSON::runNative(ObjList& stack, const ExpOperation& oper, GenObject* context)
{
ObjList args;
if (oper.name() == YSTRING("parse")) {
if (extractArgs(stack,oper,context,args) != 1)
return false;
ExpOperation* op = JsParser::parseJSON(static_cast<ExpOperation*>(args[0])->c_str(),mutex());
if (!op)
op = new ExpWrapper(0,"JSON");
ExpEvaluator::pushOne(stack,op);
}
else if (oper.name() == YSTRING("stringify")) {
if (extractArgs(stack,oper,context,args) < 1)
return false;
int spaces = args[2] ? static_cast<ExpOperation*>(args[2])->number() : 0;
ExpOperation* op = stringify(static_cast<ExpOperation*>(args[0]),spaces);
if (!op)
op = new ExpWrapper(0,"JSON");
ExpEvaluator::pushOne(stack,op);
}
else if (oper.name() == YSTRING("loadFile")) {
if (extractArgs(stack,oper,context,args) != 1)
return false;
ExpOperation* op = 0;
ExpOperation* file = static_cast<ExpOperation*>(args[0]);
if (!TelEngine::null(file)) {
File f;
if (f.openPath(*file)) {
int64_t len = f.length();
if (len > 0 && len <= 65536) {
DataBlock buf(0,len + 1);
char* text = (char*)buf.data();
if (f.readData(text,len) == len) {
text[len] = '\0';
op = JsParser::parseJSON(text,mutex());
}
}
}
}
if (!op)
op = new ExpWrapper(0,"JSON");
ExpEvaluator::pushOne(stack,op);
}
else if (oper.name() == YSTRING("saveFile")) {
if (extractArgs(stack,oper,context,args) < 2)
return false;
ExpOperation* file = static_cast<ExpOperation*>(args[0]);
bool ok = !TelEngine::null(file);
if (ok) {
ok = false;
int spaces = args[2] ? static_cast<ExpOperation*>(args[2])->number() : 0;
ExpOperation* op = stringify(static_cast<ExpOperation*>(args[1]),spaces);
if (op) {
File f;
if (f.openPath(*file,true,false,true)) {
int len = op->length();
ok = f.writeData(op->c_str(),len) == len;
}
}
TelEngine::destruct(op);
}
ExpEvaluator::pushOne(stack,new ExpOperation(ok));
}
else
return JsObject::runNative(stack,oper,context);
return true;
}
ExpOperation* JsJSON::stringify(const ExpOperation* oper, int spaces)
{
if (!oper)
return 0;
if (spaces < 0)
spaces = 0;
else if (spaces > 10)
spaces = 10;
ExpOperation* ret = new ExpOperation("","JSON");
stringify(oper,*ret,spaces);
return ret;
}
void JsJSON::stringify(const NamedString* ns, String& buf, int spaces, int indent)
{
const ExpOperation* oper = YOBJECT(ExpOperation,ns);
if (!oper) {
if (ns)
buf << strEscape(*ns);
else
buf << "null";
return;
}
if (JsParser::isNull(*oper) || JsParser::isUndefined(*oper)) {
buf << "null";
return;
}
const char* nl = spaces ? "\r\n" : "";
JsObject* jso = YOBJECT(JsObject,oper);
JsArray* jsa = YOBJECT(JsArray,jso);
if (jsa) {
if (jsa->length() <= 0) {
buf << "[]";
return;
}
String li(' ',indent);
String ci(' ',indent + spaces);
buf << "[" << nl;
for (int32_t i = 0; ; ) {
const NamedString* p = jsa->params().getParam(String(i));
if (!p)
continue;
buf << ci;
stringify(p,buf,spaces,indent + spaces);
if (++i < jsa->length())
buf << "," << nl;
else {
buf << nl;
break;
}
}
buf << li << "]";
return;
}
if (jso) {
switch (jso->params().count()) {
case 1:
if (!jso->params().getParam(protoName()))
break;
// fall through
case 0:
buf << "{}";
return;
}
ObjList* l = jso->params().paramList()->skipNull();
String li(' ',indent);
String ci(' ',indent + spaces);
const char* sep = spaces ? ": " : ":";
buf << "{" << nl;
while (l) {
const NamedString* p = static_cast<const NamedString*>(l->get());
l = l->skipNext();
if (p->name() == protoName())
continue;
buf << ci << strEscape(p->name()) << sep;
stringify(p,buf,spaces,indent + spaces);
p = static_cast<const NamedString*>(l->get());
if (p->name() == protoName())
l = l->skipNext();
if (l)
buf << ",";
buf << nl;
}
buf << li << "}";
return;
}
if (oper->isBoolean())
buf << String::boolText(oper->valBoolean());
else if (oper->isNumber()) {
if (oper->isInteger())
buf << oper->number();
else
buf << "null";
}
else
buf << strEscape(*oper);
}
String JsJSON::strEscape(const char* str)
{
{
String s("\"");
char c;
while (str && (c = *str++)) {
if (c == '\"' || c == '\\')
s += "\\";
s += c;
}
s += "\"";
return s;
}
}
void JsJSON::initialize(ScriptContext* context)
{
if (!context)
return;
Mutex* mtx = context->mutex();
Lock mylock(mtx);
NamedList& params = context->params();
if (!params.getParam(YSTRING("JSON")))
addObject(params,"JSON",new JsJSON(mtx));
}
/**
* class JsTimeEvent
*/
@ -2958,6 +3172,7 @@ bool JsAssist::init()
JsConfigFile::initialize(ctx);
JsXML::initialize(ctx);
JsHasher::initialize(ctx);
JsJSON::initialize(ctx);
if (ScriptRun::Invalid == m_runner->reset(true))
return false;
ScriptContext* chan = YOBJECT(ScriptContext,ctx->getField(m_runner->stack(),YSTRING("Channel"),m_runner));
@ -3290,6 +3505,7 @@ bool JsGlobal::runMain()
JsConfigFile::initialize(runner->context());
JsXML::initialize(runner->context());
JsHasher::initialize(runner->context());
JsJSON::initialize(runner->context());
ScriptRun::Status st = runner->run();
TelEngine::destruct(runner);
return (ScriptRun::Succeeded == st);
@ -3394,6 +3610,7 @@ bool JsModule::evalContext(String& retVal, const String& cmd, ScriptContext* con
JsConfigFile::initialize(runner->context());
JsXML::initialize(runner->context());
JsHasher::initialize(runner->context());
JsJSON::initialize(runner->context());
}
ScriptRun::Status st = runner->run();
if (st == ScriptRun::Succeeded) {