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