Питоновский синтаксис макросов
Я работал над альтернативным компилятором для Python, где все синтаксисы анализируются с помощью макросов. Я, наконец, понял, что я могу начать работу над надстройкой языка Python, где макросы являются неотъемлемым компонентом.
Моя проблема в том, что я не могу придумать синтаксис определения макроса pythonic. Я привел несколько примеров в двух разных синтаксисах в ответах ниже. Может ли кто-нибудь придумать лучший синтаксис? Мне не нужно строить какой-либо синтаксис, который я предложил в любом случае - я здесь полностью открыт. Любые комментарии, предложения и т.д. Будут полезны, так же как и альтернативные синтаксисы, показывающие приведенные мной примеры.
Заметка о структуре макросов, как показано в приведенных выше примерах: использование MultiLine/MLMacro и Partial/PartialMacro сообщает парсеру, как применяется макрос. Если он многострочный, макрос будет соответствовать нескольким спискам строк; обычно используется для конструкций. Если он частично, макрос будет соответствовать коду в середине списка; обычно используется для операторов.
Ответы
Ответ 1
Подумав об этом несколько дней назад и придумав ничто, стоящее на публикации, я вернулся к нему сейчас и придумал какой-то синтаксис, который мне больше нравится, потому что он почти похож на python:
macro PrintMacro:
syntax:
"print", OneOrMore(Var(), name='vars')
return Printnl(vars, None)
- Сделать все макросы "ключевые слова" похожими на создание объектов python (
Var()
вместо простого Var
)
-
Передайте имя элемента как "параметр ключевого слова" для элементов, для которых требуется имя.
Все же должно быть легко найти все имена в синтаксическом анализаторе, так как это синтаксическое определение в любом случае должно быть каким-то образом интерпретировано для заполнения синтаксической переменной макроклассов.
необходимо преобразовать, чтобы заполнить синтаксическую переменную результирующего класса макросов.
Внутреннее синтаксическое представление также может выглядеть одинаково:
class PrintMacro(Macro):
syntax = 'print', OneOrMore(Var(), name='vars')
...
Внутренние классы синтаксиса, такие как OneOrMore
, будут следовать этому шаблону, чтобы разрешить подпункты и необязательное имя:
class MacroSyntaxElement(object):
def __init__(self, *p, name=None):
self.subelements = p
self.name = name
Когда макрос совпадает, вы просто собираете все элементы, которые имеют имя, и передают их в качестве параметров ключевого слова для функции обработчика:
class Macro():
...
def parse(self, ...):
syntaxtree = []
nameditems = {}
# parse, however this is done
# store all elements that have a name as
# nameditems[name] = parsed_element
self.handle(syntaxtree, **nameditems)
Затем функция обработчика будет определена следующим образом:
class PrintMacro(Macro):
...
def handle(self, syntaxtree, vars):
return Printnl(vars, None)
Я добавил syntaxtree в качестве первого параметра, который всегда передается, поэтому вам не нужно иметь какие-либо именованные элементы, если вы просто хотите сделать очень простой материал в дереве синтаксиса.
Кроме того, если вам не нравятся декораторы, почему бы не добавить тип макроса, как "базовый класс"? IfMacro
будет выглядеть следующим образом:
macro IfMacro(MultiLine):
syntax:
Group("if", Var(), ":", Var(), name='if_')
ZeroOrMore("elif", Var(), ":", Var(), name='elifs')
Optional("else", Var(name='elseBody'))
return If(
[(cond, Stmt(body)) for keyword, cond, colon, body in [if_] + elifs],
None if elseBody is None else Stmt(elseBody)
)
И во внутреннем представлении:
class IfMacro(MultiLineMacro):
syntax = (
Group("if", Var(), ":", Var(), name='if_'),
ZeroOrMore("elif", Var(), ":", Var(), name='elifs'),
Optional("else", Var(name='elseBody'))
)
def handle(self, syntaxtree, if_=None, elifs=None, elseBody=None):
# Default parameters in case there is no such named item.
# In this case this can only happen for 'elseBody'.
return If(
[(cond, Stmt(body)) for keyword, cond, body in [if_] + elifs],
None if elseNody is None else Stmt(elseBody)
)
Я думаю, что это даст довольно гибкую систему. Основные преимущества:
- Легко учится (выглядит как стандартный питон)
- Легко разобрать (анализирует, как стандартный питон)
- Необязательные элементы можно легко обрабатывать, поскольку в обработчике вы можете иметь параметр по умолчанию
None
Гибкое использование названных элементов:
- Вам не нужно указывать какие-либо элементы, если вы этого не хотите, потому что дерево синтаксиса всегда передается.
- Вы можете назвать любые подвыражения в большом определении макроса, поэтому легко выбрать конкретный материал, который вам интересен.
Легко расширяемый, если вы хотите добавить дополнительные функции в макроконструкции. Например Several("abc", min=3, max=5, name="a")
. Я думаю, что это также можно использовать для добавления значений по умолчанию для дополнительных элементов, таких как Optional("step", Var(), name="step", default=1)
.
Я не уверен в синтаксисе quote/unquote с "quote:" и "$", но для этого необходим некоторый синтаксис, поскольку он значительно облегчает жизнь, если вам не нужно вручную писать деревья синтаксиса. Вероятно, это хорошая идея, чтобы потребовать (или просто разрешить?) Скобки для "$", чтобы вы могли вставлять более сложные части синтаксиса, если хотите. Как $(Stmt(a, b, c))
.
ToMacro будет выглядеть примерно так:
# macro definition
macro ToMacro(Partial):
syntax:
Var(name='start'), "to", Var(name='end'), Optional("inclusive", name='inc'), Optional("step", Var(name='step'))
if step == None:
step = quote(1)
if inclusive:
return quote:
xrange($(start), $(end)+1, $(step))
else:
return quote:
xrange($(start), $(end), $(step))
# resulting macro class
class ToMacro(PartialMacro):
syntax = Var(name='start'), "to", Var(name='end'), Optional("inclusive", name='inc'), Optional("step", Var(name='step'))
def handle(syntaxtree, start=None, end=None, inc=None, step=None):
if step is None:
step = Number(1)
if inclusive:
return ['xrange', ['(', start, [end, '+', Number(1)], step, ')']]
return ['xrange', ['(', start, end, step, ')']]
Ответ 2
Вы можете подумать о том, как Boo (язык на основе .NET с синтаксисом, в значительной степени вдохновленный Python) реализует макросы, как описано в http://boo.codehaus.org/Syntactic+Macros.
Ответ 3
Вы должны взглянуть на MetaPython, чтобы убедиться, что он выполняет то, что вы ищете.
Ответ 4
Включение BNF
class IfMacro(Macro):
syntax: "if" expression ":" suite ("elif" expression ":" suite )* ["else" ":" suite]
def handle(self, if_, elifs, elseBody):
return If(
[(expression, Stmt(suite)) for expression, suite in [if_] + elifs],
elseBody != None and Stmt(elseBody) or None
)
Ответ 5
Я публикую немного плавающих идей, чтобы понять, вдохновляет ли он.
Я не знаю много python, и я не использую настоящий синтаксис python, но ничего не бьет: p
macro PrintMacro:
syntax:
print $a
rules:
a: list(String), as vars
handle:
# do something with 'vars'
macro IfMacro:
syntax:
if $a :
$b
$c
rules:
a: 1 boolean as if_cond
b: 1 coderef as if_code
c: optional macro(ElseIf) as else_if_block
if( if_cond ):
if_code();
elsif( defined else_if_block ):
else_if_block();
Больше идей:
Внедрение стиля цитат Perl, но в Python! (его очень плохая реализация и примечание: пробелы значительны в правиле)
macro stringQuote:
syntax:
q$open$content$close
rules:
open: anyOf('[{(/_') or anyRange('a','z') or anyRange('0','9');
content: string
close: anyOf(']})/_') or anyRange('a','z') or anyRange('0','9');
detect:
return 1 if open == '[' and close == ']'
return 1 if open == '{' and close == '}'
return 1 if open == '(' and close == ')'
return 1 if open == close
return 0
handle:
return content;
Ответ 6
Это новый синтаксис макросов, который я придумал на основе идей Кента Фредрика. Он анализирует синтаксис в списке так же, как разбор кода.
Печать макроса:
macro PrintMacro:
syntax:
print $stmts
if not isinstance(stmts, list):
stmts = [stmts]
return Printnl(stmts, None)
Если макрос:
@MultiLine
macro IfMacro:
syntax:
@if_ = if $cond: $body
@elifs = ZeroOrMore(elif $cond: $body)
Optional(else: $elseBody)
return If(
[(cond, Stmt(body)) for cond, body in [if_] + elifs],
elseBody != None and Stmt(elseBody) or None
)
От X до Y [включительно] [шаг Z] макрос:
@Partial
macro ToMacro:
syntax:
$start to $end Optional(inclusive) Optional(step $step)
if step == None:
step = quote 1
if inclusive:
return quote:
xrange($start, $end+1, $step)
else:
return quote:
xrange($start, $end, $step)
Помимо незначительной проблемы использования декораторов для идентификации типа макроса, моя единственная реальная проблема заключается в том, как вы можете назвать группы, например. в случае if.
Я использую @name =..., но это просто пахнет Perl. Я не хочу просто использовать name =... потому что это может противоречить шаблону макроса. Любые идеи?
Ответ 7
Если вы только спрашиваете о синтаксисе (а не реализации) макросов в Python, то я считаю, что ответ очевиден. Синтаксис должен точно соответствовать тому, что уже существует Python (т.е. Ключевому слову < <20 > ).
Выполняете ли вы это как одно из следующих:
def macro largest(lst):
defmac largest(lst):
macro largest(lst):
но я считаю, что он должен быть точно таким же, как нормальная функция по отношению к остальным, так что:
def twice_second(a,b):
glob_i = glob_i + 1
return b * 2
x = twice_second (1,7);
и
defmac twice_second(a,b):
glob_i = glob_i + 1
return b * 2
x = twice_second (1,7);
являются функционально эквивалентными.
То, как я буду реализовывать это, - это предварительный процессор (a la C), который:
- замените все defmac на defs во входном файле.
- передать его через Python, чтобы проверить синтаксис (скрытый бит, это).
- верните defmac.
- найти все применения каждого макроса и "встроить" их, используя ваши собственные зарезервированные переменные, такие как преобразование локального var
a
с помощью __macro_second_local_a
.
- Возвращаемое значение также должно быть специальной переменной (macro_second_retval).
- глобальные переменные сохраняют свои настоящие имена.
- может быть задано имя _macro_second_param_XXX.
- После того, как будет сделана вся инкрустация, полностью удалите функции defmac.
- передать результирующий файл через Python.
Нет сомнений, что у него появятся некоторые ниггеры (например, кортежи или множественные точки возврата), но Python достаточно прочен, чтобы справиться с этим, на мой взгляд.
Итак:
x = twice_second (1,7);
становится:
# These lines are the input params.
__macro_second_param_a = 1
__macro_second_param_b = 7
# These lines are the inlined macro.
glob_i = glob_i + 1
__macro_second_retval = __macro_second_param_b * 2
# Modified call to macro.
x = __macro_second_retval
Ответ 8
Это текущий механизм определения синтаксиса с использованием стандартного класса Python.
Печать макроса:
class PrintMacro(Macro):
syntax = 'print', Var
def handle(self, stmts):
if not isinstance(stmts, list):
stmts = [stmts]
return Printnl(stmts, None)
Если класс /elif/else macro:
class IfMacro(MLMacro):
syntax = (
('if', Var, Var),
ZeroOrMore('elif', Var, Var),
Optional('else', Var)
)
def handle(self, if_, elifs, elseBody):
return If(
[(cond, Stmt(body)) for cond, body in [if_] + elifs],
elseBody != None and Stmt(elseBody) or None
)
От X до Y [включительно] [step Z] класс макроса:
class ToMacro(PartialMacro):
syntax = Var, 'to', Var, Optional('inclusive'), Optional('step', Var)
def handle(self, start, end, inclusive, step):
if inclusive:
end = ['(', end, '+', Number(1), ')']
if step == None: step = Number(1)
return ['xrange', ['(', start, end, step, ')']]
Мои проблемы с этим дизайном в том, что вещи очень многословны и не кажутся pythonic в меньшей степени. Кроме того, отсутствие возможности котировки затрудняет сложные макросы.
Ответ 9
Это синтаксис макроса, который я использовал для моего верхнего набора Python.
Печать макроса:
macro PrintMacro:
syntax:
stmts = 'print', Var
if not isinstance(stmts, list):
stmts = [stmts]
return Printnl(stmts, None)
Если макрос:
@MultiLine
macro IfMacro:
syntax:
if_ = 'if', Var, Var
elifs = ZeroOrMore('elif', Var, Var)
else_ = Optional('else', Var)
return If(
[(cond, Stmt(body)) for cond, body in [if_] + elifs],
elseBody != None and Stmt(elseBody) or None
)
От X до Y [включительно] [шаг Z] макрос:
@Partial
macro ToMacro:
syntax:
start = Var
'to'
end = Var
inclusive = Optional('inclusive')
step = Optional('step', Var)
if step == None:
step = quote 1
if inclusive:
return quote:
xrange($start, $end+1, $step)
else:
return quote:
xrange($start, $end, $step)
Моя основная проблема заключается в том, что блок синтаксиса неясен, особенно строка "to" в последнем примере. Я также не большой поклонник использования декораторов для дифференциации типов макросов.