-- SPDX-FileCopyrightText: 2020 Tocqueville Group -- -- SPDX-License-Identifier: LicenseRef-MIT-TQ {-# OPTIONS_GHC -Wno-redundant-constraints #-} -- | Strictly typed statements of Indigo language. module Indigo.Backend ( module ReExports -- * Loop , forEach , while , whileLeft -- * Contract call , selfCalling , contractCalling -- * Documentation , doc , docGroup , docStorage , contractName , finalizeParamCallingDoc , contractGeneral , contractGeneralDefault -- * Side-effects , transferTokens , setDelegate , createContract -- * Functions, Procedures and Scopes , scope -- * Comments , comment ) where import Indigo.Backend.Case as ReExports import Indigo.Backend.Conditional as ReExports import Indigo.Backend.Error as ReExports import Indigo.Backend.Lambda as ReExports import Indigo.Backend.Scope as ReExports import Indigo.Backend.Var as ReExports import Fmt (build, fmt, pretty, (+|), (|+)) import Indigo.Backend.Prelude import Indigo.Internal hiding ((<>)) import Indigo.Lorentz import qualified Lorentz.Doc as L import qualified Lorentz.Entrypoints.Doc as L (finalizeParamCallingDoc) import Lorentz.Entrypoints.Helpers (RequireSumType) import qualified Lorentz.Instr as L import qualified Lorentz.Run as L import qualified Michelson.Typed as MT import Util.Type (type (++)) ---------------------------------------------------------------------------- -- Loop ---------------------------------------------------------------------------- -- | While statement. while :: Expr Bool -- ^ Expression for the control flow -> SomeIndigoState inp -- ^ Block of code to execute, as long as the expression holds 'True' -> IndigoState inp inp while e body = IndigoState $ \md -> let expCd = exprHook md (pretty e) $ gcCode $ usingIndigoState md (compileExpr e) bodyIndigoState = runSIS body md cleanGenCode in flip (GenCode (mdStack md)) L.nop $ stmtHook md ("while (" <> pretty e <> ")") $ expCd # L.loop (bodyIndigoState # expCd) -- | While-left statement. Repeats a block of code as long as the control -- 'Either' is 'Left', returns when it is 'Right'. whileLeft :: forall l r inp . (KnownValue l, KnownValue r) => Expr (Either l r) -- ^ Expression for the control flow value -> Var l -- ^ Variable for the 'Left' value (available to the code block) -> SomeIndigoState (l : inp) -- ^ Code block to execute while the value is 'Left' -> Var r -- ^ Variable that will be assigned to the resulting value -> IndigoState inp (r : inp) whileLeft e varL body varR = IndigoState $ \md -> let cde = exprHook md (pretty e) $ gcCode $ usingIndigoState md (compileExpr e) newMd = pushRefMd varL md bodyCd = runSIS body newMd cleanGenCode resSt = pushRef varR $ mdStack md in flip (GenCode resSt) L.drop $ stmtHook md (condStmtPretty @(Var r) varR "whileLeft" e) $ exprHook md (pretty e) cde # L.loopLeft (auxiliaryHook md ("body: " <> pretty varL <> ":= fromLeft " <> pretty e) bodyCd # L.drop # cde) -- | For statements to iterate over a container. forEach :: (IterOpHs a, KnownValue (IterOpElHs a)) => Expr a -- ^ Expression for the container to traverse -> Var (IterOpElHs a) -- ^ Variable for the current item (available to the code block) -> SomeIndigoState ((IterOpElHs a) : inp) -- ^ Code block to execute over each element of the container -> IndigoState inp inp forEach container var body = IndigoState $ \md -> let cde = gcCode $ usingIndigoState md (compileExpr container) newMd = pushRefMd var md bodyIndigoState = runSIS body newMd cleanGenCode in flip (GenCode (mdStack md)) L.nop $ stmtHook md (fmt $ "foreach (" +| var |+ " in " +| container |+ ")") $ exprHook md (pretty container) cde # L.iter (bodyIndigoState # L.drop) ---------------------------------------------------------------------------- -- Documentation ---------------------------------------------------------------------------- -- | Put a document item. doc :: DocItem di => di -> IndigoState s s doc di = IndigoState \md -> GenCode (mdStack md) (L.doc di) L.nop -- | Group documentation built in the given piece of code -- into a block dedicated to one thing, e.g. to one entrypoint. docGroup :: DocItem di => (SubDoc -> di) -> SomeIndigoState i -> SomeIndigoState i docGroup gr = overSIS $ \(GenCode md cd clr) -> SomeGenCode $ GenCode md (L.docGroup gr cd) clr -- | Insert documentation of the contract storage type. The type -- should be passed using type applications. {-# DEPRECATED docStorage "Use `doc (dStorage @storage)` instead." #-} docStorage :: forall storage s. TypeHasDoc storage => IndigoState s s docStorage = doc (dStorage @storage) -- | Give a name to the given contract. Apply it to the whole contract code. {-# DEPRECATED contractName "Use `docGroup name` instead." #-} contractName :: Text -> SomeIndigoState i -> SomeIndigoState i contractName cName = docGroup (DName cName) -- | Attach general info to the given contract. {-# DEPRECATED contractGeneral "Use `docGroup DGeneralInfoSection` instead." #-} contractGeneral :: SomeIndigoState i -> SomeIndigoState i contractGeneral = docGroup DGeneralInfoSection -- | Attach default general info to the contract documentation. contractGeneralDefault :: IndigoState s s contractGeneralDefault = IndigoState \md -> GenCode (mdStack md) L.contractGeneralDefault L.nop -- | Indigo version for the function of the same name from Lorentz. finalizeParamCallingDoc :: (NiceParameterFull cp, RequireSumType cp, HasCallStack) => Var cp -> SomeIndigoState (cp : inp) -> Expr cp -> SomeIndigoState inp finalizeParamCallingDoc vc act param = SomeIndigoState $ \md -> let cde = gcCode $ usingIndigoState md (compileExpr param) newMd = pushRefMd vc md in runSIS act newMd $ \(GenCode st1 cd clr) -> SomeGenCode $ flip (GenCode st1) (clr # L.drop) $ stmtHook md ("finalizeParamCallingDoc (" <> pretty param <> ")") $ exprHook md (pretty param) cde # L.finalizeParamCallingDoc cd ---------------------------------------------------------------------------- -- Contract call ---------------------------------------------------------------------------- selfCalling :: forall p inp mname. ( NiceParameterFull p , KnownValue (GetEntrypointArgCustom p mname) ) => EntrypointRef mname -> Var (ContractRef (GetEntrypointArgCustom p mname)) -- ^ Variable that will be assigned to the resulting 'ContractRef' -> IndigoState inp (ContractRef (GetEntrypointArgCustom p mname) : inp) selfCalling epRef var = stmtHookState (pretty var <> " := selfCalling " <> pretty (eprName epRef)) $ do nullaryOp (L.selfCalling @p epRef) assignTopVar var contractCalling :: forall cp inp epRef epArg addr. ( HasEntrypointArg cp epRef epArg , ToTAddress cp addr , ToT addr ~ ToT Address , KnownValue epArg ) => epRef -> Expr addr -> Var (Maybe (ContractRef epArg)) -- ^ Variable that will be assigned to the resulting 'ContractRef' -> IndigoState inp (Maybe (ContractRef epArg) : inp) contractCalling epRef addr var = stmtHookState (pretty var <> " := contractCalling " <> pretty addr) $ do unaryOp addr (L.contractCalling @cp epRef) assignTopVar var ---------------------------------------------------------------------------- -- Side-effects ---------------------------------------------------------------------------- transferTokens :: (NiceParameter p, HasSideEffects) => Expr p -> Expr Mutez -> Expr (ContractRef p) -> IndigoState inp inp transferTokens ep em ec = withStackVars $ \s -> ternaryOpFlat ep em ec (L.transferTokens # varActionOperation s) setDelegate :: HasSideEffects => Expr (Maybe KeyHash) -> IndigoState inp inp setDelegate e = withStackVars $ \s -> unaryOpFlat e (L.setDelegate # varActionOperation s) createContract :: (HasSideEffects, NiceStorage s, NiceParameterFull p) => L.Contract p s -> Expr (Maybe KeyHash) -> Expr Mutez -> Expr s -> Var Address -- ^ Variable that will be assigned to the resulting 'Address' -> IndigoState inp (Address : inp) createContract lCtr ek em es var = stmtHookState (fmt $ build var +| " := createContract (key_hash = " +| ek |+ ", mutez = " +| em |+ ", storage = " <> build es) $ do withStackVars $ \s -> ternaryOp ek em es $ L.createContract lCtr # varActionOperation (pushNoRef s) assignTopVar var ---------------------------------------------------------------------------- -- Functions, Procedures and Scopes ---------------------------------------------------------------------------- -- | Takes an arbitrary 'IndigoM' and wraps it into an 'IndigoFunction' -- producing a local scope for its execution. Once it executed, all -- non-returned variables are cleaned up so that the stack has only -- returned variables at the top. This also can be interpreted as -- @if True then f else nop@. -- -- Note, that by default we do not define scope inside indigo functions, -- meaning that once we want to create a new variable or return it from -- a function we need to do it inside @scope $ instr@ construction, for -- example: -- -- @ -- f :: IndigoFunction s Natural -- f = scope $ do -- *[s]* -- res <- newVar (0 :: Natural) -- *[Natural, s]* -- scope $ do -- _n <- newVar (1 :: Integer) -- *[Integer, Natural, s] -- res += 4 -- *[Natural, s]* -- return res -- *[s]* -- @ scope :: forall ret inp . ScopeCodeGen ret => SomeIndigoState inp -- ^ Code block to execute inside the scope -> ret -- ^ Return value(s) of the scoped code block -> RetVars ret -- ^ Variable(s) that will be assigned to the resulting value(s) -> IndigoState inp (RetOutStack ret ++ inp) scope f ret retVars = IndigoState $ \md@MetaData{..} -> runSIS f md $ \fs -> finalizeStatement @ret mdStack retVars $ stmtHook md (prettyAssign @ret retVars "scope") $ compileScope @ret (replStkMd md) fs ret -- | Add a comment comment :: MT.CommentType -> IndigoState i i comment t = IndigoState $ \md -> GenCode (mdStack md) (L.comment t) L.nop