Haskell parsec l'analisi di una serie di articoli
Domanda
Ho una lista che ho bisogno di analizzare dove il tutto, ma l'ultimo elemento deve essere analizzato da un parser, e l'ultimo elemento deve essere analizzato da un altro parser.
a = "p1 p1b ... p2"
or
a = "p2"
In origine ho cercato
parser = do parse1 <- many parser1
parse2 <- parser2
return AParse parse1 parse2
Il problema è che parse1 può consumare un ingresso parse2. Così parse1 consuma sempre l'intero elenco, e lasciare parse2 con niente.
C'è un modo di dire per applicare parse1 a tutto ciò oltre l'ultimo elemento in una stringa, e quindi applicare parse2?
Soluzione
Come su:
parseTrain car caboose = choice
[ fmap (:[]) $ try (caboose `endBy` eof),
, liftM2 (:) car (parseTrain car caboose)
[
Gli insetti EOF me, dal momento che rende questo parser non compositiva. Cioè Non si potrebbe dire:
char '(' >> parseTrain p1 p2 >> char ')'
In questo modo compsitionally è molto difficile per un parser. Come si dovrebbe sapere di passare a char ')', senza cercare di in ogni occasione e vedere se fallisce? Ciò potrebbe esponenziali tempo.
Se avete bisogno di essere compositiva, non il problema hanno qualche struttura aggiuntiva si può sfruttare? Si può, ad esempio, analizzare un elenco di tutti gli elementi e poi elaborare l'ultimo dopo il fatto?
Altri suggerimenti
Se è possibile fattore parser1
in modo che si definisce in questo modo:
parser1 = (try parser2) <|> parser1extra
Quindi il problema diventa un elenco di parser1extra
o parser2
che deve finire in un secondo momento. È possibile codificare che, come:
parserList =
liftM2 (:) (try parser1extra) parserList
<|>
liftM2 (:) (try parser2) (option [] parserList)
Si può o non può avere bisogno le chiamate try
a seconda se tali parser hanno alcuna sovrapposizione prefisso.
Se non si desidera che il valore di ritorno ad essere un elenco, ma invece il vostro riferimento AParse, allora si potrebbe ri-scrivere in questo modo:
parserList =
do
a <- try parser1extra
prefix a parserList
<|>
do
a <- try parser2
option (AParse [] a) (prefix a parserList)
where prefix a p = do
(AParse as t) <- p
return $ (AParse (a:as) t)
In alternativa, un esempio completo:
import Control.Monad
import Text.ParserCombinators.Parsec
parseNum = do { v <- many1 digit; spaces; return v }
parseWord = do { v <- many1 letter; spaces; return v }
parsePart = parseNum <|> parseWord
parsePartListEndingInWord =
liftM2 (:) (try parseNum) parsePartListEndingInWord
<|>
liftM2 (:) (try parseWord) (option [] parsePartListEndingInWord)
In realtà, le chiamate per cercare di non sono necessari in questo caso, come parseNum
e parseWord
non corrispondono prefisso comune. Si noti che in realtà non parsePartListEndingInWord
riferimento parsePart
, ma, invece, le due opzioni che compongono la definizione di parsePart
(risposta originale, risolvendo una situazione un po 'diversa:)
Che ne dite di qualcosa di simile a:
parserTest = between (char '[') (char ']') $ do
p1s <- try parser1 `endBy` char ','
p2 <- parser2
return $ AParse p1s p2
Prendendo la punteggiatura dai vostri parser e fino in parseTest consente di utilizzare il between
combinatori e endBy
per fare il lavoro per voi. Infine, il try
è là in modo che, se parser1
e parser2
corrispondono un prefisso comune, endBy
eseguirà il backup completo corretto all'inizio del prefisso comune.
A seconda delle parser, è possibile che si può lasciare l'abbinamento punteggiatura all'interno del vostro sub-parser, e tutto il necessario potrebbe essere l'una try
intorno parser1
:
parseTest = do parse1 <- many (try parser1)
parse2 <- parser2
return AParse parse1 parse2
I tipi di combinato dei due approcci:
parserList = try (do a <- parser2
eof
return $ AParse [] a)
<|>
do a <- parser1
prefix a parserList
where
prefix a p = do
(AParse as t) <- p
return $ AParse a:as t
Credo che questo lavoro per i miei scopi. Grazie!
Questo farà il trucco:
parser1 `manyTill` (try parser2)