// file : build2/variable -*- C++ -*- // copyright : Copyright (c) 2014-2016 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file #ifndef BUILD2_VARIABLE #define BUILD2_VARIABLE #include #include #include // nullptr_t #include // pair, make_pair() #include #include // hash #include // conditional, is_reference, remove_reference, etc. #include #include #include #include #include namespace build2 { 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 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 v) {return *this = v.get ();} value& operator= (reference_wrapper v) {return *this = v.get ();} value& append (value, const variable&); // Aka operator+=(). value& prepend (value, const variable&); // Aka operator=+(). // Forwarded to the representation type's assign()/append() (see below). // template value& operator= (T); value& operator= (const char* v) {return *this = std::string (v);} template 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&); void prepend (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 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 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 lookup (V& v, const T& x): lookup (&v, &x.vars) {} }; // Representation types. // template struct value_traits; // Assign value type to the value. // template void assign (value&, const variable&); void assign (value&, const value_type*, const variable&); template typename value_traits::type as (value&); template typename value_traits::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 bool assign (name&); template typename value_traits::type as (name&); template typename value_traits::const_type as (const name&); // bool // template 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 { using type = bool_value; using const_type = bool_value; 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 build2::value_type value_type; }; extern const value_type* bool_type; // string // template <> struct value_traits { 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 build2::value_type value_type; }; extern const value_type* string_type; // dir_path // template <> struct value_traits { 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 build2::value_type value_type; }; extern const value_type* dir_path_type; // name // template <> struct value_traits { 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 build2::value_type value_type; }; extern const value_type* name_type; // vector // template struct vector_value { using size_type = typename D::size_type; using value_type = typename value_traits::type; using const_value_type = typename value_traits::const_type; template 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 (I::operator* ());} pointer operator-> () const {return &as (I::operator* ());} reference operator[] (difference_type n) const { return as (I::operator[] (n)); } }; // Make iterator the same as const_iterator if our data type is const. // using const_iterator = iterator_impl; using iterator = typename std::conditional< std::is_const::value, const_iterator, iterator_impl>::type; public: vector_value& operator= (std::vector v) {assign (std::move (v)); return *this;} vector_value& assign (std::vector); template vector_value& assign (const vector_value&); template vector_value& append (const vector_value&); public: bool empty () const {return d->empty ();} size_type size () const {return d->size ();} value_type operator[] (size_type i) {return as ((*d)[i]);} const_value_type operator[] (size_type i) const {return as ((*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 struct value_traits> { using type = vector_value; using const_type = vector_value; static type as (value&); static const_type as (const value&); template static void assign (value&, V); template static void append (value&, V); static const std::string type_name; static const build2::value_type value_type; }; template struct value_traits>: value_traits> {}; using strings_value = vector_value; using const_strings_value = vector_value; extern const value_type* strings_type; // vector aka strings extern const value_type* dir_paths_type; // vector aka dir_paths extern const value_type* names_type; // vector aka names // map // template struct map_value { template struct pair { using first_type = typename std::conditional< std::is_reference::value, std::reference_wrapper::type>, F>::type; using second_type = typename std::conditional< std::is_reference::value, std::reference_wrapper::type>, S>::type; first_type first; second_type second; }; template 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 (*i_), as (*(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::const_type, typename value_traits::type>; using const_value_type = pair::const_type, typename value_traits::const_type>; // Make iterator the same as const_iterator if our data type is const. // using const_iterator = iterator_impl; using iterator = typename std::conditional< std::is_const::value, const_iterator, iterator_impl>::type; public: map_value& operator= (std::map m) {assign (std::move (m)); return *this;} map_value& assign (std::map); 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 struct value_traits> { using type = map_value; using const_type = map_value; static type as (value&); static const_type as (const value&); template static void assign (value&, M); template static void append (value&, M); static const std::string type_name; static const build2::value_type value_type; }; template struct value_traits>: value_traits> {}; } namespace std { template <> struct hash: hash { size_t operator() (const build2::variable& v) const noexcept { return hash::operator() (v.name); } }; } namespace butl { template <> struct compare_prefix: compare_prefix { typedef compare_prefix base; explicit compare_prefix (char d): base (d) {} bool operator() (const build2::variable& x, const build2::variable& y) const { return base::operator() (x.name, y.name); } bool prefix (const build2::variable& p, const build2::variable& k) const { return base::prefix (p.name, k.name); } }; } namespace build2 { // variable_pool // using variable_pool_base = std::unordered_set; struct variable_pool: private variable_pool_base { const variable& find (string name, const build2::value_type* t = nullptr, char p = '\0') { return find (name, nullptr, t, p); } const variable& find (string name, variable_visibility v, const build2::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 build2::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 (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 (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; using size_type = map_type::size_type; template 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; 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) build2::assign (const_cast (*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) build2::assign (*r, var.type, var); return r; } lookup operator[] (const variable& var) const { return lookup (find (var), this); } lookup operator[] (const std::string& name) const { return operator[] (var_pool.find (name)); } // Non-const lookup. Only exposed on the map directly. // lookup operator[] (const variable& var) { return lookup (find (var), this); } lookup 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, 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) build2::assign (v, var.type, var); return std::make_pair (std::reference_wrapper (v), r.second); } std::pair, bool> assign (const std::string& name, const build2::value_type* type = nullptr) { return assign (var_pool.find (name, type)); } std::pair 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; struct variable_type_map: std::map, variable_pattern_map> { build2::lookup lookup (const target_type&, const string& name, const variable&) const; }; } #include #include #endif // BUILD2_VARIABLE