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