Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

MacroGroovy — работа с AST на Groovy ещё никогда не была такой примитивный

Anna | 3.06.2014 | нет комментариев
image
Последнее время Зачастую доводится трудиться с такой сильной вероятностью Groovy как Compile-time AST Transformations.

Так как я не люблю излишнюю динамику, то огромная часть проверок DSL на валидность у нас происходит на этапе компиляции, а так же мы используем дюже много генерации кода. Следственно всякий день доводится сталкиваться с составлением ASTNode-ов вручную.

def someVariable = new ConstantExpression("someValue");
def returnStatement = new ReturnStatement(
    new ConstructorCallExpression(
        ClassHelper.make(SomeCoolClass),
        new ArgumentListExpression(someVariable)
    )
);

До боли знакомые конструкции, не правда ли? Хотите, Дабы было вот так?

def someVariable = macro { "someValue" }
def returnStatement = macro { return new SomeCoolClass($v{ someVariable }) }

Либо даже так?

def constructorCall = macro { new SomeCoolClass($v{ macro { "someValue" } }) }

В данной статье речь пойдёт о моём решении этой задачи, максимально близком к родному решению Groovy —github.com/bsideup/MacroGroovy

AstBuilder

Groovy 1.7 принёс такую восхитительную, казалось бы, штуку как AstBuilder, тот, что предлагает нам 3 метода построения AST:

AstBuilder.buildFromString

Передаём строку с кодом, на выходе имеем список ASTNode-ов:

List<ASTNode> nodes = new AstBuilder().buildFromString("\"Hello\"")

Превосходства

  • Входные данные — строка, может быть взята откуда желательно;
  • Не требует понимания как устроены ASTNode-ы;
  • Разрешает указывать CompilePhase;
  • Генерирует фактически 100% валидный код;
  • Надёжный — не требует метаморфозы в вашем коде если конструкция ASTNode-ов в Groovy поменялась.

Недочеты

  • IDE не поможет вам с проверкой синтаксиса;
  • рефакторинги в IDE так же не будут трудиться;
  • Некоторые сущности сделать не получится — скажем объявление поля класса.

Часть этих недостатков призван поправить дальнейший способ.

AstBuilder.buildFromCode

Передаём замыкание (aka Closure) с кодом, на выходе имеем список нодов:

List<ASTNode> nodes = new AstBuilder().buildFromCode { "Hello" }

Превосходства (помимо превосходств предыдущего способа)

  • IDE разрешает применять autocomplete, проверку синтаксиса и рефакторинги в замыкании.

Недочеты:

  • Данный метод не решает задачи неосуществимости генерировать ряд сущностей;
  • Компилирует код, из-за чего не неизменно получится применять хитроумные конструкции, либо не присутствующий класс;
  • Самый основной недочет для меня: вызов buildFromCode требует Дабы способ вызывался именно путём создания AstBuilder-а:
    new AstBuilder().buildFromCode { ... }
    

    При этом Вы даже не сумеете перенести AstBuilder в отдельное поле либо локальную переменную (следственно авторам Groovy даже пришлось прибегнуть к AstTransformation для этой AstTransformation Дабы не писать много кода)

Для тех, кому не хватает обоих способов, есть 3-й метод:

AstBuilder.buildFromSpec

Данный способ принимает в себя замыкание (к слову, вы можете проголосовать за мою Issue либо откомментировать Pull Request Дабы на этом способе возникла красивая аннотация DelegatesTo), которое представляет из себя DSL для построения AST:

List<ASTNode> nodes = new AstBuilder().buildFromSpec {
    block {
        returnStatement {
            constant "Hello"
        }
    }
}

Превосходства

  • Разрешает применять логику на Groovy для построения нодов;
  • Предоставлят вероятность конструировать фактически всякую существующую ASTNode-у;
  • Значимый плюс, т.к. тема AST generation в Groovy документирована не безупречно: Всецело задокументирован и имеет обширные примеры применения в TestCase

Недочеты

  • Изредка трудно осознать что именно вам нужно вызвать Дабы получить желаемый итог;
  • Менее многословен чем вызовы конструкторов нодов, но всё равно остаётся таковым;
  • Необычная реализация — скажем некоторые способы принимают Class взамен ClassNode, что сводит его применение на нет;
  • Ненадёжен — AST может меняться с мажорными релизами языка;
  • Вы обязаны верно знать как ваш AST должен выглядеть в определенной фазе компиляции;
  • IDE пока что (см. мой комментарий по поводу Pull Request-а) не поддерживают autocomplete для данного DSL.
Комбинирование способов

Так же стоит упомянуть что вы можете комбинировать эти способы:

List<ASTNode> result = new AstBuilder().buildFromSpec {
    method('myMethod', Opcodes.ACC_PUBLIC, String) {
        parameters {
            parameter 'parameter': String.class
        }
        exceptions {}
        block {
            owner.expression.addAll new AstBuilder().buildFromCode {
                println 'Hello from a synthesized method!'
                println "Parameter value: $parameter"
            }
        }
        annotations {}
    }
}
MacroGroovy

Выходит, позже столь обширного обзора вероятностей, Вы можете спросить: Так на…*кхм*… фига необходим MacroGroovy?

Разглядим пример из шапки поста:

def someVariable = new ConstantExpression("someValue");
def returnStatement = new ReturnStatement(
    new ConstructorCallExpression(
        ClassHelper.make(SomeCoolClass),
        new ArgumentListExpression(someVariable)
    )
);

Видите someVariable, переданную в конструктор списка доводов? Поверьте мне, такая обстановка встречается дюже и дюже Зачастую. И она сразу отметает buildFromCode и buildFromString. Значит остаётся только buildFromSpec, но вы помните список его недостатков? Вот здесь и приходит на поддержка MacroGroovy:

def someVariable = macro { "someValue" };
def returnStatement = macro { return new SomeCoolClass($v{ someVariable }) }

Превосходства

  • Все превосходства первого и второго способа;
  • Не требует создание объекта, касательно которого вызывается способ macro — он доступен во всех классах как Extension Method
  • Внутри переиспользует код из AstBuilder, так что способ надёжный и протестированый;
  • Разрешает применять логику Groovy внутри кода, т.к. $v принимает в себя замыкание, которое должно воротить то, что нужно поставить на место его вызова;
  • Дюже, ДЮЖЕ суперкомпактный:) сравните:
    macro { return mySuperVariable }

    и

    (new AstBuilder()).buildFromCode { return mySuperVariable }.first().expressions.first()
    

Недочеты

  • К сожалению, поле класса вы с поддержкой macro {} так же не создатите;
  • Нет вероятности CompilePhase;

К слову, вы всё так же можете комбинировать buildFromSpec и macro:

List<ASTNode> result = new AstBuilder().buildFromSpec {
    method('myMethod', Opcodes.ACC_PUBLIC, String) {
        parameters {
            parameter 'parameter': String.class
        }
        exceptions {}
        block macro {
                println 'Hello from a synthesized method!'
                println "Parameter value: $parameter"
        }

        annotations {}
    }
}
Завершение

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

Библиотека доступна в Maven Central, оставлю ссылку по которой неизменно дозволено обнаружить свежую версию:
search.maven.org/#search%7Cga%7C1%7Cmacro-groovy

Спасибо.

Что выбираете Вы?

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Проголосовало 2 человека. Воздержалось 3 человека.

 

 Источник: programmingmaster.ru
Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB