packages feed

morley-1.15.0: src/Michelson/Runtime/Import.hs

-- SPDX-FileCopyrightText: 2020 Tocqueville Group
--
-- SPDX-License-Identifier: LicenseRef-MIT-TQ

module Michelson.Runtime.Import
  (
    -- * Read, parse, typecheck contract
    readContract
  , readSomeContract
  , readUntypedContract
  , importContract
  , importSomeContract
  , importUntypedContract
  , ImportContractError(..)

    -- * Read, parse, typecheck value
  , readValue
  , importValue
  , importSomeValue
  , importUntypedValue
  , ImportValueError (..)

    -- * Generic helpers
  , importUsing
  ) where

import Data.Default (def)
import qualified Data.Text.IO.Utf8 as Utf8 (readFile)
import Fmt (Buildable(build), pretty, unlinesF, (+|), (|+))

import Michelson.Parser (parseExpandValueFromFile)
import Michelson.Parser.Error (ParserException(..))
import Michelson.Runtime (parseExpandContract)
import Michelson.TypeCheck
  (TCError, typeCheckContract, typeCheckTopLevelType, typeCheckingWith,
  typeVerifyContract, typeVerifyTopLevelType)
import Michelson.Typed (Contract(..), SingI, SomeContract(..), SomeValue, Value)
import qualified Michelson.Untyped as U

----------------------------------------------------------------------------
-- Reading, parsing, typechecking contract
----------------------------------------------------------------------------

-- | Purely read an untyped contract from Michelson textual representation.
--
-- 'FilePath' is accepted solely as a hint for error messages.
readUntypedContract
  :: FilePath
  -> Text
  -> Either ImportContractError U.Contract
readUntypedContract filePath txt = do
  first (ICEParse filePath) $ parseExpandContract (Just filePath) txt

-- | Purely read a typed contract from Michelson textual representation.
readSomeContract
  :: FilePath
  -> Text
  -> Either ImportContractError SomeContract
readSomeContract filePath txt = do
  contract <- readUntypedContract filePath txt
  first (ICETypeCheck filePath) $
    typeCheckingWith def $ typeCheckContract contract

-- | Purely read a typed contract from Michelson textual representation,
-- failing if parameter or storage types mismatch with the expected ones.
readContract
  :: forall cp st .
     Each '[SingI] [cp, st]
  => FilePath
  -> Text
  -> Either ImportContractError (Contract cp st)
readContract filePath txt = do
  contract <- readUntypedContract filePath txt
  first (ICETypeCheck filePath) $
    typeCheckingWith def $ typeVerifyContract contract

-- | Read a thing from a file, using the provided parsing function.
importUsing
  :: (Exception e)
  => (FilePath -> Text -> Either e a)
  -> FilePath -> IO a
importUsing readFn file =
  either throwM pure . readFn file =<< Utf8.readFile file

-- | Import untyped contract from a given file path.
--
-- This function reads file, and parses a contract.
--
-- This function may throw 'IOException' and 'ImportContractError'.
importUntypedContract :: FilePath -> IO U.Contract
importUntypedContract = importUsing readUntypedContract

-- | Import contract from a given file path.
--
-- This function reads file, parses and type checks a contract.
-- Within the typechecking we assume that no contracts are originated,
-- otherwise a type checking error will be caused.
--
-- This function may throw 'IOException' and 'ImportContractError'.
importContract
  :: forall cp st .
     Each '[SingI] [cp, st]
  => FilePath -> IO (Contract cp st)
importContract = importUsing readContract

-- | Version of 'importContract' that doesn't require you to know
-- contract's parameter and storage types.
importSomeContract :: FilePath -> IO SomeContract
importSomeContract = importUsing readSomeContract

-- | Error type for 'importContract' function.
data ImportContractError
  = ICEParse FilePath ParserException
  | ICETypeCheck FilePath TCError
  deriving stock (Show, Eq)

instance Buildable ImportContractError where
  build =
    \case
      ICEParse filePath e -> unlinesF
        [ "Error at " <> build filePath
        , "Failed to parse the contract: " +| e |+ ""
        ]
      ICETypeCheck filePath e -> unlinesF
        [ "Error at " <> build filePath
        , "The contract is ill-typed: " +| e |+ ""
        ]

instance Exception ImportContractError where
  displayException = pretty

----------------------------------------------------------------------------
-- Reading, parsing, typechecking value
----------------------------------------------------------------------------

-- | Purely read an untyped Michelson value from textual representation.
--
-- 'FilePath' is accepted solely as a hint for error messages.
readUntypedValue
  :: FilePath
  -> Text
  -> Either ImportValueError U.Value
readUntypedValue filePath txt = do
  first (IVEParse filePath) $ parseExpandValueFromFile filePath txt

-- | Purely read a typed Michelson value from textual representation.
--
-- Expected type is provided explicitly.
readSomeValue
  :: U.Ty
  -> FilePath
  -> Text
  -> Either ImportValueError SomeValue
readSomeValue ty filePath txt = do
  valueU <- readUntypedValue filePath txt
  first (IVETypeCheck filePath) $
    typeCheckingWith def $ typeCheckTopLevelType Nothing ty valueU

-- | Purely read a typed Michelson value from textual representation.
readValue
  :: forall t. SingI t
  => FilePath
  -> Text
  -> Either ImportValueError (Value t)
readValue filePath txt = do
  valueU <- readUntypedValue filePath txt
  first (IVETypeCheck filePath) $
    typeCheckingWith def $ typeVerifyTopLevelType Nothing valueU

-- | Import an untyped value from a given file path.
importUntypedValue :: FilePath -> IO U.Value
importUntypedValue = importUsing readUntypedValue

-- | Import a typed value from a given file path.
--
-- Expected type is provided explicitly.
importSomeValue :: U.Ty -> FilePath -> IO SomeValue
importSomeValue = importUsing . readSomeValue

-- | Import a typed value from a given file path.
importValue
  :: forall t . SingI t
  => FilePath -> IO (Value t)
importValue = importUsing readValue

-- | Error type for 'importValue' function.
data ImportValueError
  = IVEParse FilePath ParserException
  | IVETypeCheck FilePath TCError
  deriving stock (Show, Eq)

instance Buildable ImportValueError where
  build =
    \case
      IVEParse filePath e -> unlinesF
        [ "Error at " <> build filePath
        , "Failed to parse the value: " +| e |+ ""
        ]
      IVETypeCheck filePath e -> unlinesF
        [ "Error at " <> build filePath
        , "The value is ill-typed: " +| e |+ ""
        ]

instance Exception ImportValueError where
  displayException = pretty