diff --git a/Machines/Utility/MachineForTarget.hpp b/Machines/Utility/MachineForTarget.hpp index e5891b5b4..a51508f47 100644 --- a/Machines/Utility/MachineForTarget.hpp +++ b/Machines/Utility/MachineForTarget.hpp @@ -20,6 +20,14 @@ #include /*! + This namespace acts as a grab-bag of functions that allow a client to: + + (i) discover the total list of implemented machines; + (ii) discover the construction and runtime options available for controlling them; and + (iii) create any implemented machine via its construction options. + + See Reflection::Struct and Reflection::Enum for getting dynamic information from the + Targets that this namespace deals in. */ namespace Machine { @@ -58,13 +66,16 @@ std::string ShortNameForTargetMachine(const Analyser::Machine target); std::string LongNameForTargetMachine(const Analyser::Machine target); /*! - @returns A list of all available machines. + @param meaningful_without_media_only If this is @c true then only machines that it is meaningful to start without a piece of media will be listed; + otherwise all supported machines will be listed. + @param long_names If this is @c true then long names will be returned; otherwise short names will be returned. + + @returns A list of all available machines. Names are always guaranteed to be in the same order. */ std::vector AllMachines(bool meaningful_without_media_only, bool long_names); /*! - Returns a map from long machine name to the list of options that machine - exposes, for all machines. + Returns a map from long machine name to the list of options that machine exposes, for all machines. */ std::map>> AllOptionsByMachineName(); diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 377c50181..407c7943d 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -4981,7 +4981,7 @@ "$(USER_LIBRARY_DIR)/Frameworks", ); GCC_C_LANGUAGE_STANDARD = gnu11; - MACOSX_DEPLOYMENT_TARGET = 10.12; + MACOSX_DEPLOYMENT_TARGET = 10.14; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; @@ -5003,7 +5003,7 @@ ); GCC_C_LANGUAGE_STANDARD = gnu11; GCC_OPTIMIZATION_LEVEL = 2; - MACOSX_DEPLOYMENT_TARGET = 10.12; + MACOSX_DEPLOYMENT_TARGET = 10.14; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme index 6f2dd2302..aac0802ab 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme @@ -34,7 +34,6 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - enableThreadSanitizer = "YES" disableMainThreadChecker = "YES" launchStyle = "0" useCustomWorkingDirectory = "NO" @@ -57,6 +56,10 @@ argument = "/Users/thomasharte/Downloads/test-dsk-for-rw-and-50-60-hz/TEST-RW-60Hz.DSK" isEnabled = "NO"> + + @@ -87,7 +90,7 @@ + isEnabled = "NO"> diff --git a/OSBindings/SDL/main.cpp b/OSBindings/SDL/main.cpp index 927ca35be..d7cf2c4b0 100644 --- a/OSBindings/SDL/main.cpp +++ b/OSBindings/SDL/main.cpp @@ -17,7 +17,6 @@ #include #include #include -#include #include @@ -364,7 +363,7 @@ bool KeyboardKeyForSDLScancode(SDL_Scancode scancode, Inputs::Keyboard::Key &key struct ParsedArguments { std::string file_name; - std::map> selections; + std::map selections; // The empty string will be inserted for arguments without an = suffix. }; /*! Parses an argc/argv pair to discern program arguments. */ @@ -389,7 +388,7 @@ ParsedArguments parse_arguments(int argc, char *argv[]) { std::size_t split_index = argument.find("="); if(split_index == std::string::npos) { - arguments.selections[argument] = true; + arguments.selections[argument]; // To create an entry with the default empty string. } else { std::string name = argument.substr(0, split_index); std::string value = argument.substr(split_index+1, std::string::npos); @@ -477,39 +476,55 @@ int main(int argc, char *argv[]) { ParsedArguments arguments = parse_arguments(argc, argv); // This may be printed either as - const std::string usage_suffix = " [file] [OPTIONS] [--rompath={path to ROMs}] [--speed={speed multiplier, e.g. 1.5}]"; /* [--logical-keyboard] */ + const std::string usage_suffix = " [file or --new={machine}] [OPTIONS] [--rompath={path to ROMs}] [--speed={speed multiplier, e.g. 1.5}] [--logical-keyboard]"; // Print a help message if requested. if(arguments.selections.find("help") != arguments.selections.end() || arguments.selections.find("h") != arguments.selections.end()) { + const auto all_machines = Machine::AllMachines(false, false); + std::cout << "Usage: " << final_path_component(argv[0]) << usage_suffix << std::endl; std::cout << "Use alt+enter to toggle full screen display. Use control+shift+V to paste text." << std::endl; - std::cout << "Required machine type and configuration is determined from the file. Machines with further options:" << std::endl << std::endl; - - const auto all_options = Machine::AllOptionsByMachineName(); - for(const auto &machine_options: all_options) { - std::cout << machine_options.first << ":" << std::endl; - for(const auto &option: machine_options.second) { - std::cout << '\t' << "--" << option->short_name; - - Configurable::ListOption *list_option = dynamic_cast(option.get()); - if(list_option) { - std::cout << "={"; - bool is_first = true; - for(const auto &option: list_option->options) { - if(!is_first) std::cout << '|'; - is_first = false; - std::cout << option; - } - std::cout << "}"; - } - std::cout << std::endl; - } - std::cout << std::endl; + std::cout << "Required machine type and configuration is determined from the file if specified; otherwise use:" << std::endl << std::endl; + std::cout << "\t--new={"; + bool is_first = true; + for(const auto &name: all_machines) { + if(!is_first) std::cout << "|"; + std::cout << name; + is_first = false; } + std::cout << "}" << std::endl << std::endl; + + std::cout << "Further machine options:" << std::endl; + std::cout << "(* means: a selection will be made automatically based on the file selected, if any)" << std::endl << std::endl; + +// const auto all_options = Machine::AllOptionsByMachineName(); +// for(const auto &machine_options: all_options) { +// std::cout << machine_options.first << ":" << std::endl; +// for(const auto &option: machine_options.second) { +// std::cout << '\t' << "--" << option->short_name; +// +// Configurable::ListOption *list_option = dynamic_cast(option.get()); +// if(list_option) { +// std::cout << "={"; +// bool is_first = true; +// for(const auto &option: list_option->options) { +// if(!is_first) std::cout << '|'; +// is_first = false; +// std::cout << option; +// } +// std::cout << "}"; +// } +// std::cout << std::endl; +// } +// std::cout << std::endl; +// } const auto targets = Machine::TargetsByMachineName(false); for(const auto &target: targets) { const auto reflectable = dynamic_cast(target.second.get()); + if(!reflectable) continue; + const auto all_keys = reflectable->all_keys(); + if(all_keys.empty()) continue; std::cout << target.first << ":" << std::endl; for(const auto &option: reflectable->all_keys()) { @@ -529,7 +544,7 @@ int main(int argc, char *argv[]) { } // TODO: if not a registered enum... then assume it was a Boolean? - std::cout << std::endl; + std::cout << "\t*" << std::endl; } std::cout << std::endl; @@ -537,17 +552,50 @@ int main(int argc, char *argv[]) { return EXIT_SUCCESS; } - // Perform a sanity check on arguments. - if(arguments.file_name.empty()) { - std::cerr << "Usage: " << final_path_component(argv[0]) << usage_suffix << std::endl; - std::cerr << "Use --help to learn more about available options." << std::endl; - return EXIT_FAILURE; + // Determine the machine for the supplied file, if any, or from --new. + Analyser::Static::TargetList targets; + if(!arguments.file_name.empty()) { + targets = Analyser::Static::GetTargets(arguments.file_name); + } + + const auto new_argument = arguments.selections.find("new"); + if(new_argument != arguments.selections.end() && !new_argument->second.empty()) { + // Perform for a case-insensitive search against short names. + const auto short_names = Machine::AllMachines(false, false); + auto short_name = short_names.begin(); + while(short_name != short_names.end()) { + if(std::equal( + short_name->begin(), short_name->end(), + new_argument->second.begin(), new_argument->second.end(), + [](char a, char b) { return tolower(b) == tolower(a); })) { + break; + } + ++short_name; + } + + // If a match was found, use the corresponding long name to look up a suitable + // Analyser::Statuc::Target and move that to the targets list. + if(short_name != short_names.end()) { + const auto long_name = Machine::AllMachines(false, true)[short_name - short_names.begin()]; + auto targets_by_machine = Machine::TargetsByMachineName(false); + std::unique_ptr tgt = std::move(targets_by_machine[long_name]); + targets.push_back(std::move(tgt)); + } } - // Determine the machine for the supplied file. - const auto targets = Analyser::Static::GetTargets(arguments.file_name); if(targets.empty()) { - std::cerr << "Cannot open " << arguments.file_name << "; no target machine found" << std::endl; + if(!arguments.file_name.empty()) { + std::cerr << "Cannot open " << arguments.file_name << "; no target machine found" << std::endl; + return EXIT_FAILURE; + } + + if(!new_argument->second.empty()) { + std::cerr << "Unknown machine: " << new_argument->second << std::endl; + return EXIT_FAILURE; + } + + std::cerr << "Usage: " << final_path_component(argv[0]) << usage_suffix << std::endl; + std::cerr << "Use --help to learn more about available options." << std::endl; return EXIT_FAILURE; } @@ -568,14 +616,14 @@ int main(int argc, char *argv[]) { "/usr/local/share/CLK/", "/usr/share/CLK/" }; -// if(arguments.selections.find("rompath") != arguments.selections.end()) { -// const std::string user_path = arguments.selections["rompath"]->list_selection()->value; -// if(user_path.back() != '/') { -// paths.push_back(user_path + "/"); -// } else { -// paths.push_back(user_path); -// } -// } + if(arguments.selections.find("rompath") != arguments.selections.end()) { + const std::string user_path = arguments.selections["rompath"]; + if(user_path.back() != '/') { + paths.push_back(user_path + "/"); + } else { + paths.push_back(user_path); + } + } std::vector>> results; for(const auto &rom: roms) { diff --git a/Reflection/Struct.cpp b/Reflection/Struct.cpp index fbf92ab09..cbe533987 100644 --- a/Reflection/Struct.cpp +++ b/Reflection/Struct.cpp @@ -51,3 +51,7 @@ template <> bool Reflection::set(Struct &target, const std::string &name, const const std::string string(value); return set(target, name, string); } + +bool Reflection::fuzzy_set(Struct &target, const std::string &name, const std::string &value) { + return false; +} diff --git a/Reflection/Struct.h b/Reflection/Struct.h index 9dcf0ac2a..256d53876 100644 --- a/Reflection/Struct.h +++ b/Reflection/Struct.h @@ -33,7 +33,7 @@ struct Struct { /*! Attempts to set the property @c name to @c value ; will perform limited type conversions. - @returns @c true if t + @returns @c true if the property was successfully set; @c false otherwise. */ template bool set(Struct &target, const std::string &name, Type value); @@ -54,12 +54,30 @@ template <> bool set(Struct &target, const std::string &name, int value); template <> bool set(Struct &target, const std::string &name, const std::string &value); template <> bool set(Struct &target, const std::string &name, const char *value); + +/*! + Fuzzy-set attempts to set any property based on a string value. This is intended to allow input provided by the user. + + Amongst other steps, it might: + * if the target is a bool, map true, false, yes, no, y, n, etc; + * if the target is an integer, parse like strtrol; + * if the target is a float, parse like strtod; or + * if the target is a reflective enum, attempt to match to enum members (possibly doing so in a case insensitive fashion). + + This method reserves the right to perform more or fewer attempted mappings, using any other logic it + decides is appropriate. + +@returns @c true if the property was successfully set; @c false otherwise. +*/ +bool fuzzy_set(Struct &target, const std::string &name, const std::string &value); + +// TODO: move this elsewhere. It's just a sketch anyway. struct Serialisable { - /// Serialises this object, appending it to @c target. - virtual void serialise(std::vector &target) = 0; - /// Deserialises this object from @c source. - /// @returns @c true if the deserialisation was successful; @c false otherwise. - virtual bool deserialise(const std::vector &source) = 0; + /// Serialises this object, appending it to @c target. + virtual void serialise(std::vector &target) = 0; + /// Deserialises this object from @c source. + /// @returns @c true if the deserialisation was successful; @c false otherwise. + virtual bool deserialise(const std::vector &source) = 0; }; template class StructImpl: public Struct {