Parsec, читать текст, заканчивающийся строкой
Я борюсь с Parsec
проанализировать небольшое подмножество синтаксиса вики проекта Google и преобразовать его в HTML. Мой синтаксис ограничен текстовыми последовательностями и списками предметов. Вот пример того, что я хочу узнать:
Text that can contain any kind of characters,
except the string "\n *"
* list item 1
* list item 2
End of list
Мой код до сих пор:
import Text.Blaze.Html5 (Html, toHtml)
import qualified Text.Blaze.Html5 as H
import Text.ParserCombinators.Parsec hiding (spaces)
parseList :: Parser Html
parseList = do
items <- many1 parseItem
return $ H.ul $ sequence_ items
parseItem :: Parser Html
parseItem = do
string "\n *"
item <- manyTill anyChar $
(try $ lookAhead $ string "\n *") <|>
(try $ string "\n\n")
return $ H.li $ toHtml item
parseText :: Parser Html
parseText = do
text <- manyTill anyChar $
(try $ lookAhead $ string "\n *") <|>
(eof >> (string ""))
return $ toHtml text
parseAll :: Parser Html
parseAll = do
l <- many (parseUl <|> parseText)
return $ H.html $ sequence_ l
При подаче заявления parseAll
для любой последовательности символов я получаю следующее сообщение об ошибке: "*** Exception: Text.ParserCombinators.Parsec.Prim.many: combinator 'many' is applied to a parser that accepts an empty string.
Я так понимаю это потому что мой парсер parseText
могу читать пустые строки, но я не вижу другого пути. Как я могу распознать текст, разделенный строкой? ("\n *"
Вот).
Я также открыт для любых замечаний или предложений относительно того, как я использую Parsec. Я не могу помочь, но вижу, что мой код немного уродлив. Могу ли я сделать все это более простым способом? Например, есть репликация кода (что довольно болезненно) из-за строки "\n *"
, который используется для распознавания конца текстовой последовательности, начала элемента списка И конца элемента списка...
2 ответа
parseItem :: Parser String
parseItem = do
manyTill anyChar $
(try $ lookAhead $ string "\n *") <|>
(try $ string "\n\n")
parseText :: Parser [String]
parseText =
string "\n *" >> -- remove this if text *can't* contain a leading '\n *'
sepBy1 parseItem (string "\n *")
Я удалил материал HTML, потому что по какой-то причине я не мог получить blaze-html
установить на мою машину. Но в принципе это должно быть по сути то же самое. Это анализирует строки, разделенные строкой "\n *" и заканчивающиеся строкой "\n\n". Я не знаю, есть ли ведущий \n
это то, что вы хотите, но это легко исправить.
Кроме того, я не знаю, допустима ли пустая строка. Вы должны изменить sepBy1
в sepBy
если это.
Что касается ошибки, которую вы получили: у вас есть string ""
Внутри many
, Это не только дает ошибку, которую вы получили, это не имеет никакого смысла! Парсер string ""
всегда будет успешным без использования чего-либо, так как пустая строка является префиксом всех строк и "" ++ x == x
, Если вы попытаетесь сделать это несколько раз, вы никогда не закончите анализ.
Помимо всего этого, ваш parseList
должен разобрать ваш язык. По сути, это то же самое, что sepBy
делает. я просто думаю sepBy
чище:)
Проблема заключается в manyTill
комбинатор соответствует нулю или более anyChar. Просто измените parseText, чтобы он соответствовал хотя бы одному anyChar, чтобы он не работал при чтении одного из разделителей - к сожалению, нет many1Till
комбинатор.
Также я предпочитаю parseAll = fmap (H.html . sequence) $ many (parseUl <|> parseText)
, так как вы упомянули советы уродства.
parseText = do
notFollowedBy $ string "\n *"
first <- anyChar
rest <- manyTill anyChar $
(try $ lookAhead $ string "\n *") <|>
(eof >> (string ""))
return $ toHtml first:rest
parseAll = fmap (H.html . sequence) $ many (parseUl <|> parseText)
Тем не менее, "parseUl" в Google дает только этот вопрос, поэтому я не знаю лучшего решения без понимания этого парсера.
Отчаявшись о своем первом принятом ответе, я написал его полностью:), просто добавьте html-материал сверху с помощью fmap (предпочтительно) или return.
module Main where
import System.Environment
import Control.Monad
import Text.ParserCombinators.Parsec hiding (spaces)
parseList :: Parser [String]
parseList = many1 parseItem
parseItem :: Parser String
parseItem = string "\n *" >> (manyTill anyChar $ try $ lookAhead $ char '\n')
parseText :: Parser String
parseText = do
notFollowedBy $ string "\n *"
first <- anyChar
rest <- manyTill anyChar $
(try $ lookAhead $ string "\n *") <|>
(eof >> (string ""))
return $ first:rest
parseAll :: Parser [String]
parseAll = many $ parseText <|> fmap concat parseList
parseIt :: String -> String
parseIt input = case parse parseAll "wiki" input of
Left err -> "No match: " ++ show err
Right val -> "It worked"
main = do
args <- getArgs
putStrLn (parseIt (args !! 0))
Я предполагал, что списки не могут содержать переводы строк, но try $ lookahead $ char '\n'
легко настраивается. Вы можете выделить string "\n *"
чтобы избежать дублирования. Здесь я раздавил все списки и проигнорировал анализ с последовательностью, но вам придется изменить это. Было бы проще, если бы вы разделили "текст" на строки текста, а затем просто проверили строку текста или строку из списка.