// dump_json // Emits the parse result as JSON in the same shape as tests/mars_text/oracle/dump.py // so the two can be compared structurally. Bytes >= 0x80 are written as // \u00XX (one escape per byte) on both sides. #include #include #include #include #include #include "mars/text/csv.h" #include "mars/text/flat_kv.h" #include "mars/text/manifest.h" using namespace mars::text; namespace { void json_string(std::string_view s, std::string& out) { out.push_back('"'); 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; default: if (c < 0x20 || c >= 0x7f) { char buf[8]; std::snprintf(buf, sizeof buf, "\\u%04x", c); out += buf; } else { out.push_back(static_cast(c)); } } } out.push_back('"'); } void json_token(const Token& t, std::string& out) { switch (t.kind()) { case ValueKind::Int: out += std::to_string(*t.as_int()); break; case ValueKind::Float: { char buf[64]; std::snprintf(buf, sizeof buf, "%.17g", *t.as_float()); std::string s = buf; if (s.find_first_of(".eEn") == std::string::npos) s += ".0"; // keep it a JSON float out += s; break; } case ValueKind::Bool: out += *t.as_bool() ? "true" : "false"; break; case ValueKind::Bareword: case ValueKind::String: json_string(t.text, out); break; } } void json_tokens(const std::vector& toks, std::string& out) { out.push_back('['); for (std::size_t i = 0; i < toks.size(); ++i) { if (i) out.push_back(','); json_token(toks[i], out); } out.push_back(']'); } // kv value: null / scalar / list, matching the reference reader's shape void json_kv_value(const std::vector& toks, std::string& out) { if (toks.empty()) out += "null"; else if (toks.size() == 1) json_token(toks[0], out); else json_tokens(toks, out); } std::string dump_kv(const FlatKV& kv) { std::map last; // exact key, last wins for (const KvEntry& e : kv.entries()) last[e.key] = &e; std::string out = "{\"values\":{"; bool first = true; for (const auto& [key, e] : last) { if (!first) out.push_back(','); first = false; json_string(key, out); out.push_back(':'); json_kv_value(e->values, out); } out += "},\"duplicates\":{"; first = true; for (const auto& [key, lines] : kv.duplicates()) { if (!first) out.push_back(','); first = false; json_string(key, out); out += ":["; for (std::size_t i = 0; i < lines.size(); ++i) out += (i ? "," : "") + std::to_string(lines[i]); out.push_back(']'); } out += "}}"; return out; } std::string dump_rows(const Rows& rows) { std::string out = "{\"rows\":["; for (std::size_t i = 0; i < rows.size(); ++i) { if (i) out.push_back(','); json_tokens(rows[i].tokens, out); } out += "]}"; return out; } const char* problem_kind(Problem::Kind k) { switch (k) { case Problem::Kind::Unrecognised: return "unrecognised"; case Problem::Kind::DuplicateId: return "duplicate_id"; case Problem::Kind::DeletedAndAssigned: return "deleted_and_assigned"; case Problem::Kind::UnbalancedQuote: return "unbalanced_quote"; case Problem::Kind::UnterminatedQuotedCell: return "unterminated_quoted_cell"; } return "?"; } std::string dump_manifest(const Result& r) { std::string out = "{\"entries\":["; bool first = true; for (const ManifestEntry& e : r.value.entries()) { if (!first) out.push_back(','); first = false; out += "[" + std::to_string(e.id) + ","; json_string(e.name, out); out.push_back(']'); } out += "],\"deleted\":["; const auto& del = r.value.deleted(); for (std::size_t i = 0; i < del.size(); ++i) out += (i ? "," : "") + std::to_string(del[i]); out += "],\"problems\":["; first = true; for (const Problem& p : r.problems) { if (!first) out.push_back(','); first = false; out += "{\"line\":" + (p.line ? std::to_string(p.line) : std::string("null")) + ",\"kind\":\"" + problem_kind(p.kind) + "\",\"id\":" + (p.id >= 0 ? std::to_string(p.id) : std::string("null")) + "}"; } out += "]}"; return out; } std::string dump_csv(const Csv& csv) { std::string out = "{\"header\":"; if (csv.header) { out.push_back('['); for (std::size_t i = 0; i < csv.header->size(); ++i) { if (i) out.push_back(','); json_string((*csv.header)[i], out); } out.push_back(']'); } else { out += "null"; } out += ",\"rows\":["; for (std::size_t i = 0; i < csv.rows.size(); ++i) { if (i) out.push_back(','); out.push_back('['); for (std::size_t j = 0; j < csv.rows[i].cells.size(); ++j) { if (j) out.push_back(','); json_string(csv.rows[i].cells[j], out); } out.push_back(']'); } out += "]}"; return out; } } // namespace int main(int argc, char** argv) { if (argc != 3) { std::fprintf(stderr, "usage: dump_json \n"); return 2; } std::ifstream in(argv[2], std::ios::binary); if (!in) { std::fprintf(stderr, "cannot read %s\n", argv[2]); return 2; } std::string text((std::istreambuf_iterator(in)), std::istreambuf_iterator()); std::string kind = argv[1]; std::string out; if (kind == "kv") out = dump_kv(parse_flat_kv(text).value); else if (kind == "rows") out = dump_rows(parse_rows(text).value); else if (kind == "manifest") out = dump_manifest(parse_manifest(text)); else if (kind == "csv") out = dump_csv(parse_csv(text).value); else { std::fprintf(stderr, "unknown kind %s\n", argv[1]); return 2; } std::fwrite(out.data(), 1, out.size(), stdout); std::fputc('\n', stdout); return 0; }