hydra-0.8.0: src/main/haskell/Hydra/Ext/Json/Coder.hs
module Hydra.Ext.Json.Coder (jsonCoder, literalJsonCoder, untypedTermToJson) where
import Hydra.Core
import Hydra.Compute
import Hydra.Graph
import Hydra.Strip
import Hydra.Basics
import Hydra.CoreEncoding
import Hydra.Tier1
import Hydra.Tier2
import Hydra.Adapters
import Hydra.TermAdapters
import Hydra.Ext.Json.Language
import Hydra.Lib.Literals
import Hydra.AdapterUtils
import qualified Hydra.Json as Json
import qualified Hydra.Dsl.Terms as Terms
import qualified Hydra.Dsl.Types as Types
import qualified Control.Monad as CM
import qualified Data.Map as M
import qualified Data.Set as S
import qualified Data.Maybe as Y
jsonCoder :: Type -> Flow Graph (Coder Graph Graph Term Json.Value)
jsonCoder typ = do
adapter <- languageAdapter jsonLanguage typ
coder <- termCoder $ adapterTarget adapter
return $ composeCoders (adapterCoder adapter) coder
literalJsonCoder :: LiteralType -> Flow Graph (Coder Graph Graph Literal Json.Value)
literalJsonCoder at = pure $ case at of
LiteralTypeBoolean -> Coder {
coderEncode = \(LiteralBoolean b) -> pure $ Json.ValueBoolean b,
coderDecode = \s -> case s of
Json.ValueBoolean b -> pure $ LiteralBoolean b
_ -> unexpected "boolean" $ show s}
LiteralTypeFloat _ -> Coder {
coderEncode = \(LiteralFloat (FloatValueBigfloat f)) -> pure $ Json.ValueNumber f,
coderDecode = \s -> case s of
Json.ValueNumber f -> pure $ LiteralFloat $ FloatValueBigfloat f
_ -> unexpected "number" $ show s}
LiteralTypeInteger _ -> Coder {
coderEncode = \(LiteralInteger (IntegerValueBigint i)) -> pure $ Json.ValueNumber $ bigintToBigfloat i,
coderDecode = \s -> case s of
Json.ValueNumber f -> pure $ LiteralInteger $ IntegerValueBigint $ bigfloatToBigint f
_ -> unexpected "number" $ show s}
LiteralTypeString -> Coder {
coderEncode = \(LiteralString s) -> pure $ Json.ValueString s,
coderDecode = \s -> case s of
Json.ValueString s' -> pure $ LiteralString s'
_ -> unexpected "string" $ show s}
recordCoder :: RowType -> Flow Graph (Coder Graph Graph Term Json.Value)
recordCoder rt = do
coders <- CM.mapM (\f -> (,) <$> pure f <*> termCoder (fieldTypeType f)) (rowTypeFields rt)
return $ Coder (encode coders) (decode coders)
where
encode coders term = case stripTerm term of
TermRecord (Record _ fields) -> Json.ValueObject . M.fromList . Y.catMaybes <$> CM.zipWithM encodeField coders fields
where
encodeField (ft, coder) (Field fname fv) = case (fieldTypeType ft, fv) of
(TypeOptional _, TermOptional Nothing) -> pure Nothing
_ -> Just <$> ((,) <$> pure (unName fname) <*> coderEncode coder fv)
_ -> unexpected "record" $ show term
decode coders n = case n of
Json.ValueObject m -> Terms.record (rowTypeTypeName rt) <$> CM.mapM (decodeField m) coders -- Note: unknown fields are ignored
where
decodeField a (FieldType fname _, coder) = do
v <- coderDecode coder $ Y.fromMaybe Json.ValueNull $ M.lookup (unName fname) m
return $ Field fname v
_ -> unexpected "mapping" $ show n
getCoder coders fname = Y.maybe error pure $ M.lookup fname coders
where
error = fail $ "no such field: " ++ fname
termCoder :: Type -> Flow Graph (Coder Graph Graph Term Json.Value)
termCoder typ = case stripType typ of
TypeLiteral at -> do
ac <- literalJsonCoder at
return Coder {
coderEncode = \(TermLiteral av) -> coderEncode ac av,
coderDecode = \n -> case n of
s -> Terms.literal <$> coderDecode ac s}
TypeList lt -> do
lc <- termCoder lt
return Coder {
coderEncode = \(TermList els) -> Json.ValueArray <$> CM.mapM (coderEncode lc) els,
coderDecode = \n -> case n of
Json.ValueArray nodes -> Terms.list <$> CM.mapM (coderDecode lc) nodes
_ -> unexpected "sequence" $ show n}
TypeOptional ot -> do
oc <- termCoder ot
return Coder {
coderEncode = \t -> case fullyStripTerm t of
TermOptional el -> Y.maybe (pure Json.ValueNull) (coderEncode oc) el
_ -> unexpected "optional term" $ show t,
coderDecode = \n -> case n of
Json.ValueNull -> pure $ Terms.optional Nothing
_ -> Terms.optional . Just <$> coderDecode oc n}
TypeMap (MapType kt vt) -> do
kc <- termCoder kt
vc <- termCoder vt
cx <- getState
let encodeEntry (k, v) = (,) (toString cx k) <$> coderEncode vc v
let decodeEntry (k, v) = (,) (fromString cx k) <$> coderDecode vc v
return Coder {
coderEncode = \(TermMap m) -> Json.ValueObject . M.fromList <$> CM.mapM encodeEntry (M.toList m),
coderDecode = \n -> case n of
Json.ValueObject m -> Terms.map . M.fromList <$> CM.mapM decodeEntry (M.toList m)
_ -> unexpected "mapping" $ show n}
where
toString cx v = if isStringKey cx
then case stripTerm v of
TermLiteral (LiteralString s) -> s
else show v
fromString cx s = Terms.string $ if isStringKey cx then s else read s
isStringKey cx = stripType kt == Types.string
TypeRecord rt -> recordCoder rt
TypeVariable name -> return $ Coder encode decode
where
encode term = pure $ Json.ValueString $ "variable '" ++ unName name ++ "' for: " ++ show term
decode term = fail $ "type variable " ++ unName name ++ " does not support decoding"
_ -> fail $ "unsupported type in JSON: " ++ show (typeVariant typ)
-- | A simplistic, unidirectional encoding for terms as JSON values. Not type-aware; best used for human consumption.
untypedTermToJson :: Term -> Flow s Json.Value
untypedTermToJson term = case term of
TermAnnotated (AnnotatedTerm term1 ann) -> do
json <- untypedTermToJson term1
pairs <- CM.mapM encodePair $ M.toList ann
return $ Json.ValueObject $ M.fromList $ [
("term", json),
("annotations", Json.ValueObject $ M.fromList pairs)]
where
encodePair (Name k, v) = do
json <- untypedTermToJson v
return (k, json)
TermApplication (Application lhs rhs) -> asRecord [
Field _Application_function lhs,
Field _Application_argument rhs]
TermFunction f -> case f of
FunctionElimination elm -> case elm of
EliminationList term1 -> asVariant "fold" term1
EliminationRecord (Projection _ fname) -> asVariant "project" $ TermVariable fname
_ -> unexp $ "unexpected elimination variant: " ++ show (eliminationVariant elm)
FunctionLambda (Lambda v d body) -> asRecord [
Field _Lambda_parameter $ TermVariable v,
Field _Lambda_domain $ TermOptional (coreEncodeType <$> d),
Field _Lambda_body body]
FunctionPrimitive name -> pure $ Json.ValueString $ unName name
TermLet (Let bindings env) -> asRecord [
Field _Let_bindings $ TermRecord $ Record (Name "") (fromBinding <$> bindings),
Field _Let_environment env]
where
fromBinding (LetBinding k v _) = Field k v
TermList terms -> Json.ValueArray <$> (CM.mapM untypedTermToJson terms)
TermLiteral lit -> pure $ case lit of
LiteralBinary s -> Json.ValueString s
LiteralBoolean b -> Json.ValueBoolean b
LiteralFloat f -> Json.ValueNumber $ floatValueToBigfloat f
LiteralInteger i -> Json.ValueNumber $ bigintToBigfloat $ integerValueToBigint i
LiteralString s -> Json.ValueString s
TermOptional mt -> case mt of
Nothing -> pure Json.ValueNull
Just t -> untypedTermToJson t
TermProduct els -> untypedTermToJson $ TermList els
TermRecord (Record _ fields) -> do
keyvals <- CM.mapM fieldToKeyval fields
return $ Json.ValueObject $ M.fromList $ Y.catMaybes keyvals
TermSet vals -> untypedTermToJson $ TermList $ S.toList vals
TermSum (Sum idx size term1) -> asRecord [
Field _Sum_index $ TermLiteral $ LiteralInteger $ IntegerValueInt32 idx,
Field _Sum_size $ TermLiteral $ LiteralInteger $ IntegerValueInt32 size,
Field _Sum_term term1]
TermTypeAbstraction (TypeAbstraction v term) -> asRecord [
Field _TypeAbstraction_parameter $ TermVariable v,
Field _TypeAbstraction_body term]
TermTypeApplication (TypedTerm term1 typ) -> asRecord [ -- Note: TermTypeApplication and TermTyped appear identical
Field _TypedTerm_term term1,
Field _TypedTerm_type $ coreEncodeType typ]
TermTyped (TypedTerm term1 typ) -> asRecord [
Field _TypedTerm_term term1,
Field _TypedTerm_type $ coreEncodeType typ]
TermUnion (Injection _ field) -> if fieldTerm field == Terms.unit
then return $ Json.ValueString $ unName $ fieldName field
else do
mkeyval <- fieldToKeyval field
return $ Json.ValueObject $ M.fromList $ case mkeyval of
Nothing -> []
Just keyval -> [keyval]
TermVariable v -> pure $ Json.ValueString $ unName v
TermWrap (WrappedTerm _ t) -> untypedTermToJson t
t -> unexp $ "unsupported term variant: " ++ show (termVariant t)
-- t -> fail $ "unexpected term variant: " ++ show (termVariant t)
where
unexp msg = pure $ Json.ValueString $ "FAIL: " ++ msg
asRecord = untypedTermToJson . TermRecord . Record (Name "")
asVariant name term = untypedTermToJson $ TermUnion $ Injection (Name "") $ Field (Name name) term
fieldToKeyval f = do
mjson <- forTerm $ fieldTerm f
return $ case mjson of
Nothing -> Nothing
Just j -> Just (unName $ fieldName f, j)
where
forTerm t = case t of
TermOptional mt -> case mt of
Nothing -> pure Nothing
Just t' -> forTerm t'
t' -> Just <$> untypedTermToJson t'