{-# LANGUAGE OverloadedStrings #-} module Text.IndentToBrace ( i2b ) where import Control.Monad.Trans.Writer (execWriter, tell, Writer) import Data.List (isInfixOf) import qualified Data.Text as T i2b :: String -> String i2b = ($ []) . execWriter . mapM_ unnest . map addClosingCount . nest . map toL . stripComments . lines . filter (/= '\r') stripComments :: [String] -> [String] stripComments = map T.unpack . go False . map T.pack where go _ [] = [] go False (l:ls) = let (before, after') = T.breakOn "/*" l in case T.stripPrefix "/*" after' of Nothing -> l : go False ls Just after -> let (x:xs) = go True $ after : ls in before `T.append` x : xs go True (l:ls) = let (_, after') = T.breakOn "*/" l in case T.stripPrefix "*/" after' of Nothing -> T.empty : go True ls Just after -> go False $ after : ls data Line = Line { lineIndent :: Int , lineContent :: String } deriving (Show, Eq) data Nest = Nest Line Int [Nest] | Blank String deriving (Show, Eq) isBlank :: Nest -> Bool isBlank Blank{} = True isBlank _ = False addClosingCount :: Nest -> Nest addClosingCount (Blank x) = Blank x addClosingCount (Nest l c children) = Nest l c $ increment $ map addClosingCount children where increment | any (not . isBlank) children = increment' | otherwise = id increment' [] = error "should never happen" increment' (Blank x:rest) = Blank x : increment' rest increment' (n@(Nest l' c' children'):rest) | any (not . isBlank) rest = n : increment' rest | any (not . isBlank) children' = Nest l' c' (increment' children') : rest | otherwise = Nest l' (c' + 1) children' : rest toL :: String -> Either String Line toL s | null y = Left s | otherwise = Right $ Line (length x) y where (x, y) = span (== ' ') s nest :: [Either String Line] -> [Nest] nest [] = [] nest (Left x:rest) = Blank x : nest rest nest (Right l:rest) = Nest l 0 (nest inside) : nest outside where (inside, outside) = span isNested rest isNested Left{} = True isNested (Right l2) = lineIndent l2 > lineIndent l tell' :: String -> Writer (String -> String) () tell' s = tell (s ++) unnest :: Nest -> Writer (String -> String) () unnest (Blank x) = do tell' x tell' "\n" unnest (Nest l count inside) = do tell' $ replicate (lineIndent l) ' ' tell' $ lineContent l tell' $ case () of () | not $ all isBlank inside -> "{" | ";" `isInfixOf` lineContent l -> "" | otherwise -> ";" tell' $ replicate count '}' tell' "\n" mapM_ unnest inside