aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/variable.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/variable.cxx')
-rw-r--r--libbuild2/variable.cxx293
1 files changed, 292 insertions, 1 deletions
diff --git a/libbuild2/variable.cxx b/libbuild2/variable.cxx
index 392c9bb..62da025 100644
--- a/libbuild2/variable.cxx
+++ b/libbuild2/variable.cxx
@@ -7,6 +7,11 @@
#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>
@@ -460,7 +465,7 @@ namespace build2
m += "name '" + to_string (n) + '\'';
}
- throw invalid_argument (m);
+ throw invalid_argument (move (m));
}
// names
@@ -1475,6 +1480,292 @@ namespace build2
&default_empty<project_name>
};
+ // json
+ //
+ json_value value_traits<json_value>::
+ convert (names&& ns)
+ {
+ auto to_string_value = [] (name& n, const char* what) -> string
+ {
+ 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;
+ };
+
+ auto to_json_value = [] (name& n, const char* what) -> json_value
+ {
+ 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 == "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));
+ }
+ else
+ {
+ // If this is not already a JSON string, array, or object, treat it as
+ // a string.
+ //
+ if (s[0] != '"')
+ {
+ // While the quote character must be first, `{` and `[` could be
+ // preceded with whitespaces.
+ //
+ size_t p (s.find_first_not_of (" \t\n\r"));
+
+ if (p == string::npos || (s[p] != '{' && s[p] != '['))
+ {
+ return json_value (move (s));
+ }
+ }
+
+ // Parse as valid JSON input.
+ //
+#ifndef BUILD2_BOOTSTRAP
+ using namespace butl::json;
+
+ try
+ {
+ parser p (s, empty_string /* name */);
+ return json_value (p);
+ }
+ catch (const invalid_json_input& e)
+ {
+ // @@ How can we communicate value, line/column, position?
+
+ string m ("invalid json value: ");
+ m += e.what ();
+
+ throw invalid_argument (move (m));
+ }
+#else
+ throw invalid_argument ("json parsing requested during bootstrap");
+#endif
+ }
+ };
+
+ size_t n (ns.size ());
+
+ if (n == 0)
+ {
+ 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.container.reserve (n / 2);
+
+ for (auto i (ns.begin ()); i != ns.end (); ++i)
+ {
+ if (!i->pair)
+ throw invalid_argument (
+ "pair expected in json member value '" + to_string (*i) + '\'');
+
+ // @@ Should we handle quoted member names?
+ //
+ string m (to_string_value (*i, "json member name"));
+ json_value v (to_json_value (*++i, "json member"));
+ v.name = move (m);
+
+ // @@ Override duplicates or fail?
+ //
+ r.container.push_back (move (v));
+ }
+
+ return r;
+ }
+ else // array
+ {
+ json_value r (json_type::array);
+ r.container.reserve (n);
+
+ for (name& n: ns)
+ r.container.push_back (to_json_value (n, "json array element"));
+
+ return r;
+ }
+ }
+ }
+
+ void value_traits<json_value>::
+ assign (value& v, json_value&& x)
+ {
+ if (v)
+ v.as<json_value> () = move (x);
+ else
+ new (&v.data_) json_value (move (x));
+ }
+
+ void value_traits<json_value>::
+ append (value& v, json_value&& x)
+ {
+ if (v)
+ {
+ json_value& p (v.as<json_value> ());
+
+ 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_) json_value (move (x));
+ }
+
+ void value_traits<json_value>::
+ prepend (value& v, json_value&& x)
+ {
+ if (v)
+ {
+ json_value& p (v.as<json_value> ());
+
+ if (!p.empty ())
+ x.insert (x.end (),
+ make_move_iterator (p.begin ()),
+ make_move_iterator (p.end ()));
+
+ p.swap (x);
+ }
+ else
+ new (&v.data_) json_value (move (x));
+ }
+
+ void
+ json_value_assign (value& v, names&& ns, const variable*)
+ {
+ if (!v)
+ {
+ new (&v.data_) json_value ();
+ v.null = false;
+ }
+
+ v.as<json_value> ().assign (make_move_iterator (ns.begin ()),
+ make_move_iterator (ns.end ()));
+ }
+
+ void
+ json_value_append (value& v, names&& ns, const variable*)
+ {
+ if (!v)
+ {
+ new (&v.data_) json_value ();
+ v.null = false;
+ }
+
+ auto& x (v.as<json_value> ());
+ x.insert (x.end (),
+ make_move_iterator (ns.begin ()),
+ make_move_iterator (ns.end ()));
+ }
+
+ void
+ json_value_prepend (value& v, names&& ns, const variable*)
+ {
+ if (!v)
+ {
+ new (&v.data_) json_value ();
+ v.null = false;
+ }
+
+ auto& x (v.as<json_value> ());
+ x.insert (x.begin (),
+ make_move_iterator (ns.begin ()),
+ make_move_iterator (ns.end ()));
+ }
+
+ static names_view
+ json_value_reverse (const value& v, names&, bool)
+ {
+ const auto& x (v.as<json_value> ());
+ return names_view (x.data (), x.size ());
+ }
+
+ static int
+ json_value_compare (const value& l, const value& r)
+ {
+ return vector_compare<name> (l, r);
+ }
+
+ 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>,
+ &cmdline_assign,
+ &cmdline_append,
+ &cmdline_prepend,
+ &cmdline_reverse,
+ nullptr, // No cast (cast data_ directly).
+ &cmdline_compare,
+ &default_empty<cmdline>
+ };
+
// cmdline
//
cmdline value_traits<cmdline>::