diff options
Diffstat (limited to 'src/emu/softlist.cpp')
-rw-r--r-- | src/emu/softlist.cpp | 361 |
1 files changed, 228 insertions, 133 deletions
diff --git a/src/emu/softlist.cpp b/src/emu/softlist.cpp index 04302f6b514..c6bb7161be3 100644 --- a/src/emu/softlist.cpp +++ b/src/emu/softlist.cpp @@ -15,6 +15,8 @@ #include "expat.h" +#include <array> +#include <cstring> #include <regex> @@ -23,7 +25,7 @@ // STATIC VARIABLES //************************************************************************** -static std::regex s_potenial_softlist_regex("\\w+(\\:\\w+)*"); +static std::regex s_potential_softlist_regex("\\w+(\\:\\w+)*"); //************************************************************************** @@ -31,11 +33,10 @@ static std::regex s_potenial_softlist_regex("\\w+(\\:\\w+)*"); //************************************************************************** //------------------------------------------------- -// feature_list_item - constructor +// software_info_item - constructor //------------------------------------------------- -feature_list_item::feature_list_item(const std::string &name, const std::string &value) - : m_next(nullptr), +software_info_item::software_info_item(const std::string &name, const std::string &value) : m_name(name), m_value(value) { @@ -43,11 +44,10 @@ feature_list_item::feature_list_item(const std::string &name, const std::string //------------------------------------------------- -// feature_list_item - constructor +// software_info_item - constructor //------------------------------------------------- -feature_list_item::feature_list_item(std::string &&name, std::string &&value) - : m_next(nullptr), +software_info_item::software_info_item(std::string &&name, std::string &&value) : m_name(std::move(name)), m_value(std::move(value)) { @@ -62,11 +62,10 @@ feature_list_item::feature_list_item(std::string &&name, std::string &&value) // software_part - constructor //------------------------------------------------- -software_part::software_part(software_info &info, std::string &&name, std::string &&interface) - : m_next(nullptr), - m_info(info), - m_name(std::move(name)), - m_interface(std::move(interface)) +software_part::software_part(software_info &info, std::string &&name, std::string &&interface) : + m_info(info), + m_name(std::move(name)), + m_interface(std::move(interface)) { } @@ -76,17 +75,11 @@ software_part::software_part(software_info &info, std::string &&name, std::strin // feature, if specified //------------------------------------------------- -const char *software_part::feature(const std::string &feature_name) const +const char *software_part::feature(std::string_view feature_name) const noexcept { // scan the feature list for an entry matching feature_name and return the value - auto iter = std::find_if( - m_featurelist.begin(), - m_featurelist.end(), - [&feature_name](const feature_list_item &feature) { return feature.name() == feature_name; }); - - return iter != m_featurelist.end() - ? iter->value().c_str() - : nullptr; + auto const iter = m_features.find(feature_name); + return (iter != m_features.end()) ? iter->value().c_str() : nullptr; } @@ -95,18 +88,25 @@ const char *software_part::feature(const std::string &feature_name) const // an interface in the provided list //------------------------------------------------- -bool software_part::matches_interface(const char *interface_list) const +bool software_part::matches_interface(const char *interface_list) const noexcept { // if we have no interface, then we match by default if (m_interface.empty()) return true; - // copy the comma-delimited interface list and ensure it ends with a final comma - std::string interfaces = std::string(interface_list).append(","); - - // then add a comma to the end of our interface and return true if we find it in the list string - std::string our_interface = std::string(m_interface).append(","); - return (interfaces.find(our_interface) != -1); + // find our interface at the beginning of the list or immediately following a comma + while (true) + { + char const *const found(std::strstr(interface_list, m_interface.c_str())); + if (!found) + return false; + if (((found == interface_list) || (',' == found[-1])) && ((',' == found[m_interface.size()]) || !found[m_interface.size()])) + return true; + interface_list = std::strchr(interface_list, ','); + if (!interface_list) + return false; + ++interface_list; + } } @@ -118,16 +118,16 @@ bool software_part::matches_interface(const char *interface_list) const // software_info - constructor //------------------------------------------------- -software_info::software_info(std::string &&name, std::string &&parent, const std::string &supported) - : m_supported(SOFTWARE_SUPPORTED_YES), - m_shortname(std::move(name)), - m_parentname(std::move(parent)) +software_info::software_info(std::string &&name, std::string &&parent, std::string_view supported) : + m_supported(software_support::SUPPORTED), + m_shortname(std::move(name)), + m_parentname(std::move(parent)) { // handle the supported flag if provided if (supported == "partial") - m_supported = SOFTWARE_SUPPORTED_PARTIAL; + m_supported = software_support::PARTIALLY_SUPPORTED; else if (supported == "no") - m_supported = SOFTWARE_SUPPORTED_NO; + m_supported = software_support::UNSUPPORTED; } @@ -136,13 +136,13 @@ software_info::software_info(std::string &&name, std::string &&parent, const std // optional interface match //------------------------------------------------- -const software_part *software_info::find_part(const std::string &part_name, const char *interface) const +const software_part *software_info::find_part(std::string_view part_name, const char *interface) const { // look for the part by name and match against the interface if provided auto iter = std::find_if( m_partdata.begin(), m_partdata.end(), - [&](const software_part &part) + [&part_name, interface] (const software_part &part) { // try to match the part_name (or all parts if part_name is empty), and then try // to match the interface (or all interfaces if interface is nullptr) @@ -176,29 +176,105 @@ bool software_info::has_multiple_parts(const char *interface) const } +namespace detail { + //************************************************************************** // SOFTWARE LIST PARSER //************************************************************************** +class softlist_parser +{ +public: + // construction (== execution) + softlist_parser( + util::read_stream &file, + std::string_view filename, + std::string &listname, + std::string &description, + std::list<software_info> &infolist, + std::ostream &errors); + +private: + enum parse_position + { + POS_ROOT, + POS_MAIN, + POS_SOFT, + POS_PART, + POS_DATA + }; + + // internal parsing helpers + const char *infoname() const { return m_current_info ? m_current_info->shortname().c_str() : "???"; } + int line() const { return XML_GetCurrentLineNumber(m_parser); } + int column() const { return XML_GetCurrentColumnNumber(m_parser); } + const char *parser_error() const { return XML_ErrorString(XML_GetErrorCode(m_parser)); } + + // internal error helpers + template <typename Format, typename... Params> void parse_error(Format &&fmt, Params &&... args); + void unknown_tag(const char *tagname) { parse_error("Unknown tag: %s", tagname); } + void unknown_attribute(const char *attrname) { parse_error("Unknown attribute: %s", attrname); } + + // internal helpers + template <size_t N> std::array<std::string_view, N> parse_attributes(const char **attributes, const char *const (&attrlist)[N]); + bool parse_name_and_value(const char **attributes, std::string_view &name, std::string_view &value); + void add_rom_entry(std::string &&name, std::string &&hashdata, u32 offset, u32 length, u32 flags); + + // expat callbacks + static void start_handler(void *data, const char *tagname, const char **attributes); + static void data_handler(void *data, const char *s, int len); + static void end_handler(void *data, const char *name); + + // internal parsing + void parse_root_start(const char *tagname, const char **attributes); + void parse_main_start(const char *tagname, const char **attributes); + void parse_soft_start(const char *tagname, const char **attributes); + void parse_part_start(const char *tagname, const char **attributes); + void parse_data_start(const char *tagname, const char **attributes); + void parse_main_end(const char *tagname); + void parse_soft_end(const char *name); + + // internal parsing state + const std::string_view m_filename; + std::list<software_info> & m_infolist; + std::ostream & m_errors; + struct XML_ParserStruct * m_parser; + std::string & m_listname; + std::string & m_description; + bool m_data_accum_expected; + bool m_ignore_cdata; + std::string m_data_accum; + software_info * m_current_info; + software_part * m_current_part; + parse_position m_pos; +}; + + //------------------------------------------------- // softlist_parser - constructor //------------------------------------------------- -softlist_parser::softlist_parser(util::core_file &file, const std::string &filename, std::string &description, std::list<software_info> &infolist, std::ostringstream &errors) - : m_file(file), - m_filename(filename), - m_infolist(infolist), - m_errors(errors), - m_done(false), - m_description(description), - m_data_accum_expected(false), - m_current_info(nullptr), - m_current_part(nullptr), - m_pos(POS_ROOT) +softlist_parser::softlist_parser( + util::read_stream &file, + std::string_view filename, + std::string &listname, + std::string &description, + std::list<software_info> &infolist, + std::ostream &errors) : + m_filename(filename), + m_infolist(infolist), + m_errors(errors), + m_listname(listname), + m_description(description), + m_data_accum_expected(false), + m_ignore_cdata(false), + m_current_info(nullptr), + m_current_part(nullptr), + m_pos(POS_ROOT) { // create the parser m_parser = XML_ParserCreate_MM(nullptr, nullptr, nullptr); - if (m_parser == nullptr) + if (!m_parser) throw std::bad_alloc(); // set the handlers @@ -207,13 +283,13 @@ softlist_parser::softlist_parser(util::core_file &file, const std::string &filen XML_SetCharacterDataHandler(m_parser, &softlist_parser::data_handler); // parse the file contents - m_file.seek(0, SEEK_SET); char buffer[1024]; - while (!m_done) + for (bool done = false; !done; ) { - u32 length = m_file.read(buffer, sizeof(buffer)); - m_done = m_file.eof(); - if (XML_Parse(m_parser, buffer, length, m_done) == XML_STATUS_ERROR) + auto const [err, length] = read(file, buffer, sizeof(buffer)); // TODO: better error handling + if (!length) + done = true; + if (XML_Parse(m_parser, buffer, length, done) == XML_STATUS_ERROR) { parse_error("%s", parser_error()); break; @@ -226,36 +302,6 @@ softlist_parser::softlist_parser(util::core_file &file, const std::string &filen //------------------------------------------------- -// line -//------------------------------------------------- - -int softlist_parser::line() const -{ - return XML_GetCurrentLineNumber(m_parser); -} - - -//------------------------------------------------- -// column -//------------------------------------------------- - -int softlist_parser::column() const -{ - return XML_GetCurrentColumnNumber(m_parser); -} - - -//------------------------------------------------- -// parser_error -//------------------------------------------------- - -const char *softlist_parser::parser_error() const -{ - return XML_ErrorString(XML_GetErrorCode(m_parser)); -} - - -//------------------------------------------------- // parse_error - append a parsing error with // filename, line and column information //------------------------------------------------- @@ -279,10 +325,10 @@ inline void softlist_parser::parse_error(Format &&fmt, Params &&... args) // attributes into a list of strings //------------------------------------------------- -template <typename T> -std::vector<std::string> softlist_parser::parse_attributes(const char **attributes, const T &attrlist) +template <size_t N> +std::array<std::string_view, N> softlist_parser::parse_attributes(const char **attributes, const char *const (&attrlist)[N]) { - std::vector<std::string> outlist(std::distance(std::begin(attrlist), std::end(attrlist))); + std::array<std::string_view, N> outlist; // iterate over attribute/value pairs for( ; attributes[0]; attributes += 2) @@ -315,7 +361,7 @@ std::vector<std::string> softlist_parser::parse_attributes(const char **attribut // latter to be defined as an empty string) //------------------------------------------------- -bool softlist_parser::parse_name_and_value(const char **attributes, std::string &name, std::string &value) +bool softlist_parser::parse_name_and_value(const char **attributes, std::string_view &name, std::string_view &value) { bool found_value = false; @@ -422,6 +468,7 @@ void softlist_parser::end_handler(void *data, const char *name) break; case POS_MAIN: + state->parse_main_end(name); state->m_current_info = nullptr; break; @@ -439,6 +486,7 @@ void softlist_parser::end_handler(void *data, const char *name) // stop accumulating state->m_data_accum_expected = false; + state->m_ignore_cdata = false; state->m_data_accum.clear(); } @@ -451,18 +499,25 @@ void softlist_parser::data_handler(void *data, const char *s, int len) { softlist_parser *state = reinterpret_cast<softlist_parser *>(data); - // if we have an std::string to accumulate data in, do it - if (state->m_data_accum_expected) + if (state->m_ignore_cdata) + { + // allowed, but we don't use it + } + else if (state->m_data_accum_expected) + { + // if we have an std::string to accumulate data in, do it state->m_data_accum.append(s, len); - - // otherwise, report an error if the data is non-blank + } else + { + // otherwise, report an error if the data is non-blank for (int i = 0; i < len; i++) if (!isspace(s[i])) { state->parse_error("Unexpected content"); break; } + } } @@ -478,6 +533,9 @@ void softlist_parser::parse_root_start(const char *tagname, const char **attribu static char const *const attrnames[] = { "name", "description" }; const auto attrvalues = parse_attributes(attributes, attrnames); + if (!attrvalues[0].empty()) + m_listname = attrvalues[0]; + if (!attrvalues[1].empty()) m_description = attrvalues[1]; } @@ -493,27 +551,37 @@ void softlist_parser::parse_root_start(const char *tagname, const char **attribu void softlist_parser::parse_main_start(const char *tagname, const char **attributes) { - // <software name='' cloneof='' supported=''> if (strcmp(tagname, "software") == 0) { + // <software name='' cloneof='' supported=''> static char const *const attrnames[] = { "name", "cloneof", "supported" }; auto attrvalues = parse_attributes(attributes, attrnames); if (!attrvalues[0].empty()) { - m_infolist.emplace_back(std::move(attrvalues[0]), std::move(attrvalues[1]), attrvalues[2].c_str()); + m_infolist.emplace_back(std::string(attrvalues[0]), std::string(attrvalues[1]), attrvalues[2]); m_current_info = &m_infolist.back(); } else parse_error("No name defined for item"); } + else if (strcmp(tagname, "notes") == 0) + { + // <notes> + m_ignore_cdata = true; + } else unknown_tag(tagname); } +void softlist_parser::parse_main_end(const char *tagname) +{ +} + + //------------------------------------------------- -// parse_main_start - handle tag start within +// parse_soft_start - handle tag start within // a software tag //------------------------------------------------- @@ -538,13 +606,17 @@ void softlist_parser::parse_soft_start(const char *tagname, const char **attribu else if (strcmp(tagname, "publisher") == 0) m_data_accum_expected = true; + // <notes> + else if (strcmp(tagname, "notes") == 0) + m_ignore_cdata = true; + // <info name='' value=''> else if (strcmp(tagname, "info") == 0) { - std::string infoname, infovalue; + std::string_view infoname, infovalue; if (parse_name_and_value(attributes, infoname, infovalue)) - m_current_info->m_other_info.emplace_back(std::move(infoname), std::move(infovalue)); + m_current_info->m_info.emplace_back(std::string(infoname), std::string(infovalue)); else parse_error("Incomplete other_info definition"); } @@ -552,12 +624,17 @@ void softlist_parser::parse_soft_start(const char *tagname, const char **attribu // <sharedfeat name='' value=''> else if (strcmp(tagname, "sharedfeat") == 0) { - std::string featname, featvalue; + std::string_view featname, featvalue; if (parse_name_and_value(attributes, featname, featvalue)) - m_current_info->m_shared_info.emplace_back(std::move(featname), std::move(featvalue)); + { + if (!m_current_info->m_shared_features.emplace(std::string(featname), std::string(featvalue)).second) + parse_error("Duplicate sharedfeat name"); + } else + { parse_error("Incomplete sharedfeat definition"); + } } // <part name='' interface=''> @@ -568,7 +645,7 @@ void softlist_parser::parse_soft_start(const char *tagname, const char **attribu if (!attrvalues[0].empty() && !attrvalues[1].empty()) { - m_current_info->m_partdata.emplace_back(*m_current_info, std::move(attrvalues[0]), std::move(attrvalues[1])); + m_current_info->m_partdata.emplace_back(*m_current_info, std::string(attrvalues[0]), std::string(attrvalues[1])); m_current_part = &m_current_info->m_partdata.back(); } else @@ -602,8 +679,8 @@ void softlist_parser::parse_part_start(const char *tagname, const char **attribu if (!attrvalues[0].empty() && !attrvalues[1].empty()) { // handle region attributes - const std::string &width = attrvalues[2]; - const std::string &endianness = attrvalues[3]; + const auto &width = attrvalues[2]; + const auto &endianness = attrvalues[3]; u32 regionflags = ROMENTRYTYPE_REGION; if (!width.empty()) @@ -629,7 +706,7 @@ void softlist_parser::parse_part_start(const char *tagname, const char **attribu parse_error("Invalid dataarea endianness"); } - add_rom_entry(std::move(attrvalues[0]), "", 0, strtol(attrvalues[1].c_str(), nullptr, 0), regionflags); + add_rom_entry(std::string(attrvalues[0]), "", 0, strtol(attrvalues[1].data(), nullptr, 0), regionflags); } else parse_error("Incomplete dataarea definition"); @@ -642,7 +719,7 @@ void softlist_parser::parse_part_start(const char *tagname, const char **attribu auto attrvalues = parse_attributes(attributes, attrnames); if (!attrvalues[0].empty()) - add_rom_entry(std::move(attrvalues[0]), "", 0, 1, ROMENTRYTYPE_REGION | ROMREGION_DATATYPEDISK); + add_rom_entry(std::string(attrvalues[0]), "", 0, 1, ROMENTRYTYPE_REGION | ROMREGION_DATATYPEDISK); else parse_error("Incomplete diskarea definition"); } @@ -650,12 +727,17 @@ void softlist_parser::parse_part_start(const char *tagname, const char **attribu // <feature name='' value=''> else if (strcmp(tagname, "feature") == 0) { - std::string featname, featvalue; + std::string_view featname, featvalue; if (parse_name_and_value(attributes, featname, featvalue)) - m_current_part->m_featurelist.emplace_back(std::move(featname), std::move(featvalue)); + { + if (!m_current_part->m_features.emplace(std::string(featname), std::string(featvalue)).second) + parse_error("Duplicate feature name"); + } else + { parse_error("Incomplete feature definition"); + } } // <dipswitch> @@ -686,18 +768,18 @@ void softlist_parser::parse_data_start(const char *tagname, const char **attribu static char const *const attrnames[] = { "name", "size", "crc", "sha1", "offset", "value", "status", "loadflag" }; auto attrvalues = parse_attributes(attributes, attrnames); - std::string &name = attrvalues[0]; - const std::string &sizestr = attrvalues[1]; - const std::string &crc = attrvalues[2]; - const std::string &sha1 = attrvalues[3]; - const std::string &offsetstr = attrvalues[4]; - std::string &value = attrvalues[5]; - const std::string &status = attrvalues[6]; - const std::string &loadflag = attrvalues[7]; + const std::string_view &name = attrvalues[0]; + const std::string_view &sizestr = attrvalues[1]; + const std::string_view &crc = attrvalues[2]; + const std::string_view &sha1 = attrvalues[3]; + const std::string_view &offsetstr = attrvalues[4]; + const std::string_view &value = attrvalues[5]; + const std::string_view &status = attrvalues[6]; + const std::string_view &loadflag = attrvalues[7]; if (!sizestr.empty()) { - u32 length = strtol(sizestr.c_str(), nullptr, 0); - u32 offset = offsetstr.empty() ? 0 : strtol(offsetstr.c_str(), nullptr, 0); + u32 length = strtol(sizestr.data(), nullptr, 0); + u32 offset = offsetstr.empty() ? 0 : strtol(offsetstr.data(), nullptr, 0); if (loadflag == "reload") add_rom_entry("", "", offset, length, ROMENTRYTYPE_RELOAD | ROM_INHERITFLAGS); @@ -706,7 +788,7 @@ void softlist_parser::parse_data_start(const char *tagname, const char **attribu else if (loadflag == "continue") add_rom_entry("", "", offset, length, ROMENTRYTYPE_CONTINUE | ROM_INHERITFLAGS); else if (loadflag == "fill") - add_rom_entry("", std::move(value), offset, length, ROMENTRYTYPE_FILL); + add_rom_entry("", std::string(value), offset, length, ROMENTRYTYPE_FILL); else if (loadflag == "ignore") add_rom_entry("", "", 0, length, ROMENTRYTYPE_IGNORE | ROM_INHERITFLAGS); else if (!name.empty()) @@ -742,7 +824,7 @@ void softlist_parser::parse_data_start(const char *tagname, const char **attribu else if (loadflag == "load32_byte") romflags = ROM_SKIP(3); - add_rom_entry(std::move(name), std::move(hashdata), offset, length, ROMENTRYTYPE_ROM | romflags); + add_rom_entry(std::string(name), std::move(hashdata), offset, length, ROMENTRYTYPE_ROM | romflags); } else parse_error("Rom name missing"); @@ -757,10 +839,10 @@ void softlist_parser::parse_data_start(const char *tagname, const char **attribu static char const *const attrnames[] = { "name", "sha1", "status", "writeable" }; auto attrvalues = parse_attributes(attributes, attrnames); - std::string &name = attrvalues[0]; - const std::string &sha1 = attrvalues[1]; - const std::string &status = attrvalues[2]; - const std::string &writeablestr = attrvalues[3]; + const std::string_view &name = attrvalues[0]; + const std::string_view &sha1 = attrvalues[1]; + const std::string_view &status = attrvalues[2]; + const std::string_view &writeablestr = attrvalues[3]; if (!name.empty() && !sha1.empty()) { const bool baddump = (status == "baddump"); @@ -768,7 +850,7 @@ void softlist_parser::parse_data_start(const char *tagname, const char **attribu const bool writeable = (writeablestr == "yes"); std::string hashdata = string_format("%c%s%s", util::hash_collection::HASH_SHA1, sha1, (nodump ? NO_DUMP : (baddump ? BAD_DUMP : ""))); - add_rom_entry(std::move(name), std::move(hashdata), 0, 0, ROMENTRYTYPE_ROM | (writeable ? DISK_READWRITE : DISK_READONLY)); + add_rom_entry(std::string(name), std::move(hashdata), 0, 0, ROMENTRYTYPE_ROM | (writeable ? DISK_READWRITE : DISK_READONLY)); } else if (status != "nodump") // a no_dump chd is not an incomplete entry parse_error("Incomplete disk definition"); @@ -793,15 +875,15 @@ void softlist_parser::parse_soft_end(const char *tagname) // <description> if (strcmp(tagname, "description") == 0) - m_current_info->m_longname = m_data_accum; + m_current_info->m_longname = std::move(m_data_accum); // <year> else if (strcmp(tagname, "year") == 0) - m_current_info->m_year = m_data_accum; + m_current_info->m_year = std::move(m_data_accum); // <publisher> else if (strcmp(tagname, "publisher") == 0) - m_current_info->m_publisher = m_data_accum; + m_current_info->m_publisher = std::move(m_data_accum); // </part> else if (strcmp(tagname, "part") == 0) @@ -815,14 +897,27 @@ void softlist_parser::parse_soft_end(const char *tagname) if (!m_current_part->m_romdata.empty()) add_rom_entry("", "", 0, 0, ROMENTRYTYPE_END); - // get the info; if present, copy shared data (we assume name/value strings live - // in the string pool and don't need to be reallocated) + // get the info; if present, copy shared data if (m_current_info != nullptr) - for (const feature_list_item &item : m_current_info->shared_info()) - m_current_part->m_featurelist.emplace_back(item.name(), item.value()); + for (const software_info_item &item : m_current_info->shared_features()) + m_current_part->m_features.emplace(item.name(), item.value()); } } +} // namespace detail + + +void parse_software_list( + util::read_stream &file, + std::string_view filename, + std::string &listname, + std::string &description, + std::list<software_info> &infolist, + std::ostream &errors) +{ + detail::softlist_parser(file, filename, listname, description, infolist, errors); +} + //------------------------------------------------- // software_name_parse - helper that splits a @@ -840,10 +935,10 @@ void softlist_parser::parse_soft_end(const char *tagname) // case. //------------------------------------------------- -bool software_name_parse(const std::string &identifier, std::string *list_name, std::string *software_name, std::string *part_name) +bool software_name_parse(std::string_view identifier, std::string *list_name, std::string *software_name, std::string *part_name) { // first, sanity check the arguments - if (!std::regex_match(identifier, s_potenial_softlist_regex)) + if (!std::regex_match(identifier.begin(), identifier.end(), s_potential_softlist_regex)) return false; // reset all output parameters (if specified of course) @@ -856,7 +951,7 @@ bool software_name_parse(const std::string &identifier, std::string *list_name, // if no colon, this is the swname by itself auto split1 = identifier.find_first_of(':'); - if (split1 == std::string::npos) + if (split1 == std::string_view::npos) { if (software_name != nullptr) *software_name = identifier; @@ -865,7 +960,7 @@ bool software_name_parse(const std::string &identifier, std::string *list_name, // if one colon, it is the swname and swpart alone auto split2 = identifier.find_first_of(':', split1 + 1); - if (split2 == std::string::npos) + if (split2 == std::string_view::npos) { if (software_name != nullptr) *software_name = identifier.substr(0, split1); |