diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..55136c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +/bin/ +/lib/ +/db/ +/dbd/ +/include/ +/configure/*.local +O.*/ +/FEED.* diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4d8193f --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ +TOP = . +include $(TOP)/configure/CONFIG + +DIRS += configure + +DIRS += common +common_DEPEND_DIRS = configure + +DIRS += tests +tests_DEPEND_DIRS = configure common + +include $(TOP)/configure/RULES_TOP diff --git a/common/Makefile b/common/Makefile new file mode 100644 index 0000000..94b5cb3 --- /dev/null +++ b/common/Makefile @@ -0,0 +1,19 @@ +TOP=.. + +include $(TOP)/configure/CONFIG +#---------------------------------------- +# ADD MACRO DEFINITIONS AFTER THIS LINE +#============================= + +LIBRARY += feedcom + +feedcom_LIBS += Com + +feedcom_SRCS += jblob.cpp + +#=========================== + +include $(TOP)/configure/RULES +#---------------------------------------- +# ADD RULES AFTER THIS LINE + diff --git a/common/jblob.cpp b/common/jblob.cpp new file mode 100644 index 0000000..806258d --- /dev/null +++ b/common/jblob.cpp @@ -0,0 +1,290 @@ + +#include +#include +#include + +#include +#include + +#include "utils.h" +#include "jblob.h" + +namespace { + +// undef implies API version 0 +#ifndef EPICS_YAJL_API_VERSION +typedef long integer_arg; +typedef unsigned size_arg; +#else +typedef long long integer_arg; +typedef size_t size_arg; +#endif + +struct context { + JBlob blob; + + std::string err, // error message + regname, + param; + unsigned depth; + + JRegister scratch; + + typedef std::list warnings_t; + warnings_t warnings; + + void warn(const std::string& msg) { + warnings.push_back(msg); + } + + context() :depth(0) {} +}; + +#define TRY context *self = (context*)ctx; try + +#define CATCH() catch(std::exception& e) { if(self->err.empty()) self->err = e.what(); return 0; } + +int jblob_null(void *ctx) +{ + TRY { + self->warn("ignoring unsupported: null"); + }CATCH() + return 1; +} + +int jblob_boolean(void *ctx, int val) +{ + TRY { + self->warn(SB()<<"ignoring unsupported: boolean value "<depth==2) { + if(self->param=="base_addr") { + self->scratch.base_addr = val; + + } else if(self->param=="addr_width") { + self->scratch.addr_width = val; + + } else if(self->param=="data_width") { + self->scratch.data_width = val; + + } else { + self->warn(SB()<regname<<"."<param<<" ignores integer value"); + } + } else + self->warn(SB()<<"ignored integer value at depth="<depth); + return 1; + }CATCH() +} + +int jblob_double(void *ctx, double val) +{ + TRY { + self->warn(SB()<<"ignoring unsupported: double value "<depth==2) { + if(self->param=="access") { + self->scratch.readable = V.find_first_of('r')!=V.npos; + self->scratch.writable = V.find_first_of('w')!=V.npos; + + } else if(self->param=="description") { + self->scratch.description = V; + + } else if(self->param=="sign") { + if(V=="unsigned") { + self->scratch.sign = JRegister::Unsigned; + + } else if(V=="signed") { + self->scratch.sign = JRegister::Signed; + + } else { + self->warn(SB()<regname<<"."<param<<" unknown value "<warn(SB()<regname<<"."<param<<" ignores string value"); + } + } else + self->warn(SB()<<"ignored string value at depth="<depth); + return 1; + }CATCH() +} + +int jblob_start_map(void *ctx) +{ + TRY { + self->depth++; + if(self->depth>2) { + throw std::runtime_error("Object depth limit (2) exceeded"); + } + return 1; + }CATCH() +} + +int jblob_map_key(void *ctx, const unsigned char *key, size_arg len) +{ + TRY { + if(len==0) + throw std::runtime_error("Zero length key not allowed"); + std::string K((const char*)key, len); + if(self->depth==1) { + self->regname = K; + if(self->blob.registers.find(K)!=self->blob.registers.end()) { + self->warn(SB()<<"Duplicate definition for register "<regname); + } + } else if(self->depth==2) { + self->param = K; + } else { + throw std::logic_error("key at unsupported depth"); + } + return 1; + }CATCH() +} + +int jblob_end_map(void *ctx) +{ + TRY { + if(self->depth==2) { + self->scratch.name = self->regname; + self->blob.registers[self->regname] = self->scratch; + self->scratch.clear(); + } + if(self->depth==0) + throw std::logic_error("Object depth underflow"); + self->depth--; + return 1; + }CATCH() +} + +yajl_callbacks jblob_cbs = { + &jblob_null, // null + &jblob_boolean, // boolean + &jblob_integer, + &jblob_double, // double + NULL, // number + &jblob_string, + &jblob_start_map, + &jblob_map_key, + &jblob_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 JBlob::parse(const char *buf) +{ + parse(buf, strlen(buf)); +} + +void JBlob::parse(const char *buf, size_t buflen) +{ +#ifndef EPICS_YAJL_API_VERSION + yajl_parser_config conf; + memset(&conf, 0, sizeof(conf)); + conf.allowComments = 1; + conf.checkUTF8 = 1; +#endif + + context ctxt; + +#ifndef EPICS_YAJL_API_VERSION + handler handle(yajl_alloc(&jblob_cbs, &conf, NULL, &ctxt)); +#else + handler handle(yajl_alloc(&jblob_cbs, NULL, &ctxt)); +#endif + + yajl_status sts = yajl_parse(handle, (const unsigned char*)buf, buflen); +#ifndef EPICS_YAJL_API_VERSION + if(sts==yajl_status_insufficient_data) { + sts = yajl_parse_complete(handle); + } +#else + if(sts==yajl_status_ok) + sts = yajl_complete_parse(handle); +#endif + switch(sts) { + case yajl_status_ok: + break; + case yajl_status_error: { + std::string msg; + unsigned char *raw = yajl_get_error(handle, 1, (const unsigned char*)buf, buflen); + try { + msg = (char*)raw; + yajl_free_error(handle, raw); + }catch(...){ + yajl_free_error(handle, raw); + throw; + } + throw std::runtime_error(msg); + } + case yajl_status_client_canceled: + throw std::runtime_error(ctxt.err); +#ifndef EPICS_YAJL_API_VERSION + case yajl_status_insufficient_data: + throw std::runtime_error("Unexpected end of input"); +#endif + } + + for(context::warnings_t::const_iterator it=ctxt.warnings.begin(), end=ctxt.warnings.end(); + it!=end; ++it) + { + errlogPrintf("JSON parser warning: %s\n", it->c_str()); + } + + registers.swap(ctxt.blob.registers); +} + +std::ostream& operator<<(std::ostream& strm, const JBlob& blob) +{ + strm<<"{"; + bool first=true; + for(JBlob::registers_t::const_iterator it=blob.registers.begin(), end=blob.registers.end(); + it!=end; ++it) + { + if(first) first=false; + else strm<<", "; + strm<<"\""<first<<"\":"<second; + } + strm<<"}"; + return strm; +} + +std::ostream& operator<<(std::ostream& strm, const JRegister& reg) +{ + strm<<"{" + "\"access\":\""<<(reg.readable?"r":"")<<(reg.writable?"w":"")<<"\", " + "\"addr_width\":"< +#include +#include + +#include + +struct JRegister { + std::string name, + description; + epicsUInt32 base_addr, + addr_width, //?? + data_width; + enum sign_t { + Unsigned, Signed + } sign; + bool readable, + writable; + + void clear() { + name.clear(); + description.clear(); + base_addr = addr_width = data_width = 0u; + sign = Unsigned; + readable = writable = false; + } + + JRegister() {clear();} +}; + +struct JBlob { + typedef std::map registers_t; + registers_t registers; + + // Parse and replace current contents + void parse(const char *buf); + void parse(const char *buf, size_t buflen); + + typedef registers_t::const_iterator const_iterator; + const_iterator begin() const { return registers.begin(); } + const_iterator end() const { return registers.end(); } + const_iterator find(const std::string& k) const { return registers.find(k); } +}; + +std::ostream& operator<<(std::ostream& strm, const JBlob& blob); +std::ostream& operator<<(std::ostream& strm, const JRegister& reg); + +#endif // JBLOB_H diff --git a/common/utils.h b/common/utils.h new file mode 100644 index 0000000..79b6bbf --- /dev/null +++ b/common/utils.h @@ -0,0 +1,15 @@ +#ifndef UTILS_H +#define UTILS_H + +#include + +// in-line string builder (eg. for exception messages) +struct SB { + std::ostringstream strm; + SB() {} + operator std::string() const { return strm.str(); } + template + SB& operator<<(T i) { strm< + +# Set this when the IOC and build host use different paths +# to the install location. This may be needed to boot from +# a Microsoft FTP server say, or on some NFS configurations. +#IOCS_APPL_TOP = + +# For application debugging purposes, override the HOST_OPT and/ +# or CROSS_OPT settings from base/configure/CONFIG_SITE +#HOST_OPT = NO +#CROSS_OPT = NO + +# These allow developers to override the CONFIG_SITE variable +# settings without having to modify the configure/CONFIG_SITE +# file itself. +-include $(TOP)/../CONFIG_SITE.local +-include $(TOP)/configure/CONFIG_SITE.local + diff --git a/configure/Makefile b/configure/Makefile new file mode 100644 index 0000000..9254309 --- /dev/null +++ b/configure/Makefile @@ -0,0 +1,8 @@ +TOP=.. + +include $(TOP)/configure/CONFIG + +TARGETS = $(CONFIG_TARGETS) +CONFIGS += $(subst ../,,$(wildcard $(CONFIG_INSTALLS))) + +include $(TOP)/configure/RULES diff --git a/configure/RELEASE b/configure/RELEASE new file mode 100644 index 0000000..5b7db23 --- /dev/null +++ b/configure/RELEASE @@ -0,0 +1,36 @@ +# RELEASE - Location of external support modules +# +# IF YOU MAKE ANY CHANGES to this file you must subsequently +# do a "gnumake rebuild" in this application's top level +# directory. +# +# The build process does not check dependencies against files +# that are outside this application, thus you should do a +# "gnumake rebuild" in the top level directory after EPICS_BASE +# or any other external module pointed to below is rebuilt. +# +# Host- or target-specific settings can be given in files named +# RELEASE.$(EPICS_HOST_ARCH).Common +# RELEASE.Common.$(T_A) +# RELEASE.$(EPICS_HOST_ARCH).$(T_A) +# +# This file is parsed by both GNUmake and an EPICS Perl script, +# so it can ONLY contain definititions of paths to other support +# modules, variable definitions that are used in module paths, +# and include statements that pull in other RELEASE files. +# Variables may be used before their values have been set. +# Build variables that are NOT used in paths should be set in +# the CONFIG_SITE file. + +# EPICS_BASE should appear last so earlier modules can override stuff: +EPICS_BASE = /usr/lib/epics + +# Set RULES here if you want to use build rules from somewhere +# other than EPICS_BASE: +#RULES = $(MODULES)/build-rules + +# These allow developers to override the RELEASE variable settings +# without having to modify the configure/RELEASE file itself. +-include $(TOP)/../RELEASE.local +-include $(TOP)/configure/RELEASE.local + diff --git a/configure/RULES b/configure/RULES new file mode 100644 index 0000000..6d56e14 --- /dev/null +++ b/configure/RULES @@ -0,0 +1,6 @@ +# RULES + +include $(CONFIG)/RULES + +# Library should be rebuilt because LIBOBJS may have changed. +$(LIBNAME): ../Makefile diff --git a/configure/RULES.ioc b/configure/RULES.ioc new file mode 100644 index 0000000..901987c --- /dev/null +++ b/configure/RULES.ioc @@ -0,0 +1,2 @@ +#RULES.ioc +include $(CONFIG)/RULES.ioc diff --git a/configure/RULES_DIRS b/configure/RULES_DIRS new file mode 100644 index 0000000..3ba269d --- /dev/null +++ b/configure/RULES_DIRS @@ -0,0 +1,2 @@ +#RULES_DIRS +include $(CONFIG)/RULES_DIRS diff --git a/configure/RULES_TOP b/configure/RULES_TOP new file mode 100644 index 0000000..d09d668 --- /dev/null +++ b/configure/RULES_TOP @@ -0,0 +1,3 @@ +#RULES_TOP +include $(CONFIG)/RULES_TOP + diff --git a/tests/Makefile b/tests/Makefile new file mode 100644 index 0000000..3046453 --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,23 @@ +TOP=.. + +include $(TOP)/configure/CONFIG +#---------------------------------------- +# ADD MACRO DEFINITIONS AFTER THIS LINE +#============================= + +USR_CPPFLAGS += -I$(TOP)/common + +TESTPROD_HOST += testjson +testjson_SRCS += testjson +testjson_LIBS += feedcom Com +TESTS += testjson + +TESTSCRIPTS_HOST += $(TESTS:%=%.t) + +#=========================== + +include $(TOP)/configure/RULES +#---------------------------------------- +# ADD RULES AFTER THIS LINE + + diff --git a/tests/testjson.cpp b/tests/testjson.cpp new file mode 100644 index 0000000..bbb0464 --- /dev/null +++ b/tests/testjson.cpp @@ -0,0 +1,88 @@ + +#include +#include + +#include +#include + +#include + +#define testThrows(EXC, STMT) try{STMT; testFail("Statement failed to throw " #EXC " : " #STMT); }catch(std::exception& e) { testPass("Caught expected exception: %s", e.what());} + +namespace { + +void testEmpty() +{ + testDiag("testEmpty()"); + JBlob blob; + + blob.parse("{}", 2); + testOk1(blob.registers.empty()); +} + +void testSyntaxError() +{ + testDiag("testSyntaxError()"); + JBlob blob; + + testThrows(std::runtime_error, blob.parse("{foo");) + + testThrows(std::runtime_error, blob.parse("{");) + + testOk1(blob.registers.empty()); +} + +void testMyErrors() +{ + testDiag("testMyErrors()"); + JBlob blob; + + // too deep + testThrows(std::runtime_error, blob.parse("{\"A\":{\"B\":{\"C\":4}}}");) + + // zero length key + testThrows(std::runtime_error, blob.parse("{\"\":5}");) +} + +void testAST() +{ + testDiag("testAST()"); + + const char bigblob[] = "{\"J18_debug\": {\"access\": \"r\", \"addr_width\": 0, \"sign\": \"unsigned\", \"base_addr\": 63, \"data_width\": 4}}"; + + JBlob blob; + + blob.parse(bigblob); + + JBlob::const_iterator it=blob.find("J18_debug"); + testOk1(it!=blob.end()); + if(it!=blob.end()) { + const JRegister& reg = it->second; + testOk(reg.base_addr==63, "base_addr %u", (unsigned)reg.base_addr); + testOk(reg.addr_width==0, "addr_width %u", (unsigned)reg.addr_width); + testOk(reg.data_width==4, "data_width %u", (unsigned)reg.data_width); + testOk(reg.sign==JRegister::Unsigned, "sign %u", (unsigned)reg.sign); + testOk1(reg.readable); + testOk1(!reg.writable); + + } else { + testSkip(6, "failed to find"); + } +} + +} + +MAIN(testjson) +{ + testPlan(13); + try { + testEmpty(); + testSyntaxError(); + testMyErrors(); + testAST(); + + }catch(std::exception& e){ + testAbort("Uncaught exception: %s", e.what()); + } + return testDone(); +}