-- Copyright (c) Microsoft. All rights reserved.
-- Licensed under the MIT license. See LICENSE file in the project root for full license information.

{-# LANGUAGE OverloadedStrings #-}

{-|
Copyright   : (c) Microsoft
License     : MIT
Maintainer  : adamsap@microsoft.com
Stability   : alpha
Portability : portable

Helper functions for combining elements into common constructs. These functions
can be used in code generation to lazily combine 'Text' elements but they are
more generic and work for any 'Monoid'.
-}

module Language.Bond.Util
    ( sepBy
    , sepEndBy
    , sepBeginBy
    , optional
    , angles
    , brackets
    , braces
    , parens
    , between
    ) where

import Data.Monoid
import Data.String (IsString)
import Prelude

-- | Maps elements of a list and combines them with 'mappend' using given
-- separator, ending with a separator.
sepEndBy :: (Monoid a, Eq a) => a -> (t -> a) -> [t] -> a
sepEndBy _ _ [] = mempty
sepEndBy s f (x:xs) 
    | next == mempty = rest
    | otherwise = next <> s <> rest
        where
            next = f x
            rest = sepEndBy s f xs

-- | Maps elements of a list and combines them with 'mappend' using given
-- separator, starting with a separator.
sepBeginBy :: (Monoid a, Eq a) => a -> (t -> a) -> [t] -> a
sepBeginBy _ _ [] = mempty
sepBeginBy s f (x:xs)
    | next == mempty = rest 
    | otherwise = s <> next <> rest
    where
        next = f x
        rest = sepBeginBy s f xs

-- | Maps elements of a list and combines them with 'mappend' using given
-- separator.
sepBy :: (Monoid a, Eq a) => a -> (t -> a) -> [t] -> a
sepBy _ _ [] = mempty
sepBy s f (x:xs)
    | null xs = next
    | next == mempty = rest
    | otherwise = next <> sepBeginBy s f xs
        where
            next = f x
            rest = sepBy s f xs

-- | The function takes a function and a Maybe value. If the Maybe value is
-- Nothing, the function returns 'mempty', otherwise, it applies the function
-- to the value inside 'Just' and returns the result.
optional :: (Monoid m) => (a -> m) -> Maybe a -> m
optional = maybe mempty

-- | If the 3rd argument is not 'mempty' the function wraps it between the
-- first and second argument using 'mappend', otherwise it return 'mempty'.
between :: (Monoid a, Eq a) => a -> a -> a -> a
between l r m
    | m == mempty = mempty
    | otherwise = l <> m <> r

angles, brackets, braces, parens :: (Monoid a, IsString a, Eq a) => a -> a
-- | Wraps the string argument between @<@ and @>@, unless the argument is
-- 'mempty' in which case the function returns 'mempty'.
angles m = between "<" ">" m

-- | Wraps the string argument between @[@ and @]@, unless the argument is
-- 'mempty' in which case the function returns 'mempty'.
brackets m = between "[" "]" m

-- | Wraps the string argument between @{@ and @}@, unless the argument is
-- 'mempty' in which case the function returns 'mempty'.
braces m = between "{" "}" m

-- | Wraps the string argument between @(@ and @)@, unless the argument is
-- 'mempty' in which case the function returns 'mempty'.
parens m = between "(" ")" m

