aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/variable.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/variable.cxx')
-rw-r--r--libbuild2/variable.cxx1810
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>>;
}