/*
 * libwebsockets - small server side websockets and web server implementation
 *
 * Copyright (C) 2010 - 2025 Andy Green <andy@warmcat.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#include "private-lib-core.h"
#include <string.h>
#include <stdio.h>

static const char * const parser_errs[] = {
	"",
	"",
	"No opening '{'",
	"Expected closing '}'",
	"Expected '\"'",
	"String underrun",
	"Illegal unescaped control char",
	"Illegal escape format",
	"Illegal hex number",
	"Expected ':'",
	"Illegal value start",
	"Digit required after decimal point",
	"Bad number format",
	"Bad exponent format",
	"Unknown token",
	"Too many ']'",
	"Mismatched ']'",
	"Expected ']'",
	"JSON nesting limit exceeded",
	"Nesting tracking used up",
	"Number too long",
	"Comma or block end expected",
	"Unknown",
	"Parser callback errored (see earlier error)",
};

/**
 * lejp_construct - prepare a struct lejp_ctx for use
 *
 * \param ctx:	pointer to your struct lejp_ctx
 * \param callback:	your user callback which will received parsed tokens
 * \param user:	optional user data pointer untouched by lejp
 * \param paths:	your array of name elements you are interested in
 * \param count_paths:	LWS_ARRAY_SIZE() of @paths
 *
 * Prepares your context struct for use with lejp
 */

void
lejp_construct(struct lejp_ctx *ctx,
	signed char (*callback)(struct lejp_ctx *ctx, char reason), void *user,
			const char * const *paths, unsigned char count_paths)
{
	ctx->st[0].s = 0;
	ctx->st[0].p = 0;
	ctx->st[0].i = 0;
	ctx->st[0].b = 0;
	ctx->sp = 0;
	ctx->ipos = 0;
	ctx->outer_array = 0;
	ctx->path_match = 0;
	ctx->path_stride = 0;
	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;
	ctx->pst[0].paths = paths;
	ctx->pst[0].count_paths = count_paths;
	ctx->pst[0].user = NULL;
	ctx->pst[0].ppos = 0;

	ctx->pst[0].callback(ctx, LEJPCB_CONSTRUCTED);
}

/**
 * lejp_destruct - retire a previously constructed struct lejp_ctx
 *
 * \param ctx:	pointer to your struct lejp_ctx
 *
 * lejp does not perform any allocations, but since your user code might, this
 * provides a one-time LEJPCB_DESTRUCTED callback at destruction time where
 * you can clean up in your callback.
 */

void
lejp_destruct(struct lejp_ctx *ctx)
{
	/* no allocations... just let callback know what it happening */
	if (ctx && ctx->pst[0].callback)
		ctx->pst[0].callback(ctx, LEJPCB_DESTRUCTED);
}

/**
 * lejp_change_callback - switch to a different callback from now on
 *
 * \param ctx:	pointer to your struct lejp_ctx
 * \param callback:	your user callback which will received parsed tokens
 *
 * This tells the old callback it was destroyed, in case you want to take any
 * action because that callback "lost focus", then changes to the new
 * callback and tells it first that it was constructed, and then started.
 *
 * Changing callback is a cheap and powerful trick to split out handlers
 * according to information earlier in the parse.  For example you may have
 * a JSON pair "schema" whose value defines what can be expected for the rest
 * of the JSON.  Rather than having one huge callback for all cases, you can
 * have an initial one looking for "schema" which then calls
 * lejp_change_callback() to a handler specific for the schema.
 *
 * Notice that afterwards, you need to construct the context again anyway to
 * parse another JSON object, and the callback is reset then to the main,
 * schema-interpreting one.  The construction action is very lightweight.
 */

void
lejp_change_callback(struct lejp_ctx *ctx,
		     signed char (*callback)(struct lejp_ctx *ctx, char reason))
{
	ctx->pst[0].callback(ctx, LEJPCB_DESTRUCTED);
	ctx->pst[0].callback = callback;
	ctx->pst[0].callback(ctx, LEJPCB_CONSTRUCTED);
	ctx->pst[0].callback(ctx, LEJPCB_START);
}

void
lejp_check_path_match(struct lejp_ctx *ctx)
{
	const char *p, *q;
	int n;
	size_t s = sizeof(char *);

	if (ctx->path_stride)
		s = ctx->path_stride;

	/* we only need to check if a match is not active */
	for (n = 0; //!ctx->path_match &&
	     n < ctx->pst[ctx->pst_sp].count_paths; n++) {
		ctx->wildcount = 0;
		p = ctx->path;

		q = *((char **)(((char *)ctx->pst[ctx->pst_sp].paths) + ((unsigned int)n * s)));
//lwsl_notice("%s: %s %s\n", __func__, p, q);
		while (*p && *q) {
			if (*q != '*') {
				if (*p != *q)
					break;
				p++;
				q++;
				continue;
			}
			ctx->wild[ctx->wildcount++] = (uint16_t)lws_ptr_diff_size_t(p, ctx->path);
			q++;
			/*
			 * if * has something after it, match to .
			 * if ends with *, eat everything.
			 * This implies match sequences must be ordered like
			 *  x.*.*
			 *  x.*
			 * if both options are possible
			 */
			while (*p && ((*p != '.' && *p != '[') || !*q))
				p++;
		}
		if (*p || *q)
			continue;

		ctx->path_match = (uint8_t)(n + 1);
		ctx->path_match_len = ctx->pst[ctx->pst_sp].ppos;
		return;
	}

	if (!ctx->path_match)
		ctx->wildcount = 0;
}

int
lejp_get_wildcard(struct lejp_ctx *ctx, int wildcard, char *dest, int len)
{
	int n;

	if (wildcard >= ctx->wildcount || !len)
		return 0;

	n = ctx->wild[wildcard];

	while (--len && n < ctx->pst[ctx->pst_sp].ppos &&
	       (n == ctx->wild[wildcard] || ctx->path[n] != '.'))
		*dest++ = ctx->path[n++];

	*dest = '\0';
	n++;

	return n - ctx->wild[wildcard];
}

/**
 * lejp_parse - interpret some more incoming data incrementally
 *
 * \param ctx:	previously constructed parsing context
 * \param json:	char buffer with the new data to interpret
 * \param len:	amount of data in the buffer
 *
 * Because lejp is a stream parser, it incrementally parses as new data
 * becomes available, maintaining all state in the context struct.  So an
 * incomplete JSON is a normal situation, getting you a LEJP_CONTINUE
 * return, signalling there's no error but to call again with more data when
 * it comes to complete the parsing.  Successful parsing completes with a
 * 0 or positive integer indicating how much of the last input buffer was
 * unused.
 */

static const char esc_char[] = "\"\\/bfnrt";
static const char esc_tran[] = "\"\\/\b\f\n\r\t";
static const char tokens[] = "rue alse ull ";

int
lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len)
{
	unsigned char c, n, s, defer = 0, comp = 0;
	int ret = LEJP_REJECT_UNKNOWN;

	if (!ctx->sp && !ctx->pst[ctx->pst_sp].ppos)
		ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_START);

	while (len--) {
		c = *json++;
		s = (unsigned char)ctx->st[ctx->sp].s;

		/* skip whitespace unless we should care */
		if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '#') {
			if (c == '\n') {
				ctx->line++;
				ctx->st[ctx->sp].s &= (char)~LEJP_FLAG_WS_COMMENTLINE;
			}
			if (!(s & LEJP_FLAG_WS_KEEP)) {
				if (c == '#')
					ctx->st[ctx->sp].s |=
						LEJP_FLAG_WS_COMMENTLINE;
				continue;
			}
		}

		if (ctx->st[ctx->sp].s & LEJP_FLAG_WS_COMMENTLINE)
			continue;

		switch (s) {
		case LEJP_IDLE:
			if (!ctx->sp && c == '[') {
				/* push */
				ctx->outer_array = 1;
				ctx->st[ctx->sp].s = LEJP_MP_ARRAY_END;
				c = LEJP_MP_VALUE;
				ctx->path[ctx->pst[ctx->pst_sp].ppos++] = '[';
				ctx->path[ctx->pst[ctx->pst_sp].ppos++] = ']';
				ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0';

				if (ctx->flags & LEJP_FLAG_FEAT_LEADING_WC)
					lejp_check_path_match(ctx);
				if (ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_ARRAY_START))
					goto reject_callback;
				ctx->i[ctx->ipos++] = 0;
				if (ctx->flags & LEJP_FLAG_FEAT_LEADING_WC)
					lejp_check_path_match(ctx);
				if (ctx->ipos > LWS_ARRAY_SIZE(ctx->i)) {
					ret = LEJP_REJECT_MP_DELIM_ISTACK;
					goto reject;
				}
				goto add_stack_level;
			}
			if (c != '{') {
				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;
			ctx->st[ctx->sp].s = LEJP_MEMBERS;
			break;
		case LEJP_MEMBERS:
			if (c == '}')
				goto pop_level_l;
			ctx->st[ctx->sp].s = LEJP_M_P;
			goto redo_character;
		case LEJP_M_P:
			if (c != '\"') {
				ret = LEJP_REJECT_MP_NO_OPEN_QUOTE;
				goto reject;
			}
			/* push */
			ctx->st[ctx->sp].s = LEJP_MP_DELIM;
			c = LEJP_MP_STRING;
			goto add_stack_level;

		case LEJP_MP_STRING:
			if (c == '\"') {
				if (!ctx->sp) { /* JSON can't end on quote */
					ret = LEJP_REJECT_MP_STRING_UNDERRUN;
					goto reject;
				}
				if (ctx->st[ctx->sp - 1].s != LEJP_MP_DELIM) {
					ctx->buf[ctx->npos] = '\0';
					if (ctx->pst[ctx->pst_sp].callback(ctx,
						      LEJPCB_VAL_STR_END) < 0)
						goto reject_callback;
				}
				/* pop */
				ctx->sp--;
				break;
			}
			if (c == '\\') {
				ctx->st[ctx->sp].s = LEJP_MP_STRING_ESC;
				break;
			}
			if (c < ' ') {/* "control characters" not allowed */
				ret = LEJP_REJECT_MP_ILLEGAL_CTRL;
				goto reject;
			}
			goto emit_string_char;

		case LEJP_MP_STRING_ESC:
			if (c == 'u') {
				ctx->st[ctx->sp].s = LEJP_MP_STRING_ESC_U1;
				ctx->uni = 0;
				break;
			}
			for (n = 0; n < sizeof(esc_char); n++) {
				if (c != esc_char[n])
					continue;
				/* found it */
				c = (unsigned char)esc_tran[n];
				ctx->st[ctx->sp].s = LEJP_MP_STRING;
				goto emit_string_char;
			}
			ret = LEJP_REJECT_MP_STRING_ESC_ILLEGAL_ESC;
			/* illegal escape char */
			goto reject;

		case LEJP_MP_STRING_ESC_U1:
		case LEJP_MP_STRING_ESC_U2:
		case LEJP_MP_STRING_ESC_U3:
		case LEJP_MP_STRING_ESC_U4:
			ctx->uni = (uint16_t)(ctx->uni << 4);
			if (c >= '0' && c <= '9')
				ctx->uni |= (uint16_t)(c - '0');
			else
				if (c >= 'a' && c <= 'f')
					ctx->uni |= (uint16_t)(c - 'a' + 10);
				else
					if (c >= 'A' && c <= 'F')
						ctx->uni |= (uint16_t)(c - 'A' + 10);
					else {
						ret = LEJP_REJECT_ILLEGAL_HEX;
						goto reject;
					}
			ctx->st[ctx->sp].s++;
			switch (s) {
			case LEJP_MP_STRING_ESC_U2:
				if (ctx->uni < 0x08)
					break;
				/*
				 * 0x08-0xff (0x0800 - 0xffff)
				 * emit 3-byte UTF-8
				 */
				c = (unsigned char)(0xe0 | ((ctx->uni >> 4) & 0xf));
				goto emit_string_char;

			case LEJP_MP_STRING_ESC_U3:
				if (ctx->uni >= 0x080) {
					/*
					 * 0x080 - 0xfff (0x0800 - 0xffff)
					 * middle 3-byte seq
					 * send ....XXXXXX..
					 */
					c = (unsigned char)(0x80 | ((ctx->uni >> 2) & 0x3f));
					goto emit_string_char;
				}
				if (ctx->uni < 0x008)
					break;
				/*
				 * 0x008 - 0x7f (0x0080 - 0x07ff)
				 * start 2-byte seq
				 */
				c = (unsigned char)(0xc0 | (ctx->uni >> 2));
				goto emit_string_char;

			case LEJP_MP_STRING_ESC_U4:
				if (ctx->uni >= 0x0080)
					/* end of 2 or 3-byte seq */
					c = (unsigned char)(0x80 | (ctx->uni & 0x3f));
				else
					/* literal */
					c = (unsigned char)ctx->uni;

				ctx->st[ctx->sp].s = LEJP_MP_STRING;
				goto emit_string_char;
			default:
				break;
			}
			break;

		case LEJP_MP_DELIM:
			if (c != ':') {
				ret = LEJP_REJECT_MP_DELIM_MISSING_COLON;
				goto reject;
			}
			ctx->st[ctx->sp].s = LEJP_MP_VALUE;
			ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0';

			lejp_check_path_match(ctx);
			if (ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_PAIR_NAME))
				goto reject_callback;
			break;

		case LEJP_MP_VALUE:
			if (c == '-' || (c >= '0' && c <= '9')) {
				ctx->npos = 0;
				ctx->dcount = 0;
				ctx->f = 0;
				ctx->st[ctx->sp].s = LEJP_MP_VALUE_NUM_INT;
				goto redo_character;
			}
			switch (c) {
			case'\"':
				/* push */
				ctx->st[ctx->sp].s = LEJP_MP_COMMA_OR_END;
				c = LEJP_MP_STRING;
				ctx->npos = 0;
				ctx->buf[0] = '\0';
				if (ctx->pst[ctx->pst_sp].callback(ctx,
							LEJPCB_VAL_STR_START))
					goto reject_callback;
				goto add_stack_level;

			case '{':
				/* push */
				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;
				ctx->path_match = 0;
				goto add_stack_level;

			case '[':
				/* push */
				ctx->st[ctx->sp].s = LEJP_MP_ARRAY_END;
				c = LEJP_MP_VALUE;
				if (ctx->pst[ctx->pst_sp].ppos + 3u >=
							sizeof(ctx->path))
					goto reject;
				ctx->path[ctx->pst[ctx->pst_sp].ppos++] = '[';
				ctx->path[ctx->pst[ctx->pst_sp].ppos++] = ']';
				ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0';
				if (ctx->flags & LEJP_FLAG_FEAT_LEADING_WC)
					lejp_check_path_match(ctx);
				if (ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_ARRAY_START))
					goto reject_callback;
				if (ctx->flags & LEJP_FLAG_FEAT_LEADING_WC)
					lejp_check_path_match(ctx);
				ctx->i[ctx->ipos++] = 0;
				if (ctx->ipos > LWS_ARRAY_SIZE(ctx->i)) {
					ret = LEJP_REJECT_MP_DELIM_ISTACK;
					goto reject;
				}
				goto add_stack_level;

			case ']':
				/* pop */
				if (!ctx->sp) { /* JSON can't end on ] */
					ret = LEJP_REJECT_MP_C_OR_E_UNDERF;
					goto reject;
				}
				ctx->sp--;
				if (ctx->st[ctx->sp].s != LEJP_MP_ARRAY_END) {
					ret = LEJP_REJECT_MP_C_OR_E_NOTARRAY;
					goto reject;
				}
				/* drop the path [n] bit */
				if (ctx->sp) {
					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)
					/*
					 * we shrank the path to be
					 * smaller than the matching point
					 */
					ctx->path_match = 0;
				if (ctx->pst_sp && !ctx->sp)
					lejp_parser_pop(ctx);
				if (ctx->flags & LEJP_FLAG_FEAT_LEADING_WC)
					lejp_check_path_match(ctx);
				if (ctx->outer_array && !ctx->sp) { /* ended on ] */
					n = LEJPCB_ARRAY_END;
					goto completed_l;
				}
				goto array_end_l;

			case 't': /* true */
				ctx->uni = 0;
				ctx->st[ctx->sp].s = LEJP_MP_VALUE_TOK;
				break;

			case 'f':
				ctx->uni = 4;
				ctx->st[ctx->sp].s = LEJP_MP_VALUE_TOK;
				break;

			case 'n':
				ctx->uni = 4 + 5;
				ctx->st[ctx->sp].s = LEJP_MP_VALUE_TOK;
				break;
			default:
				ret = LEJP_REJECT_MP_DELIM_BAD_VALUE_START;
				goto reject;
			}
			break;

		case LEJP_MP_VALUE_NUM_INT:
			if (!ctx->npos && c == '-') {
				ctx->f |= LEJP_SEEN_MINUS;
				goto append_npos;
			}

			if (ctx->dcount < 20 && c >= '0' && c <= '9') {
				if (ctx->f & LEJP_SEEN_POINT)
					ctx->f |= LEJP_SEEN_POST_POINT;
				ctx->dcount++;
				goto append_npos;
			}
			if (c == '.') {
				if (!ctx->dcount || (ctx->f & LEJP_SEEN_POINT)) {
					ret = LEJP_REJECT_MP_VAL_NUM_FORMAT;
					goto reject;
				}
				ctx->f |= LEJP_SEEN_POINT;
				goto append_npos;
			}
			/*
			 * before exponent, if we had . we must have had at
			 * least one more digit
			 */
			if ((ctx->f &
				(LEJP_SEEN_POINT | LEJP_SEEN_POST_POINT)) ==
							      LEJP_SEEN_POINT) {
				ret = LEJP_REJECT_MP_VAL_NUM_INT_NO_FRAC;
				goto reject;
			}
			if (c == 'e' || c == 'E') {
				if (ctx->f & LEJP_SEEN_EXP) {
					ret = LEJP_REJECT_MP_VAL_NUM_FORMAT;
					goto reject;
				}
				ctx->f |= LEJP_SEEN_EXP;
				ctx->st[ctx->sp].s = LEJP_MP_VALUE_NUM_EXP;
				goto append_npos;
			}
			/* if none of the above, did we even have a number? */
			if (!ctx->dcount) {
				ret = LEJP_REJECT_MP_VAL_NUM_FORMAT;
				goto reject;
			}

			ctx->buf[ctx->npos] = '\0';
			if (ctx->f & (LEJP_SEEN_POINT | LEJP_SEEN_EXP)) {
				/* 0.001 or 1E-3 are both floats, take 1E3 as float too */
				if (ctx->pst[ctx->pst_sp].callback(ctx,
							LEJPCB_VAL_NUM_FLOAT))
					goto reject_callback;
			} else {
				if (ctx->pst[ctx->pst_sp].callback(ctx,
							LEJPCB_VAL_NUM_INT))
					goto reject_callback;
			}

			/* then this is the post-number character, loop */
			ctx->st[ctx->sp].s = LEJP_MP_COMMA_OR_END;
			goto redo_character;

		case LEJP_MP_VALUE_NUM_EXP:
			ctx->st[ctx->sp].s = LEJP_MP_VALUE_NUM_INT;
			if (c >= '0' && c <= '9')
				goto redo_character;
			if (c == '+' || c == '-')
				goto append_npos;
			ret = LEJP_REJECT_MP_VAL_NUM_EXP_BAD_EXP;
			goto reject;

		case LEJP_MP_VALUE_TOK: /* true, false, null */
			if (c != tokens[ctx->uni]) {
				ret = LEJP_REJECT_MP_VAL_TOK_UNKNOWN;
				goto reject;
			}
			ctx->uni++;
			if (tokens[ctx->uni] != ' ')
				break;
			switch (ctx->uni) {
			case 3:
				ctx->buf[0] = '1';
				ctx->buf[1] = '\0';
				if (ctx->pst[ctx->pst_sp].callback(ctx,
								LEJPCB_VAL_TRUE))
					goto reject_callback;
				break;
			case 8:
				ctx->buf[0] = '0';
				ctx->buf[1] = '\0';
				if (ctx->pst[ctx->pst_sp].callback(ctx,
								LEJPCB_VAL_FALSE))
					goto reject_callback;
				break;
			case 12:
				ctx->buf[0] = '\0';
				if (ctx->pst[ctx->pst_sp].callback(ctx,
								LEJPCB_VAL_NULL))
					goto reject_callback;
				break;
			}
			ctx->st[ctx->sp].s = LEJP_MP_COMMA_OR_END;
			break;

		case LEJP_MP_COMMA_OR_END:
			ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0';
			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;
					/*
					 * since we came back to root level,
					 * no path can still match
					 */
					ctx->path_match = 0;
					break;
				}
				ctx->pst[ctx->pst_sp].ppos = (unsigned char)ctx->st[ctx->sp - 1].p;
				ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0';
				if (ctx->path_match &&
				    ctx->pst[ctx->pst_sp].ppos <= ctx->path_match_len)
					/*
					 * we shrank the path to be
					 * smaller than the matching point
					 */
					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->flags & LEJP_FLAG_FEAT_OBJECT_INDEXES))
					if (ctx->ipos)
						ctx->i[ctx->ipos - 1]++;

				ctx->st[ctx->sp].s = LEJP_MP_VALUE;
				break;
			}
			if (c == ']') {
				if (!ctx->sp) {
					ret = LEJP_REJECT_MP_C_OR_E_UNDERF;
					goto reject;
				}
				/* pop */
				ctx->sp--;
				if (ctx->st[ctx->sp].s != LEJP_MP_ARRAY_END) {
					ret = LEJP_REJECT_MP_C_OR_E_NOTARRAY;
					goto reject;
				}

				/* drop the path [n] bit */
				if (ctx->sp) {
					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)
					/*
					 * we shrank the path to be
					 * smaller than the matching point
					 */
					ctx->path_match = 0;

				if (ctx->outer_array && !ctx->sp) { /* ended on ] */
					n = LEJPCB_ARRAY_END;
					goto completed_l;
				}

				if (ctx->pst_sp && !ctx->sp)
					defer = 1;

				/* do LEJP_MP_ARRAY_END processing */
				goto redo_character;
			}
			if (c != '}') {
				ret = LEJP_REJECT_MP_C_OR_E_NEITHER;
				goto reject;
			}
			if (!ctx->sp) {
				n = LEJPCB_OBJECT_END;
				goto completed_l;
			}

			/* pop */
pop_level_l:
			comp = 0;
			if (!ctx->sp) {
				comp = 1;
				goto def_end_l;
			}
			ctx->sp--;
			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)
				/*
				 * we shrank the path to be
				 * smaller than the matching point
				 */
				ctx->path_match = 0;
def_end_l:
			lejp_check_path_match(ctx);
			if (ctx->pst[ctx->pst_sp].callback(ctx,
							   LEJPCB_OBJECT_END))
				goto reject_callback;
			if (comp) {
				n = 0;
				goto completed_l;
			}
			if (ctx->pst_sp > 1 && !ctx->sp)
				lejp_parser_pop(ctx);
			break;

		case LEJP_MP_ARRAY_END:
array_end_l:
			ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0';
			if (c == ',') {
				/* increment this stack level's index */
				if (ctx->ipos)
					ctx->i[ctx->ipos - 1]++;
				ctx->st[ctx->sp].s = LEJP_MP_VALUE;
				if (ctx->sp)
					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) {
				lejp_parser_pop(ctx);
				defer = 0;
			}
			break;
		}

		continue;

emit_string_char:
		if (!ctx->sp || ctx->st[ctx->sp - 1].s != LEJP_MP_DELIM) {
			/* assemble the string value into chunks */
			ctx->buf[ctx->npos++] = (char)c;
			if (ctx->npos == sizeof(ctx->buf) - 1) {
				if (ctx->pst[ctx->pst_sp].callback(ctx,
							  LEJPCB_VAL_STR_CHUNK))
					goto reject_callback;
				ctx->npos = 0;
			}
			continue;
		}
		/* name part of name:value pair */
		if (ctx->pst[ctx->pst_sp].ppos + 1u >= sizeof(ctx->path))
			goto reject;
		ctx->path[ctx->pst[ctx->pst_sp].ppos++] = (char)c;
		continue;

add_stack_level:
		/* push on to the object stack */
		if (ctx->pst[ctx->pst_sp].ppos &&
		    ctx->st[ctx->sp].s != LEJP_MP_COMMA_OR_END &&
		    ctx->st[ctx->sp].s != LEJP_MP_ARRAY_END) {
			if (ctx->pst[ctx->pst_sp].ppos + 1u >= sizeof(ctx->path))
				goto reject;
			ctx->path[ctx->pst[ctx->pst_sp].ppos++] = '.';
		}

		ctx->st[ctx->sp].p = (char)ctx->pst[ctx->pst_sp].ppos;
		ctx->st[ctx->sp].i = (char)ctx->ipos;
		if (++ctx->sp == LWS_ARRAY_SIZE(ctx->st)) {
			ret = LEJP_REJECT_STACK_OVERFLOW;
			goto reject;
		}
		ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0';
		ctx->st[ctx->sp].s = (char)c;
		ctx->st[ctx->sp].b = 0;
		continue;

append_npos:
		if (ctx->npos >= sizeof(ctx->buf)) {
			ret = LEJP_REJECT_NUM_TOO_LONG;
			goto reject;
		}
		ctx->buf[ctx->npos++] = (char)c;
		continue;

redo_character:
		json--;
		len++;
	}

	return LEJP_CONTINUE;

completed_l:
	ctx->path_match = 0;
	if ((n && ctx->pst[ctx->pst_sp].callback(ctx, (char)n)) ||
	    ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_COMPLETE))
		goto reject_callback;

	/* done, return unused amount */
	return len;

reject_callback:
	ret = LEJP_REJECT_CALLBACK;

reject:
	ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_FAILED);
	return ret;
}

int
lejp_parser_push(struct lejp_ctx *ctx, void *user, const char * const *paths,
		 unsigned char paths_count, lejp_callback lejp_cb)
{
	struct _lejp_parsing_stack *p;

	if (ctx->pst_sp + 1 == LEJP_MAX_PARSING_STACK_DEPTH)
		return -1;

	lejp_check_path_match(ctx);

	ctx->pst[ctx->pst_sp].path_match = ctx->path_match;
	ctx->pst_sp++;

	p = &ctx->pst[ctx->pst_sp];
	p->user = user;
	p->callback = lejp_cb;
	p->paths = paths;
	p->count_paths = paths_count;
	p->ppos = 0;

	ctx->path_match = 0;
	lejp_check_path_match(ctx);

	lwsl_debug("%s: pushed parser stack to %d (path %s)\n", __func__,
		   ctx->pst_sp, ctx->path);

	return 0;
}

int
lejp_parser_pop(struct lejp_ctx *ctx)
{
	if (!ctx->pst_sp)
		return -1;

	ctx->pst_sp--;
	lwsl_debug("%s: popped parser stack to %d\n", __func__, ctx->pst_sp);

	ctx->path_match = 0; /* force it to check */
	lejp_check_path_match(ctx);

	return 0;
}

const char *
lejp_error_to_string(int e)
{
	if (e > 0)
		e = 0;
	else
		e = -e;

	if (e >= (int)LWS_ARRAY_SIZE(parser_errs))
		return "Unknown error";

	return parser_errs[e];
}

int
lejp_string_unify(struct lejp_ctx *ctx, struct lwsac **ac)
{
	char *p;

	if (ctx->su.pieces == 1)
		return 0;

	ctx->su.fp = lwsac_use(ac, ctx->su.asl + 1u, 512);
	if (!ctx->su.fp)
		return 1;

	p = ctx->su.fp;
	while (ctx->su.sph) {
		memcpy(p, ctx->su.sph->piece, ctx->su.sph->len);
		p += ctx->su.sph->len;
		ctx->su.sph = ctx->su.sph->next;
	}
	*p = '\0';

	return 0;
}

int
lejp_string_unify_part(struct lejp_ctx *ctx, struct lwsac **ac, char reason)
{
	if (reason == LEJPCB_VAL_STR_START) {
		memset(&ctx->su, 0, sizeof(ctx->su));
		ctx->su.sp_next = &ctx->su.sph;
	}

	if (
	    (reason == LEJPCB_VAL_STR_START && ctx->npos) ||
	    (reason == LEJPCB_VAL_STR_CHUNK && ctx->npos) ||
	    (reason == LEJPCB_VAL_STR_END)) {
		lejp_string_piece_t *s;

		ctx->su.fp = lwsac_use(ac, ctx->npos + 1u, 512);
		if (!ctx->su.fp)
			return 1;
		memcpy(ctx->su.fp, ctx->buf, ctx->npos);
		ctx->su.fp[ctx->npos] = '\0';
		s = lwsac_use_zero(ac, sizeof(*s), 512);
		if (!s)
			return 1;
		*ctx->su.sp_next = s;
		s->piece = ctx->su.fp;
		s->len = ctx->npos;
		ctx->su.asl += ctx->npos;
		ctx->su.sp_next = &s->next;

		if (reason == LEJPCB_VAL_STR_END)
			ctx->su.sp_next = &ctx->su.sph;
	}

	return 0;
}

