Haskell Parsec analisando uma série de itens
Pergunta
Eu tenho uma lista que preciso analisar onde o tudo, exceto o último elemento, precisa ser analisado por um analisador, e o último elemento precisa ser analisado por outro analisador.
a = "p1 p1b ... p2"
or
a = "p2"
Originalmente eu tentei
parser = do parse1 <- many parser1
parse2 <- parser2
return AParse parse1 parse2
O problema é que o parse1 pode consumir uma entrada parse2. Portanto, o parse1 sempre consome a lista inteira e deixa o parse2 sem nada.
Existe uma maneira de dizer para aplicar o parse1 a tudo além do último elemento em uma string e depois aplicar o parse2?
Solução
Que tal:
parseTrain car caboose = choice
[ fmap (:[]) $ try (caboose `endBy` eof),
, liftM2 (:) car (parseTrain car caboose)
[
O EOF me incomoda, já que isso faz com que esse analisador não seja composicional. Ou seja, você não poderia dizer:
char '(' >> parseTrain p1 p2 >> char ')'
Fazer isso compsionalmente é muito difícil para um analisador. Como deve saber seguir para o char '), sem tentar em todas as oportunidades e ver se falha? Fazer isso poderia tempo exponencial.
Se você precisar ser composicional, seu problema tem alguma estrutura adicional que você pode explorar? Você pode, por exemplo, analisar uma lista de todos os elementos e depois processar o último após o fato?
Outras dicas
Se você pode levar em consideração parser1
Então isso é definido assim:
parser1 = (try parser2) <|> parser1extra
Então o problema se torna uma lista de parser1extra
ou parser2
Isso deve terminar mais tarde. Você pode codificar isso como:
parserList =
liftM2 (:) (try parser1extra) parserList
<|>
liftM2 (:) (try parser2) (option [] parserList)
Você pode ou não precisar do try
Chamadas, dependendo se esses analisadores tiverem alguma sobreposição de prefixo.
Se você não deseja que o valor de retorno seja uma lista, mas, em vez disso, o seu Aparse Datum, poderá reescrever novamente:
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)
Ou, um exemplo 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)
Na verdade, as ligações para tentar não são necessárias neste caso, como parseNum
e parseWord
Não corresponde ao prefixo comum. Notar que parsePartListEndingInWord
Na verdade, não se refere parsePart
, mas em vez disso, as duas opções que compõem parsePart
Definição
(Resposta original, resolvendo uma situação um pouco diferente :)
Que tal algo como:
parserTest = between (char '[') (char ']') $ do
p1s <- try parser1 `endBy` char ','
p2 <- parser2
return $ AParse p1s p2
Tirando a pontuação de seus analisadores e para o parsetest permite que você use os combinadores between
e endBy
Para fazer o trabalho para você. Por fim, o try
existe para que se parser1
e parser2
corresponder a um prefixo comum, endBy
Executará o backup completo correto para o início do prefixo comum.
Dependendo de seus analisadores, é possível que você possa deixar a pontuação correspondente dentro de seus sub-parcadores, e tudo o que você precisa pode ser o A a try
por aí parser1
:
parseTest = do parse1 <- many (try parser1)
parse2 <- parser2
return AParse parse1 parse2
Eu meio que combinei as duas abordagens:
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
Eu acho que isso funcionará para meus propósitos. Obrigado!
Isto irá fazer o truque:
parser1 `manyTill` (try parser2)