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 
217 	alias AnyAttribute = Algebraic!(PragmaExpression, Deprecated, AtAttribute, AlignAttribute, LinkageAttribute,
218 			SimpleAttribute, MemberFunctionAttribute, ContainerAttribute, UserdataAttribute);
219 
220 	class AttributeWithInfo(T)
221 	{
222 		T value;
223 		bool sticky;
224 
225 		alias value this;
226 
227 		this(T value, bool sticky)
228 		{
229 			this.value = value;
230 			this.sticky = sticky;
231 		}
232 
233 		auto opUnary(string op : "*")()
234 		{
235 			return this;
236 		}
237 	}
238 
239 	struct AnyAttributeInfo
240 	{
241 		AnyAttribute base;
242 		// if true then this attribute is applying to all the following statements in the block (attribute ':')
243 		bool sticky;
244 
245 		AttributeWithInfo!T peek(T)()
246 		{
247 			auto ret = base.peek!T;
248 			if (!!ret)
249 				return new AttributeWithInfo!T(*ret, sticky);
250 			else
251 				return null;
252 		}
253 
254 		alias base this;
255 	}
256 
257 	AnyAttributeInfo[] attributes;
258 
259 	/// Attributes only inside a container
260 	auto localAttributes() @property
261 	{
262 		auto end = attributes.retro.countUntil!(a => !!a.peek!ContainerAttribute);
263 		if (end == -1)
264 			return attributes;
265 		else
266 			return attributes[$ - end .. $];
267 	}
268 
269 	auto attributeDescriptions() @property
270 	{
271 		return attributes.map!((a) {
272 			if (auto prag = a.peek!PragmaExpression)
273 				return "pragma(" ~ prag.identifier.text ~ ", ...["
274 					~ prag.argumentList.items.length.to!string ~ "])";
275 			else if (auto depr = a.peek!Deprecated) return "deprecated";
276 			else if (auto at = a.peek!AtAttribute) return "@" ~ at.identifier.text;
277 			else if (auto align_ = a.peek!AlignAttribute) return "align";
278 			else if (auto linkage = a.peek!LinkageAttribute) return "extern (" ~ linkage.identifier.text ~ (
279 						linkage.hasPlusPlus ? "++" : "") ~ ")";
280 			else if (auto simple = a.peek!SimpleAttribute) return simple.attributes.map!(a => a.text.length
281 						? a.text : str(a.type)).join(".");
282 			else if (auto mfunAttr = a.peek!MemberFunctionAttribute) return "mfun<" ~ (mfunAttr.atAttribute
283 						? "@" ~ mfunAttr.atAttribute.identifier.text : "???") ~ ">";
284 			else if (auto container = a.peek!ContainerAttribute) return container.type.to!string[0 .. $ - 1]
285 					~ " " ~ container.name;
286 			else if (auto user = a.peek!UserdataAttribute) return "user " ~ user.name;
287 			else
288 						return "Unknown type?!";
289 		});
290 	}
291 
292 	private static auto formatAttributes(T)(T attributes)
293 	{
294 		return attributes.map!((a) {
295 			auto t = appender!string;
296 			if (auto prag = a.peek!PragmaExpression)
297 				format(t, *prag);
298 			else if (auto depr = a.peek!Deprecated)
299 				format(t, *depr);
300 			else if (auto at = a.peek!AtAttribute)
301 				format(t, *at);
302 			else if (auto align_ = a.peek!AlignAttribute)
303 				format(t, *align_);
304 			else if (auto linkage = a.peek!LinkageAttribute)
305 				format(t, *linkage);
306 			else if (auto simple = a.peek!SimpleAttribute)
307 				return simple.attributes.map!(a => a.text.length ? a.text : str(a.type)).join(".");
308 			else if (auto mfunAttr = a.peek!MemberFunctionAttribute)
309 				return str(mfunAttr.tokenType);
310 			else if (auto container = a.peek!ContainerAttribute)
311 				return null;
312 			else if (auto user = a.peek!UserdataAttribute)
313 				return null;
314 			else
315 				return "/* <<ERROR>> */";
316 			return t.data.strip;
317 		});
318 	}
319 
320 	auto formattedAttributes() @property
321 	{
322 		return formatAttributes(attributes);
323 	}
324 
325 	auto localFormattedAttributes() @property
326 	{
327 		return formatAttributes(localAttributes);
328 	}
329 
330 	auto simpleAttributes() @property
331 	{
332 		return attributes.filter!(a => !!a.peek!SimpleAttribute)
333 			.map!(a => *a.peek!SimpleAttribute);
334 	}
335 
336 	auto simpleAttributesInContainer() @property
337 	{
338 		return localAttributes.filter!(a => !!a.peek!SimpleAttribute)
339 			.map!(a => *a.peek!SimpleAttribute);
340 	}
341 
342 	auto atAttributes() @property
343 	{
344 		return attributes.filter!(a => !!a.peek!AtAttribute)
345 			.map!(a => *a.peek!AtAttribute);
346 	}
347 
348 	auto memberFunctionAttributes() @property
349 	{
350 		return attributes.filter!(a => !!a.peek!MemberFunctionAttribute)
351 			.map!(a => *a.peek!MemberFunctionAttribute);
352 	}
353 
354 	auto memberFunctionAttributesInContainer() @property
355 	{
356 		return localAttributes.filter!(a => !!a.peek!MemberFunctionAttribute)
357 			.map!(a => *a.peek!MemberFunctionAttribute);
358 	}
359 
360 	auto userdataAttributes() @property
361 	{
362 		return attributes.filter!(a => !!a.peek!UserdataAttribute)
363 			.map!(a => *a.peek!UserdataAttribute);
364 	}
365 
366 	auto containerAttributes() @property
367 	{
368 		return attributes.filter!(a => !!a.peek!ContainerAttribute)
369 			.map!(a => *a.peek!ContainerAttribute);
370 	}
371 
372 	bool isToken(IdType t) @property
373 	{
374 		return memberFunctionAttributes.any!(a => a.tokenType == t)
375 			|| simpleAttributes.any!(a => a.attribute.type == t);
376 	}
377 
378 	bool isTokenInContainer(IdType t) @property
379 	{
380 		return memberFunctionAttributesInContainer.any!(a => a.tokenType == t)
381 			|| simpleAttributesInContainer.any!(a => a.attribute.type == t);
382 	}
383 
384 	bool isNothrow() @property
385 	{
386 		return isToken(tok!"nothrow");
387 	}
388 
389 	bool isNogc() @property
390 	{
391 		return atAttributes.any!(a => a.identifier.text == "nogc");
392 	}
393 
394 	bool isStatic() @property
395 	{
396 		return isToken(tok!"static");
397 	}
398 
399 	bool isFinal() @property
400 	{
401 		return isToken(tok!"final");
402 	}
403 
404 	bool isAbstract() @property
405 	{
406 		return isToken(tok!"abstract");
407 	}
408 
409 	bool isAbstractInContainer() @property
410 	{
411 		return isTokenInContainer(tok!"abstract");
412 	}
413 
414 	/// Returns: if a block needs implementations (virtual/abstract or interface methods)
415 	/// 0 = must not be implemented (not in a container, private, static or final method)
416 	/// 1 = optionally implementable, must be implemented if there is no function body
417 	/// 9 = must be implemented
418 	int requiredImplementationLevel() @property
419 	{
420 		auto container = containerAttributes;
421 		if (container.empty || protectionType == tok!"private" || isStatic || isFinal)
422 			return 0;
423 		ContainerAttribute innerContainer = container.tail(1).front;
424 		if (innerContainer.type == ContainerAttribute.Type.class_)
425 			return isAbstractInContainer ? 9 : 1;
426 		else // interface (or others)
427 			return 9;
428 	}
429 
430 	AttributeWithInfo!SimpleAttribute protectionAttribute() @property
431 	{
432 		auto prot = simpleAttributes.filter!(a => a.firstAttribute.type.among!(tok!"public",
433 				tok!"private", tok!"protected", tok!"package")).tail(1);
434 		if (prot.empty)
435 			return null;
436 		else
437 			return prot.front;
438 	}
439 
440 	IdType protectionType() @property
441 	{
442 		auto attr = protectionAttribute;
443 		if (attr is null)
444 			return IdType.init;
445 		else
446 			return attr.attributes[0].type;
447 	}
448 
449 	void pushData(string name, Variant value, bool sticky = false)
450 	{
451 		attributes ~= AnyAttributeInfo(AnyAttribute(UserdataAttribute(name, value)), sticky);
452 	}
453 
454 	void pushData(T)(string name, T value, bool sticky = false)
455 	{
456 		pushData(name, Variant(value), sticky);
457 	}
458 
459 	void pushContainer(ContainerAttribute.Type type, string name)
460 	{
461 		attributes ~= AnyAttributeInfo(AnyAttribute(ContainerAttribute(type, name)), false);
462 	}
463 
464 	ASTContext save()
465 	{
466 		return ASTContext(attributes[]);
467 	}
468 
469 	void restore(ASTContext c)
470 	{
471 		attributes = c.attributes;
472 	}
473 }