diff --git a/pdbApp/Makefile b/pdbApp/Makefile index e55cb7c..04215f5 100644 --- a/pdbApp/Makefile +++ b/pdbApp/Makefile @@ -18,6 +18,7 @@ qsrv_SRCS += demo.cpp ifdef BASE_3_16 qsrv_SRCS += pdbgroup.cpp +qsrv_SRCS += configparse.cpp endif qsrv_LIBS += pvAccess pvData Com diff --git a/pdbApp/configparse.cpp b/pdbApp/configparse.cpp new file mode 100644 index 0000000..e786b5d --- /dev/null +++ b/pdbApp/configparse.cpp @@ -0,0 +1,247 @@ + +#include + +#include +#include + +#include +#include +#include + +#define epicsExportSharedSymbols +#include "pdbgroup.h" + +namespace { + +namespace pvd = epics::pvData; + +typedef std::map options_t; +typedef std::map config_t; + +struct context { + std::string msg; + std::string group, field, key; + + GroupConfig conf; + + void can_assign() + { + if(group.empty() || field.empty()) + throw std::runtime_error("Can't assign value in this context"); + } + + void assign(const AnyScalar& value) { + can_assign(); + GroupConfig::Group& grp = conf.groups[group]; + + if(key.empty()) { + if(field=="+atomic") { + grp.atomic = value.as(); + grp.atomic_set = true; + + } else { + conf.warning += "Unknown group option "; + conf.warning += field; + } + field.clear(); + + } else { + GroupConfig::Field& fld = grp.fields[field]; + + if(key=="+type") { + fld.type = value.ref(); + if(fld.type.empty()) + throw std::runtime_error("+type can't be empty string"); + + } else if(key=="+channel") { + fld.channel = value.ref(); + if(fld.channel.empty()) + throw std::runtime_error("+channel can't be empty string"); + + } else if(key=="+trigger") { + fld.trigger = value.ref(); + if(fld.trigger.empty()) + throw std::runtime_error("+trigger can't be empty string"); + + } else if(key=="+putorder") { + fld.putorder = value.as(); + + } else { + conf.warning += "Unknown group field option "; + conf.warning += field; + } + key.clear(); + } + } +}; + +#define TRY context *self = (context*)ctx; try + +#define CATCH() catch(std::exception& e) { if(self->msg.empty()) self->msg = e.what(); return 0; } + +int conf_null(void * ctx) +{ + TRY { + self->assign(AnyScalar()); + return 1; + }CATCH() +} + + +int conf_boolean(void * ctx, int boolVal) +{ + TRY { + self->assign(AnyScalar(pvd::boolean(boolVal))); + return 1; + }CATCH() +} + +int conf_integer(void * ctx, long integerVal) +{ + TRY { + self->assign(AnyScalar(pvd::int64(integerVal))); + return 1; + }CATCH() +} + +int conf_double(void * ctx, double doubleVal) +{ + TRY { + self->assign(AnyScalar(doubleVal)); + return 1; + }CATCH() +} + +int conf_string(void * ctx, const unsigned char * stringVal, + unsigned int stringLen) +{ + TRY { + std::string val((const char*)stringVal, stringLen); + self->assign(AnyScalar(val)); + return 1; + }CATCH() +} + +int conf_start_map(void * ctx) +{ + TRY { + if(!self->group.empty() && !self->field.empty() && !self->key.empty()) + throw std::runtime_error("Too deep"); + return 1; + }CATCH() +} + +int conf_map_key(void * ctx, const unsigned char * key, + unsigned int stringLen) +{ + TRY { + if(stringLen==0) + throw std::runtime_error("empty key"); + + std::string name((const char*)key, stringLen); + + if(self->group.empty()) + self->group.swap(name); + else if(self->field.empty()) + self->field.swap(name); + else if(self->key.empty()) + self->key.swap(name); + else + throw std::logic_error("Too deep!!"); + + return 1; + }CATCH() +} + +int conf_end_map(void * ctx) +{ + TRY { + assert(self->key.empty()); // cleared in assign() + + if(!self->field.empty()) + self->field.clear(); + else if(!self->group.empty()) + self->group.clear(); + + return 1; + }CATCH() +} + +yajl_callbacks conf_cbs = { + &conf_null, + &conf_boolean, + &conf_integer, + &conf_double, + NULL, // number + &conf_string, + &conf_start_map, + &conf_map_key, + &conf_end_map, + NULL, // start_array, + NULL, // 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; } +}; + +}// namespace + +void GroupConfig::parse(const char *txt, + GroupConfig& result) +{ + yajl_parser_config conf; + memset(&conf, 0, sizeof(conf)); + conf.allowComments = 1; + conf.checkUTF8 = 1; + + context ctxt; + + handler handle(yajl_alloc(&conf_cbs, &conf, NULL, &ctxt)); + + yajl_status sts = yajl_parse(handle, (const unsigned char*)txt, strlen(txt)); + + if(sts==yajl_status_insufficient_data) + sts = yajl_parse_complete(handle); + + switch(sts) { + case yajl_status_ok: { + size_t consumed = yajl_get_bytes_consumed(handle); + if(consumed +#include +#include + #include #include @@ -15,6 +19,49 @@ #include +struct epicsShareClass GroupConfig +{ + struct epicsShareClass Field { + std::string type, channel, trigger; + int putorder; + + Field() :putorder(std::numeric_limits::min()) {} + + void swap(Field& o) { + std::swap(type, o.type); + std::swap(channel, o.channel); + std::swap(trigger, o.trigger); + std::swap(putorder, o.putorder); + } + }; + + struct epicsShareClass Group { + typedef std::map fields_t; + fields_t fields; + bool atomic, atomic_set; + + Group() :atomic(true), atomic_set(false) {} + + void swap(Group& o) { + std::swap(fields, o.fields); + std::swap(atomic, o.atomic); + std::swap(atomic_set, o.atomic_set); + } + }; + + typedef std::map groups_t; + groups_t groups; + std::string warning; + + void swap(GroupConfig& o) { + std::swap(groups, o.groups); + std::swap(warning, o.warning); + } + + static void parse(const char *txt, + GroupConfig& result); +}; + struct PDBGroupMonitor; void pdb_group_event(void *user_arg, struct dbChannel *chan, diff --git a/testApp/Makefile b/testApp/Makefile index 8076fbb..3a23f11 100644 --- a/testApp/Makefile +++ b/testApp/Makefile @@ -67,6 +67,12 @@ testpvalink_SRCS += p2pTestIoc_registerRecordDeviceDriver.cpp testpvalink_LIBS += testutils qsrv pvAccess pvData $(EPICS_BASE_IOC_LIBS) #TESTS += testpvalink +ifdef BASE_3_16 +TESTPROD_HOST += testgroupconfig +testgroupconfig_SRCS += testgroupconfig +testgroupconfig_LIBS += qsrv pvAccess pvData $(EPICS_BASE_IOC_LIBS) +endif + TESTSCRIPTS_HOST += $(TESTS:%=%.t) diff --git a/testApp/testgroupconfig.cpp b/testApp/testgroupconfig.cpp new file mode 100644 index 0000000..c5d455b --- /dev/null +++ b/testApp/testgroupconfig.cpp @@ -0,0 +1,67 @@ +#include + +#include +#include + +#include "pdbgroup.h" + +namespace pvd = epics::pvData; + +namespace { + +void test_parse() +{ + testDiag("test_parse()"); + + const char txt[] = + "{\"grpa\":{\n" + " \"+atomic\":false,\n" + " \"fld\":{\n" + " \"+type\": \"simple\"," + " \"+putorder\": -4" + " }\n" + " },\n" + " \"fld2\":{\n" + " }\n" + "}"; + + GroupConfig conf; + GroupConfig::parse(txt, conf); + + testOk(conf.warning.empty(), "Warnings: %s", conf.warning.c_str()); + + testOk1(!conf.groups["grpa"].atomic); + testOk1(conf.groups["grpa"].atomic_set); + + testEqual(conf.groups["grpa"].fields["fld"].type, "simple"); + testEqual(conf.groups["grpa"].fields["fld"].channel, ""); + testEqual(conf.groups["grpa"].fields["fld"].putorder, -4); +} + +void test_fail() +{ + testDiag("test_fail()"); + + { + GroupConfig conf; + testThrows(std::runtime_error, GroupConfig::parse("{\"G\":{\"F\":{\"K\":{}}}}", conf)); + } + { + GroupConfig conf; + testThrows(std::runtime_error, GroupConfig::parse("{\"G\":5}", conf)); + } +} + +} // namespace + +MAIN(testanyscalar) +{ + testPlan(8); + try { + test_parse(); + test_fail(); + }catch(std::exception& e){ + testAbort("Unexpected exception: %s", e.what()); + } + return testDone(); +}