#include "yaml.hpp" #include "BasicParser.hpp" #include using namespace yaml; namespace { enum Chomping { CLIP, STRIP, KEEP }; class Parser : BasicParser { public: Parser(std::string_view filename, std::string_view source); dv::value parseValue(); dv::value parseFullValue(int indent); dv::value parseArray(int indent = 0); dv::value parseObject(dv::value&& object, int indent = 0); dv::value parseInlineArray(); dv::value parseInlineObject(); private: int countIndent(); bool expectIndent(int indent); std::string_view readYamlIdentifier(); std::string readMultilineString(int indent, bool eols, Chomping chomp); }; } inline bool is_yaml_identifier_char(int c) { return c > 20 && c != ':' && c != ' ' && c != '\n' && c != '\r' && c != '\t' && c != '\f' && c != '\v'; } static dv::value perform_literal(std::string_view literal) { if (literal == "true" || literal == "True" || literal == "false" || literal == "False") { return literal[0] == 't'; } if (literal == "null" || literal == "Null" || literal == "~") { return nullptr; } return std::string(literal); } Parser::Parser(std::string_view filename, std::string_view source) : BasicParser(filename, source) { hashComment = true; } bool Parser::expectIndent(int required) { int indent = 0; while (hasNext() && source[pos] == ' ' && indent < required) { indent++; pos++; } return indent >= required; } std::string Parser::readMultilineString(int indent, bool eols, Chomping chomp) { int next_indent = countIndent(); if (next_indent <= indent) { throw error("indentation error"); } std::stringstream ss; ss << readUntilEOL(); if (hasNext()) { skip(1); } int trailingEmpties = 0; while (true) { while (expectIndent(next_indent)) { trailingEmpties = 0; ss << (eols ? '\n' : ' '); ss << readUntilEOL(); if (hasNext()) { skip(1); } } while (true) { skipWhitespace(false); if (!hasNext() || source[pos] != '\n') { break; } skip(1); trailingEmpties++; } if (!expectIndent(next_indent)) { break; } pos = linestart; } if (chomp == KEEP) { for (int i = 0; i < trailingEmpties - 1; i++) { ss << (eols ? '\n' : ' '); } } ss << '\n'; pos = linestart; auto string = ss.str(); if (chomp == STRIP) { util::trim(string); } return string; } std::string_view Parser::readYamlIdentifier() { char c = peek(); if (!is_yaml_identifier_char(c)) { throw error("identifier expected"); } int start = pos; while (hasNext() && is_yaml_identifier_char(source[pos])) { pos++; } return source.substr(start, pos - start); } int Parser::countIndent() { int indent = 0; while (hasNext() && source[pos] == ' ') { indent++; pos++; } return indent; } dv::value Parser::parseValue() { char c = peek(); if (is_digit(c)) { return parseNumber(1); } else if (c == '-' || c == '+') { skip(1); return parseNumber(c == '-' ? -1 : 1); } else if (c == '"' || c == '\'') { skip(1); return parseString(c, true); } else if (c == '[') { return parseInlineArray(); } else if (c == '{') { return parseInlineObject(); } else { return perform_literal(readUntilEOL()); } throw error("unexpected character"); } dv::value Parser::parseInlineArray() { expect('['); auto list = dv::list(); while (peek() != ']') { if (peek() == '#') { skipLine(); continue; } list.add(parseValue()); char next = peek(); if (next == ',') { pos++; } else if (next == ']') { break; } else { throw error("',' expected"); } } pos++; return list; } dv::value Parser::parseInlineObject() { expect('{'); dv::value object = dv::object(); while (peek() != '}') { if (peek() == '#') { skipLine(); continue; } auto name = readYamlIdentifier(); expect(':'); object[std::string(name)] = parseValue(); char next = peek(); if (next == ',') { pos++; } else if (next == '}') { break; } else { throw error("',' expected"); } } pos++; return object; } dv::value Parser::parseFullValue(int indent) { dv::value value; char c = source[pos]; if (c == '\n') { skip(1); skipEmptyLines(); int init_pos = pos; int next_indent = countIndent(); if (next_indent <= indent) { pos = init_pos; return nullptr; } if (source[pos] == '-') { pos = init_pos; return parseArray(next_indent); } else { pos = init_pos; return parseObject(dv::object(), next_indent); } } else if (is_digit(c)) { return parseNumber(1); } else if (c == '-' || c == '+') { skip(1); return parseNumber(c == '-' ? -1 : 1); } else if (c == '"' || c == '\'') { skip(1); return parseString(c, true); } else if (c == '[') { return parseInlineArray(); } else if (c == '{') { return parseInlineObject(); } else if (c == '|' || c == '>') { skip(1); Chomping chomp = CLIP; if (source[pos] == '-' || source[pos] == '+') { chomp = source[pos] == '-' ? STRIP : KEEP; skip(1); } skipWhitespace(false); expectNewLine(); return readMultilineString(indent, c == '|', chomp); } else { return perform_literal(readUntilEOL()); } } dv::value Parser::parseArray(int indent) { dv::value list = dv::list(); while (hasNext()) { skipEmptyLines(); int next_indent = countIndent(); if (next_indent < indent) { pos = linestart; break; } expect('-'); skipWhitespace(); size_t nlpos = source.find('\n', pos); size_t colonpos = source.find(':', pos); if (nlpos == std::string::npos && colonpos == std::string::npos) { list.add(perform_literal(readUntilEOL())); break; } if (nlpos < colonpos) { list.add(parseFullValue(next_indent)); skipLine(); } else { auto name = readYamlIdentifier(); expect(':'); skipWhitespace(false); dv::value object = dv::object(); object[std::string(name)] = parseFullValue(next_indent); skipEmptyLines(); if (!hasNext()) { list.add(std::move(object)); break; } next_indent = countIndent(); if (next_indent > indent) { pos = linestart; object = parseObject(std::move(object), next_indent); } else { pos = linestart; } list.add(std::move(object)); } } return list; } dv::value Parser::parseObject(dv::value&& object, int indent) { skipEmptyLines(); while (hasNext()) { size_t prev_pos = pos; int next_indent = countIndent(); if (source[pos] == '\n') { skip(1); continue; } if (next_indent < indent) { pos = prev_pos; break; } char c = peek(); if (!is_yaml_identifier_char(c)) { if (!is_whitespace(c)) { throw error("invalid character"); } continue; } auto name = readYamlIdentifier(); expect(':'); skipWhitespace(false); object[std::string(name)] = parseFullValue(indent); skipEmptyLines(); } return std::move(object); } dv::value yaml::parse(std::string_view filename, std::string_view source) { return Parser(filename, source).parseObject(dv::object()); } dv::value yaml::parse(std::string_view source) { return parse("[string]", source); } static void add_indent(std::stringstream& ss, int indent) { for (int i = 0; i < indent; i++) { ss << " "; } } static void insert_string( std::stringstream& ss, const std::string& string, int indent ) { bool has_spec_chars = false; bool multiline = false; for (char c : string) { if (c < ' ' || c == '"' || c == '\'') { has_spec_chars = true; if (multiline) { break; } } if (c == '\n') { multiline = true; break; } } if (multiline) { ss << "|-\n"; size_t offset = 0; size_t newoffset = 0; do { offset = newoffset; if (offset == string.length() - 1 && string[offset] == '\n') { break; } add_indent(ss, indent); newoffset = string.find('\n', offset + 1); if (newoffset == std::string::npos) { ss << string.substr(offset); break; } else { ss << string.substr(offset + 1, newoffset - offset - 1); } ss << '\n'; } while (true); } else { if (has_spec_chars || string.empty()) { ss << util::escape(string, false); } else { ss << string; } } } static void to_string( std::stringstream& ss, const dv::value& value, int indent, bool eliminateIndent = false ) { using dv::value_type; switch (value.getType()) { case value_type::string: insert_string(ss, value.asString(), indent); break; case value_type::number: ss << std::setprecision(15) << value.asNumber(); break; case value_type::integer: ss << value.asInteger(); break; case value_type::boolean: ss << (value.asBoolean() ? "true" : "false"); break; case value_type::none: ss << "null"; break; case value_type::object: { if (value.empty()) { ss << "{}"; break; } bool first = true; for (const auto& [key, elem] : value.asObject()) { if (!first) { ss << '\n'; } if (!eliminateIndent) { add_indent(ss, indent); } else { eliminateIndent = false; } ss << key << ": "; if ((elem.isObject() || elem.isList()) && !elem.empty()) { ss << "\n"; to_string(ss, elem, indent + 1); } else { to_string(ss, elem, indent + 1); } first = false; } break; } case value_type::list: { if (value.empty()) { ss << "[]"; break; } bool first = true; for (const auto& elem : value) { if (!first) { ss << '\n'; } if (!eliminateIndent) { add_indent(ss, indent); } else { eliminateIndent = false; } ss << "- "; to_string(ss, elem, indent + 1, true); first = false; } break; } case value_type::bytes: { const auto& bytes = value.asBytes(); auto b64 = util::base64_encode(bytes.data(), bytes.size()); b64 = util::join(util::split_by_n(b64, 64), '\n'); insert_string(ss, b64, indent); break; } } } std::string yaml::stringify(const dv::value& value) { std::stringstream ss; to_string(ss, value, 0); return ss.str(); }