// 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 // hash #include // aligned_storage #include #include #include #include #include namespace build2 { class value; struct variable; struct lookup; struct value_type { const char* name; // Type name for diagnostics. const size_t size; // Type size in value::data_ (only used for PODs). // Base type, if any. We have very limited support for inheritance: a // const value (but not non-const) can be cast to the base type. In // particular, a derived/base value cannot be assigned to base/derived. // If not NULL, then the cast function below is expected to return the // base pointer if its second argument points to the base's value_type. // const value_type* base; // Destroy the value. If it is NULL, then the type is assumed to be POD // with a trivial destructor. // void (*const dtor) (value&); // Copy/move constructor and copy/move assignment for data_. If NULL, then // assume the stored data is POD. If move is true then the second argument // can be const_cast and moved from. copy_assign() is only called with // non-NULL first argument. // void (*const copy_ctor) (value&, const value&, bool move); void (*const copy_assign) (value&, const value&, bool move); // While assign cannot be NULL, if append or prepend is NULL, then this // means this type doesn't support this operation. Variable is optional // and is provided only for diagnostics. Return true if the resulting // value is not empty. // bool (*const assign) (value&, names&&, const variable*); bool (*const append) (value&, names&&, const variable*); bool (*const prepend) (value&, names&&, const variable*); // Reverse the value back to a vector of names. Storage can be used by the // implementation if necessary. Cannot be NULL. // names_view (*const reverse) (const value&, names& storage); // Cast value::data_ storage to value type so that the result can be // static_cast to const T*. If it is NULL, then cast data_ directly. Note // that this function is used for both const and non-const values. // const void* (*const cast) (const value&, const value_type*); // If NULL, then the types are compared as PODs using memcmp(). // int (*const compare) (const value&, const value&); }; 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. // // If the variable is overridden on the command line, then override is the // chain of the special override variables. Their names are derived from the // main variable name as .{__override,__prefix,__suffix} and they are // not entered into the var_pool. The override variables only vary in their // names and visibility. // // Note also that we don't propagate the variable type to override variables // and we keep override values as untyped names. They get "typed" when they // are applied. // // We use the "modify original, override on query" model. Because of that, a // modified value does not necessarily represent the actual value so care // must be taken to re-query after (direct) modification. And because of // that, variables set by the C++ code are by default non-overridable. // // Initial processing including entering of global overrides happens in // reset() before any other variables. Project wide overrides are entered in // main(). Overriding happens in scope::find_override(). // struct variable { string name; mutable const value_type* type; // If NULL, then not (yet) typed. mutable unique_ptr override; variable_visibility visibility; }; inline bool operator== (const variable& x, const variable& y) {return x.name == y.name;} typedef reference_wrapper variable_cref; // value // enum class value_state: uint8_t {null, empty, filled}; class value { public: const value_type* type; // NULL means this value is not (yet) typed. value_state state; // Extra data that is associated with the value that can be used to store // flags, etc. It is initialized to 0 and copied (but not assigned) from // one value to another but is otherwise untouched (not even when the // value is reset to NULL). // uint16_t extra; bool null () const {return state == value_state::null;} bool empty () const {return state == value_state::empty;} explicit operator bool () const {return !null ();} bool operator== (nullptr_t) const {return null ();} bool operator!= (nullptr_t) const {return !null ();} // Creation. A default-initialzied value is NULL and can be reset back to // NULL by assigning nullptr. Values can be copied and copy-assigned. Note // that for assignment, the values' types should be the same or LHS should // be untyped. // // public: ~value () {*this = nullptr;} explicit value (const value_type* t = nullptr) : type (t), state (value_state::null), extra (0) {} explicit value (names&&); // Create untyped value. // Note: preserves type. // value& operator= (nullptr_t) {if (!null ()) reset (); return *this;} value (value&&); explicit value (const value&); value& operator= (value&&); value& operator= (const value&); value& operator= (reference_wrapper); value& operator= (reference_wrapper); // Assign/Append/Prepend. // public: // Assign/append a typed value. For assign, LHS should be either of the // same type or untyped. For append, LHS should be either of the same type // or untyped and NULL. // template value& operator= (T); template value& operator+= (T); template value& operator+= (T* v) { return v != nullptr ? *this += *v : *this;} value& operator= (const char* v) {return *this = string (v);} value& operator+= (const char* v) {return *this += string (v);} // Assign/append/prepend raw data. Variable is optional and is only used // for diagnostics. // void assign (names&&, const variable*); void append (names&&, const variable*); void prepend (names&&, const variable*); // Implementation details, don't use directly except in representation // type implementations. // public: // Fast, unchecked cast of data_ to T. // template T& as () & {return reinterpret_cast (data_);} template T&& as () && {return reinterpret_cast (data_);} template const T& as () const& { return reinterpret_cast (data_);} public: // The maximum size we can store directly in the value is that of a name, // which is sufficient for the most commonly used types (string, vector, // map) on all the platforms that we support (each type should static // assert this in its value_traits specialization below). Types that don't // fit will have to be handled with an extra dynamic allocation. // std::aligned_storage::type data_; static const size_t size_ = sizeof (data_); // Make sure we have sufficient storage for untyped values. // static_assert (sizeof (names) <= size_, "insufficient space"); private: void reset (); }; // The values should be of the same type (or both be untyped) except NULL // values can also be untyped. NULL values compare equal. // bool operator== (const value&, const value&); bool operator!= (const value&, const value&); // Value cast. The first three expect the value to be not NULL. The cast // from lookup expects the value to aslo be defined. // // Why are these non-members? The cast is easier on the eyes and is also // consistent with the cast operators. The other two are for symmetry. // // template T& cast (value&); template T&& cast (value&&); template const T& cast (const value&); template const T& cast (const lookup&); // As above but returns NULL if the value is NULL (or not defined, in // case of lookup). // template T* cast_null (value&); template const T* cast_null (const value&); template const T* cast_null (const lookup&); // Assign value type to the value. In the second version the variable is // optional and is only used for diagnostics. // template void typify (value&, const variable&); void typify (value&, const value_type&, const variable*); // Reverse the value back to names. The value should no be NULL and storage // should be empty. // names_view reverse (const value&, names& storage); vector_view reverse (value&, names& storage); // lookup // // A variable can be undefined, NULL, or contain a (potentially empty) // value. // struct variable_map; struct lookup { using value_type = build2::value; const value_type* 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 ();} const value_type& operator* () const {return *value;} const value_type* 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 (in // which case it won't belong to either). // template bool belongs (const T& x) const {return vars == &x.vars;} lookup (): value (nullptr), vars (nullptr) {} template lookup (const value_type& v, const T& x): lookup (&v, &x.vars) {} lookup (const value_type& v, const variable_map& vs) : value (&v), vars (&vs) {} lookup (const value_type* v, const variable_map* vs) : value (v), vars (v != nullptr ? vs : nullptr) {} }; // Two lookups are equal if they point to the same variable. // inline bool operator== (const lookup& x, const lookup& y) { bool r (x.value == y.value); assert (!r || x.vars == y.vars); return r; } inline bool operator!= (const lookup& x, const lookup& y) {return !(x == y);} // Representation types. // // Potential optimizations: // // - Split value::operator=/+=() into const T and T&&, also overload // value_traits functions that they call. // // - Specialization for vector (if used and becomes critical). // // template struct value_traits; // { // static_assert (sizeof (T) <= value::size_, "insufficient space"); // // // Convert name to T. If rhs is not NULL, then it is the second half // // of a pair. Only needs to be provided by simple types. Throw // // invalid_argument (without a message) if the name is not a valid // // representation of value (in which case the name should remain // // unchanged for diagnostics). // // // static T convert (name&&, name* rhs); // // // Assign/append/prepend T to value and return true if the result is // // not empty. Value is already of type T but can be NULL. // // // static bool assign (value&, T&&); // static bool append (value&, T&&); // static bool prepend (value&, T&&); // // // Reverse a value back to name. Only needs to be provided by simple // // types. // // // static name reverse (const T&); // // // Compare two values. Only needs to be provided by simple types. // // // static int compare (const T&, const T&); // // static const build2::value_type value_type; // }; // Convert name to a simple value. Throw invalid_argument (without any // message) if the name is not a valid representation of value (in which // case the name remains unchanged for diagnostics). The second version is // called for a pair. // template T convert (name&&); template T convert (name&&, name&&); // Default implementations of the dtor/copy_ctor/copy_assing callbacks for // types that are stored directly in value::data_ and the provide all the // necessary functions (copy/move ctor and assignment operator). // template static void default_dtor (value&); template static void default_copy_ctor (value&, const value&, bool); template static void default_copy_assign (value&, const value&, bool); // Default implementations of the assign/append/prepend callbacks for simple // types. They call value_traits::convert() and then pass the result to // value_traits::assign()/append()/prepend(). As a result, it may not be // the most efficient way to do it. If the empty template parameter is true, // then an empty names sequence is converted to a default-constructed T. And // if false, then an empty value is not allowed. // template static bool simple_assign (value&, names&&, const variable*); template static bool simple_append (value&, names&&, const variable*); template static bool simple_prepend (value&, names&&, const variable*); // Default implementations of the reverse callback for simple types that // calls value_traits::reverse() and adds the result to the vector. As a // result, it may not be the most efficient way to do it. // template static names_view simple_reverse (const value&, names&); // Default implementations of the compare callback for simple types that // calls value_traits::compare(). // template static int simple_compare (const value&, const value&); // bool // template <> struct value_traits { static_assert (sizeof (bool) <= value::size_, "insufficient space"); static bool convert (name&&, name*); static bool assign (value&, bool); static bool append (value&, bool); // OR. static name reverse (bool x) {return name (x ? "true" : "false");} static int compare (bool, bool); static const build2::value_type value_type; }; template <> struct value_traits { static_assert (sizeof (uint64_t) <= value::size_, "insufficient space"); static uint64_t convert (name&&, name*); static bool assign (value&, uint64_t); static bool append (value&, uint64_t); // ADD. static name reverse (uint64_t x) {return name (to_string (x));} static int compare (uint64_t, uint64_t); static const build2::value_type value_type; }; // string // template <> struct value_traits { static_assert (sizeof (string) <= value::size_, "insufficient space"); static string convert (name&&, name*); static bool assign (value&, string&&); static bool append (value&, string&&); static bool prepend (value&, string&&); static name reverse (const string& x) {return name (x);} static int compare (const string&, const string&); static const build2::value_type value_type; }; // path // template <> struct value_traits { static_assert (sizeof (path) <= value::size_, "insufficient space"); static path convert (name&&, name*); static bool assign (value&, path&&); static bool append (value&, path&&); // operator/ static bool prepend (value&, path&&); // operator/ static name reverse (const path& x) {return name (x.string ());} static int compare (const path&, const path&); static const build2::value_type value_type; }; // dir_path // template <> struct value_traits { static_assert (sizeof (dir_path) <= value::size_, "insufficient space"); static dir_path convert (name&&, name*); static bool assign (value&, dir_path&&); static bool append (value&, dir_path&&); // operator/ static bool prepend (value&, dir_path&&); // operator/ static name reverse (const dir_path& x) {return name (x);} static int compare (const dir_path&, const dir_path&); static const build2::value_type value_type; }; // abs_dir_path // template <> struct value_traits { static_assert (sizeof (abs_dir_path) <= value::size_, "insufficient space"); static abs_dir_path convert (name&&, name*); static bool assign (value&, abs_dir_path&&); static bool append (value&, abs_dir_path&&); // operator/ static name reverse (const abs_dir_path& x) {return name (x);} static int compare (const abs_dir_path&, const abs_dir_path&); static const build2::value_type value_type; }; // name // template <> struct value_traits { static_assert (sizeof (name) <= value::size_, "insufficient space"); static name convert (name&&, name*); static bool assign (value&, name&&); static bool append (value&, name&&); static bool prepend (value&, name&&); static name reverse (const name& x) {return x;} static int compare (const name&, const name&); static const build2::value_type value_type; }; // vector // template struct value_traits> { static_assert (sizeof (vector) <= value::size_, "insufficient space"); static bool assign (value&, vector&&); static bool append (value&, vector&&); static bool prepend (value&, vector&&); static const string type_name; static const build2::value_type value_type; }; // map // template struct value_traits> { template using map = std::map; static_assert (sizeof (map) <= value::size_, "insufficient space"); static bool assign (value&, map&&); static bool append (value&, map&&); static bool prepend (value& v, map&& x) { return append (v, move (x));} static const string type_name; static const build2::value_type value_type; }; } // Variable map. // 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& insert (string name, bool overridable = false, variable_visibility v = variable_visibility::normal) { return insert (move (name), nullptr, v, overridable); } template const variable& insert (string name, bool overridable = false, variable_visibility v = variable_visibility::normal) { return insert ( move (name), &value_traits::value_type, v, overridable); } const variable& find (const string& name); using variable_pool_base::clear; private: const variable& insert (string name, const build2::value_type*, variable_visibility, bool overridable); }; 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; // Lookup. Note that variable overrides will not be applied, even if // set in this map. // lookup operator[] (const variable& var) const { return lookup (find (var), this); } lookup operator[] (const string& name) const { return operator[] (var_pool.find (name)); } const value* find (const variable&) const; value* find (const variable&); // The second member in the pair indicates whether the new value (which // will be NULL) was assigned. // pair, bool> assign (const variable&); pair, bool> assign (const string& name) { return assign (var_pool.find (name)); } // Unlike the two above, assign a non-overridable variable with normal // visibility. // template pair, bool> assign (string name) { return assign (var_pool.insert (move (name))); } pair find_namespace (const string& ns) const { auto r (m_.find_prefix (var_pool.find (ns))); return 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; using variable_type_map_base = std::map, variable_pattern_map>; struct variable_type_map: variable_type_map_base { lookup find (const target_type&, const string& tname, const variable&) const; }; // Override cache. // struct variable_override_value { build2::value value; const variable_map* stem_vars = nullptr; // NULL means there is no stem. }; extern std::map, variable_override_value> variable_override_cache; } #include #include #endif // BUILD2_VARIABLE