//+----------------------------------------------------------------------------+ //| Description: Magic Set Editor - Program to make Magic (tm) cards | //| Copyright: (C) Twan van Laarhoven and the other MSE developers | //| License: GNU General Public License 2 or later (see file COPYING) | //+----------------------------------------------------------------------------+ #pragma once // ----------------------------------------------------------------------------- : Includes #include #include template class Defaultable; template class Scriptable; DECLARE_POINTER_TYPE(Game); DECLARE_POINTER_TYPE(StyleSheet); class Packaged; // ----------------------------------------------------------------------------- : Reader /// The Reader can be used for reading (deserializing) objects /** This class makes use of the reflection functionality, in effect * an object tells the Reader what fields it would like to read. * The reader then sees if the requested field is currently available. * * The handle functions ensure that afterwards the reader is at the line after the * object that was just read. */ class Reader { public: /// Construct a reader that reads from the given input stream /** filename is used only for error messages * package is used for looking up included files. */ Reader(wxInputStream& input, Packaged* package = nullptr, const String& filename = wxEmptyString, bool ignore_invalid = false); ~Reader() { showWarnings(); } /// Tell the reflection code we are reading static constexpr bool isReading = true; static constexpr bool isWriting = false; static constexpr bool isScripting = false; /// Is the thing currently being read 'complex', i.e. does it have children inline bool isCompound() const { return indent != expected_indent - 1 || value.empty(); } /// Ignore old keys void handleIgnore(int, const Char*); /// Get the version of the format we are reading inline Version formatVersion() const { return file_app_version; } /// Read and check the application version void handleAppVersion(); /// Add a warning message, but continue reading void warning(const String& msg, int line_number_delta = 0, bool warn_on_previous_line = true); /// Show all warning messages, but continue reading void showWarnings(); // --------------------------------------------------- : Handling objects /// Handle an object that can read as much as it can eat template void handle_greedy(T& object) { handle_greedy_without_validate(object); after_reading(object, file_app_version); } template void handle_greedy_without_validate(T& object) { do { handle(object); if (state != HANDLED) unknownKey(object); state = OUTSIDE; } while (indent >= expected_indent); } /// Handle an object: read it if it's name matches template void handle(const Char* name, T& object) { if (enterBlock(name)) { handle_greedy(object); exitBlock(); } } /// Handle a value template inline void handleNoScript(const Char* name, T& value) { handle(name,value); } /// Reads a vector from the input stream template void handle(const Char* name, vector& vector); /// Reads an object of type T from the input stream template void handle(T&); /// Reads a intrusive_ptr from the input stream template void handle(intrusive_ptr&); /// Reads a map from the input stream template void handle(map&); /// Reads an IndexMap from the input stream, reads only keys that already exist in the map template void handle(IndexMap&); template void handle(DelayedIndexMaps&); template void handle(DelayedIndexMapsData&); /// Reads a Defaultable from the input stream template void handle(Defaultable&); /// Reads a Scriptable from the input stream template void handle(Scriptable&); // special behaviour void handle(GameP&); void handle(StyleSheetP&); /// Indicate that the last value from getValue() was not handled, allowing it to be handled again void unhandle(); /// The package being read from inline Packaged* getPackage() const { return package; } private: // --------------------------------------------------- : Data /// App version this file was made with Version file_app_version; /// The line we read String line; /// The key and value of the last line we read String key, value; /// Value of the *previous* line, only valid in state==HANDLED String previous_value; /// Indentation of the last line we read int indent; /// Indentation of the block we are in int expected_indent; /// State of the reader enum State { OUTSIDE, ///< We have not entered the block of the current key ENTERED, ///< We just entered the block of the current key HANDLED, ///< We have handled a value, and moved to the next line, previous_value is the value we just handled UNHANDLED, ///< Something has been 'unhandled()' } state; /// Should all invalid keys be ignored? bool ignore_invalid; /// Filename for error messages String filename; /// Package this file is from, if any Packaged* package; /// Line number of the current line for error messages int line_number; /// Line number of the previous_line int previous_line_number; /// Input stream we are reading from wxInputStream& input; /// Accumulated warning messages String warnings; // --------------------------------------------------- : Reading the stream /// Is there a block with the given key under the current cursor? if so, enter it bool enterBlock(const Char* name); /// Enter any block, no matter what the key bool enterAnyBlock(); /// Leave the block we are in void exitBlock(); /// Move to the next non empty line void moveNext(); /// Reads the next line from the input, and stores it in line/key/value/indent void readLine(bool in_string = false); /// Return the value on the current line const String& getValue(); /// No line was read, because nothing mathes the current key /** Maybe the key is "include file" */ template void unknownKey(T& v) { if (key == _("include_file")) { auto stream = openIncludedFile(); Reader sub_reader(*stream, package, value, ignore_invalid); if (sub_reader.file_app_version == 0) { // in an included file, use the app version of the parent if there is none sub_reader.file_app_version = file_app_version; } sub_reader.handle_greedy_without_validate(v); moveNext(); } else { unknownKey(); } } void unknownKey(); unique_ptr openIncludedFile(); }; // ----------------------------------------------------------------------------- : After reading hook // Overload to perform extra stuff after reading template inline void after_reading(T&, Version) {} template inline void after_reading(intrusive_ptr& x, Version ver) { after_reading(*x, ver); } // ----------------------------------------------------------------------------- : Container types /// Construct a new type, possibly reading something in the process. /** By default just creates a new object. * This function can be overloaded to provide different behaviour */ template intrusive_ptr read_new(Reader& reader) { return make_intrusive(); } /// Update the 'index' member of a value for use by IndexMap template void update_index(T&, size_t index) {} template void Reader::handle(const Char* name, vector& vector) { String vectorKey = singular_form(name); while (enterBlock(vectorKey.c_str())) { vector.resize(vector.size() + 1); handle_greedy(vector.back()); update_index(vector.back(), vector.size() - 1); // update index for IndexMap exitBlock(); } } template void Reader::handle(intrusive_ptr& pointer) { if (!pointer) pointer = read_new(*this); handle(*pointer); } template void Reader::handle(map& m) { while (enterAnyBlock()) { handle_greedy(m[key]); exitBlock(); } } template void Reader::handle(IndexMap& m) { for (typename IndexMap::iterator it = m.begin() ; it != m.end() ; ++it) { handle(get_key_name(*it).c_str(), *it); } } // ----------------------------------------------------------------------------- : Reflection /// Implement reflection as used by Reader #define REFLECT_OBJECT_READER(Cls) \ template<> void Reader::handle(Cls& object) { \ object.reflect(*this); \ } \ void Cls::reflect(Reader& reader) { \ reflect_impl(reader); \ } // ----------------------------------------------------------------------------- : Reflection for enumerations /// Implement enum reflection as used by Reader #define REFLECT_ENUM_READER(Enum) \ template<> void Reader::handle(Enum& enum_) { \ EnumReader reader(getValue()); \ reflect_ ## Enum(enum_, reader); \ reader.warnIfNotDone(this); \ } \ void parse_enum(const String& value, Enum& out) { \ EnumReader reader(value); \ reflect_ ## Enum(out, reader); \ reader.errorIfNotDone(); \ } /// 'Handler' to be used when reflecting enumerations for Reader class EnumReader { public: inline EnumReader(String const& read) : read(read), first(nullptr), done(false) {} /// Handle a possible value for the enum, if the name matches the name in the input template inline void handle(const Char* name, Enum value, Enum& enum_) { if (!done) { if (read == name) { done = true; enum_ = value; } else if (!first) { first = name; enum_ = value; } } } inline bool isDone() const { return done; } void warnIfNotDone(Reader* errors_to); void errorIfNotDone(); private: String read; ///< The string to match to a value name Char const* first; ///< Has the first (default) value been handled? If so, what is its name. bool done; ///< Was anything matched? String notDoneErrorMessage() const; };