В этом разделе описаны другие программы, которые мы распространяем и которые помогают в решении масштабной задачи программирования на Haskell.
12.1. «Yacc для Haskell»: happy
Andy Gill и Simon Marlow написали генератор синтаксических анализаторов для Haskell под названием happy. Happy для Haskell — то же, что Yacc для C.
Вы можете получить happy на главной странице Happy.
Happy раскрывает свои лучшие качества при компиляции с помощью GHC.
12.2. Создание интерфейсов Haskell для кода C: hsc2hs
Команду hsc2hs можно использовать для автоматизации некоторых этапов создания привязок Haskell к коду C. Она читает исходный файл, почти соответствующий синтаксису Haskell и содержащий специальные встроенные конструкции, а затем выводит настоящий файл Haskell, в котором эти конструкции обработаны на основе сведений из заголовочных файлов C. Дополнительные конструкции предназначены для доступа к данным C из Haskell.
Она также может вывести файл C с дополнительными функциями C, которые будут скомпонованы с программой, и заголовочный файл C, включаемый в код C, в который будет скомпилирован модуль Haskell (при компиляции через C), а также в сам файл C. Эти два файла создаются при использовании конструкции #def (см. ниже).
На самом деле hsc2hs не выводит файл Haskell напрямую. Она создаёт программу C, включающую заголовочные файлы, которая автоматически компилируется и запускается. Эта программа выводит код Haskell.
Далее под «файлом Haskell» подразумевается основной выходной файл (обычно файл .hs), под «скомпилированным файлом Haskell» — файл Haskell после того, как ghc скомпилировала его в C (то есть файл .hc), под «программой C» — программа, выводящая файл Haskell, под «файлом C» — необязательный создаваемый файл C, а под «заголовочным файлом C» — его заголовочный файл.
12.2.1. Синтаксис командной строки
hsc2hs принимает входные файлы в качестве аргументов, а также флаги, изменяющие её поведение:
-
-o FILE, --output=FILE -
Имя файла Haskell.
-
-t FILE, --template=FILE -
Файл шаблона (см. ниже).
-
-c PROG, --cc=PROG -
Используемый компилятор C (по умолчанию:
gcc) -
-l PROG, --ld=PROG -
Используемый компоновщик (по умолчанию:
gcc). -
-C FLAG, --cflag=FLAG -
Дополнительный флаг, передаваемый компилятору C.
-
-I DIR -
Передаётся компилятору C.
-
-L FLAG, --lflag=FLAG -
Дополнительный флаг, передаваемый компоновщику.
-
-i FILE, --include=FILE -
Эквивалентно размещению в исходном файле соответствующей директивы
#include. -
-D NAME[=VALUE], --define=NAME[=VALUE] -
Эквивалентно размещению в исходном файле соответствующей директивы
#define. -
--no-compile -
Остановиться после записи промежуточной программы C на диск. Имя файла промежуточной программы C образуется из имени входного файла заменой
.hscна_hsc_make.c. -
-k, --keep-files -
Продолжить обычную работу, но не удалять промежуточные файлы.
-
-x, --cross-compile -
Включить режим кросс-компиляции (см. раздел Кросс-компиляция).
-
--cross-safe -
Ограничить набор директив .hsc теми, которые поддерживаются в режиме
--cross-compile(см. раздел Кросс-компиляция). Это полезно, если ваши файлы.hscнеобходимо безопасно кросс-компилировать и вы хотите не допустить появления в них конструкций, несовместимых с кросс-компиляцией. -
-?, --help -
Вывести сводку доступных флагов и завершить работу без ошибки.
-
-V, --version -
Вывести сведения о версии и завершить работу без ошибки.
Имя входного файла должно иметь окончание .hsc (файл должен содержать только обычный исходный код Haskell; поддержка литерального Haskell пока отсутствует). По умолчанию имена выходных файлов формируются заменой суффикса .hsc:
| Файл Haskell |
| Заголовочный файл C |
| Файл C |
Программа C компилируется с помощью компилятора Haskell. Благодаря этому предоставляется путь поиска включаемых файлов для HsFFI.h, который автоматически включается в программу C.
12.2.2. Синтаксис входных данных
Вся специальная обработка запускается оператором #. Чтобы вывести литеральный #, укажите его дважды: ##. Символы # внутри строковых литералов и комментариев не обрабатываются.
После # могут следовать необязательные пробелы и табуляции, алфавитно-цифровое ключевое слово, описывающее тип обработки, и его аргументы. Аргументы записываются как выражения C, разделённые запятыми (без круглых скобок). Они продолжаются до ближайшей незакрытой ), ] или } либо до конца строки, если символ встречается вне любых () [] {} '' "" /**/ и перед ним нет обратной косой черты. Пары «обратная косая черта — перевод строки» удаляются.
Кроме того, #{stuff} эквивалентно #stuff, за исключением того, что оно имеет собственные ограничители и поэтому его не нужно размещать в конце строки или в скобках.
Значения ключевых слов:
-
#include <file.h>, #include "file.h" -
Указанный файл включается в программу C, скомпилированный файл Haskell и заголовочный файл C.
<HsFFI.h>включается автоматически. -
#define ⟨name⟩, #define ⟨name ⟨value⟩, #undef ⟨name⟩ -
Аналогично
#include. Обратите внимание, что#includesи#definesмогут дважды размещаться в одном файле, поэтому они не должны полагаться на обратное. -
#let ⟨name⟩ ⟨parameters⟩ = "⟨definition⟩" -
Определяет макрос, применяемый к исходному коду Haskell. Имена параметров разделяются запятыми и не заключаются в скобки. Такой макрос вызывается, как и другие конструкции
#, начиная с#name. Определение помещается в программу C в скобках в качестве аргументовprintf. Чтобы обратиться к параметру, закройте кавычку, укажите имя параметра и снова откройте кавычку — так литералы C будут объединены. Либо используйте директивы форматированияprintf. Значения аргументов должны быть строками, если только сам макрос не преобразует их в строки с помощью синтаксиса#parameterпрепроцессора C. -
#def ⟨C_definition⟩ -
Определение (функции, переменной, структуры или typedef) записывается в файл C, а его прототип или объявление extern — в заголовочный файл C. Встроенные функции обрабатываются корректно. Определения структур и typedef также записываются в программу C. Ключевое слово
inline,structилиtypedefдолжно идти непосредственно послеdef. -
#if ⟨condition⟩, #ifdef ⟨name⟩, #ifndef ⟨name⟩, #elif ⟨condition⟩, #else, #endif, #error ⟨message⟩, #warning ⟨message⟩ -
Директивы условной компиляции без изменений передаются программе C, файлу C и заголовочному файлу C. Их размещение в программе C позволяет пропускать соответствующие части файла Haskell.
-
#const ⟨C_expression⟩ -
Выражение должно преобразовываться в
longилиunsigned long. Будет выведено его значение (литерал или литерал с отрицательным знаком). -
#const_str ⟨C_expression⟩ -
Выражение должно преобразовываться в указатель на const char. Будет выведено его значение (строковый литерал).
-
#type ⟨C_type⟩ -
Будет выведен эквивалент числового типа C в Haskell. Это будет один из типов
{Int,Word}{8,16,32,64},Float,Double,LDouble. -
#peek ⟨struct_type⟩, ⟨field⟩ -
Будет выведена функция для чтения поля структуры C. Её тип —
Storable b => Ptr a -> IO b. Предполагается, что#peekи#pokeможно использовать для реализации операций классаStorableдля заданной структуры C (см. модульForeign.Storableв документации библиотеки). -
#poke ⟨struct_type⟩, ⟨field⟩ -
Аналогично для записи значения (poke). Тип функции —
Storable b => Ptr a -> b -> IO (). -
#ptr ⟨struct_type⟩, ⟨field⟩ -
Создаёт указатель на поле структуры. Его тип —
Ptr a -> Ptr b. -
#offset ⟨struct_type⟩, ⟨field⟩ -
Вычисляет смещение в байтах
fieldвstruct_type. Тип результата —Int. -
#size ⟨struct_type⟩ -
Вычисляет размер
struct_typeв байтах. Тип результата —Int. -
#alignment ⟨struct_type⟩ -
Вычисляет выравнивание
struct_typeв байтах. Тип результата —Int. -
#enum ⟨type⟩, ⟨constructor⟩, ⟨value⟩, ⟨value⟩, ... -
Сокращённая запись для нескольких определений, использующих
#const. Каждыйvalue— это имя целочисленной константы C, например значения перечисления. Имя преобразуется в Haskell: первая буква после подчёркивания становится заглавной, остальные буквы переводятся в нижний регистр, а подчёркивания удаляются. Можно задать другое преобразование, указавhs_name = c_valueвместоvalue; в этом случаеc_valueможет быть произвольным выражением.hs_nameбудет определён с указаннымtype. Его определение — указанноеconstructor(которое на самом деле может быть выражением или быть пустым), применённое к соответствующему целочисленному значению. Можно задать несколько определений#enumс одинаковымtype; эта конструкция сама по себе не выводит определение типа.
12.2.3. Пользовательские конструкции
#const, #type, #peek, #poke и #ptr не встроены непосредственно в hsc2hs, а определяются в шаблоне C, который включается в программу C: template-hsc.h. Можно также использовать пользовательские конструкции и шаблоны. Предполагается, что любую конструкцию # с неизвестным ключом обработает шаблон C.
В шаблоне C следует определить макрос или функцию с именем, начинающимся с hsc_, которая обрабатывает конструкцию, выводя результат раскрытия в stdout. Примеры см. в template-hsc.h.
Такие макросы можно также определять непосредственно в исходном файле. Они полезны для создания макроса, подобного #let, раскрытие которого использует другие макросы #let. Обычный #let добавляет hsc_ в начало имени макроса и оборачивает определение вызовом printf.
12.2.4. Кросс-компиляция
Обычно hsc2hs создаёт программу C, компилирует и запускает её. Такой подход не работает при кросс-компиляции: в этом случае компилятор C создаёт код для целевой машины, а не для хост-машины. Для этой ситуации предусмотрен специальный режим hsc2hs --cross-compile, который может создавать файл .hs, извлекая сведения только из результатов компиляции — в частности, выясняя, завершается ли компиляция ошибкой.
В --cross-compile поддерживается только подмножество синтаксиса .hsc. Не поддерживаются следующие конструкции:
#{const_str}#{let}#{def}- Пользовательские конструкции