Files
yamal/yamal.cpp
2025-11-20 17:31:52 +01:00

359 lines
11 KiB
C++

#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<std::pair<std::string, yamal>> 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<std::string> splitLines(const std::string& text) {
std::vector<std::string> 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<std::string>& 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<std::string>& lines, size_t& i, int indent) {
yamal node;
bool is_sequence = false;
std::vector<std::string> 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 "";
}