diff --git a/data/room/01.yaml b/data/room/01.yaml index 3e1706b..c034e01 100644 --- a/data/room/01.yaml +++ b/data/room/01.yaml @@ -1,5 +1,5 @@ # THE JAIL -room: 1 +room: name: "THE JAIL" bgColor: bright_blue border: blue diff --git a/main.cpp b/main.cpp index d22afa2..986fba3 100644 --- a/main.cpp +++ b/main.cpp @@ -1,30 +1,46 @@ #include -#include -#include #include "yamal.hpp" int main(int argc, char* argv[]) { - /*if (argc < 2) { - std::cerr << "Usage: " << argv[0] << " \n"; - return 1; - }*/ - - std::ifstream file("data/room/01.yaml"); //argv[1]); - if (!file) { - std::cerr << "Error: Could not open file " << argv[1] << "\n"; - return 1; - } - - std::ostringstream buffer; - buffer << file.rdbuf(); - std::string yamlText = buffer.str(); - - yamal_ns::yamal root; - root.deserialize(yamlText); - - //for (auto &att : root.attributes()) - // std::cout << att.first << ": " << att.second.asString() << std::endl; + yamal root; + root["room"]["name"] = "THE JAIL"; + root["room"]["name"].setQuoted(true); + root["room"]["bgColor"] = "bright_blue"; + root["room"]["connections"]["up"] = "null"; + root["room"]["connections"]["down"] = "null"; + root["room"]["connections"]["left"] = "null"; + root["room"]["connections"]["right"] = "02.yaml"; + root["room"]["itemColor1"] = "yellow"; + root["tilemap"][0][0] = 00; + root["tilemap"][0][1] = 01; + root["tilemap"][1][0] = 10; + root["tilemap"][1][1] = 11; + root["tilemap"][0].setInline(true); + root["tilemap"][1].setInline(true); + root["enemies"][0]["animation"] = "jailer_1.yaml"; + root["enemies"][0]["position"]["x"] = 1; + root["enemies"][0]["position"]["y"] = 13; + root["enemies"][0]["position"].setInline(true); + root["enemies"][0]["boundaries"]["position1"]["x"] = 1; + root["enemies"][0]["boundaries"]["position1"]["y"] = 1; + root["enemies"][0]["boundaries"]["position1"].setInline(true); + root["enemies"][0]["color"]= "white"; + root["enemies"][1]["animation"] = "jailer_2.yaml"; + root["enemies"][1]["position"]["x"] = 2; + root["enemies"][1]["position"]["y"] = 23; + root["enemies"][1]["position"].setInline(true); + root["enemies"][1]["boundaries"]["position1"]["x"] = 2; + root["enemies"][1]["boundaries"]["position1"]["y"] = 2; + root["enemies"][1]["boundaries"]["position1"].setInline(true); + root["enemies"][1]["color"]= "black"; + root["items"].clearToVector(); + root["items"].setInline(true); + root["room"].setComment("THE JAIL"); + root["room"].setBlankLine(false); + root["tilemap"].setComment("Tilemap: 16 filas x 32 columnas (256x192 píxeles @ 8px/tile)"); + root["tilemap"].appendComment("Índices de tiles (-1 = vacío)"); + root["room"]["bgColor"].setInlineComment("Azul oscuro"); std::cout << root.serialize() << std::endl; return 0; diff --git a/test.yaml b/test.yaml new file mode 100644 index 0000000..708d0e4 --- /dev/null +++ b/test.yaml @@ -0,0 +1,28 @@ +# THE JAIL +room: + name: "THE JAIL" + bgColor: bright_blue # Azul oscuro + connections: + up: null + down: null + left: null + right: 02.yaml + itemColor1: yellow + +# Tilemap: 16 filas x 32 columnas (256x192 píxeles @ 8px/tile) +# Índices de tiles (-1 = vacío) +tilemap: + - [ 0, 1 ] + - [ 10, 11 ] +enemies: + - animation: jailer_1.yaml + position: { x: 1, y: 13 } + boundaries: + position1: { x: 1, y: 1 } + color: white + - animation: jailer_2.yaml + position: { x: 2, y: 23 } + boundaries: + position1: { x: 2, y: 2 } + color: black +items: [ ] diff --git a/yamal.cpp b/yamal.cpp deleted file mode 100644 index dd4659b..0000000 --- a/yamal.cpp +++ /dev/null @@ -1,358 +0,0 @@ -#include "yamal.hpp" - -using namespace yamal_ns; - -bool yamal::isScalar() const { return mode == SCALAR; } -bool yamal::isVector() const { return mode == VECTOR; } -bool yamal::isMap() const { return mode == MAP; } - -void yamal::clearToScalar() { mode = SCALAR; vec_data.clear(); map_data.clear(); } -void yamal::clearToVector() { mode = VECTOR; value.clear(); map_data.clear(); } -void yamal::clearToMap() { mode = MAP; value.clear(); vec_data.clear(); } - -yamal& yamal::operator=(const std::string& v) { clearToScalar(); value = v; return *this; } -yamal& yamal::operator=(int v) { clearToScalar(); value = std::to_string(v); return *this; } -yamal& yamal::operator=(float v) { clearToScalar(); value = std::to_string(v); return *this; } -yamal& yamal::operator=(bool b) { clearToScalar(); value = b ? "true" : "false"; return *this; } - -std::string yamal::asString() const { return value; } -int yamal::asInt() const { return std::stoi(value); } -float yamal::asFloat() const { return std::stof(value); } - -bool yamal::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; - } -} - -const std::string& yamal::getComment() const { return pre_comment; } -yamal& yamal::setComment(const std::string& c) { pre_comment = c; return *this; } -yamal& yamal::appendComment(const std::string& c) { - if (!pre_comment.empty()) pre_comment += "\n"; - pre_comment += c; - return *this; -} - -yamal& yamal::setQuoted(bool q) { quoted = q; return *this; } -yamal& yamal::setInline(bool in) { inline_map = in; return *this; } - -void yamal::push_back(const yamal& item) { clearToVector(); vec_data.push_back(item); } -yamal& yamal::operator[](size_t i) { clearToVector(); return vec_data[i]; } -yamal& yamal::operator[](const std::string& key) { clearToMap(); return map_data[key]; } - -std::vector> yamal::attributes() const { - return map_data.items(); -} - -bool yamal::isBoolLiteral() const { - std::string s = value; - std::transform(s.begin(), s.end(), s.begin(), ::tolower); - return s == "true" || s == "false"; -} - -bool yamal::isNumericLiteral() const { - if (value.empty()) return false; - char* end = nullptr; - std::strtod(value.c_str(), &end); - return end != value.c_str() && *end == '\0'; -} - -bool yamal::isSafeUnquotedString() const { - if (value.empty()) return false; - if (std::isdigit(value[0])) return false; - for (char c : value) { - if (!std::isalnum(c) && c != '_' && c != '-') return false; - } - return true; -} - -// --- Parsing helpers --- -static std::vector splitLines(const std::string& text) { - std::vector lines; - std::istringstream stream(text); - std::string line; - while (std::getline(stream, line)) lines.push_back(line); - return lines; -} - -static std::string trim(const std::string& s) { - size_t start = s.find_first_not_of(" \t"); - size_t end = s.find_last_not_of(" \t"); - return (start == std::string::npos) ? "" : s.substr(start, end - start + 1); -} - -static std::string removeWhitespace(const std::string& s) { - size_t start = s.find_first_not_of(" \t\n\r"); - size_t end = s.find_last_not_of(" \t\n\r"); - return (start == std::string::npos) ? "" : s.substr(start, end - start + 1); -} - -static int countIndent(const std::string& line) { - int count = 0; - for (char c : line) if (c == ' ') ++count; else break; - return count; -} - -static std::string joinCommentLines(const std::vector& lines) { - std::ostringstream oss; - for (const auto& line : lines) oss << line << "\n"; - return trim(oss.str()); -} - -static yamal parseInlineSequence(const std::string& text) { - yamal node; - node.clearToVector(); - node.setInline(true); - std::string inner = text.substr(1, text.size() - 2); - std::istringstream ss(inner); - std::string item; - while (std::getline(ss, item, ',')) { - yamal child; - child.deserialize(trim(item)); - node.push_back(child); - } - return node; -} - -static yamal parseInlineMap(const std::string& text) { - yamal node; - node.clearToMap(); - node.setInline(true); - std::string inner = text.substr(1, text.size() - 2); - std::istringstream ss(inner); - std::string pair; - while (std::getline(ss, pair, ',')) { - auto pos = pair.find(':'); - if (pos != std::string::npos) { - std::string key = trim(pair.substr(0, pos)); - std::string val = trim(pair.substr(pos + 1)); - yamal& child = node[key]; - child.deserialize(val); - } - } - return node; -} - -static yamal parseBlock(const std::vector& lines, size_t& i, int indent) { - yamal node; - bool is_sequence = false; - std::vector pending_comment_lines; - - while (i < lines.size()) { - const std::string& line = lines[i]; - - std::string trimmed = trim(line); - if (trimmed.empty()) { - pending_comment_lines.push_back(""); - ++i; - continue; - } - - if (trimmed[0] == '#') { - pending_comment_lines.push_back(trimmed.substr(1)); - ++i; - continue; - } - - int currentIndent = countIndent(line); - if (currentIndent < indent) break; - - std::string content = trimmed; - std::string inline_comment; - size_t hash_pos = content.find('#'); - if (hash_pos != std::string::npos && hash_pos > 0) { - inline_comment = trim(content.substr(hash_pos + 1)); - content = trim(content.substr(0, hash_pos)); - } - - if (content[0] == '-') { - if (!is_sequence) { - node.clearToVector(); - is_sequence = true; - } - - std::string item = trim(content.substr(1)); - yamal child; - - if (!pending_comment_lines.empty()) { - child.pre_comment = joinCommentLines(pending_comment_lines); - pending_comment_lines.clear(); - } - - if (!inline_comment.empty()) child.inline_comment = inline_comment; - - ++i; - if (item.empty()) child = parseBlock(lines, i, indent + 2); - else if (item[0] == '[') child = parseInlineSequence(item); - else if (item[0] == '{') child = parseInlineMap(item); - else child.deserialize(item); - - node.push_back(child); - } - else if (auto pos = content.find(':'); pos != std::string::npos) { - node.clearToMap(); - std::string key = trim(content.substr(0, pos)); - std::string val = trim(content.substr(pos + 1)); - yamal& child = node[key]; - - if (!pending_comment_lines.empty()) { - child.pre_comment = joinCommentLines(pending_comment_lines); - pending_comment_lines.clear(); - } - - if (!inline_comment.empty()) child.inline_comment = inline_comment; - - ++i; - if (val.empty()) child = parseBlock(lines, i, indent + 2); - else if (val[0] == '[') child = parseInlineSequence(val); - else if (val[0] == '{') child = parseInlineMap(val); - else { - child.clearToScalar(); - if (val.size() >= 2 && val.front() == '"' && val.back() == '"') { - child.value = val.substr(1, val.size() - 2); - child.quoted = true; - } else { - child.value = val; - } - } - } - else { - ++i; - } - } - - return node; -} - -void yamal::deserialize(const std::string& yamlText) { - auto lines = splitLines(yamlText); - clearToMap(); - size_t index = 0; - yamal top = parseBlock(lines, index, 0); - for (const auto& [key, val] : top.attributes()) { - (*this)[key] = val; - } -} - -std::string yamal::serialize(int indent) const { - std::ostringstream out; - std::string pad(indent, ' '); - - std::string trimmed;// = removeWhitespace(pre_comment); - /*if (!trimmed.empty()) { - std::istringstream ss(pre_comment); - std::string line; - while (std::getline(ss, line)) { - out << pad << "# " << line << "\n"; - } - }*/ - - if (isScalar()) { - if (isBoolLiteral()) { - out << pad << value; - } else if (quoted) { - out << pad << "\"" << value << "\""; - } else if (isNumericLiteral() || isSafeUnquotedString()) { - out << pad << value; - } else { - out << pad << "\"" << value << "\""; - } - - trimmed = removeWhitespace(inline_comment); - if (!trimmed.empty()) { - out << " # " << inline_comment; - } - - return out.str(); - } - - if (isVector()) { - if (inline_map) { - out << pad << "["; - for (size_t i = 0; i < vec_data.size(); ++i) { - out << vec_data[i].serialize(0); - if (i + 1 < vec_data.size()) out << ", "; - } - out << "]"; - trimmed = removeWhitespace(inline_comment); - if (!trimmed.empty()) out << " # " << inline_comment; - out << "\n"; - return out.str(); - } - - for (const auto& item : vec_data) { - trimmed = removeWhitespace(item.pre_comment); - if (!trimmed.empty()) { - std::istringstream ss(item.pre_comment); - std::string line; - while (std::getline(ss, line)) { - out << pad << "# " << line << "\n"; - } - } - - out << pad << "-"; - if (item.isScalar()) { - out << " " << item.serialize(0); - trimmed = removeWhitespace(item.inline_comment); - if (!trimmed.empty()) { - out << " # " << item.inline_comment; - } - out << "\n"; - } else { - out << "\n" << item.serialize(indent + 2); - } - } - return out.str(); - } - - if (isMap()) { - if (inline_map) { - out << pad << "{"; - auto items = map_data.items(); - for (size_t i = 0; i < items.size(); ++i) { - const auto& [key, val] = items[i]; - out << key << ": " << val.serialize(0); - if (i + 1 < items.size()) out << ", "; - } - out << "}"; - trimmed = removeWhitespace(inline_comment); - if (!trimmed.empty()) out << " # " << inline_comment; - out << "\n"; - return out.str(); - } - - for (const auto& [key, val] : map_data.items()) { - trimmed = removeWhitespace(val.pre_comment); - if (!trimmed.empty()) { - std::istringstream ss(val.pre_comment); - std::string line; - while (std::getline(ss, line)) { - out << pad << "# " << line << "\n"; - } - } - - out << pad << key << ":"; - if (val.isScalar()) { - out << " " << val.serialize(0); - trimmed = removeWhitespace(val.inline_comment); - if (!trimmed.empty()) { - out << " # " << val.inline_comment; - } - out << "\n"; - } else { - out << "\n" << val.serialize(indent + 2); - } - } - return out.str(); - } - - return ""; -} diff --git a/yamal.hpp b/yamal.hpp index 3d4f782..9109bcc 100644 --- a/yamal.hpp +++ b/yamal.hpp @@ -6,8 +6,6 @@ #include #include -namespace yamal_ns { - template class ordered_map { std::unordered_map map; @@ -29,6 +27,10 @@ public: } } + bool is_last(const std::string& key) const { + return !keys.empty() && keys.back() == key; + } + void clear() { map.clear(); keys.clear(); @@ -59,59 +61,215 @@ public: std::string inline_comment; bool quoted = false; bool inline_map = false; + bool blank_line = false; // Mode checks - bool isScalar() const; - bool isVector() const; - bool isMap() const; + bool isScalar() const { return mode == SCALAR; } + bool isVector() const { return mode == VECTOR; } + bool isMap() const { return mode == MAP; } // Mode setters - void clearToScalar(); - void clearToVector(); - void clearToMap(); + 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(); } - // Accessors - operator std::string() const; - operator int() const; - operator float() const; - operator bool() const; + 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; } - yamal& operator=(const std::string& v); - yamal& operator=(int v); - yamal& operator=(float v); - yamal& operator=(bool b); + std::string asString() const { return value; } + int asInt() const { return std::stoi(value); } + float asFloat() const { return std::stof(value); } - std::string asString() const; - int asInt() const; - float asFloat() const; - bool asBool() const; + 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; - yamal& setComment(const std::string& c); - yamal& appendComment(const std::string& c); + 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); - yamal& setInline(bool in = true); + 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); - yamal& operator[](size_t i); - yamal& operator[](const std::string& key); + 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; + std::vector> attributes() const { + return map_data.items(); + } - // Serialization - std::string serialize(int indent = 0) const; - // Deserialization - void deserialize(const std::string& yamlText); + 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: - bool isBoolLiteral() const; - bool isNumericLiteral() const; - bool isSafeUnquotedString() const; -}; + 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(); + } -} // namespace yamal_ns +};