packages feed

jsonnet-0.1.0.0: src/Language/Jsonnet/Value.hs

{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE UndecidableInstances #-}

-- |
module Language.Jsonnet.Value where

import Control.Applicative
import Control.Arrow
import Control.Monad.Except
import Control.Monad.IO.Class
import Control.Monad.Reader
import Control.Monad.State.Lazy
import Data.Bits
import Data.ByteString (ByteString)
import Data.Data
import Data.HashMap.Lazy (HashMap)
import qualified Data.HashMap.Lazy as H
import Data.Hashable (Hashable)
import Data.IORef
import Data.Int
import Data.Map.Lazy (Map)
import qualified Data.Map.Lazy as M
import Data.Scientific (Scientific, fromFloatDigits, toRealFloat)
import Data.Text (Text)
import qualified Data.Text as T
import Data.Text.Encoding (decodeUtf8, encodeUtf8)
import Data.Typeable
import Data.Vector (Vector, (!?))
import qualified Data.Vector as V
import Debug.Trace
import GHC.Generics (Generic)
import Language.Jsonnet.Common
import Language.Jsonnet.Core
import Language.Jsonnet.Error
import {-# SOURCE #-} Language.Jsonnet.Eval (eval, evalClos)
import Language.Jsonnet.Eval.Monad
import Language.Jsonnet.Parser.SrcSpan
import Language.Jsonnet.Pretty ()
import Text.PrettyPrint.ANSI.Leijen (Doc, pretty)
import Unbound.Generics.LocallyNameless

-- jsonnet value
data Value
  = VNull
  | VBool !Bool
  | VNum !Scientific
  | VStr !Text
  | VArr !Array
  | VObj !(HashMap Text (Hideable Thunk))
  | VClos !Fun !Env
  | VFun !(Thunk -> Eval Value)
  deriving (Generic)

type Array = Vector Thunk

type Object = HashMap Text (Hideable Thunk)

valueType :: Value -> Text
valueType =
  \case
    VNull -> "null"
    VBool _ -> "boolean"
    VNum _ -> "number"
    VStr _ -> "string"
    VArr _ -> "array"
    VObj _ -> "object"
    VClos _ _ -> "function"
    VFun _ -> "function"

data Thunk = TC !Ctx !Core | TV !(Eval Value)
  deriving (Generic)

force :: Thunk -> Eval Value
force = \case
  TC rho expr -> withCtx rho (eval expr)
  TV comp -> comp

mkThunk' :: Value -> Thunk
mkThunk' = TV . pure

mkThunk :: MonadIO m => Eval Value -> m Thunk
mkThunk ev = do
  ref <- liftIO $ newIORef Nothing
  pure $
    TV $
      liftIO (readIORef ref) >>= \case
        Nothing -> do
          v <- ev
          liftIO $ writeIORef ref $ Just v
          pure v
        Just v -> pure v

proj' :: HasValue a => Thunk -> Eval a
proj' = force >=> proj

class HasValue a where
  proj :: Value -> Eval a
  inj :: a -> Value

instance {-# OVERLAPS #-} HasValue Value where
  proj = pure
  inj = id

instance HasValue Bool where
  proj (VBool n) = pure n
  proj v = throwTypeMismatch "bool" v
  inj = VBool

instance HasValue Text where
  proj (VStr s) = pure s
  proj v = throwTypeMismatch "string" v
  inj = VStr

instance {-# OVERLAPPING #-} HasValue [Char] where
  proj (VStr s) = pure $ T.unpack s
  proj v = throwTypeMismatch "string" v
  inj = VStr . T.pack

instance HasValue ByteString where
  proj (VStr s) = pure (encodeUtf8 s)
  proj v = throwTypeMismatch "string" v
  inj = VStr . decodeUtf8

instance HasValue Scientific where
  proj (VNum n) = pure n
  proj v = throwTypeMismatch "number" v
  inj = VNum

instance HasValue Double where
  proj (VNum n) = pure (toRealFloat n)
  proj v = throwTypeMismatch "number" v
  inj = VNum . fromFloatDigits

instance {-# OVERLAPS #-} Integral a => HasValue a where
  proj (VNum n) = pure (round n)
  proj v = throwTypeMismatch "number" v
  inj = VNum . fromIntegral

instance HasValue a => HasValue (Maybe a) where
  proj VNull = pure Nothing
  proj a = Just <$> proj a
  inj Nothing = VNull
  inj (Just a) = inj a

instance HasValue a => HasValue (Vector a) where
  proj (VArr as) = traverse proj' as
  proj v = throwTypeMismatch "array" v
  inj as = VArr $ mkThunk' . inj <$> as

instance {-# OVERLAPS #-} HasValue (Vector Thunk) where
  proj (VArr as) = pure as
  proj v = throwTypeMismatch "array" v
  inj = VArr

instance {-# OVERLAPPABLE #-} HasValue a => HasValue [a] where
  proj = fmap V.toList . proj
  inj = inj . V.fromList

instance {-# OVERLAPS #-} HasValue Object where
  proj (VObj o) = pure o
  proj v = throwTypeMismatch "object" v
  inj = VObj

--instance HasValue a => HasValue (Object a) where
--  proj (VObj o) = traverse proj' o
--  proj v = throwTypeMismatch "object" v
--  inj o = VObj $ mkThunk' . inj <$> o

instance {-# OVERLAPS #-} (HasValue a, HasValue b) => HasValue (a -> b) where
  proj v = throwTypeMismatch "impossible" v
  inj f = VFun $ \x -> force x >>= fmap (inj . f) . proj

instance {-# OVERLAPS #-} (HasValue a, HasValue b, HasValue c) => HasValue (a -> b -> c) where
  proj v = throwTypeMismatch "impossible" v
  inj f = inj $ \x -> inj (f x)

instance {-# OVERLAPS #-} (HasValue a, HasValue b) => HasValue (a -> Eval b) where
  proj (VFun f) = pure $ \x -> do
    r <- f (mkThunk' $ inj x)
    proj r
  proj (VClos f env) = pure $ \x -> do
    r <- evalClos (ctx env) f $ [Pos $ mkThunk' $ inj x]
    proj r
  proj v = throwTypeMismatch "function" v
  inj f = VFun $ \v -> proj' v >>= fmap inj . f

instance {-# OVERLAPS #-} (HasValue a, HasValue b, HasValue c) => HasValue (a -> b -> Eval c) where
  proj (VFun f) = pure $ \x y -> do
    VFun g <- f (mkThunk' $ inj x)
    r <- g (mkThunk' $ inj y)
    proj r
  proj (VClos f env) = pure $ \x y -> do
    r <- evalClos (ctx env) f $ Pos . mkThunk' <$> [inj x, inj y]
    proj r
  proj v = throwTypeMismatch "function" v
  inj f = inj $ \x -> inj (f x)

throwTypeMismatch :: Text -> Value -> Eval a
throwTypeMismatch expected =
  throwE
    . TypeMismatch expected
    . valueType

inj' ::
  (HasValue a, HasValue b) =>
  (a -> b) ->
  (Value -> Eval Value)
inj' f v = inj . f <$> proj v

inj'' ::
  (HasValue a, HasValue b, HasValue c) =>
  (a -> b -> c) ->
  Value ->
  Value ->
  Eval Value
inj'' f v1 v2 = inj <$> liftA2 f (proj v1) (proj v2)