1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-25 16:31:42 +00:00

Adds the ability for reflective structs to limit the permitted values to enumerated properties.

This commit is contained in:
Thomas Harte 2020-03-17 21:44:04 -04:00
parent 394ee61c78
commit f9ca443667
3 changed files with 93 additions and 9 deletions

View File

@ -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);
}
}
};

View File

@ -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<std::string> 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;
}

View File

@ -9,6 +9,7 @@
#ifndef Struct_h
#define Struct_h
#include <cstdarg>
#include <cstring>
#include <string>
#include <typeindex>
@ -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<std::string> values_for(const std::string &name) = 0;
virtual ~Struct() {}
};
@ -123,6 +125,39 @@ template <typename Owner> 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<std::string> values_for(const std::string &name) final {
std::vector<std::string> 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 <typename Owner> 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 <typename Type> 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<bool> 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 <typename Owner> 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<uint8_t *>(field) - reinterpret_cast<uint8_t *>(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 <typename Owner> class StructImpl: public Struct {
type(&type), offset(offset), size(size) {}
};
static inline std::unordered_map<std::string, Field> contents_;
static inline std::unordered_map<std::string, std::vector<bool>> permitted_enum_values_;
};
}