{-# LANGUAGE LambdaCase #-}
module Data.String.Interpolate.Lines where
import Data.Function ( on )
import Data.List ( find )
import Data.Semigroup ( Min(..) )
import Data.String.Interpolate.Types
isBlankLine :: [InterpSegment] -> Bool
isBlankLine :: [InterpSegment] -> Bool
isBlankLine [] = Bool
True
isBlankLine (Expression String
_ : [InterpSegment]
_) = Bool
False
isBlankLine (Spaces Int
_ : [InterpSegment]
rest) = [InterpSegment] -> Bool
isBlankLine [InterpSegment]
rest
isBlankLine (Tabs Int
_ : [InterpSegment]
rest) = [InterpSegment] -> Bool
isBlankLine [InterpSegment]
rest
isBlankLine (Verbatim String
str:[InterpSegment]
rest) = String -> Bool
blank String
str Bool -> Bool -> Bool
&& [InterpSegment] -> Bool
isBlankLine [InterpSegment]
rest
where
blank :: String -> Bool
blank :: String -> Bool
blank = forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (\Char
c -> forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
elem Char
c [Char
' ', Char
'\t'])
displayLine :: Line -> String
displayLine :: [InterpSegment] -> String
displayLine = forall (t :: * -> *) m a.
(Foldable t, Monoid m) =>
(a -> m) -> t a -> m
foldMap InterpSegment -> String
displaySegment
where
displaySegment :: InterpSegment -> String
displaySegment :: InterpSegment -> String
displaySegment (Expression String
expr) = String
"#{" forall a. [a] -> [a] -> [a]
++ String
expr forall a. [a] -> [a] -> [a]
++ String
"}"
displaySegment (Verbatim String
str) = String
str
displaySegment (Spaces Int
n) = forall a. Int -> a -> [a]
replicate Int
n Char
' '
displaySegment (Tabs Int
n) = forall a. Int -> a -> [a]
replicate Int
n Char
'\t'
handleIndents :: Lines -> ([IndentWarning], Lines)
handleIndents :: Lines -> ([IndentWarning], Lines)
handleIndents Lines
lines =
let mindent :: Mindent
mindent = Lines -> Mindent
mindentation Lines
lines
in (Mindent -> Lines -> [IndentWarning]
findMixedIndents Mindent
mindent Lines
lines, Mindent -> Lines -> Lines
reduceIndents Mindent
mindent Lines
lines)
data Mindent = UsesSpaces Int | UsesTabs Int
data IndentWarning = IndentWarning
{ IndentWarning -> String
indentLine :: String
, IndentWarning -> Mindent
indentBase :: Mindent
}
mindentation :: Lines -> Mindent
mindentation :: Lines -> Mindent
mindentation Lines
lines =
let
nonblank :: Lines
nonblank = forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. [InterpSegment] -> Bool
isBlankLine) Lines
lines
withIndent :: Maybe [InterpSegment]
withIndent = forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find (\case { Spaces Int
_ : [InterpSegment]
_ -> Bool
True; Tabs Int
_ : [InterpSegment]
_ -> Bool
True; [InterpSegment]
_ -> Bool
False }) Lines
nonblank
in case Maybe [InterpSegment]
withIndent of
Maybe [InterpSegment]
Nothing -> Int -> Mindent
UsesSpaces Int
0
Just (Spaces Int
_ : [InterpSegment]
_) ->
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (Int -> Mindent
UsesSpaces Int
0) Int -> Mindent
UsesSpaces forall a b. (a -> b) -> a -> b
$
(InterpSegment -> Maybe Int) -> Maybe Int -> Lines -> Maybe Int
findMinIndent (\case { Spaces Int
n -> forall a. a -> Maybe a
Just Int
n; InterpSegment
_ -> forall a. Maybe a
Nothing }) forall a. Maybe a
Nothing Lines
nonblank
Just (Tabs Int
_ : [InterpSegment]
_) ->
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (Int -> Mindent
UsesSpaces Int
0) Int -> Mindent
UsesTabs forall a b. (a -> b) -> a -> b
$
(InterpSegment -> Maybe Int) -> Maybe Int -> Lines -> Maybe Int
findMinIndent (\case { Tabs Int
n -> forall a. a -> Maybe a
Just Int
n; InterpSegment
_ -> forall a. Maybe a
Nothing }) forall a. Maybe a
Nothing Lines
nonblank
Just [InterpSegment]
_ -> Int -> Mindent
UsesSpaces Int
0
where
findMinIndent :: (InterpSegment -> Maybe Int) -> Maybe Int -> [[InterpSegment]] -> Maybe Int
findMinIndent :: (InterpSegment -> Maybe Int) -> Maybe Int -> Lines -> Maybe Int
findMinIndent InterpSegment -> Maybe Int
_ Maybe Int
found [] = Maybe Int
found
findMinIndent InterpSegment -> Maybe Int
f Maybe Int
found ((InterpSegment
seg:[InterpSegment]
_):Lines
rest) =
(InterpSegment -> Maybe Int) -> Maybe Int -> Lines -> Maybe Int
findMinIndent InterpSegment -> Maybe Int
f (forall a. Min a -> a
getMin forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall b c a. (b -> b -> c) -> (a -> b) -> a -> a -> c
on forall a. Monoid a => a -> a -> a
mappend (forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap forall a. a -> Min a
Min) (InterpSegment -> Maybe Int
f InterpSegment
seg) Maybe Int
found) Lines
rest
findMinIndent InterpSegment -> Maybe Int
f Maybe Int
found ([]:Lines
rest) = (InterpSegment -> Maybe Int) -> Maybe Int -> Lines -> Maybe Int
findMinIndent InterpSegment -> Maybe Int
f Maybe Int
found Lines
rest
reduceIndents :: Mindent -> Lines -> Lines
reduceIndents :: Mindent -> Lines -> Lines
reduceIndents Mindent
_ [] = []
reduceIndents i :: Mindent
i@(UsesSpaces Int
indent) ((Spaces Int
n:[InterpSegment]
line):Lines
rest) =
(Int -> InterpSegment
Spaces (Int
nforall a. Num a => a -> a -> a
-Int
indent)forall a. a -> [a] -> [a]
:[InterpSegment]
line) forall a. a -> [a] -> [a]
: Mindent -> Lines -> Lines
reduceIndents Mindent
i Lines
rest
reduceIndents i :: Mindent
i@(UsesTabs Int
indent) ((Tabs Int
n:[InterpSegment]
line):Lines
rest) =
(Int -> InterpSegment
Tabs (Int
nforall a. Num a => a -> a -> a
-Int
indent)forall a. a -> [a] -> [a]
:[InterpSegment]
line) forall a. a -> [a] -> [a]
: Mindent -> Lines -> Lines
reduceIndents Mindent
i Lines
rest
reduceIndents Mindent
i ([InterpSegment]
line:Lines
rest) = [InterpSegment]
line forall a. a -> [a] -> [a]
: Mindent -> Lines -> Lines
reduceIndents Mindent
i Lines
rest
findMixedIndents :: Mindent -> Lines -> [IndentWarning]
findMixedIndents :: Mindent -> Lines -> [IndentWarning]
findMixedIndents Mindent
mindent = Lines -> [IndentWarning]
go
where
go :: [[InterpSegment]] -> [IndentWarning]
go :: Lines -> [IndentWarning]
go [] = []
go ([InterpSegment]
line:Lines
lines) = do
let
ind :: [InterpSegment]
ind = [InterpSegment] -> [InterpSegment]
indentation [InterpSegment]
line
warn :: IndentWarning
warn = IndentWarning
{ indentLine :: String
indentLine = [InterpSegment] -> String
displayLine [InterpSegment]
line, indentBase :: Mindent
indentBase = Mindent
mindent }
case (Mindent
mindent, forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any InterpSegment -> Bool
isSpaces [InterpSegment]
ind, forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any InterpSegment -> Bool
isTabs [InterpSegment]
ind) of
(UsesSpaces Int
_, Bool
_, Bool
True) -> IndentWarning
warn forall a. a -> [a] -> [a]
: Lines -> [IndentWarning]
go Lines
lines
(UsesTabs Int
_, Bool
True, Bool
_) -> IndentWarning
warn forall a. a -> [a] -> [a]
: Lines -> [IndentWarning]
go Lines
lines
(Mindent, Bool, Bool)
_ -> Lines -> [IndentWarning]
go Lines
lines
indentation :: [InterpSegment] -> [InterpSegment]
indentation :: [InterpSegment] -> [InterpSegment]
indentation =
forall a. (a -> Bool) -> [a] -> [a]
takeWhile (\case { Spaces Int
_ -> Bool
True; Tabs Int
_ -> Bool
True; InterpSegment
_ -> Bool
False })
isSpaces :: InterpSegment -> Bool
isSpaces :: InterpSegment -> Bool
isSpaces (Spaces Int
n) = Int
n forall a. Ord a => a -> a -> Bool
> Int
0
isSpaces InterpSegment
_ = Bool
False
isTabs :: InterpSegment -> Bool
isTabs :: InterpSegment -> Bool
isTabs (Tabs Int
n) = Int
n forall a. Ord a => a -> a -> Bool
> Int
0
isTabs InterpSegment
_ = Bool
False