1 module workspaced.com.dcdext;
2 
3 import dparse.ast;
4 import dparse.lexer;
5 import dparse.parser;
6 import dparse.rollback_allocator;
7 
8 import core.thread;
9 
10 import std.ascii;
11 import std.file;
12 import std.functional;
13 import std.json;
14 import std.string;
15 
16 import workspaced.api;
17 import workspaced.dparseext;
18 import workspaced.com.dcd;
19 
20 @component("dcdext")
21 class DCDExtComponent : ComponentWrapper
22 {
23 	mixin DefaultComponentWrapper;
24 
25 	/// Loads dcd extension methods. Call with `{"cmd": "load", "components": ["dcdext"]}`
26 	void load()
27 	{
28 		if (!refInstance)
29 			return;
30 
31 		config.stringBehavior = StringBehavior.source;
32 	}
33 
34 	/// Implements an interface or abstract class
35 	Future!string implement(string code, int position)
36 	{
37 		auto ret = new Future!string;
38 		new Thread({
39 			try
40 			{
41 				string changes;
42 				void prependInterface(InterfaceDetails details, int maxDepth = 50)
43 				{
44 					if (maxDepth <= 0)
45 						return;
46 					if (details.methods.length)
47 					{
48 						changes ~= "// implement " ~ details.name ~ "\n\n";
49 						foreach (fn; details.methods)
50 						{
51 							if (details.needsOverride)
52 								changes ~= "override ";
53 							changes ~= fn.signature[0 .. $ - 1];
54 							changes ~= " {";
55 							if (fn.signature[$ - 1] == '{') // has body
56 							{
57 								changes ~= "\n\t";
58 								if (fn.returnType != "void")
59 									changes ~= "return ";
60 								changes ~= "super." ~ fn.name;
61 								if (fn.arguments.length)
62 									changes ~= "(" ~ fn.arguments ~ ")";
63 								else if (fn.returnType == "void")
64 									changes ~= "()"; // make functions that don't return add (), otherwise they might be attributes and don't need that
65 								changes ~= ";\n";
66 							}
67 							else if (fn.returnType != "void")
68 							{
69 								changes ~= "\n\t";
70 								if (fn.isNothrowOrNogc)
71 								{
72 									if (fn.returnType.endsWith("[]"))
73 										changes ~= "return null; // TODO: implement";
74 									else
75 										changes ~= "return " ~ fn.returnType ~ ".init; // TODO: implement";
76 								}
77 								else
78 									changes ~= `assert(false, "Method ` ~ fn.name ~ ` not implemented");`;
79 								changes ~= "\n";
80 							}
81 							changes ~= "}\n\n";
82 						}
83 					}
84 					if (!details.needsOverride || details.methods.length)
85 						foreach (parent; details.parentPositions)
86 							prependInterface(lookupInterface(details.code, parent), maxDepth - 1);
87 				}
88 
89 				prependInterface(lookupInterface(code, position));
90 				ret.finish(changes);
91 			}
92 			catch (Throwable t)
93 			{
94 				ret.error(t);
95 			}
96 		}).start();
97 		return ret;
98 	}
99 
100 private:
101 	RollbackAllocator rba;
102 	LexerConfig config;
103 
104 	InterfaceDetails lookupInterface(string code, int position)
105 	{
106 		auto data = get!DCDComponent.findDeclaration(code, position).getBlocking;
107 		string file = data.file;
108 		int newPosition = data.position;
109 
110 		if (!file.length)
111 			return InterfaceDetails.init;
112 
113 		string newCode = code;
114 		if (file != "stdin")
115 			newCode = readText(file);
116 
117 		return getInterfaceDetails(file, newCode, newPosition);
118 	}
119 
120 	InterfaceDetails getInterfaceDetails(string file, string code, int position)
121 	{
122 		auto tokens = getTokensForParser(cast(ubyte[]) code, config, &workspaced.stringCache);
123 		auto parsed = parseModule(tokens, file, &rba, (&doNothing).toDelegate);
124 		auto reader = new InterfaceMethodFinder(code, position);
125 		reader.visit(parsed);
126 		return reader.details;
127 	}
128 }
129 
130 private:
131 
132 struct MethodDetails
133 {
134 	string name, signature, arguments, returnType;
135 	bool isNothrowOrNogc;
136 }
137 
138 struct InterfaceDetails
139 {
140 	/// Entire code of the file
141 	string code;
142 	bool needsOverride;
143 	string name;
144 	MethodDetails[] methods;
145 	string[] parents;
146 	int[] parentPositions;
147 }
148 
149 final class InterfaceMethodFinder : ASTVisitor
150 {
151 	this(string code, int targetPosition)
152 	{
153 		this.code = code;
154 		details.code = code;
155 		this.targetPosition = targetPosition;
156 	}
157 
158 	override void visit(const ClassDeclaration dec)
159 	{
160 		visitInterface(dec.name, dec.baseClassList, dec.structBody, true);
161 	}
162 
163 	override void visit(const InterfaceDeclaration dec)
164 	{
165 		visitInterface(dec.name, dec.baseClassList, dec.structBody, false);
166 	}
167 
168 	private void visitInterface(const Token name, const BaseClassList baseClassList,
169 			const StructBody structBody, bool needsOverride)
170 	{
171 		if (!structBody)
172 			return;
173 		if (targetPosition >= name.index && targetPosition < structBody.endLocation)
174 		{
175 			details.name = name.text;
176 			if (baseClassList)
177 				foreach (base; baseClassList.items)
178 				{
179 					if (!base.type2 || !base.type2.typeIdentifierPart
180 							|| !base.type2.typeIdentifierPart.identifierOrTemplateInstance)
181 						continue;
182 					details.parents ~= astToString(base.type2);
183 					details.parentPositions ~= cast(
184 							int) base.type2.typeIdentifierPart.identifierOrTemplateInstance.identifier.index + 1;
185 				}
186 			inTarget = true;
187 			details.needsOverride = needsOverride;
188 			context.notInheritable = needsOverride;
189 			structBody.accept(this);
190 			inTarget = false;
191 		}
192 	}
193 
194 	override void visit(const FunctionDeclaration dec)
195 	{
196 		if (!inTarget || context.notInheritable)
197 			return;
198 		dec.accept(this);
199 		auto origBody = (cast() dec).functionBody;
200 		auto origComment = (cast() dec).comment;
201 		(cast() dec).functionBody = null;
202 		(cast() dec).comment = null;
203 		scope (exit)
204 		{
205 			(cast() dec).functionBody = origBody;
206 			(cast() dec).comment = origComment;
207 		}
208 		string method = astToString(dec, context.attributes).strip;
209 		if (origBody)
210 			method = method[0 .. $ - 1] ~ '{'; // replace ; with { to indicate that there is a body
211 		string arguments;
212 		if (dec.parameters)
213 			foreach (arg; dec.parameters.parameters)
214 			{
215 				if (arguments.length)
216 					arguments ~= ", ";
217 				arguments ~= arg.name.text;
218 			}
219 		details.methods ~= MethodDetails(dec.name.text, method, arguments,
220 				dec.returnType ? astToString(dec.returnType) : "void", context.isNothrowOrNogc);
221 	}
222 
223 	override void visit(const FunctionBody)
224 	{
225 	}
226 
227 	override void visit(const MemberFunctionAttribute attribute)
228 	{
229 		if (attribute.tokenType == tok!"nothrow")
230 			context.isNothrowOrNogc = true;
231 		attribute.accept(this);
232 	}
233 
234 	override void visit(const FunctionAttribute attribute)
235 	{
236 		if (attribute.token.text == "nothrow")
237 			context.isNothrowOrNogc = true;
238 		attribute.accept(this);
239 	}
240 
241 	override void visit(const AtAttribute attribute)
242 	{
243 		if (attribute.identifier.text == "nogc")
244 			context.isNothrowOrNogc = true;
245 		attribute.accept(this);
246 	}
247 
248 	override void visit(const Attribute attribute)
249 	{
250 		attribute.accept(this);
251 		if (attribute.attribute != tok!"")
252 		{
253 			switch (attribute.attribute.type)
254 			{
255 			case tok!"private":
256 			case tok!"final":
257 			case tok!"static":
258 				context.notInheritable = true;
259 				break;
260 			case tok!"abstract":
261 				context.notInheritable = false;
262 				return;
263 			case tok!"nothrow":
264 				context.isNothrowOrNogc = true;
265 				break;
266 			default:
267 			}
268 		}
269 		context.attributes ~= attribute;
270 	}
271 
272 	override void visit(const AttributeDeclaration dec)
273 	{
274 		resetAst = false;
275 		dec.accept(this);
276 	}
277 
278 	override void visit(const Declaration dec)
279 	{
280 		auto c = context.save;
281 		dec.accept(this);
282 
283 		if (resetAst)
284 		{
285 			context.restore(c);
286 			if (details.needsOverride)
287 				context.notInheritable = true;
288 		}
289 		resetAst = true;
290 	}
291 
292 	alias visit = ASTVisitor.visit;
293 
294 	string code;
295 	bool inTarget;
296 	int targetPosition;
297 	bool resetAst;
298 	ASTContext context;
299 	InterfaceDetails details;
300 }
301 
302 struct ASTContext
303 {
304 	const(Attribute)[] attributes;
305 	bool notInheritable;
306 	bool isNothrowOrNogc;
307 
308 	ASTContext save() const
309 	{
310 		return ASTContext(attributes[], notInheritable, isNothrowOrNogc);
311 	}
312 
313 	void restore(in ASTContext c)
314 	{
315 		attributes = c.attributes;
316 		notInheritable = c.notInheritable;
317 		isNothrowOrNogc = c.isNothrowOrNogc;
318 	}
319 }
320 
321 void doNothing(string, size_t, size_t, string, bool)
322 {
323 }