packages feed

morley-1.19.2: src/Morley/Micheline/Expression/Internal/MichelinePrimitive.hs

-- SPDX-FileCopyrightText: 2023 Oxhead Alpha
-- SPDX-License-Identifier: LicenseRef-MIT-OA

{-# OPTIONS_HADDOCK not-home #-}
{-# LANGUAGE DeriveLift #-}

-- | Micheline primitive datatype
module Morley.Micheline.Expression.Internal.MichelinePrimitive
  ( module Morley.Micheline.Expression.Internal.MichelinePrimitive
  ) where

import Data.Aeson qualified as Aeson
import Data.Char (isDigit, isLower, isUpper)
import Data.Data (Data)
import Data.Text qualified as T
import Fmt (Buildable(..), pretty)
import Language.Haskell.TH.Syntax (Lift)

import Morley.Util.Sing (genSingletonsType)

data MichelinePrimitive
  -- NOTE: The order of constructors in this datatype *matters*!
  --
  -- The position of each constructor determines which binary code it gets
  -- packed to. E.g.
  --   * "parameter" is at index 0 on the list, so it gets packed to `0x0300`
  --   * "storage" is at index 1, so it gets packed to `0x0301`
  --
  -- You can ask `octez-client` which code corresponds to a given
  -- instruction/type/constructor.
  --
  -- > octez-client convert data 'storage' from michelson to binary
  -- > 0x0301
  --
  -- Whenever new instructions/types/constructors are added to the protocol, we
  -- can regenerate this list using this script:
  --
  -- > ./scripts/get-micheline-exprs.sh
  --
  -- or find the full primitives list in the sources, see "prim_encoding"
  -- variable.
  -- https://gitlab.com/tezos/tezos/blob/master/src/proto_alpha/lib_protocol/michelson_v1_primitives.ml
  --
  -- Invariant: each constructor starts with `Prim_`
  = Prim_parameter | Prim_storage | Prim_code | Prim_False | Prim_Elt | Prim_Left
  | Prim_None | Prim_Pair | Prim_Right | Prim_Some | Prim_True | Prim_Unit
  | Prim_PACK | Prim_UNPACK | Prim_BLAKE2B | Prim_SHA256 | Prim_SHA512 | Prim_ABS
  | Prim_ADD | Prim_AMOUNT | Prim_AND | Prim_BALANCE | Prim_CAR | Prim_CDR
  | Prim_CHECK_SIGNATURE | Prim_COMPARE | Prim_CONCAT | Prim_CONS
  | Prim_CREATE_ACCOUNT | Prim_CREATE_CONTRACT | Prim_IMPLICIT_ACCOUNT | Prim_DIP
  | Prim_DROP | Prim_DUP | Prim_EDIV | Prim_EMPTY_MAP | Prim_EMPTY_SET | Prim_EQ
  | Prim_EXEC | Prim_FAILWITH | Prim_GE | Prim_GET | Prim_GT | Prim_HASH_KEY
  | Prim_IF | Prim_IF_CONS | Prim_IF_LEFT | Prim_IF_NONE | Prim_INT | Prim_LAMBDA
  | Prim_LE | Prim_LEFT | Prim_LOOP | Prim_LSL | Prim_LSR | Prim_LT | Prim_MAP
  | Prim_MEM | Prim_MUL | Prim_NEG | Prim_NEQ | Prim_NIL | Prim_NONE | Prim_NOT
  | Prim_NOW | Prim_OR | Prim_PAIR | Prim_PUSH | Prim_RIGHT | Prim_SIZE
  | Prim_SOME | Prim_SOURCE | Prim_SENDER | Prim_SELF | Prim_STEPS_TO_QUOTA
  | Prim_SUB | Prim_SWAP | Prim_TRANSFER_TOKENS | Prim_SET_DELEGATE | Prim_UNIT
  | Prim_UPDATE | Prim_XOR | Prim_ITER | Prim_LOOP_LEFT | Prim_ADDRESS
  | Prim_CONTRACT | Prim_ISNAT | Prim_CAST | Prim_RENAME | Prim_bool
  | Prim_contract | Prim_int | Prim_key | Prim_key_hash | Prim_lambda | Prim_list
  | Prim_map | Prim_big_map | Prim_nat | Prim_option | Prim_or | Prim_pair
  | Prim_set | Prim_signature | Prim_string | Prim_bytes | Prim_mutez
  | Prim_timestamp | Prim_unit | Prim_operation | Prim_address | Prim_SLICE
  | Prim_DIG | Prim_DUG | Prim_EMPTY_BIG_MAP | Prim_APPLY | Prim_chain_id
  | Prim_CHAIN_ID | Prim_LEVEL | Prim_SELF_ADDRESS | Prim_never | Prim_NEVER
  | Prim_UNPAIR | Prim_VOTING_POWER | Prim_TOTAL_VOTING_POWER | Prim_KECCAK
  | Prim_SHA3 | Prim_PAIRING_CHECK | Prim_bls12_381_g1 | Prim_bls12_381_g2
  | Prim_bls12_381_fr | Prim_sapling_state | Prim_sapling_transaction_deprecated
  | Prim_SAPLING_EMPTY_STATE | Prim_SAPLING_VERIFY_UPDATE | Prim_ticket
  | Prim_TICKET_DEPRECATED | Prim_READ_TICKET | Prim_SPLIT_TICKET
  | Prim_JOIN_TICKETS | Prim_GET_AND_UPDATE | Prim_chest | Prim_chest_key
  | Prim_OPEN_CHEST | Prim_VIEW | Prim_view | Prim_constant | Prim_SUB_MUTEZ
  | Prim_tx_rollup_l2_address | Prim_MIN_BLOCK_TIME | Prim_sapling_transaction
  | Prim_EMIT | Prim_Lambda_rec | Prim_LAMBDA_REC | Prim_TICKET | Prim_BYTES
  | Prim_NAT
  deriving stock (Eq, Ord, Enum, Bounded, Show, Read, Data, Lift)

type instance PrettyShow MichelinePrimitive = ()

instance Buildable MichelinePrimitive where
  build = build . T.drop 5 . show

instance Aeson.ToJSON MichelinePrimitive where
  toJSON = Aeson.String . pretty

instance Aeson.FromJSON MichelinePrimitive where
  parseJSON = Aeson.withText "MichelinePrimitive" \t ->
    either (fail . toString) pure $ readEither ("Prim_" <> t)

-- | Simple classification datakind for 'MichelinePrimitive'.
data MichelinePrimitiveTag
  = MPTKeyword  -- ^ Keywords, like @parameter@, @code@, @storage@, @view@
  | MPTInstr    -- ^ Instructions, e.g. @UNIT@, @DIP@, etc
  | MPTValue    -- ^ Value constructors like @Left@, @Lambda_rec@, etc
  | MPTType     -- ^ Types, like @unit@, @list@, etc
  | MPTRemoved  -- ^ Removed primitives: @CREATE_ACCOUNT@, @STEPS_TO_QUOTA@
  | MPTConstant -- ^ Keyword @constant@ for global constants
  deriving stock (Show, Eq, Enum, Bounded, Lift)

instance Buildable MichelinePrimitiveTag where
  build = \case
    MPTKeyword  -> "keyword"
    MPTInstr    -> "instruction"
    MPTValue    -> "value"
    MPTType     -> "type"
    MPTRemoved  -> "removed"
    MPTConstant -> "constant"

genSingletonsType ''MichelinePrimitiveTag

-- | Classify 'MichelinePrimitive'. This function uses heuristics to avoid
-- writing a giant case match, which means it's potentially partial. This should
-- be fine as it's used with TemplateHaskell, hence if some cases are not
-- covered, the build will fail.
primClassification :: MichelinePrimitive -> MichelinePrimitiveTag
primClassification cs
  | cs `elem` [Prim_CREATE_ACCOUNT, Prim_STEPS_TO_QUOTA] = MPTRemoved
  | cs `elem` [Prim_code, Prim_parameter, Prim_storage, Prim_view] = MPTKeyword
  | cs == Prim_constant = MPTConstant
  | all (isUpper || (== '_') || isDigit) str = MPTInstr
  | all (isLower || (== '_') || isDigit) str = MPTType
  | Just (c, rest) <- T.uncons str
  , all (isLower || (== '_') || isDigit) rest
  , isUpper c = MPTValue
  | otherwise = error $ "Unknown primitive class: " <> str
  where str = pretty cs :: Text