indigo-0.4: src/Indigo/Internal/Lookup.hs
-- SPDX-FileCopyrightText: 2020 Tocqueville Group
--
-- SPDX-License-Identifier: LicenseRef-MIT-TQ
{- |
This module contains the logic to lookup 'Var's in a stack and the actions to
manipulate it.
For efficiency, actions are implemented using Lorentz macros.
To do so every necessary constraint is checked at runtime.
-}
module Indigo.Internal.Lookup
( -- * Variable Lookup Actions
varActionGet
, varActionSet
, varActionUpdate
, varActionOperation
-- * Vinyl manipulation helpers
, rtake
, rdrop
) where
import Data.Constraint (Dict(..), HasDict)
import Data.Singletons (Sing, SingI(..))
import Data.Type.Equality (TestEquality(..))
import Data.Typeable ((:~:)(..), eqT, typeRep)
import Data.Vinyl ((<+>))
import Data.Vinyl.TypeLevel (type (++))
import Fmt (pretty)
import Prelude hiding (tail)
import Indigo.Internal.Var
(HasSideEffects, Ops, RefId, StackVars(..), StackVars', StkEl(..), Var(..), operationsVar)
import Indigo.Lorentz
import qualified Lorentz.Instr as L
import qualified Lorentz.Macro as L
import Michelson.Typed (ToTs)
import qualified Michelson.Typed.Instr as MI
import qualified Michelson.Typed.T as MI
import Util.Peano
----------------------------------------------------------------------------
-- Variable Lookup Actions
----------------------------------------------------------------------------
-- | Puts a copy of the value for the given 'Var' on top of the stack
varActionGet :: forall a stk . KnownValue a => RefId -> StackVars stk -> stk :-> a : stk
varActionGet _ FailureStack = error "You try to get a cell on failure stack"
varActionGet ref (StkElements stk) = case varDepth @a ref stk of
VarDepth n -> duupXVar n ref stk
-- | Sets the value for the given 'Var' to the topmost value on the stack
varActionSet :: forall a stk . KnownValue a => RefId -> StackVars stk -> a : stk :-> stk
varActionSet _ FailureStack = error "You try to set a cell on failure stack"
varActionSet ref (StkElements stk) = case varDepth @a ref stk of
VarDepth n -> replaceNVar (SS n) ref (NoRef :& stk)
-- | Updates the value for the given 'Var' with the topmost value on the stack
-- using the given binary instruction.
varActionUpdate
:: forall a b stk . (KnownValue a, KnownValue b)
=> RefId
-> StackVars stk
-> '[b, a] :-> '[a]
-> (b ': stk) :-> stk
varActionUpdate _ FailureStack _ = error "You try to update a cell on failure stack"
varActionUpdate v (StkElements stk) instr = case varDepth @a v stk of
VarDepth n -> updateNVar (SS n) v (NoRef :& stk) instr
-- | Given a stack with a list of 'Operation's on its bottom, updates it by
-- appending the 'Operation' on the top.
varActionOperation
:: HasSideEffects
=> StackVars stk
-> (Operation ': stk) :-> stk
varActionOperation s =
case operationsVar of
Var refId -> varActionUpdate @Ops refId s L.cons
----------------------------------------------------------------------------
-- Variable-based Macros
----------------------------------------------------------------------------
-- | Like 'varActionGet', but requires the depth of the 'Var' in the input stack
duupXVar
:: forall inp a (m :: Peano) s tail.
( HasCallStack
, KnownValue a, KnownPeano m, SingI m
, s ~ ((Take m inp) ++ (a ': a ': Drop ('S m) inp))
, tail ~ (Drop ('S m) inp)
)
=> Sing m -> RefId -> StackVars' inp -> inp :-> (a ': inp)
duupXVar m v stk =
withVarMaybeDict (duupXClassConstraint n stk a s tail) $
L.duupXImpl @('S m) @inp @a @s @tail
where
a = Ref @a v
n = SS m
s = (rtake m stk) <+> (a :& a :& rdrop n stk)
tail = rdrop n stk
-- | Like 'varActionSet', but requires the depth of the 'Var' in the input stack
replaceNVar
:: forall s a (n :: Peano) mid tail.
( HasCallStack
, KnownValue a
, mid ~ (Take n (a ': s) ++ Drop n s)
, tail ~ Drop n s
)
=> Sing n -> RefId -> StackVars' (a ': s) -> (a ': s) :-> s
replaceNVar n v stk@(_ :& s) =
withVarMaybeDict (replaceNClassConstraint n s (Ref @a v) mid tail) $
L.replaceNImpl @n @s @a @mid @tail
where
mid = rtake n stk <+> rdrop n s
tail = rdrop n s
-- | Like 'varActionUpdate', but requires the depth of the 'Var' in the input stack
updateNVar
:: forall s a b mid tail (n :: Peano).
( HasCallStack
, KnownValue b
, tail ~ Drop n s
, mid ~ ((Drop ('S 'Z) (Take n (a ': s))) ++ (a ': Drop n (a ': s)))
)
=> Sing n -> RefId -> StackVars' (a ': s)
-> '[a, b] :-> '[b]
-> (a ': s) :-> s
updateNVar n v stk@(a :& s) instr =
withVarMaybeDict (updateNClassConstraint n s a (Ref @b v) mid tail) $
L.updateNImpl @n @s @a @b @mid @tail instr
where
mid = rdrop (SS SZ) (rtake n stk) <+> a :& rdrop n stk
tail = rdrop n s
withVarMaybeDict :: (HasDict c e, HasCallStack) => Maybe e -> (c => r) -> r
withVarMaybeDict mDict = withDict (fromMaybe (error constraintFailure) mDict)
where
-- NOTE: provided that the 'VarDepth' is correctly calculated every place
-- where this is used should never result in a 'Nothing', as this is only
-- necessary to prove to GHC properties that always hold.
-- For this reason a failure here is always unexpected
constraintFailure = "Unexpected failure in Var's constraint checking"
----------------------------------------------------------------------------
-- Variable's Depth
----------------------------------------------------------------------------
-- | Keeps the 0-indexed depth of a variable as a 'Peano' 'Sing'leton
data VarDepth where
VarDepth
:: forall idx . (KnownPeano idx, SingI idx)
=> Sing (idx :: Peano) -> VarDepth
-- | Calculates the 'VarDepth' of the given 'Var' in the stack
varDepth
:: forall a s. KnownValue a
=> RefId
-> StackVars' s
-> VarDepth
varDepth refId = \case
RNil -> error $
"You are looking for manually created or leaked variable. " <>
pretty refId <> " of type " <> show (typeRep (Proxy @a))
stk@(_ :& _) -> varDepthNonEmpty @a refId stk
varDepthNonEmpty
:: forall a s x xs. (KnownValue a, s ~ (x : xs))
=> RefId -> StackVars' s -> VarDepth
varDepthNonEmpty ref (x :& xs) = case x of
Ref topRef | ref == topRef -> case eqT @a @x of
Just Refl -> VarDepth SZ
Nothing -> error $
"Invalid variable type. " <> pretty ref <>
".\nWas looking for a " <> show (typeRep $ Proxy @a) <>
", but found a: " <> show (typeRep $ Proxy @x)
_ -> case varDepth @a ref xs of
VarDepth idx -> VarDepth (SS idx)
----------------------------------------------------------------------------
-- Macro class constraints
----------------------------------------------------------------------------
duupXClassConstraint
:: Sing n -> StackVars' inp -> StkEl a -> StackVars' s -> StackVars' s'
-> Maybe (Dict (L.DuupX n inp a s s'))
duupXClassConstraint n inp a s s' = case n of
SZ -> Nothing
SS SZ -> case inp of
(x :& _xs) -> do
Refl <- testEquality x a
return Dict
_ -> Nothing
SS (SS SZ) -> case inp of
(_ :& x :& _xs) -> do
Refl <- testEquality x a
return Dict
_ -> Nothing
SS (SS (SS m)) -> do
Dict <- duupXLorentzConstraint (SS (SS m)) inp a s s'
return Dict
replaceNClassConstraint
:: Sing n -> StackVars' s -> StkEl a -> StackVars' mid -> StackVars' tail
-> Maybe (Dict (L.ReplaceN n s a mid tail))
replaceNClassConstraint n s a mid tail = case n of
SZ -> Nothing
SS SZ -> case s of
(x :& _xs) -> do
Refl <- testEquality x a
return Dict
_ -> Nothing
SS (SS m) -> do
Dict <- replaceNLorentzConstraint (SS m) s a mid tail
return Dict
updateNClassConstraint
:: Sing n -> StackVars' s -> StkEl a -> StkEl b -> StackVars' mid -> StackVars' tail
-> Maybe (Dict (L.UpdateN n s a b mid tail))
updateNClassConstraint n s a b mid tail = case n of
SZ -> Nothing
SS SZ -> case s of
(x :& xs) -> do
Refl <- testEquality x b
Refl <- testEquality xs tail
return Dict
_ -> Nothing
SS (SS SZ) -> case s of
(_x :& y :& xs) -> do
Refl <- testEquality y b
Refl <- testEquality xs tail
return Dict
_ -> Nothing
SS (SS (SS m)) -> do
Dict <- updateNLorentzConstraint (SS (SS m)) s a b mid tail
return Dict
----------------------------------------------------------------------------
-- Lorentz constraints
----------------------------------------------------------------------------
duupXLorentzConstraint
:: (KnownPeano n, SingI n)
=> Sing n -> StackVars' inp -> StkEl a -> StackVars' s -> StackVars' s'
-> Maybe (Dict (L.ConstraintDuupXLorentz n inp a s s'))
duupXLorentzConstraint n s a s1 tail = do
Refl <- testEquality tail (rdrop (SS n) s)
Refl <- testEquality (toTVals tail) (rdrop (SS n) (toTVals s))
Dict <- dipNLorentzConstraint n s s1 (a :& tail) (a :& a :& tail)
Dict <- digLorentzConstraint n s1 (a :& s) a
return Dict
updateNLorentzConstraint
:: (KnownPeano n, SingI n)
=> Sing n -> StackVars' s -> StkEl a -> StkEl b -> StackVars' mid -> StackVars' tail
-> Maybe (Dict (L.ConstraintUpdateNLorentz n s a b mid tail))
updateNLorentzConstraint n s a b mid tail = do
Refl <- testEquality tail (rdrop (SS n) s)
Refl <- testEquality (toTVals tail) (rdrop (SS n) (toTVals s))
Dict <- dugLorentzConstraint n (a :& s) mid a
Dict <- dipNLorentzConstraint n mid s (a :& b :& tail) (b :& tail)
return Dict
replaceNLorentzConstraint
:: (KnownPeano n, SingI n)
=> Sing n -> StackVars' s -> StkEl a -> StackVars' mid -> StackVars' tail
-> Maybe (Dict (L.ConstraintReplaceNLorentz n s a mid tail))
replaceNLorentzConstraint n s a mid tail = do
Refl <- testEquality tail (rdrop (SS n) s)
Refl <- testEquality (toTVals tail) (rdrop (SS n) (toTVals s))
Dict <- dipNLorentzConstraint (SS n) (a :& s) mid (a :& tail) tail
Dict <- dugLorentzConstraint n mid s a
return Dict
dugLorentzConstraint
:: (KnownPeano n, SingI n)
=> Sing n -> StackVars' inp -> StackVars' out -> StkEl a
-> Maybe (Dict (L.ConstraintDUGLorentz n inp out a))
dugLorentzConstraint n inp out a = do
Dict <- dugConstraint n inp out a
Dict <- dugConstraint n (toTVals inp) (toTVals out) (toTVal a)
return Dict
dipNLorentzConstraint
:: (KnownPeano n, SingI n)
=> Sing n -> StackVars' inp -> StackVars' out -> StackVars' s -> StackVars' s'
-> Maybe (Dict (L.ConstraintDIPNLorentz n inp out s s'))
dipNLorentzConstraint n inp out s s' = do
Dict <- dipNConstraint n inp out s s'
Dict <- dipNConstraint n (toTVals inp) (toTVals out) (toTVals s) (toTVals s')
return Dict
digLorentzConstraint
:: (KnownPeano n, SingI n)
=> Sing n -> StackVars' inp -> StackVars' out -> StkEl a
-> Maybe (Dict (L.ConstraintDIGLorentz n inp out a))
digLorentzConstraint n inp out a = do
Dict <- digConstraint n inp out a
Dict <- digConstraint n (toTVals inp) (toTVals out) (toTVal a)
return Dict
----------------------------------------------------------------------------
-- Morley constraints
----------------------------------------------------------------------------
dugConstraint
:: (KnownPeano n, SingI n, TestEquality any)
=> Sing n -> Rec any inp -> Rec any out -> any a
-> Maybe (Dict (MI.ConstraintDUG' kind n inp out a))
dugConstraint n inp out a = do
Dict <- requireLongerThan out n
Refl <- testEquality (a :& rdrop (SS SZ) inp) inp
Refl <- testEquality (rtake n (rdrop (SS SZ) inp) <+> a :& rdrop (SS n) inp) out
return Dict
dipNConstraint
:: (KnownPeano n, SingI n, TestEquality any)
=> Sing n -> Rec any inp -> Rec any out -> Rec any s -> Rec any s'
-> Maybe (Dict (MI.ConstraintDIPN' kind n inp out s s'))
dipNConstraint n inp out s s' = do
Dict <- requireLongerOrSameLength inp n
Refl <- testEquality (rtake n inp <+> s) inp
Refl <- testEquality (rtake n inp <+> s') out
return Dict
digConstraint
:: (KnownPeano n, SingI n, TestEquality any)
=> Sing n -> Rec any inp -> Rec any out -> any a
-> Maybe (Dict (MI.ConstraintDIG' kind n inp out a))
digConstraint n inp out a = do
Dict <- requireLongerThan inp n
Refl <- testEquality inp (rtake n inp <+> a :& rdrop (SS n) inp)
Refl <- testEquality out (a :& rtake n inp <+> rdrop (SS n) inp)
return Dict
----------------------------------------------------------------------------
-- Conversion for ToT constraints
----------------------------------------------------------------------------
-- | Stack representation of 'MI.T' used for constraint checking
type TValStack (stk :: [MI.T]) = Rec TVal stk
-- | Simple datatype used to keep a 'MI.T' and its 'Typeable' constraint
data TVal (a :: MI.T) where
TVal :: Typeable a => TVal a
instance TestEquality TVal where
testEquality (TVal :: TVal a) (TVal :: TVal b) = eqT @a @b
toTVal :: forall a. StkEl a -> TVal (ToT a)
toTVal = \case
NoRef -> TVal @(ToT a)
Ref _ -> TVal @(ToT a)
toTVals :: StackVars' stk -> TValStack (ToTs stk)
toTVals = \case
RNil -> RNil
(NoRef :& xs) -> TVal :& toTVals xs
(Ref _ :& xs) -> TVal :& toTVals xs
----------------------------------------------------------------------------
-- Vinyl manipulation helpers
----------------------------------------------------------------------------
rtake :: Sing n -> Rec any s -> Rec any (Take n s)
rtake sn stk = case (sn, stk) of
(SZ, _) -> RNil
(SS n, (x :& xs)) -> x :& rtake n xs
(SS _, RNil) -> error "given stack is too small"
rdrop :: Sing n -> Rec any s -> Rec any (Drop n s)
rdrop sn stk = case (sn, stk) of
(SZ, _) -> stk
(SS n, (_ :& xs)) -> rdrop n xs
(SS _, RNil) -> error "given stack is too small"