hsjuiz-0.2.6: src/Juiz/Manifest.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- | Process / Container のマニフェストの組み立て。
--
-- > manifest :: ProcessManifest
-- > manifest = processManifest "increment_process_hs"
-- > & description "Adds 1 to arg1."
-- > & addIntArg "arg1" "value to be incremented" 1
--
-- 引数を足した順が、Process の関数の引数の順になる。
-- Container ('containerManifest') では、Container を作る関数の引数の順になる。
-- ContainerProcess ('containerProcessManifest') では、第 1 引数 (Container の状態) の後に続く引数の順になる。
--
-- Component ('componentManifest') は、1 つのライブラリにまとめた Process / Container / ContainerProcess の
-- マニフェストを束ねたもの。ふつうは 'Juiz.TH.juizComponent' が組み立てる。
module Juiz.Manifest
( ProcessManifest (..)
, ContainerManifest (..)
, ComponentManifest (..)
, ArgumentManifest (..)
, HasDescription (..)
, HasArguments (..)
, processManifest
, containerManifest
, containerProcessManifest
, componentManifest
, addProcess
, addContainer
, description
, factory
, useMemo
, readonly
, containerType
, addArg
, addBoolArg
, addIntArg
, addFloatArg
, addStringArg
, addArrayArg
, addObjectArg
, addImageArg
, addTypedArg
, addTypedArgValue
, addTypedArgNoDefault
, returns
, manifestToValue
, containerManifestToValue
, componentManifestToValue
) where
import Data.Int (Int64)
import qualified Data.Map.Strict as Map
import Data.Proxy (Proxy (..))
import Data.Text (Text)
import Juiz.Capsule (ToCapsule (..), object, (.=))
import Juiz.Internal.TypeInfo
import Juiz.Type (JuizType (..))
import Juiz.Value
data ArgumentManifest = ArgumentManifest
{ argName :: !Text
-- | juiz の型名 ("Bool", "Int", "Float", "String", "Array", "Object")
, argTypeName :: !Text
, argDescription :: !Text
, argDefault :: !Value
-- | 型付きの引数の型 ('addTypedArg' など)。manifest の schema になり、呼ばれたときは値をこの型で読む
, argType :: !(Maybe TypeInfo)
}
deriving (Eq, Show)
data ProcessManifest = ProcessManifest
{ pmTypeName :: !Text
, pmDescription :: !Text
, pmLanguage :: !Text
-- | 関数を探すときのシンボル名。単体の Process は "process_factory"
, pmFactory :: !Text
, pmArguments :: ![ArgumentManifest]
, pmUseMemo :: !Bool
-- | ContainerProcess のとき、結びつける Container の型名
, pmContainerType :: !(Maybe Text)
-- | 戻り値の型 ('returns'、または関数の結果の型)。manifest の output_schema になる
, pmOutputType :: !(Maybe TypeInfo)
-- | readonly の ContainerProcess ('readonly'、juiz の readonly)
, pmReadonly :: !Bool
}
deriving (Eq, Show)
data ContainerManifest = ContainerManifest
{ cmTypeName :: !Text
, cmDescription :: !Text
, cmLanguage :: !Text
-- | 関数を探すときのシンボル名。単体の Container は "container_factory"
, cmFactory :: !Text
-- | Container を作る関数の引数 (設定ファイルの containers に書いた値)
, cmArguments :: ![ArgumentManifest]
}
deriving (Eq, Show)
-- | 1 つのライブラリにまとめる Process / Container / ContainerProcess のマニフェスト。
-- ContainerProcess ('pmContainerType' のある 'ProcessManifest') は、juiz に渡すときに
-- 同じ型名の Container の processes に入れる
data ComponentManifest = ComponentManifest
{ compTypeName :: !Text
, compDescription :: !Text
, compLanguage :: !Text
, compContainers :: ![ContainerManifest]
-- | Process と ContainerProcess (足した順)
, compProcesses :: ![ProcessManifest]
}
deriving (Eq, Show)
-- | 説明を持つマニフェスト
class HasDescription m where
setDescription :: Text -> m -> m
-- | 説明・シンボル名・引数を持つマニフェスト ('ProcessManifest' と 'ContainerManifest')
class HasDescription m => HasArguments m where
setFactory :: Text -> m -> m
appendArgument :: ArgumentManifest -> m -> m
manifestArguments :: m -> [ArgumentManifest]
instance HasDescription ProcessManifest where
setDescription d m = m { pmDescription = d }
instance HasArguments ProcessManifest where
setFactory f m = m { pmFactory = f }
appendArgument a m = m { pmArguments = pmArguments m ++ [a] }
manifestArguments = pmArguments
instance HasDescription ContainerManifest where
setDescription d m = m { cmDescription = d }
instance HasDescription ComponentManifest where
setDescription d m = m { compDescription = d }
instance HasArguments ContainerManifest where
setFactory f m = m { cmFactory = f }
appendArgument a m = m { cmArguments = cmArguments m ++ [a] }
manifestArguments = cmArguments
-- | 型名 (設定ファイルの type_name) を指定してマニフェストを作る
processManifest :: Text -> ProcessManifest
processManifest typeName = ProcessManifest
{ pmTypeName = typeName
, pmDescription = ""
, pmLanguage = "haskell"
, pmFactory = "process_factory"
, pmArguments = []
, pmUseMemo = False
, pmContainerType = Nothing
, pmOutputType = Nothing
, pmReadonly = False
}
-- | 型名 (設定ファイルの type_name) を指定して Container のマニフェストを作る
containerManifest :: Text -> ContainerManifest
containerManifest typeName = ContainerManifest
{ cmTypeName = typeName
, cmDescription = ""
, cmLanguage = "haskell"
, cmFactory = "container_factory"
, cmArguments = []
}
-- | ContainerProcess のマニフェストを作る。型名 → 結びつける Container の型名
containerProcessManifest :: Text -> Text -> ProcessManifest
containerProcessManifest typeName ctype = (processManifest typeName)
{ pmFactory = "container_process_factory"
, pmContainerType = Just ctype
}
-- | Component のマニフェストを作る。名前が設定ファイルの components に書く名前とライブラリの名前になる
componentManifest :: Text -> ComponentManifest
componentManifest typeName = ComponentManifest
{ compTypeName = typeName
, compDescription = ""
, compLanguage = "haskell"
, compContainers = []
, compProcesses = []
}
-- | Component に Process か ContainerProcess を足す。'pmFactory' は Component の中のシンボル名にしておく
addProcess :: ProcessManifest -> ComponentManifest -> ComponentManifest
addProcess p c = c { compProcesses = compProcesses c ++ [p] }
-- | Component に Container を足す。'cmFactory' は Component の中のシンボル名にしておく
addContainer :: ContainerManifest -> ComponentManifest -> ComponentManifest
addContainer m c = c { compContainers = compContainers c ++ [m] }
description :: HasDescription m => Text -> m -> m
description = setDescription
factory :: HasArguments m => Text -> m -> m
factory = setFactory
-- | 結びつける Container の型名 (juiz の container_type)
containerType :: Text -> ProcessManifest -> ProcessManifest
containerType c m = m { pmContainerType = Just c }
-- | 同じ引数で呼ばれたときに前の結果を使い回すか (juiz の use_memo)
useMemo :: Bool -> ProcessManifest -> ProcessManifest
useMemo b m = m { pmUseMemo = b }
-- | readonly の ContainerProcess にする (juiz の readonly、docs/spec/transformations.md §14)。
-- juiz は Container を読み出しロックで呼ぶので、同じ Container の readonly 同士は並行に走る。
-- 中で 'Juiz.Pose.setContainerPose' を呼ぶと 'juizPoseNotWritableError' になる。
-- Container の状態 (IORef など) を書き換えないことは書き手が守る
readonly :: Bool -> ProcessManifest -> ProcessManifest
readonly b m = m { pmReadonly = b }
-- | 引数を足す。型名 → 名前 → 説明 → 既定値
addArg :: HasArguments m => Text -> Text -> Text -> Value -> m -> m
addArg typeName name desc def = appendArgument (ArgumentManifest name typeName desc def Nothing)
addBoolArg :: HasArguments m => Text -> Text -> Bool -> m -> m
addBoolArg name desc def = addArg "Bool" name desc (JBool def)
addIntArg :: HasArguments m => Text -> Text -> Int64 -> m -> m
addIntArg name desc def = addArg "Int" name desc (JInt def)
addFloatArg :: HasArguments m => Text -> Text -> Double -> m -> m
addFloatArg name desc def = addArg "Float" name desc (JFloat def)
addStringArg :: HasArguments m => Text -> Text -> Text -> m -> m
addStringArg name desc def = addArg "String" name desc (JString def)
addArrayArg :: HasArguments m => Text -> Text -> [Value] -> m -> m
addArrayArg name desc def = addArg "Array" name desc (JArray def)
addObjectArg :: HasArguments m => Text -> Text -> [(Text, Value)] -> m -> m
addObjectArg name desc def = addArg "Object" name desc (JObject (Map.fromList def))
-- | 画像の引数を足す ('Juiz.Image.Image' で受け取る、docs/spec/large-data.md §10)。名前 → 説明。
-- Rust の add_image_arg と同じく、既定値は空のオブジェクト
addImageArg :: HasArguments m => Text -> Text -> m -> m
addImageArg name desc = addArg "Image" name desc (JObject Map.empty)
-- | 型付きの引数を足す (型は juiz-idl -g haskell の生成した型など、docs/spec/datatype.md §22)。
-- 名前 → 説明 → 既定値 (その型の値)。
--
-- > addTypedArg "target" "goal pose" defaultPose2D
addTypedArg :: forall a m. (JuizType a, HasArguments m) => Text -> Text -> a -> m -> m
addTypedArg name desc def = addTypedArgValue (Proxy :: Proxy a) name desc (toValue def)
-- | 型付きの引数を足す。既定値は辞書の表し方 (仕様 §4) で書く。'JNull' は既定値なし
addTypedArgValue :: (JuizType a, HasArguments m) => Proxy a -> Text -> Text -> Value -> m -> m
addTypedArgValue p name desc def =
let t = juizType p
in appendArgument (ArgumentManifest name (typeInfoTypeName t) desc def (Just t))
-- | 既定値のない型付きの引数を足す (呼ぶときに必ず渡す)
--
-- > addTypedArgNoDefault (Proxy :: Proxy Pose2D) "target" "goal pose"
addTypedArgNoDefault :: (JuizType a, HasArguments m) => Proxy a -> Text -> Text -> m -> m
addTypedArgNoDefault p name desc = addTypedArgValue p name desc JNull
-- | 戻り値の型 (output_schema)。関数の結果が型付きの型なら書かなくても載る。書いたときは関数の結果の型と同じにする
returns :: JuizType a => Proxy a -> ProcessManifest -> ProcessManifest
returns p m = m { pmOutputType = Just (juizType p) }
-- | juiz に渡す形 (C++ の ProcessManifest::into_value と同じ項目)
manifestToValue :: ProcessManifest -> Value
manifestToValue m = object $
[ "type_name" .= pmTypeName m
, "language" .= pmLanguage m
, "description" .= pmDescription m
, "factory" .= pmFactory m
, "use_memo" .= pmUseMemo m
, "arguments" .= map argumentToValue (pmArguments m)
]
++ maybe [] (\c -> ["container_type" .= c]) (pmContainerType m)
++ ["readonly" .= True | pmReadonly m]
++ maybe [] outputSchema (pmOutputType m)
where
-- 古いクライアント向けに、type_name だけの outputs も載せる (仕様 §6)
outputSchema t =
[ "output_schema" .= typeInfoSchema t
, "outputs" .= object
[ "enum_type" .= ("Primitive" :: Text)
, "type_name" .= typeInfoTypeName t
, "default_value" .= JNull
]
]
-- | juiz に渡す形 (C++ の ContainerManifest::into_value と同じ項目)
containerManifestToValue :: ContainerManifest -> Value
containerManifestToValue = containerWithProcessesToValue []
containerWithProcessesToValue :: [ProcessManifest] -> ContainerManifest -> Value
containerWithProcessesToValue ps m = object
[ "type_name" .= cmTypeName m
, "language" .= cmLanguage m
, "description" .= cmDescription m
, "factory" .= cmFactory m
, "arguments" .= map argumentToValue (cmArguments m)
, "processes" .= map manifestToValue ps
]
-- | juiz に渡す形 (C++ の ComponentManifest::into_value と同じ項目)。
-- ContainerProcess の Container が Component に無い、または型名が重なっていれば Left (説明)
componentManifestToValue :: ComponentManifest -> Either Text Value
componentManifestToValue c
| (n : _) <- duplicates (map cmTypeName (compContainers c) ++ map pmTypeName (compProcesses c)) =
Left ("the type name \"" <> n <> "\" is used twice in the component")
| (p : _) <- filter (not . hasContainer) containerProcesses =
Left ("the container process \"" <> pmTypeName p <> "\" belongs to the container \""
<> maybe "" id (pmContainerType p) <> "\", which is not in the component")
| otherwise = Right $ object
[ "type_name" .= compTypeName c
, "language" .= compLanguage c
, "description" .= compDescription c
, "containers" .= map containerValue (compContainers c)
, "processes" .= map manifestToValue [p | p <- compProcesses c, pmContainerType p == Nothing]
]
where
containerProcesses = [p | p <- compProcesses c, pmContainerType p /= Nothing]
hasContainer p = pmContainerType p `elem` map (Just . cmTypeName) (compContainers c)
containerValue m =
containerWithProcessesToValue [p | p <- containerProcesses, pmContainerType p == Just (cmTypeName m)] m
duplicates xs = [x | (i, x) <- zip [0 :: Int ..] xs, x `elem` take i xs]
argumentToValue :: ArgumentManifest -> Value
argumentToValue a = object $
[ "name" .= argName a
, "type_name" .= argTypeName a
, "description" .= argDescription a
, "default" .= argDefault a
]
++ maybe [] (\t -> ["schema" .= typeInfoSchema t]) (argType a)