    {-# 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 = Binding s a -> Expr s a -> MultiLet s a
    forall s a. Binding s a -> Expr s a -> MultiLet s a
    multiLet Binding s a
    b1 Expr s a
    e1
            in  NonEmpty (Binding s a) -> Expr s a -> MultiLet s a
    forall s a. NonEmpty (Binding s a) -> Expr s a -> MultiLet s a
    MultiLet (Binding s a -> NonEmpty (Binding s a) -> NonEmpty (Binding s a)
    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 -> NonEmpty (Binding s a) -> Expr s a -> MultiLet s a
    forall s a. NonEmpty (Binding s a) -> Expr s a -> MultiLet s a
    MultiLet (Binding s a
    b0 Binding s a -> [Binding s a] -> NonEmpty (Binding s a)
    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 = (Binding s a -> Expr s a -> Expr s a)
    -> Expr s a -> f (Binding s a) -> Expr s a
    forall a b. (a -> b -> b) -> b -> f a -> b
    forall (t :: * -> *) a b.
    Foldable t =>
    (a -> b -> b) -> b -> t a -> b
    foldr Binding s a -> Expr s a -> Expr s a
    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)