/* * Copyright information and license terms for this software can be * found in the file LICENSE that is included with the distribution */ #include #include #define epicsExportSharedSymbols #include #include #include #include #include "pv/json.h" namespace pvd = epics::pvData; using pvd::yajl::integer_arg; using pvd::yajl::size_arg; namespace { struct context { std::string msg; struct frame { pvd::PVFieldPtr fld; pvd::BitSet *assigned; frame(const pvd::PVFieldPtr& fld, pvd::BitSet *assigned) :fld(fld), assigned(assigned) {} }; typedef std::vector stack_t; stack_t stack; context(const pvd::PVFieldPtr& root, pvd::BitSet *assigned) { stack.push_back(frame(root, assigned)); } }; #define TRY context *self = (context*)ctx; assert(!self->stack.empty()); try #define CATCH() catch(std::exception& e) { if(self->msg.empty()) self->msg = e.what(); return 0; } int jtree_null(void * ctx) { TRY { self->msg = "NULL value not permitted"; return 0; }CATCH() } template void valueAssign(context *self, typename PVScalarT::value_type val) { assert(!self->stack.empty()); context::frame& back = self->stack.back(); pvd::Type type(back.fld->getField()->getType()); if(type==pvd::scalar) { pvd::PVScalar* fld(static_cast(back.fld.get())); fld->putFrom(val); if(back.assigned) back.assigned->set(fld->getFieldOffset()); self->stack.pop_back(); // structure at the top of the stack } else if(type==pvd::scalarArray) { pvd::PVScalarArray *fld(static_cast(back.fld.get())); pvd::shared_vector carr; fld->getAs(carr); switch(carr.original_type()) { #define CASE_STRING #define CASE_REAL_INT64 #define CASE(BASETYPE, PVATYPE, DBFTYPE, PVACODE) case epics::pvData::pv##PVACODE: { \ pvd::shared_vector arr(pvd::static_shared_vector_cast(carr)); \ pvd::shared_vector tarr(pvd::thaw(arr)); \ tarr.push_back(pvd::castUnsafe(val)); \ carr = pvd::static_shared_vector_cast(pvd::freeze(tarr)); \ } break; #include #undef CASE #undef CASE_REAL_INT64 #undef CASE_STRING } fld->putFrom(carr); // leave array field at top of stack } else if(type==pvd::union_) { pvd::PVUnion* fld(static_cast(back.fld.get())); pvd::UnionConstPtr utype(fld->getUnion()); if(utype->isVariant()) { typename PVScalarT::shared_pointer elem(pvd::getPVDataCreate()->createPVScalar()); elem->put(val); fld->set(elem); } else { // attempt automagic assignment const pvd::StringArray& names = utype->getFieldNames(); const pvd::FieldConstPtrArray types = utype->getFields(); assert(names.size()==types.size()); bool assigned = false; for(size_t i=0, N=names.size(); igetType()!=pvd::scalar) continue; pvd::PVScalarPtr ufld(fld->select(i)); try{ ufld->putFrom(val); assigned = true; }catch(std::runtime_error&){ if(i==N-1) throw; continue; } break; } if(!assigned) throw std::runtime_error("Unable to select union member"); } if(back.assigned) back.assigned->set(fld->getFieldOffset()); self->stack.pop_back(); // structure back at the top of the stack } else { throw std::invalid_argument("Can't assign value"); } } int jtree_boolean(void * ctx, int boolVal) { TRY { valueAssign(self, !!boolVal); return 1; }CATCH() } int jtree_integer(void * ctx, integer_arg integerVal) { TRY { valueAssign(self, integerVal); return 1; }CATCH() } int jtree_double(void * ctx, double doubleVal) { TRY { valueAssign(self, doubleVal); return 1; }CATCH() } int jtree_string(void * ctx, const unsigned char * stringVal, size_arg stringLen) { TRY { std::string val((const char*)stringVal, stringLen); valueAssign(self, val); return 1; }CATCH() } int jtree_start_map(void * ctx) { TRY { assert(!self->stack.empty()); context::frame& back = self->stack.back(); pvd::Type type = back.fld->getField()->getType(); if(type==pvd::structure) { // will fill in } else if(type==pvd::structureArray) { // starting new element in structure array pvd::PVStructureArray* sarr(static_cast(back.fld.get())); pvd::PVStructurePtr elem(pvd::getPVDataCreate()->createPVStructure(sarr->getStructureArray()->getStructure())); self->stack.push_back(context::frame(elem, 0)); } else { throw std::runtime_error("Can't map (sub)structure"); } assert(self->stack.back().fld->getField()->getType()==pvd::structure); return 1; }CATCH() } int jtree_map_key(void * ctx, const unsigned char * key, size_arg stringLen) { TRY { assert(!self->stack.empty()); std::string name((const char*)key, stringLen); // start_map() ensures we have a structure at the top of the stack pvd::PVStructure *fld = static_cast(self->stack.back().fld.get()); try { self->stack.push_back(context::frame(fld->getSubFieldT(name), self->stack.back().assigned)); }catch(std::runtime_error& e){ std::ostringstream strm; strm<<"At "<getFullName()<<" : "<stack.empty()); assert(self->stack.back().fld->getField()->getType()==pvd::structure); context::frame elem(self->stack.back()); self->stack.pop_back(); if(!self->stack.empty() && self->stack.back().fld->getField()->getType()==pvd::structureArray) { // append element to struct array pvd::PVStructureArray *sarr = static_cast(self->stack.back().fld.get()); pvd::PVStructureArray::const_svector cval; sarr->swap(cval); pvd::PVStructureArray::svector val(pvd::thaw(cval)); val.push_back(std::tr1::static_pointer_cast(elem.fld)); sarr->replace(pvd::freeze(val)); } return 1; }CATCH() } int jtree_start_array(void * ctx) { TRY { assert(!self->stack.empty()); pvd::PVFieldPtr& back(self->stack.back().fld); pvd::Type type = back->getField()->getType(); if(type!=pvd::structureArray && type!=pvd::scalarArray) throw std::runtime_error("Can't assign array"); return 1; }CATCH() } int jtree_end_array(void * ctx) { TRY { assert(!self->stack.empty()); if(self->stack.back().assigned) self->stack.back().assigned->set(self->stack.back().fld->getFieldOffset()); self->stack.pop_back(); return 1; }CATCH() } yajl_callbacks jtree_cbs = { &jtree_null, &jtree_boolean, &jtree_integer, &jtree_double, NULL, // number &jtree_string, &jtree_start_map, &jtree_map_key, &jtree_end_map, &jtree_start_array, &jtree_end_array, }; struct handler { yajl_handle handle; handler(yajl_handle handle) :handle(handle) { if(!handle) throw std::runtime_error("Failed to allocate yajl handle"); } ~handler() { yajl_free(handle); } operator yajl_handle() { return handle; } }; struct noop { void operator()(pvd::PVField*) {} }; } // namespace namespace epics{namespace pvData{ epicsShareFunc void parseJSON(std::istream& strm, PVField& dest, BitSet *assigned) { #ifndef EPICS_YAJL_VERSION yajl_parser_config conf; memset(&conf, 0, sizeof(conf)); conf.allowComments = 1; conf.checkUTF8 = 1; #endif // we won't create refs to 'dest' which presist beyond this call. // however, it is convienent to treat 'dest' in the same manner as // any union/structureArray memebers it may contain. PVFieldPtr fakedest(&dest, noop()); context ctxt(fakedest, assigned); #ifndef EPICS_YAJL_VERSION handler handle(yajl_alloc(&jtree_cbs, &conf, NULL, &ctxt)); #else handler handle(yajl_alloc(&jtree_cbs, NULL, &ctxt)); yajl_config(handle, yajl_allow_comments, 1); #endif if(!yajl_parse_helper(strm, handle)) throw std::runtime_error(ctxt.msg); if(!ctxt.stack.empty()) throw std::logic_error("field stack not empty"); assert(fakedest.use_count()==1); } }} // namespace epics::pvData