module Checkmate.Parser.IndentBlock
( parser
, parseSourceCode
, parseSourceFile
) where
import Control.Monad
import Data.List
import Data.Range.Range
import Data.Set
import Data.Text
import Data.Text.IO
import Text.Megaparsec
import Text.Megaparsec.Text
import Checkmate.Check
parseSourceFile :: FilePath
-> IO (Either (ParseError (Token Text) Dec) Checklist)
parseSourceFile filePath = do
input <- Data.Text.IO.readFile filePath
return $ parseSourceCode filePath input
parseSourceCode :: FilePath
-> Text
-> Either (ParseError (Token Text) Dec) Checklist
parseSourceCode = parse parser
data Line = CheckComment Text | Line | EmptyLine deriving (Eq, Ord, Show)
parser :: Parser Checklist
parser = do
lines' <- line `sepEndBy` eol
eof
let checkData = analyzeIndents 0 [] lines'
sorted = sort checkData
checks = [ Check (FileBlock path $ mkRange fromTo) i text
| (i, (path, fromTo, _, text)) <- Data.List.zip [1..] sorted
]
return $ Data.Set.fromList checks
where
mkRange :: (Int, Int) -> Range Int
mkRange (from, to)
| from >= to = SingletonRange from
| otherwise = SpanRange from to
indent :: Parser Int
indent = choice
[ tab >> return 8
, char ' ' >> return 1
]
someSpaces :: Parser ()
someSpaces = skipMany $ oneOf " \t"
checkKeyword :: Parser ()
checkKeyword = do
someSpaces
void $ string "CHECK"
someSpaces
lineCommentCheck :: Parser Text
lineCommentCheck = do
choice [void $ oneOf "#%'", void $ string "//", void $ string "--"]
checkKeyword
chars <- many $ noneOf "\n"
return $ pack chars
blockCommentPairs :: [(String, String)]
blockCommentPairs =
[ ("/*", "*/"), ("{-", "-}"), ("<!--", "-->"), ("<#", "#>")
, ("%{", "%}")
]
blockCommentCheck :: Parser Text
blockCommentCheck = choice $ fmap blockComment blockCommentPairs
blockComment :: (String, String) -> Parser Text
blockComment (start, end) = do
void $ string start
checkKeyword
chars <- manyTill anyChar (string end)
return $ pack chars
line :: Parser (FilePath, Int, Int, Line)
line = do
SourcePos filePath lineNo _ <- getPosition
widths <- many indent
lineT <- choice
[ try $ fmap CheckComment lineCommentCheck
, try $ fmap CheckComment blockCommentCheck
, try (some (noneOf "\n") >> return Line)
, return EmptyLine
]
return (filePath, read . show . unPos $ lineNo, sum widths, lineT)
analyzeIndents :: Int
-> [(FilePath, (Int, Int), Int, Text)]
-> [(FilePath, Int, Int, Line)]
-> [(FilePath, (Int, Int), Int, Text)]
analyzeIndents _ prev [] = prev
analyzeIndents prevDepth prev ((_, _, _, EmptyLine) : rest) =
analyzeIndents prevDepth prev rest
analyzeIndents prevDepth prev ((filePath, lineNo, depth, lineT) : rest) =
dedented ++ analyzeIndents depth next rest
where
isDedented :: (FilePath, (Int, Int), Int, Text) -> Bool
isDedented (_, _, checkDepth, _) =
prevDepth > depth && checkDepth > depth
(dedented, inScope) = Data.List.partition isDedented prev
next :: [(FilePath, (Int, Int), Int, Text)]
next =
[ (path, (from, lineNo), d, t)
| (path, (from, _), d, t) <- inScope
] ++ case lineT of
CheckComment t -> [(filePath, (lineNo, lineNo), depth, t)]
_ -> []