Something went wrong. Try again.
Monorepo for PackTools - tooling for Minecraft Modpacks!
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379#include "json.hpp"
#include "bindings/host.hpp"#include "lua.h"#include "report.hpp"
#include <charconv>#include <cstddef>#include <cstdint>#include <memory>#include <system_error>
namespace packluau {
namespace {
class Reader { public: Reader(lua_State *state, std::string_view text) noexcept : state_(state), text_(text) {}
std::expected<void, std::string> document() { skip_space(); if (auto pushed = value(0); !pushed) { return pushed; } skip_space(); if (at_ != text_.size()) { return fail("trailing bytes after the document"); } return {}; }
private: std::expected<void, std::string> fail(std::string_view what) const { return std::unexpected(std::string("invalid JSON at byte ") + std::to_string(at_) + ": " + std::string(what)); }
[[nodiscard]] bool done() const noexcept { return at_ >= text_.size(); } [[nodiscard]] char peek() const { return text_.at(at_); }
void skip_space() noexcept { while (!done()) { const char c = text_.at(at_); if (c != ' ' && c != '\t' && c != '\n' && c != '\r') { return; } ++at_; } }
bool literal(std::string_view word) noexcept { if (text_.substr(at_).starts_with(word)) { at_ += word.size(); return true; } return false; }
// NOLINTNEXTLINE(misc-no-recursion): bounded by max_depth. std::expected<void, std::string> value(int depth) { if (done()) { return fail("unexpected end"); } if (lua_checkstack(state_, 3) == 0) { return fail("too deep"); } switch (peek()) { case '{': return object(depth + 1); case '[': return array(depth + 1); case '"': { auto text = string(); if (!text) { return std::unexpected(text.error()); } bindings::push_text(state_, *text); return {}; } case 't': if (literal("true")) { lua_pushboolean(state_, 1); return {}; } return fail("unknown literal"); case 'f': if (literal("false")) { lua_pushboolean(state_, 0); return {}; } return fail("unknown literal"); case 'n': if (literal("null")) { lua_pushnil(state_); return {}; } return fail("unknown literal"); default: return number(); } }
// NOLINTNEXTLINE(misc-no-recursion): bounded by max_depth. std::expected<void, std::string> object(int depth) { if (depth > max_depth) { return fail("nested too deeply"); } ++at_; lua_createtable(state_, 0, 0); skip_space(); if (!done() && peek() == '}') { ++at_; bindings::seal(state_); return {}; } while (true) { skip_space(); if (done() || peek() != '"') { return fail("expected a member name"); } auto key = string(); if (!key) { return std::unexpected(key.error()); } skip_space(); if (done() || peek() != ':') { return fail("expected ':'"); } ++at_; skip_space(); bindings::push_text(state_, *key); if (auto pushed = value(depth); !pushed) { return pushed; } lua_rawset(state_, -3); skip_space(); if (done()) { return fail("unterminated object"); } if (peek() == ',') { ++at_; continue; } if (peek() == '}') { ++at_; bindings::seal(state_); return {}; } return fail("expected ',' or '}'"); } }
// NOLINTNEXTLINE(misc-no-recursion): bounded by max_depth. std::expected<void, std::string> array(int depth) { if (depth > max_depth) { return fail("nested too deeply"); } ++at_; lua_createtable(state_, 0, 0); skip_space(); if (!done() && peek() == ']') { ++at_; bindings::seal(state_); return {}; } for (int index = 1;; ++index) { skip_space(); if (auto pushed = value(depth); !pushed) { return pushed; } lua_rawseti(state_, -2, index); skip_space(); if (done()) { return fail("unterminated array"); } if (peek() == ',') { ++at_; continue; } if (peek() == ']') { ++at_; bindings::seal(state_); return {}; } return fail("expected ',' or ']'"); } }
std::expected<void, std::string> number() { const std::size_t start = at_; if (!done() && peek() == '-') { ++at_; } if (done() || peek() < '0' || peek() > '9') { return fail("expected a value"); } if (peek() == '0') { ++at_; } else { digits(); } if (!done() && peek() == '.') { ++at_; if (digits() == 0) { return fail("expected a digit after '.'"); } } if (!done() && (peek() == 'e' || peek() == 'E')) { ++at_; if (!done() && (peek() == '+' || peek() == '-')) { ++at_; } if (digits() == 0) { return fail("expected an exponent"); } } const std::string_view token = text_.substr(start, at_ - start); double result = 0; const char *last = std::to_address(token.end()); const auto [end, error] = std::from_chars(std::to_address(token.begin()), last, result); if (error != std::errc{} || end != last) { return fail("number out of range"); } lua_pushnumber(state_, result); return {}; }
std::size_t digits() noexcept { const std::size_t start = at_; while (!done() && text_.at(at_) >= '0' && text_.at(at_) <= '9') { ++at_; } return at_ - start; }
std::expected<std::string, std::string> string() { ++at_; std::string out; const std::size_t start = at_; while (true) { if (done()) { return std::unexpected(fail("unterminated string").error()); } const char c = peek(); if (c == '"') { ++at_; break; } if (static_cast<unsigned char>(c) < 0x20) { return std::unexpected(fail("control character in a string").error()); } if (c != '\\') { out += c; ++at_; continue; } ++at_; if (done()) { return std::unexpected(fail("unterminated escape").error()); } const char escape = peek(); ++at_; switch (escape) { case '"': case '\\': case '/': out += escape; break; case 'b': out += '\b'; break; case 'f': out += '\f'; break; case 'n': out += '\n'; break; case 'r': out += '\r'; break; case 't': out += '\t'; break; case 'u': if (auto appended = code_point(out); !appended) { return std::unexpected(appended.error()); } break; default: return std::unexpected(fail("unknown escape").error()); } } if (!is_utf8(out)) { at_ = start; return std::unexpected(fail("invalid UTF-8 in a string").error()); } return out; }
std::expected<std::uint32_t, std::string> hex4() { if (text_.size() - at_ < 4) { return std::unexpected(fail("short \\u escape").error()); } std::uint32_t value = 0; const std::string_view digits = text_.substr(at_, 4); const char *last = std::to_address(digits.end()); const auto [end, error] = std::from_chars(std::to_address(digits.begin()), last, value, 16); if (error != std::errc{} || end != last) { return std::unexpected(fail("bad \\u escape").error()); } at_ += 4; return value; }
std::expected<void, std::string> code_point(std::string &out) { auto first = hex4(); if (!first) { return std::unexpected(first.error()); } std::uint32_t point = *first; if (point >= 0xDC00 && point <= 0xDFFF) { return std::unexpected(fail("lone low surrogate").error()); } if (point >= 0xD800 && point <= 0xDBFF) { if (!literal("\\u")) { return std::unexpected(fail("lone high surrogate").error()); } auto second = hex4(); if (!second) { return std::unexpected(second.error()); } if (*second < 0xDC00 || *second > 0xDFFF) { return std::unexpected(fail("lone high surrogate").error()); } point = 0x10000 + ((point - 0xD800) << 10U) + (*second - 0xDC00); } append_utf8(out, point); return {}; }
static void append_utf8(std::string &out, std::uint32_t point) { const auto byte = [](std::uint32_t bits) { return static_cast<char>(bits & 0xFFU); }; if (point < 0x80) { out += byte(point); } else if (point < 0x800) { out += byte(0xC0U | (point >> 6U)); out += byte(0x80U | (point & 0x3FU)); } else if (point < 0x10000) { out += byte(0xE0U | (point >> 12U)); out += byte(0x80U | ((point >> 6U) & 0x3FU)); out += byte(0x80U | (point & 0x3FU)); } else { out += byte(0xF0U | (point >> 18U)); out += byte(0x80U | ((point >> 12U) & 0x3FU)); out += byte(0x80U | ((point >> 6U) & 0x3FU)); out += byte(0x80U | (point & 0x3FU)); } }
lua_State *state_; std::string_view text_; std::size_t at_ = 0;};
} // namespace
std::expected<void, std::string> push_json(lua_State *state, std::string_view text) { const int base = lua_gettop(state); Reader reader(state, text); auto pushed = reader.document(); if (!pushed) { lua_settop(state, base); } return pushed;}
} // namespace packluau