Генерация кода Python с помощью макросов Hy
Я пытаюсь сгенерировать некоторый код Python из Hy. Как это сделать лучше?
Я пробовал несколько подходов. Один с макросом:
(defmacro make-vars [data]
(setv res '())
(for [element data]
(setv varname (HySymbol (+ "var" (str element))))
(setv res (cons `(setv ~varname 0) res)))
`(do ~@res))
Затем, после захвата макроразложения, я печатаю разбор кода на python.
Тем не менее, кажется, что с макросами я не могу передать переменные, так что:
(setv vnames [1 2 3])
(make-vars vnames)
определяет varv
, varn
, vara
и так далее, вместо var1
, var2
, var3
, Кажется, что правильный вызов может быть сделан с:
(macroexpand `(make-vars ~vnames))
но это кажется слишком сложным.
Другой проблемой, с которой я столкнулся, является необходимость HySymbol
, что стало большим сюрпризом. Но я действительно пострадал от этого, когда попробовал второй подход, где я создал функцию, которая возвращает кавычки:
(defn make-faction-detaches [faction metadata unit-types]
(let [meta-base (get metadata "Base")
meta-pattern (get metadata "Sections")
class-cand []
class-def '()
class-grouping (dict)]
(for [(, sec-name sec-flag) (.iteritems meta-pattern)]
;; if section flag is set but no unit types with the section are found, break and return nothing
(print "checking" sec-name)
(if-not (or (not sec-flag) (any (genexpr (in sec-name (. ut roles)) [ut unit-types])))
(break)
;; save unit types for section
(do
(print "match for section" sec-name)
(setv sec-grouping (list-comp ut [ut unit-types]
(in sec-name (. ut roles))))
(print (len sec-grouping) "types found for section" sec-name)
(when sec-grouping
(assoc class-grouping sec-name sec-grouping))))
;; in case we finished the cycle
(else
(do
(def
class-name (.format "{}_{}" (. meta-base __name__) (fix-faction-string faction))
army-id (.format "{}_{}" (. meta-base army_id) (fix-faction-string faction))
army-name (.format "{} ({})" (fix-faction-name faction) (. meta-base army_name)))
(print "Class name is" class-name)
(print "Army id is" army-id)
(print "Army name is" army-name)
(setv class-cand [(HySymbol class-name)])
(setv class-def [`(defclass ~(HySymbol class-name) [~(HySymbol (. meta-base __name__))]
[army_name ~(HyString army-name)
faction ~(HyString faction)
army_id ~(HyString army-id)]
(defn --init-- [self]
(.--init-- (super) ~(HyDict (interleave (genexpr (HyString k) [k class-grouping])
(cycle [(HyInteger 1)]))))
~@(map (fn [key]
`(.add-classes (. self ~(HySymbol key))
~(HyList (genexpr (HySymbol (. ut __name__))
[ut (get class-grouping key)]))))
class-grouping)))]))))
(, class-def class-cand)))
Эта функция принимает метаданные, которые выглядят так в Python:
metadata = [
{'Base': DetachPatrol,
'Sections': {'hq': True, 'elite': False,
'troops': True, 'fast': False,
'heavy': False, 'fliers': False,
'transports': False}}]
И принимает список классов, которые имеют форму:
class SomeSection(object):
roles = ['hq']
Это потребовало широкого использования внутренних классов hy, и я не смог правильно представить True и False, прибегая к HyInteger(1)
а также HyInteger(0)
вместо.
Чтобы получить код Python из этой функции, я запускаю его результат через disassemble
,
Чтобы подвести итог:
- Что было бы лучшим способом для генерации кода Python из Hy?
- Что такое внутреннее представление для True и False?
- Можно ли вызвать функцию, которая обрабатывает свои параметры и возвращает цитируемую форму Hy из макроса и как?
1 ответ
В Hy вам обычно не нужно генерировать код Python, поскольку Hy гораздо лучше генерирует код Hy, и он так же исполняем. Это делается все время в макросах Hy.
В необычном случае, когда вам нужно сгенерировать настоящий Python, а не только Hy, лучше всего использовать строки, так же, как вы это делаете в Python. Hy компилируется в AST Python, а не в сам Python. Дизассемблер действительно только для целей отладки. Он не всегда генерирует правильный Python:
=> (setv +!@$ 42)
=> +!@$
42
=> (disassemble '(setv +!@$ 42) True)
'+!@$ = 42'
=> (exec (disassemble '(setv +!@$ 42) True))
Traceback (most recent call last):
File "/home/gilch/repos/hy/hy/importer.py", line 193, in hy_eval
return eval(ast_compile(expr, "<eval>", "eval"), namespace)
File "<eval>", line 1, in <module>
File "<string>", line 1
+!@$ = 42
^
SyntaxError: invalid syntax
=> (exec "spam = 42; print(spam)")
42
Имя переменной +!@$
так же законно, как spam
находится в AST, но Python exec
душит его, потому что он не является допустимым идентификатором Python.
Если вы понимаете и согласны с этим ограничением, вы можете использовать disassemble
, но без макросов. Обычные функции времени выполнения могут принимать и генерировать (как вы продемонстрировали) выражения Hy. Макросы на самом деле просто такие функции, которые выполняются во время компиляции. В Hy нет ничего необычного в том, что макрос передает часть своей работы обычной функции, которая принимает выражение Hy в качестве одного из аргументов и возвращает выражение Hy.
Самый простой способ создать выражение Hy в качестве данных - это заключить его в кавычки '
, Синтаксис back tick для интерполяции значений также действителен даже вне тела макроса. Вы также можете использовать это в обычных функциях времени выполнения. Но поймите, вы должны вставить кавычки в интерполяцию, если хотите разобрать ее, потому что это то, что макрос получит в качестве аргументов - сам код, а не его оцененные значения. Вот почему вы используете HySymbol
и друзья.
=> (setv class-name 'Foo) ; N.B. 'Foo is quoted
=> (print (disassemble `(defclass ~class-name) True))
class Foo:
pass
Вы можете спросить REPL, какие типы он использует для цитируемых форм.
=> (type 1)
<class 'int'>
=> (type '1)
<class 'hy.models.HyInteger'>
=> (type "foo!")
<class 'str'>
=> (type '"foo!")
<class 'hy.models.HyString'>
=> (type True)
<class 'bool'>
=> (type 'True)
<class 'hy.models.HySymbol'>
Как вы видете, True
это просто символ внутри. Обратите внимание, что я смог сгенерировать HySymbol
просто '
без использования HySymbol
вызов. Если ваш файл метаданных был написан на языке Hy и в первую очередь создан с использованием цитируемых форм Hy, вам не нужно будет конвертировать их. Но нет никаких причин, по которым это нужно делать в последнюю минуту в форме обратного удара. Это может быть сделано заранее с помощью вспомогательной функции, если вы этого хотите.
Следовать за
Можно ли вызвать функцию, которая обрабатывает свои параметры и возвращает цитируемую форму Hy из макроса и как?
Первоначально я хотел сказать, что макрос - это не тот инструмент, который вы пытаетесь сделать. Но чтобы уточнить, вы можете вызвать макрос во время выполнения, используя macroexpand
, как вы уже продемонстрировали. Можно, конечно, поставить macroexpand
вызов внутри другой функции, но macroexpand
должен иметь форму в кавычках в качестве аргумента.
Также, тот же вопрос по поводу динамически генерируемых словарей. Конструкция, которую я использовал, выглядит ужасно.
Словарная часть может быть упрощена до чего-то более похожего
{~@(interleave (map HyString class-grouping) (repeat '1))}
В то время как питон dict
поддерживается хэш-таблицей, Hy's HyDict
Модель действительно просто список. Это связано с тем, что он представляет не саму хеш-таблицу, а код, который создает dict. Вот почему вы можете склеить его, как список.
Однако, если возможно, не могли бы вы добавить пример правильной передачи динамически генерируемых строк в окончательное выражение в кавычках? Насколько я понимаю, это можно сделать, добавив еще одно назначение (что добавило бы цитату), но есть ли более элегантный способ?
Модели Hy считаются частью общедоступного API, они просто не используются вне макросов. Это нормально использовать их, когда вам нужно. Другие Лиспы не проводят такого же различия между объектами модели кода и производимыми ими данными. Hy делает это таким образом для лучшего взаимодействия с Python. Можно утверждать, что ~
синтаксис должен выполнять это преобразование автоматически для определенных типов данных, но в настоящее время это не так.
[Обновление: в текущей основной ветке компилятор Hy автоматически оборачивает совместимые значения в модели Hy, когда это возможно, поэтому обычно вам больше не нужно делать это самостоятельно.]
HySymbol
подходит для динамического генерирования символов из строк, как вы пытаетесь сделать. Это не единственный способ, но это то, что вы хотите в этом случае. Другой способ, gensym
, чаще используется в макросах, но они не могут быть такими красивыми. Ты можешь позвонить gensym
со строкой, чтобы дать ему более значимое имя для целей отладки, но он по-прежнему имеет числовой суффикс, чтобы сделать его уникальным. Вы могли бы, конечно, назначить HySymbol
более короткий псевдоним или делегируйте эту часть вспомогательной функции.
Вы также можете конвертировать его заранее, например, фрагмент
(def class-name (.format "{}_{}" (. meta-base __name__) ...
Может быть вместо
(def class-name (HySymbol (.format "{}_{}" (. meta-base __name__) ...
Тогда вам не нужно делать это дважды.
(setv class-cand [class-name])
(setv class-def [`(defclass ~class-name ...
Это, вероятно, облегчает чтение шаблона.
Обновить
Hy master теперь объединяет символы с действительными идентификаторами Python при компиляции, поэтому hy2py
Инструмент и разборка astor должны более надежно генерировать действительный код Python, даже если в символах есть специальные символы.