indigo-0.6.0: src/Indigo/Common/State.hs
-- SPDX-FileCopyrightText: 2021 Oxhead Alpha
-- SPDX-License-Identifier: LicenseRef-MIT-OA
{-# LANGUAGE InstanceSigs #-}
{- |
This module contains the core of Indigo language:
'IndigoState', a datatype that represents its state.
It also includes some convenient functions to work with it,
to provide rebindable syntax.
'IndigoState' implements the functionality of a symbolic interpreter.
During its execution Lorentz code is being generated.
Functionally, it's the same as having Lorentz instruction that can access and
modify a 'StackVars', referring to values on the stack with a 'RefId'.
-}
module Indigo.Common.State
( -- * Indigo State
IndigoState (..)
, usingIndigoState
, (>>)
, (<$>)
, iput
, nopState
, assignTopVar
, withObject
, withObjectState
, withStackVars
, DecomposedObjects
, GenCodeHooks (..)
, emptyGenCodeHooks
, MetaData (..)
, stmtHook
, stmtHookState
, auxiliaryHook
, auxiliaryHookState
, exprHook
, exprHookState
, replStkMd
, alterStkMd
, pushRefMd
, pushNoRefMd
, popNoRefMd
, GenCode (..)
, cleanGenCode
, (##)
) where
import Data.Map qualified as M
import Data.Typeable (eqT, (:~:)(..))
import Fmt (pretty)
import Indigo.Backend.Prelude
import Indigo.Common.Object
import Indigo.Common.Var
import Indigo.Lorentz
import Lorentz.Instr qualified as L
import Morley.Michelson.Typed qualified as M
import Morley.Util.Peano
----------------------------------------------------------------------------
-- Indigo State
----------------------------------------------------------------------------
-- | IndigoState data type.
--
-- It takes as input a 'StackVars' (for the initial state) and returns a
-- 'GenCode' (for the resulting state and the generated Lorentz code).
--
-- IndigoState has to be used to write backend typed Lorentz code
-- from the corresponding frontend constructions.
--
-- It has no return type, IndigoState instruction may take one or more
-- "return variables", that they assign to values produced during their execution.
newtype IndigoState inp out = IndigoState {
runIndigoState :: MetaData inp -> GenCode inp out
}
-- | Inverse of 'runIndigoState' for utility.
usingIndigoState :: MetaData inp -> IndigoState inp out -> GenCode inp out
usingIndigoState md act = runIndigoState act md
-- | Then for rebindable syntax.
(>>) :: IndigoState inp out -> IndigoState out out1 -> IndigoState inp out1
(>>) a b = IndigoState $ \md ->
let GenCode st1 cd1 cl1 = runIndigoState a md in
let GenCode st2 cd2 cl2 = runIndigoState b (replStkMd md st1) in
GenCode st2 (cd1 ## cd2) (cl2 ## cl1)
-- | Put new 'GenCode'.
iput :: GenCode inp out -> IndigoState inp out
iput gc = IndigoState $ \_ -> gc
-- | The simplest 'IndigoState', it does not modify the stack, nor the produced
-- code.
nopState :: IndigoState inp inp
nopState = IndigoState $ \md -> GenCode (mdStack md) L.nop L.nop
-- | Assigns a variable to reference the element on top of the stack.
assignTopVar :: KnownValue x => Var x -> IndigoState (x : inp) (x : inp)
assignTopVar var = IndigoState $ \md ->
GenCode (assignVarAt var (mdStack md) SZ) L.nop L.nop
withObject
:: forall a r . KnownValue a
=> DecomposedObjects
-> Var a
-> (Object a -> r)
-> r
withObject objs (Var refId) f = case M.lookup refId objs of
Nothing -> f (Cell refId)
Just so -> case so of
SomeObject (obj :: Object a1) -> case eqT @a @a1 of
Just Refl -> f obj
Nothing ->
error $ "unexpectedly SomeObject with by reference #" <> pretty refId <> " has different type"
withObjectState
:: forall a inp out . KnownValue a
=> Var a
-> (Object a -> IndigoState inp out)
-> IndigoState inp out
withObjectState v f = IndigoState $ \md -> usingIndigoState md (withObject (mdObjects md) v f)
-- | Utility function to create 'IndigoState' that need access to the current 'StackVars'.
withStackVars :: (StackVars inp -> IndigoState inp out) -> IndigoState inp out
withStackVars fIs = IndigoState $ \md -> usingIndigoState md (fIs $ mdStack md)
----------------------------------------------------------------------------
-- MetaData primitives
----------------------------------------------------------------------------
type DecomposedObjects = Map RefId SomeObject
data MetaData inp = MetaData
{ mdStack :: StackVars inp
, mdObjects :: DecomposedObjects
, mdHooks :: GenCodeHooks
}
data GenCodeHooks = GenCodeHooks
{ gchStmtHook :: forall inp out . Text -> (inp :-> out) -> (inp :-> out)
, gchAuxiliaryHook :: forall inp out . Text -> (inp :-> out) -> (inp :-> out)
, gchExprHook :: forall inp out . Text -> (inp :-> out) -> (inp :-> out)
-- pva701: dunno whether this level of verbosity is needed
--, csSubExpr :: forall a inp out . Expr a -> (inp :-> out) -> (inp :-> out)
}
instance Semigroup GenCodeHooks where
GenCodeHooks a b c <> GenCodeHooks a1 b1 c1 = GenCodeHooks
{ gchStmtHook = \t cd -> a1 t (a t cd)
, gchAuxiliaryHook = \t cd -> b1 t (b t cd)
, gchExprHook = \t cd -> c1 t (c t cd)
}
instance Monoid GenCodeHooks where
mempty = emptyGenCodeHooks
emptyGenCodeHooks :: GenCodeHooks
emptyGenCodeHooks = GenCodeHooks (const id) (const id) (const id)
stmtHook :: forall inp out any . MetaData any -> Text -> (inp :-> out) -> (inp :-> out)
stmtHook MetaData{..} tx cd = (gchStmtHook mdHooks) tx cd
stmtHookState :: Text -> IndigoState inp out -> IndigoState inp out
stmtHookState tx cd = IndigoState $ \md ->
let GenCode st c cl = usingIndigoState md cd in
GenCode st (stmtHook md tx c) cl
auxiliaryHook :: forall inp out any . MetaData any -> Text -> (inp :-> out) -> (inp :-> out)
auxiliaryHook MetaData{..} tx cd = (gchAuxiliaryHook mdHooks) tx cd
auxiliaryHookState :: Text -> IndigoState inp out -> IndigoState inp out
auxiliaryHookState tx cd = IndigoState $ \md ->
let GenCode st c cl = usingIndigoState md cd in
GenCode st (auxiliaryHook md tx c) cl
exprHook :: forall inp out any . MetaData any -> Text -> (inp :-> out) -> (inp :-> out)
exprHook MetaData{..} exTx cd = (gchExprHook mdHooks) exTx cd
exprHookState :: Text -> IndigoState inp out -> IndigoState inp out
exprHookState tx cd = IndigoState $ \md ->
let GenCode st c cl = usingIndigoState md cd in
GenCode st (exprHook md tx c) cl
replStkMd :: MetaData inp -> StackVars inp1 -> MetaData inp1
replStkMd md = alterStkMd md . const
alterStkMd :: MetaData inp -> (StackVars inp -> StackVars inp1) -> MetaData inp1
alterStkMd (MetaData stk objs cm) f = MetaData (f stk) objs cm
-- | 'pushRef' version for 'MetaData'
pushRefMd :: KnownValue a => Var a -> MetaData inp -> MetaData (a : inp)
pushRefMd var md = alterStkMd md (pushRef var)
-- | 'pushNoRef' version for 'MetaData'
pushNoRefMd :: KnownValue a => MetaData inp -> MetaData (a : inp)
pushNoRefMd md = alterStkMd md pushNoRef
-- | 'popNoRef' version for 'MetaData'
popNoRefMd :: MetaData (a : inp) -> MetaData inp
popNoRefMd md = alterStkMd md popNoRef
----------------------------------------------------------------------------
-- Code generation primitives
----------------------------------------------------------------------------
-- | Resulting state of IndigoM.
data GenCode inp out = GenCode
{ gcStack :: ~(StackVars out)
-- ^ Stack of the symbolic interpreter.
, gcCode :: inp :-> out
-- ^ Generated Lorentz code.
, gcClear :: out :-> inp
-- ^ Clearing Lorentz code.
}
-- | Produces the generated Lorentz code that cleans after itself, leaving the
-- same stack as the input one
cleanGenCode :: GenCode inp out -> inp :-> inp
cleanGenCode GenCode {..} = gcCode ## gcClear
----------------------------------------------------------------------------
-- Helpers
----------------------------------------------------------------------------
-- | Version of '#' which performs some optimizations immediately.
--
-- In particular, this avoids glueing @Nop@s.
(##) :: (a :-> b) -> (b :-> c) -> (a :-> c)
l ## r =
-- We are very verbose about cases to avoid
-- significant compilation time increase
case l of
I M.Nop -> case r of
I x -> I x
_ -> l # r
I x -> case r of
I M.Nop -> I x
_ -> l # r
_ -> l # r