Я работал над альтернативным интерфейсом компилятора для Python, где весь синтаксис анализируется с помощью макросов. Наконец, я подошел к моменту его разработки и могу начать работу над расширенным набором языка Python, в котором макросы являются неотъемлемым компонентом.
Моя проблема в том, что я не могу придумать синтаксис определения макроса Python. В ответах ниже я опубликовал несколько примеров в двух разных синтаксисах. Может ли кто-нибудь придумать лучший синтаксис? Он не должен каким-либо образом строить синтаксис, который я предложил - здесь я полностью открыт. Любые комментарии, предложения и т. д. Будут полезны, как и альтернативные синтаксисы, показывающие опубликованные мною примеры.
Замечание о структуре макросов, как показано в опубликованных мною примерах: использование MultiLine / MLMacro и Partial / PartialMacro сообщает синтаксическому анализатору, как применяется макрос. Если он многострочный, макрос будет соответствовать многострочным спискам; обычно используется для конструкций. Если он частичный, макрос будет соответствовать коду в середине списка; обычно используется для операторов.
Эти макросы позволяют вам определять полностью настраиваемый синтаксис, от новых конструкций до новых операторов. В Python в его нынешнем виде нет возможности сделать это.
Вы думали об использовании чего-то вроде BNF в определениях синтаксиса макросов?
Есть две основные проблемы с BNF. 1) Это совсем не Pythonic, и 2) это избыточно для этой проблемы и не совсем соответствует тому, как работают мои макросы. Тем не менее, спасибо за предложение.
Вы знаете о проекте Logix? livelogix.com/logix/docs.html
Да, я следил за этим, но они пошли гораздо дальше, чем я. Они сильно изменили базовый язык, допустив вложенные if и тому подобное. Они действительно сделали не питонический Python, IMO.
Макросы Pythonic - это оксюморон. В каких случаях макросы могут быть лучшим решением, чем существующие подходы?
Внедрение переключателей, согласования и т. д. Предоставление разработчикам большей свободы - никогда не плохо. Что касается макросов, которые по своей сути питоничны, я так не думаю. Я думаю, вы можете создать их так, чтобы они казались естественными, и я надеюсь, что этот вопрос поможет с этим.
Спасибо - я знал, что что-то в этом заявлении было неправильным;)
Будет ли этот проект с открытым исходным кодом?
У меня такой же вопрос. Вы обнародовали это?






Это текущий механизм определения синтаксиса с использованием стандартного класса Python.
Макрос печати:
class PrintMacro(Macro):
syntax = 'print', Var
def handle(self, stmts):
if not isinstance(stmts, list):
stmts = [stmts]
return Printnl(stmts, None)
Макрокласс if / elif / else:
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 [включительно] [шаг 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, ')']]
Мои проблемы с этим дизайном в том, что все очень многословно и нисколько не кажется питоническим. Кроме того, отсутствие возможности цитирования затрудняет создание сложных макросов.
Это синтаксис макросов, который я придумал для своего надмножества 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' в последнем примере. Я также не большой поклонник использования декораторов для различения типов макросов.
Я публикую несколько плавающих идей, чтобы посмотреть, вдохновляет ли это. Я не очень разбираюсь в 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;
Это новый синтаксис макросов, который я придумал, основанный на идеях Кента Фредрика. Он анализирует синтаксис в список так же, как анализируется код.
Макрос печати:
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)
Помимо незначительной проблемы использования декораторов для определения типа макроса, моя единственная реальная проблема с этим - способ именования групп, например в случае если. Я использую @name = ..., но от этого просто пахнет Perl. Я не хочу просто использовать name = ..., потому что это может противоречить шаблону макроса для сопоставления. Любые идеи?
разве @ не конфликтует с декораторами? Я бы избегал @ и использовал вместо него что-нибудь другое, даже C# может быть лучше
Подумав об этом несколько дней назад и не придумав ничего стоящего для публикации, я вернулся к этому сейчас и придумал некоторый синтаксис, который мне очень нравится, потому что он почти похож на python:
macro PrintMacro:
syntax:
"print", OneOrMore(Var(), name='vars')
return Printnl(vars, None)
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)
Я добавил синтаксическое дерево в качестве первого параметра, который всегда передается, поэтому вам не нужно иметь никаких именованных элементов, если вы просто хотите делать очень простые вещи в синтаксическом дереве.
Кроме того, если вам не нравятся декораторы, почему бы не добавить тип макроса как «базовый класс»? Тогда 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:» и «$», но для этого нужен некоторый синтаксис, поскольку он значительно упрощает жизнь, если вам не нужно вручную писать синтаксические деревья. Вероятно, это хорошая идея - потребовать (или просто разрешить?) Скобки для «$», чтобы вы могли вставлять более сложные части синтаксиса, если хотите. Как $(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, ')']]
Большое спасибо за ваш вклад. Я не думал об именах как об аргументах ключевых слов, но ретроспективно это кажется вполне естественным. Единственные настоящие претензии, которые у меня есть по этому поводу: 1) имена цитируются и 2) имя стоит в конце, но ни одно из них не является нарушением правил.
Принял ответ, значит, награда за вас. Спасибо еще раз за помощь!
Включение 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
)
Хм, когда я так структурирован, я вижу, что это потенциально хорошо работает. Не было бы полного BNF из-за того, как работают мои макросы, но общая структура была бы хорошей. Спасибо за вклад :)
Спасибо! Я удалил заявление об отказе от ответственности.
Если вы спрашиваете только о синтаксисе (а не о реализации) макросов в Python, то я считаю, что ответ очевиден. Синтаксис должен точно соответствовать тому, что уже есть в Python (то есть ключевому слову "def").
Реализуете ли вы это как одно из следующих, зависит от вас:
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);
функционально эквивалентны.
Я бы реализовал это с помощью препроцессора (а-ля C), который:
a в __macro_second_local_a.Без сомнения, придется позаботиться о некоторых мелочах (например, о кортежах или множественных точках возврата), но 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
Спасибо за ответ. Проблема с этим стилем в том, что он не определяет синтаксис. То есть мой механизм макросов (который уже выполнен - примеры на основе классов работают) обрабатывает макросы чисто синтаксически, а не как функции. Тем не менее, мне нравится идея def для макросов.
You might consider looking at how Boo (a .NET-based language with a syntax largely inspired by Python) implements macros, as described at http://boo.codehaus.org/Syntactic+Macros.
Спасибо за ответ. На самом деле я пытаюсь полностью избежать макросистемы Бу из-за ее громоздкости, ха-ха. Тем не менее, то, что он может работать на питонском языке, определенно был источником вдохновения.
Вам следует взглянуть на MetaPython, чтобы увидеть, выполняет ли он то, что вы ищете.
MetaPython - это круто, но он использует ловушку импорта, хотя я думаю, что это может повлиять на производительность.
Я думал, что макросы не требуются, если вы также можете выполнять код при компиляции. Извините, если я невежественен.