packages feed

nyan-interpolation-0.9: src/Text/Interpolation/Nyan/Full.hs

-- SPDX-FileCopyrightText: 2022 Serokell <https://serokell.io/>
--
-- SPDX-License-Identifier: MPL-2.0

{-# LANGUAGE CPP #-}

-- | Contains full-fledged interpolator.
module Text.Interpolation.Nyan.Full
  ( fullHaskellValueInterpolator
  ) where

import Control.Monad (when)
import Data.Char (isSpace)
import qualified Data.Text as T
import Language.Haskell.Exts.Parser (ParseMode (extensions, parseFilename),
                                     ParseResult (ParseFailed, ParseOk), defaultParseMode,
                                     parseExpWithMode)
import Language.Haskell.Meta.Syntax.Translate (toExp)
import Language.Haskell.TH (extsEnabled)

#if MIN_VERSION_haskell_src_meta(0,8,9)
import Data.Maybe (mapMaybe)
import Language.Haskell.Exts.Extension (Extension (..), Language (..))
import Language.Haskell.Exts.Parser (baseLanguage)
import Language.Haskell.Meta.Extensions (fromExtension)
#endif

import Text.Interpolation.Nyan.Core

{- | Interpolates strings containing arbitrary Haskell expressions.

This is used in the interpolator provided by "Text.Interpolation.Nyan" module.

Known issues:

* If @haskell-src-meta@ prior to @0.8.9@ version is used, a default set of
  extensions for the given Haskell dialect (e.g. Haskell2010) is used
  for the interpolated values.
  With the modern version of @haskell-src-meta@, we do our best to be trasparent
  and pick the extensions enabled in the module where interpolator is called
  (some rare extensions may still be unsupported).

-}
fullHaskellValueInterpolator :: ValueInterpolator
fullHaskellValueInterpolator = ValueInterpolator $ \txt -> do
  when (T.all isSpace txt) $
    fail "Empty placeholder"
  enabledExtensions <- extsEnabled
  let parseMode = defaultParseMode
        { parseFilename = "interpolator placeholder"
#if MIN_VERSION_haskell_src_meta(0,8,9)
        , baseLanguage = HaskellAllDisabled
#else
          -- use default language
#endif
        , extensions = providedExtensions enabledExtensions
        }
  case parseExpWithMode parseMode (T.unpack txt) of
    ParseFailed _loc e -> fail e
    ParseOk res        -> return (toExp res)
  where
    providedExtensions =
#if MIN_VERSION_haskell_src_meta(0,8,9)
      map EnableExtension . mapMaybe fromExtension
#else
      -- There is no easy way to do the conversion between template-haskell's
      -- and haskell-src-exts's Extension types, so using only language-default
      -- extensions (e.g. Haskell2010).
      --
      -- It may be tempting to hardcode some common extensions, but we better
      -- not do that, otherwise on @haskell-src-meta@ bump the user's code will
      -- start relying on the extensions enabled in the module and so may break,
      -- which would be extremly bad if occurs in a library.
      --
      -- Meanwhile, using all the language-default extensions seems safe,
      -- they all seem to be supported by relatively recent versions of
      -- @haskell-src-meta@. An issue will fire only if the user disabled
      -- some extension in the module, and this starts affecting the interpolator
      -- after the bump; let's treat that as a non-caught timely bug in the
      -- user's code.
      \_enabledExts -> []
#endif