summaryrefslogtreecommitdiff
path: root/src/json.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/json.cc')
-rw-r--r--src/json.cc175
1 files changed, 175 insertions, 0 deletions
diff --git a/src/json.cc b/src/json.cc
new file mode 100644
index 00000000..dcb4005a
--- /dev/null
+++ b/src/json.cc
@@ -0,0 +1,175 @@
+#include "json.hh"
+
+#include "exception.hh"
+#include "string_utils.hh"
+#include "unit_tests.hh"
+#include "utils.hh"
+
+#include <cstdio>
+
+namespace Kakoune
+{
+
+String to_json(int i) { return to_string(i); }
+String to_json(bool b) { return b ? "true" : "false"; }
+String to_json(StringView str)
+{
+ String res;
+ res.reserve(str.length() + 4);
+ res += '"';
+ for (auto it = str.begin(), end = str.end(); it != end; )
+ {
+ auto next = std::find_if(it, end, [](char c) {
+ return c == '\\' or c == '"' or (c >= 0 and c <= 0x1F);
+ });
+
+ res += StringView{it, next};
+ if (next == end)
+ break;
+
+ char buf[7] = {'\\', *next, 0};
+ if (*next >= 0 and *next <= 0x1F)
+ sprintf(buf, "\\u%04x", *next);
+
+ res += buf;
+ it = next+1;
+ }
+ res += '"';
+ return res;
+}
+
+static bool is_digit(char c) { return c >= '0' and c <= '9'; }
+
+JsonResult parse_json(const char* pos, const char* end)
+{
+ if (not skip_while(pos, end, is_blank))
+ return {};
+
+ if (is_digit(*pos) or *pos == '-')
+ {
+ auto digit_end = pos + 1;
+ skip_while(digit_end, end, is_digit);
+ return { Value{str_to_int({pos, digit_end})}, digit_end };
+ }
+ if (end - pos > 4 and StringView{pos, pos+4} == "true")
+ return { Value{true}, pos+4 };
+ if (end - pos > 5 and StringView{pos, pos+5} == "false")
+ return { Value{false}, pos+5 };
+ if (*pos == '"')
+ {
+ String value;
+ bool escaped = false;
+ ++pos;
+ for (auto string_end = pos; string_end != end; ++string_end)
+ {
+ if (escaped)
+ {
+ escaped = false;
+ value += StringView{pos, string_end};
+ value.back() = *string_end;
+ pos = string_end+1;
+ continue;
+ }
+ if (*string_end == '\\')
+ escaped = true;
+ if (*string_end == '"')
+ {
+ value += StringView{pos, string_end};
+ return {std::move(value), string_end+1};
+ }
+ }
+ return {};
+ }
+ if (*pos == '[')
+ {
+ JsonArray array;
+ if (++pos == end)
+ throw runtime_error("unable to parse array");
+ if (*pos == ']')
+ return {std::move(array), pos+1};
+
+ while (true)
+ {
+ auto [element, new_pos] = parse_json(pos, end);
+ if (not element)
+ return {};
+ pos = new_pos;
+ array.push_back(std::move(element));
+ if (not skip_while(pos, end, is_blank))
+ return {};
+
+ if (*pos == ',')
+ ++pos;
+ else if (*pos == ']')
+ return {std::move(array), pos+1};
+ else
+ throw runtime_error("unable to parse array, expected ',' or ']'");
+ }
+ }
+ if (*pos == '{')
+ {
+ if (++pos == end)
+ throw runtime_error("unable to parse object");
+ JsonObject object;
+ if (*pos == '}')
+ return {std::move(object), pos+1};
+
+ while (true)
+ {
+ auto [name_value, name_end] = parse_json(pos, end);
+ if (not name_value)
+ return {};
+ pos = name_end;
+ String& name = name_value.as<String>();
+ if (not skip_while(pos, end, is_blank))
+ return {};
+ if (*pos++ != ':')
+ throw runtime_error("expected :");
+
+ auto [element, element_end] = parse_json(pos, end);
+ if (not element)
+ return {};
+ pos = element_end;
+ object.insert({ std::move(name), std::move(element) });
+ if (not skip_while(pos, end, is_blank))
+ return {};
+
+ if (*pos == ',')
+ ++pos;
+ else if (*pos == '}')
+ return {std::move(object), pos+1};
+ else
+ throw runtime_error("unable to parse object, expected ',' or '}'");
+ }
+ }
+ throw runtime_error("unable to parse json");
+}
+
+JsonResult parse_json(StringView json) { return parse_json(json.begin(), json.end()); }
+
+UnitTest test_json_parser{[]()
+{
+ {
+ auto value = parse_json(R"({ "jsonrpc": "2.0", "method": "keys", "params": [ "b", "l", "a", "h" ] })").value;
+ kak_assert(value);
+ }
+
+ {
+ auto value = parse_json("[10,20]").value;
+ kak_assert(value and value.is_a<JsonArray>());
+ kak_assert(value.as<JsonArray>().at(1).as<int>() == 20);
+ }
+
+ {
+ auto value = parse_json("-1").value;
+ kak_assert(value.as<int>() == -1);
+ }
+
+ {
+ auto value = parse_json("{}").value;
+ kak_assert(value and value.is_a<JsonObject>());
+ kak_assert(value.as<JsonObject>().empty());
+ }
+}};
+
+}