184 lines
5.9 KiB
C++
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
|