kakoune/src/json.cc
Chris Webb a8d5b8bd2c Fix compiler warnings when char is unsigned
In several places, we check for a control character with something like

  char c;
  [...]
  if (c >= 0 and c <= 0x1F)
    [...]

When char is signed (e.g. amd64) this is fine, but when char is unsigned
by default (e.g. arm32 and arm64) this generates warnings about the
tautologous check that an unsigned value is non-negative.

Write as

  if ((unsigned char) c <= 0x1F)
    [...]

which is both correct and not suspicious under both conventions.
2023-12-10 11:09:55 +00:00

199 lines
5.7 KiB
C++

#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 (unsigned char) c <= 0x1F;
});
res += StringView{it, next};
if (next == end)
break;
char buf[7] = {'\\', *next, 0};
if ((unsigned char) *next <= 0x1F)
format_to(buf, "\\u{:04}", hex(*next));
res += buf;
it = next+1;
}
res += '"';
return res;
}
static bool is_digit(char c) { return c >= '0' and c <= '9'; }
static constexpr size_t max_parsing_depth = 100;
JsonResult parse_json_impl(const char* pos, const char* end, size_t depth)
{
if (not skip_while(pos, end, is_blank))
return {};
if (depth >= max_parsing_depth)
throw runtime_error("maximum parsing depth reached");
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_impl(pos, end, depth+1);
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_impl(pos, end, depth+1);
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_impl(pos, end, depth+1);
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(const char* pos, const char* end) { return parse_json_impl(pos, end, 0); }
JsonResult parse_json(StringView json) { return parse_json_impl(json.begin(), json.end(), 0); }
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());
}
{
char big_nested_array[max_parsing_depth*2+2+1] = {};
for (size_t i = 0; i < max_parsing_depth+1; i++)
{
big_nested_array[i] = '[';
big_nested_array[i+max_parsing_depth+1] = ']';
}
kak_expect_throw(runtime_error, parse_json(big_nested_array));
}
}};
UnitTest test_to_json{[]()
{
kak_assert(to_json(true) == "true");
kak_assert(to_json(false) == "false");
kak_assert(to_json(HashMap<String, Vector<int>>{{"foo", {1,2,3}}, {"\033", {3, 4, 5}}}) == R"({"foo": [1, 2, 3],"\u001b": [3, 4, 5]})");
}};
}