Синтаксис макросов Pythonic

Я работал над альтернативным интерфейсом компилятора для Python, где весь синтаксис анализируется с помощью макросов. Наконец, я подошел к моменту его разработки и могу начать работу над расширенным набором языка Python, в котором макросы являются неотъемлемым компонентом.

Моя проблема в том, что я не могу придумать синтаксис определения макроса Python. В ответах ниже я опубликовал несколько примеров в двух разных синтаксисах. Может ли кто-нибудь придумать лучший синтаксис? Он не должен каким-либо образом строить синтаксис, который я предложил - здесь я полностью открыт. Любые комментарии, предложения и т. д. Будут полезны, как и альтернативные синтаксисы, показывающие опубликованные мною примеры.

Замечание о структуре макросов, как показано в опубликованных мною примерах: использование MultiLine / MLMacro и Partial / PartialMacro сообщает синтаксическому анализатору, как применяется макрос. Если он многострочный, макрос будет соответствовать многострочным спискам; обычно используется для конструкций. Если он частичный, макрос будет соответствовать коду в середине списка; обычно используется для операторов.

Я думал, что макросы не требуются, если вы также можете выполнять код при компиляции. Извините, если я невежественен.

Cheery 18.01.2009 19:37

Эти макросы позволяют вам определять полностью настраиваемый синтаксис, от новых конструкций до новых операторов. В Python в его нынешнем виде нет возможности сделать это.

Serafina Brocious 19.01.2009 02:19

Вы думали об использовании чего-то вроде BNF в определениях синтаксиса макросов?

Ryan 19.01.2009 05:26

Есть две основные проблемы с BNF. 1) Это совсем не Pythonic, и 2) это избыточно для этой проблемы и не совсем соответствует тому, как работают мои макросы. Тем не менее, спасибо за предложение.

Serafina Brocious 20.01.2009 11:09

Вы знаете о проекте Logix? livelogix.com/logix/docs.html

codeape 20.01.2009 13:23

Да, я следил за этим, но они пошли гораздо дальше, чем я. Они сильно изменили базовый язык, допустив вложенные if и тому подобное. Они действительно сделали не питонический Python, IMO.

Serafina Brocious 20.01.2009 13:26

Макросы Pythonic - это оксюморон. В каких случаях макросы могут быть лучшим решением, чем существующие подходы?

jfs 27.01.2009 14:44

Внедрение переключателей, согласования и т. д. Предоставление разработчикам большей свободы - никогда не плохо. Что касается макросов, которые по своей сути питоничны, я так не думаю. Я думаю, вы можете создать их так, чтобы они казались естественными, и я надеюсь, что этот вопрос поможет с этим.

Serafina Brocious 27.01.2009 14:48

Спасибо - я знал, что что-то в этом заявлении было неправильным;)

Serafina Brocious 27.01.2009 16:05

Будет ли этот проект с открытым исходным кодом?

Casebash 16.05.2010 12:08

У меня такой же вопрос. Вы обнародовали это?

Erik Kaplun 08.12.2011 15:50
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
20
11
3 389
9
Перейти к ответу Данный вопрос помечен как решенный

Ответы 9

Это текущий механизм определения синтаксиса с использованием стандартного класса 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# может быть лучше

Robert Gould 02.02.2009 13:39
Ответ принят как подходящий

Подумав об этом несколько дней назад и не придумав ничего стоящего для публикации, я вернулся к этому сейчас и придумал некоторый синтаксис, который мне очень нравится, потому что он почти похож на 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)

Я добавил синтаксическое дерево в качестве первого параметра, который всегда передается, поэтому вам не нужно иметь никаких именованных элементов, если вы просто хотите делать очень простые вещи в синтаксическом дереве.

Кроме того, если вам не нравятся декораторы, почему бы не добавить тип макроса как «базовый класс»? Тогда 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) имя стоит в конце, но ни одно из них не является нарушением правил.

Serafina Brocious 27.01.2009 16:56

Принял ответ, значит, награда за вас. Спасибо еще раз за помощь!

Serafina Brocious 03.02.2009 11:29

Включение 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 из-за того, как работают мои макросы, но общая структура была бы хорошей. Спасибо за вклад :)

Serafina Brocious 28.01.2009 02:02

Спасибо! Я удалил заявление об отказе от ответственности.

Ryan 28.01.2009 04:32

Если вы спрашиваете только о синтаксисе (а не о реализации) макросов в 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), который:

  • замените все defmac на defs во входном файле.
  • передать его через Python, чтобы проверить синтаксис (хитрый бит, это).
  • вставьте defmac обратно.
  • найти все варианты использования каждого макроса и "встроить" их, используя ваши собственные зарезервированные переменные, например, преобразовать локальную переменную a в __macro_second_local_a.
  • возвращаемое значение также должно быть специальной переменной (macro_second_retval).
  • глобальные переменные сохранят свои настоящие имена.
  • параметру можно присвоить имена _macro_second_param_XXX.
  • как только все встраивание будет выполнено, полностью удалите defmac 'functions'.
  • передать полученный файл через 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

Спасибо за ответ. Проблема с этим стилем в том, что он не определяет синтаксис. То есть мой механизм макросов (который уже выполнен - ​​примеры на основе классов работают) обрабатывает макросы чисто синтаксически, а не как функции. Тем не менее, мне нравится идея def для макросов.

Serafina Brocious 30.01.2009 08:22

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.

Спасибо за ответ. На самом деле я пытаюсь полностью избежать макросистемы Бу из-за ее громоздкости, ха-ха. Тем не менее, то, что он может работать на питонском языке, определенно был источником вдохновения.

Serafina Brocious 30.01.2009 08:19

Вам следует взглянуть на MetaPython, чтобы увидеть, выполняет ли он то, что вы ищете.

MetaPython - это круто, но он использует ловушку импорта, хотя я думаю, что это может повлиять на производительность.

Casebash 16.05.2010 12:35

Другие вопросы по теме