1 /**
2  * Named method and struct literal arguments
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <ae@cy.md>
12  */
13 
14 module ae.utils.meta.args;
15 
16 import std.traits;
17 
18 // Inspired by
19 // http://forum.dlang.org/post/awjuoemsnmxbfgzhgkgx@forum.dlang.org
20 
21 /// Simulates named arguments for function calls.
22 /// Accepts arguments as lambdas (name => value) on the template parameter list,
23 /// and positional arguments on the runtime parameter list (see examples below).
24 template args(alias fun, dgs...)
25 if (is(typeof(fun) == function))
26 {
27 	auto args(PosArgs...)(auto ref PosArgs posArgs)
28 	{
29 		ParameterTypeTuple!fun args;
30 		enum names = ParameterIdentifierTuple!fun;
31 
32 		foreach (i, ref arg; posArgs)
33 			args[i] = posArgs[i];
34 		foreach (i, arg; ParameterDefaults!fun)
35 			static if (i >= posArgs.length)
36 				args[i] = ParameterDefaults!fun[i];
37 
38 		// anything works here, but use a custom type to avoid user errors
39 		static struct DummyType {}
40 
41 		foreach (dg; dgs)
42 		{
43 			alias fun = dg!DummyType;
44 			static if (is(FunctionTypeOf!fun PT == __parameters))
45 			{
46 				enum name = __traits(identifier, PT);
47 				enum index = argIndex!names(name);
48 				static assert(index >= 0, "No such argument: " ~ name);
49 				args[index] = fun(DummyType.init);
50 			}
51 			else
52 				static assert(false, "Failed to extract parameter name from " ~ fun.stringof);
53 		}
54 		return fun(args);
55 	}
56 }
57 
58 ///
59 unittest
60 {
61 	static int fun(int a=1, int b=2, int c=3, int d=4, int e=5)
62 	{
63 		return a+b+c+d+e;
64 	}
65 
66 	assert(args!(fun) == 15);
67 	assert(args!(fun, b=>3) == 16);
68 	assert(args!(fun, b=>3, d=>3) == 15);
69 
70 	static assert(!is(typeof(args!(fun, b=>b)())));
71 	static assert(!is(typeof(args!(fun, x=>42)())));
72 }
73 
74 /// Mixing named and positional arguments
75 unittest
76 {
77 	static int fun(int a, int b=2, int c=3, int d=4, int e=5)
78 	{
79 		return a+b+c+d+e;
80 	}
81 
82 	assert(args!(fun)(1) == 15);
83 	assert(args!(fun, b=>3)(1) == 16);
84 }
85 
86 /// Simulates named arguments for struct literals.
87 template args(S, dgs...)
88 if (is(S == struct))
89 {
90 	@property S args()
91 	{
92 		S s;
93 
94 		// anything works here, but use a custom type to avoid user errors
95 		static struct DummyType {}
96 
97 		foreach (dg; dgs)
98 		{
99 			alias fun = dg!DummyType;
100 			static if (is(FunctionTypeOf!fun PT == __parameters))
101 			{
102 				enum name = __traits(identifier, PT);
103 				enum index = fieldIndex!S(name);
104 				static assert(index >= 0, "No such field: " ~ name);
105 				s.tupleof[index] = fun(DummyType.init);
106 			}
107 			else
108 				static assert(false, "Failed to extract parameter name from " ~ fun.stringof);
109 		}
110 		return s;
111 	}
112 }
113 
114 unittest
115 {
116 	static struct S
117 	{
118 		int a = 1, b = 2, c = 3, d = 4, e = 5;
119 		@property int sum() { return a + b + c + d + e; }
120 	}
121 
122 	assert(args!(S).sum == 15);
123 	assert(args!(S, b=>3).sum == 16);
124 	assert(args!(S, b=>3, d=>3).sum == 15);
125 
126 	static assert(!is(typeof(args!(S, b=>b))));
127 	static assert(!is(typeof(args!(S, x=>42))));
128 }
129 
130 unittest
131 {
132 	struct S
133 	{
134 		union { int a, b; }
135 	}
136 
137 	assert(args!(S, a => 1).a == 1);
138 	assert(args!(S, b => 1).a == 1);
139 }
140 
141 private sizediff_t argIndex(names...)(string name)
142 {
143 	foreach (i, argName; names)
144 		if (name == argName)
145 			return i;
146 	return -1;
147 }
148 
149 private sizediff_t fieldIndex(S)(string name)
150 {
151 	import ae.utils.meta : rangeTuple;
152 
153 	foreach (i; rangeTuple!(S.tupleof.length))
154 		if (__traits(identifier, S.tupleof[i]) == name)
155 			return i;
156 	return -1;
157 }