ede-0.3.4.0: lib/Text/EDE/Internal/Quoting.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}
-- |
-- Module : Text.EDE.Internal.Quoting
-- Copyright : (c) 2013-2022 Brendan Hay <brendan.g.hay@gmail.com>
-- License : This Source Code Form is subject to the terms of
-- the Mozilla Public License, v. 2.0.
-- A copy of the MPL can be found in the LICENSE file or
-- you can obtain it at http://mozilla.org/MPL/2.0/.
-- Maintainer : Brendan Hay <brendan.g.hay@gmail.com>
-- Stability : experimental
-- Portability : non-portable (GHC extensions)
--
-- /Warning/: this is an internal module, and does not have a stable
-- API or name. Functions in this module may not check or enforce
-- preconditions expected by public modules. Use at your own risk!
module Text.EDE.Internal.Quoting where
import Control.Applicative ((<|>))
import Control.Monad ((>=>))
import Data.Aeson (FromJSON, ToJSON)
import qualified Data.Aeson as Aeson
import Data.Aeson.Types (Value (..))
import qualified Data.Bifunctor as Bifunctor
import qualified Data.ByteString.Char8 as ByteString.Char8
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HashMap
import Data.List (sortBy)
import Data.Ord (comparing)
import Data.Scientific (Scientific)
import qualified Data.Scientific as Scientific
import Data.Text (Text)
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text.Encoding
import qualified Data.Text.Lazy as Text.Lazy
import Data.Text.Lazy.Builder (Builder)
import qualified Data.Text.Lazy.Builder as Text.Builder
import Data.Text.Manipulate (toOrdinal)
import Data.Vector (Vector)
import qualified Data.Vector as Vector
import Prettyprinter (Pretty (..), (<+>))
import qualified Prettyprinter as PP
import Text.EDE.Internal.Types
import Text.Trifecta.Delta (Delta)
import qualified Text.Trifecta.Delta as Trifecta.Delta
import qualified Text.Trifecta.Rendering as Trifecta.Rendering
#if MIN_VERSION_aeson(2,0,0)
import Data.Aeson.KeyMap (KeyMap)
#endif
-- | A HOAS representation of (possibly partially applied) values
-- in the environment.
data Term
= TVal !Value
| TLam (Term -> Result Term)
instance AnsiPretty Term where
apretty = \case
TLam _ -> "Function"
TVal v -> bold (pp v)
-- | Fully apply two 'Term's.
qapply :: Delta -> Term -> Term -> Result Term
qapply d a b = case (a, b) of
(TLam f, x) ->
case f x of
Failure e -> Failure (Trifecta.Delta.prettyDelta d <+> red "error:" <+> e)
Success y -> pure y
(TVal x, _) ->
Failure $
"unable to apply literal"
<+> apretty a
<+> "->"
<+> apretty b
</> pp x
{-# INLINEABLE qapply #-}
-- | Quote a primitive 'Value' from the top-level.
qprim :: (ToJSON a, Quote a) => a -> Term
qprim = quote "Value" 0
{-# INLINEABLE qprim #-}
class Unquote a where
unquote :: Id -> Int -> Term -> Result a
default unquote :: FromJSON a => Id -> Int -> Term -> Result a
unquote k n = \case
f@TLam {} -> typeErr k n (apretty f) "Value"
TVal v ->
case Aeson.fromJSON v of
Aeson.Success x -> pure x
Aeson.Error e -> argumentErr k n e
{-# INLINEABLE unquote #-}
instance Unquote Value
#if MIN_VERSION_aeson(2,0,0)
instance Unquote (KeyMap Value)
#endif
instance Unquote (HashMap Text Value)
instance Unquote (Vector Value)
instance Unquote Text
instance Unquote [Text]
instance Unquote Text.Lazy.Text
instance Unquote Bool
instance Unquote Double
instance Unquote Scientific
instance Unquote Int where
unquote k n =
unquote k n
>=> maybe (typeErr k n "Double" "Int") pure
. Scientific.toBoundedInteger
{-# INLINEABLE unquote #-}
instance Unquote Integer where
unquote k n =
unquote k n
>=> either (const (typeErr k n "Double" "Integral")) pure
. (Scientific.floatingOrInteger :: Scientific -> Either Double Integer)
{-# INLINEABLE unquote #-}
instance Unquote Collection where
unquote k n q =
text <$> unquote k n q
<|> hashMap <$> unquote k n q
<|> vector <$> unquote k n q
where
text t =
Col (Text.length t)
. map (\c -> (Nothing, String (Text.singleton c)))
$ Text.unpack t
hashMap m =
Col (HashMap.size m)
. map (Bifunctor.first Just)
. sortBy (comparing fst)
$ HashMap.toList m
vector v = Col (Vector.length v) (Vector.map (Nothing,) v)
{-# INLINEABLE unquote #-}
class Quote a where
quote :: Id -> Int -> a -> Term
default quote :: ToJSON a => Id -> Int -> a -> Term
quote _ _ = TVal . Aeson.toJSON
{-# INLINEABLE quote #-}
instance (Unquote a, Quote b) => Quote (a -> b) where
quote k n f = TLam $ \x -> quote k n' . f <$> unquote k n' x
where
n' = succ n
{-# INLINEABLE quote #-}
instance Quote Term where
quote _ _ = id
{-# INLINEABLE quote #-}
instance Quote Value
#if MIN_VERSION_aeson(2,0,0)
instance Quote (KeyMap Value)
#endif
instance Quote (HashMap Text Value)
instance Quote (Vector Value)
instance Quote [Value]
instance Quote Text
instance Quote [Text]
instance Quote Text.Lazy.Text
instance Quote Bool
instance Quote Int
instance Quote Integer
instance Quote Double
instance Quote Scientific
instance Quote Builder where
quote k n = quote k n . Text.Builder.toLazyText
{-# INLINEABLE quote #-}
typeErr :: Id -> Int -> AnsiDoc -> AnsiDoc -> Result a
typeErr k n x y = Failure $ "type" <+> pp k <+> pretty n <+> x <+> "::" <+> y
argumentErr :: Pretty a => Id -> Int -> a -> Result b
argumentErr k n e = Failure $ if self then app else arg
where
app =
"unable to apply"
<+> bold (pp k)
<+> "to left hand side:"
</> PP.indent 4 (pretty e </> Trifecta.Rendering.prettyRendering mark)
arg =
"invalid"
<+> bold (pp $ toOrdinal (n - 1))
<+> "argument to"
<+> bold (pp k)
<> ":"
</> PP.indent 4 (pretty e </> Trifecta.Rendering.prettyRendering mark)
mark =
Trifecta.Rendering.renderingCaret (Trifecta.Delta.Columns col col) $
"... | " <> Text.Encoding.encodeUtf8 k <> line
col
| self = 1
| otherwise = fromIntegral (Text.length k + 4 + (n * 2))
line
| self = "\n"
| otherwise =
"(" <> ByteString.Char8.intercalate ", " (replicate (n - 1) "...") <> "\n"
self = n <= 1