From 9b6259ac82bf49cfb806c1ac7965b61070eb00a8 Mon Sep 17 00:00:00 2001 From: paulc Date: Mon, 14 Jul 2014 08:06:41 +0000 Subject: [PATCH] Added native JSON parser and stringifier. git-svn-id: http://yate.null.ro/svn/yate/trunk@5869 acf43c95-373e-0410-b603-e72c3f656dc1 --- libs/yscript/javascript.cpp | 38 ++++--- libs/yscript/yatescript.h | 5 +- modules/javascript.cpp | 217 ++++++++++++++++++++++++++++++++++++ 3 files changed, 244 insertions(+), 16 deletions(-) diff --git a/libs/yscript/javascript.cpp b/libs/yscript/javascript.cpp index 5a5ca6e0..81a0d1e5 100644 --- a/libs/yscript/javascript.cpp +++ b/libs/yscript/javascript.cpp @@ -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 diff --git a/libs/yscript/yatescript.h b/libs/yscript/yatescript.h index ba46f8d7..f46fc44d 100644 --- a/libs/yscript/yatescript.h +++ b/libs/yscript/yatescript.h @@ -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" diff --git a/modules/javascript.cpp b/modules/javascript.cpp index 93237f4d..38727219 100644 --- a/modules/javascript.cpp +++ b/modules/javascript.cpp @@ -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(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(args[2])->number() : 0; + ExpOperation* op = stringify(static_cast(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(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(args[0]); + bool ok = !TelEngine::null(file); + if (ok) { + ok = false; + int spaces = args[2] ? static_cast(args[2])->number() : 0; + ExpOperation* op = stringify(static_cast(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(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(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) {