1 module workspaced.com.snippets.generator;
2 
3 // debug = TraceGenerator;
4 
5 import dparse.ast;
6 import dparse.lexer;
7 
8 import workspaced.api;
9 import workspaced.com.snippets;
10 import workspaced.dparseext;
11 
12 import std.algorithm;
13 import std.array;
14 import std.conv;
15 import std.meta : AliasSeq;
16 
17 /// Checks if a variable is suitable to be iterated over and finds out information about it
18 /// Params:
19 ///   ret = loop scope to manipulate
20 ///   variable = variable to check
21 /// Returns: true if this variable is suitable for iteration, false if not
22 bool fillLoopScopeInfo(ref SnippetLoopScope ret, const scope VariableUsage variable)
23 {
24 	// try to minimize false positives as much as possible here!
25 	// the first true result will stop the whole loop variable finding process
26 
27 	if (!variable.name.text.length)
28 		return false;
29 
30 	if (variable.type)
31 	{
32 		if (variable.type.typeSuffixes.length)
33 		{
34 			if (variable.type.typeSuffixes[$ - 1].array)
35 			{
36 				const isMap = !!variable.type.typeSuffixes[$ - 1].type;
37 
38 				ret.stringIterator = variable.type.type2
39 					&& variable.type.type2.builtinType.among!(tok!"char", tok!"wchar", tok!"dchar");
40 				ret.type = formatType(variable.type.typeConstructors,
41 						variable.type.typeSuffixes[0 .. $ - 1], variable.type.type2);
42 				ret.iterator = variable.name.text;
43 				ret.numItems = isMap ? 2 : 1;
44 				return true;
45 			}
46 		}
47 		else if (variable.type.type2 && variable.type.type2.typeIdentifierPart)
48 		{
49 			// hardcode string, wstring and dstring
50 			const t = variable.type.type2.typeIdentifierPart;
51 			if (!t.dot && !t.indexer && !t.typeIdentifierPart && t.identifierOrTemplateInstance)
52 			{
53 				const simpleTypeName = t.identifierOrTemplateInstance.identifier.tokenText;
54 				switch (simpleTypeName)
55 				{
56 				case "string":
57 				case "wstring":
58 				case "dstring":
59 					ret.stringIterator = true;
60 					ret.iterator = variable.name.text;
61 					return true;
62 				default:
63 					break;
64 				}
65 			}
66 		}
67 	}
68 
69 	if (variable.value)
70 	{
71 		if (variable.value.arrayInitializer)
72 		{
73 			bool isMap;
74 			auto items = variable.value.arrayInitializer.arrayMemberInitializations;
75 			if (items.length)
76 				isMap = !!items[0].assignExpression; // this is the value before the ':' or null for no key
77 
78 			ret.stringIterator = false;
79 			ret.iterator = variable.name.text;
80 			ret.numItems = isMap ? 2 : 1;
81 			return true;
82 		}
83 		else if (variable.value.assignExpression)
84 		{
85 			// TODO: determine if this is a loop variable based on value
86 		}
87 	}
88 
89 	return false;
90 }
91 
92 string formatType(const IdType[] typeConstructors, const TypeSuffix[] typeSuffixes, const Type2 type2) @trusted
93 {
94 	Type t = new Type();
95 	t.typeConstructors = cast(IdType[]) typeConstructors;
96 	t.typeSuffixes = cast(TypeSuffix[]) typeSuffixes;
97 	t.type2 = cast(Type2) type2;
98 	return astToString(t);
99 }
100 
101 /// Helper struct containing variable definitions and notable assignments which 
102 struct VariableUsage
103 {
104 	const Type type;
105 	const Token name;
106 	const NonVoidInitializer value;
107 }
108 
109 unittest
110 {
111 	import std.experimental.logger : globalLogLevel, LogLevel;
112 
113 	globalLogLevel = LogLevel.trace;
114 
115 	scope backend = new WorkspaceD();
116 	auto workspace = makeTemporaryTestingWorkspace;
117 	auto instance = backend.addInstance(workspace.directory);
118 	backend.register!SnippetsComponent;
119 	SnippetsComponent snippets = backend.get!SnippetsComponent(workspace.directory);
120 
121 	static immutable loopCode = `module something;
122 
123 void foo()
124 {
125 	int[] x = [1, 2, 3];
126 	// trivial loop
127 	
128 }
129 
130 void bar()
131 {
132 	auto houseNumbers = [1, 2, 3];
133 	// non-trivial (auto) loop
134 	
135 }
136 
137 void existing()
138 {
139 	int[3] items = [1, 2, 3];
140 	int item;
141 	// clashing name
142 	
143 }
144 
145 void strings()
146 {
147 	string x;
148 	int y;
149 	// characters of x
150 	
151 }
152 
153 void noValue()
154 {
155 	int hello;
156 	// no lists
157 	
158 }
159 
160 void map()
161 {
162 	auto map = ["hello": "world", "key": "value"];
163 	// key, value
164 
165 }`.normLF;
166 
167 	SnippetInfo i;
168 	SnippetLoopScope s;
169 
170 	i = snippets.determineSnippetInfo(null, loopCode, 18);
171 	assert(i.level == SnippetLevel.global);
172 	s = i.loopScope; // empty
173 	assert(s == SnippetLoopScope.init);
174 
175 	i = snippets.determineSnippetInfo(null, loopCode, 30);
176 	assert(i.level == SnippetLevel.other, i.stack.to!string);
177 	s = i.loopScope; // empty
178 	assert(s == SnippetLoopScope.init);
179 
180 	i = snippets.determineSnippetInfo(null, loopCode, 31);
181 	assert(i.level == SnippetLevel.method, i.stack.to!string);
182 	i = snippets.determineSnippetInfo(null, loopCode, 73);
183 	assert(i.level == SnippetLevel.method, i.stack.to!string);
184 	i = snippets.determineSnippetInfo(null, loopCode, 74);
185 	assert(i.level == SnippetLevel.global, i.stack.to!string);
186 
187 	i = snippets.determineSnippetInfo(null, loopCode, 43);
188 	assert(i.level == SnippetLevel.value);
189 	s = i.loopScope; // in value
190 	assert(s == SnippetLoopScope.init);
191 
192 	i = snippets.determineSnippetInfo(null, loopCode, 72);
193 	assert(i.level == SnippetLevel.method);
194 	s = i.loopScope; // trivial of x
195 	assert(s.supported);
196 	assert(!s.stringIterator);
197 	assert(s.type == "int");
198 	assert(s.iterator == "x");
199 
200 	i = snippets.determineSnippetInfo(null, loopCode, 150);
201 	assert(i.level == SnippetLevel.method);
202 	s = i.loopScope; // non-trivial of houseNumbers (should be named houseNumber)
203 	assert(s.supported);
204 	assert(!s.stringIterator);
205 	assert(null == s.type);
206 	assert(s.iterator == "houseNumbers");
207 
208 	i = snippets.determineSnippetInfo(null, loopCode, 229);
209 	assert(i.level == SnippetLevel.method);
210 	s = i.loopScope; // non-trivial of items with existing variable name
211 	assert(s.supported);
212 	assert(!s.stringIterator);
213 	assert(s.type == "int");
214 	assert(s.iterator == "items");
215 
216 	i = snippets.determineSnippetInfo(null, loopCode, 290);
217 	assert(i.level == SnippetLevel.method);
218 	s = i.loopScope; // string iteration 
219 	assert(s.supported);
220 	assert(s.stringIterator);
221 	assert(null == s.type);
222 	assert(s.iterator == "x");
223 
224 	i = snippets.determineSnippetInfo(null, loopCode, 337);
225 	assert(i.level == SnippetLevel.method);
226 	s = i.loopScope; // no predefined variable 
227 	assert(s.supported);
228 	assert(!s.stringIterator);
229 	assert(null == s.type);
230 	assert(null == s.iterator);
231 
232 	i = snippets.determineSnippetInfo(null, loopCode, 418);
233 	assert(i.level == SnippetLevel.method);
234 	s = i.loopScope; // hash map
235 	assert(s.supported);
236 	assert(!s.stringIterator);
237 	assert(null == s.type);
238 	assert(s.iterator == "map");
239 	assert(s.numItems == 2);
240 }
241 
242 enum StackStorageScope(string val) = "if (done) return; auto __" ~ val
243 	~ "_scope = " ~ val ~ "; scope (exit) if (!done) " ~ val ~ " = __" ~ val ~ "_scope;";
244 enum SnippetLevelWrapper(SnippetLevel level) = "if (done) return; pushLevel("
245 	~ level.stringof ~ ", dec); scope (exit) popLevel(dec); "
246 	~ "if (!dec.tokens.length || dec.tokens[0].index <= position) lastStatement = null;";
247 enum FullSnippetLevelWrapper(SnippetLevel level) = SnippetLevelWrapper!level ~ " super.visit(dec);";
248 enum MethodSnippetLevelWrapper(SnippetLevel level) = "repeatLastOnDeclStmt = true; scope(exit) repeatLastOnDeclStmt = false; " ~ FullSnippetLevelWrapper!level;
249 
250 class SnippetInfoGenerator : ASTVisitor
251 {
252 	alias visit = ASTVisitor.visit;
253 
254 	this(size_t position)
255 	{
256 		this.position = position;
257 	}
258 
259 	static foreach (T; AliasSeq!(Declaration, ImportBindings, ImportBind, ModuleDeclaration))
260 		override void visit(const T dec)
261 		{
262 			mixin(FullSnippetLevelWrapper!(SnippetLevel.other));
263 		}
264 
265 	override void visit(const MixinTemplateDeclaration dec)
266 	{
267 		mixin(SnippetLevelWrapper!(SnippetLevel.mixinTemplate));
268 		// avoid TemplateDeclaration overriding scope, immediately iterate over children
269 		if (dec.templateDeclaration)
270 			dec.templateDeclaration.accept(this);
271 	}
272 
273 	override void visit(const StructBody dec)
274 	{
275 		mixin(FullSnippetLevelWrapper!(SnippetLevel.type));
276 	}
277 
278 	static foreach (T; AliasSeq!(SpecifiedFunctionBody, Unittest))
279 		override void visit(const T dec)
280 		{
281 			mixin(StackStorageScope!"variableStack");
282 			mixin(SnippetLevelWrapper!(SnippetLevel.newMethod));
283 			mixin(FullSnippetLevelWrapper!(SnippetLevel.method));
284 		}
285 
286 	override void visit(const StatementNoCaseNoDefault dec)
287 	{
288 		mixin(StackStorageScope!"variableStack");
289 		super.visit(dec);
290 	}
291 
292 	override void visit(const DeclarationOrStatement dec)
293 	{
294 		if (repeatLastOnDeclStmt)
295 		{
296 			if (done) return;
297 			pushLevel(ret.stack[$ - 2], dec);
298 			scope (exit) popLevel(dec);
299 			super.visit(dec);
300 			if (!dec.tokens.length || dec.tokens[0].index <= position)
301 				lastStatement = cast()dec;
302 		}
303 		else
304 		{
305 			super.visit(dec);
306 			if (!dec.tokens.length || dec.tokens[0].index <= position)
307 				lastStatement = cast()dec;
308 		}
309 	}
310 
311 	static foreach (T; AliasSeq!(ForeachStatement, ForStatement, WhileStatement, DoStatement))
312 		override void visit(const T dec)
313 		{
314 			mixin(MethodSnippetLevelWrapper!(SnippetLevel.loop));
315 		}
316 
317 	override void visit(const SwitchStatement dec)
318 	{
319 		mixin(MethodSnippetLevelWrapper!(SnippetLevel.switch_));
320 	}
321 
322 	static foreach (T; AliasSeq!(Arguments, ExpressionNode))
323 		override void visit(const T dec)
324 		{
325 			mixin(FullSnippetLevelWrapper!(SnippetLevel.value));
326 		}
327 
328 	override void visit(const VariableDeclaration dec)
329 	{
330 		// not quite accurate for VariableDeclaration, should only be value after = sign
331 		mixin(SnippetLevelWrapper!(SnippetLevel.value));
332 		super.visit(dec);
333 
334 		foreach (t; dec.declarators)
335 		{
336 			debug(TraceGenerator) trace("push variable ", variableStack.length, " ", t.name.text, " of type ",
337 					astToString(dec.type), " and value ", astToString(t.initializer));
338 			variableStack.assumeSafeAppend ~= VariableUsage(dec.type, t.name,
339 					t.initializer ? t.initializer.nonVoidInitializer : null);
340 		}
341 
342 		if (dec.autoDeclaration)
343 			foreach (t; dec.autoDeclaration.parts)
344 			{
345 				debug(TraceGenerator) trace("push variable ", variableStack.length, " ", t.identifier.text,
346 						" of type auto and value ", astToString(t.initializer));
347 				variableStack.assumeSafeAppend ~= VariableUsage(dec.type, t.identifier,
348 						t.initializer ? t.initializer.nonVoidInitializer : null);
349 			}
350 	}
351 
352 	ref inout(SnippetInfo) value() inout
353 	{
354 		return ret;
355 	}
356 
357 	void pushLevel(SnippetLevel level, const BaseNode node)
358 	{
359 		if (done)
360 			return;
361 		debug(TraceGenerator) trace("push ", level, " on ", typeid(node).name, " ", current, " -> ", node.tokens[0].index);
362 
363 		if (node.tokens.length)
364 		{
365 			current = node.tokens[0].index;
366 			if (current >= position)
367 			{
368 				done = true;
369 				debug(TraceGenerator) trace("done");
370 				return;
371 			}
372 		}
373 		ret.stack.assumeSafeAppend ~= level;
374 	}
375 
376 	void popLevel(const BaseNode node)
377 	{
378 		if (done)
379 			return;
380 		debug(TraceGenerator) trace("pop from ", typeid(node).name, " ", current, " -> ",
381 				node.tokens[$ - 1].index + node.tokens[$ - 1].tokenText.length);
382 
383 		if (node.tokens.length)
384 		{
385 			current = node.tokens[$ - 1].index + node.tokens[$ - 1].tokenText.length;
386 			if (current > position)
387 			{
388 				done = true;
389 				debug(TraceGenerator) trace("done");
390 				return;
391 			}
392 		}
393 
394 		ret.stack.length--;
395 	}
396 
397 	bool done;
398 	VariableUsage[] variableStack;
399 	DeclarationOrStatement lastStatement;
400 	bool repeatLastOnDeclStmt;
401 	size_t position, current;
402 	SnippetInfo ret;
403 }