jsonnet-0.1.0.0: src/Language/Jsonnet/Std/Lib.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Language.Jsonnet.Std.Lib
( std,
objectHasEx,
)
where
import Control.Monad.Except
import Control.Monad.State
import Data.Aeson (FromJSON (..))
import qualified Data.Aeson as JSON
import qualified Data.ByteString as B
import Data.HashMap.Lazy (HashMap)
import qualified Data.HashMap.Lazy as H
import Data.List (sort)
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import Data.Word
import Language.Jsonnet.Common
import Language.Jsonnet.Core (Fun (Fun), KeyValue (..))
import Language.Jsonnet.Error
import Language.Jsonnet.Eval.Monad
import Language.Jsonnet.Parser.SrcSpan
import Language.Jsonnet.Value
import System.FilePath.Posix (takeFileName)
import Text.Megaparsec.Pos (SourcePos (..))
import Text.PrettyPrint.ANSI.Leijen (text)
import Unbound.Generics.LocallyNameless
import Prelude hiding (length)
import qualified Prelude as P (length)
-- | The native subset of Jsonnet standard library
std :: Value
std = VObj $ H.fromList $ map f xs
where
f = \(k, v) -> (k, TV <$> Hideable v Hidden)
xs =
("thisFile", inj <$> thisFile) :
map
(\(k, v) -> (k, pure v))
[ ("type", inj valueType),
("primitiveEquals", inj primitiveEquals),
("length", inj length),
("pow", inj ((^^) @Double @Int)),
("exp", inj (exp @Double)),
("log", inj (log @Double)),
("exponent", inj (exponent @Double)),
("mantissa", inj (significand @Double)),
("floor", inj (floor @Double @Integer)),
("ceil", inj (ceiling @Double @Integer)),
("sqrt", inj (sqrt @Double)),
("sin", inj (sin @Double)),
("cos", inj (cos @Double)),
("tan", inj (tan @Double)),
("asin", inj (asin @Double)),
("acos", inj (acos @Double)),
("atan", inj (atan @Double)),
("modulo", inj (mod @Integer)),
("codepoint", inj (fromEnum . T.head)),
("char", inj (T.singleton . toEnum)),
("encodeUTF8", inj (B.unpack . T.encodeUtf8 :: Text -> [Word8])),
("decodeUTF8", inj (T.decodeUtf8 . B.pack :: [Word8] -> Text)),
("makeArray", inj makeArray),
("filter", inj (filterM @Eval @Value)),
("objectHasEx", inj objectHasEx),
("objectFieldsEx", inj objectFieldsEx),
("parseJson", inj (JSON.decodeStrict @Value))
]
primitiveEquals :: Value -> Value -> Eval Bool
primitiveEquals VNull VNull = pure True
primitiveEquals (VBool a) (VBool b) = pure (a == b)
primitiveEquals (VStr a) (VStr b) = pure (a == b)
primitiveEquals (VNum a) (VNum b) = pure (a == b)
primitiveEquals _ _ =
throwE
( StdError $
text $
T.unpack $
"primitiveEquals operates on primitive types"
)
objectFieldsEx :: Object -> Bool -> [Text]
objectFieldsEx o True = sort (H.keys o) -- all fields
objectFieldsEx o False = sort $ H.keys $ H.filter (not . hidden) o -- only visible (incl. forced)
objectHasEx :: Object -> Text -> Bool -> Bool
objectHasEx o f all = f `elem` objectFieldsEx o all
length :: Value -> Eval Int
length = \case
VStr s -> pure $ T.length s
VArr a -> pure $ P.length a
VObj o -> pure $ P.length (H.keys o)
VClos (Fun f) _ -> do
(ps, _) <- unbind f
pure $ P.length (unrec ps)
v ->
throwE
( StdError $
text $
T.unpack $
"length operates on strings, objects, functions and arrays, got "
<> valueType v
)
makeArray :: Int -> (Int -> Eval Value) -> Eval [Value]
makeArray n f = traverse f [0 .. n - 1]
-- hacky way of returning the current file
thisFile :: Eval (Maybe FilePath)
thisFile = f <$> gets currentPos
where
f = fmap (takeFileName . sourceName . spanBegin)
instance FromJSON Value where
parseJSON = \case
JSON.Null -> pure VNull
JSON.Bool b -> pure $ VBool b
JSON.Number n -> pure $ VNum n
JSON.String s -> pure $ VStr s
JSON.Array a -> VArr <$> traverse (fmap mkThunk' . parseJSON) a
JSON.Object o -> VObj . f <$> traverse parseJSON o
where
f :: HashMap Text Value -> Object
f o =
H.fromList
[ (mkField k (mkThunk' v))
| (k, v) <- H.toList o
]
mkField k v = (k, Hideable v Visible)