diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2015-08-24 09:51:15 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2015-08-24 14:52:43 +0200 |
commit | 68f96f9213e849d0d7c4cedf3edeaec99743ee27 (patch) | |
tree | 271913d74c906971cac555319f5e14d0c66e0c16 /build/variable | |
parent | 0d5234f4aefd3cc5b5948cc1b9dd009e50046f5e (diff) |
New variable architecture
Diffstat (limited to 'build/variable')
-rw-r--r-- | build/variable | 735 |
1 files changed, 557 insertions, 178 deletions
diff --git a/build/variable b/build/variable index bc78289..3bd8922 100644 --- a/build/variable +++ b/build/variable @@ -6,13 +6,14 @@ #define BUILD_VARIABLE #include <map> +#include <vector> #include <string> -#include <memory> // unique_ptr #include <cstddef> // nullptr_t #include <utility> // move(), pair, make_pair() #include <cassert> +#include <iterator> #include <functional> // hash, reference_wrapper -#include <typeindex> +#include <type_traits> // conditional, is_reference, remove_reference, etc. #include <unordered_set> #include <butl/prefix-map> @@ -22,12 +23,18 @@ namespace build { - struct value; + struct variable; + // If assign is NULL, then the value is assigned as is. If append + // is NULL, then the names are appended to the end of the value + // and assign is called, if not NULL. Variable is provided primarily + // for diagnostics. Return true if the resulting value is not empty. + // struct value_type { - std::type_index id; - value* (*const factory) (); + const std::string name; + bool (*const assign) (names&, const variable&); + bool (*const append) (names&, names, const variable&); }; // variable @@ -37,11 +44,12 @@ namespace build struct variable { explicit - variable (std::string n, char p = '\0'): name (std::move (n)), pairs (p) {} + variable (std::string n, const value_type* t = nullptr, char p = '\0') + : name (std::move (n)), pairs (p), type (t) {} std::string name; char pairs; - //const value_type* type = nullptr; // If NULL, then no fixed type. + const value_type* type; // If NULL, then not (yet) typed. }; inline bool @@ -51,119 +59,121 @@ namespace build // value // - struct value; - typedef std::unique_ptr<value> value_ptr; - - struct value + class value { public: - virtual value_ptr - clone () const = 0; + // By default we create NULL value. + // + explicit value (const value_type* t = nullptr) + : type (t), state_ (state_type::null) {} - virtual bool - compare (const value&) const = 0; + value (value&&) = default; - virtual - ~value () = default; - }; - - class list_value: public value, public names - { - public: - using names::names; - - list_value () = default; - explicit list_value (names d): names (std::move (d)) {} - explicit list_value (name n) {emplace_back (std::move (n));} - explicit list_value (dir_path d) {emplace_back (std::move (d));} - explicit list_value (std::string s) {emplace_back (std::move (s));} - explicit list_value (const char* s) {emplace_back (s);} + value& + operator= (std::nullptr_t) + { + data_.clear (); + state_ = state_type::null; + return *this; + } - virtual value_ptr - clone () const {return value_ptr (new list_value (*this));} + value& + operator= (value&&); - virtual bool - compare (const value& v) const + value& + operator= (const value& v) { - const list_value* lv (dynamic_cast<const list_value*> (&v)); - return lv != nullptr && static_cast<const names&> (*this) == *lv; + if (&v != this) + *this = value (v); + return *this; } - // Pair (i.e., key-value) search. Note that this funtion assumes - // the list contains only pairs and keys are simple names. Returns - // NULL if not found. - // - const name* - find_pair (const std::string& key) const + value& + operator= (std::reference_wrapper<const value> v) { - for (auto i (begin ()); i != end (); i += 2) - if (i->value == key) - return &*++i; - return nullptr; + return *this = v.get (); } - }; - typedef std::unique_ptr<list_value> list_value_ptr; - // value_proxy - // - // A variable can be undefined, null, or contain some actual value. - // Note that once value_proxy is bound to a value, the only way to - // rebind it to a different value is by using explicit rebind(). In - // particular, assigning one value proxy to another will assing the - // values. - // - struct variable_map; + value& + append (value, const variable&); // Aka operator+=(). - struct value_proxy - { - bool - defined () const {return p != nullptr;} + // Forwarded to the representation type's assign()/append() (see below). + // + template <typename T> value& operator= (T); + value& operator= (const char* v) {return *this = std::string (v);} - bool - null () const {return *p == nullptr;} + template <typename T> value& operator+= (T); + value& operator+= (const char* v) {return *this += std::string (v);} - bool - empty () const; + private: + explicit value (const value&) = default; + + public: + const value_type* type; // NULL means this value is not (yet) typed. - explicit operator bool () const {return defined () && !null ();} - explicit operator value_ptr& () const {return *p;} + bool null () const {return state_ == state_type::null;} + bool empty () const {return state_ == state_type::empty;} - // Get interface. See available specializations below. + explicit operator bool () const {return !null ();} + + // Raw data read interface. // - template <typename T> - T - as () const; + using const_iterator = names::const_iterator; + + const_iterator begin () const {return data_.begin ();} + const_iterator end () const {return data_.end ();} - // Assign. + // Raw data write interface. Note that it triggers callbacks for + // typed values. Variable is passed for diagnostics. // - const value_proxy& - operator= (value_ptr) const; + void + assign (names, const variable&); - const value_proxy& - operator= (const value_proxy&) const; + void + append (names, const variable&); - const value_proxy& - operator= (list_value) const; + public: + // Don't use directly except in the implementation of representation + // types, in which case take care to update state. + // + enum class state_type {null, empty, filled} state_; + names data_; + }; - const value_proxy& - operator= (std::string) const; + //@@ Right now we assume that for each value type each value has a + // unique representation. This is currently not the case for map. + // + inline bool + operator== (const value& x, const value& y) + { + return x.state_ == y.state_ && x.data_ == y.data_; + } + + inline bool + operator!= (const value& x, const value& y) {return !(x == y);} + + // lookup + // + // A variable can be undefined, NULL, or contain a (potentially + // empty) value. + // + struct variable_map; - const value_proxy& - operator= (dir_path) const; + template <typename V> + struct lookup + { + V* value; + const variable_map* vars; - const value_proxy& - operator= (std::nullptr_t) const; + bool + defined () const {return value != nullptr;} - // Append. + // Note: returns true if defined and not NULL. // - const value_proxy& - operator+= (const value_proxy&) const; + explicit operator bool () const {return defined () && !value->null ();} - const value_proxy& - operator+= (const list_value&) const; - - const value_proxy& - operator+= (std::string) const; // Append simple name to list_value. + V& operator* () const {return *value;} + V* operator-> () const {return value;} // Return true if this value belongs to the specified scope or target. // Note that it can also be a target type/pattern-specific value. @@ -172,92 +182,395 @@ namespace build bool belongs (const T& x) const {return vars == &x.vars;} - // Implementation details. - // - const variable_map* vars; // Variable map to which this value belongs. - - value_proxy (): vars (nullptr), p (nullptr) {} - value_proxy (value_ptr* p, const variable_map* v) - : vars (p != nullptr ? v : nullptr), p (p) {} + lookup (): value (nullptr), vars (nullptr) {} + lookup (V* v, const variable_map* vs) + : value (v), vars (v != nullptr ? vs : nullptr) {} template <typename T> - value_proxy (value_ptr& p, const T& x) - : value_proxy (&p, &x.vars) {} + lookup (V& v, const T& x): lookup (&v, &x.vars) {} + }; + + // Representation types. + // + template <typename T> struct value_traits; + + // Assign value type to the value. + // + template <typename T> + void assign (value&, const variable&); + void assign (value&, const value_type*, const variable&); - // @@ To do this properly we seem to need ro_value_proxy? + template <typename T> typename value_traits<T>::type as (value&); + template <typename T> typename value_traits<T>::const_type as (const value&); + + // "Assign" simple value type to the value stored in name. Return false + // if the value is not valid for this type. + // + template <typename T> bool assign (name&); + + template <typename T> typename value_traits<T>::type as (name&); + template <typename T> typename value_traits<T>::const_type as (const name&); + + // bool + // + template <typename D> + struct bool_value + { + explicit + operator bool () const {return d->value[0] == 't';} + + bool_value& + operator= (bool v) + { + d->value = v ? "true" : "false"; + return *this; + } + + bool_value& + operator+= (bool v) + { + if (!*this && v) + d->value = "true"; + return *this; + } + + // Implementation details. // - value_proxy (const value_ptr* p, const variable_map* v) - : value_proxy (const_cast<value_ptr*> (p), v) {} + public: + explicit bool_value (D& d): d (&d) {} - template <typename T> - value_proxy (const value_ptr& p, const T& x) - : value_proxy (const_cast<value_ptr&> (p), x) {} + bool_value (const bool_value&) = delete; + bool_value& operator= (const bool_value&) = delete; // Rebind or deep? - void - rebind (const value_proxy& x) {vars = x.vars; p = x.p;} + bool_value (bool_value&&) = default; + bool_value& operator= (bool_value&&) = delete; - private: - value_ptr* p; + D* d; // name }; template <> - inline value& value_proxy:: - as<value&> () const {return **p;} + struct value_traits<bool> + { + using type = bool_value<name>; + using const_type = bool_value<const name>; - template <> - inline const value& value_proxy:: - as<const value&> () const {return **p;} + static type as (name& n) {return type (n);} + static const_type as (const name& n) {return const_type (n);} + + static type as (value&); + static const_type as (const value&); + + static bool assign (name&); + static void assign (value&, bool); + static void append (value&, bool); + + static const build::value_type value_type; + }; + + extern const value_type* bool_type; + // string + // template <> - inline list_value& value_proxy:: - as<list_value&> () const + struct value_traits<std::string> { - list_value* lv (dynamic_cast<list_value*> (p->get ())); - assert (lv != nullptr); - return *lv; - } + using type = std::string&; + using const_type = const std::string&; - template <> - inline const list_value& value_proxy:: - as<const list_value&> () const {return as<list_value&> ();} + static type as (name& n) {return n.value;} + static const_type as (const name& n) {return n.value;} + + static type as (value&); + static const_type as (const value&); + + static bool assign (name&); + static void assign (value&, std::string); + static void append (value&, std::string); + + static const build::value_type value_type; + }; + extern const value_type* string_type; + + // dir_path + // template <> - inline const name* value_proxy:: - as<const name*> () const + struct value_traits<dir_path> { - const auto& lv (as<const list_value&> ()); - assert (lv.size () < 2); - return lv.empty () ? nullptr : &lv.front (); - } + using type = dir_path&; + using const_type = const dir_path&; + + static type as (name& n) {return n.dir;} + static const_type as (const name& n) {return n.dir;} + + static type as (value&); + static const_type as (const value&); + + static bool assign (name&); + static void assign (value&, dir_path); + static void append (value&, dir_path); + static const build::value_type value_type; + }; + + extern const value_type* dir_path_type; + + // name + // template <> - inline const name& value_proxy:: - as<const name&> () const + struct value_traits<name> { - const auto& lv (as<const list_value&> ()); - assert (lv.size () == 1); - return lv.front (); - } + using type = name&; + using const_type = const name&; - template <> - std::string& value_proxy:: - as<std::string&> () const; + static type as (name& n) {return n;} + static const_type as (const name& n) {return n;} - template <> - const std::string& value_proxy:: - as<const std::string&> () const; + static type as (value&); + static const_type as (const value&); - template <> - dir_path& value_proxy:: - as<dir_path&> () const; + static bool assign (name&) {return true;} + static void assign (value&, name); + static void append (value&, name) = delete; - template <> - const dir_path& value_proxy:: - as<const dir_path&> () const; + static const build::value_type value_type; + }; - template <> - bool value_proxy:: - as<bool> () const; + extern const value_type* name_type; + + // vector<T> + // + template <typename T, typename D> + struct vector_value + { + using size_type = typename D::size_type; + + using value_type = typename value_traits<T>::type; + using const_value_type = typename value_traits<T>::const_type; + + template <typename I, typename V, typename R> + struct iterator_impl: I + { + using value_type = V; + using pointer = value_type*; + using reference = R; + using difference_type = typename I::difference_type; + + iterator_impl () = default; + iterator_impl (const I& i): I (i) {} + + // Note: operator->() is unavailable if R is a value. + // + reference operator* () const {return as<T> (I::operator* ());} + pointer operator-> () const {return &as<T> (I::operator* ());} + reference operator[] (difference_type n) const + { + return as<T> (I::operator[] (n)); + } + }; + + // Make iterator the same as const_iterator if our data type is const. + // + using const_iterator = + iterator_impl<names::const_iterator, const T, const_value_type>; + using iterator = typename std::conditional< + std::is_const<D>::value, + const_iterator, + iterator_impl<names::iterator, T, value_type>>::type; + + public: + vector_value& + operator= (std::vector<T> v) {assign (std::move (v)); return *this;} + + vector_value& + assign (std::vector<T>); + + template <typename D1> + vector_value& + assign (const vector_value<T, D1>&); + + template <typename D1> + vector_value& + append (const vector_value<T, D1>&); + + public: + bool empty () const {return d->empty ();} + size_type size () const {return d->size ();} + + value_type operator[] (size_type i) {return as<T> ((*d)[i]);} + const_value_type operator[] (size_type i) const {return as<T> ((*d)[i]);} + + iterator begin () {return iterator (d->begin ());} + iterator end () {return iterator (d->end ());} + + const_iterator begin () const {return const_iterator (d->begin ());} + const_iterator end () const {return const_iterator (d->end ());} + + const_iterator cbegin () const {return const_iterator (d->begin ());} + const_iterator cend () const {return const_iterator (d->end ());} + + // Implementation details. + // + public: + explicit vector_value (D& d): d (&d) {} + + vector_value (const vector_value&) = delete; + vector_value& operator= (const vector_value&) = delete; // Rebind or deep? + + vector_value (vector_value&&) = default; + vector_value& operator= (vector_value&&) = default; //@@ TMP + + explicit vector_value (std::nullptr_t): d (nullptr) {} //@@ TMP + + D* d; // names + }; + + template <typename T> + struct value_traits<std::vector<T>> + { + using type = vector_value<T, names>; + using const_type = vector_value<T, const names>; + + static type as (value&); + static const_type as (const value&); + + template <typename V> static void assign (value&, V); + template <typename V> static void append (value&, V); + + static const build::value_type value_type; + }; + + template <typename T, typename D> + struct value_traits<vector_value<T, D>>: value_traits<std::vector<T>> {}; + + using strings_value = vector_value<std::string, names>; + using const_strings_value = vector_value<std::string, const names>; + + extern const value_type* strings_type; // vector<string> aka strings + extern const value_type* dir_paths_type; // vector<dir_path> aka dir_paths + extern const value_type* names_type; // vector<name> aka names + + // map<K, V> + // + template <typename K, typename V, typename D> + struct map_value + { + template <typename F, typename S> + struct pair + { + using first_type = typename std::conditional< + std::is_reference<F>::value, + std::reference_wrapper<typename std::remove_reference<F>::type>, + F>::type; + + using second_type = typename std::conditional< + std::is_reference<S>::value, + std::reference_wrapper<typename std::remove_reference<S>::type>, + S>::type; + + first_type first; + second_type second; + }; + + template <typename I, typename T> + struct iterator_impl + { + using value_type = T; + using pointer = value_type*; + using reference = value_type; // Note: value. + using difference_type = typename I::difference_type; + using iterator_category = std::bidirectional_iterator_tag; + + iterator_impl () = default; + iterator_impl (const I& i): i_ (i) {} + + pointer operator-> () const = delete; + reference operator* () const + { + return value_type {as<K> (*i_), as<V> (*(i_ + 1))}; + } + + iterator_impl& operator++ () {i_ += 2; return *this;} + iterator_impl operator++ (int) {auto r (*this); operator++ (); return r;} + + iterator_impl& operator-- () {i_ -= 2; return *this;} + iterator_impl operator-- (int) {auto r (*this); operator-- (); return r;} + + bool operator== (const iterator_impl& y) const {return i_ == y.i_;} + bool operator!= (const iterator_impl& y) const {return i_ != y.i_;} + + private: + I i_; + }; + + using size_type = typename D::size_type; + + using value_type = pair<typename value_traits<K>::const_type, + typename value_traits<V>::type>; + + using const_value_type = pair<typename value_traits<K>::const_type, + typename value_traits<V>::const_type>; + + // Make iterator the same as const_iterator if our data type is const. + // + using const_iterator = + iterator_impl<names::const_iterator, const_value_type>; + using iterator = typename std::conditional< + std::is_const<D>::value, + const_iterator, + iterator_impl<names::iterator, value_type>>::type; + + + public: + map_value& + operator= (std::map<K, V> m) {assign (std::move (m)); return *this;} + + map_value& + assign (std::map<K, V>); + + bool empty () const {return d->empty ();} + size_type size () const {return d->size ();} + + iterator find (const K&); + const_iterator find (const K&) const; + + iterator begin () {return iterator (d->begin ());} + iterator end () {return iterator (d->end ());} + + const_iterator begin () const {return const_iterator (d->begin ());} + const_iterator end () const {return const_iterator (d->end ());} + + // Implementation details. + // + public: + explicit map_value (D& d): d (&d) {} + + map_value (const map_value&) = delete; + map_value& operator= (const map_value&) = delete; // Rebind or deep? + + map_value (map_value&&) = default; + map_value& operator= (map_value&&) = delete; + + D* d; // names + }; + + template <typename K, typename V> + struct value_traits<std::map<K, V>> + { + using type = map_value<K, V, names>; + using const_type = map_value<K, V, const names>; + + static type as (value&); + static const_type as (const value&); + + template <typename M> static void assign (value&, M); + template <typename M> static void append (value&, M); + + static const build::value_type value_type; + }; + + template <typename K, typename V, typename D> + struct value_traits<map_value<K, V, D>>: value_traits<std::map<K, V>> {}; } namespace std @@ -301,15 +614,27 @@ namespace build { // variable_pool // - struct variable_set: std::unordered_set<variable> + using variable_set_base = std::unordered_set<variable>; + struct variable_set: private variable_set_base { - // @@ Need to check/set type? - // const variable& - find (std::string name) {return *emplace (std::move (name)).first;} + find (std::string name, const build::value_type* t = nullptr, char p = '\0') + { + auto r (emplace (std::move (name), t, p)); + const variable& v (*r.first); + + // Update type? + // + if (!r.second && t != nullptr && v.type != t) + { + assert (v.type == nullptr); + const_cast<variable&> (v).type = t; // Ok, not changing the key. + } + + return v; + } - const variable& - insert (variable v) {return *emplace (std::move (v)).first;} + using variable_set_base::clear; }; extern variable_set variable_pool; @@ -318,40 +643,92 @@ namespace build // struct variable_map { - using map_type = butl::prefix_map<variable_cref, value_ptr, '.'>; + using map_type = butl::prefix_map<variable_cref, value, '.'>; using size_type = map_type::size_type; - using const_iterator = map_type::const_iterator; - const value_ptr* + template <typename I> + struct iterator_adapter: I + { + iterator_adapter () = default; + iterator_adapter (const I& i): I (i) {} + typename I::reference operator* () const; + typename I::pointer operator-> () const; + }; + + using const_iterator = iterator_adapter<map_type::const_iterator>; + + const value* find (const variable& var) const { auto i (m_.find (var)); - return i != m_.end () ? &i->second : nullptr; + const value* r (i != m_.end () ? &i->second : nullptr); + + // First access after being assigned a type? + // + if (r != nullptr && var.type != nullptr && r->type != var.type) + build::assign (const_cast<value&> (*r), var.type, var); + + return r; } - value_proxy + value* + find (const variable& var) + { + auto i (m_.find (var)); + value* r (i != m_.end () ? &i->second : nullptr); + + // First access after being assigned a type? + // + if (r != nullptr && var.type != nullptr && r->type != var.type) + build::assign (*r, var.type, var); + + return r; + } + + lookup<const value> operator[] (const variable& var) const { - return value_proxy (find (var), this); + return lookup<const value> (find (var), this); } - value_proxy + lookup<const value> operator[] (const std::string& name) const { return operator[] (variable_pool.find (name)); } - // The second member in the pair indicates whether new (NULL) - // value was set. + // Non-const lookup. Only exposed on the map directly. // - std::pair<value_proxy, bool> + lookup<value> + operator[] (const variable& var) + { + return lookup<value> (find (var), this); + } + + lookup<value> + operator[] (const std::string& name) + { + return operator[] (variable_pool.find (name)); + } + + // The second member in the pair indicates whether the new + // value (which will be NULL) was assigned. + // + std::pair<std::reference_wrapper<value>, bool> assign (const variable& var) { - auto r (m_.emplace (var, value_ptr ())); - return std::make_pair (value_proxy (&r.first->second, this), r.second); + auto r (m_.emplace (var, value (var.type))); + value& v (r.first->second); + + // First access after being assigned a type? + // + if (!r.second && var.type != nullptr && v.type != var.type) + build::assign (v, var.type, var); + + return std::make_pair (std::reference_wrapper<value> (v), r.second); } - std::pair<value_proxy, bool> + std::pair<std::reference_wrapper<value>, bool> assign (const std::string& name) { return assign (variable_pool.find (name)); @@ -360,7 +737,9 @@ namespace build std::pair<const_iterator, const_iterator> find_namespace (const std::string& ns) const { - return m_.find_prefix (variable_pool.find (ns)); + auto r (m_.find_prefix (variable_pool.find (ns))); + return std::make_pair (const_iterator (r.first), + const_iterator (r.second)); } const_iterator @@ -381,17 +760,17 @@ namespace build // Target type/pattern-specific variables. // + // @@ In quite a few places we assume that we can store a reference + // to the returned value (e.g., install::lookup_install()). If + // we "instantiate" the value on the fly, then we will need to + // consider its lifetime. + // using variable_pattern_map = std::map<std::string, variable_map>; using variable_type_map = std::map<std::reference_wrapper<const target_type>, variable_pattern_map>; - - //@@ In quite a few places we assume that we can store a reference - // to the returned value (e.g., install::lookup_install()). If - // we "instantiate" the value on the fly, then we will need to - // consider its lifetime. - } #include <build/variable.ixx> +#include <build/variable.txx> #endif // BUILD_VARIABLE |