Sintaxis de la macro pythonica

He estado trabajando en un front-end de comstackdor alternativo para Python donde toda la syntax se analiza a través de macros. Finalmente he llegado al punto con su desarrollo de que puedo comenzar a trabajar en un superconjunto del lenguaje Python donde las macros son un componente integral.

Mi problema es que no se me ocurre una syntax de definición de macro pythonic. He publicado varios ejemplos en dos syntax diferentes en las respuestas a continuación. ¿Puede alguien llegar a una mejor syntax? No tiene que basarse en la syntax que he propuesto de ninguna manera, estoy completamente abierto aquí. Cualquier comentario, sugerencia, etc. sería útil, al igual que las syntax alternativas que muestran los ejemplos que he publicado.

Una nota sobre la estructura de la macro, como se ve en los ejemplos que he publicado: el uso de MultiLine / MLMacro y Partial / PartialMacro le dice al analizador cómo se aplica la macro. Si es multilínea, la macro coincidirá con varias listas de líneas; Generalmente se utiliza para construcciones. Si es parcial, la macro coincidirá con el código en medio de una lista; Generalmente se utiliza para los operadores.

Después de pensarlo un momento hace unos días, y no encontré nada que valiera la pena publicar, volví a eso ahora y encontré una syntax que me gusta, porque casi se parece a python:

macro PrintMacro: syntax: "print", OneOrMore(Var(), name='vars') return Printnl(vars, None) 
  • Haga que todas las “palabras clave” de la macro parezcan crear objetos de Python ( Var() lugar de Var simple)
  • Pase el nombre de los elementos como un “parámetro de palabra clave” a los elementos para los que queremos un nombre. Aún debería ser fácil encontrar todos los nombres en el analizador, ya que esta definición de syntax debe interpretarse de alguna manera para completar la variable de syntax de las clases de macro.

    debe convertirse para completar la variable de syntax de la clase de macro resultante.

La representación de syntax interna también podría verse igual:

 class PrintMacro(Macro): syntax = 'print', OneOrMore(Var(), name='vars') ... 

Las clases de syntax internas como OneOrMore seguirían este patrón para permitir subelementos y un nombre opcional:

 class MacroSyntaxElement(object): def __init__(self, *p, name=None): self.subelements = p self.name = name 

Cuando la macro coincide, simplemente recostack todos los elementos que tienen un nombre y los pasa como parámetros de palabras clave a la función de controlador:

 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) 

La función del controlador se definiría así:

 class PrintMacro(Macro): ... def handle(self, syntaxtree, vars): return Printnl(vars, None) 

Agregué syntaxtree como un primer parámetro que siempre se pasa, por lo que no necesitaría tener ningún elemento con nombre si solo quiere hacer cosas muy básicas en el árbol de syntax.

Además, si no te gustan los decoradores, ¿por qué no agregar el tipo de macro como una “clase base”? IfMacro entonces se vería así:

 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) ) 

Y en la representación interna:

 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) ) 

Creo que esto daría un sistema bastante flexible. Ventajas principales:

  • Fácil de aprender (parece un python estándar)
  • Fácil de analizar (analiza como Python estándar)
  • Los elementos opcionales se pueden manejar fácilmente, ya que puede tener un parámetro predeterminado None en el controlador
  • Uso flexible de los elementos nombrados:
    • No es necesario que nombre ningún elemento si no lo desea, porque el árbol de syntax siempre se pasa.
    • Puede nombrar cualquier subexpresión en una definición de macro grande, por lo que es fácil seleccionar cosas específicas que le interesen
  • Fácilmente extensible si desea agregar más características a las construcciones de macro. Por ejemplo, Several("abc", min=3, max=5, name="a") . Creo que esto también podría usarse para agregar valores predeterminados a elementos opcionales como Optional("step", Var(), name="step", default=1) .

No estoy seguro de la syntax de comillas / no entre comillas con “quote:” y “$”, pero se necesita algo de syntax para esto, ya que hace la vida mucho más fácil si no tiene que escribir manualmente los árboles de syntax. Probablemente sea una buena idea requerir (o simplemente permitir) un paréntesis para “$”, de modo que pueda insertar partes de syntax más complicadas, si así lo desea. Como $(Stmt(a, b, c)) .

El ToMacro se vería así:

 # 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, ')']] 

Podría considerar ver cómo Boo (un lenguaje basado en .NET con una syntax inspirada en gran medida por Python) implementa macros, como se describe en http://boo.codehaus.org/Syntactic+Macros .

Debería echar un vistazo a MetaPython para ver si cumple con lo que está buscando.

Incorporando 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 ) 

Esta es una nueva syntax macro que he creado en base a las ideas de Kent Fredric. Analiza la syntax en una lista al igual que se analiza el código.

Imprimir macro:

 macro PrintMacro: syntax: print $stmts if not isinstance(stmts, list): stmts = [stmts] return Printnl(stmts, None) 

Si macro:

 @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 a Y [inclusive] [paso Z] macro:

 @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) 

Aparte del problema menor de usar decoradores para identificar el tipo de macro, mi único problema real es la forma en que puede nombrar grupos, por ejemplo, en el caso if. Estoy usando @name = …, pero esto solo apesta a Perl. No quiero solo usar nombre = … porque eso podría entrar en conflicto con un patrón macro para coincidir. ¿Algunas ideas?

Estoy publicando un poco de ideas flotantes para ver si inspira. No sé mucho de python, y no estoy usando una syntax de python real, pero no supera a nada: 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(); 

Más ideas:

Implementando el estilo de cita de Perl, pero en Python! (Es una implementación muy mala, y nota: el espacio en blanco es importante en la regla)

 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; 

Si solo estás preguntando acerca de la syntax (no la implementación) de las macros dentro de Python, entonces creo que la respuesta es obvia. La syntax debe coincidir con lo que Python ya tiene (es decir, la palabra clave ” def “).

Si implementa esto como uno de los siguientes, depende de usted:

 def macro largest(lst): defmac largest(lst): macro largest(lst): 

pero creo que debería ser exactamente igual a una función normal con respecto al rest para que:

 def twice_second(a,b): glob_i = glob_i + 1 return b * 2 x = twice_second (1,7); 

y

 defmac twice_second(a,b): glob_i = glob_i + 1 return b * 2 x = twice_second (1,7); 

son funcionalmente equivalentes.

La forma en que lo implementaría es con un preprocesador (a la C) que:

  • Reemplace todos los defmac con defs en el archivo de entrada.
  • pásalo a través de Python para verificar la syntax (esto es poco).
  • vuelve a poner el defmac.
  • encuentre todos los usos de cada macro y “inline” ellos, usando sus propias variables reservadas, como convertir var a local con __macro_second_local_a .
  • el valor de retorno también debe ser una variable especial (macro_second_retval).
  • Las variables globales mantendrían sus nombres reales.
  • se le puede dar el parámetro _macro_second_param_XXX nombres.
  • Una vez que se haya completado todo el proceso de inline, elimine las ‘funciones’ de defmac por completo.
  • pasar el archivo resultante a través de Python.

No hay duda de que habrá que preocuparse por algunos (como las tuplas o los múltiples puntos de retorno), pero Python es lo suficientemente robusto para manejar eso en mi opinión.

Asi que:

 x = twice_second (1,7); 

se convierte en:

 # 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 

Este es el mecanismo actual para definir la syntax mediante el uso de una clase estándar de Python.

Imprimir macro:

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

Si / elif / else macro class:

 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 a Y [inclusive] [paso Z] macro clase:

 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, ')']] 

Mis problemas con este diseño es que las cosas son muy verbales y no se sienten en lo más mínimo. Además, la falta de capacidad de cita dificulta las macros complejas.

Esta es la syntax de macro que he creado para mi superconjunto de Python.

Imprimir macro:

 macro PrintMacro: syntax: stmts = 'print', Var if not isinstance(stmts, list): stmts = [stmts] return Printnl(stmts, None) 

Si macro:

 @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 a Y [inclusive] [paso Z] macro:

 @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) 

Mi principal problema con esto es que el bloque de syntax no está claro, particularmente la línea ‘a’ en el último ejemplo. Tampoco soy un gran fanático del uso de decoradores para diferenciar tipos de macros.