libcruft-util/json2/personality/base.cpp

333 lines
8.9 KiB
C++

/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2017 Danny Robson <danny@nerdcruft.net>
*/
#include "./base.hpp"
#include "../event.hpp"
#include "../except.hpp"
#include "../../debug.hpp"
#include "./rfc7519.hpp"
#include "./jsonish.hpp"
using util::json2::personality::base;
///////////////////////////////////////////////////////////////////////////////
static const char*
expect [[nodiscard]] (const char *first, const char *last, const char value)
{
if (first == last || *first != value)
throw util::json2::parse_error {first};
return first + 1;
}
///////////////////////////////////////////////////////////////////////////////
template <typename ParentT>
const char*
base<ParentT>::consume_whitespace (const char *first, const char *last) noexcept
{
auto cursor = first;
while (cursor != last) {
switch (*cursor) {
case 0x20:
case 0x09:
case 0x0A:
case 0x0D:
++cursor;
continue;
}
break;
}
return cursor;
}
///////////////////////////////////////////////////////////////////////////////
template <typename ParentT>
const char*
base<ParentT>::parse_number (const std::function<util::json2::callback_t> &cb,
const char *first,
const char *last)
{
(void)last;
// number: minus? int frac? exp?
auto cursor = first;
// minus: '-'
if (*cursor == '-')
++cursor;
// int: '0' | [1-9] DIGIT*
switch (*cursor) {
case '1'...'9':
{
++cursor;
while ('0' <= *cursor && *cursor <= '9')
++cursor;
break;
}
case '0':
// leading zero means we _must_ be parsing a fractional value so we
// look ahead to ensure we're about to do so. note that we don't use
// `expect' here because it implies consumption of '.'
++cursor;
if (*cursor != '.')
throw util::json2::parse_error { cursor };
break;
default:
throw util::json2::parse_error { cursor };
}
// frac: '.' digit+
if (*cursor == '.') {
++cursor;
auto frac_start = cursor;
while ('0' <= *cursor && *cursor <= '9')
++cursor;
if (frac_start == cursor)
throw util::json2::parse_error { cursor };
}
// exp: [eE] [-+]? digit+
if (*cursor == 'e' || *cursor == 'E') {
++cursor;
if (*cursor == '-' || *cursor == '+')
++cursor;
auto exp_digits = cursor;
while ('0' <= *cursor && *cursor <= '9')
++cursor;
if (exp_digits == cursor)
throw util::json2::parse_error { cursor };
}
cb ({ first, cursor });
return cursor;
}
//-----------------------------------------------------------------------------
template <typename ParentT>
template <int N>
const char*
base<ParentT>::parse_literal (const std::function<util::json2::callback_t> &cb,
const char *first,
const char *last,
const char (&value)[N])
{
CHECK_LE (first, last);
if (last - first < N - 1)
throw util::json2::overrun_error { first };
if (!std::equal (first, first + N - 1, value))
throw util::json2::parse_error { first };
cb ({ first, first + N - 1 });
return first + N - 1;
}
//-----------------------------------------------------------------------------
template <typename ParentT>
const char*
base<ParentT>::parse_string (const std::function<util::json2::callback_t> &cb,
const char *first,
const char *last)
{
CHECK_LE (first, last);
auto cursor = first;
cursor = expect (cursor, last, '"');
for ( ; cursor != last && *cursor != '"'; ) {
// advance the simple case first; unescaped character
if (*cursor++ != '\\') {
continue;
}
if (*cursor++ == 'u') {
for (int i = 0; i < 4; ++i) {
switch (*cursor) {
case 'a'...'f':
case 'A'...'F':
++cursor;
continue;
default:
throw util::json2::parse_error { cursor };
}
}
}
}
cursor = expect (cursor, last, '"');
cb ({ first, cursor });
return cursor;
}
//-----------------------------------------------------------------------------
template <typename ParentT>
const char*
base<ParentT>::parse_array (const std::function<util::json2::callback_t> &cb,
const char *first,
const char *last)
{
CHECK_LE (first, last);
auto cursor = first;
if (*cursor != '[')
throw util::json2::parse_error {cursor};
cb ({ cursor, cursor + 1 });
++cursor;
cursor = ParentT::consume_whitespace (cursor, last);
if (*cursor == ']') {
cb ({cursor, cursor + 1});
return ++cursor;
}
cursor = ParentT::parse_value (cb, cursor, last);
if (*cursor == ']') {
cb ({cursor, cursor + 1});
return ++cursor;
}
do {
cursor = ParentT::consume_whitespace (cursor, last);
cursor = expect (cursor, last, ',');
cursor = ParentT::consume_whitespace (cursor, last);
cursor = ParentT::parse_value (cb, cursor, last);
} while (*cursor != ']');
cb ({cursor, cursor + 1});
++cursor;
return cursor;
}
//-----------------------------------------------------------------------------
template <typename ParentT>
const char*
base<ParentT>::parse_object (const std::function<util::json2::callback_t> &cb,
const char *first,
const char *last)
{
CHECK_LE (first, last);
auto cursor = first;
cursor = expect (cursor, last, '{');
cb ({ cursor - 1, cursor });
cursor = ParentT::consume_whitespace (cursor, last);
if (*cursor == '}') {
cb ({cursor, cursor + 1});
return ++cursor;
};
auto parse_member = [] (auto _cb, auto _cursor, auto _last) {
_cursor = ParentT::parse_key (_cb, _cursor, _last);
_cursor = ParentT::consume_whitespace (_cursor, _last);
_cursor = expect (_cursor, _last, ':');
_cursor = ParentT::consume_whitespace (_cursor, _last);
_cursor = ParentT::parse_value (_cb, _cursor, _last);
_cursor = ParentT::consume_whitespace (_cursor, _last);
return _cursor;
};
cursor = parse_member (cb, cursor, last);
if (*cursor == '}') {
cb ({cursor, cursor + 1});
return ++cursor;
}
do {
cursor = expect (cursor, last, ',');
cursor = ParentT::consume_whitespace (cursor, last);
cursor = parse_member (cb, cursor, last);
} while (*cursor != '}');
cursor = expect (cursor, last, '}');
cb ({cursor - 1, cursor});
return cursor;
}
///////////////////////////////////////////////////////////////////////////////
template <typename ParentT>
const char*
base<ParentT>::parse_value (const std::function<util::json2::callback_t> &cb,
const char *first,
const char *last)
{
switch (*first) {
case '+':
case '-':
case '0'...'9':
return ParentT::parse_number (cb, first, last);
case '"':
return ParentT::parse_string (cb, first, last);
case 't': return ParentT::parse_literal (cb, first, last, "true");
case 'f': return ParentT::parse_literal (cb, first, last, "false");
case 'n': return ParentT::parse_literal (cb, first, last, "null");
case '[': return ParentT::parse_array (cb, first, last);
case '{': return ParentT::parse_object (cb, first, last);
}
return ParentT::parse_unknown (cb, first, last);
}
///////////////////////////////////////////////////////////////////////////////
template <typename ParentT>
const char*
base<ParentT>::parse_unknown (const std::function<util::json2::callback_t>&,
const char *first,
const char *last)
{
(void)last;
throw parse_error {first};
};
//-----------------------------------------------------------------------------
#define INSTANTIATE(KLASS) template struct util::json2::personality::base<KLASS>;
MAP_JSON2_PERSONALITY_TYPES (INSTANTIATE)