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 }