{-# LANGUAGE LambdaCase #-}

{- | Instead of converting explicitly between 'Expr's and 'MultiLet', it might
be nicer to use a pattern synonym:

> pattern MultiLet' :: NonEmpty (Binding s a) -> Expr s a -> Expr s a
> pattern MultiLet' as b <- (multiLetFromExpr -> Just (MultiLet as b)) where
>   MultiLet' as b = wrapInLets as b
>
> multiLetFromExpr :: Expr s a -> Maybe (MultiLet s a)
> multiLetFromExpr = \case
>     Let x mA a b -> Just (multiLet x mA a b)
>     _ -> Nothing

This works in principle, but GHC as of v8.8.1 doesn't handle it well:
https://gitlab.haskell.org/ghc/ghc/issues/17096

This should be fixed by GHC-8.10, so it might be worth revisiting then.
-}

module Dhall.Syntax.MultiLet
    ( MultiLet(..)
    , multiLet
    , wrapInLets
    ) where

import Data.List.NonEmpty   (NonEmpty (..))
import Dhall.Syntax.Binding (Binding)
import Dhall.Syntax.Expr    (Expr (..))

import qualified Data.List.NonEmpty as NonEmpty

{-| Generate a 'MultiLet' from the contents of a 'Let'.

    In the resulting @'MultiLet' bs e@, @e@ is guaranteed not to be a 'Let',
    but it might be a @('Note' … ('Let' …))@.

    Given parser output, 'multiLet' consolidates @let@s that formed a
    let-block in the original source.
-}
multiLet :: Binding s a -> Expr s a -> MultiLet s a
multiLet :: forall s a. Binding s a -> Expr s a -> MultiLet s a
multiLet Binding s a
b0 = \case
    Let Binding s a
b1 Expr s a
e1 ->
        let MultiLet NonEmpty (Binding s a)
bs Expr s a
e = forall s a. Binding s a -> Expr s a -> MultiLet s a
multiLet Binding s a
b1 Expr s a
e1
        in  forall s a. NonEmpty (Binding s a) -> Expr s a -> MultiLet s a
MultiLet (forall a. a -> NonEmpty a -> NonEmpty a
NonEmpty.cons Binding s a
b0 NonEmpty (Binding s a)
bs) Expr s a
e
    Expr s a
e -> forall s a. NonEmpty (Binding s a) -> Expr s a -> MultiLet s a
MultiLet (Binding s a
b0 forall a. a -> [a] -> NonEmpty a
:| []) Expr s a
e

{-| Wrap let-'Binding's around an 'Expr'.

'wrapInLets' can be understood as an inverse for 'multiLet':

> let MultiLet bs e1 = multiLet b e0
>
> wrapInLets bs e1 == Let b e0
-}
wrapInLets :: Foldable f => f (Binding s a) -> Expr s a -> Expr s a
wrapInLets :: forall (f :: * -> *) s a.
Foldable f =>
f (Binding s a) -> Expr s a -> Expr s a
wrapInLets f (Binding s a)
bs Expr s a
e = forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr forall s a. Binding s a -> Expr s a -> Expr s a
Let Expr s a
e f (Binding s a)
bs

{-| This type represents 1 or more nested `Let` bindings that have been
    coalesced together for ease of manipulation
-}
data MultiLet s a = MultiLet (NonEmpty (Binding s a)) (Expr s a)