From effbbf22d9f4238147274befbe1116a288cc535e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 9 Oct 2023 13:49:08 -0400 Subject: [PATCH 01/17] initial implementation --- CMakeLists.txt | 52 ++++ README.md | 26 ++ cmake/printjsonConfig.cmake.in | 6 + include/xo/printjson/JsonPrinter.hpp | 99 ++++++ include/xo/printjson/PrintJson.hpp | 143 +++++++++ include/xo/printjson/init_printjson.hpp | 29 ++ src/printjson/CMakeLists.txt | 13 + src/printjson/PrintJson.cpp | 398 ++++++++++++++++++++++++ src/printjson/init_printjson.cpp | 32 ++ 9 files changed, 798 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 cmake/printjsonConfig.cmake.in create mode 100644 include/xo/printjson/JsonPrinter.hpp create mode 100644 include/xo/printjson/PrintJson.hpp create mode 100644 include/xo/printjson/init_printjson.hpp create mode 100644 src/printjson/CMakeLists.txt create mode 100644 src/printjson/PrintJson.cpp create mode 100644 src/printjson/init_printjson.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..028f4c5e --- /dev/null +++ b/CMakeLists.txt @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 00000000..ab049018 --- /dev/null +++ b/README.md @@ -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 +``` diff --git a/cmake/printjsonConfig.cmake.in b/cmake/printjsonConfig.cmake.in new file mode 100644 index 00000000..c7d8974c --- /dev/null +++ b/cmake/printjsonConfig.cmake.in @@ -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@") diff --git a/include/xo/printjson/JsonPrinter.hpp b/include/xo/printjson/JsonPrinter.hpp new file mode 100644 index 00000000..c1b8b62a --- /dev/null +++ b/include/xo/printjson/JsonPrinter.hpp @@ -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 +#include + +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 + T * check_recover_native(TaggedPtr tp, std::ostream * p_os) const { + T * x = tp.recover_native(); + + if (!x) { + this->report_internal_type_consistency_error(Reflect::require(), + 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 + * prints a T-instance by using operator<< and surrounding in quotes. + * + * e.g: + * T & x = ..; + * std::ostream * p_os = ..; + * + * *p_os << "\"" << x << "\"" + * + */ + template + 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(tp, p_os); + + if(x) { + *p_os << "\"" << *x << "\""; + } + } /*print_json*/ + }; /*AsStringJsonPrinter*/ + } /*namespace json*/ +} /*namespace xo*/ + + +/* end JsonPrinter.hpp */ diff --git a/include/xo/printjson/PrintJson.hpp b/include/xo/printjson/PrintJson.hpp new file mode 100644 index 00000000..c0ddcf55 --- /dev/null +++ b/include/xo/printjson/PrintJson.hpp @@ -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 +#include + +namespace xo { + namespace json { + class PrintJson : public reflect::SelfTagging { + public: + using Reflect = xo::reflect::Reflect; + using TypeDrivenMap = xo::reflect::TypeDrivenMap>; + 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 + void print(T const & x_arg, std::ostream * p_os) const { + T * x = const_cast(&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 const & obj, std::ostream * p_os) const; + + void provide_printer(TypeId id, std::unique_ptr p) { + *(printer_map_.require(id)) = std::move(p); + } + + void provide_printer(TypeDescr td, std::unique_ptr 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 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 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 + 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 + inline + std::ostream & operator<<(std::ostream & os, jsonp_impl const & x) { + x.print(os); + return os; + } /*operator<<*/ + + /* writing out std::forward 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 + auto jsonp(T && x, PrintJson const * pjson) { + return jsonp_impl(std::forward(x), pjson); + } /*jsonp*/ + } /*namespace print*/ +#endif +} /*namespace xo*/ + +/* end JsonPrinter.hpp */ diff --git a/include/xo/printjson/init_printjson.hpp b/include/xo/printjson/init_printjson.hpp new file mode 100644 index 00000000..f2b06a19 --- /dev/null +++ b/include/xo/printjson/init_printjson.hpp @@ -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::require(); + * + * // from main(), though can resort to module initialization in a pybind11 library + * Subsystem::initialize_all(); + */ + template<> + struct InitSubsys { + static void init(); + static InitEvidence require(); + }; +} /*namespace xo*/ + + +/* end init_printjson.hpp */ diff --git a/src/printjson/CMakeLists.txt b/src/printjson/CMakeLists.txt new file mode 100644 index 00000000..cf191d02 --- /dev/null +++ b/src/printjson/CMakeLists.txt @@ -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 diff --git a/src/printjson/PrintJson.cpp b/src/printjson/PrintJson.cpp new file mode 100644 index 00000000..1ac048b6 --- /dev/null +++ b/src/printjson/PrintJson.cpp @@ -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 + +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 << "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 + */ + void + print_generic_pointer(PrintJson const & print_json, + TaggedPtr tp, + std::ostream * p_os) + { + /* e.g. if + * xo::ref::rp 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 + * std::array + */ + void + print_generic_vector(PrintJson const & print_json, + TaggedPtr tp, + std::ostream * p_os) + { + /* e.g. if + * std::array 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 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) << "canonical_name()) + << xtag("metatype", tp.td()->metatype()) + << ">"; + } + } else { + (*p_os) << ""; + } + } /*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 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(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 printer(new JsonPrinter_bool(p_json)); + + p_json->provide_printer(Reflect::require(), + std::move(printer)); + } /*provide_bool_printer*/ + } /*namespace*/ + + template + 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(); + + if (x) { + *p_os << *x; + } else { + report_internal_type_consistency_error(Reflect::require(), + tp.td(), + p_os); + } + } /*print_json*/ + }; /*JsonPrinter_integer*/ + + namespace { + template + void + provide_integer_printer(PrintJson * p_json) + { + std::unique_ptr printer(new JsonPrinter_integer(p_json)); + + p_json->provide_printer(Reflect::require(), std::move(printer)); + } /*provide_integer_printer*/ + } + + template + 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(); + + 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(), + tp.td(), + p_os); + } + } /*print_json*/ + }; /*JsonPrinter_floatingpoint*/ + + namespace { + template + void + provide_floatingpoint_printer(PrintJson * p_json) + { + std::unique_ptr printer(new JsonPrinter_floatingpoint(p_json)); + + p_json->provide_printer(Reflect::require(), std::move(printer)); + } /*provide_floatingpoint_printer*/ + } /*namespace*/ + + template + 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(); + + if (x) { + /* TODO: escapes special characters */ + *p_os << *x; + } else { + report_internal_type_consistency_error(Reflect::require(), + tp.td(), + p_os); + } + } /*print_json*/ + }; /*JsonPrinter_string*/ + + namespace { + template + void + provide_string_printer(PrintJson * p_json) + { + std::unique_ptr printer(new JsonPrinter_string(p_json)); + + p_json->provide_printer(Reflect::require(), 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(); + + 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(), + tp.td(), + p_os); + } + } /*print_json*/ + }; /*JsonPrinter_utc_nanos*/ + + namespace { + void + provide_utc_nanos_printer(PrintJson * p_json) + { + std::unique_ptr printer(new JsonPrinter_utc_nanos(p_json)); + + p_json->provide_printer(Reflect::require(), + 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(this); + provide_integer_printer(this); + provide_integer_printer(this); + provide_integer_printer(this); + provide_integer_printer(this); + provide_integer_printer(this); + + provide_floatingpoint_printer(this); + provide_floatingpoint_printer(this); + + provide_string_printer(this); + provide_string_printer(this); + provide_string_printer(this); + provide_string_printer(this); + + provide_utc_nanos_printer(this); + } /*provide_std_printers*/ + + rp + PrintJsonSingleton::s_instance; + + rp + PrintJsonSingleton::instance() + { + if (!s_instance) + s_instance = new PrintJson(); + + return s_instance; + } /*instance*/ + + } /*namespace json*/ +} /*namespace xo*/ + +/* end JsonPrinter.cpp */ diff --git a/src/printjson/init_printjson.cpp b/src/printjson/init_printjson.cpp new file mode 100644 index 00000000..6bbaf01b --- /dev/null +++ b/src/printjson/init_printjson.cpp @@ -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::init() + { + /* placeholder -- expecting there to be non-trivial content soon */ + } /*init*/ + + InitEvidence + InitSubsys::require() + { + InitEvidence retval; + + /* subsystem dependencies for printjson/ */ + retval ^= InitSubsys::require(); + + /* printjson/'s own initialization code */ + retval ^= Subsystem::provide("printjson", &init); + + return retval; + } /*require*/ +} /*namespace xo*/ + +/* end init_printjson.cpp */ From 25380b8da7b83bd0ab7dbd379d67b703e0e00baa Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 9 Oct 2023 13:51:34 -0400 Subject: [PATCH 02/17] + .gitignore --- .gitignore | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..2323db6f --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# lsp keeps state here +.cache +# typical build directories +build +ccov +# compile_commands.json symlink -> build/compile_commands.json should be created manually +compile_commands.json From 5840dffe50e5b29022e9455f3068db8c058638e1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 9 Oct 2023 13:55:12 -0400 Subject: [PATCH 03/17] github: + workflow (build on base ubuntu) --- .github/workflows/main.yml | 136 +++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..c11fc4ee --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,136 @@ +name: build on ubuntu base platform for xo-printjson + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_indentlog + + # ---------------------------------------------------------------- + + - name: Clone subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/subsys + + - name: Configure subsys + # configure cmake for subsys in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys + + - name: Build subsys + run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} + + - name: Install subsys + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_subsys + + # ---------------------------------------------------------------- + + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + # ---------------------------------------------------------------- + + - name: Clone reflect + uses: actions/checkout@v3 + with: + repository: Rconybea/reflect + path: repo/reflect + + - name: Configure reflect + # configure cmake for reflect in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reflect + + - name: Build reflect + run: cmake --build ${{github.workspace}}/build_reflect --config ${{env.BUILD_TYPE}} + + - name: Install reflect + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reflect + + # ---------------------------------------------------------------- + + - name: Configure self (printjson) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_printjson -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (printjson) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_printjson --config ${{env.BUILD_TYPE}} + + - name: Test self (printjson) + working-directory: ${{github.workspace}}/build_printjson + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} From a228728445752cd907e4c3b6c82673424df449e9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 9 Oct 2023 14:53:12 -0400 Subject: [PATCH 04/17] printjson: indentation in .hpp --- include/xo/printjson/JsonPrinter.hpp | 136 +++++++++++++-------------- 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/include/xo/printjson/JsonPrinter.hpp b/include/xo/printjson/JsonPrinter.hpp index c1b8b62a..2be405c0 100644 --- a/include/xo/printjson/JsonPrinter.hpp +++ b/include/xo/printjson/JsonPrinter.hpp @@ -12,87 +12,87 @@ #include namespace xo { - namespace json { - class PrintJson; + 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; + 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; + public: + JsonPrinter(PrintJson const * pjson) : pjson_{pjson} {} + virtual ~JsonPrinter() = default; - PrintJson const * pjson() const { return pjson_; } + PrintJson const * pjson() const { return pjson_; } - /* print tagged pointer in json format */ - virtual void print_json(TaggedPtr tp, - std::ostream * p_os) const = 0; + /* 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; + 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 - T * check_recover_native(TaggedPtr tp, std::ostream * p_os) const { - T * x = tp.recover_native(); + /* 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 + T * check_recover_native(TaggedPtr tp, std::ostream * p_os) const { + T * x = tp.recover_native(); - if (!x) { - this->report_internal_type_consistency_error(Reflect::require(), - tp.td(), - p_os); - } + if (!x) { + this->report_internal_type_consistency_error(Reflect::require(), + tp.td(), + p_os); + } - return x; - } /*check_recover_native*/ + return x; + } /*check_recover_native*/ - void assign_pjson(PrintJson const * pjson) { - assert(this->pjson_ == nullptr || this->pjson_ == pjson); - this->pjson_ = pjson; - } /*assign_pjson*/ + 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*/ + private: + /* a json printers is installed into one PrintJson instance; + * capture address of that instance at install time + */ + PrintJson const * pjson_ = nullptr; + }; /*JsonPrinter*/ - /* AsStringJsonPrinter - * prints a T-instance by using operator<< and surrounding in quotes. - * - * e.g: - * T & x = ..; - * std::ostream * p_os = ..; - * - * *p_os << "\"" << x << "\"" - * - */ - template - class AsStringJsonPrinter : public JsonPrinter { - public: - AsStringJsonPrinter(PrintJson const * pjson) : JsonPrinter(pjson) {} + /* AsStringJsonPrinter + * prints a T-instance by using operator<< and surrounding in quotes. + * + * e.g: + * T & x = ..; + * std::ostream * p_os = ..; + * + * *p_os << "\"" << x << "\"" + * + */ + template + 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(tp, p_os); + virtual void print_json(TaggedPtr tp, + std::ostream * p_os) const override + { + T * x = this->check_recover_native(tp, p_os); - if(x) { - *p_os << "\"" << *x << "\""; - } - } /*print_json*/ - }; /*AsStringJsonPrinter*/ - } /*namespace json*/ + if(x) { + *p_os << "\"" << *x << "\""; + } + } /*print_json*/ + }; /*AsStringJsonPrinter*/ + } /*namespace json*/ } /*namespace xo*/ From 3b00641d17b79ef4ed2c8617cf7fe9aa4fc0608e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 9 Oct 2023 14:53:51 -0400 Subject: [PATCH 05/17] init_printjson: always create singleton --- src/printjson/init_printjson.cpp | 34 ++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/printjson/init_printjson.cpp b/src/printjson/init_printjson.cpp index 6bbaf01b..893ffaf3 100644 --- a/src/printjson/init_printjson.cpp +++ b/src/printjson/init_printjson.cpp @@ -4,29 +4,33 @@ */ #include "init_printjson.hpp" +#include "PrintJson.hpp" #include "xo/reflect/init_reflect.hpp" #include "xo/subsys/Subsystem.hpp" namespace xo { - void - InitSubsys::init() - { - /* placeholder -- expecting there to be non-trivial content soon */ - } /*init*/ + using xo::json::PrintJsonSingleton; - InitEvidence - InitSubsys::require() - { - InitEvidence retval; + void + InitSubsys::init() + { + /* create singleton */ + PrintJsonSingleton::instance(); + } /*init*/ - /* subsystem dependencies for printjson/ */ - retval ^= InitSubsys::require(); + InitEvidence + InitSubsys::require() + { + InitEvidence retval; - /* printjson/'s own initialization code */ - retval ^= Subsystem::provide("printjson", &init); + /* subsystem dependencies for printjson/ */ + retval ^= InitSubsys::require(); - return retval; - } /*require*/ + /* printjson/'s own initialization code */ + retval ^= Subsystem::provide("printjson", &init); + + return retval; + } /*require*/ } /*namespace xo*/ /* end init_printjson.cpp */ From 0f4f874587b1530fe7d30b6247d6068322646db5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 9 Oct 2023 14:54:17 -0400 Subject: [PATCH 06/17] printjson: indentation in .hpp --- include/xo/printjson/init_printjson.hpp | 28 ++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/include/xo/printjson/init_printjson.hpp b/include/xo/printjson/init_printjson.hpp index f2b06a19..dbad03f8 100644 --- a/include/xo/printjson/init_printjson.hpp +++ b/include/xo/printjson/init_printjson.hpp @@ -8,21 +8,21 @@ #include "xo/subsys/Subsystem.hpp" namespace xo { - /* tag to represent the printjson/ subsystem within ordered initialization */ - enum S_printjson_tag {}; + /* 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::require(); - * - * // from main(), though can resort to module initialization in a pybind11 library - * Subsystem::initialize_all(); - */ - template<> - struct InitSubsys { - static void init(); - static InitEvidence require(); - }; + /* Use: + * // anywhere, to declare printjson dependency e.g. at file scope + * InitEvidence s_evidence = InitSubsys::require(); + * + * // from main(), though can resort to module initialization in a pybind11 library + * Subsystem::initialize_all(); + */ + template<> + struct InitSubsys { + static void init(); + static InitEvidence require(); + }; } /*namespace xo*/ From 45a0675436c7512398a0750b158d0a1907e6cf27 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 9 Oct 2023 14:54:39 -0400 Subject: [PATCH 07/17] printjson: add printer for nested TaggedPtr's --- src/printjson/PrintJson.cpp | 45 ++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/src/printjson/PrintJson.cpp b/src/printjson/PrintJson.cpp index 1ac048b6..7df2d5b3 100644 --- a/src/printjson/PrintJson.cpp +++ b/src/printjson/PrintJson.cpp @@ -17,6 +17,7 @@ namespace xo { using xo::reflect::TypeDescr; using xo::reflect::TaggedPtr; using xo::reflect::TaggedRcptr; + using xo::print::quoted; using xo::time::iso8601; using xo::ref::rp; using xo::xtag; @@ -183,6 +184,46 @@ namespace xo { this->print_tp(obj->self_tp(), p_os); } /*print_obj*/ + /* Consider: + * TaggedPtr tp = ...; + * std::ostream * p_os = ...; + * + * PrintJson * pjson = PrintJsonSingleton::instance(); + * + * // print json representation, depending on runtime type of tp's target + * pjson->print_tp(tp, p_os); + * + * // can also use .print(), relying on JsonPrinter_TaggedPtr + * // .print() will next original TaggedPtr in another; + * // this shim unwinds that + * // + * pjson->print(tp, p_os); + */ + class JsonPrinter_TaggedPtr : public JsonPrinter { + public: + JsonPrinter_TaggedPtr(PrintJson const * pjson) : JsonPrinter(pjson) {} + + virtual void print_json(TaggedPtr tp, + std::ostream * p_os) const override { + TaggedPtr * x = this->check_recover_native(tp, p_os); + + if (x) { + this->pjson()->print_tp(*x, p_os); + } + } /*print_json*/ + }; /*JsonPrinter_TaggedPtr*/ + + namespace { + void + provide_tagged_ptr_printer(PrintJson * p_json) + { + std::unique_ptr printer(new JsonPrinter_TaggedPtr(p_json)); + + p_json->provide_printer(Reflect::require(), + std::move(printer)); + } /*provide_tagged_ptr_printer*/ + } /*namespace*/ + class JsonPrinter_bool : public JsonPrinter { public: JsonPrinter_bool(PrintJson const * pjson) : JsonPrinter(pjson) {} @@ -299,7 +340,7 @@ namespace xo { if (x) { /* TODO: escapes special characters */ - *p_os << *x; + *p_os << quoted(*x); } else { report_internal_type_consistency_error(Reflect::require(), tp.td(), @@ -360,6 +401,8 @@ namespace xo { void PrintJson::provide_std_printers() { + provide_tagged_ptr_printer(this); + provide_bool_printer(this); provide_integer_printer(this); From 0a26fa9911f085f22d6aee1dfc49bc9d6ed4314a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 9 Oct 2023 14:55:08 -0400 Subject: [PATCH 08/17] printjson: fix+ reinstate unit test --- CMakeLists.txt | 2 +- utest/CMakeLists.txt | 58 +++++++++++++++ utest/CMakeLists.txt.safe | 55 +++++++++++++++ utest/PrintJson.test.cpp | 124 +++++++++++++++++++++++++++++++++ utest/printjson_utest_main.cpp | 6 ++ 5 files changed, 244 insertions(+), 1 deletion(-) create mode 100644 utest/CMakeLists.txt create mode 100644 utest/CMakeLists.txt.safe create mode 100644 utest/PrintJson.test.cpp create mode 100644 utest/printjson_utest_main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 028f4c5e..af42e3e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,7 @@ xo_toplevel_compile_options() # ---------------------------------------------------------------- add_subdirectory(src/printjson) -#add_subdirectory(utest) +add_subdirectory(utest) # ---------------------------------------------------------------- # provide find_package() support for printjson customers diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 00000000..0b2177e8 --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,58 @@ +# build unittest printjson/utest + +set(SELF_EXE utest.printjson) +set(SELF_SRCS printjson_utest_main.cpp PrintJson.test.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) + +add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) +target_code_coverage(${SELF_EXE} AUTO ALL) + +# ---------------------------------------------------------------- +# generic project dependency + +## PROJECT_SOURCE_DIR: +## so we can for example write +## #include "indentlog/scope.hpp" +## from anywhere in the project +## PROJECT_BINARY_DIR: +## since version file will be in build directory, need that directory +## to also be included in compiler's include path +## +#target_include_directories(${SELF_EXE} PUBLIC +# ${PROJECT_SOURCE_DIR} +# ${PROJECT_BINARY_DIR}) + +# ---------------------------------------------------------------- +# dependencies on this codebase + +xo_self_dependency(${SELF_EXE} printjson) + +# ---------------------------------------------------------------- +# dependencies on other codebases + +xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) + +# need this so that catch2/include appears in compile_commands.json, +# on which lsp integration relies. +# +# See also /nix/store/*-catch2-*/lib/cmake/Catch2/ParseAndAddCatchTests.cmake; +# commands here derived from ^ .cmake file +# +#find_path(CATCH_INCLUDE_DIR "catch2/catch.hpp") +#target_include_directories(${SELF_EXE} PUBLIC ${CATCH_INCLUDE_DIR}) + +# supplied from xo_include_options2() +## ---------------------------------------------------------------- +## make standard directories for std:: includes explicit +## so that +## (1) they appear in compile_commands.json. +## (2) clangd (run from emacs lsp-mode) can find them +## +#if(CMAKE_EXPORT_COMPILE_COMMANDS) +# set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES +# ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) +#endif() + +# end CMakeLists.txt diff --git a/utest/CMakeLists.txt.safe b/utest/CMakeLists.txt.safe new file mode 100644 index 00000000..60200a81 --- /dev/null +++ b/utest/CMakeLists.txt.safe @@ -0,0 +1,55 @@ +# build unittest printjson/utest + +set(SELF_EXECUTABLE_NAME utest.printjson) +set(SELF_SOURCE_FILES printjson_utest_main.cpp PrintJson.test.cpp) + +add_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES}) + +add_test(NAME ${SELF_EXECUTABLE_NAME} COMMAND ${SELF_EXECUTABLE_NAME}) + +# ---------------------------------------------------------------- +# generic project dependency + +# PROJECT_SOURCE_DIR: +# so we can for example write +# #include "indentlog/scope.hpp" +# from anywhere in the project +# PROJECT_BINARY_DIR: +# since version file will be in build directory, need that directory +# to also be included in compiler's include path +# +target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC + ${PROJECT_SOURCE_DIR} + ${PROJECT_BINARY_DIR}) + +# ---------------------------------------------------------------- +# internal dependencies: logutil, ... + +target_link_libraries(${SELF_EXECUTABLE_NAME} PUBLIC printjson) + +# ---------------------------------------------------------------- +# 3rd part dependency: catch2: + +find_package(Catch2 2 REQUIRED) + +# need this so that catch2/include appears in compile_commands.json, +# on which lsp integration relies. +# +# See also /nix/store/*-catch2-*/lib/cmake/Catch2/ParseAndAddCatchTests.cmake; +# commands here derived from ^ .cmake file +# +find_path(CATCH_INCLUDE_DIR "catch2/catch.hpp") +target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC ${CATCH_INCLUDE_DIR}) + +# ---------------------------------------------------------------- +# make standard directories for std:: includes explicit +# so that +# (1) they appear in compile_commands.json. +# (2) clangd (run from emacs lsp-mode) can find them +# +if(CMAKE_EXPORT_COMPILE_COMMANDS) + set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES + ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) +endif() + +# end CMakeLists.txt diff --git a/utest/PrintJson.test.cpp b/utest/PrintJson.test.cpp new file mode 100644 index 00000000..51259e23 --- /dev/null +++ b/utest/PrintJson.test.cpp @@ -0,0 +1,124 @@ +/* file PrintJson.test.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "xo/printjson/PrintJson.hpp" +#include "xo/printjson/init_printjson.hpp" +#include "xo/reflect/Reflect.hpp" +#include "xo/reflect/StructReflector.hpp" +#include +#include +#include + +//#define STRINGIFY(x) #x + +namespace xo { + using xo::json::PrintJson; + using xo::reflect::Reflect; + using xo::reflect::StructReflector; + using xo::reflect::TaggedPtr; + + namespace ut { + InitEvidence s_init_evidence = InitSubsys::require(); + + namespace { + struct TestStruct0 {}; + } + + TEST_CASE("print-json-empty-struct", "[printjson]") { + INFO(tag("s_init_evidence", s_init_evidence)); + + StructReflector sr; + + sr.require_complete(); + + TestStruct0 recd0; + + PrintJson print_json; + + TaggedPtr tp = Reflect::make_tp(&recd0); + + std::stringstream ss; + + print_json.print(tp, &ss); + + REQUIRE(ss.str() == std::string("{\"_name_\": \"TestStruct0\"}")); + } /*TEST_CASE(print-json-empty-struct)*/ + + namespace { + struct TestStruct1 { + std::int16_t i16_; std::uint16_t u16_; + std::int32_t i32_; std::uint32_t u32_; + std::int64_t i64_; std::uint64_t u64_; + float f32_; double f64_; + std::string s_; + }; + } + + TEST_CASE("print-json-s1", "[printjson]") { + INFO(tag("s_init_evidence", s_init_evidence)); + + StructReflector sr; + { + REFLECT_MEMBER(sr, i16); + REFLECT_MEMBER(sr, u16); + REFLECT_MEMBER(sr, i32); + REFLECT_MEMBER(sr, u32); + REFLECT_MEMBER(sr, i64); + REFLECT_MEMBER(sr, u64); + REFLECT_MEMBER(sr, f32); + REFLECT_MEMBER(sr, f64); + REFLECT_MEMBER(sr, s); + + sr.require_complete(); + } + + TestStruct1 recd1{-1, 2, -3, 4, -5, 6, 1.23f, 4.56, "hello, world"}; + + PrintJson print_json; + + TaggedPtr tp = Reflect::make_tp(&recd1); + + std::stringstream ss; + + print_json.print(tp, &ss); + + REQUIRE(ss.str() == std::string("{\"_name_\": \"TestStruct1\"" + ", \"i16\": -1" + ", \"u16\": 2" + ", \"i32\": -3" + ", \"u32\": 4" + ", \"i64\": -5" + ", \"u64\": 6" + ", \"f32\": 1.23" + ", \"f64\": 4.56" + ", \"s\": \"hello, world\"}")); + } /*TEST_CASE(print-json-s1)*/ + + TEST_CASE("print-json-v1", "[printjson]") { + INFO(tag("s_init_evidence", s_init_evidence)); + + std::vector v1{1, 2, 3}; + + PrintJson print_json; + + TaggedPtr tp = Reflect::make_tp(&v1); + + std::stringstream ss; + + print_json.print(tp, &ss); + + REQUIRE(ss.str() == std::string("[1, 2, 3]")); + } /*TEST_CASE(print-json-v1)*/ + + /* also see tests: + * [option_util/utest/Px2.test.cpp] + * [option_util/utest/Size2.test.cpp] + * [option_util/utest/PxSize2.test.cpp] + */ + } /*namespace ut */ +} /*namespace xo*/ + + +/* end StructReflector.test.cpp */ diff --git a/utest/printjson_utest_main.cpp b/utest/printjson_utest_main.cpp new file mode 100644 index 00000000..e7c11b66 --- /dev/null +++ b/utest/printjson_utest_main.cpp @@ -0,0 +1,6 @@ +/* file printjson_utest_main.cpp */ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/* end printjson_utest_main.cpp */ From 12d38a7e4a87b08ddfbf0590e6b6c92929b211a1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 20:12:16 -0400 Subject: [PATCH 09/17] build: + reflect dependency --- README.md | 9 ++++++--- cmake/printjsonConfig.cmake.in | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ab049018..99492884 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # printjson library -### build + install +# build + install ``` $ cd xo-printjson $ mkdir build @@ -11,15 +11,18 @@ $ make $ make install ``` -### build for unit test coverage +# 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 .. +$ make # builds executables +$ make ccov # runs instrumented unit tests +$ make ccov-all # generates lcov report ``` -### LSP support +# 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 diff --git a/cmake/printjsonConfig.cmake.in b/cmake/printjsonConfig.cmake.in index c7d8974c..e7f7f1be 100644 --- a/cmake/printjsonConfig.cmake.in +++ b/cmake/printjsonConfig.cmake.in @@ -1,6 +1,6 @@ @PACKAGE_INIT@ -#include(CMakeFindDependencyMacro) -#find_dependency(refcnt) +include(CMakeFindDependencyMacro) +find_dependency(reflect) include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") From 465a712252eefdd0af9559193b6cda0ae8b1211b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:18:40 -0400 Subject: [PATCH 10/17] build: xo_add_shared_library() -> xo_add_shared_library3() --- cmake/printjsonConfig.cmake.in | 1 + src/printjson/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cmake/printjsonConfig.cmake.in b/cmake/printjsonConfig.cmake.in index e7f7f1be..9417e239 100644 --- a/cmake/printjsonConfig.cmake.in +++ b/cmake/printjsonConfig.cmake.in @@ -2,5 +2,6 @@ include(CMakeFindDependencyMacro) find_dependency(reflect) + include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") diff --git a/src/printjson/CMakeLists.txt b/src/printjson/CMakeLists.txt index cf191d02..b0a4d532 100644 --- a/src/printjson/CMakeLists.txt +++ b/src/printjson/CMakeLists.txt @@ -3,7 +3,7 @@ set(SELF_LIB printjson) set(SELF_SRCS PrintJson.cpp init_printjson.cpp) -xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_add_shared_library3(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) # ---------------------------------------------------------------- # dependencies: indentlog, ... From 652362a127e70c06e2c26bdffb9bb1aaea2eacfc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:18:57 -0400 Subject: [PATCH 11/17] doc: README additions --- README.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 99492884..28ef1b15 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,13 @@ # printjson library -# build + install +## Getting Started + +### build + install dependencies + +- [github/Rconybea/reflect](https://github.com/Rconybea/reflect) + +### build + install + ``` $ cd xo-printjson $ mkdir build @@ -11,7 +18,8 @@ $ make $ make install ``` -# build for unit test coverage +### build for unit test coverage + ``` $ cd xo-printjson $ mkdir ccov @@ -22,7 +30,8 @@ $ make ccov # runs instrumented unit tests $ make ccov-all # generates lcov report ``` -# LSP support +### 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 From e3f6cda58fe00283ca880eb37b33ba44a3a73948 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 16:30:55 -0400 Subject: [PATCH 12/17] build: make symlink-aware --- CMakeLists.txt | 5 ----- src/printjson/CMakeLists.txt | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index af42e3e7..99aa239f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,9 +44,4 @@ add_subdirectory(utest) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) -# ---------------------------------------------------------------- -# install .hpp files - -xo_install_include_tree() - # end CMakeLists.txt diff --git a/src/printjson/CMakeLists.txt b/src/printjson/CMakeLists.txt index b0a4d532..2acf74cf 100644 --- a/src/printjson/CMakeLists.txt +++ b/src/printjson/CMakeLists.txt @@ -3,7 +3,7 @@ set(SELF_LIB printjson) set(SELF_SRCS PrintJson.cpp init_printjson.cpp) -xo_add_shared_library3(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) # ---------------------------------------------------------------- # dependencies: indentlog, ... From 884eb3acc41e80a580313c0b6b4a04f0c0e712f3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:32:51 -0400 Subject: [PATCH 13/17] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 3 +-- CMakeLists.txt | 5 ++--- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 3 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index 2323db6f..7d076252 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ # lsp keeps state here .cache # typical build directories -build -ccov +.build* # compile_commands.json symlink -> build/compile_commands.json should be created manually compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 99aa239f..b12b9f56 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,9 +5,8 @@ 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) +# common XO cmake macros (see https://github.com/rconybea/xo-cmake) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From dd2bebc7e871f3112fc5ca0f64068e5095ab424a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 14/17] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From ea8faf70328621f1469cae1dc1cfc0b7c4e73796 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 12:08:54 -0500 Subject: [PATCH 15/17] xo-printjson: build: update for latest xo-cmake macros --- CMakeLists.txt | 20 ++---------------- utest/CMakeLists.txt | 50 +------------------------------------------- 2 files changed, 3 insertions(+), 67 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b12b9f56..1ba80032 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,25 +3,11 @@ cmake_minimum_required(VERSION 3.10) project(printjson VERSION 0.1) -enable_language(CXX) -# common XO cmake macros (see https://github.com/rconybea/xo-cmake) +include(GNUInstallDirs) include(cmake/xo-bootstrap-macros.cmake) -# ---------------------------------------------------------------- -# 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/*) +xo_cxx_toplevel_options3() # ---------------------------------------------------------------- # c++ settings @@ -31,8 +17,6 @@ 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) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 0b2177e8..8b4e7828 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -3,56 +3,8 @@ set(SELF_EXE utest.printjson) set(SELF_SRCS printjson_utest_main.cpp PrintJson.test.cpp) -add_executable(${SELF_EXE} ${SELF_SRCS}) -xo_include_options2(${SELF_EXE}) - -add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) -target_code_coverage(${SELF_EXE} AUTO ALL) - -# ---------------------------------------------------------------- -# generic project dependency - -## PROJECT_SOURCE_DIR: -## so we can for example write -## #include "indentlog/scope.hpp" -## from anywhere in the project -## PROJECT_BINARY_DIR: -## since version file will be in build directory, need that directory -## to also be included in compiler's include path -## -#target_include_directories(${SELF_EXE} PUBLIC -# ${PROJECT_SOURCE_DIR} -# ${PROJECT_BINARY_DIR}) - -# ---------------------------------------------------------------- -# dependencies on this codebase - +xo_add_utest_executable(${SELF_EXE} ${SELF_SRCS}) xo_self_dependency(${SELF_EXE} printjson) - -# ---------------------------------------------------------------- -# dependencies on other codebases - xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) -# need this so that catch2/include appears in compile_commands.json, -# on which lsp integration relies. -# -# See also /nix/store/*-catch2-*/lib/cmake/Catch2/ParseAndAddCatchTests.cmake; -# commands here derived from ^ .cmake file -# -#find_path(CATCH_INCLUDE_DIR "catch2/catch.hpp") -#target_include_directories(${SELF_EXE} PUBLIC ${CATCH_INCLUDE_DIR}) - -# supplied from xo_include_options2() -## ---------------------------------------------------------------- -## make standard directories for std:: includes explicit -## so that -## (1) they appear in compile_commands.json. -## (2) clangd (run from emacs lsp-mode) can find them -## -#if(CMAKE_EXPORT_COMPILE_COMMANDS) -# set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES -# ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) -#endif() - # end CMakeLists.txt From cac526a5170fa0c2356fc1978dfa41c87058d9d5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 12:09:06 -0500 Subject: [PATCH 16/17] xo-printjson: bugfix: adjust for upstream changes: xo::ref::rp -> xo::rp xo::quoted -> xo::quot new reflection metatype mt_function --- include/xo/printjson/PrintJson.hpp | 6 +++--- src/printjson/PrintJson.cpp | 12 +++++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/include/xo/printjson/PrintJson.hpp b/include/xo/printjson/PrintJson.hpp index c0ddcf55..8f463c27 100644 --- a/include/xo/printjson/PrintJson.hpp +++ b/include/xo/printjson/PrintJson.hpp @@ -41,7 +41,7 @@ namespace xo { /* convenience -- shorthand for * .print(obj->self_tp(), p_os) */ - void print_obj(ref::rp const & obj, std::ostream * p_os) const; + void print_obj(rp const & obj, std::ostream * p_os) const; void provide_printer(TypeId id, std::unique_ptr p) { *(printer_map_.require(id)) = std::move(p); @@ -74,13 +74,13 @@ namespace xo { */ class PrintJsonSingleton { public: - static ref::rp instance(); + static rp 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 s_instance; + static rp s_instance; }; /*PrintJsonSingleton*/ } /*namespace json*/ diff --git a/src/printjson/PrintJson.cpp b/src/printjson/PrintJson.cpp index 7df2d5b3..3d5ae34b 100644 --- a/src/printjson/PrintJson.cpp +++ b/src/printjson/PrintJson.cpp @@ -17,9 +17,8 @@ namespace xo { using xo::reflect::TypeDescr; using xo::reflect::TaggedPtr; using xo::reflect::TaggedRcptr; - using xo::print::quoted; + using xo::print::quot; using xo::time::iso8601; - using xo::ref::rp; using xo::xtag; namespace json { @@ -153,6 +152,13 @@ namespace xo { case Metatype::mt_struct: print_generic_struct(*this, tp, p_os); return; + case Metatype::mt_function: + /** new branch (added for xo-expression / xo-jit) **/ + (*p_os) << "canonical_name()) + << xtag("metatype", tp.td()->metatype()) + << ">"; + return; case Metatype::mt_invalid: case Metatype::mt_atomic: break; @@ -340,7 +346,7 @@ namespace xo { if (x) { /* TODO: escapes special characters */ - *p_os << quoted(*x); + *p_os << quot(*x); } else { report_internal_type_consistency_error(Reflect::require(), tp.td(), From 6743b9ef1830cead15bbf0a378e616406e9ab173 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 12:29:49 -0500 Subject: [PATCH 17/17] xo-printjson: build: use xo-cmake-config from xo-cmake macros --- cmake/xo-bootstrap-macros.cmake | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 96592216..aba31169 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -1,14 +1,35 @@ -if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) - # default to typical install location for xo-project-macros - set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") endif() +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + if (NOT XO_SUBMODULE_BUILD) - message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") - message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) # -include(xo_macros/xo-project-macros) +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message()