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