1 module workspaced.visitors.attributes; 2 3 import dparse.ast; 4 import dparse.formatter; 5 import dparse.lexer; 6 7 import std.algorithm; 8 import std.conv; 9 import std.range; 10 import std.string; 11 import std.variant; 12 13 class AttributesVisitor : ASTVisitor 14 { 15 bool sticky; 16 17 override void visit(const MemberFunctionAttribute attribute) 18 { 19 if (attribute.tokenType != IdType.init) 20 context.attributes ~= ASTContext.AnyAttributeInfo( 21 ASTContext.AnyAttribute(cast() attribute), sticky); 22 attribute.accept(this); 23 } 24 25 override void visit(const FunctionAttribute attribute) 26 { 27 if (attribute.token.type != IdType.init) 28 context.attributes ~= ASTContext.AnyAttributeInfo(ASTContext.AnyAttribute( 29 ASTContext.SimpleAttribute([cast() attribute.token])), sticky); 30 attribute.accept(this); 31 } 32 33 override void visit(const AtAttribute attribute) 34 { 35 context.attributes ~= ASTContext.AnyAttributeInfo( 36 ASTContext.AnyAttribute(cast() attribute), sticky); 37 attribute.accept(this); 38 } 39 40 override void visit(const PragmaExpression attribute) 41 { 42 context.attributes ~= ASTContext.AnyAttributeInfo( 43 ASTContext.AnyAttribute(cast() attribute), sticky); 44 attribute.accept(this); 45 } 46 47 override void visit(const Deprecated attribute) 48 { 49 context.attributes ~= ASTContext.AnyAttributeInfo( 50 ASTContext.AnyAttribute(cast() attribute), sticky); 51 attribute.accept(this); 52 } 53 54 override void visit(const AlignAttribute attribute) 55 { 56 context.attributes ~= ASTContext.AnyAttributeInfo( 57 ASTContext.AnyAttribute(cast() attribute), sticky); 58 attribute.accept(this); 59 } 60 61 override void visit(const LinkageAttribute attribute) 62 { 63 context.attributes ~= ASTContext.AnyAttributeInfo( 64 ASTContext.AnyAttribute(cast() attribute), sticky); 65 attribute.accept(this); 66 } 67 68 override void visit(const Attribute attribute) 69 { 70 Token[] tokens; 71 if (attribute.attribute.type != IdType.init) 72 tokens ~= attribute.attribute; 73 if (attribute.identifierChain) 74 tokens ~= attribute.identifierChain.identifiers; 75 if (tokens.length) 76 context.attributes ~= ASTContext.AnyAttributeInfo( 77 ASTContext.AnyAttribute(ASTContext.SimpleAttribute(tokens)), sticky); 78 attribute.accept(this); 79 } 80 81 override void visit(const StorageClass storage) 82 { 83 if (storage.token.type != IdType.init) 84 context.attributes ~= ASTContext.AnyAttributeInfo(ASTContext.AnyAttribute( 85 ASTContext.SimpleAttribute([cast() storage.token])), sticky); 86 storage.accept(this); 87 } 88 89 override void visit(const AttributeDeclaration dec) 90 { 91 sticky = true; 92 dec.accept(this); 93 sticky = false; 94 } 95 96 override void visit(const Declaration dec) 97 { 98 auto c = context.save; 99 // attribute ':' (private:) 100 bool attribDecl = !!dec.attributeDeclaration; 101 if (attribDecl) 102 { 103 sticky = true; 104 processDeclaration(dec); 105 sticky = false; 106 } 107 else 108 { 109 processDeclaration(dec); 110 context.restore(c); 111 } 112 } 113 114 override void visit(const InterfaceDeclaration dec) 115 { 116 auto c = context.save; 117 context.pushContainer(ASTContext.ContainerAttribute.Type.class_, dec.name.text); 118 super.visit(dec); 119 context.restore(c); 120 } 121 122 override void visit(const ClassDeclaration dec) 123 { 124 auto c = context.save; 125 context.pushContainer(ASTContext.ContainerAttribute.Type.class_, dec.name.text); 126 super.visit(dec); 127 context.restore(c); 128 } 129 130 override void visit(const StructDeclaration dec) 131 { 132 auto c = context.save; 133 context.pushContainer(ASTContext.ContainerAttribute.Type.struct_, dec.name.text); 134 super.visit(dec); 135 context.restore(c); 136 } 137 138 override void visit(const UnionDeclaration dec) 139 { 140 auto c = context.save; 141 context.pushContainer(ASTContext.ContainerAttribute.Type.union_, dec.name.text); 142 super.visit(dec); 143 context.restore(c); 144 } 145 146 override void visit(const EnumDeclaration dec) 147 { 148 auto c = context.save; 149 context.pushContainer(ASTContext.ContainerAttribute.Type.enum_, dec.name.text); 150 super.visit(dec); 151 context.restore(c); 152 } 153 154 override void visit(const TemplateDeclaration dec) 155 { 156 auto c = context.save; 157 context.pushContainer(ASTContext.ContainerAttribute.Type.template_, dec.name.text); 158 super.visit(dec); 159 context.restore(c); 160 } 161 162 void processDeclaration(const Declaration dec) 163 { 164 dec.accept(this); 165 } 166 167 ASTContext context; 168 169 alias visit = ASTVisitor.visit; 170 } 171 172 struct ASTContext 173 { 174 struct UserdataAttribute 175 { 176 string name; 177 Variant variant; 178 } 179 180 struct ContainerAttribute 181 { 182 enum Type 183 { 184 class_, 185 interface_, 186 struct_, 187 union_, 188 enum_, 189 template_ 190 } 191 192 Type type; 193 string name; 194 } 195 196 struct SimpleAttribute 197 { 198 Token[] attributes; 199 200 Token attribute() @property 201 { 202 if (attributes.length == 1) 203 return attributes[0]; 204 else 205 return Token.init; 206 } 207 208 Token firstAttribute() @property 209 { 210 if (attributes.length >= 1) 211 return attributes[0]; 212 else 213 return Token.init; 214 } 215 216 string toString() const 217 { 218 return attributes.map!(a => a.text.length ? a.text : str(a.type)).join("."); 219 } 220 } 221 222 alias AnyAttribute = Algebraic!(PragmaExpression, Deprecated, AtAttribute, AlignAttribute, LinkageAttribute, 223 SimpleAttribute, MemberFunctionAttribute, ContainerAttribute, UserdataAttribute); 224 225 class AttributeWithInfo(T) 226 { 227 T value; 228 bool sticky; 229 230 alias value this; 231 232 this(T value, bool sticky) 233 { 234 this.value = value; 235 this.sticky = sticky; 236 } 237 238 auto opUnary(string op : "*")() 239 { 240 return this; 241 } 242 } 243 244 struct AnyAttributeInfo 245 { 246 AnyAttribute base; 247 // if true then this attribute is applying to all the following statements in the block (attribute ':') 248 bool sticky; 249 250 AttributeWithInfo!T peek(T)() 251 { 252 auto ret = base.peek!T; 253 if (!!ret) 254 return new AttributeWithInfo!T(*ret, sticky); 255 else 256 return null; 257 } 258 259 alias base this; 260 } 261 262 AnyAttributeInfo[] attributes; 263 264 /// Attributes only inside a container 265 auto localAttributes() @property 266 { 267 auto end = attributes.retro.countUntil!(a => !!a.peek!ContainerAttribute); 268 if (end == -1) 269 return attributes; 270 else 271 return attributes[$ - end .. $]; 272 } 273 274 auto attributeDescriptions() @property 275 { 276 return attributes.map!((a) { 277 if (auto prag = a.peek!PragmaExpression) 278 return "pragma(" ~ prag.identifier.text ~ ", ...[" 279 ~ prag.argumentList.items.length.to!string ~ "])"; 280 else if (auto depr = a.peek!Deprecated) 281 return "deprecated"; 282 else if (auto at = a.peek!AtAttribute) 283 return "@" ~ at.identifier.text; 284 else if (auto align_ = a.peek!AlignAttribute) 285 return "align"; 286 else if (auto linkage = a.peek!LinkageAttribute) 287 return "extern (" ~ linkage.identifier.text ~ (linkage.hasPlusPlus ? "++" : "") ~ ")"; 288 else if (auto simple = a.peek!SimpleAttribute) 289 return simple.attributes.map!(a => a.text.length ? a.text : str(a.type)).join("."); 290 else if (auto mfunAttr = a.peek!MemberFunctionAttribute) 291 return "mfun<" ~ (mfunAttr.atAttribute 292 ? "@" ~ mfunAttr.atAttribute.identifier.text : "???") ~ ">"; 293 else if (auto container = a.peek!ContainerAttribute) 294 return container.type.to!string[0 .. $ - 1] ~ " " ~ container.name; 295 else if (auto user = a.peek!UserdataAttribute) 296 return "user " ~ user.name; 297 else 298 return "Unknown type?!"; 299 }); 300 } 301 302 private static auto formatAttributes(T)(T attributes) 303 { 304 return attributes.map!((a) { 305 auto t = appender!string; 306 if (auto prag = a.peek!PragmaExpression) 307 format(t, *prag); 308 else if (auto depr = a.peek!Deprecated) 309 format(t, *depr); 310 else if (auto at = a.peek!AtAttribute) 311 format(t, *at); 312 else if (auto align_ = a.peek!AlignAttribute) 313 format(t, *align_); 314 else if (auto linkage = a.peek!LinkageAttribute) 315 format(t, *linkage); 316 else if (auto simple = a.peek!SimpleAttribute) 317 return simple.attributes.map!(a => a.text.length ? a.text : str(a.type)).join("."); 318 else if (auto mfunAttr = a.peek!MemberFunctionAttribute) 319 return str(mfunAttr.tokenType); 320 else if (auto container = a.peek!ContainerAttribute) 321 return null; 322 else if (auto user = a.peek!UserdataAttribute) 323 return null; 324 else 325 return "/* <<ERROR>> */"; 326 return t.data.strip; 327 }); 328 } 329 330 auto formattedAttributes() @property 331 { 332 return formatAttributes(attributes.normalizeAttributes); 333 } 334 335 auto localFormattedAttributes() @property 336 { 337 return formatAttributes(localAttributes.normalizeAttributes); 338 } 339 340 auto simpleAttributes() @property 341 { 342 return attributes.filter!(a => !!a.peek!SimpleAttribute) 343 .map!(a => *a.peek!SimpleAttribute); 344 } 345 346 auto simpleAttributesInContainer() @property 347 { 348 return localAttributes.filter!(a => !!a.peek!SimpleAttribute) 349 .map!(a => *a.peek!SimpleAttribute); 350 } 351 352 auto atAttributes() @property 353 { 354 return attributes.filter!(a => !!a.peek!AtAttribute) 355 .map!(a => *a.peek!AtAttribute); 356 } 357 358 auto atAttributesInContainer() @property 359 { 360 return localAttributes.filter!(a => !!a.peek!AtAttribute) 361 .map!(a => *a.peek!AtAttribute); 362 } 363 364 auto memberFunctionAttributes() @property 365 { 366 return attributes.filter!(a => !!a.peek!MemberFunctionAttribute) 367 .map!(a => *a.peek!MemberFunctionAttribute); 368 } 369 370 auto memberFunctionAttributesInContainer() @property 371 { 372 return localAttributes.filter!(a => !!a.peek!MemberFunctionAttribute) 373 .map!(a => *a.peek!MemberFunctionAttribute); 374 } 375 376 auto userdataAttributes() @property 377 { 378 return attributes.filter!(a => !!a.peek!UserdataAttribute) 379 .map!(a => *a.peek!UserdataAttribute); 380 } 381 382 auto containerAttributes() @property 383 { 384 return attributes.filter!(a => !!a.peek!ContainerAttribute) 385 .map!(a => *a.peek!ContainerAttribute); 386 } 387 388 bool isToken(IdType t) @property 389 { 390 return memberFunctionAttributes.any!(a => a.tokenType == t) 391 || simpleAttributes.any!(a => a.attribute.type == t); 392 } 393 394 bool isTokenInContainer(IdType t) @property 395 { 396 return memberFunctionAttributesInContainer.any!(a => a.tokenType == t) 397 || simpleAttributesInContainer.any!(a => a.attribute.type == t); 398 } 399 400 bool isNothrow() @property 401 { 402 return isToken(tok!"nothrow"); 403 } 404 405 bool isNothrowInContainer() @property 406 { 407 return isTokenInContainer(tok!"nothrow"); 408 } 409 410 bool isNogc() @property 411 { 412 return atAttributes.any!(a => a.identifier.text == "nogc"); 413 } 414 415 bool isNogcInContainer() @property 416 { 417 return atAttributesInContainer.any!(a => a.identifier.text == "nogc"); 418 } 419 420 bool isStatic() @property 421 { 422 return isToken(tok!"static"); 423 } 424 425 bool isFinal() @property 426 { 427 return isToken(tok!"final"); 428 } 429 430 bool isAbstract() @property 431 { 432 return isToken(tok!"abstract"); 433 } 434 435 bool isAbstractInContainer() @property 436 { 437 return isTokenInContainer(tok!"abstract"); 438 } 439 440 /// Returns: if a block needs implementations (virtual/abstract or interface methods) 441 /// 0 = must not be implemented (not in a container, private, static or final method) 442 /// 1 = optionally implementable, must be implemented if there is no function body 443 /// 9 = must be implemented 444 int requiredImplementationLevel() @property 445 { 446 auto container = containerAttributes; 447 if (container.empty || protectionType == tok!"private" || isStatic || isFinal) 448 return 0; 449 ContainerAttribute innerContainer = container.tail(1).front; 450 if (innerContainer.type == ContainerAttribute.Type.class_) 451 return isAbstractInContainer ? 9 : 1; 452 else // interface (or others) 453 return 9; 454 } 455 456 AttributeWithInfo!SimpleAttribute protectionAttribute() @property 457 { 458 auto prot = simpleAttributes.filter!(a => a.firstAttribute.isProtectionToken).tail(1); 459 if (prot.empty) 460 return null; 461 else 462 return prot.front; 463 } 464 465 IdType protectionType() @property 466 { 467 auto attr = protectionAttribute; 468 if (attr is null) 469 return IdType.init; 470 else 471 return attr.attributes[0].type; 472 } 473 474 void pushData(string name, Variant value, bool sticky = false) 475 { 476 attributes ~= AnyAttributeInfo(AnyAttribute(UserdataAttribute(name, value)), sticky); 477 } 478 479 void pushData(T)(string name, T value, bool sticky = false) 480 { 481 pushData(name, Variant(value), sticky); 482 } 483 484 void pushContainer(ContainerAttribute.Type type, string name) 485 { 486 attributes ~= AnyAttributeInfo(AnyAttribute(ContainerAttribute(type, name)), false); 487 } 488 489 ASTContext save() 490 { 491 return ASTContext(attributes[]); 492 } 493 494 void restore(ASTContext c) 495 { 496 attributes = c.attributes; 497 } 498 } 499 500 bool isProtectionToken(const Token t) @property 501 { 502 return !!t.type.among!(tok!"public", tok!"private", tok!"protected", tok!"package"); 503 } 504 505 ASTContext.AnyAttributeInfo[] normalizeAttributes(ASTContext.AnyAttributeInfo[] attributes) 506 { 507 ASTContext.AnyAttributeInfo[] ret; 508 ret.reserve(attributes.length); 509 bool gotProtection; 510 bool gotSafety; 511 foreach_reverse (ref attr; attributes) 512 { 513 if (auto simple = attr.peek!(ASTContext.SimpleAttribute)) 514 { 515 if (simple.firstAttribute.isProtectionToken) 516 { 517 if (gotProtection) 518 continue; 519 gotProtection = true; 520 } 521 } 522 else if (auto at = attr.peek!AtAttribute) 523 { 524 // search & replace for existing @safe, @trusted, @system 525 if (at.identifier.text.among!("safe", "trusted", "system")) 526 { 527 if (gotSafety) 528 continue; 529 gotSafety = true; 530 } 531 } 532 ret ~= attr; 533 } 534 ret.reverse(); 535 return ret; 536 }