JSON Links implementation
The lnkConst.c implementation is not yet complete, no support for arrays of strings (JMOP). Link error messages should display their record & field name, which is not yet possible. The ability to embed links as parameters to other link types is not complete yet; this will be required for the calc link type. This code currently passes all existing tests, but additional tests are needed for the new functionality.
This commit is contained in:
@@ -11,7 +11,7 @@ SRC_DIRS += $(STDDIR)/link
|
||||
|
||||
DBD += links.dbd
|
||||
|
||||
# dbRecStd_SRCS += ...
|
||||
dbRecStd_SRCS += lnkConst.c
|
||||
|
||||
HTMLS += links.html
|
||||
|
||||
|
||||
+37
-18
@@ -1,7 +1,7 @@
|
||||
=head1 Link Types
|
||||
=head1 JSON Links
|
||||
|
||||
Links are an extensible mechanism for adding new kinds of database link,
|
||||
using JSON for link addresses.
|
||||
JSON Links are an extensible mechanism for adding new kinds of database link,
|
||||
using JSON for the link address.
|
||||
The following link types are available in this release:
|
||||
|
||||
=over
|
||||
@@ -12,6 +12,8 @@ The following link types are available in this release:
|
||||
|
||||
=item * L<Channel Access|/"Channel Access Link ca">
|
||||
|
||||
=item * L<Calc|/"Calculation Link calc">
|
||||
|
||||
=back
|
||||
|
||||
=head2 Using Links
|
||||
@@ -21,35 +23,36 @@ must appear inside a pair of braces C< {} > expressed as a JSON
|
||||
(L<JavaScript Object Notation|http://www.json.org/>) object, which allows link
|
||||
parameters to be defined as needed.
|
||||
|
||||
Note that due to the required presence of the double-quote characters in the
|
||||
JSON strings in a link field value string in a database file, it will usually
|
||||
be necessary to escape all double-quote characters in the JSON object by
|
||||
preceding them with a backslash C< \ > character.
|
||||
Database configuration tools that support this link mechanism must be careful
|
||||
to handle these escapes correctly on reading and writing string values from/to
|
||||
a .db file.
|
||||
|
||||
=head2 Filter Reference
|
||||
=head2 Link Type Reference
|
||||
|
||||
=cut
|
||||
|
||||
link(const,lsetConst)
|
||||
link(const, lnkConstIf)
|
||||
|
||||
=head3 Constant Link C<"const">
|
||||
|
||||
...
|
||||
Constant links provide one or more values at link initalization time, but do not return
|
||||
any data when their C<getValue()> routine is called. Most record types support the use of
|
||||
constant links by calling C<recGblInitConstantLink()> at initialization, which results in
|
||||
the constant value being loaded into the target field at that time.
|
||||
|
||||
=head4 Parameters
|
||||
|
||||
...
|
||||
A const link takes a parameter which may be an integer, double or string, or an array of
|
||||
those types. If an array contains both integers and double values the integers will be
|
||||
promoted to doubles. Mixing strings and numbers in an array will result in an error.
|
||||
|
||||
=head4 Example
|
||||
=head4 Examples
|
||||
|
||||
...
|
||||
{const: 3.14159265358979}
|
||||
{const: "Pi"}
|
||||
{const: [1, 2.718281828459, 3.14159265358979]}
|
||||
{const: ["One", "e", "Pi"]}
|
||||
|
||||
=cut
|
||||
|
||||
link(db,lsetDatabase)
|
||||
#link(db, lnkDbIf)
|
||||
|
||||
=head3 Database Link C<"db">
|
||||
|
||||
@@ -65,7 +68,7 @@ link(db,lsetDatabase)
|
||||
|
||||
=cut
|
||||
|
||||
link(ca,lsetChannelAccess)
|
||||
#link(ca, lnkCaIf)
|
||||
|
||||
=head3 Channel Access Link C<"ca">
|
||||
|
||||
@@ -90,3 +93,19 @@ link(ca,lsetChannelAccess)
|
||||
...
|
||||
|
||||
=cut
|
||||
|
||||
#link(calc, lnkCalcIf)
|
||||
|
||||
=head3 Calculation Link C<"calc">
|
||||
|
||||
...
|
||||
|
||||
=head4 Parameters
|
||||
|
||||
...
|
||||
|
||||
=head4 Example
|
||||
|
||||
...
|
||||
|
||||
=cut
|
||||
|
||||
@@ -0,0 +1,412 @@
|
||||
/*************************************************************************\
|
||||
* Copyright (c) 2016 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.
|
||||
\*************************************************************************/
|
||||
/* lnkConst.c */
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "dbDefs.h"
|
||||
#include "errlog.h"
|
||||
#include "epicsAssert.h"
|
||||
#include "epicsTypes.h"
|
||||
#include "dbAccessDefs.h"
|
||||
#include "dbConvertFast.h"
|
||||
#include "dbLink.h"
|
||||
#include "dbJLink.h"
|
||||
#include "epicsExport.h"
|
||||
|
||||
|
||||
/* Change 'undef' to 'define' to turn on debug statements: */
|
||||
#undef DEBUG_LINK
|
||||
|
||||
#ifdef DEBUG_LINK
|
||||
int lnkConstDebug = 10;
|
||||
# define IFDEBUG(n) \
|
||||
if (lnkConstDebug >= n) /* block or statement */
|
||||
#else
|
||||
# define IFDEBUG(n) \
|
||||
if(0) /* Compiler will elide the block or statement */
|
||||
#endif
|
||||
|
||||
typedef long (*FASTCONVERT)();
|
||||
|
||||
typedef struct clink {
|
||||
struct jlink jlink; /* embedded */
|
||||
int nElems;
|
||||
enum {s0, si32, sf64, sc40, a0, ai32, af64, ac40} type;
|
||||
union {
|
||||
epicsInt32 scalar_integer;
|
||||
epicsFloat64 scalar_double;
|
||||
char * scalar_string;
|
||||
void *pmem;
|
||||
epicsInt32 *pintegers;
|
||||
epicsFloat64 *pdoubles;
|
||||
char **pstrings;
|
||||
} value;
|
||||
} clink;
|
||||
|
||||
static lset lnkConst_lset;
|
||||
|
||||
|
||||
/*************************** jlif Routines **************************/
|
||||
|
||||
static jlink* lnkConst_alloc(struct link *plink) {
|
||||
clink *clink = calloc(1, sizeof(struct clink));
|
||||
|
||||
IFDEBUG(10)
|
||||
printf("lnkConst_alloc()\n");
|
||||
|
||||
clink->type = s0;
|
||||
clink->nElems = 0;
|
||||
clink->value.pmem = NULL;
|
||||
|
||||
IFDEBUG(10)
|
||||
printf("lnkConst_alloc -> const@%p\n", clink);
|
||||
|
||||
return &clink->jlink;
|
||||
}
|
||||
|
||||
static void lnkConst_free(jlink *pjlink) {
|
||||
clink *clink = CONTAINER(pjlink, struct clink, jlink);
|
||||
|
||||
IFDEBUG(10)
|
||||
printf("lnkConst_free(const@%p)\n", pjlink);
|
||||
|
||||
switch (clink->type) {
|
||||
int i;
|
||||
case ac40:
|
||||
for (i=0; i<clink->nElems; i++)
|
||||
free(clink->value.pstrings[i]);
|
||||
/* fall through */
|
||||
case ai32:
|
||||
case af64:
|
||||
free(clink->value.pmem);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
free(clink);
|
||||
}
|
||||
|
||||
static jlif_result lnkConst_integer(jlink *pjlink, long num) {
|
||||
clink *clink = CONTAINER(pjlink, struct clink, jlink);
|
||||
|
||||
IFDEBUG(10)
|
||||
printf("lnkConst_integer(const@%p, %ld)\n", pjlink, num);
|
||||
|
||||
switch (clink->type) {
|
||||
void *buf;
|
||||
int nElems;
|
||||
|
||||
case s0:
|
||||
clink->nElems = 1;
|
||||
clink->type = si32;
|
||||
clink->value.scalar_integer = num;
|
||||
break;
|
||||
|
||||
case a0:
|
||||
clink->type = ai32;
|
||||
/* fall thorough */
|
||||
case ai32:
|
||||
nElems = clink->nElems + 1;
|
||||
buf = realloc(clink->value.pmem, nElems * sizeof(epicsInt32));
|
||||
if (!buf) break;
|
||||
clink->value.pmem = buf;
|
||||
clink->value.pintegers[clink->nElems] = num;
|
||||
clink->nElems = nElems;
|
||||
break;
|
||||
|
||||
case af64:
|
||||
nElems = clink->nElems + 1;
|
||||
buf = realloc(clink->value.pmem, nElems * sizeof(epicsFloat64));
|
||||
if (!buf) break;
|
||||
clink->value.pmem = buf;
|
||||
clink->value.pdoubles[clink->nElems++] = num;
|
||||
break;
|
||||
|
||||
case ac40:
|
||||
errlogPrintf("lnkConst: Mixed data types in array\n");
|
||||
/* FIXME ??? */
|
||||
default:
|
||||
return jlif_stop;
|
||||
}
|
||||
return jlif_continue;
|
||||
}
|
||||
|
||||
static jlif_result lnkConst_boolean(jlink *pjlink, int val) {
|
||||
IFDEBUG(10)
|
||||
printf("lnkConst_boolean(const@%p, %d)\n", pjlink, val);
|
||||
|
||||
return lnkConst_integer(pjlink, val);
|
||||
}
|
||||
|
||||
static jlif_result lnkConst_double(jlink *pjlink, double num) {
|
||||
clink *clink = CONTAINER(pjlink, struct clink, jlink);
|
||||
|
||||
IFDEBUG(10)
|
||||
printf("lnkConst_double(const@%p, %g)\n", pjlink, num);
|
||||
|
||||
switch (clink->type) {
|
||||
epicsFloat64 *f64buf;
|
||||
int nElems, i;
|
||||
|
||||
case s0:
|
||||
clink->nElems = 1;
|
||||
clink->type = sf64;
|
||||
clink->value.scalar_double = num;
|
||||
break;
|
||||
|
||||
case a0:
|
||||
clink->type = af64;
|
||||
/* fall thorough */
|
||||
case af64:
|
||||
nElems = clink->nElems + 1;
|
||||
f64buf = realloc(clink->value.pmem, nElems * sizeof(epicsFloat64));
|
||||
if (!f64buf) break;
|
||||
clink->value.pdoubles = f64buf;
|
||||
clink->value.pdoubles[clink->nElems++] = num;
|
||||
break;
|
||||
|
||||
case ai32: /* promote earlier ai32 values to af64 */
|
||||
f64buf = calloc(clink->nElems + 1, sizeof(epicsFloat64));
|
||||
if (!f64buf) break;
|
||||
for (i = 0; i < clink->nElems; i++) {
|
||||
f64buf[i] = clink->value.pintegers[i];
|
||||
}
|
||||
f64buf[clink->nElems++] = num;
|
||||
free(clink->value.pmem);
|
||||
clink->value.pdoubles = f64buf;
|
||||
clink->type = af64;
|
||||
break;
|
||||
|
||||
case ac40:
|
||||
errlogPrintf("lnkConst: Mixed data types in array\n");
|
||||
/* FIXME ??? */
|
||||
default:
|
||||
return jlif_stop;
|
||||
}
|
||||
|
||||
return jlif_continue;
|
||||
}
|
||||
|
||||
static jlif_result lnkConst_string(jlink *pjlink, const char *val, size_t len) {
|
||||
clink *clink = CONTAINER(pjlink, struct clink, jlink);
|
||||
|
||||
IFDEBUG(10)
|
||||
printf("lnkConst_string(const@%p, \"%.*s\")\n", clink, (int) len, val);
|
||||
|
||||
if (len > MAX_STRING_SIZE)
|
||||
len = MAX_STRING_SIZE;
|
||||
|
||||
if (clink->type == s0) {
|
||||
char *buf = malloc(len + 1);
|
||||
|
||||
if (!buf)
|
||||
return jlif_stop;
|
||||
|
||||
strncpy(buf, val, len);
|
||||
buf[len] = '\0';
|
||||
|
||||
clink->nElems = 1;
|
||||
clink->type = sc40;
|
||||
clink->value.scalar_string = buf;
|
||||
return jlif_continue;
|
||||
}
|
||||
/* FIXME Implement a0 and ac40 */
|
||||
/* FIXME How to handle ai32 and af64? */
|
||||
errlogPrintf("lnkConst: Mixed data types in array\n");
|
||||
|
||||
return jlif_stop;
|
||||
}
|
||||
|
||||
static jlif_result lnkConst_start_array(jlink *pjlink) {
|
||||
clink *clink = CONTAINER(pjlink, struct clink, jlink);
|
||||
|
||||
IFDEBUG(10)
|
||||
printf("lnkConst_start_array(const@%p)\n", pjlink);
|
||||
|
||||
if (clink->type != s0) {
|
||||
errlogPrintf("lnkConst: Embedded array value\n");
|
||||
return jlif_stop;
|
||||
}
|
||||
|
||||
clink->type = a0;
|
||||
return jlif_continue;
|
||||
}
|
||||
|
||||
static jlif_result lnkConst_end_array(jlink *pjlink) {
|
||||
IFDEBUG(10)
|
||||
printf("lnkConst_end_array(const@%p)\n", pjlink);
|
||||
|
||||
return jlif_continue;
|
||||
}
|
||||
|
||||
static struct lset* lnkConst_get_lset(const jlink *pjlink) {
|
||||
IFDEBUG(10)
|
||||
printf("lnkConst_get_lset(const@%p)\n", pjlink);
|
||||
|
||||
return &lnkConst_lset;
|
||||
}
|
||||
|
||||
static void lnkConst_report(const jlink *pjlink) {
|
||||
clink *clink = CONTAINER(pjlink, struct clink, jlink);
|
||||
|
||||
IFDEBUG(10)
|
||||
printf("lnkConst_report(const@%p)\n", clink);
|
||||
|
||||
/* FIXME Implement! */
|
||||
}
|
||||
|
||||
/*************************** lset Routines **************************/
|
||||
|
||||
static long lnkConst_loadScalar(struct link *plink, short dbrType, void *pbuffer)
|
||||
{
|
||||
clink *clink = CONTAINER(plink->value.json.jlink, struct clink, jlink);
|
||||
long status;
|
||||
|
||||
IFDEBUG(10)
|
||||
printf("lnkConst_loadScalar(const@%p, %d, %p)\n",
|
||||
clink, dbrType, pbuffer);
|
||||
|
||||
switch (clink->type) {
|
||||
case si32:
|
||||
status = dbFastPutConvertRoutine[DBF_LONG][dbrType]
|
||||
(&clink->value.scalar_integer, pbuffer, NULL);
|
||||
break;
|
||||
|
||||
case sf64:
|
||||
status = dbFastPutConvertRoutine[DBF_DOUBLE][dbrType]
|
||||
(&clink->value.scalar_double, pbuffer, NULL);
|
||||
break;
|
||||
|
||||
case sc40:
|
||||
status = dbFastPutConvertRoutine[DBF_STRING][dbrType]
|
||||
(clink->value.scalar_string, pbuffer, NULL);
|
||||
break;
|
||||
|
||||
case ai32:
|
||||
status = dbFastPutConvertRoutine[DBF_LONG][dbrType]
|
||||
(clink->value.pintegers, pbuffer, NULL);
|
||||
break;
|
||||
|
||||
case af64:
|
||||
status = dbFastPutConvertRoutine[DBF_DOUBLE][dbrType]
|
||||
(clink->value.pdoubles, pbuffer, NULL);
|
||||
break;
|
||||
|
||||
default:
|
||||
status = S_db_badField;
|
||||
break;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static long lnkConst_loadArray(struct link *plink, short dbrType, void *pbuffer,
|
||||
long *pnReq)
|
||||
{
|
||||
clink *clink = CONTAINER(plink->value.json.jlink, struct clink, jlink);
|
||||
short dbrSize = dbValueSize(dbrType);
|
||||
char *pdest = pbuffer;
|
||||
int nElems = clink->nElems;
|
||||
FASTCONVERT conv;
|
||||
long status;
|
||||
|
||||
IFDEBUG(10)
|
||||
printf("lnkConst_loadArray(const@%p, %d, %p, (%ld))\n",
|
||||
clink, dbrType, pbuffer, *pnReq);
|
||||
|
||||
if (nElems > *pnReq)
|
||||
nElems = *pnReq;
|
||||
|
||||
switch (clink->type) {
|
||||
int i;
|
||||
|
||||
case si32:
|
||||
status = dbFastPutConvertRoutine[DBF_LONG][dbrType]
|
||||
(&clink->value.scalar_integer, pdest, NULL);
|
||||
break;
|
||||
|
||||
case sf64:
|
||||
status = dbFastPutConvertRoutine[DBF_DOUBLE][dbrType]
|
||||
(&clink->value.scalar_double, pdest, NULL);
|
||||
break;
|
||||
|
||||
case sc40:
|
||||
status = dbFastPutConvertRoutine[DBF_STRING][dbrType]
|
||||
(clink->value.scalar_string, pbuffer, NULL);
|
||||
break;
|
||||
|
||||
case ai32:
|
||||
conv = dbFastPutConvertRoutine[DBF_LONG][dbrType];
|
||||
for (i = 0; i < nElems; i++) {
|
||||
conv(&clink->value.pintegers[i], pdest, NULL);
|
||||
pdest += dbrSize;
|
||||
}
|
||||
status = 0;
|
||||
break;
|
||||
|
||||
case af64:
|
||||
conv = dbFastPutConvertRoutine[DBF_DOUBLE][dbrType];
|
||||
for (i = 0; i < nElems; i++) {
|
||||
conv(&clink->value.pdoubles[i], pdest, NULL);
|
||||
pdest += dbrSize;
|
||||
}
|
||||
status = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
status = S_db_badField;
|
||||
}
|
||||
*pnReq = nElems;
|
||||
return status;
|
||||
}
|
||||
|
||||
static long lnkConst_getNelements(const struct link *plink, long *nelements)
|
||||
{
|
||||
IFDEBUG(10)
|
||||
printf("lnkConst_getNelements(const@%p, (%ld))\n",
|
||||
plink->value.json.jlink, *nelements);
|
||||
|
||||
*nelements = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long lnkConst_getValue(struct link *plink, short dbrType, void *pbuffer,
|
||||
epicsEnum16 *pstat, epicsEnum16 *psevr, long *pnRequest)
|
||||
{
|
||||
IFDEBUG(10)
|
||||
printf("lnkConst_loadScalar(const@%p, %d, %p, ... (%ld))\n",
|
||||
plink->value.json.jlink, dbrType, pbuffer, *pnRequest);
|
||||
|
||||
if (pnRequest)
|
||||
*pnRequest = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/************************* Interface Tables *************************/
|
||||
|
||||
static lset lnkConst_lset = {
|
||||
1, 0, /* Constant, not Volatile */
|
||||
NULL, lnkConst_loadScalar, lnkConst_loadArray, NULL,
|
||||
NULL, lnkConst_getNelements, lnkConst_getValue,
|
||||
NULL, NULL, NULL,
|
||||
NULL, NULL,
|
||||
NULL, NULL,
|
||||
NULL, NULL,
|
||||
NULL
|
||||
};
|
||||
|
||||
static jlif lnkConstIf = {
|
||||
"const", lnkConst_alloc, lnkConst_free, NULL,
|
||||
NULL, lnkConst_boolean, lnkConst_integer, lnkConst_double, lnkConst_string,
|
||||
NULL, NULL, NULL, lnkConst_start_array, lnkConst_end_array,
|
||||
NULL, lnkConst_get_lset, lnkConst_report
|
||||
};
|
||||
epicsExportAddress(jlif, lnkConstIf);
|
||||
|
||||
Reference in New Issue
Block a user