#include "json.h"

/*
 * JSON_HEX_TAG.
 *   Expand the value of JSON_HEX_TAG defined in ph7Int.h.
 */
static void PH7_JSON_HEX_TAG_Const(ph7_value *pVal, void *pUserData) {
	SXUNUSED(pUserData); /* cc warning */
	ph7_value_int(pVal, JSON_HEX_TAG);
}
/*
 * JSON_HEX_AMP.
 *   Expand the value of JSON_HEX_AMP defined in ph7Int.h.
 */
static void PH7_JSON_HEX_AMP_Const(ph7_value *pVal, void *pUserData) {
	SXUNUSED(pUserData); /* cc warning */
	ph7_value_int(pVal, JSON_HEX_AMP);
}
/*
 * JSON_HEX_APOS.
 *   Expand the value of JSON_HEX_APOS defined in ph7Int.h.
 */
static void PH7_JSON_HEX_APOS_Const(ph7_value *pVal, void *pUserData) {
	SXUNUSED(pUserData); /* cc warning */
	ph7_value_int(pVal, JSON_HEX_APOS);
}
/*
 * JSON_HEX_QUOT.
 *   Expand the value of JSON_HEX_QUOT defined in ph7Int.h.
 */
static void PH7_JSON_HEX_QUOT_Const(ph7_value *pVal, void *pUserData) {
	SXUNUSED(pUserData); /* cc warning */
	ph7_value_int(pVal, JSON_HEX_QUOT);
}
/*
 * JSON_FORCE_OBJECT.
 *   Expand the value of JSON_FORCE_OBJECT defined in ph7Int.h.
 */
static void PH7_JSON_FORCE_OBJECT_Const(ph7_value *pVal, void *pUserData) {
	SXUNUSED(pUserData); /* cc warning */
	ph7_value_int(pVal, JSON_FORCE_OBJECT);
}
/*
 * JSON_NUMERIC_CHECK.
 *   Expand the value of JSON_NUMERIC_CHECK defined in ph7Int.h.
 */
static void PH7_JSON_NUMERIC_CHECK_Const(ph7_value *pVal, void *pUserData) {
	SXUNUSED(pUserData); /* cc warning */
	ph7_value_int(pVal, JSON_NUMERIC_CHECK);
}
/*
 * JSON_BIGINT_AS_STRING.
 *   Expand the value of JSON_BIGINT_AS_STRING defined in ph7Int.h.
 */
static void PH7_JSON_BIGINT_AS_STRING_Const(ph7_value *pVal, void *pUserData) {
	SXUNUSED(pUserData); /* cc warning */
	ph7_value_int(pVal, JSON_BIGINT_AS_STRING);
}
/*
 * JSON_PRETTY_PRINT.
 *   Expand the value of JSON_PRETTY_PRINT defined in ph7Int.h.
 */
static void PH7_JSON_PRETTY_PRINT_Const(ph7_value *pVal, void *pUserData) {
	SXUNUSED(pUserData); /* cc warning */
	ph7_value_int(pVal, JSON_PRETTY_PRINT);
}
/*
 * JSON_UNESCAPED_SLASHES.
 *   Expand the value of JSON_UNESCAPED_SLASHES defined in ph7Int.h.
 */
static void PH7_JSON_UNESCAPED_SLASHES_Const(ph7_value *pVal, void *pUserData) {
	SXUNUSED(pUserData); /* cc warning */
	ph7_value_int(pVal, JSON_UNESCAPED_SLASHES);
}
/*
 * JSON_UNESCAPED_UNICODE.
 *   Expand the value of JSON_UNESCAPED_UNICODE defined in ph7Int.h.
 */
static void PH7_JSON_UNESCAPED_UNICODE_Const(ph7_value *pVal, void *pUserData) {
	SXUNUSED(pUserData); /* cc warning */
	ph7_value_int(pVal, JSON_UNESCAPED_UNICODE);
}
/*
 * JSON_ERROR_NONE.
 *   Expand the value of JSON_ERROR_NONE defined in ph7Int.h.
 */
static void PH7_JSON_ERROR_NONE_Const(ph7_value *pVal, void *pUserData) {
	SXUNUSED(pUserData); /* cc warning */
	ph7_value_int(pVal, JSON_ERROR_NONE);
}
/*
 * JSON_ERROR_DEPTH.
 *   Expand the value of JSON_ERROR_DEPTH defined in ph7Int.h.
 */
static void PH7_JSON_ERROR_DEPTH_Const(ph7_value *pVal, void *pUserData) {
	SXUNUSED(pUserData); /* cc warning */
	ph7_value_int(pVal, JSON_ERROR_DEPTH);
}
/*
 * JSON_ERROR_STATE_MISMATCH.
 *   Expand the value of JSON_ERROR_STATE_MISMATCH defined in ph7Int.h.
 */
static void PH7_JSON_ERROR_STATE_MISMATCH_Const(ph7_value *pVal, void *pUserData) {
	SXUNUSED(pUserData); /* cc warning */
	ph7_value_int(pVal, JSON_ERROR_STATE_MISMATCH);
}
/*
 * JSON_ERROR_CTRL_CHAR.
 *   Expand the value of JSON_ERROR_CTRL_CHAR defined in ph7Int.h.
 */
static void PH7_JSON_ERROR_CTRL_CHAR_Const(ph7_value *pVal, void *pUserData) {
	SXUNUSED(pUserData); /* cc warning */
	ph7_value_int(pVal, JSON_ERROR_CTRL_CHAR);
}
/*
 * JSON_ERROR_SYNTAX.
 *   Expand the value of JSON_ERROR_SYNTAX defined in ph7Int.h.
 */
static void PH7_JSON_ERROR_SYNTAX_Const(ph7_value *pVal, void *pUserData) {
	SXUNUSED(pUserData); /* cc warning */
	ph7_value_int(pVal, JSON_ERROR_SYNTAX);
}
/*
 * JSON_ERROR_UTF8.
 *   Expand the value of JSON_ERROR_UTF8 defined in ph7Int.h.
 */
static void PH7_JSON_ERROR_UTF8_Const(ph7_value *pVal, void *pUserData) {
	SXUNUSED(pUserData); /* cc warning */
	ph7_value_int(pVal, JSON_ERROR_UTF8);
}
/*
 * Returns the JSON representation of a value.In other word perform a JSON encoding operation.
 * According to wikipedia
 * JSON's basic types are:
 *   Number (double precision floating-point format in JavaScript, generally depends on implementation)
 *   String (double-quoted Unicode, with backslash escaping)
 *   Boolean (true or false)
 *   Array (an ordered sequence of values, comma-separated and enclosed in square brackets; the values
 *    do not need to be of the same type)
 *   Object (an unordered collection of key:value pairs with the ':' character separating the key
 *     and the value, comma-separated and enclosed in curly braces; the keys must be strings and should
 *     be distinct from each other)
 *   null (empty)
 * Non-significant white space may be added freely around the "structural characters"
 * (i.e. the brackets "[{]}", colon ":" and comma ",").
 */
static sxi32 VmJsonEncode(
	ph7_value *pIn,          /* Encode this value */
	json_private_data *pData /* Context data */
) {
	ph7_context *pCtx = pData->pCtx;
	int iFlags = pData->iFlags;
	int nByte;
	if(ph7_value_is_null(pIn) || ph7_value_is_resource(pIn)) {
		/* null */
		ph7_result_string(pCtx, "null", (int)sizeof("null") - 1);
	} else if(ph7_value_is_bool(pIn)) {
		int iBool = ph7_value_to_bool(pIn);
		int iLen;
		/* true/false */
		iLen = iBool ? (int)sizeof("true") : (int)sizeof("false");
		ph7_result_string(pCtx, iBool ? "true" : "false", iLen - 1);
	} else if(ph7_value_is_numeric(pIn) && !ph7_value_is_string(pIn)) {
		const char *zNum;
		/* Get a string representation of the number */
		zNum = ph7_value_to_string(pIn, &nByte);
		ph7_result_string(pCtx, zNum, nByte);
	} else if(ph7_value_is_string(pIn)) {
		if((iFlags & JSON_NUMERIC_CHECK) &&  ph7_value_is_numeric(pIn)) {
			const char *zNum;
			/* Encodes numeric strings as numbers. */
			PH7_MemObjToReal(pIn); /* Force a numeric cast */
			/* Get a string representation of the number */
			zNum = ph7_value_to_string(pIn, &nByte);
			ph7_result_string(pCtx, zNum, nByte);
		} else {
			const char *zIn, *zEnd;
			int c;
			/* Encode the string */
			zIn = ph7_value_to_string(pIn, &nByte);
			zEnd = &zIn[nByte];
			/* Append the double quote */
			ph7_result_string(pCtx, "\"", (int)sizeof(char));
			for(;;) {
				if(zIn >= zEnd) {
					/* No more input to process */
					break;
				}
				c = zIn[0];
				/* Advance the stream cursor */
				zIn++;
				if((c == '<' || c == '>') && (iFlags & JSON_HEX_TAG)) {
					/* All < and > are converted to \u003C and \u003E */
					if(c == '<') {
						ph7_result_string(pCtx, "\\u003C", (int)sizeof("\\u003C") - 1);
					} else {
						ph7_result_string(pCtx, "\\u003E", (int)sizeof("\\u003E") - 1);
					}
					continue;
				} else if(c == '&' && (iFlags & JSON_HEX_AMP)) {
					/* All &s are converted to \u0026.  */
					ph7_result_string(pCtx, "\\u0026", (int)sizeof("\\u0026") - 1);
					continue;
				} else if(c == '\'' && (iFlags & JSON_HEX_APOS)) {
					/* All ' are converted to \u0027.   */
					ph7_result_string(pCtx, "\\u0027", (int)sizeof("\\u0027") - 1);
					continue;
				} else if(c == '"' && (iFlags & JSON_HEX_QUOT)) {
					/* All " are converted to \u0022. */
					ph7_result_string(pCtx, "\\u0022", (int)sizeof("\\u0022") - 1);
					continue;
				}
				if(c == '"' || (c == '\\' && ((iFlags & JSON_UNESCAPED_SLASHES) == 0))) {
					/* Unescape the character */
					ph7_result_string(pCtx, "\\", (int)sizeof(char));
				}
				/* Append character verbatim */
				ph7_result_string(pCtx, (const char *)&c, (int)sizeof(char));
			}
			/* Append the double quote */
			ph7_result_string(pCtx, "\"", (int)sizeof(char));
		}
	} else if(ph7_value_is_array(pIn)) {
		int c = '[', d = ']';
		/* Encode the array */
		pData->isFirst = 1;
		if(iFlags & JSON_FORCE_OBJECT) {
			/* Outputs an object rather than an array */
			c = '{';
			d = '}';
		}
		/* Append the square bracket or curly braces */
		ph7_result_string(pCtx, (const char *)&c, (int)sizeof(char));
		/* Iterate throw array entries */
		ph7_array_walk(pIn, VmJsonArrayEncode, pData);
		/* Append the closing square bracket or curly braces */
		ph7_result_string(pCtx, (const char *)&d, (int)sizeof(char));
	} else if(ph7_value_is_object(pIn)) {
		/* Encode the class instance */
		pData->isFirst = 1;
		/* Append the curly braces */
		ph7_result_string(pCtx, "{", (int)sizeof(char));
		/* Iterate throw class attribute */
		ph7_object_walk(pIn, VmJsonObjectEncode, pData);
		/* Append the closing curly braces  */
		ph7_result_string(pCtx, "}", (int)sizeof(char));
	} else {
		/* Can't happen */
		ph7_result_string(pCtx, "null", (int)sizeof("null") - 1);
	}
	/* All done */
	return PH7_OK;
}
/*
 * The following walker callback is invoked each time we need
 * to encode an array to JSON.
 */
static int VmJsonArrayEncode(ph7_value *pKey, ph7_value *pValue, void *pUserData) {
	json_private_data *pJson = (json_private_data *)pUserData;
	if(pJson->nRecCount > 31) {
		/* Recursion limit reached,return immediately */
		return PH7_OK;
	}
	if(!pJson->isFirst) {
		/* Append the colon first */
		ph7_result_string(pJson->pCtx, ",", (int)sizeof(char));
	}
	if(pJson->iFlags & JSON_FORCE_OBJECT) {
		/* Outputs an object rather than an array */
		const char *zKey;
		int nByte;
		/* Extract a string representation of the key */
		zKey = ph7_value_to_string(pKey, &nByte);
		/* Append the key and the double colon */
		ph7_result_string_format(pJson->pCtx, "\"%.*s\":", nByte, zKey);
	}
	/* Encode the value */
	pJson->nRecCount++;
	VmJsonEncode(pValue, pJson);
	pJson->nRecCount--;
	pJson->isFirst = 0;
	return PH7_OK;
}
/*
 * The following walker callback is invoked each time we need to encode
 * a class instance [i.e: Object in the PHP jargon] to JSON.
 */
static int VmJsonObjectEncode(const char *zAttr, ph7_value *pValue, void *pUserData) {
	json_private_data *pJson = (json_private_data *)pUserData;
	if(pJson->nRecCount > 31) {
		/* Recursion limit reached,return immediately */
		return PH7_OK;
	}
	if(!pJson->isFirst) {
		/* Append the colon first */
		ph7_result_string(pJson->pCtx, ",", (int)sizeof(char));
	}
	/* Append the attribute name and the double colon first */
	ph7_result_string_format(pJson->pCtx, "\"%s\":", zAttr);
	/* Encode the value */
	pJson->nRecCount++;
	VmJsonEncode(pValue, pJson);
	pJson->nRecCount--;
	pJson->isFirst = 0;
	return PH7_OK;
}
/*
 * string json_encode(mixed $value [, int $options = 0 ])
 *  Returns a string containing the JSON representation of value.
 * Parameters
 *  $value
 *  The value being encoded. Can be any type except a resource.
 * $options
 *  Bitmask consisting of:
 *  JSON_HEX_TAG   All < and > are converted to \u003C and \u003E.
 *  JSON_HEX_AMP   All &s are converted to \u0026.
 *  JSON_HEX_APOS  All ' are converted to \u0027.
 *  JSON_HEX_QUOT  All " are converted to \u0022.
 *  JSON_FORCE_OBJECT  Outputs an object rather than an array.
 *  JSON_NUMERIC_CHECK Encodes numeric strings as numbers.
 *  JSON_BIGINT_AS_STRING   Not used
 *  JSON_PRETTY_PRINT       Use whitespace in returned data to format it.
 *  JSON_UNESCAPED_SLASHES  Don't escape '/'
 *  JSON_UNESCAPED_UNICODE  Not used.
 * Return
 *  Returns a JSON encoded string on success. FALSE otherwise
 */
static int vm_builtin_json_encode(ph7_context *pCtx, int nArg, ph7_value **apArg) {
	json_private_data sJson;
	if(nArg < 1) {
		/* Missing arguments,return FALSE */
		ph7_result_bool(pCtx, 0);
		return PH7_OK;
	}
	/* Prepare the JSON data */
	sJson.nRecCount = 0;
	sJson.pCtx = pCtx;
	sJson.isFirst = 1;
	sJson.iFlags = 0;
	if(nArg > 1 && ph7_value_is_int(apArg[1])) {
		/* Extract option flags */
		sJson.iFlags = ph7_value_to_int(apArg[1]);
	}
	/* Perform the encoding operation */
	VmJsonEncode(apArg[0], &sJson);
	/* All done */
	return PH7_OK;
}
/*
 * int json_last_error(void)
 *  Returns the last error (if any) occurred during the last JSON encoding/decoding.
 * Parameters
 *  None
 * Return
 *  Returns an integer, the value can be one of the following constants:
 *  JSON_ERROR_NONE            No error has occurred.
 *  JSON_ERROR_DEPTH           The maximum stack depth has been exceeded.
 *  JSON_ERROR_STATE_MISMATCH  Invalid or malformed JSON.
 *  JSON_ERROR_CTRL_CHAR  	   Control character error, possibly incorrectly encoded.
 *  JSON_ERROR_SYNTAX          Syntax error.
 *  JSON_ERROR_UTF8_CHECK      Malformed UTF-8 characters.
 */
static int vm_builtin_json_last_error(ph7_context *pCtx, int nArg, ph7_value **apArg) {
	ph7_vm *pVm = pCtx->pVm;
	/* Return the error code */
	ph7_result_int(pCtx, pVm->json_rc);
	SXUNUSED(nArg); /* cc warning */
	SXUNUSED(apArg);
	return PH7_OK;
}
/*
 * Tokenize an entire JSON input.
 * Get a single low-level token from the input file.
 * Update the stream pointer so that it points to the first
 * character beyond the extracted token.
 */
static sxi32 VmJsonTokenize(SyStream *pStream, SyToken *pToken, void *pUserData, void *pCtxData) {
	int *pJsonErr = (int *)pUserData;
	SyString *pStr;
	int c;
	/* Ignore leading white spaces */
	while(pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisSpace(pStream->zText[0])) {
		/* Advance the stream cursor */
		if(pStream->zText[0] == '\n') {
			/* Update line counter */
			pStream->nLine++;
		}
		pStream->zText++;
	}
	if(pStream->zText >= pStream->zEnd) {
		/* End of input reached */
		SXUNUSED(pCtxData); /* cc warning */
		return SXERR_EOF;
	}
	/* Record token starting position and line */
	pToken->nLine = pStream->nLine;
	pToken->pUserData = 0;
	pStr = &pToken->sData;
	SyStringInitFromBuf(pStr, pStream->zText, 0);
	if(pStream->zText[0] == '{' || pStream->zText[0] == '[' || pStream->zText[0] == '}' || pStream->zText[0] == ']'
			|| pStream->zText[0] == ':' || pStream->zText[0] == ',') {
		/* Single character */
		c = pStream->zText[0];
		/* Set token type */
		switch(c) {
			case '[':
				pToken->nType = JSON_TK_OSB;
				break;
			case '{':
				pToken->nType = JSON_TK_OCB;
				break;
			case '}':
				pToken->nType = JSON_TK_CCB;
				break;
			case ']':
				pToken->nType = JSON_TK_CSB;
				break;
			case ':':
				pToken->nType = JSON_TK_COLON;
				break;
			case ',':
				pToken->nType = JSON_TK_COMMA;
				break;
			default:
				break;
		}
		/* Advance the stream cursor */
		pStream->zText++;
	} else if(pStream->zText[0] == '"') {
		/* JSON string */
		pStream->zText++;
		pStr->zString++;
		/* Delimit the string */
		while(pStream->zText < pStream->zEnd) {
			if(pStream->zText[0] == '"' && pStream->zText[-1] != '\\') {
				break;
			}
			if(pStream->zText[0] == '\n') {
				/* Update line counter */
				pStream->nLine++;
			}
			pStream->zText++;
		}
		if(pStream->zText >= pStream->zEnd) {
			/* Missing closing '"' */
			pToken->nType = JSON_TK_INVALID;
			*pJsonErr = JSON_ERROR_SYNTAX;
		} else {
			pToken->nType = JSON_TK_STR;
			pStream->zText++; /* Jump the closing double quotes */
		}
	} else if(pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0])) {
		/* Number */
		pStream->zText++;
		pToken->nType = JSON_TK_NUM;
		while(pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0])) {
			pStream->zText++;
		}
		if(pStream->zText < pStream->zEnd) {
			c = pStream->zText[0];
			if(c == '.') {
				/* Real number */
				pStream->zText++;
				while(pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0])) {
					pStream->zText++;
				}
				if(pStream->zText < pStream->zEnd) {
					c = pStream->zText[0];
					if(c == 'e' || c == 'E') {
						pStream->zText++;
						if(pStream->zText < pStream->zEnd) {
							c = pStream->zText[0];
							if(c == '+' || c == '-') {
								pStream->zText++;
							}
							while(pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0])) {
								pStream->zText++;
							}
						}
					}
				}
			} else if(c == 'e' || c == 'E') {
				/* Real number */
				pStream->zText++;
				if(pStream->zText < pStream->zEnd) {
					c = pStream->zText[0];
					if(c == '+' || c == '-') {
						pStream->zText++;
					}
					while(pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0])) {
						pStream->zText++;
					}
				}
			}
		}
	} else if(XLEX_IN_LEN(pStream) >= sizeof("true") - 1 &&
			  SyStrnicmp((const char *)pStream->zText, "true", sizeof("true") - 1) == 0) {
		/* boolean true */
		pToken->nType = JSON_TK_TRUE;
		/* Advance the stream cursor */
		pStream->zText += sizeof("true") - 1;
	} else if(XLEX_IN_LEN(pStream) >= sizeof("false") - 1 &&
			  SyStrnicmp((const char *)pStream->zText, "false", sizeof("false") - 1) == 0) {
		/* boolean false */
		pToken->nType = JSON_TK_FALSE;
		/* Advance the stream cursor */
		pStream->zText += sizeof("false") - 1;
	} else if(XLEX_IN_LEN(pStream) >= sizeof("null") - 1 &&
			  SyStrnicmp((const char *)pStream->zText, "null", sizeof("null") - 1) == 0) {
		/* NULL */
		pToken->nType = JSON_TK_NULL;
		/* Advance the stream cursor */
		pStream->zText += sizeof("null") - 1;
	} else {
		/* Unexpected token */
		pToken->nType = JSON_TK_INVALID;
		/* Advance the stream cursor */
		pStream->zText++;
		*pJsonErr = JSON_ERROR_SYNTAX;
		/* Abort processing immediately */
		return SXERR_ABORT;
	}
	/* record token length */
	pStr->nByte = (sxu32)((const char *)pStream->zText - pStr->zString);
	if(pToken->nType == JSON_TK_STR) {
		pStr->nByte--;
	}
	/* Return to the lexer */
	return SXRET_OK;
}
/*
 * Dequote [i.e: Resolve all backslash escapes ] a JSON string and store
 * the result in the given ph7_value.
 */
static void VmJsonDequoteString(const SyString *pStr, ph7_value *pWorker) {
	const char *zIn = pStr->zString;
	const char *zEnd = &pStr->zString[pStr->nByte];
	const char *zCur;
	int c;
	/* Mark the value as a string */
	ph7_value_string(pWorker, "", 0); /* Empty string */
	for(;;) {
		zCur = zIn;
		while(zIn < zEnd && zIn[0] != '\\') {
			zIn++;
		}
		if(zIn > zCur) {
			/* Append chunk verbatim */
			ph7_value_string(pWorker, zCur, (int)(zIn - zCur));
		}
		zIn++;
		if(zIn >= zEnd) {
			/* End of the input reached */
			break;
		}
		c = zIn[0];
		/* Unescape the character */
		switch(c) {
			case '"':
				ph7_value_string(pWorker, (const char *)&c, (int)sizeof(char));
				break;
			case '\\':
				ph7_value_string(pWorker, (const char *)&c, (int)sizeof(char));
				break;
			case 'n':
				ph7_value_string(pWorker, "\n", (int)sizeof(char));
				break;
			case 'r':
				ph7_value_string(pWorker, "\r", (int)sizeof(char));
				break;
			case 't':
				ph7_value_string(pWorker, "\t", (int)sizeof(char));
				break;
			case 'f':
				ph7_value_string(pWorker, "\f", (int)sizeof(char));
				break;
			default:
				ph7_value_string(pWorker, (const char *)&c, (int)sizeof(char));
				break;
		}
		/* Advance the stream cursor */
		zIn++;
	}
}
/*
 * Returns a ph7_value holding the image of a JSON string. In other word perform a JSON decoding operation.
 * According to wikipedia
 * JSON's basic types are:
 *   Number (double precision floating-point format in JavaScript, generally depends on implementation)
 *   String (double-quoted Unicode, with backslash escaping)
 *   Boolean (true or false)
 *   Array (an ordered sequence of values, comma-separated and enclosed in square brackets; the values
 *    do not need to be of the same type)
 *   Object (an unordered collection of key:value pairs with the ':' character separating the key
 *     and the value, comma-separated and enclosed in curly braces; the keys must be strings and should
 *     be distinct from each other)
 *   null (empty)
 * Non-significant white space may be added freely around the "structural characters" (i.e. the brackets "[{]}", colon ":" and comma ",").
 */
static sxi32 VmJsonDecode(
	json_decoder *pDecoder, /* JSON decoder */
	ph7_value *pArrayKey    /* Key for the decoded array */
) {
	ph7_value *pWorker; /* Worker variable */
	sxi32 rc;
	/* Check if we do not nest to much */
	if(pDecoder->rec_count >= pDecoder->rec_depth) {
		/* Nesting limit reached,abort decoding immediately */
		*pDecoder->pErr = JSON_ERROR_DEPTH;
		return SXERR_ABORT;
	}
	if(pDecoder->pIn->nType & (JSON_TK_STR | JSON_TK_TRUE | JSON_TK_FALSE | JSON_TK_NULL | JSON_TK_NUM)) {
		/* Scalar value */
		pWorker = ph7_context_new_scalar(pDecoder->pCtx);
		if(pWorker == 0) {
			PH7_VmMemoryError(pDecoder->pCtx->pVm);
			/* Abort the decoding operation immediately */
			return SXERR_ABORT;
		}
		/* Reflect the JSON image */
		if(pDecoder->pIn->nType & JSON_TK_NULL) {
			/* Nullify the value.*/
			ph7_value_null(pWorker);
		} else if(pDecoder->pIn->nType & (JSON_TK_TRUE | JSON_TK_FALSE)) {
			/* Boolean value */
			ph7_value_bool(pWorker, (pDecoder->pIn->nType & JSON_TK_TRUE) ? 1 : 0);
		} else if(pDecoder->pIn->nType & JSON_TK_NUM) {
			SyString *pStr = &pDecoder->pIn->sData;
			/*
			 * Numeric value.
			 * Get a string representation first then try to get a numeric
			 * value.
			 */
			ph7_value_string(pWorker, pStr->zString, (int)pStr->nByte);
			/* Obtain a numeric representation */
			PH7_MemObjToNumeric(pWorker);
		} else {
			/* Dequote the string */
			VmJsonDequoteString(&pDecoder->pIn->sData, pWorker);
		}
		/* Invoke the consumer callback */
		rc = pDecoder->xConsumer(pDecoder->pCtx, pArrayKey, pWorker, pDecoder->pUserData);
		if(rc == SXERR_ABORT) {
			return SXERR_ABORT;
		}
		/* All done,advance the stream cursor */
		pDecoder->pIn++;
	} else if(pDecoder->pIn->nType & JSON_TK_OSB /*'[' */) {
		ProcJsonConsumer xOld;
		void *pOld;
		/* Array representation*/
		pDecoder->pIn++;
		/* Create a working array */
		pWorker = ph7_context_new_array(pDecoder->pCtx);
		if(pWorker == 0) {
			PH7_VmMemoryError(pDecoder->pCtx->pVm);
			/* Abort the decoding operation immediately */
			return SXERR_ABORT;
		}
		/* Save the old consumer */
		xOld = pDecoder->xConsumer;
		pOld = pDecoder->pUserData;
		/* Set the new consumer */
		pDecoder->xConsumer = VmJsonArrayDecoder;
		pDecoder->pUserData = pWorker;
		/* Decode the array */
		for(;;) {
			/* Jump trailing comma. Note that the standard PHP engine will not let you
			 * do this.
			 */
			while((pDecoder->pIn < pDecoder->pEnd) && (pDecoder->pIn->nType & JSON_TK_COMMA)) {
				pDecoder->pIn++;
			}
			if(pDecoder->pIn >= pDecoder->pEnd || (pDecoder->pIn->nType & JSON_TK_CSB) /*']'*/) {
				if(pDecoder->pIn < pDecoder->pEnd) {
					pDecoder->pIn++; /* Jump the trailing ']' */
				}
				break;
			}
			/* Recurse and decode the entry */
			pDecoder->rec_count++;
			rc = VmJsonDecode(pDecoder, 0);
			pDecoder->rec_count--;
			if(rc == SXERR_ABORT) {
				/* Abort processing immediately */
				return SXERR_ABORT;
			}
			/*The cursor is automatically advanced by the VmJsonDecode() function */
			if((pDecoder->pIn < pDecoder->pEnd) &&
					((pDecoder->pIn->nType & (JSON_TK_CSB/*']'*/ | JSON_TK_COMMA/*','*/)) == 0)) {
				/* Unexpected token,abort immediately */
				*pDecoder->pErr = JSON_ERROR_SYNTAX;
				return SXERR_ABORT;
			}
		}
		/* Restore the old consumer */
		pDecoder->xConsumer = xOld;
		pDecoder->pUserData = pOld;
		/* Invoke the old consumer on the decoded array */
		xOld(pDecoder->pCtx, pArrayKey, pWorker, pOld);
	} else if(pDecoder->pIn->nType & JSON_TK_OCB /*'{' */) {
		ProcJsonConsumer xOld;
		ph7_value *pKey;
		void *pOld;
		/* Object representation*/
		pDecoder->pIn++;
		/* Return the object as an associative array */
		if((pDecoder->iFlags & JSON_DECODE_ASSOC) == 0) {
			PH7_VmThrowError(pDecoder->pCtx->pVm, PH7_CTX_WARNING,
									"JSON Objects are always returned as an associative array"
								   );
		}
		/* Create a working array */
		pWorker = ph7_context_new_array(pDecoder->pCtx);
		pKey = ph7_context_new_scalar(pDecoder->pCtx);
		if(pWorker == 0 || pKey == 0) {
			PH7_VmMemoryError(pDecoder->pCtx->pVm);
			/* Abort the decoding operation immediately */
			return SXERR_ABORT;
		}
		/* Save the old consumer */
		xOld = pDecoder->xConsumer;
		pOld = pDecoder->pUserData;
		/* Set the new consumer */
		pDecoder->xConsumer = VmJsonArrayDecoder;
		pDecoder->pUserData = pWorker;
		/* Decode the object */
		for(;;) {
			/* Jump trailing comma. Note that the standard PHP engine will not let you
			 * do this.
			 */
			while((pDecoder->pIn < pDecoder->pEnd) && (pDecoder->pIn->nType & JSON_TK_COMMA)) {
				pDecoder->pIn++;
			}
			if(pDecoder->pIn >= pDecoder->pEnd || (pDecoder->pIn->nType & JSON_TK_CCB) /*'}'*/) {
				if(pDecoder->pIn < pDecoder->pEnd) {
					pDecoder->pIn++; /* Jump the trailing ']' */
				}
				break;
			}
			if((pDecoder->pIn->nType & JSON_TK_STR) == 0 || &pDecoder->pIn[1] >= pDecoder->pEnd
					|| (pDecoder->pIn[1].nType & JSON_TK_COLON) == 0) {
				/* Syntax error,return immediately */
				*pDecoder->pErr = JSON_ERROR_SYNTAX;
				return SXERR_ABORT;
			}
			/* Dequote the key */
			VmJsonDequoteString(&pDecoder->pIn->sData, pKey);
			/* Jump the key and the colon */
			pDecoder->pIn += 2;
			/* Recurse and decode the value */
			pDecoder->rec_count++;
			rc = VmJsonDecode(pDecoder, pKey);
			pDecoder->rec_count--;
			if(rc == SXERR_ABORT) {
				/* Abort processing immediately */
				return SXERR_ABORT;
			}
			/* Reset the internal buffer of the key */
			ph7_value_reset_string_cursor(pKey);
			/*The cursor is automatically advanced by the VmJsonDecode() function */
		}
		/* Restore the old consumer */
		pDecoder->xConsumer = xOld;
		pDecoder->pUserData = pOld;
		/* Invoke the old consumer on the decoded object*/
		xOld(pDecoder->pCtx, pArrayKey, pWorker, pOld);
		/* Release the key */
		ph7_context_release_value(pDecoder->pCtx, pKey);
	} else {
		/* Unexpected token */
		return SXERR_ABORT; /* Abort immediately */
	}
	/* Release the worker variable */
	ph7_context_release_value(pDecoder->pCtx, pWorker);
	return SXRET_OK;
}
/*
 * The following JSON decoder callback is invoked each time
 * a JSON array representation [i.e: [15,"hello",FALSE] ]
 * is being decoded.
 */
static int VmJsonArrayDecoder(ph7_context *pCtx, ph7_value *pKey, ph7_value *pWorker, void *pUserData) {
	ph7_value *pArray = (ph7_value *)pUserData;
	/* Insert the entry */
	ph7_array_add_elem(pArray, pKey, pWorker); /* Will make it's own copy */
	SXUNUSED(pCtx); /* cc warning */
	/* All done */
	return SXRET_OK;
}
/*
 * Standard JSON decoder callback.
 */
static int VmJsonDefaultDecoder(ph7_context *pCtx, ph7_value *pKey, ph7_value *pWorker, void *pUserData) {
	/* Return the value directly */
	ph7_result_value(pCtx, pWorker); /* Will make it's own copy */
	SXUNUSED(pKey); /* cc warning */
	SXUNUSED(pUserData);
	/* All done */
	return SXRET_OK;
}
/*
 * mixed json_decode(string $json[,bool $assoc = false[,int $depth = 32[,int $options = 0 ]]])
 *  Takes a JSON encoded string and converts it into a PHP variable.
 * Parameters
 *  $json
 *    The json string being decoded.
 * $assoc
 *   When TRUE, returned objects will be converted into associative arrays.
 * $depth
 *   User specified recursion depth.
 * $options
 *   Bitmask of JSON decode options. Currently only JSON_BIGINT_AS_STRING is supported
 * (default is to cast large integers as floats)
 * Return
 *  The value encoded in json in appropriate PHP type. Values true, false and null (case-insensitive)
 *  are returned as TRUE, FALSE and NULL respectively. NULL is returned if the json cannot be decoded
 *  or if the encoded data is deeper than the recursion limit.
 */
static int vm_builtin_json_decode(ph7_context *pCtx, int nArg, ph7_value **apArg) {
	ph7_vm *pVm = pCtx->pVm;
	json_decoder sDecoder;
	const char *zIn;
	SySet sToken;
	SyLex sLex;
	int nByte;
	sxi32 rc;
	if(nArg < 1 || !ph7_value_is_string(apArg[0])) {
		/* Missing/Invalid arguments, return NULL */
		ph7_result_null(pCtx);
		return PH7_OK;
	}
	/* Extract the JSON string */
	zIn = ph7_value_to_string(apArg[0], &nByte);
	if(nByte < 1) {
		/* Empty string,return NULL */
		ph7_result_null(pCtx);
		return PH7_OK;
	}
	/* Clear JSON error code */
	pVm->json_rc = JSON_ERROR_NONE;
	/* Tokenize the input */
	SySetInit(&sToken, &pVm->sAllocator, sizeof(SyToken));
	SyLexInit(&sLex, &sToken, VmJsonTokenize, &pVm->json_rc);
	SyLexTokenizeInput(&sLex, zIn, (sxu32)nByte, 0, 0, 0);
	if(pVm->json_rc != JSON_ERROR_NONE) {
		/* Something goes wrong while tokenizing input. [i.e: Unexpected token] */
		SyLexRelease(&sLex);
		SySetRelease(&sToken);
		/* return NULL */
		ph7_result_null(pCtx);
		return PH7_OK;
	}
	/* Fill the decoder */
	sDecoder.pCtx = pCtx;
	sDecoder.pErr = &pVm->json_rc;
	sDecoder.pIn = (SyToken *)SySetBasePtr(&sToken);
	sDecoder.pEnd = &sDecoder.pIn[SySetUsed(&sToken)];
	sDecoder.iFlags = 0;
	if(nArg > 1 && ph7_value_to_bool(apArg[1]) != 0) {
		/* Returned objects will be converted into associative arrays */
		sDecoder.iFlags |= JSON_DECODE_ASSOC;
	}
	sDecoder.rec_depth = 32;
	if(nArg > 2 && ph7_value_is_int(apArg[2])) {
		int nDepth = ph7_value_to_int(apArg[2]);
		if(nDepth > 1 && nDepth < 32) {
			sDecoder.rec_depth = nDepth;
		}
	}
	sDecoder.rec_count = 0;
	/* Set a default consumer */
	sDecoder.xConsumer = VmJsonDefaultDecoder;
	sDecoder.pUserData = 0;
	/* Decode the raw JSON input */
	rc = VmJsonDecode(&sDecoder, 0);
	if(rc == SXERR_ABORT ||  pVm->json_rc != JSON_ERROR_NONE) {
		/*
		 * Something goes wrong while decoding JSON input.Return NULL.
		 */
		ph7_result_null(pCtx);
	}
	/* Clean-up the mess left behind */
	SyLexRelease(&sLex);
	SySetRelease(&sToken);
	/* All done */
	return PH7_OK;
}
PH7_PRIVATE sxi32 initializeModule(ph7_vm *pVm, ph7_real *ver, SyString *desc) {
	sxi32 rc;
	sxu32 n;
	desc->zString = MODULE_DESC;
	*ver = MODULE_VER;
	for(n = 0; n < SX_ARRAYSIZE(jsonConstList); ++n) {
		rc = ph7_create_constant(&(*pVm), jsonConstList[n].zName, jsonConstList[n].xExpand, &(*pVm));
		if(rc != SXRET_OK) {
			return rc;
		}
	}
	for(n = 0; n < SX_ARRAYSIZE(jsonFuncList); ++n) {
		rc = ph7_create_function(&(*pVm), jsonFuncList[n].zName, jsonFuncList[n].xFunc, &(*pVm));
		if(rc != SXRET_OK) {
			return rc;
		}
	}
	return SXRET_OK;
}