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 (is(T == struct) || is(T == union))
157 	{
158 		static foreach(member; DrawableMembers!T)
159 			static if (!is(typeof(isProcessible) == bool) &&
160 				!isProcessible!(mixin("T." ~ member)))
161 			{
162 				enum isProcessible = false;
163 			}
164 
165 		static if (!is(typeof(isProcessible) == bool))
166 			enum isProcessible = true;
167 	}
168 	else static if (
169 	       isStaticArray!T
170 	    || isRandomAccessRange!T
171 	    || isAssociativeArray!T
172 	    || isSomeString!T
173 	    || isFloatingPoint!T
174 	    || isIntegral!T
175 	    || isSomeChar!T
176 	    || isPointer!T
177 	    || is(T == bool))
178 	{
179 		enum isProcessible = true;
180 	}
181 	else
182 		enum isProcessible = false;
183 }
184 
185 private enum isIgnored(alias tested) = __traits(isSame, ignored, tested);
186 
187 // This trait defines what members should be drawn -
188 // public members that are either readable and writable or getter properties
189 package template isMemberDrawable(alias value, string member)
190 {
191 	import std.algorithm : among;
192 	import std.traits : isSomeFunction, isType, Unqual;
193 
194 	static if (isType!value)
195 		alias T = Unqual!value;
196 	else
197 		alias T = Unqual!(typeof(value));
198 
199 	static if (isItSequence!value)
200 		enum isMemberDrawable = false;
201 	else static if (hasProtection!(value, member) && !isPublic!(value, member))
202 		enum isMemberDrawable = false;
203 	else static if (__traits(compiles, { auto v = mixin("T." ~ member); })) // static members
204 		enum isMemberDrawable = false;
205 	else static if (isItSequence!(__traits(getMember, value, member)))
206 		enum isMemberDrawable = false;
207 	else static if (isMemberStatic!(T, member))
208 		enum isMemberDrawable = false;
209 	else static if (isReadableAndWritable!(value, member) && !isSomeFunction!(__traits(getMember, T, member)))
210 		enum isMemberDrawable = !member.among("__ctor", "__dtor");
211 	else static if (isReadable!(value, member))
212 		enum isMemberDrawable = isProperty!(value, member); // a readable property is getter
213 	else
214 		enum isMemberDrawable = false;
215 }
216 
217 template isMemberDrawableAndNotIgnored(alias value, string member)
218 {
219 	import std.algorithm : among;
220 	import std.meta : AliasSeq, Filter;
221 	import std.traits : isSomeFunction, isType, Unqual;
222 
223 	static if (isType!value)
224 		alias T = Unqual!value;
225 	else
226 		alias T = Unqual!(typeof(value));
227 
228 	static if (member.among("__ctor", "__dtor", "this", "~this"))
229 		enum isMemberDrawableAndNotIgnored = false;
230 	else static if (isSymbol!(__traits(getMember, T, member)))
231 	{
232 		// check if the symbol has `ignored` attribute assigned
233 		static if (Filter!(isIgnored, AliasSeq!(__traits(getAttributes, __traits(getMember, T, member)))).length)
234 			enum isMemberDrawableAndNotIgnored = false;
235 		else
236 			enum isMemberDrawableAndNotIgnored = isMemberDrawable!(value, member);
237 	}
238 	else
239 		enum isMemberDrawableAndNotIgnored = isMemberDrawable!(value, member);
240 }
241 
242 /// returns alias sequence, members of which are members of value
243 /// that should be drawn
244 package template DrawableMembers(alias A)
245 {
246 	import std.meta : ApplyLeft, Filter, AliasSeq;
247 	import std.traits : isType, Unqual;
248 
249 	static if (isType!A)
250 	{
251 		alias Type = Unqual!A;
252 	}
253 	else
254 	{
255 		alias Type = Unqual!(typeof(A));
256 	}
257 
258 	Type symbol;
259 
260 	alias AllMembers = AliasSeq!(__traits(allMembers, Type));
261 	alias isProper = ApplyLeft!(isMemberDrawableAndNotIgnored, symbol);
262 	alias DrawableMembers = Filter!(isProper, AllMembers);
263 }
264 
265 /*
266 Rendering proxy
267 */
268 struct renderedAs(T){}
269 struct renderedAsPointee(string N)
270 {
271 	enum string name = N;
272 }
273 struct renderedAsMember(string N)
274 {
275 	enum string name = N;
276 }
277 struct ignored{}
278 private enum bool isRenderedAs(A) = is(A : renderedAs!T, T);
279 private enum bool isRenderedAs(alias a) = false;
280 package alias getRenderedAs(T : renderedAs!Proxy, Proxy) = Proxy;
281 package template getRenderedAs(alias value)
282 {
283 	private alias _list = ProxyList!value;
284 	static assert(_list.length == 1, `Only single rendering proxy is allowed`);
285 	alias getRenderedAs = _list[0];
286 }
287 
288 private import std.traits : isInstanceOf;
289 private import std.meta : staticMap, Filter;
290 
291 private alias ProxyList(alias value) = staticMap!(getRenderedAs, Filter!(isRenderedAs, __traits(getAttributes, value)));
292 private template isSymbol(A...)
293 {
294 	static if (A.length == 1)
295 		enum isSymbol = .isSymbol!A;
296 	else
297 		enum isSymbol = false;
298 }
299 private template isSymbol(alias A)
300 {
301 	static if (__traits(compiles, { enum id = __traits(identifier, A); }))
302 		enum isSymbol = __traits(identifier, A) != "A";
303 	else
304 		enum isSymbol = false;
305 }
306 
307 package template hasRenderedAs(alias A)
308 {
309 	import std.traits : isBuiltinType, isType;
310 	static if (isSymbol!A)
311 	{
312 		private enum listLength = ProxyList!A.length;
313 		static assert(listLength <= 1, `Only single rendering proxy is allowed`);
314 		enum hasRenderedAs = listLength == 1;
315 	}
316 	else
317 		enum hasRenderedAs = false;
318 }
319 
320 private template isRenderedAsMember(alias A) if (isSymbol!A)
321 {
322 	enum bool isRenderedAsMember = isInstanceOf!(renderedAsMember, TypeOf!A);
323 }
324 
325 private template isRenderedAsMember(alias A) if (!isSymbol!A)
326 {
327 	enum bool isRenderedAsMember = false;
328 }
329 
330 package template hasRenderedAsMember(T) if (!isSymbol!T)
331 {
332 	enum hasRenderedAsMember = false;
333 }
334 
335 package template hasRenderedAsMember(T) if (isSymbol!T)
336 {
337 	import std.meta : Filter;
338 
339 	alias attr = Filter!(isRenderedAsMember, __traits(getAttributes, T));
340 	static if (attr.length == 1)
341 		enum hasRenderedAsMember = true;
342 	else static if (attr.length == 0)
343 		enum hasRenderedAsMember = false;
344 	else
345 		static assert(0, "Only single renderedAsMember attribute is allowd");
346 }
347 
348 package template getRenderedAsMember(T)
349 {
350 	alias Attributes = Filter!(isRenderedAsMember, __traits(getAttributes, T));
351 	enum string getRenderedAsMember = Attributes[0].name;
352 }
353 
354 alias getRenderedAsMemberString(alias A) = getGivenAttributeAsString!(A, "renderedAsMember");
355 alias getRenderedAsPointeeString(alias A) = getGivenAttributeAsString!(A, "renderedAsPointee");
356 
357 version(unittest) @Name("getRenderedAsMemberString")
358 unittest
359 {
360 	import std.meta : AliasSeq;
361 
362 	static struct S0
363 	{
364 		int i;
365 		float f;
366 	}
367 
368 	S0 s1;
369 	@("renderedAsMember.i")
370 	S0 s2;
371 
372 	static assert ([getRenderedAsMemberString!S0] == []);
373 	static assert ([getRenderedAsMemberString!s1] == []);
374 	static assert ([getRenderedAsMemberString!s2] == ["i"]);
375 
376 	static struct S2
377 	{
378 		S0 s;
379 	}
380 
381 	static struct S3
382 	{
383 		@("renderedAsMember.s.f")
384 		S0 s;
385 	}
386 
387 	static assert ([getRenderedAsMemberString!S2] == []);
388 	static assert ([getRenderedAsMemberString!(S3.s)] == ["s.f"]);
389 }
390 
391 package template getGivenAttributeAsString(alias A, string GivenAttribute)
392 {
393 	import std.meta : staticMap, Filter, AliasSeq;
394 
395 	enum isString(alias A) = is(TypeOf!A == string);
396 	enum L = GivenAttribute.length;
397 	enum isGivenAttribute(string s) = s.length > L && s[0..L] == GivenAttribute;
398 	enum dropPrefix(string s) = s[L+1..$];
399 	alias T = TypeOf!A;
400 
401 	template impl(alias N)
402 	{
403 		alias AllString = Filter!(isString, __traits(getAttributes, N));
404 		alias AllAttrib = Filter!(isGivenAttribute, AllString);
405 		alias Attr = staticMap!(dropPrefix, AllAttrib);
406 		static assert(Attr.length < 2, "Only single " ~ GivenAttribute ~ " attribute is allowed");
407 
408 		static if (Attr.length == 1)
409 			alias impl = Attr;
410 		else
411 			alias impl = AliasSeq!();
412 	}
413 
414 	static if (isSymbol!A)
415 		alias getGivenAttributeAsString = impl!A;
416 	else static if (isSymbol!T)
417 		alias getGivenAttributeAsString = impl!T;
418 	else
419 		alias getGivenAttributeAsString = AliasSeq!();
420 }