diff --git a/src/ioc/db/Makefile b/src/ioc/db/Makefile index 8bfec9434..324edecb5 100644 --- a/src/ioc/db/Makefile +++ b/src/ioc/db/Makefile @@ -17,6 +17,7 @@ INC += dbAccessDefs.h INC += dbCa.h INC += dbAddr.h INC += dbBkpt.h +INC += dbChannel.h INC += dbConvert.h INC += dbConvertFast.h INC += dbEvent.h @@ -53,6 +54,7 @@ DBDINC += dbCommon dbCore_SRCS += dbLock.c dbCore_SRCS += dbAccess.c dbCore_SRCS += dbBkpt.c +dbCore_SRCS += dbChannel.c dbCore_SRCS += dbConvert.c dbCore_SRCS += dbFastLinkConv.c dbCore_SRCS += dbNotify.c diff --git a/src/ioc/db/dbAccessDefs.h b/src/ioc/db/dbAccessDefs.h index 73d68c4ef..6c12d3df5 100644 --- a/src/ioc/db/dbAccessDefs.h +++ b/src/ioc/db/dbAccessDefs.h @@ -5,7 +5,7 @@ * Operator of Los Alamos National Laboratory. * EPICS BASE Versions 3.13.7 * and higher are distributed subject to a Software License Agreement found -* in file LICENSE that is included with this distribution. +* in file LICENSE that is included with this distribution. \*************************************************************************/ /* dbAccessDefs.h */ /* $Revision-Id$ */ @@ -47,7 +47,7 @@ epicsShareExtern volatile int interruptAccept; #define DBR_CTRL_DOUBLE 0x00000100 #define DBR_AL_LONG 0x00000200 #define DBR_AL_DOUBLE 0x00000400 - + /********************************************************************** * The next page contains macros for defining requests. * As an example the following defines a buffer to accept an array @@ -69,7 +69,7 @@ epicsShareExtern volatile int interruptAccept; * options = DBR_STATUS|DBR_TIME; * number_elements = 10; * rtnval=dbGetField(paddr,DBR_FLOAT,&buffer,&options,&number_elements); - * + * * When dbGetField returns: * rtnval is error status (0 means success) * options has a bit set for each option that was accepted @@ -97,7 +97,7 @@ epicsShareExtern volatile int interruptAccept; * MYBUFFER *pbuf1; * MYBUFFER buf; *************************************************************************/ - + /* Macros for defining each option */ #define DBRstatus \ epicsUInt16 status; /* alarm status */\ @@ -143,7 +143,7 @@ epicsShareExtern volatile int interruptAccept; epicsFloat64 upper_warning_limit;\ epicsFloat64 lower_warning_limit;\ epicsFloat64 lower_alarm_limit; - + /* structures for each option type */ struct dbr_status {DBRstatus}; struct dbr_units {DBRunits}; @@ -198,7 +198,7 @@ struct dbr_alDouble {DBRalDouble}; #define S_db_cntSpwn (M_dbAccess|63) /*Cannot spawn dbContTask*/ #define S_db_cntCont (M_dbAccess|65) /*Cannot resume dbContTask*/ #define S_db_noMemory (M_dbAccess|66) /*unable to allocate data structure from pool*/ - + /* Global Database Access Routines*/ #define dbGetLink(PLNK, DBRTYPE, PBUFFER, OPTIONS, NREQUEST) \ ( ( ( (PLNK)->type == CONSTANT ) && \ diff --git a/src/ioc/db/dbAddr.h b/src/ioc/db/dbAddr.h index d7fdd94e9..a91bcf76d 100644 --- a/src/ioc/db/dbAddr.h +++ b/src/ioc/db/dbAddr.h @@ -4,7 +4,7 @@ * Copyright (c) 2002 The Regents of the University of California, as * Operator of Los Alamos National Laboratory. * EPICS BASE is distributed subject to a Software License Agreement found -* in file LICENSE that is included with this distribution. +* in file LICENSE that is included with this distribution. \*************************************************************************/ #ifndef dbAddrh diff --git a/src/ioc/db/dbChannel.c b/src/ioc/db/dbChannel.c new file mode 100644 index 000000000..d58df92a0 --- /dev/null +++ b/src/ioc/db/dbChannel.c @@ -0,0 +1,246 @@ +/*************************************************************************\ +* Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne + * National Laboratory. + * EPICS BASE is distributed subject to a Software License Agreement found + * in file LICENSE that is included with this distribution. + \*************************************************************************/ + +#include +#include + +#include "cantProceed.h" +#include "dbChannel.h" +#include "dbBase.h" +#include "link.h" +#include "dbAccessDefs.h" +#include "epicsAssert.h" +#include "errlog.h" +#include "yajl_parse.h" + + +typedef struct parseContext { + dbChannel *chan; + chFilter *filter; + int depth; +} parseContext; + + +#define CALLIF(rtn) rtn && rtn + +static void chp_value(parseContext *parser, int result) +{ + chFilter *filter = parser->filter; + + if (!result || parser->depth > 0) return; + + parser->filter = NULL; + if (filter->fif->parse_end(filter)) { + ellAdd(&parser->chan->filters, &filter->node); + } +} + + +static int chp_null(void * ctx) +{ + parseContext *parser = (parseContext *) ctx; + chFilter *filter = parser->filter; + int result; + + assert(filter); + result = CALLIF(filter->fif->parse_null) (filter ); + chp_value(parser, result); + return result; +} + +static int chp_boolean(void * ctx, int boolVal) +{ + parseContext *parser = (parseContext *) ctx; + chFilter *filter = parser->filter; + int result; + + assert(filter); + result = CALLIF(filter->fif->parse_boolean) (filter , boolVal); + chp_value(parser, result); + return result; +} + +static int chp_integer(void * ctx, long integerVal) +{ + parseContext *parser = (parseContext *) ctx; + chFilter *filter = parser->filter; + int result; + + assert(filter); + result = CALLIF(filter->fif->parse_integer) (filter , integerVal); + chp_value(parser, result); + return result; +} + +static int chp_double(void * ctx, double doubleVal) +{ + parseContext *parser = (parseContext *) ctx; + chFilter *filter = parser->filter; + int result; + + assert(filter); + result = CALLIF(filter->fif->parse_double) (filter , doubleVal); + chp_value(parser, result); + return result; +} + +static int chp_string(void * ctx, const unsigned char * stringVal, + unsigned int stringLen) +{ + parseContext *parser = (parseContext *) ctx; + chFilter *filter = parser->filter; + int result; + + assert(filter); + result = CALLIF(filter->fif->parse_string) (filter , (const char *) stringVal, stringLen); + chp_value(parser, result); + return result; +} + +static int chp_start_map(void * ctx) +{ + parseContext *parser = (parseContext *) ctx; + chFilter *filter = parser->filter; + + if (!filter) { + assert(parser->depth == 0); + return 1; /* Opening '{' */ + } + + ++parser->depth; + return CALLIF(filter->fif->parse_start_map) (filter ); +} + +static int chp_map_key(void * ctx, const unsigned char * key, + unsigned int stringLen) +{ + parseContext *parser = (parseContext *) ctx; + chFilter *filter = parser->filter; + const chFilterIf *fif; + int result; + + if (filter) { + return CALLIF(filter->fif->parse_map_key) (filter , (const char *) key, stringLen); + } + + assert(parser->depth == 0); + fif = dbFindFilter((const char *) key, stringLen); + if (!fif) { + printf("Channel filter '%.*s' not found\n", stringLen, key); + return parse_stop; + } + + filter = (chFilter *) callocMustSucceed(1, sizeof(*filter), "Creating dbChannel filter"); + filter->chan = parser->chan; + filter->fif = fif; + filter->puser = NULL; + + result = fif->parse_start(filter); + if (result == parse_continue) { + parser->filter = filter; + } else { + free(filter); + } + return result; +} + +static int chp_end_map(void * ctx) +{ + parseContext *parser = (parseContext *) ctx; + chFilter *filter = parser->filter; + + if (filter) { + int result = CALLIF(filter->fif->parse_end_map) (filter ); + + if (--parser->depth == 0) chp_value(parser, result); + return result; + } + assert(parser->depth == 0); + return parse_continue; /* Final closing '}' */ +} + +static int chp_start_array(void * ctx) +{ + parseContext *parser = (parseContext *) ctx; + chFilter *filter = parser->filter; + + assert(filter); + ++parser->depth; + return CALLIF(filter->fif->parse_start_array) (filter ); +} + +static int chp_end_array(void * ctx) +{ + parseContext *parser = (parseContext *) ctx; + chFilter *filter = parser->filter; + int result; + + assert(filter); + result = CALLIF(filter->fif->parse_end_array) (filter ); + if (--parser->depth == 0) chp_value(parser, result); + return result; +} + +static const yajl_callbacks chp_callbacks = + { chp_null, chp_boolean, chp_integer, chp_double, NULL, chp_string, + chp_start_map, chp_map_key, chp_end_map, chp_start_array, chp_end_array }; + +static const yajl_parser_config chp_config = + { 0, 1 }; /* allowComments = NO , checkUTF8 = YES */ + + +void dbChannelInit(dbChannel *chan) +{ + ellInit(&chan->filters); +} +// Move ellInit into ChannelFind, rename it again? +long dbChannelFind(dbChannel *chan, const char *name) +{ + parseContext parser = + { chan, NULL, 0}; + yajl_handle yh = yajl_alloc(&chp_callbacks, &chp_config, NULL, &parser); + size_t len = strlen(name); + yajl_status status; + + status = yajl_parse(yh, (const unsigned char *) name, len); + if (status != yajl_status_ok && parser.filter) { + parser.filter->fif->parse_abort(parser.filter); + free(parser.filter); + } + yajl_free(yh); + return status == yajl_status_ok; +} + +long dbChannelOpen(dbChannel *chan) +{ + return S_db_notFound; +} + +long dbChannelClose(dbChannel *chan) +{ + return 0; +} + + +const chFilterIf * filt = NULL; +const char *fname = NULL; + +void dbRegisterFilter(const char *key, const chFilterIf *fif) +{ + /* FIXME: Add filter to a list and hash in pdbbase */ + filt = fif; + fname = key; + fif->plugin_init(); +} + +const chFilterIf * dbFindFilter(const char *key, size_t len) +{ + /* FIXME: gpHash lookup */ + if (strncmp(fname, key, len) == 0) + return filt; + return NULL; +} diff --git a/src/ioc/db/dbChannel.h b/src/ioc/db/dbChannel.h new file mode 100644 index 000000000..1508d5c1a --- /dev/null +++ b/src/ioc/db/dbChannel.h @@ -0,0 +1,83 @@ +/*************************************************************************\ +* Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne + * National Laboratory. + * EPICS BASE is distributed subject to a Software License Agreement found + * in file LICENSE that is included with this distribution. + \*************************************************************************/ + +#ifndef INC_dbChannel_H +#define INC_dbChannel_H + +#include "dbDefs.h" +#include "dbAddr.h" +#include "ellLib.h" +#include "shareLib.h" + +enum {parse_stop, parse_continue}; + +#ifdef __cplusplus +// extern "C" { +#endif + +/* A dbChannel points to a record field, and can have multiple filters */ +typedef struct dbChannel { + dbAddr addr; + ELLLIST filters; +} dbChannel; + +typedef struct chFilter chFilter; + +/* These routines must be implemented by each filter plug-in */ +typedef struct chFilterIf { + /* Plug-in management */ + void (* plugin_init)(void); + void (* plugin_exit)(void); + + /* Parsing event handlers */ + int (* parse_start)(chFilter *filter); + void (* parse_abort)(chFilter *filter); + int (* parse_end)(chFilter *filter); + + int (* parse_null)(chFilter *filter); + int (* parse_boolean)(chFilter *filter, int boolVal); + int (* parse_integer)(chFilter *filter, long integerVal); + int (* parse_double)(chFilter *filter, double doubleVal); + int (* parse_string)(chFilter *filter, const char *stringVal, + size_t stringLen); /* NB: stringVal is not zero-terminated: */ + + int (* parse_start_map)(chFilter *filter); + int (* parse_map_key)(chFilter *filter, const char *key, + size_t stringLen); + int (* parse_end_map)(chFilter *filter); + + int (* parse_start_array)(chFilter *filter); + int (* parse_end_array)(chFilter *filter); + + /* Channel operations */ + long (* channel_open)(chFilter *filter); + void (* channel_report)(chFilter *filter, int level); + /* FIXME: More routines here ... */ + void (* channel_close)(chFilter *filter); +} chFilterIf; + +/* A chFilter holds instance data for a single filter */ +struct chFilter { + ELLNODE node; + dbChannel *chan; + const chFilterIf *fif; + void *puser; +}; + +epicsShareFunc void dbChannelInit(dbChannel *chan); +epicsShareFunc long dbChannelFind(dbChannel *chan, const char *name); +epicsShareFunc long dbChannelOpen(dbChannel *chan); +epicsShareFunc long dbChannelClose(dbChannel *chan); + +epicsShareFunc void dbRegisterFilter(const char *key, const chFilterIf *fif); +epicsShareFunc const chFilterIf * dbFindFilter(const char *key, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif /* INC_dbChannel_H */ diff --git a/src/ioc/db/test/Makefile b/src/ioc/db/test/Makefile index 3ee77ca07..a1bc70d5e 100644 --- a/src/ioc/db/test/Makefile +++ b/src/ioc/db/test/Makefile @@ -20,6 +20,11 @@ TESTS += callbackTest # When we add more test programs here, this must become a vxTestHarness TESTSPEC_vxWorks = callbackTest.munch; callbackTest +TESTPROD_HOST += dbChannelTest +dbChannelTest_SRCS += dbChannelTest.c +OBJS_IOC_vxWorks += dbChannelTest +TESTS += dbChannelTest + TESTSCRIPTS_HOST += $(TESTS:%=%.t) include $(TOP)/configure/RULES diff --git a/src/ioc/db/test/dbChannelTest.c b/src/ioc/db/test/dbChannelTest.c new file mode 100644 index 000000000..31ca3180d --- /dev/null +++ b/src/ioc/db/test/dbChannelTest.c @@ -0,0 +1,170 @@ +/*************************************************************************\ +* Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne + * National Laboratory. + * EPICS BASE is distributed subject to a Software License Agreement found + * in file LICENSE that is included with this distribution. + \*************************************************************************/ + +#include "dbChannel.h" +#include "epicsUnitTest.h" +#include "testMain.h" + +/* Expected call bit definitions */ +#define e_init 0x00000001 +#define e_exit 0x00000002 +#define e_start 0x00000004 +#define e_abort 0x00000008 +#define e_end 0x00000010 +#define e_null 0x00000020 +#define e_boolean 0x00000040 +#define e_integer 0x00000080 +#define e_double 0x00000100 +#define e_string 0x00000200 +#define e_start_map 0x00000400 +#define e_map_key 0x00000800 +#define e_end_map 0x00001000 +#define e_start_array 0x00002000 +#define e_end_array 0x00004000 +#define e_open 0x00008000 +#define e_report 0x00010000 +#define e_close 0x00020000 + +unsigned int e; + + +void f_init(void) +{ + testOk(e & e_init, "plugin_init expected"); +} +void f_exit(void) +{ + testOk(e & e_exit, "plugin_exit expected"); +} + +int p_start(chFilter *filter) +{ + testOk(e & e_start, "parse_start expected"); + return parse_continue; +} + +void p_abort(chFilter *filter) +{ + testOk(e & e_abort, "parse_abort expected"); +} +int p_end(chFilter *filter) +{ + testOk(e & e_end, "parse_end expected"); + return parse_continue; +} + +int p_null(chFilter *filter) +{ + testOk(e & e_null, "parse_null expected"); + return parse_continue; +} +int p_boolean(chFilter *filter, int boolVal) +{ + testOk(e & e_boolean, "parse_boolean expected, val = %d", boolVal); + return parse_continue; +} +int p_integer(chFilter *filter, long integerVal) +{ + testOk(e & e_integer, "parse_integer expected, val = %ld", integerVal); + return parse_continue; +} +int p_double(chFilter *filter, double doubleVal) +{ + testOk(e & e_double, "parse_double expected, val = %g", doubleVal); + return parse_continue; +} +int p_string(chFilter *filter, const char *stringVal, size_t stringLen) +{ + testOk(e & e_string, "parse_string expected, val = '%.*s'", stringLen, stringVal); + return parse_continue; +} + +int p_start_map(chFilter *filter) +{ + testOk(e & e_start_map, "parse_start_map expected"); + return parse_continue; +} +int p_map_key(chFilter *filter, const char *key, size_t stringLen) +{ + testOk(e & e_map_key, "parse_map_key expected, key = '%.*s'", stringLen, key); + return parse_continue; +} +int p_end_map(chFilter *filter) +{ + testOk(e & e_end_map, "parse_end_map expected"); + return parse_continue; +} + +int p_start_array(chFilter *filter) +{ + testOk(e & e_start_array, "parse_start_array expected"); + return parse_continue; +} +int p_end_array(chFilter *filter) +{ + testOk(e & e_end_array, "parse_end_array expected"); + return parse_continue; +} + +long c_open(chFilter *filter) +{ + testOk(e & e_open, "channel_open expected"); + return 0; +} +void c_report(chFilter *filter, int level) +{ + testOk(e & e_report, "channel_report expected, level = %d", level); +} +void c_close(chFilter *filter) +{ + testOk(e & e_close, "channel_close expected"); +} + +chFilterIf testIf = + { f_init, f_exit, p_start, p_abort, p_end, p_null, p_boolean, + p_integer, p_double, p_string, p_start_map, p_map_key, p_end_map, + p_start_array, p_end_array, c_open, c_report, c_close }; + +chFilterIf scalarIf = + { f_init, f_exit, p_start, p_abort, p_end, p_null, p_boolean, + p_integer, p_double, p_string, NULL, NULL, NULL, NULL, NULL, c_open, + c_report, c_close }; + +MAIN(dbChannelTest) +{ + dbChannel ch; + + testPlan(33); + dbChannelInit(&ch); + + e = 0; + testOk1(dbChannelFind(&ch, "{}")); + + e = e_init; + dbRegisterFilter("test", &testIf); + + e = e_start | e_null | e_end; + testOk1(dbChannelFind(&ch, "{\"test\":null}")); + + e = e_start | e_start_array | e_boolean | e_integer | e_end_array | e_end; + testOk1(dbChannelFind(&ch, "{\"test\":[true,1]}")); + + e = e_start | e_start_map | e_map_key | e_double | e_string | e_end_map | e_end; + testOk1(dbChannelFind(&ch, "{\"test\":{\"a\":2.7183,\"b\":\"c\"}}")); + + e = e_init; + dbRegisterFilter("scalar", &scalarIf); + + e = e_start | e_null | e_end; + testOk1(dbChannelFind(&ch, "{\"scalar\":null}")); + + e = e_start | e_abort; + testOk1(!dbChannelFind(&ch, "{\"scalar\":[null]}")); + testOk1(!dbChannelFind(&ch, "{\"scalar\":{}}")); + + return testDone(); +}