Something went wrong. Try again.
Monorepo for PackTools - tooling for Minecraft Modpacks!
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166#include "json_write.hpp"
#include "json.hpp"#include "lua.h"#include "report.hpp"
#include <algorithm>#include <array>#include <charconv>#include <cmath>#include <cstddef>#include <cstdint>#include <memory>#include <string_view>#include <system_error>#include <vector>
namespace packluau {
namespace {
constexpr double exact_integer_limit = 9007199254740992.0; // 2^53
class Writer { public: Writer(lua_State *state, std::string &out) noexcept : state_(state), out_(&out) {}
// NOLINTNEXTLINE(misc-no-recursion): bounded by max_depth. std::expected<void, std::string> value(int index, int depth, const std::string &path) { if (depth > max_depth) { return fail(path, "is nested too deeply (or refers to itself)"); } switch (lua_type(state_, index)) { case LUA_TBOOLEAN: *out_ += lua_toboolean(state_, index) != 0 ? "true" : "false"; return {}; case LUA_TNUMBER: return number(lua_tonumber(state_, index), path); case LUA_TSTRING: { std::size_t length = 0; const char *text = lua_tolstring(state_, index, &length); const std::string_view bytes(text, length); if (!is_utf8(bytes)) { return fail(path, "is not valid UTF-8"); } append_json_string(*out_, bytes); return {}; } case LUA_TTABLE: return table(index, depth, path); case LUA_TNIL: return fail(path, "is nil, which JSON cannot hold here"); default: return fail(path, std::string("is a ") + lua_typename(state_, lua_type(state_, index)) + ", which JSON cannot hold"); } }
private: static std::expected<void, std::string> fail(const std::string &path, std::string_view why) { return std::unexpected("value" + path + " " + std::string(why)); }
std::expected<void, std::string> number(double value, const std::string &path) { if (!std::isfinite(value)) { return fail(path, "is not a finite number"); } std::array<char, 32> buffer{}; char *first = buffer.data(); char *last = std::to_address(buffer.end()); std::to_chars_result result{}; if (value == std::floor(value) && std::fabs(value) < exact_integer_limit) { result = std::to_chars(first, last, static_cast<std::int64_t>(value)); } else { result = std::to_chars(first, last, value); } if (result.ec != std::errc{}) { return fail(path, "could not be written"); } out_->append(first, result.ptr); return {}; }
// NOLINTNEXTLINE(misc-no-recursion): bounded by max_depth. std::expected<void, std::string> table(int index, int depth, const std::string &path) { const int absolute = index < 0 ? lua_gettop(state_) + index + 1 : index; if (lua_checkstack(state_, 3) == 0) { return fail(path, "is nested too deeply"); } std::vector<std::string> keys; std::size_t numeric = 0; bool other = false; lua_pushnil(state_); while (lua_next(state_, absolute) != 0) { lua_pop(state_, 1); if (lua_type(state_, -1) == LUA_TSTRING) { std::size_t length = 0; const char *text = lua_tolstring(state_, -1, &length); keys.emplace_back(text, length); } else if (lua_type(state_, -1) == LUA_TNUMBER) { ++numeric; } else { other = true; } } const auto count = static_cast<std::size_t>(lua_objlen(state_, absolute)); if (other || (numeric > 0 && !keys.empty()) || (numeric > 0 && numeric != count)) { return fail(path, "is a table that is neither an array (keys 1..n) nor an object " "(string keys)"); } if (numeric > 0) { *out_ += '['; for (std::size_t i = 1; i <= count; ++i) { if (i > 1) { *out_ += ','; } lua_rawgeti(state_, absolute, static_cast<int>(i)); auto written = value(-1, depth + 1, path + "[" + std::to_string(i) + "]"); lua_pop(state_, 1); if (!written) { return written; } } *out_ += ']'; return {}; } std::ranges::sort(keys); *out_ += '{'; for (std::size_t i = 0; i < keys.size(); ++i) { const std::string &key = keys.at(i); if (!is_utf8(key)) { return fail(path, "has a key that is not valid UTF-8"); } if (i > 0) { *out_ += ','; } append_json_string(*out_, key); *out_ += ':'; lua_pushlstring(state_, key.empty() ? "" : key.data(), key.size()); lua_rawget(state_, absolute); std::string member_path = path; member_path += "."; member_path += key; auto written = value(-1, depth + 1, member_path); lua_pop(state_, 1); if (!written) { return written; } } *out_ += '}'; return {}; }
lua_State *state_; std::string *out_;};
} // namespace
std::expected<void, std::string> write_json(lua_State *state, int index, std::string &out) { Writer writer(state, out); return writer.value(index, 0, "");}
} // namespace packluau