2013-12-22 5 views
2

Я борюсь с Parsec, чтобы разобрать небольшое подмножество Google project wiki syntax и преобразовать его в HTML. Мой синтаксис ограничен текстовыми последовательностями и списками элементов. Вот пример того, что я хочу, чтобы признать:Parsec, текст, заканчивающийся строкой

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. I что мой парсер parseText может читать пустые строки, но я не вижу другого способа. Как распознать текст, ограниченный строкой? ("\n *" здесь).

Я также открыт для любых замечаний или предложений относительно того, как я пользуюсь Parsec. Я не могу не видеть, что мой код немного уродлив. Могу ли я сделать все это проще? Например, существует репликация кода (что является болезненным) из-за строки "\n *", которая используется для распознавания конца текстовой последовательности, начала элемента списка и конца элемента списка ...

+1

Does 'sepEndBy1 (string" * ">> many (noneOf" \ n ")) (строка" \ n ")' делать то, что вы хотите? Мне кажется, что язык, который вы описываете, это просто строка, содержащая множество строк, начинающихся с «*». В этом случае вам даже не нужен parsec: 'map (\ ('': '*': x) -> x). lines' – user2407038

+0

Элемент списка может содержать символы новой строки и звезды, истинным разделителем для элементов списка является «\ n *». – eskaev

+0

Единственными «незаконными» последовательностями являются строки, а не символы («\ n *» и «\ n \ n»). Вот почему noneOf не будет работать. И мне еще нужен способ разобрать текст, который может появиться перед списком. – eskaev

ответ

1
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 is cleaner :)

+0

Благодарим вас за ответ и за подсказку с 'sepBy'. Проблема состоит в том, чтобы не распознавать список (parseList выполняет задание), а распознавать текст, который может отображаться перед списком, и который может содержать любой символ. Я не думаю, что 'eof >> string" "' является проблемой, она читает «конец файла», а затем пустую строку. Я сделал это, потому что 'eof' имеет неправильный тип (я должен был написать' eof >> return ""). – eskaev

+1

Если вы удалите 'string" \ n * ">>', тогда этот парсер примет любой текст, не содержащий «\ n *», до разбора элементов списка. Итак, 'parse parseText" "" Text \ n * item 1 \ n * item 2 \ n \ n "== Right [" Text "," item 1 "," item 2 "]'. Разве это не то, что вы хотите? – user2407038

+0

Моя ошибка, вы правы. И мне просто нужно добавить разделитель '' \ n \ n "' (и небольшой 'lookAhead' внутри' parseItem'), и я даже могу прочитать текст после списка. Я нашел [этот вопрос] (http://stackoverflow.com/questions/19899931/haskell-parsec-combinator-many-is-applied-to-a-parser-that-accepts-an-empty-st), который проблема очень близка к моей, и они советуют ему использовать 'sepBy'. – eskaev

2

Проблема в том, что комбинатор 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 материал на вершине с БПМЖ (предпочтительно) или возврата.

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 *" во избежание дублирования. Здесь я раздавил все списки и проигнорировал синтаксический анализ с последовательностью, но вам придется это сделать. Все будет проще, если вы разделите текст на строки текста, а затем просто проверите либо строку текста, либо строку из списка.

+0

Спасибо за ваш ответ и ваши усилия! Я принимаю другой ответ, хотя его решение короче, и он был быстрее. Ваша идея 'parseText' действительно может быть полезна. Кстати, 'parseItem' не работает так, как есть, мне пришлось изменить его на:' string '\ n * ">> (много $ noneOf" \ n ")'. – eskaev

+0

работал для меня '(много $ noneOf" \ n ")' намного лучше (и то же самое) - я согласен предпочесть короткое решение, но слово предупреждения - его parseText на самом деле parseList, так что вы все равно будете иметь проблема parseText, сопоставляющая пустую строку и вызывающая исключение, когда используется в 'many ... <|> parseText', поэтому это будет очень грязно ... – user3125280

+0

whoops - я хотел отредактировать - я согласен предпочитаю короткое решение , но слово предупреждения - это будет очень грязно ... (лучше всего отделить эти вещи так или иначе, так как вы, вероятно, добавляете другие анализы, а другой синтаксис только разрешает текст раньше и не говорит, что есть и что не является товаром :( – user3125280

Смежные вопросы