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 }`;
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 
249 class SnippetInfoGenerator : ASTVisitor
250 {
251 	alias visit = ASTVisitor.visit;
252 
253 	this(size_t position)
254 	{
255 		this.position = position;
256 	}
257 
258 	static foreach (T; AliasSeq!(Declaration, ImportBindings, ImportBind, ModuleDeclaration))
259 		override void visit(const T dec)
260 		{
261 			mixin(FullSnippetLevelWrapper!(SnippetLevel.other));
262 		}
263 
264 	override void visit(const MixinTemplateDeclaration dec)
265 	{
266 		mixin(SnippetLevelWrapper!(SnippetLevel.mixinTemplate));
267 		// avoid TemplateDeclaration overriding scope, immediately iterate over children
268 		if (dec.templateDeclaration)
269 			dec.templateDeclaration.accept(this);
270 	}
271 
272 	override void visit(const StructBody dec)
273 	{
274 		mixin(FullSnippetLevelWrapper!(SnippetLevel.type));
275 	}
276 
277 	static foreach (T; AliasSeq!(SpecifiedFunctionBody, Unittest))
278 		override void visit(const T dec)
279 		{
280 			mixin(StackStorageScope!"variableStack");
281 			mixin(FullSnippetLevelWrapper!(SnippetLevel.method));
282 		}
283 
284 	override void visit(const StatementNoCaseNoDefault dec)
285 	{
286 		mixin(StackStorageScope!"variableStack");
287 		super.visit(dec);
288 	}
289 
290 	override void visit(const DeclarationOrStatement dec)
291 	{
292 		super.visit(dec);
293 		if (!dec.tokens.length || dec.tokens[0].index <= position)
294 			lastStatement = cast()dec;
295 	}
296 
297 	static foreach (T; AliasSeq!(Arguments, ExpressionNode))
298 		override void visit(const T dec)
299 		{
300 			mixin(FullSnippetLevelWrapper!(SnippetLevel.value));
301 		}
302 
303 	override void visit(const VariableDeclaration dec)
304 	{
305 		// not quite accurate for VariableDeclaration, should only be value after = sign
306 		mixin(SnippetLevelWrapper!(SnippetLevel.value));
307 		super.visit(dec);
308 
309 		foreach (t; dec.declarators)
310 		{
311 			debug(TraceGenerator) trace("push variable ", variableStack.length, " ", t.name.text, " of type ",
312 					astToString(dec.type), " and value ", astToString(t.initializer));
313 			variableStack.assumeSafeAppend ~= VariableUsage(dec.type, t.name,
314 					t.initializer ? t.initializer.nonVoidInitializer : null);
315 		}
316 
317 		if (dec.autoDeclaration)
318 			foreach (t; dec.autoDeclaration.parts)
319 			{
320 				debug(TraceGenerator) trace("push variable ", variableStack.length, " ", t.identifier.text,
321 						" of type auto and value ", astToString(t.initializer));
322 				variableStack.assumeSafeAppend ~= VariableUsage(dec.type, t.identifier,
323 						t.initializer ? t.initializer.nonVoidInitializer : null);
324 			}
325 	}
326 
327 	ref inout(SnippetInfo) value() inout
328 	{
329 		return ret;
330 	}
331 
332 	void pushLevel(SnippetLevel level, const BaseNode node)
333 	{
334 		if (done)
335 			return;
336 		debug(TraceGenerator) trace("push ", level, " on ", typeid(node).name, " ", current, " -> ", node.tokens[0].index);
337 
338 		if (node.tokens.length)
339 		{
340 			current = node.tokens[0].index;
341 			if (current >= position)
342 			{
343 				done = true;
344 				debug(TraceGenerator) trace("done");
345 				return;
346 			}
347 		}
348 		ret.stack.assumeSafeAppend ~= level;
349 	}
350 
351 	void popLevel(const BaseNode node)
352 	{
353 		if (done)
354 			return;
355 		debug(TraceGenerator) trace("pop from ", typeid(node).name, " ", current, " -> ",
356 				node.tokens[$ - 1].index + node.tokens[$ - 1].tokenText.length);
357 
358 		if (node.tokens.length)
359 		{
360 			current = node.tokens[$ - 1].index + node.tokens[$ - 1].tokenText.length;
361 			if (current > position)
362 			{
363 				done = true;
364 				debug(TraceGenerator) trace("done");
365 				return;
366 			}
367 		}
368 
369 		ret.stack.length--;
370 	}
371 
372 	bool done;
373 	VariableUsage[] variableStack;
374 	DeclarationOrStatement lastStatement;
375 	size_t position, current;
376 	SnippetInfo ret;
377 }