Last active
November 7, 2022 07:59
-
-
Save Biotronic/fffa7d4c96d760da5129d27ba3307f73 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import std.meta : staticIndexOf, AliasSeq, Alias, staticMap, ApplyRight, aliasSeqOf; | |
import std.traits : isImplicitlyConvertible, Parameters, ParameterDefaults, ParameterIdentifierTuple, isCallable, fullyQualifiedName, isSomeString; | |
import std.conv : text, to; | |
import std.range : iota, join; | |
import std.algorithm.searching : count, find; | |
import std.array : array, byPair; | |
/// Helper type for creating named parameters on the form args.foo = 42. | |
struct args { | |
static auto opDispatch(string name, T)(T arg) { | |
return Arg!(name, T)(arg); | |
} | |
} | |
// | |
private struct Arg(string _name, T) if (_name != "") { | |
T value; | |
alias value this; | |
} | |
private enum isArg(T) = is(T == Arg!U, U...); | |
private template getName(T) { | |
static if (is(T == Arg!(name, U), string name, U)) { | |
enum getName = name; | |
} else { | |
enum getName = ""; | |
} | |
} | |
private template getType(T) { | |
static if (is(T == Arg!(name, U), string name, U)) { | |
alias getType = U; | |
} else { | |
alias getType = T; | |
} | |
} | |
private template hasName(string name) { | |
enum hasName(T) = name == getName!T; | |
} | |
private template getOverloads(func...) | |
if (func.length == 1 && isCallable!func) { | |
enum isModuleOrPackage(alias s) = !__traits(compiles, { alias _ = typeof(s); }); | |
alias Parent = AliasSeq!(__traits(parent, func))[0]; | |
static if (is(Parent) || isModuleOrPackage!Parent) { | |
alias getOverloads = AliasSeq!(__traits(getOverloads, Parent, __traits(identifier, func))); | |
} else { | |
alias getOverloads = func; | |
} | |
} | |
/// Wraps a function to allow the use of named arguments like this: named!func(args.a = 2) | |
template named(alias fn) { | |
alias overloads = getOverloads!fn; | |
// Found a matching overload | |
auto named(Args...)(auto ref Args args) | |
if (findOverload!Args > -1) { | |
alias overload = Alias!(overloads[findOverload!Args()]); | |
alias defaults = ParameterDefaults!overload; | |
alias realArgs = staticMap!(getParam!(overload, Args), aliasSeqOf!(defaults.length.iota)); | |
return mixin("overload("~join([realArgs], ", ").array~")"); | |
} | |
// No matching overload | |
void named(string file = __FILE__, int line = __LINE__, Args...)(auto ref Args args) | |
if (findOverload!Args == -1) { | |
pragma(msg, text(file, "(", line, "): function ", fullyQualifiedName!fn, " can't be invoked with arguments (", join([staticMap!(paramRepresentation, Args)], ", "), ")")); | |
pragma(msg, "Alternatives are:"); | |
static foreach (i, overload; overloads) { | |
pragma(msg, text(" ", fullyQualifiedName!fn, "(", join([ParameterInfo!overload], ", "), ")")); | |
} | |
static assert(false, "Failure to instantiate"); | |
} | |
template paramRepresentation(T) { | |
static if (isArg!T) { | |
enum paramRepresentation = text("(named ", getType!T.stringof, " ", getName!T, ")"); | |
} else { | |
enum paramRepresentation = T.stringof; | |
} | |
} | |
template argRepresentation(alias fn, int n) { | |
enum name = ParameterIdentifierTuple!fn[n]; | |
alias Param = Parameters!fn[n]; | |
alias defValue = ParameterDefaults!fn[n]; | |
static if (is(defValue == void)) { | |
enum argRepresentation = text(Param.stringof, " ", name); | |
} else static if (isSomeString!(typeof(defValue))) { | |
enum argRepresentation = text(Param.stringof, " ", name, " = \"", defValue, '"'); | |
} else { | |
enum argRepresentation = text(Param.stringof, " ", name, " = ", defValue); | |
} | |
} | |
template ParameterInfo(alias fn, size_t n = 0) { | |
static if (n >= Parameters!fn.length) { | |
alias ParameterInfo = AliasSeq!(); | |
} else { | |
enum ParameterInfo = AliasSeq!(argRepresentation!(fn, n), ParameterInfo!(fn, n+1)); | |
} | |
} | |
template getParam(alias overload, Args...) { | |
template getParam(int i) { | |
alias paramNames = ParameterIdentifierTuple!overload; | |
alias Params = Parameters!overload; | |
alias argNames = staticMap!(getName, Args); | |
enum long idx = staticIndexOf!(paramNames[i], argNames); | |
static if (idx > -1) { | |
enum getParam = "args["~idx.to!string~"].value"; | |
} else static if (i < Args.length | |
&& isImplicitlyConvertible!(Args[i], Params[i]) | |
&& getName!(Args[i]) == "") { | |
enum getParam = "args["~i.to!string~"]"; | |
} else { | |
enum getParam = "defaults["~i.to!string~"]"; | |
} | |
} | |
} | |
enum Match { | |
Perfect, | |
Conversion, | |
Failed | |
} | |
Match getMatch(alias overload, Args...)() { | |
alias Params = Parameters!overload; | |
alias paramNames = ParameterIdentifierTuple!overload; | |
alias defaults = ParameterDefaults!overload; | |
alias argNames = staticMap!(getName, Args); | |
Match result = Match.Perfect; | |
// Named parameters go after nameless ones | |
alias namelessParams = staticMap!(hasName!"", Args); | |
enum firstNamed = staticIndexOf!(false, namelessParams); | |
static if (firstNamed > -1 && staticIndexOf!(true, namelessParams[firstNamed..$]) > -1) { | |
result = Match.Failed; | |
} | |
static foreach (i, Par; Params) {{ | |
enum argIndex = staticIndexOf!(paramNames[i], argNames); | |
static if (argIndex > -1) { | |
alias curArg = Args[argIndex]; | |
} else static if (i < Args.length) { | |
alias curArg = Args[i]; | |
} else static if (!is(defaults[i] == void)) { | |
alias curArg = typeof(defaults[i]); | |
} else { | |
// Missin argument | |
struct curArg {} | |
} | |
// Missing nameless parameter | |
if (argIndex == -1 && is(defaults[i] == void) && getName!curArg != "") | |
result = Match.Failed; | |
// Non-convertible parameter | |
else if (!isImplicitlyConvertible!(curArg, Par)) | |
result = Match.Failed; | |
// Convertible parameter | |
else if (!is(const(getType!curArg) == const(Par)) && result == Match.Perfect) | |
result = Match.Conversion; | |
}} | |
return result; | |
} | |
int findOverload(Args...)() { | |
enum matches = [staticMap!(ApplyRight!(getMatch, Args), overloads)]; | |
static if (matches.count(Match.Perfect) == 1) | |
return matches.length - matches.find(Match.Perfect).length; | |
else static if (matches.count(Match.Conversion) == 1) | |
return matches.length - matches.find(Match.Conversion).length; | |
else return -1; | |
} | |
} | |
version(unittest) { | |
string fun(int a, int b = 2, int c = 3) { | |
return text(a, b, c); | |
} | |
string fun(int a, string b = "2", string c = "3") { | |
return text(a, b, c); | |
} | |
string fun(int a, int b, string c) { | |
return text(a, b, c); | |
} | |
} | |
unittest { | |
alias fun2 = named!fun; | |
assert(fun2(1, args.c = 4) == "124"); | |
assert(fun2(args.a = 2, args.b = "4") == "243"); | |
static assert(!__traits(compiles, fun2(args.a = 2, "4"))); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment