1 module auxil.traits;
2 
3 version(unittest) import unit_threaded : Name;
4 
5 import std.traits : isTypeTuple;
6 
7 template isNullable(T)
8 {
9 	import std.traits : isInstanceOf;
10 	import std.typecons : Nullable;
11 
12 	enum isNullable = is(T == struct) && isInstanceOf!(Nullable, T);
13 }
14 
15 template isTimemarked(T)
16 {
17 	version (HAVE_TIMEMARKED)
18 	{
19 		import std.traits : isInstanceOf;
20 		import rdp.timemarked : Timemarked;
21 
22 		enum isTimemarked = is(T == struct) && isInstanceOf!(Timemarked, T);
23 	}
24 	else
25 		enum isTimemarked = false;
26 }
27 
28 template hasRenderHeader(alias A)
29 {
30 	import auxil.model : FixedAppender;
31 
32 	FixedAppender!32 app;
33 
34 	static if (is(typeof(A.renderHeader(app))))
35 		enum hasRenderHeader = true;
36 	else
37 		enum hasRenderHeader = false;
38 }
39 
40 private bool privateOrPackage()(string protection)
41 {
42 	return protection == "private" || protection == "package";
43 }
44 
45 template TypeOf(alias A)
46 {
47 	import std.traits : isType, Unqual;
48 
49 	static if (isType!A)
50 	{
51 		alias TypeOf = Unqual!A;
52 	}
53 	else
54 	{
55 		alias TypeOf = Unqual!(typeof(A));
56 	}
57 }
58 
59 // check if the member is readable/writeble?
60 private enum isReadableAndWritable(alias aggregate, string member) = __traits(compiles, __traits(getMember, aggregate, member) = __traits(getMember, aggregate, member));
61 private enum isPublic(alias aggregate, string member) = !__traits(getProtection, __traits(getMember, aggregate, member)).privateOrPackage;
62 
63 // check if the member is property with const qualifier
64 private template isConstProperty(alias aggregate, string member)
65 {
66 	import std.traits : isSomeFunction, hasFunctionAttributes;
67 
68 	static if(isSomeFunction!(__traits(getMember, typeof(aggregate), member)))
69 		enum isConstProperty = hasFunctionAttributes!(__traits(getMember, aggregate, member), "const", "@property");
70 	else
71 		enum isConstProperty = false;
72 }
73 
74 // check if the member is property
75 private template isProperty(alias aggregate, string member)
76 {
77 	import std.traits : isSomeFunction, functionAttributes, FunctionAttribute;
78 
79 	static if(isSomeFunction!(__traits(getMember, typeof(aggregate), member)))
80 		enum isProperty = !!(functionAttributes!(__traits(getMember, typeof(aggregate), member)) & FunctionAttribute.property);
81 	else
82 		enum isProperty = false;
83 }
84 
85 // check if the member is readable
86 private enum isReadable(alias aggregate, string member) = __traits(compiles, { auto _val = __traits(getMember, aggregate, member); });
87 private enum isMemberStatic(T, string member) = __traits(compiles, { auto a = mixin("T." ~ member); });
88 
89 private template isItSequence(T...)
90 {
91 	static if (T.length < 2)
92 		enum isItSequence = false;
93 	else
94 		enum isItSequence = true;
95 }
96 
97 private template hasProtection(alias aggregate, string member)
98 {
99 	enum hasProtection = __traits(compiles, { enum pl = __traits(getProtection, __traits(getMember, typeof(aggregate), member)); });
100 }
101 
102 version(unittest) @Name("aggregates")
103 @safe
104 unittest
105 {
106 	import unit_threaded : should, be;
107 
108 	static struct Test
109 	{
110 		float f = 7.7;
111 		int i = 8;
112 		string s = "some text";
113 	}
114 
115 	static struct StructWithStruct
116 	{
117 		double d = 8.8;
118 		long l = 999;
119 		Test t;
120 	}
121 
122 	static class TestClass
123 	{
124 
125 	}
126 
127 	static struct StructWithPointerAndClass
128 	{
129 		double* d;
130 		TestClass tc;
131 	}
132 
133 	static struct StructWithNestedClass
134 	{
135 		TestClass tc;
136 	}
137 
138 	assert( isProcessible!float);
139 	assert( isProcessible!(float*));
140 	assert( isProcessible!Test );
141 	assert( isProcessible!StructWithStruct);
142 	assert(!isProcessible!TestClass);
143 	assert(!isProcessible!StructWithPointerAndClass);
144 	assert(!isProcessible!StructWithNestedClass);
145 }
146 
147 template isProcessible(alias A)
148 {
149 	import std.traits, std.range;
150 
151 	static if (isType!A)
152 		alias T = Unqual!A;
153 	else
154 		alias T = Unqual!(typeof(A));
155 
156 	static if (isRandomAccessRange!T)
157 		enum isProcessible = true;
158 	else static if (is(T == struct) || is(T == union))
159 	{
160 		static foreach(member; DrawableMembers!T)
161 			static if (!is(typeof(isProcessible) == bool) &&
162 				!isProcessible!(mixin("T." ~ member)))
163 			{
164 				enum isProcessible = false;
165 			}
166 
167 		static if (!is(typeof(isProcessible) == bool))
168 			enum isProcessible = true;
169 	}
170 	else static if (
171 	       isStaticArray!T
172 	    || isAssociativeArray!T
173 	    || isSomeString!T
174 	    || isFloatingPoint!T
175 	    || isIntegral!T
176 	    || isSomeChar!T
177 	    || isPointer!T
178 	    || is(T == bool))
179 	{
180 		enum isProcessible = true;
181 	}
182 	else
183 		enum isProcessible = false;
184 }
185 
186 private enum isIgnored(alias tested) = __traits(isSame, ignored, tested);
187 
188 // This trait defines what members should be drawn -
189 // public members that are either readable and writable or getter properties
190 package template isMemberDrawable(alias value, string member)
191 {
192 	import std.algorithm : among;
193 	import std.traits : isSomeFunction, isType, Unqual;
194 
195 	static if (isType!value)
196 		alias T = Unqual!value;
197 	else
198 		alias T = Unqual!(typeof(value));
199 
200 	static if (isItSequence!value)
201 		enum isMemberDrawable = false;
202 	else static if (hasProtection!(value, member) && !isPublic!(value, member))
203 		enum isMemberDrawable = false;
204 	else static if (__traits(compiles, { auto v = mixin("T." ~ member); })) // static members
205 		enum isMemberDrawable = false;
206 	else static if (isItSequence!(__traits(getMember, value, member)))
207 		enum isMemberDrawable = false;
208 	else static if (isMemberStatic!(T, member))
209 		enum isMemberDrawable = false;
210 	else static if (isReadableAndWritable!(value, member) && !isSomeFunction!(__traits(getMember, T, member)))
211 		enum isMemberDrawable = !member.among("__ctor", "__dtor");
212 	else static if (isReadable!(value, member))
213 		enum isMemberDrawable = isProperty!(value, member); // a readable property is getter
214 	else
215 		enum isMemberDrawable = false;
216 }
217 
218 template isMemberDrawableAndNotIgnored(alias value, string member)
219 {
220 	import std.algorithm : among;
221 	import std.meta : AliasSeq, Filter;
222 	import std.traits : isSomeFunction, isType, Unqual;
223 
224 	static if (isType!value)
225 		alias T = Unqual!value;
226 	else
227 		alias T = Unqual!(typeof(value));
228 
229 	static if (member.among("__ctor", "__dtor", "this", "~this"))
230 		enum isMemberDrawableAndNotIgnored = false;
231 	else static if (isSymbol!(__traits(getMember, T, member)))
232 	{
233 		// check if the symbol has `ignored` attribute assigned
234 		static if (Filter!(isIgnored, AliasSeq!(__traits(getAttributes, __traits(getMember, T, member)))).length)
235 			enum isMemberDrawableAndNotIgnored = false;
236 		else
237 			enum isMemberDrawableAndNotIgnored = isMemberDrawable!(value, member);
238 	}
239 	else
240 		enum isMemberDrawableAndNotIgnored = isMemberDrawable!(value, member);
241 }
242 
243 /// returns alias sequence, members of which are members of value
244 /// that should be drawn
245 package template DrawableMembers(alias A)
246 {
247 	import std.meta : ApplyLeft, Filter, AliasSeq;
248 	import std.traits : isType, Unqual;
249 
250 	static if (isType!A)
251 	{
252 		alias Type = Unqual!A;
253 	}
254 	else
255 	{
256 		alias Type = Unqual!(typeof(A));
257 	}
258 
259 	Type symbol;
260 
261 	alias AllMembers = AliasSeq!(__traits(allMembers, Type));
262 	alias isProper = ApplyLeft!(isMemberDrawableAndNotIgnored, symbol);
263 	alias DrawableMembers = Filter!(isProper, AllMembers);
264 }
265 
266 /*
267 Rendering proxy
268 */
269 struct renderedAs(T){}
270 struct renderedAsPointee(string N)
271 {
272 	enum string name = N;
273 }
274 struct renderedAsMember(string N)
275 {
276 	enum string name = N;
277 }
278 struct ignored{}
279 private enum bool isRenderedAs(A) = is(A : renderedAs!T, T);
280 private enum bool isRenderedAs(alias a) = false;
281 package alias getRenderedAs(T : renderedAs!Proxy, Proxy) = Proxy;
282 package template getRenderedAs(alias value)
283 {
284 	private alias _list = ProxyList!value;
285 	static assert(_list.length == 1, `Only single rendering proxy is allowed`);
286 	alias getRenderedAs = _list[0];
287 }
288 
289 private import std.traits : isInstanceOf;
290 private import std.meta : staticMap, Filter;
291 
292 private alias ProxyList(alias value) = staticMap!(getRenderedAs, Filter!(isRenderedAs, __traits(getAttributes, value)));
293 private template isSymbol(A...)
294 {
295 	static if (A.length == 1)
296 		enum isSymbol = .isSymbol!A;
297 	else
298 		enum isSymbol = false;
299 }
300 private template isSymbol(alias A)
301 {
302 	static if (__traits(compiles, { enum id = __traits(identifier, A); }))
303 		enum isSymbol = __traits(identifier, A) != "A";
304 	else
305 		enum isSymbol = false;
306 }
307 
308 package template hasRenderedAs(alias A)
309 {
310 	import std.traits : isBuiltinType, isType;
311 	static if (isSymbol!A)
312 	{
313 		private enum listLength = ProxyList!A.length;
314 		static assert(listLength <= 1, `Only single rendering proxy is allowed`);
315 		enum hasRenderedAs = listLength == 1;
316 	}
317 	else
318 		enum hasRenderedAs = false;
319 }
320 
321 private template isRenderedAsMember(alias A) if (isSymbol!A)
322 {
323 	enum bool isRenderedAsMember = isInstanceOf!(renderedAsMember, TypeOf!A);
324 }
325 
326 private template isRenderedAsMember(alias A) if (!isSymbol!A)
327 {
328 	enum bool isRenderedAsMember = false;
329 }
330 
331 package template hasRenderedAsMember(T) if (!isSymbol!T)
332 {
333 	enum hasRenderedAsMember = false;
334 }
335 
336 package template hasRenderedAsMember(T) if (isSymbol!T)
337 {
338 	import std.meta : Filter;
339 
340 	alias attr = Filter!(isRenderedAsMember, __traits(getAttributes, T));
341 	static if (attr.length == 1)
342 		enum hasRenderedAsMember = true;
343 	else static if (attr.length == 0)
344 		enum hasRenderedAsMember = false;
345 	else
346 		static assert(0, "Only single renderedAsMember attribute is allowd");
347 }
348 
349 package template getRenderedAsMember(T)
350 {
351 	alias Attributes = Filter!(isRenderedAsMember, __traits(getAttributes, T));
352 	enum string getRenderedAsMember = Attributes[0].name;
353 }
354 
355 alias getRenderedAsMemberString(alias A) = getGivenAttributeAsString!(A, "renderedAsMember");
356 alias getRenderedAsPointeeString(alias A) = getGivenAttributeAsString!(A, "renderedAsPointee");
357 
358 version(unittest) @Name("getRenderedAsMemberString")
359 unittest
360 {
361 	import std.meta : AliasSeq;
362 
363 	static struct S0
364 	{
365 		int i;
366 		float f;
367 	}
368 
369 	S0 s1;
370 	@("renderedAsMember.i")
371 	S0 s2;
372 
373 	static assert ([getRenderedAsMemberString!S0] == []);
374 	static assert ([getRenderedAsMemberString!s1] == []);
375 	static assert ([getRenderedAsMemberString!s2] == ["i"]);
376 
377 	static struct S2
378 	{
379 		S0 s;
380 	}
381 
382 	static struct S3
383 	{
384 		@("renderedAsMember.s.f")
385 		S0 s;
386 	}
387 
388 	static assert ([getRenderedAsMemberString!S2] == []);
389 	static assert ([getRenderedAsMemberString!(S3.s)] == ["s.f"]);
390 }
391 
392 package template getGivenAttributeAsString(alias A, string GivenAttribute)
393 {
394 	import std.meta : staticMap, Filter, AliasSeq;
395 
396 	enum isString(alias A) = is(TypeOf!A == string);
397 	enum L = GivenAttribute.length;
398 	enum isGivenAttribute(string s) = s.length > L && s[0..L] == GivenAttribute;
399 	enum dropPrefix(string s) = s[L+1..$];
400 	alias T = TypeOf!A;
401 
402 	template impl(alias N)
403 	{
404 		alias AllString = Filter!(isString, __traits(getAttributes, N));
405 		alias AllAttrib = Filter!(isGivenAttribute, AllString);
406 		alias Attr = staticMap!(dropPrefix, AllAttrib);
407 		static assert(Attr.length < 2, "Only single " ~ GivenAttribute ~ " attribute is allowed");
408 
409 		static if (Attr.length == 1)
410 			alias impl = Attr;
411 		else
412 			alias impl = AliasSeq!();
413 	}
414 
415 	static if (isSymbol!A)
416 		alias getGivenAttributeAsString = impl!A;
417 	else static if (isSymbol!T)
418 		alias getGivenAttributeAsString = impl!T;
419 	else
420 		alias getGivenAttributeAsString = AliasSeq!();
421 }