Выбрать главу

Пользовательские флаги можно определить с помощью опций --define или -D. Например, если вы хотите проверить наличие flag? :foo, флаг можно определить, выполнив crystal run -Dfoo main.cr. Флаги времени компиляции либо присутствуют, либо нет; они не могут включать значение. Однако переменные окружающей среды могут стать хорошей заменой, если требуется большая гибкость.

Переменные среды можно прочитать во время компиляции с помощью метода макроса env. Хорошим вариантом использования этого является возможность встраивания в двоичный файл информации о времени сборки, такой как эпоха сборки, время сборки и т. д. В этом примере во время компиляции значение константы будет установлено либо в значение переменной среды BUILD_SHA_HASH, либо в пустую строку, если она не была установлена (все это происходит во время компиляции):

COMMIT_SHA = {{ env("BUILD_SHA_HASH") || "" }}

pp COMMIT_SHA

При запуске этого кода обычно печатается пустая строка, а при установке связанной переменной env выводится это значение. Установка этого значения через переменную env, а не генерация внутри самого макроса с помощью системного вызова, гораздо более переносима, поскольку не зависит от Git, а также гораздо проще интегрируется с внешними системами сборки, такими как Make.

Одним из ограничений макросов является то, что сгенерированный из макроса код также должен быть действительным кодом Crystal, как показано здесь:

def {{"foo".id}}

  "foo"

end

Этот предыдущий код не является допустимой программой, поскольку метод неполный и не полностью определен в макросе. Этот метод можно включить в макрос, обернув все тегами {% begin %}/{% end %}, которые будут выглядеть следующим образом:

{% begin %}

  def {{"foo".id}}

    "foo"

  end

{% end %}

На этом этапе вы должны иметь четкое начальное представление о том, что такое макросы, как их определять и для каких случаев использования они предназначены, что позволит вам сохранить ваш код СУХИМ (DRY). Далее мы рассмотрим API макросов, чтобы можно было создавать более сложные макросы.

Понимание API макросов

В примерах из предыдущего раздела в контексте макроса использовались различные переменные разных типов, такие как числа, которые мы перебираем, строки, которые мы используем для создания идентификаторов, и логические значения, которые мы сравниваем для условной генерации кода. Было бы легко предположить, что это напрямую соответствует стандартным типам Number, String и Bool. Однако это не так. Как мы упоминали в разделе «Определение макросов» этой главы, макросы работают на узлах AST и, как таковые, имеют свой собственный набор типов, похожий на связанные с ними обычные типы Crystal, но с подмножеством API. Например, типы, с которыми мы до сих пор работали, включают NumberLiteral, StringLiteral и BoolLiteral.

Все типы макросов находятся в пространстве имен Crystaclass="underline" :Macros в документации API, которая находится по адресу https://crystal-lang.org/api/Crystal/Macros.html. К наиболее распространенным/полезным типам относятся следующие:

 Def: описывает определение метода.

 TypeNode: описывает тип (класс, структура, модуль, библиотека).

 MetaVar: описывает переменную экземпляра.

 Arg: описывает аргумент метода.

Annotation: представляет аннотацию, применяемую к типу, методу или переменной экземпляра (подробнее об этом в следующей главе).

Crystal предоставляет удобный способ получить экземпляр первых двух типов в виде макропеременных @def и @type. Как следует из их названий, использование @def внутри метода вернет экземпляр Def, представляющий этот метод. Аналогично, использование @type вернет экземпляр TypeNode для связанного типа. Доступ к другим типам можно получить через методы, основанные на одном из этих двух типов. Например, запуск следующей программы выведет "Метод hello внутри Foo":

class Foo

  def hello

    {{"The #{@def.name} method within #{@type.name}"}}

  end