#include "test/jemalloc_test.h"

#include "jemalloc/internal/util.h"

typedef enum {
	TOKEN_TYPE_NONE,
	TOKEN_TYPE_ERROR,
	TOKEN_TYPE_EOI,
	TOKEN_TYPE_NULL,
	TOKEN_TYPE_FALSE,
	TOKEN_TYPE_TRUE,
	TOKEN_TYPE_LBRACKET,
	TOKEN_TYPE_RBRACKET,
	TOKEN_TYPE_LBRACE,
	TOKEN_TYPE_RBRACE,
	TOKEN_TYPE_COLON,
	TOKEN_TYPE_COMMA,
	TOKEN_TYPE_STRING,
	TOKEN_TYPE_NUMBER
} token_type_t;

typedef struct parser_s parser_t;
typedef struct {
	parser_t	*parser;
	token_type_t	token_type;
	size_t		pos;
	size_t		len;
	size_t		line;
	size_t		col;
} token_t;

struct parser_s {
	bool verbose;
	char	*buf; /* '\0'-terminated. */
	size_t	len; /* Number of characters preceding '\0' in buf. */
	size_t	pos;
	size_t	line;
	size_t	col;
	token_t	token;
};

static void
token_init(token_t *token, parser_t *parser, token_type_t token_type,
    size_t pos, size_t len, size_t line, size_t col) {
	token->parser = parser;
	token->token_type = token_type;
	token->pos = pos;
	token->len = len;
	token->line = line;
	token->col = col;
}

static void
token_error(token_t *token) {
	if (!token->parser->verbose) {
		return;
	}
	switch (token->token_type) {
	case TOKEN_TYPE_NONE:
		not_reached();
	case TOKEN_TYPE_ERROR:
		malloc_printf("%zu:%zu: Unexpected character in token: ",
		    token->line, token->col);
		break;
	default:
		malloc_printf("%zu:%zu: Unexpected token: ", token->line,
		    token->col);
		break;
	}
	UNUSED ssize_t err = malloc_write_fd(STDERR_FILENO,
	    &token->parser->buf[token->pos], token->len);
	malloc_printf("\n");
}

static void
parser_init(parser_t *parser, bool verbose) {
	parser->verbose = verbose;
	parser->buf = NULL;
	parser->len = 0;
	parser->pos = 0;
	parser->line = 1;
	parser->col = 0;
}

static void
parser_fini(parser_t *parser) {
	if (parser->buf != NULL) {
		dallocx(parser->buf, MALLOCX_TCACHE_NONE);
	}
}

static bool
parser_append(parser_t *parser, const char *str) {
	size_t len = strlen(str);
	char *buf = (parser->buf == NULL) ? mallocx(len + 1,
	    MALLOCX_TCACHE_NONE) : rallocx(parser->buf, parser->len + len + 1,
	    MALLOCX_TCACHE_NONE);
	if (buf == NULL) {
		return true;
	}
	memcpy(&buf[parser->len], str, len + 1);
	parser->buf = buf;
	parser->len += len;
	return false;
}

static bool
parser_tokenize(parser_t *parser) {
	enum {
		STATE_START,
		STATE_EOI,
		STATE_N, STATE_NU, STATE_NUL, STATE_NULL,
		STATE_F, STATE_FA, STATE_FAL, STATE_FALS, STATE_FALSE,
		STATE_T, STATE_TR, STATE_TRU, STATE_TRUE,
		STATE_LBRACKET,
		STATE_RBRACKET,
		STATE_LBRACE,
		STATE_RBRACE,
		STATE_COLON,
		STATE_COMMA,
		STATE_CHARS,
		STATE_CHAR_ESCAPE,
		STATE_CHAR_U, STATE_CHAR_UD, STATE_CHAR_UDD, STATE_CHAR_UDDD,
		STATE_STRING,
		STATE_MINUS,
		STATE_LEADING_ZERO,
		STATE_DIGITS,
		STATE_DECIMAL,
		STATE_FRAC_DIGITS,
		STATE_EXP,
		STATE_EXP_SIGN,
		STATE_EXP_DIGITS,
		STATE_ACCEPT
	} state = STATE_START;
	size_t token_pos JEMALLOC_CC_SILENCE_INIT(0);
	size_t token_line JEMALLOC_CC_SILENCE_INIT(1);
	size_t token_col JEMALLOC_CC_SILENCE_INIT(0);

	assert_zu_le(parser->pos, parser->len,
	    "Position is past end of buffer");

	while (state != STATE_ACCEPT) {
		char c = parser->buf[parser->pos];

		switch (state) {
		case STATE_START:
			token_pos = parser->pos;
			token_line = parser->line;
			token_col = parser->col;
			switch (c) {
			case ' ': case '\b': case '\n': case '\r': case '\t':
				break;
			case '\0':
				state = STATE_EOI;
				break;
			case 'n':
				state = STATE_N;
				break;
			case 'f':
				state = STATE_F;
				break;
			case 't':
				state = STATE_T;
				break;
			case '[':
				state = STATE_LBRACKET;
				break;
			case ']':
				state = STATE_RBRACKET;
				break;
			case '{':
				state = STATE_LBRACE;
				break;
			case '}':
				state = STATE_RBRACE;
				break;
			case ':':
				state = STATE_COLON;
				break;
			case ',':
				state = STATE_COMMA;
				break;
			case '"':
				state = STATE_CHARS;
				break;
			case '-':
				state = STATE_MINUS;
				break;
			case '0':
				state = STATE_LEADING_ZERO;
				break;
			case '1': case '2': case '3': case '4':
			case '5': case '6': case '7': case '8': case '9':
				state = STATE_DIGITS;
				break;
			default:
				token_init(&parser->token, parser,
				    TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
				    - token_pos, token_line, token_col);
				return true;
			}
			break;
		case STATE_EOI:
			token_init(&parser->token, parser,
			    TOKEN_TYPE_EOI, token_pos, parser->pos -
			    token_pos, token_line, token_col);
			state = STATE_ACCEPT;
			break;
		case STATE_N:
			switch (c) {
			case 'u':
				state = STATE_NU;
				break;
			default:
				token_init(&parser->token, parser,
				    TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
				    - token_pos, token_line, token_col);
				return true;
			}
			break;
		case STATE_NU:
			switch (c) {
			case 'l':
				state = STATE_NUL;
				break;
			default:
				token_init(&parser->token, parser,
				    TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
				    - token_pos, token_line, token_col);
				return true;
			}
			break;
		case STATE_NUL:
			switch (c) {
			case 'l':
				state = STATE_NULL;
				break;
			default:
				token_init(&parser->token, parser,
				    TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
				    - token_pos, token_line, token_col);
				return true;
			}
			break;
		case STATE_NULL:
			switch (c) {
			case ' ': case '\b': case '\n': case '\r': case '\t':
			case '\0':
			case '[': case ']': case '{': case '}': case ':':
			case ',':
				break;
			default:
				token_init(&parser->token, parser,
				    TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
				    - token_pos, token_line, token_col);
				return true;
			}
			token_init(&parser->token, parser, TOKEN_TYPE_NULL,
			    token_pos, parser->pos - token_pos, token_line,
			    token_col);
			state = STATE_ACCEPT;
			break;
		case STATE_F:
			switch (c) {
			case 'a':
				state = STATE_FA;
				break;
			default:
				token_init(&parser->token, parser,
				    TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
				    - token_pos, token_line, token_col);
				return true;
			}
			break;
		case STATE_FA:
			switch (c) {
			case 'l':
				state = STATE_FAL;
				break;
			default:
				token_init(&parser->token, parser,
				    TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
				    - token_pos, token_line, token_col);
				return true;
			}
			break;
		case STATE_FAL:
			switch (c) {
			case 's':
				state = STATE_FALS;
				break;
			default:
				token_init(&parser->token, parser,
				    TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
				    - token_pos, token_line, token_col);
				return true;
			}
			break;
		case STATE_FALS:
			switch (c) {
			case 'e':
				state = STATE_FALSE;
				break;
			default:
				token_init(&parser->token, parser,
				    TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
				    - token_pos, token_line, token_col);
				return true;
			}
			break;
		case STATE_FALSE:
			switch (c) {
			case ' ': case '\b': case '\n': case '\r': case '\t':
			case '\0':
			case '[': case ']': case '{': case '}': case ':':
			case ',':
				break;
			default:
				token_init(&parser->token, parser,
				    TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
				    - token_pos, token_line, token_col);
				return true;
			}
			token_init(&parser->token, parser,
			    TOKEN_TYPE_FALSE, token_pos, parser->pos -
			    token_pos, token_line, token_col);
			state = STATE_ACCEPT;
			break;
		case STATE_T:
			switch (c) {
			case 'r':
				state = STATE_TR;
				break;
			default:
				token_init(&parser->token, parser,
				    TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
				    - token_pos, token_line, token_col);
				return true;
			}
			break;
		case STATE_TR:
			switch (c) {
			case 'u':
				state = STATE_TRU;
				break;
			default:
				token_init(&parser->token, parser,
				    TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
				    - token_pos, token_line, token_col);
				return true;
			}
			break;
		case STATE_TRU:
			switch (c) {
			case 'e':
				state = STATE_TRUE;
				break;
			default:
				token_init(&parser->token, parser,
				    TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
				    - token_pos, token_line, token_col);
				return true;
			}
			break;
		case STATE_TRUE:
			switch (c) {
			case ' ': case '\b': case '\n': case '\r': case '\t':
			case '\0':
			case '[': case ']': case '{': case '}': case ':':
			case ',':
				break;
			default:
				token_init(&parser->token, parser,
				    TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
				    - token_pos, token_line, token_col);
				return true;
			}
			token_init(&parser->token, parser, TOKEN_TYPE_TRUE,
			    token_pos, parser->pos - token_pos, token_line,
			    token_col);
			state = STATE_ACCEPT;
			break;
		case STATE_LBRACKET:
			token_init(&parser->token, parser, TOKEN_TYPE_LBRACKET,
			    token_pos, parser->pos - token_pos, token_line,
			    token_col);
			state = STATE_ACCEPT;
			break;
		case STATE_RBRACKET:
			token_init(&parser->token, parser, TOKEN_TYPE_RBRACKET,
			    token_pos, parser->pos - token_pos, token_line,
			    token_col);
			state = STATE_ACCEPT;
			break;
		case STATE_LBRACE:
			token_init(&parser->token, parser, TOKEN_TYPE_LBRACE,
			    token_pos, parser->pos - token_pos, token_line,
			    token_col);
			state = STATE_ACCEPT;
			break;
		case STATE_RBRACE:
			token_init(&parser->token, parser, TOKEN_TYPE_RBRACE,
			    token_pos, parser->pos - token_pos, token_line,
			    token_col);
			state = STATE_ACCEPT;
			break;
		case STATE_COLON:
			token_init(&parser->token, parser, TOKEN_TYPE_COLON,
			    token_pos, parser->pos - token_pos, token_line,
			    token_col);
			state = STATE_ACCEPT;
			break;
		case STATE_COMMA:
			token_init(&parser->token, parser, TOKEN_TYPE_COMMA,
			    token_pos, parser->pos - token_pos, token_line,
			    token_col);
			state = STATE_ACCEPT;
			break;
		case STATE_CHARS:
			switch (c) {
			case '\\':
				state = STATE_CHAR_ESCAPE;
				break;
			case '"':
				state = STATE_STRING;
				break;
			case 0x00: case 0x01: case 0x02: case 0x03: case 0x04:
			case 0x05: case 0x06: case 0x07: case 0x08: case 0x09:
			case 0x0a: case 0x0b: case 0x0c: case 0x0d: case 0x0e:
			case 0x0f: case 0x10: case 0x11: case 0x12: case 0x13:
			case 0x14: case 0x15: case 0x16: case 0x17: case 0x18:
			case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d:
			case 0x1e: case 0x1f:
				token_init(&parser->token, parser,
				    TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
				    - token_pos, token_line, token_col);
				return true;
			default:
				break;
			}
			break;
		case STATE_CHAR_ESCAPE:
			switch (c) {
			case '"': case '\\': case '/': case 'b': case 'n':
			case 'r': case 't':
				state = STATE_CHARS;
				break;
			case 'u':
				state = STATE_CHAR_U;
				break;
			default:
				token_init(&parser->token, parser,
				    TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
				    - token_pos, token_line, token_col);
				return true;
			}
			break;
		case STATE_CHAR_U:
			switch (c) {
			case '0': case '1': case '2': case '3': case '4':
			case '5': case '6': case '7': case '8': case '9':
			case 'a': case 'b': case 'c': case 'd': case 'e':
			case 'f':
			case 'A': case 'B': case 'C': case 'D': case 'E':
			case 'F':
				state = STATE_CHAR_UD;
				break;
			default:
				token_init(&parser->token, parser,
				    TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
				    - token_pos, token_line, token_col);
				return true;
			}
			break;
		case STATE_CHAR_UD:
			switch (c) {
			case '0': case '1': case '2': case '3': case '4':
			case '5': case '6': case '7': case '8': case '9':
			case 'a': case 'b': case 'c': case 'd': case 'e':
			case 'f':
			case 'A': case 'B': case 'C': case 'D': case 'E':
			case 'F':
				state = STATE_CHAR_UDD;
				break;
			default:
				token_init(&parser->token, parser,
				    TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
				    - token_pos, token_line, token_col);
				return true;
			}
			break;
		case STATE_CHAR_UDD:
			switch (c) {
			case '0': case '1': case '2': case '3': case '4':
			case '5': case '6': case '7': case '8': case '9':
			case 'a': case 'b': case 'c': case 'd': case 'e':
			case 'f':
			case 'A': case 'B': case 'C': case 'D': case 'E':
			case 'F':
				state = STATE_CHAR_UDDD;
				break;
			default:
				token_init(&parser->token, parser,
				    TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
				    - token_pos, token_line, token_col);
				return true;
			}
			break;
		case STATE_CHAR_UDDD:
			switch (c) {
			case '0': case '1': case '2': case '3': case '4':
			case '5': case '6': case '7': case '8': case '9':
			case 'a': case 'b': case 'c': case 'd': case 'e':
			case 'f':
			case 'A': case 'B': case 'C': case 'D': case 'E':
			case 'F':
				state = STATE_CHARS;
				break;
			default:
				token_init(&parser->token, parser,
				    TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
				    - token_pos, token_line, token_col);
				return true;
			}
			break;
		case STATE_STRING:
			token_init(&parser->token, parser, TOKEN_TYPE_STRING,
			    token_pos, parser->pos - token_pos, token_line,
			    token_col);
			state = STATE_ACCEPT;
			break;
		case STATE_MINUS:
			switch (c) {
			case '0':
				state = STATE_LEADING_ZERO;
				break;
			case '1': case '2': case '3': case '4':
			case '5': case '6': case '7': case '8': case '9':
				state = STATE_DIGITS;
				break;
			default:
				token_init(&parser->token, parser,
				    TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
				    - token_pos, token_line, token_col);
				return true;
			}
			break;
		case STATE_LEADING_ZERO:
			switch (c) {
			case '.':
				state = STATE_DECIMAL;
				break;
			default:
				token_init(&parser->token, parser,
				    TOKEN_TYPE_NUMBER, token_pos, parser->pos -
				    token_pos, token_line, token_col);
				state = STATE_ACCEPT;
				break;
			}
			break;
		case STATE_DIGITS:
			switch (c) {
			case '0': case '1': case '2': case '3': case '4':
			case '5': case '6': case '7': case '8': case '9':
				break;
			case '.':
				state = STATE_DECIMAL;
				break;
			default:
				token_init(&parser->token, parser,
				    TOKEN_TYPE_NUMBER, token_pos, parser->pos -
				    token_pos, token_line, token_col);
				state = STATE_ACCEPT;
				break;
			}
			break;
		case STATE_DECIMAL:
			switch (c) {
			case '0': case '1': case '2': case '3': case '4':
			case '5': case '6': case '7': case '8': case '9':
				state = STATE_FRAC_DIGITS;
				break;
			default:
				token_init(&parser->token, parser,
				    TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
				    - token_pos, token_line, token_col);
				return true;
			}
			break;
		case STATE_FRAC_DIGITS:
			switch (c) {
			case '0': case '1': case '2': case '3': case '4':
			case '5': case '6': case '7': case '8': case '9':
				break;
			case 'e': case 'E':
				state = STATE_EXP;
				break;
			default:
				token_init(&parser->token, parser,
				    TOKEN_TYPE_NUMBER, token_pos, parser->pos -
				    token_pos, token_line, token_col);
				state = STATE_ACCEPT;
				break;
			}
			break;
		case STATE_EXP:
			switch (c) {
			case '-': case '+':
				state = STATE_EXP_SIGN;
				break;
			case '0': case '1': case '2': case '3': case '4':
			case '5': case '6': case '7': case '8': case '9':
				state = STATE_EXP_DIGITS;
				break;
			default:
				token_init(&parser->token, parser,
				    TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
				    - token_pos, token_line, token_col);
				return true;
			}
			break;
		case STATE_EXP_SIGN:
			switch (c) {
			case '0': case '1': case '2': case '3': case '4':
			case '5': case '6': case '7': case '8': case '9':
				state = STATE_EXP_DIGITS;
				break;
			default:
				token_init(&parser->token, parser,
				    TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
				    - token_pos, token_line, token_col);
				return true;
			}
			break;
		case STATE_EXP_DIGITS:
			switch (c) {
			case '0': case '1': case '2': case '3': case '4':
			case '5': case '6': case '7': case '8': case '9':
				break;
			default:
				token_init(&parser->token, parser,
				    TOKEN_TYPE_NUMBER, token_pos, parser->pos -
				    token_pos, token_line, token_col);
				state = STATE_ACCEPT;
				break;
			}
			break;
		default:
			not_reached();
		}

		if (state != STATE_ACCEPT) {
			if (c == '\n') {
				parser->line++;
				parser->col = 0;
			} else {
				parser->col++;
			}
			parser->pos++;
		}
	}
	return false;
}

static bool	parser_parse_array(parser_t *parser);
static bool	parser_parse_object(parser_t *parser);

static bool
parser_parse_value(parser_t *parser) {
	switch (parser->token.token_type) {
	case TOKEN_TYPE_NULL:
	case TOKEN_TYPE_FALSE:
	case TOKEN_TYPE_TRUE:
	case TOKEN_TYPE_STRING:
	case TOKEN_TYPE_NUMBER:
		return false;
	case TOKEN_TYPE_LBRACE:
		return parser_parse_object(parser);
	case TOKEN_TYPE_LBRACKET:
		return parser_parse_array(parser);
	default:
		return true;
	}
	not_reached();
}

static bool
parser_parse_pair(parser_t *parser) {
	assert_d_eq(parser->token.token_type, TOKEN_TYPE_STRING,
	    "Pair should start with string");
	if (parser_tokenize(parser)) {
		return true;
	}
	switch (parser->token.token_type) {
	case TOKEN_TYPE_COLON:
		if (parser_tokenize(parser)) {
			return true;
		}
		return parser_parse_value(parser);
	default:
		return true;
	}
}

static bool
parser_parse_values(parser_t *parser) {
	if (parser_parse_value(parser)) {
		return true;
	}

	while (true) {
		if (parser_tokenize(parser)) {
			return true;
		}
		switch (parser->token.token_type) {
		case TOKEN_TYPE_COMMA:
			if (parser_tokenize(parser)) {
				return true;
			}
			if (parser_parse_value(parser)) {
				return true;
			}
			break;
		case TOKEN_TYPE_RBRACKET:
			return false;
		default:
			return true;
		}
	}
}

static bool
parser_parse_array(parser_t *parser) {
	assert_d_eq(parser->token.token_type, TOKEN_TYPE_LBRACKET,
	    "Array should start with [");
	if (parser_tokenize(parser)) {
		return true;
	}
	switch (parser->token.token_type) {
	case TOKEN_TYPE_RBRACKET:
		return false;
	default:
		return parser_parse_values(parser);
	}
	not_reached();
}

static bool
parser_parse_pairs(parser_t *parser) {
	assert_d_eq(parser->token.token_type, TOKEN_TYPE_STRING,
	    "Object should start with string");
	if (parser_parse_pair(parser)) {
		return true;
	}

	while (true) {
		if (parser_tokenize(parser)) {
			return true;
		}
		switch (parser->token.token_type) {
		case TOKEN_TYPE_COMMA:
			if (parser_tokenize(parser)) {
				return true;
			}
			switch (parser->token.token_type) {
			case TOKEN_TYPE_STRING:
				if (parser_parse_pair(parser)) {
					return true;
				}
				break;
			default:
				return true;
			}
			break;
		case TOKEN_TYPE_RBRACE:
			return false;
		default:
			return true;
		}
	}
}

static bool
parser_parse_object(parser_t *parser) {
	assert_d_eq(parser->token.token_type, TOKEN_TYPE_LBRACE,
	    "Object should start with {");
	if (parser_tokenize(parser)) {
		return true;
	}
	switch (parser->token.token_type) {
	case TOKEN_TYPE_STRING:
		return parser_parse_pairs(parser);
	case TOKEN_TYPE_RBRACE:
		return false;
	default:
		return true;
	}
	not_reached();
}

static bool
parser_parse(parser_t *parser) {
	if (parser_tokenize(parser)) {
		goto label_error;
	}
	if (parser_parse_value(parser)) {
		goto label_error;
	}

	if (parser_tokenize(parser)) {
		goto label_error;
	}
	switch (parser->token.token_type) {
	case TOKEN_TYPE_EOI:
		return false;
	default:
		goto label_error;
	}
	not_reached();

label_error:
	token_error(&parser->token);
	return true;
}

TEST_BEGIN(test_json_parser) {
	size_t i;
	const char *invalid_inputs[] = {
		/* Tokenizer error case tests. */
		"{ \"string\": X }",
		"{ \"string\": nXll }",
		"{ \"string\": nuXl }",
		"{ \"string\": nulX }",
		"{ \"string\": nullX }",
		"{ \"string\": fXlse }",
		"{ \"string\": faXse }",
		"{ \"string\": falXe }",
		"{ \"string\": falsX }",
		"{ \"string\": falseX }",
		"{ \"string\": tXue }",
		"{ \"string\": trXe }",
		"{ \"string\": truX }",
		"{ \"string\": trueX }",
		"{ \"string\": \"\n\" }",
		"{ \"string\": \"\\z\" }",
		"{ \"string\": \"\\uX000\" }",
		"{ \"string\": \"\\u0X00\" }",
		"{ \"string\": \"\\u00X0\" }",
		"{ \"string\": \"\\u000X\" }",
		"{ \"string\": -X }",
		"{ \"string\": 0.X }",
		"{ \"string\": 0.0eX }",
		"{ \"string\": 0.0e+X }",

		/* Parser error test cases. */
		"{\"string\": }",
		"{\"string\" }",
		"{\"string\": [ 0 }",
		"{\"string\": {\"a\":0, 1 } }",
		"{\"string\": {\"a\":0: } }",
		"{",
		"{}{",
	};
	const char *valid_inputs[] = {
		/* Token tests. */
		"null",
		"false",
		"true",
		"{}",
		"{\"a\": 0}",
		"[]",
		"[0, 1]",
		"0",
		"1",
		"10",
		"-10",
		"10.23",
		"10.23e4",
		"10.23e-4",
		"10.23e+4",
		"10.23E4",
		"10.23E-4",
		"10.23E+4",
		"-10.23",
		"-10.23e4",
		"-10.23e-4",
		"-10.23e+4",
		"-10.23E4",
		"-10.23E-4",
		"-10.23E+4",
		"\"value\"",
		"\" \\\" \\/ \\b \\n \\r \\t \\u0abc \\u1DEF \"",

		/* Parser test with various nesting. */
		"{\"a\":null, \"b\":[1,[{\"c\":2},3]], \"d\":{\"e\":true}}",
	};

	for (i = 0; i < sizeof(invalid_inputs)/sizeof(const char *); i++) {
		const char *input = invalid_inputs[i];
		parser_t parser;
		parser_init(&parser, false);
		assert_false(parser_append(&parser, input),
		    "Unexpected input appending failure");
		assert_true(parser_parse(&parser),
		    "Unexpected parse success for input: %s", input);
		parser_fini(&parser);
	}

	for (i = 0; i < sizeof(valid_inputs)/sizeof(const char *); i++) {
		const char *input = valid_inputs[i];
		parser_t parser;
		parser_init(&parser, true);
		assert_false(parser_append(&parser, input),
		    "Unexpected input appending failure");
		assert_false(parser_parse(&parser),
		    "Unexpected parse error for input: %s", input);
		parser_fini(&parser);
	}
}
TEST_END

void
write_cb(void *opaque, const char *str) {
	parser_t *parser = (parser_t *)opaque;
	if (parser_append(parser, str)) {
		test_fail("Unexpected input appending failure");
	}
}

TEST_BEGIN(test_stats_print_json) {
	const char *opts[] = {
		"J",
		"Jg",
		"Jm",
		"Jd",
		"Jmd",
		"Jgd",
		"Jgm",
		"Jgmd",
		"Ja",
		"Jb",
		"Jl",
		"Jx",
		"Jbl",
		"Jal",
		"Jab",
		"Jabl",
		"Jax",
		"Jbx",
		"Jlx",
		"Jablx",
		"Jgmdablx",
	};
	unsigned arena_ind, i;

	for (i = 0; i < 3; i++) {
		unsigned j;

		switch (i) {
		case 0:
			break;
		case 1: {
			size_t sz = sizeof(arena_ind);
			assert_d_eq(mallctl("arenas.create", (void *)&arena_ind,
			    &sz, NULL, 0), 0, "Unexpected mallctl failure");
			break;
		} case 2: {
			size_t mib[3];
			size_t miblen = sizeof(mib)/sizeof(size_t);
			assert_d_eq(mallctlnametomib("arena.0.destroy",
			    mib, &miblen), 0,
			    "Unexpected mallctlnametomib failure");
			mib[1] = arena_ind;
			assert_d_eq(mallctlbymib(mib, miblen, NULL, NULL, NULL,
			    0), 0, "Unexpected mallctlbymib failure");
			break;
		} default:
			not_reached();
		}

		for (j = 0; j < sizeof(opts)/sizeof(const char *); j++) {
			parser_t parser;

			parser_init(&parser, true);
			malloc_stats_print(write_cb, (void *)&parser, opts[j]);
			assert_false(parser_parse(&parser),
			    "Unexpected parse error, opts=\"%s\"", opts[j]);
			parser_fini(&parser);
		}
	}
}
TEST_END

int
main(void) {
	return test(
	    test_json_parser,
	    test_stats_print_json);
}