#include "canon.h" #include #include #include #include #include #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 items; // Array std::vector> 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*> 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(mars::parse::ascii_lower(static_cast(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(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