#pragma once #include #include #include #include #include #include template class ordered_map { std::unordered_map map; std::vector keys; public: Value& operator[](const std::string& key) { if (!map.contains(key)) keys.push_back(key); return map[key]; } bool contains(const std::string& key) const { return map.find(key) != map.end(); } void erase(const std::string& key) { if (map.erase(key)) { keys.erase(std::remove(keys.begin(), keys.end(), key), keys.end()); } } bool is_last(const std::string& key) const { return !keys.empty() && keys.back() == key; } void clear() { map.clear(); keys.clear(); } size_t size() const { return keys.size(); } std::vector> items() const { std::vector> result; for (const auto& k : keys) result.emplace_back(k, map.at(k)); return result; } const Value& at(const std::string& key) const { return map.at(key); } Value& at(const std::string& key) { return map.at(key); } }; class yamal { public: enum Mode { SCALAR, VECTOR, MAP }; Mode mode = SCALAR; std::string value; std::vector vec_data; ordered_map map_data; std::string pre_comment; std::string inline_comment; bool quoted = false; bool inline_map = false; bool blank_line = false; // Mode checks bool isScalar() const { return mode == SCALAR; } bool isVector() const { return mode == VECTOR; } bool isMap() const { return mode == MAP; } // Mode setters void clearToScalar() { mode = SCALAR; vec_data.clear(); map_data.clear(); } void clearToVector() { mode = VECTOR; value.clear(); map_data.clear(); } void clearToMap() { mode = MAP; value.clear(); vec_data.clear(); } yamal& operator=(const char* v) { clearToScalar(); value = std::string(v); return *this; } yamal& operator=(const std::string& v) { clearToScalar(); value = v; return *this; } yamal& operator=(int v) { clearToScalar(); value = std::to_string(v); return *this; } yamal& operator=(float v) { clearToScalar(); value = std::to_string(v); return *this; } yamal& operator=(bool b) { clearToScalar(); value = b ? "true" : "false"; return *this; } std::string asString() const { return value; } int asInt() const { return std::stoi(value); } float asFloat() const { return std::stof(value); } bool asBool() const { std::string s = value; std::transform(s.begin(), s.end(), s.begin(), ::tolower); if (s == "true") return true; if (s == "false") return false; try { size_t idx; float f = std::stof(s, &idx); return (idx != 0 && f != 0.0f); } catch (...) { return false; } } // Comment accessors const std::string& getComment() const { return pre_comment; } yamal& setComment(const std::string& c) { pre_comment = c; blank_line = true; return *this; } yamal& appendComment(const std::string& c) { if (!pre_comment.empty()) pre_comment += "\n"; pre_comment += c; return *this; } // Inline comment accessors const std::string& getInlineComment() const { return inline_comment; } yamal& setInlineComment(const std::string& c) { inline_comment = c; return *this; } // Formatting yamal& setQuoted(bool q = true) { quoted = q; return *this; } yamal& setInline(bool in = true) { inline_map = in; return *this; } yamal& setBlankLine(bool b = true) { blank_line = b; return *this; } // Vector and map access void push_back(const yamal& item) { clearToVector(); vec_data.push_back(item); } yamal& operator[](size_t i) { clearToVector(); if (vec_data.size() <= i) vec_data.resize(i+1); return vec_data[i]; } yamal& operator[](const std::string& key) { clearToMap(); return map_data[key]; } std::vector> attributes() const { return map_data.items(); } std::string serialize(int indent = 0, bool emit_self_comment = true) const { std::ostringstream out; switch (mode) { case SCALAR: { if (emit_self_comment && !pre_comment.empty()) { out << formatComment(pre_comment, indent); } std::string val = quoted ? "\"" + value + "\"" : value; out << std::string(indent, ' ') << val; if (!inline_comment.empty()) out << " # " << inline_comment; break; } case MAP: { if (inline_map) { if (emit_self_comment && blank_line) { out << "\n"; } if (emit_self_comment && !pre_comment.empty()) { out << formatComment(pre_comment, indent); } out << std::string(indent, ' ') << "{ "; bool first = true; for (auto& kv : map_data.items()) { if (!first) out << ", "; out << kv.first << ": " << kv.second.serialize(0, false); first = false; } out << " }"; if (!inline_comment.empty()) out << " # " << inline_comment; } else { if (emit_self_comment && blank_line) { out << "\n"; } // Only emit this node’s own comment at the start if requested if (emit_self_comment && !pre_comment.empty()) { out << formatComment(pre_comment, indent); } for (auto& kv : map_data.items()) { // Emit child’s pre-comment BEFORE the key line if (kv.second.blank_line) { out << "\n"; } if (!kv.second.pre_comment.empty()) { out << formatComment(kv.second.pre_comment, indent); } out << std::string(indent, ' ') << kv.first << ": "; if (kv.second.inline_map || kv.second.isScalar()) { out << kv.second.serialize(0, false); } else { out << "\n" << kv.second.serialize(indent + 2, false); } //if (!kv.second.inline_comment.empty()) { // out << " # " << kv.second.inline_comment; //} if (!map_data.is_last(kv.first)) out << "\n"; } } break; } case VECTOR: { if (inline_map) { if (emit_self_comment && blank_line) { out << "\n"; } if (emit_self_comment && !pre_comment.empty()) { out << formatComment(pre_comment, indent); } out << std::string(indent, ' ') << "[ "; for (size_t i = 0; i < vec_data.size(); ++i) { if (i > 0) out << ", "; out << vec_data[i].serialize(0, false); } out << " ]"; if (!inline_comment.empty()) out << " # " << inline_comment; } else { if (emit_self_comment && blank_line) { out << "\n"; } if (emit_self_comment && !pre_comment.empty()) { out << formatComment(pre_comment, indent); } if (vec_data.empty()) { out << std::string(indent, ' ') << "[]"; if (!inline_comment.empty()) out << " # " << inline_comment; } else { for (size_t i = 0; i < vec_data.size(); ++i) { const yamal& item = vec_data[i]; if (emit_self_comment && item.blank_line) { out << "\n"; } // Emit item’s comment BEFORE the dash line if (!item.pre_comment.empty()) { out << formatComment(item.pre_comment, indent); } if (i > 0) out << "\n"; out << std::string(indent, ' ') << "- "; if (item.isScalar() || item.inline_map) { out << item.serialize(0, false); } else if (item.isMap()) { auto attrs = item.attributes(); if (!attrs.empty()) { out << attrs[0].first << ": " << attrs[0].second.serialize(0, false); for (size_t j = 1; j < attrs.size(); ++j) { out << "\n" << std::string(indent + 2, ' ') << attrs[j].first << ": "; if (attrs[j].second.inline_map || attrs[j].second.isScalar()) { out << attrs[j].second.serialize(0, false); } else { out << "\n" << attrs[j].second.serialize(indent + 4, false); } } } } else { out << "\n" << item.serialize(indent + 2, false); } } if (!inline_comment.empty()) out << " # " << inline_comment; } } break; } } return out.str(); } private: static std::string formatComment(const std::string& c, int indent) { std::ostringstream out; std::istringstream in(c); std::string line; while (std::getline(in, line)) { if (line.empty()) { // preserve blank line out << "\n"; } else { out << std::string(indent, ' ') << "# " << line << "\n"; } } return out.str(); } };