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 godot.tools.classfinder; 9 10 import godot.util.string; 11 import godot.util.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) { 29 return node.tokens.front.index; 30 } 31 32 size_t endLocation(in BaseNode node) { 33 return node.tokens.back.index; 34 } 35 36 /// A named scope such as a class or struct and its location in the file 37 struct ScopeRange { 38 string name; 39 enum Type { 40 class_, 41 struct_, 42 template_ 43 } 44 45 Type type; 46 size_t start, end; 47 } 48 49 /// libdparse visitor to be used with a dsymbol-like simple parser 50 private class GDVisitor : ASTVisitor { 51 FileInfo file; 52 string[] moduleName; 53 string overrideName; // manually set the class; TODO: not implemented yet 54 size_t[2][] overrideAttributeRanges; 55 56 ScopeRange[] scopeRanges; 57 size_t[] classScopeRange; /// which ScopeRange each class corresponds to 58 59 alias visit = ASTVisitor.visit; 60 override void visit(in ModuleDeclaration m) { 61 moduleName = m.moduleName.identifiers.map!(t => cast(string) t.text).array; 62 super.visit(m); 63 } 64 65 // TODO: not implemented yet 66 version (none) override void visit(in AtAttribute a) { 67 import std.algorithm.searching : canFind; 68 69 if (a.argumentList.items.canFind!(e => (cast(PrimaryExpression) e) && ( 70 cast(PrimaryExpression) e).primary.text == "MainClass")) { 71 overrideAttributeRanges ~= [a.startLocation, a.endLocation]; 72 } 73 } 74 75 override void visit(in MixinTemplateName m) { 76 import std.algorithm.searching; 77 78 if (m.tokens.canFind!(t => t.text == "GodotNativeLibrary")) 79 file.hasEntryPoint = true; 80 } 81 82 override void visit(in StructDeclaration s) { 83 ScopeRange range = ScopeRange(s.name.text, ScopeRange.Type.struct_, s.startLocation, s 84 .endLocation); 85 scopeRanges ~= range; 86 } 87 88 override void visit(in InterfaceDeclaration i) { 89 ScopeRange range = ScopeRange(i.name.text, ScopeRange.Type.struct_, i.startLocation, i 90 .endLocation); 91 scopeRanges ~= range; 92 } 93 94 override void visit(in ClassDeclaration c) { 95 string name = c.name.text.dup; 96 97 classScopeRange ~= scopeRanges.length; 98 ScopeRange range = ScopeRange(name, ScopeRange.Type.class_, c.startLocation, c.endLocation); 99 scopeRanges ~= range; 100 101 file.classes ~= name; 102 if (c.name.text.toLower == moduleName.back || c.name.text.camelToSnake == moduleName.back) { 103 if (!file.mainClass.empty) 104 writefln!"Module %s: found multiple classes matching the module name (%s and %s)"( 105 moduleName, file.mainClass, name); 106 else 107 file.mainClass = name; 108 } 109 110 super.visit(c); 111 } 112 } 113 114 /// 115 FileInfo parse(string path) { 116 RollbackAllocator rba; 117 StringCache stringCache = StringCache(StringCache.defaultBucketCount); 118 119 ubyte[] bytes = cast(ubyte[]) std.file.read(path); 120 121 LexerConfig lexerConfig; 122 lexerConfig.fileName = path; 123 auto tokens = getTokensForParser(bytes, lexerConfig, &stringCache); 124 125 Module m; 126 m = parseModuleSimple(tokens, path, &rba); 127 128 auto visitor = new GDVisitor; 129 visitor.file.name = path; 130 // for root modules 131 visitor.moduleName = [path.baseName.stripExtension]; 132 133 m.accept(visitor); 134 visitor.file.moduleName = visitor.moduleName.joiner(".").text; 135 if (visitor.file.mainClass.empty && visitor.file.classes.length == 1) 136 visitor.file.mainClass = visitor.file.classes[0]; 137 138 foreach (ci, c; visitor.file.classes) { 139 import std.algorithm : filter, multiSort, map; 140 141 auto cScope = visitor.scopeRanges[visitor.classScopeRange[ci]]; 142 // names of the scopes that enclose cScope, outermost first 143 auto parentScopeNames = visitor.scopeRanges 144 .filter!(sr => sr.start <= cScope.start && sr.end >= cScope.end) 145 .array 146 .multiSort!((a, b) => a.start < b.start, (a, b) => a.end > b.end) 147 .map!(sr => sr.name); 148 149 visitor.file.classes[ci] = chain(only(visitor.file.moduleName), parentScopeNames).joiner(".") 150 .text; 151 } 152 153 return visitor.file; 154 }