json2: initial import

This commit is contained in:
Danny Robson 2017-10-03 17:49:08 +11:00
parent d64e3d244e
commit 340981dac2
14 changed files with 1159 additions and 0 deletions

View File

@ -318,6 +318,18 @@ list (
json/schema.hpp json/schema.hpp
json/tree.cpp json/tree.cpp
json/tree.hpp json/tree.hpp
json2/fwd.hpp
json2/event.hpp
json2/event.cpp
json2/except.hpp
json2/personality/base.cpp
json2/personality/base.hpp
json2/personality/jsonish.cpp
json2/personality/jsonish.hpp
json2/personality/rfc7519.cpp
json2/personality/rfc7519.hpp
json2/tree.cpp
json2/tree.hpp
library.hpp library.hpp
log.cpp log.cpp
log.hpp log.hpp
@ -509,6 +521,7 @@ if (TESTS)
iterator iterator
job/queue job/queue
json_types json_types
json2/event
maths maths
matrix matrix
memory/deleter memory/deleter

90
json2/event.cpp Normal file
View File

@ -0,0 +1,90 @@
/*
* 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 "./event.hpp"
#include "./except.hpp"
#include "../debug.hpp"
using util::json2::event::packet;
///////////////////////////////////////////////////////////////////////////////
util::json2::event::type_t
packet::type (void) const noexcept
{
CHECK_NEQ (first, last);
const auto &c = *first;
switch (c) {
case '{': return type_t::OBJECT_BEGIN;
case '}': return type_t::OBJECT_END;
case '[': return type_t::ARRAY_BEGIN;
case ']': return type_t::ARRAY_END;
case '"': return type_t::STRING;
case 'n': return type_t::NONE;
case 't':
case 'f':
return type_t::BOOLEAN;
case '-':
case '0'...'9':
return type_t::NUMBER;
}
unhandled (c);
}
///////////////////////////////////////////////////////////////////////////////
#include "./personality/rfc7519.hpp"
#include "./personality/jsonish.hpp"
template <typename PersonalityT>
const char*
util::json2::event::parse (const std::function<util::json2::callback_t> &cb,
const char *first,
const char *last)
{
auto cursor = first;
PersonalityT p {};
cursor = p.consume_whitespace (cursor, last);
cursor = p.parse_value (cb, cursor, last);
cursor = p.consume_whitespace (cursor, last);
return cursor;
}
//-----------------------------------------------------------------------------
template
const char* util::json2::event::parse<util::json2::personality::rfc7159> (
const std::function<util::json2::callback_t> &,
const char*,
const char*
);
//-----------------------------------------------------------------------------
template
const char* util::json2::event::parse<util::json2::personality::jsonish> (
const std::function<util::json2::callback_t> &,
const char*,
const char*
);

55
json2/event.hpp Normal file
View File

@ -0,0 +1,55 @@
/*
* 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>
*/
#ifndef CRUFT_JSON2_EVENT_HPP
#define CRUFT_JSON2_EVENT_HPP
#include "./fwd.hpp"
#include <functional>
namespace util::json2::event {
// It is important that scalars come after compound types because it
// simplifies categorisation as we can use a simple '>' to classify the
// types.
//
// the value of the enumerants isn't important, but they might make it
// fractionally easier to visualise in a debugger on some occasions.
enum class type_t {
OBJECT_BEGIN = '{',
OBJECT_END = '}',
ARRAY_BEGIN = '[',
ARRAY_END = ']',
STRING = '"',
NUMBER = '1',
BOOLEAN = 't',
NONE = 'n',
};
struct packet {
type_t type (void) const noexcept;
const char *first;
const char *last;
};
template <typename PersonalityT = personality::rfc7159>
const char*
parse (const std::function<callback_t>&, const char *first, const char *last);
};
#endif

40
json2/except.hpp Normal file
View File

@ -0,0 +1,40 @@
/*
* 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>
*/
#ifndef CRUFT_UTIL_JSON2_EXCEPT_HPP
#define CRUFT_UTIL_JSON2_EXCEPT_HPP
#include <exception>
namespace util::json2 {
struct error : public std::exception {};
struct parse_error : public error {
parse_error (const char *_position):
position (_position)
{ ; }
const char *position;
};
struct overrun_error : public parse_error {
using parse_error::parse_error;
};
}
#endif

47
json2/fwd.hpp Normal file
View File

@ -0,0 +1,47 @@
/*
* 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>
*/
#ifndef CRUFT_UTIL_JSON2_FWD_HPP
#define CRUFT_UTIL_JSON2_FWD_HPP
#include "../preprocessor.hpp"
namespace util::json2 {
namespace personality {
template <typename> struct base;
struct rfc7159;
struct jsonish;
#define MAP_JSON2_PERSONALITY_TYPES(FUNC) \
MAP0(FUNC, \
util::json2::personality::rfc7159,\
util::json2::personality::jsonish)
}
namespace event {
enum class type_t;
struct packet;
}
using callback_t = void(const event::packet&);
struct error;
struct parse_error;
struct overrun_error;
}
#endif

319
json2/personality/base.cpp Normal file
View File

@ -0,0 +1,319 @@
/*
* 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 = parse_string (_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 '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);
}
throw util::json2::parse_error (first);
}
//-----------------------------------------------------------------------------
#define INSTANTIATE(KLASS) template struct util::json2::personality::base<KLASS>;
MAP_JSON2_PERSONALITY_TYPES (INSTANTIATE)

View File

@ -0,0 +1,83 @@
/*
* 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>
*/
#ifndef CRUFT_UTIL_JSON2_PERSONALITY_BASE_HPP
#define CRUFT_UTIL_JSON2_PERSONALITY_BASE_HPP
#include "../fwd.hpp"
#include <functional>
namespace util::json2::personality {
template <typename T>
struct base {
static const char*
consume_whitespace [[nodiscard]] (const char *first, const char *last) noexcept;
static const char*
parse_number [[nodiscard]] (
const std::function<callback_t>&,
const char *first,
const char *last
);
template <int N>
static const char*
parse_literal [[nodiscard]] (
const std::function<callback_t>&,
const char *first,
const char *last,
const char (&value)[N]
);
static const char*
parse_string [[nodiscard]] (
const std::function<callback_t>&,
const char *first,
const char *last
);
static const char*
parse_array [[nodiscard]] (
const std::function<callback_t>&,
const char *first,
const char *last
);
static const char*
parse_object [[nodiscard]] (
const std::function<callback_t>&,
const char *first,
const char *last
);
static const char*
parse_value [[nodiscard]] (
const std::function<callback_t>&,
const char *first,
const char *last
);
};
};
#endif

View File

@ -0,0 +1,24 @@
/*
* 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 "./jsonish.hpp"
#include "./base.hpp"
using util::json2::personality::jsonish;
///////////////////////////////////////////////////////////////////////////////

View File

@ -0,0 +1,84 @@
/*
* 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>
*/
#ifndef CRUFT_UTIL_JSON2_PERSONALITY_JSONISH_HPP
#define CRUFT_UTIL_JSON2_PERSONALITY_JSONISH_HPP
#include "../fwd.hpp"
#include "./base.hpp"
#include <functional>
namespace util::json2::personality {
struct jsonish {
static const char*
consume_whitespace [[nodiscard]] (const char *first, const char *last) noexcept
{ return base<jsonish>::consume_whitespace (first, last); }
static const char*
parse_value [[nodiscard]] (
const std::function<callback_t> &cb,
const char *first, const char *last
) { return base<jsonish>::parse_value (cb, first, last); }
static const char*
parse_number [[nodiscard]] (
const std::function<callback_t> &cb,
const char *first,
const char *last
) { return base<jsonish>::parse_number (cb, first, last); }
template <int N>
static const char*
parse_literal [[nodiscard]] (
const std::function<callback_t> &cb,
const char *first,
const char *last,
const char (&value)[N]
) { return base<jsonish>::parse_literal (cb, first, last, value); }
static const char*
parse_string [[nodiscard]] (
const std::function<callback_t> &cb,
const char *first,
const char *last
) { return base<jsonish>::parse_string (cb, first, last); }
static const char*
parse_array [[nodiscard]] (
const std::function<callback_t> &cb,
const char *first,
const char *last
) { return base<jsonish>::parse_array (cb, first, last); }
static const char*
parse_object [[nodiscard]] (
const std::function<callback_t> &cb,
const char *first,
const char *last
) { return base<jsonish>::parse_object (cb, first, last); }
};
};
#endif

View File

@ -0,0 +1,21 @@
/*
* 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 "./rfc7519.hpp"
#include "./base.hpp"
using util::json2::personality::rfc7159;

View File

@ -0,0 +1,84 @@
/*
* 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>
*/
#ifndef CRUFT_UTIL_JSON2_PERSONALITY_RFC7159_HPP
#define CRUFT_UTIL_JSON2_PERSONALITY_RFC7159_HPP
#include "./base.hpp"
#include "../fwd.hpp"
#include <functional>
namespace util::json2::personality {
struct rfc7159 {
static const char*
consume_whitespace [[nodiscard]] (const char *first, const char *last) noexcept
{ return base<rfc7159>::consume_whitespace (first, last); }
static const char*
parse_value [[nodiscard]] (
const std::function<callback_t> &cb,
const char *first, const char *last
) { return base<rfc7159>::parse_value (cb, first, last); }
static const char*
parse_number [[nodiscard]] (
const std::function<callback_t> &cb,
const char *first,
const char *last
) { return base<rfc7159>::parse_number (cb, first, last); }
template <int N>
static const char*
parse_literal [[nodiscard]] (
const std::function<callback_t> &cb,
const char *first,
const char *last,
const char (&value)[N]
) { return base<rfc7159>::parse_literal (cb, first, last, value); }
static const char*
parse_string [[nodiscard]] (
const std::function<callback_t> &cb,
const char *first,
const char *last
) { return base<rfc7159>::parse_string (cb, first, last); }
static const char*
parse_array [[nodiscard]] (
const std::function<callback_t> &cb,
const char *first,
const char *last
) { return base<rfc7159>::parse_array (cb, first, last); }
static const char*
parse_object [[nodiscard]] (
const std::function<callback_t> &cb,
const char *first,
const char *last
) { return base<rfc7159>::parse_object (cb, first, last); }
};
};
#endif

1
json2/tree.cpp Normal file
View File

@ -0,0 +1 @@
#include "./tree.hpp"

22
json2/tree.hpp Normal file
View File

@ -0,0 +1,22 @@
/*
* 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>
*/
#ifndef CRUFT_JSON2_TREE_HPP
#define CRUFT_JSON2_TREE_HPP
#endif

276
test/json2/event.cpp Normal file
View File

@ -0,0 +1,276 @@
#include "json2/event.hpp"
#include "json2/except.hpp"
#include "tap.hpp"
#include <vector>
#include <cstring>
#include <functional>
void
test_numbers (util::TAP::logger &tap)
{
static const struct {
const char *data;
bool good;
const char *message;
} TESTS[] = {
{ "1", true, "single digit" },
{ "01", false, "leading zero" },
{ "-1", true, "leading minus" },
{ "1.", false, "truncated fraction" },
{ "1.0", true, "fraction" },
{ "1.0e", false, "truncated exponential" },
{ "1.0e1", true, "lower exponential" },
{ "1.0E1", true, "upper exponential" },
{ "1.0e+1", true, "positive exponential" },
{ "1.0e+", false, "truncated positive exponential" },
{ "1.0e-1", true, "negative exponential" },
{ "1.0e-", false, "truncated negative exponential" },
};
for (const auto &t: TESTS) {
auto first = t.data;
auto last = first + strlen (first);
if (!t.good) {
tap.expect_throw<util::json2::error> (
[&] () {
util::json2::event::parse ([] (auto) {}, first, last);
},
"number, %s",
t.message
);
} else {
util::json2::event::type_t type;
auto end = util::json2::event::parse (
[&type] (auto p) { type = p.type (); }, first, last
);
tap.expect (
last == end && type == util::json2::event::type_t::NUMBER,
"number, %s",
t.message
);
}
}
}
void
test_literals (util::TAP::logger &tap)
{
static const struct {
const char *data;
bool good;
util::json2::event::type_t type;
const char *message;
} TESTS[] = {
{ "true", true, util::json2::event::type_t::BOOLEAN, "lower true" },
{ "TRUE", false, util::json2::event::type_t::BOOLEAN, "upper true" },
{ "truE", false, util::json2::event::type_t::BOOLEAN, "mixed true" },
{ "tru", false, util::json2::event::type_t::BOOLEAN, "truncated true" },
{ "false", true, util::json2::event::type_t::BOOLEAN, "lower false" },
{ "FALSE", false, util::json2::event::type_t::BOOLEAN, "upper false" },
{ "falSe", false, util::json2::event::type_t::BOOLEAN, "mixed false" },
{ "fals", false, util::json2::event::type_t::BOOLEAN, "truncated false" },
{ "null", true, util::json2::event::type_t::NONE, "lower null" },
{ "NULL", false, util::json2::event::type_t::NONE, "upper null" },
{ "nUll", false, util::json2::event::type_t::NONE, "mixed null" },
{ "nul", false, util::json2::event::type_t::NONE, "truncated null" },
};
for (const auto &t: TESTS) {
auto first = t.data;
auto last = first + strlen (first);
if (t.good) {
util::json2::event::type_t type;
util::json2::event::parse ([&type] (auto p) { type = p.type (); }, first, last);
tap.expect_eq (type, t.type, "literal, %s", t.message);
} else {
tap.expect_throw<util::json2::parse_error> (
[first,last] () {
util::json2::event::parse ([] (auto) {}, first, last);
},
"literal, %s",
t.message
);
}
}
}
void
test_strings (util::TAP::logger &tap)
{
static const struct {
const char *data;
bool good;
const char *message;
} TESTS[] = {
{ "\"abc\"", true, "abc" },
{ "\"\"", true, "empty" },
{ "\"", false, "unbalanced quote" },
{ "\"\\n\"", true, "newline escape" },
{ "\"\\a\"", true, "valid unnecessary escape" },
{ "\"\\uABCD\"", true, "upper unicode hex escape" },
{ "\"\\uabcd\"", true, "lower unicode hex escape" },
{ "\"\\uab\"", false, "truncated unicode hex escape" },
{ "\"\\uabxy\"", false, "invalid unicode hex escape" },
};
for (const auto &t: TESTS) {
auto first = t.data;
auto last = first + strlen (first);
if (t.good) {
util::json2::event::type_t type;
util::json2::event::parse ([&type] (auto p) { type = p.type (); }, first, last);
tap.expect_eq (type, util::json2::event::type_t::STRING, "string, %s", t.message);
} else {
tap.expect_throw<util::json2::parse_error> (
[first,last] () {
util::json2::event::parse ([] (auto) {}, first, last);
},
"string, %s",
t.message
);
}
}
};
///////////////////////////////////////////////////////////////////////////////
void
test_arrays (util::TAP::logger &tap)
{
using util::json2::event::type_t;
static const struct {
const char *data;
bool good;
std::vector<util::json2::event::type_t> types;
const char *message;
} TESTS[] = {
{ "[]", true, { type_t::ARRAY_BEGIN, type_t::ARRAY_END }, "empty" },
{ "[1]", true, { type_t::ARRAY_BEGIN, type_t::NUMBER, type_t::ARRAY_END }, "single number" },
{ "[1true]", false, { type_t::ARRAY_BEGIN, type_t::NUMBER }, "contatenated number/bool" },
{ "[1,2]", true, { type_t::ARRAY_BEGIN, type_t::NUMBER, type_t::NUMBER, type_t::ARRAY_END }, "two numbers" },
{ "[1,]", false, { type_t::ARRAY_BEGIN, type_t::NUMBER }, "single trailing comma" },
{ "[1,2,]", false, { type_t::ARRAY_BEGIN, type_t::NUMBER, type_t::NUMBER }, "double trailing comma" },
{ "[,]", false, { type_t::ARRAY_BEGIN }, "only comma" },
{ "[", false, { type_t::ARRAY_BEGIN }, "missing terminator" },
{ "[[]]", true, { type_t::ARRAY_BEGIN, type_t::ARRAY_BEGIN, type_t::ARRAY_END, type_t::ARRAY_END }, "nested array" },
{ "[[]", false, { type_t::ARRAY_BEGIN, type_t::ARRAY_END }, "unbalanced nested array" },
};
for (const auto &t: TESTS) {
auto first = t.data;
auto last = first + strlen (first);
if (t.good) {
std::vector<type_t> types;
util::json2::event::parse ([&types] (auto p) { types.push_back (p.type ()); }, first, last);
tap.expect_eq (types, t.types, "array, %s", t.message);
} else {
tap.expect_throw<util::json2::parse_error> (
[&] () {
util::json2::event::parse ([] (auto) { }, first, last);
}, "array, %s", t.message
);
}
}
};
///////////////////////////////////////////////////////////////////////////////
void
test_objects (util::TAP::logger &tap)
{
using util::json2::event::type_t;
static const struct {
const char *data;
bool good;
std::vector<type_t> types;
std::vector<std::string> strings;
const char *message;
} TESTS[] = {
{ "{}", true, { type_t::OBJECT_BEGIN, type_t::OBJECT_END }, {}, "empty" },
{ "{", false, { type_t::OBJECT_BEGIN }, {}, "missing terminator" },
{ "{\"a\":1}", true, { type_t::OBJECT_BEGIN, type_t::STRING, type_t::NUMBER, type_t::OBJECT_END }, {"\"a\""}, "empty" },
{ "{1:1}", false, { type_t::OBJECT_BEGIN }, {}, "integer key" },
{ "{:1}", false, { type_t::OBJECT_BEGIN }, {}, "no key" },
{ "{\"a\":}", false, { type_t::OBJECT_BEGIN, type_t::STRING }, {}, "no value" },
{
R"json({"a":[]})json",
true,
{
type_t::OBJECT_BEGIN,
type_t::STRING,
type_t::ARRAY_BEGIN,
type_t::ARRAY_END,
type_t::OBJECT_END
},
{ "\"a\"" },
"array value" },
{
R"json({ "a": { "b": null } })json",
true,
{
type_t::OBJECT_BEGIN,
type_t::STRING,
type_t::OBJECT_BEGIN,
type_t::STRING,
type_t::NONE,
type_t::OBJECT_END,
type_t::OBJECT_END
},
{ "\"a\"", "\"b\"" },
"recursive object"
}
};
for (const auto &t: TESTS) {
auto first = t.data;
auto last = first + strlen (first);
if (t.good) {
std::vector<type_t> types;
std::vector<std::string> strings;
util::json2::event::parse ([&] (auto p) {
types.push_back (p.type ());
if (p.type () == type_t::STRING)
strings.push_back (std::string {p.first, p.last});
}, first, last);
tap.expect (types == t.types && strings == t.strings, "object, %s", t.message);
} else {
tap.expect_throw<util::json2::parse_error> (
[&] () {
util::json2::event::parse ([] (auto) {}, first, last);
}, "object, %s", t.message
);
}
}
};
///////////////////////////////////////////////////////////////////////////////
int
main (void)
{
util::TAP::logger tap;
test_numbers (tap);
test_literals (tap);
test_strings (tap);
test_arrays (tap);
test_objects (tap);
return tap.status ();
}