Вот рабочий прототип, который выводит необходимые базовые данные на консоль в качестве доказательства концепции. http://goo.gl/oeshdx.
Как это работает
Я адаптировал концепции из @dk14 на верхнем шаблоне из макро-рай.
Рай макросов позволяет определить аннотацию, которая будет применяться к любому объекту с аннотациями в исходном коде. Оттуда у вас есть доступ к AST, который компилятор генерирует для исходного кода, и API отражения scala можно использовать для изучения информации о типе элементов AST. Квазицитаты (этимология от haskell или что-то в этом роде) используются для сопоставления AST для соответствующих элементов.
Подробнее о квазицитатах
Вообще важно отметить, что квазикавычки работают с AST, но они представляют собой странный на первый взгляд API, а не прямое представление AST (!). AST подбирается для вас аннотацией макросов рая, а затем квазикавычки являются инструментом для изучения имеющегося AST: вы сопоставляете, нарезаете и делите на кубики абстрактное синтаксическое дерево, используя квазикавычки.
Практическое замечание о квазицитатах заключается в том, что существуют фиксированные шаблоны квазицитатов для соответствия каждому типу scala AST — шаблон для определения класса scala, шаблон для определения метода scala и т. д. Все эти шаблоны представлены здесь, что упрощает сопоставление и деконструкцию доступный AST к его интересным составляющим. Хотя на первый взгляд шаблоны могут показаться устрашающими, в основном это просто шаблоны, имитирующие синтаксис scala, и вы можете свободно изменять в них имена переменных с добавлением $
на имена, которые вам больше нравятся.
Мне все еще нужно отточить используемые мной квазицитаты, которые в настоящее время не идеальны. Тем не менее, мой код, кажется, дает желаемый результат во многих случаях, и оттачивание совпадений до точности 95% может быть вполне выполнимым.
Пример вывода
found class B
class B has method doB
found object DefaultExpander
object DefaultExpander has method foo
object DefaultExpander has method apply
which calls Console on object scala of type package scala
which calls foo on object DefaultExpander.this of type object DefaultExpander
which calls <init> on object new A of type class A
which calls doA on object a of type class A
which calls <init> on object new B of type class B
which calls doB on object b of type class B
which calls mkString on object tags.map[String, Seq[String]](((tag: logTag) => "[".+(Util.getObjectName(tag)).+("]")))(collection.this.Seq.canBuildFrom[String]) of type trait Seq
which calls map on object tags of type trait Seq
which calls $plus on object "[".+(Util.getObjectName(tag)) of type class String
which calls $plus on object "[" of type class String
which calls getObjectName on object Util of type object Util
which calls canBuildFrom on object collection.this.Seq of type object Seq
which calls Seq on object collection.this of type package collection
.
.
.
Из этих данных легко увидеть, как можно сопоставить вызывающих и вызываемых абонентов и как можно отфильтровать или выделить цели вызовов за пределами источника проекта. Это все для scala 2.11. Используя этот код, нужно будет добавить аннотацию к каждому классу/объекту/и т. д. в каждом исходном файле.
Проблемы, которые остаются, в основном:
Оставшиеся проблемы:
- Это вылетает после завершения работы. Зависимость от https://github.com/scalamacros/paradise/issues/67
- Нужно найти способ в конечном итоге применить магию ко всем исходным файлам без ручного аннотирования каждого класса и объекта статической аннотацией. На данный момент это довольно незначительно, и, по общему признанию, есть преимущества в возможности контролировать классы, чтобы включать и игнорировать их в любом случае. Стадия предварительной обработки, которая вставляет аннотацию перед (почти) каждым определением исходного файла верхнего уровня, была бы одним из хороших решений.
- Оттачивание сопоставителей таким образом, чтобы сопоставлялись все и только соответствующие определения - чтобы сделать это общим и надежным за пределами моего упрощенного и беглого тестирования.
Альтернативный подход к размышлению
acyclic напоминает о совершенно противоположном подходе, который все еще остается в области компилятора scala - он проверяет все символы, сгенерированные для источника компилятором (насколько я понимаю из источника). Что он делает, так это проверяет наличие циклических ссылок (подробное определение см. в репозитории). Предполагается, что к каждому символу прикреплена достаточная информация для построения графа ссылок, который должен генерировать acyclic.
Решение, основанное на этом подходе, может, если возможно, найти родительский владелец каждого символа, а не фокусироваться на графике подключений к исходным файлам, как это делает сама ациклика. Таким образом, с некоторыми усилиями он восстановит владение классом/объектом каждого метода. Не уверен, что этот дизайн не взорвется вычислительно, и как детерминистически получить класс, охватывающий каждый символ.
Положительным моментом будет то, что здесь нет необходимости в аннотациях макросов. Недостатком является то, что это не может разбрызгивать инструментарий времени выполнения, как довольно легко позволяет макрорай, что иногда может быть полезно.
person
matanster
schedule
27.04.2015