From e440b36823da0a91d7d185b73588ec70ebde91ef Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 14 Nov 2023 15:51:30 +0300 Subject: [PATCH] toml format is now used for settings file --- src/coders/commons.cpp | 288 +++++++++++++++++++++++++++++++++++++++++ src/coders/commons.h | 83 ++++++++++++ src/coders/toml.cpp | 238 ++++++++++++++++++++++++++++++++++ src/coders/toml.h | 68 ++++++++++ 4 files changed, 677 insertions(+) create mode 100644 src/coders/commons.cpp create mode 100644 src/coders/commons.h create mode 100644 src/coders/toml.cpp create mode 100644 src/coders/toml.h diff --git a/src/coders/commons.cpp b/src/coders/commons.cpp new file mode 100644 index 00000000..120c180d --- /dev/null +++ b/src/coders/commons.cpp @@ -0,0 +1,288 @@ +#include "commons.h" + +#include +#include + +using std::string; + +inline int char2int(int c) { + if (c >= '0' && c <= '9') { + return c - '0'; + } + if (c >= 'a' && c <= 'f') { + return 10 + c - 'a'; + } + if (c >= 'A' && c <= 'F') { + return 10 + c - 'A'; + } + return -1; +} + +inline double power(double base, int64_t power) { + double result = 1.0; + for (int64_t i = 0; i < power; i++) { + result *= base; + } + return result; +} + +parsing_error::parsing_error(string message, + string filename, + string source, + uint pos, + uint line, + uint linestart) + : std::runtime_error(message), filename(filename), source(source), + pos(pos), line(line), linestart(linestart) { +} + +string parsing_error::errorLog() const { + std::stringstream ss; + uint linepos = pos - linestart; + ss << "parsing error in file '" << filename; + ss << "' at " << (line+1) << ":" << linepos << ": " << this->what() << "\n"; + size_t end = source.find("\n", linestart); + if (end == string::npos) { + end = source.length(); + } + ss << source.substr(linestart, end-linestart) << "\n"; + for (uint i = 0; i < linepos; i++) { + ss << " "; + } + ss << "^"; + return ss.str(); + +} + +string escape_string(string s) { + std::stringstream ss; + ss << '"'; + for (char c : s) { + switch (c) { + case '\n': ss << "\\n"; break; + case '\r': ss << "\\r"; break; + case '\t': ss << "\\t"; break; + case '\f': ss << "\\f"; break; + case '\b': ss << "\\b"; break; + case '"': ss << "\\\""; break; + case '\\': ss << "\\\\"; break; + default: + if (c < ' ') { + ss << "\\" << std::oct << (int)c; + break; + } + ss << c; + break; + } + } + ss << '"'; + return ss.str(); +} + +BasicParser::BasicParser(std::string file, std::string source) : filename(file), source(source) { +} + +void BasicParser::skipWhitespace() { + while (hasNext()) { + char next = source[pos]; + if (next == '\n') { + line++; + linestart = ++pos; + continue; + } + if (is_whitespace(next)) { + pos++; + } else { + break; + } + } +} + +bool BasicParser::hasNext() { + return pos < source.length(); +} + +char BasicParser::nextChar() { + if (!hasNext()) { + throw error("unexpected end"); + } + return source[pos++]; +} + +void BasicParser::expect(char expected) { + char c = peek(); + if (c != expected) { + throw error("'"+string({expected})+"' expected"); + } + pos++; +} + +void BasicParser::expectNewLine() { + while (hasNext()) { + char next = source[pos]; + if (next == '\n') { + line++; + linestart = ++pos; + return; + } + if (is_whitespace(next)) { + pos++; + } else { + throw error("line separator expected"); + } + } +} + +char BasicParser::peek() { + skipWhitespace(); + if (pos >= source.length()) { + throw error("unexpected end"); + } + return source[pos]; +} + +string BasicParser::parseName() { + char c = peek(); + if (!is_identifier_start(c)) { + if (c == '"') { + pos++; + return parseString(c); + } + throw error("identifier expected"); + } + int start = pos; + while (hasNext() && is_identifier_part(source[pos])) { + pos++; + } + return source.substr(start, pos-start); +} + +int64_t BasicParser::parseSimpleInt(int base) { + char c = peek(); + int index = char2int(c); + if (index == -1 || index >= base) { + throw error("invalid number literal"); + } + int64_t value = index; + pos++; + while (hasNext()) { + c = source[pos]; + while (c == '_') { + c = source[++pos]; + } + index = char2int(c); + if (index == -1 || index >= base) { + return value; + } + value *= base; + value += index; + pos++; + } + return value; +} + +double BasicParser::parseNumber(int sign) { + char c = peek(); + int base = 10; + if (c == '0' && pos + 1 < source.length() && + (base = is_box(source[pos+1])) != 10) { + pos += 2; + return parseSimpleInt(base); + } else if (c == 'i' && pos + 2 < source.length() && source[pos+1] == 'n' && source[pos+2] == 'f') { + pos += 3; + return INFINITY * sign; + } else if (c == 'n' && pos + 2 < source.length() && source[pos+1] == 'a' && source[pos+2] == 'n') { + pos += 3; + return NAN * sign; + } + int64_t value = parseSimpleInt(base); + if (!hasNext()) { + return value * sign; + } + c = source[pos]; + if (c == 'e' || c == 'E') { + pos++; + int s = 1; + if (peek() == '-') { + s = -1; + pos++; + } else if (peek() == '+'){ + pos++; + } + return sign * value * power(10.0, s * parseSimpleInt(10)); + } + if (c == '.') { + pos++; + int64_t expo = 1; + while (hasNext() && source[pos] == '0') { + expo *= 10; + pos++; + } + int64_t afterdot = 0; + if (hasNext() && is_digit(source[pos])) { + afterdot = parseSimpleInt(10); + } + expo *= power(10, fmax(0, log10(afterdot) + 1)); + c = source[pos]; + + double dvalue = (value + (afterdot / (double)expo)); + if (c == 'e' || c == 'E') { + pos++; + int s = 1; + if (peek() == '-') { + s = -1; + pos++; + } else if (peek() == '+'){ + pos++; + } + return sign * dvalue * power(10.0, s * parseSimpleInt(10)); + } + return dvalue; + } + return value; +} + +string BasicParser::parseString(char quote) { + std::stringstream ss; + while (hasNext()) { + char c = source[pos]; + if (c == quote) { + pos++; + 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': pos++; continue; + default: + throw error("'\\" + string({c}) + "' is an illegal escape"); + } + continue; + } + if (c == '\n') { + throw error("non-closed string literal"); + } + ss << c; + pos++; + } + throw error("unexpected end"); +} + +parsing_error BasicParser::error(std::string message) { + return parsing_error(message, filename, source, pos, line, linestart); +} \ No newline at end of file diff --git a/src/coders/commons.h b/src/coders/commons.h new file mode 100644 index 00000000..136c5817 --- /dev/null +++ b/src/coders/commons.h @@ -0,0 +1,83 @@ +#ifndef CODERS_COMMONS_H_ +#define CODERS_COMMONS_H_ + +#include +#include + +inline int is_box(int c) { + switch (c) { + case 'B': + case 'b': + return 2; + case 'O': + case 'o': + return 8; + case 'X': + case 'x': + return 16; + } + return 10; +} + +inline bool is_digit(int c) { + return (c >= '0' && c <= '9'); +} + +inline bool is_whitespace(int c) { + return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == '\f'; +} + +inline bool is_identifier_start(int c) { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_' || c == '-' || c == '.'; +} + +inline bool is_identifier_part(int c) { + return is_identifier_start(c) || is_digit(c); +} + +extern std::string escape_string(std::string s); + +class parsing_error : public std::runtime_error { +public: + std::string filename; + std::string source; + uint pos; + uint line; + uint linestart; + + parsing_error(std::string message, + std::string filename, + std::string source, + uint pos, + uint line, + uint linestart); + + std::string errorLog() const; +}; + +class BasicParser { +protected: + std::string filename; + std::string source; + uint pos = 0; + uint line = 1; + uint linestart = 0; + + virtual void skipWhitespace(); + void expect(char expected); + char peek(); + char nextChar(); + bool hasNext(); + void expectNewLine(); + + std::string parseName(); + int64_t parseSimpleInt(int base); + double parseNumber(int sign); + std::string parseString(char chr); + + parsing_error error(std::string message); + + BasicParser(std::string filename, std::string source); +}; + +#endif // CODERS_COMMONS_H_ \ No newline at end of file diff --git a/src/coders/toml.cpp b/src/coders/toml.cpp new file mode 100644 index 00000000..4bea44f7 --- /dev/null +++ b/src/coders/toml.cpp @@ -0,0 +1,238 @@ +#include "toml.h" +#include "commons.h" + +#include +#include +#include +#include + +using std::string; + +using namespace toml; + +Section::Section(string name) : name(name) { +} + +void Section::add(std::string name, Field field) { + if (fields.find(name) != fields.end()) { + throw std::runtime_error("field duplication"); + } + fields[name] = field; + keyOrder.push_back(name); +} + +void Section::add(string name, bool* ptr) { + add(name, {fieldtype::ftbool, ptr}); +} + +void Section::add(string name, int* ptr) { + add(name, {fieldtype::ftint, ptr}); +} + +void Section::add(string name, uint* ptr) { + add(name, {fieldtype::ftuint, ptr}); +} + +void Section::add(string name, float* ptr) { + add(name, {fieldtype::ftfloat, ptr}); +} + +void Section::add(string name, string* ptr) { + add(name, {fieldtype::ftstring, ptr}); +} + +string Section::getName() const { + return name; +} + +const Field* Section::field(std::string name) const { + auto found = fields.find(name); + if (found == nullptr) { + return nullptr; + } + return &found->second; +} + +const std::vector& Section::keys() const { + return keyOrder; +} + +Wrapper::~Wrapper() { +} + +Section& Wrapper::add(std::string name) { + if (sections.find(name) != sections.end()) { + throw std::runtime_error("section duplication"); + } + Section* section = new Section(name); + sections[name] = section; + keyOrder.push_back(name); + return *section; +} + +Section* Wrapper::section(std::string name) { + auto found = sections.find(name); + if (found == sections.end()) { + return nullptr; + } + return found->second; +} + +std::string Wrapper::write() const { + std::stringstream ss; + for (string key : keyOrder) { + const Section* section = sections.at(key); + ss << "[" << key << "]\n"; + for (const string& key : section->keys()) { + ss << key << " = "; + const Field* field = section->field(key); + assert(field != nullptr); + switch (field->type) { + case fieldtype::ftbool: + ss << (*((bool*)field->ptr) ? "true" : "false"); + break; + case fieldtype::ftint: ss << *((int*)field->ptr); break; + case fieldtype::ftuint: ss << *((uint*)field->ptr); break; + case fieldtype::ftfloat: ss << *((float*)field->ptr); break; + case fieldtype::ftstring: + ss << escape_string(*((const string*)field->ptr)); + break; + } + ss << "\n"; + } + ss << "\n"; + } + return ss.str(); +} + +Reader::Reader(Wrapper* wrapper, string file, string source) : BasicParser(file, source), wrapper(wrapper) { +} + +void Reader::skipWhitespace() { + BasicParser::skipWhitespace(); + if (hasNext() && source[pos] == '#') { + pos++; + while (hasNext()) { + if (source[pos] == '\n') { + pos++; + linestart = pos; + line++; + break; + } + pos++; + } + if (hasNext() && is_whitespace(peek())) { + skipWhitespace(); + } + } +} + +void Reader::read() { + skipWhitespace(); + if (!hasNext()) { + return; + } + readSection(nullptr); +} + +inline bool is_numeric_type(fieldtype type) { + return type == fieldtype::ftint || type == fieldtype::ftfloat; +} + +void Section::set(string name, double value) { + const Field* field = this->field(name); + if (field == nullptr) { + std::cerr << "warning: unknown key '" << name << "'" << std::endl; + } else { + switch (field->type) { + case fieldtype::ftbool: *(bool*)(field->ptr) = fabs(value) > 0.0; break; + case fieldtype::ftint: *(int*)(field->ptr) = value; break; + case fieldtype::ftuint: *(uint*)(field->ptr) = value; break; + case fieldtype::ftfloat: *(float*)(field->ptr) = value; break; + case fieldtype::ftstring: *(string*)(field->ptr) = std::to_string(value); break; + default: + std::cerr << "error: type error for key '" << name << "'" << std::endl; + } + } +} + +void Section::set(std::string name, bool value) { + const Field* field = this->field(name); + if (field == nullptr) { + std::cerr << "warning: unknown key '" << name << "'" << std::endl; + } else { + switch (field->type) { + case fieldtype::ftbool: *(bool*)(field->ptr) = value; break; + case fieldtype::ftint: *(int*)(field->ptr) = (int)value; break; + case fieldtype::ftuint: *(uint*)(field->ptr) = (uint)value; break; + case fieldtype::ftfloat: *(float*)(field->ptr) = (float)value; break; + case fieldtype::ftstring: *(string*)(field->ptr) = value ? "true" : "false"; break; + default: + std::cerr << "error: type error for key '" << name << "'" << std::endl; + } + } +} + +void Section::set(std::string name, std::string value) { + const Field* field = this->field(name); + if (field == nullptr) { + std::cerr << "warning: unknown key '" << name << "'" << std::endl; + } else { + switch (field->type) { + case fieldtype::ftstring: *(string*)(field->ptr) = value; break; + default: + std::cerr << "error: type error for key '" << name << "'" << std::endl; + } + } +} + +void Reader::readSection(Section* section /*nullable*/) { + while (hasNext()) { + skipWhitespace(); + if (!hasNext()) { + break; + } + char c = nextChar(); + if (c == '[') { + string name = parseName(); + Section* section = wrapper->section(name); + pos++; + readSection(section); + return; + } + pos--; + string name = parseName(); + expect('='); + c = peek(); + if (is_digit(c)) { + double number = parseNumber(1); + if (section) { + section->set(name, number); + } + } else if (c == '-' || c == '+') { + int sign = c == '-' ? -1 : 1; + pos++; + double number = parseNumber(sign); + if (section) { + section->set(name, number); + } + } else if (is_identifier_start(c)) { + string identifier = parseName(); + if (identifier == "true" || identifier == "false") { + bool flag = identifier == "true"; + if (section) { + section->set(name, flag); + } + } + } else if (c == '"' || c == '\'') { + pos++; + string str = parseString(c); + if (section) { + section->set(name, str); + } + } else { + throw error("feature is not supported"); + } + expectNewLine(); + } +} \ No newline at end of file diff --git a/src/coders/toml.h b/src/coders/toml.h new file mode 100644 index 00000000..80fa85ca --- /dev/null +++ b/src/coders/toml.h @@ -0,0 +1,68 @@ +#ifndef CODERS_TOML_H_ +#define CODERS_TOML_H_ + +#include +#include +#include + +#include "commons.h" + +namespace toml { + enum class fieldtype { + ftbool, + ftint, + ftuint, + ftfloat, + ftstring, + }; + + struct Field { + fieldtype type; + void* ptr; + }; + + class Section { + std::unordered_map fields; + std::vector keyOrder; + std::string name; + void add(std::string name, Field field); + public: + Section(std::string name); + void add(std::string name, bool* ptr); + void add(std::string name, int* ptr); + void add(std::string name, uint* ptr); + void add(std::string name, float* ptr); + void add(std::string name, std::string* ptr); + + const Field* field(std::string name) const; + + void set(std::string name, double value); + void set(std::string name, bool value); + void set(std::string name, std::string value); + + std::string getName() const; + const std::vector& keys() const; + }; + + class Wrapper { + std::unordered_map sections; + std::vector keyOrder; + public: + ~Wrapper(); + Section& add(std::string section); + Section* section(std::string name); + + std::string write() const; + }; + + class Reader : public BasicParser { + Wrapper* wrapper; + void skipWhitespace() override; + void readSection(Section* section); + public: + Reader(Wrapper* wrapper, std::string file, std::string source); + void read(); + }; +} + +#endif // CODERS_TOML_H_ \ No newline at end of file