From 6b7075adc71104c5f6ad652b99fb753565eb67d8 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Fri, 18 Nov 2016 17:28:46 +0200 Subject: Add function machinery, implement path.normalize() Note that multi-argument functions are not yet "callable" since there is no support for value packs. --- build2/function | 534 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 534 insertions(+) create mode 100644 build2/function (limited to 'build2/function') 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 // index_sequence +#include // aligned_storage +#include + +#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). + // 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. + // + // 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, 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 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 () = 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 + 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::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; + 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 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, 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 + struct function_arg + { + static const bool null = false; + static const bool opt = false; + + static 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 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 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 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; + + static const optional types[max]; + }; + + template + const optional + function_args::types[function_args::max] = { + function_arg::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 types; // NULL + }; + + // 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) (vector_view, const void*); + R (*const impl) (A...); + }; + + static value + thunk (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 void return type. In this case we return NULL value. + // + template + struct function_cast + { + struct data + { + value (*const thunk) (vector_view, const void*); + void (*const impl) (A...); + }; + + static value + thunk (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)...); + } + }; + + // Customization for member functions. + // + template + struct function_cast_memf + { + struct data + { + value (*const thunk) (vector_view, const void*); + R (T::*const impl) () const; + }; + + static value + thunk (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) (vector_view, const void*); + void (T::*const impl) () const; + }; + + static value + thunk (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) (vector_view, const void*); + R T::*const impl; + }; + + static value + thunk (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})); + } + + // Support for assigning a (capture-less) lambda. + // + 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); + } + + // 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 -- cgit v1.1