aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/function.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/function.cxx')
-rw-r--r--libbuild2/function.cxx400
1 files changed, 400 insertions, 0 deletions
diff --git a/libbuild2/function.cxx b/libbuild2/function.cxx
new file mode 100644
index 0000000..2d4dce9
--- /dev/null
+++ b/libbuild2/function.cxx
@@ -0,0 +1,400 @@
+// file : libbuild2/function.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/function.hxx>
+
+#include <cstring> // strchr()
+
+using namespace std;
+
+namespace build2
+{
+ ostream&
+ operator<< (ostream& os, const function_overload& f)
+ {
+ os << f.name << '(';
+
+ bool v (f.arg_max == function_overload::arg_variadic);
+ size_t n (v ? max (f.arg_min, f.arg_types.size ()): f.arg_max);
+
+ // Handle variadic tail as the last pseudo-argument.
+ //
+ for (size_t i (0); i != n + (v ? 1 : 0); ++i)
+ {
+ if (i == f.arg_min)
+ os << (i != 0 ? " [" : "[");
+
+ os << (i != 0 ? ", " : "");
+
+ if (i == n) // Variadic tail (last).
+ os << "...";
+ else
+ {
+ // If count is greater than f.arg_typed, then we assume the rest are
+ // valid but untyped.
+ //
+ const optional<const value_type*> t (
+ i < f.arg_types.size () ? f.arg_types[i] : nullopt);
+
+ os << (t ? (*t != nullptr ? (*t)->name : "<untyped>") : "<anytype>");
+ }
+ }
+
+ if (n + (v ? 1 : 0) > f.arg_min)
+ os << ']';
+
+ os << ')';
+
+ if (f.alt_name != nullptr)
+ {
+ auto k (strchr (f.alt_name, '.') == nullptr
+ ? "unqualified"
+ : "qualified");
+
+ os << ", " << k << " name " << f.alt_name;
+ }
+
+ return os;
+ }
+
+ bool function_map::
+ defined (const string& name) const
+ {
+ assert (!name.empty ());
+
+ // If this is a qualified function name then check if it is already
+ // defined.
+ //
+ if (name.back () != '.')
+ return map_.find (name) != map_.end ();
+
+ // If any function of the specified family is already defined, then one of
+ // them should be the first element that is greater than the dot-terminated
+ // family name. Here we rely on the fact that the dot character is less
+ // than any character of unqualified function and family names.
+ //
+ size_t n (name.size ());
+ assert (n > 1);
+
+ auto i (map_.upper_bound (name));
+ return i != map_.end () && i->first.compare (0, n, name) == 0;
+ }
+
+ auto function_map::
+ insert (string name, function_overload f) -> iterator
+ {
+ // Sanity checks.
+ //
+ assert (f.arg_min <= f.arg_max &&
+ f.arg_types.size () <= f.arg_max &&
+ f.impl != nullptr);
+
+ auto i (map_.emplace (move (name), move (f)));
+
+ i->second.name = i->first.c_str ();
+ return i;
+ }
+
+ pair<value, bool> function_map::
+ call (const scope* base,
+ const string& name,
+ vector_view<value> args,
+ const location& loc,
+ bool fa) const
+ {
+ auto print_call = [&name, &args] (ostream& os)
+ {
+ os << name << '(';
+
+ for (size_t i (0); i != args.size (); ++i)
+ {
+ const value_type* t (args[i].type);
+ os << (i != 0 ? ", " : "") << (t != nullptr ? t->name : "<untyped>");
+ }
+
+ os << ')';
+ };
+
+ // Overload resolution.
+ //
+ // Ours is pretty simple: we sort all the overloads into three ranks:
+ //
+ // 0 -- all the arguments match exactly (perfect match)
+ // 1 -- one or more arguments match via the derived-to-base conversion
+ // 2 -- one or more arguments match via the reversal to untyped
+ //
+ // More than one match of the same rank is ambiguous.
+ //
+ auto ip (map_.equal_range (name));
+
+ size_t rank (~0);
+ small_vector<const function_overload*, 2> ovls;
+ {
+ size_t count (args.size ());
+
+ for (auto it (ip.first); it != ip.second; ++it)
+ {
+ const function_overload& f (it->second);
+
+ // Argument count match.
+ //
+ if (count < f.arg_min || count > f.arg_max)
+ continue;
+
+ // Argument types match.
+ //
+ size_t r (0);
+ {
+ size_t i (0), n (min (count, f.arg_types.size ()));
+ for (; i != n; ++i)
+ {
+ if (!f.arg_types[i]) // Anytyped.
+ continue;
+
+ const value_type* at (args[i].type);
+ const value_type* ft (*f.arg_types[i]);
+
+ if (at == ft) // Types match perfectly.
+ continue;
+
+ if (at != nullptr && ft != nullptr)
+ {
+ while ((at = at->base_type) != nullptr && at != ft) ;
+
+ if (at != nullptr) // Types match via derived-to-base.
+ {
+ if (r < 1)
+ r = 1;
+ continue;
+ }
+ }
+
+ if (ft == nullptr) // Types match via reversal to untyped.
+ {
+ if (r < 2)
+ r = 2;
+ continue;
+ }
+
+ break; // No match.
+ }
+
+ if (i != n)
+ continue; // No match.
+ }
+
+ // Better or just as good a match?
+ //
+ if (r <= rank)
+ {
+ if (r < rank) // Better.
+ {
+ rank = r;
+ ovls.clear ();
+ }
+
+ ovls.push_back (&f);
+ }
+
+ // Continue looking to detect ambiguities.
+ }
+ }
+
+ switch (ovls.size ())
+ {
+ case 1:
+ {
+ // Print the call location in case the function fails.
+ //
+ auto g (
+ make_exception_guard (
+ [fa, &loc, &print_call] ()
+ {
+ if (fa && verb != 0)
+ {
+ diag_record dr (info (loc));
+ dr << "while calling "; print_call (dr.os);
+ }
+ }));
+
+ auto f (ovls.back ());
+
+ // If one or more arguments match via the reversal to untyped (rank 2),
+ // then we need to go over the overload's arguments one more time an
+ // untypify() those that we need to reverse.
+ //
+ if (rank == 2)
+ {
+ size_t n (args.size ());
+ assert (n <= f->arg_types.size ());
+
+ for (size_t i (0); i != n; ++i)
+ {
+ if (f->arg_types[i] &&
+ *f->arg_types[i] == nullptr &&
+ args[i].type != nullptr)
+ untypify (args[i]);
+ }
+ }
+
+ try
+ {
+ return make_pair (f->impl (base, move (args), *f), true);
+ }
+ catch (const invalid_argument& e)
+ {
+ diag_record dr (fail);
+ dr << "invalid argument";
+
+ if (*e.what () != '\0')
+ dr << ": " << e;
+
+ dr << endf;
+ }
+ }
+ case 0:
+ {
+ if (!fa)
+ return make_pair (value (nullptr), false);
+
+ // No match.
+ //
+ diag_record dr;
+
+ dr << fail (loc) << "unmatched call to "; print_call (dr.os);
+
+ for (auto i (ip.first); i != ip.second; ++i)
+ dr << info << "candidate: " << i->second;
+
+ // If this is an unqualified name, then also print qualified
+ // functions that end with this name. But skip functions that we
+ // have already printed in the previous loop.
+ //
+ if (name.find ('.') == string::npos)
+ {
+ size_t n (name.size ());
+
+ for (auto i (functions.begin ()); i != functions.end (); ++i)
+ {
+ const string& q (i->first);
+ const function_overload& f (i->second);
+
+ if ((f.alt_name == nullptr || f.alt_name != name) &&
+ q.size () > n)
+ {
+ size_t p (q.size () - n);
+ if (q[p - 1] == '.' && q.compare (p, n, name) == 0)
+ dr << info << "candidate: " << i->second;
+ }
+ }
+ }
+
+ dr << endf;
+ }
+ default:
+ {
+ // Ambigous match.
+ //
+ diag_record dr;
+ dr << fail (loc) << "ambiguous call to "; print_call (dr.os);
+
+ for (auto f: ovls)
+ dr << info << "candidate: " << *f;
+
+ dr << endf;
+ }
+ }
+ }
+
+ value function_family::
+ default_thunk (const scope* base,
+ vector_view<value> args,
+ const function_overload& f)
+ {
+ // Call the cast thunk.
+ //
+ struct cast_data // Prefix of function_cast::data.
+ {
+ value (*const thunk) (const scope*, vector_view<value>, const void*);
+ };
+
+ auto d (reinterpret_cast<const cast_data*> (&f.data));
+ return d->thunk (base, move (args), d);
+ }
+
+#if !defined(_WIN32)
+ constexpr const optional<const value_type*>* function_args<>::types;
+#else
+ const optional<const value_type*>* const function_args<>::types = nullptr;
+#endif
+
+ void function_family::entry::
+ insert (string n, function_overload f) const
+ {
+ // Figure out qualification.
+ //
+ string qn;
+ size_t p (n.find ('.'));
+
+ if (p == string::npos)
+ {
+ if (!qual.empty ())
+ {
+ qn = qual;
+ qn += '.';
+ qn += n;
+ }
+ }
+ else if (p == 0)
+ {
+ assert (!qual.empty ());
+ n.insert (0, qual);
+ }
+
+ auto i (qn.empty () ? functions.end () : functions.insert (move (qn), f));
+ auto j (functions.insert (move (n), move (f)));
+
+ // If we have both, then set alternative names.
+ //
+ if (i != functions.end ())
+ {
+ i->second.alt_name = j->first.c_str ();
+ j->second.alt_name = i->first.c_str ();
+ }
+ }
+
+ // Static-initialize the function map and populate with builtin functions.
+ //
+ function_map functions;
+
+ void builtin_functions (); // functions-builtin.cxx
+ void filesystem_functions (); // functions-filesystem.cxx
+ void name_functions (); // functions-name.cxx
+ void path_functions (); // functions-path.cxx
+ void process_functions (); // functions-process.cxx
+ void process_path_functions (); // functions-process-path.cxx
+ void regex_functions (); // functions-regex.cxx
+ void string_functions (); // functions-string.cxx
+ void target_triplet_functions (); // functions-target-triplet.cxx
+ void project_name_functions (); // functions-target-triplet.cxx
+
+ struct functions_init
+ {
+ functions_init ()
+ {
+ builtin_functions ();
+ filesystem_functions ();
+ name_functions ();
+ path_functions ();
+ process_functions ();
+ process_path_functions ();
+ regex_functions ();
+ string_functions ();
+ target_triplet_functions ();
+ project_name_functions ();
+ }
+ };
+
+ static const functions_init init_;
+}