From 3ce44330cca9dbc4314feebb27403ebc3175b6c2 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 28 Mar 2016 09:14:31 +0200 Subject: New variable architecture --- build2/b.cxx | 91 +++--- build2/bin/module.cxx | 6 +- build2/bin/rule.cxx | 4 +- build2/cli/module.cxx | 10 +- build2/cli/rule.cxx | 2 +- build2/config/operation.cxx | 32 +- build2/config/utility.cxx | 2 +- build2/cxx/compile.cxx | 16 +- build2/cxx/link.cxx | 30 +- build2/cxx/module.cxx | 12 +- build2/cxx/utility.txx | 2 +- build2/dist/operation.cxx | 14 +- build2/dump.cxx | 5 +- build2/file | 10 + build2/file.cxx | 212 ++++++++----- build2/install/module.cxx | 2 +- build2/install/rule.cxx | 29 +- build2/module.cxx | 10 +- build2/name | 83 ++--- build2/name.cxx | 6 +- build2/name.ixx | 42 +++ build2/parser.cxx | 36 ++- build2/scope | 5 +- build2/target | 5 +- build2/target.txx | 2 +- build2/test/rule.cxx | 10 +- build2/types | 11 +- build2/utility | 6 +- build2/utility.cxx | 10 +- build2/utility.ixx | 1 + build2/variable | 752 ++++++++++++++++---------------------------- build2/variable.cxx | 604 +++++++++++++++++++++-------------- build2/variable.ixx | 525 ++++++++++++++++++------------- build2/variable.txx | 401 +++++++++++++++++------ 34 files changed, 1649 insertions(+), 1339 deletions(-) create mode 100644 build2/name.ixx diff --git a/build2/b.cxx b/build2/b.cxx index 2f94ad3..f2cb9eb 100644 --- a/build2/b.cxx +++ b/build2/b.cxx @@ -482,51 +482,61 @@ main (int argc, char* argv[]) // scope& rs (create_root (out_root, src_root)); - bootstrap_out (rs); + bool bootstrapped (build2::bootstrapped (rs)); - // See if the bootstrap process set/changed src_root. - // - value& v (rs.assign ("src_root")); - - if (v) + if (!bootstrapped) { - // If we also have src_root specified by the user, make - // sure they match. - // - const dir_path& p (as (v)); + bootstrap_out (rs); - if (src_root.empty ()) - src_root = p; - else if (src_root != p) - fail << "bootstrapped src_root " << p << " does not match " - << "specified " << src_root; - } - else - { - // Neither bootstrap nor the user produced src_root. + // See if the bootstrap process set/changed src_root. // - if (src_root.empty ()) + value& v (rs.assign ("src_root")); + + if (v) { - // If it also wasn't explicitly specified, see if it is - // the same as out_root. + // If we also have src_root specified by the user, make + // sure they match. // - if (is_src_root (out_root)) - src_root = out_root; - else + const dir_path& p (cast (v)); + + if (src_root.empty ()) + src_root = p; + else if (src_root != p) + fail << "bootstrapped src_root " << p << " does not match " + << "specified " << src_root; + } + else + { + // Neither bootstrap nor the user produced src_root. + // + if (src_root.empty ()) { - // If not, then assume we are running from src_base - // and calculate src_root based on out_root/out_base. + // If it also wasn't explicitly specified, see if it is + // the same as out_root. // - src_base = work; - src_root = src_base.directory (out_base.leaf (out_root)); - guessing = true; + if (is_src_root (out_root)) + src_root = out_root; + else + { + // If not, then assume we are running from src_base + // and calculate src_root based on out_root/out_base. + // + src_base = work; + src_root = src_base.directory (out_base.leaf (out_root)); + guessing = true; + } } + + v = src_root; } - v = src_root; - } + setup_root (rs); - setup_root (rs); + // Now that we have src_root, load the src_root bootstrap file, + // if there is one. + // + bootstrapped = bootstrap_src (rs); + } // At this stage we should have both roots and out_base figured // out. If src_base is still undetermined, calculate it. @@ -534,25 +544,20 @@ main (int argc, char* argv[]) if (src_base.empty ()) src_base = src_root / out_base.leaf (out_root); - // Now that we have src_root, load the src_root bootstrap file, - // if there is one. - // - bool bootstrapped (bootstrap_src (rs)); - // Check that out_root that we have found is the innermost root // for this project. If it is not, then it means we are trying // to load a disfigured sub-project and that we do not support. // Why don't we support it? Because things are already complex // enough here. // + // Note that the subprojects variable has already been processed + // and converted to a map by the bootstrap_src() call above. + // if (auto l = rs.vars["subprojects"]) { - for (const name& n: *l) + for (const auto& p: cast (*l)) { - if (n.pair) - continue; // Skip project names. - - if (out_base.sub (out_root / n.dir)) + if (out_base.sub (out_root / p.second)) fail << tn << " is in a subproject of " << out_root << info << "explicitly specify src_base for this target"; } diff --git a/build2/bin/module.cxx b/build2/bin/module.cxx index 702e762..496b1da 100644 --- a/build2/bin/module.cxx +++ b/build2/bin/module.cxx @@ -156,7 +156,7 @@ namespace build2 // See the cxx module for details on merging. // if (const value& v = config::optional (r, "config.bin.rpath")) - b.assign ("bin.rpath") += as (v); + b.assign ("bin.rpath") += cast (v); // config.bin.ar // config.bin.ranlib @@ -170,8 +170,8 @@ namespace build2 auto p (config::required (r, "config.bin.ar", "ar")); auto& v (config::optional (r, "config.bin.ranlib")); - const path& ar (path (as (p.first))); // @@ VAR - const path& ranlib (v ? path (as (v)) : path ()); // @@ VAR + const path& ar (path (cast (p.first))); // @@ VAR + const path& ranlib (v ? path (cast (v)) : path ()); // @@ VAR bin_info bi (guess (ar, ranlib)); diff --git a/build2/bin/rule.cxx b/build2/bin/rule.cxx index 4b1da8f..d74dd65 100644 --- a/build2/bin/rule.cxx +++ b/build2/bin/rule.cxx @@ -46,7 +46,7 @@ namespace build2 // Get the library type to build. If not set for a target, this // should be configured at the project scope by init_lib(). // - const string& type (as (*t["bin.lib"])); + const string& type (cast (*t["bin.lib"])); bool ar (type == "static" || type == "both"); bool so (type == "shared" || type == "both"); @@ -121,7 +121,7 @@ namespace build2 // prerequisite vs prerequisite_target. // // - const string& type (as (*t["bin.lib"])); + const string& type (cast (*t["bin.lib"])); bool ar (type == "static" || type == "both"); bool so (type == "shared" || type == "both"); diff --git a/build2/cli/module.cxx b/build2/cli/module.cxx index 6497766..907bd0b 100644 --- a/build2/cli/module.cxx +++ b/build2/cli/module.cxx @@ -45,7 +45,7 @@ namespace build2 { auto l (base["cxx.loaded"]); - if (!l || !as (*l)) + if (!l || !cast (*l)) fail (loc) << "cxx module must be loaded before cli"; } @@ -90,7 +90,7 @@ namespace build2 { auto l (root["config.cli.configured"]); - if (l && !as (*l)) + if (l && !cast (*l)) return false; } @@ -175,7 +175,7 @@ namespace build2 else { auto p (config::required (root, "config.cli", cli)); - assert (p.second && as (p.first) == cli); + assert (p.second && cast (p.first) == cli); } } else @@ -186,7 +186,7 @@ namespace build2 // if (p.second) { - cli = as (p.first).c_str (); + cli = cast (p.first).c_str (); ver = test (cli); if (ver.empty ()) @@ -209,7 +209,7 @@ namespace build2 // this merging semantics and some of its tricky aspects. // if (const value& v = config::optional (root, "config.cli.options")) - base.assign ("cli.options") += as (v); + base.assign ("cli.options") += cast (v); // Register our rules. // diff --git a/build2/cli/rule.cxx b/build2/cli/rule.cxx index bf52993..243c6aa 100644 --- a/build2/cli/rule.cxx +++ b/build2/cli/rule.cxx @@ -244,7 +244,7 @@ namespace build2 path rels (relative (s->path ())); scope& rs (t.root_scope ()); - const string& cli (as (*rs["config.cli"])); + const string& cli (cast (*rs["config.cli"])); cstrings args {cli.c_str ()}; diff --git a/build2/config/operation.cxx b/build2/config/operation.cxx index 6199f8b..8047e61 100644 --- a/build2/config/operation.cxx +++ b/build2/config/operation.cxx @@ -86,7 +86,7 @@ namespace build2 if (auto l = root.vars["amalgamation"]) { - const dir_path& d (as (*l)); + const dir_path& d (cast (*l)); ofs << "# Base configuration inherited from " << d << endl << "#" << endl; @@ -95,6 +95,8 @@ namespace build2 // Save all the variables in the config namespace that are set // on the project's root scope. // + names storage; + for (auto p (root.vars.find_namespace ("config")); p.first != p.second; ++p.first) @@ -115,7 +117,7 @@ namespace build2 if (n.size () > 11 && n.compare (n.size () - 11, 11, ".configured") == 0) { - if (val == nullptr || as (val)) + if (val == nullptr || cast (val)) continue; } @@ -132,14 +134,11 @@ namespace build2 if (val) { - ofs << var.name << " = " << val.data_ << endl; - //text << var.name << " = " << val.data_; + storage.clear (); + ofs << var.name << " = " << reverse (val, storage) << endl; } else - { ofs << var.name << " = #[null]" << endl; // @@ TODO: [null] - //text << var.name << " = [null]"; - } } } catch (const ofstream::failure&) @@ -189,7 +188,7 @@ namespace build2 // if (auto l = root.vars["subprojects"]) { - for (auto p: as (*l)) + for (auto p: cast (*l)) { const dir_path& pd (p.second); dir_path out_nroot (out_root / pd); @@ -314,7 +313,7 @@ namespace build2 // if (auto l = root.vars["subprojects"]) { - for (auto p: as (*l)) + for (auto p: cast (*l)) { const dir_path& pd (p.second); @@ -325,16 +324,19 @@ namespace build2 // The same logic for src_root as in create_bootstrap_inner(). // scope& nroot (create_root (out_nroot, dir_path ())); - bootstrap_out (nroot); - value& val (nroot.assign ("src_root")); + if (!bootstrapped (nroot)) + { + bootstrap_out (nroot); - if (!val) - val = is_src_root (out_nroot) ? out_nroot : (src_root / pd); + value& val (nroot.assign ("src_root")); - setup_root (nroot); + if (!val) + val = is_src_root (out_nroot) ? out_nroot : (src_root / pd); - bootstrap_src (nroot); + setup_root (nroot); + bootstrap_src (nroot); + } m = disfigure_project (a, nroot) || m; diff --git a/build2/config/utility.cxx b/build2/config/utility.cxx index d27e1ff..e96a896 100644 --- a/build2/config/utility.cxx +++ b/build2/config/utility.cxx @@ -43,7 +43,7 @@ namespace build2 if (v && !v.empty ()) { - dir_path& d (as (v)); + dir_path& d (cast (v)); if (d.relative ()) { diff --git a/build2/cxx/compile.cxx b/build2/cxx/compile.cxx index df2b798..a2b00de 100644 --- a/build2/cxx/compile.cxx +++ b/build2/cxx/compile.cxx @@ -130,7 +130,7 @@ namespace build2 // if (a == perform_update_id) { - const string& sys (as (*rs["cxx.target.system"])); + const string& sys (cast (*rs["cxx.target.system"])); // The cached prerequisite target should be the same as what is in // t.prerequisite_targets since we used standard search() and match() @@ -165,7 +165,7 @@ namespace build2 // Then the compiler checksum. // - if (dd.expect (as (*rs["cxx.checksum"])) != nullptr) + if (dd.expect (cast (*rs["cxx.checksum"])) != nullptr) l4 ([&]{trace << "compiler mismatch forcing update of " << t;}); // Then the options checksum. @@ -237,7 +237,7 @@ namespace build2 -> const target_type* { if (auto l = s.lookup (tt, n, var)) - if (as (*l) == e) + if (cast (*l) == e) return &tt; return nullptr; @@ -283,7 +283,7 @@ namespace build2 if (auto l = t[var]) { - const auto& v (as (*l)); + const auto& v (cast (*l)); for (auto i (v.begin ()), e (v.end ()); i != e; ++i) { @@ -461,8 +461,8 @@ namespace build2 auto init_args = [&t, &s, &rs, &args, &cxx_std] () { - const string& cxx (as (*rs["config.cxx"])); - const string& sys (as (*rs["cxx.target.system"])); + const string& cxx (cast (*rs["config.cxx"])); + const string& sys (cast (*rs["cxx.target.system"])); args.push_back (cxx.c_str ()); @@ -923,8 +923,8 @@ namespace build2 path rels (relative (s->path ())); scope& rs (t.root_scope ()); - const string& cxx (as (*rs["config.cxx"])); - const string& sys (as (*rs["cxx.target.system"])); + const string& cxx (cast (*rs["config.cxx"])); + const string& sys (cast (*rs["cxx.target.system"])); cstrings args {cxx.c_str ()}; diff --git a/build2/cxx/link.cxx b/build2/cxx/link.cxx index 5ed36b7..ea20a47 100644 --- a/build2/cxx/link.cxx +++ b/build2/cxx/link.cxx @@ -42,7 +42,7 @@ namespace build2 case type::so: var = "bin.libso.lib"; break; } - const auto& v (as (*t[var])); + const auto& v (cast (*t[var])); return v[0] == "shared" ? v.size () > 1 && v[1] == "static" ? order::so_a : order::so : v.size () > 1 && v[1] == "shared" ? order::a_so : order::a; @@ -52,7 +52,7 @@ namespace build2 link_member (bin::lib& l, order lo) { bool lso (true); - const string& at (as (*l["bin.lib"])); // Available types. + const string& at (cast (*l["bin.lib"])); // Available types. switch (lo) { @@ -92,7 +92,7 @@ namespace build2 // if (auto l = bs["cxx.loptions"]) { - const auto& v (as (*l)); + const auto& v (cast (*l)); for (auto i (v.begin ()), e (v.end ()); i != e; ++i) { @@ -123,7 +123,7 @@ namespace build2 cstrings args; string std_storage; - args.push_back (as (*rs["config.cxx"]).c_str ()); + args.push_back (cast (*rs["config.cxx"]).c_str ()); append_options (args, bs, "cxx.coptions"); append_std (args, bs, std_storage); append_options (args, bs, "cxx.loptions"); @@ -218,7 +218,7 @@ namespace build2 return p.target; scope& rs (*p.scope.root_scope ()); - const string& sys (as (*rs["cxx.target.system"])); + const string& sys (cast (*rs["cxx.target.system"])); bool l (p.is_a ()); const string* ext (l ? nullptr : p.ext); // Only for liba/libso. @@ -483,7 +483,7 @@ namespace build2 path_target& t (static_cast (xt)); scope& rs (t.root_scope ()); - const string& sys (as (*rs["cxx.target.system"])); + const string& sys (cast (*rs["cxx.target.system"])); type lt (link_type (t)); bool so (lt == type::so); @@ -510,7 +510,7 @@ namespace build2 case type::so: { auto l (t["bin.libprefix"]); - const char* p (l ? as (*l).c_str () : "lib"); + const char* p (l ? cast (*l).c_str () : "lib"); const char* e; if (lt == type::a) @@ -769,7 +769,7 @@ namespace build2 bool up (execute_prerequisites (a, t, t.mtime ())); scope& rs (t.root_scope ()); - const string& sys (as (*rs["cxx.target.system"])); + const string& sys (cast (*rs["cxx.target.system"])); // Check/update the dependency database. // @@ -793,10 +793,10 @@ namespace build2 const char* rl ( ranlib - ? as (*rs["bin.ranlib.checksum"]).c_str () + ? cast (*rs["bin.ranlib.checksum"]).c_str () : "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); - if (dd.expect (as (*rs["bin.ar.checksum"])) != nullptr) + if (dd.expect (cast (*rs["bin.ar.checksum"])) != nullptr) l4 ([&]{trace << "ar mismatch forcing update of " << t;}); if (dd.expect (rl) != nullptr) @@ -804,7 +804,7 @@ namespace build2 } else { - if (dd.expect (as (*rs["cxx.checksum"])) != nullptr) + if (dd.expect (cast (*rs["cxx.checksum"])) != nullptr) l4 ([&]{trace << "compiler mismatch forcing update of " << t;}); } @@ -877,7 +877,7 @@ namespace build2 // precedence. // if (auto l = t["bin.rpath"]) - for (const string& p: as (*l)) + for (const string& p: cast (*l)) sargs.push_back ("-Wl,-rpath," + p); // Then the paths of the shared libraries we are linking to. Unless @@ -978,12 +978,12 @@ namespace build2 if (lt == type::a) { - args[0] = as (*rs["config.bin.ar"]).c_str (); + args[0] = cast (*rs["config.bin.ar"]).c_str (); args.push_back (relt.string ().c_str ()); } else { - args[0] = as (*rs["config.cxx"]).c_str (); + args[0] = cast (*rs["config.cxx"]).c_str (); args.push_back ("-o"); args.push_back (relt.string ().c_str ()); } @@ -1048,7 +1048,7 @@ namespace build2 if (ranlib) { const char* args[] = { - as (*ranlib).c_str (), relt.string ().c_str (), nullptr}; + cast (*ranlib).c_str (), relt.string ().c_str (), nullptr}; if (verb >= 2) print_process (args); diff --git a/build2/cxx/module.cxx b/build2/cxx/module.cxx index 7c6a4dd..d491079 100644 --- a/build2/cxx/module.cxx +++ b/build2/cxx/module.cxx @@ -46,7 +46,7 @@ namespace build2 { auto l (b["bin.loaded"]); - if (!l || !as (*l)) + if (!l || !cast (*l)) load_module (false, "bin", r, b, loc); } @@ -157,16 +157,16 @@ namespace build2 // cxx.coptions += # Note: '+='. // if (const value& v = config::optional (r, "config.cxx.poptions")) - b.assign ("cxx.poptions") += as (v); + b.assign ("cxx.poptions") += cast (v); if (const value& v = config::optional (r, "config.cxx.coptions")) - b.assign ("cxx.coptions") += as (v); + b.assign ("cxx.coptions") += cast (v); if (const value& v = config::optional (r, "config.cxx.loptions")) - b.assign ("cxx.loptions") += as (v); + b.assign ("cxx.loptions") += cast (v); if (const value& v = config::optional (r, "config.cxx.libs")) - b.assign ("cxx.libs") += as (v); + b.assign ("cxx.libs") += cast (v); // config.cxx // @@ -176,7 +176,7 @@ namespace build2 // Figure out which compiler we are dealing with, its target, etc. // - const path& cxx (path (as (p.first))); // @@ VAR + const path& cxx (path (cast (p.first))); // @@ VAR compiler_info ci (guess (cxx, r["cxx.coptions"])); // If this is a new value (e.g., we are configuring), then print the diff --git a/build2/cxx/utility.txx b/build2/cxx/utility.txx index ed35fdb..c20e437 100644 --- a/build2/cxx/utility.txx +++ b/build2/cxx/utility.txx @@ -14,7 +14,7 @@ namespace build2 { if (auto l = t["cxx.std"]) { - const string& v (as (*l)); + const string& v (cast (*l)); // Translate 11 to 0x and 14 to 1y for compatibility with older // versions of the compiler. diff --git a/build2/dist/operation.cxx b/build2/dist/operation.cxx index d1c2266..4489485 100644 --- a/build2/dist/operation.cxx +++ b/build2/dist/operation.cxx @@ -90,7 +90,7 @@ namespace build2 fail << "unknown root distribution directory" << info << "did you forget to specify config.dist.root?"; - const dir_path& dist_root (as (*l)); + const dir_path& dist_root (cast (*l)); if (!dir_exists (dist_root)) fail << "root distribution directory " << dist_root @@ -102,8 +102,8 @@ namespace build2 fail << "unknown distribution package name" << info << "did you forget to set dist.package?"; - const string& dist_package (as (*l)); - const string& dist_cmd (as (*rs->vars["dist.cmd"])); + const string& dist_package (cast (*l)); + const string& dist_cmd (cast (*rs->vars["dist.cmd"])); // Get the list of operations supported by this project. Skip // default_id. @@ -171,7 +171,7 @@ namespace build2 // if (auto l = rs->vars["subprojects"]) { - for (auto p: as (*l)) + for (auto p: cast (*l)) { const dir_path& pd (p.second); dir_path out_nroot (out_root / pd); @@ -209,7 +209,7 @@ namespace build2 // auto l ((*ft)[dist_var]); - if (l && !as (*l)) + if (l && !cast (*l)) l5 ([&]{trace << "excluding " << *ft;}); else files.push_back (ft); @@ -223,7 +223,7 @@ namespace build2 // auto l ((*ft)[dist_var]); - if (l && as (*l)) + if (l && cast (*l)) { l5 ([&]{trace << "including " << *ft;}); files.push_back (ft); @@ -295,7 +295,7 @@ namespace build2 // if (auto l = rs->vars["dist.archives"]) { - for (const string& e: as (*l)) + for (const string& e: cast (*l)) archive (dist_root, dist_package, e); } } diff --git a/build2/dump.cxx b/build2/dump.cxx index 13accf7..7e55b1e 100644 --- a/build2/dump.cxx +++ b/build2/dump.cxx @@ -22,7 +22,10 @@ namespace build2 if (val.null ()) os << "[null]"; else - os << val.data_; + { + names storage; + os << reverse (val, storage); + } } static void diff --git a/build2/file b/build2/file index 4f8c8db..b198557 100644 --- a/build2/file +++ b/build2/file @@ -93,6 +93,16 @@ namespace build2 bool bootstrap_src (scope& root); + // Return true if this scope has already been bootstrapped, that is, the + // following calls have already been made: + // + // bootstrap_out() + // setup_root() + // bootstrap_src() + // + bool + bootstrapped (scope& root); + // Create and bootstrap outer root scopes, if any. Loading is // done by load_root_pre() below. // diff --git a/build2/file.cxx b/build2/file.cxx index 09332f1..30ebed4 100644 --- a/build2/file.cxx +++ b/build2/file.cxx @@ -154,7 +154,7 @@ namespace build2 v = out_root; else { - const dir_path& p (as (v)); + const dir_path& p (cast (v)); if (p != out_root) fail << "new out_root " << out_root << " does not match " @@ -170,7 +170,7 @@ namespace build2 v = src_root; else { - const dir_path& p (as (v)); + const dir_path& p (cast (v)); if (p != src_root) fail << "new src_root " << src_root << " does not match " @@ -190,7 +190,8 @@ namespace build2 // Register and set src_path. // if (s.src_path_ == nullptr) - s.src_path_ = &scopes.insert (as (v), &s, false, true)->first; + s.src_path_ = &scopes.insert ( + cast (v), &s, false, true)->first; } scope& @@ -227,7 +228,7 @@ namespace build2 if (!v) v = out_base; else - assert (as (v) == out_base); + assert (cast (v) == out_base); } { @@ -236,7 +237,7 @@ namespace build2 if (!v) v = src_base; else - assert (as (v) == src_base); + assert (cast (v) == src_base); } return s; @@ -330,7 +331,7 @@ namespace build2 else { src_root_v = extract_variable (f, "src_root"); - src_root = &as (src_root_v); + src_root = &cast (src_root_v); l5 ([&]{trace << "extracted src_root " << *src_root << " for " << out_root;}); } @@ -339,7 +340,7 @@ namespace build2 string name; { value v (extract_variable (*src_root / bootstrap_file, "project")); - name = move (as (v)); + name = cast (move (v)); } l5 ([&]{trace << "extracted project name '" << name << "' for " @@ -486,7 +487,7 @@ namespace build2 } else { - const dir_path& vd (as (v)); + const dir_path& vd (cast (v)); if (vd != rd) { @@ -523,16 +524,15 @@ namespace build2 } // See if we have any subprojects. In a sense, this is the other - // side/direction of the amalgamation logic above. Here, the - // subprojects variable may or may not be set by the user (in - // bootstrap.build) or by an earlier call to this function for - // the same scope. When set by the user, the empty special value - // means that there are no subproject and none should be searched - // for (and which we convert to NULL below). Otherwise, it is a - // list of directory[=project] pairs. The directory must be - // relative to our out_root. If the project name is not specified, - // then we have to figure it out. When subprojects are calculated, - // the NULL value indicates that we found no subprojects. + // side/direction of the amalgamation logic above. Here, the subprojects + // variable may or may not be set by the user (in bootstrap.build) or by + // an earlier call to this function for the same scope. When set by the + // user, the empty special value means that there are no subproject and + // none should be searched for (and which we convert to NULL below). + // Otherwise, it is a list of [project@]directory pairs. The directory + // must be relative to our out_root. If the project name is not specified, + // then we have to figure it out. When subprojects are calculated, the + // NULL value indicates that we found no subprojects. // { const variable& var (var_pool.find ("subprojects")); @@ -567,53 +567,73 @@ namespace build2 v = nullptr; else { - // Pre-scan the value and convert it to the "canonical" form, + // Scan the (untyped) value and convert it to the "canonical" form, // that is, a list of name@dir pairs. // - for (auto i (v.data_.begin ()); i != v.data_.end (); ++i) + subprojects sps; + names& ns (cast (v)); + + for (auto i (ns.begin ()); i != ns.end (); ++i) { + // Project name. + // + string n; if (i->pair) { - // Project name. - // - if (!assign (*i) || as (*i).empty ()) + try + { + n = convert (move (*i)); + + if (n.empty ()) + fail << "empty project name in variable subprojects"; + } + catch (const invalid_argument&) + { fail << "expected project name instead of '" << *i << "' in " - << "the subprojects variable"; + << "variable subprojects"; + } ++i; // Got to have the second half of the pair. } - if (!assign (*i)) - fail << "expected directory instead of '" << *i << "' in the " - << "subprojects variable"; + // Directory. + // + dir_path d; + try + { + d = convert (move (*i)); - auto& d (as (*i)); + if (d.empty ()) + fail << "empty directory in variable subprojects"; + } + catch (const invalid_argument&) + { + fail << "expected directory instead of '" << *i << "' in " + << "variable subprojects"; + } // Figure out the project name if the user didn't specify one. // - if (!i->pair) + if (n.empty ()) { // Pass fallback src_root since this is a subproject that // was specified by the user so it is most likely in our // src. // - string n (find_project_name (out_root / d, src_root / d)); + n = find_project_name (out_root / d, src_root / d); // See find_subprojects() for details on unnamed projects. // if (n.empty ()) n = d.posix_string () + '/'; - - i = v.data_.emplace (i, move (n)); - - i->pair = true; - ++i; } + + sps.emplace (move (n), move (d)); } - // Make it of the map type. + // Change the value to the typed map. // - assign (v, var); + v = move (sps); } } } @@ -621,6 +641,17 @@ namespace build2 return r; } + bool + bootstrapped (scope& root) + { + // Use the subprojects variable set by bootstrap_src() as an indicator. + // It should either be NULL or typed (so we assume that the user will + // never set it to NULL). + // + auto l (root.vars["subprojects"]); + return l.defined () && (l->null () || l->type != nullptr); + } + void create_bootstrap_outer (scope& root) { @@ -629,7 +660,7 @@ namespace build2 if (!l) return; - const dir_path& d (as (*l)); + const dir_path& d (cast (*l)); dir_path out_root (root.out_path () / d); out_root.normalize (); @@ -645,25 +676,29 @@ namespace build2 // by #1 seems reasonable. // scope& rs (create_root (out_root, dir_path ())); - bootstrap_out (rs); // #3 happens here, if at all. - - value& v (rs.assign ("src_root")); - if (!v) + if (!bootstrapped (rs)) { - if (is_src_root (out_root)) // #2 - v = out_root; - else // #1 + bootstrap_out (rs); // #3 happens here, if at all. + + value& v (rs.assign ("src_root")); + + if (!v) { - dir_path src_root (root.src_path () / d); - src_root.normalize (); - v = move (src_root); + if (is_src_root (out_root)) // #2 + v = out_root; + else // #1 + { + dir_path src_root (root.src_path () / d); + src_root.normalize (); + v = move (src_root); + } } - } - setup_root (rs); + setup_root (rs); + bootstrap_src (rs); + } - bootstrap_src (rs); create_bootstrap_outer (rs); // Check if we are strongly amalgamated by this outer root scope. @@ -677,12 +712,9 @@ namespace build2 { if (auto l = root.vars["subprojects"]) { - for (const name& n: *l) + for (const auto& p: cast (*l)) { - if (n.pair) - continue; // Skip project names. - - dir_path out_root (root.out_path () / n.dir); + dir_path out_root (root.out_path () / p.second); if (!out_base.sub (out_root)) continue; @@ -690,18 +722,21 @@ namespace build2 // The same logic to src_root as in create_bootstrap_outer(). // scope& rs (create_root (out_root, dir_path ())); - bootstrap_out (rs); - value& v (rs.assign ("src_root")); + if (!bootstrapped (rs)) + { + bootstrap_out (rs); - if (!v) - v = is_src_root (out_root) - ? out_root - : (root.src_path () / n.dir); + value& v (rs.assign ("src_root")); - setup_root (rs); + if (!v) + v = is_src_root (out_root) + ? out_root + : (root.src_path () / p.second); - bootstrap_src (rs); + setup_root (rs); + bootstrap_src (rs); + } // Check if we strongly amalgamated this inner root scope. // @@ -784,7 +819,7 @@ namespace build2 { // First check the amalgamation itself. // - if (r != &iroot && as (*r->vars["project"]) == project) + if (r != &iroot && cast (*r->vars["project"]) == project) { out_root = r->out_path (); break; @@ -792,7 +827,7 @@ namespace build2 if (auto l = r->vars["subprojects"]) { - const auto& m (as (*l)); + const auto& m (cast (*l)); auto i (m.find (project)); if (i != m.end ()) @@ -812,11 +847,11 @@ namespace build2 if (out_root.empty ()) { const variable& var ( - var_pool.find ("config.import." + project, dir_path_type)); + var_pool.find ("config.import." + project)); if (auto l = iroot[var]) { - out_root = as (*l); + out_root = cast (*l); if (l.belongs (*global_scope)) // A value from command line. { @@ -837,7 +872,7 @@ namespace build2 // // @@ CMDVAR // - dir_path& d (as (const_cast (*l))); + dir_path& d (cast (const_cast (*l))); if (d != out_root) d = out_root; } @@ -868,34 +903,37 @@ namespace build2 src_root = is_src_root (out_root) ? out_root : dir_path (); root = &create_root (out_root, src_root); - bootstrap_out (*root); - - // Check that the bootstrap process set src_root. - // - if (auto l = root->vars["src_root"]) + if (!bootstrapped (*root)) { - const dir_path& p (as (*l)); + bootstrap_out (*root); - if (!src_root.empty () && p != src_root) - fail (loc) << "bootstrapped src_root " << p << " does not match " - << "discovered " << src_root; - } - else - fail (loc) << "unable to determine src_root for imported " - << project << - info << "consider configuring " << out_root; + // Check that the bootstrap process set src_root. + // + if (auto l = root->vars["src_root"]) + { + const dir_path& p (cast (*l)); - setup_root (*root); - bootstrap_src (*root); + if (!src_root.empty () && p != src_root) + fail (loc) << "bootstrapped src_root " << p << " does not match " + << "discovered " << src_root; + } + else + fail (loc) << "unable to determine src_root for imported " + << project << + info << "consider configuring " << out_root; + + setup_root (*root); + bootstrap_src (*root); + } // Now we know this project's name as well as all its subprojects. // - if (as (*root->vars["project"]) == project) + if (cast (*root->vars["project"]) == project) break; if (auto l = root->vars["subprojects"]) { - const auto& m (as (*l)); + const auto& m (cast (*l)); auto i (m.find (project)); if (i != m.end ()) diff --git a/build2/install/module.cxx b/build2/install/module.cxx index 579d454..8e3d6fd 100644 --- a/build2/install/module.cxx +++ b/build2/install/module.cxx @@ -154,7 +154,7 @@ namespace build2 if (first) { bool s (config::specified (r, "config.install")); - const string& n (as (*r["project"])); + const string& n (cast (*r["project"])); set_dir (s, r, "root", "", "", "755", "install"); set_dir (s, r, "data_root", "root", "644"); diff --git a/build2/install/rule.cxx b/build2/install/rule.cxx index 6933828..89463f8 100644 --- a/build2/install/rule.cxx +++ b/build2/install/rule.cxx @@ -31,7 +31,7 @@ namespace build2 if (!l) return nullptr; - const dir_path& r (as (*l)); + const dir_path& r (cast (*l)); return r.simple () && r.string () == "false" ? nullptr : &r; } @@ -64,7 +64,7 @@ namespace build2 // auto l (pt["install"]); - if (l && as (*l).string () == "false") + if (l && cast (*l).string () == "false") { l5 ([&]{trace << "ignoring " << pt;}); continue; @@ -148,7 +148,7 @@ namespace build2 // See if the user instructed us not to install it. // auto l ((*pt)["install"]); - if (l && as (*l).string () == "false") + if (l && cast (*l).string () == "false") continue; build2::match (a, *pt); @@ -216,10 +216,13 @@ namespace build2 struct install_dir { + // @@ Why do we copy these? Why not just point to the values in vars? + // + dir_path dir; string sudo; string cmd; //@@ VAR type - const_strings_value options {nullptr}; + strings options; string mode; string dir_mode; }; @@ -239,7 +242,7 @@ namespace build2 args.push_back (base.cmd.c_str ()); args.push_back ("-d"); - if (base.options.d != nullptr) //@@ VAR + if (!base.options.empty ()) append_options (args, base.options); args.push_back ("-m"); @@ -285,7 +288,7 @@ namespace build2 args.push_back (base.cmd.c_str ()); - if (base.options.d != nullptr) //@@ VAR + if (!base.options.empty ()) append_options (args, base.options); args.push_back ("-m"); @@ -355,11 +358,11 @@ namespace build2 // if (var != nullptr) { - if (auto l = s[*var + ".sudo"]) r.sudo = as (*l); - if (auto l = s[*var + ".cmd"]) r.cmd = as (*l); - if (auto l = s[*var + ".mode"]) r.mode = as (*l); - if (auto l = s[*var + ".dir_mode"]) r.dir_mode = as (*l); - if (auto l = s[*var + ".options"]) r.options = as (*l); + if (auto l = s[*var + ".sudo"]) r.sudo = cast (*l); + if (auto l = s[*var + ".cmd"]) r.cmd = cast (*l); + if (auto l = s[*var + ".mode"]) r.mode = cast (*l); + if (auto l = s[*var + ".dir_mode"]) r.dir_mode = cast (*l); + if (auto l = s[*var + ".options"]) r.options = cast (*l); } // Set defaults for unspecified components. @@ -400,12 +403,12 @@ namespace build2 // install_dir d ( resolve (t.base_scope (), - as (*t["install"]))); // We know it's there. + cast (*t["install"]))); // We know it's there. // Override mode if one was specified. // if (auto l = t["install.mode"]) - d.mode = as (*l); + d.mode = cast (*l); install (d, ft); return (r |= target_state::changed); diff --git a/build2/module.cxx b/build2/module.cxx index e05e22c..b090173 100644 --- a/build2/module.cxx +++ b/build2/module.cxx @@ -98,12 +98,10 @@ namespace build2 bool l (i != lm.end ()); bool c (l && i->second.init (rs, bs, loc, i->second.module, f, opt)); - const variable& lv (var_pool.find (name + ".loaded", - variable_visibility::project, - bool_type)); - const variable& cv (var_pool.find (name + ".configured", - variable_visibility::project, - bool_type)); + const variable& lv (var_pool.find (name + ".loaded", + variable_visibility::project)); + const variable& cv (var_pool.find (name + ".configured", + variable_visibility::project)); bs.assign (lv) = l; bs.assign (cv) = c; diff --git a/build2/name b/build2/name index a4de95c..d0a115f 100644 --- a/build2/name +++ b/build2/name @@ -2,21 +2,19 @@ // copyright : Copyright (c) 2014-2016 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file +// Note: include instead of this file directly. +// + #ifndef BUILD2_NAME #define BUILD2_NAME -#include -#include -#include +// We cannot include since it includes . +// #include // move() -#include - -// Note: include instead of this file directly. -// namespace build2 { - using butl::dir_path; + using std::move; // A name is what we operate on by default. Depending on the context, // it can be interpreted as a target or prerequisite name. A name @@ -31,27 +29,29 @@ namespace build2 // struct name { + const string* proj = nullptr; // Points to project_name_pool. + dir_path dir; + string type; + string value; + bool pair = false; // True if first half of a pair. + name () = default; - explicit name (std::string v): value (std::move (v)) {} - name& operator= (std::string v) {return *this = name (std::move (v));} + explicit name (string v): value (move (v)) {} + name& operator= (string v) {return *this = name (move (v));} - explicit name (dir_path d): dir (std::move (d)) {} - name& operator= (dir_path d) {return *this = name (std::move (d));} + explicit name (dir_path d): dir (move (d)) {} + name& operator= (dir_path d) {return *this = name (move (d));} - name (std::string t, std::string v) - : type (std::move (t)), value (std::move (v)) {} + name (string t, string v): type (move (t)), value (move (v)) {} - name (dir_path d, std::string t, std::string v) - : dir (std::move (d)), type (std::move (t)), value (std::move (v)) {} + name (dir_path d, string t, string v) + : dir (move (d)), type (move (t)), value (move (v)) {} // The first argument should be from project_name_pool. // - name (const std::string* p, dir_path d, std::string t, std::string v) - : proj (p), - dir (std::move (d)), - type (std::move (t)), - value (std::move (v)) {} + name (const string* p, dir_path d, string t, string v) + : proj (p), dir (move (d)), type (move (t)), value (move (v)) {} bool qualified () const {return proj != nullptr;} @@ -87,32 +87,39 @@ namespace build2 untyped () && !dir.empty () && value.empty (); } - const std::string* proj = nullptr; // Points to project_name_pool. - dir_path dir; - std::string type; - std::string value; - bool pair = false; // True if first half of a pair. + int + compare (const name&) const; + + // The result is an unqualified, simple empty name. + // + void + clear (); }; inline bool - operator== (const name& x, const name& y) - { - return x.proj == y.proj && // Pooled, so can just compare pointers. - x.type == y.type && - x.dir == y.dir && - x.value == y.value; - } + operator== (const name& x, const name& y) {return x.compare (y) == 0;} inline bool operator!= (const name& x, const name& y) {return !(x == y);} - typedef std::vector names; + inline bool + operator< (const name& x, const name& y) {return x.compare (y) < 0;} + + ostream& + operator<< (ostream&, const name&); - std::ostream& - operator<< (std::ostream&, const name&); + // Vector of names. + // + using names = vector; + using names_view = vector_view; - std::ostream& - operator<< (std::ostream&, const names&); + ostream& + operator<< (ostream&, const names_view&); + + inline ostream& + operator<< (ostream& os, const names& n) {return os << names_view (n);} } +#include + #endif // BUILD2_NAME diff --git a/build2/name.cxx b/build2/name.cxx index 0d7211c..296b4ec 100644 --- a/build2/name.cxx +++ b/build2/name.cxx @@ -2,12 +2,10 @@ // copyright : Copyright (c) 2014-2016 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include // Note: not #include -using namespace std; - namespace build2 { ostream& @@ -42,7 +40,7 @@ namespace build2 } ostream& - operator<< (ostream& os, const names& ns) + operator<< (ostream& os, const names_view& ns) { for (auto i (ns.begin ()), e (ns.end ()); i != e; ) { diff --git a/build2/name.ixx b/build2/name.ixx new file mode 100644 index 0000000..21bd125 --- /dev/null +++ b/build2/name.ixx @@ -0,0 +1,42 @@ +// file : build2/name.ixx -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +namespace build2 +{ + inline int name:: + compare (const name& x) const + { + int r; + + // Project string is pooled, so for equality can just compare pointers. + // + r = proj == x.proj + ? 0 + : proj == nullptr || (x.proj != nullptr && *proj < *x.proj) ? -1 : 1; + + if (r == 0) + r = dir.compare (x.dir); + + if (r == 0) + r = type.compare (x.type); + + if (r == 0) + r = value.compare (x.value); + + if (r == 0) + r = pair < x.pair ? -1 : (pair > x.pair ? 1 : 0); + + return r; + } + + inline void name:: + clear () + { + proj = nullptr; + dir.clear (); + type.clear (); + value.clear (); + pair = false; + } +} diff --git a/build2/parser.cxx b/build2/parser.cxx index ab9012c..b19fcb3 100644 --- a/build2/parser.cxx +++ b/build2/parser.cxx @@ -1062,14 +1062,21 @@ namespace build2 const location nsl (get_location (t, &path_)); names_type ns (names (t, tt)); - // Should evaluate to true or false. + // Should evaluate to 'true' or 'false'. // - if (ns.size () != 1 || !assign (ns[0])) + try + { + if (ns.size () != 1) + throw invalid_argument (string ()); + + bool e (convert (move (ns[0]))); + take = (k.back () == '!' ? !e : e); + } + catch (const invalid_argument&) + { fail (nsl) << "expected " << k << "-expression to evaluate to " << "'true' or 'false' instead of '" << ns << "'"; - - bool e (ns[0].value == "true"); - take = (k.back () == '!' ? !e : e); + } } } else @@ -1574,8 +1581,8 @@ namespace build2 // pretty quickly end up with a list of names that we need // to splice into the result. // - names_type lv_data; - const names_type* plv; + names_type lv_storage; + names_view lv; location loc; const char* what; // Variable or evaluation context. @@ -1630,10 +1637,10 @@ namespace build2 tt = peek (); - if (lv_data.empty ()) + if (lv_storage.empty ()) continue; - plv = &lv_data; + lv = lv_storage; what = "function call"; } else @@ -1660,27 +1667,26 @@ namespace build2 if (!l || l->empty ()) continue; - plv = &l->data_; + lv = reverse (*l, lv_storage); what = "variable expansion"; } } else { loc = get_location (t, &path_); - lv_data = eval (t, tt); + lv_storage = eval (t, tt); tt = peek (); - if (lv_data.empty ()) + if (lv_storage.empty ()) continue; - plv = &lv_data; + lv = lv_storage; what = "context evaluation"; } - // @@ Could move if (lv == &lv_data). + // @@ Could move if lv is lv_storage. // - const names_type& lv (*plv); // Should we accumulate? If the buffer is not empty, then // we continue accumulating (the case where we are separated diff --git a/build2/scope b/build2/scope index e92361d..9878e7f 100644 --- a/build2/scope +++ b/build2/scope @@ -141,10 +141,7 @@ namespace build2 assign (const variable& var) {return vars.assign (var).first.get ();} value& - assign (const string& name, const build2::value_type* type = nullptr) - { - return vars.assign (name, type).first.get (); - } + assign (const string& name) {return vars.assign (name).first.get ();} template value& diff --git a/build2/target b/build2/target index cdf6037..4c5d046 100644 --- a/build2/target +++ b/build2/target @@ -292,10 +292,7 @@ namespace build2 assign (const variable& var) {return vars.assign (var).first;} value& - assign (const string& name, const build2::value_type* type = nullptr) - { - return vars.assign (name, type).first; - } + assign (const string& name) {return vars.assign (name).first;} template value& diff --git a/build2/target.txx b/build2/target.txx index 4999f61..2cb4d2e 100644 --- a/build2/target.txx +++ b/build2/target.txx @@ -26,7 +26,7 @@ namespace build2 { // Help the user here and strip leading '.' from the extension. // - const string& e (as (*l)); + const string& e (cast (*l)); return &extension_pool.find ( !e.empty () && e.front () == '.' ? string (e, 1) : e); } diff --git a/build2/test/rule.cxx b/build2/test/rule.cxx index efe265b..0b3f59f 100644 --- a/build2/test/rule.cxx +++ b/build2/test/rule.cxx @@ -70,7 +70,7 @@ namespace build2 l = t.base_scope ()[ var_pool.find (string("test.") + t.type ().name)]; - r = l && as (*l); + r = l && cast (*l); } // If this is the update pre-operation, then all we really need to @@ -181,12 +181,12 @@ namespace build2 fail << "both test.roundtrip and test.input/output specified " << "for target " << t; - in = on = &as (*rl); + in = on = &cast (*rl); } else { - in = il ? &as (*il) : nullptr; - on = ol ? &as (*ol) : nullptr; + in = il ? &cast (*il) : nullptr; + on = ol ? &cast (*ol) : nullptr; } // Resolve them to targets, which normally would be existing files @@ -296,7 +296,7 @@ namespace build2 } if (l) - append_options (args, as (*l)); + append_options (args, cast (*l)); } // The format of args shall be: diff --git a/build2/types b/build2/types index 54056ba..166f47a 100644 --- a/build2/types +++ b/build2/types @@ -25,8 +25,7 @@ #include #include #include - -#include +#include namespace build2 { @@ -50,6 +49,7 @@ namespace build2 using std::weak_ptr; using std::vector; + using butl::vector_view; // using strings = vector; using cstrings = vector; @@ -109,9 +109,10 @@ namespace build2 using butl::ifdstream; using butl::ofdstream; - - // - // } +// +// +#include + #endif // BUILD2_TYPES diff --git a/build2/utility b/build2/utility index a3f9be4..e906b55 100644 --- a/build2/utility +++ b/build2/utility @@ -150,8 +150,6 @@ namespace build2 // class value; template struct lookup; - template struct vector_value; - using const_strings_value = vector_value; void append_options (cstrings&, const lookup&); @@ -160,10 +158,10 @@ namespace build2 hash_options (sha256&, const lookup&); void - append_options (cstrings&, const const_strings_value&); + append_options (cstrings&, const strings&); void - hash_options (sha256&, const const_strings_value&); + hash_options (sha256&, const strings&); // Check if a specified option is present in the variable value. // T is either target or scope. diff --git a/build2/utility.cxx b/build2/utility.cxx index 4c1acd3..29e8b1e 100644 --- a/build2/utility.cxx +++ b/build2/utility.cxx @@ -147,18 +147,18 @@ namespace build2 append_options (cstrings& args, const lookup& l) { if (l) - append_options (args, as (*l)); + append_options (args, cast (*l)); } void hash_options (sha256& csum, const lookup& l) { if (l) - hash_options (csum, as (*l)); + hash_options (csum, cast (*l)); } void - append_options (cstrings& args, const const_strings_value& sv) + append_options (cstrings& args, const strings& sv) { if (!sv.empty ()) { @@ -170,7 +170,7 @@ namespace build2 } void - hash_options (sha256& csum, const const_strings_value& sv) + hash_options (sha256& csum, const strings& sv) { for (const string& s: sv) csum.append (s); @@ -181,7 +181,7 @@ namespace build2 { if (l) { - for (const string& s: as (*l)) + for (const string& s: cast (*l)) { if (s == option) return true; diff --git a/build2/utility.ixx b/build2/utility.ixx index 596eb78..d42d6fd 100644 --- a/build2/utility.ixx +++ b/build2/utility.ixx @@ -44,6 +44,7 @@ namespace build2 inline void hash_options (sha256& csum, T& s, const char* var) { + hash_options (csum, s[var]); } diff --git a/build2/variable b/build2/variable index 029aafa..b5a32d8 100644 --- a/build2/variable +++ b/build2/variable @@ -6,9 +6,8 @@ #define BUILD2_VARIABLE #include -#include // tags, etc. #include // hash -#include // conditional, is_reference, remove_reference, etc. +#include // aligned_storage #include #include @@ -20,18 +19,50 @@ namespace build2 { + class value; struct variable; - // If assign is NULL, then the value is assigned as is. If append - // is NULL, then the names are appended to the end of the value - // and assign is called, if not NULL. Variable is provided primarily - // for diagnostics. Return true if the resulting value is not empty. - // struct value_type { - const char* name; - bool (*const assign) (names&, const variable&); - bool (*const append) (names&, names, const variable&); + const char* name; // Type name for diagnostics. + const size_t size; // Type size in value::data_ (only used for PODs). + + // Destroy the value. If it is NULL, then the type is assumed to be POD + // with a trivial destructor. + // + void (*const dtor) (value&); + + // Copy/move constructor and copy/move assignment for data_. If NULL, then + // assume the stored data is POD. If move is true then the second argument + // can be const_cast and moved from. copy_assign() is only called with + // non-NULL first argument. + // + void (*const copy_ctor) (value&, const value&, bool move); + void (*const copy_assign) (value&, const value&, bool move); + + // While assign cannot be NULL, if append or prepend is NULL, then this + // means this type doesn't support this operation. Variable is provided + // primarily for diagnostics. Return true if the resulting value is not + // empty. + // + bool (*const assign) (value&, names&&, const variable&); + bool (*const append) (value&, names&&, const variable&); + bool (*const prepend) (value&, names&&, const variable&); + + // Reverse the value back to a vector of names. Storage can be used by the + // implementation if necessary. Cannot be NULL. + // + names_view (*const reverse) (const value&, names& storage); + + // Cast value::data_ storage to value type so that the result can be + // static_cast to const T*. If it is NULL, then cast data_ directly. Note + // that this function is used for both const and non-const values. + // + const void* (*const cast) (const value&); + + // If NULL, then the types are compared as PODs using memcmp(). + // + int (*const compare) (const value&, const value&); }; enum class variable_visibility @@ -59,117 +90,127 @@ namespace build2 // value // + enum class value_state {null, empty, filled}; + class value { public: - // By default we create NULL value. - // - explicit value (const value_type* t = nullptr) - : type (t), state_ (state_type::null) {} - - value (value&&) = default; - - value& - operator= (nullptr_t) - { - data_.clear (); - state_ = state_type::null; - return *this; - } + const value_type* type; // NULL means this value is not (yet) typed. + value_state state; - value& - operator= (value&&); + bool null () const {return state == value_state::null;} + bool empty () const {return state == value_state::empty;} - value& - operator= (const value& v) - { - if (&v != this) - *this = value (v); - return *this; - } + explicit operator bool () const {return !null ();} + bool operator== (nullptr_t) const {return null ();} + bool operator!= (nullptr_t) const {return !null ();} - value& - operator= (reference_wrapper v) {return *this = v.get ();} + // Creation. A newly created value is NULL and can be reset back to NULL + // by assigning nullptr. Values can be copied and copy-assigned. Note that + // for assignment, the values' types should be the same of LHS should be + // untyped. + // + // + public: + ~value () {if (!null ()) *this = nullptr;} - value& - operator= (reference_wrapper v) {return *this = v.get ();} + explicit + value (const value_type* t = nullptr) + : type (t), state (value_state::null) {} value& - append (value, const variable&); // Aka operator+=(). + operator= (nullptr_t); - value& - prepend (value, const variable&); // Aka operator=+(). + value (value&&); + explicit value (const value&); + value& operator= (value&&); + value& operator= (const value&); + value& operator= (reference_wrapper); + value& operator= (reference_wrapper); - // Forwarded to the representation type's assign()/append() (see below). + // Assign/Append/Prepend. // - template value& operator= (T); - value& operator= (const char* v) {return *this = string (v);} - - template value& operator+= (T); - value& operator+= (const char* v) {return *this += string (v);} - - private: - explicit value (const value&) = default; - public: - const value_type* type; // NULL means this value is not (yet) typed. - - bool null () const {return state_ == state_type::null;} - bool empty () const {return state_ == state_type::empty;} - - explicit operator bool () const {return !null ();} - bool operator== (nullptr_t) const {return null ();} - bool operator!= (nullptr_t) const {return !null ();} - - // Raw data read interface. + // Assign/append a typed value. For assign, LHS should be either of the + // same type or untyped. For append, LHS should be either of the same type + // or untyped and NULL. // - using const_iterator = names::const_iterator; + template value& operator= (T); + template value& operator+= (T); - const_iterator begin () const {return data_.begin ();} - const_iterator end () const {return data_.end ();} + value& operator= (const char* v) {return *this = string (v);} + value& operator+= (const char* v) {return *this += string (v);} - // Raw data write interface. Note that it triggers callbacks for - // typed values. Variable is passed for diagnostics. + // Assign/append/prepend raw data. Variable is normally only used for + // diagnostics. // void - assign (names, const variable&); + assign (names&&, const variable&); void - append (names, const variable&); + append (names&&, const variable&); void - prepend (names, const variable&); + prepend (names&&, const variable&); + // Implementation details, don't use directly except in representation + // type implementations. + // public: - // Don't use directly except in the implementation of representation - // types, in which case take care to update state. + // Fast, unchecked cast of data_ to T. // - enum class state_type {null, empty, filled} state_; - names data_; + template T& as () & {return reinterpret_cast (data_);} + template T&& as () && {return reinterpret_cast (data_);} + template const T& as () const& { + return reinterpret_cast (data_);} + + public: + // The maximum size we can store directly in the value is that of a name, + // which is sufficient for the most commonly used types (string, vector, + // map) on all the platforms that we support (each type should static + // assert this in its value_traits specialization below). Types that don't + // fit will have to be handled with an extra dynamic allocation. + // + std::aligned_storage::type data_; + static const size_t size_ = sizeof (data_); + + // Make sure we have sufficient storage for untyped values. + // + static_assert (sizeof (names) <= size_, "insufficient space"); }; - //@@ Right now we assume that for each value type each value has a - // unique representation. This is currently not the case for map. + // The values should be of the same type (or both be untyped). NULL values + // compare equal. // - inline bool - operator== (const value& x, const value& y) - { - return x.state_ == y.state_ && x.data_ == y.data_; - } + bool operator== (const value&, const value&); + bool operator!= (const value&, const value&); - inline bool - operator!= (const value& x, const value& y) {return !(x == y);} + // Value cast. + // + // Why are these non-members? The cast is easier on the eyes and is also + // consistent with the cast operators. The other two are for symmetry. + // + template T& cast (value&); + template T&& cast (value&&); + template const T& cast (const value&); - // Assign value type to the value. This triggers the assign callback. + // Assign value type to the value. Variable is normally only used for + // diagnostics. // template - void assign (value&, const variable&); - void assign (value&, const value_type*, const variable&); + void typify (value&, const variable&); + void typify (value&, const value_type&, const variable&); + + // Reverse the value back to names. The value should no be NULL and storage + // should be empty. + // + names_view + reverse (const value&, names& storage); // lookup // - // A variable can be undefined, NULL, or contain a (potentially - // empty) value. + // A variable can be undefined, NULL, or contain a (potentially empty) + // value. // struct variable_map; @@ -206,390 +247,207 @@ namespace build2 // Representation types. // - template struct value_traits; - - // Value cast. + // Potential optimizations: // - template typename value_traits::type as (value&); - template typename value_traits::const_type as (const value&); - - // Try to "assign" a simple value type to the value stored in name. Return - // false if the value is not valid for this type. The second version is - // called for a pair and it is expected to merge the result into the first - // name. + // - Split value::operator=/+=() into const T and T&&, also overload + // value_traits functions that they call. + // + // - Specialization for vector (if used and becomes critical). + // + // + template + struct value_traits; + // { + // static_assert (sizeof (T) <= value::size_, "insufficient space"); + // + // // Convert name to T. If rhs is not NULL, then it is the second half + // // of a pair. Only needs to be provided by simple types. Throw + // // invalid_argument (without a message) if the name is not a valid + // // representation of value (in which case the name should remain + // // unchanged for diagnostics). + // // + // static T convert (name&&, name* rhs); + // + // // Assign/append/prepend T to value and return true if the result is + // // not empty. Value is already of type T but can be NULL. + // // + // static bool assign (value&, T&&); + // static bool append (value&, T&&); + // static bool prepend (value&, T&&); + // + // // Reverse a value back to name. Only needs to be provided by simple + // // types. + // // + // static name reverse (const T&); // - template bool assign (name&); - template bool assign (name&, name&); + // // Compare two values. Only needs to be provided by simple types. + // // + // static int compare (const T&, const T&); + // + // static const build2::value_type value_type; + // }; - // Name cast. Can only be called after assign() above returned true. + // Convert name to a simple value. Throw invalid_argument (without any + // message) if the name is not a valid representation of value (in which + // case the name remains unchanged for diagnostics). The second version is + // called for a pair. // - template typename value_traits::type as (name&); - template typename value_traits::const_type as (const name&); + template T convert (name&&); + template T convert (name&&, name&&); - // bool + // Default implementations of the dtor/copy_ctor/copy_assing callbacks for + // types that are stored directly in value::data_ and the provide all the + // necessary functions (copy/move ctor and assignment operator). // - template - struct bool_value - { - explicit - operator bool () const {return d->value[0] == 't';} + template + static void + default_dtor (value&); - bool_value& - operator= (bool v) - { - d->value = v ? "true" : "false"; - return *this; - } + template + static void + default_copy_ctor (value&, const value&, bool); - bool_value& - operator+= (bool v) - { - if (!*this && v) - d->value = "true"; - return *this; - } + template + static void + default_copy_assign (value&, const value&, bool); + + // Default implementations of the assign/append/prepend callbacks for simple + // types. They call value_traits::convert() and then pass the result to + // value_traits::assign()/append()/prepend(). As a result, it may not be + // the most efficient way to do it. If the empty template parameter is true, + // then an empty names sequence is converted to a default-constructed T. And + // if false, then an empty value is not allowed. + // + template + static bool + simple_assign (value&, names&&, const variable&); - // Implementation details. - // - public: - explicit bool_value (D& d): d (&d) {} + template + static bool + simple_append (value&, names&&, const variable&); - bool_value (const bool_value&) = delete; - bool_value& operator= (const bool_value&) = delete; // Rebind or deep? + template + static bool + simple_prepend (value&, names&&, const variable&); - bool_value (bool_value&&) = default; - bool_value& operator= (bool_value&&) = delete; + // Default implementations of the reverse callback for simple types that + // calls value_traits::reverse() and adds the result to the vector. As a + // result, it may not be the most efficient way to do it. + // + template + static names_view + simple_reverse (const value&, names&); - D* d; // name - }; + // Default implementations of the compare callback for simple types that + // calls value_traits::compare(). + // + template + static int + simple_compare (const value&, const value&); + // bool + // template <> struct value_traits { - using type = bool_value; - using const_type = bool_value; - - static type as (name& n) {return type (n);} - static const_type as (const name& n) {return const_type (n);} + static_assert (sizeof (bool) <= value::size_, "insufficient space"); - static type as (value&); - static const_type as (const value&); - - static bool assign (name&, name*); - static void assign (value&, bool); - static void append (value&, bool); + static bool convert (name&&, name*); + static bool assign (value&, bool); + static bool append (value&, bool); // OR. + static name reverse (bool x) {return name (x ? "true" : "false");} + static int compare (bool, bool); static const build2::value_type value_type; }; - extern const value_type* bool_type; - // string // template <> struct value_traits { - using type = string&; - using const_type = const string&; - - static type as (name& n) {return n.value;} - static const_type as (const name& n) {return n.value;} + static_assert (sizeof (string) <= value::size_, "insufficient space"); - static type as (value&); - static const_type as (const value&); - - static bool assign (name&, name*); - static void assign (value&, string); - static void append (value&, string); + static string convert (name&&, name*); + static bool assign (value&, string&&); + static bool append (value&, string&&); + static bool prepend (value&, string&&); + static name reverse (const string& x) {return name (x);} + static int compare (const string&, const string&); static const build2::value_type value_type; }; - extern const value_type* string_type; //@@ Get rid (and others). - // dir_path // template <> struct value_traits { - using type = dir_path&; - using const_type = const dir_path&; - - static type as (name& n) {return n.dir;} - static const_type as (const name& n) {return n.dir;} - - static type as (value&); - static const_type as (const value&); + static_assert (sizeof (dir_path) <= value::size_, "insufficient space"); - static bool assign (name&, name*); - static void assign (value&, dir_path); - static void append (value&, dir_path); + static dir_path convert (name&&, name*); + static bool assign (value&, dir_path&&); + static bool append (value&, dir_path&&); // operator/ + static bool prepend (value&, dir_path&&); // operator/ + static name reverse (const dir_path& x) {return name (x);} + static int compare (const dir_path&, const dir_path&); static const build2::value_type value_type; }; - extern const value_type* dir_path_type; - // name // template <> struct value_traits { - using type = name&; - using const_type = const name&; - - static type as (name& n) {return n;} - static const_type as (const name& n) {return n;} - - static type as (value&); - static const_type as (const value&); + static_assert (sizeof (name) <= value::size_, "insufficient space"); - static bool assign (name&, name* r) {return r == nullptr;} - static void assign (value&, name); - static void append (value&, name) = delete; + static name convert (name&&, name*); + static bool assign (value&, name&&); + static bool append (value&, name&&); + static bool prepend (value&, name&&); + static name reverse (const name& x) {return x;} + static int compare (const name&, const name&); static const build2::value_type value_type; }; - extern const value_type* name_type; - // vector // - template - struct vector_value - { - using size_type = typename D::size_type; - - using value_type = typename value_traits::type; - using const_value_type = typename value_traits::const_type; - - template - struct iterator_impl: I - { - using value_type = V; - using pointer = value_type*; - using reference = R; - using difference_type = typename I::difference_type; - - iterator_impl () = default; - iterator_impl (const I& i): I (i) {} - - // Note: operator->() is unavailable if R is a value. - // - reference operator* () const {return as (I::operator* ());} - pointer operator-> () const {return &as (I::operator* ());} - reference operator[] (difference_type n) const - { - return as (I::operator[] (n)); - } - }; - - // Make iterator the same as const_iterator if our data type is const. - // - using const_iterator = - iterator_impl; - using iterator = typename std::conditional< - std::is_const::value, - const_iterator, - iterator_impl>::type; - - public: - vector_value& - operator= (vector v) {assign (move (v)); return *this;} - - vector_value& - assign (vector); - - template - vector_value& - assign (const vector_value&); - - template - vector_value& - append (const vector_value&); - - public: - bool empty () const {return d->empty ();} - size_type size () const {return d->size ();} - - value_type operator[] (size_type i) {return as ((*d)[i]);} - const_value_type operator[] (size_type i) const {return as ((*d)[i]);} - - iterator begin () {return iterator (d->begin ());} - iterator end () {return iterator (d->end ());} - - const_iterator begin () const {return const_iterator (d->begin ());} - const_iterator end () const {return const_iterator (d->end ());} - - const_iterator cbegin () const {return const_iterator (d->begin ());} - const_iterator cend () const {return const_iterator (d->end ());} - - // Implementation details. - // - public: - explicit vector_value (D& d): d (&d) {} - - vector_value (const vector_value&) = delete; - vector_value& operator= (const vector_value&) = delete; // Rebind or deep? - - vector_value (vector_value&&) = default; - vector_value& operator= (vector_value&&) = default; //@@ TMP - - explicit vector_value (nullptr_t): d (nullptr) {} //@@ TMP - - D* d; // names - }; - template struct value_traits> { - using type = vector_value; - using const_type = vector_value; - - static type as (value&); - static const_type as (const value&); + static_assert (sizeof (vector) <= value::size_, "insufficient space"); - template static void assign (value&, V); - template static void append (value&, V); + static bool assign (value&, vector&&); + static bool append (value&, vector&&); + static bool prepend (value&, vector&&); static const string type_name; static const build2::value_type value_type; }; - template - struct value_traits>: value_traits> {}; - - using strings_value = vector_value; - using const_strings_value = vector_value; - - extern const value_type* strings_type; // vector aka strings - extern const value_type* dir_paths_type; // vector aka dir_paths - extern const value_type* names_type; // vector aka names - // map // - template - struct map_value - { - template - struct pair - { - using first_type = typename std::conditional< - std::is_reference::value, - reference_wrapper::type>, - F>::type; - - using second_type = typename std::conditional< - std::is_reference::value, - reference_wrapper::type>, - S>::type; - - first_type first; - second_type second; - }; - - template - struct iterator_impl - { - using value_type = T; - using pointer = value_type*; - using reference = value_type; // Note: value. - using difference_type = typename I::difference_type; - using iterator_category = std::bidirectional_iterator_tag; - - iterator_impl () = default; - iterator_impl (const I& i): i_ (i) {} - - pointer operator-> () const = delete; - reference operator* () const - { - return value_type {as (*i_), as (*(i_ + 1))}; - } - - iterator_impl& operator++ () {i_ += 2; return *this;} - iterator_impl operator++ (int) {auto r (*this); operator++ (); return r;} - - iterator_impl& operator-- () {i_ -= 2; return *this;} - iterator_impl operator-- (int) {auto r (*this); operator-- (); return r;} - - bool operator== (const iterator_impl& y) const {return i_ == y.i_;} - bool operator!= (const iterator_impl& y) const {return i_ != y.i_;} - - private: - I i_; - }; - - using size_type = typename D::size_type; - - using value_type = pair::const_type, - typename value_traits::type>; - - using const_value_type = pair::const_type, - typename value_traits::const_type>; - - // Make iterator the same as const_iterator if our data type is const. - // - using const_iterator = - iterator_impl; - using iterator = typename std::conditional< - std::is_const::value, - const_iterator, - iterator_impl>::type; - - - public: - map_value& - operator= (std::map m) {assign (move (m)); return *this;} - - map_value& - assign (std::map); - - bool empty () const {return d->empty ();} - size_type size () const {return d->size ();} - - iterator find (const K&); - const_iterator find (const K&) const; - - iterator begin () {return iterator (d->begin ());} - iterator end () {return iterator (d->end ());} - - const_iterator begin () const {return const_iterator (d->begin ());} - const_iterator end () const {return const_iterator (d->end ());} - - // Implementation details. - // - public: - explicit map_value (D& d): d (&d) {} - - map_value (const map_value&) = delete; - map_value& operator= (const map_value&) = delete; // Rebind or deep? - - map_value (map_value&&) = default; - map_value& operator= (map_value&&) = delete; - - D* d; // names - }; - template struct value_traits> { - using type = map_value; - using const_type = map_value; + template using map = std::map; - static type as (value&); - static const_type as (const value&); + static_assert (sizeof (map) <= value::size_, "insufficient space"); - template static void assign (value&, M); - template static void append (value&, M); + static bool assign (value&, map&&); + static bool append (value&, map&&); + static bool prepend (value& v, map&& x) { + return append (v, move (x));} static const string type_name; static const build2::value_type value_type; }; - - template - struct value_traits>: value_traits> {}; } +// Variable map. +// namespace std { template <> @@ -634,6 +492,12 @@ namespace build2 using variable_pool_base = std::unordered_set; struct variable_pool: private variable_pool_base { + const variable& + find (string name) + { + return find (name, nullptr, nullptr); + } + template const variable& find (string name) @@ -642,56 +506,23 @@ namespace build2 } const variable& - find (string name, const build2::value_type* t = nullptr) + find (string name, variable_visibility v) { - return find (name, nullptr, t); + return find (name, &v, nullptr); } + template const variable& - find (string name, - variable_visibility v, - const build2::value_type* t = nullptr) + find (string name, variable_visibility v) { - return find (name, &v, t); + return find (name, &v, &value_traits::value_type); } using variable_pool_base::clear; private: const variable& - find (string name, - const variable_visibility* vv, - const build2::value_type* t) - { - auto r ( - insert ( - variable { - move (name), - t, - vv != nullptr ? *vv : variable_visibility::normal})); - const variable& v (*r.first); - - // Update type? - // - if (!r.second && t != nullptr && v.type != t) - { - assert (v.type == nullptr); - const_cast (v).type = t; // Not changing the key. - } - - // Change visibility? While this might at first seem like a bad idea, - // it can happen that the variable lookup happens before any values - // were set, in which case the variable will be entered with the - // default visibility. - // - if (!r.second && vv != nullptr && v.visibility != *vv) - { - assert (v.visibility == variable_visibility::normal); // Default. - const_cast (v).visibility = *vv; // Not changing the key. - } - - return v; - } + find (string name, const variable_visibility*, const build2::value_type*); }; extern variable_pool var_pool; @@ -714,34 +545,6 @@ namespace build2 using const_iterator = iterator_adapter; - const value* - find (const variable& var) const - { - auto i (m_.find (var)); - const value* r (i != m_.end () ? &i->second : nullptr); - - // First access after being assigned a type? - // - if (r != nullptr && var.type != nullptr && r->type != var.type) - build2::assign (const_cast (*r), var.type, var); - - return r; - } - - value* - find (const variable& var) - { - auto i (m_.find (var)); - value* r (i != m_.end () ? &i->second : nullptr); - - // First access after being assigned a type? - // - if (r != nullptr && var.type != nullptr && r->type != var.type) - build2::assign (*r, var.type, var); - - return r; - } - lookup operator[] (const variable& var) const { @@ -768,34 +571,29 @@ namespace build2 return operator[] (var_pool.find (name)); } + const value* + find (const variable&) const; + + value* + find (const variable&); + // The second member in the pair indicates whether the new value (which // will be NULL) was assigned. // pair, bool> - assign (const variable& var) - { - auto r (m_.emplace (var, value (var.type))); - value& v (r.first->second); - - // First access after being assigned a type? - // - if (!r.second && var.type != nullptr && v.type != var.type) - build2::assign (v, var.type, var); - - return make_pair (reference_wrapper (v), r.second); - } + assign (const variable&); pair, bool> - assign (const string& name, const build2::value_type* type = nullptr) + assign (const string& name) { - return assign (var_pool.find (name, type)); + return assign (var_pool.find (name)); } template pair, bool> assign (const string& name) { - return assign (var_pool.find (name, &value_traits::value_type)); + return assign (var_pool.find (name)); } pair diff --git a/build2/variable.cxx b/build2/variable.cxx index ce06d3e..c15a773 100644 --- a/build2/variable.cxx +++ b/build2/variable.cxx @@ -4,6 +4,8 @@ #include +#include // memcmp() + #include using namespace std; @@ -12,23 +14,50 @@ namespace build2 { // value // - void - assign (value& v, const value_type* t, const variable& var) + value& value:: + operator= (nullptr_t) { - if (v.type == nullptr) + if (!null ()) { - v.type = t; + if (type == nullptr) + as ().~names (); + else if (type->dtor != nullptr) + type->dtor (*this); - if (v && t->assign != nullptr) - v.state_ = t->assign (v.data_, var) - ? value::state_type::filled - : value::state_type::empty; + state = value_state::null; + } + + return *this; + } + + value:: + value (value&& v) + : type (v.type), state (v.state) + { + if (!null ()) + { + if (type == nullptr) + as () = move (v).as (); + else if (type->copy_ctor != nullptr) + type->copy_ctor (*this, v, true); + else + data_ = v.data_; // Copy as POD. + } + } + + value:: + value (const value& v) + : type (v.type), state (v.state) + { + if (!null ()) + { + if (type == nullptr) + as () = v.as (); + else if (type->copy_ctor != nullptr) + type->copy_ctor (*this, v, false); + else + data_ = v.data_; // Copy as POD. } - else - fail << "variable '" << var.name << "' type mismatch" << - info << "value '" << v.data_ << "' is " << v.type->name << - info << (t == var.type ? "variable" : "new type") << " is " - << (var.type != nullptr ? var.type->name : "untyped"); } value& value:: @@ -36,152 +65,264 @@ namespace build2 { assert (type == nullptr || type == v.type); - // Since the types are the same, we don't need to call - // the callbacks. - // - type = v.type; - state_ = v.state_; - data_ = move (v.data_); + if (this != &v) + { + // Prepare the receiving value. + // + if (type == nullptr && v.type != nullptr) + { + if (!null ()) + *this = nullptr; - return *this; - } + type = v.type; + } + + // Now our types are the same. If the receiving value is NULL, then call + // copy_ctor() instead of copy_assign(). + // + if (type == nullptr) + as () = move (v).as (); + else if (auto f = null () ? type->copy_ctor : type->copy_assign) + f (*this, v, true); + else + data_ = v.data_; // Assign as POD. + + state = v.state; + } - value& value:: - append (value v, const variable& var) - { - assert (type == v.type); - append (move (v.data_), var); return *this; } value& value:: - prepend (value v, const variable& var) + operator= (const value& v) { - assert (type == v.type); - prepend (move (v.data_), var); + assert (type == nullptr || type == v.type); + + if (this != &v) + { + // Prepare the receiving value. + // + if (type == nullptr && v.type != nullptr) + { + if (!null ()) + { + reinterpret_cast (data_).~names (); + state = value_state::null; + } + + type = v.type; + } + + // Now our types are the same. If the receiving value is NULL, then call + // copy_ctor() instead of copy_assign(). + // + if (type == nullptr) + as () = v.as (); + else if (auto f = null () ? type->copy_ctor : type->copy_assign) + f (*this, v, false); + else + data_ = v.data_; // Assign as POD. + + state = v.state; + } + return *this; } void value:: - append (names v, const variable& var) + assign (names&& ns, const variable& var) { - // Treat append to NULL as assign. - // - if (!null () && type != nullptr && type->append != nullptr) + assert (type == nullptr || type->assign != nullptr); + + bool r; + + if (type == nullptr) { - state_ = type->append (data_, move (v), var) - ? state_type::filled - : state_type::empty; - return; - } + names* p; + + if (null ()) + p = new (&data_) names (move (ns)); + else + { + p = &as (); + *p = move (ns); + } - if (data_.empty ()) - data_ = move (v); + r = !p->empty (); + } else - data_.insert (data_.end (), - make_move_iterator (v.begin ()), - make_move_iterator (v.end ())); - - state_ = (type != nullptr && type->assign != nullptr - ? type->assign (data_, var) - : !data_.empty ()) - ? state_type::filled - : state_type::empty; + r = type->assign (*this, move (ns), var); + + state = r ? value_state::filled : value_state::empty; } void value:: - prepend (names v, const variable& var) + append (names&& ns, const variable& var) { - // Reduce to append. - // - if (!null () && type != nullptr && type->append != nullptr) + bool r; + + if (type == nullptr) { - state_ = type->append (v, move (data_), var) - ? state_type::filled - : state_type::empty; - swap (data_, v); - return; - } + names* p; - if (data_.empty ()) - data_ = move (v); + if (null ()) + p = new (&data_) names (move (ns)); + else + { + p = &as (); + + if (p->empty ()) + *p = move (ns); + else if (!ns.empty ()) + { + p->insert (p->end (), + make_move_iterator (ns.begin ()), + make_move_iterator (ns.end ())); + } + } + + r = !p->empty (); + } else { - v.insert (v.end (), - make_move_iterator (data_.begin ()), - make_move_iterator (data_.end ())); - swap (data_, v); + if (type->append == nullptr) + fail << type->name << " value in variable " << var.name + << " cannot be appended to"; + + r = type->append (*this, move (ns), var); } - state_ = (type != nullptr && type->assign != nullptr - ? type->assign (data_, var) - : !data_.empty ()) - ? state_type::filled - : state_type::empty; + state = r ? value_state::filled : value_state::empty; } - // bool value - // - bool value_traits:: - assign (name& n, name* r) + void value:: + prepend (names&& ns, const variable& var) { - if (r == nullptr && n.simple ()) + bool r; + + if (type == nullptr) { - const string& s (n.value); + names* p; - if (s == "true" || s == "false") - return true; + if (null ()) + p = new (&data_) names (move (ns)); + else + { + p = &as (); + + if (p->empty ()) + *p = move (ns); + else if (!ns.empty ()) + { + ns.insert (ns.end (), + make_move_iterator (p->begin ()), + make_move_iterator (p->end ())); + p->swap (ns); + } + } + + r = !p->empty (); + } + else + { + if (type->prepend == nullptr) + fail << type->name << " value in variable " << var.name + << " cannot be prepended to"; + + r = type->prepend (*this, move (ns), var); } - return false; + state = r ? value_state::filled : value_state::empty; } - static bool - bool_assign (names& v, const variable& var) + bool + operator== (const value& x, const value& y) { - // Verify the value is either "true" or "false". - // - if (v.size () == 1) - { - name& n (v.front ()); + assert (x.type == y.type); - if (assign (n)) - return true; - } + if (x.state != y.state) + return false; + + if (x.null ()) + return true; + + if (x.type == nullptr) + return x.as () == y.as (); - fail << "invalid bool variable '" << var.name << "' value '" << v << "'"; - return false; + if (x.type->compare == nullptr) + return memcmp (&x.data_, &y.data_, x.type->size) == 0; + + return x.type->compare (x, y) == 0; } - static bool - bool_append (names& v, names a, const variable& var) + void + typify (value& v, const value_type& t, const variable& var) { - // Translate append to OR. - // - bool_assign (a, var); // Verify "true" or "false". + if (v.type == nullptr) + { + if (!v.null ()) + { + // Note: the order in which we do things here is important. + // + names ns (move (v).as ()); + v = nullptr; + v.type = &t; + v.assign (move (ns), var); + } + else + v.type = &t; + } + else if (v.type != &t) + { + fail << "variable " << var.name << " type mismatch" << + info << "value type is " << v.type->name << + info << (&t == var.type ? "variable" : "new") << " type is " << t.name; + } + } - if (a.front ().value[0] == 't' && v.front ().value[0] == 'f') - v = move (a); + // bool value + // + bool value_traits:: + convert (name&& n, name* r) + { + if (r == nullptr && n.simple ()) + { + const string& s (n.value); + + if (s == "true") + return true; + + if (s == "false") + return false; + + // Fall through. + } - return true; + throw invalid_argument (string ()); } const value_type value_traits::value_type { "bool", - &bool_assign, - &bool_append + sizeof (bool), + nullptr, // No dtor (POD). + nullptr, // No copy_ctor (POD). + nullptr, // No copy_assign (POD). + &simple_assign, // No empty value. + &simple_append, + &simple_append, // Prepend same as append. + &simple_reverse, + nullptr, // No cast (cast data_ directly). + nullptr // No compare (compare as POD). }; - const value_type* bool_type = &value_traits::value_type; - // string value // - bool value_traits:: - assign (name& n, name* r) + string value_traits:: + convert (name&& n, name* r) { // The goal is to reverse the name into its original representation. The - // code is a bit convoluted because we try to avoid extra allocation for + // code is a bit convoluted because we try to avoid extra allocations for // the common cases (unqualified, unpaired simple name or directory). // @@ -189,215 +330,194 @@ namespace build2 // if (!(n.simple (true) || n.directory (true)) || !(r == nullptr || r->simple (true) || r->directory (true))) - return false; + throw invalid_argument (string ()); + + string s; if (n.directory (true)) { - n.value = move (n.dir).string (); // Move string out of path. + s = move (n.dir).string (); // Move string out of path. // Add / back to the end of the path unless it is already there. Note // that the string cannot be empty (n.directory () would have been // false). // - if (!dir_path::traits::is_separator (n.value[n.value.size () - 1])) - n.value += '/'; + if (!dir_path::traits::is_separator (s[s.size () - 1])) + s += '/'; } + else + s.swap (n.value); // Convert project qualification to its string representation. // if (n.qualified ()) { - string s (*n.proj); - s += '%'; - s += n.value; - s.swap (n.value); + string p (*n.proj); + p += '%'; + p += s; + p.swap (s); } // The same for the RHS of a pair, if we have one. // if (r != nullptr) { - n.value += '@'; + s += '@'; if (r->qualified ()) { - n.value += *r->proj; - n.value += '%'; + s += *r->proj; + s += '%'; } if (r->directory (true)) { - n.value += r->dir.string (); + s += r->dir.string (); - if (!dir_path::traits::is_separator (n.value[n.value.size () - 1])) - n.value += '/'; + if (!dir_path::traits::is_separator (s[s.size () - 1])) + s += '/'; } else - n.value += r->value; - } - - return true; - } - - static bool - string_assign (names& v, const variable& var) - { - // Verify/convert the value is/to a single simple name. - // - if (v.empty ()) - { - v.emplace_back (name ()); // Canonical empty string representation. - return false; - } - else if (v.size () == 1) - { - name& n (v.front ()); - - if (assign (n)) - return !n.value.empty (); + s += r->value; } - fail << "invalid string variable '" << var.name << "' value '" << v << "'"; - return false; - } - - static bool - string_append (names& v, names a, const variable& var) - { - // Translate append to string concatenation. - // - string_assign (a, var); // Verify/convert value is/to string. - - if (v.front ().value.empty ()) - v = move (a); - else - v.front ().value += a.front ().value; - - return !v.front ().value.empty (); + return s; } const value_type value_traits::value_type { "string", - &string_assign, - &string_append + sizeof (string), + &default_dtor, + &default_copy_ctor, + &default_copy_assign, + &simple_assign, // Allow empty strings. + &simple_append, + &simple_prepend, + &simple_reverse, + nullptr, // No cast (cast data_ directly). + &simple_compare }; - const value_type* string_type = &value_traits::value_type; - // dir_path value // - bool value_traits:: - assign (name& n, name* r) + dir_path value_traits:: + convert (name&& n, name* r) { - if (r != nullptr) - return false; - - if (n.directory ()) - return true; - - if (n.simple ()) + if (r == nullptr) { - try + if (n.directory ()) + return move (n.dir); + + if (n.simple ()) { - n.dir = n.empty () ? dir_path () : dir_path (move (n.value)); - n.value.clear (); - return true; + try + { + return dir_path (move (n.value)); + } + catch (const invalid_path&) {} // Fall through. } - catch (const invalid_path&) {} // Fall through. + + // Fall through. } - return false; + throw invalid_argument (string ()); } - static bool - dir_path_assign (names& v, const variable& var) + const value_type value_traits::value_type { - // Verify/convert the value is/to a single directory name. - // - if (v.empty ()) - { - v.emplace_back (dir_path ()); // Canonical empty path representation. - return false; - } - else if (v.size () == 1) - { - name& n (v.front ()); + "dir_path", + sizeof (dir_path), + &default_dtor, + &default_copy_ctor, + &default_copy_assign, + &simple_assign, // Allow empty paths. + &simple_append, + &simple_prepend, + &simple_reverse, + nullptr, // No cast (cast data_ directly). + &simple_compare + }; - if (assign (n)) - return !n.dir.empty (); - } + // name value + // + name value_traits:: + convert (name&& n, name* r) + { + if (r != nullptr) + throw invalid_argument (string ()); - fail << "invalid dir_path variable '" << var.name << "' " - << "value '" << v << "'"; - return false; + return move (n); } - static bool - dir_path_append (names& v, names a, const variable& var) + static names_view + name_reverse (const value& v, names&) { - // Translate append to path concatenation. - // - dir_path_assign (a, var); // Verify/convert value is/to dir_path. - - dir_path& d (a.front ().dir); - if (d.relative ()) - return !(v.front ().dir /= d).empty (); - else - fail << "append of absolute path '" << d << "' to dir_path variable " - << var.name; - - return false; + return names_view (&v.as (), 1); } - const value_type value_traits::value_type + const value_type value_traits::value_type { - "dir_path", - &dir_path_assign, - &dir_path_append + "name", + sizeof (name), + &default_dtor, + &default_copy_ctor, + &default_copy_assign, + &simple_assign, // Allow empty names. + nullptr, // Append not supported. + nullptr, // Prepend not supported. + &name_reverse, + nullptr, // No cast (cast data_ directly). + &simple_compare }; - const value_type* dir_path_type = &value_traits::value_type; + // variable_pool + // + variable_pool var_pool; - // name value + // variable_map // - static bool - name_assign (names& v, const variable& var) + const value* variable_map:: + find (const variable& var) const { - // Verify the value is a single name. + auto i (m_.find (var)); + const value* r (i != m_.end () ? &i->second : nullptr); + + // First access after being assigned a type? // - if (v.size () == 1) - return v.front ().empty (); + if (r != nullptr && var.type != nullptr && r->type != var.type) + typify (const_cast (*r), *var.type, var); - fail << "invalid string variable '" << var.name << "' value '" << v << "'"; - return false; + return r; } - static bool - name_append (names&, names, const variable& var) + value* variable_map:: + find (const variable& var) { - fail << "append to name variable '" << var.name << "'"; - return false; + auto i (m_.find (var)); + value* r (i != m_.end () ? &i->second : nullptr); + + // First access after being assigned a type? + // + if (r != nullptr && var.type != nullptr && r->type != var.type) + typify (*r, *var.type, var); + + return r; } - const value_type value_traits::value_type + pair, bool> variable_map:: + assign (const variable& var) { - "name", - &name_assign, - &name_append - }; + auto r (m_.emplace (var, value (var.type))); + value& v (r.first->second); - const value_type* name_type = &value_traits::value_type; - - // vector value - // - const value_type* strings_type = &value_traits::value_type; - const value_type* dir_paths_type = &value_traits::value_type; - const value_type* names_type = &value_traits::value_type; + // First access after being assigned a type? + // + if (!r.second && var.type != nullptr && v.type != var.type) + typify (v, *var.type, var); - // variable_set - // - variable_pool var_pool; + return make_pair (reference_wrapper (v), r.second); + } // variable_type_map // diff --git a/build2/variable.ixx b/build2/variable.ixx index df3f0c5..a129835 100644 --- a/build2/variable.ixx +++ b/build2/variable.ixx @@ -2,371 +2,452 @@ // copyright : Copyright (c) 2014-2016 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file +#include // is_same + namespace build2 { // value // - template - inline void - assign (value& v, const variable& var) + inline value& value:: + operator= (reference_wrapper v) { - auto t (&value_traits::value_type); + return *this = v.get (); + } - if (v.type != t) - assign (v, t, var); + inline value& value:: + operator= (reference_wrapper v) + { + return *this = v.get (); } template - inline typename value_traits::type - as (value& v) + inline value& value:: + operator= (T v) { - return value_traits::as (v); + assert (type == &value_traits::value_type || type == nullptr); + + // Prepare the receiving value. + // + if (type == nullptr) + { + if (!null ()) + *this = nullptr; + + type = &value_traits::value_type; + } + + state = value_traits::assign (*this, move (v)) + ? value_state::filled + : value_state::empty; + + return *this; } template - inline typename value_traits::const_type - as (const value& v) + inline value& value:: + operator+= (T v) { - return value_traits::as (v); + assert (type == &value_traits::value_type || + (type == nullptr && null ())); + + // Prepare the receiving value. + // + if (type == nullptr) + type = &value_traits::value_type; + + state = value_traits::append (*this, move (v)) + ? value_state::filled + : value_state::empty; + + return *this; } - template inline bool - assign (name& n) + operator!= (const value& x, const value& y) { - return value_traits::assign (n, nullptr); + return !(x == y); } - template - inline bool - assign (name& l, name& r) + template <> + inline const names& + cast (const value& v) { - return value_traits::assign (l, &r); + // Note that it can still be a typed vector. + // + assert (!v.null () && + (v.type == nullptr || v.type == &value_traits::value_type)); + return v.as (); } template - inline typename value_traits::type - as (name& n) + inline const T& + cast (const value& v) { - return value_traits::as (n); + assert (!v.null () && v.type == &value_traits::value_type); + return *static_cast (v.type->cast == nullptr + ? static_cast (&v.data_) + : v.type->cast (v)); } template - inline typename value_traits::const_type - as (const name& n) + inline T& + cast (value& v) { - return value_traits::as (n); + return const_cast (cast (static_cast (v))); } template - inline value& value:: - operator= (T v) + inline T&& + cast (value&& v) { - value_traits::assign (*this, move (v)); - return *this; + return move (cast (v)); // Forward to T&. } template - inline value& value:: - operator+= (T v) + inline void + typify (value& v, const variable& var) { - value_traits::append (*this, move (v)); - return *this; + value_type& t (value_traits::value_type); + + if (v.type != &t) + typify (v, t, var); } - inline void value:: - assign (names v, const variable& var) + inline names_view + reverse (const value& v, names& storage) { - data_ = move (v); - state_ = (type != nullptr && type->assign != nullptr - ? type->assign (data_, var) - : !data_.empty ()) - ? state_type::filled - : state_type::empty; + assert (!v.null () && + storage.empty () && + (v.type == nullptr || v.type->reverse != nullptr)); + return v.type == nullptr ? v.as () : v.type->reverse (v, storage); } - // bool value + // value_traits // - inline bool_value value_traits:: - as (value& v) + template + inline T + convert (name&& n) { - assert (v.type == bool_type); - return bool_value (v.data_.front ()); + return value_traits::convert (move (n), nullptr); } - inline bool_value value_traits:: - as (const value& v) + template + inline T + convert (name&& l, name&& r) { - assert (v.type == bool_type); - return bool_value (v.data_.front ()); + return value_traits::convert (move (l), &r); } - inline void value_traits:: + // bool value + // + inline bool value_traits:: assign (value& v, bool x) { if (v.null ()) - { - if (v.type == nullptr) - v.type = bool_type; - v.data_.emplace_back (name ()); - v.state_ = value::state_type::empty; - } + new (&v.data_) bool (x); + else + v.as () = x; - as (v) = x; - v.state_ = value::state_type::filled; + return true; } - inline void value_traits:: + inline bool value_traits:: append (value& v, bool x) { + // Logical OR. + // if (v.null ()) - assign (v, x); + new (&v.data_) bool (x); else - as (v) += x; // Cannot be empty. + v.as () = v.as () || x; + + return true; } - // string value - // - inline string& value_traits:: - as (value& v) + inline int value_traits:: + compare (bool l, bool r) { - assert (v.type == string_type); - return v.data_.front ().value; + return l < r ? -1 : (l > r ? 1 : 0); } - inline const string& value_traits:: - as (const value& v) + // string value + // + inline bool value_traits:: + assign (value& v, string&& x) { - assert (v.type == string_type); - return v.data_.front ().value; + string* p; + + if (v.null ()) + p = new (&v.data_) string (move (x)); + else + p = &(v.as () = move (x)); + + return !p->empty (); } - inline void value_traits:: - assign (value& v, string x) + inline bool value_traits:: + append (value& v, string&& x) { + string* p; + if (v.null ()) + p = new (&v.data_) string (move (x)); + else { - if (v.type == nullptr) - v.type = string_type; - v.data_.emplace_back (name ()); - v.state_ = value::state_type::empty; + p = &v.as (); + + if (p->empty ()) + p->swap (x); + else + *p += x; } - v.state_ = (as (v) = move (x)).empty () - ? value::state_type::empty - : value::state_type::filled; + return !p->empty (); } - inline void value_traits:: - append (value& v, string x) + inline bool value_traits:: + prepend (value& v, string&& x) { + string* p; + if (v.null ()) - assign (v, move (x)); + new (&v.data_) string (move (x)); else - v.state_ = (as (v) += move (x)).empty () - ? value::state_type::empty - : value::state_type::filled; - } + { + p = &v.as (); - // dir_path value - // - inline dir_path& value_traits:: - as (value& v) - { - assert (v.type == dir_path_type); - return v.data_.front ().dir; + if (!p->empty ()) + x += *p; + + p->swap (x); + } + + return !p->empty (); } - inline const dir_path& value_traits:: - as (const value& v) + inline int value_traits:: + compare (const string& l, const string& r) { - assert (v.type == dir_path_type); - return v.data_.front ().dir; + return l.compare (r); } - inline void value_traits:: - assign (value& v, dir_path x) + // dir_path value + // + inline bool value_traits:: + assign (value& v, dir_path&& x) { + dir_path* p; + if (v.null ()) - { - if (v.type == nullptr) - v.type = dir_path_type; - v.data_.emplace_back (name ()); - v.state_ = value::state_type::empty; - } + p = new (&v.data_) dir_path (move (x)); + else + p = &(v.as () = move (x)); - v.state_ = (as (v) = move (x)).empty () - ? value::state_type::empty - : value::state_type::filled; + return !p->empty (); } - inline void value_traits:: - append (value& v, dir_path x) + inline bool value_traits:: + append (value& v, dir_path&& x) { + dir_path* p; + if (v.null ()) - assign (v, move (x)); + p = new (&v.data_) dir_path (move (x)); else - v.state_ = (as (v) /= move (x)).empty () - ? value::state_type::empty - : value::state_type::filled; - } + { + p = &v.as (); - // name value - // - inline name& value_traits:: - as (value& v) - { - assert (v.type == name_type); - return v.data_.front (); - } + if (p->empty ()) + p->swap (x); + else + *p /= x; + } - inline const name& value_traits:: - as (const value& v) - { - assert (v.type == name_type); - return v.data_.front (); + return !p->empty (); } - inline void value_traits:: - assign (value& v, name x) + inline bool value_traits:: + prepend (value& v, dir_path&& x) { + dir_path* p; + if (v.null ()) + new (&v.data_) dir_path (move (x)); + else { - if (v.type == nullptr) - v.type = name_type; - v.data_.emplace_back (name ()); - v.state_ = value::state_type::empty; - } + p = &v.as (); - v.state_ = (as (v) = move (x)).empty () - ? value::state_type::empty - : value::state_type::filled; - } + if (!p->empty ()) + x /= *p; - // vector value - // - template - inline vector_value& vector_value:: - assign (vector v) - { - d->clear (); - d->insert (d->end (), - make_move_iterator (v.begin ()), - make_move_iterator (v.end ())); - return *this; + p->swap (x); + } + + return !p->empty (); } - template - template - inline vector_value& vector_value:: - assign (const vector_value& v) + inline int value_traits:: + compare (const dir_path& l, const dir_path& r) { - d->clear (); - d->insert (d->end (), v.begin (), v.end ()); - return *this; + return l.compare (r); } - template - template - inline vector_value& vector_value:: - append (const vector_value& v) + // name value + // + inline bool value_traits:: + assign (value& v, name&& x) { - d->insert (d->end (), v.begin (), v.end ()); - return *this; + name* p; + + if (v.null ()) + p = new (&v.data_) name (move (x)); + else + p = &(v.as () = move (x)); + + return !p->empty (); } - template - inline vector_value value_traits>:: - as (value& v) + inline int value_traits:: + compare (const name& l, const name& r) { - assert (v.type == &value_traits>::value_type); - return vector_value (v.data_); + return l.compare (r); } + // vector value + // template - inline vector_value value_traits>:: - as (const value& v) + inline bool value_traits>:: + assign (value& v, vector&& x) { - assert (v.type == &value_traits>::value_type); - return vector_value (v.data_); + vector* p; + + if (v.null ()) + p = new (&v.data_) vector (move (x)); + else + p = &(v.as> () = move (x)); + + return !p->empty (); } template - template - inline void value_traits>:: - assign (value& v, V x) + inline bool value_traits>:: + append (value& v, vector&& x) { + vector* p; + if (v.null ()) + p = new (&v.data_) vector (move (x)); + else { - if (v.type == nullptr) - v.type = &value_traits>::value_type; - v.state_ = value::state_type::empty; + p = &v.as> (); + + if (p->empty ()) + p->swap (x); + else + p->insert (p->end (), + make_move_iterator (x.begin ()), + make_move_iterator (x.end ())); } - v.state_ = (as (v).assign (move (x))).empty () - ? value::state_type::empty - : value::state_type::filled; + return !p->empty (); } template - template - inline void value_traits>:: - append (value& v, V x) + inline bool value_traits>:: + prepend (value& v, vector&& x) { + vector* p; + if (v.null ()) - assign (v, move (x)); + new (&v.data_) vector (move (x)); else - v.state_ = (as (v).append (move (x))).empty () - ? value::state_type::empty - : value::state_type::filled; + { + p = &v.as> (); + + if (!p->empty ()) + x.insert (x.end (), + make_move_iterator (p->begin ()), + make_move_iterator (p->end ())); + + p->swap (x); + } + + return !p->empty (); } // map value // template - inline map_value value_traits>:: - as (value& v) + inline bool value_traits>:: + assign (value& v, map&& x) { - assert ((v.type == &value_traits>::value_type)); - return map_value (v.data_); - } + map* p; - template - inline map_value value_traits>:: - as (const value& v) - { - assert ((v.type == &value_traits>::value_type)); - return map_value (v.data_); + if (v.null ()) + p = new (&v.data_) map (move (x)); + else + p = &(v.as> () = move (x)); + + return !p->empty (); } template - template - inline void value_traits>:: - assign (value& v, M x) + inline bool value_traits>:: + append (value& v, map&& x) { + map* p; + if (v.null ()) + p = new (&v.data_) map (move (x)); + else { - if (v.type == nullptr) - v.type = &value_traits>::value_type; - v.state_ = value::state_type::empty; + p = &v.as> (); + + if (p->empty ()) + p->swap (x); + else + // Note that this will only move values. Keys (being const) are still + // copied. + // + p->insert (p->end (), + make_move_iterator (x.begin ()), + make_move_iterator (x.end ())); } - v.state_ = (as (v).assign (move (x))).empty () - ? value::state_type::empty - : value::state_type::filled; + return !p->empty (); } - template - template - inline void value_traits>:: - append (value& v, M x) + inline const variable& variable_pool:: + find (string n, const variable_visibility* vv, const build2::value_type* t) { - if (v.null ()) - assign (v, move (x)); - else - v.state_ = (as (v).append (move (x))).empty () - ? value::state_type::empty - : value::state_type::filled; + auto r ( + insert ( + variable { + move (n), + t, + vv != nullptr ? *vv : variable_visibility::normal})); + const variable& v (*r.first); + + // Update type? + // + if (!r.second && t != nullptr && v.type != t) + { + assert (v.type == nullptr); + const_cast (v).type = t; // Not changing the key. + } + + // Change visibility? While this might at first seem like a bad idea, + // it can happen that the variable lookup happens before any values + // were set, in which case the variable will be entered with the + // default visibility. + // + if (!r.second && vv != nullptr && v.visibility != *vv) + { + assert (v.visibility == variable_visibility::normal); // Default. + const_cast (v).visibility = *vv; // Not changing the key. + } + + return v; } // variable_map::iterator_adapter @@ -382,7 +463,7 @@ namespace build2 // First access after being assigned a type? // if (var.type != nullptr && val.type != var.type) - build2::assign (const_cast (val), var.type, var); + typify (const_cast (val), *var.type, var); return r; } @@ -398,7 +479,7 @@ namespace build2 // First access after being assigned a type? // if (var.type != nullptr && val.type != var.type) - build2::assign (const_cast (val), var.type, var); + typify (const_cast (val), *var.type, var); return p; } diff --git a/build2/variable.txx b/build2/variable.txx index 7a0d53a..721e3c0 100644 --- a/build2/variable.txx +++ b/build2/variable.txx @@ -6,57 +6,232 @@ namespace build2 { + // value + // + template + void + default_dtor (value& v) + { + v.as ().~T (); + } + + template + void + default_copy_ctor (value& l, const value& r, bool m) + { + if (m) + new (&l.data_) T (move (const_cast (r).as ())); + else + new (&l.data_) T (r.as ()); + } + + template + void + default_copy_assign (value& l, const value& r, bool m) + { + if (m) + l.as () = move (const_cast (r).as ()); + else + l.as () = r.as (); + } + + template + bool + simple_assign (value& v, names&& ns, const variable& var) + { + size_t n (ns.size ()); + + if (empty ? n <= 1 : n == 1) + { + try + { + return value_traits::assign ( + v, + (n == 0 + ? T () + : value_traits::convert (move (ns.front ()), nullptr))); + } + catch (const invalid_argument&) {} // Fall through. + } + + error << "invalid " << value_traits::value_type.name + << " value '" << ns << "' in variable " << var.name; + throw failed (); + } + + template + bool + simple_append (value& v, names&& ns, const variable& var) + { + size_t n (ns.size ()); + + if (empty ? n <= 1 : n == 1) + { + try + { + return value_traits::append ( + v, + (n == 0 + ? T () + : value_traits::convert (move (ns.front ()), nullptr))); + } + catch (const invalid_argument&) {} // Fall through. + } + + error << "invalid " << value_traits::value_type.name + << " value '" << ns << "' in variable " << var.name; + throw failed (); + } + + template + bool + simple_prepend (value& v, names&& ns, const variable& var) + { + size_t n (ns.size ()); + + if (empty ? n <= 1 : n == 1) + { + try + { + return value_traits::prepend ( + v, + (n == 0 + ? T () + : value_traits::convert (move (ns.front ()), nullptr))); + } + catch (const invalid_argument&) {} // Fall through. + } + + error << "invalid " << value_traits::value_type.name + << " value '" << ns << "' in variable " << var.name; + throw failed (); + } + + template + names_view + simple_reverse (const value& v, names& s) + { + s.emplace_back (value_traits::reverse (v.as ())); + return s; + } + + template + int + simple_compare (const value& l, const value& r) + { + return value_traits::compare (l.as (), r.as ()); + } + // vector value // + template bool - vector_assign (names& v, const variable& var) + vector_append (value& v, names&& ns, const variable& var) { - // Verify each element has valid value of T. Merge pairs. + vector* p (v.null () + ? new (&v.data_) vector () + : &v.as> ()); + + // Convert each element to T while merging pairs. // - for (auto i (v.begin ()); i != v.end (); ) + for (auto i (ns.begin ()); i != ns.end (); ++i) { name& n (*i); + name* r (n.pair ? &*++i : nullptr); - if (n.pair) + try { - name& r (*++i); - - if (!assign (n, r)) - fail << "invalid " << value_traits::value_type.name - << " pair '" << n << "'@'" << r << "'" - << " in variable '" << var.name << "'"; - - i = v.erase (i); + p->push_back (value_traits::convert (move (n), r)); } - else + catch (const invalid_argument&) { - if (!assign (n)) - fail << "invalid " << value_traits::value_type.name - << " element '" << n << "' in variable '" << var.name << "'"; - ++i; + diag_record dr (fail); + + dr << "invalid " << value_traits::value_type.name; + + if (n.pair) + dr << " element pair '" << n << "'@'" << *r << "'"; + else + dr << " element '" << n << "'"; + + dr << " in variable " << var.name; } } - return !v.empty (); + return !p->empty (); } template bool - vector_append (names& v, names a, const variable& var) + vector_assign (value& v, names&& ns, const variable& var) { - // Verify that what we are appending is valid. + if (!v.null ()) + v.as> ().clear (); + + return vector_append (v, move (ns), var); + } + + template + bool + vector_prepend (value& v, names&& ns, const variable& var) + { + // Reduce to append. // - vector_assign (a, var); + vector t; + vector* p; - if (v.empty ()) - v = move (a); + if (v.null ()) + p = new (&v.data_) vector (); else - v.insert (v.end (), - make_move_iterator (a.begin ()), - make_move_iterator (a.end ())); + { + p = &v.as> (); + p->swap (t); + } + + vector_append (v, move (ns), var); + + p->insert (p->end (), + make_move_iterator (t.begin ()), + make_move_iterator (t.end ())); + + return !p->empty (); + } + + template + static names_view + vector_reverse (const value& v, names& s) + { + auto& vv (v.as> ()); + s.reserve (vv.size ()); - return !v.empty (); + for (const T& x: vv) + s.push_back (value_traits::reverse (x)); + + return s; + } + + template + static int + vector_compare (const value& l, const value& r) + { + auto& lv (l.as> ()); + auto& rv (r.as> ()); + + auto li (lv.begin ()), le (lv.end ()); + auto ri (rv.begin ()), re (rv.end ()); + + for (; li != le && ri != re; ++li, ++ri) + if (int r = value_traits::compare (*li, *ri)) + return r; + + if (li == le && ri != re) // l shorter than r. + return -1; + + if (ri == re && li != le) // r shorter than l. + return 1; + + return 0; } template @@ -67,105 +242,127 @@ namespace build2 const value_type value_traits>::value_type { value_traits>::type_name.c_str (), + sizeof (vector), + &default_dtor>, + &default_copy_ctor>, + &default_copy_assign>, &vector_assign, - &vector_append + &vector_append, + &vector_prepend, + &vector_reverse, + nullptr, // No cast (cast data_ directly). + &vector_compare }; // map value // - template - map_value& map_value:: - assign (std::map m) + template + bool + map_append (value& v, names&& ns, const variable& var) { - d->clear (); - for (auto& p: m) - { - d->emplace_back (p.first); // Const, can't move. - d->back ().pair = true; - d->emplace_back (move (p.second)); - } + using std::map; - return *this; - } + map* p (v.null () + ? new (&v.data_) map () + : &v.as> ()); - template - auto map_value:: - find (const K& k) -> iterator - { - // @@ Scan backwards to handle duplicates. + // Verify we have a sequence of pairs and convert each lhs/rhs to K/V. // - for (auto i (d->rbegin ()); i != d->rend (); ++i) - if (as (*++i) == k) - return iterator (--(i.base ())); + for (auto i (ns.begin ()); i != ns.end (); ++i) + { + name& l (*i); - return end (); - } + if (!l.pair) + fail << value_traits>::value_type.name << " key-value " + << "pair expected instead of '" << l << "' " + << "in variable " << var.name; - template - auto map_value:: - find (const K& k) const -> const_iterator - { - // @@ Scan backwards to handle duplicates. - // - for (auto i (d->rbegin ()); i != d->rend (); ++i) - if (as (*++i) == k) - return const_iterator (--(i.base ())); + name& r (*++i); // Got to have the second half of the pair. + + try + { + K k (value_traits::convert (move (l), nullptr)); + + try + { + V v (value_traits::convert (move (r), nullptr)); - return end (); + p->emplace (move (k), move (v)); + } + catch (const invalid_argument&) + { + fail << "invalid " << value_traits::value_type.name + << " element value '" << r << "' in variable " << var.name; + } + } + catch (const invalid_argument&) + { + fail << "invalid " << value_traits::value_type.name + << " element key '" << l << "' in variable " << var.name; + } + } + + return !p->empty (); } template bool - map_assign (names& v, const variable& var) + map_assign (value& v, names&& ns, const variable& var) { - // Verify we have a sequence of pairs and each lhs/rhs is a valid - // value of K/V. - // - for (auto i (v.begin ()); i != v.end (); ++i) - { - if (!i->pair) - fail << value_traits>::value_type.name << " key-value " - << "pair expected instead of '" << *i << "' " - << "in variable '" << var.name << "'"; + using std::map; - if (!assign (*i)) - fail << "invalid " << value_traits::value_type.name << " key " - << "'" << *i << "' in variable '" << var.name << "'"; + if (!v.null ()) + v.as> ().clear (); - ++i; // Got to have the second half of the pair. + return map_append (v, move (ns), var); + } - if (!assign (*i)) - fail << "invalid " << value_traits::value_type.name << " value " - << "'" << *i << "' in variable '" << var.name << "'"; - } + template + static names_view + map_reverse (const value& v, names& s) + { + using std::map; - //@@ When doing sorting, note that assign() can convert the - // value. + auto& vm (v.as> ()); + s.reserve (2 * vm.size ()); - //@@ Is sorting really the right trade-off (i.e., insertion - // vs search)? Perhaps linear search is ok? + for (const auto& p: vm) + { + s.push_back (value_traits::reverse (p.first)); + s.back ().pair = true; + s.push_back (value_traits::reverse (p.second)); + } - return !v.empty (); + return s; } template - bool - map_append (names& v, names a, const variable& var) + static int + map_compare (const value& l, const value& r) { - //@@ Not weeding out duplicates. + using std::map; - // Verify that what we are appending is valid. - // - map_assign (a, var); + auto& lm (l.as> ()); + auto& rm (r.as> ()); - if (v.empty ()) - v = move (a); - else - v.insert (v.end (), - make_move_iterator (a.begin ()), - make_move_iterator (a.end ())); + auto li (lm.begin ()), le (lm.end ()); + auto ri (rm.begin ()), re (rm.end ()); + + for (; li != le && ri != re; ++li, ++ri) + { + int r; + if ((r = value_traits::compare (li->first, ri->first)) != 0 || + (r = value_traits::compare (li->second, ri->second)) != 0) + return r; + } + + if (li == le && ri != re) // l shorter than r. + return -1; + + if (ri == re && li != le) // r shorter than l. + return 1; - return !v.empty (); + return 0; } template @@ -176,8 +373,16 @@ namespace build2 template const value_type value_traits>::value_type { - value_traits>::type_name.c_str (), + value_traits>::type_name.c_str (), + sizeof (map), + &default_dtor>, + &default_copy_ctor>, + &default_copy_assign>, &map_assign, - &map_append + &map_append, + &map_append, // Prepend is the same as append. + &map_reverse, + nullptr, // No cast (cast data_ directly). + &map_compare }; } -- cgit v1.1