From 977d07a3ae47ef204665d1eda2d642e5064724f3 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 24 Jun 2019 12:01:19 +0200 Subject: Split build system into library and driver --- build2/function.hxx | 897 ---------------------------------------------------- 1 file changed, 897 deletions(-) delete mode 100644 build2/function.hxx (limited to 'build2/function.hxx') diff --git a/build2/function.hxx b/build2/function.hxx deleted file mode 100644 index 1b49f81..0000000 --- a/build2/function.hxx +++ /dev/null @@ -1,897 +0,0 @@ -// file : build2/function.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_FUNCTION_HXX -#define BUILD2_FUNCTION_HXX - -#include -#include // index_sequence -#include // aligned_storage - -#include -#include - -#include -#include - -namespace build2 -{ - // Functions can be overloaded based on types of their arguments but - // arguments can be untyped and a function can elect to accept an argument - // of any type. - // - // Functions can be qualified (e.g, string.length(), path.directory()) and - // unqualified (e.g., length(), directory()). Only functions overloaded on - // static types can be unqualified plus they should also define a qualified - // alias. - // - // Low-level function implementation would be called with a list of values - // as arguments. There is also higher-level, more convenient support for - // defining functions as pointers to functions (including capture-less - // lambdas), pointers to member functions (e.g., string::size()), or - // pointers to data members (e.g., name::type). In this case the build2 - // function types are automatically matched to C++ function types according - // to these rules: - // - // T - statically-typed (value_traits must be defined) - // names - untyped - // value - any type - // T* - NULL-able argument (here T can be names) - // value* - NULL-able any type (never NULL itself, use value::null) - // optional - optional argument (here T can be T*, names, value) - // - // Optional arguments must be last. In case of a failure the function is - // expected to issue diagnostics and throw failed. Note that the arguments - // are conceptually "moved" and can be reused by the implementation. - // - // A function can also optionally receive the current scope by having the - // first argument of the const scope* type. It may be NULL if the function - // is called out of any scope (e.g., command line). - // - // Note also that we don't pass the location to the function instead - // printing the info message pointing to the call site. - // - // A function can return value or anything that can be converted to value. - // In particular, if a function returns optional, then the result will be - // either NULL or value of type T. - // - // Normally functions come in families that share a common qualification - // (e.g., string. or path.). The function_family class is a "registrar" - // that simplifies handling of function families. For example: - // - // function_family f ("string"); - // - // // Register length() and string.length(). - // // - // f["length"] = &string::size; - // - // // Register string.max_size(). - // // - // f[".max_size"] = []() {return string ().max_size ();}; - // - // For more examples/ideas, study the existing function families (reside - // in the functions-*.cxx files). - // - // Note that normally there will be a function overload that has all the - // parameters untyped with an implementation that falls back to one of the - // overloads that have all the parameters typed, possibly inferring the type - // from the argument value "syntax" (e.g., presence of a trailing slash for - // a directory path). - // - struct function_overload; - - using function_impl = value (const scope*, - vector_view, - const function_overload&); - - struct function_overload - { - const char* name; // Set to point to key by insert() below. - const char* alt_name; // Alternative name, NULL if none. This is the - // qualified name for unqualified or vice verse. - - // Arguments. - // - // A function can have a number of optional arguments. Arguments can also - // be typed. A non-existent entry in arg_types means a value of any type. - // A NULL entry means an untyped value. - // - // If arg_max equals to arg_variadic, then the function takes an unlimited - // number of arguments. In this case the semantics of arg_min and - // arg_types is unchanged. - // - static const size_t arg_variadic = size_t (~0); - - using types = vector_view>; - - const size_t arg_min; - const size_t arg_max; - const types arg_types; - - // Function implementation. - // - function_impl* const impl; - - // Auxiliary data storage. Note that it is assumed to be POD (no - // destructors, bitwise copy, etc). - // - std::aligned_storage::type data; - static const size_t data_size = sizeof (decltype (data)); - - function_overload (const char* an, - size_t mi, size_t ma, types ts, - function_impl* im) - : alt_name (an), - arg_min (mi), arg_max (ma), arg_types (move (ts)), - impl (im) {} - - template - function_overload (const char* an, - size_t mi, size_t ma, types ts, - function_impl* im, - D d) - : function_overload (an, mi, ma, move (ts), im) - { - // std::is_pod appears to be broken in VC16 and also in GCC up to - // 5 (pointers to members). - // -#if !((defined(_MSC_VER) && _MSC_VER < 2000) || \ - (defined(__GNUC__) && !defined(__clang__) && __GNUC__ <= 5)) - static_assert (std::is_pod::value, "type is not POD"); -#endif - static_assert (sizeof (D) <= data_size, "insufficient space"); - new (&data) D (move (d)); - } - }; - - ostream& - operator<< (ostream&, const function_overload&); // Print signature. - - class function_map - { - public: - using map_type = std::multimap; - using iterator = map_type::iterator; - using const_iterator = map_type::const_iterator; - - iterator - insert (string name, function_overload); - - void - erase (iterator i) {map_.erase (i);} - - value - call (const scope* base, - const string& name, - vector_view args, - const location& l) const - { - return call (base, name, args, l, true).first; - } - - // As above but do not fail if no match was found (but still do if the - // match is ambiguous). Instead return an indication of whether the call - // was made. Used to issue custom diagnostics when calling internal - // functions. - // - pair - try_call (const scope* base, - const string& name, - vector_view args, - const location& l) const - { - return call (base, name, args, l, false); - } - - iterator - begin () {return map_.begin ();} - - iterator - end () {return map_.end ();} - - const_iterator - begin () const {return map_.begin ();} - - const_iterator - end () const {return map_.end ();} - - // Return true if the function with this name is already defined. If the - // name ends with '.', then instead check if any function with this prefix - // (which we call a family) is already defined. - // - bool - defined (const string&) const; - - private: - pair - call (const scope*, - const string&, - vector_view, - const location&, - bool fail) const; - - map_type map_; - }; - - extern function_map functions; - - class function_family - { - public: - // The call() function above catches invalid_argument and issues - // diagnostics by assuming it is related to function arguments and - // contains useful description. - // - // In order to catch additional exceptions, you can implement a custom - // thunk which would normally call this default implementation. - // - static value - default_thunk (const scope*, vector_view, const function_overload&); - - // A function family uses a common qualification (though you can pass - // empty string to supress it). For an unqualified name (doesn't not - // contain a dot) the qualified version is added automatically. A name - // containing a leading dot is a shortcut notation for a qualified-only - // name. - // - explicit - function_family (string qual, function_impl* thunk = &default_thunk) - : qual_ (qual), thunk_ (thunk) {} - - struct entry; - - entry - operator[] (string name) const; - - static bool - defined (string qual) - { - qual += '.'; - return functions.defined (qual); - } - - private: - const string qual_; - function_impl* thunk_; - }; - - // Implementation details. If you can understand and explain all of this, - // then you are hired ;-)! - // - - template - struct function_arg - { - static const bool null = false; - static const bool opt = false; - - static constexpr optional - type () {return &value_traits::value_type;} - - static T&& - cast (value* v) - { - if (v->null) - throw invalid_argument ("null value"); - - // Use fast but unchecked cast since the caller matched the types. - // - return move (v->as ()); - } - }; - - template <> - struct function_arg // Untyped. - { - static const bool null = false; - static const bool opt = false; - - static constexpr optional - type () {return nullptr;} - - static names&& - cast (value* v) - { - if (v->null) - throw invalid_argument ("null value"); - - return move (v->as ()); - } - }; - - template <> - struct function_arg // Anytyped. - { - static const bool null = false; - static const bool opt = false; - - static constexpr optional - type () {return nullopt;} - - static value&& - cast (value* v) - { - if (v->null) - throw invalid_argument ("null value"); - - return move (*v); - } - }; - - template - struct function_arg: function_arg - { - static const bool null = true; - - static T* - cast (value* v) - { - if (v->null) - return nullptr; - - // This looks bizarre but makes sense. The cast() that we are calling - // returns an r-value reference to (what's inside) v. And it has to - // return an r-value reference to that the value is moved into by-value - // arguments. - // - T&& r (function_arg::cast (v)); - return &r; - } - }; - - template <> - struct function_arg: function_arg - { - static const bool null = true; - - static value* - cast (value* v) {return v;} // NULL indicator in value::null. - }; - - template - struct function_arg>: function_arg - { - static const bool opt = true; - - static optional - cast (value* v) - { - return v != nullptr ? optional (function_arg::cast (v)) : nullopt; - } - }; - - // Number of optional arguments. Note that we currently don't check that - // they are all at the end. - // - template - struct function_args_opt - { - static const size_t count = (function_arg::opt ? 1 : 0) + - function_args_opt::count; - }; - - template - struct function_args_opt - { - static const size_t count = (function_arg::opt ? 1 : 0); - }; - - // Argument counts/types. - // - template - struct function_args - { - static const size_t max = sizeof...(A); - static const size_t min = max - function_args_opt::count; - - // VC15 doesn't realize that a pointer to static object (in our case it is - // &value_trair::value_type) is constexpr. - // -#if !defined(_MSC_VER) || _MSC_VER > 1910 - static constexpr const optional types[max] = { - function_arg::type ()...}; -#else - static const optional types[max]; -#endif - }; - - template -#if !defined(_MSC_VER) || _MSC_VER > 1910 - constexpr const optional - function_args::types[function_args::max]; -#else - const optional - function_args::types[function_args::max] = { - function_arg::type ()...}; -#endif - - // Specialization for no arguments. - // - template <> - struct function_args<> - { - static const size_t max = 0; - static const size_t min = 0; - -#if !defined(_MSC_VER) || _MSC_VER > 1910 - static constexpr const optional* types = nullptr; -#else - static const optional* const types; -#endif - }; - - // Cast data/thunk. - // - template - struct function_cast - { - // A pointer to a standard layout struct is a pointer to its first data - // member, which in our case is the cast thunk. - // - struct data - { - value (*const thunk) (const scope*, vector_view, const void*); - R (*const impl) (A...); - }; - - static value - thunk (const scope*, vector_view args, const void* d) - { - return thunk (move (args), - static_cast (d)->impl, - std::index_sequence_for ()); - } - - template - static value - thunk (vector_view args, - R (*impl) (A...), - std::index_sequence) - { - return value ( - impl ( - function_arg::cast ( - i < args.size () ? &args[i] : nullptr)...)); - } - }; - - // Specialization for functions that expect the current scope as a first - // argument. - // - template - struct function_cast - { - struct data - { - value (*const thunk) (const scope*, vector_view, const void*); - R (*const impl) (const scope*, A...); - }; - - static value - thunk (const scope* base, vector_view args, const void* d) - { - return thunk (base, move (args), - static_cast (d)->impl, - std::index_sequence_for ()); - } - - template - static value - thunk (const scope* base, vector_view args, - R (*impl) (const scope*, A...), - std::index_sequence) - { - return value ( - impl (base, - function_arg::cast ( - i < args.size () ? &args[i] : nullptr)...)); - } - }; - - // Specialization for void return type. In this case we return NULL value. - // - template - struct function_cast - { - struct data - { - value (*const thunk) (const scope*, vector_view, const void*); - void (*const impl) (A...); - }; - - static value - thunk (const scope*, vector_view args, const void* d) - { - thunk (move (args), - static_cast (d)->impl, - std::index_sequence_for ()); - return value (nullptr); - } - - template - static void - thunk (vector_view args, - void (*impl) (A...), - std::index_sequence) - { - impl (function_arg::cast (i < args.size () ? &args[i] : nullptr)...); - } - }; - - template - struct function_cast - { - struct data - { - value (*const thunk) (const scope*, vector_view, const void*); - void (*const impl) (const scope*, A...); - }; - - static value - thunk (const scope* base, vector_view args, const void* d) - { - thunk (base, move (args), - static_cast (d)->impl, - std::index_sequence_for ()); - return value (nullptr); - } - - template - static void - thunk (const scope* base, vector_view args, - void (*impl) (const scope*, A...), - std::index_sequence) - { - impl (base, - function_arg::cast (i < args.size () ? &args[i] : nullptr)...); - } - }; - - // Customization for coerced lambdas (see below). - // -#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 6 - template - struct function_cast_lamb - { - struct data - { - value (*const thunk) (const scope*, vector_view, const void*); - R (L::*const impl) (A...) const; - }; - - static value - thunk (const scope*, vector_view args, const void* d) - { - return thunk (move (args), - static_cast (d)->impl, - std::index_sequence_for ()); - } - - template - static value - thunk (vector_view args, - R (L::*impl) (A...) const, - std::index_sequence) - { - const L* l (nullptr); // Undefined behavior. - - return value ( - (l->*impl) ( - function_arg::cast ( - i < args.size () ? &args[i] : nullptr)...)); - } - }; - - template - struct function_cast_lamb - { - struct data - { - value (*const thunk) (const scope*, vector_view, const void*); - R (L::*const impl) (const scope*, A...) const; - }; - - static value - thunk (const scope* base, vector_view args, const void* d) - { - return thunk (base, move (args), - static_cast (d)->impl, - std::index_sequence_for ()); - } - - template - static value - thunk (const scope* base, vector_view args, - R (L::*impl) (const scope*, A...) const, - std::index_sequence) - { - const L* l (nullptr); // Undefined behavior. - - return value ( - (l->*impl) (base, - function_arg::cast ( - i < args.size () ? &args[i] : nullptr)...)); - } - }; - - template - struct function_cast_lamb - { - struct data - { - value (*const thunk) (const scope*, vector_view, const void*); - void (L::*const impl) (A...) const; - }; - - static value - thunk (const scope*, vector_view args, const void* d) - { - thunk (move (args), - static_cast (d)->impl, - std::index_sequence_for ()); - return value (nullptr); - } - - template - static void - thunk (vector_view args, - void (L::*impl) (A...) const, - std::index_sequence) - { - const L* l (nullptr); - (l->*impl) ( - function_arg::cast ( - i < args.size () ? &args[i] : nullptr)...); - } - }; - - template - struct function_cast_lamb - { - struct data - { - value (*const thunk) (const scope*, vector_view, const void*); - void (L::*const impl) (const scope*, A...) const; - }; - - static value - thunk (const scope* base, vector_view args, const void* d) - { - thunk (base, move (args), - static_cast (d)->impl, - std::index_sequence_for ()); - return value (nullptr); - } - - template - static void - thunk (const scope* base, vector_view args, - void (L::*impl) (const scope*, A...) const, - std::index_sequence) - { - const L* l (nullptr); - (l->*impl) (base, - function_arg::cast ( - i < args.size () ? &args[i] : nullptr)...); - } - }; -#endif - - // Customization for member functions. - // - template - struct function_cast_memf - { - struct data - { - value (*const thunk) (const scope*, vector_view, const void*); - R (T::*const impl) () const; - }; - - static value - thunk (const scope*, vector_view args, const void* d) - { - auto mf (static_cast (d)->impl); - return value ((function_arg::cast (&args[0]).*mf) ()); - } - }; - - template - struct function_cast_memf - { - struct data - { - value (*const thunk) (const scope*, vector_view, const void*); - void (T::*const impl) () const; - }; - - static value - thunk (const scope*, vector_view args, const void* d) - { - auto mf (static_cast (d)->impl); - (function_arg::cast (args[0]).*mf) (); - return value (nullptr); - } - }; - - // Customization for data members. - // - template - struct function_cast_memd - { - struct data - { - value (*const thunk) (const scope*, vector_view, const void*); - R T::*const impl; - }; - - static value - thunk (const scope*, vector_view args, const void* d) - { - auto dm (static_cast (d)->impl); - return value (move (function_arg::cast (&args[0]).*dm)); - } - }; - - struct function_family::entry - { - string name; - const string& qual; - function_impl* thunk; - - template - void - operator= (R (*impl) (A...)) && - { - using args = function_args; - using cast = function_cast; - - insert (move (name), - function_overload ( - nullptr, - args::min, - args::max, - function_overload::types (args::types, args::max), - thunk, - typename cast::data {&cast::thunk, impl})); - } - - template - void - operator= (R (*impl) (const scope*, A...)) && - { - using args = function_args; - using cast = function_cast; - - insert (move (name), - function_overload ( - nullptr, - args::min, - args::max, - function_overload::types (args::types, args::max), - thunk, - typename cast::data {&cast::thunk, impl})); - } - - // Support for assigning a (capture-less) lambda. - // - // GCC up until version 6 has a bug (#62052) that is triggered by calling - // a lambda that takes a by-value argument via its "decayed" function - // pointer. To work around this we are not going to decay it and instead - // will call its operator() on NULL pointer; yes, undefined behavior, but - // better than a guaranteed crash. - // -#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 6 - template - void - operator= (const L&) && - { - move (*this).coerce_lambda (&L::operator()); - } - - template - void - coerce_lambda (R (L::*op) (A...) const) && - { - using args = function_args; - using cast = function_cast_lamb; - - insert (move (name), - function_overload ( - nullptr, - args::min, - args::max, - function_overload::types (args::types, args::max), - thunk, - typename cast::data {&cast::thunk, op})); - } - - template - void - coerce_lambda (R (L::*op) (const scope*, A...) const) && - { - using args = function_args; - using cast = function_cast_lamb; - - insert (move (name), - function_overload ( - nullptr, - args::min, - args::max, - function_overload::types (args::types, args::max), - thunk, - typename cast::data {&cast::thunk, op})); - } -#else - template - void - operator= (const L& l) && - { - move (*this).operator= (decay_lambda (&L::operator(), l)); - } - - template - static auto - decay_lambda (R (L::*) (A...) const, const L& l) -> R (*) (A...) - { - return static_cast (l); - } -#endif - - // Support for assigning a pointer to member function (e.g. an accessor). - // - // For now we don't support passing additional (to this) arguments though - // we could probably do that. The issues would be the argument passing - // semantics (e.g., what if it's const&) and the optional/default argument - // handling. - // - template - void - operator= (R (T::*mf) () const) && - { - using args = function_args; - using cast = function_cast_memf; - - insert (move (name), - function_overload ( - nullptr, - args::min, - args::max, - function_overload::types (args::types, args::max), - thunk, - typename cast::data {&cast::thunk, mf})); - } - - // Support for assigning a pointer to data member. - // - template - void - operator= (R T::*dm) && - { - using args = function_args; - using cast = function_cast_memd; - - insert (move (name), - function_overload ( - nullptr, - args::min, - args::max, - function_overload::types (args::types, args::max), - thunk, - typename cast::data {&cast::thunk, dm})); - } - - private: - void - insert (string, function_overload) const; - }; - - inline auto function_family:: - operator[] (string name) const -> entry - { - return entry {move (name), qual_, thunk_}; - } -} - -#endif // BUILD2_FUNCTION_HXX -- cgit v1.1