-- 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) import Data.Vinyl ((<+>)) import Data.Vinyl.TypeLevel (type (++)) import Prelude hiding (tail) import Indigo.Internal.Object (HasSideEffects, IndigoObjectF(..), Ops, operationsVar) import Indigo.Internal.State (RefId, StackVars, StkEl(..)) 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 ref 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 ref 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 v 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 Cell 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 $ "Manually created or leaked variable. Ref #" <> show refId 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. Ref #" <> show ref _ -> 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"