sots-engine/tests/mars_parse/canon.cpp

184 lines
5.9 KiB
C++

#include "canon.h"
#include <algorithm>
#include <cstdio>
#include <memory>
#include <utility>
#include <vector>
#include "mars/parse/value.h"
namespace canon {
namespace {
using mars::parse::Entry;
using mars::parse::Node;
// Minimal JSON value: enough to mirror the Python dict/list/scalar shapes.
struct Json {
enum class Kind { Raw, String, Array, Object } kind = Kind::Raw;
std::string raw; // Raw: already-rendered literal
std::string str; // String
std::vector<Json> items; // Array
std::vector<std::pair<std::string, Json>> members; // Object (insertion order)
static Json rawlit(std::string s) { Json j; j.kind = Kind::Raw; j.raw = std::move(s); return j; }
static Json string(std::string s) { Json j; j.kind = Kind::String; j.str = std::move(s); return j; }
static Json array() { Json j; j.kind = Kind::Array; return j; }
static Json object() { Json j; j.kind = Kind::Object; return j; }
// Python's _add(): first value is scalar, the second turns it into a list.
void add(const std::string& key, Json value) {
for (auto& m : members) {
if (m.first != key) continue;
if (m.second.kind == Kind::Array && m.second.is_multi) {
m.second.items.push_back(std::move(value));
} else {
Json list = array();
list.is_multi = true;
list.items.push_back(std::move(m.second));
list.items.push_back(std::move(value));
m.second = std::move(list);
}
return;
}
members.emplace_back(key, std::move(value));
}
bool is_multi = false; // array created by repetition (vs. a value list)
};
void render(const Json& j, std::string& out) {
switch (j.kind) {
case Json::Kind::Raw: out += j.raw; break;
case Json::Kind::String: out += json_string(j.str); break;
case Json::Kind::Array:
out += '[';
for (std::size_t i = 0; i < j.items.size(); ++i) {
if (i) out += ',';
render(j.items[i], out);
}
out += ']';
break;
case Json::Kind::Object: {
std::vector<const std::pair<std::string, Json>*> sorted;
for (const auto& m : j.members) sorted.push_back(&m);
std::sort(sorted.begin(), sorted.end(),
[](auto* a, auto* b) { return a->first < b->first; });
out += '{';
for (std::size_t i = 0; i < sorted.size(); ++i) {
if (i) out += ',';
out += json_string(sorted[i]->first);
out += ':';
render(sorted[i]->second, out);
}
out += '}';
break;
}
}
}
// Typed rendering of a bareword, mirroring mars_data.coerce().
Json typed(const std::string& tok) {
using mars::parse::ScalarKind;
switch (mars::parse::classify(tok)) {
case ScalarKind::Int:
if (auto v = mars::parse::as_int(tok)) return Json::rawlit(std::to_string(*v));
return Json::string(tok); // out of 64-bit range: cannot match the oracle, surfaces as a diff
case ScalarKind::Float: {
char buf[64];
std::snprintf(buf, sizeof buf, "%.17g", *mars::parse::as_double(tok));
return Json::string(std::string("\x01") + buf);
}
case ScalarKind::Bool:
return Json::rawlit(*mars::parse::as_bool(tok) ? "true" : "false");
case ScalarKind::Text:
break;
}
return Json::string(tok);
}
std::string lower(const std::string& s) {
std::string out(s);
for (char& c : out) c = static_cast<char>(mars::parse::ascii_lower(static_cast<unsigned char>(c)));
return out;
}
Json node_to_json(const Node& n) {
Json obj = Json::object();
for (const Entry& e : n.entries) {
switch (e.kind) {
case Entry::Kind::Pair:
obj.add(lower(e.key), e.quoted ? Json::string(e.value) : typed(e.value));
break;
case Entry::Kind::Block:
obj.add(lower(e.key), node_to_json(*e.block));
break;
}
}
return obj;
}
Json group_to_json(const mars::parse::EffectGroup& g) {
Json list = Json::array();
for (const mars::parse::EffectEntry& e : g.entries) {
Json pair = Json::array();
pair.items.push_back(Json::string(e.key));
if (e.is_group()) {
pair.items.push_back(group_to_json(*e.group));
} else if (e.values.size() == 1) {
const auto& v = e.values[0];
pair.items.push_back(v.quoted ? Json::string(v.text) : typed(v.text));
} else {
Json vals = Json::array();
for (const auto& v : e.values)
vals.items.push_back(v.quoted ? Json::string(v.text) : typed(v.text));
pair.items.push_back(std::move(vals));
}
list.items.push_back(std::move(pair));
}
return list;
}
} // namespace
std::string json_string(const std::string& s) {
std::string out;
out.reserve(s.size() + 2);
out += '"';
for (unsigned char c : s) {
switch (c) {
case '"': out += "\\\""; break;
case '\\': out += "\\\\"; break;
case '\n': out += "\\n"; break;
case '\r': out += "\\r"; break;
case '\t': out += "\\t"; break;
case '\b': out += "\\b"; break;
case '\f': out += "\\f"; break;
default:
if (c < 0x20 || c > 0x7e) {
char buf[8];
std::snprintf(buf, sizeof buf, "\\u%04x", c);
out += buf;
} else {
out += static_cast<char>(c);
}
}
}
out += '"';
return out;
}
std::string blocks_json(const Node& root) {
std::string out;
render(node_to_json(root), out);
return out;
}
std::string effect_json(const mars::parse::EffectGroup& root) {
std::string out;
render(group_to_json(root), out);
return out;
}
} // namespace canon