Против аналогового секвенсора не попрешь, part 1: QuasiQuotation
На первоначальных этапах нам предстоит иметь дело с генерацией и преобразованиями С-кода, поэтому стоит поговорить о механизме QuasiQuotation.
Пусть у вас есть два языка программирования L и M (это может быть один и тот же язык). Вы хотите генерировать или преобразовывать программы на языке M функциями языка L. Пусть SM -- тип, описывающий синтаксис языка M в языке L. На практике тип SM это обычно запутанный набор взаимно-рекурсивных типов. Чтобы что-то с ним сделать вам придется изучить как он устроен и писать много довольной уродливых выражений. Гораздо удобнее иметь механизм, позволяющий вам писать куски кода на M, которые автоматически преобразовываются в выражения типа SM. Более того, вы можете иметь ``переменные'' внутри фрагментов M-кода, с соответствующей операцией подстановки. Это и есть механизм QuasiQuotation. Когда возможности подстановки нет, это называется просто Quotation.
Поясним на примере.
Пусть L=Haskell, M=C. Пользуемся пакетом language-c-quote. Очень, надо сказать, неудачное там определение синтаксиса C. Но для примера это даже хорошо.
Напишем функцию, которая для любых двух C-типов дает определение их прямого произведения, т.е. структуры с двумя полями заданных типов. Без QQ это выглядит так:
droot :: Decl
droot = DeclRoot noLoc
ident :: String -> Id
ident i = Id i noLoc
ty :: TypeSpec -> Type
ty t = Type (DeclSpec [] [] t noLoc) droot noLoc
struct :: Id -> [FieldGroup] -> TypeSpec
struct i [] = Tstruct (Just i) Nothing [] noLoc
struct i (x : xs) = Tstruct (Just i) (Just (x : xs)) [] noLoc
fieldG :: TypeSpec -> Id -> FieldGroup
fieldG t x = FieldGroup (DeclSpec [] [] t noLoc) [Field (Just x) (Just droot) Nothing noLoc] noLoc
product :: String -> TypeSpec -> TypeSpec -> Type
product s t0 t1 =
let
name = ident s
proj0 = ident (s ++ "_proj0")
proj1 = ident (s ++ "_proj1")
in
ty . struct name $ [fieldG t0 proj0, fieldG t1 proj1]
Ад каннибалов, не правда ли? C QQ это выглядит так:
product' :: String -> TypeSpec -> TypeSpec -> Type
product' s t0 t1 =
let
name = s
proj0 = s ++ "_proj0"
proj1 = s ++ "_proj1"
ty0 = ty t0
ty1 = ty t1
in
[cty| struct $id:name {$ty:ty0 $id:proj0 ; $ty:ty1 $id:proj1 ;} |]
В QQ-варианте из вспомогательных функций нам понадобилась только ty, все остальное делают идиоматические скобочки [cty| .. |]
Если вызвать product "product" int double, то обе функции дают нужный результат:
struct product {
int product_proj0;
double product_proj1;
}
Такие дела. На фото я.
