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 }