aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/variable.hxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/variable.hxx')
-rw-r--r--libbuild2/variable.hxx914
1 files changed, 727 insertions, 187 deletions
diff --git a/libbuild2/variable.hxx b/libbuild2/variable.hxx
index a671978..aed3350 100644
--- a/libbuild2/variable.hxx
+++ b/libbuild2/variable.hxx
@@ -4,20 +4,22 @@
#ifndef LIBBUILD2_VARIABLE_HXX
#define LIBBUILD2_VARIABLE_HXX
-#include <map>
-#include <set>
-#include <type_traits> // aligned_storage
+#include <cstddef> // max_align_t
+#include <type_traits> // is_*
#include <unordered_map>
-#include <libbutl/prefix-map.mxx>
-#include <libbutl/multi-index.mxx> // map_key
+#include <libbutl/prefix-map.hxx>
+#include <libbutl/multi-index.hxx> // map_key
#include <libbuild2/types.hxx>
#include <libbuild2/forward.hxx>
#include <libbuild2/utility.hxx>
+#include <libbuild2/json.hxx>
+
#include <libbuild2/context.hxx>
#include <libbuild2/target-type.hxx>
+#include <libbuild2/diagnostics.hxx>
#include <libbuild2/export.hxx>
@@ -49,7 +51,11 @@ namespace build2
template <typename T> const value_type* is_a () const;
- // Element type, if this is a vector.
+ // True if the type is a container.
+ //
+ bool container;
+
+ // Element type, if this is a container and the element type is named.
//
const value_type* element_type;
@@ -76,9 +82,11 @@ namespace build2
void (*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.
+ // implementation if necessary. If reduce is true, then for an empty
+ // simple value return an empty list rather than a list of one empty name.
+ // Note that the value cannot be NULL.
//
- names_view (*const reverse) (const value&, names& storage);
+ names_view (*const reverse) (const value&, names& storage, bool reduce);
// 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
@@ -92,7 +100,33 @@ namespace build2
// If NULL, then the value is never empty.
//
+ // Note that this is "semantically empty", not necessarily
+ // "representationally empty". For example, an empty JSON array is
+ // semantically empty but its representation (`[]`) is not.
+ //
bool (*const empty) (const value&);
+
+ // Custom subscript function. If NULL, then the generic implementation is
+ // used.
+ //
+ // Note that val can be NULL. If val_data points to val, then it can be
+ // moved from. The sloc and bloc arguments are the subscript and brace
+ // locations, respectively.
+ //
+ // Note: should normally be consistent with iterate.
+ //
+ value (*/*const*/ subscript) (const value& val,
+ value* val_data,
+ value&& subscript,
+ const location& sloc,
+ const location& bloc);
+
+ // Custom iteration function. It should invoked the specified function for
+ // each element in order. If NULL, then the generic implementation is
+ // used. The passed value is never NULL.
+ //
+ void (*const iterate) (const value&,
+ const function<void (value&&, bool first)>&);
};
// The order of the enumerators is arranged so that their integral values
@@ -108,6 +142,10 @@ namespace build2
scope, // This scope (no outer scopes).
target, // Target and target type/pattern-specific.
prereq // Prerequisite-specific.
+
+ // Note: remember to update the visibility attribute parsing if adding any
+ // new values here. As well as the $builtin.visibility() function
+ // documentation.
};
// VC14 reports ambiguity but seems to work if we don't provide any.
@@ -147,13 +185,27 @@ namespace build2
return o << to_string (v);
}
- // variable
+ // A variable.
//
- // The two variables are considered the same if they have the same name.
+ // A variable can be public, project-private, or script-private, which
+ // corresponds to the variable pool it belongs to (see variable_pool). The
+ // two variables from the same pool are considered the same if they have the
+ // same name. The variable access (public/private) rules are:
+ //
+ // - Qualified variable are by default public while unqualified -- private.
+ //
+ // - Private must have project or lesser visibility and not be overridable.
+ //
+ // - An unqualified public variable can only be pre-entered during the
+ // context construction (to make sure it is not entered as private).
+ //
+ // - There is no scope-private variables in our model due to side-loading,
+ // target type/pattern-specific append, etc.
//
// Variables can be aliases of each other in which case they form a circular
// linked list (the aliases pointer for variable without any aliases points
- // to the variable itself).
+ // to the variable itself). This mechanism should only be used for variables
+ // of the same access (normally public).
//
// If the variable is overridden on the command line, then override is the
// linked list of the special override variables. Their names are derived
@@ -200,6 +252,7 @@ namespace build2
struct variable
{
string name;
+ const variable_pool* owner;
const variable* aliases; // Circular linked list.
const value_type* type; // If NULL, then not (yet) typed.
unique_ptr<const variable> overrides;
@@ -278,7 +331,13 @@ namespace build2
// 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).
+ // value is reset to NULL) unless it is part of variable_map::value_data,
+ // in which case it is reset to 0 on each modification (version
+ // increment; however, see reset_extra flag in variable_map::insert()).
+ //
+ // (The reset on each modification semantics is used to implement the
+ // default value distinction as currently done in the config module but
+ // later probably will be done for ?= and $origin()).
//
// Note: if deciding to use for something make sure it is not overlapping
// with an existing usage.
@@ -292,6 +351,10 @@ namespace build2
// Check in a type-independent way if the value is empty. The value must
// not be NULL.
//
+ // Note that this is "semantically empty", not necessarily
+ // "representationally empty". For example, an empty JSON array is
+ // semantically empty but its representation (`[]`) is not.
+ //
bool
empty () const;
@@ -329,9 +392,13 @@ namespace build2
value&
operator= (nullptr_t) {if (!null) reset (); return *this;}
- value (value&&);
+ // Note that we have the noexcept specification even though copy_ctor()
+ // could potentially throw (for example, for std::map).
+ //
+ value (value&&) noexcept;
+
explicit value (const value&);
- value& operator= (value&&);
+ value& operator= (value&&); // Note: can throw for untyped RHS.
value& operator= (const value&);
value& operator= (reference_wrapper<value>);
value& operator= (reference_wrapper<const value>);
@@ -339,15 +406,17 @@ namespace build2
// 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.
+ // Assign/append/prepend a typed value. For assign, LHS should be either
+ // of the same type or untyped. For append/prepend, LHS should be either
+ // of the same type or untyped and NULL.
//
template <typename T> value& operator= (T);
template <typename T> value& operator+= (T);
+ template <typename T> value& prepend (T);
value& operator= (names);
value& operator+= (names);
+ //value& prepend (names); // See below.
template <typename T> value& operator= (T* v) {
return v != nullptr ? *this = *v : *this = nullptr;}
@@ -355,8 +424,12 @@ namespace build2
template <typename T> value& operator+= (T* v) {
return v != nullptr ? *this += *v : *this;}
+ template <typename T> value& prepend (T* v) {
+ return v != nullptr ? prepend (*v) : *this;}
+
value& operator= (const char* v) {return *this = string (v);}
value& operator+= (const char* v) {return *this += string (v);}
+ value& prepend (const char* v) {return prepend (string (v));}
// Assign/append/prepend raw data. Variable is optional and is only used
// for diagnostics.
@@ -384,8 +457,8 @@ namespace build2
// specialization below). Types that don't fit will have to be handled
// with an extra dynamic allocation.
//
- static constexpr size_t size_ = sizeof (name_pair);
- std::aligned_storage<size_>::type data_;
+ static constexpr size_t size_ = sizeof (name_pair);
+ alignas (std::max_align_t) unsigned char data_[size_];
// Make sure we have sufficient storage for untyped values.
//
@@ -425,38 +498,37 @@ namespace build2
template <typename T> T& cast (value&);
template <typename T> T&& cast (value&&);
template <typename T> const T& cast (const value&);
- template <typename T> const T& cast (const lookup&);
+ template <typename T> const T& cast (lookup);
// As above but returns NULL if the value is NULL (or not defined, in
// case of lookup).
//
template <typename T> T* cast_null (value&);
template <typename T> const T* cast_null (const value&);
- template <typename T> const T* cast_null (const lookup&);
+ template <typename T> const T* cast_null (lookup);
// As above but returns empty value if the value is NULL (or not defined, in
// case of lookup).
//
template <typename T> const T& cast_empty (const value&);
- template <typename T> const T& cast_empty (const lookup&);
+ template <typename T> const T& cast_empty (lookup);
// As above but returns the specified default if the value is NULL (or not
// defined, in case of lookup). Note that the return is by value, not by
// reference.
//
template <typename T> T cast_default (const value&, const T&);
- template <typename T> T cast_default (const lookup&, const T&);
+ template <typename T> T cast_default (lookup, const T&);
// As above but returns false/true if the value is NULL (or not defined,
// in case of lookup). Note that the template argument is only for
// documentation and should be bool (or semantically compatible).
//
template <typename T> T cast_false (const value&);
- template <typename T> T cast_false (const lookup&);
+ template <typename T> T cast_false (lookup);
template <typename T> T cast_true (const value&);
- template <typename T> T cast_true (const lookup&);
-
+ template <typename T> T cast_true (lookup);
// Assign value type to the value. The variable is optional and is only used
// for diagnostics.
@@ -469,20 +541,22 @@ namespace build2
typify_atomic (context&, value&, const value_type&, const variable*);
// Remove value type from the value reversing it to names. This is similar
- // to reverse() below except that it modifies the value itself.
+ // to reverse() below except that it modifies the value itself. Note that
+ // the reduce semantics applies to empty but not null.
//
- LIBBUILD2_SYMEXPORT void untypify (value&);
+ LIBBUILD2_SYMEXPORT void untypify (value&, bool reduce);
// Reverse the value back to names. The value should not be NULL and storage
- // should be empty.
+ // should be empty. If reduce is true, then for an empty simple value return
+ // an empty list rather than a list of one empty name.
//
vector_view<const name>
- reverse (const value&, names& storage);
+ reverse (const value&, names& storage, bool reduce);
vector_view<name>
- reverse (value&, names& storage);
+ reverse (value&, names& storage, bool reduce);
- // Variable lookup result, AKA, binding of a name to a value.
+ // Variable lookup result, AKA, binding of a variable to a value.
//
// A variable can be undefined, NULL, or contain a (potentially empty)
// value.
@@ -610,6 +684,9 @@ namespace build2
// static const build2::value_type value_type;
// };
+ template <typename T>
+ struct value_traits<const T>: value_traits<T> {};
+
// Convert name to a simple value. Throw invalid_argument (with a 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
@@ -622,7 +699,7 @@ namespace build2
// case (container) if invalid_argument is thrown, the names are not
// guaranteed to be unchanged.
//
- //template <typename T> T convert (names&&); (declaration causes ambiguity)
+ template <typename T> T convert (names&&);
// Convert value to T. If value is already of type T, then simply cast it.
// Otherwise call convert(names) above. If value is NULL, then throw
@@ -874,6 +951,8 @@ namespace build2
// name
//
+ // Note that a typed name is never a pattern.
+ //
template <>
struct LIBBUILD2_SYMEXPORT value_traits<name>
{
@@ -897,6 +976,9 @@ namespace build2
// half of a pair). If both are empty then this is an empty value (and not a
// pair of two empties).
//
+ // @@ Maybe we should redo this with optional<> to signify which half can
+ // be missing? See also dump_value(json).
+ //
template <>
struct LIBBUILD2_SYMEXPORT value_traits<name_pair>
{
@@ -946,8 +1028,8 @@ namespace build2
"insufficient space");
// Represented as a potential @-pair of name(s) corresponding to
- // process_path followed by the name@ and checksum@ pairs. So it's a
- // container-like.
+ // process_path optionally followed by the name@, checksum@, and
+ // env-checksum@ pairs. So it's a container-like.
//
static process_path_ex convert (names&&);
static void assign (value&, process_path_ex&&);
@@ -1004,9 +1086,67 @@ namespace build2
static const build2::value_type value_type;
};
+ // optional<T>
+ //
+ // This is an incomplete implementation meant to provide enough support only
+ // to be usable as elements of containers.
+ //
+ template <typename T>
+ struct value_traits<optional<T>>
+ {
+ static int compare (const optional<T>&, const optional<T>&);
+ };
+
+ // pair<F, S>
+ //
+ // Either F or S can be optional<T> making the corresponding half of the
+ // pair optional.
+ //
+ // This is an incomplete implementation meant to provide enough support only
+ // to be usable as elements of containers.
+ //
+ template <typename F, typename S>
+ struct pair_value_traits
+ {
+ static pair<F, S>
+ convert (name&&, name*, const char*, const char*, const variable*);
+
+ static void
+ reverse (const F&, const S&, names&);
+ };
+
+ template <typename F, typename S>
+ struct pair_value_traits<F, optional<S>>
+ {
+ static pair<F, optional<S>>
+ convert (name&&, name*, const char*, const char*, const variable*);
+
+ static void
+ reverse (const F&, const optional<S>&, names&);
+ };
+
+ template <typename F, typename S>
+ struct pair_value_traits<optional<F>, S>
+ {
+ static pair<optional<F>, S>
+ convert (name&&, name*, const char*, const char*, const variable*);
+
+ static void
+ reverse (const optional<F>&, const S&, names&);
+ };
+
+ template <typename F, typename S>
+ struct value_traits<pair<F, S>>: pair_value_traits<F, S>
+ {
+ static int compare (const pair<F, S>&, const pair<F, S>&);
+ };
+
// vector<T>
//
template <typename T>
+ struct vector_value_type;
+
+ template <typename T>
struct value_traits<vector<T>>
{
static_assert (sizeof (vector<T>) <= value::size_, "insufficient space");
@@ -1018,20 +1158,17 @@ namespace build2
static bool empty (const vector<T>& x) {return x.empty ();}
static const vector<T> empty_instance;
-
- // Make sure these are static-initialized together. Failed that VC will
- // make sure it's done in the wrong order.
- //
- struct value_type_ex: build2::value_type
- {
- string type_name;
- value_type_ex (value_type&&);
- };
- static const value_type_ex value_type;
+ static const vector_value_type<T> value_type;
};
// vector<pair<K, V>>
//
+ // Either K or V can be optional<T> making the corresponding half of the
+ // pair optional.
+ //
+ template <typename K, typename V>
+ struct pair_vector_value_type;
+
template <typename K, typename V>
struct value_traits<vector<pair<K, V>>>
{
@@ -1045,44 +1182,158 @@ namespace build2
static bool empty (const vector<pair<K, V>>& x) {return x.empty ();}
static const vector<pair<K, V>> empty_instance;
+ static const pair_vector_value_type<K, V> value_type;
+ };
- // Make sure these are static-initialized together. Failed that VC will
- // make sure it's done in the wrong order.
- //
- struct value_type_ex: build2::value_type
- {
- string type_name;
- value_type_ex (value_type&&);
- };
- static const value_type_ex value_type;
+ // set<T>
+ //
+ template <typename T>
+ struct set_value_type;
+
+ template <typename T>
+ struct value_traits<set<T>>
+ {
+ static_assert (sizeof (set<T>) <= value::size_, "insufficient space");
+
+ static set<T> convert (names&&);
+ static void assign (value&, set<T>&&);
+ static void append (value&, set<T>&&);
+ static void prepend (value&, set<T>&&);
+ static bool empty (const set<T>& x) {return x.empty ();}
+
+ static const set<T> empty_instance;
+ static const set_value_type<T> value_type;
};
// map<K, V>
//
+ // Either K or V can be optional<T> making the key or value optional.
+ //
+ // Note that append/+= is overriding (like insert_or_assign()) while
+ // prepend/=+ is not (like insert()). In a sense, whatever appears last
+ // (from left to right) is kept, which is consistent with what we expect to
+ // happen when specifying the same key repeatedly in a representation (e.g.,
+ // a@0 a@1).
+ //
template <typename K, typename V>
- struct value_traits<std::map<K, V>>
+ struct map_value_type;
+
+ template <typename K, typename V>
+ struct value_traits<map<K, V>>
{
- template <typename K1, typename V1> using map = std::map<K1, V1>;
+ template <typename K1, typename V1> using map = map<K1, V1>;
static_assert (sizeof (map<K, V>) <= value::size_, "insufficient space");
static void assign (value&, map<K, V>&&);
static void append (value&, map<K, V>&&);
- static void prepend (value& v, map<K, V>&& x) {
- return append (v, move (x));}
+ static void prepend (value&, map<K, V>&&);
static bool empty (const map<K, V>& x) {return x.empty ();}
static const map<K, V> empty_instance;
+ static const map_value_type<K, V> value_type;
+ };
+
+ // json
+ //
+ // Note that we do not expose json_member as a value type instead
+ // representing it as an object with one member. While we could expose
+ // member (and reverse it as a pair since there is no valid JSON
+ // representation for a standalone member), this doesn't seem to buy us much
+ // but will cause complications (for example, in supporting append/prepend).
+ // On the other hand, representing a member as an object only requires a bit
+ // of what looks like harmless looseness in a few contexts (such as the
+ // $json.member_*() functions).
+ //
+ // Note that similar to map, JSON object append/+= is overriding while
+ // prepend/=+ is not. In a sense, whatever appears last (from left to right)
+ // is kept, which is consistent with what we expect to happen when
+ // specifying the same name repeatedly (provided it's not considered
+ // invalid) in a representation (e.g., {"a":1,"a":2}).
+ //
+ template <>
+ struct LIBBUILD2_SYMEXPORT value_traits<json_value>
+ {
+ static_assert (sizeof (json_value) <= value::size_, "insufficient space");
+
+ static json_value convert (names&&);
+ static void assign (value&, json_value&&);
+ static void append (value&, json_value&&);
+ static void prepend (value&, json_value&&);
+ static bool empty (const json_value&); // null or empty array/object
- // Make sure these are static-initialized together. Failed that VC will
- // make sure it's done in the wrong order.
+ // These are provided to make it possible to use json_value as a container
+ // element.
//
- struct value_type_ex: build2::value_type
- {
- string type_name;
- value_type_ex (value_type&&);
- };
- static const value_type_ex value_type;
+ static json_value convert (name&&, name*);
+ static name reverse (const json_value&);
+ static int compare (const json_value& x, const json_value& y) {
+ return x.compare (y);}
+
+ static const json_value empty_instance; // null
+ static const char* const type_name;
+ static const build2::value_type value_type;
+ };
+
+ template <>
+ struct LIBBUILD2_SYMEXPORT value_traits<json_array>
+ {
+ static_assert (sizeof (json_array) <= value::size_, "insufficient space");
+
+ static json_array convert (names&&);
+ static void assign (value&, json_array&&);
+ static void append (value&, json_value&&); // Note: value, not array.
+ static void prepend (value&, json_value&&);
+ static bool empty (const json_array& v) {return v.array.empty ();}
+
+ static const json_array empty_instance; // empty array
+ static const char* const type_name;
+ static const build2::value_type value_type;
+ };
+
+ template <>
+ struct LIBBUILD2_SYMEXPORT value_traits<json_object>
+ {
+ static_assert (sizeof (json_object) <= value::size_, "insufficient space");
+
+ static json_object convert (names&&);
+ static void assign (value&, json_object&&);
+ static void append (value&, json_value&&); // Note: value, not object.
+ static void prepend (value&, json_value&&);
+ static bool empty (const json_object& v) {return v.object.empty ();}
+
+ static const json_object empty_instance; // empty object
+ static const char* const type_name;
+ static const build2::value_type value_type;
+ };
+
+ // Canned command line to be re-lexed (used in {Build,Test}scripts).
+ //
+ // Note that because the executable can be specific as a target or as
+ // process_path_ex, this is a list of names rather than a list of strings.
+ // Note also that unlike vector<name> this type allows name pairs.
+ //
+ struct cmdline: vector<name>
+ {
+ using vector<name>::vector;
+
+ cmdline () {} // For Clang.
+ };
+
+ template <>
+ struct LIBBUILD2_SYMEXPORT value_traits<cmdline>
+ {
+ static_assert (sizeof (cmdline) <= value::size_, "insufficient space");
+
+ static cmdline convert (names&&);
+ static void assign (value&, cmdline&&);
+ static void append (value&, cmdline&&);
+ static void prepend (value&, cmdline&&);
+ static bool empty (const cmdline& x) {return x.empty ();}
+
+ static const cmdline empty_instance;
+ static const char* const type_name;
+ static const build2::value_type value_type;
};
// Explicitly pre-instantiate and export value_traits templates for
@@ -1091,6 +1342,8 @@ namespace build2
// value type objects for the same traits type (and we use their addressed
// as identity; see cast(const value&) for an example).
//
+ // NOTE: REMEMBER TO UPDATE dump_value(json) IF CHANGING ANYTHING HERE!
+ //
extern template struct LIBBUILD2_DECEXPORT value_traits<strings>;
extern template struct LIBBUILD2_DECEXPORT value_traits<vector<name>>;
extern template struct LIBBUILD2_DECEXPORT value_traits<paths>;
@@ -1102,10 +1355,34 @@ namespace build2
value_traits<vector<pair<string, string>>>;
extern template struct LIBBUILD2_DECEXPORT
- value_traits<std::map<string, string>>;
+ value_traits<vector<pair<string, optional<string>>>>;
+
+ extern template struct LIBBUILD2_DECEXPORT
+ value_traits<vector<pair<optional<string>, string>>>;
+
+ extern template struct LIBBUILD2_DECEXPORT
+ value_traits<vector<pair<string, optional<bool>>>>;
+
+ extern template struct LIBBUILD2_DECEXPORT value_traits<set<string>>;
+ extern template struct LIBBUILD2_DECEXPORT value_traits<set<json_value>>;
+
+ extern template struct LIBBUILD2_DECEXPORT
+ value_traits<map<string, string>>;
extern template struct LIBBUILD2_DECEXPORT
- value_traits<std::map<project_name, dir_path>>;
+ value_traits<map<json_value, json_value>>;
+
+ extern template struct LIBBUILD2_DECEXPORT
+ value_traits<map<string, optional<string>>>;
+
+ extern template struct LIBBUILD2_DECEXPORT
+ value_traits<map<optional<string>, string>>;
+
+ extern template struct LIBBUILD2_DECEXPORT
+ value_traits<map<string, optional<bool>>>;
+
+ extern template struct LIBBUILD2_DECEXPORT
+ value_traits<map<project_name, dir_path>>; // var_subprojects
// Project-wide (as opposed to global) variable overrides (see context ctor
// for details).
@@ -1122,9 +1399,12 @@ namespace build2
// Variable pool.
//
- // The global (as in, context-wide) version is protected by the phase mutex.
+ // The shared versions (as in, context or project-wide) are protected by the
+ // phase mutex and thus can only be modified during the load phase.
//
- class variable_pool
+ class variable_patterns;
+
+ class LIBBUILD2_SYMEXPORT variable_pool
{
public:
// Find existing (assert exists).
@@ -1144,29 +1424,29 @@ namespace build2
//
// Note also that a pattern and later insertions may restrict (but not
// relax) visibility and overridability.
-
+ //
const variable&
insert (string name)
{
- return insert (move (name), nullptr, nullptr, nullptr);
+ return insert (move (name), nullptr, nullptr, nullptr).first;
}
const variable&
insert (string name, variable_visibility v)
{
- return insert (move (name), nullptr, &v, nullptr);
+ return insert (move (name), nullptr, &v, nullptr).first;
}
const variable&
insert (string name, bool overridable)
{
- return insert (move (name), nullptr, nullptr, &overridable);
+ return insert (move (name), nullptr, nullptr, &overridable).first;
}
const variable&
insert (string name, bool overridable, variable_visibility v)
{
- return insert (move (name), nullptr, &v, &overridable);
+ return insert (move (name), nullptr, &v, &overridable). first;
}
template <typename T>
@@ -1174,14 +1454,15 @@ namespace build2
insert (string name)
{
return insert (
- move (name), &value_traits<T>::value_type, nullptr, nullptr);
+ move (name), &value_traits<T>::value_type, nullptr, nullptr).first;
}
template <typename T>
const variable&
insert (string name, variable_visibility v)
{
- return insert (move (name), &value_traits<T>::value_type, &v, nullptr);
+ return insert (
+ move (name), &value_traits<T>::value_type, &v, nullptr).first;
}
template <typename T>
@@ -1189,7 +1470,7 @@ namespace build2
insert (string name, bool overridable)
{
return insert (
- move (name), &value_traits<T>::value_type, nullptr, &overridable);
+ move (name), &value_traits<T>::value_type, nullptr, &overridable).first;
}
template <typename T>
@@ -1197,7 +1478,13 @@ namespace build2
insert (string name, bool overridable, variable_visibility v)
{
return insert (
- move (name), &value_traits<T>::value_type, &v, &overridable);
+ move (name), &value_traits<T>::value_type, &v, &overridable).first;
+ }
+
+ const variable&
+ insert (string name, const value_type* type)
+ {
+ return insert (move (name), type, nullptr, nullptr).first;
}
const variable&
@@ -1206,7 +1493,7 @@ namespace build2
bool overridable,
variable_visibility v)
{
- return insert (move (name), type, &v, &overridable);
+ return insert (move (name), type, &v, &overridable).first;
}
// Alias an existing variable with a new name.
@@ -1221,70 +1508,74 @@ namespace build2
// Overridable aliased variables are most likely a bad idea: without a
// significant effort, the overrides will only be applied along the alias
// names (i.e., there would be no cross-alias overriding). So for now we
- // don't allow this (use the common variable mechanism instead).
+ // don't allow this (manually handle multiple names by merging their
+ // values instead).
//
- LIBBUILD2_SYMEXPORT const variable&
+ // Note: currently only public variables can be aliased.
+ //
+ const variable&
insert_alias (const variable& var, string name);
- // Insert a variable pattern. Any variable that matches this pattern will
- // have the specified type, visibility, and overridability. If match is
- // true, then individual insertions of the matching variable must match
- // the specified type/visibility/overridability. Otherwise, individual
- // insertions can provide alternative values and the pattern values are a
- // fallback (if you specify false you better be very clear about what you
- // are trying to achieve).
+ // Iteration.
//
- // The pattern must be in the form [<prefix>.](*|**)[.<suffix>] where '*'
- // matches single component stems (i.e., 'foo' but not 'foo.bar') and '**'
- // matches single and multi-component stems. Note that only multi-
- // component variables are considered for pattern matching (so just '*'
- // won't match anything).
+ public:
+ using key = butl::map_key<string>;
+ using map = std::unordered_map<key, variable>;
+
+ using const_iterator = butl::map_iterator_adapter<map::const_iterator>;
+
+ const_iterator begin () const {return const_iterator (map_.begin ());}
+ const_iterator end () const {return const_iterator (map_.end ());}
+
+ // Construction.
//
- // The patterns are matched in the more-specific-first order where the
- // pattern is considered more specific if it has a greater sum of its
- // prefix and suffix lengths. If the prefix and suffix are equal, then the
- // '*' pattern is considered more specific than '**'. If neither is more
- // specific, then they are matched in the reverse order of insertion.
+ // There are three specific variable pool instances:
//
- // If retro is true then a newly inserted pattern is also applied
- // retrospectively to all the existing variables that match but only
- // if no more specific pattern already exists (which is then assumed
- // to have been applied). So if you use this functionality, watch out
- // for the insertion order (you probably want more specific first).
+ // shared outer
+ // ----------------
+ // true null -- public variable pool in context
+ // true not null -- project-private pool in scope::root_extra
+ // with outer pointing to context::var_pool
+ // false not null -- temporary scope-private pool in temp_scope
+ // with outer pointing to context::var_pool
+ // false null -- script-private pool in script::environment
//
- public:
- LIBBUILD2_SYMEXPORT void
- insert_pattern (const string& pattern,
- optional<const value_type*> type,
- optional<bool> overridable,
- optional<variable_visibility>,
- bool retro = false,
- bool match = true);
+ // Notice that the script-private pool doesn't rely on outer and does
+ // its own pool chaining. So currently we assume that if outer is not
+ // NULL, then this is a project-private pool.
+ //
+ private:
+ friend class context;
+ friend class temp_scope;
- template <typename T>
- void
- insert_pattern (const string& p,
- optional<bool> overridable,
- optional<variable_visibility> v,
- bool retro = false,
- bool match = true)
- {
- insert_pattern (
- p, &value_traits<T>::value_type, overridable, v, retro, match);
- }
+ // Shared pool (public or project-private). The shared argument is
+ // flag/context.
+ //
+ variable_pool (context* shared,
+ variable_pool* outer,
+ const variable_patterns* patterns)
+ : shared_ (shared), outer_ (outer), patterns_ (patterns) {}
public:
- void
- clear () {map_.clear ();}
+ // Script-private pool.
+ //
+ explicit
+ variable_pool (const variable_patterns* patterns = nullptr)
+ : shared_ (nullptr), outer_ (nullptr), patterns_ (patterns) {}
+
+ variable_pool (variable_pool&&) = delete;
+ variable_pool& operator= (variable_pool&&) = delete;
- variable_pool (): variable_pool (nullptr) {}
+ variable_pool (const variable_pool&) = delete;
+ variable_pool& operator= (const variable_pool&) = delete;
- // RW access (only for the global pool).
+ public:
+ // RW access (only for shared pools plus the temp_scope special case).
//
variable_pool&
rw () const
{
- assert (global_->phase == run_phase::load);
+ assert (shared_ == nullptr || shared_->phase == run_phase::load);
return const_cast<variable_pool&> (*this);
}
@@ -1300,14 +1591,16 @@ namespace build2
// Note that in insert() NULL overridable is interpreted as false unless
// overridden by a pattern while in update() NULL overridable is ignored.
//
- LIBBUILD2_SYMEXPORT variable&
+ pair<variable&, bool>
insert (string name,
const value_type*,
const variable_visibility*,
const bool* overridable,
bool pattern = true);
- LIBBUILD2_SYMEXPORT void
+ // Note: the variable must belong to this pool.
+ //
+ void
update (variable&,
const value_type*,
const variable_visibility*,
@@ -1316,9 +1609,6 @@ namespace build2
// Variable map.
//
private:
- using key = butl::map_key<string>;
- using map = std::unordered_map<key, variable>;
-
pair<map::iterator, bool>
insert (variable&& var)
{
@@ -1327,19 +1617,127 @@ namespace build2
// gets hairy very quickly (there is no std::hash for C-strings). So
// let's rely on small object-optimized std::string for now.
//
- string n (var.name);
+ string n (var.name); // @@ PERF (maybe keep reuse buffer at least?)
auto r (map_.insert (map::value_type (&n, move (var))));
if (r.second)
+ {
+#if 0
+ if (shared_ && outer_ == nullptr) // Global pool in context.
+ {
+ size_t n (map_.bucket_count ());
+ if (n > buckets_)
+ {
+ text << "variable_pool buckets: " << buckets_ << " -> " << n
+ << " (" << map_.size () << ")";
+ buckets_ = n;
+ }
+ }
+#endif
r.first->first.p = &r.first->second.name;
+ }
return r;
}
+ private:
+ friend class variable_patterns;
+
+ context* shared_;
+ variable_pool* outer_;
+ const variable_patterns* patterns_;
map map_;
- // Patterns.
+#if 0
+ size_t buckets_ = 0;
+#endif
+ };
+
+ // Variable patterns.
+ //
+ // This mechanism is used to assign variable types/visibility/overridability
+ // based on the variable name pattern. This mechanism can only be used for
+ // qualified variables and is thus only provided for the public variable
+ // pool.
+ //
+ // Similar to variable_pool, the shared versions are protected by the phase
+ // mutex and thus can only be modified during the load phase.
+ //
+ class LIBBUILD2_SYMEXPORT variable_patterns
+ {
+ public:
+ // Insert a variable pattern. Any variable that matches this pattern will
+ // have the specified type, visibility, and overridability. If match is
+ // true, then individual insertions of the matching variable must match
+ // the specified type/visibility/overridability. Otherwise, individual
+ // insertions can provide alternative values and the pattern values are a
+ // fallback (if you specify false you better be very clear about what you
+ // are trying to achieve).
+ //
+ // The pattern must be in the form [<prefix>.](*|**)[.<suffix>] where '*'
+ // matches single component stems (i.e., 'foo' but not 'foo.bar') and '**'
+ // matches single and multi-component stems. Note that only multi-
+ // component variables are considered for pattern matching (so just '*'
+ // won't match anything).
+ //
+ // The patterns are matched in the more-specific-first order where the
+ // pattern is considered more specific if it has a greater sum of its
+ // prefix and suffix lengths. If the prefix and suffix are equal, then the
+ // '*' pattern is considered more specific than '**'. If neither is more
+ // specific, then they are matched in the reverse order of insertion.
+ //
+ // If retro is true then a newly inserted pattern is also applied
+ // retrospectively to all the existing variables that match but only
+ // if no more specific pattern already exists (which is then assumed
+ // to have been applied). So if you use this functionality, watch out
+ // for the insertion order (you probably want more specific first).
+ //
+ void
+ insert (const string& pattern,
+ optional<const value_type*> type,
+ optional<bool> overridable,
+ optional<variable_visibility>,
+ bool retro = false,
+ bool match = true);
+
+ template <typename T>
+ void
+ insert (const string& p,
+ optional<bool> overridable,
+ optional<variable_visibility> v,
+ bool retro = false,
+ bool match = true)
+ {
+ insert (p, &value_traits<T>::value_type, overridable, v, retro, match);
+ }
+
+ public:
+ // The shared argument is flag/context. The pool argument is for
+ // retrospective pattern application.
//
+ explicit
+ variable_patterns (context* shared, variable_pool* pool)
+ : shared_ (shared), pool_ (pool) {}
+
+ variable_patterns (variable_patterns&&) = delete;
+ variable_patterns& operator= (variable_patterns&&) = delete;
+
+ variable_patterns (const variable_patterns&) = delete;
+ variable_patterns& operator= (const variable_patterns&) = delete;
+
+ public:
+ // RW access (only for shared pools).
+ //
+ variable_patterns&
+ rw () const
+ {
+ assert (shared_->phase == run_phase::load);
+ return const_cast<variable_patterns&> (*this);
+ }
+
+ variable_patterns&
+ rw (scope&) const {return const_cast<variable_patterns&> (*this);}
+
public:
struct pattern
{
@@ -1367,17 +1765,11 @@ namespace build2
};
private:
- std::multiset<pattern> patterns_;
+ friend class variable_pool;
- // Global pool flag/context.
- //
- private:
- friend class context;
-
- explicit
- variable_pool (context* global): global_ (global) {}
-
- context* global_;
+ context* shared_;
+ variable_pool* pool_;
+ multiset<pattern> patterns_;
};
}
@@ -1418,7 +1810,10 @@ namespace build2
using value::value;
using value::operator=;
- size_t version = 0; // Incremented on each modification (variable_cache).
+ // Incremented on each modification, at which point we also reset
+ // value::extra to 0.
+ //
+ size_t version = 0;
};
// Note that we guarantee ascending iteration order (e.g., for predictable
@@ -1460,8 +1855,13 @@ namespace build2
lookup_type
operator[] (const variable& var) const
{
- auto p (lookup (var));
- return lookup_type (p.first, &p.second, this);
+ lookup_type r;
+ if (!empty ())
+ {
+ auto p (lookup (var));
+ r = lookup_type (p.first, &p.second, this);
+ }
+ return r;
}
lookup_type
@@ -1474,30 +1874,58 @@ namespace build2
lookup_type
operator[] (const string& name) const
{
- const variable* var (ctx != nullptr
- ? ctx->var_pool.find (name)
- : nullptr);
- return var != nullptr ? operator[] (*var) : lookup_type ();
+ assert (owner_ != owner::context);
+
+ lookup_type r;
+ if (!empty ())
+ r = lookup (name);
+ return r;
}
- // If typed is false, leave the value untyped even if the variable is.
- // The second half of the pair is the storage variable.
+ lookup_type
+ lookup (const string& name) const;
+
+ // If typed is false, leave the value untyped even if the variable is. If
+ // aliased is false, then don't consider aliases (used by the variable
+ // override machinery where the aliases chain is repurrposed for something
+ // else). The second half of the pair is the storage variable.
//
pair<const value_data*, const variable&>
- lookup (const variable&, bool typed = true) const;
+ lookup (const variable&, bool typed = true, bool aliased = true) const;
pair<value_data*, const variable&>
lookup_to_modify (const variable&, bool typed = true);
+ pair<const_iterator, const_iterator>
+ lookup_namespace (const variable& ns) const
+ {
+ auto r (m_.find_sub (ns));
+ return make_pair (const_iterator (r.first, *this),
+ const_iterator (r.second, *this));
+ }
+
+ pair<const_iterator, const_iterator>
+ lookup_namespace (string ns) const
+ {
+ // It's ok to use the temporary here since we compare names and don't
+ // insert anything.
+ //
+ return lookup_namespace (variable {
+ move (ns),
+ nullptr, nullptr, nullptr, nullptr,
+ variable_visibility::project});
+ }
+
// Convert a lookup pointing to a value belonging to this variable map
// to its non-const version. Note that this is only safe on the original
- // values (see find_original()).
+ // values (see lookup_original()).
//
value&
modify (const lookup_type& l)
{
assert (l.vars == this);
value& r (const_cast<value&> (*l.value));
+ r.extra = 0;
static_cast<value_data&> (r).version++;
return r;
}
@@ -1514,27 +1942,37 @@ namespace build2
return assign (*var);
}
- // Note that the variable is expected to have already been registered.
+ // Note that the variable is expected to have already been inserted.
//
value&
- assign (const string& name) {return insert (ctx->var_pool[name]).first;}
+ assign (const string& name);
// As above but also return an indication of whether the new value (which
// will be NULL) was actually inserted. Similar to find(), if typed is
- // false, leave the value untyped even if the variable is.
+ // false, leave the value untyped even if the variable is. If reset_extra
+ // is false, then don't reset the existing value's value::extra.
//
- pair<reference_wrapper<value>, bool>
- insert (const variable&, bool typed = true);
+ pair<value&, bool>
+ insert (const variable&, bool typed = true, bool reset_extra = true);
- pair<const_iterator, const_iterator>
- lookup_namespace (const variable& ns) const
+ // Note: the following functions do not deal with aliases.
+ //
+ const_iterator
+ find (const variable& var) const
{
- auto r (m_.find_sub (ns));
- return make_pair (const_iterator (r.first, *this),
- const_iterator (r.second, *this));
+ return const_iterator (m_.find (var), *this);
}
const_iterator
+ find (const string& name) const;
+
+ bool
+ erase (const variable&);
+
+ const_iterator
+ erase (const_iterator);
+
+ const_iterator
begin () const {return const_iterator (m_.begin (), *this);}
const_iterator
@@ -1547,21 +1985,58 @@ namespace build2
size () const {return m_.size ();}
public:
- // Global should be true if this map is part of the global build state
- // (e.g., scopes, etc).
+ // Shared should be true if this map is part of the shared build state
+ // (e.g., scopes) and thus should only be modified during the load phase.
//
explicit
- variable_map (context& c, bool global = false)
- : ctx (&c), global_ (global) {}
+ variable_map (const scope& owner, bool shared = false);
+
+ explicit
+ variable_map (const target& owner, bool shared = false);
+
+ explicit
+ variable_map (const prerequisite& owner, bool shared = false);
+
+ variable_map (variable_map&&, const prerequisite&, bool shared = false);
+ variable_map (const variable_map&, const prerequisite&, bool shared = false);
+
+ variable_map&
+ operator= (variable_map&& v) noexcept {m_ = move (v.m_); return *this;}
+
+ variable_map&
+ operator= (const variable_map& v) {m_ = v.m_; return *this;}
+
+ // The context owner is for special "managed" variable maps. Note that
+ // such maps cannot lookup/insert variable names specified as strings.
+ //
+ variable_map (context& c, bool shared)
+ : shared_ (shared), owner_ (owner::context), ctx (&c) {}
+
+ // Note: std::map's move constructor can throw.
+ //
+ variable_map (variable_map&& v)
+ : shared_ (v.shared_), owner_ (v.owner_), ctx (v.ctx), m_ (move (v.m_))
+ {
+ assert (owner_ == owner::context);
+ }
+
+ variable_map (const variable_map& v)
+ : shared_ (v.shared_), owner_ (v.owner_), ctx (v.ctx), m_ (v.m_)
+ {
+ assert (v.owner_ == owner::context);
+ }
void
clear () {m_.clear ();}
- // Implementation details (only used for empty_variable_map).
+ // Implementation details.
//
public:
+ enum class owner {empty, context, scope, target, prereq};
+
explicit
- variable_map (context* c): ctx (c) {}
+ variable_map (owner o, context* c = nullptr, bool shared = false)
+ : shared_ (shared), owner_ (o), ctx (c) {}
private:
friend class variable_type_map;
@@ -1570,9 +2045,18 @@ namespace build2
typify (const value_data&, const variable&) const;
private:
+ friend class target_set;
+
+ bool shared_;
+ owner owner_;
+ union
+ {
+ const scope* scope_;
+ const target* target_;
+ const prerequisite* prereq_;
+ };
context* ctx;
map_type m_;
- bool global_;
};
LIBBUILD2_SYMEXPORT extern const variable_map empty_variable_map;
@@ -1638,7 +2122,7 @@ namespace build2
stem_version (sver) {}
};
- using map_type = std::map<K, entry_type>;
+ using map_type = map<K, entry_type>;
map_type m_;
};
@@ -1660,17 +2144,69 @@ namespace build2
class variable_pattern_map
{
public:
- using map_type = std::map<string, variable_map>;
+ using pattern_type = name::pattern_type;
+
+ // We use the map to keep the patterns in the shortest-first order. This
+ // is used during match where we starting from the longest values so that
+ // the more "specific" patterns (i.e., those that cover fewer characters
+ // with the wildcard) take precedence.
+ //
+ // Note that this is only an approximation (e.g., `*[0-9]` vs `[0-9]`) but
+ // it's sufficient in practice (e.g., `*` vs `*.txt`). We also have the
+ // ambiguity problem (e.g., `foo-foo` matching both `foo-*` and `*-foo`).
+ //
+ // And, of course, this doesn't apply accross pattern types so we always
+ // treat regex patterns as more specific than path patterns.
+ //
+ // While it feels like this should be a union (with pattern_type as the
+ // discriminator), we need to keep the original regex text for dumping.
+ // So we just keep optional<regex> which is absent for path patterns (it's
+ // optional since a default-constructed regex has a pattern). BTW, the
+ // size of std::regex object ranges between 32 and 64 bytes, depending on
+ // the implementation.
+ //
+ struct pattern
+ {
+ pattern_type type;
+ mutable bool match_ext; // Match extension flag.
+ string text;
+ mutable optional<build2::regex> regex;
+ };
+
+ struct pattern_compare
+ {
+ bool operator() (const pattern& x, const pattern& y) const
+ {
+ return x.type != y.type
+ ? x.type == pattern_type::path
+ : (x.text.size () != y.text.size ()
+ ? x.text.size () < y.text.size ()
+ : x.text < y.text);
+ }
+ };
+
+ using map_type = map<pattern, variable_map, pattern_compare>;
using const_iterator = map_type::const_iterator;
using const_reverse_iterator = map_type::const_reverse_iterator;
- variable_pattern_map (context& c, bool global)
- : ctx (c), global_ (global) {}
+ variable_pattern_map (context& c, bool shared)
+ : ctx (c), shared_ (shared) {}
+
+ // Note that here we assume the "outer" pattern format (delimiters, flags,
+ // etc) is valid.
+ //
+ // Note: may throw regex_error in which case text is preserved.
+ //
+ variable_map&
+ insert (pattern_type type, string&& text);
+ // Convenience shortcut or path patterns.
+ //
variable_map&
- operator[] (const string& v)
+ operator[] (string text)
{
- return map_.emplace (v, variable_map (ctx, global_)).first->second;
+ return map_.emplace (pattern {pattern_type::path, false, move (text), {}},
+ variable_map (ctx, shared_)).first->second;
}
const_iterator begin () const {return map_.begin ();}
@@ -1682,31 +2218,35 @@ namespace build2
private:
context& ctx;
map_type map_;
- bool global_;
+ bool shared_;
};
class LIBBUILD2_SYMEXPORT variable_type_map
{
public:
- using map_type = std::map<reference_wrapper<const target_type>,
- variable_pattern_map>;
+ using map_type = map<reference_wrapper<const target_type>,
+ variable_pattern_map>;
using const_iterator = map_type::const_iterator;
- variable_type_map (context& c, bool global): ctx (c), global_ (global) {}
+ variable_type_map (context& c, bool shared): ctx (c), shared_ (shared) {}
variable_pattern_map&
operator[] (const target_type& t)
{
return map_.emplace (
- t, variable_pattern_map (ctx, global_)).first->second;
+ t, variable_pattern_map (ctx, shared_)).first->second;
}
const_iterator begin () const {return map_.begin ();}
const_iterator end () const {return map_.end ();}
bool empty () const {return map_.empty ();}
+ // If found append/prepend then name is guaranteed to either contain the
+ // full name that was used for the match or be empty in which case the
+ // orginal target name was used.
+ //
lookup
- find (const target_type&, const string& tname, const variable&) const;
+ find (const target_key&, const variable&, optional<string>& name) const;
// Prepend/append value cache.
//
@@ -1715,7 +2255,7 @@ namespace build2
// "target identity" (as target type and target name). Note that while at
// first it may seem like we don't need the target identity, we actually
// do since the stem may itself be target-type/pattern-specific. See
- // scope::find_original() for details.
+ // scope::lookup_original() for details.
//
mutable
variable_cache<tuple<const value*, const target_type*, string>>
@@ -1724,7 +2264,7 @@ namespace build2
private:
context& ctx;
map_type map_;
- bool global_;
+ bool shared_;
};
}