diff options
Diffstat (limited to 'libbuild2/variable.cxx')
-rw-r--r-- | libbuild2/variable.cxx | 1810 |
1 files changed, 1658 insertions, 152 deletions
diff --git a/libbuild2/variable.cxx b/libbuild2/variable.cxx index f5476b5..078c13a 100644 --- a/libbuild2/variable.cxx +++ b/libbuild2/variable.cxx @@ -3,10 +3,16 @@ #include <libbuild2/variable.hxx> -#include <cstring> // memcmp() +#include <cstring> // memcmp(), memcpy() -#include <libbutl/path-pattern.mxx> +#include <libbutl/path-pattern.hxx> +#ifndef BUILD2_BOOTSTRAP +# include <libbutl/json/parser.hxx> +# include <libbutl/json/serializer.hxx> +#endif + +#include <libbuild2/target.hxx> #include <libbuild2/diagnostics.hxx> using namespace std; @@ -46,7 +52,7 @@ namespace build2 } value:: - value (value&& v) + value (value&& v) noexcept : type (v.type), null (v.null), extra (v.extra) { if (!null) @@ -56,7 +62,7 @@ namespace build2 else if (type->copy_ctor != nullptr) type->copy_ctor (*this, v, true); else - data_ = v.data_; // Copy as POD. + memcpy (data_, v.data_, size_); // Copy as POD. } } @@ -71,7 +77,7 @@ namespace build2 else if (type->copy_ctor != nullptr) type->copy_ctor (*this, v, false); else - data_ = v.data_; // Copy as POD. + memcpy (data_, v.data_, size_); // Copy as POD. } } @@ -98,12 +104,14 @@ namespace build2 if (null) new (&data_) names (move (v).as<names> ()); else + // Note: can throw (see small_vector for details). + // as<names> () = move (v).as<names> (); } else if (auto f = null ? type->copy_ctor : type->copy_assign) f (*this, v, true); else - data_ = v.data_; // Assign as POD. + memcpy (data_, v.data_, size_); // Assign as POD. null = v.null; } @@ -142,7 +150,7 @@ namespace build2 else if (auto f = null ? type->copy_ctor : type->copy_assign) f (*this, v, false); else - data_ = v.data_; // Assign as POD. + memcpy (data_, v.data_, size_); // Assign as POD. null = v.null; } @@ -366,8 +374,8 @@ namespace build2 // Typification is kind of like caching so we reuse that mutex shard. // shared_mutex& m ( - ctx.mutexes.variable_cache[ - hash<value*> () (&v) % ctx.mutexes.variable_cache_size]); + ctx.mutexes->variable_cache[ + hash<value*> () (&v) % ctx.mutexes->variable_cache_size]); // Note: v.type is rechecked by typify() under lock. // @@ -376,7 +384,7 @@ namespace build2 } void - untypify (value& v) + untypify (value& v, bool reduce) { if (v.type == nullptr) return; @@ -388,7 +396,7 @@ namespace build2 } names ns; - names_view nv (v.type->reverse (v, ns)); + names_view nv (v.type->reverse (v, ns, reduce)); if (nv.empty () || nv.data () == ns.data ()) { @@ -431,26 +439,33 @@ namespace build2 // Throw invalid_argument for an invalid simple value. // [[noreturn]] static void - throw_invalid_argument (const name& n, const name* r, const char* type) + throw_invalid_argument (const name& n, + const name* r, + const char* type, + bool pair_ok = false) { string m; string t (type); - if (r != nullptr) + // Note that the message should be suitable for appending "in variable X". + // + if (!pair_ok && r != nullptr) m = "pair in " + t + " value"; + else if (n.pattern || (r != nullptr && r->pattern)) + m = "pattern in " + t + " value"; else { - m = "invalid " + t + " value: "; + m = "invalid " + t + " value "; if (n.simple ()) - m += "'" + n.value + "'"; + m += '\'' + n.value + '\''; else if (n.directory ()) - m += "'" + n.dir.representation () + "'"; + m += '\'' + n.dir.representation () + '\''; else - m += "complex name"; + m += "name '" + to_string (n) + '\''; } - throw invalid_argument (m); + throw invalid_argument (move (m)); } // names @@ -462,7 +477,7 @@ namespace build2 bool value_traits<bool>:: convert (const name& n, const name* r) { - if (r == nullptr && n.simple ()) + if (r == nullptr && !n.pattern && n.simple ()) { const string& s (n.value); @@ -485,6 +500,7 @@ namespace build2 type_name, sizeof (bool), nullptr, // No base. + false, // Not container. nullptr, // No element. nullptr, // No dtor (POD). nullptr, // No copy_ctor (POD). @@ -495,7 +511,9 @@ namespace build2 &simple_reverse<bool>, nullptr, // No cast (cast data_ directly). nullptr, // No compare (compare as POD). - nullptr // Never empty. + nullptr, // Never empty. + nullptr, // Subscript. + nullptr // Iterate. }; // int64_t value @@ -503,17 +521,26 @@ namespace build2 int64_t value_traits<int64_t>:: convert (const name& n, const name* r) { - if (r == nullptr && n.simple ()) + if (r == nullptr && !n.pattern && n.simple ()) { try { - // May throw invalid_argument or out_of_range. - // - size_t i; - int64_t r (stoll (n.value, &i)); + const string& v (n.value); - if (i == n.value.size ()) - return r; + if (!wspace (v[0])) + { + // Note that unlike uint64, we don't support hex notation for int64. + + // May throw invalid_argument or out_of_range. + // + size_t i; + int64_t r (stoll (v, &i)); + + if (i == v.size ()) + return r; + + // Fall through. + } // Fall through. } @@ -533,6 +560,7 @@ namespace build2 type_name, sizeof (int64_t), nullptr, // No base. + false, // Not container. nullptr, // No element. nullptr, // No dtor (POD). nullptr, // No copy_ctor (POD). @@ -543,7 +571,9 @@ namespace build2 &simple_reverse<int64_t>, nullptr, // No cast (cast data_ directly). nullptr, // No compare (compare as POD). - nullptr // Never empty. + nullptr, // Never empty. + nullptr, // Subscript. + nullptr // Iterate. }; // uint64_t value @@ -551,17 +581,28 @@ namespace build2 uint64_t value_traits<uint64_t>:: convert (const name& n, const name* r) { - if (r == nullptr && n.simple ()) + if (r == nullptr && !n.pattern && n.simple ()) { try { - // May throw invalid_argument or out_of_range. - // - size_t i; - uint64_t r (stoull (n.value, &i)); + const string& v (n.value); + + if (!wspace (v[0])) + { + // Note: see also similar code in to_json_value(). + // + int b (v[0] == '0' && (v[1] == 'x' || v[1] == 'X') ? 16 : 10); + + // May throw invalid_argument or out_of_range. + // + size_t i; + uint64_t r (stoull (v, &i, b)); + + if (i == v.size ()) + return r; - if (i == n.value.size ()) - return r; + // Fall through. + } // Fall through. } @@ -581,6 +622,7 @@ namespace build2 type_name, sizeof (uint64_t), nullptr, // No base. + false, // Not container. nullptr, // No element. nullptr, // No dtor (POD). nullptr, // No copy_ctor (POD). @@ -591,7 +633,9 @@ namespace build2 &simple_reverse<uint64_t>, nullptr, // No cast (cast data_ directly). nullptr, // No compare (compare as POD). - nullptr // Never empty. + nullptr, // Never empty. + nullptr, // Subscript. + nullptr // Iterate. }; // string value @@ -604,21 +648,31 @@ namespace build2 // the common cases (unqualified, unpaired simple name or directory). // - // We can only convert project-qualified simple and directory names. + // We can only convert project-qualified untyped names. // - if (!(n.simple (true) || n.directory (true)) || - !(r == nullptr || r->simple (true) || r->directory (true))) - throw_invalid_argument (n, r, "string"); + if (n.pattern || n.typed ()) + throw_invalid_argument (n, nullptr, "string"); + + if (r != nullptr) + { + if (r->pattern || r->typed ()) + throw_invalid_argument (*r, nullptr, "string"); + } string s; - if (n.directory (true)) + if (n.simple (true)) + s.swap (n.value); + else + { // Note that here we cannot assume what's in dir is really a // path (think s/foo/bar/) so we have to reverse it exactly. // s = move (n.dir).representation (); // Move out of path. - else - s.swap (n.value); + + if (!n.value.empty ()) + s += n.value; // Separator is already there. + } // Convert project qualification to its string representation. // @@ -642,10 +696,15 @@ namespace build2 s += '%'; } - if (r->directory (true)) - s += move (r->dir).representation (); - else + if (r->simple (true)) s += r->value; + else + { + s += move (r->dir).representation (); + + if (!r->value.empty ()) + s += r->value; + } } return s; @@ -660,6 +719,7 @@ namespace build2 type_name, sizeof (string), nullptr, // No base. + false, // Not container. nullptr, // No element. &default_dtor<string>, &default_copy_ctor<string>, @@ -670,7 +730,9 @@ namespace build2 &simple_reverse<string>, nullptr, // No cast (cast data_ directly). &simple_compare<string>, - &default_empty<string> + &default_empty<string>, + nullptr, // Subscript. + nullptr // Iterate. }; // path value @@ -678,7 +740,7 @@ namespace build2 path value_traits<path>:: convert (name&& n, name* r) { - if (r == nullptr) + if (r == nullptr && !n.pattern) { // A directory path is a path. // @@ -727,6 +789,7 @@ namespace build2 type_name, sizeof (path), nullptr, // No base. + false, // Not container. nullptr, // No element. &default_dtor<path>, &default_copy_ctor<path>, @@ -737,7 +800,9 @@ namespace build2 &simple_reverse<path>, nullptr, // No cast (cast data_ directly). &simple_compare<path>, - &default_empty<path> + &default_empty<path>, + nullptr, // Subscript. + nullptr // Iterate. }; // dir_path value @@ -745,7 +810,7 @@ namespace build2 dir_path value_traits<dir_path>:: convert (name&& n, name* r) { - if (r == nullptr) + if (r == nullptr && !n.pattern) { if (n.directory ()) return move (n.dir); @@ -794,6 +859,7 @@ namespace build2 sizeof (dir_path), &value_traits<path>::value_type, // Base (assuming direct cast works for // both). + false, // Not container. nullptr, // No element. &default_dtor<dir_path>, &default_copy_ctor<dir_path>, @@ -804,7 +870,9 @@ namespace build2 &simple_reverse<dir_path>, nullptr, // No cast (cast data_ directly). &simple_compare<dir_path>, - &default_empty<dir_path> + &default_empty<dir_path>, + nullptr, // Subscript. + nullptr // Iterate. }; // abs_dir_path value @@ -812,7 +880,7 @@ namespace build2 abs_dir_path value_traits<abs_dir_path>:: convert (name&& n, name* r) { - if (r == nullptr && (n.simple () || n.directory ())) + if (r == nullptr && !n.pattern && (n.simple () || n.directory ())) { try { @@ -828,7 +896,14 @@ namespace build2 return abs_dir_path (move (d)); } - catch (const invalid_path&) {} // Fall through. + catch (invalid_path& e) + { + // We moved from name so reconstruct the path. Let's always make it + // simple since we may not be able to construct dir_path. Should be + // good enough for diagnostics. + // + n.value = move (e.path); + } } throw_invalid_argument (n, r, "abs_dir_path"); @@ -842,6 +917,7 @@ namespace build2 sizeof (abs_dir_path), &value_traits<dir_path>::value_type, // Base (assuming direct cast works // for both). + false, // Not container. nullptr, // No element. &default_dtor<abs_dir_path>, &default_copy_ctor<abs_dir_path>, @@ -852,7 +928,9 @@ namespace build2 &simple_reverse<abs_dir_path>, nullptr, // No cast (cast data_ directly). &simple_compare<abs_dir_path>, - &default_empty<abs_dir_path> + &default_empty<abs_dir_path>, + nullptr, // Subscript. + nullptr // Iterate. }; // name value @@ -860,17 +938,17 @@ namespace build2 name value_traits<name>:: convert (name&& n, name* r) { - if (r == nullptr) + if (r == nullptr && !n.pattern) return move (n); throw_invalid_argument (n, r, "name"); } static names_view - name_reverse (const value& v, names&) + name_reverse (const value& v, names&, bool reduce) { const name& n (v.as<name> ()); - return n.empty () ? names_view (nullptr, 0) : names_view (&n, 1); + return reduce && n.empty () ? names_view (nullptr, 0) : names_view (&n, 1); } const char* const value_traits<name>::type_name = "name"; @@ -880,6 +958,7 @@ namespace build2 type_name, sizeof (name), nullptr, // No base. + false, // Not container. nullptr, // No element. &default_dtor<name>, &default_copy_ctor<name>, @@ -890,7 +969,9 @@ namespace build2 &name_reverse, nullptr, // No cast (cast data_ directly). &simple_compare<name>, - &default_empty<name> + &default_empty<name>, + nullptr, // Subscript. + nullptr // Iterate. }; // name_pair @@ -898,6 +979,9 @@ namespace build2 name_pair value_traits<name_pair>:: convert (name&& n, name* r) { + if (n.pattern || (r != nullptr && r->pattern)) + throw_invalid_argument (n, r, "name_pair", true /* pair_ok */); + n.pair = '\0'; // Keep "unpaired" in case r is empty. return name_pair (move (n), r != nullptr ? move (*r) : name ()); } @@ -931,13 +1015,13 @@ namespace build2 } static names_view - name_pair_reverse (const value& v, names& ns) + name_pair_reverse (const value& v, names& ns, bool reduce) { const name_pair& p (v.as<name_pair> ()); const name& f (p.first); const name& s (p.second); - if (f.empty () && s.empty ()) + if (reduce && f.empty () && s.empty ()) return names_view (nullptr, 0); if (f.empty ()) @@ -959,6 +1043,7 @@ namespace build2 type_name, sizeof (name_pair), nullptr, // No base. + false, // Not container. nullptr, // No element. &default_dtor<name_pair>, &default_copy_ctor<name_pair>, @@ -969,7 +1054,9 @@ namespace build2 &name_pair_reverse, nullptr, // No cast (cast data_ directly). &simple_compare<name_pair>, - &default_empty<name_pair> + &default_empty<name_pair>, + nullptr, // Subscript. + nullptr // Iterate. }; // process_path value @@ -978,8 +1065,8 @@ namespace build2 static T process_path_convert (name&& n, name* r, const char* what) { - if ( n.untyped () && n.unqualified () && !n.empty () && - (r == nullptr || (r->untyped () && r->unqualified () && !r->empty ()))) + if ( !n.pattern && n.untyped () && n.unqualified () && !n.empty () && + (r == nullptr || (!r->pattern && r->untyped () && r->unqualified () && !r->empty ()))) { path rp (move (n.dir)); if (rp.empty ()) @@ -1002,7 +1089,7 @@ namespace build2 return pp; } - throw_invalid_argument (n, r, what); + throw_invalid_argument (n, r, what, true /* pair_ok */); } process_path value_traits<process_path>:: @@ -1089,10 +1176,14 @@ namespace build2 } static names_view - process_path_reverse (const value& v, names& s) + process_path_reverse (const value& v, names& s, bool) { const auto& x (v.as<process_path> ()); + // Note that strictly speaking process_path doesn't have empty + // representation (see convert() above). Thus we always return reduced + // representation. + // if (!x.empty ()) { s.reserve (x.effect.empty () ? 1 : 2); @@ -1109,6 +1200,7 @@ namespace build2 type_name, sizeof (process_path), nullptr, // No base. + false, // Not container. nullptr, // No element. &default_dtor<process_path>, &process_path_copy_ctor<process_path>, @@ -1119,7 +1211,9 @@ namespace build2 &process_path_reverse, nullptr, // No cast (cast data_ directly). &simple_compare<process_path>, - &default_empty<process_path> + &default_empty<process_path>, + nullptr, // Subscript. + nullptr // Iterate. }; // process_path_ex value @@ -1141,7 +1235,7 @@ namespace build2 if (!i->pair) throw invalid_argument ("non-pair in process_path_ex value"); - if (!i->simple ()) + if (i->pattern || !i->simple ()) throw_invalid_argument (*i, nullptr, "process_path_ex"); const string& k ((i++)->value); @@ -1150,18 +1244,27 @@ namespace build2 // if (k == "name") { - if (!i->simple ()) + if (i->pattern || !i->simple ()) throw_invalid_argument (*i, nullptr, "process_path_ex name"); pp.name = move (i->value); } else if (k == "checksum") { - if (!i->simple ()) - throw_invalid_argument (*i, nullptr, "process_path_ex checksum"); + if (i->pattern || !i->simple ()) + throw_invalid_argument ( + *i, nullptr, "process_path_ex executable checksum"); pp.checksum = move (i->value); } + else if (k == "env-checksum") + { + if (i->pattern || !i->simple ()) + throw_invalid_argument ( + *i, nullptr, "process_path_ex environment checksum"); + + pp.env_checksum = move (i->value); + } else throw invalid_argument ( "unknown key '" + k + "' in process_path_ex value"); @@ -1176,7 +1279,9 @@ namespace build2 auto b (ns.begin ()), i (b), e (ns.end ()); for (i += i->pair ? 2 : 1; i != e && i->pair; i += 2) { - if (!i->simple () || (i->value != "name" && i->value != "checksum")) + if (!i->simple () || (i->value != "name" && + i->value != "checksum" && + i->value != "env-checksum")) break; } @@ -1217,6 +1322,7 @@ namespace build2 lhs.name = move (rhs.name); lhs.checksum = move (rhs.checksum); + lhs.env_checksum = move (rhs.env_checksum); } else { @@ -1224,6 +1330,7 @@ namespace build2 lhs.name = rhs.name; lhs.checksum = rhs.checksum; + lhs.env_checksum = rhs.env_checksum; } } @@ -1244,15 +1351,19 @@ namespace build2 } static names_view - process_path_ex_reverse (const value& v, names& s) + process_path_ex_reverse (const value& v, names& s, bool) { const auto& x (v.as<process_path_ex> ()); + // Note that process_path_ex only has reduced empty representation (see + // convert() above). + // if (!x.empty ()) { s.reserve ((x.effect.empty () ? 1 : 2) + (x.name ? 2 : 0) + - (x.checksum ? 2 : 0)); + (x.checksum ? 2 : 0) + + (x.env_checksum ? 2 : 0)); process_path_reverse_impl (x, s); @@ -1269,6 +1380,13 @@ namespace build2 s.back ().pair = '@'; s.push_back (name (*x.checksum)); } + + if (x.env_checksum) + { + s.push_back (name ("env-checksum")); + s.back ().pair = '@'; + s.push_back (name (*x.env_checksum)); + } } return s; @@ -1283,6 +1401,7 @@ namespace build2 sizeof (process_path_ex), &value_traits< // Base (assuming direct cast works process_path>::value_type, // for both). + false, // Not container. nullptr, // No element. &default_dtor<process_path_ex>, &process_path_ex_copy_ctor, @@ -1293,7 +1412,9 @@ namespace build2 &process_path_ex_reverse, nullptr, // No cast (cast data_ directly). &simple_compare<process_path>, // For now compare as process_path. - &default_empty<process_path_ex> + &default_empty<process_path_ex>, + nullptr, // Subscript. + nullptr // Iterate. }; // target_triplet value @@ -1301,22 +1422,17 @@ namespace build2 target_triplet value_traits<target_triplet>:: convert (name&& n, name* r) { - if (r == nullptr) + if (r == nullptr && !n.pattern && n.simple ()) { - if (n.simple ()) + try { - try - { - return n.empty () ? target_triplet () : target_triplet (n.value); - } - catch (const invalid_argument& e) - { - throw invalid_argument ( - string ("invalid target_triplet value: ") + e.what ()); - } + return n.empty () ? target_triplet () : target_triplet (n.value); + } + catch (const invalid_argument& e) + { + throw invalid_argument ( + string ("invalid target_triplet value: ") + e.what ()); } - - // Fall through. } throw_invalid_argument (n, r, "target_triplet"); @@ -1329,6 +1445,7 @@ namespace build2 type_name, sizeof (target_triplet), nullptr, // No base. + false, // Not container. nullptr, // No element. &default_dtor<target_triplet>, &default_copy_ctor<target_triplet>, @@ -1339,7 +1456,9 @@ namespace build2 &simple_reverse<target_triplet>, nullptr, // No cast (cast data_ directly). &simple_compare<target_triplet>, - &default_empty<target_triplet> + &default_empty<target_triplet>, + nullptr, // Subscript. + nullptr // Iterate. }; // project_name value @@ -1347,22 +1466,17 @@ namespace build2 project_name value_traits<project_name>:: convert (name&& n, name* r) { - if (r == nullptr) + if (r == nullptr && !n.pattern && n.simple ()) { - if (n.simple ()) + try { - try - { - return n.empty () ? project_name () : project_name (move (n.value)); - } - catch (const invalid_argument& e) - { - throw invalid_argument ( - string ("invalid project_name value: ") + e.what ()); - } + return n.empty () ? project_name () : project_name (move (n.value)); + } + catch (const invalid_argument& e) + { + throw invalid_argument ( + string ("invalid project_name value: ") + e.what ()); } - - // Fall through. } throw_invalid_argument (n, r, "project_name"); @@ -1378,6 +1492,7 @@ namespace build2 type_name, sizeof (project_name), nullptr, // No base. + false, // Not container. nullptr, // No element. &default_dtor<project_name>, &default_copy_ctor<project_name>, @@ -1388,7 +1503,1105 @@ namespace build2 &simple_reverse<project_name>, nullptr, // No cast (cast data_ directly). &simple_compare<project_name>, - &default_empty<project_name> + &default_empty<project_name>, + nullptr, // Subscript. + nullptr // Iterate. + }; + + // json + // + static string + to_string_value (name& n, const char* what) + { + if (n.typed () || n.qualified () || n.pattern) + throw_invalid_argument (n, nullptr, what); + + string s; + + if (n.simple ()) + s.swap (n.value); + else + { + // Note that here we cannot assume what's in dir is really a path (think + // s/foo/bar/) so we have to reverse it exactly. + // + s = move (n.dir).representation (); // Move out of path. + + if (!n.value.empty ()) + s += n.value; // Separator is already there. + } + + return s; + } + + static json_value + to_json_value (name& n, const char* what) + { + if (n.typed () || n.qualified () || n.pattern) + throw_invalid_argument (n, nullptr, what); + + string s; + + if (n.simple ()) + s.swap (n.value); + else + { + // Note that here we cannot assume what's in dir is really a path (think + // s/foo/bar/) so we have to reverse it exactly. + // + s = move (n.dir).representation (); // Move out of path. + + if (!n.value.empty ()) + s += n.value; // Separator is already there. + + // A path is always interpreted as a JSON string. + // + return json_value (move (s)); + } + + bool f; + if (s.empty ()) + return json_value (string ()); + if (s == "null") + return json_value (); + else if ((f = (s == "true")) || s == "false") + return json_value (f); + else if (s.find_first_not_of ( + "0123456789", (f = (s[0] == '-')) ? 1 : 0) == string::npos) + { + name n (move (s)); + return f + ? json_value (value_traits<int64_t>::convert (n, nullptr)) + : json_value (value_traits<uint64_t>::convert (n, nullptr)); + } + // + // Handle the hex notation similar to <uint64_t>::convert() (and JSON5). + // + else if (s[0] == '0' && + (s[1] == 'x' || s[1] == 'X') && + s.size () > 2 && + s.find_first_not_of ("0123456789aAbBcCdDeEfF", 2) == string::npos) + { + return json_value ( + value_traits<uint64_t>::convert (name (move (s)), nullptr), + true /* hex */); + } + else + { + // If this is not a JSON representation of string, array, or object, + // then treat it as a string. + // + // Note that the special `"`, `{`, and `[` characters could be preceded + // with whitespaces. Note: see similar test in json_object below. + // + size_t p (s.find_first_not_of (" \t\n\r")); + + if (p == string::npos || (s[p] != '"' && s[p] != '{' && s[p] != '[')) + return json_value (move (s)); + + // Parse as valid JSON input text. + // +#ifndef BUILD2_BOOTSTRAP + try + { + json_parser p (s, nullptr /* name */); + return json_value (p); + } + catch (const invalid_json_input& e) + { + // Turned out printing line/column/offset can be misleading since we + // could be parsing a single name from a potential list of names. + // feels like without also printing the value this is of not much use. + // +#if 0 + string m ("invalid json input at line "); + m += to_string (e.line); + m += ", column "; + m += to_string (e.column); + m += ", byte offset "; + m += to_string (e.position); + m += ": "; + m += e.what (); +#else + string m ("invalid json input: "); + m += e.what (); +#endif + throw invalid_argument (move (m)); + } +#else + throw invalid_argument ("json parsing requested during bootstrap"); +#endif + } + } + + json_value value_traits<json_value>:: + convert (name&& l, name* r) + { + // Here we expect either a simple value or a serialized representation. + // + if (r != nullptr) + throw invalid_argument ("pair in json element value"); + + return to_json_value (l, "json element"); + } + + json_value value_traits<json_value>:: + convert (names&& ns) + { + size_t n (ns.size ()); + + if (n == 0) + { + // Note: this is the ([json] ) case, not ([json] ""). See also the + // relevant note in json_reverse() below. + // + return json_value (); // null + } + else if (n == 1) + { + return to_json_value (ns.front (), "json"); + } + else + { + if (ns.front ().pair) // object + { + json_value r (json_type::object); + r.object.reserve (n / 2); + + for (auto i (ns.begin ()); i != ns.end (); ++i) + { + if (!i->pair) + throw invalid_argument ( + "expected pair in json member value '" + to_string (*i) + '\''); + + // Note that we could support JSON-quoted member names but it's + // unclear why would someone want that (and if they do, they can + // always specify JSON text instead). + // + // @@ The empty pair value ([json] one@ ) which is currently empty + // string is inconsistent with empty value ([json] ) above which + // is null. Maybe we could distinguish the one@ and one@"" cases + // via type hints? + // + string n (to_string_value (*i, "json member name")); + json_value v (to_json_value (*++i, "json member")); + + // Check for duplicates. One can use append/prepend to merge. + // + if (find_if (r.object.begin (), r.object.end (), + [&n] (const json_member& m) + { + return m.name == n; + }) != r.object.end ()) + { + throw invalid_argument ( + "duplicate json object member '" + n + '\''); + } + + r.object.push_back (json_member {move (n), move (v)}); + } + + return r; + } + else // array + { + json_value r (json_type::array); + r.array.reserve (n); + + for (name& n: ns) + { + if (n.pair) + throw invalid_argument ( + "unexpected pair in json array element value '" + + to_string (n) + '\''); + + r.array.push_back (to_json_value (n, "json array element")); + } + + return r; + } + } + } + + static void + json_assign (value& v, names&& ns, const variable* var) + { + using traits = value_traits<json_value>; + + try + { + traits::assign (v, traits::convert (move (ns))); + } + catch (const invalid_argument& e) + { + // Note: ns is not guaranteed to be valid. + // + diag_record dr (fail); + dr << "invalid json value"; + + if (var != nullptr) + dr << " in variable " << var->name; + + dr << ": " << e; + } + } + + static void + json_append (value& v, names&& ns, const variable* var) + { + using traits = value_traits<json_value>; + + try + { + traits::append (v, traits::convert (move (ns))); + } + catch (const invalid_argument& e) + { + // Note: ns is not guaranteed to be valid. + // + diag_record dr (fail); + dr << "invalid json value"; + + if (var != nullptr) + dr << " in variable " << var->name; + + dr << ": " << e; + } + } + + static void + json_prepend (value& v, names&& ns, const variable* var) + { + using traits = value_traits<json_value>; + + try + { + traits::prepend (v, traits::convert (move (ns))); + } + catch (const invalid_argument& e) + { + // Note: ns is not guaranteed to be valid. + // + diag_record dr (fail); + dr << "invalid json value"; + + if (var != nullptr) + dr << " in variable " << var->name; + + dr << ": " << e; + } + } + + name value_traits<json_value>:: + reverse (const json_value& v) + { + switch (v.type) + { + case json_type::null: + { + // Note that here we cannot return empty (e.g., to be consistent with + // other places) because we treat empty name (as opposed to empty + // names) as string, not null (see to_json_value() above). + // + // Thankfully this version of reverse() is only used when json_value + // representation is needed as part of a container. Which means in + // "consumption" contexts (e.g., result of subscript) null will still + // decay to empty. + // +#if 1 + return name ("null"); +#else + return name (); +#endif + } + case json_type::boolean: + { + return name (v.boolean ? "true" : "false"); + } + case json_type::signed_number: + { + return value_traits<int64_t>::reverse (v.signed_number); + } + case json_type::unsigned_number: + { + return value_traits<uint64_t>::reverse (v.unsigned_number); + } + case json_type::hexadecimal_number: + { + return name (to_string (v.unsigned_number, 16)); + } + case json_type::string: + // + // @@ Hm, it would be nice if this somehow got mapped to unquoted + // string but still be round-trippable to JSON value. Perhaps via + // the type hint idea? This is pretty bad. See also subscript we + // hacked around this somewhat. + // + // Note that it may be tempting to fix this by only quoting strings + // that would otherwise be mis-interpreted (null, true, all digits, + // etc). But that would be worse: things would seem to work but + // fall apart in the perhaps unlikely event of encountering one of + // the problematic values. It is better to produce a consistent + // result. + // + case json_type::array: + case json_type::object: + { + // Serialize as JSON output text. + // + string o; + +#ifndef BUILD2_BOOTSTRAP + try + { + // Disable pretty-printing so that the output is all on the same + // line. While it's not going to be easy to read for larger JSON + // outputs, it will fit better into the existing model where none of + // the value representations use formatting newlines. If a pretty- + // printed representation is required, then the $json.serialize() + // function can be used to obtain it. + // + json_buffer_serializer s (o, 0 /* indentation */); + v.serialize (s); + } + catch (const invalid_json_output& e) + { + // Note that while it feels like value_traits::reverse() should + // throw invalid_argument, we don't currently handle it anywhere so + // for now let's just fail. + // + // Note: the same diagnostics as in $json.serialize(). + // + diag_record dr; + dr << fail << "invalid json value: " << e; + + if (e.event) + dr << info << "while serializing " << to_string (*e.event); + + if (e.offset != string::npos) + dr << info << "offending byte offset " << e.offset; + } +#else + fail << "json serialization requested during bootstrap"; +#endif + return name (move (o)); + } + } + + assert (false); + return name (); + } + + static names_view + json_reverse (const value& x, names& ns, bool reduce) + { + const json_value& v (x.as<json_value> ()); + + // @@ Hm, it would be nice if JSON null somehow got mapped to [null]/empty + // but still be round-trippable to JSON null. Perhaps via type hint? + // + // But won't `print ([json] null)` printing nothing be surprising. + // Also, it's not clear that mapping JSON null to out [null] is a good + // idea since our [null] means "no value" while JSON null means "null + // value". + // + // Maybe the current semantics is the best: we map our [null] and empty + // names to JSON null (naturally) but we always reverse JSON null to + // the JSON "null" literal. Or maybe we could reverse it to null but + // type-hint it that it's a spelling or [null]/empty. Quite fuzzy, + // admittedly. In our model null values decay to empty so JSON null + // decaying to "null" literal is strange. Let's try and see how it + // goes. See also json_subscript_impl() below. + // + if (v.type != json_type::null || !reduce) + ns.push_back (value_traits<json_value>::reverse (v)); + + return ns; + } + + static int + json_compare (const value& l, const value& r) + { + return l.as<json_value> ().compare (r.as<json_value> ()); + } + + // Return the value as well as the indication of whether the index/name is + // in range. + // + static pair<value, bool> + json_subscript_impl (const value& val, value* val_data, + uint64_t i, const string& n, bool index) + { + const json_value& jv (val.as<json_value> ()); + + json_value jr; + + if (index) + { + if (i >= (jv.type == json_type::array ? jv.array.size () : + jv.type == json_type::object ? jv.object.size () : + jv.type == json_type::null ? 0 : 1)) + return make_pair (value (), false); + + switch (jv.type) + { + case json_type::boolean: + case json_type::signed_number: + case json_type::unsigned_number: + case json_type::hexadecimal_number: + case json_type::string: + { + // Steal the value if possible. + // + jr = (&val == val_data + ? json_value (move (const_cast<json_value&> (jv))) + : json_value (jv)); + break; + } + case json_type::array: + { + // Steal the value if possible. + // + const json_value& r (jv.array[i]); + jr = (&val == val_data + ? json_value (move (const_cast<json_value&> (r))) + : json_value (r)); + break; + } + case json_type::object: + { + // Represent as an object with one member. + // + new (&jr.object) json_value::object_type (); + jr.type = json_type::object; + + // Steal the member if possible. + // + const json_member& m (jv.object[i]); + jr.object.push_back (&val == val_data + ? json_member (move (const_cast<json_member&> (m))) + : json_member (m)); + break; + } + case json_type::null: + assert (false); + } + } + else + { + auto i (find_if (jv.object.begin (), + jv.object.end (), + [&n] (const json_member& m) + { + return m.name == n; + })); + + if (i == jv.object.end ()) + return make_pair (value (), false); + + // Steal the member value if possible. + // + jr = (&val == val_data + ? json_value (move (const_cast<json_value&> (i->value))) + : json_value (i->value)); + } + + // @@ As a temporary work around for the lack of type hints (see + // json_reverse() for background), reverse simple JSON values to the + // corresponding fundamental type values. The thinking here is that + // subscript (and iteration) is primarily meant for consumption (as + // opposed to reverse() where it is used to build up values and thus + // needs things to be fully reversible). Once we add type hints, then + // this should become unnecessary and we should be able to just always + // return json_value. See also $json.member_value() where we do the + // same thing. + // + // @@ TODO: split this function into two (index/name) once get rid of this. + // + value r; + switch (jr.type) + { + // Seeing that we are reversing for consumption, it feels natural to + // reverse JSON null to our [null] rather than empty. This, in + // particular, helps chained subscript. + // +#if 0 + case json_type::null: r = value (names {}); break; +#else + case json_type::null: r = value (); break; +#endif + case json_type::boolean: r = value (jr.boolean); break; + case json_type::signed_number: r = value (jr.signed_number); break; + case json_type::unsigned_number: + case json_type::hexadecimal_number: r = value (jr.unsigned_number); break; + case json_type::string: r = value (move (jr.string)); break; + case json_type::array: + case json_type::object: r = value (move (jr)); break; + } + + return make_pair (move (r), true); + } + + static value + json_subscript (const value& val, value* val_data, + value&& sub, + const location& sloc, + const location& bloc) + { + const json_value* jv (val.null ? nullptr : &val.as<json_value> ()); + + // For consistency with other places treat JSON null value as maybe + // missing array/object. In particular, we don't want to fail trying to + // lookup by-name on a null value which could have been an object. + // + if (jv != nullptr && jv->type == json_type::null) + jv = nullptr; + + // Process subscript even if the value is null to make sure it is valid. + // + bool index; + uint64_t i (0); + string n; + + // Always interpret uint64-typed subscript as index even for objects. + // This can be used to, for example, to iterate with an index over object + // members. + // + if (!sub.null && sub.type == &value_traits<uint64_t>::value_type) + { + i = sub.as<uint64_t> (); + index = true; + } + else + { + // How we interpret the subscript depends on the JSON value type. For + // objects we treat it as a string (member name) and for everything else + // as an index. + // + // What if the value is null and we don't have a JSON type? In this case + // we treat as a string since a valid number is also a valid string. + // + try + { + if (jv == nullptr || jv->type == json_type::object) + { + n = convert<string> (move (sub)); + index = false; + } + else + { + i = convert<uint64_t> (move (sub)); + index = true; + } + } + catch (const invalid_argument& e) + { + // We will likely be trying to interpret a member name as an integer + // due to the incorrect value type so issue appropriate diagnostics. + // + diag_record dr; + dr << fail (sloc) << "invalid json value subscript: " << e; + + if (jv != nullptr && jv->type != json_type::object) + dr << info << "json value type is " << jv->type; + + dr << info (bloc) << "use the '\\[' escape sequence if this is a " + << "wildcard pattern" << endf; + } + } + + value r (jv != nullptr + ? json_subscript_impl (val, val_data, i, n, index).first + : value ()); + + // Typify null values so that we get called for chained subscripts. + // + if (r.null) + r.type = &value_traits<json_value>::value_type; + + return r; + } + + static void + json_iterate (const value& val, + const function<void (value&&, bool first)>& f) + { + // Implement in terms of subscript for consistency (in particular, + // iterating over simple values like number, string). + // + for (uint64_t i (0);; ++i) + { + pair<value, bool> e (json_subscript_impl (val, nullptr, i, {}, true)); + + if (!e.second) + break; + + f (move (e.first), i == 0); + } + } + + const json_value value_traits<json_value>::empty_instance; + const char* const value_traits<json_value>::type_name = "json"; + + // Note that whether the json value is a container or not depends on its + // payload type. However, for our purposes it feels correct to assume it is + // a container rather than not with itself as the element type (see + // value_traits::{container, element_type} usage for details). + // + const value_type value_traits<json_value>::value_type + { + type_name, + sizeof (json_value), + nullptr, // No base. + true, // Container. + &value_traits<json_value>::value_type, // Element (itself). + &default_dtor<json_value>, + &default_copy_ctor<json_value>, + &default_copy_assign<json_value>, + &json_assign, + json_append, + json_prepend, + &json_reverse, + nullptr, // No cast (cast data_ directly). + &json_compare, + &default_empty<json_value>, + &json_subscript, + &json_iterate + }; + + // json_array + // + json_array value_traits<json_array>:: + convert (names&& ns) + { + json_array r; + + size_t n (ns.size ()); + if (n == 0) + ; // Empty. + else if (n == 1) + { + // Tricky: this can still be JSON input text that is an array. And if + // it's not, then make it an element of an array. + // + // @@ Hm, this is confusing: [json_array] a = null ! Maybe not? But then + // this won't work: [json_array] a = ([json_array] null). Maybe + // distinguish in assign? + // + json_value v (to_json_value (ns.front (), "json")); + + if (v.type == json_type::array) + r.array = move (v.array); + else + r.array.push_back (move (v)); + } + else + { + r.array.reserve (n); + + for (name& n: ns) + { + if (n.pair) + throw invalid_argument ( + "unexpected pair in json array element value '" + + to_string (n) + '\''); + + r.array.push_back (to_json_value (n, "json array element")); + } + } + + return r; + } + + static void + json_array_assign (value& v, names&& ns, const variable* var) + { + using traits = value_traits<json_array>; + + try + { + traits::assign (v, traits::convert (move (ns))); + } + catch (const invalid_argument& e) + { + // Note: ns is not guaranteed to be valid. + // + diag_record dr (fail); + dr << "invalid json array"; + + if (var != nullptr) + dr << " in variable " << var->name; + + dr << ": " << e; + } + } + + static void + json_array_append (value& v, names&& ns, const variable* var) + { + using val_traits = value_traits<json_value>; + using arr_traits = value_traits<json_array>; + + try + { + arr_traits::append (v, val_traits::convert (move (ns))); + } + catch (const invalid_argument& e) + { + // Note: ns is not guaranteed to be valid. + // + diag_record dr (fail); + dr << "invalid json array"; + + if (var != nullptr) + dr << " in variable " << var->name; + + dr << ": " << e; + } + } + + static void + json_array_prepend (value& v, names&& ns, const variable* var) + { + using val_traits = value_traits<json_value>; + using arr_traits = value_traits<json_array>; + + try + { + arr_traits::prepend (v, val_traits::convert (move (ns))); + } + catch (const invalid_argument& e) + { + // Note: ns is not guaranteed to be valid. + // + diag_record dr (fail); + dr << "invalid json array"; + + if (var != nullptr) + dr << " in variable " << var->name; + + dr << ": " << e; + } + } + + const json_array value_traits<json_array>::empty_instance; + const char* const value_traits<json_array>::type_name = "json_array"; + + const value_type value_traits<json_array>::value_type + { + type_name, + sizeof (json_array), + &value_traits<json_value>::value_type, // Base (assuming direct cast works + // for both). + true, // Container. + &value_traits<json_value>::value_type, // Element (json_value). + &default_dtor<json_array>, + &default_copy_ctor<json_array>, + &default_copy_assign<json_array>, + &json_array_assign, + &json_array_append, + &json_array_prepend, + &json_reverse, + nullptr, // No cast (cast data_ directly). + &json_compare, + &default_empty<json_array>, + &json_subscript, + &json_iterate + }; + + // json_object + // + json_object value_traits<json_object>:: + convert (names&& ns) + { + json_object r; + + size_t n (ns.size ()); + if (n == 0) + ; // Empty. + else if (n == 1) + { + // Tricky: this can still be JSON input text that is an object. So do + // a similar check as in to_json_value() above. + // + name& n (ns.front ()); + + if (!n.simple () || n.pattern) + throw_invalid_argument (n, nullptr, "json object"); + + string& s (n.value); + size_t p (s.find_first_not_of (" \t\n\r")); + + if (p == string::npos || s[p] != '{') + { + // Unlike for array above, we cannot turn any value into a member. + // + throw invalid_argument ("expected json object instead of '" + s + '\''); + } + + json_value v (to_json_value (ns.front (), "json object")); + assert (v.type == json_type::object); + r.object = move (v.object); + } + else + { + r.object.reserve (n / 2); + + for (auto i (ns.begin ()); i != ns.end (); ++i) + { + if (!i->pair) + throw invalid_argument ( + "expected pair in json member value '" + to_string (*i) + '\''); + + string n (to_string_value (*i, "json member name")); + json_value v (to_json_value (*++i, "json member")); + + if (find_if (r.object.begin (), r.object.end (), + [&n] (const json_member& m) + { + return m.name == n; + }) != r.object.end ()) + { + throw invalid_argument ( + "duplicate json object member '" + n + '\''); + } + + r.object.push_back (json_member {move (n), move (v)}); + } + } + + return r; + } + + static void + json_object_assign (value& v, names&& ns, const variable* var) + { + using traits = value_traits<json_object>; + + try + { + traits::assign (v, traits::convert (move (ns))); + } + catch (const invalid_argument& e) + { + // Note: ns is not guaranteed to be valid. + // + diag_record dr (fail); + dr << "invalid json object"; + + if (var != nullptr) + dr << " in variable " << var->name; + + dr << ": " << e; + } + } + + static void + json_object_append (value& v, names&& ns, const variable* var) + { + using val_traits = value_traits<json_value>; + using obj_traits = value_traits<json_object>; + + try + { + obj_traits::append (v, val_traits::convert (move (ns))); + } + catch (const invalid_argument& e) + { + // Note: ns is not guaranteed to be valid. + // + diag_record dr (fail); + dr << "invalid json object"; + + if (var != nullptr) + dr << " in variable " << var->name; + + dr << ": " << e; + } + } + + static void + json_object_prepend (value& v, names&& ns, const variable* var) + { + using val_traits = value_traits<json_value>; + using obj_traits = value_traits<json_object>; + + try + { + obj_traits::prepend (v, val_traits::convert (move (ns))); + } + catch (const invalid_argument& e) + { + // Note: ns is not guaranteed to be valid. + // + diag_record dr (fail); + dr << "invalid json object"; + + if (var != nullptr) + dr << " in variable " << var->name; + + dr << ": " << e; + } + } + + const json_object value_traits<json_object>::empty_instance; + const char* const value_traits<json_object>::type_name = "json_object"; + + const value_type value_traits<json_object>::value_type + { + type_name, + sizeof (json_object), + &value_traits<json_value>::value_type, // Base (assuming direct cast works + // for both). + true, // Container. + &value_traits<json_value>::value_type, // Element (json_value). + &default_dtor<json_object>, + &default_copy_ctor<json_object>, + &default_copy_assign<json_object>, + &json_object_assign, + &json_object_append, + &json_object_prepend, + &json_reverse, + nullptr, // No cast (cast data_ directly). + &json_compare, + &default_empty<json_object>, + &json_subscript, + &json_iterate + }; + + // cmdline + // + cmdline value_traits<cmdline>:: + convert (names&& ns) + { + return cmdline (make_move_iterator (ns.begin ()), + make_move_iterator (ns.end ())); + } + + void value_traits<cmdline>:: + assign (value& v, cmdline&& x) + { + if (v) + v.as<cmdline> () = move (x); + else + new (&v.data_) cmdline (move (x)); + } + + void value_traits<cmdline>:: + append (value& v, cmdline&& x) + { + if (v) + { + cmdline& p (v.as<cmdline> ()); + + if (p.empty ()) + p.swap (x); + else + p.insert (p.end (), + make_move_iterator (x.begin ()), + make_move_iterator (x.end ())); + } + else + new (&v.data_) cmdline (move (x)); + } + + void value_traits<cmdline>:: + prepend (value& v, cmdline&& x) + { + if (v) + { + cmdline& p (v.as<cmdline> ()); + + if (!p.empty ()) + x.insert (x.end (), + make_move_iterator (p.begin ()), + make_move_iterator (p.end ())); + + p.swap (x); + } + else + new (&v.data_) cmdline (move (x)); + } + + static void + cmdline_assign (value& v, names&& ns, const variable*) + { + if (!v) + { + new (&v.data_) cmdline (); + v.null = false; + } + + v.as<cmdline> ().assign (make_move_iterator (ns.begin ()), + make_move_iterator (ns.end ())); + } + + static void + cmdline_append (value& v, names&& ns, const variable*) + { + if (!v) + { + new (&v.data_) cmdline (); + v.null = false; + } + + auto& x (v.as<cmdline> ()); + x.insert (x.end (), + make_move_iterator (ns.begin ()), + make_move_iterator (ns.end ())); + } + + static void + cmdline_prepend (value& v, names&& ns, const variable*) + { + if (!v) + { + new (&v.data_) cmdline (); + v.null = false; + } + + auto& x (v.as<cmdline> ()); + x.insert (x.begin (), + make_move_iterator (ns.begin ()), + make_move_iterator (ns.end ())); + } + + static names_view + cmdline_reverse (const value& v, names&, bool) + { + const auto& x (v.as<cmdline> ()); + return names_view (x.data (), x.size ()); + } + + static int + cmdline_compare (const value& l, const value& r) + { + return vector_compare<name> (l, r); + } + + const cmdline value_traits<cmdline>::empty_instance; + + const char* const value_traits<cmdline>::type_name = "cmdline"; + + const value_type value_traits<cmdline>::value_type + { + type_name, + sizeof (cmdline), + nullptr, // No base. + true, // Container. + &value_traits<string>::value_type, // Element type. + &default_dtor<cmdline>, + &default_copy_ctor<cmdline>, + &default_copy_assign<cmdline>, + &cmdline_assign, + &cmdline_append, + &cmdline_prepend, + &cmdline_reverse, + nullptr, // No cast (cast data_ directly). + &cmdline_compare, + &default_empty<cmdline>, + nullptr, // Subscript. + nullptr // Iterate. }; // variable_pool @@ -1399,6 +2612,17 @@ namespace build2 const variable_visibility* v, const bool* o) const { + assert (var.owner == this); + + if (outer_ != nullptr) + { + // Project-private variable. Assert visibility/overridability, the same + // as in insert(). + // + assert ((o == nullptr || !*o) && + (v == nullptr || *v >= variable_visibility::project)); + } + // Check overridability (all overrides, if any, should already have // been entered; see context ctor for details). // @@ -1480,7 +2704,7 @@ namespace build2 } static inline void - merge_pattern (const variable_pool::pattern& p, + merge_pattern (const variable_patterns::pattern& p, const build2::value_type*& t, const variable_visibility*& v, const bool*& o) @@ -1524,27 +2748,75 @@ namespace build2 } } - variable& variable_pool:: + pair<variable&, bool> variable_pool:: insert (string n, const build2::value_type* t, const variable_visibility* v, const bool* o, bool pat) { - assert (!global_ || global_->phase == run_phase::load); + if (outer_ != nullptr) + { + // Project-private pool. + // + if (n.find ('.') != string::npos) // Qualified. + return outer_->insert (move (n), t, v, o, pat); + + // Unqualified. + // + // The pool chaining semantics for insertion: first check the outer pool + // then, if not found, insert in own pool. + // + if (const variable* var = outer_->find (n)) + { + // Verify type/visibility/overridability. + // + // Should we assert or fail? Currently the buildfile parser goes + // through update() to set these so let's do assert for now. We also + // require equality (these are a handful of special variables). + // + assert ((t == nullptr || t == var->type) && + (v == nullptr || *v == var->visibility) && + (o == nullptr || *o || var->overrides == nullptr)); + + return pair<variable&, bool> (const_cast<variable&> (*var), false); + } + + // Project-private variable. Assert visibility/overridability and fall + // through. Again, we expect the buildfile parser to verify and diagnose + // these. + // + // Note: similar code in update(). + // + assert ((o == nullptr || !*o) && + (v == nullptr || *v >= variable_visibility::project)); + } + else if (shared_) + { + // Public pool. + // + // Make sure all the unqualified variables are pre-entered during + // initialization. + // + assert (shared_->load_generation == 0 || n.find ('.') != string::npos); + } + + assert (!shared_ || shared_->phase == run_phase::load); // Apply pattern. // + using pattern = variable_patterns::pattern; + const pattern* pa (nullptr); auto pt (t); auto pv (v); auto po (o); - if (pat) + if (pat && patterns_ != nullptr) { if (n.find ('.') != string::npos) { // Reverse means from the "largest" (most specific). // - for (const pattern& p: reverse_iterate (patterns_)) + for (const pattern& p: reverse_iterate (patterns_->patterns_)) { if (match_pattern (n, p.prefix, p.suffix, p.multi)) { @@ -1561,6 +2833,7 @@ namespace build2 variable { move (n), nullptr, + nullptr, pt, nullptr, pv != nullptr ? *pv : variable_visibility::project})); @@ -1568,7 +2841,10 @@ namespace build2 variable& var (r.first->second); if (r.second) + { + var.owner = this; var.aliases = &var; + } else // Note: overridden variable will always exist. { // This is tricky: if the pattern does not require a match, then we @@ -1590,19 +2866,27 @@ namespace build2 update (var, pt, pv, po); // Not changing the key. } - return var; + return pair<variable&, bool> (var, r.second); } const variable& variable_pool:: insert_alias (const variable& var, string n) { - assert (var.aliases != nullptr && var.overrides == nullptr); + if (outer_ != nullptr) + { + assert (n.find ('.') != string::npos); // Qualified. + return outer_->insert_alias (var, move (n)); + } + + assert (var.owner == this && + var.aliases != nullptr && + var.overrides == nullptr); variable& a (insert (move (n), var.type, &var.visibility, nullptr /* override */, - false /* pattern */)); + false /* pattern */).first); assert (a.overrides == nullptr); @@ -1617,15 +2901,15 @@ namespace build2 return a; } - void variable_pool:: - insert_pattern (const string& p, - optional<const value_type*> t, - optional<bool> o, - optional<variable_visibility> v, - bool retro, - bool match) + void variable_patterns:: + insert (const string& p, + optional<const value_type*> t, + optional<bool> o, + optional<variable_visibility> v, + bool retro, + bool match) { - assert (!global_ || global_->phase == run_phase::load); + assert (!shared_ || shared_->phase == run_phase::load); size_t pn (p.size ()); @@ -1659,9 +2943,9 @@ namespace build2 // Apply retrospectively to existing variables. // - if (retro) + if (retro && pool_ != nullptr) { - for (auto& p: map_) + for (auto& p: pool_->map_) { variable& var (p.second); @@ -1678,10 +2962,10 @@ namespace build2 } if (j == e) - update (var, - t ? *t : nullptr, - v ? &*v : nullptr, - o ? &*o : nullptr); // Not changing the key. + pool_->update (var, + t ? *t : nullptr, + v ? &*v : nullptr, + o ? &*o : nullptr); // Not changing the key. } } } @@ -1689,10 +2973,69 @@ namespace build2 // variable_map // - const variable_map empty_variable_map (nullptr /* context */); + const variable_map empty_variable_map (variable_map::owner::empty); + + // Need scope/target definition thus not inline. + // + variable_map:: + variable_map (const scope& s, bool shared) + : shared_ (shared), owner_ (owner::scope), scope_ (&s), ctx (&s.ctx) + { + } + + variable_map:: + variable_map (const target& t, bool shared) + : shared_ (shared), owner_ (owner::target), target_ (&t), ctx (&t.ctx) + { + } + + variable_map:: + variable_map (const prerequisite& p, bool shared) + : shared_ (shared), + owner_ (owner::prereq), prereq_ (&p), + ctx (&p.scope.ctx) + { + } + + variable_map:: + variable_map (variable_map&& v, const prerequisite& p, bool shared) + : shared_ (shared), + owner_ (owner::scope), prereq_ (&p), + ctx (&p.scope.ctx), + m_ (move (v.m_)) + { + } + + variable_map:: + variable_map (const variable_map& v, const prerequisite& p, bool shared) + : shared_ (shared), + owner_ (owner::scope), prereq_ (&p), + ctx (&p.scope.ctx), + m_ (v.m_) + { + } + + lookup variable_map:: + lookup (const string& name) const + { + lookup_type r; + + const scope* bs (owner_ == owner::scope ? scope_ : + owner_ == owner::target ? &target_->base_scope () : + owner_ == owner::prereq ? &prereq_->scope : + nullptr); + + if (const variable* var = bs->var_pool ().find (name)) + { + auto p (lookup (*var)); + r = lookup_type (p.first, &p.second, this); + } + + return r; + } auto variable_map:: - lookup (const variable& var, bool typed) const -> + lookup (const variable& var, bool typed, bool aliased) const -> pair<const value_data*, const variable&> { const variable* v (&var); @@ -1710,7 +3053,8 @@ namespace build2 break; } - v = v->aliases; + if (aliased) + v = v->aliases; } while (v != &var && v != nullptr); @@ -1731,24 +3075,43 @@ namespace build2 auto* r (const_cast<value_data*> (p.first)); if (r != nullptr) + { + r->extra = 0; r->version++; + } return pair<value_data*, const variable&> (r, p.second); } - pair<reference_wrapper<value>, bool> variable_map:: - insert (const variable& var, bool typed) + value& variable_map:: + assign (const string& name) { - assert (!global_ || ctx->phase == run_phase::load); + assert (owner_ != owner::context); + + const scope* bs (owner_ == owner::scope ? scope_ : + owner_ == owner::target ? &target_->base_scope () : + owner_ == owner::prereq ? &prereq_->scope : + nullptr); + + return insert (bs->var_pool ()[name]).first; + } + + pair<value&, bool> variable_map:: + insert (const variable& var, bool typed, bool reset_extra) + { + assert (!shared_ || ctx->phase == run_phase::load); auto p (m_.emplace (var, value_data (typed ? var.type : nullptr))); value_data& r (p.first->second); if (!p.second) { + if (reset_extra) + r.extra = 0; + // Check if this is the first access after being assigned a type. // - // Note: we still need atomic in case this is not a global state. + // Note: we still need atomic in case this is not a shared state. // if (typed && var.type != nullptr) typify (r, var); @@ -1756,44 +3119,153 @@ namespace build2 r.version++; - return make_pair (reference_wrapper<value> (r), p.second); + return pair<value&, bool> (r, p.second); + } + + auto variable_map:: + find (const string& name) const -> const_iterator + { + assert (owner_ != owner::context); + + const scope* bs (owner_ == owner::scope ? scope_ : + owner_ == owner::target ? &target_->base_scope () : + owner_ == owner::prereq ? &prereq_->scope : + nullptr); + + + const variable* var (bs->var_pool ().find (name)); + return var != nullptr ? find (*var) : end (); + } + + bool variable_map:: + erase (const variable& var) + { + assert (!shared_ || ctx->phase == run_phase::load); + + return m_.erase (var) != 0; + } + + variable_map::const_iterator variable_map:: + erase (const_iterator i) + { + assert (!shared_ || ctx->phase == run_phase::load); + + return const_iterator (m_.erase (i), *this); + } + + // variable_pattern_map + // + variable_map& variable_pattern_map:: + insert (pattern_type type, string&& text) + { + // Note that this variable map is special and we use context as its owner + // (see variable_map for details). + // + auto r (map_.emplace (pattern {type, false, move (text), {}}, + variable_map (ctx, shared_))); + + // Compile the regex. + // + if (r.second && type == pattern_type::regex_pattern) + { + // On exception restore the text argument (so that it's available for + // diagnostics) and remove the element from the map. + // + auto eg (make_exception_guard ( + [&text, &r, this] () + { + text = r.first->first.text; + map_.erase (r.first); + })); + + const string& t (r.first->first.text); + size_t n (t.size ()), p (t.rfind (t[0])); + + // Convert flags. + // + regex::flag_type f (regex::ECMAScript); + for (size_t i (p + 1); i != n; ++i) + { + switch (t[i]) + { + case 'i': f |= regex::icase; break; + case 'e': r.first->first.match_ext = true; break; + } + } + + // Skip leading delimiter as well as trailing delimiter and flags. + // + r.first->first.regex = regex (t.c_str () + 1, p - 1, f); + } + + return r.first->second; } // variable_type_map // lookup variable_type_map:: - find (const target_type& type, - const string& name, - const variable& var) const + find (const target_key& tk, + const variable& var, + optional<string>& oname) const { + // Compute and cache "effective" name that we will be matching. + // + // See also the additional match_ext logic below. + // + auto name = [&tk, &oname] () -> const string& + { + if (!oname) + { + oname = string (); + tk.effective_name (*oname); + } + + return oname->empty () ? *tk.name : *oname; + }; + // Search across target type hierarchy. // - for (auto tt (&type); tt != nullptr; tt = tt->base) + for (auto tt (tk.type); tt != nullptr; tt = tt->base) { auto i (map_.find (*tt)); if (i == end ()) continue; - // Try to match the pattern, starting from the longest values - // so that the more "specific" patterns (i.e., those that cover - // fewer characters with the wildcard) take precedence. See - // tests/variable/type-pattern. + // Try to match the pattern, starting from the longest values. // const variable_pattern_map& m (i->second); - for (auto j (m.rbegin ()); j != m.rend (); ++j) { - const string& pat (j->first); + using pattern = variable_pattern_map::pattern; + using pattern_type = variable_pattern_map::pattern_type; - //@@ TODO: should we detect ambiguity? 'foo-*' '*-foo' and 'foo-foo'? - // Right now the last defined will be used. - // - if (pat != "*") + const pattern& pat (j->first); + + bool r, e (false); + if (pat.type == pattern_type::path) + { + r = pat.text == "*" || butl::path_match (name (), pat.text); + } + else { - if (name.size () < pat.size () - 1 || // One for '*' or '?'. - !butl::path_match (name, pat)) - continue; + const string& n (name ()); + + // Deal with match_ext: first see if the extension would be added by + // default. If not, then temporarily add it in oname and then clean + // it up if there is no match (to prevent another pattern from using + // it). While we may keep adding it if there are multiple patterns + // with such a flag, we will at least reuse the buffer in oname. + // + e = pat.match_ext && tk.ext && !tk.ext->empty () && oname->empty (); + if (e) + { + *oname = *tk.name; + *oname += '.'; + *oname += *tk.ext; + } + + r = regex_match (e ? *oname : n, *pat.regex); } // Ok, this pattern matches. But is there a variable? @@ -1802,8 +3274,9 @@ namespace build2 // to automatically type it. And if it is assignment, then typify it // ourselves. // - const variable_map& vm (j->second); + if (r) { + const variable_map& vm (j->second); auto p (vm.lookup (var, false)); if (const variable_map::value_data* v = p.first) { @@ -1812,9 +3285,18 @@ namespace build2 if (v->extra == 0 && var.type != nullptr) vm.typify (*v, var); + // Make sure the effective name is computed if this is + // append/prepend (it is used as a cache key). + // + if (v->extra != 0 && !oname) + name (); + return lookup (*v, p.second, vm); } } + + if (e) + oname->clear (); } } @@ -1832,8 +3314,32 @@ namespace build2 value_traits<vector<pair<string, string>>>; template struct LIBBUILD2_DEFEXPORT - value_traits<std::map<string, string>>; + value_traits<vector<pair<string, optional<string>>>>; + + template struct LIBBUILD2_DEFEXPORT + value_traits<vector<pair<optional<string>, string>>>; + + template struct LIBBUILD2_DEFEXPORT + value_traits<vector<pair<string, optional<bool>>>>; + + template struct LIBBUILD2_DEFEXPORT value_traits<set<string>>; + template struct LIBBUILD2_DEFEXPORT value_traits<set<json_value>>; + + template struct LIBBUILD2_DEFEXPORT + value_traits<map<string, string>>; + + template struct LIBBUILD2_DEFEXPORT + value_traits<map<json_value, json_value>>; + + template struct LIBBUILD2_DEFEXPORT + value_traits<map<string, optional<string>>>; + + template struct LIBBUILD2_DEFEXPORT + value_traits<map<optional<string>, string>>; + + template struct LIBBUILD2_DEFEXPORT + value_traits<map<string, optional<bool>>>; template struct LIBBUILD2_DEFEXPORT - value_traits<std::map<project_name, dir_path>>; + value_traits<map<project_name, dir_path>>; } |