Skip to content

Instantly share code, notes, and snippets.

@Biotronic
Last active November 7, 2022 07:59
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Biotronic/fffa7d4c96d760da5129d27ba3307f73 to your computer and use it in GitHub Desktop.
Save Biotronic/fffa7d4c96d760da5129d27ba3307f73 to your computer and use it in GitHub Desktop.
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