aboutsummaryrefslogtreecommitdiff
path: root/build/variable
diff options
context:
space:
mode:
Diffstat (limited to 'build/variable')
-rw-r--r--build/variable735
1 files changed, 557 insertions, 178 deletions
diff --git a/build/variable b/build/variable
index bc78289..3bd8922 100644
--- a/build/variable
+++ b/build/variable
@@ -6,13 +6,14 @@
#define BUILD_VARIABLE
#include <map>
+#include <vector>
#include <string>
-#include <memory> // unique_ptr
#include <cstddef> // nullptr_t
#include <utility> // move(), pair, make_pair()
#include <cassert>
+#include <iterator>
#include <functional> // hash, reference_wrapper
-#include <typeindex>
+#include <type_traits> // conditional, is_reference, remove_reference, etc.
#include <unordered_set>
#include <butl/prefix-map>
@@ -22,12 +23,18 @@
namespace build
{
- struct value;
+ 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
{
- std::type_index id;
- value* (*const factory) ();
+ const std::string name;
+ bool (*const assign) (names&, const variable&);
+ bool (*const append) (names&, names, const variable&);
};
// variable
@@ -37,11 +44,12 @@ namespace build
struct variable
{
explicit
- variable (std::string n, char p = '\0'): name (std::move (n)), pairs (p) {}
+ variable (std::string n, const value_type* t = nullptr, char p = '\0')
+ : name (std::move (n)), pairs (p), type (t) {}
std::string name;
char pairs;
- //const value_type* type = nullptr; // If NULL, then no fixed type.
+ const value_type* type; // If NULL, then not (yet) typed.
};
inline bool
@@ -51,119 +59,121 @@ namespace build
// value
//
- struct value;
- typedef std::unique_ptr<value> value_ptr;
-
- struct value
+ class value
{
public:
- virtual value_ptr
- clone () const = 0;
+ // By default we create NULL value.
+ //
+ explicit value (const value_type* t = nullptr)
+ : type (t), state_ (state_type::null) {}
- virtual bool
- compare (const value&) const = 0;
+ value (value&&) = default;
- virtual
- ~value () = default;
- };
-
- class list_value: public value, public names
- {
- public:
- using names::names;
-
- list_value () = default;
- explicit list_value (names d): names (std::move (d)) {}
- explicit list_value (name n) {emplace_back (std::move (n));}
- explicit list_value (dir_path d) {emplace_back (std::move (d));}
- explicit list_value (std::string s) {emplace_back (std::move (s));}
- explicit list_value (const char* s) {emplace_back (s);}
+ value&
+ operator= (std::nullptr_t)
+ {
+ data_.clear ();
+ state_ = state_type::null;
+ return *this;
+ }
- virtual value_ptr
- clone () const {return value_ptr (new list_value (*this));}
+ value&
+ operator= (value&&);
- virtual bool
- compare (const value& v) const
+ value&
+ operator= (const value& v)
{
- const list_value* lv (dynamic_cast<const list_value*> (&v));
- return lv != nullptr && static_cast<const names&> (*this) == *lv;
+ if (&v != this)
+ *this = value (v);
+ return *this;
}
- // Pair (i.e., key-value) search. Note that this funtion assumes
- // the list contains only pairs and keys are simple names. Returns
- // NULL if not found.
- //
- const name*
- find_pair (const std::string& key) const
+ value&
+ operator= (std::reference_wrapper<const value> v)
{
- for (auto i (begin ()); i != end (); i += 2)
- if (i->value == key)
- return &*++i;
- return nullptr;
+ return *this = v.get ();
}
- };
- typedef std::unique_ptr<list_value> list_value_ptr;
- // value_proxy
- //
- // A variable can be undefined, null, or contain some actual value.
- // Note that once value_proxy is bound to a value, the only way to
- // rebind it to a different value is by using explicit rebind(). In
- // particular, assigning one value proxy to another will assing the
- // values.
- //
- struct variable_map;
+ value&
+ append (value, const variable&); // Aka operator+=().
- struct value_proxy
- {
- bool
- defined () const {return p != nullptr;}
+ // 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);}
- bool
- null () const {return *p == nullptr;}
+ template <typename T> value& operator+= (T);
+ value& operator+= (const char* v) {return *this += std::string (v);}
- bool
- empty () const;
+ private:
+ explicit value (const value&) = default;
+
+ public:
+ const value_type* type; // NULL means this value is not (yet) typed.
- explicit operator bool () const {return defined () && !null ();}
- explicit operator value_ptr& () const {return *p;}
+ bool null () const {return state_ == state_type::null;}
+ bool empty () const {return state_ == state_type::empty;}
- // Get interface. See available specializations below.
+ explicit operator bool () const {return !null ();}
+
+ // Raw data read interface.
//
- template <typename T>
- T
- as () const;
+ using const_iterator = names::const_iterator;
+
+ const_iterator begin () const {return data_.begin ();}
+ const_iterator end () const {return data_.end ();}
- // Assign.
+ // Raw data write interface. Note that it triggers callbacks for
+ // typed values. Variable is passed for diagnostics.
//
- const value_proxy&
- operator= (value_ptr) const;
+ void
+ assign (names, const variable&);
- const value_proxy&
- operator= (const value_proxy&) const;
+ void
+ append (names, const variable&);
- const value_proxy&
- operator= (list_value) const;
+ 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_;
+ };
- const value_proxy&
- operator= (std::string) const;
+ //@@ 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;
- const value_proxy&
- operator= (dir_path) const;
+ template <typename V>
+ struct lookup
+ {
+ V* value;
+ const variable_map* vars;
- const value_proxy&
- operator= (std::nullptr_t) const;
+ bool
+ defined () const {return value != nullptr;}
- // Append.
+ // Note: returns true if defined and not NULL.
//
- const value_proxy&
- operator+= (const value_proxy&) const;
+ explicit operator bool () const {return defined () && !value->null ();}
- const value_proxy&
- operator+= (const list_value&) const;
-
- const value_proxy&
- operator+= (std::string) const; // Append simple name to list_value.
+ 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.
@@ -172,92 +182,395 @@ namespace build
bool
belongs (const T& x) const {return vars == &x.vars;}
- // Implementation details.
- //
- const variable_map* vars; // Variable map to which this value belongs.
-
- value_proxy (): vars (nullptr), p (nullptr) {}
- value_proxy (value_ptr* p, const variable_map* v)
- : vars (p != nullptr ? v : nullptr), p (p) {}
+ lookup (): value (nullptr), vars (nullptr) {}
+ lookup (V* v, const variable_map* vs)
+ : value (v), vars (v != nullptr ? vs : nullptr) {}
template <typename T>
- value_proxy (value_ptr& p, const T& x)
- : value_proxy (&p, &x.vars) {}
+ 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&);
- // @@ To do this properly we seem to need ro_value_proxy?
+ 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.
//
- value_proxy (const value_ptr* p, const variable_map* v)
- : value_proxy (const_cast<value_ptr*> (p), v) {}
+ public:
+ explicit bool_value (D& d): d (&d) {}
- template <typename T>
- value_proxy (const value_ptr& p, const T& x)
- : value_proxy (const_cast<value_ptr&> (p), x) {}
+ bool_value (const bool_value&) = delete;
+ bool_value& operator= (const bool_value&) = delete; // Rebind or deep?
- void
- rebind (const value_proxy& x) {vars = x.vars; p = x.p;}
+ bool_value (bool_value&&) = default;
+ bool_value& operator= (bool_value&&) = delete;
- private:
- value_ptr* p;
+ D* d; // name
};
template <>
- inline value& value_proxy::
- as<value&> () const {return **p;}
+ struct value_traits<bool>
+ {
+ using type = bool_value<name>;
+ using const_type = bool_value<const name>;
- template <>
- inline const value& value_proxy::
- as<const value&> () const {return **p;}
+ static type as (name& n) {return type (n);}
+ static const_type as (const name& n) {return const_type (n);}
+
+ static type as (value&);
+ static const_type as (const value&);
+
+ static bool assign (name&);
+ static void assign (value&, bool);
+ static void append (value&, bool);
+
+ static const build::value_type value_type;
+ };
+
+ extern const value_type* bool_type;
+ // string
+ //
template <>
- inline list_value& value_proxy::
- as<list_value&> () const
+ struct value_traits<std::string>
{
- list_value* lv (dynamic_cast<list_value*> (p->get ()));
- assert (lv != nullptr);
- return *lv;
- }
+ using type = std::string&;
+ using const_type = const std::string&;
- template <>
- inline const list_value& value_proxy::
- as<const list_value&> () const {return as<list_value&> ();}
+ static type as (name& n) {return n.value;}
+ static const_type as (const name& n) {return n.value;}
+
+ static type as (value&);
+ static const_type as (const value&);
+
+ static bool assign (name&);
+ static void assign (value&, std::string);
+ static void append (value&, std::string);
+
+ static const build::value_type value_type;
+ };
+ extern const value_type* string_type;
+
+ // dir_path
+ //
template <>
- inline const name* value_proxy::
- as<const name*> () const
+ struct value_traits<dir_path>
{
- const auto& lv (as<const list_value&> ());
- assert (lv.size () < 2);
- return lv.empty () ? nullptr : &lv.front ();
- }
+ using type = dir_path&;
+ using const_type = const dir_path&;
+
+ static type as (name& n) {return n.dir;}
+ static const_type as (const name& n) {return n.dir;}
+
+ static type as (value&);
+ static const_type as (const value&);
+
+ static bool assign (name&);
+ static void assign (value&, dir_path);
+ static void append (value&, dir_path);
+ static const build::value_type value_type;
+ };
+
+ extern const value_type* dir_path_type;
+
+ // name
+ //
template <>
- inline const name& value_proxy::
- as<const name&> () const
+ struct value_traits<name>
{
- const auto& lv (as<const list_value&> ());
- assert (lv.size () == 1);
- return lv.front ();
- }
+ using type = name&;
+ using const_type = const name&;
- template <>
- std::string& value_proxy::
- as<std::string&> () const;
+ static type as (name& n) {return n;}
+ static const_type as (const name& n) {return n;}
- template <>
- const std::string& value_proxy::
- as<const std::string&> () const;
+ static type as (value&);
+ static const_type as (const value&);
- template <>
- dir_path& value_proxy::
- as<dir_path&> () const;
+ static bool assign (name&) {return true;}
+ static void assign (value&, name);
+ static void append (value&, name) = delete;
- template <>
- const dir_path& value_proxy::
- as<const dir_path&> () const;
+ static const build::value_type value_type;
+ };
- template <>
- bool value_proxy::
- as<bool> () const;
+ 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 build::value_type value_type;
+ };
+
+ template <typename T, typename D>
+ struct value_traits<vector_value<T, D>>: value_traits<std::vector<T>> {};
+
+ using strings_value = vector_value<std::string, names>;
+ using const_strings_value = vector_value<std::string, const names>;
+
+ extern const value_type* strings_type; // vector<string> aka strings
+ extern const value_type* dir_paths_type; // vector<dir_path> aka dir_paths
+ extern const value_type* names_type; // vector<name> aka names
+
+ // map<K, V>
+ //
+ template <typename K, typename V, typename D>
+ struct map_value
+ {
+ template <typename F, typename S>
+ struct pair
+ {
+ using first_type = typename std::conditional<
+ std::is_reference<F>::value,
+ std::reference_wrapper<typename std::remove_reference<F>::type>,
+ F>::type;
+
+ using second_type = typename std::conditional<
+ std::is_reference<S>::value,
+ std::reference_wrapper<typename std::remove_reference<S>::type>,
+ S>::type;
+
+ first_type first;
+ second_type second;
+ };
+
+ template <typename I, typename T>
+ struct iterator_impl
+ {
+ using value_type = T;
+ using pointer = value_type*;
+ using reference = value_type; // Note: value.
+ using difference_type = typename I::difference_type;
+ using iterator_category = std::bidirectional_iterator_tag;
+
+ iterator_impl () = default;
+ iterator_impl (const I& i): i_ (i) {}
+
+ pointer operator-> () const = delete;
+ reference operator* () const
+ {
+ return value_type {as<K> (*i_), as<V> (*(i_ + 1))};
+ }
+
+ iterator_impl& operator++ () {i_ += 2; return *this;}
+ iterator_impl operator++ (int) {auto r (*this); operator++ (); return r;}
+
+ iterator_impl& operator-- () {i_ -= 2; return *this;}
+ iterator_impl operator-- (int) {auto r (*this); operator-- (); return r;}
+
+ bool operator== (const iterator_impl& y) const {return i_ == y.i_;}
+ bool operator!= (const iterator_impl& y) const {return i_ != y.i_;}
+
+ private:
+ I i_;
+ };
+
+ using size_type = typename D::size_type;
+
+ using value_type = pair<typename value_traits<K>::const_type,
+ typename value_traits<V>::type>;
+
+ using const_value_type = pair<typename value_traits<K>::const_type,
+ typename value_traits<V>::const_type>;
+
+ // Make iterator the same as const_iterator if our data type is const.
+ //
+ using const_iterator =
+ iterator_impl<names::const_iterator, const_value_type>;
+ using iterator = typename std::conditional<
+ std::is_const<D>::value,
+ const_iterator,
+ iterator_impl<names::iterator, value_type>>::type;
+
+
+ public:
+ map_value&
+ operator= (std::map<K, V> m) {assign (std::move (m)); return *this;}
+
+ map_value&
+ assign (std::map<K, V>);
+
+ bool empty () const {return d->empty ();}
+ size_type size () const {return d->size ();}
+
+ iterator find (const K&);
+ const_iterator find (const K&) const;
+
+ iterator begin () {return iterator (d->begin ());}
+ iterator end () {return iterator (d->end ());}
+
+ const_iterator begin () const {return const_iterator (d->begin ());}
+ const_iterator end () const {return const_iterator (d->end ());}
+
+ // Implementation details.
+ //
+ public:
+ explicit map_value (D& d): d (&d) {}
+
+ map_value (const map_value&) = delete;
+ map_value& operator= (const map_value&) = delete; // Rebind or deep?
+
+ map_value (map_value&&) = default;
+ map_value& operator= (map_value&&) = delete;
+
+ D* d; // names
+ };
+
+ template <typename K, typename V>
+ struct value_traits<std::map<K, V>>
+ {
+ using type = map_value<K, V, names>;
+ using const_type = map_value<K, V, const names>;
+
+ static type as (value&);
+ static const_type as (const value&);
+
+ template <typename M> static void assign (value&, M);
+ template <typename M> static void append (value&, M);
+
+ static const build::value_type value_type;
+ };
+
+ template <typename K, typename V, typename D>
+ struct value_traits<map_value<K, V, D>>: value_traits<std::map<K, V>> {};
}
namespace std
@@ -301,15 +614,27 @@ namespace build
{
// variable_pool
//
- struct variable_set: std::unordered_set<variable>
+ using variable_set_base = std::unordered_set<variable>;
+ struct variable_set: private variable_set_base
{
- // @@ Need to check/set type?
- //
const variable&
- find (std::string name) {return *emplace (std::move (name)).first;}
+ find (std::string name, const build::value_type* t = nullptr, char p = '\0')
+ {
+ auto r (emplace (std::move (name), t, 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; // Ok, not changing the key.
+ }
+
+ return v;
+ }
- const variable&
- insert (variable v) {return *emplace (std::move (v)).first;}
+ using variable_set_base::clear;
};
extern variable_set variable_pool;
@@ -318,40 +643,92 @@ namespace build
//
struct variable_map
{
- using map_type = butl::prefix_map<variable_cref, value_ptr, '.'>;
+ using map_type = butl::prefix_map<variable_cref, value, '.'>;
using size_type = map_type::size_type;
- using const_iterator = map_type::const_iterator;
- const value_ptr*
+ 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));
- return i != m_.end () ? &i->second : nullptr;
+ const value* r (i != m_.end () ? &i->second : nullptr);
+
+ // First access after being assigned a type?
+ //
+ if (r != nullptr && var.type != nullptr && r->type != var.type)
+ build::assign (const_cast<value&> (*r), var.type, var);
+
+ return r;
}
- value_proxy
+ value*
+ find (const variable& var)
+ {
+ auto i (m_.find (var));
+ value* r (i != m_.end () ? &i->second : nullptr);
+
+ // First access after being assigned a type?
+ //
+ if (r != nullptr && var.type != nullptr && r->type != var.type)
+ build::assign (*r, var.type, var);
+
+ return r;
+ }
+
+ lookup<const value>
operator[] (const variable& var) const
{
- return value_proxy (find (var), this);
+ return lookup<const value> (find (var), this);
}
- value_proxy
+ lookup<const value>
operator[] (const std::string& name) const
{
return operator[] (variable_pool.find (name));
}
- // The second member in the pair indicates whether new (NULL)
- // value was set.
+ // Non-const lookup. Only exposed on the map directly.
//
- std::pair<value_proxy, bool>
+ lookup<value>
+ operator[] (const variable& var)
+ {
+ return lookup<value> (find (var), this);
+ }
+
+ lookup<value>
+ operator[] (const std::string& name)
+ {
+ return operator[] (variable_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_ptr ()));
- return std::make_pair (value_proxy (&r.first->second, this), r.second);
+ auto r (m_.emplace (var, value (var.type)));
+ value& v (r.first->second);
+
+ // First access after being assigned a type?
+ //
+ if (!r.second && var.type != nullptr && v.type != var.type)
+ build::assign (v, var.type, var);
+
+ return std::make_pair (std::reference_wrapper<value> (v), r.second);
}
- std::pair<value_proxy, bool>
+ std::pair<std::reference_wrapper<value>, bool>
assign (const std::string& name)
{
return assign (variable_pool.find (name));
@@ -360,7 +737,9 @@ namespace build
std::pair<const_iterator, const_iterator>
find_namespace (const std::string& ns) const
{
- return m_.find_prefix (variable_pool.find (ns));
+ auto r (m_.find_prefix (variable_pool.find (ns)));
+ return std::make_pair (const_iterator (r.first),
+ const_iterator (r.second));
}
const_iterator
@@ -381,17 +760,17 @@ namespace build
// 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>;
using variable_type_map = std::map<std::reference_wrapper<const target_type>,
variable_pattern_map>;
-
- //@@ 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.
-
}
#include <build/variable.ixx>
+#include <build/variable.txx>
#endif // BUILD_VARIABLE