{-# LANGUAGE FlexibleInstances , OverlappingInstances , TypeSynonymInstances , UndecidableInstances #-} module Code.Build where import Data.List -- | Representation of code, each string represents a line newtype Code = Code { unCode :: [String] } showCode :: Code -> String showCode = intercalate "\n" . unCode -- | Type class for lifting data structures into code class Codeable a where code :: a -> Code instance Codeable Code where code = id instance Codeable String where code = Code . (:[]) instance Codeable a => Codeable (Maybe a) where code = maybe noCode code instance Show a => Codeable a where code = Code . (:[]) . show class CodeList a where codeList :: a -> [Code] instance Codeable a => CodeList a where codeList = (:[]) . code instance Codeable a => CodeList [a] where codeList = map code -- * Functions on code noCode :: Code noCode = Code [] line :: Code line = Code [""] isNull :: Codeable a => a -> Bool isNull = null . unCode . code numLines :: CodeList a => a -> Int numLines = length . unCode . mkStack . codeList singleLine :: CodeList a => a -> Bool singleLine = (==1) . numLines indent :: Codeable a => Int -> a -> Code indent n = Code . map (replicate n ' ' ++) . unCode . code surround :: Codeable a => String -> String -> a -> Code surround l r a = l <+> a <+> r parenthesis :: Codeable a => a -> Code parenthesis = surround "(" ")" accolades :: Codeable a => a -> Code accolades = surround "{" "}" square :: Codeable a => a -> Code square = surround "[" "]" align :: Codeable a => a -> Code align v = Code . map addWhite . unCode . code $ v where addWhite l = l ++ replicate (codeWidth v - length l) ' ' codeWidth :: Codeable a => a -> Int codeWidth = foldr max 0 . map length . unCode . code codeLines :: Codeable a => a -> [Code] codeLines = map (Code . (:[])) . unCode . code many :: Codeable a => a -> Code many = Code . concat . repeat . unCode . code mkSequence :: Codeable a => [a] -> Code mkSequence = foldl (<+>) noCode . map code mkStack :: Codeable a => [a] -> Code mkStack = foldl (<->) noCode . map code interleave :: (Codeable a, CodeList l) => a -> l -> Code interleave c l = case codeList l of [] -> noCode [x] -> x (x: xs) -> x <+> code c <+> interleave c xs -- * Combinators for building blocks of code infixl 4 <+> infixl 4 <++> infixl 4 <+| infixl 3 |>+<| infixl 3 |><| infixl 3 >< infixl 3 |>< infixl 3 ><| infixl 2 <-> -- | Join two blocks line by line, in the way of inner join, so both lines have to be present. (|><|) :: (Codeable a, Codeable b) => a -> b -> Code a |><| b = Code $ zipWith (++) ca cb where ca = unCode . code $ a cb = unCode . code $ b -- | Join two blocks line by line, in the way of outer join, so both missing lines are discarded. (><) :: (Codeable a, Codeable b) => a -> b -> Code a >< b = a <-> mkStack (replicate ((numLines (code b) - numLines (code a)) `max` 0) line) |><| b <-> mkStack (replicate ((numLines (code a) - numLines (code b)) `max` 0) line) -- | Left outer-join (|><) :: (Codeable a, Codeable b) => a -> b -> Code a |>< b = a <-> mkStack (replicate ((numLines (code b) - numLines (code a)) `max` 0) line) |><| b -- | Right outer-join (><|) :: (Codeable a, Codeable b) => a -> b -> Code a ><| b = a |><| b <-> mkStack (replicate ((numLines (code a) - numLines (code b)) `max` 0) line) -- | Sequencing. Place the second block after the last line of the first block. Aligns the second block (<+>) :: (Codeable a, Codeable b) => a -> b -> Code a <+> b = case ca of [] -> code b ls -> case cb of [] -> code a (bl: bls) -> Code $ init ls ++ [last ls ++ bl] ++ bls where ca = unCode . code $ a cb = unCode . code $ b -- | Same as <++> but with space (<++>) :: (Codeable a, Codeable b) => a -> b -> Code a <++> b | empty a = code b | empty b = code a | otherwise = a <+> " " <+> b where empty x = all (== "") (unCode (code x)) -- | Place the second block after the last line of the first block. Aligns the second block (<+|) :: (Codeable a, Codeable b) => a -> b -> Code a <+| b = case ca of [] -> code b ls -> case cb of [] -> code a (bl: bls) -> Code $ init ls ++ [last ls ++ bl] ++ map (replicate (length $ last ls) ' ' ++) bls where ca = unCode . code $ a cb = unCode . code $ b -- | Combination of join and sequence. The code blocks in the second argument are sequenced with the first argument. (|>+<|) :: (Codeable a, CodeList b) => a -> b -> Code a |>+<| b = mkStack $ zipWith (<+|) (codeLines a) (codeList b) -- | Place two pieces of code under each other (<->) :: (Codeable a, Codeable b) => a -> b -> Code a <-> b = Code $ (unCode $ code a) ++ (unCode $ code b)