D: поиск всех функций с определенным атрибутом

В настоящее время возможно сканирование/запрос/повторение всех функций (или классов) с помощью некоторого атрибута через модули?

Например:


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 ... */) {
        ...
    }
}

Ответы

Ответ 1

Верьте или нет, но да, это своего рода... хотя это ДЕЙСТВИТЕЛЬНО взломанно и имеет много дыр. Код: http://arsdnet.net/d-walk/

Запуск, который будет печатать:

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

Вам нужно быстро взглянуть на c.d, b.d и main.d, чтобы увидеть использование. Функция onEach в main.d обрабатывает каждое нажатие на вспомогательные функции, здесь просто печатается имя. В функции main вы увидите безумный вид mixin(__MODULE__) - это хакерский трюк, чтобы получить ссылку на текущий модуль как отправную точку для нашей итерации.

Также обратите внимание, что файл main.d имеет строку module project.main; вверху вверх - если имя модуля было просто main, поскольку оно автоматически без этой декларации, взломать mixin будет путать модуль для функции main. Этот код действительно хрупкий!

Теперь обратим ваше внимание на 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;
            }
        }
    }
}

Во-первых, у нас есть определение атрибута и некоторые помощники, чтобы обнаружить его присутствие. Если вы раньше использовали UDA, ничего нового здесь не было - просто сканирование кортежей атрибутов для интересующего вас типа.

Шаблоны helper - это трюк, позволяющий сокращать повторные вызовы на __traits(getMember) - он просто псевдонизирует его до более удобного имени, избегая при этом глупой ошибки синтаксического анализа в компиляторе.

Наконец, у нас есть мясо ходока. Он перебирает allMembers, D рабочую лошадь отражения времени компиляции (если вы не знакомы с этим, возьмите gander в главе примера моей D Cookbook https://www.packtpub.com/application-development/d-cookbook - ссылка "Бесплатный образец" - это глава о отражении времени компиляции)

Далее, первый static if просто гарантирует, что мы действительно можем получить член, который хотим получить. Без этого он будет вызывать ошибки при попытке получить частных членов автоматически импортированного модуля object.

Конец функции прост - он просто называет нашу onEach вещь для каждого элемента. Но в середине есть волшебство: если он обнаруживает модуль (sooo hacky btw, но только так, как я знаю, чтобы это сделать) импортировать в прогулку, он импортирует его здесь, получая доступ к нему с помощью трюка mixin(module), используемого на верхний уровень... таким образом, рекурсивный через график импорта программ.

Если вы поиграете, вы увидите, что это действительно работает. (Скомпилируйте все эти файлы вместе в командной строке btw для получения наилучших результатов: dmd main.d attr.d b.d c.d)

Но он также имеет ряд ограничений:

  • Вхождение в члены класса/структуры возможно, но не реализовано здесь. Довольно просто: если член является классом, просто возвращайтесь в него рекурсивно.

  • Он может сломаться, если модуль имеет имя с членом, например пример с main, упомянутый выше. Работайте, используя уникальные имена модулей с некоторыми точками пакета, должны быть в порядке.

  • Он не будет входить в функцию-локальный импорт, то есть можно использовать функцию в программе, которая не будет подхвачена этим трюком. Я не знаю о каком-либо решении этого в D сегодня, даже если вы готовы использовать каждый взломать язык.

  • Добавление кода с UDA всегда сложно, но вдвойне здесь, потому что onEach - это функция с ее областью. Вы могли бы создать глобальный ассоциативный массив делегатов для обработчиков для вещей: void delegate()[string] handlers; /* ... */ handlers[memberName] = &localHandlerForThis; вид вещи для доступа к информации времени.

  • Я уверен, что он тоже не сможет скомпилировать более сложные вещи, я просто сейчас хлопнул это вместе как игрушечное доказательство концепции.

Большинство D-кода вместо того, чтобы пытаться пройти дерево импорта, как это, просто требует, чтобы вы mixin UdaHandler!T; в отдельном агрегате или модуле, где он используется, например. mixin RegisterSerializableClass!MyClass; после каждого. Может быть, не супер СУХОЙ, а более надежный.

изменить: Там была другая ошибка, которую я не заметил при написании ответа: "module b.d;" фактически не подхватили. Переименование его на "модуль b"; работает, но не тогда, когда он включает пакет.

ooooh Потому что это считается "модулем пакета" в stringof.... который не имеет членов. Может быть, если компилятор просто назвал его "module foo.bar" вместо "package foo", мы бы были в бизнесе. (конечно, это нецелесообразно для писателей-приложений), которые в настоящее время разрушают полезность трюка)