morley-1.1.0: src/Michelson/Untyped/EntryPoints.hs
module Michelson.Untyped.EntryPoints
( EpName (..)
, pattern DefEpName
, epNameFromParamAnn
, epNameToParamAnn
, epNameFromRefAnn
, epNameToRefAnn
, EpNameFromRefAnnError (..)
, mkEntrypointsMap
) where
import Data.Default (Default(..))
import qualified Data.Map as Map
import Fmt (Buildable(..), pretty, (+|), (|+))
import Test.QuickCheck (Arbitrary(..), suchThatMap)
import Michelson.Untyped.Annotation
import Michelson.Untyped.Type
import Util.CLI
-- | Entrypoint name.
--
-- Empty if this entrypoint is default one.
-- Cannot be equal to "default", the reference implementation forbids that.
-- Also, set of allowed characters should be the same as in annotations.
newtype EpName = EpNameUnsafe { unEpName :: Text }
deriving (Show, Eq, Ord, Generic)
instance NFData EpName
pattern DefEpName :: EpName
pattern DefEpName = EpNameUnsafe ""
instance Buildable EpName where
build = \case
DefEpName -> "<default>"
EpNameUnsafe name -> build name
instance Default EpName where
def = EpNameUnsafe ""
-- | Make up 'EpName' from annotation in parameter type declaration.
--
-- Returns 'Nothing' if no entrypoint is assigned here.
epNameFromParamAnn :: FieldAnn -> Maybe EpName
epNameFromParamAnn an@(Annotation a)
| an == noAnn = Nothing
| a == "default" = Just (EpNameUnsafe "")
| otherwise = Just $ EpNameUnsafe a
-- | Turn entrypoint name into annotation for contract parameter declaration.
epNameToParamAnn :: EpName -> FieldAnn
epNameToParamAnn (EpNameUnsafe name)
| name == "" = ann "default"
| otherwise = ann name
data EpNameFromRefAnnError
= InEpNameBadAnnotation FieldAnn
deriving stock (Show, Eq, Generic)
instance NFData EpNameFromRefAnnError
instance Buildable EpNameFromRefAnnError where
build = \case
InEpNameBadAnnotation (Annotation an) ->
"Invalid entrypoint reference `" +| an |+ "`"
-- | Make up 'EpName' from annotation which is reference to an entrypoint
-- (e.g. annotation in @CONTRACT@ instruction).
--
-- Fails if annotation is invalid.
epNameFromRefAnn :: FieldAnn -> Either EpNameFromRefAnnError EpName
epNameFromRefAnn an@(Annotation a)
| a == "default" = Left $ InEpNameBadAnnotation an
| otherwise = Right $ EpNameUnsafe a
-- | Turn entrypoint name into annotation used as reference to entrypoint.
epNameToRefAnn :: EpName -> FieldAnn
epNameToRefAnn (EpNameUnsafe name) = ann name
instance Arbitrary FieldAnn => Arbitrary EpName where
arbitrary = arbitrary `suchThatMap` (rightToMaybe . epNameFromRefAnn)
instance HasCLReader EpName where
getReader = eitherReader parseEntrypointDo
where
parseEntrypointDo (toText -> txt) = do
an <-
mkAnnotation txt
& first (mappend "Failed to parse entrypoint: " . pretty)
epNameFromRefAnn an
& first pretty
getMetavar = "ENTRYPOINT"
-- | Given an untyped type, extract a map that maps entrypoint names to the
-- their parameter types. If there are duplicate entrypoints in the given Type
-- then the duplicate entrypoints at a deeper nesting level will get
-- overwritten with the ones that are on top.
mkEntrypointsMap :: Type -> Map EpName Type
mkEntrypointsMap (Type t _) = case t of
-- We are only interested in `Or` branches to extract entrypoint
-- annotations.
TOr f1 f2 t1 t2 -> extractSubmap f1 t1 <> extractSubmap f2 t2
_ -> mempty
where
extractSubmap :: FieldAnn -> Type -> Map EpName Type
extractSubmap f1 t1 = let
-- If the field annotation is not empty then merge this field
-- annotation/type pair with the entrypoint map of inner type. Else
-- just fetch entrypoint map of inner type.
innerMap = mkEntrypointsMap t1
in case epNameFromParamAnn f1 of
Just epName -> Map.singleton epName t1 <> innerMap
Nothing -> innerMap