Corrispondenza delle parole chiave in Pyparsing: slurping non avido di token
Domanda
Pythonisti:
Si supponga di voler analizzare la stringa seguente utilizzando Pyparsing:
'ABC_123_SPEED_X 123'
erano ABC_123
è un identificatore; SPEED_X
è un parametro, e 123
è un valore. Pensai alla seguente BNF utilizzando Pyparsing:
Identifier = Word( alphanums + '_' )
Parameter = Keyword('SPEED_X') or Keyword('SPEED_Y') or Keyword('SPEED_Z')
Value = # assume I already have an expression valid for any value
Entry = Identifier + Literal('_') + Parameter + Value
tokens = Entry.parseString('ABC_123_SPEED_X 123')
#Error: pyparsing.ParseException: Expected "_" (at char 16), (line:1, col:17)
Se rimuovo il carattere di sottolineatura dalla metà (e regolare la definizione Entry
di conseguenza) che analizza in modo corretto.
Come posso fare questo parser essere un po 'più pigri e attendere fino a che non corrisponde alla parola chiave (al contrario di slurping l'intera stringa come un identificatore e in attesa del _
, che non esiste.
Grazie.
[Nota: Questa è una completa riscrittura della mia domanda; Non avevo capito qual è il vero problema era]
Soluzione
Ho basato la mia risposta fuori di questo , dal momento che ciò che si' stiamo cercando di fare è ottenere un match non-greedy. Sembra che questo è difficile da far accadere in pyparsing, ma non impossibile con una certa intelligenza e il compromesso. Quanto segue sembra funzionare:
from pyparsing import *
Parameter = Literal('SPEED_X') | Literal('SPEED_Y') | Literal('SPEED_Z')
UndParam = Suppress('_') + Parameter
Identifier = SkipTo(UndParam)
Value = Word(nums)
Entry = Identifier + UndParam + Value
Quando si esegue questa dall'interprete interattivo, possiamo vedere il seguente:
>>> Entry.parseString('ABC_123_SPEED_X 123')
(['ABC_123', 'SPEED_X', '123'], {})
Si noti che questo è un compromesso; perché io uso SkipTo
, il Identifier
può essere piena di personaggi disgustosi, il male, non solo bella alphanums
con la sottolineatura occasionale.
Modifica Grazie a Paul McGuire, siamo in grado di inventare una soluzione davvero elegante impostando Identifier
al seguente:
Identifier = Combine(Word(alphanums) +
ZeroOrMore('_' + ~Parameter + Word(alphanums)))
Diamo ispezionare come funziona. In primo luogo, ignorare il Combine
esterno; ci arriveremo più avanti. A partire da Word(alphanums)
sappiamo che otterremo la parte 'ABC'
della stringa di riferimento, 'ABC_123_SPEED_X 123'
. E 'importante notare che non abbiamo permettiamo la "parola" per contenere underscore in questo caso. Costruiamo che separatamente alla logica.
Quindi, abbiamo bisogno di catturare la parte '_123'
senza succhiare anche in '_SPEED_X'
. Saltiamo anche oltre ZeroOrMore
a questo punto e tornare in un secondo momento. Si comincia con il trattino basso come Literal
, ma siamo in grado di scorciatoia con un solo '_'
, che ci ottenere la sottolineatura, ma non tutti '_123'
. Instictively, ci sarebbe posto un'altra Word(alphanums)
per catturare il resto, ma questo è esattamente ciò che ci porterà nei guai consumando tutto il '_123_SPEED_X'
rimanente. Invece, diciamo: "Fino a quando ciò che segue la sottolineatura è non il Parameter
, analizziamo che, come parte del mio Identifier
. Affermiamo che in pyparsing termini come '_' + ~Parameter + Word(alphanums)
. Dal momento che diamo per scontato che possiamo avere un arbitrario numero di sottolineatura + WordButNotParameter ripete, abbiamo avvolgere quell'espressione un costrutto ZeroOrMore
. (Se vi aspettate sempre almeno sottolineare + WordButNotParameter dopo l'iniziale, è possibile utilizzare OneOrMore
.)
Infine, abbiamo bisogno di avvolgere la parola iniziale e la sottolineatura speciale + Word ripete insieme in modo che si capisce che sono contigui, non separati da spazi, in modo da avvolgere l'intera espressione in un costrutto Combine
. In questo modo 'ABC _123_SPEED_X'
alzerà un errore di analisi, ma 'ABC_123_SPEED_X'
sarà analizzare correttamente.
Si noti anche che ho dovuto cambiare Keyword
per Literal
perché le vie della ex sono troppo sottili e veloce da rabbia. Non mi fido Keyword
s, nè potrei avere corrispondenza con loro.
Altri suggerimenti
Se si è certi che l'identificatore non finisce mai con una sottolineatura, è possibile farla rispettare nella definizione:
from pyparsing import *
my_string = 'ABC_123_SPEED_X 123'
Identifier = Combine(Word(alphanums) + Literal('_') + Word(alphanums))
Parameter = Literal('SPEED_X') | Literal('SPEED_Y') | Literal('SPEED_Z')
Value = Word(nums)
Entry = Identifier + Literal('_').suppress() + Parameter + Value
tokens = Entry.parseString(my_string)
print tokens # prints: ['ABC_123', 'SPEED_X', '123']
Se non è il caso, ma se la lunghezza identificatore è fissa è possibile definire Identifier in questo modo:
Identifier = Word( alphanums + '_' , exact=7)
È anche possibile analizzare l'identificatore e il parametro come un gettone, e dividerli in un'azione di analisi:
from pyparsing import *
import re
def split_ident_and_param(tokens):
mo = re.match(r"^(.*?_.*?)_(.*?_.*?)$", tokens[0])
return [mo.group(1), mo.group(2)]
ident_and_param = Word(alphanums + "_").setParseAction(split_ident_and_param)
value = Word(nums)
entry = ident_and_param + value
print entry.parseString("APC_123_SPEED_X 123")
L'esempio sopra presuppone che gli identificatori e parametri hanno sempre la XXX_YYY formato (contenente un singolo trattino).
Se questo non è il caso, è necessario regolare il metodo split_ident_and_param()
.
Questo risponde a una domanda che probabilmente hanno anche chiesti: "Che cosa è un mondo reale applicazione per reduce
):
>>> keys = ['CAT', 'DOG', 'HORSE', 'DEER', 'RHINOCEROS']
>>> p = reduce(lambda x, y: x | y, [Keyword(x) for x in keys])
>>> p
{{{{"CAT" | "DOG"} | "HORSE"} | "DEER"} | "RHINOCEROS"}
Modifica
Questa è stata una buona risposta alla domanda iniziale. Dovrò lavorare su quello nuovo.
Ulteriori edit:
Sono abbastanza sicuro che non si può fare quello che stai cercando di fare. Il parser che pyparsing
crea non fa lookahead. Quindi, se gli si dice di abbinare Word(alphanums + '_')
, sta andando a mantenere i caratteri corrispondenti finché non si trova uno che non è una lettera, un numero, o di sottolineatura.