1228 lines
27 KiB
C++
1228 lines
27 KiB
C++
#ifndef MINIJSON_READER_H
|
|
#define MINIJSON_READER_H
|
|
|
|
#include <cstdlib>
|
|
#include <cctype>
|
|
#include <stdint.h>
|
|
#include <climits>
|
|
#include <cstring>
|
|
#include <cerrno>
|
|
|
|
#include <vector>
|
|
#include <list>
|
|
#include <string>
|
|
#include <utility>
|
|
|
|
#include <stdexcept>
|
|
#include <istream>
|
|
|
|
#define MJR_CPP11_SUPPORTED __cplusplus > 199711L || _MSC_VER >= 1800
|
|
|
|
#if MJR_CPP11_SUPPORTED
|
|
|
|
#define MJR_FINAL final
|
|
|
|
#else
|
|
|
|
#define MJR_FINAL
|
|
|
|
#endif // MJR_CPP11_SUPPORTED
|
|
|
|
#ifndef MJR_NESTING_LIMIT
|
|
#define MJR_NESTING_LIMIT 32
|
|
#endif
|
|
|
|
#define MJR_STRINGIFY(S) MJR_STRINGIFY_HELPER(S)
|
|
#define MJR_STRINGIFY_HELPER(S) #S
|
|
|
|
namespace minijson
|
|
{
|
|
|
|
namespace detail
|
|
{
|
|
|
|
class noncopyable
|
|
{
|
|
private:
|
|
|
|
// C++03 idiom to prevent copy construction and copy assignment
|
|
noncopyable(const noncopyable&);
|
|
noncopyable& operator=(const noncopyable&);
|
|
|
|
public:
|
|
|
|
noncopyable()
|
|
{
|
|
}
|
|
}; // class noncopyable
|
|
|
|
class context_base : noncopyable
|
|
{
|
|
public:
|
|
|
|
enum context_nested_status
|
|
{
|
|
NESTED_STATUS_NONE,
|
|
NESTED_STATUS_OBJECT,
|
|
NESTED_STATUS_ARRAY
|
|
};
|
|
|
|
private:
|
|
|
|
context_nested_status m_nested_status;
|
|
size_t m_nesting_level;
|
|
|
|
public:
|
|
|
|
context_base() :
|
|
m_nested_status(NESTED_STATUS_NONE),
|
|
m_nesting_level(0)
|
|
{
|
|
}
|
|
|
|
char nested_status() const
|
|
{
|
|
return m_nested_status;
|
|
}
|
|
|
|
void begin_nested(context_nested_status nested_status)
|
|
{
|
|
m_nested_status = nested_status;
|
|
m_nesting_level++;
|
|
}
|
|
|
|
void reset_nested_status()
|
|
{
|
|
m_nested_status = NESTED_STATUS_NONE;
|
|
}
|
|
|
|
void end_nested()
|
|
{
|
|
if (m_nesting_level > 0)
|
|
{
|
|
m_nesting_level--;
|
|
}
|
|
}
|
|
|
|
size_t nesting_level() const
|
|
{
|
|
return m_nesting_level;
|
|
}
|
|
}; // class context_base
|
|
|
|
class buffer_context_base : public context_base
|
|
{
|
|
protected:
|
|
|
|
const char* m_read_buffer;
|
|
char* m_write_buffer;
|
|
size_t m_length;
|
|
size_t m_read_offset;
|
|
size_t m_write_offset;
|
|
const char* m_current_write_buffer;
|
|
|
|
explicit buffer_context_base(const char* read_buffer, char* write_buffer, size_t length) :
|
|
m_read_buffer(read_buffer),
|
|
m_write_buffer(write_buffer),
|
|
m_length(length),
|
|
m_read_offset(0),
|
|
m_write_offset(0),
|
|
m_current_write_buffer(NULL)
|
|
{
|
|
new_write_buffer();
|
|
}
|
|
|
|
public:
|
|
|
|
char read()
|
|
{
|
|
if (m_read_offset >= m_length)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return m_read_buffer[m_read_offset++];
|
|
}
|
|
|
|
size_t read_offset() const
|
|
{
|
|
return m_read_offset;
|
|
}
|
|
|
|
void new_write_buffer()
|
|
{
|
|
m_current_write_buffer = m_write_buffer + m_write_offset;
|
|
}
|
|
|
|
void write(char c)
|
|
{
|
|
if (m_write_offset >= m_read_offset)
|
|
{
|
|
throw std::runtime_error("Invalid write call, please file a bug report");
|
|
}
|
|
|
|
m_write_buffer[m_write_offset++] = c;
|
|
}
|
|
|
|
const char* write_buffer() const
|
|
{
|
|
return m_current_write_buffer;
|
|
}
|
|
}; // class buffer_context_base
|
|
|
|
} // namespace detail
|
|
|
|
class buffer_context MJR_FINAL : public detail::buffer_context_base
|
|
{
|
|
public:
|
|
|
|
explicit buffer_context(char* buffer, size_t length) :
|
|
detail::buffer_context_base(buffer, buffer, length)
|
|
{
|
|
}
|
|
}; // class buffer_context
|
|
|
|
class const_buffer_context MJR_FINAL : public detail::buffer_context_base
|
|
{
|
|
public:
|
|
|
|
explicit const_buffer_context(const char* buffer, size_t length) :
|
|
detail::buffer_context_base(buffer, new char[length], length) // don't worry about leaks, buffer_context_base can't throw
|
|
{
|
|
}
|
|
|
|
~const_buffer_context()
|
|
{
|
|
delete[] m_write_buffer;
|
|
}
|
|
}; // class const_buffer_context
|
|
|
|
class istream_context MJR_FINAL : public detail::context_base
|
|
{
|
|
private:
|
|
|
|
std::istream& m_stream;
|
|
size_t m_read_offset;
|
|
std::list<std::vector<char> > m_write_buffers;
|
|
|
|
public:
|
|
|
|
explicit istream_context(std::istream& stream) :
|
|
m_stream(stream),
|
|
m_read_offset(0)
|
|
{
|
|
new_write_buffer();
|
|
}
|
|
|
|
char read()
|
|
{
|
|
const char c = m_stream.get();
|
|
|
|
if (m_stream)
|
|
{
|
|
m_read_offset++;
|
|
|
|
return c;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
size_t read_offset() const
|
|
{
|
|
return m_read_offset;
|
|
}
|
|
|
|
void new_write_buffer()
|
|
{
|
|
m_write_buffers.push_back(std::vector<char>());
|
|
}
|
|
|
|
void write(char c)
|
|
{
|
|
m_write_buffers.back().push_back(c);
|
|
}
|
|
|
|
// This method to retrieve the address of the write buffer MUST be called
|
|
// AFTER all the calls to write() for the current write buffer have been performed
|
|
const char* write_buffer() const
|
|
{
|
|
return !m_write_buffers.back().empty() ? &m_write_buffers.back()[0] : NULL;
|
|
}
|
|
}; // class istream_context
|
|
|
|
class parse_error : public std::exception
|
|
{
|
|
public:
|
|
|
|
enum error_reason
|
|
{
|
|
UNKNOWN,
|
|
EXPECTED_OPENING_QUOTE,
|
|
EXPECTED_UTF16_LOW_SURROGATE,
|
|
INVALID_ESCAPE_SEQUENCE,
|
|
INVALID_UTF16_CHARACTER,
|
|
EXPECTED_CLOSING_QUOTE,
|
|
INVALID_VALUE,
|
|
UNTERMINATED_VALUE,
|
|
EXPECTED_OPENING_BRACKET,
|
|
EXPECTED_COLON,
|
|
EXPECTED_COMMA_OR_CLOSING_BRACKET,
|
|
NESTED_OBJECT_OR_ARRAY_NOT_PARSED,
|
|
EXCEEDED_NESTING_LIMIT
|
|
};
|
|
|
|
private:
|
|
|
|
size_t m_offset;
|
|
error_reason m_reason;
|
|
|
|
template<typename Context>
|
|
static size_t get_offset(const Context& context)
|
|
{
|
|
const size_t read_offset = context.read_offset();
|
|
|
|
return (read_offset != 0) ? (read_offset - 1) : 0;
|
|
}
|
|
|
|
public:
|
|
|
|
template<typename Context>
|
|
explicit parse_error(const Context& context, error_reason reason) :
|
|
m_offset(get_offset(context)),
|
|
m_reason(reason)
|
|
{
|
|
}
|
|
|
|
size_t offset() const
|
|
{
|
|
return m_offset;
|
|
}
|
|
|
|
error_reason reason() const
|
|
{
|
|
return m_reason;
|
|
}
|
|
|
|
const char* what() const throw()
|
|
{
|
|
switch (m_reason)
|
|
{
|
|
case UNKNOWN: return "Unknown parse error";
|
|
case EXPECTED_OPENING_QUOTE: return "Expected opening quote";
|
|
case EXPECTED_UTF16_LOW_SURROGATE: return "Expected UTF-16 low surrogate";
|
|
case INVALID_ESCAPE_SEQUENCE: return "Invalid escape sequence";
|
|
case INVALID_UTF16_CHARACTER: return "Invalid UTF-16 character";
|
|
case EXPECTED_CLOSING_QUOTE: return "Expected closing quote";
|
|
case INVALID_VALUE: return "Invalid value";
|
|
case UNTERMINATED_VALUE: return "Unterminated value";
|
|
case EXPECTED_OPENING_BRACKET: return "Expected opening bracket";
|
|
case EXPECTED_COLON: return "Expected colon";
|
|
case EXPECTED_COMMA_OR_CLOSING_BRACKET: return "Expected comma or closing bracket";
|
|
case NESTED_OBJECT_OR_ARRAY_NOT_PARSED: return "Nested object or array not parsed";
|
|
case EXCEEDED_NESTING_LIMIT: return "Exceeded nesting limit (" MJR_STRINGIFY(MJR_NESTING_LIMIT) ")";
|
|
}
|
|
|
|
return ""; // to suppress compiler warnings -- LCOV_EXCL_LINE
|
|
}
|
|
}; // class parse_error
|
|
|
|
namespace detail
|
|
{
|
|
|
|
struct utf8_char
|
|
{
|
|
uint8_t bytes[4];
|
|
|
|
utf8_char()
|
|
{
|
|
// wanted use value-initialization, but couldn't because of a weird VS2013 warning
|
|
std::fill_n(bytes, sizeof(bytes), 0);
|
|
}
|
|
|
|
explicit utf8_char(uint8_t b0, uint8_t b1, uint8_t b2, uint8_t b3)
|
|
{
|
|
bytes[0] = b0;
|
|
bytes[1] = b1;
|
|
bytes[2] = b2;
|
|
bytes[3] = b3;
|
|
}
|
|
|
|
uint8_t& operator[](size_t i)
|
|
{
|
|
return bytes[i];
|
|
}
|
|
|
|
const uint8_t& operator[](size_t i) const
|
|
{
|
|
return bytes[i];
|
|
}
|
|
|
|
bool operator==(const utf8_char& other) const
|
|
{
|
|
return std::equal(bytes, bytes + sizeof(bytes), other.bytes);
|
|
}
|
|
|
|
bool operator!=(const utf8_char& other) const
|
|
{
|
|
return !operator==(other);
|
|
}
|
|
}; // struct utf8_char
|
|
|
|
// this exception is not to be propagated outside minijson
|
|
struct encoding_error
|
|
{
|
|
};
|
|
|
|
inline uint32_t utf16_to_utf32(uint16_t high, uint16_t low)
|
|
{
|
|
uint32_t result;
|
|
|
|
if (high <= 0xD7FF || high >= 0xE000)
|
|
{
|
|
if (low != 0)
|
|
{
|
|
// since the high code unit is not a surrogate, the low code unit should be zero
|
|
throw encoding_error();
|
|
}
|
|
|
|
result = high;
|
|
}
|
|
else
|
|
{
|
|
if (high > 0xDBFF) // we already know high >= 0xD800
|
|
{
|
|
// the high surrogate is not within the expected range
|
|
throw encoding_error();
|
|
}
|
|
|
|
if (low < 0xDC00 || low > 0xDFFF)
|
|
{
|
|
// the low surrogate is not within the expected range
|
|
throw encoding_error();
|
|
}
|
|
|
|
high -= 0xD800;
|
|
low -= 0xDC00;
|
|
result = 0x010000 + ((high << 10) | low);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
inline utf8_char utf32_to_utf8(uint32_t utf32_char)
|
|
{
|
|
utf8_char result;
|
|
|
|
if (utf32_char <= 0x00007F)
|
|
{
|
|
result[0] = utf32_char;
|
|
}
|
|
else if (utf32_char <= 0x0007FF)
|
|
{
|
|
result[0] = 0xC0 | ((utf32_char & (0x1F << 6)) >> 6);
|
|
result[1] = 0x80 | ((utf32_char & (0x3F )) );
|
|
}
|
|
else if (utf32_char <= 0x00FFFF)
|
|
{
|
|
result[0] = 0xE0 | ((utf32_char & (0x0F << 12)) >> 12);
|
|
result[1] = 0x80 | ((utf32_char & (0x3F << 6)) >> 6);
|
|
result[2] = 0x80 | ((utf32_char & (0x3F )) );
|
|
}
|
|
else if (utf32_char <= 0x1FFFFF)
|
|
{
|
|
result[0] = 0xF0 | ((utf32_char & (0x07 << 18)) >> 18);
|
|
result[1] = 0x80 | ((utf32_char & (0x3F << 12)) >> 12);
|
|
result[2] = 0x80 | ((utf32_char & (0x3F << 6)) >> 6);
|
|
result[3] = 0x80 | ((utf32_char & (0x3F )) );
|
|
}
|
|
else
|
|
{
|
|
// invalid code unit
|
|
throw encoding_error();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
inline utf8_char utf16_to_utf8(uint16_t high, uint16_t low)
|
|
{
|
|
return utf32_to_utf8(utf16_to_utf32(high, low));
|
|
}
|
|
|
|
// this exception is not to be propagated outside minijson
|
|
struct number_parse_error
|
|
{
|
|
};
|
|
|
|
inline long parse_long(const char* str, int base = 10)
|
|
{
|
|
if ((str == NULL) || (*str == 0) || isspace(str[0])) // we don't accept empty strings or strings with leading spaces
|
|
{
|
|
throw number_parse_error();
|
|
}
|
|
|
|
int saved_errno = errno; // save errno
|
|
errno = 0; // reset errno
|
|
|
|
char* endptr;
|
|
const long result = std::strtol(str, &endptr, base);
|
|
|
|
std::swap(saved_errno, errno); // restore errno
|
|
|
|
if (*endptr != 0) // we didn't consume the whole string
|
|
{
|
|
throw number_parse_error();
|
|
}
|
|
else if ((saved_errno == ERANGE) && ((result == LONG_MIN) || (result == LONG_MAX))) // overflow
|
|
{
|
|
throw number_parse_error();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
inline double parse_double(const char* str)
|
|
{
|
|
if ((str == NULL) || (*str == 0)) // we don't accept empty strings
|
|
{
|
|
throw number_parse_error();
|
|
}
|
|
|
|
// we perform this check to reject hex numbers (supported in C++11) and string with leading spaces
|
|
for (const char* c = str; *c != 0; c++)
|
|
{
|
|
if (!(isdigit(*c) || (*c == '+') || (*c == '-') || (*c == '.') || (*c == 'e') || (*c == 'E')))
|
|
{
|
|
throw number_parse_error();
|
|
}
|
|
}
|
|
|
|
int saved_errno = errno; // save errno
|
|
errno = 0; // reset errno
|
|
|
|
char* endptr;
|
|
const double result = std::strtod(str, &endptr);
|
|
|
|
std::swap(saved_errno, errno); // restore errno
|
|
|
|
if (*endptr != 0) // we didn't consume the whole string
|
|
{
|
|
throw number_parse_error();
|
|
}
|
|
else if (saved_errno == ERANGE) // underflow or overflow
|
|
{
|
|
throw number_parse_error();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static const size_t UTF16_ESCAPE_SEQ_LENGTH = 4;
|
|
|
|
inline uint16_t parse_utf16_escape_sequence(const char* seq)
|
|
{
|
|
for (size_t i = 0; i < UTF16_ESCAPE_SEQ_LENGTH; i++)
|
|
{
|
|
if (!isxdigit(seq[i]))
|
|
{
|
|
throw encoding_error();
|
|
}
|
|
}
|
|
|
|
return static_cast<uint16_t>(parse_long(seq, 16));
|
|
}
|
|
|
|
template<typename Context>
|
|
void write_utf8_char(Context& context, const utf8_char& c)
|
|
{
|
|
for (size_t i = 0; i < sizeof(c.bytes); i++)
|
|
{
|
|
const char byte = c[i];
|
|
if ((i > 0) && (byte == 0))
|
|
{
|
|
break;
|
|
}
|
|
|
|
context.write(byte);
|
|
}
|
|
}
|
|
|
|
template<typename Context>
|
|
void read_quoted_string(Context& context, bool skip_opening_quote = false)
|
|
{
|
|
enum
|
|
{
|
|
OPENING_QUOTE,
|
|
CHARACTER,
|
|
ESCAPE_SEQUENCE,
|
|
UTF16_SEQUENCE,
|
|
CLOSED
|
|
} state = (skip_opening_quote) ? CHARACTER : OPENING_QUOTE;
|
|
|
|
bool empty = true;
|
|
char utf16_seq[UTF16_ESCAPE_SEQ_LENGTH + 1] = { 0 };
|
|
size_t utf16_seq_offset = 0;
|
|
uint16_t high_surrogate = 0;
|
|
|
|
char c;
|
|
|
|
while ((state != CLOSED) && ((c = context.read()) != 0))
|
|
{
|
|
empty = false;
|
|
|
|
switch (state)
|
|
{
|
|
case OPENING_QUOTE:
|
|
|
|
if (c != '"')
|
|
{
|
|
throw parse_error(context, parse_error::EXPECTED_OPENING_QUOTE);
|
|
}
|
|
state = CHARACTER;
|
|
|
|
break;
|
|
|
|
case CHARACTER:
|
|
|
|
if (c == '\\')
|
|
{
|
|
state = ESCAPE_SEQUENCE;
|
|
}
|
|
else if (high_surrogate != 0)
|
|
{
|
|
throw parse_error(context, parse_error::EXPECTED_UTF16_LOW_SURROGATE);
|
|
}
|
|
else if (c == '"')
|
|
{
|
|
state = CLOSED;
|
|
}
|
|
else
|
|
{
|
|
context.write(c);
|
|
}
|
|
|
|
break;
|
|
|
|
case ESCAPE_SEQUENCE:
|
|
|
|
state = CHARACTER;
|
|
|
|
switch (c)
|
|
{
|
|
case '"': context.write('"'); break;
|
|
case '\\': context.write('\\'); break;
|
|
case '/': context.write('/'); break;
|
|
case 'b': context.write('\b'); break;
|
|
case 'f': context.write('\f'); break;
|
|
case 'n': context.write('\n'); break;
|
|
case 'r': context.write('\r'); break;
|
|
case 't': context.write('\t'); break;
|
|
case 'u': state = UTF16_SEQUENCE; break;
|
|
default: throw parse_error(context, parse_error::INVALID_ESCAPE_SEQUENCE);
|
|
}
|
|
|
|
break;
|
|
|
|
case UTF16_SEQUENCE:
|
|
|
|
utf16_seq[utf16_seq_offset++] = c;
|
|
|
|
if (utf16_seq_offset == sizeof(utf16_seq) - 1)
|
|
{
|
|
try
|
|
{
|
|
const uint16_t code_unit = parse_utf16_escape_sequence(utf16_seq);
|
|
|
|
if (high_surrogate != 0)
|
|
{
|
|
// we were waiting for the low surrogate (that now is code_unit)
|
|
write_utf8_char(context, utf16_to_utf8(high_surrogate, code_unit));
|
|
high_surrogate = 0;
|
|
}
|
|
else if (code_unit >= 0xD800 && code_unit <= 0xDBFF)
|
|
{
|
|
high_surrogate = code_unit;
|
|
}
|
|
else
|
|
{
|
|
write_utf8_char(context, utf16_to_utf8(code_unit, 0));
|
|
}
|
|
}
|
|
catch (const encoding_error&)
|
|
{
|
|
throw parse_error(context, parse_error::INVALID_UTF16_CHARACTER);
|
|
}
|
|
|
|
utf16_seq_offset = 0;
|
|
|
|
state = CHARACTER;
|
|
}
|
|
|
|
break;
|
|
|
|
case CLOSED: // to silence compiler warnings
|
|
|
|
throw std::runtime_error("This line should never be reached, please file a bug report"); // LCOV_EXCL_LINE
|
|
}
|
|
}
|
|
|
|
if (empty && !skip_opening_quote)
|
|
{
|
|
throw parse_error(context, parse_error::EXPECTED_OPENING_QUOTE);
|
|
}
|
|
else if (state != CLOSED)
|
|
{
|
|
throw parse_error(context, parse_error::EXPECTED_CLOSING_QUOTE);
|
|
}
|
|
|
|
context.write(0);
|
|
}
|
|
|
|
// reads any value that is not a string (or an object/array)
|
|
template<typename Context>
|
|
char read_unquoted_value(Context& context, char first_char = 0)
|
|
{
|
|
if (first_char != 0)
|
|
{
|
|
context.write(first_char);
|
|
}
|
|
|
|
char c;
|
|
|
|
while (((c = context.read()) != 0) && (c != ',') && (c != '}') && (c != ']') && !isspace(c))
|
|
{
|
|
context.write(c);
|
|
}
|
|
|
|
if (c == 0)
|
|
{
|
|
throw parse_error(context, parse_error::UNTERMINATED_VALUE);
|
|
}
|
|
|
|
context.write(0);
|
|
|
|
return c; // return the termination character (or it will be lost forever)
|
|
}
|
|
|
|
} // namespace detail
|
|
|
|
enum value_type
|
|
{
|
|
String,
|
|
Number,
|
|
Boolean,
|
|
Object,
|
|
Array,
|
|
Null
|
|
};
|
|
|
|
class value MJR_FINAL
|
|
{
|
|
private:
|
|
|
|
value_type m_type;
|
|
const char* m_buffer;
|
|
long m_long_value;
|
|
double m_double_value;
|
|
|
|
public:
|
|
|
|
explicit value(value_type type = Null, const char* buffer = "", long long_value = 0, double double_value = 0.0) :
|
|
m_type(type),
|
|
m_buffer(buffer),
|
|
m_long_value(long_value),
|
|
m_double_value(double_value)
|
|
{
|
|
}
|
|
|
|
value_type type() const
|
|
{
|
|
return m_type;
|
|
}
|
|
|
|
const char* as_string() const
|
|
{
|
|
return m_buffer;
|
|
}
|
|
|
|
long as_long() const
|
|
{
|
|
return m_long_value;
|
|
}
|
|
|
|
bool as_bool() const
|
|
{
|
|
return (m_long_value) ? true : false; // to avoid VS2013 warnings
|
|
}
|
|
|
|
double as_double() const
|
|
{
|
|
return m_double_value;
|
|
}
|
|
}; // class value
|
|
|
|
namespace detail
|
|
{
|
|
|
|
template<typename Context>
|
|
value parse_unquoted_value(const Context& context)
|
|
{
|
|
const char* const buffer = context.write_buffer();
|
|
|
|
if (strcmp(buffer, "true") == 0)
|
|
{
|
|
return value(Boolean, buffer, 1, 1.0);
|
|
}
|
|
else if (strcmp(buffer, "false") == 0)
|
|
{
|
|
return value(Boolean, buffer, 0, 0.0);
|
|
}
|
|
else if (strcmp(buffer, "null") == 0)
|
|
{
|
|
return value(Null, buffer, 0, 0.0);
|
|
}
|
|
else
|
|
{
|
|
long long_value = 0;
|
|
double double_value = 0.0;
|
|
|
|
try
|
|
{
|
|
long_value = parse_long(buffer);
|
|
double_value = long_value;
|
|
}
|
|
catch (const number_parse_error&)
|
|
{
|
|
try
|
|
{
|
|
double_value = parse_double(buffer);
|
|
}
|
|
catch (const number_parse_error&)
|
|
{
|
|
throw parse_error(context, parse_error::INVALID_VALUE);
|
|
}
|
|
}
|
|
|
|
return value(Number, buffer, long_value, double_value);
|
|
}
|
|
}
|
|
|
|
template<typename Context>
|
|
std::pair<value, char> read_value(Context& context, char first_char)
|
|
{
|
|
if (first_char == '{')
|
|
{
|
|
return std::make_pair(value(Object), 0);
|
|
}
|
|
else if (first_char == '[')
|
|
{
|
|
return std::make_pair(value(Array), 0);
|
|
}
|
|
else if (first_char == '"') // quoted string
|
|
{
|
|
context.new_write_buffer();
|
|
read_quoted_string(context, true);
|
|
|
|
return std::make_pair(value(String, context.write_buffer()), 0);
|
|
}
|
|
else // unquoted value
|
|
{
|
|
context.new_write_buffer();
|
|
const char ending_char = read_unquoted_value(context, first_char);
|
|
|
|
return std::make_pair(parse_unquoted_value(context), ending_char);
|
|
}
|
|
}
|
|
|
|
template<typename Context>
|
|
void parse_init_helper(const Context& context, char& c, bool& must_read)
|
|
{
|
|
switch (context.nested_status())
|
|
{
|
|
case Context::NESTED_STATUS_NONE:
|
|
c = 0;
|
|
must_read = true;
|
|
break;
|
|
case Context::NESTED_STATUS_OBJECT:
|
|
c = '{';
|
|
must_read = false;
|
|
break;
|
|
case Context::NESTED_STATUS_ARRAY:
|
|
c = '[';
|
|
must_read = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
template<typename Context>
|
|
value parse_value_helper(Context& context, char& c, bool& must_read)
|
|
{
|
|
const std::pair<value, char> read_value_result = detail::read_value(context, c);
|
|
const value v = read_value_result.first;
|
|
|
|
if (v.type() == Object)
|
|
{
|
|
context.begin_nested(Context::NESTED_STATUS_OBJECT);
|
|
}
|
|
else if (v.type() == Array)
|
|
{
|
|
context.begin_nested(Context::NESTED_STATUS_ARRAY);
|
|
}
|
|
else if (v.type() != String)
|
|
{
|
|
c = read_value_result.second;
|
|
must_read = false;
|
|
}
|
|
|
|
return v;
|
|
}
|
|
|
|
} // namespace detail
|
|
|
|
template<typename Context, typename Handler>
|
|
void parse_object(Context& context, Handler handler)
|
|
{
|
|
const size_t nesting_level = context.nesting_level();
|
|
if (nesting_level > MJR_NESTING_LIMIT)
|
|
{
|
|
throw parse_error(context, parse_error::EXCEEDED_NESTING_LIMIT);
|
|
}
|
|
|
|
char c = 0;
|
|
bool must_read = false;
|
|
|
|
parse_init_helper(context, c, must_read);
|
|
context.reset_nested_status();
|
|
|
|
enum
|
|
{
|
|
OPENING_BRACKET,
|
|
FIELD_NAME_OR_CLOSING_BRACKET, // in case the object is empty
|
|
FIELD_NAME,
|
|
COLON,
|
|
FIELD_VALUE,
|
|
COMMA_OR_CLOSING_BRACKET,
|
|
END
|
|
} state = OPENING_BRACKET;
|
|
|
|
const char* field_name = "";
|
|
|
|
while (state != END)
|
|
{
|
|
if (context.nesting_level() != nesting_level)
|
|
{
|
|
throw parse_error(context, parse_error::NESTED_OBJECT_OR_ARRAY_NOT_PARSED);
|
|
}
|
|
|
|
if (must_read)
|
|
{
|
|
c = context.read();
|
|
}
|
|
|
|
must_read = true;
|
|
|
|
if (isspace(c)) // skip whitespace
|
|
{
|
|
continue;
|
|
}
|
|
|
|
switch (state)
|
|
{
|
|
case OPENING_BRACKET:
|
|
if (c != '{')
|
|
{
|
|
throw parse_error(context, parse_error::EXPECTED_OPENING_BRACKET);
|
|
}
|
|
state = FIELD_NAME_OR_CLOSING_BRACKET;
|
|
break;
|
|
|
|
case FIELD_NAME_OR_CLOSING_BRACKET:
|
|
if (c == '}')
|
|
{
|
|
state = END;
|
|
break;
|
|
}
|
|
// intentional fall-through
|
|
|
|
case FIELD_NAME:
|
|
if (c != '"')
|
|
{
|
|
throw parse_error(context, parse_error::EXPECTED_OPENING_QUOTE);
|
|
}
|
|
context.new_write_buffer();
|
|
detail::read_quoted_string(context, true);
|
|
field_name = context.write_buffer();
|
|
state = COLON;
|
|
break;
|
|
|
|
case COLON:
|
|
if (c != ':')
|
|
{
|
|
throw parse_error(context, parse_error::EXPECTED_COLON);
|
|
}
|
|
state = FIELD_VALUE;
|
|
break;
|
|
|
|
case FIELD_VALUE:
|
|
handler(field_name, parse_value_helper(context, c, must_read));
|
|
state = COMMA_OR_CLOSING_BRACKET;
|
|
break;
|
|
|
|
case COMMA_OR_CLOSING_BRACKET:
|
|
if (c == ',')
|
|
{
|
|
state = FIELD_NAME;
|
|
}
|
|
else if (c == '}')
|
|
{
|
|
state = END;
|
|
}
|
|
else
|
|
{
|
|
throw parse_error(context, parse_error::EXPECTED_COMMA_OR_CLOSING_BRACKET);
|
|
}
|
|
break;
|
|
|
|
case END:
|
|
|
|
throw std::runtime_error("This line should never be reached, please file a bug report"); // LCOV_EXCL_LINE
|
|
}
|
|
|
|
if (c == 0)
|
|
{
|
|
throw std::runtime_error("This line should never be reached, please file a bug report"); // LCOV_EXCL_LINE
|
|
}
|
|
}
|
|
|
|
context.end_nested();
|
|
}
|
|
|
|
template<typename Context, typename Handler>
|
|
void parse_array(Context& context, Handler handler)
|
|
{
|
|
const size_t nesting_level = context.nesting_level();
|
|
if (nesting_level > MJR_NESTING_LIMIT)
|
|
{
|
|
throw parse_error(context, parse_error::EXCEEDED_NESTING_LIMIT);
|
|
}
|
|
|
|
char c = 0;
|
|
bool must_read = false;
|
|
|
|
parse_init_helper(context, c, must_read);
|
|
context.reset_nested_status();
|
|
|
|
enum
|
|
{
|
|
OPENING_BRACKET,
|
|
VALUE_OR_CLOSING_BRACKET, // in case the array is empty
|
|
VALUE,
|
|
COMMA_OR_CLOSING_BRACKET,
|
|
END
|
|
} state = OPENING_BRACKET;
|
|
|
|
while (state != END)
|
|
{
|
|
if (context.nesting_level() != nesting_level)
|
|
{
|
|
throw parse_error(context, parse_error::NESTED_OBJECT_OR_ARRAY_NOT_PARSED);
|
|
}
|
|
|
|
if (must_read)
|
|
{
|
|
c = context.read();
|
|
}
|
|
|
|
must_read = true;
|
|
|
|
if (isspace(c)) // skip whitespace
|
|
{
|
|
continue;
|
|
}
|
|
|
|
switch (state)
|
|
{
|
|
case OPENING_BRACKET:
|
|
if (c != '[')
|
|
{
|
|
throw parse_error(context, parse_error::EXPECTED_OPENING_BRACKET);
|
|
}
|
|
state = VALUE_OR_CLOSING_BRACKET;
|
|
break;
|
|
|
|
case VALUE_OR_CLOSING_BRACKET:
|
|
if (c == ']')
|
|
{
|
|
state = END;
|
|
break;
|
|
}
|
|
// intentional fall-through
|
|
|
|
case VALUE:
|
|
handler(parse_value_helper(context, c, must_read));
|
|
state = COMMA_OR_CLOSING_BRACKET;
|
|
break;
|
|
|
|
case COMMA_OR_CLOSING_BRACKET:
|
|
if (c == ',')
|
|
{
|
|
state = VALUE;
|
|
}
|
|
else if (c == ']')
|
|
{
|
|
state = END;
|
|
}
|
|
else
|
|
{
|
|
throw parse_error(context, parse_error::EXPECTED_COMMA_OR_CLOSING_BRACKET);
|
|
}
|
|
break;
|
|
|
|
case END:
|
|
|
|
throw std::runtime_error("This line should never be reached, please file a bug report"); // LCOV_EXCL_LINE
|
|
}
|
|
|
|
if (c == 0)
|
|
{
|
|
throw std::runtime_error("This line should never be reached, please file a bug report"); // LCOV_EXCL_LINE
|
|
}
|
|
}
|
|
|
|
context.end_nested();
|
|
}
|
|
|
|
namespace detail
|
|
{
|
|
|
|
class dispatch_rule; // forward declaration
|
|
|
|
} // namespace detail
|
|
|
|
class dispatch : detail::noncopyable
|
|
{
|
|
friend class detail::dispatch_rule;
|
|
|
|
private:
|
|
|
|
const char* m_field_name;
|
|
bool m_handled;
|
|
|
|
public:
|
|
|
|
explicit dispatch(const char* field_name) :
|
|
m_field_name(field_name),
|
|
m_handled(false)
|
|
{
|
|
}
|
|
|
|
explicit dispatch(const std::string& field_name) :
|
|
m_field_name(field_name.c_str()),
|
|
m_handled(false)
|
|
{
|
|
}
|
|
|
|
detail::dispatch_rule operator<<(const char* field_name);
|
|
detail::dispatch_rule operator<<(const std::string& field_name);
|
|
}; // class dispatch
|
|
|
|
namespace detail
|
|
{
|
|
|
|
class dispatch_rule
|
|
{
|
|
private:
|
|
|
|
dispatch& m_dispatch;
|
|
const char* m_field_name;
|
|
|
|
public:
|
|
|
|
explicit dispatch_rule(dispatch& parent_dispatch, const char* field_name) :
|
|
m_dispatch(parent_dispatch),
|
|
m_field_name(field_name)
|
|
{
|
|
}
|
|
|
|
template<typename Handler>
|
|
dispatch& operator>>(Handler handler) const
|
|
{
|
|
if (!m_dispatch.m_handled && ((m_field_name == NULL) || (strcmp(m_dispatch.m_field_name, m_field_name) == 0)))
|
|
{
|
|
handler();
|
|
m_dispatch.m_handled = true;
|
|
}
|
|
|
|
return m_dispatch;
|
|
}
|
|
}; // class dispatch_rule
|
|
|
|
template<typename Context>
|
|
class ignore
|
|
{
|
|
Context& m_context;
|
|
|
|
public:
|
|
|
|
explicit ignore(Context& context) :
|
|
m_context(context)
|
|
{
|
|
}
|
|
|
|
void operator()(const char*, value)
|
|
{
|
|
operator()();
|
|
}
|
|
|
|
void operator()(value)
|
|
{
|
|
operator()();
|
|
}
|
|
|
|
void operator()() const
|
|
{
|
|
switch (m_context.nested_status())
|
|
{
|
|
case Context::NESTED_STATUS_NONE:
|
|
break;
|
|
case Context::NESTED_STATUS_OBJECT:
|
|
parse_object(m_context, *this);
|
|
break;
|
|
case Context::NESTED_STATUS_ARRAY:
|
|
parse_array(m_context, *this);
|
|
break;
|
|
}
|
|
}
|
|
}; // class ignore
|
|
|
|
} // namespace detail
|
|
|
|
inline detail::dispatch_rule dispatch::operator<<(const char* field_name)
|
|
{
|
|
return detail::dispatch_rule(*this, field_name);
|
|
}
|
|
|
|
inline detail::dispatch_rule dispatch::operator<<(const std::string& field_name)
|
|
{
|
|
return operator<<(field_name.c_str());
|
|
}
|
|
|
|
static const char* const any = NULL;
|
|
|
|
template<typename Context>
|
|
void ignore(Context& context)
|
|
{
|
|
detail::ignore<Context> ignore(context);
|
|
ignore();
|
|
}
|
|
|
|
} // namespace minijson
|
|
|
|
#endif // MINIJSON_READER_H
|
|
|
|
#undef MJR_STRINGIFY
|
|
#undef MJR_STRINGIFY_HELPER
|