diff options
-rw-r--r-- | lib/jsonParse.h | 43 | ||||
-rw-r--r-- | lib/jsonParse.impl.cpp | 39 | ||||
-rw-r--r-- | lib/jsonParse.ll | 156 | ||||
-rw-r--r-- | test/Jamfile.jam | 1 | ||||
-rw-r--r-- | test/test-persistance.cpp | 281 |
5 files changed, 520 insertions, 0 deletions
diff --git a/lib/jsonParse.h b/lib/jsonParse.h new file mode 100644 index 0000000..6b1249f --- /dev/null +++ b/lib/jsonParse.h @@ -0,0 +1,43 @@ +#ifndef JSONFLEXLEXER_H +#define JSONFLEXLEXER_H + +#ifndef FLEX_SCANNER +# define yyFlexLexer jsonBaseFlexLexer +# include <FlexLexer.h> +#endif +#include <cassert> +#include <filesystem> +#include <fstream> +#include <memory> +#include <stdexcept> +#include <string> + +namespace json { + class jsonParser : public yyFlexLexer { + public: + using yyFlexLexer::yyFlexLexer; + int yylex() override; + + static void appendEscape(const char *, std::string &); + static void appendEscape(unsigned long, std::string &); + + protected: + virtual void BeginObject() = 0; + virtual void BeginArray() = 0; + + virtual void PushBoolean(bool) = 0; + virtual void PushNumber(float) = 0; + virtual void PushNull() = 0; + virtual void PushText(std::string &&) = 0; + virtual void PushKey(std::string &&) = 0; + + virtual void EndArray() = 0; + virtual void EndObject() = 0; + + void LexerError(const char * msg) override; + + std::string buf; + }; +} + +#endif diff --git a/lib/jsonParse.impl.cpp b/lib/jsonParse.impl.cpp new file mode 100644 index 0000000..27e73b4 --- /dev/null +++ b/lib/jsonParse.impl.cpp @@ -0,0 +1,39 @@ +#include "jsonParse.h" + +void +json::jsonParser::LexerError(const char * msg) +{ + throw std::runtime_error(msg); +} + +void +json::jsonParser::appendEscape(const char * cphs, std::string & str) +{ + appendEscape(std::strtoul(cphs, nullptr, 16), str); +} + +void +json::jsonParser::appendEscape(unsigned long cp, std::string & str) +{ + if (cp <= 0x7F) { + str += cp; + } + else if (cp <= 0x7FF) { + str += (cp >> 6) + 192; + str += (cp & 63) + 128; + } + else if (0xd800 <= cp && cp <= 0xdfff) { + throw std::range_error("Invalid UTF-8 sequence"); + } + else if (cp <= 0xFFFF) { + str += (cp >> 12) + 224; + str += ((cp >> 6) & 63) + 128; + str += (cp & 63) + 128; + } + else if (cp <= 0x10FFFF) { + str += (cp >> 18) + 240; + str += ((cp >> 12) & 63) + 128; + str += ((cp >> 6) & 63) + 128; + str += (cp & 63) + 128; + } +} diff --git a/lib/jsonParse.ll b/lib/jsonParse.ll new file mode 100644 index 0000000..0fc27e9 --- /dev/null +++ b/lib/jsonParse.ll @@ -0,0 +1,156 @@ +%option batch +%option c++ +%option noyywrap +%option 8bit +%option stack +%option yylineno +%option yyclass="json::jsonParser" +%option prefix="jsonBase" + +%{ +#include <string> +#include "jsonParse.h" +#pragma GCC diagnostic ignored "-Wsign-compare" +#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wnull-conversion" +#endif +%} + +beginobj "{" +endobj "}" +beginarray "[" +endarray "]" +beginstr "\"" +endstr "\"" +true "true" +false "false" +null "null" +number [-+]?[0-9]+(\.[0-9]+)? +colon ":" +separator "," +escape "\\" +text [^\\\"]* + +%x OBJECT_ITEM +%x OBJECT_ITEM_OR_END +%x OBJECT_NEXT +%x ARRAY_ITEM +%x ARRAY_NEXT +%x COLON +%x TEXT +%x STRING +%x ESCAPE + +%% + +<ARRAY_ITEM,INITIAL>{true} { + PushBoolean(true); + yy_pop_state(); +} + +<ARRAY_ITEM,INITIAL>{false} { + PushBoolean(false); + yy_pop_state(); +} + +<ARRAY_ITEM,INITIAL>{number} { + PushNumber(std::strtof(YYText(), NULL)); + yy_pop_state(); +} + +<ARRAY_ITEM,INITIAL>{null} { + PushNull(); + yy_pop_state(); +} + +<ARRAY_ITEM,INITIAL,OBJECT_ITEM,OBJECT_ITEM_OR_END>{beginstr} { + yy_push_state(STRING); +} + +<ARRAY_ITEM,INITIAL>{beginobj} { + BeginObject(); + BEGIN(OBJECT_ITEM_OR_END); +} + +<ARRAY_ITEM,INITIAL>{beginarray} { + BeginArray(); + BEGIN(ARRAY_NEXT); + yy_push_state(ARRAY_ITEM); +} + +<STRING>{endstr} { + yy_pop_state(); + switch (YY_START) { + case ARRAY_ITEM: + case INITIAL: + PushText(std::move(buf)); + yy_pop_state(); + break; + case OBJECT_ITEM: + case OBJECT_ITEM_OR_END: + PushKey(std::move(buf)); + BEGIN(COLON); + break; + } + buf.clear(); +} + +<OBJECT_NEXT,OBJECT_ITEM_OR_END>{endobj} { + EndObject(); + yy_pop_state(); +} + +<ARRAY_ITEM>{endarray} { + EndArray(); + yy_pop_state(); + yy_pop_state(); +} + +<ARRAY_NEXT>{endarray} { + EndArray(); + yy_pop_state(); +} + +<COLON>{colon} { + BEGIN(OBJECT_NEXT); + yy_push_state(INITIAL); +} + +<OBJECT_NEXT>{separator} { + BEGIN(OBJECT_ITEM); +} + +<ARRAY_NEXT>{separator} { + yy_push_state(INITIAL); +} + +<STRING>{escape} { + yy_push_state(ESCAPE); +} + +<ESCAPE>"\"" { buf += "\""; yy_pop_state(); } +<ESCAPE>"\\" { buf += "\\"; yy_pop_state(); } +<ESCAPE>"/" { buf += "/"; yy_pop_state(); } +<ESCAPE>"b" { buf += "\b"; yy_pop_state(); } +<ESCAPE>"f" { buf += "\f"; yy_pop_state(); } +<ESCAPE>"n" { buf += "\n"; yy_pop_state(); } +<ESCAPE>"r" { buf += "\r"; yy_pop_state(); } +<ESCAPE>"t" { buf += "\t"; yy_pop_state(); } + +<ESCAPE>"u"[0-9a-fA-Z]{4} { + appendEscape(YYText(), buf); + yy_pop_state(); +} + +<STRING>{text} { + buf += YYText(); +} + +<*>[ \t\r\n\f] { +} + +%% + +// Make iwyu think unistd.h is required +[[maybe_unused]]static auto x=getpid; diff --git a/test/Jamfile.jam b/test/Jamfile.jam index f1fda72..c065dd8 100644 --- a/test/Jamfile.jam +++ b/test/Jamfile.jam @@ -18,3 +18,4 @@ run test-collection.cpp ; run test-obj.cpp ; run test-maths.cpp ; run test-network.cpp ; +run test-persistance.cpp ; diff --git a/test/test-persistance.cpp b/test/test-persistance.cpp new file mode 100644 index 0000000..7d66675 --- /dev/null +++ b/test/test-persistance.cpp @@ -0,0 +1,281 @@ +#define BOOST_TEST_MODULE test_persistance + +#include <boost/test/unit_test.hpp> + +#include <gfx/models/vertex.hpp> +#include <glm/glm.hpp> +#include <jsonParse.h> +#include <maths.h> +#include <memory> +#include <stack> +#include <stream_support.hpp> +#include <utility> +#include <variant> +#include <vector> + +struct PersistanceStore; +struct Selection; +using SelectionPtr = std::unique_ptr<Selection>; +struct Selection { + virtual ~Selection() = default; + virtual void + operator()(float &) + { + throw std::runtime_error("Unexpected float"); + } + virtual void + operator()(bool &) + { + throw std::runtime_error("Unexpected bool"); + } + virtual void + operator()(const std::nullptr_t &) + { + throw std::runtime_error("Unexpected null"); + } + virtual void + operator()(std::string &) + { + throw std::runtime_error("Unexpected string"); + } + virtual void + BeginArray() + { + throw std::runtime_error("Unexpected array"); + } + virtual SelectionPtr + BeginObject() + { + throw std::runtime_error("Unexpected object"); + } + virtual SelectionPtr select(std::string) + { + throw std::runtime_error("Unexpected persist"); + } +}; + +template<typename T> struct SelectionT : public Selection { + SelectionT(T & value) : v {value} { } + void + operator()(T & evalue) override + { + std::swap(v, evalue); + } + T & v; +}; + +template<glm::length_t L, glm::qualifier Q> struct SelectionT<glm::vec<L, float, Q>> : public Selection { + SelectionT(glm::vec3 & value) : v {value} + { + CLOG(__PRETTY_FUNCTION__); + } + void + operator()(float & value) override + { + v[idx++] = value; + } + void + BeginArray() override + { + } + glm::vec3 & v; + glm::vec3::length_type idx {0}; +}; + +template<typename T> struct SelectionT<std::vector<T>> : public Selection { + SelectionT(std::vector<T> & value) : v {value} { } + void + operator()(T & value) override + { + v.push_back(value); + } + void + BeginArray() + { + } + std::vector<T> & v; +}; + +struct TestObject; + +template<typename Ptr> struct MakeObjectByTypeName : public Selection { + MakeObjectByTypeName(Ptr & o) : o {o} { } + virtual void + operator()(std::string &) + { + o = std::make_unique<TestObject>(); // TODO shared_ptr + } + Ptr & o; +}; + +struct PersistanceStore { + // virtual bool persistType(const std::type_info &) = 0; + template<typename T> + bool + persistValue(const std::string_view & key, T & value) + { + if (key == name) { + sel = std::make_unique<SelectionT<T>>(std::ref(value)); + return false; + } + return true; + } + const std::string & name; + std::unique_ptr<Selection> sel {}; +}; + +template<typename Ptr> struct SelectionObj : public Selection { + SelectionObj(Ptr & o) : v {o} { } + + SelectionPtr + select(std::string mbr) override + { + if (mbr == "@typeid") { + return std::make_unique<MakeObjectByTypeName<Ptr>>(std::ref(v)); + } + else if (v) { + CLOG(mbr); + PersistanceStore ps {mbr}; + v->persist(ps); + return std::move(ps.sel); + } + throw std::runtime_error("cannot select member of null object"); + } + + Ptr & v; +}; + +template<typename T> struct SelectionT<std::unique_ptr<T>> : public Selection { + SelectionT(std::unique_ptr<T> & o) : v {o} { } + SelectionPtr + BeginObject() override + { + CLOG(__PRETTY_FUNCTION__); + return std::make_unique<SelectionObj<std::unique_ptr<T>>>(v); + } + std::unique_ptr<T> & v; +}; + +struct Persistable { + virtual void persist(PersistanceStore & store) = 0; +}; + +struct JsonLoadPersistanceStore : public json::jsonParser { + JsonLoadPersistanceStore(std::istream & in) : json::jsonParser {&in} { } + + std::stack<std::unique_ptr<Selection>> stk; + std::unique_ptr<Selection> selection; + + template<typename T> + void + loadState(std::unique_ptr<T> & t) + { + yy_push_state(0); + stk.push(std::make_unique<SelectionT<std::unique_ptr<T>>>(std::ref(t))); + yylex(); + } + void + BeginObject() override + { + CLOG(__FUNCTION__); + stk.push(stk.top()->BeginObject()); + } + void + BeginArray() override + { + CLOG(__FUNCTION__); + selection->BeginArray(); + } + void + PushBoolean(bool value) override + { + CLOG(__FUNCTION__); + (*selection)(value); + } + void + PushNumber(float value) override + { + CLOG(__FUNCTION__); + (*selection)(value); + } + void + PushNull() override + { + CLOG(__FUNCTION__); + (*selection)(nullptr); + } + void + PushText(std::string && value) override + { + CLOG(__FUNCTION__); + (*selection)(value); + } + void + PushKey(std::string && k) override + { + CLOG(__FUNCTION__); + selection = stk.top()->select(std::move(k)); + } + void + EndArray() override + { + CLOG(__FUNCTION__); + } + void + EndObject() override + { + CLOG(__FUNCTION__); + stk.pop(); + } +}; + +#define STORE_TYPE store.persistType(typeid(*this)) +#define STORE_MEMBER(mbr) store.persistValue(#mbr##sv, mbr) + +struct TestObject : public Persistable { + TestObject() = default; + + float flt {}; + std::string str {}; + bool bl {}; + glm::vec3 pos {}; + std::vector<float> flts; + + void + persist(PersistanceStore & store) override + { + using namespace std::literals; + STORE_MEMBER(flt) && STORE_MEMBER(str) && STORE_MEMBER(bl) && STORE_MEMBER(pos) && STORE_MEMBER(flts); + } +}; + +const std::string input(R"J({ + "@typeid": "TestObject", + "flt": 3.14, + "str": "Lovely string", + "bl": true, + "pos": [3.14, 6.28, 1.57], + "flts": [3.14, 6.28, 1.57, 0, -1, -3.14], +})J"); + +BOOST_AUTO_TEST_CASE(load_object) +{ + std::stringstream ss {input}; + JsonLoadPersistanceStore jlps {ss}; + std::unique_ptr<TestObject> to; + jlps.loadState(to); + + BOOST_CHECK_CLOSE(to->flt, 3.14, 0.01); + BOOST_CHECK_EQUAL(to->str, "Lovely string"); + BOOST_CHECK_EQUAL(to->bl, true); + BOOST_CHECK_CLOSE(to->pos[0], 3.14, 0.01); + BOOST_CHECK_CLOSE(to->pos[1], 6.28, 0.01); + BOOST_CHECK_CLOSE(to->pos[2], 1.57, 0.01); + BOOST_REQUIRE_EQUAL(to->flts.size(), 6); + BOOST_CHECK_CLOSE(to->flts[0], 3.14, 0.01); + BOOST_CHECK_CLOSE(to->flts[1], 6.28, 0.01); + BOOST_CHECK_CLOSE(to->flts[2], 1.57, 0.01); + BOOST_CHECK_CLOSE(to->flts[3], 0, 0.01); + BOOST_CHECK_CLOSE(to->flts[4], -1, 0.01); + BOOST_CHECK_CLOSE(to->flts[5], -3.14, 0.01); +} |