packages feed

argo-0.2021.11.7: source/library/Argo/Encoder.hs

module Argo.Encoder where

import qualified Argo.Literal as Literal
import qualified Argo.Vendor.Builder as Builder
import qualified Argo.Vendor.Transformers as Trans
import qualified Control.Monad as Monad
import qualified Data.Functor.Identity as Identity
import qualified Data.Semigroup as Semigroup

type Encoder = Trans.ReaderT Config (Trans.WriterT Builder.Builder Identity.Identity)

run :: Config -> Encoder a -> (a, Builder.Builder)
run c = Identity.runIdentity . Trans.runWriterT . flip Trans.runReaderT c

data Config = Config
    { indent :: Indent
    , level :: Int
    } deriving (Eq, Show)

defaultConfig :: Config
defaultConfig = Config
    { indent = Spaces 0
    , level = 0
    }

data Indent
    = Spaces Int
    | Tab
    deriving (Eq, Show)

hasIndent :: Config -> Bool
hasIndent x = case indent x of
    Spaces y -> y > 0
    Tab -> True

increaseLevel :: Config -> Config
increaseLevel x = x { level = level x + 1 }

list :: Encoder () -> Encoder () -> Encoder () -> (a -> Encoder ()) -> [a] -> Encoder ()
list l r s f xs = case xs of
    [] -> do
        l
        r
    x : ys -> do
        l
        c <- Trans.ask
        let newLine = if hasIndent c then Builder.word8 Literal.newLine else mempty
        Trans.local increaseLevel $ do
            i <- makeIndent <$> Trans.ask
            Trans.lift . Trans.tell $ newLine <> i
            f x
            Monad.forM_ ys $ \ y -> do
                s
                Trans.lift . Trans.tell $ newLine <> i
                f y
        Trans.lift . Trans.tell $ newLine <> makeIndent c
        r

makeIndent :: Config -> Builder.Builder
makeIndent x = case indent x of
    Spaces y -> if y <= 0 then mempty else
        Semigroup.stimesMonoid (level x)
        . Semigroup.stimes y
        $ Builder.word8 Literal.space
    Tab -> Semigroup.stimesMonoid (level x)
        $ Builder.word8 Literal.horizontalTabulation