diff options
| author | Frank LENORMAND <lenormf@gmail.com> | 2019-11-13 09:54:17 +0100 |
|---|---|---|
| committer | Frank LENORMAND <lenormf@gmail.com> | 2019-11-17 09:27:46 +0100 |
| commit | 7cdbe1d3d24c1cc13bd7cbc3fe252f1e88747ffb (patch) | |
| tree | 3d1bc2cb81cb4c9623bd18a0dcc9fc607fa8db53 /src/json.cc | |
| parent | a7d3976a1002a80c4e5a17c989921025ded89450 (diff) | |
src: Move JSON parsing code to its own file
The `json_ui.cc` file contained both data-parsing and UI-related
code. This commit moves the JSON parsing code to its own `json.cc`
file, to separate concerns, make compilation faster when changes are
made to either UI or parsing code, and make the parsing code more
accessible to fuzzers.
The signature of the following function:
```
auto parse_json(StringView json);
```
was changed to:
```
JsonResult parse_json(StringView json);
```
to avoid `auto` deduction issues at compile-time.
Diffstat (limited to 'src/json.cc')
| -rw-r--r-- | src/json.cc | 175 |
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()); + } +}}; + +} |
