1
0
Fork 0
mirror of https://github.com/warmcat/libwebsockets.git synced 2025-03-30 00:00:16 +01:00

lejp: object indexes

This adds an optional feature LEJP_FLAG_FEAT_OBJECT_INDEXES that changes
lejp to treat { } items as indexable in ctx->i[] / ctx->ipos, since they
also can take commas.

This may break existing uses so it requires the default-off feature flag to
enable it.  The flags context field is zeroed by lejp_construct(), so any
flags should be set on ctx->flags after alling that.

There's also a flag LEJP_FLAG_LATEST available as an alias to enable any
desirable but not-backwards-compatible behaviour, including this.

Add the info to the README and adapt the unit test to do it both with and
without the FEAT_OBJECT_INDEXES flag.
This commit is contained in:
Andy Green 2021-11-05 07:01:11 +00:00
parent 9734cadf11
commit c935df1e7e
4 changed files with 188 additions and 60 deletions

View file

@ -24,6 +24,21 @@ The features are:
- collates utf-8 text payloads into a 250-byte chunk buffer in the json parser
context object for ease of access
## LEJP Context initialization
lejp doesn't allocate at all, you define a `struct lejp_ctx` usually on the
stack somewhere, and call `lejp_construct()` to initialize it.
To minimize surprises as lejp evolves, there is now a `flags` member of the
ctx, which defaults to zero for compatibility with older versions. After
the `lejp_construct()` call, you can set `ctx.flags` to indicate you want
newer options
|lejp flags|Meaning|
|---|---|
|LEJP_FLAG_FEAT_OBJECT_INDEXES|Provide indexes for { x, y, x } lists same as for arrays|
|LEJP_FLAG_LATEST|Alias indicating you want the "best" current options, even if incompatible with old behaviours|
## Type handling
LEJP leaves all numbers in text form, they are signalled in different callbacks
@ -87,6 +102,20 @@ or the match index from your path array starting from 1 for the first entry.
## Details of object and array indexes
LEJP maintains a "stack" of index counters, each element represents one level
in the current hierarchy that may have a list or array of objects in it.
The amount of levels currently is held in `ctx->ipos`, and `ctx->i[]` holds
`uint16_t` index counts for each level.
By querying these, you can understand at which element index in a hierarchy of
arrays in the JSON you are at, unambiguously.
By default that is done for each `[]` array level, if you set `ctx.flags` with
`LEJP_FLAG_FEAT_OBJECT_INDEXES` option, it is also done for each `{}` object
level, which can also take comma-separated lists that need index tracking.
## Comparison with LECP (CBOR parser)
LECP is based on the same principles as LEJP and shares most of the callbacks.

View file

@ -245,6 +245,10 @@ struct lejp_ctx {
/* short */
uint16_t uni;
#define LEJP_FLAG_FEAT_OBJECT_INDEXES (1 << 0)
#define LEJP_FLAG_LATEST \
(LEJP_FLAG_FEAT_OBJECT_INDEXES)
uint16_t flags;
/* char */

View file

@ -82,6 +82,7 @@ lejp_construct(struct lejp_ctx *ctx,
ctx->path[0] = '\0';
ctx->user = user;
ctx->line = 1;
ctx->flags = 0; /* user may set after construction */
ctx->pst_sp = 0;
ctx->pst[0].callback = callback;
@ -287,6 +288,17 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len)
ret = LEJP_REJECT_IDLE_NO_BRACE;
goto reject;
}
if (ctx->flags & LEJP_FLAG_FEAT_OBJECT_INDEXES) {
/* since insides of {} can have ',', we should
* add an index level so we can count them
*/
ctx->i[ctx->ipos++] = 0;
if (ctx->ipos > LWS_ARRAY_SIZE(ctx->i)) {
ret = LEJP_REJECT_MP_DELIM_ISTACK;
goto reject;
}
}
if (ctx->pst[ctx->pst_sp].callback(ctx,
LEJPCB_OBJECT_START))
goto reject_callback;
@ -458,6 +470,16 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len)
ctx->st[ctx->sp].s = LEJP_MP_COMMA_OR_END;
c = LEJP_MEMBERS;
lejp_check_path_match(ctx);
if (ctx->flags & LEJP_FLAG_FEAT_OBJECT_INDEXES) {
/* since insides of {} can have ',', we should
* add an index level so we can count them
*/
ctx->i[ctx->ipos++] = 0;
if (ctx->ipos > LWS_ARRAY_SIZE(ctx->i)) {
ret = LEJP_REJECT_MP_DELIM_ISTACK;
goto reject;
}
}
if (ctx->pst[ctx->pst_sp].callback(ctx,
LEJPCB_OBJECT_START))
goto reject_callback;
@ -499,7 +521,9 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len)
ctx->pst[ctx->pst_sp].ppos = (unsigned char)
ctx->st[ctx->sp - 1].p;
ctx->ipos = (unsigned char)ctx->st[ctx->sp - 1].i;
}
} else
if (ctx->flags & LEJP_FLAG_FEAT_OBJECT_INDEXES)
ctx->ipos--;
ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0';
if (ctx->path_match &&
ctx->pst[ctx->pst_sp].ppos <= ctx->path_match_len)
@ -643,6 +667,11 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len)
if (c == ',') {
/* increment this stack level's index */
ctx->st[ctx->sp].s = LEJP_M_P;
if (ctx->flags & LEJP_FLAG_FEAT_OBJECT_INDEXES)
if (ctx->ipos)
ctx->i[ctx->ipos - 1]++;
if (!ctx->sp) {
ctx->pst[ctx->pst_sp].ppos = 0;
/*
@ -662,11 +691,15 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len)
*/
ctx->path_match = 0;
lejp_check_path_match(ctx);
if (ctx->st[ctx->sp - 1].s != LEJP_MP_ARRAY_END)
break;
/* top level is definitely an array... */
if (ctx->ipos)
ctx->i[ctx->ipos - 1]++;
if (!(ctx->flags & LEJP_FLAG_FEAT_OBJECT_INDEXES))
if (ctx->ipos)
ctx->i[ctx->ipos - 1]++;
ctx->st[ctx->sp].s = LEJP_MP_VALUE;
break;
}
@ -687,7 +720,10 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len)
ctx->pst[ctx->pst_sp].ppos = (unsigned char)
ctx->st[ctx->sp - 1].p;
ctx->ipos = (unsigned char)ctx->st[ctx->sp - 1].i;
}
} else
if (ctx->flags & LEJP_FLAG_FEAT_OBJECT_INDEXES)
ctx->ipos--;
ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0';
if (ctx->path_match &&
ctx->pst[ctx->pst_sp].ppos <= ctx->path_match_len)
@ -731,8 +767,11 @@ pop_level:
if (ctx->sp) {
ctx->pst[ctx->pst_sp].ppos = (unsigned char)ctx->st[ctx->sp].p;
ctx->ipos = (unsigned char)ctx->st[ctx->sp].i;
}
} else
if (ctx->flags & LEJP_FLAG_FEAT_OBJECT_INDEXES)
ctx->ipos--;
ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0';
if (ctx->path_match &&
ctx->pst[ctx->pst_sp].ppos <= ctx->path_match_len)
/*
@ -761,13 +800,14 @@ array_end:
ctx->pst[ctx->pst_sp].ppos = (unsigned char)
ctx->st[ctx->sp - 1].p;
ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0';
lejp_check_path_match(ctx);
break;
}
if (c != ']') {
ret = LEJP_REJECT_MP_ARRAY_END_MISSING;
goto reject;
}
lejp_check_path_match(ctx);
ctx->st[ctx->sp].s = LEJP_MP_COMMA_OR_END;
ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_ARRAY_END);
if (defer) {

View file

@ -132,6 +132,11 @@ static const char * const json_tests[] = {
"{" /* test 11: array of arrays */
"\"array1\": [[\"a\", \"b\", \"b1\"], [\"c\", \"d\", \"d1\"]],"
"\"array2\": [[\"e\", \"f\", \"f1\"], [\"g\", \"h\", \"h1\"]]"
"}",
"{" /* test 12: test 11 but done with LEJP_FLAG_FEAT_OBJECT_INDEXES */
"\"array1\": [[\"a\", \"b\", \"b1\"], [\"c\", \"d\", \"d1\"]],"
"\"array2\": [[\"e\", \"f\", \"f1\"], [\"g\", \"h\", \"h1\"]]"
"}"
};
@ -350,69 +355,118 @@ struct lejp_results {
{ 2, 0, 0, { 0 }, "", "123" },
{ 16, 0, 0, { 0 }, "", "123" },
{ 5, 0, 0, { 0 }, "array1", "123" },
{ 14, 0, 0, { 0 }, "array1[]", "123" },
{ 14, 1, 0, { 0, }, "array1[][]", "123" },
{ 11, 2, 0, { 0, 0, }, "array1[][]", "" },
{ 77, 2, 0, { 0, 0, }, "array1[][]", "a" },
{ 11, 2, 0, { 0, 1, }, "array1[][]", "" },
{ 77, 2, 0, { 0, 1, }, "array1[][]", "b" },
{ 11, 2, 0, { 0, 2, }, "array1[][]", "" },
{ 77, 2, 0, { 0, 2, }, "array1[][]", "b1" },
{ 15, 1, 0, { 0, }, "array1[]", "b1" },
{ 14, 1, 0, { 1, }, "array1[][]", "b1" },
{ 11, 2, 0, { 1, 0, }, "array1[][]", "" },
{ 77, 2, 0, { 1, 0, }, "array1[][]", "c" },
{ 11, 2, 0, { 1, 1, }, "array1[][]", "" },
{ 77, 2, 0, { 1, 1, }, "array1[][]", "d" },
{ 11, 2, 0, { 1, 2, }, "array1[][]", "" },
{ 77, 2, 0, { 1, 2, }, "array1[][]", "d1" },
{ 15, 1, 0, { 1, }, "array1[]", "d1" },
{ 15, 1, 0, { 1, }, "array1[]", "d1" },
{ 14, 0, 2, { 0 }, "array1[]", "123" },
{ 14, 1, 1, { 0, }, "array1[][]", "123" },
{ 11, 2, 1, { 0, 0, }, "array1[][]", "" },
{ 77, 2, 1, { 0, 0, }, "array1[][]", "a" },
{ 11, 2, 1, { 0, 1, }, "array1[][]", "" },
{ 77, 2, 1, { 0, 1, }, "array1[][]", "b" },
{ 11, 2, 1, { 0, 2, }, "array1[][]", "" },
{ 77, 2, 1, { 0, 2, }, "array1[][]", "b1" },
{ 15, 1, 2, { 0, }, "array1[]", "b1" },
{ 14, 1, 1, { 1, }, "array1[][]", "b1" },
{ 11, 2, 1, { 1, 0, }, "array1[][]", "" },
{ 77, 2, 1, { 1, 0, }, "array1[][]", "c" },
{ 11, 2, 1, { 1, 1, }, "array1[][]", "" },
{ 77, 2, 1, { 1, 1, }, "array1[][]", "d" },
{ 11, 2, 1, { 1, 2, }, "array1[][]", "" },
{ 77, 2, 1, { 1, 2, }, "array1[][]", "d1" },
{ 15, 1, 2, { 1, }, "array1[]", "d1" },
{ 15, 1, 2, { 1, }, "array1[]", "d1" },
{ 5, 1, 0, { 1, }, "array2", "d1" },
{ 14, 1, 0, { 1, }, "array2[]", "d1" },
{ 14, 2, 0, { 1, 0, }, "array2[][]", "d1" },
{ 11, 3, 0, { 1, 0, 0, }, "array2[][]", "" },
{ 77, 3, 0, { 1, 0, 0, }, "array2[][]", "e" },
{ 11, 3, 0, { 1, 0, 1, }, "array2[][]", "" },
{ 77, 3, 0, { 1, 0, 1, }, "array2[][]", "f" },
{ 11, 3, 0, { 1, 0, 2, }, "array2[][]", "" },
{ 77, 3, 0, { 1, 0, 2, }, "array2[][]", "f1" },
{ 15, 2, 0, { 1, 0, }, "array2[]", "f1" },
{ 14, 2, 0, { 1, 1, }, "array2[][]", "f1" },
{ 11, 3, 0, { 1, 1, 0, }, "array2[][]", "" },
{ 77, 3, 0, { 1, 1, 0, }, "array2[][]", "g" },
{ 11, 3, 0, { 1, 1, 1, }, "array2[][]", "" },
{ 77, 3, 0, { 1, 1, 1, }, "array2[][]", "h" },
{ 11, 3, 0, { 1, 1, 2, }, "array2[][]", "" },
{ 77, 3, 0, { 1, 1, 2, }, "array2[][]", "h1" },
{ 15, 2, 0, { 1, 1, }, "array2[]", "h1" },
{ 15, 2, 0, { 1, 1, }, "array2[]", "h1" },
{ 14, 1, 2, { 1, }, "array2[]", "d1" },
{ 14, 2, 1, { 1, 0, }, "array2[][]", "d1" },
{ 11, 3, 1, { 1, 0, 0, }, "array2[][]", "" },
{ 77, 3, 1, { 1, 0, 0, }, "array2[][]", "e" },
{ 11, 3, 1, { 1, 0, 1, }, "array2[][]", "" },
{ 77, 3, 1, { 1, 0, 1, }, "array2[][]", "f" },
{ 11, 3, 1, { 1, 0, 2, }, "array2[][]", "" },
{ 77, 3, 1, { 1, 0, 2, }, "array2[][]", "f1" },
{ 15, 2, 2, { 1, 0, }, "array2[]", "f1" },
{ 14, 2, 1, { 1, 1, }, "array2[][]", "f1" },
{ 11, 3, 1, { 1, 1, 0, }, "array2[][]", "" },
{ 77, 3, 1, { 1, 1, 0, }, "array2[][]", "g" },
{ 11, 3, 1, { 1, 1, 1, }, "array2[][]", "" },
{ 77, 3, 1, { 1, 1, 1, }, "array2[][]", "h" },
{ 11, 3, 1, { 1, 1, 2, }, "array2[][]", "" },
{ 77, 3, 1, { 1, 1, 2, }, "array2[][]", "h1" },
{ 15, 2, 2, { 1, 1, }, "array2[]", "h1" },
{ 15, 2, 2, { 1, 1, }, "array2[]", "h1" },
{ 17, 2, 0, { 1, 1, }, "array2[]", "h1" },
{ 3, 2, 0, { 1, 1, }, "array2[]", "h1" },
}, r12[] = { /* test 11 but done with LEJP_FLAG_FEAT_OBJECT_INDEXES */
{ 0, 0, 0, { 0 }, "", "h1" },
{ 2, 0, 0, { 0 }, "", "h1" },
{ 16, 1, 0, { 0, }, "", "h1" },
{ 5, 1, 0, { 0, }, "array1", "h1" },
{ 14, 1, 2, { 0, }, "array1[]", "h1" },
{ 14, 2, 1, { 0, 0, }, "array1[][]", "h1" },
{ 11, 3, 1, { 0, 0, 0, }, "array1[][]", "" },
{ 77, 3, 1, { 0, 0, 0, }, "array1[][]", "a" },
{ 11, 3, 1, { 0, 0, 1, }, "array1[][]", "" },
{ 77, 3, 1, { 0, 0, 1, }, "array1[][]", "b" },
{ 11, 3, 1, { 0, 0, 2, }, "array1[][]", "" },
{ 77, 3, 1, { 0, 0, 2, }, "array1[][]", "b1" },
{ 15, 2, 2, { 0, 0, }, "array1[]", "b1" },
{ 14, 2, 1, { 0, 1, }, "array1[][]", "b1" },
{ 11, 3, 1, { 0, 1, 0, }, "array1[][]", "" },
{ 77, 3, 1, { 0, 1, 0, }, "array1[][]", "c" },
{ 11, 3, 1, { 0, 1, 1, }, "array1[][]", "" },
{ 77, 3, 1, { 0, 1, 1, }, "array1[][]", "d" },
{ 11, 3, 1, { 0, 1, 2, }, "array1[][]", "" },
{ 77, 3, 1, { 0, 1, 2, }, "array1[][]", "d1" },
{ 15, 2, 2, { 0, 1, }, "array1[]", "d1" },
{ 15, 1, 2, { 0, 1, }, "array1[]", "d1" },
{ 5, 1, 0, { 1, }, "array2", "d1" },
{ 14, 1, 2, { 1, }, "array2[]", "d1" },
{ 14, 2, 1, { 1, 0, }, "array2[][]", "d1" },
{ 11, 3, 1, { 1, 0, 0, }, "array2[][]", "" },
{ 77, 3, 1, { 1, 0, 0, }, "array2[][]", "e" },
{ 11, 3, 1, { 1, 0, 1, }, "array2[][]", "" },
{ 77, 3, 1, { 1, 0, 1, }, "array2[][]", "f" },
{ 11, 3, 1, { 1, 0, 2, }, "array2[][]", "" },
{ 77, 3, 1, { 1, 0, 2, }, "array2[][]", "f1" },
{ 15, 2, 2, { 1, 0, }, "array2[]", "f1" },
{ 14, 2, 1, { 1, 1, }, "array2[][]", "f1" },
{ 11, 3, 1, { 1, 1, 0, }, "array2[][]", "" },
{ 77, 3, 1, { 1, 1, 0, }, "array2[][]", "g" },
{ 11, 3, 1, { 1, 1, 1, }, "array2[][]", "" },
{ 77, 3, 1, { 1, 1, 1, }, "array2[][]", "h" },
{ 11, 3, 1, { 1, 1, 2, }, "array2[][]", "" },
{ 77, 3, 1, { 1, 1, 2, }, "array2[][]", "h1" },
{ 15, 2, 2, { 1, 1, }, "array2[]", "h1" },
{ 15, 1, 2, { 1, }, "array2[]", "h1" },
{ 17, 1, 0, { 1, }, "array2[]", "h1" },
{ 3, 1, 0, { 1, }, "array2[]", "h1" },
};
static const char * const tok[] = {
"something",
}, * const tok_test11[] = { /* matches for test 11, 12 */
"*[][]",
"*[]",
};
struct lejp_results_pkg {
const struct lejp_results *r;
size_t len;
const char * const *tokens;
size_t tokens_len;
uint16_t ctx_flags;
} rpkg[] = {
{ r1, LWS_ARRAY_SIZE(r1) },
{ r2, LWS_ARRAY_SIZE(r2) },
{ r3, LWS_ARRAY_SIZE(r3) },
{ r4, LWS_ARRAY_SIZE(r4) },
{ r5, LWS_ARRAY_SIZE(r5) },
{ r6, LWS_ARRAY_SIZE(r6) },
{ r7, LWS_ARRAY_SIZE(r7) },
{ r8, LWS_ARRAY_SIZE(r8) },
{ r9, LWS_ARRAY_SIZE(r9) },
{ r10, LWS_ARRAY_SIZE(r10) },
{ r11, LWS_ARRAY_SIZE(r11) },
};
static const char * const tok[] = {
"something",
{ r1, LWS_ARRAY_SIZE(r1), tok, LWS_ARRAY_SIZE(tok), 0 },
{ r2, LWS_ARRAY_SIZE(r2), tok, LWS_ARRAY_SIZE(tok), 0 },
{ r3, LWS_ARRAY_SIZE(r3), tok, LWS_ARRAY_SIZE(tok), 0 },
{ r4, LWS_ARRAY_SIZE(r4), tok, LWS_ARRAY_SIZE(tok), 0 },
{ r5, LWS_ARRAY_SIZE(r5), tok, LWS_ARRAY_SIZE(tok), 0 },
{ r6, LWS_ARRAY_SIZE(r6), tok, LWS_ARRAY_SIZE(tok), 0 },
{ r7, LWS_ARRAY_SIZE(r7), tok, LWS_ARRAY_SIZE(tok), 0 },
{ r8, LWS_ARRAY_SIZE(r8), tok, LWS_ARRAY_SIZE(tok), 0 },
{ r9, LWS_ARRAY_SIZE(r9), tok, LWS_ARRAY_SIZE(tok), 0 },
{ r10, LWS_ARRAY_SIZE(r10), tok, LWS_ARRAY_SIZE(tok), 0 },
{ r11, LWS_ARRAY_SIZE(r11), tok_test11, LWS_ARRAY_SIZE(tok_test11), 0 },
{ r12, LWS_ARRAY_SIZE(r12), tok_test11, LWS_ARRAY_SIZE(tok_test11),
LEJP_FLAG_FEAT_OBJECT_INDEXES },
};
@ -491,7 +545,8 @@ int main(int argc, const char **argv)
lwsl_user("%s: ++++++++++++++++ test %d\n", __func__, m + 1);
step = 0;
lejp_construct(&ctx, test_cb, NULL, tok, LWS_ARRAY_SIZE(tok));
lejp_construct(&ctx, test_cb, NULL, rpkg[m].tokens, (uint8_t)rpkg[m].tokens_len);
ctx.flags = rpkg[m].ctx_flags;
lwsl_hexdump_info(json_tests[m], strlen(json_tests[m]));