From 88640e677fa0695783eac68014d7d8d5bc42d117 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 19 Feb 2024 12:23:10 +0200 Subject: Add string_set buildfile value type This exposes the std::set type to buildfiles. New functions: $size() Subscript returns true if the value is present and false otherwise (so it is mapped to std::set::contains()). For example: set = [string_set] a b c if ($set[b]) ... Note that append (+=) and prepend (=+) have the same semantics (std::set::insert()). For example: set = [string_set] a b set += c b # a b c set =+ d b # a b c d Example of iteration: set = [string_set] a b c for k: $set ... --- libbuild2/functions-string.cxx | 6 +- libbuild2/parser.cxx | 1 + libbuild2/variable.cxx | 2 + libbuild2/variable.hxx | 22 ++++ libbuild2/variable.ixx | 38 +++++++ libbuild2/variable.txx | 223 +++++++++++++++++++++++++++++++++++++++ tests/function/string/testscript | 3 +- tests/type/set/buildfile | 4 + tests/type/set/testscript | 52 +++++++++ 9 files changed, 348 insertions(+), 3 deletions(-) create mode 100644 tests/type/set/buildfile create mode 100644 tests/type/set/testscript diff --git a/libbuild2/functions-string.cxx b/libbuild2/functions-string.cxx index b332efa..367923f 100644 --- a/libbuild2/functions-string.cxx +++ b/libbuild2/functions-string.cxx @@ -119,14 +119,16 @@ namespace build2 }; // $size() + // $size() // $size() // $size() // - // First two forms: return the number of elements in the sequence. + // First three forms: return the number of elements in the sequence. // - // Third form: return the number of characters (bytes) in the string. + // Fourth form: return the number of characters (bytes) in the string. // f["size"] += [] (strings v) {return v.size ();}; + f["size"] += [] (set v) {return v.size ();}; f["size"] += [] (map v) {return v.size ();}; f["size"] += [] (string v) {return v.size ();}; diff --git a/libbuild2/parser.cxx b/libbuild2/parser.cxx index b68f009..bf806be 100644 --- a/libbuild2/parser.cxx +++ b/libbuild2/parser.cxx @@ -6101,6 +6101,7 @@ namespace build2 if (n[6] == '\0') return &value_traits::value_type; if (n[6] == 's' && n[7] == '\0') return &value_traits::value_type; + if (n == "string_set") return &value_traits>::value_type; if (n == "string_map") return &value_traits>::value_type; } diff --git a/libbuild2/variable.cxx b/libbuild2/variable.cxx index 40eeccd..6f9f0fb 100644 --- a/libbuild2/variable.cxx +++ b/libbuild2/variable.cxx @@ -3292,6 +3292,8 @@ namespace build2 template struct LIBBUILD2_DEFEXPORT value_traits>>>; + template struct LIBBUILD2_DEFEXPORT value_traits>; + template struct LIBBUILD2_DEFEXPORT value_traits>; diff --git a/libbuild2/variable.hxx b/libbuild2/variable.hxx index d67098e..b157806 100644 --- a/libbuild2/variable.hxx +++ b/libbuild2/variable.hxx @@ -1177,6 +1177,26 @@ namespace build2 static const pair_vector_value_type value_type; }; + // set + // + template + struct set_value_type; + + template + struct value_traits> + { + static_assert (sizeof (set) <= value::size_, "insufficient space"); + + static set convert (names&&); + static void assign (value&, set&&); + static void append (value&, set&&); + static void prepend (value&, set&&); + static bool empty (const set& x) {return x.empty ();} + + static const set empty_instance; + static const set_value_type value_type; + }; + // map // // Either K or V can be optional making the key or value optional. @@ -1321,6 +1341,8 @@ namespace build2 extern template struct LIBBUILD2_DECEXPORT value_traits>>>; + extern template struct LIBBUILD2_DECEXPORT value_traits>; + extern template struct LIBBUILD2_DECEXPORT value_traits>; diff --git a/libbuild2/variable.ixx b/libbuild2/variable.ixx index b8f80e3..a448cd8 100644 --- a/libbuild2/variable.ixx +++ b/libbuild2/variable.ixx @@ -853,6 +853,44 @@ namespace build2 new (&v.data_) vector> (move (x)); } + // set value + // + template + inline void value_traits>:: + assign (value& v, set&& x) + { + if (v) + v.as> () = move (x); + else + new (&v.data_) set (move (x)); + } + + template + inline void value_traits>:: + append (value& v, set&& x) + { + if (v) + { + set& p (v.as> ()); + + if (p.empty ()) + p.swap (x); + else + // Keys (being const) can only be copied. + // + p.insert (x.begin (), x.end ()); + } + else + new (&v.data_) set (move (x)); + } + + template + inline void value_traits>:: + prepend (value& v, set&& x) + { + append (v, move (x)); + } + // map value // template diff --git a/libbuild2/variable.txx b/libbuild2/variable.txx index 485f9dc..501e103 100644 --- a/libbuild2/variable.txx +++ b/libbuild2/variable.txx @@ -479,6 +479,7 @@ namespace build2 convert (names&& ns) { vector v; + v.reserve (ns.size ()); // Normally there won't be any pairs. // Similar to vector_append() below except we throw instead of issuing // diagnostics. @@ -511,6 +512,8 @@ namespace build2 ? v.as> () : *new (&v.data_) vector ()); + p.reserve (p.size () + ns.size ()); // Normally there won't be any pairs. + // Convert each element to T while merging pairs. // for (auto i (ns.begin ()); i != ns.end (); ++i) @@ -834,6 +837,226 @@ namespace build2 nullptr // Iterate. }; + // set value + // + template + set value_traits>:: + convert (names&& ns) + { + set s; + + // Similar to set_append() below except we throw instead of issuing + // diagnostics. + // + for (auto i (ns.begin ()); i != ns.end (); ++i) + { + name& n (*i); + name* r (nullptr); + + if (n.pair) + { + r = &*++i; + + if (n.pair != '@') + throw invalid_argument ( + string ("invalid pair character: '") + n.pair + '\''); + } + + s.insert (value_traits::convert (move (n), r)); + } + + return s; + } + + template + void + set_append (value& v, names&& ns, const variable* var) + { + set& s (v ? v.as> () : *new (&v.data_) set ()); + + // Convert each element to T while merging pairs. + // + for (auto i (ns.begin ()); i != ns.end (); ++i) + { + name& n (*i); + name* r (nullptr); + + if (n.pair) + { + r = &*++i; + + if (n.pair != '@') + { + diag_record dr (fail); + + dr << "unexpected pair style for " + << value_traits::value_type.name << " value " + << "'" << n << "'" << n.pair << "'" << *r << "'"; + + if (var != nullptr) + dr << " in variable " << var->name; + } + } + + try + { + s.insert (value_traits::convert (move (n), r)); + } + catch (const invalid_argument& e) + { + diag_record dr (fail); + + dr << e; + if (var != nullptr) + dr << " in variable " << var->name; + + dr << info << "while converting "; + if (n.pair) + dr << " element pair '" << n << "'@'" << *r << "'"; + else + dr << " element '" << n << "'"; + } + } + } + + template + void + set_assign (value& v, names&& ns, const variable* var) + { + if (v) + v.as> ().clear (); + + set_append (v, move (ns), var); + } + + template + names_view + set_reverse (const value& v, names& s, bool) + { + auto& sv (v.as> ()); + s.reserve (sv.size ()); + + for (const T& x: sv) + s.push_back (value_traits::reverse (x)); + + return s; + } + + template + int + set_compare (const value& l, const value& r) + { + auto& ls (l.as> ()); + auto& rs (r.as> ()); + + auto li (ls.begin ()), le (ls.end ()); + auto ri (rs.begin ()), re (rs.end ()); + + for (; li != le && ri != re; ++li, ++ri) + if (int r = value_traits::compare (*li, *ri)) + return r; + + if (li == le && ri != re) // l shorter than r. + return -1; + + if (ri == re && li != le) // r shorter than l. + return 1; + + return 0; + } + + // Map subscript to set::contains(). + // + template + value + set_subscript (const value& val, value*, + value&& sub, + const location& sloc, + const location& bloc) + { + // Process subscript even if the value is null to make sure it is valid. + // + T k; + try + { + k = convert (move (sub)); + } + catch (const invalid_argument& e) + { + fail (sloc) << "invalid " << value_traits>::value_type.name + << " value subscript: " << e << + info (bloc) << "use the '\\[' escape sequence if this is a " + << "wildcard pattern"; + } + + bool r (false); + if (!val.null) + { + const auto& s (val.as> ()); + r = s.find (k) != s.end (); + } + + return value (r); + } + + // Make sure these are static-initialized together. Failed that VC will make + // sure it's done in the wrong order. + // + template + struct set_value_type: value_type + { + string type_name; + + set_value_type (value_type&& v) + : value_type (move (v)) + { + // set + // + type_name = "set<"; + type_name += value_traits::type_name; + type_name += '>'; + name = type_name.c_str (); + } + }; + + // Convenience aliases for certain set cases. + // + template <> + struct set_value_type: value_type + { + set_value_type (value_type&& v) + : value_type (move (v)) + { + name = "string_set"; + } + }; + + template + const set value_traits>::empty_instance; + + template + const set_value_type + value_traits>::value_type = build2::value_type // VC14 wants =. + { + nullptr, // Patched above. + sizeof (set), + nullptr, // No base. + true, // Container. + &value_traits::value_type, // Element type. + &default_dtor>, + &default_copy_ctor>, + &default_copy_assign>, + &set_assign, + &set_append, + &set_append, // Prepend the same as append. + &set_reverse, + nullptr, // No cast (cast data_ directly). + &set_compare, + &default_empty>, + &set_subscript, + nullptr // Iterate. + }; + // map value // template diff --git a/tests/function/string/testscript b/tests/function/string/testscript index a363cc3..96f5c52 100644 --- a/tests/function/string/testscript +++ b/tests/function/string/testscript @@ -46,7 +46,8 @@ $* <'print $size([string] abc)' >'3' : basics $* <'print $size([string] )' >'0' : zero $* <'print $size([strings] a b c)' >'3' : strings - $* <'print $size([string_map] a@1 b@2 c@3)' >'3' : string_map + $* <'print $size([string_set] a b b)' >'2' : string-set + $* <'print $size([string_map] a@1 b@2 b@3)' >'2' : string-map } : find diff --git a/tests/type/set/buildfile b/tests/type/set/buildfile new file mode 100644 index 0000000..55b37bb --- /dev/null +++ b/tests/type/set/buildfile @@ -0,0 +1,4 @@ +# file : tests/type/set/buildfile +# license : MIT; see accompanying LICENSE file + +./: testscript $b diff --git a/tests/type/set/testscript b/tests/type/set/testscript new file mode 100644 index 0000000..3897220 --- /dev/null +++ b/tests/type/set/testscript @@ -0,0 +1,52 @@ +# file : tests/type/set/testscript +# license : MIT; see accompanying LICENSE file + +# See also tests in function/*/ (size()). + +.include ../../common.testscript + +: basics +: +$* <>EOO +s = [string_set] a b a +print $s +s += c b +print $s +s =+ d b +print $s +EOI +a b +a b c +a b c d +EOO + +: type +: +$* <>EOO +s = [string_set] +print $type($s) +EOI +string_set +EOO + +: subscript +: +$* <>EOO +s = [string_set] a b c +print ($s[b]) +print ($s[z]) +EOI +true +false +EOO + +: iteration +: +$* <>EOO +for s: [string_set] a b c + print $s +EOI +a +b +c +EOO -- cgit v1.1