Haskell parsec, анализирующий строку элементов
Вопрос
У меня есть список, который мне нужно проанализировать, где все элементы, кроме последнего, должны быть проанализированы одним анализатором, а последний элемент должен быть проанализирован другим анализатором.
a = "p1 p1b ... p2"
or
a = "p2"
Первоначально я пытался
parser = do parse1 <- many parser1
parse2 <- parser2
return AParse parse1 parse2
Проблема в том, что parse1 может использовать входные данные parse2.Таким образом, parse1 всегда использует весь список, а parse2 оставляет ни с чем.
Есть ли способ сказать, чтобы применить parse1 ко всему, кроме последнего элемента в строке, а затем применить parse2?
Решение
Как насчет:
parseTrain car caboose = choice
[ fmap (:[]) $ try (caboose `endBy` eof),
, liftM2 (:) car (parseTrain car caboose)
[
Eof меня беспокоит, так как это делает этот анализатор не композиционным.То есть.ты не мог бы сказать:
char '(' >> parseTrain p1 p2 >> char ')'
Выполнить это комплексно для синтаксического анализатора очень сложно.Как он должен знать, что нужно переходить к символу ')', не пытаясь делать это при каждой возможности и не проверяя, не сработает ли это?Это может привести к экспоненциальному увеличению времени.
Если вам нужно, чтобы она была композиционной, есть ли в вашей проблеме какая-то дополнительная структура, которую вы могли бы использовать?Можете ли вы, например, проанализировать список всех элементов, а затем обработать последний постфактум?
Другие советы
Если вы можете учесть parser1
итак, это определяется следующим образом:
parser1 = (try parser2) <|> parser1extra
Тогда проблема превращается в список parser1extra
или parser2
это должно закончиться позже.Вы можете закодировать это как:
parserList =
liftM2 (:) (try parser1extra) parserList
<|>
liftM2 (:) (try parser2) (option [] parserList)
Вам может понадобиться, а может и не понадобиться try
вызывает в зависимости от того, имеют ли эти анализаторы какое-либо перекрытие префиксов.
Если вы не хотите, чтобы возвращаемым значением был список, а вместо этого ваши данные AParse, то вы могли бы переписать его таким образом:
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)
Или, полный пример:
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)
На самом деле, вызовы try в этом случае не нужны, поскольку parseNum
и parseWord
не соответствует ни одному общему префиксу.Обратите внимание , что parsePartListEndingInWord
на самом деле не ссылается parsePart
, но вместо этого два варианта, которые составляют parsePart
определение 's
(Оригинальный ответ, решающий несколько иную ситуацию:)
Как насчет чего-то вроде:
parserTest = between (char '[') (char ']') $ do
p1s <- try parser1 `endBy` char ','
p2 <- parser2
return $ AParse p1s p2
Удаление знаков препинания из ваших анализаторов в parseTest позволяет вам использовать комбинаторы between
и endBy
чтобы сделать эту работу за вас.Наконец, в try
существует ли так, что если parser1
и parser2
сопоставьте общий префикс, endBy
выполнит правильное полное резервное копирование до начала общего префикса.
В зависимости от ваших анализаторов возможно, что вы можете оставить соответствие знаков препинания внутри ваших вспомогательных анализаторов, и все, что вам нужно, может быть a try
вокруг parser1
:
parseTest = do parse1 <- many (try parser1)
parse2 <- parser2
return AParse parse1 parse2
Я как бы объединил эти два подхода:
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
Я думаю, что это сработает для моих целей.Спасибо!
Это сделает свое дело:
parser1 `manyTill` (try parser2)