diff --git a/Machines/AmstradCPC/AmstradCPC.hpp b/Machines/AmstradCPC/AmstradCPC.hpp index 8e8e843c7..d122b2bb8 100644 --- a/Machines/AmstradCPC/AmstradCPC.hpp +++ b/Machines/AmstradCPC/AmstradCPC.hpp @@ -38,8 +38,7 @@ class Machine { if(needs_declare()) { DeclareField(output); AnnounceEnumNS(Configurable, Display); - // TODO: some way to set limited enum support for a struct? - // In this case, to support only RGB and CompositeColour. + limit_enum(&output, Configurable::Display::RGB, Configurable::Display::CompositeColour, -1); } } }; diff --git a/OSBindings/SDL/main.cpp b/OSBindings/SDL/main.cpp index b387b0c00..33741b15c 100644 --- a/OSBindings/SDL/main.cpp +++ b/OSBindings/SDL/main.cpp @@ -495,7 +495,6 @@ int main(int argc, char *argv[]) { 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 targets = Machine::TargetsByMachineName(false); const auto runtime_options = Machine::AllOptionsByMachineName(); @@ -520,17 +519,18 @@ int main(int argc, char *argv[]) { std::cout << machine << ":" << std::endl; - // Join the two lists of properties. + // Join the two lists of properties and sort the result. std::vector all_options = options_keys; all_options.insert(all_options.end(), target_keys.begin(), target_keys.end()); + std::sort(all_options.begin(), all_options.end()); for(const auto &option: all_options) { std::cout << '\t' << "--" << option; - bool is_construction_option = true; + auto source = target_reflectable; auto type = target_reflectable->type_of(option); if(!type) { - is_construction_option = false; + source = options_reflectable; type = options_reflectable->type_of(option); } @@ -538,7 +538,7 @@ int main(int argc, char *argv[]) { if(!Reflection::Enum::name(*type).empty()) { std::cout << "={"; bool is_first = true; - for(const auto &value: Reflection::Enum::all_values(*type)) { + for(const auto &value: source->values_for(option)) { if(!is_first) std::cout << '|'; is_first = false; std::cout << value; @@ -546,9 +546,10 @@ int main(int argc, char *argv[]) { std::cout << "}"; } - // TODO: if not a registered enum... then assume it was a Boolean? + // The above effectively assumes that every field is either a + // Boolean or an enum. This may need to be revisted. It also + // assumes no name collisions, but that's kind of unavoidable. - if(is_construction_option) std::cout << "\t*"; std::cout << std::endl; } diff --git a/Reflection/Struct.h b/Reflection/Struct.h index 4b1af1ea4..ee94ba6ce 100644 --- a/Reflection/Struct.h +++ b/Reflection/Struct.h @@ -9,6 +9,7 @@ #ifndef Struct_h #define Struct_h +#include #include #include #include @@ -27,6 +28,7 @@ struct Struct { virtual const std::type_info *type_of(const std::string &name) = 0; virtual void set(const std::string &name, const void *value) = 0; virtual const void *get(const std::string &name) = 0; + virtual std::vector values_for(const std::string &name) = 0; virtual ~Struct() {} }; @@ -123,6 +125,39 @@ template class StructImpl: public Struct { return iterator->second.type; } + /*! + @returns a list of the valid enum value names for field @c name if it is a declared enum field of this struct; + the empty list otherwise. + */ + std::vector values_for(const std::string &name) final { + std::vector result; + + // Return an empty vector if this field isn't declared. + const auto type = type_of(name); + if(!type) return result; + + // Also return an empty vector if this field isn't a registered enum. + const auto all_values = Enum::all_values(*type); + if(all_values.empty()) return result; + + // If no restriction is stored, return all values. + const auto permitted_values = permitted_enum_values_.find(name); + if(permitted_values == permitted_enum_values_.end()) return all_values; + + // Compile a vector of only those values the stored set indicates. + auto value = all_values.begin(); + auto flag = permitted_values->second.begin(); + while(value != all_values.end() && flag != permitted_values->second.end()) { + if(*flag) { + result.push_back(*value); + } + ++flag; + ++value; + } + + return result; + } + /*! @returns A vector of all declared fields for this struct. */ @@ -158,6 +193,35 @@ template class StructImpl: public Struct { )); } + /*! + If @c t is a previously-declared field that links to a declared enum then the variable + arguments provide a list of the acceptable values for that field. The list should be terminated + with a value of -1. + */ + template void limit_enum(Type *t, ...) { + const auto name = name_of(t); + if(name.empty()) return; + + // The default vector size of '8' isn't especially scientific, + // but I feel like it's a good choice. + std::vector permitted_values(8); + + va_list list; + va_start(list, t); + while(true) { + const int next = va_arg(list, int); + if(next < 0) break; + + if(permitted_values.size() <= next) { + permitted_values.resize(permitted_values.size() << 1); + } + permitted_values[next] = true; + } + va_end(list); + + permitted_enum_values_.emplace(std::make_pair(name, permitted_values)); + } + /*! @returns @c true if this subclass of @c Struct has not yet declared any fields. */ @@ -165,6 +229,25 @@ template class StructImpl: public Struct { return !contents_.size(); } + /*! + Performs a reverse lookup from field to name. + */ + std::string name_of(void *field) { + const ssize_t offset = reinterpret_cast(field) - reinterpret_cast(this); + + auto iterator = contents_.begin(); + while(iterator != contents_.end()) { + if(iterator->second.offset == offset) break; + ++iterator; + } + + if(iterator != contents_.end()) { + return iterator->first; + } else { + return ""; + } + } + private: struct Field { const std::type_info *type; @@ -174,6 +257,7 @@ template class StructImpl: public Struct { type(&type), offset(offset), size(size) {} }; static inline std::unordered_map contents_; + static inline std::unordered_map> permitted_enum_values_; }; }