1 module workspaced.com.dcdext; 2 3 import dparse.ast; 4 import dparse.lexer; 5 import dparse.parser; 6 import dparse.rollback_allocator; 7 8 import core.thread; 9 10 import std.ascii; 11 import std.file; 12 import std.functional; 13 import std.json; 14 import std.string; 15 16 import workspaced.api; 17 import workspaced.dparseext; 18 import workspaced.com.dcd; 19 20 @component("dcdext") 21 class DCDExtComponent : ComponentWrapper 22 { 23 mixin DefaultComponentWrapper; 24 25 /// Loads dcd extension methods. Call with `{"cmd": "load", "components": ["dcdext"]}` 26 void load() 27 { 28 if (!refInstance) 29 return; 30 31 config.stringBehavior = StringBehavior.source; 32 } 33 34 /// Implements an interface or abstract class 35 Future!string implement(string code, int position) 36 { 37 auto ret = new Future!string; 38 new Thread({ 39 try 40 { 41 string changes; 42 void prependInterface(InterfaceDetails details, int maxDepth = 50) 43 { 44 if (maxDepth <= 0) 45 return; 46 if (details.methods.length) 47 { 48 changes ~= "// implement " ~ details.name ~ "\n\n"; 49 foreach (fn; details.methods) 50 { 51 if (details.needsOverride) 52 changes ~= "override "; 53 changes ~= fn.signature[0 .. $ - 1]; 54 changes ~= " {"; 55 if (fn.signature[$ - 1] == '{') // has body 56 { 57 changes ~= "\n\t"; 58 if (fn.returnType != "void") 59 changes ~= "return "; 60 changes ~= "super." ~ fn.name; 61 if (fn.arguments.length) 62 changes ~= "(" ~ fn.arguments ~ ")"; 63 else if (fn.returnType == "void") 64 changes ~= "()"; // make functions that don't return add (), otherwise they might be attributes and don't need that 65 changes ~= ";\n"; 66 } 67 else if (fn.returnType != "void") 68 { 69 changes ~= "\n\t"; 70 if (fn.isNothrowOrNogc) 71 { 72 if (fn.returnType.endsWith("[]")) 73 changes ~= "return null; // TODO: implement"; 74 else 75 changes ~= "return " ~ fn.returnType ~ ".init; // TODO: implement"; 76 } 77 else 78 changes ~= `assert(false, "Method ` ~ fn.name ~ ` not implemented");`; 79 changes ~= "\n"; 80 } 81 changes ~= "}\n\n"; 82 } 83 } 84 if (!details.needsOverride || details.methods.length) 85 foreach (parent; details.parentPositions) 86 prependInterface(lookupInterface(details.code, parent), maxDepth - 1); 87 } 88 89 prependInterface(lookupInterface(code, position)); 90 ret.finish(changes); 91 } 92 catch (Throwable t) 93 { 94 ret.error(t); 95 } 96 }).start(); 97 return ret; 98 } 99 100 private: 101 RollbackAllocator rba; 102 LexerConfig config; 103 104 InterfaceDetails lookupInterface(string code, int position) 105 { 106 auto data = get!DCDComponent.findDeclaration(code, position).getBlocking; 107 string file = data.file; 108 int newPosition = data.position; 109 110 if (!file.length) 111 return InterfaceDetails.init; 112 113 string newCode = code; 114 if (file != "stdin") 115 newCode = readText(file); 116 117 return getInterfaceDetails(file, newCode, newPosition); 118 } 119 120 InterfaceDetails getInterfaceDetails(string file, string code, int position) 121 { 122 auto tokens = getTokensForParser(cast(ubyte[]) code, config, &workspaced.stringCache); 123 auto parsed = parseModule(tokens, file, &rba, (&doNothing).toDelegate); 124 auto reader = new InterfaceMethodFinder(code, position); 125 reader.visit(parsed); 126 return reader.details; 127 } 128 } 129 130 private: 131 132 struct MethodDetails 133 { 134 string name, signature, arguments, returnType; 135 bool isNothrowOrNogc; 136 } 137 138 struct InterfaceDetails 139 { 140 /// Entire code of the file 141 string code; 142 bool needsOverride; 143 string name; 144 MethodDetails[] methods; 145 string[] parents; 146 int[] parentPositions; 147 } 148 149 final class InterfaceMethodFinder : ASTVisitor 150 { 151 this(string code, int targetPosition) 152 { 153 this.code = code; 154 details.code = code; 155 this.targetPosition = targetPosition; 156 } 157 158 override void visit(const ClassDeclaration dec) 159 { 160 visitInterface(dec.name, dec.baseClassList, dec.structBody, true); 161 } 162 163 override void visit(const InterfaceDeclaration dec) 164 { 165 visitInterface(dec.name, dec.baseClassList, dec.structBody, false); 166 } 167 168 private void visitInterface(const Token name, const BaseClassList baseClassList, 169 const StructBody structBody, bool needsOverride) 170 { 171 if (!structBody) 172 return; 173 if (targetPosition >= name.index && targetPosition < structBody.endLocation) 174 { 175 details.name = name.text; 176 if (baseClassList) 177 foreach (base; baseClassList.items) 178 { 179 if (!base.type2 || !base.type2.typeIdentifierPart 180 || !base.type2.typeIdentifierPart.identifierOrTemplateInstance) 181 continue; 182 details.parents ~= astToString(base.type2); 183 details.parentPositions ~= cast( 184 int) base.type2.typeIdentifierPart.identifierOrTemplateInstance.identifier.index + 1; 185 } 186 inTarget = true; 187 details.needsOverride = needsOverride; 188 context.notInheritable = needsOverride; 189 structBody.accept(this); 190 inTarget = false; 191 } 192 } 193 194 override void visit(const FunctionDeclaration dec) 195 { 196 if (!inTarget || context.notInheritable) 197 return; 198 dec.accept(this); 199 auto origBody = (cast() dec).functionBody; 200 auto origComment = (cast() dec).comment; 201 (cast() dec).functionBody = null; 202 (cast() dec).comment = null; 203 scope (exit) 204 { 205 (cast() dec).functionBody = origBody; 206 (cast() dec).comment = origComment; 207 } 208 string method = astToString(dec, context.attributes).strip; 209 if (origBody) 210 method = method[0 .. $ - 1] ~ '{'; // replace ; with { to indicate that there is a body 211 string arguments; 212 if (dec.parameters) 213 foreach (arg; dec.parameters.parameters) 214 { 215 if (arguments.length) 216 arguments ~= ", "; 217 arguments ~= arg.name.text; 218 } 219 details.methods ~= MethodDetails(dec.name.text, method, arguments, 220 dec.returnType ? astToString(dec.returnType) : "void", context.isNothrowOrNogc); 221 } 222 223 override void visit(const FunctionBody) 224 { 225 } 226 227 override void visit(const MemberFunctionAttribute attribute) 228 { 229 if (attribute.tokenType == tok!"nothrow") 230 context.isNothrowOrNogc = true; 231 attribute.accept(this); 232 } 233 234 override void visit(const FunctionAttribute attribute) 235 { 236 if (attribute.token.text == "nothrow") 237 context.isNothrowOrNogc = true; 238 attribute.accept(this); 239 } 240 241 override void visit(const AtAttribute attribute) 242 { 243 if (attribute.identifier.text == "nogc") 244 context.isNothrowOrNogc = true; 245 attribute.accept(this); 246 } 247 248 override void visit(const Attribute attribute) 249 { 250 attribute.accept(this); 251 if (attribute.attribute != tok!"") 252 { 253 switch (attribute.attribute.type) 254 { 255 case tok!"private": 256 case tok!"final": 257 case tok!"static": 258 context.notInheritable = true; 259 break; 260 case tok!"abstract": 261 context.notInheritable = false; 262 return; 263 case tok!"nothrow": 264 context.isNothrowOrNogc = true; 265 break; 266 default: 267 } 268 } 269 context.attributes ~= attribute; 270 } 271 272 override void visit(const AttributeDeclaration dec) 273 { 274 resetAst = false; 275 dec.accept(this); 276 } 277 278 override void visit(const Declaration dec) 279 { 280 auto c = context.save; 281 dec.accept(this); 282 283 if (resetAst) 284 { 285 context.restore(c); 286 if (details.needsOverride) 287 context.notInheritable = true; 288 } 289 resetAst = true; 290 } 291 292 alias visit = ASTVisitor.visit; 293 294 string code; 295 bool inTarget; 296 int targetPosition; 297 bool resetAst; 298 ASTContext context; 299 InterfaceDetails details; 300 } 301 302 struct ASTContext 303 { 304 const(Attribute)[] attributes; 305 bool notInheritable; 306 bool isNothrowOrNogc; 307 308 ASTContext save() const 309 { 310 return ASTContext(attributes[], notInheritable, isNothrowOrNogc); 311 } 312 313 void restore(in ASTContext c) 314 { 315 attributes = c.attributes; 316 notInheritable = c.notInheritable; 317 isNothrowOrNogc = c.isNothrowOrNogc; 318 } 319 } 320 321 void doNothing(string, size_t, size_t, string, bool) 322 { 323 }