1 module workspaced.visitors.attributes;
2 
3 import dparse.ast;
4 import dparse.formatter;
5 import dparse.lexer;
6 
7 import std.algorithm;
8 import std.conv;
9 import std.range;
10 import std.string;
11 import std.variant;
12 
13 class AttributesVisitor : ASTVisitor
14 {
15 	bool sticky;
16 
17 	override void visit(const MemberFunctionAttribute attribute)
18 	{
19 		if (attribute.tokenType != IdType.init)
20 			context.attributes ~= ASTContext.AnyAttributeInfo(
21 					ASTContext.AnyAttribute(cast() attribute), sticky);
22 		attribute.accept(this);
23 	}
24 
25 	override void visit(const FunctionAttribute attribute)
26 	{
27 		if (attribute.token.type != IdType.init)
28 			context.attributes ~= ASTContext.AnyAttributeInfo(ASTContext.AnyAttribute(
29 					ASTContext.SimpleAttribute([cast() attribute.token])), sticky);
30 		attribute.accept(this);
31 	}
32 
33 	override void visit(const AtAttribute attribute)
34 	{
35 		context.attributes ~= ASTContext.AnyAttributeInfo(
36 				ASTContext.AnyAttribute(cast() attribute), sticky);
37 		attribute.accept(this);
38 	}
39 
40 	override void visit(const PragmaExpression attribute)
41 	{
42 		context.attributes ~= ASTContext.AnyAttributeInfo(
43 				ASTContext.AnyAttribute(cast() attribute), sticky);
44 		attribute.accept(this);
45 	}
46 
47 	override void visit(const Deprecated attribute)
48 	{
49 		context.attributes ~= ASTContext.AnyAttributeInfo(
50 				ASTContext.AnyAttribute(cast() attribute), sticky);
51 		attribute.accept(this);
52 	}
53 
54 	override void visit(const AlignAttribute attribute)
55 	{
56 		context.attributes ~= ASTContext.AnyAttributeInfo(
57 				ASTContext.AnyAttribute(cast() attribute), sticky);
58 		attribute.accept(this);
59 	}
60 
61 	override void visit(const LinkageAttribute attribute)
62 	{
63 		context.attributes ~= ASTContext.AnyAttributeInfo(
64 				ASTContext.AnyAttribute(cast() attribute), sticky);
65 		attribute.accept(this);
66 	}
67 
68 	override void visit(const Attribute attribute)
69 	{
70 		Token[] tokens;
71 		if (attribute.attribute.type != IdType.init)
72 			tokens ~= attribute.attribute;
73 		if (attribute.identifierChain)
74 			tokens ~= attribute.identifierChain.identifiers;
75 		if (tokens.length)
76 			context.attributes ~= ASTContext.AnyAttributeInfo(
77 					ASTContext.AnyAttribute(ASTContext.SimpleAttribute(tokens)), sticky);
78 		attribute.accept(this);
79 	}
80 
81 	override void visit(const StorageClass storage)
82 	{
83 		if (storage.token.type != IdType.init)
84 			context.attributes ~= ASTContext.AnyAttributeInfo(ASTContext.AnyAttribute(
85 					ASTContext.SimpleAttribute([cast() storage.token])), sticky);
86 		storage.accept(this);
87 	}
88 
89 	override void visit(const AttributeDeclaration dec)
90 	{
91 		sticky = true;
92 		dec.accept(this);
93 		sticky = false;
94 	}
95 
96 	override void visit(const Declaration dec)
97 	{
98 		auto c = context.save;
99 		// attribute ':' (private:)
100 		bool attribDecl = !!dec.attributeDeclaration;
101 		if (attribDecl)
102 		{
103 			sticky = true;
104 			processDeclaration(dec);
105 			sticky = false;
106 		}
107 		else
108 		{
109 			processDeclaration(dec);
110 			context.restore(c);
111 		}
112 	}
113 
114 	override void visit(const InterfaceDeclaration dec)
115 	{
116 		auto c = context.save;
117 		context.pushContainer(ASTContext.ContainerAttribute.Type.class_, dec.name.text);
118 		super.visit(dec);
119 		context.restore(c);
120 	}
121 
122 	override void visit(const ClassDeclaration dec)
123 	{
124 		auto c = context.save;
125 		context.pushContainer(ASTContext.ContainerAttribute.Type.class_, dec.name.text);
126 		super.visit(dec);
127 		context.restore(c);
128 	}
129 
130 	override void visit(const StructDeclaration dec)
131 	{
132 		auto c = context.save;
133 		context.pushContainer(ASTContext.ContainerAttribute.Type.struct_, dec.name.text);
134 		super.visit(dec);
135 		context.restore(c);
136 	}
137 
138 	override void visit(const UnionDeclaration dec)
139 	{
140 		auto c = context.save;
141 		context.pushContainer(ASTContext.ContainerAttribute.Type.union_, dec.name.text);
142 		super.visit(dec);
143 		context.restore(c);
144 	}
145 
146 	override void visit(const EnumDeclaration dec)
147 	{
148 		auto c = context.save;
149 		context.pushContainer(ASTContext.ContainerAttribute.Type.enum_, dec.name.text);
150 		super.visit(dec);
151 		context.restore(c);
152 	}
153 
154 	override void visit(const TemplateDeclaration dec)
155 	{
156 		auto c = context.save;
157 		context.pushContainer(ASTContext.ContainerAttribute.Type.template_, dec.name.text);
158 		super.visit(dec);
159 		context.restore(c);
160 	}
161 
162 	void processDeclaration(const Declaration dec)
163 	{
164 		dec.accept(this);
165 	}
166 
167 	ASTContext context;
168 
169 	alias visit = ASTVisitor.visit;
170 }
171 
172 struct ASTContext
173 {
174 	struct UserdataAttribute
175 	{
176 		string name;
177 		Variant variant;
178 	}
179 
180 	struct ContainerAttribute
181 	{
182 		enum Type
183 		{
184 			class_,
185 			interface_,
186 			struct_,
187 			union_,
188 			enum_,
189 			template_
190 		}
191 
192 		Type type;
193 		string name;
194 	}
195 
196 	struct SimpleAttribute
197 	{
198 		Token[] attributes;
199 
200 		Token attribute() @property
201 		{
202 			if (attributes.length == 1)
203 				return attributes[0];
204 			else
205 				return Token.init;
206 		}
207 
208 		Token firstAttribute() @property
209 		{
210 			if (attributes.length >= 1)
211 				return attributes[0];
212 			else
213 				return Token.init;
214 		}
215 
216 		string toString() const
217 		{
218 			return attributes.map!(a => a.text.length ? a.text : str(a.type)).join(".");
219 		}
220 	}
221 
222 	alias AnyAttribute = Algebraic!(PragmaExpression, Deprecated, AtAttribute, AlignAttribute, LinkageAttribute,
223 			SimpleAttribute, MemberFunctionAttribute, ContainerAttribute, UserdataAttribute);
224 
225 	class AttributeWithInfo(T)
226 	{
227 		T value;
228 		bool sticky;
229 
230 		alias value this;
231 
232 		this(T value, bool sticky)
233 		{
234 			this.value = value;
235 			this.sticky = sticky;
236 		}
237 
238 		auto opUnary(string op : "*")()
239 		{
240 			return this;
241 		}
242 	}
243 
244 	struct AnyAttributeInfo
245 	{
246 		AnyAttribute base;
247 		// if true then this attribute is applying to all the following statements in the block (attribute ':')
248 		bool sticky;
249 
250 		AttributeWithInfo!T peek(T)()
251 		{
252 			auto ret = base.peek!T;
253 			if (!!ret)
254 				return new AttributeWithInfo!T(*ret, sticky);
255 			else
256 				return null;
257 		}
258 
259 		alias base this;
260 	}
261 
262 	AnyAttributeInfo[] attributes;
263 
264 	/// Attributes only inside a container
265 	auto localAttributes() @property
266 	{
267 		auto end = attributes.retro.countUntil!(a => !!a.peek!ContainerAttribute);
268 		if (end == -1)
269 			return attributes;
270 		else
271 			return attributes[$ - end .. $];
272 	}
273 
274 	auto attributeDescriptions() @property
275 	{
276 		return attributes.map!((a) {
277 			if (auto prag = a.peek!PragmaExpression)
278 				return "pragma(" ~ prag.identifier.text ~ ", ...["
279 					~ prag.argumentList.items.length.to!string ~ "])";
280 			else if (auto depr = a.peek!Deprecated)
281 				return "deprecated";
282 			else if (auto at = a.peek!AtAttribute)
283 				return "@" ~ at.identifier.text;
284 			else if (auto align_ = a.peek!AlignAttribute)
285 				return "align";
286 			else if (auto linkage = a.peek!LinkageAttribute)
287 				return "extern (" ~ linkage.identifier.text ~ (linkage.hasPlusPlus ? "++" : "") ~ ")";
288 			else if (auto simple = a.peek!SimpleAttribute)
289 				return simple.attributes.map!(a => a.text.length ? a.text : str(a.type)).join(".");
290 			else if (auto mfunAttr = a.peek!MemberFunctionAttribute)
291 				return "mfun<" ~ (mfunAttr.atAttribute
292 					? "@" ~ mfunAttr.atAttribute.identifier.text : "???") ~ ">";
293 			else if (auto container = a.peek!ContainerAttribute)
294 				return container.type.to!string[0 .. $ - 1] ~ " " ~ container.name;
295 			else if (auto user = a.peek!UserdataAttribute)
296 				return "user " ~ user.name;
297 			else
298 				return "Unknown type?!";
299 		});
300 	}
301 
302 	private static auto formatAttributes(T)(T attributes)
303 	{
304 		return attributes.map!((a) {
305 			auto t = appender!string;
306 			if (auto prag = a.peek!PragmaExpression)
307 				format(t, *prag);
308 			else if (auto depr = a.peek!Deprecated)
309 				format(t, *depr);
310 			else if (auto at = a.peek!AtAttribute)
311 				format(t, *at);
312 			else if (auto align_ = a.peek!AlignAttribute)
313 				format(t, *align_);
314 			else if (auto linkage = a.peek!LinkageAttribute)
315 				format(t, *linkage);
316 			else if (auto simple = a.peek!SimpleAttribute)
317 				return simple.attributes.map!(a => a.text.length ? a.text : str(a.type)).join(".");
318 			else if (auto mfunAttr = a.peek!MemberFunctionAttribute)
319 				return str(mfunAttr.tokenType);
320 			else if (auto container = a.peek!ContainerAttribute)
321 				return null;
322 			else if (auto user = a.peek!UserdataAttribute)
323 				return null;
324 			else
325 				return "/* <<ERROR>> */";
326 			return t.data.strip;
327 		});
328 	}
329 
330 	auto formattedAttributes() @property
331 	{
332 		return formatAttributes(attributes.normalizeAttributes);
333 	}
334 
335 	auto localFormattedAttributes() @property
336 	{
337 		return formatAttributes(localAttributes.normalizeAttributes);
338 	}
339 
340 	auto simpleAttributes() @property
341 	{
342 		return attributes.filter!(a => !!a.peek!SimpleAttribute)
343 			.map!(a => *a.peek!SimpleAttribute);
344 	}
345 
346 	auto simpleAttributesInContainer() @property
347 	{
348 		return localAttributes.filter!(a => !!a.peek!SimpleAttribute)
349 			.map!(a => *a.peek!SimpleAttribute);
350 	}
351 
352 	auto atAttributes() @property
353 	{
354 		return attributes.filter!(a => !!a.peek!AtAttribute)
355 			.map!(a => *a.peek!AtAttribute);
356 	}
357 
358 	auto atAttributesInContainer() @property
359 	{
360 		return localAttributes.filter!(a => !!a.peek!AtAttribute)
361 			.map!(a => *a.peek!AtAttribute);
362 	}
363 
364 	auto memberFunctionAttributes() @property
365 	{
366 		return attributes.filter!(a => !!a.peek!MemberFunctionAttribute)
367 			.map!(a => *a.peek!MemberFunctionAttribute);
368 	}
369 
370 	auto memberFunctionAttributesInContainer() @property
371 	{
372 		return localAttributes.filter!(a => !!a.peek!MemberFunctionAttribute)
373 			.map!(a => *a.peek!MemberFunctionAttribute);
374 	}
375 
376 	auto userdataAttributes() @property
377 	{
378 		return attributes.filter!(a => !!a.peek!UserdataAttribute)
379 			.map!(a => *a.peek!UserdataAttribute);
380 	}
381 
382 	auto containerAttributes() @property
383 	{
384 		return attributes.filter!(a => !!a.peek!ContainerAttribute)
385 			.map!(a => *a.peek!ContainerAttribute);
386 	}
387 
388 	bool isToken(IdType t) @property
389 	{
390 		return memberFunctionAttributes.any!(a => a.tokenType == t)
391 			|| simpleAttributes.any!(a => a.attribute.type == t);
392 	}
393 
394 	bool isTokenInContainer(IdType t) @property
395 	{
396 		return memberFunctionAttributesInContainer.any!(a => a.tokenType == t)
397 			|| simpleAttributesInContainer.any!(a => a.attribute.type == t);
398 	}
399 
400 	bool isNothrow() @property
401 	{
402 		return isToken(tok!"nothrow");
403 	}
404 
405 	bool isNothrowInContainer() @property
406 	{
407 		return isTokenInContainer(tok!"nothrow");
408 	}
409 
410 	bool isNogc() @property
411 	{
412 		return atAttributes.any!(a => a.identifier.text == "nogc");
413 	}
414 
415 	bool isNogcInContainer() @property
416 	{
417 		return atAttributesInContainer.any!(a => a.identifier.text == "nogc");
418 	}
419 
420 	bool isStatic() @property
421 	{
422 		return isToken(tok!"static");
423 	}
424 
425 	bool isFinal() @property
426 	{
427 		return isToken(tok!"final");
428 	}
429 
430 	bool isAbstract() @property
431 	{
432 		return isToken(tok!"abstract");
433 	}
434 
435 	bool isAbstractInContainer() @property
436 	{
437 		return isTokenInContainer(tok!"abstract");
438 	}
439 
440 	/// Returns: if a block needs implementations (virtual/abstract or interface methods)
441 	/// 0 = must not be implemented (not in a container, private, static or final method)
442 	/// 1 = optionally implementable, must be implemented if there is no function body
443 	/// 9 = must be implemented
444 	int requiredImplementationLevel() @property
445 	{
446 		auto container = containerAttributes;
447 		if (container.empty || protectionType == tok!"private" || isStatic || isFinal)
448 			return 0;
449 		ContainerAttribute innerContainer = container.tail(1).front;
450 		if (innerContainer.type == ContainerAttribute.Type.class_)
451 			return isAbstractInContainer ? 9 : 1;
452 		else // interface (or others)
453 			return 9;
454 	}
455 
456 	AttributeWithInfo!SimpleAttribute protectionAttribute() @property
457 	{
458 		auto prot = simpleAttributes.filter!(a => a.firstAttribute.isProtectionToken).tail(1);
459 		if (prot.empty)
460 			return null;
461 		else
462 			return prot.front;
463 	}
464 
465 	IdType protectionType() @property
466 	{
467 		auto attr = protectionAttribute;
468 		if (attr is null)
469 			return IdType.init;
470 		else
471 			return attr.attributes[0].type;
472 	}
473 
474 	void pushData(string name, Variant value, bool sticky = false)
475 	{
476 		attributes ~= AnyAttributeInfo(AnyAttribute(UserdataAttribute(name, value)), sticky);
477 	}
478 
479 	void pushData(T)(string name, T value, bool sticky = false)
480 	{
481 		pushData(name, Variant(value), sticky);
482 	}
483 
484 	void pushContainer(ContainerAttribute.Type type, string name)
485 	{
486 		attributes ~= AnyAttributeInfo(AnyAttribute(ContainerAttribute(type, name)), false);
487 	}
488 
489 	ASTContext save()
490 	{
491 		return ASTContext(attributes[]);
492 	}
493 
494 	void restore(ASTContext c)
495 	{
496 		attributes = c.attributes;
497 	}
498 }
499 
500 bool isProtectionToken(const Token t) @property
501 {
502 	return !!t.type.among!(tok!"public", tok!"private", tok!"protected", tok!"package");
503 }
504 
505 ASTContext.AnyAttributeInfo[] normalizeAttributes(ASTContext.AnyAttributeInfo[] attributes)
506 {
507 	ASTContext.AnyAttributeInfo[] ret;
508 	ret.reserve(attributes.length);
509 	bool gotProtection;
510 	bool gotSafety;
511 	foreach_reverse (ref attr; attributes)
512 	{
513 		if (auto simple = attr.peek!(ASTContext.SimpleAttribute))
514 		{
515 			if (simple.firstAttribute.isProtectionToken)
516 			{
517 				if (gotProtection)
518 					continue;
519 				gotProtection = true;
520 			}
521 		}
522 		else if (auto at = attr.peek!AtAttribute)
523 		{
524 			// search & replace for existing @safe, @trusted, @system
525 			if (at.identifier.text.among!("safe", "trusted", "system"))
526 			{
527 				if (gotSafety)
528 					continue;
529 				gotSafety = true;
530 			}
531 		}
532 		ret ~= attr;
533 	}
534 	ret.reverse();
535 	return ret;
536 }