aboutsummaryrefslogtreecommitdiff
path: root/haddock-library/src/Documentation/Haddock/Parser.hs
diff options
context:
space:
mode:
authorAlec Theriault <alec.theriault@gmail.com>2018-04-25 11:24:07 -0700
committerAlexander Biehl <alexbiehl@gmail.com>2018-04-25 11:24:07 -0700
commit79c7159101c03bbbc7350e07963896ca2bb97c02 (patch)
treec754c425ed9d4ff8755dbe67589fa3c9dbbce10c /haddock-library/src/Documentation/Haddock/Parser.hs
parent979c7338cfcdc59f0b0dda562a53558c416cc362 (diff)
Replace 'attoparsec' with 'parsec' (#799)
* Remove attoparsec with parsec and start fixing failed parses * Make tests pass * Fix encoding issues The Haddock parser no longer needs to worry about bytestrings. All the internal parsing work in haddock-library happens over 'Text'. * Remove attoparsec vendor * Fix stuff broken in 'attoparsec' -> 'parsec' * hyperlinks * codeblocks * examples Pretty much all issues are due to attoparsec's backtracking failure behaviour vs. parsec's non-backtracking failure behaviour. * Fix small TODOs * Missing quote + Haddocks * Better handle spaces before/after paragraphs * Address review comments
Diffstat (limited to 'haddock-library/src/Documentation/Haddock/Parser.hs')
-rw-r--r--haddock-library/src/Documentation/Haddock/Parser.hs428
1 files changed, 236 insertions, 192 deletions
diff --git a/haddock-library/src/Documentation/Haddock/Parser.hs b/haddock-library/src/Documentation/Haddock/Parser.hs
index 4921b3a7..d79da40b 100644
--- a/haddock-library/src/Documentation/Haddock/Parser.hs
+++ b/haddock-library/src/Documentation/Haddock/Parser.hs
@@ -16,27 +16,35 @@
-- to be
--
-- @'toRegular' . '_doc' . 'parseParas'@
-module Documentation.Haddock.Parser ( parseString, parseParas
- , overIdentifier, toRegular, Identifier
- ) where
+module Documentation.Haddock.Parser (
+ parseString,
+ parseParas,
+ overIdentifier,
+ toRegular,
+ Identifier
+) where
import Control.Applicative
import Control.Arrow (first)
import Control.Monad
-import qualified Data.ByteString.Char8 as BS
-import Data.Char (chr, isUpper, isAlpha, isAlphaNum)
-import Data.List (stripPrefix, intercalate, unfoldr, elemIndex)
+import Data.Char (chr, isUpper, isAlpha, isAlphaNum, isSpace)
+import Data.List (intercalate, unfoldr, elemIndex, notElem)
import Data.Maybe (fromMaybe, mapMaybe)
import Data.Monoid
import qualified Data.Set as Set
import Documentation.Haddock.Doc
-import Documentation.Haddock.Parser.Monad hiding (take, endOfLine)
+import Documentation.Haddock.Parser.Monad
import Documentation.Haddock.Parser.Util
import Documentation.Haddock.Types
-import Documentation.Haddock.Utf8
import Prelude hiding (takeWhile)
import qualified Prelude as P
+import qualified Text.Parsec as Parsec
+import Text.Parsec (try)
+
+import qualified Data.Text as T
+import Data.Text (Text)
+
#if MIN_VERSION_base(4,9,0)
import Text.Read.Lex (isSymbolChar)
#else
@@ -56,7 +64,7 @@ isSymbolChar c = not (isPuncChar c) && case generalCategory c of
ModifierSymbol -> True
OtherSymbol -> True
DashPunctuation -> True
- OtherPunctuation -> not (c `elem` ("'\"" :: String))
+ OtherPunctuation -> c `notElem` ("'\"" :: String)
ConnectorPunctuation -> c /= '_'
_ -> False
where
@@ -109,8 +117,14 @@ overIdentifier f d = g d
g (DocHeader (Header l x)) = DocHeader . Header l $ g x
g (DocTable (Table h b)) = DocTable (Table (map (fmap g) h) (map (fmap g) b))
-parse :: Parser a -> BS.ByteString -> (ParserState, a)
-parse p = either err id . parseOnly (p <* endOfInput)
+
+choice' :: [Parser a] -> Parser a
+choice' [] = empty
+choice' [p] = p
+choice' (p : ps) = try p <|> choice' ps
+
+parse :: Parser a -> Text -> (ParserState, a)
+parse p = either err id . parseOnly (p <* Parsec.eof)
where
err = error . ("Haddock.Parser.parse: " ++)
@@ -127,31 +141,46 @@ parseParas pkg input = case parseParasState input of
}
parseParasState :: String -> (ParserState, DocH mod Identifier)
-parseParasState =
- parse (p <* skipSpace) . encodeUtf8 . (++ "\n") . filter (/= '\r')
+parseParasState = parse (emptyLines *> p) . T.pack . (++ "\n") . filter (/= '\r')
where
p :: Parser (DocH mod Identifier)
- p = docConcat <$> paragraph `sepBy` many (skipHorizontalSpace *> "\n")
+ p = docConcat <$> many (paragraph <* emptyLines)
+
+ emptyLines :: Parser ()
+ emptyLines = void $ many (try (skipHorizontalSpace *> "\n"))
parseParagraphs :: String -> Parser (DocH mod Identifier)
parseParagraphs input = case parseParasState input of
- (state, a) -> setParserState state >> return a
+ (state, a) -> Parsec.putState state *> pure a
--- | Parse a text paragraph. Actually just a wrapper over 'parseStringBS' which
--- drops leading whitespace and encodes the string to UTF8 first.
+-- | Variant of 'parseText' for 'String' instead of 'Text'
parseString :: String -> DocH mod Identifier
-parseString = parseStringBS . encodeUtf8 . dropWhile isSpace . filter (/= '\r')
+parseString = parseText . T.pack
-parseStringBS :: BS.ByteString -> DocH mod Identifier
-parseStringBS = snd . parse p
+-- | Parse a text paragraph. Actually just a wrapper over 'parseParagraph' which
+-- drops leading whitespace.
+parseText :: Text -> DocH mod Identifier
+parseText = parseParagraph . T.dropWhile isSpace . T.filter (/= '\r')
+
+parseParagraph :: Text -> DocH mod Identifier
+parseParagraph = snd . parse p
where
p :: Parser (DocH mod Identifier)
- p = docConcat <$> many (monospace <|> anchor <|> identifier <|> moduleName
- <|> picture <|> mathDisplay <|> mathInline
- <|> markdownImage
- <|> hyperlink <|> bold
- <|> emphasis <|> encodedChar <|> string'
- <|> skipSpecialChar)
+ p = docConcat <$> many (choice' [ monospace
+ , anchor
+ , identifier
+ , moduleName
+ , picture
+ , mathDisplay
+ , mathInline
+ , markdownImage
+ , hyperlink
+ , bold
+ , emphasis
+ , encodedChar
+ , string'
+ , skipSpecialChar
+ ])
-- | Parses and processes
-- <https://en.wikipedia.org/wiki/Numeric_character_reference Numeric character references>
@@ -175,7 +204,7 @@ specialChar = "_/<@\"&'`# "
-- to ensure that we have already given a chance to more meaningful parsers
-- before capturing their characers.
string' :: Parser (DocH mod a)
-string' = DocString . unescape . decodeUtf8 <$> takeWhile1_ (notInClass specialChar)
+string' = DocString . unescape . T.unpack <$> takeWhile1_ (`notElem` specialChar)
where
unescape "" = ""
unescape ('\\':x:xs) = x : unescape xs
@@ -185,45 +214,45 @@ string' = DocString . unescape . decodeUtf8 <$> takeWhile1_ (notInClass specialC
-- This is done to skip over any special characters belonging to other
-- elements but which were not deemed meaningful at their positions.
skipSpecialChar :: Parser (DocH mod a)
-skipSpecialChar = DocString . return <$> satisfy (inClass specialChar)
+skipSpecialChar = DocString . return <$> Parsec.oneOf specialChar
-- | Emphasis parser.
--
-- >>> parseString "/Hello world/"
-- DocEmphasis (DocString "Hello world")
emphasis :: Parser (DocH mod Identifier)
-emphasis = DocEmphasis . parseStringBS <$>
- mfilter ('\n' `BS.notElem`) ("/" *> takeWhile1_ (/= '/') <* "/")
+emphasis = DocEmphasis . parseParagraph <$>
+ disallowNewline ("/" *> takeWhile1_ (/= '/') <* "/")
-- | Bold parser.
--
-- >>> parseString "__Hello world__"
-- DocBold (DocString "Hello world")
bold :: Parser (DocH mod Identifier)
-bold = DocBold . parseStringBS <$> disallowNewline ("__" *> takeUntil "__")
+bold = DocBold . parseParagraph <$> disallowNewline ("__" *> takeUntil "__")
-disallowNewline :: Parser BS.ByteString -> Parser BS.ByteString
-disallowNewline = mfilter ('\n' `BS.notElem`)
+disallowNewline :: Parser Text -> Parser Text
+disallowNewline = mfilter (T.all (/= '\n'))
-- | Like `takeWhile`, but unconditionally take escaped characters.
-takeWhile_ :: (Char -> Bool) -> Parser BS.ByteString
-takeWhile_ p = scan False p_
+takeWhile_ :: (Char -> Bool) -> Parser Text
+takeWhile_ p = scan p_ False
where
p_ escaped c
| escaped = Just False
| not $ p c = Nothing
| otherwise = Just (c == '\\')
--- | Like `takeWhile1`, but unconditionally take escaped characters.
-takeWhile1_ :: (Char -> Bool) -> Parser BS.ByteString
-takeWhile1_ = mfilter (not . BS.null) . takeWhile_
+-- | Like 'takeWhile1', but unconditionally take escaped characters.
+takeWhile1_ :: (Char -> Bool) -> Parser Text
+takeWhile1_ = mfilter (not . T.null) . takeWhile_
-- | Text anchors to allow for jumping around the generated documentation.
--
-- >>> parseString "#Hello world#"
-- DocAName "Hello world"
anchor :: Parser (DocH mod a)
-anchor = DocAName . decodeUtf8 <$>
+anchor = DocAName . T.unpack <$>
disallowNewline ("#" *> takeWhile1_ (/= '#') <* "#")
-- | Monospaced strings.
@@ -231,7 +260,7 @@ anchor = DocAName . decodeUtf8 <$>
-- >>> parseString "@cruel@"
-- DocMonospaced (DocString "cruel")
monospace :: Parser (DocH mod Identifier)
-monospace = DocMonospaced . parseStringBS
+monospace = DocMonospaced . parseParagraph
<$> ("@" *> takeWhile1_ (/= '@') <* "@")
-- | Module names.
@@ -239,14 +268,14 @@ monospace = DocMonospaced . parseStringBS
-- Note that we allow '#' and '\' to support anchors (old style anchors are of
-- the form "SomeModule\#anchor").
moduleName :: Parser (DocH mod a)
-moduleName = DocModule <$> (char '"' *> modid <* char '"')
+moduleName = DocModule <$> ("\"" *> modid <* "\"")
where
- modid = intercalate "." <$> conid `sepBy1` "."
+ modid = intercalate "." <$> conid `Parsec.sepBy1` "."
conid = (:)
- <$> satisfyUnicode (\c -> isAlpha c && isUpper c)
- <*> many (satisfyUnicode conChar <|> char '\\' <|> char '#')
+ <$> Parsec.satisfy (\c -> isAlpha c && isUpper c)
+ <*> many (conChar <|> Parsec.oneOf "\\#")
- conChar c = isAlphaNum c || c == '_'
+ conChar = Parsec.alphaNum <|> Parsec.char '_'
-- | Picture parser, surrounded by \<\< and \>\>. It's possible to specify
-- a title for the picture.
@@ -256,7 +285,7 @@ moduleName = DocModule <$> (char '"' *> modid <* char '"')
-- >>> parseString "<<hello.png world>>"
-- DocPic (Picture {pictureUri = "hello.png", pictureTitle = Just "world"})
picture :: Parser (DocH mod a)
-picture = DocPic . makeLabeled Picture . decodeUtf8
+picture = DocPic . makeLabeled Picture
<$> disallowNewline ("<<" *> takeUntil ">>")
-- | Inline math parser, surrounded by \\( and \\).
@@ -264,7 +293,7 @@ picture = DocPic . makeLabeled Picture . decodeUtf8
-- >>> parseString "\\(\\int_{-\\infty}^{\\infty} e^{-x^2/2} = \\sqrt{2\\pi}\\)"
-- DocMathInline "\\int_{-\\infty}^{\\infty} e^{-x^2/2} = \\sqrt{2\\pi}"
mathInline :: Parser (DocH mod a)
-mathInline = DocMathInline . decodeUtf8
+mathInline = DocMathInline . T.unpack
<$> disallowNewline ("\\(" *> takeUntil "\\)")
-- | Display math parser, surrounded by \\[ and \\].
@@ -272,7 +301,7 @@ mathInline = DocMathInline . decodeUtf8
-- >>> parseString "\\[\\int_{-\\infty}^{\\infty} e^{-x^2/2} = \\sqrt{2\\pi}\\]"
-- DocMathDisplay "\\int_{-\\infty}^{\\infty} e^{-x^2/2} = \\sqrt{2\\pi}"
mathDisplay :: Parser (DocH mod a)
-mathDisplay = DocMathDisplay . decodeUtf8
+mathDisplay = DocMathDisplay . T.unpack
<$> ("\\[" *> takeUntil "\\]")
markdownImage :: Parser (DocH mod a)
@@ -282,20 +311,21 @@ markdownImage = fromHyperlink <$> ("!" *> linkParser)
-- | Paragraph parser, called by 'parseParas'.
paragraph :: Parser (DocH mod Identifier)
-paragraph = examples <|> table <|> do
- indent <- takeIndent
- choice
- [ since
- , unorderedList indent
- , orderedList indent
- , birdtracks
- , codeblock
- , property
- , header
- , textParagraphThatStartsWithMarkdownLink
- , definitionList indent
- , docParagraph <$> textParagraph
- ]
+paragraph = choice' [ examples
+ , table
+ , do indent <- takeIndent
+ choice' [ since
+ , unorderedList indent
+ , orderedList indent
+ , birdtracks
+ , codeblock
+ , property
+ , header
+ , textParagraphThatStartsWithMarkdownLink
+ , definitionList indent
+ , docParagraph <$> textParagraph
+ ]
+ ]
-- | Provides support for grid tables.
--
@@ -317,45 +347,45 @@ table :: Parser (DocH mod Identifier)
table = do
-- first we parse the first row, which determines the width of the table
firstRow <- parseFirstRow
- let len = BS.length firstRow
+ let len = T.length firstRow
-- then we parse all consequtive rows starting and ending with + or |,
-- of the width `len`.
- restRows <- many (parseRestRows len)
+ restRows <- many (try (parseRestRows len))
-- Now we gathered the table block, the next step is to split the block
-- into cells.
DocTable <$> tableStepTwo len (firstRow : restRows)
where
- parseFirstRow :: Parser BS.ByteString
+ parseFirstRow :: Parser Text
parseFirstRow = do
skipHorizontalSpace
-- upper-left corner is +
- c <- char '+'
- cs <- many1 (char '-' <|> char '+')
+ c <- Parsec.char '+'
+ cs <- some (Parsec.char '-' <|> Parsec.char '+')
-- upper right corner is + too
guard (last cs == '+')
-- trailing space
skipHorizontalSpace
- _ <- char '\n'
+ _ <- Parsec.newline
- return (BS.cons c $ BS.pack cs)
+ return (T.cons c $ T.pack cs)
- parseRestRows :: Int -> Parser BS.ByteString
+ parseRestRows :: Int -> Parser Text
parseRestRows l = do
skipHorizontalSpace
- c <- char '|' <|> char '+'
- bs <- scan (l - 2) predicate
- c2 <- char '|' <|> char '+'
+ c <- Parsec.char '|' <|> Parsec.char '+'
+ bs <- scan predicate (l - 2)
+ c2 <- Parsec.char '|' <|> Parsec.char '+'
-- trailing space
skipHorizontalSpace
- _ <- char '\n'
+ _ <- Parsec.newline
- return (BS.cons c (BS.snoc bs c2))
+ return (T.cons c (T.snoc bs c2))
where
predicate n c
| n <= 0 = Nothing
@@ -366,22 +396,22 @@ table = do
-- and changes to '=' to '-'.
tableStepTwo
:: Int -- ^ width
- -> [BS.ByteString] -- ^ rows
+ -> [Text] -- ^ rows
-> Parser (Table (DocH mod Identifier))
tableStepTwo width = go 0 [] where
go _ left [] = tableStepThree width (reverse left) Nothing
go n left (r : rs)
- | BS.all (`elem` ['+', '=']) r =
+ | T.all (`elem` ['+', '=']) r =
tableStepThree width (reverse left ++ r' : rs) (Just n)
| otherwise =
go (n + 1) (r : left) rs
where
- r' = BS.map (\c -> if c == '=' then '-' else c) r
+ r' = T.map (\c -> if c == '=' then '-' else c) r
-- Third step recognises cells in the table area, returning a list of TC, cells.
tableStepThree
:: Int -- ^ width
- -> [BS.ByteString] -- ^ rows
+ -> [Text] -- ^ rows
-> Maybe Int -- ^ index of header separator
-> Parser (Table (DocH mod Identifier))
tableStepThree width rs hdrIndex = do
@@ -408,32 +438,32 @@ tableStepThree width rs hdrIndex = do
scanRight :: Int -> Int -> Maybe (Int, Int)
scanRight x y = go (x + 1) where
bs = rs !! y
- go x' | x' >= width = fail "overflow right "
- | BS.index bs x' == '+' = scanDown x y x' <|> go (x' + 1)
- | BS.index bs x' == '-' = go (x' + 1)
- | otherwise = fail $ "not a border (right) " ++ show (x,y,x')
+ go x' | x' >= width = fail "overflow right "
+ | T.index bs x' == '+' = scanDown x y x' <|> go (x' + 1)
+ | T.index bs x' == '-' = go (x' + 1)
+ | otherwise = fail $ "not a border (right) " ++ show (x,y,x')
-- scan down looking for +
scanDown :: Int -> Int -> Int -> Maybe (Int, Int)
scanDown x y x2 = go (y + 1) where
- go y' | y' >= height = fail "overflow down"
- | BS.index (rs !! y') x2 == '+' = scanLeft x y x2 y' <|> go (y' + 1)
- | BS.index (rs !! y') x2 == '|' = go (y' + 1)
- | otherwise = fail $ "not a border (down) " ++ show (x,y,x2,y')
+ go y' | y' >= height = fail "overflow down"
+ | T.index (rs !! y') x2 == '+' = scanLeft x y x2 y' <|> go (y' + 1)
+ | T.index (rs !! y') x2 == '|' = go (y' + 1)
+ | otherwise = fail $ "not a border (down) " ++ show (x,y,x2,y')
-- check that at y2 x..x2 characters are '+' or '-'
scanLeft :: Int -> Int -> Int -> Int -> Maybe (Int, Int)
scanLeft x y x2 y2
- | all (\x' -> BS.index bs x' `elem` ['+', '-']) [x..x2] = scanUp x y x2 y2
- | otherwise = fail $ "not a border (left) " ++ show (x,y,x2,y2)
+ | all (\x' -> T.index bs x' `elem` ['+', '-']) [x..x2] = scanUp x y x2 y2
+ | otherwise = fail $ "not a border (left) " ++ show (x,y,x2,y2)
where
bs = rs !! y2
-- check that at y2 x..x2 characters are '+' or '-'
scanUp :: Int -> Int -> Int -> Int -> Maybe (Int, Int)
scanUp x y x2 y2
- | all (\y' -> BS.index (rs !! y') x `elem` ['+', '|']) [y..y2] = return (x2, y2)
- | otherwise = fail $ "not a border (up) " ++ show (x,y,x2,y2)
+ | all (\y' -> T.index (rs !! y') x `elem` ['+', '|']) [y..y2] = return (x2, y2)
+ | otherwise = fail $ "not a border (up) " ++ show (x,y,x2,y2)
-- | table cell: top left bottom right
data TC = TC !Int !Int !Int !Int
@@ -446,7 +476,7 @@ tcYS :: TC -> [Int]
tcYS (TC y _ y2 _) = [y, y2]
-- | Fourth step. Given the locations of cells, forms 'Table' structure.
-tableStepFour :: [BS.ByteString] -> Maybe Int -> [TC] -> Parser (Table (DocH mod Identifier))
+tableStepFour :: [Text] -> Maybe Int -> [TC] -> Parser (Table (DocH mod Identifier))
tableStepFour rs hdrIndex cells = case hdrIndex of
Nothing -> return $ Table [] rowsDoc
Just i -> case elemIndex i yTabStops of
@@ -464,7 +494,7 @@ tableStepFour rs hdrIndex cells = case hdrIndex of
init' [_] = []
init' (x : xs) = x : init' xs
- rowsDoc = (fmap . fmap) parseStringBS rows
+ rowsDoc = (fmap . fmap) parseParagraph rows
rows = map makeRow (init' yTabStops)
where
@@ -477,9 +507,9 @@ tableStepFour rs hdrIndex cells = case hdrIndex of
yts = length $ P.takeWhile (< y2) $ dropWhile (< y) yTabStops
-- extract cell contents given boundaries
- extract :: Int -> Int -> Int -> Int -> BS.ByteString
- extract x y x2 y2 = BS.intercalate "\n"
- [ BS.take (x2 - x + 1) $ BS.drop x $ rs !! y'
+ extract :: Int -> Int -> Int -> Int -> Text
+ extract x y x2 y2 = T.intercalate "\n"
+ [ T.take (x2 - x + 1) $ T.drop x $ rs !! y'
| y' <- [y .. y2]
]
@@ -487,7 +517,7 @@ tableStepFour rs hdrIndex cells = case hdrIndex of
since :: Parser (DocH mod a)
since = ("@since " *> version <* skipHorizontalSpace <* endOfLine) >>= setSince >> return DocEmpty
where
- version = decimal `sepBy1'` "."
+ version = decimal `Parsec.sepBy1` "."
-- | Headers inside the comment denoted with @=@ signs, up to 6 levels
-- deep.
@@ -498,38 +528,39 @@ since = ("@since " *> version <* skipHorizontalSpace <* endOfLine) >>= setSince
-- Right (DocHeader (Header {headerLevel = 2, headerTitle = DocString "World"}))
header :: Parser (DocH mod Identifier)
header = do
- let psers = map (string . encodeUtf8 . concat . flip replicate "=") [6, 5 .. 1]
- pser = foldl1 (<|>) psers
- delim <- decodeUtf8 <$> pser
- line <- skipHorizontalSpace *> nonEmptyLine >>= return . parseString
- rest <- paragraph <|> return DocEmpty
+ let psers = map (string . flip T.replicate "=") [6, 5 .. 1]
+ pser = choice' psers
+ delim <- T.unpack <$> pser
+ line <- skipHorizontalSpace *> nonEmptyLine >>= return . parseText
+ rest <- try paragraph <|> return DocEmpty
return $ DocHeader (Header (length delim) line) `docAppend` rest
textParagraph :: Parser (DocH mod Identifier)
-textParagraph = parseString . intercalate "\n" <$> many1 nonEmptyLine
+textParagraph = parseText . T.intercalate "\n" <$> some nonEmptyLine
textParagraphThatStartsWithMarkdownLink :: Parser (DocH mod Identifier)
textParagraphThatStartsWithMarkdownLink = docParagraph <$> (docAppend <$> markdownLink <*> optionalTextParagraph)
where
optionalTextParagraph :: Parser (DocH mod Identifier)
- optionalTextParagraph = (docAppend <$> whitespace <*> textParagraph) <|> pure DocEmpty
+ optionalTextParagraph = choice' [ docAppend <$> whitespace <*> textParagraph
+ , pure DocEmpty ]
whitespace :: Parser (DocH mod a)
whitespace = DocString <$> (f <$> takeHorizontalSpace <*> optional "\n")
where
- f :: BS.ByteString -> Maybe BS.ByteString -> String
+ f :: Text -> Maybe Text -> String
f xs (fromMaybe "" -> x)
- | BS.null (xs <> x) = ""
+ | T.null (xs <> x) = ""
| otherwise = " "
-- | Parses unordered (bullet) lists.
-unorderedList :: BS.ByteString -> Parser (DocH mod Identifier)
+unorderedList :: Text -> Parser (DocH mod Identifier)
unorderedList indent = DocUnorderedList <$> p
where
p = ("*" <|> "-") *> innerList indent p
-- | Parses ordered lists (numbered or dashed).
-orderedList :: BS.ByteString -> Parser (DocH mod Identifier)
+orderedList :: Text -> Parser (DocH mod Identifier)
orderedList indent = DocOrderedList <$> p
where
p = (paren <|> dot) *> innerList indent p
@@ -541,104 +572,110 @@ orderedList indent = DocOrderedList <$> p
-- same paragraph. Usually used as
--
-- > someListFunction = listBeginning *> innerList someListFunction
-innerList :: BS.ByteString -> Parser [DocH mod Identifier]
+innerList :: Text -> Parser [DocH mod Identifier]
-> Parser [DocH mod Identifier]
innerList indent item = do
c <- takeLine
(cs, items) <- more indent item
- let contents = docParagraph . parseString . dropNLs . unlines $ c : cs
+ let contents = docParagraph . parseText . dropNLs . T.unlines $ c : cs
return $ case items of
Left p -> [contents `docAppend` p]
Right i -> contents : i
-- | Parses definition lists.
-definitionList :: BS.ByteString -> Parser (DocH mod Identifier)
+definitionList :: Text -> Parser (DocH mod Identifier)
definitionList indent = DocDefList <$> p
where
p = do
- label <- "[" *> (parseStringBS <$> takeWhile1_ (notInClass "]\n")) <* ("]" <* optional ":")
+ label <- "[" *> (parseParagraph <$> takeWhile1_ (`notElem` ("]\n" :: String))) <* ("]" <* optional ":")
c <- takeLine
(cs, items) <- more indent p
- let contents = parseString . dropNLs . unlines $ c : cs
+ let contents = parseText . dropNLs . T.unlines $ c : cs
return $ case items of
Left x -> [(label, contents `docAppend` x)]
Right i -> (label, contents) : i
-- | Drops all trailing newlines.
-dropNLs :: String -> String
-dropNLs = reverse . dropWhile (== '\n') . reverse
+dropNLs :: Text -> Text
+dropNLs = T.dropWhileEnd (== '\n')
-- | Main worker for 'innerList' and 'definitionList'.
-- We need the 'Either' here to be able to tell in the respective functions
-- whether we're dealing with the next list or a nested paragraph.
-more :: Monoid a => BS.ByteString -> Parser a
- -> Parser ([String], Either (DocH mod Identifier) a)
-more indent item = innerParagraphs indent
- <|> moreListItems indent item
- <|> moreContent indent item
- <|> pure ([], Right mempty)
+more :: Monoid a => Text -> Parser a
+ -> Parser ([Text], Either (DocH mod Identifier) a)
+more indent item = choice' [ innerParagraphs indent
+ , moreListItems indent item
+ , moreContent indent item
+ , pure ([], Right mempty)
+ ]
-- | Used by 'innerList' and 'definitionList' to parse any nested paragraphs.
-innerParagraphs :: BS.ByteString
- -> Parser ([String], Either (DocH mod Identifier) a)
+innerParagraphs :: Text
+ -> Parser ([Text], Either (DocH mod Identifier) a)
innerParagraphs indent = (,) [] . Left <$> ("\n" *> indentedParagraphs indent)
-- | Attempts to fetch the next list if possibly. Used by 'innerList' and
-- 'definitionList' to recursively grab lists that aren't separated by a whole
-- paragraph.
-moreListItems :: BS.ByteString -> Parser a
- -> Parser ([String], Either (DocH mod Identifier) a)
+moreListItems :: Text -> Parser a
+ -> Parser ([Text], Either (DocH mod Identifier) a)
moreListItems indent item = (,) [] . Right <$> indentedItem
where
- indentedItem = string indent *> skipSpace *> item
+ indentedItem = string indent *> Parsec.spaces *> item
-- | Helper for 'innerList' and 'definitionList' which simply takes
-- a line of text and attempts to parse more list content with 'more'.
-moreContent :: Monoid a => BS.ByteString -> Parser a
- -> Parser ([String], Either (DocH mod Identifier) a)
+moreContent :: Monoid a => Text -> Parser a
+ -> Parser ([Text], Either (DocH mod Identifier) a)
moreContent indent item = first . (:) <$> nonEmptyLine <*> more indent item
-- | Parses an indented paragraph.
-- The indentation is 4 spaces.
-indentedParagraphs :: BS.ByteString -> Parser (DocH mod Identifier)
+indentedParagraphs :: Text -> Parser (DocH mod Identifier)
indentedParagraphs indent =
- (concat <$> dropFrontOfPara indent') >>= parseParagraphs
+ (T.unpack . T.concat <$> dropFrontOfPara indent') >>= parseParagraphs
where
- indent' = string $ BS.append indent " "
+ indent' = string $ indent <> " "
-- | Grab as many fully indented paragraphs as we can.
-dropFrontOfPara :: Parser BS.ByteString -> Parser [String]
+dropFrontOfPara :: Parser Text -> Parser [Text]
dropFrontOfPara sp = do
- currentParagraph <- some (sp *> takeNonEmptyLine)
+ currentParagraph <- some (try (sp *> takeNonEmptyLine))
followingParagraphs <-
- skipHorizontalSpace *> nextPar -- we have more paragraphs to take
- <|> skipHorizontalSpace *> nlList -- end of the ride, remember the newline
- <|> endOfInput *> return [] -- nothing more to take at all
+ choice' [ skipHorizontalSpace *> nextPar -- we have more paragraphs to take
+ , skipHorizontalSpace *> nlList -- end of the ride, remember the newline
+ , Parsec.eof *> return [] -- nothing more to take at all
+ ]
return (currentParagraph ++ followingParagraphs)
where
nextPar = (++) <$> nlList <*> dropFrontOfPara sp
nlList = "\n" *> return ["\n"]
-nonSpace :: BS.ByteString -> Parser BS.ByteString
+nonSpace :: Text -> Parser Text
nonSpace xs
- | not $ any (not . isSpace) $ decodeUtf8 xs = fail "empty line"
+ | T.all isSpace xs = fail "empty line"
| otherwise = return xs
-- | Takes a non-empty, not fully whitespace line.
--
-- Doesn't discard the trailing newline.
-takeNonEmptyLine :: Parser String
+takeNonEmptyLine :: Parser Text
takeNonEmptyLine = do
- (++ "\n") . decodeUtf8 <$> (takeWhile1 (/= '\n') >>= nonSpace) <* "\n"
+ l <- takeWhile1 (Parsec.noneOf "\n") >>= nonSpace
+ _ <- "\n"
+ pure (l <> "\n")
-- | Takes indentation of first non-empty line.
--
-- More precisely: skips all whitespace-only lines and returns indentation
-- (horizontal space, might be empty) of that non-empty line.
-takeIndent :: Parser BS.ByteString
+takeIndent :: Parser Text
takeIndent = do
indent <- takeHorizontalSpace
- "\n" *> takeIndent <|> return indent
+ choice' [ "\n" *> takeIndent
+ , return indent
+ ]
-- | Blocks of text of the form:
--
@@ -647,97 +684,98 @@ takeIndent = do
-- >> baz
--
birdtracks :: Parser (DocH mod a)
-birdtracks = DocCodeBlock . DocString . intercalate "\n" . stripSpace <$> many1 line
+birdtracks = DocCodeBlock . DocString . T.unpack . T.intercalate "\n" . stripSpace <$> some line
where
- line = skipHorizontalSpace *> ">" *> takeLine
+ line = try (skipHorizontalSpace *> ">" *> takeLine)
-stripSpace :: [String] -> [String]
+stripSpace :: [Text] -> [Text]
stripSpace = fromMaybe <*> mapM strip'
where
- strip' (' ':xs') = Just xs'
- strip' "" = Just ""
- strip' _ = Nothing
+ strip' t = case T.uncons t of
+ Nothing -> Just ""
+ Just (' ',t') -> Just t'
+ _ -> Nothing
-- | Parses examples. Examples are a paragraph level entitity (separated by an empty line).
-- Consecutive examples are accepted.
examples :: Parser (DocH mod a)
-examples = DocExamples <$> (many (skipHorizontalSpace *> "\n") *> go)
+examples = DocExamples <$> (many (try (skipHorizontalSpace *> "\n")) *> go)
where
go :: Parser [Example]
go = do
- prefix <- decodeUtf8 <$> takeHorizontalSpace <* ">>>"
+ prefix <- takeHorizontalSpace <* ">>>"
expr <- takeLine
(rs, es) <- resultAndMoreExamples
return (makeExample prefix expr rs : es)
where
- resultAndMoreExamples :: Parser ([String], [Example])
- resultAndMoreExamples = moreExamples <|> result <|> pure ([], [])
+ resultAndMoreExamples :: Parser ([Text], [Example])
+ resultAndMoreExamples = choice' [ moreExamples, result, pure ([], []) ]
where
- moreExamples :: Parser ([String], [Example])
+ moreExamples :: Parser ([Text], [Example])
moreExamples = (,) [] <$> go
- result :: Parser ([String], [Example])
+ result :: Parser ([Text], [Example])
result = first . (:) <$> nonEmptyLine <*> resultAndMoreExamples
- makeExample :: String -> String -> [String] -> Example
+ makeExample :: Text -> Text -> [Text] -> Example
makeExample prefix expression res =
- Example (strip expression) result
+ Example (T.unpack (T.strip expression)) result
where
- result = map (substituteBlankLine . tryStripPrefix) res
+ result = map (T.unpack . substituteBlankLine . tryStripPrefix) res
- tryStripPrefix xs = fromMaybe xs (stripPrefix prefix xs)
+ tryStripPrefix xs = fromMaybe xs (T.stripPrefix prefix xs)
substituteBlankLine "<BLANKLINE>" = ""
substituteBlankLine xs = xs
-nonEmptyLine :: Parser String
-nonEmptyLine = mfilter (any (not . isSpace)) takeLine
+nonEmptyLine :: Parser Text
+nonEmptyLine = try (mfilter (T.any (not . isSpace)) takeLine)
-takeLine :: Parser String
-takeLine = decodeUtf8 <$> takeWhile (/= '\n') <* endOfLine
+takeLine :: Parser Text
+takeLine = try (takeWhile (Parsec.noneOf "\n") <* endOfLine)
endOfLine :: Parser ()
-endOfLine = void "\n" <|> endOfInput
+endOfLine = void "\n" <|> Parsec.eof
-- | Property parser.
--
-- >>> snd <$> parseOnly property "prop> hello world"
-- Right (DocProperty "hello world")
property :: Parser (DocH mod a)
-property = DocProperty . strip . decodeUtf8 <$> ("prop>" *> takeWhile1 (/= '\n'))
+property = DocProperty . T.unpack . T.strip <$> ("prop>" *> takeWhile1 (Parsec.noneOf "\n"))
-- |
-- Paragraph level codeblock. Anything between the two delimiting \@ is parsed
-- for markup.
codeblock :: Parser (DocH mod Identifier)
codeblock =
- DocCodeBlock . parseStringBS . dropSpaces
+ DocCodeBlock . parseParagraph . dropSpaces
<$> ("@" *> skipHorizontalSpace *> "\n" *> block' <* "@")
where
dropSpaces xs =
- let rs = decodeUtf8 xs
- in case splitByNl rs of
+ case splitByNl xs of
[] -> xs
- ys -> case last ys of
- ' ':_ -> case mapM dropSpace ys of
- Nothing -> xs
- Just zs -> encodeUtf8 $ intercalate "\n" zs
+ ys -> case T.uncons (last ys) of
+ Just (' ',_) -> case mapM dropSpace ys of
+ Nothing -> xs
+ Just zs -> T.intercalate "\n" zs
_ -> xs
-- This is necessary because ‘lines’ swallows up a trailing newline
-- and we lose information about whether the last line belongs to @ or to
-- text which we need to decide whether we actually want to be dropping
-- anything at all.
- splitByNl = unfoldr (\x -> case x of
- '\n':s -> Just (span (/= '\n') s)
- _ -> Nothing)
- . ('\n' :)
+ splitByNl = unfoldr (\x -> case T.uncons x of
+ Just ('\n',x') -> Just (T.span (/= '\n') x')
+ _ -> Nothing)
+ . ("\n" <>)
- dropSpace "" = Just ""
- dropSpace (' ':xs) = Just xs
- dropSpace _ = Nothing
+ dropSpace t = case T.uncons t of
+ Nothing -> Just ""
+ Just (' ',t') -> Just t'
+ _ -> Nothing
- block' = scan False p
+ block' = scan p False
where
p isNewline c
| isNewline && c == '@' = Nothing
@@ -745,11 +783,11 @@ codeblock =
| otherwise = Just $ c == '\n'
hyperlink :: Parser (DocH mod a)
-hyperlink = angleBracketLink <|> markdownLink <|> autoUrl
+hyperlink = choice' [ angleBracketLink, markdownLink, autoUrl ]
angleBracketLink :: Parser (DocH mod a)
angleBracketLink =
- DocHyperlink . makeLabeled Hyperlink . decodeUtf8
+ DocHyperlink . makeLabeled Hyperlink
<$> disallowNewline ("<" *> takeUntil ">")
markdownLink :: Parser (DocH mod a)
@@ -759,7 +797,7 @@ linkParser :: Parser Hyperlink
linkParser = flip Hyperlink <$> label <*> (whitespace *> url)
where
label :: Parser (Maybe String)
- label = Just . strip . decode <$> ("[" *> takeUntil "]")
+ label = Just . decode . T.strip <$> ("[" *> takeUntil "]")
whitespace :: Parser ()
whitespace = skipHorizontalSpace <* optional ("\n" *> skipHorizontalSpace)
@@ -770,19 +808,25 @@ linkParser = flip Hyperlink <$> label <*> (whitespace *> url)
rejectWhitespace :: MonadPlus m => m String -> m String
rejectWhitespace = mfilter (all (not . isSpace))
- decode :: BS.ByteString -> String
- decode = removeEscapes . decodeUtf8
+ decode :: Text -> String
+ decode = T.unpack . removeEscapes
-- | Looks for URL-like things to automatically hyperlink even if they
-- weren't marked as links.
autoUrl :: Parser (DocH mod a)
autoUrl = mkLink <$> url
where
- url = mappend <$> ("http://" <|> "https://" <|> "ftp://") <*> takeWhile1 (not . isSpace)
- mkLink :: BS.ByteString -> DocH mod a
- mkLink s = case unsnoc s of
- Just (xs, x) | inClass ",.!?" x -> DocHyperlink (Hyperlink (decodeUtf8 xs) Nothing) `docAppend` DocString [x]
- _ -> DocHyperlink (Hyperlink (decodeUtf8 s) Nothing)
+ url = mappend <$> choice' [ "http://", "https://", "ftp://"] <*> takeWhile1 (Parsec.satisfy (not . isSpace))
+
+ mkLink :: Text -> DocH mod a
+ mkLink s = case T.unsnoc s of
+ Just (xs,x) | x `elem` (",.!?" :: String) -> DocHyperlink (mkHyperlink xs) `docAppend` DocString [x]
+ _ -> DocHyperlink (mkHyperlink s)
+
+ mkHyperlink :: Text -> Hyperlink
+ mkHyperlink lnk = Hyperlink (T.unpack lnk) Nothing
+
+
-- | Parses strings between identifier delimiters. Consumes all input that it
-- deems to be valid in an identifier. Note that it simply blindly consumes
@@ -790,14 +834,14 @@ autoUrl = mkLink <$> url
parseValid :: Parser String
parseValid = p some
where
- idChar = satisfyUnicode (\c -> isAlphaNum c || isSymbolChar c || c == '_')
+ idChar = Parsec.satisfy (\c -> isAlphaNum c || isSymbolChar c || c == '_')
p p' = do
vs <- p' idChar
c <- peekChar'
case c of
'`' -> return vs
- '\'' -> (\x -> vs ++ "'" ++ x) <$> ("'" *> p many') <|> return vs
+ '\'' -> choice' [ (\x -> vs ++ "'" ++ x) <$> ("'" *> p many), return vs ]
_ -> fail "outofvalid"
-- | Parses identifiers with help of 'parseValid'. Asks GHC for
@@ -809,4 +853,4 @@ identifier = do
e <- idDelim
return $ DocIdentifier (o, vid, e)
where
- idDelim = satisfy (\c -> c == '\'' || c == '`')
+ idDelim = Parsec.satisfy (\c -> c == '\'' || c == '`')