diff options
Diffstat (limited to 'build2/variable')
-rw-r--r-- | build2/variable | 831 |
1 files changed, 831 insertions, 0 deletions
diff --git a/build2/variable b/build2/variable new file mode 100644 index 0000000..6304979 --- /dev/null +++ b/build2/variable @@ -0,0 +1,831 @@ +// file : build2/variable -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_VARIABLE +#define BUILD2_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 <build2/types> +#include <build2/utility> + +#include <build2/target-type> + +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<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+=(). + + value& + prepend (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&); + + 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 <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 build2::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 build2::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 build2::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 build2::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 build2::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 build2::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<build2::variable>: hash<string> + { + size_t + operator() (const build2::variable& v) const noexcept + { + return hash<string>::operator() (v.name); + } + }; +} + +namespace butl +{ + template <> + struct compare_prefix<build2::variable_cref>: compare_prefix<std::string> + { + typedef compare_prefix<std::string> 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<variable>; + 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<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) + build2::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) + build2::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) + build2::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> + { + build2::lookup<const value> + lookup (const target_type&, const string& name, const variable&) const; + }; +} + +#include <build2/variable.ixx> +#include <build2/variable.txx> + +#endif // BUILD2_VARIABLE |