/* nanojson - very small JSON event parser with push API - token level

   This nanolib is written by Tibor 'Igor2' Palinkas in 2023 and 2024 and
   is licensed under the Creative Common CC0 (or is placed in the Public Domain
   where it is permitted by the law). */
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <string.h>
#include "nanojson.h"

static void buf_append(njson_buf_t *buf, char chr)
{
	if ((buf->used+1) >= buf->alloced) {
		if (buf->alloced == 0)
			buf->alloced = 32;
		else if (buf->alloced < 65536)
			buf->alloced *= 2;
		else
			buf->alloced += 32768;
		buf->arr = realloc(buf->arr, buf->alloced);
	}
	buf->arr[buf->used++] = chr;
	buf->arr[buf->used] = '\0';
}

static char buf_pop_last(njson_buf_t *buf)
{
	char res = '\0';

	if (buf->used > 0) {
		res = buf->arr[--buf->used];
		buf->arr[buf->used] = '\0';
	}

	return res;
}

#define st_ret(st_, ret_) \
do { \
	ctx->state = (st_); \
	return (ret_); \
} while(0)

#define err_ret(msg) \
do { \
	ctx->error = msg; \
	ctx->state = NJSON_ST_ERROR; \
	return NJSON_EV_error; \
} while(0)

/* String buffer shorthands */
#define reset()    do { ctx->str.used = 0; if (ctx->str.arr != NULL) ctx->str.arr[0] = '\0'; } while(0)
#define append(c)  buf_append(&ctx->str, c)

/* Stack shorthands */
#define stk_push(ctx, chr) \
do { \
	buf_append(&ctx->stack, chr); \
	ctx->stack_top = chr; \
} while(0)

#define stk_pop(ctx, expected) \
do { \
	char last = buf_pop_last(&ctx->stack); \
	if (last == '\0')                 err_ret("stack underflow"); \
	if (last != expected)             err_ret("close mismatch"); \
	if (ctx->stack.used == 0)         { ctx->state = NJSON_ST_WANT_EOF; ctx->stack_top = 0; } \
	else ctx->stack_top = ctx->stack.arr[ctx->stack.used-1]; \
} while(0)

#define loc_update(ctx, chr) \
do { \
	if (chr == '\n') { \
		ctx->lineno++; \
		ctx->col = 0; \
	} \
	else \
		ctx->col++; \
} while(0)

njson_ev_t njson_push(njson_ctx_t *ctx, int chr)
{

	/* if we have a char already read ahead, try to process it */
	if (ctx->ahead != 0) {
		char ah;
		njson_ev_t ev;
		
		ah = ctx->ahead;
		ctx->ahead = 0;
		ev = njson_push(ctx, ah);

		if (ev != NJSON_EV_more) {
			/* the ahead char resulted in a new token - we need to return that, so
			   remember chr as ahead for the next cycle */
			ctx->ahead = chr;
			return ev;
		}
		/* else we can now process chr */
	}

	loc_update(ctx, chr);

	switch(ctx->state) {
		case NJSON_ST_IDLE:
			if (isspace(chr)) return NJSON_EV_more;
			switch(chr) {
				case '{': stk_push(ctx, '{'); ctx->has_name = 0; return NJSON_EV_OBJECT_BEGIN;
				case '}': stk_pop(ctx, '{'); return NJSON_EV_OBJECT_END;
				case '[': stk_push(ctx, '['); ctx->has_name = 0; return NJSON_EV_ARRAY_BEGIN;
				case ']': stk_pop(ctx, '['); return NJSON_EV_ARRAY_END;
				case '-': case '0': case '1': case '2': case '3': case '4': 
				case '5': case '6': case '7': case '8': case '9':
					ctx->num_has_dot = ctx->num_has_exp = 0;
					reset(); append(chr); st_ret(NJSON_ST_NUMBER, NJSON_EV_more);
				case '"': reset(); st_ret(NJSON_ST_STRING, NJSON_EV_more);
				case 't': case 'f': case 'n': reset(); append(chr); st_ret(NJSON_ST_KEYWORD, NJSON_EV_more);
				case ',': ctx->has_name = 0; return NJSON_EV_more;
				case ':':
					if (ctx->stack_top != '{')  err_ret("colon in array");
					if (ctx->has_name)          err_ret("multiple colons in object");
					ctx->has_name = 1;
					return NJSON_EV_more;
				case -1: err_ret("premature eof");
			}
			err_ret("unexpected character between tokens");

		case NJSON_ST_STRING:
			switch(chr) {
				case '\\': st_ret(NJSON_ST_STRING_ESC, NJSON_EV_more);
				case '\"':
					ctx->value.string = ctx->str.arr;
					if ((ctx->stack_top == '{') && !ctx->has_name)
						st_ret(NJSON_ST_IDLE, NJSON_EV_NAME);
					else
						st_ret(NJSON_ST_IDLE, NJSON_EV_STRING);
			}
			append(chr);
			return NJSON_EV_more;

		case NJSON_ST_STRING_ESC:
			switch(chr) {
				case 'b': append('\b'); break;
				case 'f': append('\f'); break;
				case 'n': append('\n'); break;
				case 'r': append('\r'); break;
				case 't': append('\t'); break;
				case 'u': ctx->hex_used = 0; st_ret(NJSON_ST_STRING_HEX, NJSON_EV_more);
				default: append(chr);
			}
			st_ret(NJSON_ST_STRING, NJSON_EV_more);

		case NJSON_ST_STRING_HEX:
			if (ctx->hex_used >= 4) err_ret("internal error: hex_used confusion");
			ctx->hex[ctx->hex_used++] = chr;
			if (ctx->hex_used == 4) {
				/* TODO: figure what to do with these */
				append('?');
			}
			st_ret(NJSON_ST_STRING, NJSON_EV_more);

		case NJSON_ST_NUMBER:
			if (ctx->after_exp) { /* an e followed by a sign */
				ctx->after_exp = 0;
				if ((chr == '+') || (chr == '-')) {
					append(chr);
					return NJSON_EV_more;
				}
			}
			switch(chr) {
				case '0': case '1': case '2': case '3': case '4': 
				case '5': case '6': case '7': case '8': case '9':
					append(chr); return NJSON_EV_more;
				case '.':
					if (ctx->num_has_dot)
						err_ret("number error: excess dot");
					append(chr);
					ctx->num_has_dot = 1;
					return NJSON_EV_more;
				case 'e': case 'E':
					if (ctx->num_has_exp)
						err_ret("number error: multiple exponents");
					append(chr);
					ctx->num_has_exp = ctx->after_exp = ctx->num_has_dot = 1;
					return NJSON_EV_more;
			}

			/* any other char is part of the next token */
			ctx->value.number = strtod(ctx->str.arr, NULL);
			reset();
			ctx->ahead = chr;
			st_ret(NJSON_ST_IDLE, NJSON_EV_NUMBER);

		case NJSON_ST_KEYWORD:
			append(chr);
			if ((ctx->str.used == 4) && (memcmp(ctx->str.arr, "null", 4) == 0))
				st_ret(NJSON_ST_IDLE, NJSON_EV_NULL);
			if ((ctx->str.used == 4) && (memcmp(ctx->str.arr, "true", 4) == 0))
				st_ret(NJSON_ST_IDLE, NJSON_EV_TRUE);
			if ((ctx->str.used == 5) && (memcmp(ctx->str.arr, "false", 5) == 0))
				st_ret(NJSON_ST_IDLE, NJSON_EV_FALSE);
			if (ctx->str.used >= 5)
				err_ret("syntax error: invalid keyword");
			return NJSON_EV_more;

		case NJSON_ST_WANT_EOF:
			if (isspace(chr)) return NJSON_EV_more;
			if (chr == -1) st_ret(NJSON_ST_GOT_EOF, NJSON_EV_eof);
			err_ret("excess characters after last close");

		case NJSON_ST_GOT_EOF:
			return NJSON_EV_eof;
		case NJSON_ST_ERROR:
			return NJSON_EV_error;

	}

	err_ret("internal error: invalid state");
}

void njson_uninit(njson_ctx_t *ctx)
{
	free(ctx->str.arr);
	free(ctx->stack.arr);
	memset(ctx, 0, sizeof(njson_ctx_t));
}

