polyglot-0.1.1.0: src/Lexers/Haskell/Layout.hs
module Lexers.Haskell.Layout where
import Parsers.Haskell.Common (anyComment)
import Utils.Foldable (hasNone, hasSome)
import Utils.List (safeHead, safeTail)
import Utils.String (joinWords, wrapCurly', wrapDoubleQuotes', wrapParens',
wrapQuotes')
import Bookhound.Parser (ParseError, Parser, check, runParser,
withError)
import Bookhound.ParserCombinators (IsMatch (is, isNot, noneOf, oneOf), (->>-),
(<|>), (|*), (|+), (|?))
import Bookhound.Parsers.Char (char, space)
import Bookhound.Parsers.String (spacing, withinDoubleQuotes, withinParens,
withinQuotes, word)
import Control.Monad (foldM)
import Data.Foldable (Foldable (fold))
import Data.List (isPrefixOf)
import Data.Monoid.HT (when)
import Data.Text (Text, pack)
adaptLayout :: Text -> Either [ParseError] Text
adaptLayout str = pack . (++ " }") . unlines . fst4 <$> layoutLines
where
layoutLines = foldM layout args . (++ pure "") . filter hasSome =<< input
input = lines . fold <$> runParser parensLayout str
args = ([], [], False, False)
fst4 (x, _, _, _) = x
layout :: ([String], [Int], Bool, Bool) -> String -> Either [ParseError] ([String], [Int], Bool, Bool)
layout (x, y, z, t) str = runParser layoutParser $ pack str
where
layoutParser = withError "Layout lexer" $
do spaces' <- (space |*)
beginning <- otherText
layoutText <- (layoutBegin |?)
spaces'' <- (space |*)
rest <- otherText
let hasIn = maybe False ("in" ==) (safeHead $ words beginning)
hasCurly = isPrefixOf "{" rest
indents = when z [length spaces'] ++
if not hasIn then y
else (length spaces' + 1) : (fold $ safeTail y)
layoutNextLine = hasSome layoutText && hasNone rest
contextIndent = length $ spaces' ++ beginning ++ fold layoutText ++ spaces''
(newIndents, beginSep, stop) = calcIndent indents (length spaces')
(t || hasCurly) beginning
endSep = when (hasSome layoutText && not hasCurly) " {"
indents' = when (hasSome layoutText && hasSome rest)
[contextIndent] ++ newIndents
text = x ++ [spaces' ++ beginSep ++ beginning ++ fold
layoutText ++ endSep ++ spaces'' ++ rest]
pure $ (text, indents', layoutNextLine, stop || hasCurly)
parensLayout :: Parser [String]
parensLayout = (((spacing |?) ->>- elem'
<|> parensParser
<|> (wrapParens' . fold <$> withinParens parensLayout) ->>-
(spacing |?)) |*)
where
elem' = lexeme' id
parensParser = wrapParens' <$> withinParens
((spacing |?) ->>- layoutBegin ->>- spacing ->>-
(wrapCurly' . fold <$> parensLayout))
calcIndent :: [Int] -> Int -> Bool -> String -> ([Int], String, Bool)
calcIndent indentLvls curr stop beginning =
(newIndentLvls, joinWords [closeContexts, sep], shouldStop)
where
extraElems = if not stop then extra else fold $ safeTail extra
closeContexts = fold ("} " <$ extraElems)
shouldStop = stop && hasNone extra
sep = when (any (== curr) (safeHead newIndentLvls) &&
notElem beginning nestTokens) "; "
(extra, newIndentLvls) = span (curr <) indentLvls
nestTokens :: [String]
nestTokens = ["then", "else"]
layoutTokens :: [String]
layoutTokens = [("(" ++), id] <*> ["where", "let", "do", "of", "\\case"]
layoutBegin :: Parser String
layoutBegin = oneOf layoutTokens
lexeme :: Parser String
lexeme = wrapDoubleQuotes' <$> withinDoubleQuotes (isNot '"' |+)
<|> wrapQuotes' . pure <$> withinQuotes (char <|> (is '\\' |?) *> char)
<|> anyComment
<|> word
otherText :: Parser String
otherText = fold <$> elems
where
elems = (((check "" (`notElem` layoutTokens) lexeme) ->>- (space |*)) |*)
lexeme' :: (Parser String -> Parser String) -> Parser String
lexeme' f = (spacing |?) ->>- f lexeme ->>- (spacing |?)
otherText' :: Parser String
otherText' = lexeme' (check "" (`notElem` layoutTokens))
word' :: Parser String
word' = ((noneOf [' ', '\n', '\t', '(', ')']) |+)