diff options
Diffstat (limited to 'libbuild2/functions-name.cxx')
-rw-r--r-- | libbuild2/functions-name.cxx | 442 |
1 files changed, 348 insertions, 94 deletions
diff --git a/libbuild2/functions-name.cxx b/libbuild2/functions-name.cxx index e821e52..456f85b 100644 --- a/libbuild2/functions-name.cxx +++ b/libbuild2/functions-name.cxx @@ -1,6 +1,8 @@ // file : libbuild2/functions-name.cxx -*- C++ -*- // license : MIT; see accompanying LICENSE file +#include <libbuild2/functions-name.hxx> + #include <libbuild2/scope.hxx> #include <libbuild2/function.hxx> #include <libbuild2/variable.hxx> @@ -10,39 +12,173 @@ using namespace std; namespace build2 { + extern bool + functions_sort_flags (optional<names>); // functions-builtin.cxx + // Convert name to target'ish name (see below for the 'ish part). Return // raw/unprocessed data in case this is an unknown target type (or called - // out of scope). See scope::find_target_type() for details. + // out of scope). See scope::find_target_type() for details. Allow out- + // qualified names (out is discarded). // - static pair<name, optional<string>> - to_target_name (const scope* s, name&& n) + static pair<const target_type*, optional<string>> + to_target_type (const scope* s, name& n, const name& o = name ()) { - optional<string> e; + if (n.pair && !o.directory ()) + fail << "name pair in names"; - if (s != nullptr) - { - auto rp (s->find_target_type (n, location ())); + return s != nullptr + ? s->find_target_type (n, location ()) + : pair<const target_type*, optional<string>> {nullptr, nullopt}; + } - if (rp.first != nullptr) - n.type = rp.first->name; + static pair<name, optional<string>> + to_target_name (const scope* s, name&& n, const name& o = name ()) + { + auto rp (to_target_type (s, n, o)); - e = move (rp.second); + if (rp.first != nullptr) + n.type = rp.first->name; + + if (n.value.empty () && (n.type == "dir" || n.type == "fsdir")) + { + n.value = n.dir.leaf ().string (); + n.dir.make_directory (); } - return make_pair (move (n), move (e)); + return make_pair (move (n), move (rp.second)); } - // Note: this helper mey be used by other functions that operate on targets. - // - LIBBUILD2_SYMEXPORT const target& + const target& to_target (const scope& s, name&& n, name&& o) { + // Note: help the user out and search in both out and src like a + // prerequisite. + // if (const target* r = search_existing (n, s, o.dir)) return *r; - fail << "target " - << (n.pair ? names {move (n), move (o)} : names {move (n)}) - << " not found" << endf; + // Inside recipes we don't treat `{}` as special so a literal target name + // will have no type and won't be found, which is confusing as hell. + // + bool typed (n.typed ()); + + diag_record dr (fail); + + dr << "target " + << (n.pair ? names {move (n), move (o)} : names {move (n)}) + << " not found"; + + if (!typed) + dr << info << "wrap it in ([names] ...) if this is literal target name " + << "specified inside recipe"; + + dr << endf; + } + + const target& + to_target (const scope& s, names&& ns) + { + assert (ns.size () == (ns[0].pair ? 2 : 1)); + + name o; + return to_target (s, move (ns[0]), move (ns[0].pair ? ns[1] : o)); + } + + static bool + is_a (const scope* s, name&& n, const name& o, names&& t) + { + if (s == nullptr) + fail << "name.is_a() called out of scope"; + + string tts (convert<string> (move (t))); + const target_type* tt (s->find_target_type (tts)); + if (tt == nullptr) + fail << "unknown target type " << tts; + + const target_type* ntt (to_target_type (s, n, o).first); + if (ntt == nullptr) + { + // If this is an imported target and the target type is unknown, then + // it cannot possibly match one of the known types. We handle it like + // this instead of failing because the later failure (e.g., as a + // result of this target listed as prerequisite) will have more + // accurate diagnostics. See also filter() below. + // + if (n.proj) + return false; + + fail << "unknown target type " << n.type << " in " << n; + } + + return ntt->is_a (*tt); + } + + static names + filter (const scope* s, names ns, names ts, bool out) + { + if (s == nullptr) + fail << "name." << (out ? "filter_out" : "filter") + << "() called out of scope"; + + small_vector<const target_type*, 1> tts; + for (const name& n: ts) + { + if (!n.simple ()) + fail << "invalid target type name " << n; + + if (n.pair) + fail << "pair in target type name " << n; + + const target_type* tt (s->find_target_type (n.value)); + if (tt == nullptr) + fail << "unknown target type " << n.value; + + tts.push_back (tt); + } + + names r; + for (auto i (ns.begin ()); i != ns.end (); ++i) + { + name& n (*i); + bool p (n.pair); + + // to_target_type() splits the name into the target name and extension. + // While we could try to reconstitute it with combine_name(), there are + // murky corner cases (see the default_extension argument) which won't + // be easy to handle. So let's just make a copy. Looking at the + // implementation of scope::find_target_type(), we can optimize for the + // (common) typed case by only copying the type. + // + name c (n.typed () ? name (n.type, "") : n); + + const target_type* ntt (to_target_type (s, c, p ? *++i : name ()).first); + if (ntt == nullptr) + { + // If this is an imported target and the target type is unknown, then + // it cannot possibly match one of the known types. We handle it like + // this instead of failing because the later failure (e.g., as a + // result of this target listed as prerequisite) will have more + // accurate diagnostics. See also is_a() above. + // + if (!n.proj) + fail << "unknown target type " << n.type << " in " << n; + } + + if (ntt != nullptr + ? (find_if (tts.begin (), tts.end (), + [ntt] (const target_type* tt) + { + return ntt->is_a (*tt); + }) != tts.end ()) != out + : out) + { + r.push_back (move (n)); + if (p) + r.push_back (move (*i)); + } + } + + return r; } void @@ -55,143 +191,261 @@ namespace build2 // on prerequisite names. They also won't always return the same result as // if we were interrogating an actual target (e.g., the directory may be // relative). Plus we now have functions that can only be called on - // targets (see below). + // targets (see functions-target.cxx). // - function_family fn (m, "name"); + function_family f (m, "name"); - fn["name"] += [](const scope* s, name n) + // Note: let's leave this undocumented for now since it's not often needed + // and is a can of worms. + // + // Note that we must handle NULL values (relied upon by the parser + // to provide conversion semantics consistent with untyped values). + // + f["string"] += [](name* n) + { + return n != nullptr ? to_string (move (*n)) : string (); + }; + + // $name(<names>) + // + // Return the name of a target (or a list of names for a list of targets). + // + f["name"] += [](const scope* s, name n) { return to_target_name (s, move (n)).first.value; }; - fn["name"] += [](const scope* s, names ns) + f["name"] += [](const scope* s, names ns) { - return to_target_name (s, convert<name> (move (ns))).first.value; + small_vector<string, 1> r; + + for (auto i (ns.begin ()); i != ns.end (); ++i) + { + name& n (*i); + r.push_back ( + to_target_name (s, move (n), n.pair ? *++i : name ()).first.value); + } + + if (r.size () == 1) + return value (move (r[0])); + + return value (strings (make_move_iterator (r.begin ()), + make_move_iterator (r.end ()))); }; - // Note: returns NULL if extension is unspecified (default) and empty if - // specified as no extension. + // $extension(<name>) + // + // Return the extension of a target. // - fn["extension"] += [](const scope* s, name n) + // Note that this function returns `null` if the extension is unspecified + // (default) and empty string if it's specified as no extension. + // + f["extension"] += [](const scope* s, name n) { return to_target_name (s, move (n)).second; }; - fn["extension"] += [](const scope* s, names ns) + f["extension"] += [](const scope* s, names ns) { - return to_target_name (s, convert<name> (move (ns))).second; + // Note: can't do multiple due to NULL semantics. + // + auto i (ns.begin ()); + + name& n (*i); + const name& o (n.pair ? *++i : name ()); + + if (++i != ns.end ()) + fail << "invalid name value: multiple names"; // Like in convert(). + + return to_target_name (s, move (n), o).second; }; - fn["directory"] += [](const scope* s, name n) + // $directory(<names>) + // + // Return the directory of a target (or a list of directories for a list + // of targets). + // + f["directory"] += [](const scope* s, name n) { return to_target_name (s, move (n)).first.dir; }; - fn["directory"] += [](const scope* s, names ns) + f["directory"] += [](const scope* s, names ns) { - return to_target_name (s, convert<name> (move (ns))).first.dir; + small_vector<dir_path, 1> r; + + for (auto i (ns.begin ()); i != ns.end (); ++i) + { + name& n (*i); + r.push_back ( + to_target_name (s, move (n), n.pair ? *++i : name ()).first.dir); + } + + if (r.size () == 1) + return value (move (r[0])); + + return value (dir_paths (make_move_iterator (r.begin ()), + make_move_iterator (r.end ()))); }; - fn["target_type"] += [](const scope* s, name n) + // $target_type(<names>) + // + // Return the target type name of a target (or a list of target type names + // for a list of targets). + // + f["target_type"] += [](const scope* s, name n) { return to_target_name (s, move (n)).first.type; }; - fn["target_type"] += [](const scope* s, names ns) + f["target_type"] += [](const scope* s, names ns) { - return to_target_name (s, convert<name> (move (ns))).first.type; + small_vector<string, 1> r; + + for (auto i (ns.begin ()); i != ns.end (); ++i) + { + name& n (*i); + r.push_back ( + to_target_name (s, move (n), n.pair ? *++i : name ()).first.type); + } + + if (r.size () == 1) + return value (move (r[0])); + + return value (strings (make_move_iterator (r.begin ()), + make_move_iterator (r.end ()))); }; - // Note: returns NULL if no project specified. + // $project(<name>) + // + // Return the project of a target or `null` if not project-qualified. // - fn["project"] += [](const scope* s, name n) + f["project"] += [](const scope* s, name n) { return to_target_name (s, move (n)).first.proj; }; - fn["project"] += [](const scope* s, names ns) + f["project"] += [](const scope* s, names ns) { - return to_target_name (s, convert<name> (move (ns))).first.proj; + // Note: can't do multiple due to NULL semantics. + // + auto i (ns.begin ()); + + name& n (*i); + const name& o (n.pair ? *++i : name ()); + + if (++i != ns.end ()) + fail << "invalid name value: multiple names"; // Like in convert(). + + return to_target_name (s, move (n), o).first.proj; }; - // Functions that can be called only on real targets. + // $is_a(<name>, <target-type>) // - function_family ft (m, "target"); + // Return true if the <name>'s target type is-a <target-type>. Note that + // this is a dynamic type check that takes into account target type + // inheritance. + // + f["is_a"] += [](const scope* s, name n, names t) + { + return is_a (s, move (n), name (), move (t)); + }; + f["is_a"] += [](const scope* s, names ns, names t) + { + auto i (ns.begin ()); - // Note that while this function is not technically pure, we don't mark it - // as such since it can only be called (normally form a recipe) after the - // target has been matched, meaning that this target is a prerequisite and - // therefore this impurity has been accounted for. + name& n (*i); + const name& o (n.pair ? *++i : name ()); + + if (++i != ns.end ()) + fail << "invalid name value: multiple names"; // Like in convert(). + + return is_a (s, move (n), o, move (t)); + }; + + // $filter(<names>, <target-types>) + // $filter_out(<names>, <target-types>) + // + // Return names with target types which are-a (`filter`) or not are-a + // (`filter_out`) one of <target-types>. See `$is_a()` for background. // - ft["path"] += [](const scope* s, names ns) + f["filter"] += [](const scope* s, names ns, names ts) { - if (s == nullptr) - fail << "target.path() called out of scope"; + return filter (s, move (ns), move (ts), false /* out */); + }; - // Most of the time we will have a single target so optimize for that. - // - small_vector<path, 1> r; + f["filter_out"] += [](const scope* s, names ns, names ts) + { + return filter (s, move (ns), move (ts), true /* out */); + }; + + // $size(<names>) + // + // Return the number of elements in the sequence. + // + f["size"] += [] (names ns) + { + size_t n (0); for (auto i (ns.begin ()); i != ns.end (); ++i) { - name& n (*i), o; - const target& t (to_target (*s, move (n), move (n.pair ? *++i : o))); - - if (const auto* pt = t.is_a<path_target> ()) - { - const path& p (pt->path ()); - - if (&p != &empty_path) - r.push_back (p); - else - fail << "target " << t << " path is not assigned"; - } - else - fail << "target " << t << " is not path-based"; + ++n; + if (i->pair && !(++i)->directory ()) + fail << "name pair in names"; } - // We want the result to be path if we were given a single target and - // paths if multiple (or zero). The problem is, we cannot distinguish it - // based on the argument type (e.g., name vs names) since passing an - // out-qualified single target requires two names. - // - if (r.size () == 1) - return value (move (r[0])); - - return value (paths (make_move_iterator (r.begin ()), - make_move_iterator (r.end ()))); + return n; }; - // This one can only be called on a single target since we don't support - // containers of process_path's (though we probably could). + // $sort(<names>[, <flags>]) + // + // Sort names in ascending order. // - // Note that while this function is not technically pure, we don't mark it - // as such for the same reasons as $path() above. + // The following flags are supported: // - fn["process_path"] += [](const scope* s, names ns) + // dedup - in addition to sorting also remove duplicates + // + f["sort"] += [] (names ns, optional<names> fs) { - if (s == nullptr) - fail << "target.process_path() called out of scope"; + //@@ TODO: shouldn't we do this in a pair-aware manner? - if (ns.empty () || ns.size () != (ns[0].pair ? 2 : 1)) - fail << "target.process_path() expects single target"; + sort (ns.begin (), ns.end ()); - name o; - const target& t ( - to_target (*s, move (ns[0]), move (ns[0].pair ? ns[1] : o))); + if (functions_sort_flags (move (fs))) + ns.erase (unique (ns.begin (), ns.end ()), ns.end ()); - if (const auto* et = t.is_a<exe> ()) - { - process_path r (et->process_path ()); + return ns; + }; + + // $find(<names>, <name>) + // + // Return true if the name sequence contains the specified name. + // + f["find"] += [](names vs, names v) + { + //@@ TODO: shouldn't we do this in a pair-aware manner? - if (r.empty ()) - fail << "target " << t << " path is not assigned"; + return find (vs.begin (), vs.end (), + convert<name> (move (v))) != vs.end (); + }; - return r; - } - else - fail << "target " << t << " is not process_path-based" << endf; + // $find_index(<names>, <name>) + // + // Return the index of the first element in the name sequence that is + // equal to the specified name or `$size(names)` if none is found. + // + f["find_index"] += [](names vs, names v) + { + //@@ TODO: shouldn't we do this in a pair-aware manner? + + auto i (find (vs.begin (), vs.end (), convert<name> (move (v)))); + return i != vs.end () ? i - vs.begin () : vs.size (); }; // Name-specific overloads from builtins. // function_family fb (m, "builtin"); + // Note that while we should normally handle NULL values (relied upon by + // the parser to provide concatenation semantics consistent with untyped + // values), the result will unlikely be what the user expected. So for now + // we keep it a bit tighter. + // fb[".concat"] += [](dir_path d, name n) { d /= n.dir; |