diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/3rd-party/toml/LICENSE | 24 | ||||
| -rw-r--r-- | src/3rd-party/toml/toml.h | 1958 | ||||
| -rw-r--r-- | src/browser.cpp | 21 | ||||
| -rw-r--r-- | src/browser.h | 5 | ||||
| -rw-r--r-- | src/forms/blockerdialog.cpp | 5 | ||||
| -rw-r--r-- | src/forms/downloaddialog.cpp | 6 | ||||
| -rw-r--r-- | src/mainwindow.cpp | 27 | ||||
| -rw-r--r-- | src/settings.cpp | 80 | ||||
| -rw-r--r-- | src/settings.h | 25 | ||||
| -rw-r--r-- | src/widgets/webviewtabbar.cpp | 15 | 
10 files changed, 2092 insertions, 74 deletions
| diff --git a/src/3rd-party/toml/LICENSE b/src/3rd-party/toml/LICENSE new file mode 100644 index 0000000..1514196 --- /dev/null +++ b/src/3rd-party/toml/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2014, MAYAH +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this +  list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, +  this list of conditions and the following disclaimer in the documentation +  and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/src/3rd-party/toml/toml.h b/src/3rd-party/toml/toml.h new file mode 100644 index 0000000..ec8c489 --- /dev/null +++ b/src/3rd-party/toml/toml.h @@ -0,0 +1,1958 @@ +#ifndef TINYTOML_H_ +#define TINYTOML_H_ + +#include <algorithm> +#include <cassert> +#include <cctype> +#include <chrono> +#include <cmath> +#include <cstdint> +#include <cstdio> +#include <ctime> +#include <iomanip> +#include <istream> +#include <sstream> +#include <stdexcept> +#include <string> +#include <map> +#include <memory> +#include <utility> +#include <vector> + +namespace toml { + +// ---------------------------------------------------------------------- +// Declarations + +class Value; +typedef std::chrono::system_clock::time_point Time; +typedef std::vector<Value> Array; +typedef std::map<std::string, Value> Table; + +namespace internal { +template<typename T> struct call_traits_value { +    typedef T return_type; +}; +template<typename T> struct call_traits_ref { +    typedef const T& return_type; +}; +} // namespace internal + +template<typename T> struct call_traits; +template<> struct call_traits<bool> : public internal::call_traits_value<bool> {}; +template<> struct call_traits<int> : public internal::call_traits_value<int> {}; +template<> struct call_traits<int64_t> : public internal::call_traits_value<int64_t> {}; +template<> struct call_traits<double> : public internal::call_traits_value<double> {}; +template<> struct call_traits<std::string> : public internal::call_traits_ref<std::string> {}; +template<> struct call_traits<Time> : public internal::call_traits_ref<Time> {}; +template<> struct call_traits<Array> : public internal::call_traits_ref<Array> {}; +template<> struct call_traits<Table> : public internal::call_traits_ref<Table> {}; + +// A value is returned for std::vector<T>. Not reference. +// This is because a fresh vector is made. +template<typename T> struct call_traits<std::vector<T>> : public internal::call_traits_value<std::vector<T>> {}; + +// Formatting flags +enum FormatFlag { +    FORMAT_NONE = 0, +    FORMAT_INDENT = 1 +}; + +class Value { +public: +    enum Type { +        NULL_TYPE, +        BOOL_TYPE, +        INT_TYPE, +        DOUBLE_TYPE, +        STRING_TYPE, +        TIME_TYPE, +        ARRAY_TYPE, +        TABLE_TYPE, +    }; + +    Value() : type_(NULL_TYPE), null_(nullptr) {} +    Value(bool v) : type_(BOOL_TYPE), bool_(v) {} +    Value(int v) : type_(INT_TYPE), int_(v) {} +    Value(int64_t v) : type_(INT_TYPE), int_(v) {} +    Value(double v) : type_(DOUBLE_TYPE), double_(v) {} +    Value(const std::string& v) : type_(STRING_TYPE), string_(new std::string(v)) {} +    Value(const char* v) : type_(STRING_TYPE), string_(new std::string(v)) {} +    Value(const Time& v) : type_(TIME_TYPE), time_(new Time(v)) {} +    Value(const Array& v) : type_(ARRAY_TYPE), array_(new Array(v)) {} +    Value(const Table& v) : type_(TABLE_TYPE), table_(new Table(v)) {} +    Value(std::string&& v) : type_(STRING_TYPE), string_(new std::string(std::move(v))) {} +    Value(Array&& v) : type_(ARRAY_TYPE), array_(new Array(std::move(v))) {} +    Value(Table&& v) : type_(TABLE_TYPE), table_(new Table(std::move(v))) {} + +    Value(const Value& v); +    Value(Value&& v) noexcept; +    Value& operator=(const Value& v); +    Value& operator=(Value&& v) noexcept; + +    // Guards from unexpected Value construction. +    // Someone might use a value like this: +    //   toml::Value v = x->find("foo"); +    // But this is wrong. Without this constructor, +    // value will be unexpectedly initialized with bool. +    Value(const void* v) = delete; +    ~Value(); + +    // Retruns Value size. +    // 0 for invalid value. +    // The number of inner elements for array or table. +    // 1 for other types. +    size_t size() const; +    bool empty() const; +    Type type() const { return type_; } + +    bool valid() const { return type_ != NULL_TYPE; } +    template<typename T> bool is() const; +    template<typename T> typename call_traits<T>::return_type as() const; + +    friend bool operator==(const Value& lhs, const Value& rhs); +    friend bool operator!=(const Value& lhs, const Value& rhs) { return !(lhs == rhs); } + +    // ---------------------------------------------------------------------- +    // For integer/floating value + +    // Returns true if the value is int or double. +    bool isNumber() const; +    // Returns number. Convert to double. +    double asNumber() const; + +    // ---------------------------------------------------------------------- +    // For Time value + +    // Converts to time_t if the internal value is Time. +    // We don't have as<std::time_t>(). Since time_t is basically signed long, +    // it's something like a method to converting to (normal) integer. +    std::time_t as_time_t() const; + +    // ---------------------------------------------------------------------- +    // For Table value +    template<typename T> typename call_traits<T>::return_type get(const std::string&) const; +    Value* set(const std::string& key, const Value& v); +    // Finds a Value with |key|. |key| can contain '.' +    // Note: if you would like to find a child value only, you need to use findChild. +    const Value* find(const std::string& key) const; +    Value* find(const std::string& key); +    bool has(const std::string& key) const { return find(key) != nullptr; } +    bool erase(const std::string& key); + +    Value& operator[](const std::string& key); + +    // Merge table. Returns true if succeeded. Otherwise, |this| might be corrupted. +    // When the same key exists, it will be overwritten. +    bool merge(const Value&); + +    // Finds a value with |key|. It searches only children. +    Value* findChild(const std::string& key); +    const Value* findChild(const std::string& key) const; +    // Sets a value, and returns the pointer to the created value. +    // When the value having the same key exists, it will be overwritten. +    Value* setChild(const std::string& key, const Value& v); +    Value* setChild(const std::string& key, Value&& v); +    bool eraseChild(const std::string& key); + +    // ---------------------------------------------------------------------- +    // For Array value + +    template<typename T> typename call_traits<T>::return_type get(size_t index) const; +    const Value* find(size_t index) const; +    Value* find(size_t index); +    Value* push(const Value& v); +    Value* push(Value&& v); + +    // ---------------------------------------------------------------------- +    // Others + +    // Writer. +    static std::string spaces(int num); +    static std::string escapeKey(const std::string& key); + +    void write(std::ostream*, const std::string& keyPrefix = std::string(), int indent = -1) const; +    void writeFormatted(std::ostream*, FormatFlag flags) const; + +    friend std::ostream& operator<<(std::ostream&, const Value&); + +private: +    static const char* typeToString(Type); + +    template<typename T> void assureType() const; +    Value* ensureValue(const std::string& key); + +    template<typename T> struct ValueConverter; + +    Type type_; +    union { +        void* null_; +        bool bool_; +        int64_t int_; +        double double_; +        std::string* string_; +        Time* time_; +        Array* array_; +        Table* table_; +    }; + +    template<typename T> friend struct ValueConverter; +}; + +// parse() returns ParseResult. +struct ParseResult { +    ParseResult(toml::Value v, std::string er) : +        value(std::move(v)), +        errorReason(std::move(er)) {} + +    bool valid() const { return value.valid(); } + +    toml::Value value; +    std::string errorReason; +}; + +// Parses from std::istream. +ParseResult parse(std::istream&); + +// ---------------------------------------------------------------------- +// Declarations for Implementations +//   You don't need to understand the below to use this library. + +#if defined(_WIN32) +// Windows does not have timegm but have _mkgmtime. +inline time_t timegm(std::tm* timeptr) +{ +    return _mkgmtime(timeptr); +} +#endif +#if defined(_MSC_VER) +// Visual studio doesn't define gmtime_r, but mingw does +inline std::tm* gmtime_r(const time_t* timer, std::tm* result) +{ +    gmtime_s(result, timer); +    return result; +} +#endif + +namespace internal { + +enum class TokenType { +    ERROR, +    END_OF_FILE, +    END_OF_LINE, +    IDENT, +    STRING, +    MULTILINE_STRING, +    BOOL, +    INT, +    DOUBLE, +    TIME, +    COMMA, +    DOT, +    EQUAL, +    LBRACKET, +    RBRACKET, +    LBRACE, +    RBRACE, +}; + +class Token { +public: +    explicit Token(TokenType type) : type_(type) {} +    Token(TokenType type, const std::string& v) : type_(type), str_value_(v) {} +    Token(TokenType type, bool v) : type_(type), int_value_(v) {} +    Token(TokenType type, std::int64_t v) : type_(type), int_value_(v) {} +    Token(TokenType type, double v) : type_(type), double_value_(v) {} +    Token(TokenType type, std::chrono::system_clock::time_point tp) : type_(type), time_value_(tp) {} + +    TokenType type() const { return type_; } +    const std::string& strValue() const { return str_value_; } +    bool boolValue() const { return int_value_ != 0; } +    std::int64_t intValue() const { return int_value_; } +    double doubleValue() const { return double_value_; } +    std::chrono::system_clock::time_point timeValue() const { return time_value_; } + +private: +    TokenType type_; +    std::string str_value_; +    std::int64_t int_value_; +    double double_value_; +    std::chrono::system_clock::time_point time_value_; +}; + +class Lexer { +public: +    explicit Lexer(std::istream& is) : is_(is), lineNo_(1) {} + +    Token nextKeyToken(); +    Token nextValueToken(); + +    int lineNo() const { return lineNo_; } + +private: +    bool current(char* c); +    void next(); +    bool consume(char c); + +    Token nextToken(bool isValueToken); + +    void skipUntilNewLine(); + +    Token nextStringDoubleQuote(); +    Token nextStringSingleQuote(); + +    Token nextKey(); +    Token nextValue(); + +    Token parseAsTime(const std::string&); + +    std::istream& is_; +    int lineNo_; +}; + +class Parser { +public: +    explicit Parser(std::istream& is) : lexer_(is), token_(TokenType::ERROR) { nextKey(); } + +    // Parses. If failed, value should be invalid value. +    // You can get the error by calling errorReason(). +    Value parse(); +    const std::string& errorReason(); + +private: +    const Token& token() const { return token_; } +    void nextKey() { token_ = lexer_.nextKeyToken(); } +    void nextValue() { token_ = lexer_.nextValueToken(); } + +    void skipForKey(); +    void skipForValue(); + +    bool consumeForKey(TokenType); +    bool consumeForValue(TokenType); +    bool consumeEOLorEOFForKey(); + +    Value* parseGroupKey(Value* root); + +    bool parseKeyValue(Value*); +    bool parseKey(std::string*); +    bool parseValue(Value*); +    bool parseBool(Value*); +    bool parseNumber(Value*); +    bool parseArray(Value*); +    bool parseInlineTable(Value*); + +    void addError(const std::string& reason); + +    Lexer lexer_; +    Token token_; +    std::string errorReason_; +}; + +} // namespace internal + +// ---------------------------------------------------------------------- +// Implementations + +inline ParseResult parse(std::istream& is) +{ +    internal::Parser parser(is); +    toml::Value v = parser.parse(); + +    if (v.valid()) +        return ParseResult(std::move(v), std::string()); + +    return ParseResult(std::move(v), std::move(parser.errorReason())); +} + +inline std::string format(std::stringstream& ss) +{ +    return ss.str(); +} + +template<typename T, typename... Args> +std::string format(std::stringstream& ss, T&& t, Args&&... args) +{ +    ss << std::forward<T>(t); +    return format(ss, std::forward<Args>(args)...); +} + +template<typename... Args> +#if defined(_MSC_VER) +__declspec(noreturn) +#else +[[noreturn]] +#endif +void failwith(Args&&... args) +{ +    std::stringstream ss; +    throw std::runtime_error(format(ss, std::forward<Args>(args)...)); +} + +namespace internal { + +inline std::string removeDelimiter(const std::string& s) +{ +    std::string r; +    for (char c : s) { +        if (c == '_') +            continue; +        r += c; +    } +    return r; +} + +inline std::string unescape(const std::string& codepoint) +{ +    std::uint32_t x; +    std::uint8_t buf[8]; +    std::stringstream ss(codepoint); + +    ss >> std::hex >> x; + +    if (x <= 0x7FUL) { +        // 0xxxxxxx +        buf[0] = 0x00 | ((x >> 0) & 0x7F); +        buf[1] = '\0'; +    } else if (x <= 0x7FFUL) { +        // 110yyyyx 10xxxxxx +        buf[0] = 0xC0 | ((x >> 6) & 0xDF); +        buf[1] = 0x80 | ((x >> 0) & 0xBF); +        buf[2] = '\0'; +    } else if (x <= 0xFFFFUL) { +        // 1110yyyy 10yxxxxx 10xxxxxx +        buf[0] = 0xE0 | ((x >> 12) & 0xEF); +        buf[1] = 0x80 | ((x >> 6) & 0xBF); +        buf[2] = 0x80 | ((x >> 0) & 0xBF); +        buf[3] = '\0'; +    } else if (x <= 0x10FFFFUL) { +        // 11110yyy 10yyxxxx 10xxxxxx 10xxxxxx +        buf[0] = 0xF0 | ((x >> 18) & 0xF7); +        buf[1] = 0x80 | ((x >> 12) & 0xBF); +        buf[2] = 0x80 | ((x >> 6) & 0xBF); +        buf[3] = 0x80 | ((x >> 0) & 0xBF); +        buf[4] = '\0'; +    } else { +        buf[0] = '\0'; +    } + +    return reinterpret_cast<char*>(buf); +} + +// Returns true if |s| is integer. +// [+-]?\d+(_\d+)* +inline bool isInteger(const std::string& s) +{ +    if (s.empty()) +        return false; + +    std::string::size_type p = 0; +    if (s[p] == '+' || s[p] == '-') +        ++p; + +    while (p < s.size() && '0' <= s[p] && s[p] <= '9') { +        ++p; +        if (p < s.size() && s[p] == '_') { +            ++p; +            if (!(p < s.size() && '0' <= s[p] && s[p] <= '9')) +                return false; +        } +    } + +    return p == s.size(); +} + +// Returns true if |s| is double. +// [+-]? (\d+(_\d+)*)? (\.\d+(_\d+)*)? ([eE] [+-]? \d+(_\d+)*)? +//       1-----------  2-------------  3---------------------- +// 2 or (1 and 3) should exist. +inline bool isDouble(const std::string& s) +{ +    if (s.empty()) +        return false; + +    std::string::size_type p = 0; +    if (s[p] == '+' || s[p] == '-') +        ++p; + +    bool ok = false; +    while (p < s.size() && '0' <= s[p] && s[p] <= '9') { +        ++p; +        ok = true; + +        if (p < s.size() && s[p] == '_') { +            ++p; +            if (!(p < s.size() && '0' <= s[p] && s[p] <= '9')) +                return false; +        } +    } + +    if (p < s.size() && s[p] == '.') +        ++p; + +    while (p < s.size() && '0' <= s[p] && s[p] <= '9') { +        ++p; +        ok = true; + +        if (p < s.size() && s[p] == '_') { +            ++p; +            if (!(p < s.size() && '0' <= s[p] && s[p] <= '9')) +                return false; +        } +    } + +    if (!ok) +        return false; + +    ok = false; +    if (p < s.size() && (s[p] == 'e' || s[p] == 'E')) { +        ++p; +        if (p < s.size() && (s[p] == '+' || s[p] == '-')) +            ++p; +        while (p < s.size() && '0' <= s[p] && s[p] <= '9') { +            ++p; +            ok = true; + +            if (p < s.size() && s[p] == '_') { +                ++p; +                if (!(p < s.size() && '0' <= s[p] && s[p] <= '9')) +                    return false; +            } +        } +        if (!ok) +            return false; +    } + +    return p == s.size(); +} + +// static +inline std::string escapeString(const std::string& s) +{ +    std::stringstream ss; +    for (size_t i = 0; i < s.size(); ++i) { +        switch (s[i]) { +        case '\n': ss << "\\n"; break; +        case '\r': ss << "\\r"; break; +        case '\t': ss << "\\t"; break; +        case '\"': ss << "\\\""; break; +        case '\'': ss << "\\\'"; break; +        case '\\': ss << "\\\\"; break; +        default: ss << s[i]; break; +        } +    } + +    return ss.str(); +} + +} // namespace internal + +// ---------------------------------------------------------------------- +// Lexer + +namespace internal { + +inline bool Lexer::current(char* c) +{ +    int x = is_.peek(); +    if (x == EOF) +        return false; +    *c = static_cast<char>(x); +    return true; +} + +inline void Lexer::next() +{ +    int x = is_.get(); +    if (x == '\n') +        ++lineNo_; +} + +inline bool Lexer::consume(char c) +{ +    char x; +    if (!current(&x)) +        return false; +    if (x != c) +        return false; +    next(); +    return true; +} + +inline void Lexer::skipUntilNewLine() +{ +    char c; +    while (current(&c)) { +        if (c == '\n') +            return; +        next(); +    } +} + +inline Token Lexer::nextStringDoubleQuote() +{ +    if (!consume('"')) +        return Token(TokenType::ERROR, std::string("string didn't start with '\"'")); + +    std::string s; +    char c; +    bool multiline = false; + +    if (current(&c) && c == '"') { +        next(); +        if (!current(&c) || c != '"') { +            // OK. It's empty string. +            return Token(TokenType::STRING, std::string()); +        } + +        next(); +        // raw string literal started. +        // Newline just after """ should be ignored. +        while (current(&c) && (c == ' ' || c == '\t')) +            next(); +        if (current(&c) && c == '\n') +            next(); +        multiline = true; +    } + +    while (current(&c)) { +        next(); +        if (c == '\\') { +            if (!current(&c)) +                return Token(TokenType::ERROR, std::string("string has unknown escape sequence")); +            next(); +            switch (c) { +            case 't': c = '\t'; break; +            case 'n': c = '\n'; break; +            case 'r': c = '\r'; break; +            case 'u': +            case 'U': { +                int size = c == 'u' ? 4 : 8; +                std::string codepoint; +                for (int i = 0; i < size; ++i) { +                  if (current(&c) && (('0' <= c && c <= '9') || ('A' <= c && c <= 'F') || ('a' <= c && c <= 'f'))) { +                    codepoint += c; +                    next(); +                  } else { +                    return Token(TokenType::ERROR, std::string("string has unknown escape sequence")); +                  } +                } +                s += unescape(codepoint); +                continue; +            } +            case '"': c = '"'; break; +            case '\'': c = '\''; break; +            case '\\': c = '\\'; break; +            case '\n': +                while (current(&c) && (c == ' ' || c == '\t' || c == '\r' || c == '\n')) { +                    next(); +                } +                continue; +            default: +                return Token(TokenType::ERROR, std::string("string has unknown escape sequence")); +            } +        } else if (c == '"') { +            if (multiline) { +                if (current(&c) && c == '"') { +                    next(); +                    if (current(&c) && c == '"') { +                        next(); +                        return Token(TokenType::MULTILINE_STRING, s); +                    } else { +                        s += '"'; +                        s += '"'; +                        continue; +                    } +                } else { +                    s += '"'; +                    continue; +                } +            } else { +                return Token(TokenType::STRING, s); +            } +        } + +        s += c; +    } + +    return Token(TokenType::ERROR, std::string("string didn't end")); +} + +inline Token Lexer::nextStringSingleQuote() +{ +    if (!consume('\'')) +        return Token(TokenType::ERROR, std::string("string didn't start with '\''?")); + +    std::string s; +    char c; + +    if (current(&c) && c == '\'') { +        next(); +        if (!current(&c) || c != '\'') { +            // OK. It's empty string. +            return Token(TokenType::STRING, std::string()); +        } +        next(); +        // raw string literal started. +        // Newline just after """ should be ignored. +        if (current(&c) && c == '\n') +            next(); + +        while (current(&c)) { +            if (c == '\'') { +                next(); +                if (current(&c) && c == '\'') { +                    next(); +                    if (current(&c) && c == '\'') { +                        next(); +                        return Token(TokenType::MULTILINE_STRING, s); +                    } else { +                        s += '\''; +                        s += '\''; +                        continue; +                    } +                } else { +                    s += '\''; +                    continue; +                } +            } + +            next(); +            s += c; +            continue; +        } + +        return Token(TokenType::ERROR, std::string("string didn't end with '\'\'\'' ?")); +    } + +    while (current(&c)) { +        next(); +        if (c == '\'') { +            return Token(TokenType::STRING, s); +        } + +        s += c; +    } + +    return Token(TokenType::ERROR, std::string("string didn't end with '\''?")); +} + +inline Token Lexer::nextKey() +{ +    std::string s; +    char c; +    while (current(&c) && (isalnum(c) || c == '_' || c == '-')) { +        s += c; +        next(); +    } + +    if (s.empty()) +        return Token(TokenType::ERROR, std::string("Unknown key format")); + +    return Token(TokenType::IDENT, s); +} + +inline Token Lexer::nextValue() +{ +    std::string s; +    char c; + +    if (current(&c) && isalpha(c)) { +        s += c; +        next(); +        while (current(&c) && isalpha(c)) { +            s += c; +            next(); +        } + +        if (s == "true") +            return Token(TokenType::BOOL, true); +        if (s == "false") +            return Token(TokenType::BOOL, false); +        return Token(TokenType::ERROR, std::string("Unknown ident: ") + s); +    } + +    while (current(&c) && (('0' <= c && c <= '9') || c == '.' || c == 'e' || c == 'E' || +                           c == 'T' || c == 'Z' || c == '_' || c == ':' || c == '-' || c == '+')) { +        next(); +        s += c; +    } + +    if (isInteger(s)) { +        std::stringstream ss(removeDelimiter(s)); +        std::int64_t x; +        ss >> x; +        return Token(TokenType::INT, x); +    } + +    if (isDouble(s)) { +        std::stringstream ss(removeDelimiter(s)); +        double d; +        ss >> d; +        return Token(TokenType::DOUBLE, d); +    } + +    return parseAsTime(s); +} + +inline Token Lexer::parseAsTime(const std::string& str) +{ +    const char* s = str.c_str(); + +    int n; +    int YYYY, MM, DD; +    if (sscanf(s, "%d-%d-%d%n", &YYYY, &MM, &DD, &n) != 3) +        return Token(TokenType::ERROR, std::string("Invalid token")); + +    if (s[n] == '\0') { +        std::tm t; +        t.tm_sec = 0; +        t.tm_min = 0; +        t.tm_hour = 0; +        t.tm_mday = DD; +        t.tm_mon = MM - 1; +        t.tm_year = YYYY - 1900; +        auto tp = std::chrono::system_clock::from_time_t(timegm(&t)); +        return Token(TokenType::TIME, tp); +    } + +    if (s[n] != 'T') +        return Token(TokenType::ERROR, std::string("Invalid token")); + +    s = s + n + 1; + +    int hh, mm; +    double ss; // double for fraction +    if (sscanf(s, "%d:%d:%lf%n", &hh, &mm, &ss, &n) != 3) +        return Token(TokenType::ERROR, std::string("Invalid token")); + +    std::tm t; +    t.tm_sec = static_cast<int>(ss); +    t.tm_min = mm; +    t.tm_hour = hh; +    t.tm_mday = DD; +    t.tm_mon = MM - 1; +    t.tm_year = YYYY - 1900; +    auto tp = std::chrono::system_clock::from_time_t(timegm(&t)); +    ss -= static_cast<int>(ss); +    // TODO(mayah): workaround GCC 4.9.3 on cygwin does not have std::round, but round(). +    tp += std::chrono::microseconds(static_cast<std::int64_t>(round(ss * 1000000))); + +    if (s[n] == '\0') +        return Token(TokenType::TIME, tp); + +    if (s[n] == 'Z' && s[n + 1] == '\0') +        return Token(TokenType::TIME, tp); + +    s = s + n; +    // offset +    // [+/-]%d:%d +    char pn; +    int oh, om; +    if (sscanf(s, "%c%d:%d", &pn, &oh, &om) != 3) +        return Token(TokenType::ERROR, std::string("Invalid token")); + +    if (pn != '+' && pn != '-') +        return Token(TokenType::ERROR, std::string("Invalid token")); + +    if (pn == '+') { +        tp -= std::chrono::hours(oh); +        tp -= std::chrono::minutes(om); +    } else { +        tp += std::chrono::hours(oh); +        tp += std::chrono::minutes(om); +    } + +    return Token(TokenType::TIME, tp); +} + +inline Token Lexer::nextKeyToken() +{ +    return nextToken(false); +} + +inline Token Lexer::nextValueToken() +{ +    return nextToken(true); +} + +inline Token Lexer::nextToken(bool isValueToken) +{ +    char c; +    while (current(&c)) { +        if (c == ' ' || c == '\t' || c == '\r') { +            next(); +            continue; +        } + +        if (c == '#') { +            skipUntilNewLine(); +            continue; +        } + +        switch (c) { +        case '\n': +            next(); +            return Token(TokenType::END_OF_LINE); +        case '=': +            next(); +            return Token(TokenType::EQUAL); +        case '{': +            next(); +            return Token(TokenType::LBRACE); +        case '}': +            next(); +            return Token(TokenType::RBRACE); +        case '[': +            next(); +            return Token(TokenType::LBRACKET); +        case ']': +            next(); +            return Token(TokenType::RBRACKET); +        case ',': +            next(); +            return Token(TokenType::COMMA); +        case '.': +            next(); +            return Token(TokenType::DOT); +        case '\"': +            return nextStringDoubleQuote(); +        case '\'': +            return nextStringSingleQuote(); +        default: +            if (isValueToken) { +                return nextValue(); +            } else { +                return nextKey(); +            } +        } +    } + +    return Token(TokenType::END_OF_FILE); +} + +} // namespace internal + +// ---------------------------------------------------------------------- + +// static +inline const char* Value::typeToString(Value::Type type) +{ +    switch (type) { +    case NULL_TYPE:   return "null"; +    case BOOL_TYPE:   return "bool"; +    case INT_TYPE:    return "int"; +    case DOUBLE_TYPE: return "double"; +    case STRING_TYPE: return "string"; +    case TIME_TYPE:   return "time"; +    case ARRAY_TYPE:  return "array"; +    case TABLE_TYPE:  return "table"; +    default:          return "unknown"; +    } +} + +inline Value::Value(const Value& v) : +    type_(v.type_) +{ +    switch (v.type_) { +    case NULL_TYPE: null_ = v.null_; break; +    case BOOL_TYPE: bool_ = v.bool_; break; +    case INT_TYPE: int_ = v.int_; break; +    case DOUBLE_TYPE: double_ = v.double_; break; +    case STRING_TYPE: string_ = new std::string(*v.string_); break; +    case TIME_TYPE: time_ = new Time(*v.time_); break; +    case ARRAY_TYPE: array_ = new Array(*v.array_); break; +    case TABLE_TYPE: table_ = new Table(*v.table_); break; +    default: +        assert(false); +        type_ = NULL_TYPE; +        null_ = nullptr; +    } +} + +inline Value::Value(Value&& v) noexcept : +    type_(v.type_) +{ +    switch (v.type_) { +    case NULL_TYPE: null_ = v.null_; break; +    case BOOL_TYPE: bool_ = v.bool_; break; +    case INT_TYPE: int_ = v.int_; break; +    case DOUBLE_TYPE: double_ = v.double_; break; +    case STRING_TYPE: string_ = v.string_; break; +    case TIME_TYPE: time_ = v.time_; break; +    case ARRAY_TYPE: array_ = v.array_; break; +    case TABLE_TYPE: table_ = v.table_; break; +    default: +        assert(false); +        type_ = NULL_TYPE; +        null_ = nullptr; +    } + +    v.type_ = NULL_TYPE; +    v.null_ = nullptr; +} + +inline Value& Value::operator=(const Value& v) +{ +    if (this == &v) +        return *this; + +    this->~Value(); + +    type_ = v.type_; +    switch (v.type_) { +    case NULL_TYPE: null_ = v.null_; break; +    case BOOL_TYPE: bool_ = v.bool_; break; +    case INT_TYPE: int_ = v.int_; break; +    case DOUBLE_TYPE: double_ = v.double_; break; +    case STRING_TYPE: string_ = new std::string(*v.string_); break; +    case TIME_TYPE: time_ = new Time(*v.time_); break; +    case ARRAY_TYPE: array_ = new Array(*v.array_); break; +    case TABLE_TYPE: table_ = new Table(*v.table_); break; +    default: +        assert(false); +        type_ = NULL_TYPE; +        null_ = nullptr; +    } + +    return *this; +} + +inline Value& Value::operator=(Value&& v) noexcept +{ +    if (this == &v) +        return *this; + +    this->~Value(); + +    type_ = v.type_; +    switch (v.type_) { +    case NULL_TYPE: null_ = v.null_; break; +    case BOOL_TYPE: bool_ = v.bool_; break; +    case INT_TYPE: int_ = v.int_; break; +    case DOUBLE_TYPE: double_ = v.double_; break; +    case STRING_TYPE: string_ = v.string_; break; +    case TIME_TYPE: time_ = v.time_; break; +    case ARRAY_TYPE: array_ = v.array_; break; +    case TABLE_TYPE: table_ = v.table_; break; +    default: +        assert(false); +        type_ = NULL_TYPE; +        null_ = nullptr; +    } + +    v.type_ = NULL_TYPE; +    v.null_ = nullptr; +    return *this; +} + +inline Value::~Value() +{ +    switch (type_) { +    case STRING_TYPE: +        delete string_; +        break; +    case TIME_TYPE: +        delete time_; +        break; +    case ARRAY_TYPE: +        delete array_; +        break; +    case TABLE_TYPE: +        delete table_; +        break; +    default: +        break; +    } +} + +inline size_t Value::size() const +{ +    switch (type_) { +    case NULL_TYPE: +        return 0; +    case ARRAY_TYPE: +        return array_->size(); +    case TABLE_TYPE: +        return table_->size(); +    default: +        return 1; +    } +} + +inline bool Value::empty() const +{ +    return size() == 0; +} + +template<> struct Value::ValueConverter<bool> +{ +    bool is(const Value& v) { return v.type() == Value::BOOL_TYPE; } +    bool to(const Value& v) { v.assureType<bool>(); return v.bool_; } + +}; +template<> struct Value::ValueConverter<int64_t> +{ +    bool is(const Value& v) { return v.type() == Value::INT_TYPE; } +    int64_t to(const Value& v) { v.assureType<int64_t>(); return v.int_; } +}; +template<> struct Value::ValueConverter<int> +{ +    bool is(const Value& v) { return v.type() == Value::INT_TYPE; } +    int to(const Value& v) { v.assureType<int>(); return static_cast<int>(v.int_); } +}; +template<> struct Value::ValueConverter<double> +{ +    bool is(const Value& v) { return v.type() == Value::DOUBLE_TYPE; } +    double to(const Value& v) { v.assureType<double>(); return v.double_; } +}; +template<> struct Value::ValueConverter<std::string> +{ +    bool is(const Value& v) { return v.type() == Value::STRING_TYPE; } +    const std::string& to(const Value& v) { v.assureType<std::string>(); return *v.string_; } +}; +template<> struct Value::ValueConverter<Time> +{ +    bool is(const Value& v) { return v.type() == Value::TIME_TYPE; } +    const Time& to(const Value& v) { v.assureType<Time>(); return *v.time_; } +}; +template<> struct Value::ValueConverter<Array> +{ +    bool is(const Value& v) { return v.type() == Value::ARRAY_TYPE; } +    const Array& to(const Value& v) { v.assureType<Array>(); return *v.array_; } +}; +template<> struct Value::ValueConverter<Table> +{ +    bool is(const Value& v) { return v.type() == Value::TABLE_TYPE; } +    const Table& to(const Value& v) { v.assureType<Table>(); return *v.table_; } +}; + +template<typename T> +struct Value::ValueConverter<std::vector<T>> +{ +    bool is(const Value& v) +    { +        if (v.type() != Value::ARRAY_TYPE) +            return false; +        const Array& array = v.as<Array>(); +        if (array.empty()) +            return true; +        return array.front().is<T>(); +    } + +    std::vector<T> to(const Value& v) +    { +        const Array& array = v.as<Array>(); +        if (array.empty()) +            return std::vector<T>(); +        array.front().assureType<T>(); + +        std::vector<T> result; +        for (const auto& element : array) { +            result.push_back(element.as<T>()); +        } + +        return result; +    } +}; + +namespace internal { +template<typename T> inline const char* type_name(); +template<> inline const char* type_name<bool>() { return "bool"; } +template<> inline const char* type_name<int>() { return "int"; } +template<> inline const char* type_name<int64_t>() { return "int64_t"; } +template<> inline const char* type_name<double>() { return "double"; } +template<> inline const char* type_name<std::string>() { return "string"; } +template<> inline const char* type_name<toml::Time>() { return "time"; } +template<> inline const char* type_name<toml::Array>() { return "array"; } +template<> inline const char* type_name<toml::Table>() { return "table"; } +} // namespace internal + +template<typename T> +inline void Value::assureType() const +{ +    if (!is<T>()) +        failwith("type error: this value is ", typeToString(type_), " but ", internal::type_name<T>(), " was requested"); +} + +template<typename T> +inline bool Value::is() const +{ +    return ValueConverter<T>().is(*this); +} + +template<typename T> +inline typename call_traits<T>::return_type Value::as() const +{ +    return ValueConverter<T>().to(*this); +} + +inline bool Value::isNumber() const +{ +    return is<int>() || is<double>(); +} + +inline double Value::asNumber() const +{ +    if (is<int>()) +        return as<int>(); +    if (is<double>()) +        return as<double>(); + +    failwith("type error: this value is ", typeToString(type_), " but number is requested"); +} + +inline std::time_t Value::as_time_t() const +{ +    return std::chrono::system_clock::to_time_t(as<Time>()); +} + +inline std::string Value::spaces(int num) +{ +    if (num <= 0) +        return std::string(); + +    return std::string(num, ' '); +} + +inline std::string Value::escapeKey(const std::string& key) +{ +    auto position = std::find_if(key.begin(), key.end(), [](char c) -> bool { +        if (std::isalnum(c) || c == '_' || c == '-') +            return false; +        return true; +    }); + +    if (position != key.end()) { +        std::string escaped = "\""; +        for (const char& c : key) { +            if (c == '\\' || c  == '"') +                escaped += '\\'; +            escaped += c; +        } +        escaped += "\""; + +        return escaped; +    } + +    return key; +} + +inline void Value::write(std::ostream* os, const std::string& keyPrefix, int indent) const +{ +    switch (type_) { +    case NULL_TYPE: +        failwith("null type value is not a valid value"); +        break; +    case BOOL_TYPE: +        (*os) << (bool_ ? "true" : "false"); +        break; +    case INT_TYPE: +        (*os) << int_; +        break; +    case DOUBLE_TYPE: { +        (*os) << std::fixed << std::showpoint << double_; +        break; +    } +    case STRING_TYPE: +        (*os) << '"' << internal::escapeString(*string_) << '"'; +        break; +    case TIME_TYPE: { +        time_t tt = std::chrono::system_clock::to_time_t(*time_); +        std::tm t; +        gmtime_r(&tt, &t); +        char buf[256]; +        sprintf(buf, "%04d-%02d-%02dT%02d:%02d:%02dZ", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec); +        (*os) << buf; +        break; +    } +    case ARRAY_TYPE: +        (*os) << '['; +        for (size_t i = 0; i < array_->size(); ++i) { +            if (i) +                (*os) << ", "; +            (*array_)[i].write(os, keyPrefix, -1); +        } +        (*os) << ']'; +        break; +    case TABLE_TYPE: +        for (const auto& kv : *table_) { +            if (kv.second.is<Table>()) +                continue; +            if (kv.second.is<Array>() && kv.second.size() > 0 && kv.second.find(0)->is<Table>()) +                continue; +            (*os) << spaces(indent) << escapeKey(kv.first) << " = "; +            kv.second.write(os, keyPrefix, indent >= 0 ? indent + 1 : indent); +            (*os) << '\n'; +        } +        for (const auto& kv : *table_) { +            if (kv.second.is<Table>()) { +                std::string key(keyPrefix); +                if (!keyPrefix.empty()) +                    key += "."; +                key += escapeKey(kv.first); +                (*os) << "\n" << spaces(indent) << "[" << key << "]\n"; +                kv.second.write(os, key, indent >= 0 ? indent + 1 : indent); +            } +            if (kv.second.is<Array>() && kv.second.size() > 0 && kv.second.find(0)->is<Table>()) { +                std::string key(keyPrefix); +                if (!keyPrefix.empty()) +                    key += "."; +                key += escapeKey(kv.first); +                for (const auto& v : kv.second.as<Array>()) { +                    (*os) << "\n" << spaces(indent) << "[[" << key << "]]\n"; +                    v.write(os, key, indent >= 0 ? indent + 1 : indent); +                } +            } +        } +        break; +    default: +        failwith("writing unknown type"); +        break; +    } +} + +inline void Value::writeFormatted(std::ostream* os, FormatFlag flags) const +{ +    int indent = flags & FORMAT_INDENT ? 0 : -1; + +    write(os, std::string(), indent); +} + +// static +inline FormatFlag operator|(FormatFlag lhs, FormatFlag rhs) +{ +    return static_cast<FormatFlag>(static_cast<int>(lhs) | static_cast<int>(rhs)); +} + +// static +inline std::ostream& operator<<(std::ostream& os, const toml::Value& v) +{ +    v.write(&os); +    return os; +} + +// static +inline bool operator==(const Value& lhs, const Value& rhs) +{ +    if (lhs.type() != rhs.type()) +        return false; + +    switch (lhs.type()) { +    case Value::Type::NULL_TYPE: +        return true; +    case Value::Type::BOOL_TYPE: +        return lhs.bool_ == rhs.bool_; +    case Value::Type::INT_TYPE: +        return lhs.int_ == rhs.int_; +    case Value::Type::DOUBLE_TYPE: +        return lhs.double_ == rhs.double_; +    case Value::Type::STRING_TYPE: +        return *lhs.string_ == *rhs.string_; +    case Value::Type::TIME_TYPE: +        return *lhs.time_ == *rhs.time_; +    case Value::Type::ARRAY_TYPE: +        return *lhs.array_ == *rhs.array_; +    case Value::Type::TABLE_TYPE: +        return *lhs.table_ == *rhs.table_; +    default: +        failwith("unknown type"); +    } +} + +template<typename T> +inline typename call_traits<T>::return_type Value::get(const std::string& key) const +{ +    if (!is<Table>()) +        failwith("type must be table to do get(key)."); + +    const Value* obj = find(key); +    if (!obj) +        failwith("key ", key, " was not found."); + +    return obj->as<T>(); +} + +inline const Value* Value::find(const std::string& key) const +{ +    if (!is<Table>()) +        return nullptr; + +    std::istringstream ss(key); +    internal::Lexer lexer(ss); + +    const Value* current = this; +    while (true) { +        internal::Token t = lexer.nextKeyToken(); +        if (!(t.type() == internal::TokenType::IDENT || t.type() == internal::TokenType::STRING)) +            return nullptr; + +        std::string part = t.strValue(); +        t = lexer.nextKeyToken(); +        if (t.type() == internal::TokenType::DOT) { +            current = current->findChild(part); +            if (!current || !current->is<Table>()) +                return nullptr; +        } else if (t.type() == internal::TokenType::END_OF_FILE) { +            return current->findChild(part); +        } else { +            return nullptr; +        } +    } +} + +inline Value* Value::find(const std::string& key) +{ +    return const_cast<Value*>(const_cast<const Value*>(this)->find(key)); +} + +inline bool Value::merge(const toml::Value& v) +{ +    if (this == &v) +        return true; +    if (!is<Table>() || !v.is<Table>()) +        return false; + +    for (const auto& kv : *v.table_) { +        if (Value* tmp = find(kv.first)) { +            // If both are table, we merge them. +            if (tmp->is<Table>() && kv.second.is<Table>()) { +                if (!tmp->merge(kv.second)) +                    return false; +            } else { +                setChild(kv.first, kv.second); +            } +        } else { +            setChild(kv.first, kv.second); +        } +    } + +    return true; +} + +inline Value* Value::set(const std::string& key, const Value& v) +{ +    Value* result = ensureValue(key); +    *result = v; +    return result; +} + +inline Value* Value::setChild(const std::string& key, const Value& v) +{ +    if (!valid()) +        *this = Value((Table())); + +    if (!is<Table>()) +        failwith("type must be table to do set(key, v)."); + +    (*table_)[key] = v; +    return &(*table_)[key]; +} + +inline Value* Value::setChild(const std::string& key, Value&& v) +{ +    if (!valid()) +        *this = Value((Table())); + +    if (!is<Table>()) +        failwith("type must be table to do set(key, v)."); + +    (*table_)[key] = std::move(v); +    return &(*table_)[key]; +} + +inline bool Value::erase(const std::string& key) +{ +    if (!is<Table>()) +        return false; + +    std::istringstream ss(key); +    internal::Lexer lexer(ss); + +    Value* current = this; +    while (true) { +        internal::Token t = lexer.nextKeyToken(); +        if (!(t.type() == internal::TokenType::IDENT || t.type() == internal::TokenType::STRING)) +            return false; + +        std::string part = t.strValue(); +        t = lexer.nextKeyToken(); +        if (t.type() == internal::TokenType::DOT) { +            current = current->findChild(part); +            if (!current || !current->is<Table>()) +                return false; +        } else if (t.type() == internal::TokenType::END_OF_FILE) { +            return current->eraseChild(part); +        } else { +            return false; +        } +    } +} + +inline bool Value::eraseChild(const std::string& key) +{ +    if (!is<Table>()) +        failwith("type must be table to do erase(key)."); + +    return table_->erase(key) > 0; +} + +inline Value& Value::operator[](const std::string& key) +{ +    if (!valid()) +        *this = Value((Table())); + +    if (Value* v = findChild(key)) +        return *v; + +    return *setChild(key, Value()); +} + +template<typename T> +inline typename call_traits<T>::return_type Value::get(size_t index) const +{ +    if (!is<Array>()) +        failwith("type must be array to do get(index)."); + +    if (array_->size() <= index) +        failwith("index out of bound"); + +    return (*array_)[index].as<T>(); +} + +inline const Value* Value::find(size_t index) const +{ +    if (!is<Array>()) +        return nullptr; +    if (index < array_->size()) +        return &(*array_)[index]; +    return nullptr; +} + +inline Value* Value::find(size_t index) +{ +    return const_cast<Value*>(const_cast<const Value*>(this)->find(index)); +} + +inline Value* Value::push(const Value& v) +{ +    if (!valid()) +        *this = Value((Array())); +    else if (!is<Array>()) +        failwith("type must be array to do push(Value)."); + +    array_->push_back(v); +    return &array_->back(); +} + +inline Value* Value::push(Value&& v) +{ +    if (!valid()) +        *this = Value((Array())); +    else if (!is<Array>()) +        failwith("type must be array to do push(Value)."); + +    array_->push_back(std::move(v)); +    return &array_->back(); +} + +inline Value* Value::ensureValue(const std::string& key) +{ +    if (!valid()) +        *this = Value((Table())); +    if (!is<Table>()) { +        failwith("encountered non table value"); +    } + +    std::istringstream ss(key); +    internal::Lexer lexer(ss); + +    Value* current = this; +    while (true) { +        internal::Token t = lexer.nextKeyToken(); +        if (!(t.type() == internal::TokenType::IDENT || t.type() == internal::TokenType::STRING)) { +            failwith("invalid key"); +        } + +        std::string part = t.strValue(); +        t = lexer.nextKeyToken(); +        if (t.type() == internal::TokenType::DOT) { +            if (Value* candidate = current->findChild(part)) { +                if (!candidate->is<Table>()) +                    failwith("encountered non table value"); + +                current = candidate; +            } else { +                current = current->setChild(part, Table()); +            } +        } else if (t.type() == internal::TokenType::END_OF_FILE) { +            if (Value* v = current->findChild(part)) +                return v; +            return current->setChild(part, Value()); +        } else { +            failwith("invalid key"); +        } +    } +} + +inline Value* Value::findChild(const std::string& key) +{ +    assert(is<Table>()); + +    auto it = table_->find(key); +    if (it == table_->end()) +        return nullptr; + +    return &it->second; +} + +inline const Value* Value::findChild(const std::string& key) const +{ +    assert(is<Table>()); + +    auto it = table_->find(key); +    if (it == table_->end()) +        return nullptr; + +    return &it->second; +} + +// ---------------------------------------------------------------------- + +namespace internal { + +inline void Parser::skipForKey() +{ +    while (token().type() == TokenType::END_OF_LINE) +        nextKey(); +} + +inline void Parser::skipForValue() +{ +    while (token().type() == TokenType::END_OF_LINE) +        nextValue(); +} + +inline bool Parser::consumeForKey(TokenType type) +{ +    if (token().type() == type) { +        nextKey(); +        return true; +    } + +    return false; +} + +inline bool Parser::consumeForValue(TokenType type) +{ +    if (token().type() == type) { +        nextValue(); +        return true; +    } + +    return false; +} + +inline bool Parser::consumeEOLorEOFForKey() +{ +    if (token().type() == TokenType::END_OF_LINE || token().type() == TokenType::END_OF_FILE) { +        nextKey(); +        return true; +    } + +    return false; +} + +inline void Parser::addError(const std::string& reason) +{ +    if (!errorReason_.empty()) +        return; + +    std::stringstream ss; +    ss << "Error: line " << lexer_.lineNo() << ": " << reason; +    errorReason_ = ss.str(); +} + +inline const std::string& Parser::errorReason() +{ +    return errorReason_; +} + +inline Value Parser::parse() +{ +    Value root((Table())); +    Value* currentValue = &root; + +    while (true) { +        skipForKey(); +        if (token().type() == TokenType::END_OF_FILE) +            break; +        if (token().type() == TokenType::LBRACKET) { +            currentValue = parseGroupKey(&root); +            if (!currentValue) { +                addError("error when parsing group key"); +                return Value(); +            } +            continue; +        } + +        if (!parseKeyValue(currentValue)) { +            addError("error when parsing key Value"); +            return Value(); +        } +    } +    return root; +} + +inline Value* Parser::parseGroupKey(Value* root) +{ +    if (!consumeForKey(TokenType::LBRACKET)) +        return nullptr; + +    bool isArray = false; +    if (token().type() == TokenType::LBRACKET) { +        nextKey(); +        isArray = true; +    } + +    Value* currentValue = root; +    while (true) { +        if (token().type() != TokenType::IDENT && token().type() != TokenType::STRING) +            return nullptr; + +        std::string key = token().strValue(); +        nextKey(); + +        if (token().type() == TokenType::DOT) { +            nextKey(); +            if (Value* candidate = currentValue->findChild(key)) { +                if (candidate->is<Array>() && candidate->size() > 0) +                    candidate = candidate->find(candidate->size() - 1); +                if (!candidate->is<Table>()) +                    return nullptr; +                currentValue = candidate; +            } else { +                currentValue = currentValue->setChild(key, Table()); +            } +            continue; +        } + +        if (token().type() == TokenType::RBRACKET) { +            nextKey(); +            if (Value* candidate = currentValue->findChild(key)) { +                if (isArray) { +                    if (!candidate->is<Array>()) +                        return nullptr; +                    currentValue = candidate->push(Table()); +                } else { +                    if (candidate->is<Array>() && candidate->size() > 0) +                        candidate = candidate->find(candidate->size() - 1); +                    if (!candidate->is<Table>()) +                        return nullptr; +                    currentValue = candidate; +                } +            } else { +                if (isArray) { +                    currentValue = currentValue->setChild(key, Array()); +                    currentValue = currentValue->push(Table()); +                } else { +                    currentValue = currentValue->setChild(key, Table()); +                } +            } +            break; +        } + +        return nullptr; +    } + +    if (isArray) { +        if (!consumeForKey(TokenType::RBRACKET)) +            return nullptr; +    } + +    if (!consumeEOLorEOFForKey()) +        return nullptr; + +    return currentValue; +} + +inline bool Parser::parseKeyValue(Value* current) +{ +    std::string key; +    if (!parseKey(&key)) { +        addError("parse key failed"); +        return false; +    } +    if (!consumeForValue(TokenType::EQUAL)) { +        addError("no equal?"); +        return false; +    } + +    Value v; +    if (!parseValue(&v)) +        return false; +    if (!consumeEOLorEOFForKey()) +        return false; + +    if (current->has(key)) { +        addError("Multiple same key: " + key); +        return false; +    } + +    current->setChild(key, std::move(v)); +    return true; +} + +inline bool Parser::parseKey(std::string* key) +{ +    key->clear(); + +    if (token().type() == TokenType::IDENT || token().type() == TokenType::STRING) { +        *key = token().strValue(); +        nextValue(); +        return true; +    } + +    return false; +} + +inline bool Parser::parseValue(Value* v) +{ +    switch (token().type()) { +    case TokenType::STRING: +    case TokenType::MULTILINE_STRING: +        *v = token().strValue(); +        nextValue(); +        return true; +    case TokenType::LBRACKET: +        return parseArray(v); +    case TokenType::LBRACE: +        return parseInlineTable(v); +    case TokenType::BOOL: +        *v = token().boolValue(); +        nextValue(); +        return true; +    case TokenType::INT: +        *v = token().intValue(); +        nextValue(); +        return true; +    case TokenType::DOUBLE: +        *v = token().doubleValue(); +        nextValue(); +        return true; +    case TokenType::TIME: +        *v = token().timeValue(); +        nextValue(); +        return true; +    case TokenType::ERROR: +        addError(token().strValue()); +        return false; +    default: +        addError("unexpected token"); +        return false; +    } +} + +inline bool Parser::parseBool(Value* v) +{ +    if (token().strValue() == "true") { +        nextValue(); +        *v = true; +        return true; +    } + +    if (token().strValue() == "false") { +        nextValue(); +        *v = false; +        return true; +    } + +    return false; +} + +inline bool Parser::parseArray(Value* v) +{ +    if (!consumeForValue(TokenType::LBRACKET)) +        return false; + +    Array a; +    while (true) { +        skipForValue(); + +        if (token().type() == TokenType::RBRACKET) +            break; + +        skipForValue(); +        Value x; +        if (!parseValue(&x)) +            return false; + +        if (!a.empty()) { +            if (a.front().type() != x.type()) { +                addError("type check failed"); +                return false; +            } +        } + +        a.push_back(std::move(x)); +        skipForValue(); +        if (token().type() == TokenType::RBRACKET) +            break; +        if (token().type() == TokenType::COMMA) +            nextValue(); +    } + +    if (!consumeForValue(TokenType::RBRACKET)) +        return false; +    *v = std::move(a); +    return true; +} + +inline bool Parser::parseInlineTable(Value* value) +{ +    // For inline table, next is KEY, so use consumeForKey here. +    if (!consumeForKey(TokenType::LBRACE)) +        return false; + +    Value t((Table())); +    bool first = true; +    while (true) { +        if (token().type() == TokenType::RBRACE) { +            break; +        } + +        if (!first) { +            if (token().type() != TokenType::COMMA) { +                addError("inline table didn't have ',' for delimiter?"); +                return false; +            } +            nextKey(); +        } +        first = false; + +        std::string key; +        if (!parseKey(&key)) +            return false; +        if (!consumeForValue(TokenType::EQUAL)) +            return false; +        Value v; +        if (!parseValue(&v)) +            return false; + +        if (t.has(key)) { +            addError("inline table has multiple same keys: key=" + key); +            return false; +        } + +        t.set(key, v); +    } + +    if (!consumeForValue(TokenType::RBRACE)) +        return false; +    *value = std::move(t); +    return true; +} + +} // namespace internal +} // namespace toml + +#endif // TINYTOML_H_ diff --git a/src/browser.cpp b/src/browser.cpp index a1c097a..f709e1e 100644 --- a/src/browser.cpp +++ b/src/browser.cpp @@ -20,7 +20,6 @@  #include "browser.h"  #include "mainwindow.h" -#include "settings.h"  #include <QtWebEngine>  #include <QMessageBox> @@ -47,12 +46,11 @@ Browser::~Browser()   */  void Browser::firstRun()  { -    Settings settings; -    if(settings.allKeys().isEmpty()) { +    if(m_settings->isEmpty()) {          // There are no keys in the settings          QMessageBox::information(0,                                   tr("Configuration is empty"), -                                 tr("The configuration file <i>%1</i> is empty. Using default values").arg(settings.staticFilePath())); +                                 tr("The configuration file <i>%1</i> is empty. Using default values").arg(m_settings->filePath()));      }  } @@ -61,10 +59,8 @@ void Browser::firstRun()   */  bool Browser::preLaunch(QStringList urls)  { -    Settings settings; - -    if(settings.value("browser/singleInstance", true).toBool()) { -        QString serverName = settings.value("browser/localSocket", "smolbote-singlelock").toString(); +    if(m_settings->value("browser.singleInstance", true).toBool()) { +        QString serverName = m_settings->value("browser.localSocket", "smolbote-singlelock").toString();          // Check for other running instance          QLocalSocket socket; @@ -105,6 +101,11 @@ Browser *Browser::instance()      return static_cast<Browser *>(QCoreApplication::instance());  } +Settings *Browser::settings() +{ +    return m_settings; +} +  BookmarksDialog *Browser::bookmarks()  {      return m_bookmarksManager; @@ -119,10 +120,10 @@ void Browser::setConfigPath(const QString &path)  {      if(path.isEmpty()) {          // set default config path -        Settings::setFilePath(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + "/config.ini"); +        m_settings = new Settings(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + "/config.toml");      } else {          // set custom config path -        Settings::setFilePath(path); +        m_settings = new Settings(path);      }  } diff --git a/src/browser.h b/src/browser.h index 3d3b297..0a6702f 100644 --- a/src/browser.h +++ b/src/browser.h @@ -26,6 +26,9 @@  #include "forms/bookmarksdialog.h"  #include "forms/downloaddialog.h"  #include <QLocalServer> +#include "settings.h" + +#define sSettings Browser::instance()->settings()  class MainWindow;  class Browser : public QApplication @@ -41,6 +44,7 @@ public:      static Browser *instance(); +    Settings *settings();      BookmarksDialog *bookmarks();      DownloadDialog *downloads(); @@ -55,6 +59,7 @@ private slots:      void handleNewConnection();  private: +    Settings *m_settings;      QLocalServer *m_localServer;      QVector<MainWindow*> m_windows;      BookmarksDialog *m_bookmarksManager; diff --git a/src/forms/blockerdialog.cpp b/src/forms/blockerdialog.cpp index fa613bd..9c6bd0a 100644 --- a/src/forms/blockerdialog.cpp +++ b/src/forms/blockerdialog.cpp @@ -21,7 +21,7 @@  #include "blockerdialog.h"  #include "ui_blockerdialog.h" -#include "settings.h" +#include "browser.h"  #include <QLabel>  #include <QListWidget> @@ -29,11 +29,10 @@ BlockerDialog::BlockerDialog(QWidget *parent) :      QDialog(parent),      ui(new Ui::UrlInterceptorDialog)  { -    Settings settings;      ui->setupUi(this);      m_subscription = new BlockerSubscription(this); -    QString sublocation = settings.value("blocker/subscription").toString(); +    QString sublocation = sSettings->value("blocker.path").toString();      if(!sublocation.isEmpty()) {          m_subscription->loadFromFile(sublocation);      } diff --git a/src/forms/downloaddialog.cpp b/src/forms/downloaddialog.cpp index 950fa96..d182076 100644 --- a/src/forms/downloaddialog.cpp +++ b/src/forms/downloaddialog.cpp @@ -27,7 +27,7 @@  #include <QListWidget>  #include <QLabel>  #include "webengine/downloaditemform.h" -#include "settings.h" +#include "browser.h"  DownloadDialog::DownloadDialog(QWidget *parent) :      QDialog(parent), @@ -46,9 +46,7 @@ DownloadDialog::~DownloadDialog()  void DownloadDialog::addDownload(QWebEngineDownloadItem *item)  { -    Settings settings; - -    QString filepath = QFileDialog::getSaveFileName(this, tr("Save"), settings.value("downloads/path").toString() + "/" + QFileInfo(item->path()).fileName()); +    QString filepath = QFileDialog::getSaveFileName(this, tr("Save"), sSettings->value("downloads.path").toString() + "/" + QFileInfo(item->path()).fileName());      if(filepath.isEmpty()) {          // user cancelled the save dialog diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 32c282d..6870a65 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -20,7 +20,6 @@  #include "mainwindow.h"  #include "ui_mainwindow.h" -#include "settings.h"  #include <QMenu>  #include <QMenuBar>  #include <QCloseEvent> @@ -42,13 +41,11 @@ MainWindow::MainWindow(QUrl defaultUrl, QWidget *parent) :      urlLineEdit(new UrlLineEdit(navigationToolBar)),      progressBar(new LoadingBar(this))  { -    Settings settings; -      // Load profile and connect its signals -    loadProfile(settings.value("profile").toString()); +    loadProfile(sSettings->value("general.profile").toString());      ui->setupUi(this); -    resize(settings.value("window/width", 800).toInt(), settings.value("window/height", 600).toInt()); +    resize(sSettings->value("window.width", 800).toInt(), sSettings->value("window.height", 600).toInt());      // Browser menu      QMenu *browserMenu = new QMenu(qApp->applicationName(), ui->menuBar); @@ -64,9 +61,9 @@ MainWindow::MainWindow(QUrl defaultUrl, QWidget *parent) :      QMenu *toolsMenu = new QMenu(tr("Tools"), ui->menuBar);      ui->menuBar->addMenu(toolsMenu);      QAction *downloadsAction = toolsMenu->addAction(tr("Downloads"), Browser::instance()->downloads(), SLOT(show())); -    downloadsAction->setShortcut(QKeySequence::fromString(settings.value("downloads/dialogShortcut").toString())); +    downloadsAction->setShortcut(QKeySequence::fromString(sSettings->value("downloads.dialogShortcut").toString()));      QAction *bookmarksAction = toolsMenu->addAction(tr("Bookmarks"), Browser::instance()->bookmarks(), SLOT(show())); -    bookmarksAction->setShortcut(QKeySequence(settings.value("bookmarks/dialogShortcut").toString())); +    bookmarksAction->setShortcut(QKeySequence(sSettings->value("bookmarks.dialogShortcut").toString()));      toolsMenu->addSeparator();      toolsMenu->addAction(tr("Blocker"), blocklistManager, SLOT(show())); @@ -78,10 +75,10 @@ MainWindow::MainWindow(QUrl defaultUrl, QWidget *parent) :      //profileMenu->addAction(tr("Settings"));      //profileMenu->addAction(tr("Cookies")); -    navigationToolBar->setMovable(settings.value("ui/navtoolbarMovable", true).toBool()); +    navigationToolBar->setMovable(sSettings->value("ui.navtoolbarMovable", true).toBool());      this->addToolBar(Qt::TopToolBarArea, navigationToolBar);      this->addToolBarBreak(Qt::TopToolBarArea); -    tabToolBar->setMovable(settings.value("ui/tabtoolbarMovable", true).toBool()); +    tabToolBar->setMovable(sSettings->value("ui.tabtoolbarMovable", true).toBool());      this->addToolBar(Qt::TopToolBarArea, tabToolBar);      navigationToolBar->addWidget(urlLineEdit); @@ -95,14 +92,14 @@ MainWindow::MainWindow(QUrl defaultUrl, QWidget *parent) :      // shortcuts      QAction *focusAddressAction = new QAction(this); -    focusAddressAction->setShortcut(QKeySequence::fromString(settings.value("shortcuts/focusAddress").toString())); +    focusAddressAction->setShortcut(QKeySequence::fromString(sSettings->value("shortcuts.focusAddress").toString()));      connect(focusAddressAction, SIGNAL(triggered(bool)), this, SLOT(focusAddress()));      addAction(focusAddressAction);      if(!defaultUrl.isEmpty()) {          addNewTab(defaultUrl);      } else { -        addNewTab(settings.value("homepage", QUrl("about:blank")).toUrl()); +        addNewTab(sSettings->value("general.homepage", QUrl("about:blank")).toUrl());      }  } @@ -124,8 +121,7 @@ void MainWindow::addNewTab(const QUrl &url)      if(!url.isEmpty()) {          tabBar->addTab(m_profile, url);      } else { -        Settings settings; -        tabBar->addTab(m_profile, settings.value("newtab").toUrl()); +        tabBar->addTab(m_profile, sSettings->value("general.newtab").toUrl());      }  } @@ -155,7 +151,7 @@ void MainWindow::about()                         "<p>This program comes with ABSOLUTELY NO WARRANTY. "                         "This is free software, and you are welcome to redistribute it under the conditions set by the GNU GPLv3.</p>"                         "<p>Configuration lives in: %2</p>") -                       .arg(qApp->applicationVersion()).arg(Settings::staticFilePath())); +                       .arg(qApp->applicationVersion()).arg(sSettings->filePath()));  }  void MainWindow::loadProfile(const QString &name) @@ -226,8 +222,7 @@ void MainWindow::handleUrlChanged()  void MainWindow::handleTitleUpdated(const QString &title)  { -    Settings settings; -    setWindowTitle(settings.value("window/title").toString().replace("$title", title).replace("$profile", m_profile->storageName())); +    setWindowTitle(sSettings->value("window.title").toString().replace("$title", title).replace("$profile", m_profile->storageName()));  }  void MainWindow::execProfileEditor() diff --git a/src/settings.cpp b/src/settings.cpp index f030dfd..edbecc0 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -21,41 +21,85 @@  #include "settings.h"  #include <QStandardPaths> -QString Settings::_path = QString(""); +#include <fstream> -Settings::Settings(QObject *parent) : -    QSettings(_path, QSettings::IniFormat, parent) +Settings::Settings(const QString &configFile)  { -    setIniCodec("UTF-8"); +    path = configFile; +    std::ifstream ifs(configFile.toStdString().c_str()); +    toml::ParseResult pr = toml::parse(ifs); + +    if(!pr.valid()) { +        qWarning("Invalid configuration: %s", pr.errorReason.c_str()); +        return; +    } + +    v = pr.value;  } -void Settings::setFilePath(const QString &path) +Settings::~Settings()  { -    qDebug("config=[%s]", qUtf8Printable(path)); -    _path = path;  } -QString Settings::staticFilePath() +QString Settings::filePath() const  { -    return _path; +    return path; +} + +bool Settings::isEmpty() const +{ +    return v.empty(); +} + +bool Settings::contains(const QString &key) +{ +    const toml::Value *x = v.find(key.toStdString()); +    if(x) { +        return true; +    } else { +        return false; +    }  }  QVariant Settings::value(const QString &key, const QVariant &defaultValue) const  { -    // get the actual value -    QString value = QSettings::value(key, defaultValue).toString(); - -    // check if there is a reference -    QRegularExpressionMatch referenceMatch = referencePattern.match(value); -    if(referenceMatch.hasMatch()) { -        QString pattern = referenceMatch.capturedTexts().first(); -        value.replace(pattern, this->value(pattern.mid(1, pattern.length()-2)).toString()); +    const toml::Value *x = v.find(key.toStdString()); + +    // check if value exists +    if(!x) { +        return defaultValue; +    } + +    QVariant r; +    switch (x->type()) { +    case toml::Value::NULL_TYPE: +        r = defaultValue; +        break; + +    case toml::Value::BOOL_TYPE: +        r = QVariant(x->as<bool>()); +        break; + +    case toml::Value::INT_TYPE: +        r = QVariant(x->as<int>()); +        break; + +    case toml::Value::STRING_TYPE: +        r = QVariant(QString::fromStdString(x->as<std::string>())); +        break; + +    default: +        qWarning("Unhandled type in configuration"); +        r = defaultValue; +        break;      }      // check if key is a path, in which case replace '~' with the home location      if(key.endsWith(QLatin1String("path"), Qt::CaseInsensitive)) { +        QString value = r.toString();          value.replace('~', QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); +        r = QVariant(value);      } -    return QVariant(value); +    return r;  } diff --git a/src/settings.h b/src/settings.h index 01eff6b..57d868c 100644 --- a/src/settings.h +++ b/src/settings.h @@ -21,28 +21,23 @@  #ifndef SETTINGS_H  #define SETTINGS_H -#include <QSettings> -#include <QRegularExpression> +#include <toml/toml.h> +#include <QVariant> -class Settings : public QSettings +class Settings  { -    Q_OBJECT  public: -    explicit Settings(QObject *parent = 0); - -    static void setFilePath(const QString &path); -    static QString staticFilePath(); +    Settings(const QString &configFile); +    ~Settings(); +    QString filePath() const; +    bool isEmpty() const; +    bool contains(const QString &key);      QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const; -    //void setValue(const QString &key, const QVariant &value); - -signals: - -public slots:  private: -    static QString _path; -    const QRegularExpression referencePattern = QRegularExpression("~[\\w/]+~"); +    toml::Value v; +    QString path;  };  #endif // SETTINGS_H diff --git a/src/widgets/webviewtabbar.cpp b/src/widgets/webviewtabbar.cpp index db44bc9..745a7a5 100644 --- a/src/widgets/webviewtabbar.cpp +++ b/src/widgets/webviewtabbar.cpp @@ -19,7 +19,7 @@   ******************************************************************************/  #include "webviewtabbar.h" -#include "settings.h" +#include "browser.h"  #include <QAction>  WebViewTabBar::WebViewTabBar(QWidget *parent) : @@ -32,26 +32,25 @@ WebViewTabBar::WebViewTabBar(QWidget *parent) :      connect(this, SIGNAL(currentChanged(int)), this, SLOT(handleCurrentChanged(int)));      connect(this, SIGNAL(tabMoved(int,int)), this, SLOT(updateVectorArrangement(int,int))); -    Settings settings; -    if(settings.contains("shortcuts/tabClose")) { +    if(sSettings->contains("shortcuts.tabClose")) {          QAction *tabCloseAction = new QAction(this); -        tabCloseAction->setShortcut(QKeySequence::fromString(settings.value("shortcuts/tabClose").toString())); +        tabCloseAction->setShortcut(QKeySequence::fromString(sSettings->value("shortcuts.tabClose").toString()));          connect(tabCloseAction, &QAction::triggered, [this]() {              this->handleTabClose(currentIndex());          });          addAction(tabCloseAction);      } -    if(settings.contains("shortcuts/tabLeft")) { +    if(sSettings->contains("shortcuts.tabLeft")) {          QAction *tabLeftAction = new QAction(this); -        tabLeftAction->setShortcut(QKeySequence::fromString(settings.value("shortcuts/tabLeft").toString())); +        tabLeftAction->setShortcut(QKeySequence::fromString(sSettings->value("shortcuts.tabLeft").toString()));          connect(tabLeftAction, &QAction::triggered, [this]() {              this->setCurrentIndex(currentIndex()-1);          });          addAction(tabLeftAction);      } -    if(settings.contains("shortcuts/tabRight")) { +    if(sSettings->contains("shortcuts.tabRight")) {          QAction *tabRightAction = new QAction(this); -        tabRightAction->setShortcut(QKeySequence::fromString(settings.value("shortcuts/tabRight").toString())); +        tabRightAction->setShortcut(QKeySequence::fromString(sSettings->value("shortcuts.tabRight").toString()));          connect(tabRightAction, &QAction::triggered, [this]() {              this->setCurrentIndex(currentIndex()+1);          }); | 
