Análisis de llamadas de funciones anidadas mediante pyparsing

Estoy tratando de usar pyparsing para analizar las llamadas de función en la forma:

 f(x, y) 

Eso es fácil. Pero como se trata de un analizador de descendencia recursiva, también debería ser fácil de analizar:

 f(g(x), y) 

Eso es lo que no puedo conseguir. Aquí hay un ejemplo resumido:

 from pyparsing import Forward, Word, alphas, alphanums, nums, ZeroOrMore, Literal lparen = Literal("(") rparen = Literal(")") identifier = Word(alphas, alphanums + "_") integer = Word( nums ) functor = identifier # allow expression to be used recursively expression = Forward() arg = identifier | integer | expression args = arg + ZeroOrMore("," + arg) expression << functor + lparen + args + rparen print expression.parseString("f(x, y)") print expression.parseString("f(g(x), y)") 

Y aquí está la salida:

 ['f', '(', 'x', ',', 'y', ')'] Traceback (most recent call last): File "tmp.py", line 14, in  print expression.parseString("f(g(x), y)") File "/usr/local/lib/python2.6/dist-packages/pyparsing-1.5.6-py2.6.egg/pyparsing.py", line 1032, in parseString raise exc pyparsing.ParseException: Expected ")" (at char 3), (line:1, col:4) 

¿Por qué mi analizador interpreta el functor de la expresión interna como un identificador independiente?

Buena captura al descubrir que el identifier estaba ocultando la expression en tu definición de arg . Aquí hay algunos otros consejos sobre su analizador:

x + ZeroOrMore(',' + x) es un patrón muy común en los analizadores de pyparsing, por lo que pyparsing incluye un método de ayuda delimitedList que le permite reemplazar esa expresión con delimitedList(x) . En realidad, delimitedList hace otra cosa: suprime las comas delimitadoras (u otro delimitador si se usa usando el argumento delim opcional), basado en la noción de que los delimitadores son útiles en el momento del análisis, pero son simplemente elementos de desorden al tratar de filtrar datos analizados después. Así que puedes reescribir args como args = delimitedList(arg) , y obtendrás solo los argumentos en una lista, sin comas que tengan que “pasar”.

Puede usar la clase de Group para crear una estructura real en sus tokens analizados. Esto construirá su jerarquía de anidamiento para usted, sin tener que recorrer esta lista buscando ‘(‘ y ‘)’ para decirle cuándo ha bajado un nivel en la función de anidamiento:

  arg = Group(expression) | identifier | integer expression << functor + Group(lparen + args + rparen) 

Ya que tus argumentos se están agrupando para ti, puedes suprimir aún más los parens, ya que al igual que las comas delimitadoras, hacen su trabajo durante el análisis, pero al agrupar tus tokens, ya no son necesarios:

 lparen = Literal("(").suppress() rparen = Literal(")").suppress() 

Asumo que 'h ()' es una llamada de función válida, solo que no hay argumentos. Puedes permitir que los argumentos sean opcionales usando Optional :

 expression << functor + Group(lparen + Optional(args) + rparen) 

Ahora puede analizar "f (g (x), y, h ())".

Bienvenido a pyparsing!

La definición de arg debe organizar con el elemento que comienza con otro a la izquierda, de modo que coincida preferentemente:

 arg = expression | identifier | integer 

El post de Paul ayudó mucho. Solo para la referencia de otros, lo mismo se puede usar para definir los for loops siguiente manera (pseudo-parser simplificado aquí, para mostrar la estructura):

 sep = Literal(';') if_ = Keyword('if') then_ = Keyword('then') elif_ = Keyword('elif') end_ = Keyword('end') if_block = Forward() do_block = Forward() stmt = other | if_block stmts = OneOrMore(stmt +sep) case = Group(guard +then_ +stmts) cases = case +OneOrMore(elif_ +case) if_block << if_ +cases +end_