// by jeremie miller - 2014
// public domain or MIT license, contributions/improvements welcome via github at https://github.com/quartzjer/js0n

#include <string.h> // one strncmp() is used to do key comparison, and a strlen(key) if no len passed in
#include "js0n.h"


#ifdef __clang__

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Winitializer-overrides"

/* allow GNU style [0 ... 255] range designators in initializers */
#pragma clang diagnostic ignored "-Wgnu-designator"

#else /* to clang or not to clang */

// gcc started warning for the init syntax used here, is not helpful so don't generate the spam, suppressing the warning is really inconsistently supported across versions
#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
#pragma GCC diagnostic push
#endif
#pragma GCC diagnostic ignored "-Wunknown-pragmas"
#pragma GCC diagnostic ignored "-Wpragmas"
#pragma GCC diagnostic ignored "-Winitializer-overrides"
#pragma GCC diagnostic ignored "-Woverride-init"

/* allow GNU style [0 ... 255] range designators in initializer */
#pragma GCC diagnostic ignored "-Wpedantic"

#endif /* to clang or not to clang */


// only at depth 1, track start pointers to match key/value
#define PUSH(i) if(depth == 1) { if(!index) { val = cur+i; }else{ if(klen && index == 1) start = cur+i; else index--; } }

// determine if key matches or value is complete
#define CAP(i) if(depth == 1) { if(val && !index) {*vlen = (size_t)((cur+i+1) - val); return val;}; if(klen && start) {index = (klen == (size_t)(cur-start) && strncmp(key,start,klen)==0) ? 0 : 2; start = 0;} }

// this makes a single pass across the json bytes, using each byte as an index into a jump table to build an index and transition state
const char *js0n(const char *key, size_t klen,
				 const char *json, size_t jlen, size_t *vlen)
{
	const char *val = 0;
	const char *cur, *end, *start;
	size_t index = 1;
	int depth = 0;
	int utf8_remain = 0;
	static void *gostruct[] =
	{
		[0 ... 255] = &&l_bad,
		['\t'] = &&l_loop, [' '] = &&l_loop, ['\r'] = &&l_loop, ['\n'] = &&l_loop,
		['"'] = &&l_qup,
		[':'] = &&l_loop,[','] = &&l_loop,
		['['] = &&l_up, [']'] = &&l_down, // tracking [] and {} individually would allow fuller validation but is really messy
		['{'] = &&l_up, ['}'] = &&l_down,
		['-'] = &&l_bare, [48 ... 57] = &&l_bare, // 0-9
		[65 ... 90] = &&l_bare, // A-Z
		[97 ... 122] = &&l_bare // a-z
	};
	static void *gobare[] =
	{
		[0 ... 31] = &&l_bad,
		[32 ... 126] = &&l_loop, // could be more pedantic/validation-checking
		['\t'] = &&l_unbare, [' '] = &&l_unbare, ['\r'] = &&l_unbare, ['\n'] = &&l_unbare,
		[','] = &&l_unbare, [']'] = &&l_unbare, ['}'] = &&l_unbare, [':'] = &&l_unbare,
		[127 ... 255] = &&l_bad
	};
	static void *gostring[] =
	{
		[0 ... 31] = &&l_bad, [127] = &&l_bad,
		[32 ... 126] = &&l_loop,
		['\\'] = &&l_esc, ['"'] = &&l_qdown,
		[128 ... 191] = &&l_bad,
		[192 ... 223] = &&l_utf8_2,
		[224 ... 239] = &&l_utf8_3,
		[240 ... 247] = &&l_utf8_4,
		[248 ... 255] = &&l_bad
	};
	static void *goutf8_continue[] =
	{
		[0 ... 127] = &&l_bad,
		[128 ... 191] = &&l_utf_continue,
		[192 ... 255] = &&l_bad
	};
	static void *goesc[] =
	{
		[0 ... 255] = &&l_bad,
		['"'] = &&l_unesc, ['\\'] = &&l_unesc, ['/'] = &&l_unesc, ['b'] = &&l_unesc,
		['f'] = &&l_unesc, ['n'] = &&l_unesc, ['r'] = &&l_unesc, ['t'] = &&l_unesc, ['u'] = &&l_unesc
	};
	void **go = gostruct;

	if(!json || jlen <= 0 || !vlen) return 0;
	*vlen = 0;

	// no key is array mode, klen provides requested index
	if(!key)
	{
		index = klen;
		klen = 0;
	}else{
		if(klen <= 0) klen = strlen(key); // convenience
	}

	for(start=cur=json,end=cur+jlen; cur<end; cur++)
	{
			goto *go[(unsigned char)*cur];
			l_loop:;
	}

	if(depth) *vlen = jlen; // incomplete
	return 0;

	l_bad:
		*vlen = cur - json; // where error'd
		return 0;

	l_up:
		PUSH(0);
		++depth;
		goto l_loop;

	l_down:
		--depth;
		CAP(0);
		goto l_loop;

	l_qup:
		PUSH(1);
		go=gostring;
		goto l_loop;

	l_qdown:
		CAP(-1);
		go=gostruct;
		goto l_loop;

	l_esc:
		go = goesc;
		goto l_loop;

	l_unesc:
		go = gostring;
		goto l_loop;

	l_bare:
		PUSH(0);
		go = gobare;
		goto l_loop;

	l_unbare:
		CAP(-1);
		go = gostruct;
		goto *go[(unsigned char)*cur];

	l_utf8_2:
		go = goutf8_continue;
		utf8_remain = 1;
		goto l_loop;

	l_utf8_3:
		go = goutf8_continue;
		utf8_remain = 2;
		goto l_loop;

	l_utf8_4:
		go = goutf8_continue;
		utf8_remain = 3;
		goto l_loop;

	l_utf_continue:
		if (!--utf8_remain)
			go=gostring;
		goto l_loop;

}


#ifdef __clang__

#pragma clang diagnostic pop

#else /* to clang or not to clang */

#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
#pragma GCC diagnostic pop
#endif

#endif /* to clang or not to clang */
