initial implementation

This commit is contained in:
Roland Conybeare 2023-10-09 13:49:08 -04:00
commit effbbf22d9
9 changed files with 798 additions and 0 deletions

52
CMakeLists.txt Normal file
View file

@ -0,0 +1,52 @@
# printjson/CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(printjson VERSION 0.1)
enable_language(CXX)
# common XO cmake macros (see proj/xo-cmake)
include(xo_macros/xo_cxx)
include(xo_macros/code-coverage)
# ----------------------------------------------------------------
# unit test setup
enable_testing()
# activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON)
add_code_coverage()
# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc.
# we're not interested in code coverage for these sources.
# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves;
# rather, want coverage on the code that the unit tests exercise.
#
# NOTE: this seems to work only with the 'ccov-all' target. In particular, doesn't seem to do anything with the 'ccov' target
#
add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* ${PROJECT_BINARY_DIR}/local/* ${PROJECT_SOURCE_DIR}/repo/*)
# ----------------------------------------------------------------
# c++ settings
# one-time project-specific c++ flags. usually empty
set(PROJECT_CXX_FLAGS "")
#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2")
add_definitions(${PROJECT_CXX_FLAGS})
xo_toplevel_compile_options()
# ----------------------------------------------------------------
add_subdirectory(src/printjson)
#add_subdirectory(utest)
# ----------------------------------------------------------------
# provide find_package() support for printjson customers
xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets)
# ----------------------------------------------------------------
# install .hpp files
xo_install_include_tree()
# end CMakeLists.txt

26
README.md Normal file
View file

@ -0,0 +1,26 @@
# printjson library
### build + install
```
$ cd xo-printjson
$ mkdir build
$ cd build
$ PREFIX=/usr/local # or wherever you prefer
$ cmake -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCMAKE_INSTALL_PREFIX=${PREFIX} ..
$ make
$ make install
```
### build for unit test coverage
```
$ cd xo-printjson
$ mkdir ccov
$ cd ccov
$ cmake -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug ..
```
### LSP support
```
$ cd xo-printjson
$ ln -s build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree
```

View file

@ -0,0 +1,6 @@
@PACKAGE_INIT@
#include(CMakeFindDependencyMacro)
#find_dependency(refcnt)
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")
check_required_components("@PROJECT_NAME@")

View file

@ -0,0 +1,99 @@
/* @file JsonPrinter.hpp
*
* author: Roland Conybeare, Aug 2022
*/
#pragma once
#include "xo/reflect/Reflect.hpp"
#include "xo/reflect/TypeDrivenMap.hpp"
#include "xo/reflect/TaggedPtr.hpp"
//#include <memory>
#include <iostream>
namespace xo {
namespace json {
class PrintJson;
class JsonPrinter {
public:
using Reflect = xo::reflect::Reflect;
using TaggedPtr = xo::reflect::TaggedPtr;
using TypeDescr = xo::reflect::TypeDescr;
using TypeId = xo::reflect::TypeId;
public:
JsonPrinter(PrintJson const * pjson) : pjson_{pjson} {}
virtual ~JsonPrinter() = default;
PrintJson const * pjson() const { return pjson_; }
/* print tagged pointer in json format */
virtual void print_json(TaggedPtr tp,
std::ostream * p_os) const = 0;
void report_internal_type_consistency_error(TypeDescr td1,
TypeDescr td2,
std::ostream * p_os) const;
/* convenience method for derived printers.
* retrieves contents of tp as a T*, complains to *p_os if that fails.
*
* (Failure would occur if printer for type T was instead installed
* for some unrelated type U)
*/
template<typename T>
T * check_recover_native(TaggedPtr tp, std::ostream * p_os) const {
T * x = tp.recover_native<T>();
if (!x) {
this->report_internal_type_consistency_error(Reflect::require<T>(),
tp.td(),
p_os);
}
return x;
} /*check_recover_native*/
void assign_pjson(PrintJson const * pjson) {
assert(this->pjson_ == nullptr || this->pjson_ == pjson);
this->pjson_ = pjson;
} /*assign_pjson*/
private:
/* a json printers is installed into one PrintJson instance;
* capture address of that instance at install time
*/
PrintJson const * pjson_ = nullptr;
}; /*JsonPrinter*/
/* AsStringJsonPrinter<T>
* prints a T-instance by using operator<< and surrounding in quotes.
*
* e.g:
* T & x = ..;
* std::ostream * p_os = ..;
*
* *p_os << "\"" << x << "\""
*
*/
template<typename T>
class AsStringJsonPrinter : public JsonPrinter {
public:
AsStringJsonPrinter(PrintJson const * pjson) : JsonPrinter(pjson) {}
virtual void print_json(TaggedPtr tp,
std::ostream * p_os) const override
{
T * x = this->check_recover_native<T>(tp, p_os);
if(x) {
*p_os << "\"" << *x << "\"";
}
} /*print_json*/
}; /*AsStringJsonPrinter*/
} /*namespace json*/
} /*namespace xo*/
/* end JsonPrinter.hpp */

View file

@ -0,0 +1,143 @@
/* @file JsonPrinter.hpp
*
* author: Roland Conybeare, Aug 2022
*/
#pragma once
#include "JsonPrinter.hpp"
#include "xo/reflect/TypeDrivenMap.hpp"
#include "xo/reflect/SelfTagging.hpp"
#include <memory>
#include <iostream>
namespace xo {
namespace json {
class PrintJson : public reflect::SelfTagging {
public:
using Reflect = xo::reflect::Reflect;
using TypeDrivenMap = xo::reflect::TypeDrivenMap<std::unique_ptr<JsonPrinter>>;
using SelfTagging = xo::reflect::SelfTagging;
using TaggedPtr = xo::reflect::TaggedPtr;
using TaggedRcptr = xo::reflect::TaggedRcptr;
using TypeDescr = xo::reflect::TypeDescr;
using TypeId = xo::reflect::TypeId;
public:
PrintJson();
~PrintJson() = default;
template<typename T>
void print(T const & x_arg, std::ostream * p_os) const {
T * x = const_cast<T *>(&x_arg);
this->print_tp(Reflect::make_tp(x), p_os);
} /*print*/
/* print object tp on stream *p_os, in JSON format;
*/
void print_tp(TaggedPtr tp, std::ostream * p_os) const;
/* convenience -- shorthand for
* .print(obj->self_tp(), p_os)
*/
void print_obj(ref::rp<SelfTagging> const & obj, std::ostream * p_os) const;
void provide_printer(TypeId id, std::unique_ptr<JsonPrinter> p) {
*(printer_map_.require(id)) = std::move(p);
}
void provide_printer(TypeDescr td, std::unique_ptr<JsonPrinter> p) {
this->provide_printer(td->id(), std::move(p));
}
/* write json representation for tp on *p_os */
void print_aux(TaggedPtr tp, std::ostream * p_os) const;
// ----- inherited from SelfTagging -----
virtual TaggedRcptr self_tp();
private:
/* provide printers for common basic types */
void provide_std_printers();
private:
/* map contains specialized printers for specific c++ types */
TypeDrivenMap printer_map_;
}; /*PrintJson*/
/* Using singleton here to collect type-specific json printers,
* collected during program initialization.
*
* Could relabel as PrintJsonInitContext if desired
*/
class PrintJsonSingleton {
public:
static ref::rp<PrintJson> instance();
private:
/* we don't need this to be stored as pointer.
* memory burned if unused will be one empty std::vector<>
*/
static ref::rp<PrintJson> s_instance;
}; /*PrintJsonSingleton*/
} /*namespace json*/
#ifdef NOT_USING
namespace print {
using PrintJson = xo::json::PrintJson;
/* stream inserter for printing a T-instance in json format */
template<typename T>
class jsonp_impl {
public:
jsonp_impl(T const & x, PrintJson const * pjson) : value_(x), pjson_{pjson} {}
//jsonp_impl(T const & x, PrintJson const * pjson) : value_{x}, pjson_{pjson} {}
//jsonp_impl(T && x, PrintJson const * pjson) : value_(std::move(x)), pjson_{pjson} {}
void print(std::ostream & os) const {
using xo::reflect::Reflect;
this->pjson_->print_tp(Reflect::make_tp(&value_), &os);
} /*print*/
private:
/* value, to be printed, in json format */
T value_;
/* json printer (bc we don't care for singletons) */
PrintJson const * pjson_ = nullptr;
}; /*jsonp_impl*/
template<typename T>
inline
std::ostream & operator<<(std::ostream & os, jsonp_impl<T> const & x) {
x.print(os);
return os;
} /*operator<<*/
/* writing out std::forward<T> behavior for completeness' sake:
*
* 1. call jsonp(x) with rvalue std::string x, then:
* - T will be deduced to [std::string]
* (in particular: _not_ std::string &, std::string const &, std::string &&)
* - rvalue std::string passed to jsonp_impl ctor
*
* 2a. call jsonp(x) with std::string & x, then:
* - T deduced to [std::string &]
* - std::string & passed to jsonp_impl ctor
*
* 2b. call jsonp(x) with std::string const & x, then:
* - T deduced to [std::string const &]
* - std::string const & passed to jsonp_impl ctor
*/
template<typename T>
auto jsonp(T && x, PrintJson const * pjson) {
return jsonp_impl<T>(std::forward<T>(x), pjson);
} /*jsonp*/
} /*namespace print*/
#endif
} /*namespace xo*/
/* end JsonPrinter.hpp */

View file

@ -0,0 +1,29 @@
/* file init_printjson.hpp
*
* author: Roland Conybeare, Sep 2022
*/
#pragma once
#include "xo/subsys/Subsystem.hpp"
namespace xo {
/* tag to represent the printjson/ subsystem within ordered initialization */
enum S_printjson_tag {};
/* Use:
* // anywhere, to declare printjson dependency e.g. at file scope
* InitEvidence s_evidence = InitSubsys<S_printjson_tag>::require();
*
* // from main(), though can resort to module initialization in a pybind11 library
* Subsystem::initialize_all();
*/
template<>
struct InitSubsys<S_printjson_tag> {
static void init();
static InitEvidence require();
};
} /*namespace xo*/
/* end init_printjson.hpp */

View file

@ -0,0 +1,13 @@
# xo-printjson/src/printjson/CMakeLists.txt
set(SELF_LIB printjson)
set(SELF_SRCS PrintJson.cpp init_printjson.cpp)
xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS})
# ----------------------------------------------------------------
# dependencies: indentlog, ...
xo_dependency(${SELF_LIB} reflect)
# end CMakeLists.txt

398
src/printjson/PrintJson.cpp Normal file
View file

@ -0,0 +1,398 @@
/* file JsonPrinter.cpp
*
* author: Roland Conybeare, Aug 2022
*/
#include "PrintJson.hpp"
//#include "time/Time.hpp"
#include "xo/reflect/TypeDescr.hpp"
#include "xo/indentlog/print/tag.hpp"
#include <cmath>
namespace xo {
using xo::time::utc_nanos;
using xo::reflect::Metatype;
using xo::reflect::Reflect;
using xo::reflect::SelfTagging;
using xo::reflect::TypeDescr;
using xo::reflect::TaggedPtr;
using xo::reflect::TaggedRcptr;
using xo::time::iso8601;
using xo::ref::rp;
using xo::xtag;
namespace json {
TaggedRcptr
PrintJson::self_tp()
{
return Reflect::make_rctp(this);
//return TaggedRcptr::make(this);
} /*self_tp*/
void
JsonPrinter::report_internal_type_consistency_error(TypeDescr td1,
TypeDescr td2,
std::ostream * p_os) const
{
*p_os << "<internal-error: type mismatch between T & S"
<< xtag("T", td1->canonical_name())
<< xtag("S", td2->canonical_name())
<< ">";
} /*report_internal_type_consistency_error*/
namespace {
/* this will be used when TaggedPtr refers to a pointer-like value,
* e.g.
* xo::ref::rp<T>
*/
void
print_generic_pointer(PrintJson const & print_json,
TaggedPtr tp,
std::ostream * p_os)
{
/* e.g. if
* xo::ref::rp<VanillaOption> opt = ...;
* then expect to print just as we would for
* VanillaOption & opt = ...;
* if pointer is null, will print {}
*/
if (tp.n_child()) {
print_json.print_aux(tp.get_child(0), p_os);
} else {
/* note: this can be distinguished from a bona fide struct,
* b/c it doesn't supply the _name_ member
*/
*p_os << "{}";
}
} /*print_generic_pointer*/
/* this will be used when TaggedPtr refers to a vector-like value,
* e.g.
* std::vector<T>
* std::array<T, N>
*/
void
print_generic_vector(PrintJson const & print_json,
TaggedPtr tp,
std::ostream * p_os)
{
/* e.g. if
* std::array<double, 3> v{1, 2, 3};
*
* then expect to print
* [1.0, 2.0, 3.0]
*/
*p_os << "[";
for (uint32_t i = 0, n = tp.n_child(); i < n; ++i) {
if (i > 0)
*p_os << ", ";
print_json.print_aux(tp.get_child(i), p_os);
}
*p_os << "]";
} /*print_generic_vector*/
/* this will be used when TaggedPtr is understood to refer to a struct-like value.
*/
void
print_generic_struct(PrintJson const & print_json,
TaggedPtr tp,
std::ostream * p_os)
{
/* e.g. if
* struct Foo { int x_; double y_; };
* Foo foo{1, 1.4142};
*
* then expect to print
* {"_name_": "Foo", "x": 1, "y": 1.4142}
*
* note that python json parser requires property names in double quotes
*/
*p_os << "{";
*p_os << "\"_name_\": \"" << tp.td()->short_name() << "\"";
for (uint32_t i = 0, n = tp.n_child(); i < n; ++i) {
*p_os << ", \"" << tp.struct_member_name(i) << "\": ";
print_json.print_aux(tp.get_child(i), p_os);
}
*p_os << "}";
} /*print_generic_struct*/
} /*namespace*/
void
PrintJson::print_aux(TaggedPtr tp,
std::ostream * p_os) const
{
if (tp.td()) {
TypeId id = tp.td()->id();
std::unique_ptr<JsonPrinter> const * printer
= this->printer_map_.lookup(id);
if (printer && *printer) {
(*printer)->print_json(tp, p_os);
} else {
/* if no special-case printer, apply generic printing behavior */
switch (tp.td()->metatype()) {
case Metatype::mt_pointer:
print_generic_pointer(*this, tp, p_os);
return;
case Metatype::mt_vector:
print_generic_vector(*this, tp, p_os);
return;
case Metatype::mt_struct:
print_generic_struct(*this, tp, p_os);
return;
case Metatype::mt_invalid:
case Metatype::mt_atomic:
break;
}
(*p_os) << "<error-json-printer-not-found"
<< xtag("type", tp.td()->canonical_name())
<< xtag("metatype", tp.td()->metatype())
<< ">";
}
} else {
(*p_os) << "<error-null-tp>";
}
} /*print_aux*/
void
PrintJson::print_tp(TaggedPtr tp,
std::ostream * p_os) const
{
this->print_aux(tp, p_os);
//*p_os << std::ends;
} /*print*/
void
PrintJson::print_obj(rp<SelfTagging> const & obj, std::ostream * p_os) const
{
assert(obj.get());
this->print_tp(obj->self_tp(), p_os);
} /*print_obj*/
class JsonPrinter_bool : public JsonPrinter {
public:
JsonPrinter_bool(PrintJson const * pjson) : JsonPrinter(pjson) {}
virtual void print_json(TaggedPtr tp,
std::ostream * p_os) const override {
bool * x = this->check_recover_native<bool>(tp, p_os);
if (x) {
/* json boolean format is lower case true/false.
* (note that this conflicts with python True/False, achtung!)
*/
*p_os << (*x ? "true" : "false");
}
} /*print_json*/
}; /*JsonPrinter_bool*/
namespace {
void
provide_bool_printer(PrintJson * p_json)
{
std::unique_ptr<JsonPrinter> printer(new JsonPrinter_bool(p_json));
p_json->provide_printer(Reflect::require<bool>(),
std::move(printer));
} /*provide_bool_printer*/
} /*namespace*/
template<typename T>
class JsonPrinter_integer : public JsonPrinter {
public:
JsonPrinter_integer(PrintJson const * pjson) : JsonPrinter(pjson) {}
virtual void print_json(TaggedPtr tp,
std::ostream * p_os) const override {
T * x = tp.recover_native<T>();
if (x) {
*p_os << *x;
} else {
report_internal_type_consistency_error(Reflect::require<T>(),
tp.td(),
p_os);
}
} /*print_json*/
}; /*JsonPrinter_integer*/
namespace {
template<typename T>
void
provide_integer_printer(PrintJson * p_json)
{
std::unique_ptr<JsonPrinter> printer(new JsonPrinter_integer<T>(p_json));
p_json->provide_printer(Reflect::require<T>(), std::move(printer));
} /*provide_integer_printer*/
}
template<typename T>
class JsonPrinter_floatingpoint : public JsonPrinter {
public:
JsonPrinter_floatingpoint(PrintJson const * pjson) : JsonPrinter(pjson) {}
virtual void print_json(TaggedPtr tp,
std::ostream * p_os) const override
{
T * x = tp.recover_native<T>();
if (x) {
if (std::isfinite(*x)) {
*p_os << *x;
} else {
/* special cases.
* use javascript-friendly format
*
* Note non-finite floating-point values are not representable in
* standard json (?!#), though it's a standard extension
*/
if (std::isnan(*x))
*p_os << "NaN";
else if (*x > 0.0)
*p_os << "Infinity";
else
*p_os << "-Infinity";
}
} else {
report_internal_type_consistency_error(Reflect::require<T>(),
tp.td(),
p_os);
}
} /*print_json*/
}; /*JsonPrinter_floatingpoint*/
namespace {
template<typename T>
void
provide_floatingpoint_printer(PrintJson * p_json)
{
std::unique_ptr<JsonPrinter> printer(new JsonPrinter_floatingpoint<T>(p_json));
p_json->provide_printer(Reflect::require<T>(), std::move(printer));
} /*provide_floatingpoint_printer*/
} /*namespace*/
template<typename T>
class JsonPrinter_string : public JsonPrinter {
public:
JsonPrinter_string(PrintJson const * pjson) : JsonPrinter(pjson) {}
virtual void print_json(TaggedPtr tp,
std::ostream * p_os) const override {
T * x = tp.recover_native<T>();
if (x) {
/* TODO: escapes special characters */
*p_os << *x;
} else {
report_internal_type_consistency_error(Reflect::require<T>(),
tp.td(),
p_os);
}
} /*print_json*/
}; /*JsonPrinter_string*/
namespace {
template<typename T>
void
provide_string_printer(PrintJson * p_json)
{
std::unique_ptr<JsonPrinter> printer(new JsonPrinter_string<T>(p_json));
p_json->provide_printer(Reflect::require<T>(), std::move(printer));
} /*provide_string_printer*/
} /*namespace */
class JsonPrinter_utc_nanos : public JsonPrinter {
public:
JsonPrinter_utc_nanos(PrintJson * pjson) : JsonPrinter(pjson) {}
virtual void print_json(TaggedPtr tp,
std::ostream * p_os) const override {
utc_nanos * x = tp.recover_native<utc_nanos>();
if (x) {
/* format like
* "2012-04-23T18:25:43.511Z"
* since that's what javascript uses
*/
*p_os << "\"" << iso8601(*x) << "\"";
} else {
report_internal_type_consistency_error(Reflect::require<utc_nanos>(),
tp.td(),
p_os);
}
} /*print_json*/
}; /*JsonPrinter_utc_nanos*/
namespace {
void
provide_utc_nanos_printer(PrintJson * p_json)
{
std::unique_ptr<JsonPrinter> printer(new JsonPrinter_utc_nanos(p_json));
p_json->provide_printer(Reflect::require<utc_nanos>(),
std::move(printer));
} /*provide_utc_nanos_printer*/
} /*namespace*/
PrintJson::PrintJson() {
this->provide_std_printers();
} /*ctor*/
/* provide printers for common basic types */
void
PrintJson::provide_std_printers()
{
provide_bool_printer(this);
provide_integer_printer<std::int16_t>(this);
provide_integer_printer<std::uint16_t>(this);
provide_integer_printer<std::int32_t>(this);
provide_integer_printer<std::uint32_t>(this);
provide_integer_printer<std::int64_t>(this);
provide_integer_printer<std::uint64_t>(this);
provide_floatingpoint_printer<float>(this);
provide_floatingpoint_printer<double>(this);
provide_string_printer<char *>(this);
provide_string_printer<char const *>(this);
provide_string_printer<std::string>(this);
provide_string_printer<std::string_view>(this);
provide_utc_nanos_printer(this);
} /*provide_std_printers*/
rp<PrintJson>
PrintJsonSingleton::s_instance;
rp<PrintJson>
PrintJsonSingleton::instance()
{
if (!s_instance)
s_instance = new PrintJson();
return s_instance;
} /*instance*/
} /*namespace json*/
} /*namespace xo*/
/* end JsonPrinter.cpp */

View file

@ -0,0 +1,32 @@
/* file init_printjson.cpp
*
* author: Roland Conybeare, Sep 2022
*/
#include "init_printjson.hpp"
#include "xo/reflect/init_reflect.hpp"
#include "xo/subsys/Subsystem.hpp"
namespace xo {
void
InitSubsys<S_printjson_tag>::init()
{
/* placeholder -- expecting there to be non-trivial content soon */
} /*init*/
InitEvidence
InitSubsys<S_printjson_tag>::require()
{
InitEvidence retval;
/* subsystem dependencies for printjson/ */
retval ^= InitSubsys<S_reflect_tag>::require();
/* printjson/'s own initialization code */
retval ^= Subsystem::provide<S_printjson_tag>("printjson", &init);
return retval;
} /*require*/
} /*namespace xo*/
/* end init_printjson.cpp */