// file : build/variable -*- C++ -*- // copyright : Copyright (c) 2014-2015 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file #ifndef BUILD_VARIABLE #define BUILD_VARIABLE #include <map> #include <vector> #include <cstddef> // nullptr_t #include <utility> // pair, make_pair() #include <iterator> #include <functional> // hash #include <type_traits> // conditional, is_reference, remove_reference, etc. #include <unordered_set> #include <butl/prefix-map> #include <build/types> #include <build/utility> #include <build/target-type> namespace build { 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 { const char* name; bool (*const assign) (names&, const variable&); bool (*const append) (names&, names, const variable&); }; enum class variable_visibility { scope, // This scope (no outer scopes). project, // This project (no outer projects). normal // All outer scopes. }; // variable // // The two variables are considered the same if they have the same name. // struct variable { std::string name; const value_type* type; // If NULL, then not (yet) typed. variable_visibility visibility; char pairs; // Pair symbold or '\0' if not used. }; inline bool operator== (const variable& x, const variable& y) {return x.name == y.name;} typedef std::reference_wrapper<const variable> variable_cref; // value // class value { public: // By default we create NULL value. // explicit value (const value_type* t = nullptr) : type (t), state_ (state_type::null) {} value (value&&) = default; value& operator= (std::nullptr_t) { data_.clear (); state_ = state_type::null; return *this; } value& operator= (value&&); value& operator= (const value& v) { if (&v != this) *this = value (v); return *this; } value& operator= (reference_wrapper<value> v) {return *this = v.get ();} value& operator= (reference_wrapper<const value> v) {return *this = v.get ();} value& append (value, const variable&); // Aka operator+=(). // 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);} template <typename T> value& operator+= (T); value& operator+= (const char* v) {return *this += std::string (v);} private: explicit value (const value&) = default; public: const value_type* type; // NULL means this value is not (yet) typed. bool null () const {return state_ == state_type::null;} bool empty () const {return state_ == state_type::empty;} explicit operator bool () const {return !null ();} bool operator== (std::nullptr_t) const {return null ();} bool operator!= (std::nullptr_t) const {return !null ();} // Raw data read interface. // using const_iterator = names::const_iterator; const_iterator begin () const {return data_.begin ();} const_iterator end () const {return data_.end ();} // Raw data write interface. Note that it triggers callbacks for // typed values. Variable is passed for diagnostics. // void assign (names, const variable&); void append (names, const variable&); 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_; }; //@@ 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; template <typename V> struct lookup { V* value; const variable_map* vars; bool defined () const {return value != nullptr;} // Note: returns true if defined and not NULL. // explicit operator bool () const {return defined () && !value->null ();} 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. // template <typename T> bool belongs (const T& x) const {return vars == &x.vars;} lookup (): value (nullptr), vars (nullptr) {} lookup (V* v, const variable_map* vs) : value (v), vars (v != nullptr ? vs : nullptr) {} template <typename T> 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&); 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. // public: explicit bool_value (D& d): d (&d) {} bool_value (const bool_value&) = delete; bool_value& operator= (const bool_value&) = delete; // Rebind or deep? bool_value (bool_value&&) = default; bool_value& operator= (bool_value&&) = delete; D* d; // name }; template <> struct value_traits<bool> { using type = bool_value<name>; using const_type = bool_value<const name>; 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 <> struct value_traits<std::string> { using type = std::string&; using const_type = const std::string&; 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 <> struct value_traits<dir_path> { 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 <> struct value_traits<name> { using type = name&; using const_type = const name&; static type as (name& n) {return n;} static const_type as (const name& n) {return n;} static type as (value&); static const_type as (const value&); static bool assign (name&) {return true;} static void assign (value&, name); static void append (value&, name) = delete; static const build::value_type value_type; }; 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 std::string type_name; 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 std::string type_name; 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 { template <> struct hash<build::variable>: hash<string> { size_t operator() (const build::variable& v) const noexcept { return hash<string>::operator() (v.name); } }; } namespace butl { template <> struct compare_prefix<build::variable_cref>: compare_prefix<std::string> { typedef compare_prefix<std::string> base; explicit compare_prefix (char d): base (d) {} bool operator() (const build::variable& x, const build::variable& y) const { return base::operator() (x.name, y.name); } bool prefix (const build::variable& p, const build::variable& k) const { return base::prefix (p.name, k.name); } }; } namespace build { // variable_pool // using variable_pool_base = std::unordered_set<variable>; struct variable_pool: private variable_pool_base { const variable& find (string name, const build::value_type* t = nullptr, char p = '\0') { return find (name, nullptr, t, p); } const variable& find (string name, variable_visibility v, const build::value_type* t = nullptr, char p = '\0') { return find (name, &v, t, p); } using variable_pool_base::clear; private: const variable& find (string name, const variable_visibility* vv, const build::value_type* t, char p) { auto r ( insert ( variable { std::move (name), t, vv != nullptr ? *vv : variable_visibility::normal, 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; // Not changing the key. } // Change visibility? While this might at first seem like a bad idea, // it can happen that the variable lookup happens before any values // were set, in which case the variable will be entered with the // default visibility. // if (!r.second && vv != nullptr && v.visibility != *vv) { assert (v.visibility == variable_visibility::normal); // Default. const_cast<variable&> (v).visibility = *vv; // Not changing the key. } return v; } }; extern variable_pool var_pool; // variable_map // struct variable_map { using map_type = butl::prefix_map<variable_cref, value, '.'>; using size_type = map_type::size_type; 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)); 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* 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 lookup<const value> (find (var), this); } lookup<const value> operator[] (const std::string& name) const { return operator[] (var_pool.find (name)); } // Non-const lookup. Only exposed on the map directly. // lookup<value> operator[] (const variable& var) { return lookup<value> (find (var), this); } lookup<value> operator[] (const std::string& name) { return operator[] (var_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 (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<std::reference_wrapper<value>, bool> assign (const std::string& name) { return assign (var_pool.find (name)); } std::pair<const_iterator, const_iterator> find_namespace (const std::string& ns) const { auto r (m_.find_prefix (var_pool.find (ns))); return std::make_pair (const_iterator (r.first), const_iterator (r.second)); } const_iterator begin () const {return m_.begin ();} const_iterator end () const {return m_.end ();} bool empty () const {return m_.empty ();} size_type size () const {return m_.size ();} private: map_type m_; }; // 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>; struct variable_type_map: std::map<std::reference_wrapper<const target_type>, variable_pattern_map> { build::lookup<const value> lookup (const target_type&, const string& name, const variable&) const; }; } #include <build/variable.ixx> #include <build/variable.txx> #endif // BUILD_VARIABLE