/* Copyright (c) 2013, Vsevolod Stakhov
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *       * Redistributions of source code must retain the above copyright
 *         notice, this list of conditions and the following disclaimer.
 *       * Redistributions in binary form must reproduce the above copyright
 *         notice, this list of conditions and the following disclaimer in the
 *         documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <math.h>
#include "ucl.h"
#include "ucl_internal.h"
#include "ucl_chartable.h"

/**
 * @file ucl_parser.c
 * The implementation of ucl parser
 */

struct ucl_parser_saved_state {
	unsigned int line;
	unsigned int column;
	size_t remain;
	const unsigned char *pos;
};

/**
 * Move up to len characters
 * @param parser
 * @param begin
 * @param len
 * @return new position in chunk
 */
#define ucl_chunk_skipc(chunk, p) \
	do {                          \
		if (p == chunk->end) {    \
			break;                \
		}                         \
		if (*(p) == '\n') {       \
			(chunk)->line++;      \
			(chunk)->column = 0;  \
		}                         \
		else                      \
			(chunk)->column++;    \
		(p++);                    \
		(chunk)->pos++;           \
		(chunk)->remain--;        \
	} while (0)

static inline void
ucl_set_err(struct ucl_parser *parser, int code, const char *str, UT_string **err)
{
	const char *fmt_string, *filename;
	struct ucl_chunk *chunk = parser->chunks;

	if (parser->cur_file) {
		filename = parser->cur_file;
	}
	else {
		filename = "<unknown>";
	}

	if (chunk->pos < chunk->end) {
		if (isgraph(*chunk->pos)) {
			fmt_string = "error while parsing %s: "
						 "line: %d, column: %d - '%s', character: '%c'";
		}
		else {
			fmt_string = "error while parsing %s: "
						 "line: %d, column: %d - '%s', character: '0x%02x'";
		}
		ucl_create_err(err, fmt_string,
					   filename, chunk->line, chunk->column,
					   str, *chunk->pos);
	}
	else {
		ucl_create_err(err, "error while parsing %s: at the end of chunk: %s",
					   filename, str);
	}

	parser->err_code = code;
	parser->state = UCL_STATE_ERROR;
}

static void
ucl_save_comment(struct ucl_parser *parser, const char *begin, size_t len)
{
	ucl_object_t *nobj;

	if (len > 0 && begin != NULL) {
		nobj = ucl_object_fromstring_common(begin, len, 0);

		if (parser->last_comment) {
			/* We need to append data to an existing object */
			DL_APPEND(parser->last_comment, nobj);
		}
		else {
			parser->last_comment = nobj;
		}
	}
}

static void
ucl_attach_comment(struct ucl_parser *parser, ucl_object_t *obj, bool before)
{
	if (parser->last_comment) {
		ucl_object_insert_key(parser->comments, parser->last_comment,
							  (const char *) &obj, sizeof(void *), true);

		if (before) {
			parser->last_comment->flags |= UCL_OBJECT_INHERITED;
		}

		parser->last_comment = NULL;
	}
}

/**
 * Skip all comments from the current pos resolving nested and multiline comments
 * @param parser
 * @return
 */
static bool
ucl_skip_comments(struct ucl_parser *parser)
{
	struct ucl_chunk *chunk = parser->chunks;
	const unsigned char *p, *beg = NULL;
	int comments_nested = 0;
	bool quoted = false;

	p = chunk->pos;

start:
	if (chunk->remain > 0 && *p == '#') {
		if (parser->state != UCL_STATE_SCOMMENT &&
			parser->state != UCL_STATE_MCOMMENT) {
			beg = p;

			while (p < chunk->end) {
				if (*p == '\n') {
					if (parser->flags & UCL_PARSER_SAVE_COMMENTS) {
						ucl_save_comment(parser, beg, p - beg);
						beg = NULL;
					}

					ucl_chunk_skipc(chunk, p);

					goto start;
				}
				ucl_chunk_skipc(chunk, p);
			}
		}
	}
	else if (chunk->remain >= 2 && *p == '/' && p[1] == '*') {
		beg = p;
		comments_nested++;
		ucl_chunk_skipc(chunk, p);
		ucl_chunk_skipc(chunk, p);
		while (p < chunk->end) {
			if (*p == '"' && *(p - 1) != '\\') {
				/* begin or end double-quoted string */
				quoted = !quoted;
				ucl_chunk_skipc(chunk, p);
			}
			else if (quoted) {
				/* quoted character */
				ucl_chunk_skipc(chunk, p);
			}
			else if (chunk->remain >= 2 && *p == '*' && p[1] == '/') {
				/* end of comment */
				ucl_chunk_skipc(chunk, p);
				ucl_chunk_skipc(chunk, p);
				comments_nested--;
				if (comments_nested == 0) {
					if (parser->flags & UCL_PARSER_SAVE_COMMENTS) {
						ucl_save_comment(parser, beg, p - beg + 1);
						beg = NULL;
					}
					goto start;
				}
			}
			else if (chunk->remain >= 2 && *p == '/' && p[1] == '*') {
				/* start of nested comment */
				comments_nested++;
				ucl_chunk_skipc(chunk, p);
				ucl_chunk_skipc(chunk, p);
			}
			else {
				/* anything else */
				ucl_chunk_skipc(chunk, p);
			}
		}
		if (comments_nested != 0) {
			ucl_set_err(parser, UCL_ENESTED,
						"unfinished multiline comment", &parser->err);
			return false;
		}
	}

	if (beg && p > beg && (parser->flags & UCL_PARSER_SAVE_COMMENTS)) {
		ucl_save_comment(parser, beg, p - beg);
	}

	return true;
}

/**
 * Return multiplier for a character
 * @param c multiplier character
 * @param is_bytes if true use 1024 multiplier
 * @return multiplier
 */
static inline unsigned long
ucl_lex_num_multiplier(const unsigned char c, bool is_bytes)
{
	const struct {
		char c;
		long mult_normal;
		long mult_bytes;
	} multipliers[] = {
		{'m', 1000 * 1000, 1024 * 1024},
		{'k', 1000, 1024},
		{'g', 1000 * 1000 * 1000, 1024 * 1024 * 1024}};
	int i;

	for (i = 0; i < 3; i++) {
		if (tolower(c) == multipliers[i].c) {
			if (is_bytes) {
				return multipliers[i].mult_bytes;
			}
			return multipliers[i].mult_normal;
		}
	}

	return 1;
}


/**
 * Return multiplier for time scaling
 * @param c
 * @return
 */
static inline double
ucl_lex_time_multiplier(const unsigned char c)
{
	const struct {
		char c;
		double mult;
	} multipliers[] = {
		{'m', 60},
		{'h', 60 * 60},
		{'d', 60 * 60 * 24},
		{'w', 60 * 60 * 24 * 7},
		{'y', 60 * 60 * 24 * 365}};
	int i;

	for (i = 0; i < 5; i++) {
		if (tolower(c) == multipliers[i].c) {
			return multipliers[i].mult;
		}
	}

	return 1;
}

/**
 * Return true if a character is a end of an atom
 * @param c
 * @return
 */
static inline bool
ucl_lex_is_atom_end(const unsigned char c)
{
	return ucl_test_character(c, UCL_CHARACTER_VALUE_END);
}

static inline bool
ucl_lex_is_comment(const unsigned char c1, const unsigned char c2)
{
	if (c1 == '/') {
		if (c2 == '*') {
			return true;
		}
	}
	else if (c1 == '#') {
		return true;
	}
	return false;
}

/**
 * Check variable found
 * @param parser
 * @param ptr
 * @param remain
 * @param out_len
 * @param strict
 * @param found
 * @return
 */
static inline const char *
ucl_check_variable_safe(struct ucl_parser *parser, const char *ptr, size_t remain,
						size_t *out_len, bool strict, bool *found)
{
	struct ucl_variable *var;
	unsigned char *dst;
	size_t dstlen;
	bool need_free = false;

	LL_FOREACH(parser->variables, var)
	{
		if (strict) {
			if (remain == var->var_len) {
				if (memcmp(ptr, var->var, var->var_len) == 0) {
					*out_len += var->value_len;
					*found = true;
					return (ptr + var->var_len);
				}
			}
		}
		else {
			if (remain >= var->var_len) {
				if (memcmp(ptr, var->var, var->var_len) == 0) {
					*out_len += var->value_len;
					*found = true;
					return (ptr + var->var_len);
				}
			}
		}
	}

	/* XXX: can only handle ${VAR} */
	if (!(*found) && parser->var_handler != NULL && strict) {
		/* Call generic handler */
		if (parser->var_handler(ptr, remain, &dst, &dstlen, &need_free,
								parser->var_data)) {
			*found = true;
			*out_len = dstlen;

			if (need_free) {
				free(dst);
			}
			return (ptr + remain);
		}
	}

	return ptr;
}

/**
 * Check for a variable in a given string
 * @param parser
 * @param ptr
 * @param remain
 * @param out_len
 * @param vars_found
 * @return
 */
static const char *
ucl_check_variable(struct ucl_parser *parser, const char *ptr,
				   size_t remain, size_t *out_len, bool *vars_found)
{
	const char *p, *end, *ret = ptr;
	bool found = false;

	if (*ptr == '{') {
		/* We need to match the variable enclosed in braces */
		p = ptr + 1;
		end = ptr + remain;
		while (p < end) {
			if (*p == '}') {
				ret = ucl_check_variable_safe(parser, ptr + 1, p - ptr - 1,
											  out_len, true, &found);
				if (found) {
					/* {} must be excluded actually */
					ret++;
					if (!*vars_found) {
						*vars_found = true;
					}
				}
				else {
					*out_len += 2;
				}
				break;
			}
			p++;
		}
		if (p == end) {
			(*out_len)++;
		}
	}
	else if (*ptr != '$') {
		/* Not count escaped dollar sign */
		ret = ucl_check_variable_safe(parser, ptr, remain, out_len, false, &found);
		if (found && !*vars_found) {
			*vars_found = true;
		}
		if (!found) {
			(*out_len)++;
		}
	}
	else {
		ret++;
		(*out_len)++;
	}

	return ret;
}

/**
 * Expand a single variable
 * @param parser
 * @param ptr
 * @param in_len
 * @param dest
 * @param out_len
 * @return
 */
static const char *
ucl_expand_single_variable(struct ucl_parser *parser, const char *ptr,
						   size_t in_len, unsigned char **dest, size_t out_len)
{
	unsigned char *d = *dest, *dst;
	const char *p = ptr + 1, *ret;
	struct ucl_variable *var;
	size_t dstlen;
	bool need_free = false;
	bool found = false;
	bool strict = false;

	ret = ptr + 1;
	/* For the $ sign */
	in_len--;

	if (*p == '$') {
		*d++ = *p++;
		*dest = d;
		return p;
	}
	else if (*p == '{') {
		p++;
		in_len--;
		strict = true;
		ret += 2;
	}

	LL_FOREACH(parser->variables, var)
	{
		if (out_len >= var->value_len && in_len >= (var->var_len + (strict ? 1 : 0))) {
			if (memcmp(p, var->var, var->var_len) == 0) {
				if (!strict || p[var->var_len] == '}') {
					memcpy(d, var->value, var->value_len);
					ret += var->var_len;
					d += var->value_len;
					found = true;
					break;
				}
			}
		}
	}

	if (!found) {
		if (strict && parser->var_handler != NULL) {
			dstlen = out_len;

			if (parser->var_handler(p, in_len, &dst, &dstlen, &need_free,
									parser->var_data)) {
				if (dstlen > out_len) {
					/* We do not have enough space! */
					if (need_free) {
						free(dst);
					}
				}
				else {
					memcpy(d, dst, dstlen);
					ret += in_len;
					d += dstlen;
					found = true;

					if (need_free) {
						free(dst);
					}
				}
			}
		}

		/* Leave variable as is, in this case we use dest */
		if (!found) {
			if (strict && out_len >= 2) {
				/* Copy '${' */
				memcpy(d, ptr, 2);
				d += 2;
				ret--;
			}
			else {
				memcpy(d, ptr, 1);
				d++;
			}
		}
	}

	*dest = d;
	return ret;
}

/**
 * Expand variables in string
 * @param parser
 * @param dst
 * @param src
 * @param in_len
 * @return
 */
static ssize_t
ucl_expand_variable(struct ucl_parser *parser, unsigned char **dst,
					const char *src, size_t in_len)
{
	const char *p, *end = src + in_len;
	unsigned char *d, *d_end;
	size_t out_len = 0;
	bool vars_found = false;

	if (parser->flags & UCL_PARSER_DISABLE_MACRO) {
		*dst = NULL;
		return in_len;
	}

	p = src;
	while (p != end) {
		if (*p == '$' && p + 1 != end) {
			p = ucl_check_variable(parser, p + 1, end - p - 1, &out_len, &vars_found);
		}
		else {
			p++;
			out_len++;
		}
	}

	if (!vars_found) {
		/* Trivial case */
		*dst = NULL;
		return in_len;
	}

	*dst = UCL_ALLOC(out_len + 1);
	if (*dst == NULL) {
		return in_len;
	}

	d = *dst;
	d_end = d + out_len;
	p = src;
	while (p != end && d != d_end) {
		if (*p == '$' && p + 1 != end) {
			p = ucl_expand_single_variable(parser, p, end - p, &d, d_end - d);
		}
		else {
			*d++ = *p++;
		}
	}

	*d = '\0';

	return out_len;
}

/**
 * Store or copy pointer to the trash stack
 * @param parser parser object
 * @param src src string
 * @param dst destination buffer (trash stack pointer)
 * @param dst_const const destination pointer (e.g. value of object)
 * @param in_len input length
 * @param need_unescape need to unescape source (and copy it)
 * @param need_lowercase need to lowercase value (and copy)
 * @param need_expand need to expand variables (and copy as well)
 * @param unescape_squote unescape single quoted string
 * @return output length (excluding \0 symbol)
 */
static inline ssize_t
ucl_copy_or_store_ptr(struct ucl_parser *parser,
					  const unsigned char *src, unsigned char **dst,
					  const char **dst_const, size_t in_len,
					  bool need_unescape, bool need_lowercase, bool need_expand,
					  bool unescape_squote)
{
	ssize_t ret = -1, tret;
	unsigned char *tmp;

	if (need_unescape || need_lowercase ||
		(need_expand && parser->variables != NULL) ||
		!(parser->flags & UCL_PARSER_ZEROCOPY)) {
		/* Copy string */
		*dst = UCL_ALLOC(in_len + 1);
		if (*dst == NULL) {
			ucl_set_err(parser, UCL_EINTERNAL, "cannot allocate memory for a string",
						&parser->err);
			return false;
		}
		if (need_lowercase) {
			ret = ucl_strlcpy_tolower(*dst, src, in_len + 1);
		}
		else {
			ret = ucl_strlcpy_unsafe(*dst, src, in_len + 1);
		}

		if (need_unescape) {
			if (!unescape_squote) {
				ret = ucl_unescape_json_string(*dst, ret);
			}
			else {
				ret = ucl_unescape_squoted_string(*dst, ret);
			}
		}

		if (need_expand) {
			tmp = *dst;
			tret = ret;
			ret = ucl_expand_variable(parser, dst, tmp, ret);
			if (*dst == NULL) {
				/* Nothing to expand */
				*dst = tmp;
				ret = tret;
			}
			else {
				/* Free unexpanded value */
				UCL_FREE(in_len + 1, tmp);
			}
		}
		*dst_const = *dst;
	}
	else {
		*dst_const = src;
		ret = in_len;
	}

	return ret;
}

/**
 * Create and append an object at the specified level
 * @param parser
 * @param is_array
 * @param level
 * @return
 */
static inline ucl_object_t *
ucl_parser_add_container(ucl_object_t *obj, struct ucl_parser *parser,
						 bool is_array, uint32_t level, bool has_obrace)
{
	struct ucl_stack *st;
	ucl_object_t *nobj;

	if (obj == NULL) {
		nobj = ucl_object_new_full(is_array ? UCL_ARRAY : UCL_OBJECT, parser->chunks->priority);
		if (nobj == NULL) {
			goto enomem0;
		}
	}
	else {
		if (obj->type == (is_array ? UCL_OBJECT : UCL_ARRAY)) {
			/* Bad combination for merge: array and object */
			ucl_set_err(parser, UCL_EMERGE,
						"cannot merge an object with an array",
						&parser->err);

			return NULL;
		}
		nobj = obj;
		nobj->type = is_array ? UCL_ARRAY : UCL_OBJECT;
	}

	if (!is_array) {
		if (nobj->value.ov == NULL) {
			nobj->value.ov = ucl_hash_create(parser->flags & UCL_PARSER_KEY_LOWERCASE);
			if (nobj->value.ov == NULL) {
				goto enomem1;
			}
		}
		parser->state = UCL_STATE_KEY;
	}
	else {
		parser->state = UCL_STATE_VALUE;
	}

	st = UCL_ALLOC(sizeof(struct ucl_stack));

	if (st == NULL) {
		goto enomem1;
	}

	st->obj = nobj;

	if (level >= UINT16_MAX) {
		ucl_set_err(parser, UCL_ENESTED,
					"objects are nesting too deep (over 65535 limit)",
					&parser->err);
		if (nobj != obj) {
			ucl_object_unref(obj);
		}

		UCL_FREE(sizeof(struct ucl_stack), st);

		return NULL;
	}


	st->e.params.level = level;
	st->e.params.line = parser->chunks->line;
	st->chunk = parser->chunks;

	if (has_obrace) {
		st->e.params.flags = UCL_STACK_HAS_OBRACE;
	}
	else {
		st->e.params.flags = 0;
	}

	LL_PREPEND(parser->stack, st);
	parser->cur_obj = nobj;

	return nobj;
enomem1:
	if (nobj != obj)
		ucl_object_unref(nobj);
enomem0:
	ucl_set_err(parser, UCL_EINTERNAL, "cannot allocate memory for an object",
				&parser->err);
	return NULL;
}

int ucl_maybe_parse_number(ucl_object_t *obj,
						   const char *start, const char *end, const char **pos,
						   bool allow_double, bool number_bytes, bool allow_time)
{
	const char *p = start, *c = start;
	char *endptr;
	bool got_dot = false, got_exp = false, need_double = false,
		 is_time = false, valid_start = false, is_hex = false;
	int is_neg = 0;
	double dv = 0;
	int64_t lv = 0;

	if (*p == '-') {
		is_neg = 1;
		c++;
		p++;
	}
	while (p < end) {
		if (is_hex && isxdigit(*p)) {
			p++;
		}
		else if (isdigit(*p)) {
			valid_start = true;
			p++;
		}
		else if (!is_hex && (*p == 'x' || *p == 'X')) {
			is_hex = true;
			allow_double = false;
			c = p + 1;
			p++;
		}
		else if (allow_double) {
			if (p == c) {
				/* Empty digits sequence, not a number */
				*pos = start;
				return EINVAL;
			}
			else if (*p == '.') {
				if (got_dot) {
					/* Double dots, not a number */
					*pos = start;
					return EINVAL;
				}
				else {
					got_dot = true;
					need_double = true;
					p++;
				}
			}
			else if (*p == 'e' || *p == 'E') {
				if (got_exp) {
					/* Double exp, not a number */
					*pos = start;
					return EINVAL;
				}
				else {
					got_exp = true;
					need_double = true;
					p++;
					if (p >= end) {
						*pos = start;
						return EINVAL;
					}
					if (!isdigit(*p) && *p != '+' && *p != '-') {
						/* Wrong exponent sign */
						*pos = start;
						return EINVAL;
					}
					else {
						p++;
					}
				}
			}
			else {
				/* Got the end of the number, need to check */
				break;
			}
		}
		else if (!allow_double && *p == '.') {
			/* Unexpected dot */
			*pos = start;
			return EINVAL;
		}
		else {
			break;
		}
	}

	if (!valid_start || p == c) {
		*pos = start;
		return EINVAL;
	}

	char numbuf[128];

	if ((size_t) (p - c + 1) >= sizeof(numbuf)) {
		*pos = start;
		return EINVAL;
	}

	if (is_neg) {
		numbuf[0] = '-';
		ucl_strlcpy(&numbuf[1], c, p - c + 1);
	}
	else {
		ucl_strlcpy(numbuf, c, p - c + 1);
	}

	errno = 0;
	if (need_double) {
		dv = strtod(numbuf, &endptr);
	}
	else {
		if (is_hex) {
			lv = strtoimax(numbuf, &endptr, 16);
		}
		else {
			lv = strtoimax(numbuf, &endptr, 10);
		}
	}
	if (errno == ERANGE) {
		*pos = start;
		return ERANGE;
	}

	/* Now check endptr and move it from numbuf to the real ending */
	if (endptr != NULL) {
		long shift = endptr - numbuf - is_neg;
		endptr = (char *) c + shift;
	}
	if (endptr >= end) {
		p = end;
		goto set_obj;
	}
	if (endptr == NULL || ucl_lex_is_atom_end(*endptr) || *endptr == '\0') {
		p = endptr;
		goto set_obj;
	}

	if (endptr < end && endptr != start) {
		p = endptr;
		switch (*p) {
		case 'm':
		case 'M':
		case 'g':
		case 'G':
		case 'k':
		case 'K':
			if (end - p >= 2) {
				if (p[1] == 's' || p[1] == 'S') {
					/* Milliseconds */
					if (!need_double) {
						need_double = true;
						dv = lv;
					}
					is_time = true;
					if (p[0] == 'm' || p[0] == 'M') {
						dv /= 1000.;
					}
					else {
						dv *= ucl_lex_num_multiplier(*p, false);
					}
					p += 2;
					if (end - p > 0 && !ucl_lex_is_atom_end(*p)) {
						*pos = start;
						return EINVAL;
					}
					goto set_obj;
				}
				else if (number_bytes || (p[1] == 'b' || p[1] == 'B')) {
					/* Bytes */
					if (need_double) {
						need_double = false;
						lv = dv;
					}
					lv *= ucl_lex_num_multiplier(*p, true);
					p += 2;
					if (end - p > 0 && !ucl_lex_is_atom_end(*p)) {
						*pos = start;
						return EINVAL;
					}
					goto set_obj;
				}
				else if (ucl_lex_is_atom_end(p[1])) {
					if (need_double) {
						dv *= ucl_lex_num_multiplier(*p, false);
					}
					else {
						lv *= ucl_lex_num_multiplier(*p, number_bytes);
					}
					p++;
					goto set_obj;
				}
				else if (allow_time && end - p >= 3) {
					if (tolower(p[0]) == 'm' &&
						tolower(p[1]) == 'i' &&
						tolower(p[2]) == 'n') {
						/* Minutes */
						if (!need_double) {
							need_double = true;
							dv = lv;
						}
						is_time = true;
						dv *= 60.;
						p += 3;
						if (end - p > 0 && !ucl_lex_is_atom_end(*p)) {
							*pos = start;
							return EINVAL;
						}
						goto set_obj;
					}
				}
			}
			else {
				if (need_double) {
					dv *= ucl_lex_num_multiplier(*p, false);
				}
				else {
					lv *= ucl_lex_num_multiplier(*p, number_bytes);
				}
				p++;
				if (end - p > 0 && !ucl_lex_is_atom_end(*p)) {
					*pos = start;
					return EINVAL;
				}
				goto set_obj;
			}
			break;
		case 'S':
		case 's':
			if (allow_time &&
				(p == end - 1 || ucl_lex_is_atom_end(p[1]))) {
				if (!need_double) {
					need_double = true;
					dv = lv;
				}
				p++;
				is_time = true;
				goto set_obj;
			}
			break;
		case 'h':
		case 'H':
		case 'd':
		case 'D':
		case 'w':
		case 'W':
		case 'Y':
		case 'y':
			if (allow_time &&
				(p == end - 1 || ucl_lex_is_atom_end(p[1]))) {
				if (!need_double) {
					need_double = true;
					dv = lv;
				}
				is_time = true;
				dv *= ucl_lex_time_multiplier(*p);
				p++;
				goto set_obj;
			}
			break;
		case '\t':
		case ' ':
			while (p < end && ucl_test_character(*p, UCL_CHARACTER_WHITESPACE)) {
				p++;
			}
			if (p == end || ucl_lex_is_atom_end(*p))
				goto set_obj;
			break;
		}
	}
	else if (endptr == end) {
		/* Just a number at the end of chunk */
		p = end;
		goto set_obj;
	}

	*pos = c;
	return EINVAL;

set_obj:
	if (obj != NULL) {
		if (allow_double && (need_double || is_time)) {
			if (!is_time) {
				obj->type = UCL_FLOAT;
			}
			else {
				obj->type = UCL_TIME;
			}
			obj->value.dv = dv;
		}
		else {
			obj->type = UCL_INT;
			obj->value.iv = lv;
		}
	}
	*pos = p;
	return 0;
}

/**
 * Parse possible number
 * @param parser
 * @param chunk
 * @param obj
 * @return true if a number has been parsed
 */
static bool
ucl_lex_number(struct ucl_parser *parser,
			   struct ucl_chunk *chunk, ucl_object_t *obj)
{
	const unsigned char *pos;
	int ret;

	ret = ucl_maybe_parse_number(obj, chunk->pos, chunk->end, (const char **) &pos,
								 true, false, ((parser->flags & UCL_PARSER_NO_TIME) == 0));

	if (ret == 0) {
		chunk->remain -= pos - chunk->pos;
		chunk->column += pos - chunk->pos;
		chunk->pos = pos;
		return true;
	}
	else if (ret == ERANGE) {
		ucl_set_err(parser, UCL_ESYNTAX, "numeric value out of range",
					&parser->err);
	}

	return false;
}

/**
 * Parse quoted string with possible escapes
 * @param parser
 * @param chunk
 * @param need_unescape
 * @param ucl_escape
 * @param var_expand
 * @return true if a string has been parsed
 */
static bool
ucl_lex_json_string(struct ucl_parser *parser,
					struct ucl_chunk *chunk,
					bool *need_unescape,
					bool *ucl_escape,
					bool *var_expand)
{
	const unsigned char *p = chunk->pos;
	unsigned char c;
	int i;

	while (p < chunk->end) {
		c = *p;
		if (c < 0x1F) {
			/* Unmasked control character */
			if (c == '\n') {
				ucl_set_err(parser, UCL_ESYNTAX, "unexpected newline",
							&parser->err);
			}
			else {
				ucl_set_err(parser, UCL_ESYNTAX, "unexpected control character",
							&parser->err);
			}
			return false;
		}
		else if (c == '\\') {
			ucl_chunk_skipc(chunk, p);
			if (p >= chunk->end) {
				ucl_set_err(parser, UCL_ESYNTAX, "unfinished escape character",
							&parser->err);
				return false;
			}
			c = *p;
			if (ucl_test_character(c, UCL_CHARACTER_ESCAPE)) {
				if (c == 'u') {
					ucl_chunk_skipc(chunk, p);
					for (i = 0; i < 4 && p < chunk->end; i++) {
						if (!isxdigit(*p)) {
							ucl_set_err(parser, UCL_ESYNTAX, "invalid utf escape",
										&parser->err);
							return false;
						}
						ucl_chunk_skipc(chunk, p);
					}
					if (p >= chunk->end) {
						ucl_set_err(parser, UCL_ESYNTAX,
									"unfinished escape character",
									&parser->err);
						return false;
					}
				}
				else {
					ucl_chunk_skipc(chunk, p);
				}
			}
			*need_unescape = true;
			*ucl_escape = true;
			continue;
		}
		else if (c == '"') {
			ucl_chunk_skipc(chunk, p);
			return true;
		}
		else if (ucl_test_character(c, UCL_CHARACTER_UCL_UNSAFE)) {
			*ucl_escape = true;
		}
		else if (c == '$') {
			*var_expand = true;
		}
		ucl_chunk_skipc(chunk, p);
	}

	ucl_set_err(parser, UCL_ESYNTAX,
				"no quote at the end of json string",
				&parser->err);
	return false;
}

/**
 * Process single quoted string
 * @param parser
 * @param chunk
 * @param need_unescape
 * @return
 */
static bool
ucl_lex_squoted_string(struct ucl_parser *parser,
					   struct ucl_chunk *chunk, bool *need_unescape)
{
	const unsigned char *p = chunk->pos;
	unsigned char c;

	while (p < chunk->end) {
		c = *p;
		if (c == '\\') {
			ucl_chunk_skipc(chunk, p);

			if (p >= chunk->end) {
				ucl_set_err(parser, UCL_ESYNTAX,
							"unfinished escape character",
							&parser->err);
				return false;
			}
			else {
				ucl_chunk_skipc(chunk, p);
			}

			*need_unescape = true;
			continue;
		}
		else if (c == '\'') {
			ucl_chunk_skipc(chunk, p);
			return true;
		}

		ucl_chunk_skipc(chunk, p);
	}

	ucl_set_err(parser, UCL_ESYNTAX,
				"no quote at the end of single quoted string",
				&parser->err);
	return false;
}

static void
ucl_parser_append_elt(struct ucl_parser *parser, ucl_hash_t *cont,
					  ucl_object_t *top,
					  ucl_object_t *elt)
{
	ucl_object_t *nobj;

	if ((parser->flags & UCL_PARSER_NO_IMPLICIT_ARRAYS) == 0) {
		/* Implicit array */
		top->flags |= UCL_OBJECT_MULTIVALUE;
		DL_APPEND(top, elt);
		parser->stack->obj->len++;
	}
	else {
		if ((top->flags & UCL_OBJECT_MULTIVALUE) != 0) {
			/* Just add to the explicit array */
			ucl_array_append(top, elt);
		}
		else {
			/* Convert to an array */
			nobj = ucl_object_typed_new(UCL_ARRAY);
			nobj->key = top->key;
			nobj->keylen = top->keylen;
			nobj->flags |= UCL_OBJECT_MULTIVALUE;
			ucl_array_append(nobj, top);
			ucl_array_append(nobj, elt);
			ucl_hash_replace(cont, top, nobj);
		}
	}
}

bool ucl_parser_process_object_element(struct ucl_parser *parser, ucl_object_t *nobj)
{
	ucl_hash_t *container;
	ucl_object_t *tobj = NULL, *cur;
	char errmsg[256];

	container = parser->stack->obj->value.ov;

	DL_FOREACH(parser->stack->obj, cur)
	{
		if (cur->type == UCL_OBJECT) {
			tobj = __DECONST(ucl_object_t *, ucl_hash_search_obj(cur->value.ov, nobj));

			if (tobj != NULL) {
				/*
				 * Check if we have found an object in the same container.
				 * If not, we should probably ignore it as we cannot replace it
				 * effectively and we definitely should not unref it.
				 */
				if (cur->value.ov != container) {
					tobj = NULL;
					continue;
				}
				break;
			}
		}
	}


	if (tobj == NULL) {
		container = ucl_hash_insert_object(container, nobj,
										   parser->flags & UCL_PARSER_KEY_LOWERCASE);
		if (container == NULL) {
			return false;
		}
		nobj->prev = nobj;
		nobj->next = NULL;
		parser->stack->obj->len++;
	}
	else {
		unsigned priold = ucl_object_get_priority(tobj),
				 prinew = ucl_object_get_priority(nobj);
		switch (parser->chunks->strategy) {

		case UCL_DUPLICATE_APPEND:
			/*
			 * The logic here is the following:
			 *
			 * - if we have two objects with the same priority, then we form an
			 * implicit or explicit array
			 * - if a new object has bigger priority, then we overwrite an old one
			 * - if a new object has lower priority, then we ignore it
			 */
			/* Special case for inherited objects */
			if (tobj->flags & UCL_OBJECT_INHERITED) {
				prinew = priold + 1;
			}

			if (priold == prinew) {
				ucl_parser_append_elt(parser, container, tobj, nobj);
			}
			else if (priold > prinew) {
				/*
				 * We add this new object to a list of trash objects just to ensure
				 * that it won't come to any real object
				 * XXX: rather inefficient approach
				 */
				DL_APPEND(parser->trash_objs, nobj);
			}
			else {
				ucl_hash_replace(container, tobj, nobj);
				ucl_object_unref(tobj);
			}

			break;

		case UCL_DUPLICATE_REWRITE:
			/* We just rewrite old values regardless of priority */
			ucl_hash_replace(container, tobj, nobj);
			ucl_object_unref(tobj);

			break;

		case UCL_DUPLICATE_ERROR:
			snprintf(errmsg, sizeof(errmsg),
					 "duplicate element for key '%s' found",
					 nobj->key);
			ucl_set_err(parser, UCL_EMERGE, errmsg, &parser->err);
			return false;

		case UCL_DUPLICATE_MERGE:
			/*
			 * Here we do have some old object so we just push it on top of objects stack
			 * Check priority and then perform the merge on the remaining objects
			 */
			if (tobj->type == UCL_OBJECT || tobj->type == UCL_ARRAY) {
				ucl_object_unref(nobj);
				nobj = tobj;
			}
			else if (priold == prinew) {
				ucl_parser_append_elt(parser, container, tobj, nobj);
			}
			else if (priold > prinew) {
				/*
				 * We add this new object to a list of trash objects just to ensure
				 * that it won't come to any real object
				 * XXX: rather inefficient approach
				 */
				DL_APPEND(parser->trash_objs, nobj);
			}
			else {
				ucl_hash_replace(container, tobj, nobj);
				ucl_object_unref(tobj);
			}
			break;
		}
	}

	parser->stack->obj->value.ov = container;
	parser->cur_obj = nobj;
	ucl_attach_comment(parser, nobj, false);

	return true;
}

/**
 * Parse a key in an object
 * @param parser
 * @param chunk
 * @param next_key
 * @param end_of_object
 * @return true if a key has been parsed
 */
static bool
ucl_parse_key(struct ucl_parser *parser, struct ucl_chunk *chunk,
			  bool *next_key, bool *end_of_object, bool *got_content)
{
	const unsigned char *p, *c = NULL, *end, *t;
	const char *key = NULL;
	bool got_quote = false, got_eq = false, got_semicolon = false,
		 need_unescape = false, ucl_escape = false, var_expand = false,
		 got_sep = false;
	ucl_object_t *nobj;
	ssize_t keylen;

	p = chunk->pos;

	if (*p == '.' && !(parser->flags & UCL_PARSER_DISABLE_MACRO)) {
		ucl_chunk_skipc(chunk, p);
		parser->prev_state = parser->state;
		parser->state = UCL_STATE_MACRO_NAME;
		*end_of_object = false;
		return true;
	}
	while (p < chunk->end) {
		/*
		 * A key must start with alpha, number, '/' or '_' and end with space character
		 */
		if (c == NULL) {
			if (chunk->remain >= 2 && ucl_lex_is_comment(p[0], p[1])) {
				if (!ucl_skip_comments(parser)) {
					return false;
				}
				p = chunk->pos;
			}
			else if (ucl_test_character(*p, UCL_CHARACTER_WHITESPACE_UNSAFE)) {
				ucl_chunk_skipc(chunk, p);
			}
			else if (ucl_test_character(*p, UCL_CHARACTER_KEY_START)) {
				/* The first symbol */
				c = p;
				ucl_chunk_skipc(chunk, p);
				*got_content = true;
			}
			else if (*p == '"') {
				/* JSON style key */
				c = p + 1;
				got_quote = true;
				*got_content = true;
				ucl_chunk_skipc(chunk, p);
			}
			else if (*p == '}') {
				/* We have actually end of an object */
				*end_of_object = true;
				return true;
			}
			else if (*p == '.' && !(parser->flags & UCL_PARSER_DISABLE_MACRO)) {
				ucl_chunk_skipc(chunk, p);
				parser->prev_state = parser->state;
				parser->state = UCL_STATE_MACRO_NAME;
				return true;
			}
			else {
				/* Invalid identifier */
				ucl_set_err(parser, UCL_ESYNTAX, "key must begin with a letter",
							&parser->err);
				return false;
			}
		}
		else {
			/* Parse the body of a key */
			if (!got_quote) {
				if (ucl_test_character(*p, UCL_CHARACTER_KEY)) {
					*got_content = true;
					ucl_chunk_skipc(chunk, p);
				}
				else if (ucl_test_character(*p, UCL_CHARACTER_KEY_SEP)) {
					end = p;
					break;
				}
				else {
					ucl_set_err(parser, UCL_ESYNTAX, "invalid character in a key",
								&parser->err);
					return false;
				}
			}
			else {
				/* We need to parse json like quoted string */
				if (!ucl_lex_json_string(parser, chunk, &need_unescape, &ucl_escape, &var_expand)) {
					return false;
				}
				/* Always escape keys obtained via json */
				end = chunk->pos - 1;
				p = chunk->pos;
				break;
			}
		}
	}

	if (p >= chunk->end && *got_content) {
		ucl_set_err(parser, UCL_ESYNTAX, "unfinished key", &parser->err);
		return false;
	}
	else if (!*got_content) {
		return true;
	}
	*end_of_object = false;
	/* We are now at the end of the key, need to parse the rest */
	while (p < chunk->end) {
		if (ucl_test_character(*p, UCL_CHARACTER_WHITESPACE)) {
			ucl_chunk_skipc(chunk, p);
		}
		else if (*p == '=') {
			if (!got_eq && !got_semicolon) {
				ucl_chunk_skipc(chunk, p);
				got_eq = true;
			}
			else {
				ucl_set_err(parser, UCL_ESYNTAX, "unexpected '=' character",
							&parser->err);
				return false;
			}
		}
		else if (*p == ':') {
			if (!got_eq && !got_semicolon) {
				ucl_chunk_skipc(chunk, p);
				got_semicolon = true;
			}
			else {
				ucl_set_err(parser, UCL_ESYNTAX, "unexpected ':' character",
							&parser->err);
				return false;
			}
		}
		else if (chunk->remain >= 2 && ucl_lex_is_comment(p[0], p[1])) {
			/* Check for comment */
			if (!ucl_skip_comments(parser)) {
				return false;
			}
			p = chunk->pos;
		}
		else {
			/* Start value */
			break;
		}
	}

	if (p >= chunk->end && got_content) {
		ucl_set_err(parser, UCL_ESYNTAX, "unfinished key", &parser->err);
		return false;
	}

	got_sep = got_semicolon || got_eq;

	if (!got_sep) {
		/*
		 * Maybe we have more keys nested, so search for termination character.
		 * Possible choices:
		 * 1) key1 key2 ... keyN [:=] value <- we treat that as error
		 * 2) key1 ... keyN {} or [] <- we treat that as nested objects
		 * 3) key1 value[;,\n] <- we treat that as linear object
		 */
		t = p;
		*next_key = false;
		while (ucl_test_character(*t, UCL_CHARACTER_WHITESPACE)) {
			t++;
		}
		/* Check first non-space character after a key */
		if (*t != '{' && *t != '[') {
			while (t < chunk->end) {
				if (*t == ',' || *t == ';' || *t == '\n' || *t == '\r') {
					break;
				}
				else if (*t == '{' || *t == '[') {
					*next_key = true;
					break;
				}
				t++;
			}
		}
	}

	/* Create a new object */
	nobj = ucl_object_new_full(UCL_NULL, parser->chunks->priority);
	if (nobj == NULL) {
		return false;
	}
	keylen = ucl_copy_or_store_ptr(parser, c, &nobj->trash_stack[UCL_TRASH_KEY],
								   &key, end - c, need_unescape, parser->flags & UCL_PARSER_KEY_LOWERCASE,
								   false, false);
	if (keylen == -1) {
		ucl_object_unref(nobj);
		return false;
	}
	else if (keylen == 0) {
		ucl_set_err(parser, UCL_ESYNTAX, "empty keys are not allowed", &parser->err);
		ucl_object_unref(nobj);
		return false;
	}

	nobj->key = key;
	nobj->keylen = keylen;

	if (!ucl_parser_process_object_element(parser, nobj)) {
		return false;
	}

	if (ucl_escape) {
		nobj->flags |= UCL_OBJECT_NEED_KEY_ESCAPE;
	}


	return true;
}

/**
 * Parse a cl string
 * @param parser
 * @param chunk
 * @param var_expand
 * @param need_unescape
 * @return true if a key has been parsed
 */
static bool
ucl_parse_string_value(struct ucl_parser *parser,
					   struct ucl_chunk *chunk, bool *var_expand, bool *need_unescape)
{
	const unsigned char *p;
	enum {
		UCL_BRACE_ROUND = 0,
		UCL_BRACE_SQUARE,
		UCL_BRACE_FIGURE
	};
	int braces[3][2] = {{0, 0}, {0, 0}, {0, 0}};

	p = chunk->pos;

	while (p < chunk->end) {

		/* Skip pairs of figure braces */
		if (*p == '{') {
			braces[UCL_BRACE_FIGURE][0]++;
		}
		else if (*p == '}') {
			braces[UCL_BRACE_FIGURE][1]++;
			if (braces[UCL_BRACE_FIGURE][1] <= braces[UCL_BRACE_FIGURE][0]) {
				/* This is not a termination symbol, continue */
				ucl_chunk_skipc(chunk, p);
				continue;
			}
		}
		/* Skip pairs of square braces */
		else if (*p == '[') {
			braces[UCL_BRACE_SQUARE][0]++;
		}
		else if (*p == ']') {
			braces[UCL_BRACE_SQUARE][1]++;
			if (braces[UCL_BRACE_SQUARE][1] <= braces[UCL_BRACE_SQUARE][0]) {
				/* This is not a termination symbol, continue */
				ucl_chunk_skipc(chunk, p);
				continue;
			}
		}
		else if (*p == '$') {
			*var_expand = true;
		}
		else if (*p == '\\') {
			*need_unescape = true;
			ucl_chunk_skipc(chunk, p);
			if (p < chunk->end) {
				ucl_chunk_skipc(chunk, p);
			}
			continue;
		}

		if (ucl_lex_is_atom_end(*p) || (chunk->remain >= 2 && ucl_lex_is_comment(p[0], p[1]))) {
			break;
		}
		ucl_chunk_skipc(chunk, p);
	}

	return true;
}

/**
 * Parse multiline string ending with \n{term}\n
 * @param parser
 * @param chunk
 * @param term
 * @param term_len
 * @param beg
 * @param var_expand
 * @return size of multiline string or 0 in case of error
 */
static int
ucl_parse_multiline_string(struct ucl_parser *parser,
						   struct ucl_chunk *chunk, const unsigned char *term,
						   int term_len, unsigned char const **beg,
						   bool *var_expand)
{
	const unsigned char *p, *c, *tend;
	bool newline = false;
	int len = 0;

	p = chunk->pos;

	c = p;

	while (p < chunk->end) {
		if (newline) {
			if (chunk->end - p < term_len) {
				return 0;
			}
			else if (memcmp(p, term, term_len) == 0) {
				tend = p + term_len;
				if (tend < chunk->end && *tend != '\n' && *tend != ';' && *tend != ',') {
					/* Incomplete terminator */
					ucl_chunk_skipc(chunk, p);
					continue;
				}
				len = p - c;
				chunk->remain -= term_len;
				chunk->pos = p + term_len;
				chunk->column = term_len;
				*beg = c;
				break;
			}
		}
		if (*p == '\n') {
			newline = true;
		}
		else {
			if (*p == '$') {
				*var_expand = true;
			}
			newline = false;
		}
		ucl_chunk_skipc(chunk, p);
	}

	return len;
}

static inline ucl_object_t *
ucl_parser_get_container(struct ucl_parser *parser)
{
	ucl_object_t *t, *obj = NULL;

	if (parser == NULL || parser->stack == NULL || parser->stack->obj == NULL) {
		return NULL;
	}

	if (parser->stack->obj->type == UCL_ARRAY) {
		/* Object must be allocated */
		obj = ucl_object_new_full(UCL_NULL, parser->chunks->priority);
		t = parser->stack->obj;

		if (!ucl_array_append(t, obj)) {
			ucl_object_unref(obj);
			return NULL;
		}

		parser->cur_obj = obj;
		ucl_attach_comment(parser, obj, false);
	}
	else {
		/* Object has been already allocated */
		obj = parser->cur_obj;
	}

	return obj;
}

/**
 * Handle value data
 * @param parser
 * @param chunk
 * @return
 */
static bool
ucl_parse_value(struct ucl_parser *parser, struct ucl_chunk *chunk)
{
	const unsigned char *p, *c;
	ucl_object_t *obj = NULL;
	unsigned int stripped_spaces;
	ssize_t str_len;
	bool need_unescape = false, ucl_escape = false, var_expand = false;

	p = chunk->pos;

	/* Skip any spaces and comments */
	if (ucl_test_character(*p, UCL_CHARACTER_WHITESPACE_UNSAFE) ||
		(chunk->remain >= 2 && ucl_lex_is_comment(p[0], p[1]))) {
		while (p < chunk->end && ucl_test_character(*p, UCL_CHARACTER_WHITESPACE_UNSAFE)) {
			ucl_chunk_skipc(chunk, p);
		}
		if (!ucl_skip_comments(parser)) {
			return false;
		}
		p = chunk->pos;
	}

	while (p < chunk->end) {
		c = p;
		switch (*p) {
		case '"':
			ucl_chunk_skipc(chunk, p);

			if (!ucl_lex_json_string(parser, chunk, &need_unescape, &ucl_escape,
									 &var_expand)) {
				return false;
			}

			obj = ucl_parser_get_container(parser);
			if (!obj) {
				return false;
			}

			str_len = chunk->pos - c - 2;
			obj->type = UCL_STRING;
			if ((str_len = ucl_copy_or_store_ptr(parser, c + 1,
												 &obj->trash_stack[UCL_TRASH_VALUE],
												 &obj->value.sv, str_len, need_unescape, false,
												 var_expand, false)) == -1) {
				return false;
			}

			obj->len = str_len;
			parser->state = UCL_STATE_AFTER_VALUE;

			return true;
			break;
		case '\'':
			ucl_chunk_skipc(chunk, p);

			if (!ucl_lex_squoted_string(parser, chunk, &need_unescape)) {
				return false;
			}

			obj = ucl_parser_get_container(parser);
			if (!obj) {
				return false;
			}

			str_len = chunk->pos - c - 2;
			obj->type = UCL_STRING;
			obj->flags |= UCL_OBJECT_SQUOTED;

			if ((str_len = ucl_copy_or_store_ptr(parser, c + 1,
												 &obj->trash_stack[UCL_TRASH_VALUE],
												 &obj->value.sv, str_len, need_unescape, false,
												 var_expand, true)) == -1) {
				return false;
			}

			obj->len = str_len;

			parser->state = UCL_STATE_AFTER_VALUE;

			return true;
			break;
		case '{':
			obj = ucl_parser_get_container(parser);
			if (obj == NULL) {
				parser->state = UCL_STATE_ERROR;
				ucl_set_err(parser, UCL_ESYNTAX, "object value must be a part of an object",
							&parser->err);
				return false;
			}
			/* We have a new object */
			if (parser->stack) {
				obj = ucl_parser_add_container(obj, parser, false,
											   parser->stack->e.params.level, true);
			}
			else {
				return false;
			}
			if (obj == NULL) {
				return false;
			}

			ucl_chunk_skipc(chunk, p);

			return true;
			break;
		case '[':
			obj = ucl_parser_get_container(parser);
			if (obj == NULL) {
				parser->state = UCL_STATE_ERROR;
				ucl_set_err(parser, UCL_ESYNTAX, "array value must be a part of an object",
							&parser->err);
				return false;
			}
			/* We have a new array */
			if (parser->stack) {
				obj = ucl_parser_add_container(obj, parser, true,
											   parser->stack->e.params.level, true);
			}
			else {
				return false;
			}

			if (obj == NULL) {
				return false;
			}

			ucl_chunk_skipc(chunk, p);

			return true;
			break;
		case ']':
			/* We have the array ending */
			if (parser->stack && parser->stack->obj->type == UCL_ARRAY) {
				parser->state = UCL_STATE_AFTER_VALUE;
				return true;
			}
			else {
				goto parse_string;
			}
			break;
		case '<':
			obj = ucl_parser_get_container(parser);
			if (obj == NULL) {
				parser->state = UCL_STATE_ERROR;
				ucl_set_err(parser, UCL_ESYNTAX, "multiline value must be a part of an object",
							&parser->err);
				return false;
			}
			/* We have something like multiline value, which must be <<[A-Z]+\n */
			if (chunk->end - p > 3) {
				if (memcmp(p, "<<", 2) == 0) {
					p += 2;
					/* We allow only uppercase characters in multiline definitions */
					while (p < chunk->end && *p >= 'A' && *p <= 'Z') {
						p++;
					}
					if (p == chunk->end) {
						ucl_set_err(parser, UCL_ESYNTAX,
									"unterminated multiline value", &parser->err);
						return false;
					}
					if (*p == '\n') {
						/* Set chunk positions and start multiline parsing */
						chunk->remain -= p - c + 1;
						c += 2;
						chunk->pos = p + 1;
						chunk->column = 0;
						chunk->line++;
						if ((str_len = ucl_parse_multiline_string(parser, chunk, c,
																  p - c, &c, &var_expand)) == 0) {
							ucl_set_err(parser, UCL_ESYNTAX,
										"unterminated multiline value", &parser->err);
							return false;
						}

						obj->type = UCL_STRING;
						obj->flags |= UCL_OBJECT_MULTILINE;
						if ((str_len = ucl_copy_or_store_ptr(parser, c,
															 &obj->trash_stack[UCL_TRASH_VALUE],
															 &obj->value.sv, str_len - 1, false,
															 false, var_expand, false)) == -1) {
							return false;
						}
						obj->len = str_len;

						parser->state = UCL_STATE_AFTER_VALUE;

						return true;
					}
				}
			}
			/* Fallback to ordinary strings */
			/* FALLTHRU */
		default:
		parse_string:
			if (obj == NULL) {
				obj = ucl_parser_get_container(parser);
			}

			if (obj == NULL) {
				parser->state = UCL_STATE_ERROR;
				ucl_set_err(parser, UCL_ESYNTAX, "value must be a part of an object",
							&parser->err);
				return false;
			}

			/* Parse atom */
			if (ucl_test_character(*p, UCL_CHARACTER_VALUE_DIGIT_START)) {
				if (!ucl_lex_number(parser, chunk, obj)) {
					if (parser->state == UCL_STATE_ERROR) {
						return false;
					}
				}
				else {
					parser->state = UCL_STATE_AFTER_VALUE;
					return true;
				}
				/* Fallback to normal string */
			}

			if (!ucl_parse_string_value(parser, chunk, &var_expand,
										&need_unescape)) {
				return false;
			}
			/* Cut trailing spaces */
			stripped_spaces = 0;
			while (ucl_test_character(*(chunk->pos - 1 - stripped_spaces),
									  UCL_CHARACTER_WHITESPACE)) {
				stripped_spaces++;
			}
			str_len = chunk->pos - c - stripped_spaces;
			if (str_len <= 0) {
				ucl_set_err(parser, UCL_ESYNTAX, "string value must not be empty",
							&parser->err);
				return false;
			}
			else if (str_len == 4 && memcmp(c, "null", 4) == 0) {
				obj->len = 0;
				obj->type = UCL_NULL;
			}
			else if (str_len == 3 && memcmp(c, "nan", 3) == 0) {
				obj->len = 0;
				obj->type = UCL_FLOAT;
				obj->value.dv = NAN;
			}
			else if (str_len == 3 && memcmp(c, "inf", 3) == 0) {
				obj->len = 0;
				obj->type = UCL_FLOAT;
				obj->value.dv = INFINITY;
			}
			else if (!ucl_maybe_parse_boolean(obj, c, str_len)) {
				obj->type = UCL_STRING;
				if ((str_len = ucl_copy_or_store_ptr(parser, c,
													 &obj->trash_stack[UCL_TRASH_VALUE],
													 &obj->value.sv, str_len, need_unescape,
													 false, var_expand, false)) == -1) {
					return false;
				}
				obj->len = str_len;
			}

			parser->state = UCL_STATE_AFTER_VALUE;

			return true;
			break;
		}
	}

	return true;
}

/**
 * Handle after value data
 * @param parser
 * @param chunk
 * @return
 */
static bool
ucl_parse_after_value(struct ucl_parser *parser, struct ucl_chunk *chunk)
{
	const unsigned char *p;
	bool got_sep = false;
	struct ucl_stack *st;

	p = chunk->pos;

	while (p < chunk->end) {
		if (ucl_test_character(*p, UCL_CHARACTER_WHITESPACE)) {
			/* Skip whitespaces */
			ucl_chunk_skipc(chunk, p);
		}
		else if (chunk->remain >= 2 && ucl_lex_is_comment(p[0], p[1])) {
			/* Skip comment */
			if (!ucl_skip_comments(parser)) {
				return false;
			}
			/* Treat comment as a separator */
			got_sep = true;
			p = chunk->pos;
		}
		else if (ucl_test_character(*p, UCL_CHARACTER_VALUE_END)) {
			if (*p == '}' || *p == ']') {
				if (parser->stack == NULL) {
					ucl_set_err(parser, UCL_ESYNTAX,
								"end of array or object detected without corresponding start",
								&parser->err);
					return false;
				}
				if ((*p == '}' && parser->stack->obj->type == UCL_OBJECT) ||
					(*p == ']' && parser->stack->obj->type == UCL_ARRAY)) {

					/* Pop all nested objects from a stack */
					st = parser->stack;

					if (!(st->e.params.flags & UCL_STACK_HAS_OBRACE)) {
						parser->err_code = UCL_EUNPAIRED;
						ucl_create_err(&parser->err,
									   "%s:%d object closed with } is not opened with { at line %d",
									   chunk->fname ? chunk->fname : "memory",
									   parser->chunks->line, st->e.params.line);

						return false;
					}

					if ((st->e.params.flags & UCL_STACK_AUTOMATIC)) {
						st->e.params.flags = 0;
					}
					else {
						parser->stack = st->next;
						UCL_FREE(sizeof(struct ucl_stack), st);
					}

					if (parser->cur_obj) {
						ucl_attach_comment(parser, parser->cur_obj, true);
					}

					while (parser->stack != NULL) {
						st = parser->stack;

						if (st->next == NULL) {
							break;
						}
						else if (st->next->e.params.level == st->e.params.level) {
							break;
						}


						parser->stack = st->next;
						parser->cur_obj = st->obj;
						UCL_FREE(sizeof(struct ucl_stack), st);
					}
				}
				else {
					ucl_set_err(parser, UCL_ESYNTAX,
								"unexpected terminating symbol detected",
								&parser->err);
					return false;
				}

				if (parser->stack == NULL) {
					/* Ignore everything after a top object */
					return true;
				}
				else {
					ucl_chunk_skipc(chunk, p);
				}
				got_sep = true;
			}
			else {
				/* Got a separator */
				got_sep = true;
				ucl_chunk_skipc(chunk, p);
			}
		}
		else {
			/* Anything else */
			if (!got_sep) {
				ucl_set_err(parser, UCL_ESYNTAX, "delimiter is missing",
							&parser->err);
				return false;
			}
			return true;
		}
	}

	return true;
}

static bool
ucl_skip_macro_as_comment(struct ucl_parser *parser,
						  struct ucl_chunk *chunk)
{
	const unsigned char *p, *c;
	enum {
		macro_skip_start = 0,
		macro_has_symbols,
		macro_has_obrace,
		macro_has_quote,
		macro_has_backslash,
		macro_has_sqbrace,
		macro_save
	} state = macro_skip_start,
	  prev_state = macro_skip_start;

	p = chunk->pos;
	c = chunk->pos;

	while (p < chunk->end) {
		switch (state) {
		case macro_skip_start:
			if (!ucl_test_character(*p, UCL_CHARACTER_WHITESPACE)) {
				state = macro_has_symbols;
			}
			else if (ucl_test_character(*p, UCL_CHARACTER_WHITESPACE_UNSAFE)) {
				state = macro_save;
				continue;
			}

			ucl_chunk_skipc(chunk, p);
			break;

		case macro_has_symbols:
			if (*p == '{') {
				state = macro_has_sqbrace;
			}
			else if (*p == '(') {
				state = macro_has_obrace;
			}
			else if (*p == '"') {
				state = macro_has_quote;
			}
			else if (*p == '\n') {
				state = macro_save;
				continue;
			}

			ucl_chunk_skipc(chunk, p);
			break;

		case macro_has_obrace:
			if (*p == '\\') {
				prev_state = state;
				state = macro_has_backslash;
			}
			else if (*p == ')') {
				state = macro_has_symbols;
			}

			ucl_chunk_skipc(chunk, p);
			break;

		case macro_has_sqbrace:
			if (*p == '\\') {
				prev_state = state;
				state = macro_has_backslash;
			}
			else if (*p == '}') {
				state = macro_save;
			}

			ucl_chunk_skipc(chunk, p);
			break;

		case macro_has_quote:
			if (*p == '\\') {
				prev_state = state;
				state = macro_has_backslash;
			}
			else if (*p == '"') {
				state = macro_save;
			}

			ucl_chunk_skipc(chunk, p);
			break;

		case macro_has_backslash:
			state = prev_state;
			ucl_chunk_skipc(chunk, p);
			break;

		case macro_save:
			if (parser->flags & UCL_PARSER_SAVE_COMMENTS) {
				ucl_save_comment(parser, c, p - c);
			}

			return true;
		}
	}

	return false;
}

/**
 * Handle macro data
 * @param parser
 * @param chunk
 * @param marco
 * @param macro_start
 * @param macro_len
 * @return
 */
static bool
ucl_parse_macro_value(struct ucl_parser *parser,
					  struct ucl_chunk *chunk, struct ucl_macro *macro,
					  unsigned char const **macro_start, size_t *macro_len)
{
	const unsigned char *p, *c;
	bool need_unescape = false, ucl_escape = false, var_expand = false;

	p = chunk->pos;

	switch (*p) {
	case '"':
		/* We have macro value encoded in quotes */
		c = p;
		ucl_chunk_skipc(chunk, p);
		if (!ucl_lex_json_string(parser, chunk, &need_unescape, &ucl_escape, &var_expand)) {
			return false;
		}

		*macro_start = c + 1;
		*macro_len = chunk->pos - c - 2;
		p = chunk->pos;
		break;
	case '{':
		/* We got a multiline macro body */
		ucl_chunk_skipc(chunk, p);
		/* Skip spaces at the beginning */
		while (p < chunk->end) {
			if (ucl_test_character(*p, UCL_CHARACTER_WHITESPACE_UNSAFE)) {
				ucl_chunk_skipc(chunk, p);
			}
			else {
				break;
			}
		}
		c = p;
		while (p < chunk->end) {
			if (*p == '}') {
				break;
			}
			ucl_chunk_skipc(chunk, p);
		}
		*macro_start = c;
		*macro_len = p - c;
		ucl_chunk_skipc(chunk, p);
		break;
	default:
		/* Macro is not enclosed in quotes or braces */
		c = p;
		while (p < chunk->end) {
			if (ucl_lex_is_atom_end(*p)) {
				break;
			}
			ucl_chunk_skipc(chunk, p);
		}
		*macro_start = c;
		*macro_len = p - c;
		break;
	}

	/* We are at the end of a macro */
	/* Skip ';' and space characters and return to previous state */
	while (p < chunk->end) {
		if (!ucl_test_character(*p, UCL_CHARACTER_WHITESPACE_UNSAFE) && *p != ';') {
			break;
		}
		ucl_chunk_skipc(chunk, p);
	}
	return true;
}

/**
 * Parse macro arguments as UCL object
 * @param parser parser structure
 * @param chunk the current data chunk
 * @return
 */
static ucl_object_t *
ucl_parse_macro_arguments(struct ucl_parser *parser,
						  struct ucl_chunk *chunk)
{
	ucl_object_t *res = NULL;
	struct ucl_parser *params_parser;
	int obraces = 1, ebraces = 0, state = 0;
	const unsigned char *p, *c;
	size_t args_len = 0;
	struct ucl_parser_saved_state saved;

	saved.column = chunk->column;
	saved.line = chunk->line;
	saved.pos = chunk->pos;
	saved.remain = chunk->remain;
	p = chunk->pos;

	if (*p != '(' || chunk->remain < 2) {
		return NULL;
	}

	/* Set begin and start */
	ucl_chunk_skipc(chunk, p);
	c = p;

	while ((p) < (chunk)->end) {
		switch (state) {
		case 0:
			/* Parse symbols and check for '(', ')' and '"' */
			if (*p == '(') {
				obraces++;
			}
			else if (*p == ')') {
				ebraces++;
			}
			else if (*p == '"') {
				state = 1;
			}
			/* Check pairing */
			if (obraces == ebraces) {
				state = 99;
			}
			else {
				args_len++;
			}
			/* Check overflow */
			if (chunk->remain == 0) {
				goto restore_chunk;
			}
			ucl_chunk_skipc(chunk, p);
			break;
		case 1:
			/* We have quote character, so skip all but quotes */
			if (*p == '"' && *(p - 1) != '\\') {
				state = 0;
			}
			if (chunk->remain == 0) {
				goto restore_chunk;
			}
			args_len++;
			ucl_chunk_skipc(chunk, p);
			break;
		case 99:
			/*
			 * We have read the full body of arguments, so we need to parse and set
			 * object from that
			 */
			params_parser = ucl_parser_new(parser->flags);
			if (!ucl_parser_add_chunk(params_parser, c, args_len)) {
				ucl_set_err(parser, UCL_ESYNTAX, "macro arguments parsing error",
							&parser->err);
			}
			else {
				res = ucl_parser_get_object(params_parser);
			}
			ucl_parser_free(params_parser);

			return res;

			break;
		}
	}

	return res;

restore_chunk:
	chunk->column = saved.column;
	chunk->line = saved.line;
	chunk->pos = saved.pos;
	chunk->remain = saved.remain;

	return NULL;
}

#define SKIP_SPACES_COMMENTS(parser, chunk, p)                                    \
	do {                                                                          \
		while ((p) < (chunk)->end) {                                              \
			if (!ucl_test_character(*(p), UCL_CHARACTER_WHITESPACE_UNSAFE)) {     \
				if ((chunk)->remain >= 2 && ucl_lex_is_comment((p)[0], (p)[1])) { \
					if (!ucl_skip_comments(parser)) {                             \
						return false;                                             \
					}                                                             \
					p = (chunk)->pos;                                             \
				}                                                                 \
				break;                                                            \
			}                                                                     \
			ucl_chunk_skipc(chunk, p);                                            \
		}                                                                         \
	} while (0)

/**
 * Handle the main states of rcl parser
 * @param parser parser structure
 * @return true if chunk has been parsed and false in case of error
 */
static bool
ucl_state_machine(struct ucl_parser *parser)
{
	ucl_object_t *obj, *macro_args;
	struct ucl_chunk *chunk = parser->chunks;
	const unsigned char *p, *c = NULL, *macro_start = NULL;
	unsigned char *macro_escaped;
	size_t macro_len = 0;
	struct ucl_macro *macro = NULL;
	bool next_key = false, end_of_object = false, got_content = false, ret;

	if (parser->top_obj == NULL) {
		parser->state = UCL_STATE_INIT;
	}

	p = chunk->pos;
	while (chunk->pos < chunk->end) {
		switch (parser->state) {
		case UCL_STATE_INIT:
			/*
			 * At the init state we can either go to the parse array or object
			 * if we got [ or { correspondingly or can just treat new data as
			 * a key of newly created object
			 */
			if (!ucl_skip_comments(parser)) {
				parser->prev_state = parser->state;
				parser->state = UCL_STATE_ERROR;
				return false;
			}
			else {
				bool seen_obrace = false;

				/* Skip any spaces */
				while (p < chunk->end && ucl_test_character(*p,
															UCL_CHARACTER_WHITESPACE_UNSAFE)) {
					ucl_chunk_skipc(chunk, p);
				}

				p = chunk->pos;

				if (p < chunk->end) {
					if (*p == '[') {
						parser->state = UCL_STATE_VALUE;
						ucl_chunk_skipc(chunk, p);
						seen_obrace = true;
					}
					else {

						if (*p == '{') {
							ucl_chunk_skipc(chunk, p);
							parser->state = UCL_STATE_KEY_OBRACE;
							seen_obrace = true;
						}
						else {
							parser->state = UCL_STATE_KEY;
						}
					}
				}

				if (parser->top_obj == NULL) {
					if (parser->state == UCL_STATE_VALUE) {
						obj = ucl_parser_add_container(NULL, parser, true, 0,
													   seen_obrace);
					}
					else {
						obj = ucl_parser_add_container(NULL, parser, false, 0,
													   seen_obrace);
					}

					if (obj == NULL) {
						return false;
					}

					parser->top_obj = obj;
					parser->cur_obj = obj;
				}
			}
			break;
		case UCL_STATE_KEY_OBRACE:
			parser->stack->e.params.flags |= UCL_STACK_HAS_OBRACE | UCL_STACK_AUTOMATIC;
			/* FALLTHROUGHT */
		case UCL_STATE_KEY:
			/* Skip any spaces */
			while (p < chunk->end && ucl_test_character(*p, UCL_CHARACTER_WHITESPACE_UNSAFE)) {
				ucl_chunk_skipc(chunk, p);
			}
			if (p == chunk->end || *p == '}') {
				/* We have the end of an object */
				parser->state = UCL_STATE_AFTER_VALUE;
				continue;
			}
			if (parser->stack == NULL) {
				/* No objects are on stack, but we want to parse a key */
				ucl_set_err(parser, UCL_ESYNTAX, "top object is finished but the parser "
												 "expects a key",
							&parser->err);
				parser->prev_state = parser->state;
				parser->state = UCL_STATE_ERROR;
				return false;
			}

			got_content = false;

			if (!ucl_parse_key(parser, chunk, &next_key, &end_of_object, &got_content)) {
				parser->prev_state = parser->state;
				parser->state = UCL_STATE_ERROR;
				return false;
			}

			if (end_of_object) {
				p = chunk->pos;
				parser->state = UCL_STATE_AFTER_VALUE;
				continue;
			}
			else if (parser->state != UCL_STATE_MACRO_NAME) {
				if (next_key && parser->stack->obj->type == UCL_OBJECT) {
					/* Parse more keys and nest objects accordingly */
					obj = ucl_parser_add_container(parser->cur_obj,
												   parser,
												   false,
												   parser->stack->e.params.level + 1,
												   parser->state == UCL_STATE_KEY_OBRACE);
					if (obj == NULL) {
						return false;
					}
				}
				else if (got_content) {
					/* Do not switch state if we have not read any content */
					parser->state = UCL_STATE_VALUE;
				}
			}
			else {
				c = chunk->pos;
			}
			p = chunk->pos;
			break;
		case UCL_STATE_VALUE:
			/* We need to check what we do have */
			if (!parser->cur_obj || !ucl_parse_value(parser, chunk)) {
				parser->prev_state = parser->state;
				parser->state = UCL_STATE_ERROR;
				return false;
			}
			/* State is set in ucl_parse_value call */
			p = chunk->pos;
			break;
		case UCL_STATE_AFTER_VALUE:
			if (!ucl_parse_after_value(parser, chunk)) {
				parser->prev_state = parser->state;
				parser->state = UCL_STATE_ERROR;
				return false;
			}

			if (parser->stack != NULL) {
				if (parser->stack->obj->type == UCL_OBJECT) {
					parser->state = UCL_STATE_KEY;
				}
				else {
					/* Array */
					parser->state = UCL_STATE_VALUE;
				}
			}
			else {
				/* Skip everything at the end */
				return true;
			}

			p = chunk->pos;
			break;
		case UCL_STATE_MACRO_NAME:
			if (parser->flags & UCL_PARSER_DISABLE_MACRO) {
				if (!ucl_skip_macro_as_comment(parser, chunk)) {
					/* We have invalid macro */
					ucl_create_err(&parser->err,
								   "error at %s:%d at column %d: invalid macro",
								   chunk->fname ? chunk->fname : "memory",
								   chunk->line,
								   chunk->column);
					parser->state = UCL_STATE_ERROR;
					return false;
				}
				else {
					p = chunk->pos;
					parser->state = parser->prev_state;
				}
			}
			else {
				if (!ucl_test_character(*p, UCL_CHARACTER_WHITESPACE_UNSAFE) &&
					*p != '(') {
					ucl_chunk_skipc(chunk, p);
				}
				else {
					if (c != NULL && p - c > 0) {
						/* We got macro name */
						macro_len = (size_t) (p - c);
						HASH_FIND(hh, parser->macroes, c, macro_len, macro);
						if (macro == NULL) {
							ucl_create_err(&parser->err,
										   "error at %s:%d at column %d: "
										   "unknown macro: '%.*s', character: '%c'",
										   chunk->fname ? chunk->fname : "memory",
										   chunk->line,
										   chunk->column,
										   (int) (p - c),
										   c,
										   *chunk->pos);
							parser->state = UCL_STATE_ERROR;
							return false;
						}
						/* Now we need to skip all spaces */
						SKIP_SPACES_COMMENTS(parser, chunk, p);
						parser->state = UCL_STATE_MACRO;
					}
					else {
						/* We have invalid macro name */
						ucl_create_err(&parser->err,
									   "error at %s:%d at column %d: invalid macro name",
									   chunk->fname ? chunk->fname : "memory",
									   chunk->line,
									   chunk->column);
						parser->state = UCL_STATE_ERROR;
						return false;
					}
				}
			}
			break;
		case UCL_STATE_MACRO:
			if (*chunk->pos == '(') {
				macro_args = ucl_parse_macro_arguments(parser, chunk);
				p = chunk->pos;
				if (macro_args) {
					SKIP_SPACES_COMMENTS(parser, chunk, p);
				}
			}
			else {
				macro_args = NULL;
			}
			if (!ucl_parse_macro_value(parser, chunk, macro,
									   &macro_start, &macro_len)) {
				parser->prev_state = parser->state;
				parser->state = UCL_STATE_ERROR;
				return false;
			}
			macro_len = ucl_expand_variable(parser, &macro_escaped,
											macro_start, macro_len);
			parser->state = parser->prev_state;

			if (macro_escaped == NULL && macro != NULL) {
				if (macro->is_context) {
					ret = macro->h.context_handler(macro_start, macro_len,
												   macro_args,
												   parser->top_obj,
												   macro->ud);
				}
				else {
					ret = macro->h.handler(macro_start, macro_len, macro_args,
										   macro->ud);
				}
			}
			else if (macro != NULL) {
				if (macro->is_context) {
					ret = macro->h.context_handler(macro_escaped, macro_len,
												   macro_args,
												   parser->top_obj,
												   macro->ud);
				}
				else {
					ret = macro->h.handler(macro_escaped, macro_len, macro_args,
										   macro->ud);
				}

				UCL_FREE(macro_len + 1, macro_escaped);
			}
			else {
				ret = false;
				ucl_set_err(parser, UCL_EINTERNAL,
							"internal error: parser has macro undefined", &parser->err);
			}

			/*
			 * Chunk can be modified within macro handler
			 */
			chunk = parser->chunks;
			p = chunk->pos;

			if (macro_args) {
				ucl_object_unref(macro_args);
			}

			if (!ret) {
				return false;
			}
			break;
		case UCL_STATE_ERROR:
			/* Already in the error state */
			return false;
		default:
			ucl_set_err(parser, UCL_EINTERNAL,
						"internal error: parser is in an unknown state", &parser->err);
			parser->state = UCL_STATE_ERROR;
			return false;
		}
	}

	if (parser->last_comment) {
		if (parser->cur_obj) {
			ucl_attach_comment(parser, parser->cur_obj, true);
		}
		else if (parser->stack && parser->stack->obj) {
			ucl_attach_comment(parser, parser->stack->obj, true);
		}
		else if (parser->top_obj) {
			ucl_attach_comment(parser, parser->top_obj, true);
		}
		else {
			ucl_object_unref(parser->last_comment);
		}
	}

	if (parser->stack != NULL && parser->state != UCL_STATE_ERROR) {
		struct ucl_stack *st;
		bool has_error = false;

		LL_FOREACH(parser->stack, st)
		{
			if (st->chunk != parser->chunks) {
				break; /* Not our chunk, give up */
			}
			if (st->e.params.flags & UCL_STACK_HAS_OBRACE) {
				if (parser->err == NULL) {
					utstring_new(parser->err);
				}

				utstring_printf(parser->err, "%s:%d unmatched open brace at %d; ",
								chunk->fname ? chunk->fname : "memory",
								parser->chunks->line,
								st->e.params.line);

				has_error = true;
			}
		}

		if (has_error) {
			parser->err_code = UCL_EUNPAIRED;

			return false;
		}
	}

	return true;
}

#define UPRM_SAFE(fn, a, b, c, el) \
	do {                           \
		if (!fn(a, b, c, a))       \
			goto el;               \
	} while (0)

struct ucl_parser *
ucl_parser_new(int flags)
{
	struct ucl_parser *parser;

	parser = UCL_ALLOC(sizeof(struct ucl_parser));
	if (parser == NULL) {
		return NULL;
	}

	memset(parser, 0, sizeof(struct ucl_parser));

	UPRM_SAFE(ucl_parser_register_macro, parser, "include", ucl_include_handler, e0);
	UPRM_SAFE(ucl_parser_register_macro, parser, "try_include", ucl_try_include_handler, e0);
	UPRM_SAFE(ucl_parser_register_macro, parser, "includes", ucl_includes_handler, e0);
	UPRM_SAFE(ucl_parser_register_macro, parser, "priority", ucl_priority_handler, e0);
	UPRM_SAFE(ucl_parser_register_macro, parser, "load", ucl_load_handler, e0);
	UPRM_SAFE(ucl_parser_register_context_macro, parser, "inherit", ucl_inherit_handler, e0);

	parser->flags = flags;
	parser->includepaths = NULL;

	if (flags & UCL_PARSER_SAVE_COMMENTS) {
		parser->comments = ucl_object_typed_new(UCL_OBJECT);
	}

	if (!(flags & UCL_PARSER_NO_FILEVARS)) {
		/* Initial assumption about filevars */
		ucl_parser_set_filevars(parser, NULL, false);
	}

	return parser;
e0:
	ucl_parser_free(parser);
	return NULL;
}

bool ucl_parser_set_default_priority(struct ucl_parser *parser, unsigned prio)
{
	if (parser == NULL) {
		return false;
	}

	parser->default_priority = prio;

	return true;
}

int ucl_parser_get_default_priority(struct ucl_parser *parser)
{
	if (parser == NULL) {
		return -1;
	}

	return parser->default_priority;
}

bool ucl_parser_register_macro(struct ucl_parser *parser, const char *macro,
							   ucl_macro_handler handler, void *ud)
{
	struct ucl_macro *new;

	if (macro == NULL || handler == NULL) {
		return false;
	}

	new = UCL_ALLOC(sizeof(struct ucl_macro));
	if (new == NULL) {
		return false;
	}

	memset(new, 0, sizeof(struct ucl_macro));
	new->h.handler = handler;
	new->name = UCL_STRDUP(macro);
	if (new->name == NULL) {
		UCL_FREE(sizeof(struct ucl_macro), new);
		return false;
	}
	new->ud = ud;
	HASH_ADD_KEYPTR(hh, parser->macroes, new->name, strlen(new->name), new);
	return true;
}

bool ucl_parser_register_context_macro(struct ucl_parser *parser, const char *macro,
									   ucl_context_macro_handler handler, void *ud)
{
	struct ucl_macro *new;

	if (macro == NULL || handler == NULL) {
		return false;
	}

	new = UCL_ALLOC(sizeof(struct ucl_macro));
	if (new == NULL) {
		return false;
	}

	memset(new, 0, sizeof(struct ucl_macro));
	new->h.context_handler = handler;
	new->name = UCL_STRDUP(macro);
	if (new->name == NULL) {
		UCL_FREE(sizeof(struct ucl_macro), new);
		return false;
	}
	new->ud = ud;
	new->is_context = true;
	HASH_ADD_KEYPTR(hh, parser->macroes, new->name, strlen(new->name), new);
	return true;
}

void ucl_parser_register_variable(struct ucl_parser *parser, const char *var,
								  const char *value)
{
	struct ucl_variable *new = NULL, *cur;

	if (var == NULL) {
		return;
	}

	/* Find whether a variable already exists */
	LL_FOREACH(parser->variables, cur)
	{
		if (strcmp(cur->var, var) == 0) {
			new = cur;
			break;
		}
	}

	if (value == NULL) {

		if (new != NULL) {
			/* Remove variable */
			DL_DELETE(parser->variables, new);
			UCL_FREE(new->var_len + 1, new->var);
			UCL_FREE(new->value_len + 1, new->value);
			UCL_FREE(sizeof(struct ucl_variable), new);
		}
		else {
			/* Do nothing */
			return;
		}
	}
	else {
		if (new == NULL) {
			new = UCL_ALLOC(sizeof(struct ucl_variable));
			if (new == NULL) {
				return;
			}
			memset(new, 0, sizeof(struct ucl_variable));
			new->var = UCL_STRDUP(var);
			new->var_len = strlen(var);
			new->value = UCL_STRDUP(value);
			new->value_len = strlen(value);

			DL_APPEND(parser->variables, new);
		}
		else {
			UCL_FREE(new->value_len + 1, new->value);
			new->value = UCL_STRDUP(value);
			new->value_len = strlen(value);
		}
	}
}

void ucl_parser_set_variables_handler(struct ucl_parser *parser,
									  ucl_variable_handler handler, void *ud)
{
	parser->var_handler = handler;
	parser->var_data = ud;
}

bool ucl_parser_add_chunk_full(struct ucl_parser *parser, const unsigned char *data,
							   size_t len, unsigned priority, enum ucl_duplicate_strategy strat,
							   enum ucl_parse_type parse_type)
{
	struct ucl_chunk *chunk;
	struct ucl_parser_special_handler *special_handler;

	if (parser == NULL) {
		return false;
	}

	if (data == NULL && len != 0) {
		ucl_create_err(&parser->err, "invalid chunk added");
		return false;
	}

	if (parser->state != UCL_STATE_ERROR) {
		chunk = UCL_ALLOC(sizeof(struct ucl_chunk));
		if (chunk == NULL) {
			ucl_create_err(&parser->err, "cannot allocate chunk structure");
			return false;
		}

		memset(chunk, 0, sizeof(*chunk));

		/* Apply all matching handlers from the first to the last */
		LL_FOREACH(parser->special_handlers, special_handler)
		{
			if ((special_handler->flags & UCL_SPECIAL_HANDLER_PREPROCESS_ALL) ||
				(len >= special_handler->magic_len &&
				 memcmp(data, special_handler->magic, special_handler->magic_len) == 0)) {
				unsigned char *ndata = NULL;
				size_t nlen = 0;

				if (!special_handler->handler(parser, data, len, &ndata, &nlen,
											  special_handler->user_data)) {
					UCL_FREE(sizeof(struct ucl_chunk), chunk);
					ucl_create_err(&parser->err, "call for external handler failed");

					return false;
				}

				struct ucl_parser_special_handler_chain *nchain;
				nchain = UCL_ALLOC(sizeof(*nchain));
				nchain->begin = ndata;
				nchain->len = nlen;
				nchain->special_handler = special_handler;

				/* Free order is reversed */
				LL_PREPEND(chunk->special_handlers, nchain);

				data = ndata;
				len = nlen;
			}
		}

		if (parse_type == UCL_PARSE_AUTO && len > 0) {
			/* We need to detect parse type by the first symbol */
			if ((*data & 0x80) == 0x80) {
				parse_type = UCL_PARSE_MSGPACK;
			}
			else if (*data == '(') {
				parse_type = UCL_PARSE_CSEXP;
			}
			else {
				parse_type = UCL_PARSE_UCL;
			}
		}

		chunk->begin = data;
		chunk->remain = len;
		chunk->pos = chunk->begin;
		chunk->end = chunk->begin + len;
		chunk->line = 1;
		chunk->column = 0;
		chunk->priority = priority;
		chunk->strategy = strat;
		chunk->parse_type = parse_type;

		if (parser->cur_file) {
			chunk->fname = UCL_STRDUP(parser->cur_file);
		}

		LL_PREPEND(parser->chunks, chunk);
		parser->recursion++;

		if (parser->recursion > UCL_MAX_RECURSION) {
			ucl_create_err(&parser->err, "maximum include nesting limit is reached: %d",
						   parser->recursion);
			return false;
		}

		if (len > 0) {
			/* Need to parse something */
			switch (parse_type) {
			default:
			case UCL_PARSE_UCL:
				return ucl_state_machine(parser);
			case UCL_PARSE_MSGPACK:
				return ucl_parse_msgpack(parser);
			case UCL_PARSE_CSEXP:
				return ucl_parse_csexp(parser);
			}
		}
		else {
			/* Just add empty chunk and go forward */
			if (parser->top_obj == NULL) {
				/*
				 * In case of empty object, create one to indicate that we've
				 * read something
				 */
				parser->top_obj = ucl_object_new_full(UCL_OBJECT, priority);
			}

			return true;
		}
	}

	ucl_create_err(&parser->err, "a parser is in an invalid state");

	return false;
}

bool ucl_parser_add_chunk_priority(struct ucl_parser *parser,
								   const unsigned char *data, size_t len, unsigned priority)
{
	/* We dereference parser, so this check is essential */
	if (parser == NULL) {
		return false;
	}

	return ucl_parser_add_chunk_full(parser, data, len,
									 priority, UCL_DUPLICATE_APPEND, UCL_PARSE_UCL);
}

bool ucl_parser_add_chunk(struct ucl_parser *parser, const unsigned char *data,
						  size_t len)
{
	if (parser == NULL) {
		return false;
	}

	return ucl_parser_add_chunk_full(parser, data, len,
									 parser->default_priority, UCL_DUPLICATE_APPEND, UCL_PARSE_UCL);
}

bool ucl_parser_insert_chunk(struct ucl_parser *parser, const unsigned char *data,
							 size_t len)
{
	if (parser == NULL || parser->top_obj == NULL) {
		return false;
	}

	bool res;
	struct ucl_chunk *chunk;

	int state = parser->state;
	parser->state = UCL_STATE_INIT;

	/* Prevent inserted chunks from unintentionally closing the current object */
	if (parser->stack != NULL && parser->stack->next != NULL) {
		parser->stack->e.params.level = parser->stack->next->e.params.level;
	}

	res = ucl_parser_add_chunk_full(parser, data, len, parser->chunks->priority,
									parser->chunks->strategy, parser->chunks->parse_type);

	/* Remove chunk from the stack */
	chunk = parser->chunks;
	if (chunk != NULL) {
		parser->chunks = chunk->next;
		ucl_chunk_free(chunk);
		parser->recursion--;
	}

	parser->state = state;

	return res;
}

bool ucl_parser_add_string_priority(struct ucl_parser *parser, const char *data,
									size_t len, unsigned priority)
{
	if (data == NULL) {
		ucl_create_err(&parser->err, "invalid string added");
		return false;
	}
	if (len == 0) {
		len = strlen(data);
	}

	return ucl_parser_add_chunk_priority(parser,
										 (const unsigned char *) data, len, priority);
}

bool ucl_parser_add_string(struct ucl_parser *parser, const char *data,
						   size_t len)
{
	if (parser == NULL) {
		return false;
	}

	return ucl_parser_add_string_priority(parser,
										  (const unsigned char *) data, len, parser->default_priority);
}

bool ucl_set_include_path(struct ucl_parser *parser, ucl_object_t *paths)
{
	if (parser == NULL || paths == NULL || paths->type != UCL_ARRAY) {
		return false;
	}

	if (parser->includepaths == NULL) {
		parser->includepaths = ucl_object_copy(paths);
	}
	else {
		ucl_object_unref(parser->includepaths);
		parser->includepaths = ucl_object_copy(paths);
	}

	if (parser->includepaths == NULL) {
		return false;
	}

	return true;
}

unsigned char ucl_parser_chunk_peek(struct ucl_parser *parser)
{
	if (parser == NULL || parser->chunks == NULL || parser->chunks->pos == NULL || parser->chunks->end == NULL ||
		parser->chunks->pos == parser->chunks->end) {
		return 0;
	}

	return (*parser->chunks->pos);
}

bool ucl_parser_chunk_skip(struct ucl_parser *parser)
{
	if (parser == NULL || parser->chunks == NULL || parser->chunks->pos == NULL || parser->chunks->end == NULL ||
		parser->chunks->pos == parser->chunks->end) {
		return false;
	}

	const unsigned char *p = parser->chunks->pos;
	ucl_chunk_skipc(parser->chunks, p);
	if (parser->chunks->pos != NULL) return true;
	return false;
}

ucl_object_t *
ucl_parser_get_current_stack_object(struct ucl_parser *parser, unsigned int depth)
{
	ucl_object_t *obj;

	if (parser == NULL || parser->stack == NULL) {
		return NULL;
	}

	struct ucl_stack *stack = parser->stack;
	if (stack == NULL || stack->obj == NULL || ucl_object_type(stack->obj) != UCL_OBJECT) {
		return NULL;
	}

	for (unsigned int i = 0; i < depth; ++i) {
		stack = stack->next;
		if (stack == NULL || stack->obj == NULL || ucl_object_type(stack->obj) != UCL_OBJECT) {
			return NULL;
		}
	}

	obj = ucl_object_ref(stack->obj);
	return obj;
}
