1 // Copyright Mario Kröplin 2015. 2 // Distributed under the Boost Software License, Version 1.0. 3 // (See accompanying file LICENSE_1_0.txt or copy at 4 // http://www.boost.org/LICENSE_1_0.txt) 5 6 module outliner; 7 8 import dparse.ast; 9 import dparse.formatter; 10 import dparse.lexer; 11 import std.algorithm; 12 import std.array; 13 import std.conv; 14 import std.stdio; 15 import std.typecons; 16 17 class Outliner : ASTVisitor 18 { 19 private File output; 20 21 private string fileName; 22 23 private Classifier classifier; 24 25 private string visibility = "+"; 26 27 private string[] modifiers; 28 29 private Classifier[] classifiers = null; 30 31 alias visit = ASTVisitor.visit; 32 33 public this(File output, string fileName) 34 { 35 this.output = output; 36 this.fileName = fileName; 37 } 38 39 public override void visit(const AttributeDeclaration attributeDeclaration) 40 { 41 const attributes = protectionAttributes(attributeDeclaration.attribute); 42 if (!attributes.empty) 43 visibility = attributes.back.attribute.toVisibility; 44 } 45 46 public override void visit(const ClassDeclaration classDeclaration) 47 { 48 auto qualifiedName = classifier.qualifiedName; 49 auto fullyQualifiedName = classifier.fullyQualifiedName; 50 auto outliner = scoped!Outliner(output, fileName); 51 with (outliner) 52 { 53 classifier.type = "class"; 54 classifier.qualifiedName = qualifiedName ~ classDeclaration.name.text; 55 classifier.fullyQualifiedName = fullyQualifiedName ~ classDeclaration.name.text; 56 classDeclaration.accept(outliner); 57 } 58 classifiers ~= outliner.classifier ~ outliner.classifiers; 59 } 60 61 public override void visit(const Constructor constructor) 62 { 63 Method method; 64 method.visibility = visibility; 65 method.name = "this"; 66 auto app = appender!(char[]); 67 app.format(constructor.parameters); 68 method.parameters = app.data.to!string; 69 classifier.methods ~= method; 70 } 71 72 public override void visit(const Declaration declaration) 73 { 74 string visibility = this.visibility; 75 const attributes = protectionAttributes(declaration); 76 if (!attributes.empty) 77 this.visibility = attributes.back.attribute.toVisibility; 78 this.modifiers = declaration.modifiers; 79 super.visit(declaration); 80 if (!attributes.empty) 81 this.visibility = visibility; 82 } 83 84 public override void visit(const Destructor destructor) 85 { 86 Method method; 87 method.visibility = visibility; 88 method.name = "~this"; 89 classifier.methods ~= method; 90 } 91 92 public override void visit(const EnumDeclaration enumDeclaration) 93 { 94 auto qualifiedName = classifier.qualifiedName; 95 auto fullyQualifiedName = classifier.fullyQualifiedName; 96 auto outliner = scoped!Outliner(output, fileName); 97 with (outliner) 98 { 99 classifier.type = "enum"; 100 classifier.qualifiedName = qualifiedName ~ enumDeclaration.name.text; 101 classifier.fullyQualifiedName = fullyQualifiedName ~ enumDeclaration.name.text; 102 enumDeclaration.accept(outliner); 103 } 104 classifiers ~= outliner.classifier ~ outliner.classifiers; 105 } 106 107 public override void visit(const EnumMember enumMember) 108 { 109 Field field; 110 field.name = enumMember.name.text; 111 classifier.fields ~= field; 112 } 113 114 public override void visit(const FunctionDeclaration functionDeclaration) 115 { 116 Method method; 117 method.visibility = visibility; 118 method.modifiers = modifiers.dup; 119 method.name = functionDeclaration.name.text; 120 if (functionDeclaration.hasAuto) 121 method.modifiers ~= "auto"; 122 if (functionDeclaration.hasRef) 123 method.modifiers ~= "ref"; 124 if (functionDeclaration.returnType !is null) 125 { 126 auto app = appender!(char[]); 127 app.format(functionDeclaration.returnType); 128 method.type = app.data.to!string; 129 } 130 auto app = appender!(char[]); 131 app.format(functionDeclaration.parameters); 132 method.parameters = app.data.to!string; 133 classifier.methods ~= method; 134 } 135 136 public override void visit(const InterfaceDeclaration interfaceDeclaration) 137 { 138 auto qualifiedName = classifier.qualifiedName; 139 auto fullyQualifiedName = classifier.fullyQualifiedName; 140 auto outliner = scoped!Outliner(output, fileName); 141 with (outliner) 142 { 143 classifier.type = "interface"; 144 classifier.qualifiedName = qualifiedName ~ interfaceDeclaration.name.text; 145 classifier.fullyQualifiedName = fullyQualifiedName ~ interfaceDeclaration.name.text; 146 interfaceDeclaration.accept(outliner); 147 } 148 classifiers ~= outliner.classifier ~ outliner.classifiers; 149 } 150 151 public override void visit(const Invariant invariant_) 152 { 153 // skip 154 } 155 156 public override void visit(const Module module_) 157 { 158 import std.string : toLower; 159 160 classifier.fullyQualifiedName = module_.moduleDeclaration.moduleName.identifiers.map!"a.text".array; 161 162 super.visit(module_); 163 string name; 164 if (module_.moduleDeclaration is null) 165 { 166 import std.path : baseName, stripExtension; 167 name = fileName.stripExtension.baseName; 168 } 169 else 170 { 171 name = module_.moduleDeclaration.moduleName.identifiers.back.text; 172 } 173 name = name.toLower; // XXX workaround for module Foo; class Foo; 174 if (!classifier.fields.empty || !classifier.methods.empty) 175 { 176 classifier.type = "class"; 177 classifier.qualifiedName = [name]; 178 classifier.stereotype = "<<(M,gold)>>"; 179 classifier.write(output.lockingTextWriter); 180 } 181 foreach (classifier; classifiers) 182 classifier.write(output.lockingTextWriter); 183 } 184 185 public override void visit(const SharedStaticConstructor sharedStaticConstructor) 186 { 187 Method method; 188 method.visibility = visibility; 189 method.modifiers = ["{static}", "shared"]; 190 method.name = "this"; 191 classifier.methods ~= method; 192 } 193 194 public override void visit(const SharedStaticDestructor sharedStaticDestructor) 195 { 196 Method method; 197 method.visibility = visibility; 198 method.modifiers = ["{static}", "shared"]; 199 method.name = "~this"; 200 classifier.methods ~= method; 201 } 202 203 public override void visit(const StaticConstructor staticConstructor) 204 { 205 Method method; 206 method.visibility = visibility; 207 method.modifiers = ["{static}"]; 208 method.name = "this"; 209 classifier.methods ~= method; 210 } 211 212 public override void visit(const StaticDestructor staticDestructor) 213 { 214 Method method; 215 method.visibility = visibility; 216 method.modifiers = ["{static}"]; 217 method.name = "~this"; 218 classifier.methods ~= method; 219 } 220 221 public override void visit(const StructDeclaration structDeclaration) 222 { 223 auto qualifiedName = classifier.qualifiedName; 224 auto fullyQualifiedName = classifier.fullyQualifiedName; 225 auto outliner = scoped!Outliner(output, fileName); 226 with (outliner) 227 { 228 classifier.type = "class"; 229 classifier.qualifiedName = qualifiedName ~ structDeclaration.name.text; 230 classifier.fullyQualifiedName = fullyQualifiedName ~ structDeclaration.name.text; 231 classifier.stereotype = "<<(S,silver)>>"; 232 structDeclaration.accept(outliner); 233 } 234 classifiers ~= outliner.classifier ~ outliner.classifiers; 235 } 236 237 public override void visit(const Unittest unittest_) 238 { 239 // skip 240 } 241 242 public override void visit(const VariableDeclaration variableDeclaration) 243 { 244 Field field; 245 field.visibility = visibility; 246 field.modifiers = modifiers.dup; 247 if (variableDeclaration.type !is null) 248 { 249 auto app = appender!(char[]); 250 app.format(variableDeclaration.type); 251 field.type = app.data.to!string; 252 } 253 foreach (declarator; variableDeclaration.declarators) 254 { 255 field.name = declarator.name.text; 256 classifier.fields ~= field; 257 } 258 } 259 } 260 261 struct Classifier 262 { 263 const string indent = " "; 264 265 string type; 266 267 string[] qualifiedName = null; 268 269 const(string)[] fullyQualifiedName = null; 270 271 string stereotype; 272 273 Field[] fields; 274 275 Method[] methods; 276 277 void write(Sink)(Sink sink) const 278 { 279 sink.put(type); 280 sink.put(' '); 281 foreach (index, name; qualifiedName) 282 { 283 if (index > 0) 284 sink.put('.'); 285 sink.put(name); 286 } 287 sink.put(' '); 288 if (!stereotype.empty) 289 { 290 sink.put(stereotype); 291 sink.put(' '); 292 } 293 sink.put("$generated"); 294 sink.put(' '); 295 sink.put("$"); 296 foreach (index, name; fullyQualifiedName) 297 { 298 if (index > 0) 299 sink.put('.'); 300 sink.put(name); 301 } 302 sink.put(' '); 303 sink.put("{"); 304 sink.put('\n'); 305 foreach (field; fields) 306 { 307 sink.put(indent); 308 field.write(sink); 309 sink.put('\n'); 310 } 311 foreach (method; methods) 312 { 313 sink.put(indent); 314 method.write(sink); 315 sink.put('\n'); 316 } 317 sink.put("}"); 318 sink.put('\n'); 319 } 320 } 321 322 struct Field 323 { 324 string visibility; 325 326 string[] modifiers; 327 328 string type; 329 330 string name; 331 332 void write(Sink)(Sink sink) const 333 { 334 // override check for parenthesis to choose between methods and fields 335 sink.put("{field} "); 336 sink.put(visibility); 337 foreach (modifier; modifiers) 338 { 339 sink.put(modifier); 340 sink.put(' '); 341 } 342 if (!type.empty) 343 { 344 sink.put(type); 345 sink.put(' '); 346 } 347 sink.put(name); 348 } 349 } 350 351 struct Method 352 { 353 string visibility; 354 355 string[] modifiers; 356 357 string type; 358 359 string name; 360 361 string parameters = "()"; 362 363 void write(Sink)(Sink sink) const 364 { 365 sink.put(visibility); 366 foreach (modifier; modifiers) 367 { 368 sink.put(modifier); 369 sink.put(' '); 370 } 371 if (!type.empty) 372 { 373 sink.put(type); 374 sink.put(' '); 375 } 376 sink.put(name); 377 sink.put(escape(parameters)); 378 } 379 } 380 381 private string escape(string source) pure 382 { 383 return source.replace(`\`, `\\`); 384 } 385 386 private const(Attribute[]) protectionAttributes(const Declaration declaration) pure 387 { 388 const(Attribute)[] attributes = null; 389 foreach (attribute; declaration.attributes) 390 attributes ~= protectionAttributes(attribute); 391 return attributes; 392 } 393 394 private const(Attribute[]) protectionAttributes(const Attribute attribute) pure 395 { 396 return (attribute.attribute.type.isProtection) ? [attribute] : null; 397 } 398 399 private string toVisibility(const Token token) pure 400 in 401 { 402 assert(token.type.isProtection); 403 } 404 body 405 { 406 switch (token.type) 407 { 408 case tok!"package": 409 return "~"; 410 case tok!"private": 411 return "-"; 412 case tok!"protected": 413 return "#"; 414 case tok!"public": 415 return "+"; 416 default: 417 return "+"; 418 } 419 } 420 421 private string[] modifiers(const Declaration declaration) pure 422 { 423 string[] modifiers = null; 424 if (declaration.attributes.any!(a => a.attribute == tok!"abstract")) 425 modifiers ~= "{abstract}"; 426 if (declaration.attributes.any!(a => a.attribute == tok!"static")) 427 modifiers ~= "{static}"; 428 return modifiers; 429 }