diff options
39 files changed, 1990 insertions, 957 deletions
diff --git a/build/b.cxx b/build/b.cxx index 11280e1..412fb12 100644 --- a/build/b.cxx +++ b/build/b.cxx @@ -31,6 +31,7 @@ #include <build/diagnostics> #include <build/context> #include <build/utility> +#include <build/variable> #include <build/token> #include <build/lexer> @@ -408,14 +409,14 @@ main (int argc, char* argv[]) // See if the bootstrap process set/changed src_root. // { - auto v (rs.assign ("src_root")); + value& v (rs.assign ("src_root")); if (v) { // If we also have src_root specified by the user, make // sure they match. // - const dir_path& p (v.as<const dir_path&> ()); + const dir_path& p (as<dir_path> (v)); if (src_root.empty ()) src_root = p; @@ -448,7 +449,7 @@ main (int argc, char* argv[]) v = src_root; } - rs.src_path_ = &v.as<const dir_path&> (); + rs.src_path_ = &as<dir_path> (v); } // At this stage we should have both roots and out_base figured @@ -468,9 +469,9 @@ main (int argc, char* argv[]) // Why don't we support it? Because things are already complex // enough here. // - if (auto v = rs.vars["subprojects"]) + if (auto l = rs.vars["subprojects"]) { - for (const name& n: v.as<const list_value&> ()) + for (const name& n: *l) { if (n.pair != '\0') continue; // Skip project names. diff --git a/build/bin/module.cxx b/build/bin/module.cxx index e03c658..4c5cca5 100644 --- a/build/bin/module.cxx +++ b/build/bin/module.cxx @@ -25,16 +25,16 @@ namespace build // Default config.bin.*.lib values. // - static const list_value exe_lib (names {name ("shared"), name ("static")}); - static const list_value liba_lib ("static"); - static const list_value libso_lib ("shared"); + static const strings exe_lib {"shared", "static"}; + static const strings liba_lib {"static"}; + static const strings libso_lib {"shared"}; extern "C" void bin_init (scope& r, scope& b, const location&, std::unique_ptr<module>&, - bool) + bool first) { tracer trace ("bin::init"); level4 ([&]{trace << "for " << b.path ();}); @@ -75,6 +75,21 @@ namespace build rs.insert<lib> (install_id, "bin.lib", lib_); } + // Enter module variables. + // + if (first) + { + variable_pool.find ("config.bin.lib", string_type); + variable_pool.find ("config.bin.exe.lib", strings_type); + variable_pool.find ("config.bin.liba.lib", strings_type); + variable_pool.find ("config.bin.libso.lib", strings_type); + + variable_pool.find ("bin.lib", string_type); + variable_pool.find ("bin.exe.lib", strings_type); + variable_pool.find ("bin.liba.lib", strings_type); + variable_pool.find ("bin.libso.lib", strings_type); + } + // Configure. // using config::required; @@ -92,7 +107,7 @@ namespace build // config.bin.lib // { - auto v (b.assign ("bin.lib")); + value& v (b.assign ("bin.lib")); if (!v) v = required (r, "config.bin.lib", "both").first; } @@ -100,7 +115,7 @@ namespace build // config.bin.exe.lib // { - auto v (b.assign ("bin.exe.lib")); + value& v (b.assign ("bin.exe.lib")); if (!v) v = required (r, "config.bin.exe.lib", exe_lib).first; } @@ -108,7 +123,7 @@ namespace build // config.bin.liba.lib // { - auto v (b.assign ("bin.liba.lib")); + value& v (b.assign ("bin.liba.lib")); if (!v) v = required (r, "config.bin.liba.lib", liba_lib).first; } @@ -116,14 +131,14 @@ namespace build // config.bin.libso.lib // { - auto v (b.assign ("bin.libso.lib")); + value& v (b.assign ("bin.libso.lib")); if (!v) v = required (r, "config.bin.libso.lib", libso_lib).first; } // Configure "installability" of our target types. // - install::path<exe> (b, "bin"); // Install into install.bin. + install::path<exe> (b, dir_path ("bin")); // Install into install.bin. // Should shared libraries have executable bit? That depends on // who you ask. In Debian, for example, it should not unless, it @@ -143,9 +158,9 @@ namespace build // // Everyone is happy then? // - install::path<libso> (b, "lib"); // Install into install.lib. + install::path<libso> (b, dir_path ("lib")); // Install into install.lib. - install::path<liba> (b, "lib"); // Install into install.lib. + install::path<liba> (b, dir_path ("lib")); // Install into install.lib. install::mode<liba> (b, "644"); } } diff --git a/build/bin/rule.cxx b/build/bin/rule.cxx index a09bffd..21db183 100644 --- a/build/bin/rule.cxx +++ b/build/bin/rule.cxx @@ -46,7 +46,7 @@ namespace build // 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 (t["bin.lib"].as<const string&> ()); + const string& type (as<string> (*t["bin.lib"])); bool ar (type == "static" || type == "both"); bool so (type == "shared" || type == "both"); @@ -121,7 +121,7 @@ namespace build // prerequisite vs prerequisite_target. // // - const string& type (t["bin.lib"].as<const string&> ()); + const string& type (as<string> (*t["bin.lib"])); bool ar (type == "static" || type == "both"); bool so (type == "shared" || type == "both"); diff --git a/build/buildfile b/build/buildfile index 7522dc2..ce917f6 100644 --- a/build/buildfile +++ b/build/buildfile @@ -8,13 +8,13 @@ config = config/{operation module utility} bin = bin/{target rule module} cxx = cxx/{target compile link install module utility} cli = cli/{target rule module} -test = test/{operation rule module} -install = install/{operation rule module} +test1 = test/{operation rule module} +install1 = install/{operation rule module} exe{b}: cxx{b algorithm name operation spec scope variable target \ prerequisite rule file module context search diagnostics token \ lexer parser path-io utility dump options $config $bin $cxx $cli \ - $test $install} $libs + $test1 $install1} $libs #@@ TODO # diff --git a/build/cli/module.cxx b/build/cli/module.cxx index f112cd4..7702589 100644 --- a/build/cli/module.cxx +++ b/build/cli/module.cxx @@ -78,6 +78,16 @@ namespace build rs.insert<cxx::ixx> (clean_id, "cli.compile", compile_); } + // Enter module variables. + // + if (first) + { + variable_pool.find ("config.cli", string_type); //@@ VAR type + + variable_pool.find ("config.cli.options", strings_type); + variable_pool.find ("cli.options", strings_type); + } + // Configure. // @@ -85,13 +95,13 @@ namespace build // if (first) { - auto r (config::required (root, "config.cli", "cli")); + auto p (config::required (root, "config.cli", "cli")); // If we actually set a new value, test it by trying to execute. // - if (r.second) + if (p.second) { - const string& cli (r.first); + const string& cli (as<string> (p.first)); const char* args[] = {cli.c_str (), "--version", nullptr}; if (verb) @@ -142,8 +152,8 @@ namespace build // cli.* variables. See the cxx module for more information on // this merging semantics and some of its tricky aspects. // - if (auto* v = config::optional<list_value> (root, "config.cli.options")) - base.assign ("cli.options") += *v; + if (const value& v = config::optional (root, "config.cli.options")) + base.assign ("cli.options") += as<strings> (v); } } } diff --git a/build/cli/rule.cxx b/build/cli/rule.cxx index 247b5b4..a892d7c 100644 --- a/build/cli/rule.cxx +++ b/build/cli/rule.cxx @@ -223,7 +223,7 @@ namespace build path rels (relative (s->path ())); scope& rs (t.root_scope ()); - const string& cli (rs["config.cli"].as<const string&> ()); + const string& cli (as<string> (*rs["config.cli"])); cstrings args {cli.c_str ()}; diff --git a/build/config/operation.cxx b/build/config/operation.cxx index a602325..913ae29 100644 --- a/build/config/operation.cxx +++ b/build/config/operation.cxx @@ -81,9 +81,9 @@ namespace build << "# feel free to edit." << endl << "#" << endl; - if (auto v = root.vars["amalgamation"]) + if (auto l = root.vars["amalgamation"]) { - const dir_path& d (v.as<const dir_path&> ()); + const dir_path& d (as<dir_path> (*l)); ofs << "# Base configuration inherited from " << d << endl << "#" << endl; @@ -97,27 +97,23 @@ namespace build ++p.first) { const variable& var (p.first->first); - const value_ptr& pval (p.first->second); + const value& val (p.first->second); // Warn the user if the value that we are saving differs // from the one they specified on the command line. // - if (auto gval = (*global_scope)[var]) + auto l ((*global_scope)[var]); + if (l.defined () && *l != val) { - if (pval == nullptr || !pval->compare (gval.as<const value&> ())) - warn << "variable " << var.name << " configured value " - << "differs from command line value" << - info << "reconfigure the project to use command line value"; + warn << "variable " << var.name << " configured value " + << "differs from command line value" << + info << "reconfigure the project to use command line value"; } - if (pval != nullptr) + if (val) { - //@@ TODO: assuming list - // - const list_value& lv (dynamic_cast<const list_value&> (*pval)); - - ofs << var.name << " = " << lv << endl; - //text << var.name << " = " << lv; + ofs << var.name << " = " << val.data_ << endl; + //text << var.name << " = " << val.data_; } else { @@ -171,14 +167,12 @@ namespace build // Configure subprojects that have been loaded. // - if (auto v = root.vars["subprojects"]) + if (auto l = root.vars["subprojects"]) { - for (const name& n: v.as<const list_value&> ()) + for (auto p: as<subprojects> (*l)) { - if (n.pair != '\0') - continue; // Skip project names. - - dir_path out_nroot (out_root / n.dir); + const dir_path& pd (p.second); + dir_path out_nroot (out_root / pd); scope& nroot (scopes.find (out_nroot)); // @@ Strictly speaking we need to check whether the config @@ -271,30 +265,27 @@ namespace build // Disfigure subprojects. Since we don't load buildfiles during // disfigure, we do it for all known subprojects. // - if (auto v = root.vars["subprojects"]) + if (auto l = root.vars["subprojects"]) { - for (const name& n: v.as<const list_value&> ()) + for (auto p: as<subprojects> (*l)) { - if (n.pair != '\0') - continue; // Skip project names. + const dir_path& pd (p.second); // Create and bootstrap subproject's root scope. // - dir_path out_nroot (out_root / n.dir); + dir_path out_nroot (out_root / pd); // The same logic for src_root as in create_bootstrap_inner(). // scope& nroot (create_root (out_nroot, dir_path ())); bootstrap_out (nroot); - auto val (nroot.assign ("src_root")); + value& val (nroot.assign ("src_root")); if (!val) - val = is_src_root (out_nroot) - ? out_nroot - : (src_root / n.dir); + val = is_src_root (out_nroot) ? out_nroot : (src_root / pd); - nroot.src_path_ = &val.as<const dir_path&> (); + nroot.src_path_ = &as<dir_path> (val); bootstrap_src (nroot); @@ -304,9 +295,9 @@ namespace build // which means there could be empty parent directories left // behind. Clean them up. // - if (!n.dir.simple () && out_root != src_root) + if (!pd.simple () && out_root != src_root) { - for (dir_path d (n.dir.directory ()); + for (dir_path d (pd.directory ()); !d.empty (); d = d.directory ()) { diff --git a/build/config/utility b/build/config/utility index ef3ceed..82f71fe 100644 --- a/build/config/utility +++ b/build/config/utility @@ -6,15 +6,16 @@ #define BUILD_CONFIG_UTILITY #include <string> -#include <utility> // pair +#include <utility> // pair +#include <functional> // reference_wrapper #include <build/types> +#include <build/variable> #include <build/diagnostics> namespace build { class scope; - class list_value; namespace config { @@ -24,35 +25,36 @@ namespace build // whether the variable has actually been set. // template <typename T> - std::pair<const T&, bool> - required (scope& root, const char* name, const T& default_value); + std::pair<std::reference_wrapper<const value>, bool> + required (scope& root, const variable&, const T& default_value); template <typename T> - inline std::pair<const T&, bool> + inline std::pair<std::reference_wrapper<const value>, bool> required (scope& root, const std::string& name, const T& default_value) { - return required<T> (root, name.c_str (), default_value); + return required (root, variable_pool.find (name), default_value); } - std::pair<const std::string&, bool> - required (scope& root, const char* name, const char* default_value); + inline std::pair<std::reference_wrapper<const value>, bool> + required (scope& root, const std::string& name, const char* default_value) + { + return required (root, name, std::string (default_value)); + } // Set, if necessary, an optional config.* variable. In particular, // an unspecified variable is set to NULL which is used to distinguish // between the "configured as unspecified" and "not yet configured" // cases. // - // Return the pointer to the value, which can be NULL. + // Return the value, which can be NULL. // - template <typename T> - const T* - optional (scope& root, const char* name); + const value& + optional (scope& root, const variable& var); - template <typename T> - inline const T* + inline const value& optional (scope& root, const std::string& name) { - return optional<T> (root, name.c_str ()); + return optional (root, variable_pool.find (name)); } // Check whether there are any variables specified from the @@ -60,8 +62,8 @@ namespace build // are any, say, config.install.* values. If there are none, // then we can assume this functionality is not (yet) used // and omit writing a whole bunch of NULL config.install.* - // values to config.build. We call it omitted/delayed - // configuration. + // values to the config.build file . We call it omitted/ + // delayed configuration. // bool specified (scope& root, const std::string& ns); @@ -70,19 +72,20 @@ namespace build // // Add all the values from a variable to the C-string list. T is - // either target or scope. + // either target or scope. The variable is expected to be of type + // strings. // template <typename T> void append_options (cstrings& args, T& s, const char* var); - // As above but from the list value directly. Variable name is for - // diagnostics. + // As above but from the strings value directly. // void - append_options (cstrings& args, const list_value&, const char* var); + append_options (cstrings& args, const const_strings_value&); - // Check if a specified option is present. T is either target or scope. + // Check if a specified option is present in the variable value. + // T is either target or scope. // template <typename T> bool @@ -91,5 +94,6 @@ namespace build } #include <build/config/utility.txx> +#include <build/config/utility.ixx> #endif // BUILD_CONFIG_UTILITY diff --git a/build/config/utility.cxx b/build/config/utility.cxx index e2afc80..ce723fe 100644 --- a/build/config/utility.cxx +++ b/build/config/utility.cxx @@ -10,32 +10,14 @@ namespace build { namespace config { - // The same as the template except it is a bit more efficient - // when it comes to not creating the default value string - // unnecessarily. - // - pair<const string&, bool> - required (scope& root, const char* name, const char* def_value) + const value& + optional (scope& root, const variable& var) { - string r; - const variable& var (variable_pool.find (name)); + auto l (root[var]); - if (auto v = root[var]) - { - const string& s (v.as<const string&> ()); - - if (!v.belongs (*global_scope)) // A value from (some) config.build. - return pair<const string&, bool> (s, false); - - r = s; - } - else - r = def_value; - - auto v (root.assign (var)); - v = move (r); - - return pair<const string&, bool> (v.as<const string&> (), true); + return l.defined () + ? l.belongs (*global_scope) ? (root.assign (var) = *l) : *l + : root.assign (var); // NULL } bool @@ -54,17 +36,14 @@ namespace build } void - append_options (cstrings& args, const list_value& lv, const char* var) + append_options (cstrings& args, const const_strings_value& sv) { - for (const name& n: lv) + if (!sv.empty ()) { - if (n.simple ()) - args.push_back (n.value.c_str ()); - else if (n.directory ()) - args.push_back (n.dir.string ().c_str ()); - else - fail << "expected option instead of " << n << - info << "in variable " << var; + args.reserve (args.size () + sv.size ()); + + for (const string& s: sv) + args.push_back (s.c_str ()); } } } diff --git a/build/config/utility.ixx b/build/config/utility.ixx new file mode 100644 index 0000000..4e32119 --- /dev/null +++ b/build/config/utility.ixx @@ -0,0 +1,17 @@ +// file : build/config/utility.ixx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +namespace build +{ + namespace config + { + template <typename T> + inline void + append_options (cstrings& args, T& s, const char* var) + { + if (auto l = s[var]) + append_options (args, as<strings> (*l)); + } + } +} diff --git a/build/config/utility.txx b/build/config/utility.txx index cffdecf..943d308 100644 --- a/build/config/utility.txx +++ b/build/config/utility.txx @@ -2,79 +2,37 @@ // copyright : Copyright (c) 2014-2015 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include <utility> // move() - #include <build/scope> -#include <build/variable> namespace build { namespace config { template <typename T> - std::pair<const T&, bool> - required (scope& root, const char* name, const T& def_value) - { - T r; - const variable& var (variable_pool.find (name)); - - if (auto v = root[var]) - { - const T& s (v.as<const T&> ()); - - if (!v.belongs (*global_scope)) // A value from (some) config.build. - return std::pair<const T&, bool> (s, false); - - r = s; - } - else - r = def_value; - - auto v (root.assign (var)); - v = std::move (r); - - return std::pair<const T&, bool> (v.as<const T&> (), true); - } - - template <typename T> - const T* - optional (scope& root, const char* name) + std::pair<std::reference_wrapper<const value>, bool> + required (scope& root, const variable& var, const T& def_value) { - const T* r (nullptr); - const variable& var (variable_pool.find (name)); + using result = std::pair<std::reference_wrapper<const value>, bool>; - auto v (root[var]); - - if (v.defined ()) + if (auto l = root[var]) { - if (v.belongs (*global_scope)) - root.assign (var) = v; - - r = v.null () ? nullptr : &v.as<const T&> (); + return l.belongs (*global_scope) + ? result (root.assign (var) = *l, true) + : result (*l, false); } else - root.assign (var) = nullptr; - - return r; - } - - template <typename T> - void - append_options (cstrings& args, T& s, const char* var) - { - if (auto val = s[var]) - append_options (args, val.template as<const list_value&> (), var); + return result (root.assign (var) = def_value, true); } template <typename T> bool find_option (const char* option, T& s, const char* var) { - if (auto val = s[var]) + if (auto l = s[var]) { - for (const name& n: val.template as<const list_value&> ()) + for (const std::string& s: as<strings> (*l)) { - if (n.simple () && n.value == option) + if (s == option) return true; } } diff --git a/build/context.cxx b/build/context.cxx index 259c6ea..530ac7a 100644 --- a/build/context.cxx +++ b/build/context.cxx @@ -57,7 +57,20 @@ namespace build // Enter builtin variables. // - variable_pool.insert (variable ("subprojects", '=')); + variable_pool.find ("work", dir_path_type); + variable_pool.find ("home", dir_path_type); + + variable_pool.find ("src_root", dir_path_type); + variable_pool.find ("out_root", dir_path_type); + variable_pool.find ("src_base", dir_path_type); + variable_pool.find ("out_base", dir_path_type); + + variable_pool.find ("project", string_type); + variable_pool.find ("amalgamation", dir_path_type); + + // Shouldn't be typed since the value requires pre-processing. + // + variable_pool.find ("subprojects", nullptr, '='); // Create global scope. For Win32 we use the empty path since there // is no "real" root path. On POSIX, however, this is a real path. diff --git a/build/cxx/compile.cxx b/build/cxx/compile.cxx index 8dcad1e..65806ed 100644 --- a/build/cxx/compile.cxx +++ b/build/cxx/compile.cxx @@ -166,23 +166,23 @@ namespace build { ext_map m; - if (auto val = r["h.ext"]) - m[&extension_pool.find (val.as<const string&> ())] = &h::static_type; + if (auto l = r["h.ext"]) + m[&extension_pool.find (as<string> (*l))] = &h::static_type; - if (auto val = r["c.ext"]) - m[&extension_pool.find (val.as<const string&> ())] = &c::static_type; + if (auto l = r["c.ext"]) + m[&extension_pool.find (as<string> (*l))] = &c::static_type; - if (auto val = r["hxx.ext"]) - m[&extension_pool.find (val.as<const string&> ())] = &hxx::static_type; + if (auto l = r["hxx.ext"]) + m[&extension_pool.find (as<string> (*l))] = &hxx::static_type; - if (auto val = r["ixx.ext"]) - m[&extension_pool.find (val.as<const string&> ())] = &ixx::static_type; + if (auto l = r["ixx.ext"]) + m[&extension_pool.find (as<string> (*l))] = &ixx::static_type; - if (auto val = r["txx.ext"]) - m[&extension_pool.find (val.as<const string&> ())] = &txx::static_type; + if (auto l = r["txx.ext"]) + m[&extension_pool.find (as<string> (*l))] = &txx::static_type; - if (auto val = r["cxx.ext"]) - m[&extension_pool.find (val.as<const string&> ())] = &cxx::static_type; + if (auto l = r["cxx.ext"]) + m[&extension_pool.find (as<string> (*l))] = &cxx::static_type; return m; } @@ -215,26 +215,24 @@ namespace build const dir_path& out_base (t.dir); const dir_path& out_root (rs->path ()); - if (auto val = t[var]) + if (auto l = t[var]) { - const list_value& l (val.template as<const list_value&> ()); + const auto& v (as<strings> (*l)); - // Assume the names have already been vetted by append_options(). - // - for (auto i (l.begin ()), e (l.end ()); i != e; ++i) + for (auto i (v.begin ()), e (v.end ()); i != e; ++i) { - // -I can either be in the -Ifoo or -I foo form. + // -I can either be in the "-Ifoo" or "-I foo" form. // dir_path d; - if (i->value == "-I") + if (*i == "-I") { if (++i == e) break; // Let the compiler complain. - d = i->simple () ? dir_path (i->value) : i->dir; + d = dir_path (*i); } - else if (i->value.compare (0, 2, "-I") == 0) - d = dir_path (i->value, 2, string::npos); + else if (i->compare (0, 2, "-I") == 0) + d = dir_path (*i, 2, string::npos); else continue; @@ -368,7 +366,7 @@ namespace build tracer trace ("cxx::compile::inject_prerequisites"); scope& rs (t.root_scope ()); - const string& cxx (rs["config.cxx"].as<const string&> ()); + const string& cxx (as<string> (*rs["config.cxx"])); cstrings args {cxx.c_str ()}; @@ -715,7 +713,7 @@ namespace build path rels (relative (s->path ())); scope& rs (t.root_scope ()); - const string& cxx (rs["config.cxx"].as<const string&> ()); + const string& cxx (as<string> (*rs["config.cxx"])); cstrings args {cxx.c_str ()}; diff --git a/build/cxx/link.cxx b/build/cxx/link.cxx index 97c9696..1b900c2 100644 --- a/build/cxx/link.cxx +++ b/build/cxx/link.cxx @@ -59,10 +59,10 @@ namespace build case type::so: var = "bin.libso.lib"; break; } - const list_value& lv (t[var].as<const list_value&> ()); - return lv[0].value == "shared" - ? lv.size () > 1 && lv[1].value == "static" ? order::so_a : order::so - : lv.size () > 1 && lv[1].value == "shared" ? order::a_so : order::a; + const auto& v (as<strings> (*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; } link::search_paths link:: @@ -73,32 +73,24 @@ namespace build // Extract user-supplied search paths (i.e., -L). // - if (auto val = bs["cxx.loptions"]) + if (auto l = bs["cxx.loptions"]) { - const list_value& l (val.as<const list_value&> ()); + const auto& v (as<strings> (*l)); - for (auto i (l.begin ()), e (l.end ()); i != e; ++i) + for (auto i (v.begin ()), e (v.end ()); i != e; ++i) { - if (!i->simple ()) - continue; - - // -L can either be in the -Lfoo or -L foo form. + // -L can either be in the "-Lfoo" or "-L foo" form. // dir_path d; - if (i->value == "-L") + if (*i == "-L") { if (++i == e) break; // Let the compiler complain. - if (i->simple ()) - d = dir_path (i->value); - else if (i->directory ()) - d = i->dir; - else - break; // Let the compiler complain. + d = dir_path (*i); } - else if (i->value.compare (0, 2, "-L") == 0) - d = dir_path (i->value, 2, string::npos); + else if (i->compare (0, 2, "-L") == 0) + d = dir_path (*i, 2, string::npos); else continue; @@ -114,7 +106,7 @@ namespace build cstrings args; string std_storage; - args.push_back (rs["config.cxx"].as<const string&> ().c_str ()); + args.push_back (as<string> (*rs["config.cxx"]).c_str ()); append_options (args, bs, "cxx.coptions"); append_std (args, bs, std_storage); append_options (args, bs, "cxx.loptions"); @@ -550,7 +542,7 @@ namespace build // Determine the library type to link. // bool lso (true); - const string& at ((*l)["bin.lib"].as<const string&> ()); + const string& at (as<string> (*(*l)["bin.lib"])); if (!lo) lo = link_order (t); @@ -776,10 +768,8 @@ namespace build } else { - args.push_back (rs["config.cxx"].as<const string&> ().c_str ()); - + args.push_back (as<string> (*rs["config.cxx"]).c_str ()); append_options (args, t, "cxx.coptions"); - append_std (args, t, storage1); if (so) diff --git a/build/cxx/module.cxx b/build/cxx/module.cxx index 042fb53..7f200cd 100644 --- a/build/cxx/module.cxx +++ b/build/cxx/module.cxx @@ -91,6 +91,43 @@ namespace build rs.insert<libso> (install_id, "cxx.install", install::instance); } + // Enter module variables. + // + // @@ Probably should only be done on load; make sure reset() unloads + // modules. + // + // @@ Should probably cache the variable pointers so we don't have + // to keep looking them up. + // + if (first) + { + variable_pool.find ("config.cxx", string_type); //@@ VAR type + + variable_pool.find ("config.cxx.poptions", strings_type); + variable_pool.find ("config.cxx.coptions", strings_type); + variable_pool.find ("config.cxx.loptions", strings_type); + variable_pool.find ("config.cxx.libs", strings_type); + + variable_pool.find ("cxx.poptions", strings_type); + variable_pool.find ("cxx.coptions", strings_type); + variable_pool.find ("cxx.loptions", strings_type); + variable_pool.find ("cxx.libs", strings_type); + + variable_pool.find ("cxx.export.poptions", strings_type); + variable_pool.find ("cxx.export.coptions", strings_type); + variable_pool.find ("cxx.export.loptions", strings_type); + variable_pool.find ("cxx.export.libs", strings_type); + + variable_pool.find ("cxx.std", string_type); + + variable_pool.find ("h.ext", string_type); + variable_pool.find ("c.ext", string_type); + variable_pool.find ("hxx.ext", string_type); + variable_pool.find ("ixx.ext", string_type); + variable_pool.find ("txx.ext", string_type); + variable_pool.find ("cxx.ext", string_type); + } + // Configure. // @@ -104,7 +141,7 @@ namespace build // if (p.second) { - const string& cxx (p.first); + const string& cxx (as<string> (p.first)); const char* args[] = {cxx.c_str (), "-dumpversion", nullptr}; if (verb) @@ -157,27 +194,27 @@ namespace build // using cxx // cxx.coptions += <overriding options> # Note: '+='. // - if (auto* v = config::optional<list_value> (r, "config.cxx.poptions")) - b.assign ("cxx.poptions") += *v; + if (const value& v = config::optional (r, "config.cxx.poptions")) + b.assign ("cxx.poptions") += as<strings> (v); - if (auto* v = config::optional<list_value> (r, "config.cxx.coptions")) - b.assign ("cxx.coptions") += *v; + if (const value& v = config::optional (r, "config.cxx.coptions")) + b.assign ("cxx.coptions") += as<strings> (v); - if (auto* v = config::optional<list_value> (r, "config.cxx.loptions")) - b.assign ("cxx.loptions") += *v; + if (const value& v = config::optional (r, "config.cxx.loptions")) + b.assign ("cxx.loptions") += as<strings> (v); - if (auto* v = config::optional<list_value> (r, "config.cxx.libs")) - b.assign ("cxx.libs") += *v; + if (const value& v = config::optional (r, "config.cxx.libs")) + b.assign ("cxx.libs") += as<strings> (v); // Configure "installability" of our target types. // { using build::install::path; - path<hxx> (b, "include"); // Install into install.include. - path<ixx> (b, "include"); - path<txx> (b, "include"); - path<h> (b, "include"); + path<hxx> (b, dir_path ("include")); // Install into install.include. + path<ixx> (b, dir_path ("include")); + path<txx> (b, dir_path ("include")); + path<h> (b, dir_path ("include")); } } } diff --git a/build/cxx/utility.txx b/build/cxx/utility.txx index 7d9d686..561a54e 100644 --- a/build/cxx/utility.txx +++ b/build/cxx/utility.txx @@ -12,9 +12,9 @@ namespace build void append_std (cstrings& args, T& t, std::string& s) { - if (auto val = t["cxx.std"]) + if (auto l = t["cxx.std"]) { - const std::string& v (val.template as<const string&> ()); + const std::string& v (as<string> (*l)); // Translate 11 to 0x and 14 to 1y for compatibility with // older versions of the compiler. diff --git a/build/dump.cxx b/build/dump.cxx index e426f0b..f3b8c4b 100644 --- a/build/dump.cxx +++ b/build/dump.cxx @@ -19,18 +19,14 @@ using namespace std; namespace build { static void - dump_variable (ostream& os, const variable& var, const value_ptr& val) + dump_variable (ostream& os, const variable& var, const value& val) { os << var.name << " = "; - if (val == nullptr) + if (val.null ()) os << "[null]"; else - { - //@@ TODO: assuming it is a list. - // - os << dynamic_cast<list_value&> (*val); - } + os << val.data_; } static void @@ -5,6 +5,9 @@ #ifndef BUILD_FILE #define BUILD_FILE +#include <map> +#include <string> + #include <build/types> #include <build/variable> // list_value @@ -15,6 +18,8 @@ namespace build class location; class prerequisite_key; + using subprojects = std::map<std::string, dir_path>; + extern const dir_path build_dir; // build extern const dir_path bootstrap_dir; // build/bootstrap @@ -114,7 +119,7 @@ namespace build // there is a package foo available in repository bar. Wanna // download and use it?" // - list_value + names import (scope& base, name, const location&); target& diff --git a/build/file.cxx b/build/file.cxx index e5deecf..b303740 100644 --- a/build/file.cxx +++ b/build/file.cxx @@ -4,7 +4,6 @@ #include <build/file> -#include <map> #include <fstream> #include <utility> // move() @@ -135,13 +134,13 @@ namespace build // consistent. // { - auto v (rs.assign ("out_root")); + value& v (rs.assign ("out_root")); if (!v) v = out_root; else { - const dir_path& p (v.as<const dir_path&> ()); + const dir_path& p (as<dir_path> (v)); if (p != out_root) fail << "new out_root " << out_root << " does not match " @@ -151,13 +150,13 @@ namespace build if (!src_root.empty ()) { - auto v (rs.assign ("src_root")); + value& v (rs.assign ("src_root")); if (!v) v = src_root; else { - const dir_path& p (v.as<const dir_path&> ()); + const dir_path& p (as<dir_path> (v)); if (p != src_root) fail << "new src_root " << src_root << " does not match " @@ -188,7 +187,7 @@ namespace build // expected to be the first non-comment line and not to rely on // any variable expansion other than those from the global scope. // - static value_ptr + static value extract_variable (const path& bf, const char* var) { ifstream ifs (bf.string ()); @@ -201,34 +200,32 @@ namespace build { path rbf (diag_relative (bf)); - lexer l (ifs, rbf.string ()); - token t (l.next ()); + lexer lex (ifs, rbf.string ()); + token t (lex.next ()); token_type tt; if (t.type () != token_type::name || t.name () != var || - ((tt = l.next ().type ()) != token_type::equal && + ((tt = lex.next ().type ()) != token_type::equal && tt != token_type::plus_equal)) fail << "variable '" << var << "' expected as first line in " << rbf; parser p; temp_scope tmp (*global_scope); - p.parse_variable (l, tmp, t.name (), tt); + p.parse_variable (lex, tmp, t.name (), tt); - auto val (tmp.vars[var]); - assert (val.defined ()); - value_ptr& vp (val); - return move (vp); // Steal the value, the scope is going away. + auto l (tmp.vars[var]); + assert (l.defined ()); + value& v (*l); + return move (v); // Steal the value, the scope is going away. } catch (const std::ios_base::failure&) { fail << "failed to read from " << bf; } - return nullptr; + return value (); // Never reaches. } - using subprojects = map<string, dir_path>; - // Extract the project name from bootstrap.build. // static string @@ -243,7 +240,7 @@ namespace build // have to discover its src_root. // const dir_path* src_root; - value_ptr src_root_vp; // Need it to live until the end. + value src_root_v; // Need it to live until the end. if (src_hint != nullptr ? *src_hint : is_src_root (out_root)) src_root = &out_root; @@ -255,9 +252,8 @@ namespace build src_root = &fallback_src_root; else { - src_root_vp = extract_variable (f, "src_root"); - value_proxy v (&src_root_vp, nullptr); // Read-only. - src_root = &v.as<const dir_path&> (); + src_root_v = extract_variable (f, "src_root"); + src_root = &as<dir_path> (src_root_v); level4 ([&]{trace << "extracted src_root " << *src_root << " for " << out_root;}); } @@ -265,9 +261,8 @@ namespace build string name; { - value_ptr vp (extract_variable (*src_root / bootstrap_file, "project")); - value_proxy v (&vp, nullptr); // Read-only. - name = move (v.as<string&> ()); + value v (extract_variable (*src_root / bootstrap_file, "project")); + name = move (as<string> (v)); } level4 ([&]{trace << "extracted project name " << name << " for " @@ -367,11 +362,11 @@ namespace build // amalgamated. // { - auto rp (root.vars.assign("amalgamation")); // Set NULL by default. - auto& val (rp.first); + auto rp (root.vars.assign ("amalgamation")); // Set NULL by default. + value& v (rp.first); - if (!val.null () && val.empty ()) // Convert empty to NULL. - val = nullptr; + if (v && v.empty ()) // Convert empty to NULL. + v = nullptr; if (scope* aroot = root.parent_scope ()->root_scope ()) { @@ -383,14 +378,14 @@ namespace build // if (!rp.second) { - if (val.null ()) + if (!v) { fail << out_root << " cannot be amalgamated" << info << "amalgamated by " << ad; } else { - const dir_path& vd (val.as<const dir_path&> ()); + const dir_path& vd (as<dir_path> (v)); if (vd != rd) { @@ -405,7 +400,7 @@ namespace build // Otherwise, use the outer root as our amalgamation. // level4 ([&]{trace << out_root << " amalgamated as " << rd;}); - val = move (rd); + v = move (rd); } } else if (rp.second) @@ -421,7 +416,7 @@ namespace build { dir_path rd (ad.relative (out_root)); level4 ([&]{trace << out_root << " amalgamated as " << rd;}); - val = move (rd); + v = move (rd); } } } @@ -439,8 +434,9 @@ namespace build // the NULL value indicates that we found no subprojects. // { - auto rp (root.vars.assign("subprojects")); // Set NULL by default. - auto& val (rp.first); + const variable& var (variable_pool.find ("subprojects")); + auto rp (root.vars.assign(var)); // Set NULL by default. + value& v (rp.first); if (rp.second) { @@ -459,34 +455,21 @@ namespace build if (out_root != src_root) find_subprojects (sps, src_root, src_root, false); - // Transform our map to list_value. - // - if (!sps.empty ()) - { - list_value_ptr vp (new list_value); - for (auto& p: sps) - { - vp->emplace_back (p.first); - vp->back ().pair = '='; - vp->emplace_back (move (p.second)); - } - val = move (vp); - } + if (!sps.empty ()) // Keep it NULL if no subprojects. + v = move (sps); } - else if (!val.null ()) + else if (v) { // Convert empty to NULL. // - if (val.empty ()) - val = nullptr; + if (v.empty ()) + v = nullptr; else { - // Scan the value and convert it to the "canonical" form, - // that is, a list of dir=simple pairs. + // Pre-scan the value and convert it to the "canonical" form, + // that is, a list of simple=dir pairs. // - list_value& lv (val.as<list_value&> ()); - - for (auto i (lv.begin ()); i != lv.end (); ++i) + for (auto i (v.data_.begin ()); i != v.data_.end (); ++i) { bool p (i->pair != '\0'); @@ -494,25 +477,19 @@ namespace build { // Project name. // - if (!i->simple () || i->empty ()) + if (!assign<string> (*i) || as<string> (*i).empty ()) fail << "expected project name instead of '" << *i << "' in " << "the subprojects variable"; ++i; // Got to have the second half of the pair. } - name& n (*i); - - if (n.simple ()) - { - n.dir = dir_path (move (n.value)); - n.value.clear (); - } - - if (!n.directory ()) - fail << "expected directory instead of '" << n << "' in the " + if (!assign<dir_path> (*i)) + fail << "expected directory instead of '" << *i << "' in the " << "subprojects variable"; + auto& d (as<dir_path> (*i)); + // Figure out the project name if the user didn't specify one. // if (!p) @@ -521,12 +498,18 @@ namespace build // was specified by the user so it is most likely in our // src. // - i = lv.emplace (i, find_project_name (out_root / n.dir, - src_root / n.dir)); + i = v.data_.emplace ( + i, + find_project_name (out_root / d, src_root / d)); + i->pair = '='; ++i; } } + + // Make it of the map type. + // + assign<subprojects> (v, var); } } } @@ -537,12 +520,12 @@ namespace build void create_bootstrap_outer (scope& root) { - auto v (root.vars["amalgamation"]); + auto l (root.vars["amalgamation"]); - if (!v) + if (!l) return; - const dir_path& d (v.as<const dir_path&> ()); + const dir_path& d (as<dir_path> (*l)); dir_path out_root (root.path () / d); out_root.normalize (); @@ -560,21 +543,21 @@ namespace build scope& rs (create_root (out_root, dir_path ())); bootstrap_out (rs); // #3 happens here, if at all. - auto val (rs.assign ("src_root")); + value& v (rs.assign ("src_root")); - if (!val) + if (!v) { if (is_src_root (out_root)) // #2 - val = out_root; + v = out_root; else // #1 { dir_path src_root (root.src_path () / d); src_root.normalize (); - val = move (src_root); + v = move (src_root); } } - rs.src_path_ = &val.as<const dir_path&> (); + rs.src_path_ = &as<dir_path> (v); bootstrap_src (rs); create_bootstrap_outer (rs); @@ -588,9 +571,9 @@ namespace build scope& create_bootstrap_inner (scope& root, const dir_path& out_base) { - if (auto v = root.vars["subprojects"]) + if (auto l = root.vars["subprojects"]) { - for (const name& n: v.as<const list_value&> ()) + for (const name& n: *l) { if (n.pair != '\0') continue; // Skip project names. @@ -605,14 +588,14 @@ namespace build scope& rs (create_root (out_root, dir_path ())); bootstrap_out (rs); - auto val (rs.assign ("src_root")); + value& v (rs.assign ("src_root")); - if (!val) - val = is_src_root (out_root) + if (!v) + v = is_src_root (out_root) ? out_root : (root.src_path () / n.dir); - rs.src_path_ = &val.as<const dir_path&> (); + rs.src_path_ = &as<dir_path> (v); bootstrap_src (rs); @@ -646,8 +629,8 @@ namespace build source_once (bf, root, root); } - list_value - import (scope& ibase, name target, const location& l) + names + import (scope& ibase, name target, const location& loc) { tracer trace ("import"); @@ -659,7 +642,7 @@ namespace build if (target.unqualified ()) { target.proj = &project_name_pool.find (""); - return list_value (move (target)); + return names {move (target)}; } // Otherwise, get the project name and convert the target to @@ -680,14 +663,16 @@ namespace build // for (scope* r (&iroot);; r = r->parent_scope ()->root_scope ()) { - if (auto v = r->vars["subprojects"]) + if (auto l = r->vars["subprojects"]) { - // @@ Map sure would have been handy. - // - if (const name* n = v.as<const list_value&> ().find_pair (project)) + const auto& m (as<subprojects> (*l)); + auto i (m.find (project)); + + if (i != m.end ()) { - out_root = r->path () / n->dir; - fallback_src_root = r->src_path () / n->dir; + const dir_path& d ((*i).second); + out_root = r->path () / d; + fallback_src_root = r->src_path () / d; break; } } @@ -700,42 +685,34 @@ namespace build // if (out_root.empty ()) { - string var ("config.import." + project); + const variable& var ( + variable_pool.find ("config.import." + project, + dir_path_type)); - if (auto v = iroot[var]) + if (auto l = iroot[var]) { - if (!v.belongs (*global_scope)) // A value from (some) config.build. - out_root = v.as<const dir_path&> (); - else + out_root = as<dir_path> (*l); + + if (l.belongs (*global_scope)) // A value from command line. { - // Process the path by making it absolute and normalized. Also, - // for usability's sake, treat a simple name that doesn't end - // with '/' as a directory. + // Process the path by making it absolute and normalized. // - list_value& lv (v.as<list_value&> ()); - - if (lv.size () != 1 || lv[0].empty () || lv[0].typed ()) - fail (l) << "invalid " << var << " value " << lv; - - name& n (lv[0]); - - if (n.directory ()) - out_root = n.dir; - else - out_root = dir_path (n.value); - if (out_root.relative ()) out_root = work / out_root; out_root.normalize (); + + // Set on our root scope (part of our configuration). + // iroot.assign (var) = out_root; // Also update the command-line value. This is necessary to avoid // a warning issued by the config module about global/root scope - // value mismatch. + // value mismatch. Not very clean. // - if (n.dir != out_root) - n = name (out_root); + dir_path& d (as<dir_path> (const_cast<value&> (*l))); + if (d != out_root) + d = out_root; } } else @@ -746,7 +723,7 @@ namespace build // target.proj = &project; level4 ([&]{trace << "postponing " << target;}); - return list_value (move (target)); + return names {move (target)}; } } @@ -761,13 +738,13 @@ namespace build // Check that the bootstrap process set src_root. // - if (auto v = root.vars["src_root"]) + if (auto l = root.vars["src_root"]) { - const dir_path& p (v.as<const dir_path&> ()); + const dir_path& p (as<dir_path> (*l)); if (!src_root.empty () && p != src_root) - fail (l) << "bootstrapped src_root " << p << " does not match " - << "discovered " << src_root; + fail (loc) << "bootstrapped src_root " << p << " does not match " + << "discovered " << src_root; root.src_path_ = &p; } @@ -775,12 +752,12 @@ namespace build // else if (!fallback_src_root.empty ()) { - auto v (root.assign ("src_root")); + value& v (root.assign ("src_root")); v = move (fallback_src_root); - root.src_path_ = &v.as<const dir_path&> (); + root.src_path_ = &as<dir_path> (v); } else - fail (l) << "unable to determine src_root for imported " << project << + fail (loc) << "unable to determine src_root for imported " << project << info << "consider configuring " << out_root; bootstrap_src (root); @@ -807,10 +784,10 @@ namespace build // Also pass the target being imported. // { - auto v (ts.assign ("target")); + value& v (ts.assign ("target")); if (!target.empty ()) // Otherwise leave NULL. - v = list_value {move (target)}; + v = move (target); } // Load the export stub. Note that it is loaded in the context @@ -821,7 +798,7 @@ namespace build path es (root.src_path () / path ("build/export.build")); ifstream ifs (es.string ()); if (!ifs.is_open ()) - fail (l) << "unable to open " << es; + fail (loc) << "unable to open " << es; level4 ([&]{trace << "importing " << es;}); @@ -830,18 +807,18 @@ namespace build try { - p.parse_buildfile (ifs, es, iroot, ts); + // @@ Should we verify these are all unqualified names? Or maybe + // there is a use-case for the export stub to return a qualified + // name? + // + return p.parse_export_stub (ifs, es, iroot, ts); } catch (const std::ios_base::failure&) { - fail (l) << "failed to read from " << es; + fail (loc) << "failed to read from " << es; } - // @@ Should we verify these are all unqualified names? Or maybe - // there is a use-case for the export stub to return a qualified - // name? - // - return p.export_value (); + return names (); // Never reached. } target& diff --git a/build/install/module.cxx b/build/install/module.cxx index 834b0e8..610154a 100644 --- a/build/install/module.cxx +++ b/build/install/module.cxx @@ -29,54 +29,64 @@ namespace build // to set all the install.* values to defaults, as if we had the // default configuration. // + template <typename T> static void - set_dir (bool spec, + set_var (bool spec, scope& r, const char* name, - const char* path, - const char* mode = nullptr, - const char* dir_mode = nullptr, - const char* cmd = nullptr, - const char* options = nullptr) + const char* var, + const T* dv) { - auto set = [spec, &r, name] (const char* var, const char* dv) - { - string vn; - const list_value* lv (nullptr); - - if (spec) - { - vn = "config.install."; - vn += name; - vn += var; - - lv = dv != nullptr - ? &config::required (r, vn, list_value (dv)).first - : config::optional<list_value> (r, vn); - } - - vn = "install."; - vn += name; - vn += var; - auto v (r.assign (vn)); - - if (spec) - { - if (lv != nullptr && !lv->empty ()) - v = *lv; - } - else - { - if (dv != nullptr) - v = dv; - } - }; - - set ("", path); - set (".mode", mode); - set (".dir_mode", dir_mode); - set (".cmd", cmd); - set (".options", options); + string vn; + const value* cv (nullptr); + + if (spec) + { + vn = "config.install."; + vn += name; + vn += var; + const variable& vr ( + variable_pool.find (move (vn), &value_traits<T>::value_type)); + + cv = dv != nullptr + ? &config::required (r, vr, *dv).first.get () + : &config::optional (r, vr); + } + + vn = "install."; + vn += name; + vn += var; + const variable& vr ( + variable_pool.find (move (vn), &value_traits<T>::value_type)); + + value& v (r.assign (vr)); + + if (spec) + { + if (cv != nullptr && *cv && !cv->empty ()) + v = *cv; + } + else + { + if (dv != nullptr) + v = *dv; + } + } + + static void + set_dir (bool s, + scope& r, + const char* name, + const dir_path& path, + const string& fmode = string (), + const string& dmode = string (), + const string& cmd = string ()) + { + set_var (s, r, name, "", path.empty () ? nullptr : &path); + set_var (s, r, name, ".mode", fmode.empty () ? nullptr : &fmode); + set_var (s, r, name, ".dir_mode", dmode.empty () ? nullptr : &dmode); + set_var (s, r, name, ".cmd", cmd.empty () ? nullptr : &cmd); + set_var<strings> (s, r, name, ".options", nullptr); } static rule rule_; @@ -120,6 +130,15 @@ namespace build rs.insert<file> (install_id, "install", rule_); } + // Enter module variables. + // + // Note that the set_dir() calls below enter some more. + // + if (first) + { + variable_pool.find ("install", dir_path_type); + } + // Configuration. // // Note that we don't use any defaults for root -- the location @@ -129,31 +148,31 @@ namespace build if (first) { bool s (config::specified (r, "config.install")); - const string& n (r["project"].as<const string&> ()); + const string& n (as<string> (*r["project"])); - set_dir (s, r, "root", nullptr, nullptr, "755", "install"); - set_dir (s, r, "data_root", "root", "644"); - set_dir (s, r, "exec_root", "root", "755"); + set_dir (s, r, "root", dir_path (), "", "755", "install"); + set_dir (s, r, "data_root", dir_path ("root"), "644"); + set_dir (s, r, "exec_root", dir_path ("root"), "755"); - set_dir (s, r, "sbin", "exec_root/sbin"); - set_dir (s, r, "bin", "exec_root/bin"); - set_dir (s, r, "lib", "exec_root/lib"); - set_dir (s, r, "libexec", ("exec_root/libexec/" + n).c_str ()); + set_dir (s, r, "sbin", dir_path ("exec_root/sbin")); + set_dir (s, r, "bin", dir_path ("exec_root/bin")); + set_dir (s, r, "lib", dir_path ("exec_root/lib")); + set_dir (s, r, "libexec", dir_path ("exec_root/libexec/" + n)); - set_dir (s, r, "data", ("data_root/share/" + n).c_str ()); - set_dir (s, r, "include", "data_root/include"); + set_dir (s, r, "data", dir_path ("data_root/share/" + n)); + set_dir (s, r, "include", dir_path ("data_root/include")); - set_dir (s, r, "doc", ("data_root/share/doc/" + n).c_str ()); - set_dir (s, r, "man", "data_root/share/man"); + set_dir (s, r, "doc", dir_path ("data_root/share/doc/" + n)); + set_dir (s, r, "man", dir_path ("data_root/share/man")); - set_dir (s, r, "man1", "man/man1"); + set_dir (s, r, "man1", dir_path ("man/man1")); } // Configure "installability" for built-in target types. // - path<doc> (b, "doc"); // Install into install.doc. - path<man> (b, "man"); // Install into install.man. - path<man> (b, "man1"); // Install into install.man1. + path<doc> (b, dir_path ("doc")); // Install into install.doc. + path<man> (b, dir_path ("man")); // Install into install.man. + path<man1> (b, dir_path ("man1")); // Install into install.man1. } } } diff --git a/build/install/rule.cxx b/build/install/rule.cxx index f14547c..8f67001 100644 --- a/build/install/rule.cxx +++ b/build/install/rule.cxx @@ -21,48 +21,23 @@ namespace build { namespace install { - // Lookup the install or install.* variable and check that - // the value makes sense. Return NULL if not found or if - // the value is the special 'false' name (which means do - // not install). T is either scope or target. + // Lookup the install or install.* variable. Return NULL if + // not found or if the value is the special 'false' name (which + // means do not install). T is either scope or target. // template <typename T> - static const name* - lookup (T& t, const char* var) + static const dir_path* + lookup (T& t, const string& var) { - auto v (t[var]); + auto l (t[var]); - const name* r (nullptr); + if (!l) + return nullptr; - if (!v) - return r; - - const list_value& lv (v.template as<const list_value&> ()); - - if (lv.empty ()) - return r; - - if (lv.size () == 1) - { - const name& n (lv.front ()); - - if (n.simple () && n.value == "false") - return r; - - if (!n.empty () && (n.simple () || n.directory ())) - return &n; - } - - fail << "expected directory instead of '" << lv << "' in " - << "the " << var << " variable"; - - return r; + const dir_path& r (as<dir_path> (*l)); + return r.simple () && r.string () == "false" ? nullptr : &r; } - template <typename T> - static inline const name* - lookup (T& t, const string& var) {return lookup (t, var.c_str ());} - match_result rule:: match (action a, target& t, const std::string&) const { @@ -185,8 +160,8 @@ namespace build struct install_dir { dir_path dir; - string cmd; - const list_value* options {nullptr}; + string cmd; //@@ VAR type + const_strings_value options {nullptr}; string mode; string dir_mode; }; @@ -200,8 +175,8 @@ namespace build cstrings args {base.cmd.c_str (), "-d"}; - if (base.options != nullptr) - config::append_options (args, *base.options, "install.*.options"); + if (base.options.d != nullptr) //@@ VAR + config::append_options (args, base.options); args.push_back ("-m"); args.push_back (base.dir_mode.c_str ()); @@ -241,8 +216,8 @@ namespace build cstrings args {base.cmd.c_str ()}; - if (base.options != nullptr) - config::append_options (args, *base.options, "install.*.options"); + if (base.options.d != nullptr) //@@ VAR + config::append_options (args, base.options); args.push_back ("-m"); args.push_back (base.mode.c_str ()); @@ -277,10 +252,9 @@ namespace build // creating leading directories as necessary. // static install_dir - resolve (scope& s, const name& n, const string* var = nullptr) + resolve (scope& s, dir_path d, const string* var = nullptr) { install_dir r; - dir_path d (n.simple () ? dir_path (n.value) : n.dir); if (d.absolute ()) { @@ -298,11 +272,11 @@ namespace build // as the installation directory name, e.g., bin, sbin, lib, // etc. Look it up and recurse. // - const string& dn (*d.begin ()); - const string var ("install." + dn); - if (const name* n = lookup (s, var)) + const string& sn (*d.begin ()); + const string var ("install." + sn); + if (const dir_path* dn = lookup (s, var)) { - r = resolve (s, *n, &var); + r = resolve (s, *dn, &var); d = r.dir / dir_path (++d.begin (), d.end ()); d.normalize (); @@ -310,7 +284,7 @@ namespace build install (r, d); // install -d } else - fail << "unknown installation directory name " << dn << + fail << "unknown installation directory name " << sn << info << "did you forget to specify config." << var << "?"; } @@ -320,12 +294,10 @@ namespace build // if (var != nullptr) { - if (auto v = s[*var + ".cmd"]) r.cmd = v.as<const string&> (); - if (auto v = s[*var + ".mode"]) r.mode = v.as<const string&> (); - if (auto v = s[*var + ".dir_mode"]) - r.dir_mode = v.as<const string&> (); - if (auto v = s[*var + ".options"]) - r.options = &v.as<const list_value&> (); + if (auto l = s[*var + ".cmd"]) r.cmd = as<string> (*l); + if (auto l = s[*var + ".mode"]) r.mode = as<string> (*l); + if (auto l = s[*var + ".dir_mode"]) r.dir_mode = as<string> (*l); + if (auto l = s[*var + ".options"]) r.options = as<strings> (*l); } // Set defaults for unspecified components. @@ -351,12 +323,12 @@ namespace build // install_dir d ( resolve (t.base_scope (), - t["install"].as<const name&> ())); // We know it's there. + as<dir_path> (*t["install"]))); // We know it's there. // Override mode if one was specified. // - if (auto v = t["install.mode"]) - d.mode = v.as<const string&> (); + if (auto l = t["install.mode"]) + d.mode = as<string> (*l); install (d, ft); return (r |= target_state::changed); diff --git a/build/install/utility b/build/install/utility index 5c703fc..9bc41f1 100644 --- a/build/install/utility +++ b/build/install/utility @@ -5,7 +5,11 @@ #ifndef BUILD_INSTALL_UTILITY #define BUILD_INSTALL_UTILITY +#include <string> +#include <utility> + #include <build/scope> +#include <build/types> namespace build { @@ -15,20 +19,20 @@ namespace build // template <typename T> inline void - path (scope& s, const char* v) + path (scope& s, dir_path d) { - auto p (s.target_vars[T::static_type]["*"].assign ("install")); - if (p.second) // Already set by the user? - p.first = v; + auto r (s.target_vars[T::static_type]["*"].assign ("install")); + if (r.second) // Already set by the user? + r.first.get () = std::move (d); } template <typename T> inline void - mode (scope& s, const char* v) + mode (scope& s, std::string m) { - auto m (s.target_vars[T::static_type]["*"].assign ("install.mode")); - if (m.second) // Already set by the user? - m.first = v; + auto r (s.target_vars[T::static_type]["*"].assign ("install.mode")); + if (r.second) // Already set by the user? + r.first.get () = std::move (m); } } } @@ -33,11 +33,11 @@ namespace build { name () = default; - explicit - name (std::string v): value (std::move (v)) {} + explicit name (std::string v): value (std::move (v)) {} + name& operator= (std::string v) {return *this = name (std::move (v));} - explicit - name (dir_path d): dir (std::move (d)) {} + explicit name (dir_path d): dir (std::move (d)) {} + name& operator= (dir_path d) {return *this = name (std::move (d));} name (std::string t, std::string v) : type (std::move (t)), value (std::move (v)) {} diff --git a/build/operation.cxx b/build/operation.cxx index 607658f..a92c912 100644 --- a/build/operation.cxx +++ b/build/operation.cxx @@ -67,8 +67,7 @@ namespace build scope& base (scopes[out_base]); base.assign ("out_base") = out_base; - auto v (base.assign ("src_base") = src_base); - base.src_path_ = &v.as<const dir_path&> (); + base.src_path_ = &as<dir_path> (base.assign ("src_base") = src_base); // Load the buildfile unless it has already been loaded. // diff --git a/build/parser b/build/parser index 6eefccd..c6d275b 100644 --- a/build/parser +++ b/build/parser @@ -24,6 +24,8 @@ namespace build class parser { public: + typedef build::names names_type; + parser (): fail (&path_) {} // Issue diagnostics and throw failed in case of an error. @@ -37,19 +39,16 @@ namespace build token parse_variable (lexer&, scope&, std::string name, token_type kind); - list_value - export_value () + names_type + parse_export_stub (std::istream& is, const path& p, scope& r, scope& b) { - list_value r (std::move (export_value_)); - export_value_.clear (); // Empty state. - return r; + parse_buildfile (is, p, r, b); + return std::move (export_value_); } // Recursive descent parser. // private: - typedef build::names names_type; - void clause (token&, token_type&); @@ -139,7 +138,7 @@ namespace build const dir_path* out_root_; const dir_path* src_root_; target* default_target_; - list_value export_value_; + names_type export_value_; token peek_ {token_type::eos, false, 0, 0}; bool peeked_ {false}; diff --git a/build/parser.cxx b/build/parser.cxx index 682a720..c728cb8 100644 --- a/build/parser.cxx +++ b/build/parser.cxx @@ -478,8 +478,8 @@ namespace build // if (src_root_ == nullptr) { - auto v (root_->vars["src_root"]); - src_root_ = v ? &v.as<const dir_path&> () : nullptr; + auto l (root_->vars["src_root"]); + src_root_ = l ? &as<dir_path> (*l) : nullptr; } } @@ -597,8 +597,8 @@ namespace build scope_ = &scopes[out_base]; scope_->assign ("out_base") = move (out_base); - auto v (scope_->assign ("src_base") = move (src_base)); - scope_->src_path_ = &v.as<const dir_path&> (); + scope_->src_path_ = &as<dir_path> ( + scope_->assign ("src_base") = move (src_base)); target* odt (default_target_); default_target_ = nullptr; @@ -641,7 +641,9 @@ namespace build // // import [<var>=](<project>|<project>/<target>])+ // - value_proxy val; + value* val (nullptr); + const build::variable* var (nullptr); + token_type at; // Assignment type. if (tt == type::name) { @@ -649,9 +651,10 @@ namespace build if (at == token_type::equal || at == token_type::plus_equal) { - val.rebind (at == token_type::equal - ? scope_->assign (t.name ()) - : scope_->append (t.name ())); + var = &variable_pool.find (t.name ()); + val = at == token_type::equal + ? &scope_->assign (*var) + : &scope_->append (*var); next (t, tt); // Consume =/+=. next (t, tt); } @@ -669,14 +672,14 @@ namespace build { // build::import() will check the name, if required. // - list_value r (build::import (*scope_, move (n), l)); + names_type r (build::import (*scope_, move (n), l)); - if (val.defined ()) + if (val != nullptr) { if (at == token_type::equal) - val = move (r); + val->assign (move (r), *var); else - val += move (r); + val->append (move (r), *var); } } @@ -701,9 +704,8 @@ namespace build // The rest is a value. Parse it as names to get variable expansion. // build::import() will check the names, if required. // - export_value_ = list_value (tt != type::newline && tt != type::eos - ? names (t, tt) - : names_type ()); + if (tt != type::newline && tt != type::eos) + export_value_ = names (t, tt); if (tt == type::newline) next (t, tt); @@ -785,28 +787,17 @@ namespace build : names_type ()); if (assign) { - auto v (target_ != nullptr - ? target_->assign (var) - : scope_->assign (var)); - v = list_value (move (vns)); + value& v (target_ != nullptr + ? target_->assign (var) + : scope_->assign (var)); + v.assign (move (vns), var); } else { - auto v (target_ != nullptr - ? target_->append (var) - : scope_->append (var)); - - // More efficient than calling operator+=. - // - if (v) - { - list_value& lv (v.as<list_value&> ()); - lv.insert (lv.end (), - make_move_iterator (vns.begin ()), - make_move_iterator (vns.end ())); - } - else - v = list_value (move (vns)); // Same as assignment. + value& v (target_ != nullptr + ? target_->append (var) + : scope_->append (var)); + v.append (move (vns), var); } } @@ -1031,12 +1022,12 @@ namespace build n = t.name (); const auto& var (variable_pool.find (move (n))); - auto val (target_ != nullptr ? (*target_)[var] : (*scope_)[var]); + auto l (target_ != nullptr ? (*target_)[var] : (*scope_)[var]); - // Undefined namespace variables are not allowed. + // Undefined/NULL namespace variables are not allowed. // - if (!val && var.name.find ('.') != string::npos) - fail (t) << "undefined namespace variable " << var.name; + if (!l && var.name.find ('.') != string::npos) + fail (t) << "undefined/null namespace variable " << var.name; if (paren) { @@ -1048,15 +1039,10 @@ namespace build tt = peek (); - if (!val) + if (!l || l->empty ()) continue; - //@@ TODO: assuming it is a list. - // - const list_value& lv (val.as<list_value&> ()); - - if (lv.empty ()) - continue; + const names_type& lv (l->data_); // Should we accumulate? If the buffer is not empty, then // we continue accumulating (the case where we are separated @@ -1077,11 +1063,11 @@ namespace build const name& n (lv[0]); - if (n.proj != nullptr) + if (n.qualified ()) fail (t) << "concatenating expansion of " << var.name << " contains project name"; - if (!n.type.empty ()) + if (n.typed ()) fail (t) << "concatenating expansion of " << var.name << " contains type"; @@ -1452,10 +1438,10 @@ namespace build // to their return values are not guaranteed to be stable (and, // in fact, path()'s is not). // - out_root_ = &root_->vars["out_root"].as<const dir_path&> (); + out_root_ = &as<dir_path> (*root_->vars["out_root"]); - auto v (root_->vars["src_root"]); - src_root_ = v ? &v.as<const dir_path&> () : nullptr; + auto l (root_->vars["src_root"]); + src_root_ = l ? &as<dir_path> (*l) : nullptr; } return r; diff --git a/build/scope b/build/scope index 6edb43a..50c4f6b 100644 --- a/build/scope +++ b/build/scope @@ -68,37 +68,37 @@ namespace build // Lookup, including in outer scopes. If you only want to lookup // in this scope, do it on the the variables map directly. // - value_proxy + lookup<const value> operator[] (const variable&) const; - value_proxy + lookup<const value> operator[] (const std::string& name) const { return operator[] (variable_pool.find (name)); } - // Return a value_proxy suitable for assignment (or append if you - // only want to append to the value from this scope). If the variable - // does not exist in this scope's map, then a new one with the NULL - // value is added and returned. Otherwise the existing value is - // returned. + // Return a value suitable for assignment (or append if you only + // want to append to the value from this scope). If the variable + // does not exist in this scope's map, then a new one with the + // NULL value is added and returned. Otherwise the existing value + // is returned. // - value_proxy - assign (const variable& var) {return vars.assign (var).first;} + value& + assign (const variable& var) {return vars.assign (var).first.get ();} - value_proxy - assign (const std::string& name) {return vars.assign (name).first;} + value& + assign (const std::string& name) {return vars.assign (name).first.get ();} - // Return a value_proxy suitable for appending. If the variable - // does not exist in this scope's map, then outer scopes are - // searched for the same variable. If found then a new variable - // with the found value is added to this scope and returned. - // Otherwise this function proceeds as assign(). + // Return a value suitable for appending. If the variable does not + // exist in this scope's map, then outer scopes are searched for + // the same variable. If found then a new variable with the found + // value is added to this scope and returned. Otherwise this + // function proceeds as assign(). // - value_proxy + value& append (const variable&); - value_proxy + value& append (const std::string& name) { return append (variable_pool.find (name)); diff --git a/build/scope.cxx b/build/scope.cxx index d878726..e6d165e 100644 --- a/build/scope.cxx +++ b/build/scope.cxx @@ -12,30 +12,33 @@ namespace build { // scope // - value_proxy scope:: + lookup<const value> scope:: operator[] (const variable& var) const { - for (const scope* s (this); s != nullptr; s = s->parent_scope ()) + const value* r (nullptr); + const scope* s (this); + + for (; s != nullptr; s = s->parent_scope ()) { - if (const value_ptr* v = s->vars.find (var)) - return value_proxy (v, &s->vars); + if ((r = s->vars.find (var)) != nullptr) + break; } - return value_proxy (); + return lookup<const value> (r, &s->vars); } - value_proxy scope:: + value& scope:: append (const variable& var) { - value_proxy val (operator[] (var)); + auto l (operator[] (var)); - if (val && val.belongs (*this)) // Existing variable in this scope. - return val; + if (l && l.belongs (*this)) // Existing variable in this scope. + return const_cast<value&> (*l); - value_proxy r (assign (var)); + value& r (assign (var)); - if (val) - r = val; // Copy value from the outer scope. + if (l) + r = *l; // Copy value from the outer scope. return r; } diff --git a/build/target b/build/target index 9e15550..2c05ea1 100644 --- a/build/target +++ b/build/target @@ -262,33 +262,33 @@ namespace build // Lookup, including in groups to which this target belongs and // then in outer scopes (including target type/pattern-specific // variables). If you only want to lookup in this target, do it - // on the the variable map directly. + // on the variable map directly. // - value_proxy + lookup<const value> operator[] (const variable&) const; - value_proxy + lookup<const value> operator[] (const std::string& name) const { return operator[] (variable_pool.find (name)); } - // Return a value_proxy suitable for assignment. See class scope - // for details. + // Return a value suitable for assignment. See class scope for + // details. // - value_proxy + value& assign (const variable& var) {return vars.assign (var).first;} - value_proxy + value& assign (const std::string& name) {return vars.assign (name).first;} - // Return a value_proxy suitable for appending. See class scope - // for details. + // Return a value suitable for appending. See class scope for + // details. // - value_proxy + value& append (const variable&); - value_proxy + value& append (const std::string& name) { return append (variable_pool.find (name)); diff --git a/build/target.cxx b/build/target.cxx index aaf8a5e..6315d04 100644 --- a/build/target.cxx +++ b/build/target.cxx @@ -116,16 +116,18 @@ namespace build return *r; } - value_proxy target:: + lookup<const value> target:: operator[] (const variable& var) const { + using result = lookup<const value>; + if (auto p = vars.find (var)) - return value_proxy (p, &vars); + return result (p, &vars); if (group != nullptr) { if (auto p = group->vars.find (var)) - return value_proxy (p, &group->vars); + return result (p, &group->vars); } // Cannot simply delegate to scope's operator[] since we also @@ -135,7 +137,7 @@ namespace build { if (!s->target_vars.empty ()) { - auto find_specific = [&var, s] (const target& t) -> value_proxy + auto find_specific = [&var, s] (const target& t) -> result { // Search across target type hierarchy. // @@ -154,10 +156,10 @@ namespace build continue; if (auto p = j->second.find (var)) - return value_proxy (p, &j->second); + return result (p, &j->second); } - return value_proxy (); + return result (); }; if (auto p = find_specific (*this)) @@ -171,24 +173,24 @@ namespace build } if (auto p = s->vars.find (var)) - return value_proxy (p, &s->vars); + return result (p, &s->vars); } - return value_proxy (); + return result (); } - value_proxy target:: + value& target:: append (const variable& var) { - value_proxy val (operator[] (var)); + auto l (operator[] (var)); - if (val && val.belongs (*this)) // Existing variable in this target. - return val; + if (l && l.belongs (*this)) // Existing variable in this target. + return const_cast<value&> (*l); - value_proxy r (assign (var)); + value& r (assign (var)); - if (val) - r = val; // Copy value from the outer scope. + if (l) + r = *l; // Copy value from the outer scope. return r; } diff --git a/build/target.txx b/build/target.txx index bded149..a293018 100644 --- a/build/target.txx +++ b/build/target.txx @@ -20,9 +20,9 @@ namespace build const std::string& target_extension_var (const target_key& tk, scope& s) { - auto val (s[var]); + auto l (s[var]); - if (!val) + if (!l) { diag_record dr; dr << fail << "no default extension in variable " << var @@ -42,6 +42,6 @@ namespace build } } - return extension_pool.find (val.as<const std::string&> ()); + return extension_pool.find (as<std::string> (*l)); } } diff --git a/build/test/module.cxx b/build/test/module.cxx index cba930f..9495275 100644 --- a/build/test/module.cxx +++ b/build/test/module.cxx @@ -59,6 +59,18 @@ namespace build // rs.insert<target> (test_id, "test", rule_); } + + // Enter module variables. + // + if (first) + { + variable_pool.find ("test", bool_type); + variable_pool.find ("test.input", name_type); + variable_pool.find ("test.output", name_type); + variable_pool.find ("test.roundtrip", name_type); + variable_pool.find ("test.options", strings_type); + variable_pool.find ("test.arguments", strings_type); + } } } } diff --git a/build/test/rule.cxx b/build/test/rule.cxx index 10b628d..eecd613 100644 --- a/build/test/rule.cxx +++ b/build/test/rule.cxx @@ -11,6 +11,8 @@ #include <build/algorithm> #include <build/diagnostics> +#include <build/config/utility> // add_options() + using namespace std; using namespace butl; @@ -28,7 +30,7 @@ namespace build // this is a test. So take care of that as well. // bool r (false); - value_proxy v; + lookup<const value> l; // @@ This logic doesn't take into account target type/pattern- // specific variables. @@ -40,13 +42,13 @@ namespace build ++p.first) { const variable& var (p.first->first); - const value_ptr& val (p.first->second); + const value& val (p.first->second); // If we have test, then always use that. // if (var.name == "test") { - v.rebind (value_proxy (val, t)); + l = lookup<const value> (val, t); break; } @@ -68,10 +70,11 @@ namespace build { // See if there is a scope variable. // - if (!v) - v.rebind (t.base_scope ()[string("test.") + t.type ().name]); + if (!l.defined ()) + l = t.base_scope ()[ + variable_pool.find (string("test.") + t.type ().name, bool_type)]; - r = v && v.as<bool> (); + r = l && as<bool> (*l); } // If this is the update pre-operation, then all we really need to @@ -129,50 +132,47 @@ namespace build // First check the target-specific vars since they override any // scope ones. // - auto iv (t.vars["test.input"]); - auto ov (t.vars["test.output"]); - auto rv (t.vars["test.roundtrip"]); - - // Can either be input or arguments. - // - auto av (t.vars["test.arguments"]); + auto il (t.vars["test.input"]); + auto ol (t.vars["test.output"]); + auto rl (t.vars["test.roundtrip"]); + auto al (t.vars["test.arguments"]); // Should be input or arguments. - if (av) + if (al) { - if (iv) + if (il) fail << "both test.input and test.arguments specified for " << "target " << t; - if (rv) + if (rl) fail << "both test.roundtrip and test.arguments specified for " << "target " << t; } scope& bs (t.base_scope ()); - if (!iv && !ov && !rv) + if (!il && !ol && !rl) { string n ("test."); n += t.type ().name; - const variable& in (variable_pool.find (n + ".input")); - const variable& on (variable_pool.find (n + ".output")); - const variable& rn (variable_pool.find (n + ".roundtrip")); + const variable& in (variable_pool.find (n + ".input", name_type)); + const variable& on (variable_pool.find (n + ".output", name_type)); + const variable& rn (variable_pool.find (n + ".roundtrip", name_type)); // We should only keep value(s) that were specified together // in the innermost scope. // for (scope* s (&bs); s != nullptr; s = s->parent_scope ()) { - ov.rebind (s->vars[on]); + ol = s->vars[on]; - if (!av) // Not overriden at target level by test.arguments? + if (!al) // Not overriden at target level by test.arguments? { - iv.rebind (s->vars[in]); - rv.rebind (s->vars[rn]); + il = s->vars[in]; + rl = s->vars[rn]; } - if (iv || ov || rv) + if (il || ol || rl) break; } } @@ -182,18 +182,18 @@ namespace build // Reduce the roundtrip case to input/output. // - if (rv) + if (rl) { - if (iv || ov) + if (il || ol) fail << "both test.roundtrip and test.input/output specified " << "for target " << t; - in = on = rv.as<const name*> (); + in = on = &as<name> (*rl); } else { - in = iv ? iv.as<const name*> () : nullptr; - on = ov ? ov.as<const name*> () : nullptr; + in = il ? &as<name> (*il) : nullptr; + on = ol ? &as<name> (*ol) : nullptr; } // Resolve them to targets, which normally would be existing files @@ -283,35 +283,24 @@ namespace build } static void - add_arguments (cstrings& args, target& t, const char* n) + add_arguments (cstrings& args, const target& t, const char* n) { string var ("test."); var += n; - auto v (t.vars[var]); + auto l (t.vars[var]); - if (!v) + if (!l) { var.resize (5); var += t.type ().name; var += '.'; var += n; - v.rebind (t.base_scope ()[var]); + l = t.base_scope ()[variable_pool.find (var, strings_type)]; } - if (v) - { - for (const name& n: v.as<const list_value&> ()) - { - if (n.simple ()) - args.push_back (n.value.c_str ()); - else if (n.directory ()) - args.push_back (n.dir.string ().c_str ()); - else - fail << "expected argument instead of " << n << - info << "in variable " << var; - } - } + if (l) + config::append_options (args, as<strings> (*l)); } // The format of args shall be: diff --git a/build/variable b/build/variable index bc78289..3bd8922 100644 --- a/build/variable +++ b/build/variable @@ -6,13 +6,14 @@ #define BUILD_VARIABLE #include <map> +#include <vector> #include <string> -#include <memory> // unique_ptr #include <cstddef> // nullptr_t #include <utility> // move(), pair, make_pair() #include <cassert> +#include <iterator> #include <functional> // hash, reference_wrapper -#include <typeindex> +#include <type_traits> // conditional, is_reference, remove_reference, etc. #include <unordered_set> #include <butl/prefix-map> @@ -22,12 +23,18 @@ namespace build { - struct 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 { - std::type_index id; - value* (*const factory) (); + const std::string name; + bool (*const assign) (names&, const variable&); + bool (*const append) (names&, names, const variable&); }; // variable @@ -37,11 +44,12 @@ namespace build struct variable { explicit - variable (std::string n, char p = '\0'): name (std::move (n)), pairs (p) {} + variable (std::string n, const value_type* t = nullptr, char p = '\0') + : name (std::move (n)), pairs (p), type (t) {} std::string name; char pairs; - //const value_type* type = nullptr; // If NULL, then no fixed type. + const value_type* type; // If NULL, then not (yet) typed. }; inline bool @@ -51,119 +59,121 @@ namespace build // value // - struct value; - typedef std::unique_ptr<value> value_ptr; - - struct value + class value { public: - virtual value_ptr - clone () const = 0; + // By default we create NULL value. + // + explicit value (const value_type* t = nullptr) + : type (t), state_ (state_type::null) {} - virtual bool - compare (const value&) const = 0; + value (value&&) = default; - virtual - ~value () = default; - }; - - class list_value: public value, public names - { - public: - using names::names; - - list_value () = default; - explicit list_value (names d): names (std::move (d)) {} - explicit list_value (name n) {emplace_back (std::move (n));} - explicit list_value (dir_path d) {emplace_back (std::move (d));} - explicit list_value (std::string s) {emplace_back (std::move (s));} - explicit list_value (const char* s) {emplace_back (s);} + value& + operator= (std::nullptr_t) + { + data_.clear (); + state_ = state_type::null; + return *this; + } - virtual value_ptr - clone () const {return value_ptr (new list_value (*this));} + value& + operator= (value&&); - virtual bool - compare (const value& v) const + value& + operator= (const value& v) { - const list_value* lv (dynamic_cast<const list_value*> (&v)); - return lv != nullptr && static_cast<const names&> (*this) == *lv; + if (&v != this) + *this = value (v); + return *this; } - // Pair (i.e., key-value) search. Note that this funtion assumes - // the list contains only pairs and keys are simple names. Returns - // NULL if not found. - // - const name* - find_pair (const std::string& key) const + value& + operator= (std::reference_wrapper<const value> v) { - for (auto i (begin ()); i != end (); i += 2) - if (i->value == key) - return &*++i; - return nullptr; + return *this = v.get (); } - }; - typedef std::unique_ptr<list_value> list_value_ptr; - // value_proxy - // - // A variable can be undefined, null, or contain some actual value. - // Note that once value_proxy is bound to a value, the only way to - // rebind it to a different value is by using explicit rebind(). In - // particular, assigning one value proxy to another will assing the - // values. - // - struct variable_map; + value& + append (value, const variable&); // Aka operator+=(). - struct value_proxy - { - bool - defined () const {return p != nullptr;} + // Forwarded to the representation type's assign()/append() (see below). + // + template <typename T> value& operator= (T); + value& operator= (const char* v) {return *this = std::string (v);} - bool - null () const {return *p == nullptr;} + template <typename T> value& operator+= (T); + value& operator+= (const char* v) {return *this += std::string (v);} - bool - empty () const; + private: + explicit value (const value&) = default; + + public: + const value_type* type; // NULL means this value is not (yet) typed. - explicit operator bool () const {return defined () && !null ();} - explicit operator value_ptr& () const {return *p;} + bool null () const {return state_ == state_type::null;} + bool empty () const {return state_ == state_type::empty;} - // Get interface. See available specializations below. + explicit operator bool () const {return !null ();} + + // Raw data read interface. // - template <typename T> - T - as () const; + using const_iterator = names::const_iterator; + + const_iterator begin () const {return data_.begin ();} + const_iterator end () const {return data_.end ();} - // Assign. + // Raw data write interface. Note that it triggers callbacks for + // typed values. Variable is passed for diagnostics. // - const value_proxy& - operator= (value_ptr) const; + void + assign (names, const variable&); - const value_proxy& - operator= (const value_proxy&) const; + void + append (names, const variable&); - const value_proxy& - operator= (list_value) const; + public: + // Don't use directly except in the implementation of representation + // types, in which case take care to update state. + // + enum class state_type {null, empty, filled} state_; + names data_; + }; - const value_proxy& - operator= (std::string) const; + //@@ Right now we assume that for each value type each value has a + // unique representation. This is currently not the case for map. + // + inline bool + operator== (const value& x, const value& y) + { + return x.state_ == y.state_ && x.data_ == y.data_; + } + + inline bool + operator!= (const value& x, const value& y) {return !(x == y);} + + // lookup + // + // A variable can be undefined, NULL, or contain a (potentially + // empty) value. + // + struct variable_map; - const value_proxy& - operator= (dir_path) const; + template <typename V> + struct lookup + { + V* value; + const variable_map* vars; - const value_proxy& - operator= (std::nullptr_t) const; + bool + defined () const {return value != nullptr;} - // Append. + // Note: returns true if defined and not NULL. // - const value_proxy& - operator+= (const value_proxy&) const; + explicit operator bool () const {return defined () && !value->null ();} - const value_proxy& - operator+= (const list_value&) const; - - const value_proxy& - operator+= (std::string) const; // Append simple name to list_value. + V& operator* () const {return *value;} + V* operator-> () const {return value;} // Return true if this value belongs to the specified scope or target. // Note that it can also be a target type/pattern-specific value. @@ -172,92 +182,395 @@ namespace build bool belongs (const T& x) const {return vars == &x.vars;} - // Implementation details. - // - const variable_map* vars; // Variable map to which this value belongs. - - value_proxy (): vars (nullptr), p (nullptr) {} - value_proxy (value_ptr* p, const variable_map* v) - : vars (p != nullptr ? v : nullptr), p (p) {} + lookup (): value (nullptr), vars (nullptr) {} + lookup (V* v, const variable_map* vs) + : value (v), vars (v != nullptr ? vs : nullptr) {} template <typename T> - value_proxy (value_ptr& p, const T& x) - : value_proxy (&p, &x.vars) {} + lookup (V& v, const T& x): lookup (&v, &x.vars) {} + }; + + // Representation types. + // + template <typename T> struct value_traits; + + // Assign value type to the value. + // + template <typename T> + void assign (value&, const variable&); + void assign (value&, const value_type*, const variable&); - // @@ To do this properly we seem to need ro_value_proxy? + template <typename T> typename value_traits<T>::type as (value&); + template <typename T> typename value_traits<T>::const_type as (const value&); + + // "Assign" simple value type to the value stored in name. Return false + // if the value is not valid for this type. + // + template <typename T> bool assign (name&); + + template <typename T> typename value_traits<T>::type as (name&); + template <typename T> typename value_traits<T>::const_type as (const name&); + + // bool + // + template <typename D> + struct bool_value + { + explicit + operator bool () const {return d->value[0] == 't';} + + bool_value& + operator= (bool v) + { + d->value = v ? "true" : "false"; + return *this; + } + + bool_value& + operator+= (bool v) + { + if (!*this && v) + d->value = "true"; + return *this; + } + + // Implementation details. // - value_proxy (const value_ptr* p, const variable_map* v) - : value_proxy (const_cast<value_ptr*> (p), v) {} + public: + explicit bool_value (D& d): d (&d) {} - template <typename T> - value_proxy (const value_ptr& p, const T& x) - : value_proxy (const_cast<value_ptr&> (p), x) {} + bool_value (const bool_value&) = delete; + bool_value& operator= (const bool_value&) = delete; // Rebind or deep? - void - rebind (const value_proxy& x) {vars = x.vars; p = x.p;} + bool_value (bool_value&&) = default; + bool_value& operator= (bool_value&&) = delete; - private: - value_ptr* p; + D* d; // name }; template <> - inline value& value_proxy:: - as<value&> () const {return **p;} + struct value_traits<bool> + { + using type = bool_value<name>; + using const_type = bool_value<const name>; - template <> - inline const value& value_proxy:: - as<const value&> () const {return **p;} + static type as (name& n) {return type (n);} + static const_type as (const name& n) {return const_type (n);} + + static type as (value&); + static const_type as (const value&); + + static bool assign (name&); + static void assign (value&, bool); + static void append (value&, bool); + + static const build::value_type value_type; + }; + + extern const value_type* bool_type; + // string + // template <> - inline list_value& value_proxy:: - as<list_value&> () const + struct value_traits<std::string> { - list_value* lv (dynamic_cast<list_value*> (p->get ())); - assert (lv != nullptr); - return *lv; - } + using type = std::string&; + using const_type = const std::string&; - template <> - inline const list_value& value_proxy:: - as<const list_value&> () const {return as<list_value&> ();} + static type as (name& n) {return n.value;} + static const_type as (const name& n) {return n.value;} + + static type as (value&); + static const_type as (const value&); + + static bool assign (name&); + static void assign (value&, std::string); + static void append (value&, std::string); + + static const build::value_type value_type; + }; + extern const value_type* string_type; + + // dir_path + // template <> - inline const name* value_proxy:: - as<const name*> () const + struct value_traits<dir_path> { - const auto& lv (as<const list_value&> ()); - assert (lv.size () < 2); - return lv.empty () ? nullptr : &lv.front (); - } + 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 bool assign (name&); + static void assign (value&, dir_path); + static void append (value&, dir_path); + static const build::value_type value_type; + }; + + extern const value_type* dir_path_type; + + // name + // template <> - inline const name& value_proxy:: - as<const name&> () const + struct value_traits<name> { - const auto& lv (as<const list_value&> ()); - assert (lv.size () == 1); - return lv.front (); - } + using type = name&; + using const_type = const name&; - template <> - std::string& value_proxy:: - as<std::string&> () const; + static type as (name& n) {return n;} + static const_type as (const name& n) {return n;} - template <> - const std::string& value_proxy:: - as<const std::string&> () const; + static type as (value&); + static const_type as (const value&); - template <> - dir_path& value_proxy:: - as<dir_path&> () const; + static bool assign (name&) {return true;} + static void assign (value&, name); + static void append (value&, name) = delete; - template <> - const dir_path& value_proxy:: - as<const dir_path&> () const; + static const build::value_type value_type; + }; - template <> - bool value_proxy:: - as<bool> () const; + extern const value_type* name_type; + + // vector<T> + // + template <typename T, typename D> + struct vector_value + { + using size_type = typename D::size_type; + + using value_type = typename value_traits<T>::type; + using const_value_type = typename value_traits<T>::const_type; + + template <typename I, typename V, typename R> + 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<T> (I::operator* ());} + pointer operator-> () const {return &as<T> (I::operator* ());} + reference operator[] (difference_type n) const + { + return as<T> (I::operator[] (n)); + } + }; + + // Make iterator the same as const_iterator if our data type is const. + // + using const_iterator = + iterator_impl<names::const_iterator, const T, const_value_type>; + using iterator = typename std::conditional< + std::is_const<D>::value, + const_iterator, + iterator_impl<names::iterator, T, value_type>>::type; + + public: + vector_value& + operator= (std::vector<T> v) {assign (std::move (v)); return *this;} + + vector_value& + assign (std::vector<T>); + + template <typename D1> + vector_value& + assign (const vector_value<T, D1>&); + + template <typename D1> + vector_value& + append (const vector_value<T, D1>&); + + public: + bool empty () const {return d->empty ();} + size_type size () const {return d->size ();} + + value_type operator[] (size_type i) {return as<T> ((*d)[i]);} + const_value_type operator[] (size_type i) const {return as<T> ((*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 (std::nullptr_t): d (nullptr) {} //@@ TMP + + D* d; // names + }; + + template <typename T> + struct value_traits<std::vector<T>> + { + using type = vector_value<T, names>; + using const_type = vector_value<T, const names>; + + static type as (value&); + static const_type as (const value&); + + template <typename V> static void assign (value&, V); + template <typename V> static void append (value&, V); + + static const build::value_type value_type; + }; + + template <typename T, typename D> + struct value_traits<vector_value<T, D>>: value_traits<std::vector<T>> {}; + + using strings_value = vector_value<std::string, names>; + using const_strings_value = vector_value<std::string, const names>; + + extern const value_type* strings_type; // vector<string> aka strings + extern const value_type* dir_paths_type; // vector<dir_path> aka dir_paths + extern const value_type* names_type; // vector<name> aka names + + // map<K, V> + // + template <typename K, typename V, typename D> + struct map_value + { + template <typename F, typename S> + struct pair + { + using first_type = typename std::conditional< + std::is_reference<F>::value, + std::reference_wrapper<typename std::remove_reference<F>::type>, + F>::type; + + using second_type = typename std::conditional< + std::is_reference<S>::value, + std::reference_wrapper<typename std::remove_reference<S>::type>, + S>::type; + + first_type first; + second_type second; + }; + + template <typename I, typename T> + 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<K> (*i_), as<V> (*(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<typename value_traits<K>::const_type, + typename value_traits<V>::type>; + + using const_value_type = pair<typename value_traits<K>::const_type, + typename value_traits<V>::const_type>; + + // Make iterator the same as const_iterator if our data type is const. + // + using const_iterator = + iterator_impl<names::const_iterator, const_value_type>; + using iterator = typename std::conditional< + std::is_const<D>::value, + const_iterator, + iterator_impl<names::iterator, value_type>>::type; + + + public: + map_value& + operator= (std::map<K, V> m) {assign (std::move (m)); return *this;} + + map_value& + assign (std::map<K, V>); + + 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 <typename K, typename V> + struct value_traits<std::map<K, V>> + { + using type = map_value<K, V, names>; + using const_type = map_value<K, V, const names>; + + static type as (value&); + static const_type as (const value&); + + template <typename M> static void assign (value&, M); + template <typename M> static void append (value&, M); + + static const build::value_type value_type; + }; + + template <typename K, typename V, typename D> + struct value_traits<map_value<K, V, D>>: value_traits<std::map<K, V>> {}; } namespace std @@ -301,15 +614,27 @@ namespace build { // variable_pool // - struct variable_set: std::unordered_set<variable> + using variable_set_base = std::unordered_set<variable>; + struct variable_set: private variable_set_base { - // @@ Need to check/set type? - // const variable& - find (std::string name) {return *emplace (std::move (name)).first;} + find (std::string name, const build::value_type* t = nullptr, char p = '\0') + { + auto r (emplace (std::move (name), t, p)); + const variable& v (*r.first); + + // Update type? + // + if (!r.second && t != nullptr && v.type != t) + { + assert (v.type == nullptr); + const_cast<variable&> (v).type = t; // Ok, not changing the key. + } + + return v; + } - const variable& - insert (variable v) {return *emplace (std::move (v)).first;} + using variable_set_base::clear; }; extern variable_set variable_pool; @@ -318,40 +643,92 @@ namespace build // struct variable_map { - using map_type = butl::prefix_map<variable_cref, value_ptr, '.'>; + using map_type = butl::prefix_map<variable_cref, value, '.'>; using size_type = map_type::size_type; - using const_iterator = map_type::const_iterator; - const value_ptr* + template <typename I> + struct iterator_adapter: I + { + iterator_adapter () = default; + iterator_adapter (const I& i): I (i) {} + typename I::reference operator* () const; + typename I::pointer operator-> () const; + }; + + using const_iterator = iterator_adapter<map_type::const_iterator>; + + const value* find (const variable& var) const { auto i (m_.find (var)); - return i != m_.end () ? &i->second : nullptr; + 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) + build::assign (const_cast<value&> (*r), var.type, var); + + return r; } - value_proxy + 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) + build::assign (*r, var.type, var); + + return r; + } + + lookup<const value> operator[] (const variable& var) const { - return value_proxy (find (var), this); + return lookup<const value> (find (var), this); } - value_proxy + lookup<const value> operator[] (const std::string& name) const { return operator[] (variable_pool.find (name)); } - // The second member in the pair indicates whether new (NULL) - // value was set. + // Non-const lookup. Only exposed on the map directly. // - std::pair<value_proxy, bool> + lookup<value> + operator[] (const variable& var) + { + return lookup<value> (find (var), this); + } + + lookup<value> + operator[] (const std::string& name) + { + return operator[] (variable_pool.find (name)); + } + + // The second member in the pair indicates whether the new + // value (which will be NULL) was assigned. + // + std::pair<std::reference_wrapper<value>, bool> assign (const variable& var) { - auto r (m_.emplace (var, value_ptr ())); - return std::make_pair (value_proxy (&r.first->second, this), r.second); + 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) + build::assign (v, var.type, var); + + return std::make_pair (std::reference_wrapper<value> (v), r.second); } - std::pair<value_proxy, bool> + std::pair<std::reference_wrapper<value>, bool> assign (const std::string& name) { return assign (variable_pool.find (name)); @@ -360,7 +737,9 @@ namespace build std::pair<const_iterator, const_iterator> find_namespace (const std::string& ns) const { - return m_.find_prefix (variable_pool.find (ns)); + auto r (m_.find_prefix (variable_pool.find (ns))); + return std::make_pair (const_iterator (r.first), + const_iterator (r.second)); } const_iterator @@ -381,17 +760,17 @@ namespace build // Target type/pattern-specific variables. // + // @@ In quite a few places we assume that we can store a reference + // to the returned value (e.g., install::lookup_install()). If + // we "instantiate" the value on the fly, then we will need to + // consider its lifetime. + // using variable_pattern_map = std::map<std::string, variable_map>; using variable_type_map = std::map<std::reference_wrapper<const target_type>, variable_pattern_map>; - - //@@ In quite a few places we assume that we can store a reference - // to the returned value (e.g., install::lookup_install()). If - // we "instantiate" the value on the fly, then we will need to - // consider its lifetime. - } #include <build/variable.ixx> +#include <build/variable.txx> #endif // BUILD_VARIABLE diff --git a/build/variable.cxx b/build/variable.cxx index eab77e5..d215b02 100644 --- a/build/variable.cxx +++ b/build/variable.cxx @@ -4,86 +4,317 @@ #include <build/variable> +#include <iterator> // make_move_iterator() + #include <build/utility> +#include <build/diagnostics> using namespace std; namespace build { - variable_set variable_pool; + // value + // + void + assign (value& v, const value_type* t, const variable& var) + { + if (v.type == nullptr) + { + v.type = t; + + if (v && t->assign != nullptr) + v.state_ = t->assign (v.data_, var) + ? value::state_type::filled + : value::state_type::empty; + } + 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:: + operator= (value&& v) + { + 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_); + + return *this; + } + + value& value:: + append (value v, const variable& var) + { + assert (type == v.type); + append (move (v.data_), var); + return *this; + } + + void value:: + append (names v, const variable& var) + { + // Treat append to NULL as assign. + // + if (!null () && type != nullptr && type->append != nullptr) + { + state_ = type->append (data_, move (v), var) + ? state_type::filled + : state_type::empty; + return; + } + + if (data_.empty ()) + data_ = move (v); + else + data_.insert (data_.end (), + make_move_iterator (v.begin ()), + make_move_iterator (v.end ())); - // value_proxy + state_ = (type != nullptr && type->assign != nullptr + ? type->assign (data_, var) + : !data_.empty ()) + ? state_type::filled + : state_type::empty; + } + + // bool value // - template <> - string& value_proxy:: - as<string&> () const - { - list_value& lv (as<list_value&> ()); - assert (lv.size () == 1); - name& n (lv.front ()); - assert (n.simple ()); - return n.value; + bool value_traits<bool>:: + assign (name& n) + { + if (n.simple ()) + { + const string& s (n.value); + + if (s == "true" || s == "false") + return true; + } + + return false; } - template <> - const string& value_proxy:: - as<const string&> () const + static bool + bool_assign (names& v, const variable& var) { - const list_value& lv (as<list_value&> ()); - assert (lv.size () < 2); + // Verify the value is either "true" or "false". + // + if (v.size () == 1) + { + name& n (v.front ()); - if (lv.empty ()) - return empty_string; + if (value_traits<bool>::assign (n)) + return true; + } + + fail << "invalid bool variable '" << var.name << "' value '" << v << "'"; + return false; + } + + static bool + bool_append (names& v, names a, const variable& var) + { + // Translate append to OR. + // + bool_assign (a, var); // Verify "true" or "false". - const name& n (lv.front ()); + if (a.front ().value[0] == 't' && v.front ().value[0] == 'f') + v = move (a); - assert (n.simple ()); - return n.value; + return true; } - template <> - dir_path& value_proxy:: - as<dir_path&> () const + const value_type value_traits<bool>::value_type { - list_value& lv (as<list_value&> ()); - assert (lv.size () == 1); - name& n (lv.front ()); - assert (n.directory ()); - return n.dir; + "bool", + &bool_assign, + &bool_append + }; + + const value_type* bool_type = &value_traits<bool>::value_type; + + // string value + // + bool value_traits<string>:: + assign (name& n) + { + // Convert directory to string. + // + if (n.directory ()) + { + n.value = std::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 += '/'; + } + + return n.simple (); } - template <> - const dir_path& value_proxy:: - as<const dir_path&> () const + static bool + string_assign (names& v, const variable& var) { - const list_value& lv (as<list_value&> ()); - assert (lv.size () < 2); + // 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 (lv.empty ()) - return empty_dir_path; + if (value_traits<string>::assign (n)) + return !n.value.empty (); + } + + fail << "invalid string variable '" << var.name << "' value '" << v << "'"; + return false; + } - const name& n (lv.front ()); + 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 (n.empty ()) - return empty_dir_path; + if (v.front ().value.empty ()) + v = move (a); + else + v.front ().value += a.front ().value; - assert (n.directory ()); - return n.dir; + return !v.front ().value.empty (); } - template <> - bool value_proxy:: - as<bool> () const + const value_type value_traits<string>::value_type + { + "string", + &string_assign, + &string_append + }; + + const value_type* string_type = &value_traits<string>::value_type; + + // dir_path value + // + bool value_traits<dir_path>:: + assign (name& n) { - const list_value& lv (as<const list_value&> ()); - assert (lv.size () == 1); - const name& n (lv.front ()); - assert (n.simple ()); - if (n.value == "true") + if (n.directory ()) return true; - else if (n.value == "false") + + if (n.simple ()) + { + try + { + n.dir = n.empty () ? dir_path () : dir_path (move (n.value)); + n.value.clear (); + return true; + } + catch (const invalid_path&) {} // Fall through. + } + + return false; + } + + static bool + dir_path_assign (names& v, const variable& var) + { + // 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 ()); + + if (value_traits<dir_path>::assign (n)) + return !n.dir.empty (); + } + + fail << "invalid dir_path variable '" << var.name << "' " + << "value '" << v << "'"; + return false; + } + + static bool + dir_path_append (names& v, names a, const variable& var) + { + // 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 - assert (false); // Bool value should be 'true' or 'false'. + fail << "append of absolute path '" << d << "' to dir_path variable " + << var.name; + + return false; } + + const value_type value_traits<dir_path>::value_type + { + "dir_path", + &dir_path_assign, + &dir_path_append + }; + + const value_type* dir_path_type = &value_traits<dir_path>::value_type; + + // name value + // + static bool + name_assign (names& v, const variable& var) + { + // Verify the value is a single name. + // + if (v.size () == 1) + return v.front ().empty (); + + fail << "invalid string variable '" << var.name << "' value '" << v << "'"; + return false; + } + + static bool + name_append (names&, names, const variable& var) + { + fail << "append to name variable '" << var.name << "'"; + return false; + } + + const value_type value_traits<name>::value_type + { + "name", + &name_assign, + &name_append + }; + + const value_type* name_type = &value_traits<name>::value_type; + + // vector<T> value + // + const value_type* strings_type = &value_traits<strings>::value_type; + const value_type* dir_paths_type = &value_traits<dir_paths>::value_type; + const value_type* names_type = &value_traits<names>::value_type; + + // variable_set + // + variable_set variable_pool; } diff --git a/build/variable.ixx b/build/variable.ixx index b97815e..16ea7c0 100644 --- a/build/variable.ixx +++ b/build/variable.ixx @@ -4,108 +4,395 @@ namespace build { - // value_proxy + // value // - inline bool value_proxy:: - empty () const {return as<const list_value&> ().empty ();} + template <typename T> + inline void + assign (value& v, const variable& var) + { + auto t (&value_traits<T>::value_type); + + if (v.type != t) + assign (v, t, var); + } + + template <typename T> + inline typename value_traits<T>::type + as (value& v) + { + return value_traits<T>::as (v); + } + + template <typename T> + inline typename value_traits<T>::const_type + as (const value& v) + { + return value_traits<T>::as (v); + } + + template <typename T> + inline bool + assign (name& n) + { + return value_traits<T>::assign (n); + } + + template <typename T> + inline typename value_traits<T>::type + as (name& n) + { + return value_traits<T>::as (n); + } - inline const value_proxy& value_proxy:: - operator= (value_ptr v) const + template <typename T> + inline typename value_traits<T>::const_type + as (const name& n) { - *p = std::move (v); + return value_traits<T>::as (n); + } + + template <typename T> + inline value& value:: + operator= (T v) + { + value_traits<T>::assign (*this, std::move (v)); + return *this; + } + + template <typename T> + inline value& value:: + operator+= (T v) + { + value_traits<T>::append (*this, std::move (v)); return *this; } - inline const value_proxy& value_proxy:: - operator= (const value_proxy& v) const + inline void value:: + assign (names v, const variable& var) + { + data_ = std::move (v); + state_ = (type != nullptr && type->assign != nullptr + ? type->assign (data_, var) + : !data_.empty ()) + ? state_type::filled + : state_type::empty; + } + + // bool value + // + inline bool_value<name> value_traits<bool>:: + as (value& v) + { + assert (v.type == bool_type); + return bool_value<name> (v.data_.front ()); + } + + inline bool_value<const name> value_traits<bool>:: + as (const value& v) { - if (this != &v) + assert (v.type == bool_type); + return bool_value<const name> (v.data_.front ()); + } + + inline void value_traits<bool>:: + assign (value& v, bool x) + { + if (v.null ()) { - if (v) - *p = v.as<const value&> ().clone (); - else - p->reset (); + if (v.type == nullptr) + v.type = bool_type; + v.data_.emplace_back (name ()); + v.state_ = value::state_type::empty; } - return *this; + as (v) = x; + v.state_ = value::state_type::filled; } - inline const value_proxy& value_proxy:: - operator= (list_value v) const + inline void value_traits<bool>:: + append (value& v, bool x) { - if (*p == nullptr) - p->reset (new list_value (std::move (v))); + if (v.null ()) + assign (v, x); else - //@@ Assuming it is a list_value. - // - as<list_value&> () = std::move (v); + as (v) += x; // Cannot be empty. + } - return *this; + // string value + // + inline std::string& value_traits<std::string>:: + as (value& v) + { + assert (v.type == string_type); + return v.data_.front ().value; } - inline const value_proxy& value_proxy:: - operator= (std::string v) const + inline const std::string& value_traits<std::string>:: + as (const value& v) { - // In most cases this is used to initialize a new variable, so - // don't bother trying to optimize for the case where p is not - // NULL. - // - p->reset (new list_value (std::move (v))); + assert (v.type == string_type); + return v.data_.front ().value; + } + + inline void value_traits<std::string>:: + assign (value& v, std::string x) + { + if (v.null ()) + { + if (v.type == nullptr) + v.type = string_type; + v.data_.emplace_back (name ()); + v.state_ = value::state_type::empty; + } + + v.state_ = (as (v) = std::move (x)).empty () + ? value::state_type::empty + : value::state_type::filled; + } + + inline void value_traits<std::string>:: + append (value& v, std::string x) + { + if (v.null ()) + assign (v, std::move (x)); + else + v.state_ = (as (v) += std::move (x)).empty () + ? value::state_type::empty + : value::state_type::filled; + } + + // dir_path value + // + inline dir_path& value_traits<dir_path>:: + as (value& v) + { + assert (v.type == dir_path_type); + return v.data_.front ().dir; + } + + inline const dir_path& value_traits<dir_path>:: + as (const value& v) + { + assert (v.type == dir_path_type); + return v.data_.front ().dir; + } + + inline void value_traits<dir_path>:: + assign (value& v, dir_path x) + { + if (v.null ()) + { + if (v.type == nullptr) + v.type = dir_path_type; + v.data_.emplace_back (name ()); + v.state_ = value::state_type::empty; + } + + v.state_ = (as (v) = std::move (x)).empty () + ? value::state_type::empty + : value::state_type::filled; + } + + inline void value_traits<dir_path>:: + append (value& v, dir_path x) + { + if (v.null ()) + assign (v, std::move (x)); + else + v.state_ = (as (v) /= std::move (x)).empty () + ? value::state_type::empty + : value::state_type::filled; + } + + // name value + // + inline name& value_traits<name>:: + as (value& v) + { + assert (v.type == name_type); + return v.data_.front (); + } + + inline const name& value_traits<name>:: + as (const value& v) + { + assert (v.type == name_type); + return v.data_.front (); + } + + inline void value_traits<name>:: + assign (value& v, name x) + { + if (v.null ()) + { + if (v.type == nullptr) + v.type = name_type; + v.data_.emplace_back (name ()); + v.state_ = value::state_type::empty; + } + + v.state_ = (as (v) = std::move (x)).empty () + ? value::state_type::empty + : value::state_type::filled; + } + + // vector<T> value + // + template <typename T, typename D> + inline vector_value<T, D>& vector_value<T, D>:: + assign (std::vector<T> v) + { + d->clear (); + d->insert (d->end (), + std::make_move_iterator (v.begin ()), + std::make_move_iterator (v.end ())); return *this; } - inline const value_proxy& value_proxy:: - operator= (dir_path v) const + template <typename T, typename D> + template <typename D1> + inline vector_value<T, D>& vector_value<T, D>:: + assign (const vector_value<T, D1>& v) { - p->reset (new list_value (std::move (v))); + d->clear (); + d->insert (d->end (), v.begin (), v.end ()); return *this; } - inline const value_proxy& value_proxy:: - operator= (std::nullptr_t) const + template <typename T, typename D> + template <typename D1> + inline vector_value<T, D>& vector_value<T, D>:: + append (const vector_value<T, D1>& v) { - p->reset (); + d->insert (d->end (), v.begin (), v.end ()); return *this; } - inline const value_proxy& value_proxy:: - operator+= (const value_proxy& v) const + template <typename T> + inline vector_value<T, names> value_traits<std::vector<T>>:: + as (value& v) + { + assert (v.type == &value_traits<std::vector<T>>::value_type); + return vector_value<T, names> (v.data_); + } + + template <typename T> + inline vector_value<T, const names> value_traits<std::vector<T>>:: + as (const value& v) { - if (v && this != &v) + assert (v.type == &value_traits<std::vector<T>>::value_type); + return vector_value<T, const names> (v.data_); + } + + template <typename T> + template <typename V> + inline void value_traits<std::vector<T>>:: + assign (value& v, V x) + { + if (v.null ()) { - if (*p == nullptr) - *this = v; - else - //@@ Assuming it is a list_value. - // - *this += v.as<const list_value&> (); + if (v.type == nullptr) + v.type = &value_traits<std::vector<T>>::value_type; + v.state_ = value::state_type::empty; } - return *this; + v.state_ = (as (v).assign (std::move (x))).empty () + ? value::state_type::empty + : value::state_type::filled; } - inline const value_proxy& value_proxy:: - operator+= (const list_value& v) const + template <typename T> + template <typename V> + inline void value_traits<std::vector<T>>:: + append (value& v, V x) { - if (*p == nullptr) - *this = v; + if (v.null ()) + assign (v, std::move (x)); else + v.state_ = (as (v).append (std::move (x))).empty () + ? value::state_type::empty + : value::state_type::filled; + } + + // map<K, V> value + // + template <typename K, typename V> + inline map_value<K, V, names> value_traits<std::map<K, V>>:: + as (value& v) + { + assert ((v.type == &value_traits<std::map<K, V>>::value_type)); + return map_value<K, V, names> (v.data_); + } + + template <typename K, typename V> + inline map_value<K, V, const names> value_traits<std::map<K, V>>:: + as (const value& v) + { + assert ((v.type == &value_traits<std::map<K, V>>::value_type)); + return map_value<K, V, const names> (v.data_); + } + + template <typename K, typename V> + template <typename M> + inline void value_traits<std::map<K, V>>:: + assign (value& v, M x) + { + if (v.null ()) { - list_value& lv (as<list_value&> ()); - lv.insert (lv.end (), v.begin (), v.end ()); + if (v.type == nullptr) + v.type = &value_traits<std::map<K, V>>::value_type; + v.state_ = value::state_type::empty; } - return *this; + v.state_ = (as (v).assign (std::move (x))).empty () + ? value::state_type::empty + : value::state_type::filled; } - inline const value_proxy& value_proxy:: - operator+= (std::string v) const + template <typename K, typename V> + template <typename M> + inline void value_traits<std::map<K, V>>:: + append (value& v, M x) { - if (*p == nullptr) - *this = v; + if (v.null ()) + assign (v, std::move (x)); else - as<list_value&> ().emplace_back (std::move (v)); + v.state_ = (as (v).append (std::move (x))).empty () + ? value::state_type::empty + : value::state_type::filled; + } - return *this; + // variable_map::iterator_adapter + // + template <typename I> + inline typename I::reference variable_map::iterator_adapter<I>:: + operator* () const + { + auto& r (I::operator* ()); + const variable& var (r.first); + auto& val (r.second); + + // First access after being assigned a type? + // + if (var.type != nullptr && val.type != var.type) + build::assign (const_cast<value&> (val), var.type, var); + + return r; + } + + template <typename I> + inline typename I::pointer variable_map::iterator_adapter<I>:: + operator-> () const + { + auto p (I::operator-> ()); + const variable& var (p->first); + auto& val (p->second); + + // First access after being assigned a type? + // + if (var.type != nullptr && val.type != var.type) + build::assign (const_cast<value&> (val), var.type, var); + + return p; } } diff --git a/build/variable.txx b/build/variable.txx new file mode 100644 index 0000000..1b32584 --- /dev/null +++ b/build/variable.txx @@ -0,0 +1,160 @@ +// file : build/variable.txx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include <iterator> // make_move_iterator() + +#include <build/diagnostics> + +namespace build +{ + // vector<T> value + // + template <typename T> + bool + vector_assign (names& v, const variable& var) + { + // Verify each element has valid value of T. + // + for (name& n: v) + { + if (!assign<T> (n)) + fail << "invalid " << value_traits<T>::value_type.name << " element " + << "'" << n << "' in variable '" << var.name << "'"; + } + + return !v.empty (); + } + + template <typename T> + bool + vector_append (names& v, names a, const variable& var) + { + // Verify that what we are appending is valid. + // + vector_assign<T> (a, var); + + if (v.empty ()) + v = move (a); + else + v.insert (v.end (), + std::make_move_iterator (a.begin ()), + std::make_move_iterator (a.end ())); + + return !v.empty (); + } + + template <typename T> + const value_type value_traits<std::vector<T>>::value_type + { + value_traits<T>::value_type.name + 's', + &vector_assign<T>, + &vector_append<T> + }; + + // map<K, V> value + // + template <typename K, typename V, typename D> + map_value<K, V, D>& map_value<K, V, D>:: + assign (std::map<K, V> m) + { + d->clear (); + for (auto& p: m) + { + d->emplace_back (p.first); // Const, can't move. + d->back ().pair = '='; + d->emplace_back (std::move (p.second)); + } + + return *this; + } + + template <typename K, typename V, typename D> + auto map_value<K, V, D>:: + find (const K& k) -> iterator + { + // @@ Scan backwards to handle duplicates. + // + for (auto i (d->rbegin ()); i != d->rend (); ++i) + if (as<K> (*++i) == k) + return iterator (--(i.base ())); + + return end (); + } + + template <typename K, typename V, typename D> + auto map_value<K, V, D>:: + find (const K& k) const -> const_iterator + { + // @@ Scan backwards to handle duplicates. + // + for (auto i (d->rbegin ()); i != d->rend (); ++i) + if (as<K> (*++i) == k) + return const_iterator (--(i.base ())); + + return end (); + } + + template <typename K, typename V> + bool + map_assign (names& v, 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 == '\0') + fail << value_traits<std::map<K, V>>::value_type.name << " key-value " + << "pair expected instead of '" << *i << "' " + << "in variable '" << var.name << "'"; + + if (!assign<K> (*i)) + fail << "invalid " << value_traits<K>::value_type.name << " key " + << "'" << *i << "' in variable '" << var.name << "'"; + + ++i; // Got to have the second half of the pair. + + if (!assign<V> (*i)) + fail << "invalid " << value_traits<V>::value_type.name << " value " + << "'" << *i << "' in variable '" << var.name << "'"; + } + + //@@ When doing sorting, note that assign() can convert the + // value. + + //@@ Is sorting really the right trade-off (i.e., insertion + // vs search)? Perhaps linear search is ok? + + return !v.empty (); + } + + template <typename K, typename V> + bool + map_append (names& v, names a, const variable& var) + { + //@@ Not weeding out duplicates. + + // Verify that what we are appending is valid. + // + map_assign<K, V> (a, var); + + if (v.empty ()) + v = move (a); + else + v.insert (v.end (), + std::make_move_iterator (a.begin ()), + std::make_move_iterator (a.end ())); + + return !v.empty (); + } + + template <typename K, typename V> + const value_type value_traits<std::map<K, V>>::value_type + { + value_traits<K>::value_type.name + '_' + + value_traits<V>::value_type.name + "_map", + &map_assign<K, V>, + &map_append<K, V> + }; +} diff --git a/tests/amalgam/config/buildfile b/tests/amalgam/config/buildfile index 2f01ba3..56f7f9a 100644 --- a/tests/amalgam/config/buildfile +++ b/tests/amalgam/config/buildfile @@ -1,3 +1,3 @@ -d = #t/ #l/ +d = #t/ l/ .: $d include $d diff --git a/tests/amalgam/test/buildfile b/tests/amalgam/test/buildfile index 3fadac3..5440dbf 100644 --- a/tests/amalgam/test/buildfile +++ b/tests/amalgam/test/buildfile @@ -3,6 +3,6 @@ using cxx hxx.ext = hxx cxx.ext = cxx -import libs += amalgam-libtest +import libs = amalgam-libtest%lib{test} exe{driver}: cxx{driver} $libs |