Estoy tratando de crear una gramática para analizar algunas fórmulas similares a Excel que he ideado, donde un carácter especial al comienzo de una cadena significa una fuente diferente. Por ejemplo, $
puede significar una cadena, por lo que " $This is text
" se trataría como una entrada de cadena en el programa y &
puede significar una función, por lo que &foo()
se puede tratar como una llamada a la función interna foo
.
El problema al que me enfrento es cómo construir la gramática correctamente. Por ejemplo, esta es una versión simplificada como MWE:
grammar = r'''start: instruction
?instruction: simple
| func
STARTSYMBOL: "!"|"#"|"$"|"&"|"~"
SINGLESTR: (LETTER+|DIGIT+|"_"|" ")*
simple: STARTSYMBOL [SINGLESTR] (WORDSEP SINGLESTR)*
ARGSEP: ",," // argument separator
WORDSEP: "," // word separator
CONDSEP: ";;" // condition separator
STAR: "*"
func: STARTSYMBOL SINGLESTR "(" [simple|func] (ARGSEP simple|func)* ")"
%import common.LETTER
%import common.WORD
%import common.DIGIT
%ignore ARGSEP
%ignore WORDSEP
'''
parser = lark.Lark(grammar, parser='earley')
Así pues, con esta gramática, cosas como: $This is a string
, &foo()
, &foo(#arg1)
, &foo($arg1,,#arg2)
y &foo(!w1,w2,w3,,!w4,w5,w6)
están todos analizados como se esperaba. Pero si quisiera agregar más flexibilidad a mi simple
terminal, entonces necesito comenzar a jugar con la SINGLESTR
definición del token que no es conveniente.
Que he probado
La parte que no puedo superar es que si quiero tener una cadena que incluya paréntesis (que son literales de func
), entonces no puedo manejarlos en mi situación actual.
- Si agrego los paréntesis
SINGLESTR
, obtengoExpected STARTSYMBOL
, porque se está mezclando con lafunc
definición y cree que se debe pasar un argumento de función, lo cual tiene sentido. - Si redefino la gramática para reservar el símbolo de ampersand solo para las funciones y agrego los paréntesis
SINGLESTR
, entonces puedo analizar una cadena con paréntesis, pero cada función que estoy tratando de analizar daExpected LPAR
.
Mi intención es que cualquier cosa que comience con a $
se analice como una SINGLESTR
ficha y luego pueda analizar cosas como &foo($first arg (has) parentheses,,$second arg)
.
Mi solución, por ahora, es que estoy usando palabras de 'escape' como LEFTPAR y RIGHTPAR en mis cadenas y he escrito funciones de ayuda para cambiarlas entre paréntesis cuando proceso el árbol. Entonces, $This is a LEFTPARtestRIGHTPAR
produce el árbol correcto y cuando lo proceso, esto se traduce a This is a (test)
.
Para formular una pregunta general: ¿Puedo definir mi gramática de tal manera que algunos caracteres que son especiales para la gramática sean tratados como caracteres normales en algunas situaciones y como especiales en cualquier otro caso?
EDITAR 1
Basado en un comentario de jbndlr
Revisé mi gramática para crear modos individuales basados en el símbolo de inicio:
grammar = r'''start: instruction
?instruction: simple
| func
SINGLESTR: (LETTER+|DIGIT+|"_"|" ") (LETTER+|DIGIT+|"_"|" "|"("|")")*
FUNCNAME: (LETTER+) (LETTER+|DIGIT+|"_")* // no parentheses allowed in the func name
DB: "!" SINGLESTR (WORDSEP SINGLESTR)*
TEXT: "$" SINGLESTR
MD: "#" SINGLESTR
simple: TEXT|DB|MD
ARGSEP: ",," // argument separator
WORDSEP: "," // word separator
CONDSEP: ";;" // condition separator
STAR: "*"
func: "&" FUNCNAME "(" [simple|func] (ARGSEP simple|func)* ")"
%import common.LETTER
%import common.WORD
%import common.DIGIT
%ignore ARGSEP
%ignore WORDSEP
'''
Esto cae (algo) en mi segundo caso de prueba. Puedo analizar todos los simple
tipos de cadenas (tokens TEXT, MD o DB que pueden contener paréntesis) y funciones que están vacías; por ejemplo, &foo()
o &foo(&bar())
analizar correctamente. En el momento en que pongo un argumento dentro de una función (no importa qué tipo), obtengo un UnexpectedEOF Error: Expected ampersand, RPAR or ARGSEP
. Como prueba de concepto, si elimino los paréntesis de la definición de SINGLESTR en la nueva gramática anterior, entonces todo funciona como debería, pero vuelvo al punto de partida.
fuente
STARTSYMBOL
) y agregas separadores y paréntesis cuando es necesario que sean claros; No veo ninguna ambigüedad aquí. Aún tendría que dividir suSTARTSYMBOL
lista en elementos individuales para poder distinguirlos.Respuestas:
Salida:
Espero que sea lo que estabas buscando.
Esos han sido locos unos días. Traté de alondra y fallé. También intenté
persimonious
ypyparsing
. Todos estos analizadores diferentes tenían el mismo problema con el token 'argumento' que consumía el paréntesis correcto que era parte de la función, y finalmente fallaba porque los paréntesis de la función no estaban cerrados.El truco consistía en descubrir cómo se define un paréntesis correcto que "no es especial". Vea la expresión regular para
MIDTEXTRPAR
en el código anterior. Lo definí como un paréntesis correcto que no es seguido por la separación de argumentos o por el final de la cadena. Lo hice usando la extensión de expresión regular(?!...)
que coincide solo si no es seguida...
pero no consume caracteres. Afortunadamente, incluso permite que el final de la cadena coincida dentro de esta extensión especial de expresión regular.EDITAR:
El método mencionado anteriormente solo funciona si no tiene un argumento que termine con a), porque entonces la expresión regular MIDTEXTRPAR no captará eso) y pensará que ese es el final de la función a pesar de que hay más argumentos para procesar. Además, puede haber ambigüedades como ... asdf) ,, ..., puede ser el final de una declaración de función dentro de un argumento, o un 'texto') dentro de un argumento y la declaración de función continúa.
Este problema está relacionado con el hecho de que lo que describe en su pregunta no es una gramática libre de contexto ( https://en.wikipedia.org/wiki/Context-free_grammar ) para la que existen analizadores como la alondra. En cambio, es una gramática sensible al contexto ( https://en.wikipedia.org/wiki/Context-sensitive_grammar ).
La razón de que sea una gramática sensible al contexto es porque necesita que el analizador 'recuerde' que está anidado dentro de una función y cuántos niveles de anidamiento hay, y que esta memoria está disponible dentro de la sintaxis de la gramática de alguna manera.
EDIT2:
También eche un vistazo al siguiente analizador que es sensible al contexto y parece resolver el problema, pero tiene una complejidad de tiempo exponencial en el número de funciones anidadas, ya que trata de analizar todas las posibles barreras de función hasta que encuentre una que funcione. Creo que tiene que tener una complejidad exponencial ya que no está libre de contexto.
fuente
&
por ejemplo.El problema es que los argumentos de función están encerrados entre paréntesis, donde uno de los argumentos puede contener paréntesis.
Una de las posibles soluciones es usar la tecla de retroceso \ before (o) cuando es parte de String
Solución similar utilizada por C, para incluir comillas dobles (") como parte de la constante de cadena donde la constante de cadena está entre comillas dobles.
La salida es
fuente