2014-08-28 14 views
16

Czy jest obecnie możliwe skanowanie/zapytanie/iterowanie wszystkich funkcji (lub klas) z pewnym atrybutem w modułach?D: znajdowanie wszystkich funkcji z pewnym atrybutem

Na przykład:


source/packageA/something.d:

@sillyWalk(10) 
void doSomething() 
{ 
} 

source/packageB/anotherThing.d:

@sillyWalk(50) 
void anotherThing() 
{ 
} 

source/main.d:

void main() 
{ 
    for (func; /* All @sillWalk ... */) { 
     ... 
    } 
} 

Odpowiedz

37

Wierzcie lub nie, ale tak, to trochę jest ... choć jest to naprawdę hacky i ma wiele dziur. Kod: http://arsdnet.net/d-walk/

Bieg które drukują:

Processing: module main 
Processing: module object 
Processing: module c 
Processing: module attr 
test2() has sillyWalk 
main() has sillyWalk 

będziemy chcieli podjąć szybkie spojrzenie na c.d, b.d i main.d zobaczyć użytkowania. Funkcja onEach w main.d przetwarza każde trafienie znalezione przez funkcję pomocnika, tutaj wystarczy wydrukować nazwę. W funkcji main zobaczysz szalenie wyglądającego mixin(__MODULE__) - jest to hacky trick, aby uzyskać odniesienie do obecnego modułu jako punkt wyjścia dla naszej iteracji.

Zauważ też, że plik main.d ma module project.main; korekty top - jeśli nazwa modułu właśnie main jak to jest automatycznie bez tego zgłoszenia, mixin Hack by zmylić moduł dla funkcji main. Ten kod jest naprawdę kruchy!

Teraz kierować swoją uwagę na attr.d: http://arsdnet.net/d-walk/attr.d

module attr; 

struct sillyWalk { int i; } 

enum isSillyWalk(alias T) = is(typeof(T) == sillyWalk); 

import std.typetuple; 
alias hasSillyWalk(alias what) = anySatisfy!(isSillyWalk, __traits(getAttributes, what)); 
enum hasSillyWalk(what) = false; 

alias helper(alias T) = T; 
alias helper(T) = T; 

void allWithSillyWalk(alias a, alias onEach)() { 
    pragma(msg, "Processing: " ~ a.stringof); 
    foreach(memberName; __traits(allMembers, a)) { 
     // guards against errors from trying to access private stuff etc. 
     static if(__traits(compiles, __traits(getMember, a, memberName))) { 
      alias member = helper!(__traits(getMember, a, memberName)); 

      // pragma(msg, "looking at " ~ memberName); 
      import std.string; 
      static if(!is(typeof(member)) && member.stringof.startsWith("module ")) { 
       enum mn = member.stringof["module ".length .. $]; 
       mixin("import " ~ mn ~ ";"); 
       allWithSillyWalk!(mixin(mn), onEach); 
      } 

      static if(hasSillyWalk!(member)) { 
       onEach!member; 
      } 
     } 
    } 
} 

Po pierwsze, mamy definicję atrybutu i niektóre pomocników, aby wykryć jego obecność. Jeśli używany Udas wcześniej, nic nowego tutaj - po prostu skanowania atrybuty krotka dla typu jesteśmy zainteresowani

W helper szablony są podstęp skrót powtarzające się apele do __traits(getMember). - to po prostu aliasy go do ładniejsza nazwa przy uniknięciu głupiego błędu parsowania w kompilatorze.

Wreszcie mamy mięso wędrowca. Zapętla się nad allMembers, kompilatorem czasu kompilacji D'czasu odbicia (jeśli nie jesteś tego obeznany, weź przykład z rozdziału D mojej książki kucharskiej https://www.packtpub.com/application-development/d-cookbook - link "Bezpłatne próbka" jest rozdziałem poświęconym czasowi kompilacji)

Następnie pierwszy static if po prostu upewnia się, że rzeczywiście możemy uzyskać członka, którego chcemy uzyskać. Bez tego powodowałoby to błędy przy próbie uzyskania prywatnych członków automatycznie importowanego modułu object.

Koniec funkcji jest prosty - po prostu wywołuje naszą rzecz z onEach na każdym elemencie. Ale środek jest tam, gdzie jest magia: jeśli wykryje moduł (sooo hacky btw ale tylko sposób wiem, że to zrobię) import w chodzeniu, importuje go tutaj, uzyskując do niego dostęp za pomocą sztuczki mixin(module) używanej na najwyższym poziomie ... w ten sposób powtarza się wykres importu programu.

Jeśli się bawisz, zobaczysz, że to naprawdę działa. (Kompilacja wszystkie te pliki razem w wierszu poleceń btw dla najlepszych wyników: dmd main.d attr.d b.d c.d)

Ale ma również szereg ograniczeń:

  • wchodząc w klasie/członków struct jest możliwe, ale nie realizowane tutaj . Całkiem proste: jeśli członek jest klasą, po prostu zejdź do niego również rekurencyjnie.

  • Jest podatny na złamanie, jeśli moduł udostępnia nazwę członkowi, na przykład wspomnianemu wyżej przykładowi z main. Obchodź się, używając unikalnych nazw modułów z niektórymi kropkami, powinno być w porządku.

  • Nie przejdzie do funkcji import lokalny, co oznacza, że ​​można użyć funkcji w programie, która nie zostanie pobrana przez tę sztuczkę. Nie znam żadnego rozwiązania tego w dniu dzisiejszym, nawet jeśli zechcesz użyć każdego hackowania w tym języku.

  • Dodawanie kodu z UDA jest zawsze trudne, ale podwójnie, więc tutaj, ponieważ onEach jest funkcją z zakresem działania. Być może można zbudować globalną tablicę asocjacyjną delegatów do modułów obsługi dla rzeczy: void delegate()[string] handlers; /* ... */ handlers[memberName] = &localHandlerForThis; rodzaju rzeczy do runtime dostęp do informacji.

  • Założę się, że nie uda się skompilować na bardziej skomplikowanych rzeczach, po prostu spoliczkowałem to teraz jako zabawkowy dowód koncepcji.

Większość kod D, zamiast próbować chodzić drzewo import takiego, po prostu wymaga, że ​​mixin UdaHandler!T; w indywidualnej kruszywa lub moduł, gdzie jest on stosowany, na przykład mixin RegisterSerializableClass!MyClass; po każdym. Może nie super DRY, ale bardziej niezawodny.

edycja: Jest jeszcze jeden błąd, którego nie zauważyłem podczas pisania odpowiedzi pierwotnie: "moduł b.d;" tak naprawdę nie został odebrany. Zmiana nazwy na "moduł b;" działa, ale nie wtedy, gdy zawiera pakiet.

ooooh, ponieważ jest uważany za "pakiet mod" w stringof .... który nie ma członków. Być może, gdyby kompilator nazwał to "modułem foo.bar" zamiast "pakietem foo", działalibyśmy. (Oczywiście jest to niepraktyczne dla twórców aplikacji ... które w pewnym sensie psują przydatność tej sztuczki)

+2

W pewnym sensie akceptacja i głosowanie w górę takiej odpowiedzi nie wystarcza. Myślę, że ta odpowiedź jest warta przynajmniej 2-3 litry! –

+2

Dla rekordu, zaimplementowałem go w końcu z twoją sugestią użycia mixins ('mixin sillWalk! (10, func)'). Ale granie twojego kodu było bardzo cennym doświadczeniem edukacyjnym! –

+0

@Adam, byłoby dobrze, gdybyś mógł edytować swoją odpowiedź, aby dołączyć kod do treści odpowiedzi. –

Powiązane problemy