diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2016-11-18 17:28:46 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2016-11-18 17:30:47 +0200 |
commit | 6b7075adc71104c5f6ad652b99fb753565eb67d8 (patch) | |
tree | 1f4d91b7cd9ee7cca793f0ecc504ccc4d8dde0d2 /build2/function | |
parent | dd008d6e48b0bb66e1b9fdc489d9d1d9b4cb8d25 (diff) |
Add function machinery, implement path.normalize()
Note that multi-argument functions are not yet "callable" since there is
no support for value packs.
Diffstat (limited to 'build2/function')
-rw-r--r-- | build2/function | 534 |
1 files changed, 534 insertions, 0 deletions
diff --git a/build2/function b/build2/function new file mode 100644 index 0000000..7a7b43e --- /dev/null +++ b/build2/function @@ -0,0 +1,534 @@ +// file : build2/function -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_FUNCTION +#define BUILD2_FUNCTION + +#include <utility> // index_sequence +#include <type_traits> // aligned_storage +#include <unordered_map> + +#include <build2/types> +#include <build2/utility> + +#include <build2/variable> +#include <build2/diagnostics> + +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<T> must be defined) + // names - untyped + // value - any type + // T* - NULL-able argument (here T can be names, value). + // optional<T> - 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. + // + // 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). + // + struct function_overload; + + using function_impl = value (vector_view<value>, const function_overload&); + + struct function_overload + { + const char* name; // Set to point to key by insert() below. + const char* qual_name; // Qualified name, NULL if none. + + // 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 optional<const value_type*>>; + + 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<sizeof (void*) * 3>::type data; + static const size_t data_size = sizeof (decltype (data)); + + function_overload () = default; + + function_overload (const char* qn, + size_t mi, size_t ma, types ts, + function_impl* im) + : qual_name (qn), + arg_min (mi), arg_max (ma), arg_types (move (ts)), + impl (im) {} + + template <typename D> + function_overload (const char* qn, + size_t mi, size_t ma, types ts, + function_impl* im, + D d) + : function_overload (qn, mi, ma, move (ts), im) + { + // std::is_pod appears to be broken in VC15. + // +#if !defined(_MSC_VER) || _MSC_VER > 1900 + static_assert (std::is_pod<D>::value, "type is not POD"); +#endif + static_assert (sizeof (D) <= data_size, "insufficient space"); + new (&data) D (move (d)); + } + }; + + class function_map + { + public: + using map_type = std::unordered_multimap<string, function_overload>; + using iterator = map_type::iterator; + + iterator + insert (string name, function_overload); + + void + erase (iterator i) {map_.erase (i);} + + value + call (const string& name, vector_view<value> args, const location&); + + private: + map_type map_; + }; + + extern function_map functions; + + class function_family + { + public: + // The default thunk catches invalid_argument and issues diagnostics + // by assuming it is related to function arguments and contains useful + // description. + // + // In order to implement a custom thunk (e.g., to catch additional extra + // exceptions), you would normally call the default implementation. + // + static value + default_thunk (vector_view<value>, 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; + + private: + const string qual_; + function_impl* thunk_; + }; + + // Implementation details. If you can understand and explain all of this, + // then you are hired ;-)! + // + + template <typename T> + struct function_arg + { + static const bool null = false; + static const bool opt = false; + + static optional<const value_type*> + type () {return &value_traits<T>::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<T> ()); + } + }; + + template <> + struct function_arg<names> // Untyped. + { + static const bool null = false; + static const bool opt = false; + + static optional<const value_type*> + type () {return nullptr;} + + static names&& + cast (value* v) + { + if (v->null) + throw invalid_argument ("null value"); + + return move (v->as<names> ()); + } + }; + + template <> + struct function_arg<value> // Anytyped. + { + static const bool null = false; + static const bool opt = false; + + static optional<const value_type*> + type () {return nullopt;} + + static value&& + cast (value* v) + { + if (v->null) + throw invalid_argument ("null value"); + + return move (*v); + } + }; + + template <typename T> + struct function_arg<T*>: function_arg<T> + { + 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<T>::cast (v)); + return &r; + } + }; + + template <typename T> + struct function_arg<optional<T>>: function_arg<T> + { + static const bool opt = true; + + static optional<T> + cast (value* v) + { + return v != nullptr ? optional<T> (function_arg<T>::cast (v)) : nullopt; + } + }; + + // Number of optional arguments. Note that we currently don't check that + // they are all at the end. + // + template <typename A0, typename... A> + struct function_args_opt + { + static const size_t count = (function_arg<A0>::opt ? 1 : 0) + + function_args_opt<A...>::count; + }; + + template <typename A0> + struct function_args_opt<A0> + { + static const size_t count = (function_arg<A0>::opt ? 1 : 0); + }; + + // Argument counts/types. + // + template <typename... A> + struct function_args + { + static const size_t max = sizeof...(A); + static const size_t min = max - function_args_opt<A...>::count; + + static const optional<const value_type*> types[max]; + }; + + template <typename... A> + const optional<const value_type*> + function_args<A...>::types[function_args<A...>::max] = { + function_arg<A>::type ()...}; + + // Specialization for no arguments. + // + template <> + struct function_args<> + { + static const size_t max = 0; + static const size_t min = 0; + static const optional<const value_type*>* const types; // NULL + }; + + // Cast data/thunk. + // + template <typename R, typename... A> + 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) (vector_view<value>, const void*); + R (*const impl) (A...); + }; + + static value + thunk (vector_view<value> args, const void* d) + { + return thunk (move (args), + static_cast<const data*> (d)->impl, + std::index_sequence_for<A...> ()); + } + + template <size_t... i> + static value + thunk (vector_view<value> args, + R (*impl) (A...), + std::index_sequence<i...>) + { + return value ( + impl ( + function_arg<A>::cast ( + i < args.size () ? &args[i] : nullptr)...)); + } + }; + + // Specialization for void return type. In this case we return NULL value. + // + template <typename... A> + struct function_cast<void, A...> + { + struct data + { + value (*const thunk) (vector_view<value>, const void*); + void (*const impl) (A...); + }; + + static value + thunk (vector_view<value> args, const void* d) + { + thunk (move (args), + static_cast<const data*> (d)->impl, + std::index_sequence_for<A...> ()); + return value (nullptr); + } + + template <size_t... i> + static void + thunk (vector_view<value> args, + void (*impl) (A...), + std::index_sequence<i...>) + { + impl (function_arg<A>::cast (i < args.size () ? &args[i] : nullptr)...); + } + }; + + // Customization for member functions. + // + template <typename R, typename T> + struct function_cast_memf + { + struct data + { + value (*const thunk) (vector_view<value>, const void*); + R (T::*const impl) () const; + }; + + static value + thunk (vector_view<value> args, const void* d) + { + auto mf (static_cast<const data*> (d)->impl); + return value ((function_arg<T>::cast (&args[0]).*mf) ()); + } + }; + + template <typename T> + struct function_cast_memf<void, T> + { + struct data + { + value (*const thunk) (vector_view<value>, const void*); + void (T::*const impl) () const; + }; + + static value + thunk (vector_view<value> args, const void* d) + { + auto mf (static_cast<const data*> (d)->impl); + (function_arg<T>::cast (args[0]).*mf) (); + return value (nullptr); + } + }; + + // Customization for data members. + // + template <typename R, typename T> + struct function_cast_memd + { + struct data + { + value (*const thunk) (vector_view<value>, const void*); + R T::*const impl; + }; + + static value + thunk (vector_view<value> args, const void* d) + { + auto dm (static_cast<const data*> (d)->impl); + return value (move (function_arg<T>::cast (&args[0]).*dm)); + } + }; + + struct function_family::entry + { + string name; + const string& qual; + function_impl* thunk; + + template <typename R, typename... A> + void + operator= (R (*impl) (A...)) && + { + using args = function_args<A...>; + using cast = function_cast<R, A...>; + + 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. + // + template <typename L> + void + operator= (const L& l) && + { + move (*this).operator= (decay_lambda (&L::operator(), l)); + } + + template <typename L, typename R, typename... A> + static auto + decay_lambda (R (L::*) (A...) const, const L& l) -> R (*) (A...) + { + return static_cast<R (*) (A...)> (l); + } + + // 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 <typename R, typename T> + void + operator= (R (T::*mf) () const) && + { + using args = function_args<T>; + using cast = function_cast_memf<R, T>; + + 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 <typename R, typename T> + void + operator= (R T::*dm) && + { + using args = function_args<T>; + using cast = function_cast_memd<R, T>; + + 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 |