summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/jsonParse.h43
-rw-r--r--lib/jsonParse.impl.cpp39
-rw-r--r--lib/jsonParse.ll156
-rw-r--r--test/Jamfile.jam1
-rw-r--r--test/test-persistance.cpp281
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);
+}