Pythonic макрос синтаксис - PullRequest
20 голосов
/ 18 января 2009

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

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

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

Ответы [ 9 ]

10 голосов
/ 27 января 2009

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

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

3 голосов
/ 30 января 2009

Вы могли бы рассмотреть, как Boo (язык на основе .NET с синтаксисом, в значительной степени основанным на Python) реализует макросы, как описано в http://boo.codehaus.org/Syntactic+Macros.

2 голосов
/ 27 января 2009

Включая БНФ

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
            )
1 голос
/ 30 января 2009

Если вы спрашиваете только о синтаксисе (а не о реализации) макросов в 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 на def во входном файле.
  • передать его через 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
1 голос
/ 20 января 2009

Это новый макрос-синтаксис, который я придумал, основываясь на идеях Кента Фредрика. Он анализирует синтаксис в список так же, как анализируется код.

Макрос печати:

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. Я не хочу просто использовать имя = ..., потому что это может конфликтовать с шаблоном макроса, чтобы соответствовать. Есть идеи?

1 голос
/ 20 января 2009

Я публикую немного плавающих идей, чтобы увидеть, вдохновляет ли это. Я не знаю много 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;
0 голосов
/ 20 января 2009

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

0 голосов
/ 20 января 2009

Это текущий механизм определения синтаксиса с использованием стандартного класса Python.

Макрос печати:

class PrintMacro(Macro):
  syntax = 'print', Var
  def handle(self, stmts):
    if not isinstance(stmts, list):
      stmts = [stmts]
    return Printnl(stmts, None)

Если / 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, ')']]

Мои проблемы с этим дизайном состоят в том, что все очень многословно и ни в коей мере не кажется питоническим. Кроме того, отсутствие возможности цитирования усложняет сложные макросы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...