296 lines
9.0 KiB
C++
296 lines
9.0 KiB
C++
#include "toml.hpp"
|
|
|
|
#include <assert.h>
|
|
#include <math.h>
|
|
|
|
#include <iomanip>
|
|
#include <sstream>
|
|
|
|
#include "data/setting.hpp"
|
|
#include "files/settings_io.hpp"
|
|
#include "util/stringutil.hpp"
|
|
#include "commons.hpp"
|
|
|
|
using namespace toml;
|
|
|
|
class TomlReader : BasicParser {
|
|
dv::value root;
|
|
|
|
void skipWhitespace() override {
|
|
BasicParser::skipWhitespace();
|
|
if (hasNext() && source[pos] == '#') {
|
|
skipLine();
|
|
if (hasNext() && is_whitespace(peek())) {
|
|
skipWhitespace();
|
|
}
|
|
}
|
|
}
|
|
|
|
// modified version of BaseParser.parseString
|
|
// todo: extract common part
|
|
std::string parseMultilineString() {
|
|
pos += 2;
|
|
char next = peek();
|
|
|
|
std::stringstream ss;
|
|
while (hasNext()) {
|
|
char c = source[pos];
|
|
if (c == '"' && remain() >= 2 &&
|
|
source[pos+1] == '"' &&
|
|
source[pos+2] == '"') {
|
|
pos += 3;
|
|
return ss.str();
|
|
}
|
|
if (c == '\\') {
|
|
pos++;
|
|
c = nextChar();
|
|
if (c >= '0' && c <= '7') {
|
|
pos--;
|
|
ss << (char)parseSimpleInt(8);
|
|
continue;
|
|
}
|
|
switch (c) {
|
|
case 'n': ss << '\n'; break;
|
|
case 'r': ss << '\r'; break;
|
|
case 'b': ss << '\b'; break;
|
|
case 't': ss << '\t'; break;
|
|
case 'f': ss << '\f'; break;
|
|
case '\'': ss << '\\'; break;
|
|
case '"': ss << '"'; break;
|
|
case '\\': ss << '\\'; break;
|
|
case '/': ss << '/'; break;
|
|
case '\n': continue;
|
|
default:
|
|
throw error(
|
|
"'\\" + std::string({c}) + "' is an illegal escape"
|
|
);
|
|
}
|
|
continue;
|
|
}
|
|
ss << c;
|
|
pos++;
|
|
}
|
|
throw error("unexpected end");
|
|
}
|
|
|
|
dv::value parseValue() {
|
|
char c = peek();
|
|
if (is_digit(c)) {
|
|
int start = pos;
|
|
// parse numeric literal
|
|
auto value = parseNumber(1);
|
|
if (hasNext() && peekNoJump() == '-') {
|
|
while (hasNext()) {
|
|
c = source[pos];
|
|
if (!is_digit(c) && c != ':' && c != '.' && c != '-' &&
|
|
c != 'T' && c != 'Z') {
|
|
break;
|
|
}
|
|
pos++;
|
|
}
|
|
return std::string(source.substr(start, pos - start));
|
|
}
|
|
return value;
|
|
} else if (c == '-' || c == '+') {
|
|
int sign = c == '-' ? -1 : 1;
|
|
pos++;
|
|
// parse numeric literal
|
|
return parseNumber(sign);
|
|
} else if (is_identifier_start(c)) {
|
|
// parse keywords
|
|
std::string keyword = parseName();
|
|
if (keyword == "true" || keyword == "false") {
|
|
return keyword == "true";
|
|
} else if (keyword == "inf") {
|
|
return INFINITY;
|
|
} else if (keyword == "nan") {
|
|
return NAN;
|
|
}
|
|
throw error("unknown keyword " + util::quote(keyword));
|
|
} else if (c == '"' || c == '\'') {
|
|
pos++;
|
|
if (remain() >= 2 &&
|
|
c == '"' &&
|
|
source[pos] == '"' &&
|
|
source[pos+1] == '"') {
|
|
return parseMultilineString();
|
|
}
|
|
return parseString(c);
|
|
} else if (c == '[') {
|
|
// parse array
|
|
pos++;
|
|
dv::list_t values;
|
|
while (peek() != ']') {
|
|
values.push_back(parseValue());
|
|
if (peek() != ']') {
|
|
expect(',');
|
|
}
|
|
}
|
|
pos++;
|
|
return dv::value(std::move(values));
|
|
} else if (c == '{') {
|
|
// parse inline table
|
|
pos++;
|
|
auto table = dv::object();
|
|
while (peek() != '}') {
|
|
auto key = parseName();
|
|
expect('=');
|
|
table[key] = parseValue();
|
|
if (peek() != '}') {
|
|
expect(',');
|
|
}
|
|
}
|
|
pos++;
|
|
return table;
|
|
} else {
|
|
throw error("feature is not supported");
|
|
}
|
|
}
|
|
|
|
dv::value& parseLValue(dv::value& root) {
|
|
dv::value* lvalue = &root;
|
|
while (hasNext()) {
|
|
char c = peek();
|
|
std::string name;
|
|
if (c == '\'' || c == '"') {
|
|
pos++;
|
|
name = parseString(c);
|
|
} else {
|
|
name = parseName();
|
|
}
|
|
if (lvalue->getType() == dv::value_type::none) {
|
|
*lvalue = dv::object();
|
|
}
|
|
lvalue = &(*lvalue)[name];
|
|
if (peek() != '.') {
|
|
break;
|
|
}
|
|
pos++;
|
|
}
|
|
return *lvalue;
|
|
}
|
|
|
|
void readSection(dv::value& map, dv::value& root) {
|
|
while (hasNext()) {
|
|
skipWhitespace();
|
|
if (!hasNext()) {
|
|
break;
|
|
}
|
|
char c = nextChar();
|
|
if (c == '[') {
|
|
if (hasNext() && peek() == '[') {
|
|
pos++;
|
|
// parse list of tables
|
|
dv::value& list = parseLValue(root);
|
|
if (list == nullptr) {
|
|
list = dv::list();
|
|
} else if (!list.isList()) {
|
|
throw error("target is not an array");
|
|
}
|
|
expect(']');
|
|
expect(']');
|
|
dv::value section = dv::object();
|
|
readSection(section, root);
|
|
list.add(std::move(section));
|
|
return;
|
|
}
|
|
// parse table
|
|
dv::value& section = parseLValue(root);
|
|
if (section == nullptr) {
|
|
section = dv::object();
|
|
} else if (!section.isObject()) {
|
|
throw error("target is not a table");
|
|
}
|
|
expect(']');
|
|
readSection(section, root);
|
|
return;
|
|
}
|
|
pos--;
|
|
dv::value& lvalue = parseLValue(map);
|
|
expect('=');
|
|
lvalue = parseValue();
|
|
expectNewLine();
|
|
}
|
|
}
|
|
public:
|
|
TomlReader(std::string_view file, std::string_view source)
|
|
: BasicParser(file, source), root(dv::object()) {
|
|
}
|
|
|
|
dv::value read() {
|
|
skipWhitespace();
|
|
if (!hasNext()) {
|
|
return std::move(root);
|
|
}
|
|
readSection(root, root);
|
|
return std::move(root);
|
|
}
|
|
};
|
|
|
|
void toml::parse(
|
|
SettingsHandler& handler, std::string_view file, std::string_view source
|
|
) {
|
|
auto map = parse(file, source);
|
|
|
|
for (const auto& [sectionName, sectionMap] : map.asObject()) {
|
|
if (!sectionMap.isObject()) {
|
|
continue;
|
|
}
|
|
for (const auto& [name, value] : sectionMap.asObject()) {
|
|
auto fullname = sectionName + "." + name;
|
|
if (handler.has(fullname)) {
|
|
handler.setValue(fullname, value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
dv::value toml::parse(std::string_view file, std::string_view source) {
|
|
return TomlReader(file, source).read();
|
|
}
|
|
|
|
std::string toml::stringify(const dv::value& root, const std::string& name) {
|
|
std::stringstream ss;
|
|
if (!name.empty()) {
|
|
ss << "[" << name << "]\n";
|
|
}
|
|
for (const auto& [key, value] : root.asObject()) {
|
|
if (!value.isObject()) {
|
|
ss << key << " = " << value << "\n";
|
|
}
|
|
}
|
|
for (const auto& [key, value] : root.asObject()) {
|
|
if (value.isObject()) {
|
|
ss << "\n" << toml::stringify(value,
|
|
name.empty() ? key : name + "." + key);
|
|
}
|
|
}
|
|
return ss.str();
|
|
}
|
|
|
|
std::string toml::stringify(SettingsHandler& handler) {
|
|
auto& sections = handler.getSections();
|
|
|
|
std::stringstream ss;
|
|
for (auto& section : sections) {
|
|
ss << "[" << section.name << "]\n";
|
|
for (const std::string& key : section.keys) {
|
|
ss << key << " = ";
|
|
auto setting = handler.getSetting(section.name + "." + key);
|
|
assert(setting != nullptr);
|
|
if (auto integer = dynamic_cast<IntegerSetting*>(setting)) {
|
|
ss << integer->get();
|
|
} else if (auto number = dynamic_cast<NumberSetting*>(setting)) {
|
|
ss << number->get();
|
|
} else if (auto flag = dynamic_cast<FlagSetting*>(setting)) {
|
|
ss << (flag->get() ? "true" : "false");
|
|
} else if (auto string = dynamic_cast<StringSetting*>(setting)) {
|
|
ss << util::escape(string->get());
|
|
}
|
|
ss << "\n";
|
|
}
|
|
ss << "\n";
|
|
}
|
|
return ss.str();
|
|
}
|