marzer / tomlplusplus

Header-only TOML config file parser and serializer for C++17.
https://marzer.github.io/tomlplusplus/
MIT License
1.47k stars 141 forks source link

File that was written with toml++ can't be parsed #207

Closed Raildex closed 9 months ago

Raildex commented 9 months ago

settings.toml.txt

Environment

toml v 3.3.0

Compiler:
Visual Studio 2022

C++ standard mode:
c++latest

Target arch:
x64

Describe the bug

File that was generated with toml++ results in an error when trying to parse it.

Steps to reproduce (or a small repro code sample)

#include <toml++/toml.h>
#include <fstream>
#include <ostream>
#include <iosfwd>
namespace raid {

    enum class game_language : unsigned {
        english = 0,
        german = 1,
        italian = 2,
        polish = 3,
        french = 4,
        spanish = 5,
        japanese = 6,
        chinese = 7,
        russian = 8,
        dansk = 9,
        dutch = 10,
        finnish = 11,
        portuguese = 12
    };
    const char* get_language_string(const game_language lang) noexcept {
        switch(lang) {
            case game_language::german:
                return "german";
            case game_language::italian:
                return "italian";
            case game_language::polish:
                return "polish";
            case game_language::french:
                return "french";
            case game_language::spanish:
                return "spanish";
            case game_language::japanese:
                return "japanese";
            case game_language::chinese:
                return "chinese";
            case game_language::russian:
                return "russian";
            case game_language::dansk:
                return "dansk";
            case game_language::dutch:
                return "dutch";
            case game_language::finnish:
                return "finnish";
            case game_language::portuguese:
                return "portuguese";
            default:
                return "english";
        }
    }

    game_language get_language_from_string(const std::string_view s) noexcept {
        if(s == "english") {
            return game_language::english;
        }
        if(s == "german") {
            return game_language::german;
        }
        if(s == "french") {
            return game_language::french;
        }
        if(s == "italian") {
            return game_language::italian;
        }
        if(s == "dutch") {
            return game_language::dutch;
        }
        if(s == "russian") {
            return game_language::russian;
        }
        if(s == "chinese") {
            return game_language::chinese;
        }
        if(s == "japanese") {
            return game_language::japanese;
        }
        if(s == "polish") {
            return game_language::polish;
        }
        if(s == "portuguese") {
            return game_language::portuguese;
        }
        if(s == "spanish") {
            return game_language::spanish;
        }
        if(s == "dansk") {
            return game_language::dansk;
        }
        return game_language::english;
    }
} // namespace raid

namespace raid {

    enum class microphone_mode : unsigned char {
        camera,
        player,
        player_head,
    };

    enum class settings_quality : unsigned char {
        off = 0,
        low = 1,
        medium = 2,
        high = 3,
        highest = 4,
    };

    enum class control_method : unsigned char { keyboard, game_controller };

    struct graphics_settings {
        bool windowed;
        unsigned resolution_width;
        unsigned resolution_height;
        unsigned refresh_rate;
        settings_quality lighting;
        settings_quality animation;
        settings_quality texture;
        settings_quality shadow;
        settings_quality fog;
        settings_quality particle;
        settings_quality post_processing;
        settings_quality reflection;
    };

    struct audio_settings {
        float music_volume;
        float effect_volume;
        float dialogue_volume;
        settings_quality quality;
        settings_quality reverb;
        microphone_mode mic_mode;
    };

    struct control_settings {
        unsigned char method;
        unsigned short layout[32];
        unsigned short pad_layout[32];
    };

    struct settings {
        graphics_settings graphics;
        audio_settings audio;
        control_settings controls;
        game_language lang;
    };

} // namespace raid

namespace raid {

    settings deserialize_settings(std::istream& stream) {
        settings s{};
        toml::parse_result result = toml::parse(stream);
        if(!result.succeeded()) { // error here
            throw -1;
        }
        const auto& table = result.table();
        const auto& graphics = table["graphics"];
        s.graphics.windowed = graphics["windowed"].value_or<bool>(true);
        s.graphics.animation =  (settings_quality)graphics["animation"].value_or<int>(int(settings_quality::medium));
        s.graphics.fog =  (settings_quality)graphics["fog"].value_or<int>(int(settings_quality::medium));
        s.graphics.lighting =  (settings_quality)graphics["lighting"].value_or<int>(int(settings_quality::medium));
        s.graphics.particle =  (settings_quality)graphics["particle"].value_or<int>(int(settings_quality::medium));
        s.graphics.post_processing =  (settings_quality)graphics["post-processing"].value_or<int>(int(settings_quality::medium));
        s.graphics.reflection =  (settings_quality)graphics["reflection"].value_or<int>(int(settings_quality::medium));
        s.graphics.texture =  (settings_quality)graphics["texture"].value_or<int>(int(settings_quality::medium));
        s.graphics.shadow =  (settings_quality)graphics["shadow"].value_or<int>(int(settings_quality::medium));
        s.graphics.refresh_rate =  int(graphics["refresh"].value_or(30));
        s.graphics.resolution_width =  int(graphics["width"].value_or(800));
        s.graphics.resolution_height =  int(graphics["height"].value_or(450));
        const auto& audio = table["audio"];
        s.audio.dialogue_volume = audio["dialogue"].value_or(1.0f);
        s.audio.music_volume = audio["music"].value_or(1.0f);
        s.audio.effect_volume = audio["effect"].value_or(1.0f);
        s.audio.mic_mode = (microphone_mode)audio["mic-mode"].value_or<int>(int(microphone_mode::camera));
        s.audio.quality = (settings_quality)audio["quality"].value_or<int>(int(settings_quality::medium));
        s.audio.reverb = (settings_quality)audio["reverb"].value_or<int>(int(settings_quality::medium));
        s.lang = get_language_from_string(table["language"].value_or<std::string>(get_language_string(game_language::english)));
        const auto& controls = table["controls"];
        s.controls.method = controls["control-method"].value_or(0);
        auto* layout_arr = controls["keyboard-layout"].as_array();
        if(!layout_arr) {
            throw -1;
        }
        if(layout_arr->size() < std::size(s.controls.layout)) {
            throw -1;
        }
        for(int i=0; i < std::size(s.controls.layout);i++) {

            s.controls.layout[i] = layout_arr->get(i)->value_or((unsigned short)-1);
        }
        layout_arr = controls["pad-layout"].as_array();
        if(!layout_arr) {
            throw -1;
        }
        if(layout_arr->size() < std::size(s.controls.layout)) {
            throw -1;
        }
        for(int i=0; i < std::size(s.controls.layout);i++) {
            s.controls.pad_layout[i] = layout_arr->get(i)->value_or((unsigned short)-1);
        }
        return s;
    }

    void serialize_settings(const settings& s, std::ostream& stream) {

        auto keyboard_layout_arr = toml::array{};
        for(int i = 0; i < std::size(s.controls.layout);i++) {
            keyboard_layout_arr.push_back(s.controls.layout[i]);
        }

        auto pad_layout_arr = toml::array{};
        for(int i = 0; i < std::size(s.controls.pad_layout);i++) {
            pad_layout_arr.push_back(s.controls.pad_layout[i]);
        }
        toml::table tbl {
            {"graphics", toml::table{
                {"windowed",s.graphics.windowed},
                {"animation",int(s.graphics.animation)},
                {"fog",int(s.graphics.fog)},
                {"lighting",int(s.graphics.lighting)},
                {"particle",int(s.graphics.particle)},
                {"post-processing",int(s.graphics.post_processing)},
                {"reflection",int(s.graphics.reflection)},
                {"texture",int(s.graphics.texture)},
                {"shadow",int(s.graphics.shadow)},
                {"refresh",int(s.graphics.refresh_rate)},
                {"width",int(s.graphics.resolution_width)},
                {"height",int(s.graphics.animation)},
            }},
            {"audio", toml::table{
                {"dialogue-volume",s.audio.dialogue_volume},
                {"music-volume",s.audio.music_volume},
                {"effect-volume",s.audio.effect_volume},
                {"quality",int(s.audio.quality)},
                {"mic-mode",int(s.audio.mic_mode)},
                {"reverb-quality",int(s.audio.reverb)}
            }},
            {"controls", toml::table{
                {"control-method",int(s.controls.method)},
                {"keyboard-layout",keyboard_layout_arr},
                {"pad-layout",pad_layout_arr}
            }},
            {"language",get_language_string(s.lang)}
        };
        stream << tbl << std::endl;
    }
} // namespace raid

int main() {

    using namespace raid;

    settings s{};
    {
        auto out_stream = std::ofstream("test.toml");
        serialize_settings(s, out_stream);
    }
    {
        auto in_stream = std::ifstream("test.toml");
        s = deserialize_settings(in_stream);
    }

}

The function deserialize_settings reads and throws an error (see comment "error here")

marzer commented 9 months ago

Ah, I see. I suspect this has something to do with read-mode, since TOML is UTF-8 and newline-agnostic. Can you try opening the std::ifstream with the std::ios::binary flag?

marzer commented 9 months ago

As an aside, from memory, these lines:

s.graphics.fog =  (settings_quality)graphics["fog"].value_or<int>(int(settings_quality::medium));

can probably just be:

s.graphics.fog =  graphics["fog"].value_or<settings_quality>(settings_quality::medium);

i.e. it should accept an enum directly - if it doesn't let me know! (I'm not 100% sure, it's been a while since I worked on that part of the library)

Raildex commented 9 months ago

@marzer whether I use ios::binary or not doesn't make a difference.

marzer commented 9 months ago

Interesting, alright well I guess I'll dig into this and see what's up.

Raildex commented 9 months ago

I've double checked now: It works on binary mode but not on text mode. My own stream class was broken and I did not notice it until now.

marzer commented 9 months ago

Ah, good news. I'll close this out, then, since I wasn't able to reproduce this locally.