2010-03-15 4 views
8

Ho una lista che ho bisogno di analizzare dove tutti gli elementi tranne l'ultimo devono essere analizzati da un parser e l'ultimo elemento deve essere analizzato da un altro parser.Haskell parsec analizzando una stringa di elementi

a = "p1 p1b ... p2" 
or 
a = "p2" 

originale Cercai

parser = do parse1 <- many parser1 
      parse2 <- parser2 
      return AParse parse1 parse2 

Il problema è che parse1 può consumare un ingresso parse2. Quindi parse1 consuma sempre l'intera lista e lascia parse2 con niente.

C'è un modo per dire di applicare parse1 a tutto tranne l'ultimo elemento in una stringa, e quindi applicare parse2?

risposta

2

ne dite:

parseTrain car caboose = choice 
    [ fmap (:[]) $ try (caboose `endBy` eof), 
    , liftM2 (:) car (parseTrain car caboose) 
    [ 

L'eof mi infastidisce, poiché questo rende questo parser non compositivo. Cioènon si poteva dire:

char '(' >> parseTrain p1 p2 >> char ')' 

Fare questo compsitional è molto difficile per un parser. Come si può sapere di passare a char ')', senza cercare di cogliere ogni occasione e vedere se fallisce? Fare così potrebbe essere un tempo esponenziale.

Se è necessario che sia compositivo, il problema presenta qualche struttura aggiuntiva che è possibile sfruttare? Puoi, ad esempio, analizzare un elenco di tutti gli elementi e quindi elaborare l'ultimo dopo il fatto?

2

Se è possibile fattore parser1 in modo che si definisce in questo modo:

parser1 = (try parser2) <|> parser1extra 

Allora 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 hanno bisogno i try chiamate a seconda se tali parser hanno alcuna sovrapposizione prefisso.

Se non si desidera che il valore di ritorno per 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) 

Oppure, 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 da provare non sono necessarie in questo caso, poiché parseNum e parseWord non corrispondono a nessun prefisso comune. Si noti che in realtà non parsePartListEndingInWord riferimento parsePart, ma, invece, le due opzioni che compongono la definizione parsePart 's


(risposta originale, risolvere una situazione un po' diversa :)

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 i combinatori between e endBy a fare il lavoro per tu. Infine, lo try è così che se parser1 e parser2 corrispondono a 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 
+0

ho commesso un errore nella questione e ha dichiarato che ho avuto un elenco di elementi. Dovrei dire che avevo una serie di elementi. Mi dispiace per quello Ho fatto la correzione nella domanda. Il secondo esempio nel codice non funzionerà perché parser1 consumerà l'intera stringa. – Chris

+0

Grazie per il codice. Il parser1extra non consumerebbe semplicemente tutta la stringa? – Chris

+0

L'idea era che parser1extra avrebbe solo analizzato quelle cose che dovrebbero essere in parser1, ma non corrispondono a parser2. Quindi parser1extra corrisponde solo a dove parser2 non lo fa. – MtnViewMark

0

io tipo di combinazione 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 

penso che questo lavoro per i miei scopi. Grazie!

0

questo farà il trucco:

parser1 `manyTill` (try parser2)