module Language.Preprocessor.Unlit
(
unlit
, UnlitError (..)
, unlitLinesWith
, ghcParsers
, birdParser
, latexParser
, cppParser
, pandocMarkdownParser
, Parser (..)
, Classified (..)
, Active (..)
, lines
, unlines
) where
import Prelude hiding (lines, unlines)
import Control.Arrow (first, second, (&&&))
import Data.Char (isSpace)
import Data.Function
import Data.List hiding (lines, unlines)
import Data.Maybe (fromMaybe)
import qualified Data.Text as T
unlit :: T.Text -> ([UnlitError], T.Text)
unlit = second unlines . unlitLinesWith ghcParsers . lines
unlitLinesWith :: [Parser T.Text] -> [T.Text] -> ([UnlitError], [T.Text])
unlitLinesWith parsers
= (checkErrors &&& map unclassify)
. classify parsers
where
checkErrors :: [Classified x] -> [UnlitError]
checkErrors [] = []
checkErrors (c:cs) = concat $ zipWith3 adjacentError [1..] (c:cs) (cs ++ [Blank])
adjacentError :: Integer -> Classified x -> Classified x -> [UnlitError]
adjacentError _ (Program NeedHug _ _) Blank = []
adjacentError _ Blank (Program NeedHug _ _) = []
adjacentError ln (Program NeedHug _ _) _ = [NotFollowedByBlank ln]
adjacentError ln _ (Program NeedHug _ _) = [NotPrecededByBlank (succ ln)]
adjacentError _ _ _ = []
data UnlitError
= NotPrecededByBlank Integer
| NotFollowedByBlank Integer
data Classified x
= Program NeedHug Active x
| Comment
| Blank
data NeedHug
= NeedHug
| Whatever
data Active
= Active
| Inactive
newtype Parser x = Parser (x -> [x] -> Maybe ([Classified x], Maybe [x]))
classify :: [Parser T.Text] -> [T.Text] -> [Classified T.Text]
classify parsers = go
where
go [] = []
go (x:xs)
| Just (cs, more) <- checkParsers x xs
= cs ++ go (fromMaybe [] more)
| T.all isSpace x = Blank : go xs
| otherwise = Comment : go xs
checkParsers p1 p2
= foldr orElse Nothing
. map (\(Parser f) -> f p1 p2)
$ parsers
unclassify :: Classified T.Text -> T.Text
unclassify (Program _ Active x) = x
unclassify _ = T.empty
ghcParsers :: [Parser T.Text]
ghcParsers = [latexParser, birdParser, cppParser]
latexParser :: Parser T.Text
latexParser = Parser p
where
p ln lns
| Just sbz <- T.pack "\\begin{code}" `T.stripPrefix` sln
= block sbz Active (T.pack "\\end{code}") lns
| Just sbz <- T.pack "\\begin{pseudocode}" `T.stripPrefix` sln
= block sbz Active (T.pack "\\end{pseudocode}") lns
| otherwise = Nothing
where
sln = T.dropWhile isSpace ln
block sbz act delim
| not (T.all isSpace sbz)
= block T.empty act delim
block _ act delim = Just . first (Blank:) . go
where
go [] = ([], Nothing)
go (ln:lns)
| Just _sbz <- delim `T.stripPrefix` sln
= ([Blank], Just lns)
| otherwise = first (Program Whatever act ln:) (go lns)
where
sln = T.dropWhile isSpace ln
birdParser :: Parser T.Text
birdParser = Parser p
where
p ln lns
| T.pack ">" `T.isPrefixOf` sln = result Active
| T.pack "<" `T.isPrefixOf` sln = result Inactive
| otherwise = Nothing
where
(prefix, sln) = T.span isSpace ln
rln = T.concat [prefix, T.pack " ", T.drop 1 ln]
result act = Just ([Program NeedHug act rln], Just lns)
cppParser :: Parser T.Text
cppParser = Parser p
where
p ln lns
| T.pack "#" `T.isPrefixOf` ln
= Just ([Program Whatever Active ln], Just lns)
| otherwise = Nothing
pandocMarkdownParser :: (T.Text -> Active) -> Parser T.Text
pandocMarkdownParser testAttributes = Parser p
where
p ln lns
| T.pack "```" `T.isPrefixOf` ln
= block (testAttributes btTail) btFence lns
| T.pack "~~~" `T.isPrefixOf` ln
= block (testAttributes tiTail) tiFence lns
| otherwise = Nothing
where
(btFence, btTail) = T.span (== '`') ln
(tiFence, tiTail) = T.span (== '~') ln
block act delim = Just . first (Blank:) . go
where
go [] = ([], Nothing)
go (ln:lns)
| delim `T.isPrefixOf` ln = ([Blank], Just lns)
| otherwise = first (Program NeedHug act ln:) (go lns)
lines :: T.Text -> [T.Text]
lines = linesWith [T.pack "\r\n", T.pack "\r", T.pack "\n"]
unlines :: [T.Text] -> T.Text
unlines = unlinesWith (T.pack "\n")
linesWith :: [T.Text] -> T.Text -> [T.Text]
linesWith delimiters tx = if T.null tx then [] else go tx
where
ds = sortBy (compare `on` (negate . T.length)) delimiters
stripAnyPrefix t = foldr orElse Nothing . map (flip T.stripPrefix t) $ ds
dh = nub . map (T.head) . filter (not . T.null) $ delimiters
go t = if T.null tt then [th]
else case stripAnyPrefix tt of
Just tn -> th : go tn
Nothing -> case go tt' of
[] -> [th']
(y:ys) -> T.append th' y:ys
where
(th, tt) = T.span (not . (`elem` dh)) t
(th', tt') = (th' `T.snoc` T.head tt', T.tail tt')
unlinesWith :: T.Text -> [T.Text] -> T.Text
unlinesWith nl = T.concat . map (`T.append` nl)
orElse :: Maybe a -> Maybe a -> Maybe a
orElse Nothing y = y
orElse x _ = x