summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--meson.build22
-rw-r--r--src/json.cc305
-rw-r--r--src/json.hh46
-rw-r--r--test/json.cc221
4 files changed, 594 insertions, 0 deletions
diff --git a/meson.build b/meson.build
index 2b74a48..b17ed18 100644
--- a/meson.build
+++ b/meson.build
@@ -179,6 +179,18 @@ signals_dep = declare_dependency(
dependencies: [io_dep, looper_dep],
)
+json_lib = library(
+ 'json',
+ sources: [
+ 'src/json.cc',
+ 'src/json.hh',
+ ],
+ include_directories: inc,
+)
+json_dep = declare_dependency(
+ link_with: json_lib,
+)
+
base64_lib = library(
'base64',
sources: [
@@ -323,6 +335,16 @@ test('cfg', executable(
],
))
+test('json', executable(
+ 'test_json',
+ sources: ['test/json.cc'],
+ include_directories: inc,
+ dependencies : [
+ json_dep,
+ test_dependencies,
+ ],
+))
+
test('base64', executable(
'test_base64',
sources: ['test/base64.cc'],
diff --git a/src/json.cc b/src/json.cc
new file mode 100644
index 0000000..94ff0d4
--- /dev/null
+++ b/src/json.cc
@@ -0,0 +1,305 @@
+#include "json.hh"
+
+#include <cassert>
+#include <charconv>
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <string_view>
+#include <system_error>
+#include <vector>
+
+namespace json {
+
+namespace {
+
+struct StackEntry {
+ enum class Type : uint8_t {
+ kRoot,
+ kObject,
+ kArray,
+ };
+
+ Type type;
+ bool need_comma{false};
+
+ explicit StackEntry(Type type) : type(type) {}
+};
+
+class BaseWriter : public Writer {
+ public:
+ BaseWriter() : stack_({StackEntry(StackEntry::Type::kRoot)}) {}
+
+ void value(std::string_view value) override {
+ before_value();
+ quote(value);
+ }
+
+ void value(int64_t value) override {
+ before_value();
+ write(value);
+ }
+
+ void value(uint64_t value) override {
+ before_value();
+ write(value);
+ }
+
+ void value(float value) override {
+ before_value();
+ write(value);
+ }
+
+ void value(double value) override {
+ before_value();
+ write(value);
+ }
+
+ void value(bool value) override {
+ before_value();
+ write(value);
+ }
+
+ void start_array() override {
+ before_value();
+ stack_.emplace_back(StackEntry::Type::kArray);
+ write('[');
+ }
+
+ void end_array() override {
+ if (stack_.empty()) {
+ assert(false);
+ return;
+ }
+ assert(stack_.back().type == StackEntry::Type::kArray);
+ stack_.pop_back();
+ write(']');
+ if (!stack_.empty())
+ stack_.back().need_comma = true;
+ }
+
+ void start_object() override {
+ before_value();
+ stack_.emplace_back(StackEntry::Type::kObject);
+ write('{');
+ }
+
+ void key(std::string_view name) override {
+ if (stack_.empty()) {
+ assert(false);
+ return;
+ }
+ assert(stack_.back().type == StackEntry::Type::kObject);
+ if (stack_.back().need_comma) {
+ write(',');
+ stack_.back().need_comma = false;
+ }
+ quote(name);
+ write(':');
+ }
+
+ void end_object() override {
+ if (stack_.empty()) {
+ assert(false);
+ return;
+ }
+ assert(stack_.back().type == StackEntry::Type::kObject);
+ stack_.pop_back();
+ write('}');
+ if (!stack_.empty())
+ stack_.back().need_comma = true;
+ }
+
+ void clear() override {
+ stack_.clear();
+ stack_.emplace_back(StackEntry::Type::kRoot);
+ }
+
+ protected:
+ virtual void write(int64_t value) = 0;
+ virtual void write(uint64_t value) = 0;
+ virtual void write(float value) = 0;
+ virtual void write(double value) = 0;
+ virtual void write(bool value) = 0;
+ virtual void write(char value) = 0;
+ virtual void write(std::string_view value) = 0;
+
+ void write(char const* value) { this->write(std::string_view(value)); }
+
+ private:
+ void before_value() {
+ if (stack_.empty()) {
+ assert(false);
+ return;
+ }
+ if (stack_.back().need_comma) {
+ assert(stack_.back().type != StackEntry::Type::kRoot);
+ write(',');
+ } else {
+ stack_.back().need_comma = true;
+ }
+ }
+
+ void quote(std::string_view str) {
+ write('"');
+ size_t last = 0;
+ while (true) {
+ auto pos = need_quote(str, last);
+ if (pos == std::string_view::npos) {
+ write(str.substr(last));
+ break;
+ }
+ write(str.substr(last, pos - last));
+ switch (str[pos]) {
+ case '"':
+ write("\\\"");
+ break;
+ case '\\':
+ write("\\\\");
+ break;
+ case '\n':
+ write("\\n");
+ break;
+ case '\r':
+ write("\\r");
+ break;
+ case '\t':
+ write("\\t");
+ break;
+ case '\b':
+ write("\\b");
+ break;
+ case '\f':
+ write("\\f");
+ break;
+ default: {
+ char tmp[4];
+ write("\\u");
+ auto [ptr, ec] = std::to_chars(tmp, tmp + sizeof(tmp), str[pos], 16);
+ if (ec == std::errc()) {
+ size_t len = ptr - tmp;
+ assert(len > 0);
+ for (size_t i = 4; i > len; --i)
+ write('0');
+ write(std::string_view(tmp, len));
+ } else {
+ assert(false);
+ }
+ break;
+ };
+ }
+ last = pos + 1;
+ }
+ write('"');
+ }
+
+ static inline size_t need_quote(std::string_view str, size_t offset) {
+ for (; offset < str.size(); ++offset) {
+ if (str[offset] == '\\' || str[offset] == '"' ||
+ (str[offset] >= 0 && str[offset] < ' '))
+ return offset;
+ }
+ return std::string_view::npos;
+ }
+
+ std::vector<StackEntry> stack_;
+};
+
+class IosWriter : public BaseWriter {
+ public:
+ explicit IosWriter(std::ostream& out) : out_(out) {}
+
+ void write(std::string_view value) override { out_ << value; }
+
+ void write(int64_t value) override { out_ << value; }
+
+ void write(uint64_t value) override { out_ << value; }
+
+ void write(float value) override { out_ << value; }
+
+ void write(double value) override { out_ << value; }
+
+ void write(bool value) override { out_ << value; }
+
+ void write(char value) override { out_ << value; }
+
+ private:
+ std::ostream& out_;
+};
+
+class StringWriter : public BaseWriter {
+ public:
+ explicit StringWriter(std::string& out) : out_(out) {}
+
+ void write(std::string_view value) override { out_.append(value); }
+
+ void write(int64_t value) override {
+ auto [ptr, ec] = std::to_chars(tmp_, tmp_ + sizeof(tmp_), value);
+ if (ec == std::errc()) {
+ out_.append(tmp_, ptr - tmp_);
+ } else {
+ assert(false);
+ }
+ }
+
+ void write(uint64_t value) override {
+ auto [ptr, ec] = std::to_chars(tmp_, tmp_ + sizeof(tmp_), value);
+ if (ec == std::errc()) {
+ out_.append(tmp_, ptr - tmp_);
+ } else {
+ assert(false);
+ }
+ }
+
+ void write(float value) override {
+ auto [ptr, ec] = std::to_chars(tmp_, tmp_ + sizeof(tmp_), value);
+ if (ec == std::errc()) {
+ out_.append(tmp_, ptr - tmp_);
+ } else {
+ assert(false);
+ }
+ }
+
+ void write(double value) override {
+ auto [ptr, ec] = std::to_chars(tmp_, tmp_ + sizeof(tmp_), value);
+ if (ec == std::errc()) {
+ out_.append(tmp_, ptr - tmp_);
+ } else {
+ assert(false);
+ }
+ }
+
+ void write(bool value) override { out_.append(value ? "true" : "false"); }
+
+ void write(char value) override { out_.push_back(value); }
+
+ void clear() override {
+ BaseWriter::clear();
+ out_.clear();
+ }
+
+ private:
+ std::string& out_;
+ char tmp_[100];
+};
+
+} // namespace
+
+void Writer::value(char const* value) { this->value(std::string_view(value)); }
+
+void Writer::value(int value) {
+ static_assert(sizeof(int) <= sizeof(int64_t));
+ this->value(static_cast<int64_t>(value));
+}
+
+std::unique_ptr<Writer> writer(std::string& out) {
+ return std::make_unique<StringWriter>(out);
+}
+
+std::unique_ptr<Writer> writer(std::ostream& out) {
+ return std::make_unique<IosWriter>(out);
+}
+
+} // namespace json
diff --git a/src/json.hh b/src/json.hh
new file mode 100644
index 0000000..d872dde
--- /dev/null
+++ b/src/json.hh
@@ -0,0 +1,46 @@
+#ifndef JSON_HH
+#define JSON_HH
+
+#include <cstdint>
+#include <iosfwd>
+#include <memory>
+#include <string>
+#include <string_view>
+
+namespace json {
+
+class Writer {
+ public:
+ virtual ~Writer() = default;
+
+ virtual void value(std::string_view value) = 0;
+ virtual void value(int64_t value) = 0;
+ virtual void value(uint64_t value) = 0;
+ virtual void value(float value) = 0;
+ virtual void value(double value) = 0;
+ virtual void value(bool value) = 0;
+
+ void value(char const* value);
+ void value(int value);
+
+ virtual void start_array() = 0;
+ virtual void end_array() = 0;
+
+ virtual void start_object() = 0;
+ virtual void key(std::string_view name) = 0;
+ virtual void end_object() = 0;
+
+ virtual void clear() = 0;
+
+ protected:
+ Writer() = default;
+ Writer(Writer const&) = delete;
+ Writer& operator=(Writer const&) = delete;
+};
+
+std::unique_ptr<Writer> writer(std::string& out);
+std::unique_ptr<Writer> writer(std::ostream& out);
+
+} // namespace json
+
+#endif // JSON_HH
diff --git a/test/json.cc b/test/json.cc
new file mode 100644
index 0000000..2fc7ad2
--- /dev/null
+++ b/test/json.cc
@@ -0,0 +1,221 @@
+#include "json.hh"
+
+#include <cstdint>
+#include <gtest/gtest.h>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <utility>
+
+namespace {
+
+enum class Output : uint8_t {
+ String,
+ Stream,
+};
+
+class Writer : public testing::TestWithParam<Output> {
+ protected:
+ std::unique_ptr<json::Writer> make() {
+ switch (GetParam()) {
+ case Output::String:
+ return json::writer(str_);
+ case Output::Stream:
+ return json::writer(stream_);
+ }
+ std::unreachable();
+ }
+
+ std::string str() {
+ switch (GetParam()) {
+ case Output::String:
+ return std::move(str_);
+ case Output::Stream:
+ return stream_.str();
+ }
+ std::unreachable();
+ }
+
+ private:
+ std::string str_;
+ std::stringstream stream_;
+};
+
+} // namespace
+
+TEST_P(Writer, example1) {
+ auto writer = make();
+ writer->start_object();
+
+ writer->key("glossary");
+ writer->start_object();
+
+ writer->key("title");
+ writer->value("example glossary");
+
+ writer->key("GlossDiv");
+ writer->start_object();
+
+ writer->key("title");
+ writer->value("S");
+
+ writer->key("GlossList");
+ writer->start_object();
+
+ writer->key("GlossEntry");
+ writer->start_object();
+
+ writer->key("ID");
+ writer->value("SGML");
+ writer->key("SortAs");
+ writer->value("SGML");
+ writer->key("GlossTerm");
+ writer->value("Standard Generalized Markup Language");
+ writer->key("Acronym");
+ writer->value("SGML");
+ writer->key("Abbrev");
+ writer->value("ISO 8879:1986");
+
+ writer->key("GlossDef");
+ writer->start_object();
+
+ writer->key("para");
+ writer->value(
+ "A meta-markup language, used to create markup languages such as "
+ "DocBook.");
+
+ writer->key("GlossSeeAlso");
+ writer->start_array();
+ writer->value("GML");
+ writer->value("XML");
+ writer->end_array();
+
+ writer->end_object();
+
+ writer->key("GlossSee");
+ writer->value("markup");
+
+ writer->end_object();
+
+ writer->end_object();
+
+ writer->end_object();
+
+ writer->end_object();
+
+ writer->end_object();
+
+ EXPECT_EQ(
+ R"({"glossary":{"title":"example glossary","GlossDiv":{"title":"S","GlossList":{"GlossEntry":{"ID":"SGML","SortAs":"SGML","GlossTerm":"Standard Generalized Markup Language","Acronym":"SGML","Abbrev":"ISO 8879:1986","GlossDef":{"para":"A meta-markup language, used to create markup languages such as DocBook.","GlossSeeAlso":["GML","XML"]},"GlossSee":"markup"}}}}})",
+ str());
+}
+
+TEST_P(Writer, example2) {
+ auto writer = make();
+ writer->start_object();
+
+ writer->key("widget");
+ writer->start_object();
+
+ writer->key("debug");
+ writer->value("on");
+
+ writer->key("window");
+ writer->start_object();
+
+ writer->key("title");
+ writer->value("Sample Konfabulator Widget");
+ writer->key("name");
+ writer->value("main_window");
+ writer->key("width");
+ writer->value(500);
+ writer->key("height");
+ writer->value(500);
+
+ writer->end_object();
+
+ writer->key("image");
+ writer->start_object();
+
+ writer->key("src");
+ writer->value("Images/Sun.png");
+ writer->key("name");
+ writer->value("sun1");
+ writer->key("hOffset");
+ writer->value(250);
+ writer->key("vOffset");
+ writer->value(250);
+ writer->key("alignment");
+ writer->value("center");
+
+ writer->end_object();
+
+ writer->key("text");
+ writer->start_object();
+
+ writer->key("data");
+ writer->value("Click Here");
+ writer->key("size");
+ writer->value(36);
+ writer->key("style");
+ writer->value("bold");
+ writer->key("name");
+ writer->value("text1");
+ writer->key("hOffset");
+ writer->value(250);
+ writer->key("vOffset");
+ writer->value(100);
+ writer->key("alignment");
+ writer->value("center");
+ writer->key("onMouseUp");
+ writer->value("sun1.opacity = (sun1.opacity / 100) * 90;");
+
+ writer->end_object();
+
+ writer->end_object();
+
+ writer->end_object();
+
+ EXPECT_EQ(
+ R"({"widget":{"debug":"on","window":{"title":"Sample Konfabulator Widget","name":"main_window","width":500,"height":500},"image":{"src":"Images/Sun.png","name":"sun1","hOffset":250,"vOffset":250,"alignment":"center"},"text":{"data":"Click Here","size":36,"style":"bold","name":"text1","hOffset":250,"vOffset":100,"alignment":"center","onMouseUp":"sun1.opacity = (sun1.opacity / 100) * 90;"}}})",
+ str());
+}
+
+TEST_P(Writer, quote1) {
+ auto writer = make();
+ writer->value(R"("Example text")");
+
+ EXPECT_EQ(R"("\"Example text\"")", str());
+}
+
+TEST_P(Writer, quote2) {
+ auto writer = make();
+ writer->value(R"(\"Example text\")");
+
+ EXPECT_EQ(R"("\\\"Example text\\\"")", str());
+}
+
+TEST_P(Writer, quote3) {
+ auto writer = make();
+ writer->value(R"(
+)");
+
+ EXPECT_EQ(R"("\n")", str());
+}
+
+TEST_P(Writer, float) {
+ auto writer = make();
+ writer->value(3.14F);
+
+ EXPECT_EQ("3.14", str());
+}
+
+TEST_P(Writer, double) {
+ auto writer = make();
+ writer->value(3.14);
+
+ EXPECT_EQ("3.14", str());
+}
+
+INSTANTIATE_TEST_SUITE_P(AllOutputs, Writer,
+ testing::Values(Output::String, Output::Stream));