Hello libart-json
All checks were successful
on-push / build-and-test (push) Successful in 21s

This commit is contained in:
2025-10-18 00:29:14 +02:00
commit 50418b02e4
48 changed files with 4661 additions and 0 deletions

17
.editorconfig Normal file
View File

@@ -0,0 +1,17 @@
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
indent_size = 4
max_line_length = off
trim_trailing_whitespace = false
[*.yaml]
indent_size = 2

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
* text=auto

View File

@@ -0,0 +1,31 @@
name: on-push
on:
push:
tags-ignore:
- '*'
branches:
- '**'
jobs:
build-and-test:
runs-on: linux
container: code.helloryan.se/art/infra/buildenv/x86_64-fedora_42-unified:latest
volumes:
- /build
steps:
- name: Configure repository access
run: |
git config --global http.$GITHUB_SERVER_URL/.extraheader "Authorization: token ${{ secrets.ACT_RUNNER_TOKEN }}"
- name: Configure build directory
run: |
bpkg create -d /build cc config.cxx=clang++ config.cc.coptions="-Wall -Werror -Wno-unknown-pragmas"
- name: Build package
run: |
cd /build
bpkg build --yes --trust-yes $GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git##$GITHUB_SHA
- name: Test package
run: |
cd /build
b test

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
patreon: helloryan

31
.gitignore vendored Normal file
View File

@@ -0,0 +1,31 @@
.bdep/
# Local default options files.
#
.build2/local/
# Compiler/linker output.
#
*.d
*.t
*.i
*.i.*
*.ii
*.ii.*
*.o
*.obj
*.gcm
*.pcm
*.ifc
*.so
*.dylib
*.dll
*.a
*.lib
*.exp
*.pdb
*.ilk
*.exe
*.exe.dlls/
*.exe.manifest
*.pc

31
LICENSE Normal file
View File

@@ -0,0 +1,31 @@
Copyright © 2024 Ryan. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. 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.
3. All advertising materials mentioning features or use of this software must
display the following acknowledgement:
This product includes software developed by Ryan, http://helloryan.se/.
4. Neither the name(s) of the copyright holder(s) nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER "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 COPYRIGHT HOLDER 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.

10
README.md Normal file
View File

@@ -0,0 +1,10 @@
# libart-json
![Build badge](https://code.helloryan.se/art/libart-json/actions/workflows/on-push.yaml/badge.svg)
libart-json implements JSON serialization/deserialization and parsing for C++.
## Sponsorship
You can sponsor the development of this project via Patreon. Read more
over at https://patreon.com/helloryan.

9
art/json/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
# Generated version header.
#
version.hxx
# Unit test executables and Testscript output directories
# (can be symlinks).
#
*.test
test-*.test

BIN
art/json/.swp Normal file

Binary file not shown.

65
art/json/buildfile Normal file
View File

@@ -0,0 +1,65 @@
intf_libs = # Interface dependencies.
impl_libs = # Implementation dependencies.
test_libs = # Test dependencies.
import intf_libs =+ libart-unicode%lib{art-unicode}
./: lib{art-json}: libul{art-json}
libul{art-json}: {hxx ixx txx cxx}{** -**.test... -version} \
{hxx }{ version}
libul{art-json}: $impl_libs $intf_libs
# Unit tests.
#
exe{*.test}:
{
test = true
install = false
}
for t: cxx{**.test...}
{
d = $directory($t)
n = $name($t)...
./: $d/exe{$n}: $t $d/{hxx ixx txx}{+$n} $d/testscript{+$n} $test_libs
$d/exe{$n}: libul{art-json}: bin.whole = false
}
hxx{version}: in{version} $src_root/manifest
{
dist = true
clean = ($src_root != $out_root)
}
# Build options.
#
cxx.poptions =+ "-I$out_root" "-I$src_root"
# Export options.
#
lib{art-json}:
{
cxx.export.poptions = "-I$out_root" "-I$src_root"
cxx.export.libs = $intf_libs
}
# For pre-releases use the complete version to make sure they cannot
# be used in place of another pre-release or the final version. See
# the version module for details on the version.* variable values.
#
if $version.pre_release
lib{art-json}: bin.lib.version = "-$version.project_id"
else
lib{art-json}: bin.lib.version = "-$version.major.$version.minor"
# Install into the art/json/ subdirectory of, say, /usr/include/
# recreating subdirectories.
#
{hxx ixx txx}{*}:
{
install = include/art/json/
install.subdirs = true
}

45
art/json/diagnostics.cxx Normal file
View File

@@ -0,0 +1,45 @@
#include <art/json/diagnostics.hxx>
namespace art::json {
std::vector< std::pair< diagnostics::location, std::string > > const&
diagnostics::warnings() const
{
return warnings_;
}
std::vector< std::pair< diagnostics::location, std::string > > const&
diagnostics::errors() const
{
return errors_;
}
void
diagnostics::warning(location loc, std::string description)
{
warnings_.emplace_back(std::make_pair(loc, description));
}
void
diagnostics::error(location loc, std::string description)
{
errors_.emplace_back(std::make_pair(loc, description));
}
std::ostream&
operator<<(std::ostream& o, diagnostics const& d)
{
for (auto const& j : d.warnings()) {
o << "warning: " << j.first.line << ':' << j.first.column << ": "
<< j.second << '\n';
}
for (auto const& j : d.errors()) {
o << "error: " << j.first.line << ':' << j.first.column << ": " << j.second
<< '\n';
}
return o;
}
} // namespace art::json

43
art/json/diagnostics.hxx Normal file
View File

@@ -0,0 +1,43 @@
#ifndef art__json__diagnostics_hxx_
#define art__json__diagnostics_hxx_
#include <cstdint>
#include <ostream>
#include <string>
#include <utility>
#include <vector>
namespace art::json {
class diagnostics
{
public:
struct location
{
std::uint32_t line;
std::uint32_t column;
};
std::vector< std::pair< diagnostics::location, std::string > > const&
warnings() const;
std::vector< std::pair< diagnostics::location, std::string > > const&
errors() const;
void
warning(location loc, std::string description);
void
error(location loc, std::string description);
private:
std::vector< std::pair< location, std::string > > warnings_;
std::vector< std::pair< location, std::string > > errors_;
};
std::ostream&
operator<<(std::ostream& o, diagnostics const& d);
} // namespace art::json
#endif

54
art/json/emitter.hxx Normal file
View File

@@ -0,0 +1,54 @@
#ifndef art__json__emitter_hxx_
#define art__json__emitter_hxx_
#include <art/json/variant.hxx>
#include <ostream>
#include <stack>
namespace art::json {
class emitter {
public:
explicit emitter(std::ostream& output);
std::ostream&
output();
void
operator()(variant::undefined_t const& value);
void
operator()(bool const& value);
void
operator()(long long int const& value);
void
operator()(unsigned long long int const& value);
void
operator()(long double const& value);
void
operator()(std::string const& value);
void
operator()(std::vector< variant > const& value);
void
operator()(std::map< std::string, variant > const& value);
private:
void
do_indent();
std::ostream& output_;
std::size_t indent_{ 0 };
};
} // namespace art::json
#include <art/json/emitter.ixx>
#endif

141
art/json/emitter.ixx Normal file
View File

@@ -0,0 +1,141 @@
namespace art::json {
inline emitter::emitter(std::ostream& output) : output_{ output }
{}
inline std::ostream&
emitter::output()
{
return output_;
}
inline void
emitter::operator()(variant::undefined_t const& value)
{
output() << "null";
}
inline void
emitter::operator()(bool const& value)
{
output() << (value ? "true" : "false");
}
inline void
emitter::operator()(long long int const& value)
{
output() << std::to_string(value);
}
inline void
emitter::operator()(unsigned long long int const& value)
{
output() << std::to_string(value);
}
inline void
emitter::operator()(long double const& value)
{
output() << std::to_string(value);
}
inline void
emitter::operator()(std::string const& value)
{
output() << '\"';
for (auto const j : value) {
switch (j) {
case '\n':
output() << "\\n";
break;
case '\t':
output() << "\\t";
break;
case '\\':
output() << "\\\\";
break;
case '"':
output() << "\\\"";
break;
// case '\x22':
// case '\x5c':
// case '\x2f':
// case '\x62':
// case '\x66':
// case '\x6e':
// case '\x72':
// case '\x74':
default:
output() << j;
}
}
output() << '\"';
}
inline void
emitter::operator()(std::vector< variant > const& value)
{
if (value.empty()) {
output() << "[]";
return;
}
output() << '[' << '\n';
++indent_;
bool first{ true };
for (auto const& j : value) {
if (!first)
output() << ',' << '\n';
do_indent();
first = false;
visit(*this, j.value_);
}
output() << '\n';
--indent_;
do_indent();
output() << ']';
}
inline void
emitter::operator()(std::map< std::string, variant > const& value)
{
if (value.empty()) {
output() << "{}";
return;
}
output() << "{\n";
++indent_;
bool first{ true };
for (auto const& j : value) {
if (!first)
output() << ",\n";
first = false;
do_indent();
(*this)(j.first);
output() << ':' << ' ';
visit(*this, j.second.value_);
}
output() << '\n';
--indent_;
do_indent();
output() << '}';
}
inline void
emitter::do_indent()
{
std::size_t i{ indent_ * 2 };
while (i-- > 0)
output() << ' ';
}
} // namespace art::json

115
art/json/emitter.test.cxx Normal file
View File

@@ -0,0 +1,115 @@
#include <art/json/emitter.hxx>
#include <iostream>
#include <sstream>
#define DEFINE_TEST(x) std::cout << x << '\n';
#define TEST_EQUAL(x, y) if ((x) != (y)) return __LINE__;
int
main()
{
DEFINE_TEST("undefined")
{
std::stringstream str;
art::json::emitter emitter{ str };
emitter(art::json::variant::undefined_t{});
TEST_EQUAL(str.str(), "null");
}
DEFINE_TEST("boolean: true")
{
std::stringstream str;
art::json::emitter emitter{ str };
emitter(true);
TEST_EQUAL(str.str(), "true");
}
DEFINE_TEST("boolean: false")
{
std::stringstream str;
art::json::emitter emitter{ str };
emitter(false);
TEST_EQUAL(str.str(), "false");
}
DEFINE_TEST("signed number")
{
std::stringstream str;
art::json::emitter emitter{ str };
emitter(-10LL);
TEST_EQUAL(str.str(), "-10");
}
DEFINE_TEST("unsigned number")
{
std::stringstream str;
art::json::emitter emitter{ str };
emitter(100LL);
TEST_EQUAL(str.str(), "100");
}
DEFINE_TEST("real number")
{
std::stringstream str;
art::json::emitter emitter{ str };
emitter(1.2L);
TEST_EQUAL(str.str(), "1.2");
}
DEFINE_TEST("string")
{
std::stringstream str;
art::json::emitter emitter{ str };
emitter(std::string{ "hello" });
TEST_EQUAL(str.str(), "\"hello\"");
}
DEFINE_TEST("array: []")
{
std::stringstream str;
art::json::emitter emitter{ str };
emitter(std::vector< art::json::variant >{});
TEST_EQUAL(str.str(), "[]");
}
DEFINE_TEST("array: [null]")
{
std::stringstream str;
art::json::emitter emitter{ str };
emitter(std::vector< art::json::variant >{ art::json::variant{} });
TEST_EQUAL(str.str(), "[\n null\n]");
}
DEFINE_TEST("object: {}")
{
std::stringstream str;
art::json::emitter emitter{ str };
emitter(std::map< std::string, art::json::variant >{});
TEST_EQUAL(str.str(), "{}");
}
DEFINE_TEST("object: {...}")
{
std::stringstream str;
art::json::emitter emitter{ str };
emitter(std::map< std::string, art::json::variant >{ { "", {} } });
TEST_EQUAL(str.str(), "{\n \"\": null\n}");
}
return 0;
}

43
art/json/exception.cxx Normal file
View File

@@ -0,0 +1,43 @@
#include <art/json/exception.hxx>
namespace art::json {
invalid_json::invalid_json(diagnostics::location const& loc,
std::string const& what)
: std::runtime_error{ make_error_string(loc, what) }, location_{ loc }
{}
diagnostics::location const&
invalid_json::location() const
{
return location_;
}
std::string
invalid_json::make_error_string(diagnostics::location const& loc,
std::string const& what)
{
std::ostringstream str;
str << loc.column << " " << loc.line << ": " << what;
return str.str();
}
invalid_type::invalid_type() : std::runtime_error{ "invalid type" }
{}
invalid_syntax::invalid_syntax() : std::runtime_error{ "invalid syntax" }
{}
unexpected_type::unexpected_type()
: std::runtime_error{ "unexpected JSON type" }
{}
invalid_object_key::invalid_object_key(std::string const& key)
: std::runtime_error{ "invalid key '" + key + "'" }
{}
invalid_array_index::invalid_array_index(std::string const& index)
: std::runtime_error{ "invalid key '" + index + "'" }
{}
} // namespace art::json

59
art/json/exception.hxx Normal file
View File

@@ -0,0 +1,59 @@
#ifndef art__json__exception_hxx_
#define art__json__exception_hxx_
#include <art/json/diagnostics.hxx>
#include <sstream>
#include <stdexcept>
#include <string>
namespace art::json {
/// Exception-class used to indicated invalid JSON.
class invalid_json : public std::runtime_error {
public:
invalid_json(diagnostics::location const& loc, std::string const& what);
diagnostics::location const&
location() const;
static std::string
make_error_string(diagnostics::location const& loc, std::string const& what);
private:
diagnostics::location location_;
};
/// Exception-class used to indicate an invalid type.
class invalid_type : public std::runtime_error {
public:
invalid_type();
};
/// Exception class used to indicate invalid JSON pointer syntax.
class invalid_syntax : public std::runtime_error {
public:
invalid_syntax();
};
/// Exception class used to indicate an unexpected JSON type.
class unexpected_type : public std::runtime_error {
public:
unexpected_type();
};
/// Exception-class used to indicate an object key.
class invalid_object_key : public std::runtime_error {
public:
explicit invalid_object_key(std::string const& key);
};
/// Exception-class used to indicate an invalid array index.
class invalid_array_index : public std::runtime_error {
public:
explicit invalid_array_index(std::string const& index);
};
} // namespace art::json
#endif

883
art/json/marshaling.hxx Normal file
View File

@@ -0,0 +1,883 @@
#ifndef art__json__marshaling_hxx_
#define art__json__marshaling_hxx_
#include <art/json/optional.hxx>
#include <art/json/pointer.hxx>
#include <art/json/traits.hxx>
#include <art/json/variant.hxx>
#include <chrono>
#include <ctime>
#include <deque>
#include <iomanip>
#include <list>
#include <locale>
#include <map>
#include <set>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>
#include <iostream> // fixme: remove
namespace art::json
{
class marshaling_context_t
{
protected:
virtual ~marshaling_context_t() = default;
};
template<typename T>
struct marshaling_traits;
template<std::size_t N>
struct member_name_t
{
constexpr member_name_t(char const (&str)[N])
{
std::copy_n(str, N, name);
}
std::string const
str() const
{
return name;
}
operator std::string const() const
{
return str();
}
char name[N];
};
template<typename>
struct member_traits;
template<typename T, typename M>
struct member_traits<M T::*>
{
using class_type = T;
using member_type = M;
};
template<typename, typename = std::void_t<>>
struct has_validator
: std::false_type
{};
template<typename T>
struct has_validator<
T,
std::void_t<
decltype(std::declval<T const&>().validate(std::declval<marshaling_context_t*>()))
>
>
: std::true_type
{};
template<member_name_t Name, auto Member, typename Validator = void>
struct member_t
{
using T = member_traits<decltype(Member)>::class_type;
using M = member_traits<decltype(Member)>::member_type;
using V = Validator;
static constexpr bool is_optional{
is_optional_v<M>
};
static
void
marshal(variant& v, T const& instance, marshaling_context_t* context)
{
v.set(Name, marshaling_traits<M>::marshal(instance.*Member, context));
}
static
void
unmarshal(T& instance, variant const& v, marshaling_context_t* context)
{
if (!v.contains(Name.str())) {
if (is_optional) {
return;
}
throw std::runtime_error{"missing field '" + Name.str() + "'"};
}
M value = marshaling_traits<M>::unmarshal(v.get(Name), context);
if constexpr (!std::is_same_v<V, void>) {
V::validate(value, context);
}
instance.*Member = std::move(value);
}
struct pointer_t
{
using T = member_traits<decltype(Member)>::class_type;
using M = member_traits<decltype(Member)>::member_type;
static pointer const&
ptr()
{
static pointer ptr{Name};
return ptr;
}
static
void
marshal(variant& v, T const& instance, marshaling_context_t* context)
{
ptr().write(v, marshaling_traits<M>::marshal(instance.*Member, context));
}
static
void
unmarshal(T& instance, variant const& v, marshaling_context_t* context)
{
auto ptr_v = ptr().read(v);
if (!ptr_v) {
if (is_optional) {
return;
}
throw std::runtime_error{ "missing field '" + to_string(ptr()) + "'" };
}
M value = marshaling_traits<M>::unmarshal(*ptr_v, context);
if constexpr (!std::is_same_v<V, void>) {
V::validate(value, context);
}
instance.*Member = std::move(value);
}
};
};
template<typename First, typename... Members>
struct mapping_t
{
using T = First::T;
static
void
do_marshal(variant& v, T const& instance, marshaling_context_t* context)
{
First::marshal(v, instance, context);
((Members::marshal(v, instance, context)), ...);
}
static
variant
marshal(T const& instance, marshaling_context_t* context)
{
variant v{std::map<std::string, variant>{}};
do_marshal(v, instance, context);
return v;
}
static
void
do_unmarshal(T& instance, variant const& v, marshaling_context_t* context)
{
First::unmarshal(instance, v, context);
((Members::unmarshal(instance, v, context)), ...);
if constexpr (has_validator<T>::value) {
instance.validate(context);
}
}
static
T
unmarshal(variant const& v, marshaling_context_t* context)
{
T instance;
do_unmarshal(instance, v, context);
return instance;
}
template<typename... Bases>
struct inherit_t
{
static_assert(sizeof...(Bases) > 0, "at least one base must be specified");
static
void
do_marshal(variant& v, T const& instance, marshaling_context_t* context)
{
((Bases::json::do_marshal(v, instance, context)), ...);
First::marshal(v, instance, context);
((Members::marshal(v, instance, context)), ...);
}
static
variant
marshal(T const& instance, marshaling_context_t* context)
{
variant v{std::map<std::string, variant>{}};
do_marshal(v, instance, context);
return v;
}
static
void
do_unmarshal(T& instance, variant const& v, marshaling_context_t* context)
{
((Bases::json::do_unmarshal(instance, v, context)), ...);
First::unmarshal(instance, v, context);
((Members::unmarshal(instance, v, context)), ...);
if constexpr (has_validator<T>::value) {
instance.validate(context);
}
}
static
T
unmarshal(variant const& v, marshaling_context_t* context)
{
T instance;
do_unmarshal(instance, v, context);
return instance;
}
};
};
template<member_name_t Name, typename Type>
struct emplace_member_t
{
using T = Type;
static constexpr bool is_optional{
is_optional_v<T>
};
static
T
unmarshal(variant const& v, marshaling_context_t* context)
{
if (!v.contains(Name.str())) {
if constexpr (is_optional) {
return std::nullopt;
}
throw std::runtime_error{"missing field '" + Name.str() + "'"};
}
return marshaling_traits<T>::unmarshal(v.get(Name), context);
}
};
template<typename T, typename... Members>
struct emplace_t
{
static
T
unmarshal(variant const& v, marshaling_context_t* context)
{
return T{
Members::unmarshal(v, context)...
};
}
};
template<typename T>
struct marshaling_traits
{
static
variant
marshal(T const& instance, marshaling_context_t* context)
{
return T::json::marshal(instance, context);
}
static
T
unmarshal(variant const& v, marshaling_context_t* context)
{
return T::json::unmarshal(v, context);
}
};
template<>
struct marshaling_traits<variant>
{
static
variant
marshal(variant const& v, marshaling_context_t* context)
{
return v;
}
static
variant
unmarshal(variant const& v, marshaling_context_t* context)
{
return v;
}
};
template<typename T>
struct marshaling_traits<optional<T>>
{
static
variant
marshal(optional<T> const& model, marshaling_context_t* context)
{
if (model) {
return marshaling_traits<T>::marshal(*model, context);
}
return {};
}
static optional<T>
unmarshal(variant const& value, marshaling_context_t* context)
{
if (value.is_undefined()) {
return std::nullopt;
}
return marshaling_traits<T>::unmarshal(value, context);
}
};
template<typename... T>
struct marshaling_traits<std::variant<T...>>
{
static
variant
marshal(std::variant<T...> const& model, marshaling_context_t* context)
{
variant result;
auto visitor = [&result, context](const auto& obj)
{
using type = std::decay_t<decltype(obj)>;
result = marshaling_traits<type>::marshal(obj, context);
result.set("$type", std::decay_t<decltype(obj)>::type_identifier);
};
std::visit(visitor, model);
return result;
}
template<typename Current, typename... Next>
struct unmarshaler
{
static
std::variant<T...>
unmarshal(variant const& value, marshaling_context_t* context)
{
if (!value.contains("$type"))
throw std::invalid_argument{"variant missing '$type' identifier"};
if (value.get("$type").get_string() == Current::type_identifier)
return marshaling_traits<Current>::unmarshal(value, context);
if constexpr (sizeof...(Next)> 0)
return unmarshaler<Next...>::unmarshal(value, context);
throw std::invalid_argument{"couldn't unmarshal value"};
}
};
static
std::variant<T...>
unmarshal(variant const& value, marshaling_context_t* context)
{
return unmarshaler<T...>::unmarshal(value, context);
}
};
template<typename T>
struct marshaling_traits<std::vector<T>>
{
static
variant
marshal(std::vector<T> const& model, marshaling_context_t* context)
{
std::vector<variant> a;
for (auto const& j : model)
a.emplace_back(marshaling_traits<T>::marshal(j, context));
return a;
}
static
std::vector<T>
unmarshal(variant const& value, marshaling_context_t* context)
{
if (!value.is_array())
throw std::runtime_error{"not an array"};
std::vector<T> model;
for (auto const& j : value)
model.emplace_back(marshaling_traits<T>::unmarshal(j, context));
return model;
}
};
template<typename T>
struct marshaling_traits<std::list<T>>
{
static
variant
marshal(std::list<T> const& model, marshaling_context_t* context)
{
std::vector<variant> a;
for (auto const& j : model)
a.emplace_back(marshaling_traits<T>::marshal(j, context));
return a;
}
static
std::list<T>
unmarshal(variant const& value, marshaling_context_t* context)
{
if (!value.is_array())
throw std::runtime_error{"not an array"};
std::list<T> model;
for (auto const& j : value)
model.emplace_back(marshaling_traits<T>::unmarshal(j, context));
return model;
}
};
template<typename T>
struct marshaling_traits<std::deque<T>>
{
static
variant
marshal(std::deque<T> const& model, marshaling_context_t* context)
{
std::vector<variant> a;
for (auto const& j : model)
a.emplace_back(marshaling_traits<T>::marshal(j, context));
return a;
}
static
std::deque<T>
unmarshal(variant const& value, marshaling_context_t* context)
{
if (!value.is_array())
throw std::runtime_error{"not an array"};
std::deque<T> model;
for (auto const& j : value)
model.emplace_back(marshaling_traits<T>::unmarshal(j, context));
return model;
}
};
template<typename T>
struct marshaling_traits<std::set<T>>
{
static
variant
marshal(std::set<T> const& model, marshaling_context_t* context)
{
std::vector<variant> a;
for (auto const& j : model)
a.emplace_back(marshaling_traits<T>::marshal(j, context));
return a;
}
static
std::set<T>
unmarshal(variant const& value, marshaling_context_t* context)
{
if (!value.is_array())
throw std::runtime_error{"not an array"};
std::set<T> model;
for (auto const& j : value)
model.emplace(marshaling_traits<T>::unmarshal(j, context));
return model;
}
};
template<>
struct marshaling_traits<bool>
{
static
variant
marshal(bool model, marshaling_context_t*)
{
return model;
}
static
bool
unmarshal(variant const& value, marshaling_context_t*)
{
if (!value.is_boolean())
throw std::runtime_error{"not a boolean"};
return value.get_boolean();
}
};
template<>
struct marshaling_traits<short int> {
static
variant
marshal(short int model, marshaling_context_t*)
{
return model;
}
static
short int
unmarshal(variant const& value, marshaling_context_t*)
{
if (!value.is_number())
throw std::runtime_error{"not a number"};
return value.get_number<short int>();
}
};
template<>
struct marshaling_traits<int>
{
static
variant
marshal(int model, marshaling_context_t*)
{
return model;
}
static
int
unmarshal(variant const& v, marshaling_context_t*)
{
if (!v.is_number()) {
throw std::runtime_error{"not a number"};
}
return v.get_number<int>();
}
};
template<>
struct marshaling_traits<long int> {
static
variant
marshal(long int model, marshaling_context_t*)
{
return model;
}
static
long int
unmarshal(variant const& value, marshaling_context_t*)
{
if (!value.is_number())
throw std::runtime_error{"not a number"};
return value.get_number<long int>();
}
};
template<>
struct marshaling_traits<long long int> {
static
variant
marshal(long long int model, marshaling_context_t*)
{
return model;
}
static
long long int
unmarshal(variant const& value, marshaling_context_t*)
{
if (!value.is_number())
throw std::runtime_error{"not a number"};
return value.get_number<long long int>();
}
};
template<>
struct marshaling_traits<unsigned short int> {
static
variant
marshal(unsigned short int model, marshaling_context_t*)
{
return model;
}
static
unsigned short int
unmarshal(variant const& value, marshaling_context_t*)
{
if (!value.is_number())
throw std::runtime_error{"not a number"};
return value.get_number<unsigned short int>();
}
};
template<>
struct marshaling_traits<unsigned int> {
static
variant
marshal(unsigned int model, marshaling_context_t*)
{
return model;
}
static
unsigned int
unmarshal(variant const& value, marshaling_context_t*)
{
if (!value.is_number())
throw std::runtime_error{"not a number"};
return value.get_number<unsigned int>();
}
};
template<>
struct marshaling_traits<unsigned long int> {
static
variant
marshal(unsigned long int model, marshaling_context_t*)
{
return model;
}
static
unsigned long int
unmarshal(variant const& value, marshaling_context_t*)
{
if (!value.is_number())
throw std::runtime_error{"not a number"};
return value.get_number<unsigned long int>();
}
};
// unsigned long long int
template<>
struct marshaling_traits<unsigned long long int> {
static
variant
marshal(unsigned long long int model, marshaling_context_t*)
{
return model;
}
static
unsigned long long int
unmarshal(variant const& value, marshaling_context_t*)
{
if (!value.is_number())
throw std::runtime_error{"not a number"};
return value.get_number<unsigned long long int>();
}
};
template<>
struct marshaling_traits<float> {
static
variant
marshal(float model, marshaling_context_t*)
{
return model;
}
static
float
unmarshal(variant const& value, marshaling_context_t*)
{
if (!value.is_number())
throw std::runtime_error{"not a number"};
return value.get_number<float>();
}
};
template<>
struct marshaling_traits<double> {
static
variant
marshal(double model, marshaling_context_t*)
{
return model;
}
static
double
unmarshal(variant const& value, marshaling_context_t*)
{
if (!value.is_number())
throw std::runtime_error{"not a number"};
return value.get_number<double>();
}
};
template<>
struct marshaling_traits<long double> {
static
variant
marshal(long double model, marshaling_context_t*)
{
return model;
}
static
long double
unmarshal(variant const& value, marshaling_context_t*)
{
if (!value.is_number())
throw std::runtime_error{"not a number"};
return value.get_number<long double>();
}
};
template<>
struct marshaling_traits<std::string>
{
static
variant
marshal(std::string const& model, marshaling_context_t*)
{
return model;
}
static
std::string
unmarshal(variant const& v, marshaling_context_t*)
{
if (!v.is_string()) {
throw std::runtime_error{"not a string"};
}
return v.get_string();
}
};
template<>
struct marshaling_traits<std::chrono::system_clock::time_point>
{
using model_type = std::string;
//static constexpr const char time_format[] = "%a, %d %b %Y %H:%M:%S GMT";
static constexpr const char time_format[] = "%Y-%m-%dT%H:%M:%S.000Z";
static
variant
marshal(std::chrono::system_clock::time_point const& model, marshaling_context_t*)
{
std::time_t now_c = std::chrono::system_clock::to_time_t(model);
struct std::tm tm_buf;
std::stringstream str;
str.imbue(std::locale{});
#ifdef _MSC_VER
::gmtime_s(&tm_buf, &now_c); // Stupid Microsoft.
str << std::put_time(&tm_buf, time_format);
#else
::gmtime_r(&now_c, &tm_buf);
str << std::put_time(&tm_buf, time_format);
#endif
return str.str();
}
static
std::chrono::system_clock::time_point
unmarshal(variant const& value, marshaling_context_t*)
{
if (!value.is_string())
throw std::runtime_error{ "not a string" };
std::tm tm{};
std::istringstream str{ value.get_string() };
str.imbue(std::locale{});
str >> std::get_time(&tm, time_format);
if (str.fail())
return {};
#ifdef _MSC_VER
auto localtime = _mkgmtime(&tm);
return std::chrono::system_clock::from_time_t(localtime);
#else
auto localtime = timegm(&tm);
return std::chrono::system_clock::from_time_t(localtime);
#endif
}
};
template<typename T>
variant
marshal(T const& model, marshaling_context_t* context = nullptr)
{
return marshaling_traits<T>::marshal(model, context);
}
template<typename T>
T
unmarshal(variant const& v, marshaling_context_t* context = nullptr)
{
return marshaling_traits<T>::unmarshal(v, context);
}
} // namespace art::json
#endif

View File

@@ -0,0 +1,174 @@
#include <art/json/marshaling.hxx>
#include <art/json/optional.hxx>
#include <art/json/serialize.hxx>
#include <cstdint>
#include <deque>
#include <iostream>
#include <list>
#include <string>
#include <vector>
#define DEFINE_TEST(x) std::cout << x << '\n';
#define TEST_TRUE(x) if (!(x)) return __LINE__;
#define TEST_FALSE(x) if ((x)) return __LINE__;
#define TEST_EQUAL(x, y) if ((x) != (y)) return __LINE__;
using art::json::mapping_t;
using art::json::marshal;
using art::json::marshaling_traits;
using art::json::member_t;
using art::json::unmarshal;
struct name_t
{
std::string first;
std::string last;
using json = mapping_t<
member_t<"/person/first", &name_t::first>::pointer_t,
member_t<"/person/last", &name_t::last>::pointer_t
>;
};
int
main()
{
DEFINE_TEST("optional<int>{}")
{
using traits_type = marshaling_traits<art::json::optional<int>>;
std::optional<int> model;
auto j = traits_type::marshal(model, nullptr);
TEST_TRUE(j.is_undefined());
}
DEFINE_TEST("optional<int>{0}")
{
using traits_type = marshaling_traits<art::json::optional<int>>;
std::optional<int> model{0};
auto j = traits_type::marshal(model, nullptr);
TEST_FALSE(j.is_undefined());
TEST_TRUE(j.is_number());
}
DEFINE_TEST("string")
{
using traits_type = marshaling_traits<std::string>;
std::string model{"hello, world"};
auto j = traits_type::marshal(model, nullptr);
TEST_TRUE(j.is_string());
TEST_EQUAL(j.get_string(), "hello, world");
}
DEFINE_TEST("vector<int>{}")
{
using traits_type = marshaling_traits<std::vector<int>>;
std::vector<int> model1;
auto j = traits_type::marshal(model1, nullptr);
TEST_TRUE(j.is_array());
auto model2 = traits_type::unmarshal(j, nullptr);
TEST_EQUAL(model1, model2);
}
DEFINE_TEST("vector<int>{...}")
{
using traits_type = marshaling_traits<std::vector<int>>;
std::vector<int> model1{ 1, 2, 3, 4 };
auto j = traits_type::marshal(model1, nullptr);
TEST_TRUE(j.is_array());
auto model2 = traits_type::unmarshal(j, nullptr);
TEST_EQUAL(model1, model2);
}
DEFINE_TEST("list<int>{}")
{
using traits_type = marshaling_traits<std::list<int>>;
std::list<int> model1;
auto j = traits_type::marshal(model1, nullptr);
TEST_TRUE(j.is_array());
auto model2 = traits_type::unmarshal(j, nullptr);
TEST_EQUAL(model1, model2);
}
DEFINE_TEST("list<int>{...}")
{
using traits_type = marshaling_traits<std::list<int>>;
std::list<int> model1{ 1, 2, 3, 4 };
auto j = traits_type::marshal(model1, nullptr);
TEST_TRUE(j.is_array());
auto model2 = traits_type::unmarshal(j, nullptr);
TEST_EQUAL(model1, model2);
}
DEFINE_TEST("deque<int>{}")
{
using traits_type = marshaling_traits<std::deque<int>>;
std::deque<int> model1;
auto j = traits_type::marshal(model1, nullptr);
TEST_TRUE(j.is_array());
auto model2 = traits_type::unmarshal(j, nullptr);
TEST_EQUAL(model1, model2);
}
DEFINE_TEST("deque<int>{...}")
{
using traits_type = marshaling_traits<std::deque<int>>;
std::deque<int> model1{ 1, 2, 3, 4 };
auto j = traits_type::marshal(model1, nullptr);
TEST_TRUE(j.is_array());
auto model2 = traits_type::unmarshal(j, nullptr);
TEST_EQUAL(model1, model2);
}
DEFINE_TEST("pointer")
{
name_t name1{"Jane", "Doe"};
auto v = marshal<name_t>(name1);
auto name2 = unmarshal<name_t>(v);
TEST_EQUAL(name1.first, name2.first);
TEST_EQUAL(name1.last, name2.last);
}
return 0;
}

30
art/json/optional.hxx Normal file
View File

@@ -0,0 +1,30 @@
#ifndef art__json__optional_hxx_
#define art__json__optional_hxx_
/*
* This file will be REMOVED once <optional> is widely available.
*/
#if !defined(__has_include)
# error "__has_include not supported"
#endif
#if __has_include(<optional>)
# include <optional>
#elif __has_include(<experimental/optional>)
# include <experimental/optional>
#else
# error "No support for <optional>"
#endif
namespace art::json {
#if __has_include(<optional>)
using std::optional;
#elif __has_include(<experimental/optional>)
using std::experimental::optional;
#endif
} // namespace art::json
#endif

759
art/json/parser.hxx Normal file
View File

@@ -0,0 +1,759 @@
#ifndef art__json__parser_hxx_
#define art__json__parser_hxx_
#include <art/json/diagnostics.hxx>
#include <art/json/exception.hxx>
#include <art/json/optional.hxx>
#include <art/json/variant.hxx>
#include <art/unicode/utf8-decoder.hxx>
#include <art/unicode/utf8-encoder.hxx>
#include <iostream> // TODO remove
#include <sstream>
#include <string>
#include <limits>
namespace art::json {
class variant;
class parser {
public:
parser() = default;
template< typename InputIterator >
optional< variant >
try_parse(diagnostics& d, InputIterator& first, InputIterator last)
{
auto v = parse(d, first, last);
if (d.errors().size() > 0)
return variant::undefined;
return v;
}
template< typename InputIterator >
optional< variant >
try_parse(diagnostics& d, InputIterator&& first, InputIterator last)
{
return try_parse(d, first, last);
}
template< typename InputIterator >
variant
parse(diagnostics& d, InputIterator& first, InputIterator last)
{
skip_whitespace(first, last);
auto v = try_parse_any(d, first, last);
if (d.errors().size() > 0)
return variant::undefined;
skip_whitespace(first, last);
if (first != last) {
d.error(location(), "unexpected trailing data");
return variant::undefined;
}
return v;
}
template< typename InputIterator >
variant
parse(diagnostics& d, InputIterator&& first, InputIterator last)
{
return parse(d, first, last);
}
private:
template< typename InputIterator >
variant
try_parse_undefined(diagnostics& d, InputIterator& i, InputIterator last)
{
auto loc = location();
if (*i != 'n') {
d.error(location(), "expected 'n'");
return { loc, variant::undefined };
}
advance(i, last);
if (i == last || *i != 'u') {
d.error(location(), "expected 'u'");
return { loc, variant::undefined };
}
advance(i, last);
if (i == last || *i != 'l') {
d.error(location(), "expected 'l'");
return { loc, variant::undefined };
}
advance(i, last);
if (i == last || *i != 'l') {
d.error(location(), "expected 'l'");
return { loc, variant::undefined };
}
advance(i, last);
return { std::move(loc), variant::undefined };
}
template< typename InputIterator >
variant
try_parse_array(diagnostics& d, InputIterator& i, InputIterator last)
{
auto loc = location();
if (*i != '[') {
d.error(location(), "expected '['");
return variant::undefined;
}
advance(i, last); // skip '['
skip_whitespace(i, last);
if (i == last) {
d.error(location(), "premature end");
return variant::undefined;
}
if (*i == ']') {
advance(i, last);
return variant{ std::move(loc), std::vector< variant >{} };
}
std::vector< variant > a;
for (;;) {
auto v = try_parse_any(d, i, last);
if (d.errors().size() > 0)
return variant::undefined;
a.emplace_back(std::move(v));
skip_whitespace(i, last);
if (i == last) {
d.error(location(), "premature end");
return variant::undefined;
}
if (',' != *i)
break;
advance(i, last);
skip_whitespace(i, last);
}
if (i == last) {
d.error(location(), "premature end");
return variant::undefined;
}
if (*i != ']') {
d.error(location(), "expected ']'");
return variant::undefined;
}
advance(i, last);
return variant{ std::move(loc), std::move(a) };
}
template< typename InputIterator >
variant
try_parse_boolean(diagnostics& d, InputIterator& i, InputIterator last)
{
auto loc = location();
if (*i == 't') {
advance(i, last);
if (i == last) {
d.error(location(), "premature end");
return variant::undefined;
}
if (*i != 'r') {
d.error(location(), "unexpected character");
return variant::undefined;
}
advance(i, last); // skip 'r'
if (i == last) {
d.error(location(), "premature end");
return variant::undefined;
}
if (*i != 'u') {
d.error(location(), "unexpected character");
return variant::undefined;
}
advance(i, last); // skip 'u'
if (i == last) {
d.error(location(), "premature end");
return variant::undefined;
}
if (*i != 'e') {
d.error(location(), "unexpected character");
return variant::undefined;
}
advance(i, last); // skip 'e'
return variant{ std::move(loc), true };
}
if (*i != 'f')
return {};
advance(i, last);
if (i == last || *i != 'a')
return {};
advance(i, last);
if (i == last || *i != 'l')
return {};
advance(i, last);
if (i == last || *i != 's')
return {};
advance(i, last);
if (i == last || *i != 'e')
return {};
advance(i, last);
return variant{ std::move(loc), false };
}
template< typename InputIterator >
variant
try_parse_number(diagnostics& d, InputIterator& i, InputIterator last)
{
auto loc = location();
std::string buf;
buf.reserve(std::numeric_limits< long long >::digits10 * 2);
bool is_signed{ false };
if (i != last && *i == '-') {
buf.push_back(*i++);
is_signed = true;
}
if (i == last) {
d.error(location(), "premature end");
return variant::undefined;
}
// int
if (*i == '0') {
buf.push_back(*i++);
}
else if ('1' <= *i && *i <= '9') {
do {
buf.push_back(*i++);
} while (i != last && '0' <= *i && *i <= '9');
}
else {
d.error(location(), "unexpected character");
return variant::undefined;
}
bool is_real{ false };
// frac
if (i != last && *i == '.') {
is_real = true;
buf.push_back(*i++);
if (i == last) {
d.error(location(), "premature end");
return variant::undefined;
}
if (*i < '0' || '9' < *i) {
d.error(location(), "unexpected character");
return variant::undefined;
}
do {
buf.push_back(*i++);
} while (i != last && '0' <= *i && *i <= '9');
}
// exp
if (i != last && (*i == 'e' || *i == 'E')) {
is_real = true;
buf.push_back(*i++);
if (i != last && (*i == '-' || *i == '+'))
buf.push_back(*i++);
if (i == last) {
d.error(location(), "premature end");
return variant::undefined;
}
if (*i < '0' || '9' < *i) {
d.error(location(), "unexpected character");
return variant::undefined;
}
do {
buf.push_back(*i++);
} while (i != last && '0' <= *i && *i <= '9');
}
try {
if (is_real) {
return variant{ loc, std::stold(buf) };
}
if (is_signed) {
return variant{ loc, std::stoll(buf) };
}
return variant{ loc, std::stoull(buf) };
}
catch (std::out_of_range const&) {
d.error(loc, "number too large");
}
return variant::undefined;
}
template< typename InputIterator >
variant
try_parse_object(diagnostics& d, InputIterator& i, InputIterator last)
{
auto loc = location();
if (*i != '{')
return {};
advance(i, last);
skip_whitespace(i, last);
if (i == last)
return {};
if (*i == '}') {
advance(i, last);
return std::map< std::string, variant >{};
}
std::map< std::string, variant > o;
for (;;) {
auto k = try_parse_string(d, i, last);
if (d.errors().size() > 0)
return {};
skip_whitespace(i, last);
if (i == last || *i != ':')
return {};
advance(i, last);
skip_whitespace(i, last);
if (i == last) {
d.error(location(), "unexpected end");
return {};
}
auto v = try_parse_any(d, i, last);
if (d.errors().size() > 0)
return {};
variant v_v{ std::move(v) };
o.emplace(k.get_string(), std::move(v_v));
skip_whitespace(i, last);
if (i == last)
return {};
if (',' != *i)
break;
advance(i, last);
skip_whitespace(i, last);
}
if (i == last || *i != '}')
return {};
advance(i, last);
return variant{ std::move(loc), std::move(o) };
}
template< typename InputIterator >
variant
try_parse_string(diagnostics& d, InputIterator& i, InputIterator last)
{
auto loc = location();
std::string text;
if (*i != '"') {
d.error(location(), "unexpected character");
return variant::undefined;
}
advance(i, last);
if (i == last) {
d.error(location(), "premature end");
return variant::undefined;
}
while (i != last && *i != '"') {
if (*i == '\\') { // Parse escape sequence.
advance(i, last); // skip '\'
if (i == last) {
d.error(location(), "premature end");
return variant::undefined;
}
switch (*i) {
case '"':
text += '\x22';
advance(i, last);
break;
case '\\':
text += '\x5c';
advance(i, last);
break;
case '/':
text += '\x2f';
advance(i, last);
break;
case 'b':
text += '\x62';
advance(i, last);
break;
case 'f':
text += '\x66';
advance(i, last);
break;
case 'n':
text += '\x6e';
advance(i, last);
break;
case 'r':
text += '\x72';
advance(i, last);
break;
case 't':
text += '\x74';
advance(i, last);
break;
case 'u': {
advance(i, last); // skip u
std::uint32_t u[4];
for (int index = 0; index < 4; ++index, advance(i, last)) {
if (i == last) {
d.error(location(), "premature end");
return variant::undefined;
}
std::uint32_t c = (unsigned char)*i;
if (U'0' <= c && c <= U'9') {
u[index] = c - U'0';
}
else if (U'a' <= c && c <= U'f') {
u[index] = (c - U'a') + 10;
}
else if (U'A' <= c && c <= U'F') {
u[index] = (c - U'A') + 10;
}
else {
d.error(location(), "expected digit");
return variant::undefined;
}
}
std::uint32_t utf32 = u[0] << 12 | u[1] << 8 | u[2] << 4 | u[3];
if (0xdc00 <= utf32 && utf32 <= 0xdfff) {
d.error(location(), "invalid unicode code point");
return variant::undefined;
}
if (0xd800 <= utf32 && utf32 <= 0xdbff) {
// We found a high surrogate, expect a following low-surrogate.
auto high = utf32;
if (i == last) {
d.error(location(), "premature end");
return variant::undefined;
}
if (*i != '\\') {
d.error(location(), "unexpected character");
return variant::undefined;
}
advance(i, last); // skip backslash
if (i == last) {
d.error(location(), "premature end");
return variant::undefined;
}
if (*i != 'u') {
d.error(location(), "unexpected character2");
return variant::undefined;
}
advance(i, last); // skip 'u'
for (int index = 0; index < 4; ++index, advance(i, last)) {
if (i == last) {
d.error(location(), "premature end");
return variant::undefined;
}
std::uint32_t c = (unsigned char)*i;
if (U'0' <= c && c <= U'9') {
u[index] = c - U'0';
}
else if (U'a' <= c && c <= U'f') {
u[index] = (c - U'a') + 10;
}
else if (U'A' <= c && c <= U'F') {
u[index] = (c - U'A') + 10;
}
else {
d.error(location(), "expected digit");
return {};
}
}
std::uint32_t low = u[0] << 12 | u[1] << 8 | u[2] << 4 | u[3];
utf32 = (high << 10) + low - 0x35fdc00;
}
std::string str;
unicode::string_writer_t writer{str};
unicode::utf8_encoder_t encoder{writer};
encoder.encode(utf32);
text += str;
break;
}
}
continue;
}
if (is_control(i)) {
d.error(location(), "invalid character detected");
return variant::undefined;
}
text += *i++;
}
if (i == last) {
d.error(location(), "premature end");
return variant::undefined;
}
if (*i != '"') {
d.error(location(), "unexpected character");
return variant::undefined;
}
advance(i, last);
return variant{ std::move(loc), std::move(text) };
}
template< typename InputIterator >
variant
try_parse_any(diagnostics& d, InputIterator& i, InputIterator last)
{
auto loc = location();
if (i == last) {
d.error(loc, "premature end");
return variant::undefined;
}
switch (*i) {
case 'n':
return try_parse_undefined(d, i, last);
case '[':
return try_parse_array(d, i, last);
case 't':
case 'f':
return try_parse_boolean(d, i, last);
case '-':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
return try_parse_number(d, i, last);
case '{':
return try_parse_object(d, i, last);
case '"':
return try_parse_string(d, i, last);
}
std::ostringstream str;
str << "unexpected character '" << *i << "'";
d.error(loc, str.str());
return variant::undefined;
}
template< typename InputIterator >
void
skip_whitespace(InputIterator& i, InputIterator last)
{
while (i != last && is_whitespace< InputIterator >(i))
advance(i, last);
}
template< typename InputIterator >
bool
is_whitespace(InputIterator const& c)
{
switch (*c) {
case 0x09: // tab
case 0x0a: // lf
case 0x0d: // cr
case 0x20: // space
return true;
}
return false;
}
template< typename InputIterator >
bool
is_control(InputIterator const& c)
{
switch (*c) {
case '\x00':
case '\x01':
case '\x02':
case '\x03':
case '\x04':
case '\x05':
case '\x06':
case '\x07':
case '\x08':
case '\x09':
case '\x0A':
case '\x0B':
case '\x0C':
case '\x0D':
case '\x0E':
case '\x0F':
case '\x10':
case '\x11':
case '\x12':
case '\x13':
case '\x14':
case '\x15':
case '\x16':
case '\x17':
case '\x18':
case '\x19':
case '\x1A':
case '\x1B':
case '\x1C':
case '\x1D':
case '\x1E':
case '\x1F':
return true;
}
return false;
}
diagnostics::location
location()
{
return diagnostics::location{ line_, column_ };
}
template< typename InputIterator >
void
advance(InputIterator& i, InputIterator last)
{
if (i == last)
return;
if (*i == '\r') {
++line_;
column_ = 1;
++i;
if (i != last && *i == '\n')
++i;
return;
}
if (*i == '\n') {
++line_;
column_ = 1;
++i;
return;
}
++column_;
++i;
}
std::uint32_t line_{ 1 };
std::uint32_t column_{ 1 };
};
} // namespace art::json
#endif

163
art/json/pointer.cxx Normal file
View File

@@ -0,0 +1,163 @@
#include <art/json/pointer.hxx>
namespace art::json {
pointer::
pointer(std::string const& expression)
: refs_{parse_refs(expression)}
{}
optional< variant >
pointer::
read(variant const& source) const
{
auto const* current = &source;
auto refs = refs_;
while (refs.size() > 0) {
auto cref = refs.front();
refs.pop();
if (current->is_object()) {
if (!current->contains(cref))
return std::nullopt;
current = &current->get(cref);
}
else if (current->is_array()) {
if (cref.size() == 1 && '~' == cref[0])
throw invalid_array_index{ cref };
auto i = std::stoul(cref);
if (current->size() <= i)
throw invalid_array_index{ cref };
current = &current->get(i);
}
else {
throw unexpected_type{};
}
}
return *current;
}
void
pointer::
write(variant& target, variant value) const
{
if (!target.is_object())
throw std::invalid_argument{"expected object"};
auto* current = &target;
auto refs = refs_;
while (refs.size() > 1) {
auto cref = refs.front();
refs.pop();
// Check what current is.
if (current->is_object()) {
if (current->contains(cref)) {
current = &current->get(cref);
}
else {
current->set(cref, std::map< std::string, variant >{});
current = &current->get(cref);
}
}
else if (current->is_array()) {
if (cref.size() == 1 && '~' == cref[0])
throw invalid_array_index{ cref };
auto i = std::stoul(cref);
if (current->size() <= i)
throw invalid_array_index{ cref };
current = &current->get(i);
}
else {
throw unexpected_type{};
}
}
if (refs.empty())
throw std::invalid_argument{"invalid argument"};
auto cref = refs.front();
refs.pop();
if (current->is_object()) {
current->set(cref, value);
}
else if (current->is_array()) {
// TODO: Implement.
}
else {
throw unexpected_type{};
}
}
std::queue< std::string >
pointer::
parse_refs(std::string const& expression)
{
std::queue< std::string > refs;
for (auto it = std::begin(expression); it != std::end(expression);) {
if ('/' != *it)
throw invalid_syntax{};
++it;
std::string cref;
while (it != std::end(expression) && '/' != *it) {
switch (*it) {
case '~':
++it; // skip tilde
if (it == std::end(expression))
throw invalid_syntax{};
else if ('0' == *it)
cref += '~';
else if ('1' == *it)
cref += '/';
else
throw invalid_syntax{};
++it;
break;
default:
cref += *it;
++it;
break;
}
}
if (!cref.empty())
refs.push(std::move(cref));
}
return refs;
}
std::string
to_string(pointer const& ptr)
{
std::string rendered;
auto refs = ptr.refs_;
while (refs.size() > 0) {
auto cref = refs.front();
refs.pop();
rendered += '/';
rendered += cref;
}
return rendered;
}
} // namespace art::json

41
art/json/pointer.hxx Normal file
View File

@@ -0,0 +1,41 @@
#ifndef art__json__pointer_hxx_
#define art__json__pointer_hxx_
#include <art/json/exception.hxx>
#include <art/json/optional.hxx>
#include <art/json/read.hxx>
#include <art/json/variant.hxx>
#include <queue>
#include <string>
namespace art::json {
class pointer {
public:
explicit
pointer(std::string const& expression);
optional< variant >
read(variant const& source) const;
void
write(variant& target, variant value) const;
friend
std::string
to_string(pointer const& ptr);
private:
std::queue< std::string >
parse_refs(std::string const& expression);
std::queue< std::string > refs_;
};
std::string
to_string(pointer const& ptr);
} // namespace art::json
#endif

95
art/json/pointer.test.cxx Normal file
View File

@@ -0,0 +1,95 @@
#include <art/json/pointer.hxx>
#include <string>
#include <iostream>
#define DEFINE_TEST(x) std::cout << x << '\n';
#define TEST_TRUE(x) if (!(x)) return __LINE__;
#define TEST_EQUAL(x, y) if ((x) != (y)) return __LINE__;
int
main()
{
auto resolve = [](std::string const& data, char const* path)
{
art::json::pointer ptr{std::string{path}};
return ptr.read(art::json::read(data));
};
DEFINE_TEST("simple object")
{
std::string json = R"JSON(
{ "haystack": "needle" }
)JSON";
auto variant = resolve(json, "/haystack");
TEST_TRUE(variant->is_string());
TEST_EQUAL(variant->get_string(), "needle");
}
DEFINE_TEST("simple array")
{
std::string json = R"JSON(
[ "needle" ]
)JSON";
auto variant = resolve(json, "/0");
TEST_TRUE(variant->is_string());
TEST_EQUAL(variant->get_string(), "needle");
}
DEFINE_TEST("complex")
{
std::string json = R"JSON(
{
"employees": [
{
"name": "Doe, John",
"departments": ["HQ"],
"age": 37
},
{
"name": "Doe, Jane",
"departments": ["Software", "Finance"],
"age": 29
}
]
}
)JSON";
auto emp_0_name = resolve(json, "/employees/0/name");
auto emp_0_department_0 = resolve(json, "/employees/0/departments/0");
auto emp_0_age = resolve(json, "/employees/0/age");
auto emp_1_name = resolve(json, "/employees/1/name");
auto emp_1_department_0 = resolve(json, "/employees/1/departments/0");
auto emp_1_department_1 = resolve(json, "/employees/1/departments/1");
auto emp_1_age = resolve(json, "/employees/1/age");
TEST_TRUE(emp_0_name->is_string());
TEST_TRUE(emp_0_department_0->is_string());
TEST_TRUE(emp_0_age->is_number());
TEST_TRUE(emp_0_age->is_unsigned());
TEST_TRUE(emp_1_name->is_string());
TEST_TRUE(emp_1_department_0->is_string());
TEST_TRUE(emp_1_department_1->is_string());
TEST_TRUE(emp_1_age->is_number());
TEST_TRUE(emp_1_age->is_unsigned());
TEST_EQUAL(emp_0_name->get_string(), "Doe, John");
TEST_EQUAL(emp_0_department_0->get_string(), "HQ");
TEST_EQUAL(emp_0_age->get_number< signed int >(), 37);
TEST_EQUAL(emp_0_age->get_number< unsigned int >(), 37);
TEST_EQUAL(emp_1_name->get_string(), "Doe, Jane");
TEST_EQUAL(emp_1_department_0->get_string(), "Software");
TEST_EQUAL(emp_1_department_1->get_string(), "Finance");
TEST_EQUAL(emp_1_age->get_number< signed int >(), 29);
TEST_EQUAL(emp_1_age->get_number< unsigned int >(), 29);
}
return 0;
}

74
art/json/read.hxx Normal file
View File

@@ -0,0 +1,74 @@
#ifndef art__json__read_hxx_
#define art__json__read_hxx_
#include <art/json/optional.hxx>
#include <art/json/parser.hxx>
#include <art/json/variant.hxx>
#include <istream>
#include <iterator>
namespace art::json {
inline variant
read(std::string const& input)
{
diagnostics d;
auto first = input.begin();
return parser{}.parse(d, first, input.end());
}
inline variant
read(diagnostics& d, std::string const& input)
{
auto first = input.begin();
return parser{}.parse(d, first, input.end());
}
inline optional< variant >
try_read(diagnostics& d, std::string const& input)
{
auto first = input.begin();
return parser{}.try_parse(d, first, input.end());
}
inline optional< variant >
try_read(std::string const& input)
{
diagnostics d;
auto first = input.begin();
return parser{}.try_parse(d, first, input.end());
}
inline variant
read(diagnostics& d, std::istream& input)
{
input.unsetf(std::istream::skipws);
std::istreambuf_iterator< char > first{ input };
return parser{}.parse(d, first, std::istreambuf_iterator< char >{});
}
inline variant
read(std::istream& input)
{
diagnostics d;
return read(d, input);
}
inline optional< variant >
try_read(std::istream& input)
{
input.unsetf(std::istream::skipws);
diagnostics d;
std::istreambuf_iterator< char > first{ input };
return parser{}.try_parse(d, first, std::istreambuf_iterator< char >{});
}
} // namespace art::json
#endif

607
art/json/read.test.cxx Normal file
View File

@@ -0,0 +1,607 @@
#include <art/json/parser.hxx>
#include <art/json/read.hxx>
#include <iostream>
#define DEFINE_TEST(x) std::cout << x << '\n';
#define TEST_TRUE(x) if (!(x)) return __LINE__;
#define TEST_FALSE(x) if ((x)) return __LINE__;
#define TEST_EQUAL(x, y) if ((x) != (y)) return __LINE__;
int
main()
{
DEFINE_TEST("boolean: parse false")
{
std::string const text{ "false" };
art::json::diagnostics diag;
auto var = art::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_boolean());
TEST_EQUAL(var.get_boolean(), false);
TEST_EQUAL(var.location().line, 1);
TEST_EQUAL(var.location().column, 1);
}
DEFINE_TEST("boolean: parse false, with whitespace")
{
std::string const text{ " false " };
art::json::diagnostics diag;
auto var = art::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_boolean());
TEST_EQUAL(var.get_boolean(), false);
TEST_EQUAL(var.location().line, 1);
TEST_EQUAL(var.location().column, 2);
}
DEFINE_TEST("boolean: parse true")
{
std::string const text{ "true" };
art::json::diagnostics diag;
auto var = art::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_boolean());
TEST_EQUAL(var.get_boolean(), true);
}
DEFINE_TEST("boolean: parse true, with whitespace")
{
std::string const text{ " true " };
art::json::diagnostics diag;
auto var = art::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_boolean());
TEST_EQUAL(var.get_boolean(), true);
TEST_EQUAL(var.location().line, 1);
TEST_EQUAL(var.location().column, 2);
}
DEFINE_TEST("null: parse null")
{
std::string const text{ "null" };
art::json::diagnostics diag;
auto var = art::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_undefined());
TEST_EQUAL(var.location().line, 1);
TEST_EQUAL(var.location().column, 1);
}
DEFINE_TEST("null: parse null, with whitespace")
{
std::string const text{ " null " };
art::json::diagnostics diag;
auto var = art::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_undefined());
TEST_EQUAL(var.location().line, 1);
TEST_EQUAL(var.location().column, 3);
}
DEFINE_TEST("array: parse empty")
{
std::string const text{ "[]" };
art::json::diagnostics diag;
auto var = art::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_array());
TEST_TRUE(var.empty());
TEST_EQUAL(var.location().line, 1);
TEST_EQUAL(var.location().column, 1);
}
DEFINE_TEST("array: parse empty, with whitespace")
{
std::string const text{ " [ ] " };
art::json::diagnostics diag;
auto var = art::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_array());
TEST_TRUE(var.empty());
TEST_EQUAL(var.location().line, 1);
TEST_EQUAL(var.location().column, 2);
}
DEFINE_TEST("array: parse null element")
{
std::string const text{ "[null]" };
art::json::diagnostics diag;
auto var = art::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_array());
TEST_EQUAL(var.location().line, 1);
TEST_EQUAL(var.location().column, 1);
TEST_FALSE(var.empty());
TEST_EQUAL(var.size(), 1);
TEST_TRUE(var.get(0).is_undefined());
TEST_EQUAL(var.get(0).location().line, 1);
TEST_EQUAL(var.get(0).location().column, 2);
}
DEFINE_TEST("array: parse null element, with whitespace")
{
std::string const text{ " [ null ] " };
art::json::diagnostics diag;
auto var = art::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_array());
TEST_FALSE(var.empty());
TEST_EQUAL(var.size(), 1);
TEST_TRUE(var.get(0).is_undefined());
TEST_EQUAL(var.get(0).location().line, 1);
TEST_EQUAL(var.get(0).location().column, 4);
}
DEFINE_TEST("array: parse double null elements")
{
std::string const text{ "[null,null]" };
art::json::diagnostics diag;
auto var = art::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_array());
TEST_FALSE(var.empty());
TEST_EQUAL(var.size(), 2);
TEST_TRUE(var.get(0).is_undefined());
TEST_EQUAL(var.get(0).location().line, 1);
TEST_EQUAL(var.get(0).location().column, 2);
TEST_TRUE(var.get(1).is_undefined());
TEST_EQUAL(var.get(1).location().line, 1);
TEST_EQUAL(var.get(1).location().column, 7);
}
DEFINE_TEST("array: parse double null elements, with whitespace")
{
std::string const text{ " [ null , null ] " };
art::json::diagnostics diag;
auto var = art::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE( var.is_array());
TEST_FALSE(var.empty());
TEST_EQUAL(var.size(), 2);
TEST_TRUE( var.get(0).is_undefined());
TEST_EQUAL(var.get(0).location().line, 1);
TEST_EQUAL(var.get(0).location().column, 4);
TEST_TRUE( var.get(1).is_undefined());
TEST_EQUAL(var.get(1).location().line, 1);
TEST_EQUAL(var.get(1).location().column, 11);
}
DEFINE_TEST("array: parse nested")
{
std::string const text{ " [ [ null , null ] , [ null ] ]" };
art::json::diagnostics diag;
auto var = art::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_array());
TEST_FALSE(var.empty());
TEST_EQUAL(var.size(), 2);
TEST_TRUE(var.get(0).is_array());
TEST_FALSE(var.get(0).empty());
TEST_EQUAL(var.get(0).size(), 2);
TEST_TRUE(var.get(0).get(0).is_undefined());
TEST_TRUE(var.get(0).get(1).is_undefined());
TEST_TRUE(var. get(1).is_array());
TEST_FALSE(var.get(1).empty());
TEST_EQUAL(var.get(1).size(), 1);
TEST_TRUE(var. get(1).get(0).is_undefined());
}
DEFINE_TEST("object: parse empty")
{
std::string const text{ "{}" };
art::json::diagnostics diag;
auto var = art::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_object());
TEST_TRUE(var.keys().empty());
}
DEFINE_TEST("object: parse empty, with whitespace")
{
std::string const text{ " { } " };
art::json::diagnostics diag;
auto var = art::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_object());
TEST_EQUAL(var.keys().size(), 0UL);
}
DEFINE_TEST("object: parse 1-element object")
{
std::string const text{ "{\"key\":null}" };
art::json::diagnostics diag;
auto var = art::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_object());
TEST_EQUAL(var.keys().size(), 1UL);
TEST_TRUE(var.get("key").is_undefined());
}
DEFINE_TEST("object: parse 1-element object (with whitespace)")
{
std::string const text{ " { \"key\" : null } " };
art::json::diagnostics diag;
auto var = art::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_object());
TEST_EQUAL(var.keys().size(), 1UL);
TEST_TRUE(var.get("key").is_undefined());
}
DEFINE_TEST("object: parse 2-element object")
{
std::string const text{ "{\"key1\":null,\"key2\":null}" };
art::json::diagnostics diag;
auto var = art::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_object());
TEST_EQUAL(var.keys().size(), 2UL);
TEST_TRUE(var.get("key1").is_undefined());
TEST_TRUE(var.get("key2").is_undefined());
}
DEFINE_TEST("object: parse 2-element object (with whitespace)")
{
std::string const text{ " { \"key1\" : null, \"key2\" : null } " };
art::json::diagnostics diag;
auto var = art::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_object());
TEST_EQUAL(var.keys().size(), 2UL);
TEST_TRUE(var.get("key1").is_undefined());
TEST_TRUE(var.get("key2").is_undefined());
}
DEFINE_TEST("object: parse nested object")
{
std::string const text{ R"#({
"k1": {
"k1": null
},
"k2": {
"k1": null,
"k2": null
}
})#" };
art::json::diagnostics diag;
auto var = art::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_object());
TEST_EQUAL(var.keys().size(), 2UL);
// Nested object 1.
auto object1 = var.get("k1");
TEST_TRUE(object1.is_object());
TEST_EQUAL(object1.keys().size(), 1UL);
TEST_TRUE(object1.get("k1").is_undefined());
// Nested object 2.
auto object2 = var.get("k2");
TEST_TRUE(object2.is_object());
TEST_EQUAL(object2.keys().size(), 2UL);
TEST_TRUE(object2.get("k1").is_undefined());
TEST_TRUE(object2.get("k2").is_undefined());
}
DEFINE_TEST("number: integer: 0")
{
std::string const text{ "0" };
art::json::diagnostics diag;
auto var = art::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_FALSE(var.is_signed());
TEST_TRUE(var.is_unsigned());
TEST_EQUAL(var.get_number< unsigned long long >(), 0);
}
DEFINE_TEST("number: integer: 1")
{
std::string const text{ "1" };
art::json::diagnostics diag;
auto var = art::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_FALSE(var.is_signed());
TEST_TRUE(var.is_unsigned());
TEST_EQUAL(var.get_number< unsigned long long >(), 1);
}
DEFINE_TEST("number: integer: -1")
{
std::string const text{ "-1" };
art::json::diagnostics diag;
auto var = art::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_signed());
TEST_FALSE(var.is_unsigned());
TEST_EQUAL(var.get_number< long long >(), -1);
}
DEFINE_TEST("number: integer: 18446744073709551615")
{
std::string const text{ "18446744073709551615" };
art::json::diagnostics diag;
auto var = art::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_unsigned());
TEST_FALSE(var.is_signed());
TEST_EQUAL(var.get_number< unsigned long long >(), 18446744073709551615ULL);
}
DEFINE_TEST("number: real: 0.0")
{
std::string const text{ "0.0" };
art::json::diagnostics diag;
auto var = art::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_real());
TEST_EQUAL(var.get_number< double >(), 0.0);
}
DEFINE_TEST("number: real: 1.0")
{
std::string const text{ "1.0" };
art::json::diagnostics diag;
auto var = art::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_real());
TEST_EQUAL(var.get_number< double >(), 1.0);
}
DEFINE_TEST("number: real: 1e3")
{
std::string const text{ "1e3" };
art::json::diagnostics diag;
auto var = art::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_real());
TEST_EQUAL(var.get_number< double >(), 1000.0);
}
DEFINE_TEST("number: real: 1E3")
{
std::string const text{ "1E3" };
art::json::diagnostics diag;
auto var = art::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_real());
TEST_EQUAL(var.get_number< double >(), 1000.0);
}
DEFINE_TEST("number: real: 1e+3")
{
std::string const text{ "1e+3" };
art::json::diagnostics diag;
auto var = art::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_real());
TEST_EQUAL(var.get_number< double >(), 1000.0);
}
DEFINE_TEST("number: real: 1E+3")
{
std::string const text{ "1E+3" };
art::json::diagnostics diag;
auto var = art::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_real());
TEST_EQUAL(var.get_number< double >(), 1000.0);
}
DEFINE_TEST("number: real: 1e-3")
{
std::string const text{ "1e-3" };
art::json::diagnostics diag;
auto var = art::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_real());
TEST_EQUAL(var.get_number< double >(), 0.001);
}
DEFINE_TEST("number: real: 1E-3")
{
std::string const text{ "1E-3" };
art::json::diagnostics diag;
auto var = art::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_real());
TEST_EQUAL(var.get_number< double >(), 0.001);
}
DEFINE_TEST("string: empty string")
{
art::json::diagnostics diag;
auto var = art::json::read(R"("")");
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_string());
TEST_EQUAL(var.get_string(), "");
}
DEFINE_TEST("string: escape sequences")
{
std::pair< std::string, std::string > escapes[] = {
{ R"("\"")", "\x22" }, // " (quotation mark)
{ R"("\\")", "\x5C" }, // \ (reverse solidus)
{ R"("\/")", "\x2F" }, // / (solidus)
{ R"("\b")", "\x62" }, // b (backspace)
{ R"("\f")", "\x66" }, // f (form feed)
{ R"("\n")", "\x6E" }, // n (line feed)
{ R"("\r")", "\x72" }, // r (carriage return)
{ R"("\t")", "\x74" } // t (tab)
};
for (auto const& esc : escapes) {
art::json::diagnostics diag;
auto var = art::json::read(esc.first);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_string());
TEST_EQUAL(var.get_string(), esc.second);
}
}
DEFINE_TEST("string: utf-16 escape sequences")
{
std::pair< std::string, std::string > escapes[]{
{ R"("{\ud834\udd1e}")", "{\xf0\x9d\x84\x9e}" }, // MUSICAL SYMBOL G CLEF
};
for (auto const& escape : escapes) {
std::string text = escape.first;
art::json::diagnostics diag;
auto var = art::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_string());
TEST_EQUAL(var.get_string(), escape.second);
}
}
return 0;
}

23
art/json/resolve.test.cxx Normal file
View File

@@ -0,0 +1,23 @@
#include <art/json/pointer.hxx>
#include <art/json/write.hxx>
#include <string>
#include <iostream>
int
main(int argc, char* argv[])
{
#if 0
std::stringstream str;
str << std::cin.rdbuf();
auto var = art::json::read(str.str());
std::cout << art::json::write(var);
art::json::resolve(str.str(), argv[1]);
return 0;
#endif
}

84
art/json/serialize.hxx Normal file
View File

@@ -0,0 +1,84 @@
#ifndef art__json__serialize_hxx_
#define art__json__serialize_hxx_
#include <art/json/marshaling.hxx>
#include <art/json/read.hxx>
#include <art/json/write.hxx>
#include <istream>
namespace art::json
{
template<typename T>
void
serialize(std::ostream& o, T const& model)
{
write(o, marshal(model));
}
template<typename T>
std::string
serialize(T const& model)
{
std::ostringstream str;
serialize(str, model);
return str.str();
}
template<typename T>
T
deserialize(diagnostics& d,
std::istream& i,
marshaling_context_t* context = nullptr)
{
return unmarshal<T>(read(d, i), context);
}
template<typename T>
T
deserialize(std::istream& i, marshaling_context_t* context = nullptr)
{
return unmarshal<T>(read(i), context);
}
template<typename T>
T
deserialize(diagnostics& d,
std::istream&& i,
marshaling_context_t* context = nullptr)
{
return deserialize<T>(d, i, context);
}
template<typename T>
T
deserialize(std::istream&& i, marshaling_context_t* context = nullptr)
{
return deserialize<T>(i, context);
}
template<typename T>
T
deserialize(diagnostics& d,
std::string const& str,
marshaling_context_t* context = nullptr)
{
// TODO use std::string overload of read
return deserialize<T>(
d, std::istringstream{ str, std::ios::in | std::ios::binary }, context);
}
template<typename T>
T
deserialize(std::string const& str, marshaling_context_t* context = nullptr)
{
// TODO use std::string overload of read
return deserialize<T>(
std::istringstream{ str, std::ios::in | std::ios::binary }, context);
}
} // namespace art::json
#endif

View File

@@ -0,0 +1,53 @@
#include <art/json/marshaling.hxx>
#include <art/json/serialize.hxx>
#include <iostream>
#define DEFINE_TEST(x) std::cout << x << '\n';
#define TEST_TRUE(x) if (!(x)) return __LINE__;
#define TEST_FALSE(x) if ((x)) return __LINE__;
#define TEST_EQUAL(x, y) if ((x) != (y)) return __LINE__;
using art::json::mapping_t;
using art::json::member_t;
using art::json::serialize;
using art::json::deserialize;
struct person_name {
std::string first;
std::string last;
using json = mapping_t<
member_t<"first", &person_name::first>,
member_t<"last", &person_name::last>
>;
};
struct person {
person_name name;
int age;
using json = mapping_t<
member_t<"name", &person::name>,
member_t<"age", &person::age>
>;
};
int
main()
{
DEFINE_TEST("round trip")
{
person p1{{"Jane", "Doe"}, 37};
auto p2 = deserialize<person>(serialize(p1));
TEST_EQUAL(p1.name.first, p2.name.first);
TEST_EQUAL(p1.name.last, p2.name.last);
TEST_EQUAL(p1.age, p2.age);
}
return 0;
}

72
art/json/traits.hxx Normal file
View File

@@ -0,0 +1,72 @@
#ifndef art__json__traits_hxx_
#define art__json__traits_hxx_
#include <art/json/optional.hxx>
#include <type_traits>
namespace art::json {
// is_optional<T>.
//
template< typename >
struct is_optional : std::false_type {
};
template< typename T >
struct is_optional< optional< T > > : std::true_type {
};
template< typename T >
constexpr bool is_optional_v = is_optional< T >::value;
// function_traits<T>.
//
template< typename, typename = std::void_t< > >
struct function_traits;
template< typename Ret, typename... Args >
struct function_traits< Ret(Args...) > {
static constexpr std::size_t arity = sizeof...(Args);
using return_type = std::decay_t< Ret >;
using argument_tuple = std::tuple< std::decay_t< Args >... >;
};
template< typename Ret, typename Class, typename... Args >
struct function_traits< Ret (Class::*)(Args...) > {
static constexpr std::size_t arity = sizeof...(Args);
using return_type = std::decay_t< Ret >;
using class_type = Class;
using argument_tuple = std::tuple< std::decay_t< Args >... >;
};
template< typename Ret, typename Class, typename... Args >
struct function_traits< Ret (Class::*)(Args...) const > {
static constexpr std::size_t arity = sizeof...(Args);
using return_type = std::decay_t< Ret >;
using class_type = Class;
using argument_tuple = std::tuple< std::decay_t< Args >... >;
};
template< typename Ret, typename... Args >
struct function_traits< Ret(*)(Args...) > {
static constexpr std::size_t arity = sizeof...(Args);
using return_type = std::decay_t< Ret >;
using argument_tuple = std::tuple< std::decay_t< Args >... >;
};
template< typename Callable >
struct function_traits<
Callable,
std::void_t<
decltype(&Callable::operator())
>
>
: function_traits<decltype(&Callable::operator())> {
};
} // namespace art::json
#endif

285
art/json/variant.hxx Normal file
View File

@@ -0,0 +1,285 @@
#ifndef art__json__variant_hxx_
#define art__json__variant_hxx_
#include <art/json/diagnostics.hxx>
#include <art/json/exception.hxx>
#include <map>
#include <set>
#include <stdexcept>
#include <string>
#include <variant>
#include <vector>
class emitter;
namespace art::json {
//! Variant holding any valid JSON value.
class variant {
public:
//! Array iterator type.
using iterator = std::vector< variant >::iterator;
//! Array iterator type.
using const_iterator = std::vector< variant >::const_iterator;
//! Type used to denote an undefined variant.
struct undefined_t { };
static constexpr undefined_t undefined{};
//! Construct an undefined variant.
variant();
//! Construct an undefined variant.
variant(undefined_t);
//! Construct a variant holding a boolean value.
variant(bool value);
//! Construct a variant holding a signed number.
variant(short int value);
//! Construct a variant holding a signed number.
variant(int value);
//! Construct a variant holding a signed number.
variant(long int value);
//! Construct a variant holding a signed number.
variant(long long int value);
//! Construct a variant holding an unsigned number.
variant(unsigned short int value);
//! Construct a variant holding an unsigned number.
variant(unsigned int value);
//! Construct a variant holding an unsigned number.
variant(unsigned long int value);
//! Construct a variant holding an unsigned number.
variant(unsigned long long int value);
//! Construct a variant holding a real number.
variant(float value);
//! Construct a variant holding a real number.
variant(double value);
//! Construct a variant holding a real number.
variant(long double value);
//! Construct a variant holding a string.
variant(std::string value);
//! Construct a variant holding a string.
variant(char const* value);
//! Construct a variant holding an array.
variant(std::vector< variant > value);
//! Construct a variant holding an object.
variant(std::map< std::string, variant > value);
variant(diagnostics::location location, undefined_t);
variant(diagnostics::location location, bool value);
variant(diagnostics::location location, short int value);
variant(diagnostics::location location, int value);
variant(diagnostics::location location, long int value);
variant(diagnostics::location location, long long int value);
variant(diagnostics::location location, unsigned short int value);
variant(diagnostics::location location, unsigned int value);
variant(diagnostics::location location, unsigned long int value);
variant(diagnostics::location location, unsigned long long int value);
variant(diagnostics::location location, float value);
variant(diagnostics::location location, double value);
variant(diagnostics::location location, long double value);
variant(diagnostics::location location, std::string value);
variant(diagnostics::location location, char const* value);
variant(diagnostics::location location, std::vector< variant > value);
variant(diagnostics::location location, std::map< std::string, variant > value);
//! Check if this variant is undefined.
bool
is_undefined() const;
//! Check if this variant holds a boolean.
bool
is_boolean() const;
//! Get boolean value.
bool
get_boolean() const;
//! Check if this variant holds a number value.
bool
is_number() const;
//! Check if this variant holds a signed value.
bool
is_signed() const;
//! Check if this variant holds an unsigned value.
bool
is_unsigned() const;
//! Check if this variant holds a real value.
bool
is_real() const;
//! Get number value.
template< typename ArithmeticType >
ArithmeticType
get_number() const;
//! Check if this variant holds a string.
bool
is_string() const;
//! Get string value.
std::string const&
get_string() const;
//! Check if this variant holds an array.
bool
is_array() const;
//! Check if empty.
bool
empty() const;
//! Get array size.
std::size_t
size() const;
//! Get array iterator.
iterator
begin();
//! Get array iterator.
const_iterator
begin() const;
//! Get array iterator.
const_iterator
cbegin() const;
//! Get array iterator.
iterator
end();
//! Get array iterator.
const_iterator
end() const;
//! Get array iterator.
const_iterator
cend() const;
//! Get value at array index.
variant&
get(std::size_t index);
//! Get value at array index.
variant const&
get(std::size_t index) const;
//! Get value at array index.
variant const&
cget(std::size_t index) const;
//! Push value to back of array.
void
push_back(variant v);
//! Remove element from array.
void
erase(std::size_t index);
//! Check if this variant holds an object.
bool
is_object() const;
//! Check if object contains key.
bool
contains(std::string const& key) const;
//! Get object keys.
std::set< std::string >
keys() const;
//! Get object value.
variant&
get(std::string const& key);
//! Get object value.
variant const&
get(std::string const& key) const;
//! Get object value.
variant const&
cget(std::string const& key) const;
//! Set object value.
void
set(std::string const& key, variant v);
//! Remove element from object.
void
erase(std::string const& key);
//! Get the location of this value.
diagnostics::location const&
location() const;
template< typename Visitor >
friend void
visit(Visitor&& visitor, variant const& value)
{
std::visit(std::forward< Visitor >(visitor), value.value_);
}
private:
friend class emitter;
//! Internal variant type.
using variant_type = std::variant< undefined_t,
bool,
long long int,
unsigned long long int,
long double,
std::string,
std::vector< variant >,
std::map< std::string, variant > >;
//! Optional source location of this variant.
diagnostics::location location_;
//! The value.
variant_type value_;
};
} // namespace art::json
#include <art/json/variant.ixx>
#include <art/json/variant.txx>
#endif

409
art/json/variant.ixx Normal file
View File

@@ -0,0 +1,409 @@
namespace art::json {
inline variant::variant() : value_{ undefined_t{} }
{}
inline variant::variant(undefined_t) : value_{ undefined_t{} }
{}
inline variant::variant(bool value) : value_{ value }
{}
inline variant::variant(short int value)
: value_{ static_cast< long long int >(value) }
{}
inline variant::variant(int value)
: value_{ static_cast< long long int >(value) }
{}
inline variant::variant(long int value)
: value_{ static_cast< long long int >(value) }
{}
inline variant::variant(long long int value) : value_{ value }
{}
inline variant::variant(unsigned short int value)
: value_{ static_cast< unsigned long long int >(value) }
{}
inline variant::variant(unsigned int value)
: value_{ static_cast< unsigned long long int >(value) }
{}
inline variant::variant(unsigned long int value)
: value_{ static_cast< unsigned long long int >(value) }
{}
inline variant::variant(unsigned long long int value) : value_{ value }
{}
inline variant::variant(float value)
: value_{ static_cast< long double >(value) }
{}
inline variant::variant(double value)
: value_{ static_cast< long double >(value) }
{}
inline variant::variant(long double value) : value_{ value }
{}
inline variant::variant(std::string value) : value_{ value }
{}
inline variant::variant(char const* value) : value_{ std::string{ value } }
{}
inline variant::variant(std::vector< variant > value) : value_{ std::move(value) }
{}
inline variant::variant(std::map< std::string, variant > value) : value_{ std::move(value) }
{}
inline variant::variant(diagnostics::location location, undefined_t)
: location_{ std::move(location) }, value_{ undefined_t{} }
{}
inline variant::variant(diagnostics::location location, bool value)
: location_{ std::move(location) }, value_{ value }
{}
inline variant::variant(diagnostics::location location, short int value)
: location_{ std::move(location) },
value_{ static_cast< long long int >(value) }
{}
inline variant::variant(diagnostics::location location, int value)
: location_{ std::move(location) },
value_{ static_cast< long long int >(value) }
{}
inline variant::variant(diagnostics::location location, long int value)
: location_{ std::move(location) },
value_{ static_cast< long long int >(value) }
{}
inline variant::variant(diagnostics::location location, long long int value)
: location_{ std::move(location) }, value_{ value }
{}
inline variant::variant(diagnostics::location location,
unsigned short int value)
: location_{ std::move(location) },
value_{ static_cast< unsigned long long int >(value) }
{}
inline variant::variant(diagnostics::location location, unsigned int value)
: location_{ std::move(location) },
value_{ static_cast< unsigned long long int >(value) }
{}
inline variant::variant(diagnostics::location location, unsigned long int value)
: location_{ std::move(location) },
value_{ static_cast< unsigned long long int >(value) }
{}
inline variant::variant(diagnostics::location location,
unsigned long long int value)
: location_{ std::move(location) }, value_{ value }
{}
inline variant::variant(diagnostics::location location, float value)
: location_{ std::move(location) },
value_{ static_cast< long double >(value) }
{}
inline variant::variant(diagnostics::location location, double value)
: location_{ std::move(location) },
value_{ static_cast< long double >(value) }
{}
inline variant::variant(diagnostics::location location, long double value)
: location_{ std::move(location) }, value_{ value }
{}
inline variant::variant(diagnostics::location location, std::string value)
: location_{ std::move(location) }, value_{ std::move(value) }
{}
inline variant::variant(diagnostics::location location, char const* value)
: location_{ std::move(location) }, value_{ std::string{ value } }
{}
inline variant::variant(diagnostics::location location, std::vector< variant > value)
: location_{ std::move(location) }, value_{ std::move(value) }
{}
inline variant::variant(diagnostics::location location, std::map< std::string, variant > value)
: location_{ std::move(location) }, value_{ std::move(value) }
{}
inline bool
variant::is_undefined() const
{
return std::holds_alternative< undefined_t >(value_);
}
inline bool
variant::is_boolean() const
{
return std::holds_alternative< bool >(value_);
}
inline bool
variant::get_boolean() const
{
if (std::holds_alternative< bool >(value_))
return std::get< bool >(value_);
throw invalid_type{};
}
inline bool
variant::is_number() const
{
return is_signed() || is_unsigned() || is_real();
}
inline bool
variant::is_signed() const
{
return std::holds_alternative< long long int >(value_);
}
inline bool
variant::is_unsigned() const
{
return std::holds_alternative< unsigned long long int >(value_);
}
inline bool
variant::is_real() const
{
return std::holds_alternative< long double >(value_);
}
inline bool
variant::is_string() const
{
return std::holds_alternative< std::string >(value_);
}
inline std::string const&
variant::get_string() const
{
if (std::holds_alternative< std::string >(value_))
return std::get< std::string >(value_);
throw invalid_type{};
}
inline bool
variant::is_array() const
{
return std::holds_alternative< std::vector< variant > >(value_);
}
inline bool
variant::empty() const
{
if (std::holds_alternative< std::vector< variant > >(value_))
return std::get< std::vector< variant > >(value_).empty();
throw invalid_type{};
}
inline std::size_t
variant::size() const
{
if (std::holds_alternative< std::vector< variant > >(value_))
return std::get< std::vector< variant > >(value_).size();
throw invalid_type{};
}
inline variant::iterator
variant::begin()
{
if (std::holds_alternative< std::vector< variant > >(value_))
return std::get< std::vector< variant > >(value_).begin();
throw invalid_type{};
}
inline variant::const_iterator
variant::begin() const
{
if (std::holds_alternative< std::vector< variant > >(value_))
return std::get< std::vector< variant > >(value_).begin();
throw invalid_type{};
}
inline variant::const_iterator
variant::cbegin() const
{
if (std::holds_alternative< std::vector< variant > >(value_))
return std::get< std::vector< variant > >(value_).cbegin();
throw invalid_type{};
}
inline variant::iterator
variant::end()
{
if (std::holds_alternative< std::vector< variant > >(value_))
return std::get< std::vector< variant > >(value_).end();
throw invalid_type{};
}
inline variant::const_iterator
variant::end() const
{
if (std::holds_alternative< std::vector< variant > >(value_))
return std::get< std::vector< variant > >(value_).end();
throw invalid_type{};
}
inline variant::const_iterator
variant::cend() const
{
if (std::holds_alternative< std::vector< variant > >(value_))
return std::get< std::vector< variant > >(value_).cend();
throw invalid_type{};
}
inline variant&
variant::get(std::size_t index)
{
if (std::holds_alternative< std::vector< variant > >(value_))
return std::get< std::vector< variant > >(value_)[index];
throw invalid_type{};
}
inline variant const&
variant::get(std::size_t index) const
{
if (std::holds_alternative< std::vector< variant > >(value_))
return std::get< std::vector< variant > >(value_)[index];
throw invalid_type{};
}
inline variant const&
variant::cget(std::size_t index) const
{
return get(index);
}
inline void
variant::push_back(variant v)
{
if (!std::holds_alternative< std::vector< variant > >(value_))
throw invalid_type{};
std::get< std::vector< variant > >(value_).push_back(std::move(v));
}
inline void
variant::erase(std::size_t index)
{
if (!std::holds_alternative< std::vector< variant > >(value_))
throw invalid_type{};
auto& v = std::get< std::vector< variant > >(value_);
if (index >= v.size())
throw std::out_of_range{"index"};
v.erase(v.begin() + index);
}
inline bool
variant::is_object() const
{
return std::holds_alternative< std::map< std::string, variant > >(value_);
}
inline bool
variant::contains(std::string const& key) const
{
if (std::holds_alternative< std::map< std::string, variant > >(value_))
return std::get< std::map< std::string, variant > >(value_).count(key) > 0;
throw invalid_type{};
}
inline std::set< std::string >
variant::keys() const
{
if (std::holds_alternative< std::map< std::string, variant > >(value_)) {
std::set< std::string > keys;
for (auto const& j : std::get< std::map< std::string, variant > >(value_))
keys.emplace(j.first);
return keys;
}
throw invalid_type{};
}
inline variant&
variant::get(std::string const& key)
{
if (std::holds_alternative< std::map< std::string, variant > >(value_))
return std::get< std::map< std::string, variant > >(value_).at(key);
throw invalid_type{};
}
inline variant const&
variant::get(std::string const& key) const
{
if (std::holds_alternative< std::map< std::string, variant > >(value_))
return std::get< std::map< std::string, variant > >(value_).at(key);
throw invalid_type{};
}
inline variant const&
variant::cget(std::string const& key) const
{
if (std::holds_alternative< std::map< std::string, variant > >(value_))
return std::get< std::map< std::string, variant > >(value_).at(key);
throw invalid_type{};
}
inline void
variant::set(std::string const& key, variant v)
{
if (!std::holds_alternative< std::map< std::string, variant > >(value_))
throw invalid_type{};
std::get< std::map< std::string, variant > >(value_)[key] = std::move(v);
}
inline void
variant::erase(std::string const& key)
{
if (!std::holds_alternative< std::map< std::string, variant > >(value_))
throw invalid_type{};
std::get< std::map< std::string, variant > >(value_).erase(key);
}
inline diagnostics::location const&
variant::location() const
{
return location_;
}
} // namespace art::json

19
art/json/variant.txx Normal file
View File

@@ -0,0 +1,19 @@
namespace art::json {
template< typename ArithmeticType >
ArithmeticType
variant::get_number() const
{
if constexpr (std::is_arithmetic_v< ArithmeticType >) {
if (std::holds_alternative< long long int >(value_))
return static_cast< ArithmeticType >(std::get< long long int >(value_));
else if (std::holds_alternative< unsigned long long int >(value_))
return static_cast< ArithmeticType >(std::get< unsigned long long int >(value_));
else if (std::holds_alternative< long double >(value_))
return static_cast< ArithmeticType >(std::get< long double >(value_));
}
throw invalid_type{};
}
} // namespace art::json

37
art/json/version.hxx.in Normal file
View File

@@ -0,0 +1,37 @@
#ifndef art__json__version_hxx_
#define art__json__version_hxx_
// The numeric version format is AAAAABBBBBCCCCCDDDE where:
//
// AAAAA - major version number
// BBBBB - minor version number
// CCCCC - bugfix version number
// DDD - alpha / beta (DDD + 500) version number
// E - final (0) / snapshot (1)
//
// When DDDE is not 0, 1 is subtracted from AAAAABBBBBCCCCC. For example:
//
// Version AAAAABBBBBCCCCCDDDE
//
// 0.1.0 0000000001000000000
// 0.1.2 0000000001000020000
// 1.2.3 0000100002000030000
// 2.2.0-a.1 0000200001999990010
// 3.0.0-b.2 0000299999999995020
// 2.2.0-a.1.z 0000200001999990011
//
#define LIBART_JSON_VERSION $libart_json.version.project_number$ULL
#define LIBART_JSON_VERSION_STR "$libart_json.version.project$"
#define LIBART_JSON_VERSION_ID "$libart_json.version.project_id$"
#define LIBART_JSON_VERSION_FULL "$libart_json.version$"
#define LIBART_JSON_VERSION_MAJOR $libart_json.version.major$
#define LIBART_JSON_VERSION_MINOR $libart_json.version.minor$
#define LIBART_JSON_VERSION_PATCH $libart_json.version.patch$
#define LIBART_JSON_PRE_RELEASE $libart_json.version.pre_release$
#define LIBART_JSON_SNAPSHOT_SN $libart_json.version.snapshot_sn$ULL
#define LIBART_JSON_SNAPSHOT_ID "$libart_json.version.snapshot_id$"
#endif

31
art/json/write.hxx Normal file
View File

@@ -0,0 +1,31 @@
#ifndef art__json__write_hxx_
#define art__json__write_hxx_
#include <art/json/emitter.hxx>
#include <ostream>
#include <sstream>
#include <string>
namespace art::json {
class variant;
inline void
write(std::ostream& os, variant const& v)
{
visit(emitter{ os }, v);
os << '\n';
}
inline std::string
write(variant const& json)
{
std::stringstream str;
write(str, json);
return str.str();
}
} // namespace art::json
#endif

31
art/json/write.test.cxx Normal file
View File

@@ -0,0 +1,31 @@
#include <art/json/variant.hxx>
#include <art/json/write.hxx>
#include <iostream>
#include <sstream>
#define DEFINE_TEST(x) std::cout << x << '\n';
#define TEST_EQUAL(x, y) if ((x) != (y)) return __LINE__;
int
main()
{
DEFINE_TEST("write")
{
auto document = std::map< std::string, art::json::variant >{
{ { "title", "Coca-Cola Regular 1.5L" },
{ "gtin13", "5449000139306" } }
};
std::stringstream output;
art::json::write(output, document);
TEST_EQUAL(output.str(), R"({
"gtin13": "5449000139306",
"title": "Coca-Cola Regular 1.5L"
}
)");
}
return 0;
}

4
build/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/config.build
/root/
/bootstrap/
build/

7
build/bootstrap.build Normal file
View File

@@ -0,0 +1,7 @@
project = libart-json
using version
using config
using test
using install
using dist

6
build/export.build Normal file
View File

@@ -0,0 +1,6 @@
$out_root/
{
include art/json/
}
export $out_root/art/json/$import.target

16
build/root.build Normal file
View File

@@ -0,0 +1,16 @@
# Uncomment to suppress warnings coming from external libraries.
#
#cxx.internal.scope = current
cxx.std = latest
using cxx
hxx{*}: extension = hxx
ixx{*}: extension = ixx
txx{*}: extension = txx
cxx{*}: extension = cxx
# The test target for cross-testing (running tests under Wine, etc).
#
test.target = $cxx.target

5
buildfile Normal file
View File

@@ -0,0 +1,5 @@
./: {art/ tests/} doc{README.md} legal{LICENSE} manifest
# Don't install tests.
#
tests/: install = false

13
manifest Normal file
View File

@@ -0,0 +1,13 @@
: 1
name: libart-json
version: 0.1.0-a.0.z
language: c++
summary: libart-json C++ library
license: BSD-4-Clause
description-file: README.md
url: https://art.helloryan.se/
email: art@helloryan.se
depends: * build2 >= 0.17.0
depends: * bpkg >= 0.17.0
depends: libart-validation ^0.1.0-
depends: libart-unicode ^0.1.0-

10
repositories.manifest Normal file
View File

@@ -0,0 +1,10 @@
: 1
summary: libart-json project repository
:
role: prerequisite
location: https://code.helloryan.se/art/libart-validation.git##HEAD
:
role: prerequisite
location: https://code.helloryan.se/art/libart-unicode.git##HEAD

8
tests/.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
# Test executables.
#
driver
# Testscript output directories (can be symlinks).
#
test
test-*

4
tests/build/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/config.build
/root/
/bootstrap/
build/

View File

@@ -0,0 +1,5 @@
project = # Unnamed tests subproject.
using config
using test
using dist

16
tests/build/root.build Normal file
View File

@@ -0,0 +1,16 @@
cxx.std = latest
using cxx
hxx{*}: extension = hxx
ixx{*}: extension = ixx
txx{*}: extension = txx
cxx{*}: extension = cxx
# Every exe{} in this subproject is by default a test.
#
exe{*}: test = true
# The test target for cross-testing (running tests under Wine, etc).
#
test.target = $cxx.target

1
tests/buildfile Normal file
View File

@@ -0,0 +1 @@
./: {*/ -build/}