blob: 865856a674b09c842392117e3d6147a949360b0d [file] [log] [blame]
// vi: ts=4 sw=4 noet:
/*
==================================================================================
Copyright (c) 2020 Nokia
Copyright (c) 2020 AT&T Intellectual Property.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================================
*/
/*
Mnemonic: jwrapper.c
Abstract: A wrapper interface to the jsmn library which makes it a bit easier
to use. Parses a json string capturing the contents in a symtab.
This code is based on the AT&T VFd open source library available
on github.com/att/vfd. The changes are mostly to port to the
RMR version of symtab from VFd's version, however the parsing loop
was considerably refactored to eliminate the bad practices in the
original code, and to squelch the sonar complaint about a potential,
though not valid, NPE.
Author: E. Scott Daniels
Date: 26 June 2020
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#ifndef DEBUG
#define DEBUG 0
#endif
#define JSMN_STATIC 1 // jsmn no longer builds into a library; this pulls as static functions
#include <jsmn.h>
#include <rmr/rmr_symtab.h>
extern void jw_nuke( void* st );
#define JSON_SYM_NAME "_jw_json_string"
#define MAX_THINGS 1024 * 4 // max objects/elements
#define PT_UNKNOWN 0 // primative types; unknown for non prim
#define PT_VALUE 1
#define PT_BOOL 2
#define PT_NULL 3
#define PT_STRING 4
#define OBJ_SPACE 1 // space in the symbol table where json bits are stashed
#define MGT_SPACE 2 // non-json objects in the hash (management things)
// ---------------------------------------------------------------------------------------
/*
This is what we will manage in the symtab. Right now we store all values (primatives)
as double, but we could be smarter about it and look for a decimal. Unsigned and
differences between long, long long etc are tough.
*/
typedef struct jthing {
int jsmn_type; // propigated type from jsmn (jsmn constants)
int prim_type; // finer grained primative type (bool, null, value)
int nele; // number of elements if applies
union {
double fv;
void *pv;
} v;
} jthing_t;
/*
Given the json token, 'extract' the element by marking the end with a
nil character, and returning a pointer to the start. We do this so that
we don't create a bunch of small buffers that must be found and freed; we
can just release the json string and we'll be done (read won't leak).
*/
static char* extract( char* buf, const jsmntok_t *jtoken ) {
buf[jtoken->end] = 0;
return &buf[jtoken->start];
}
#if DEBUG > 0
/*
For debugging we should NOT extract as that disrupts the "flow" by
adding a nil before the parser gets a chance to actually parse an
object.
*/
static void pull( const char* dest, char* src, jsmntok_t* jtoken ) {
memcpy( dest, &src[jtoken->start], jtoken->end - jtoken->start );
dest[jtoken->end - jtoken->start] = 0;
}
#endif
/*
create a new jthing and add a reference to it in the symbol table st.
sets the number of elements to 1 by default.
*/
static jthing_t *mk_thing( void *st, char *name, int jsmn_type ) {
jthing_t *jtp = NULL;
if( st != NULL && name != NULL ) {
if( (jtp = (jthing_t *) malloc( sizeof( *jtp ) )) != NULL ) {
if( DEBUG > 1 ) fprintf( stderr, "<DBUG> jwrapper adding: %s type=%d\n", name, jsmn_type );
jtp->jsmn_type = jsmn_type;
jtp->prim_type = PT_UNKNOWN; // caller must set this
jtp->nele = 1;
jtp->v.fv = 0;
rmr_sym_put( st, name, OBJ_SPACE, jtp );
} else {
fprintf( stderr, "[WARN] jwrapper: unable to create '%s' type=%d\n", name, jsmn_type );
}
}
return jtp;
}
/*
Find the named array. Returns a pointer to the jthing that represents
the array (type, size and pointer to actual array of jthings).
Returns nil pointer if the named thing isn't there or isn't an array.
*/
static jthing_t* suss_array( void* st, const char* name ) {
jthing_t* jtp = NULL; // thing that is referenced by the symtab
if( st != NULL && name != NULL ) {
if( (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
jtp = jtp->jsmn_type == JSMN_ARRAY ? jtp : NULL;
}
}
return jtp;
}
/*
Suss an array from the hash and return the ith element.
*/
static jthing_t* suss_element( void* st, const char* name, int idx ) {
jthing_t* jtp; // thing that is referenced by the symtab
jthing_t* jarray;
jthing_t* rv = NULL;
if( (jtp = suss_array( st, name )) != NULL // have pointer
&& idx >= 0 // and in range
&& idx < jtp->nele
&& jtp->v.pv != NULL ) { // and exists
jarray = jtp->v.pv;
rv = &jarray[idx];
}
return rv;
}
/*
Invoked for each thing in the symtab; we free the things that actually point to
allocated data (e.g. arrays) and recurse to handle objects.
Only the element passed is used, but this is a prototype which is required by
the RMR symtab implementaion, so we play games in the code to keep sonar quiet.
Sonar will grumble aobut the parms needing to be marked const. Until RMR changes
the signature we can't and sonar will just have to unbunch its knickers.
*/
static void nix_things( void* st, void* se, const char* name, void* ele, void *data ) {
jthing_t* j;
jthing_t* jarray;
int i;
if( (j = (jthing_t *) ele) == NULL ) {
if( st == NULL && name == NULL && se == NULL && data == NULL ) { // these are ignored, but this keeps sonar from screaming bug
fprintf( stderr, "jwrapper: nix_thigs: all params were nil\n" );
}
return;
}
switch( j->jsmn_type ) {
case JSMN_ARRAY:
if( (jarray = (jthing_t *) j->v.pv) != NULL ) {
for( i = 0; i < j->nele; i++ ) { // must look for embedded objects
if( jarray[i].jsmn_type == JSMN_OBJECT ) {
jw_nuke( jarray[i].v.pv );
jarray[i].jsmn_type = JSMN_UNDEFINED; // prevent accidents
}
}
free( j->v.pv ); // must free the array (arrays aren't nested, so all things in the array don't reference allocated mem)
free( j );
}
break;
case JSMN_OBJECT: // delete the sub symtab
jw_nuke( j->v.pv );
j->jsmn_type = JSMN_UNDEFINED; // prevent a double free
free( j );
break;
case JSMN_STRING:
case JSMN_PRIMITIVE:
free( j );
break;
default:
break; // more unneeded games to keep sonar complaints away
}
}
/*
Nix non-json things that are also in the hash.
Silly games played to keep sonar from complaining. This is driven by RMR
symtab code which defines the set of params and we use what we need.
Sonar will grumble aobut the parms needing to be marked const. Until RMR changes
the signature we can't and sonar will just have to unbunch its knickers.
*/
static void nix_mgt( void* st, void* se, const char* name, void* ele, void *data ) {
if( ele == NULL ) {
if( st == NULL && name == NULL && se == NULL && data == NULL ) { // these are ignored, but this keeps sonar from screaming bug
fprintf( stderr, "jwrapper: dump_things: all params were nil\n" );
}
return;
}
free( ele );
}
/*
Invoked for each thing and prints what we can to stderr.
Most parms ignored, but symtab code in RMR defines the prototype so they are required.
Sonar will grumble aobut the parms needing to be marked const. Until RMR changes
the signature we can't and sonar will just have to unbunch its knickers.
*/
static void dump_things( void* st, void* se, const char* name, void* ele, void *data ) {
const jthing_t* j;
j = (jthing_t *) ele;
if( j ) {
fprintf( stderr, "<DBUG> jwrapper: element '%s' has ptype %d, jsmn type %d\n", name, j->prim_type, j->jsmn_type );
} else {
if( st == NULL && name == NULL && se == NULL && data == NULL ) { // these are ignored, but this keeps sonar from screaming bug
fprintf( stderr, "jwrapper: dump_things: all params were nil\n" );
}
fprintf( stderr, "<DBUG> jwrapper: element has no data: '%s'\n", name );
}
}
/*
Real work for parsing an object ({...}) from the json. Called by jw_new() and
recurses to deal with sub-objects.
The jsmn parser returns an array of tokens which are expected to be name/data
pair. Name must be jsmn string type or we will fail the parse. Data can be
any primative type (int,bool) or a complex type (array, object). An array
will allow an object as an element, but NOT a nested array; that will cause us
to report a failure.
*/
void* parse_jobject( void* st, char *json, char* prefix ) {
jthing_t *jtp; // json thing that we just created
int i;
int n;
char *name; // name in the json
char *data; // data string from the json
jthing_t* jarray; // array of jthings we'll coonstruct
int size;
int njtokens; // tokens actually sussed out
jsmn_parser jp; // 'parser' object
jsmntok_t *jtokens; // pointer to tokens returned by the parser
char pname[1024]; // name with prefix
char* dstr; // dup'd string
int step = 0; // parsing step value to skip tokens picked up
int data_idx; // index into tokens for the next bit of data
int di; // data skip index for object hopping
int stop; // loop index termination point
jsmn_init( &jp ); // does this have a failure mode?
if( DEBUG ) fprintf( stderr, "<DBUG>> ================= recursion begins =======================\n" );
jtokens = (jsmntok_t *) malloc( sizeof( *jtokens ) * MAX_THINGS );
if( jtokens == NULL ) {
fprintf( stderr, "[CRI] jwrapper: cannot allocate tokens array\n" );
return NULL;
}
if( DEBUG > 1 ) fprintf( stderr, "<DBUG> parsing(%s)\n", json );
njtokens = jsmn_parse( &jp, json, strlen( json ), jtokens, MAX_THINGS );
if( DEBUG ) fprintf( stderr, "<DBUG>> tokens=%d\n", njtokens );
#if DEBUG > 1
for( di = 0; di < njtokens; di++ ) {
wbuf[4096];
pull( wbuf, json, &jtokens[di] );
fprintf( stderr, "<DBUG> [%d] t=%d start=%d end=%d (%s)\n",
di, jtokens[di].type, jtokens[di].start, jtokens[di].end, wbuf );
}
}
#endif
if( jtokens[0].type != JSMN_OBJECT ) { // if it's not an object then we can't parse it.
fprintf( stderr, "[WARN] jwrapper: badly formed json; initial opening bracket ({) not detected\n" );
rmr_sym_free( st );
free( jtokens );
return NULL;
}
i = 1;
while( i < njtokens ) { // a final name without data will end up being silently skipped
step = 2; // will always need to step over name and data; object and array will add to this
if( jtokens[i].type != JSMN_STRING ) {
fprintf( stderr, "[WARN] jwrapper: badly formed json [%d]; expected name (string) found type=%d %s\n",
i, jtokens[i].type, extract( json, &jtokens[i] ) );
rmr_sym_free( st );
free( jtokens );
return NULL;
}
name = extract( json, &jtokens[i] );
if( DEBUG ) fprintf( stderr, "\n<DBUG> [%d] parsing %s t=%d\n", i, name, jtokens[i].type );
if( *prefix != 0 ) {
snprintf( pname, sizeof( pname ), "%s.%s", prefix, name );
name = pname;
}
size = jtokens[i].size;
data_idx = i + 1; // at data token
switch( jtokens[data_idx].type ) {
case JSMN_OBJECT: // save object in two ways: as an object 'blob' and in the current symtab using name as a base (original)
if( DEBUG ) fprintf( stderr, "<DBUG> [%d] %s (object) has %d things\n", data_idx, name, jtokens[data_idx].size );
if( (jtp = mk_thing( st, name, jtokens[data_idx].type )) != NULL ) { // create thing and reference it
jtp->v.pv = rmr_sym_alloc( 255 ); // object is just a blob; make it
}
if( jtp != NULL && jtp->v.pv != NULL ) { // double check allows for better coverage and keeps sonar happy
dstr = strdup( extract( json, &jtokens[data_idx] ) );
rmr_sym_put( jtp->v.pv, JSON_SYM_NAME, MGT_SPACE, dstr ); // must stash json so it is freed during nuke()
parse_jobject( jtp->v.pv, dstr, "" ); // recurse across the object and build a new symtab
stop = jtokens[data_idx].end; // calc step; must loop as it's NOT just simple size*2 b/c nested objects are var length
if( DEBUG ) fprintf( stderr, "<DBUG> computing step over object elements\n" );
for( di = data_idx+1; di < njtokens-1 && jtokens[di].end < stop ; di++ ) {
step++;
if( DEBUG ) {
fprintf( stderr, "\tskip: [%d] object element start=%d end=%d (%s)\n",
di, jtokens[di].start, jtokens[di].end, extract( json, &jtokens[di]) );
}
}
}
if( DEBUG ) fprintf( stderr, "<DBUG> %s object finished step= %d\n", name, step );
break;
case JSMN_ARRAY:
size = jtokens[data_idx].size; // size is burried here, NOT with the name
if( DEBUG ) fprintf( stderr, "<DBUG> %s is array size=%d\n", name, size );
jtp = mk_thing( st, name, jtokens[data_idx].type );
if( jtp == NULL || data_idx + size > njtokens ) {
fprintf( stderr, "[WARN] jwrapper: alloc, or size, error processing element [%d] in json; size=%d ntok=%d\n",
i, size, njtokens );
rmr_sym_free( st );
free( jtokens );
return NULL;
}
data_idx++; // skip first ele; it is the whole array string which I don't grock the need for, but it's their code...
jarray = jtp->v.pv = (jsmntok_t *) malloc( sizeof( *jarray ) * size ); // allocate the array
memset( jarray, 0, sizeof( *jarray ) * size );
jtp->nele = size;
for( n = 0; n < size; n++ ) { // for each array element
step = 1; // array elements aren't named, so just one initial step
if( DEBUG ) fprintf( stderr, "\n<DBUG> parsing [%d] %s element %d of %d\n", data_idx, name, n, size );
jarray[n].prim_type = PT_UNKNOWN; // initially mark as unknown
switch( jtokens[data_idx].type ) {
case JSMN_OBJECT:
jarray[n].v.pv = rmr_sym_alloc( 255 );
if( DEBUG ) fprintf( stderr, "<DBUG> %s[%d] is object size=%d\n", name, n, jtokens[data_idx].size );
if( jarray[n].v.pv != NULL ) {
jarray[n].jsmn_type = JSMN_OBJECT;
parse_jobject( jarray[n].v.pv, extract( json, &jtokens[data_idx] ), "" ); // recurse across the object and build a new symtab
stop = jtokens[data_idx].end; // same as before, must manually calc step
if( DEBUG ) fprintf( stderr, "<DBUG> computing step over object elements start=%d stop=%d\n", data_idx+1, stop );
for( di = data_idx+1; di < njtokens-1 && jtokens[di].end < stop ; di++ ) {
step++;
if( DEBUG > 1 ) {
fprintf( stderr, "\tskip: [%d] object element start=%d end=%d (%s)\n",
di, jtokens[di].start, jtokens[di].end, extract( json, &jtokens[di]) );
}
}
}
if( DEBUG ) fprintf( stderr, "<DBUG> %s[%d] object element finished step= %d\n", name, n, step );
break;
case JSMN_ARRAY:
fprintf( stderr, "[ERR] jwrapper: %s [%d] array element is not a valid type: nested arrays not supported.\n", name, n );
free( jtp );
free( jarray );
jarray = NULL;
free( jtokens );
return NULL;
case JSMN_STRING:
data = extract( json, &jtokens[data_idx] );
jarray[n].v.pv = (void *) data;
jarray[n].prim_type = PT_STRING;
jarray[n].jsmn_type = JSMN_STRING;
break;
case JSMN_PRIMITIVE:
data = extract( json, &jtokens[data_idx] );
switch( *data ) {
case 'T':
case 't':
jarray[n].v.fv = 1;
jarray[n].prim_type = PT_BOOL;
if( DEBUG ) fprintf( stderr, "<DBUG> %s[%d] bool = true\n", name, n );
break;
case 'F':
case 'f':
jarray[n].prim_type = PT_BOOL;
jarray[n].v.fv = 0;
if( DEBUG ) fprintf( stderr, "<DBUG> %s[%d] bool = false\n", name, n );
break;
case 'N': // assume null, nil, or some variant
case 'n':
jarray[n].prim_type = PT_NULL;
jarray[n].v.fv = 0;
if( DEBUG ) fprintf( stderr, "<DBUG> %s[%d] null primative\n", name, n );
break;
default:
jarray[n].prim_type = PT_VALUE;
jarray[n].v.fv = strtod( data, NULL ); // store all numerics as double
if( DEBUG ) fprintf( stderr, "<DBUG> %s[%d] = %.03f\n", name, n, jarray[n].v.fv );
break;
}
jarray[n].jsmn_type = JSMN_PRIMITIVE;
break;
default:
if( DEBUG ) fprintf( stderr, "[ERR] jwrapper: %s [%d] array ele is unknown type: %d\n",
name, n, jtokens[data_idx].type );
rmr_sym_free( st );
free( jtokens );
return NULL;
}
if( DEBUG ) fprintf( stderr, "<DBUG> %s[%d] finished, step = %d\n", name, n, step );
data_idx += step;
}
step = data_idx - i; // data_index has been moved along, so it's a simple subtraction at this point
if( DEBUG ) fprintf( stderr, "<DBUG> %s array finished, total step = %d\n", name, step );
break;
case JSMN_STRING:
data = extract( json, &jtokens[data_idx] );
if( (jtp = mk_thing( st, name, jtokens[data_idx].type )) != NULL ) {
jtp->prim_type = PT_STRING;
jtp->v.pv = (void *) data; // just point into the large json string
}
break;
case JSMN_PRIMITIVE:
data = extract( json, &jtokens[data_idx] );
if( (jtp = mk_thing( st, name, jtokens[data_idx].type )) == NULL ) {
break;
}
switch( *data ) { // assume T|t is true and F|f is false
case 'T':
case 't':
jtp->prim_type = PT_BOOL;
jtp->v.fv = 1;
break;
case 'F':
case 'f':
jtp->prim_type = PT_BOOL;
jtp->v.fv = 0;
break;
case 'N': // Null or some form of that
case 'n':
jtp->prim_type = PT_NULL;
jtp->v.fv = 0;
break;
default:
jtp->prim_type = PT_VALUE;
jtp->v.fv = strtod( data, NULL ); // store all numerics as double
break;
}
break;
default:
fprintf( stderr, "[WARN] jwrapper: element [%d] is undefined or of unknown type\n", i );
break;
}
if( DEBUG ) fprintf( stderr, "<DBUG> stepping i = %d by %d max=%d\n", i, step, njtokens );
i += step; // step to next name token
}
if( DEBUG ) fprintf( stderr, "<DBUG> ================= recursion ends =======================\n\n" );
free( jtokens );
return st;
}
// --------------- public functions -----------------------------------------------------------------
/*
Destroy all operating structures assocaited with the symtab pointer passed in.
*/
extern void jw_nuke( void* st ) {
if( st != NULL ) {
rmr_sym_foreach_class( st, OBJ_SPACE, nix_things, NULL ); // free any json thing that the symtab references
rmr_sym_foreach_class( st, MGT_SPACE, nix_mgt, NULL ); // free management things
rmr_sym_free( st ); // free the symtab itself
}
}
/*
Scan the given st and write some useful (?) info to stderr.
*/
extern void jw_dump( void* st ) {
if( st != NULL ) {
rmr_sym_foreach_class( st, OBJ_SPACE, dump_things, NULL );
} else {
fprintf( stderr, "<DBUG> jwrapper: dump: no table\n" );
}
}
/*
Given a json string, parse it, and put the things into a symtab.
return the symtab pointer to the caller. They pass the symtab
pointer back to the various get functions.
This is the entry point. It sets up the symbol table and invokes the parse object
funtion to start at the first level. Parse object will recurse for nested objects
if present.
*/
extern void* jw_new( const char* json ) {
void *st = NULL; // symbol table
char* djson; // dup so we can save it
void* rp = NULL; // return value
if( json != NULL ) {
if( (st = rmr_sym_alloc( MAX_THINGS/4 )) != NULL ) {
djson = strdup( json ); // allows user to free/overlay their buffer as needed
rp = parse_jobject( st, djson, "" ); // empty prefix for the root object; parse_jo will clean up and free st
if( rp == NULL ) {
free( djson );
} else {
rmr_sym_put( st, (unsigned char *) JSON_SYM_NAME, MGT_SPACE, djson ); // must have a reference to the string until symtab is trashed
}
}
}
return rp;
}
/*
Returns true (1) if the named field is missing.
*/
extern int jw_missing( void* st, const char* name ) {
int rv = 0;
if( st != NULL && name != NULL ) {
rv = rmr_sym_get( st, name, OBJ_SPACE ) == NULL;
}
return rv;
}
/*
Returns true (1) if the named field is in the blob.
*/
extern int jw_exists( void* st, const char* name ) {
int rv = 0;
if( st != NULL && name != NULL ) {
rv = rmr_sym_get( st, name, OBJ_SPACE ) != NULL;
}
return rv;
}
/*
Returns true (1) if the primative type is value (double).
*/
extern int jw_is_value( void* st, const char* name ) {
const jthing_t* jtp; // thing that is referenced by the symtab
int rv = 0;
if( st != NULL && name != NULL ) {
if( (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
rv = jtp->prim_type == PT_VALUE;
}
}
return rv;
}
/*
Returns true (1) if the primative type is string.
*/
extern int jw_is_string( void* st, const char* name ) {
const jthing_t* jtp; // thing that is referenced by the symtab
int rv = 0;
if( st != NULL && name != NULL ) {
if( (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
rv = jtp->prim_type == PT_STRING;
}
}
return rv;
}
/*
Returns true (1) if the primative type is boolean.
*/
extern int jw_is_bool( void* st, const char* name ) {
const jthing_t* jtp; // thing that is referenced by the symtab
int rv = 0;
if( st != NULL && name != NULL ) {
if( (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
rv = jtp->prim_type == PT_BOOL;
}
}
return rv;
}
/*
Returns true (1) if the primative type was a 'null' type.
*/
extern int jw_is_null( void* st, const char* name ) {
const jthing_t* jtp; // thing that is referenced by the symtab
int rv = 0;
if( st != NULL && name != NULL ) {
if( (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
rv = jtp->prim_type == PT_NULL;
}
}
return rv;
}
/*
Look up the name in the symtab and return the string (data).
*/
extern char* jw_string( void* st, const char* name ) {
const jthing_t* jtp; // thing that is referenced by the symtab
char* rv = NULL;
if( st != NULL && name != NULL ) {
if( (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL && jtp->jsmn_type == JSMN_STRING ) {
rv = (char *) jtp->v.pv;
}
}
return rv;
}
/*
Look up name and return the value.
*/
extern double jw_value( void* st, const char* name ) {
jthing_t* jtp; // thing that is referenced by the symtab
double rv = 0.0;
if( st != NULL && name != NULL ) {
if( (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL && jtp->jsmn_type == JSMN_PRIMITIVE ) {
rv = jtp->v.fv;
}
}
return rv;
}
/*
Look up name and return the blob (symtab).
*/
extern void* jw_blob( void* st, const char* name ) {
const jthing_t* jtp; // thing that is referenced by the symtab
void* rv = NULL;
if( st != NULL && name != NULL ) {
if( (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL && jtp->jsmn_type == JSMN_OBJECT ) {
rv = (void *) jtp->v.pv;
}
}
return rv;
}
/*
Look up the element and return boolean state; This takes the C approach and
returns true/false based on the value.
*/
extern int jw_bool_ele( void* st, const char* name, int idx ) {
const jthing_t* jtp; // thing that is referenced by the symtab entry
int rv = 0;
if( st != NULL && name != NULL ) {
if( (jtp = suss_element( st, name, idx )) != NULL ) {
rv = !! ((int) jtp->v.fv);
}
}
return rv;
}
/*
Look up array element as a string. Returns NULL if:
name is not an array
name is not in the hash
index is out of range
element is not a string
*/
extern char* jw_string_ele( void* st, const char* name, int idx ) {
const jthing_t* jtp; // thing that is referenced by the symtab entry
char* rv = NULL;
if( st != NULL && name != NULL ) {
if( (jtp = suss_element( st, name, idx )) != NULL && jtp->jsmn_type == JSMN_STRING ) {
rv = (char *) jtp->v.pv;
}
}
return rv;
}
/*
Look up array element as a value. Returns 0 if:
name is not an array
name is not in the hash
index is out of range
element is not a value
*/
extern double jw_value_ele( void* st, const char* name, int idx ) {
const jthing_t* jtp; // thing that is referenced by the symtab entry
double rv = 0.0;
if( st != NULL && name != NULL ) {
if( (jtp = suss_element( st, name, idx )) != NULL && jtp->prim_type == PT_VALUE ) {
rv = jtp->v.fv;
}
}
return rv;
}
/*
Look up the element and check to see if it is a string.
Return true (1) if it is.
*/
extern int jw_is_string_ele( void* st, const char* name, int idx ) {
jthing_t* jtp; // thing that is referenced by the symtab entry
int rv = 0;
if( st != NULL && name != NULL ) {
if( (jtp = suss_element( st, name, idx )) != NULL ) {
rv = jtp->prim_type == PT_STRING;
}
}
return rv;
}
/*
Look up the element and check to see if it is a value primative.
Return true (1) if it is.
*/
extern int jw_is_value_ele( void* st, const char* name, int idx ) {
const jthing_t* jtp; // thing that is referenced by the symtab entry
int rv = 0;
if( st != NULL && name != NULL ) {
if( (jtp = suss_element( st, name, idx )) != NULL ) {
rv = jtp->prim_type == PT_VALUE;
}
}
return rv;
}
/*
Look up the element and check to see if it is a boolean primative.
Return true (1) if it is.
*/
extern int jw_is_bool_ele( void* st, const char* name, int idx ) {
const jthing_t* jtp; // thing that is referenced by the symtab entry
int rv = 0;
if( st != NULL && name != NULL ) {
if( (jtp = suss_element( st, name, idx )) != NULL ) {
rv = jtp->prim_type == PT_BOOL;
}
}
return rv;
}
/*
Look up the element and check to see if it is a null primative.
Return true (1) if it is.
*/
extern int jw_is_null_ele( void* st, const char* name, int idx ) {
const jthing_t* jtp; // thing that is referenced by the symtab entry
int rv = 0;
if( st != NULL && name != NULL ) {
if( (jtp = suss_element( st, name, idx )) != NULL ) {
rv = jtp->prim_type == PT_NULL;
}
}
return rv;
}
/*
Look up array element as an object. Returns NULL if:
name is not an array
name is not in the hash
index is out of range
element is not an object
An object in an array is a standalone symbol table. Thus the object
is treated differently than a nested object whose members are a
part of the parent namespace. An object in an array has its own
namespace.
*/
extern void* jw_obj_ele( void* st, const char* name, int idx ) {
const jthing_t* jtp; // thing that is referenced by the symtab entry
void* rv = NULL;
if( st != NULL && name != NULL ) {
if( (jtp = suss_element( st, name, idx )) != NULL && jtp->jsmn_type == JSMN_OBJECT ) {
rv = (void *) jtp->v.pv;
}
}
return rv;
}
/*
Return the size of the array named. Returns -1 if the thing isn't an array,
and returns the number of elements otherwise.
*/
extern int jw_array_len( void* st, const char* name ) {
const jthing_t* jtp; // thing that is referenced by the symtab entry
int rv = -1;
if( st != NULL && name != NULL ) {
if( (jtp = suss_array( st, name )) != NULL ) {
rv = jtp->nele;
}
}
return rv;
}