From 26146d391f179dd9e4e5e1f70a52ba99d6a0847d Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Fri, 1 Apr 2016 09:49:18 +0200 Subject: Get part of variable override implementation --- build2/b.cxx | 7 -- build2/context.cxx | 17 +---- build2/scope | 21 +++++- build2/scope.cxx | 214 +++++++++++++++++++++++++++++++++++++++++++++++++++- build2/target.cxx | 33 +++++--- build2/variable | 17 ++++- build2/variable.cxx | 4 + 7 files changed, 276 insertions(+), 37 deletions(-) diff --git a/build2/b.cxx b/build2/b.cxx index 219f4eb..44fa032 100644 --- a/build2/b.cxx +++ b/build2/b.cxx @@ -820,13 +820,6 @@ main (int argc, char* argv[]) value& v (p.first); v.assign (names (o.val), o.var); // Original var for diagnostics. - - // Also make sure the original variable itself is set (to at least - // NULL) so that lookup finds something if nobody actually sets it - // down the line. - // - rs.vars.assign (o.var); - first = false; } diff --git a/build2/context.cxx b/build2/context.cxx index dbfffdb..21d683b 100644 --- a/build2/context.cxx +++ b/build2/context.cxx @@ -47,6 +47,8 @@ namespace build2 variable_overrides vos; + variable_override_cache.clear (); + targets.clear (); scopes.clear (); var_pool.clear (); @@ -108,13 +110,6 @@ namespace build2 const variable& var (var_pool.find (t.value)); const string& n (var.name); - // The first variable in the override list is always the cache. Note - // that we might already be overridden by an earlier cmd line var. - // - if (var.override == nullptr) - var.override.reset (new variable { - n + ".__cache", nullptr, nullptr, variable_visibility::normal}); - // Calculate visibility and kind. // variable_visibility v (c == '%' @@ -125,7 +120,7 @@ namespace build2 // We might already have a variable for this kind of override. // - const variable* o (var.override.get ()); + const variable* o (&var); // Step behind. for (; o->override != nullptr; o = o->override.get ()) { if (o->override->visibility == v && @@ -159,12 +154,6 @@ namespace build2 value& v (p.first); v.assign (move (val), var); // Original var for diagnostics. - - // Also make sure the original variable itself is set (to at least - // NULL) so that lookup finds something if nobody actually sets it - // down the line. - // - gs.vars.assign (var); } else vos.emplace_back (variable_override {var, *o, move (val)}); diff --git a/build2/scope b/build2/scope index 6050bf8..b4c4e96 100644 --- a/build2/scope +++ b/build2/scope @@ -128,9 +128,24 @@ namespace build2 } lookup - find (const variable&, - const target_type* tt, const string* tn, - const target_type* gt = nullptr, const string* gn = nullptr) const; + find (const variable& var, const target_type* tt, const string* tn) const + { + lookup l (find_original (var, tt, tn)); + return var.override == nullptr + ? l + : find_override (var, move (l), false); + } + + // Implementation details (used by target lookup). + // + lookup + find_original ( + const variable&, + const target_type* tt, const string* tn, + const target_type* gt = nullptr, const string* gn = nullptr) const; + + lookup + find_override (const variable&, lookup&& original, bool orig_tspec) const; // Return a value suitable for assignment (or append if you only // want to append to the value from this scope). If the variable diff --git a/build2/scope.cxx b/build2/scope.cxx index 77efccd..5b1e3fd 100644 --- a/build2/scope.cxx +++ b/build2/scope.cxx @@ -13,9 +13,9 @@ namespace build2 // scope // lookup scope:: - find (const variable& var, - const target_type* tt, const string* tn, - const target_type* gt, const string* gn) const + find_original (const variable& var, + const target_type* tt, const string* tn, + const target_type* gt, const string* gn) const { for (const scope* s (this); s != nullptr; ) { @@ -54,6 +54,214 @@ namespace build2 return lookup (); } + lookup scope:: + find_override (const variable& var, lookup&& orig, bool tspec) const + { + // Normally there would be no overrides and if there are, there will only + // be a few of them. As a result, here we concentrate on keeping the logic + // as straightforward as possible without trying to optimize anything. + // + assert (var.override != nullptr); + + // The first step is to find out where our cache will reside. After some + // meditation it becomes clear it should be next to the innermost (scope- + // wise) value (override or original) that contributes to the end result. + // + // One special case is if the original is target-specific, which is the + // most innermost (or is it inermostest). + // + const variable_map* vars (tspec ? orig.vars : nullptr); + + const scope* s; + + // Return override value if it is present, applies, and ends with suffix. + // + auto find = [&vars, &s, this] (const variable* o, const char* sf = nullptr) + -> lookup + { + if (s != nullptr && o->name.rfind (sf) == string::npos) + return lookup (); + + // Next see if it would apply. If there is nothing "inner", then any + // override will still be "visible". + // + if (vars != nullptr) + { + switch (o->visibility) + { + case variable_visibility::scope: + { + // Does not apply if the innermost value is not in this scope. + // + if (vars != &s->vars) + return lookup (); + + break; + } + case variable_visibility::project: + { + // Does not apply if in a different project. + // + if (root_scope () != s->root_scope ()) + return lookup (); + + break; + } + case variable_visibility::normal: + break; + } + } + + return lookup (s->vars.find (*o), &s->vars); + }; + + // Return true if a value is from this scope (either target-specific or + // normal). + // + auto test = [&s] (const lookup& l) -> bool + { + for (auto& p1: s->target_vars) + for (auto& p2: p1.second) + if (l.vars == &p2.second) + return true; + + return l.vars == &s->vars; + }; + + // While looking for the cache we can also detect if none of the overrides + // apply. In this case the result is simply the original value (if any). + // + bool apply (false); + + for (s = this; s != nullptr; s = s->parent_scope ()) + { + // If we are still looking for the cache, see if the original comes from + // this scope. We check this before the overrides since it can come from + // the target type/patter-specific variables, which is "more inner" than + // normal scope variables (see find_original()). + // + if (vars == nullptr && orig && test (orig)) + vars = orig.vars; + + for (const variable* o (var.override.get ()); + o != nullptr; + o = o->override.get ()) + { + if (auto l = find (o)) + { + if (vars == nullptr) + vars = l.vars; + + apply = true; + break; + } + } + + // If we found the cache and at least one override applies, then we can + // stop. + // + if (vars != nullptr && apply) + break; + } + + if (!apply) + return move (orig); + + assert (vars != nullptr); + + // Implementing proper caching is tricky so for now we are going to re- + // calculate the value every time. Later, the plan is to use value + // versioning (incremented on every update) to detect stem value changes. + // We also need to watch out for the change of the stem itself in addition + // to its value (think of a new variable set since last lookup which is + // now a new stem). + // + // @@ MT + // + variable_override_value& cache (variable_override_cache[vars]); + + // Now find our "stem", that is the value to which we will be appending + // suffixes and prepending prefixes. This is either the original or the + // __override, depending on which one is the innermost. We may also not + // have one at all. + // + lookup stem (tspec ? orig : lookup ()); + + for (s = this; s != nullptr; s = s->parent_scope ()) + { + // First check if the original is from this scope. + // + if (orig && test (orig)) + { + stem = orig; + break; + } + + // Then look for an __override that applies. + // + for (const variable* o (var.override.get ()); + o != nullptr; + o = o->override.get ()) + { + if ((stem = find (o, ".__override"))) + break; + } + + if (stem) + break; + } + + // If there is a stem, set it as the initial value of the cache. + // Otherwise, start with a NULL value. + // + if (stem) + { + cache.value = *stem; + cache.stem_vars = stem.vars; + } + else + { + cache.value = nullptr; + cache.stem_vars = nullptr; // No stem. + } + + // Typify the cache value. If the stem is the original, then the type + // would get propagated automatically. But the stem could also be the + // override, which is kept untyped. Or the stem might not be there at all + // while we still need to apply prefixes/suffixes in the type-aware way. + // + if (cache.value.type != var.type) + typify (cache.value, *var.type, var); + + // Now apply override prefixes and suffixes. + // + for (s = this; s != nullptr; s = s->parent_scope ()) + { + for (const variable* o (var.override.get ()); + o != nullptr; + o = o->override.get ()) + { + // Note that we keep override values as untyped names even if the + // variable itself is typed. We also pass the original variable for + // diagnostics. + // + if (auto l = find (o, ".__prefix")) + { + cache.value.prepend (names (cast (l)), var); + } + else if (auto l = find (o, ".__suffix")) + { + cache.value.append (names (cast (l)), var); + } + } + } + + // Use the location of the cache (innermost value that contributes) as + // the location of the result. + // + return lookup (&cache.value, vars); + } + value& scope:: append (const variable& var) { diff --git a/build2/target.cxx b/build2/target.cxx index e945d6d..f1f0637 100644 --- a/build2/target.cxx +++ b/build2/target.cxx @@ -117,23 +117,38 @@ namespace build2 lookup target:: operator[] (const variable& var) const { + lookup l; + bool tspec (false); + + scope& s (base_scope ()); + if (auto p = vars.find (var)) - return lookup (p, &vars); + { + tspec = true; + l = lookup (p, &vars); + } - if (group != nullptr) + if (!l && group != nullptr) { if (auto p = group->vars.find (var)) - return lookup (p, &group->vars); + { + tspec = true; + l = lookup (p, &group->vars); + } } // Delegate to scope's find(). // - return base_scope ().find ( - var, - &type (), - &name, - group != nullptr ? &group->type () : nullptr, - group != nullptr ? &group->name : nullptr); + if (!l) + l = s.find_original (var, + &type (), + &name, + group != nullptr ? &group->type () : nullptr, + group != nullptr ? &group->name : nullptr); + + return var.override == nullptr + ? l + : s.find_override (var, move (l), tspec); } value& target:: diff --git a/build2/variable b/build2/variable index 15f9c47..d22d6da 100644 --- a/build2/variable +++ b/build2/variable @@ -79,6 +79,10 @@ namespace build2 // // @@ Document override semantics. // + // Note that we don't propagate variable type to override variables and we + // keep override values as untyped names. So they get "typed" when they are + // applied. + // struct variable { string name; @@ -111,7 +115,7 @@ namespace build2 // Creation. A newly created value is NULL and can be reset back to NULL // by assigning nullptr. Values can be copied and copy-assigned. Note that - // for assignment, the values' types should be the same of LHS should be + // for assignment, the values' types should be the same or LHS should be // untyped. // // @@ -672,6 +676,17 @@ namespace build2 lookup find (const target_type&, const string& tname, const variable&) const; }; + + // Override cache. + // + struct variable_override_value + { + build2::value value; + const variable_map* stem_vars = nullptr; // NULL means there is no stem. + }; + + extern std::map variable_override_cache; } #include diff --git a/build2/variable.cxx b/build2/variable.cxx index 0870000..2c2dbc2 100644 --- a/build2/variable.cxx +++ b/build2/variable.cxx @@ -703,4 +703,8 @@ namespace build2 return lookup (); } + + // variable_override + // + map variable_override_cache; } -- cgit v1.1