1 /++ 2 The class finder parses D files and lists any classes defined inside. 3 4 It does not attempt to limit the search to Godot-related classes or expand 5 mixins; a completely accurate class list would require its own compiler or a 6 compiler plugin system for D to list the classes as they're compiled. 7 +/ 8 module classfinder; 9 10 import godotutil..string; 11 import godotutil.classes; 12 13 import dparse.parser, dparse.lexer; 14 import dparse.ast; 15 import dparse.rollback_allocator; 16 17 import dsymbol.conversion : parseModuleSimple; 18 19 import std.file, std.path; 20 import std..string; 21 import std.range; 22 import std.meta; 23 import std.typecons : scoped; 24 import std.algorithm.iteration : joiner, map; 25 import std.conv : text; 26 import std.stdio : writeln, writefln; 27 28 size_t startLocation(in BaseNode node) { return node.tokens.front.index; } 29 size_t endLocation(in BaseNode node) { return node.tokens.back.index; } 30 31 /// A named scope such as a class or struct and its location in the file 32 struct ScopeRange 33 { 34 string name; 35 enum Type { class_, struct_, template_ } 36 Type type; 37 size_t start, end; 38 } 39 40 /// libdparse visitor to be used with a dsymbol-like simple parser 41 private class GDVisitor : ASTVisitor 42 { 43 FileInfo file; 44 string[] moduleName; 45 string overrideName; // manually set the class; TODO: not implemented yet 46 size_t[2][] overrideAttributeRanges; 47 48 ScopeRange[] scopeRanges; 49 size_t[] classScopeRange; /// which ScopeRange each class corresponds to 50 51 alias visit = ASTVisitor.visit; 52 override void visit(in ModuleDeclaration m) 53 { 54 moduleName = m.moduleName.identifiers.map!(t => cast(string)t.text).array; 55 super.visit(m); 56 } 57 58 // TODO: not implemented yet 59 version(none) override void visit(in AtAttribute a) 60 { 61 import std.algorithm.searching : canFind; 62 63 if(a.argumentList.items.canFind!(e => (cast(PrimaryExpression)e) && (cast(PrimaryExpression)e).primary.text == "MainClass")) 64 { 65 overrideAttributeRanges ~= [a.startLocation, a.endLocation]; 66 } 67 } 68 69 override void visit(in MixinTemplateName m) 70 { 71 import std.algorithm.searching; 72 if(m.tokens.canFind!(t => t.text == "GodotNativeLibrary")) file.hasEntryPoint = true; 73 } 74 75 override void visit(in StructDeclaration s) 76 { 77 ScopeRange range = ScopeRange(s.name.text, ScopeRange.Type.struct_, s.startLocation, s.endLocation); 78 scopeRanges ~= range; 79 } 80 override void visit(in InterfaceDeclaration i) 81 { 82 ScopeRange range = ScopeRange(i.name.text, ScopeRange.Type.struct_, i.startLocation, i.endLocation); 83 scopeRanges ~= range; 84 } 85 override void visit(in ClassDeclaration c) 86 { 87 string name = c.name.text.dup; 88 89 classScopeRange ~= scopeRanges.length; 90 ScopeRange range = ScopeRange(name, ScopeRange.Type.class_, c.startLocation, c.endLocation); 91 scopeRanges ~= range; 92 93 file.classes ~= name; 94 if(c.name.text.toLower == moduleName.back || c.name.text.camelToSnake == moduleName.back) 95 { 96 if(!file.mainClass.empty) writefln!"Module %s: found multiple classes matching the module name (%s and %s)"( 97 moduleName, file.mainClass, name); 98 else file.mainClass = name; 99 } 100 101 super.visit(c); 102 } 103 } 104 105 /// 106 FileInfo parse(string path) 107 { 108 RollbackAllocator rba; 109 StringCache stringCache = StringCache(StringCache.defaultBucketCount); 110 111 ubyte[] bytes = cast(ubyte[])std.file.read(path); 112 113 LexerConfig lexerConfig; 114 lexerConfig.fileName = path; 115 auto tokens = getTokensForParser(bytes, lexerConfig, &stringCache); 116 117 Module m; 118 m = parseModuleSimple(tokens, path, &rba); 119 120 auto visitor = new GDVisitor; 121 visitor.file.name = path; 122 // for root modules 123 visitor.moduleName = [path.baseName.stripExtension]; 124 125 m.accept(visitor); 126 visitor.file.moduleName = visitor.moduleName.joiner(".").text; 127 if(visitor.file.mainClass.empty && visitor.file.classes.length == 1) visitor.file.mainClass = visitor.file.classes[0]; 128 129 foreach(ci, c; visitor.file.classes) 130 { 131 import std.algorithm : filter, multiSort, map; 132 133 auto cScope = visitor.scopeRanges[visitor.classScopeRange[ci]]; 134 // names of the scopes that enclose cScope, outermost first 135 auto parentScopeNames = visitor.scopeRanges 136 .filter!(sr => sr.start <= cScope.start && sr.end >= cScope.end) 137 .array 138 .multiSort!((a, b) => a.start < b.start, (a, b) => a.end > b.end) 139 .map!(sr => sr.name); 140 141 visitor.file.classes[ci] = chain(only(visitor.file.moduleName), parentScopeNames).joiner(".").text; 142 } 143 144 return visitor.file; 145 } 146 147 148 149