lentil-1.2.2.0: src/Lentil/Parse/Source.hs
-----------------------------------------------------------------------------
-- |
-- Module : Lentil.Parse.Source
-- Copyright : © 2015 Francesco Ariis
-- License : GPLv3 (see the LICENSE file)
--
-- Comments from source files
-----------------------------------------------------------------------------
module Lentil.Parse.Source where
import Lentil.Types
import Lentil.Helpers
import Text.Megaparsec
import Text.Megaparsec.Char
import qualified Data.Char as C
import qualified Data.Maybe as M
import qualified Data.List as L
-----------
-- TYPES --
-----------
-- from source to comment strings
type ParSource a = StateParser () a
data ParSyntax = -- 'standard' language (with single line and/or multiline
-- markers, etc.
StdSyntax { psLineComms :: [String],
psBlockComms :: [(String, String)],
psStringStyle :: EscapeStyle,
psStringLits :: [Char],
psCharStyle :: CharStyle,
psCharLits :: [Char] }
| OrgModeSyntax -- org-mode parsing
| RstSyntax -- reStructuredText/sphinx parsing
deriving (Show)
data EscapeStyle = ClangLike | SQLLike
deriving (Show, Eq)
data CharStyle = CommonChr | ErlangChr
deriving (Show, Eq)
--------------
-- COMMENTS --
--------------
data CommentString = SingleLine Row String -- sans newline
| MultiLine Row String
deriving (Show, Eq)
-- parses stuff like "-- comment"
lineComment :: String -> ParSource CommentString
lineComment s = SingleLine <$>
(initId s *> getRow) <*>
manyTill anySingle eol
<?> "line comment"
where
-- for 1-char line comments (#, ;, etc.), erases repeating
initId :: String -> ParSource ()
initId [a] = () <$ some (char a)
initId as = () <$ string as
blockComment :: (String, String) -> ParSource CommentString
blockComment (i, e) = MultiLine <$>
(string i *> getRow) <*>
manyTill anySingle (try $ string e)
<?> "block comment"
getRow :: ParSource Row
getRow = fmap (unPos . sourceLine) getSourcePos
-------------
-- STRINGS --
-------------
litString :: EscapeStyle -> Char -> ParSource String
litString ClangLike ic = cString ic
litString SQLLike ic = sqlString ic
-- string ancillaries --
-- quoted strings, escaped by \, by mauke^
cString :: Char -> ParSource String
cString ic = q *> many ((char '\\' *> anySingle) <|> noneOf no) <* q
<?> "codestring"
where q = char ic
no = C.showLitChar ic "\\"
-- sqllike string (no escape with \, '' to escape ')
sqlString :: Char -> ParSource String
sqlString ic = char ic *> manyTill anySingle (char ic)
-- we treat 'cdscsad cdscdsa '' csdcs' as two strings
-- because the content is meaningless to our program
-----------
-- CHARS --
-----------
litChar :: CharStyle -> Char -> ParSource Char
litChar CommonChr ic = commonChar ic
litChar ErlangChr ic = erlangChar ic
-- quoted single character
commonChar :: Char -> ParSource Char
commonChar ic = q *> ((char '\\' *> anySingle) <|> anySingle) <* q
<?> "char string sign"
where q = char ic
-- $a for 'a' (where ic = '$')
erlangChar :: Char -> ParSource Char
erlangChar ic = char ic *> anySingle
------------------
-- OTHER BLOCKS --
------------------
-- a program is instructions to the computer. Ends when you meet
-- a well formed element from above (linecomm, blockcom, stringlit,
-- charLit)
program :: ParSyntax -> ParSource String
program ps = someTill anySingle (endp <|> ("" <$ eof))
<?> "program"
where
-- endp :: [ParSource String]
endp = lookAhead . choice . map try $ map string posts ++ [lchars]
-- every "post" symbol (init comments, listring char)
posts = psLineComms ps ++ -- line
map fst (psBlockComms ps) ++ -- block
map (:[]) (psStringLits ps) -- lit string
-- ambiguous symbols (pall'aaa'foo is valid haskell identifier,
-- so it should stay here
lchars = "" <$ choice (map (litChar (psCharStyle ps))
(psCharLits ps))
-----------------------
-- RST+SPHINX SYNTAX --
-----------------------
rstDocumentPart :: ParSource (Maybe CommentString)
rstDocumentPart = (choice . map try $ [Just <$> rstTodo, Nothing <$ rstOther])
<?> "rst document part"
-- `.. todo::` directive
rstTodo :: ParSource CommentString
rstTodo = MultiLine <$> getRow <*>
(rstTodoIncipit *> fmap modText todoBody)
<?> "rst todo directive"
where
startPara :: ParSource ()
startPara = eol *> (() <$ satisfy (not . C.isSpace) <|> eof)
todoBody :: ParSource String
todoBody = someTill anySingle
(lookAhead . try $ startPara <|> eof)
-- modText does 2 things
-- 1. injects a dummy todo keyword, having discarded `.. todo::`
-- 2. replaces '\n' with "\n ", so that issue is not broken
-- by newline
modText :: String -> String
modText cs = "TODO " ++ addSpace cs
addSpace :: String -> String
addSpace [] = []
addSpace ('\n':cs) = '\n' : ' ' : addSpace cs
addSpace (c:cs) = c : addSpace cs
-- everything else
rstOther :: ParSource String
rstOther = someTill anySingle (endp <|> eof)
<?> "rst other text"
where
endp = (lookAhead . try) (eol *> rstTodoIncipit) *>
eol *> pure () -- if you succeed, consume newline
rstTodoIncipit :: ParSource ()
rstTodoIncipit = () <$ string ".. todo::"
<?> "rst todo incipit"
---------------------
-- ORG-MODE SYNTAX --
---------------------
orgModeDocument :: ParSource (Maybe CommentString)
orgModeDocument = choice [Just <$> try orgTodo,
Just <$> try orgListMulti,
Just <$> try orgListSingle,
Nothing <$ orgOther]
<?> "org-mode document"
orgTodo :: ParSource CommentString
orgTodo = lineComment "*" <?> "org TODO"
orgListMulti :: ParSource CommentString
orgListMulti = MultiLine <$>
(orgInitList *> getRow) <*>
body
<?> "org-mode list item over multiple lines"
where
body :: ParSource String
body = (++) <$> pure " TODO " <*>
manyTill anySingle
-- A list item over multiple lines terminates either:
-- + when an empty lines follows the last
-- line of the current item,
-- + or when a new list item follows. In
-- this case, we must not consume the
-- header of the list item.
( try (eol *> eol) <|>
try (eol <* lookAhead (listMarker *> space)) <|>
("x" <$ eof)
)
orgListSingle :: ParSource CommentString
orgListSingle = SingleLine <$>
(orgInitList *> getRow) <*>
body
<?> "org-mode list item in a single-line"
where
body :: ParSource String
body = (++) <$> pure " TODO " <*> manyTill anySingle eol
-- - [ ] or + [ ] or 1. or 1)
orgInitList :: ParSource ()
orgInitList = listMarker *> space *>
checkBox *> space
where
checkBox :: ParSource ()
checkBox = () <$ string "[ ]"
listMarker :: ParSource Char
listMarker = choice
[char '-',
char '+',
numMark]
where
numMark :: ParSource Char
numMark = digitChar *> (char '.' <|> char ')')
orgOther :: ParSource ()
orgOther = choice
[() <$ eol, -- an empty line
() <$ someTill anySingle (endp <|> eof)] -- a non-empty one
<?> "org-mode other text"
where
endp = () <$ eol
------------
-- SOURCE --
------------
-- given a set of lineparsers / blockparsers
sourcePart :: ParSyntax -> ParSource (Maybe CommentString)
sourcePart OrgModeSyntax = orgModeDocument
sourcePart RstSyntax = rstDocumentPart
sourcePart ps@(StdSyntax lc bc es sl cs cl) =
choice [plc, pbc, psl, pcl, ppr]
<?> "source file part"
where
ct = choice . map try
plc = Just <$> (ct . map lineComment $ lc) -- line comm
pbc = Just <$> (ct . map blockComment $ bc) -- block comm
psl = Nothing <$ (choice . map (try . litString es) $ sl)
-- str lit
pcl = Nothing <$ (ct . map (litChar cs) $ cl) -- char lit
ppr = Nothing <$ program ps -- program
-- ps: syntax to sever comment from the rest
source :: ParSyntax -> ParSource [CommentString]
source ps = fmap M.catMaybes (some (sourcePart ps)) <|> ([] <$ eof)
<?> "source file"
-------------
-- CONVERT --
-------------
type Comment = (Row, String)
comms2Tuple :: [CommentString] -> [Comment]
comms2Tuple [] = []
comms2Tuple (c:cs) | isSl c = let (a,z) = span isSl (c:cs)
in groupLineComms a ++ comms2Tuple z
| otherwise = comment2Tuple c : comms2Tuple cs
where
isSl (SingleLine _ _) = True
isSl _ = False
-- use it on Line comments **only**!
groupLineComms :: [CommentString] -> [Comment]
groupLineComms cs = let cs' = map comment2Tuple cs
f (r, s) i = (r, s, r-i)
zipped = zipWith f cs' (enumFrom 1)
grouped = L.groupBy threeEq zipped
in map (flatComm . map back) grouped
where
threeEq (_, _, x) (_, _, y) = x == y
back (a, b, _) = (a, b)
flatComm cs' = (fst . head $ cs',
unlines . map snd $ cs')
comment2Tuple :: CommentString -> Comment
comment2Tuple (SingleLine r t) = (r, t)
comment2Tuple (MultiLine r t) = (r, t)