J'essaie de créer une grammaire pour analyser certaines formules de type Excel que j'ai conçues, où un caractère spécial au début d'une chaîne signifie une source différente. Par exemple, $
peut signifier une chaîne, donc " $This is text
" serait traité comme une entrée de chaîne dans le programme et &
peut signifier une fonction, donc &foo()
peut être traité comme un appel à la fonction interne foo
.
Le problème auquel je suis confronté est de savoir comment construire correctement la grammaire. Par exemple, il s'agit d'une version simplifiée en tant que 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')
Ainsi, avec cette grammaire, des choses comme: $This is a string
, &foo()
, &foo(#arg1)
, &foo($arg1,,#arg2)
et &foo(!w1,w2,w3,,!w4,w5,w6)
sont tous analysés comme prévu. Mais si je souhaite ajouter plus de flexibilité à mon simple
terminal, je dois commencer à bidouiller avec la SINGLESTR
définition de jeton, ce qui n'est pas pratique.
Qu'est-ce que j'ai essayé
La partie que je ne peux pas dépasser est que si je veux avoir une chaîne comprenant des parenthèses (qui sont des littéraux de func
), je ne peux pas les gérer dans ma situation actuelle.
- Si j'ajoute les parenthèses
SINGLESTR
, alors j'obtiensExpected STARTSYMBOL
, car cela se confond avec lafunc
définition et il pense qu'un argument de fonction devrait être passé, ce qui est logique. - Si je redéfinis la grammaire pour réserver le symbole esperluette aux fonctions uniquement et que j'ajoute les parenthèses
SINGLESTR
, je peux analyser une chaîne avec des parenthèses, mais chaque fonction que j'essaie d'analyser donneExpected LPAR
.
Mon intention est que tout ce qui commence par un $
soit analysé comme un SINGLESTR
jeton et que je puisse ensuite analyser des choses comme &foo($first arg (has) parentheses,,$second arg)
.
Ma solution, pour l'instant, est que j'utilise des mots «d'échappement» comme LEFTPAR et RIGHTPAR dans mes chaînes et j'ai écrit des fonctions d'aide pour les changer entre parenthèses lorsque je traite l'arbre. Donc, $This is a LEFTPARtestRIGHTPAR
produit le bon arbre et quand je le traite, cela se traduit par This is a (test)
.
Pour formuler une question générale: Puis-je définir ma grammaire de telle manière que certains caractères qui sont spéciaux à la grammaire sont traités comme des caractères normaux dans certaines situations et comme spéciaux dans tous les autres cas?
EDIT 1
Sur la base d'un commentaire de jbndlr
j'ai révisé ma grammaire pour créer des modes individuels basés sur le symbole de départ:
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
'''
Cela relève (quelque peu) de mon deuxième cas de test. Je peux analyser tous les simple
types de chaînes (jetons TEXT, MD ou DB qui peuvent contenir des parenthèses) et les fonctions qui sont vides; par exemple, &foo()
ou &foo(&bar())
analyser correctement. Au moment où je mets un argument dans une fonction (quel que soit le type), j'obtiens un UnexpectedEOF Error: Expected ampersand, RPAR or ARGSEP
. Comme preuve de concept, si je retire les parenthèses de la définition de SINGLESTR dans la nouvelle grammaire ci-dessus, alors tout fonctionne comme il se doit, mais je reviens à la case départ.
STARTSYMBOL
) et vous ajoutez des séparateurs et des parenthèses là où cela est nécessaire pour être clair; Je ne vois aucune ambiguïté ici. Vous devez encore diviser votreSTARTSYMBOL
liste en éléments individuels pour pouvoir les distinguer.