mirror of
https://github.com/MihailRis/voxelcore.git
synced 2026-10-04 18:41:51 +00:00
459 lines
12 KiB
C++
459 lines
12 KiB
C++
#include "yaml.hpp"
|
|
#include "BasicParser.hpp"
|
|
|
|
#include <iomanip>
|
|
|
|
using namespace yaml;
|
|
|
|
namespace {
|
|
enum Chomping {
|
|
CLIP, STRIP, KEEP
|
|
};
|
|
|
|
class Parser : BasicParser<char> {
|
|
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") {
|
|
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) {
|
|
throw error("indentation error");
|
|
}
|
|
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();
|
|
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 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();
|
|
}
|