Подтвердить что ты не робот

Питоновский синтаксис макросов

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

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

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

4b9b3361

Ответ 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" в последнем примере. Я также не большой поклонник использования декораторов для дифференциации типов макросов.