-- SPDX-FileCopyrightText: 2020 Tocqueville Group -- -- SPDX-License-Identifier: LicenseRef-MIT-TQ -- | This module contains everything related to compilation from Indigo to Lorentz, -- including plain Indigo code, as well as Indigo contracts. module Indigo.Compilation ( compileIndigo , IndigoWithParams , IndigoContract , compileIndigoContract , Ops , HasSideEffects , operationsVar , HasStorage , storageVar ) where import qualified Data.Map as M import Data.Reflection (give) import qualified Data.Set as S import Data.Singletons (SingI(..)) import Data.Typeable ((:~:)(..), eqT) import Data.Vinyl.Core (RMap(..)) import qualified Indigo.Backend as B import Indigo.Compilation.Lambda import Indigo.Compilation.Params import Indigo.Frontend.Program (IndigoM(..), Program(..)) import Indigo.Frontend.Statement import Indigo.Internal hiding (SetField, return, (>>), (>>=)) import qualified Indigo.Internal as I import Indigo.Lorentz import Indigo.Prelude import qualified Lorentz.Instr as L import qualified Lorentz.Macro as L import Util.Peano -- | Iteration over Indigo freer monad compileIndigoM :: forall inp a . (forall x anyInp . StatementF IndigoM x -> SomeIndigoState anyInp x) -> IndigoM a -> SomeIndigoState inp a compileIndigoM _ (IndigoM (Done a)) = returnSIS a compileIndigoM interp (IndigoM (Instr i)) = interp i compileIndigoM interp (IndigoM (Bind instr cont)) = compileIndigoM interp (IndigoM instr) `bindSIS` (compileIndigoM interp . IndigoM . cont) -- | Convert frontend Freer to 'IndigoState'. -- -- First of all, this function generates the definitions of -- lambdas, creates the variables that refer to them -- and calls them in the places where they are used. -- This happens only for those lambdas that are called -- at least twice, those that are used only once will be -- inlined instead. -- -- After that the generation of the body code starts. simpleCompileIndigoM :: forall inp a . IndigoM a -> SomeIndigoState inp a simpleCompileIndigoM indigoM = let lambdas = S.toList (collectLambdas indigoM) in forMSIS lambdas defineLambda `bindSIS` (\defined -> let definedLambdas = M.fromList $ map (\l -> (_clName l, l)) defined in compileBody definedLambdas indigoM ) where compileBody definedLambdas = compileIndigoM (usingReader definedLambdas . compileSt) compileSt :: StatementF IndigoM x -> Reader (Map String CompiledLambda) (SomeIndigoState anyInp x) compileSt (LiftIndigoState cd) = pure cd compileSt (NewVar ex) = pure $ toSIS (B.newVar ex) compileSt (SetVar v ex) = pure $ toSIS (B.setVar v ex) compileSt (SetField v fName ex) = pure $ toSIS (B.setField v fName ex) compileSt (VarModification act var ex) = pure $ toSIS (B.updateVar act var ex) compileSt (LambdaPure1Call lName (body :: (Var arg -> IndigoM res)) argm) = execGenericLambda @'[] @res (B.executeLambdaPure1 @res) lName body argm compileSt (Lambda1Call (_ :: Proxy st) lName (body :: (Var arg -> IndigoM res)) argm) = execGenericLambda @'[st] @res (B.executeLambda1 @st @res) lName body argm compileSt (LambdaEff1Call (_ :: Proxy st) lName (body :: (Var arg -> IndigoM res)) argm) = execGenericLambda @'[st, Ops] @res (B.executeLambdaEff1 @st @res) lName body argm compileSt (Scope cd) = do definedLambdas <- ask pure $ withSIS (compileBody definedLambdas cd) (toSIS . B.scope) compileSt (If ex tb fb) = do definedLambdas <- ask pure $ withSIS (compileBody definedLambdas tb) $ \tb' -> withSIS (compileBody definedLambdas fb) $ \fb' -> toSIS (B.if_ ex tb' fb') compileSt (IfSome ex tb fb) = do definedLambdas <- ask pure $ withSIS1 (compileBody definedLambdas . tb) $ \tb' -> withSIS (compileBody definedLambdas fb) $ \fb' -> toSIS (B.ifSome ex tb' fb') compileSt (IfRight ex rb lb) = do definedLambdas <- ask pure $ withSIS1 (compileBody definedLambdas . rb) $ \rb' -> withSIS1 (compileBody definedLambdas . lb) $ \lb' -> toSIS (B.ifRight ex rb' lb') compileSt (IfCons ex tb fb) = do definedLambdas <- ask pure $ withSIS2 (\x y -> compileBody definedLambdas $ tb x y) $ \tb' -> withSIS (compileBody definedLambdas fb) $ \fb' -> toSIS (B.ifCons ex tb' fb') compileSt (Case grd clauses) = do definedLambdas <- ask pure $ toSIS $ B.caseRec grd (rmapClauses definedLambdas clauses) compileSt (EntryCase proxy grd clauses) = do definedLambdas <- ask pure $ toSIS $ B.entryCaseRec proxy grd (rmapClauses definedLambdas clauses) compileSt (EntryCaseSimple grd clauses) = do definedLambdas <- ask pure $ toSIS $ B.entryCaseSimpleRec grd (rmapClauses definedLambdas clauses) compileSt (While ex body) = do definedLambdas <- ask pure $ withSIS (compileBody definedLambdas body) $ \bd -> toSIS (B.while ex bd) compileSt (WhileLeft ex lb) = do definedLambdas <- ask pure $ withSIS1 (compileBody definedLambdas . lb) $ \lb' -> do toSIS (B.whileLeft ex lb') compileSt (ForEach e body) = do definedLambdas <- ask pure $ withSIS1 (compileBody definedLambdas . body) $ \bd -> toSIS (B.forEach e bd) compileSt (ContractName cName contr) = do definedLambdas <- ask pure $ withSIS (compileBody definedLambdas contr) $ toSIS . B.contractName cName compileSt (DocGroup gr ii) = do definedLambdas <- ask pure $ withSIS (compileBody definedLambdas ii) $ toSIS . B.docGroup gr compileSt (ContractGeneral contr) = do definedLambdas <- ask pure $ withSIS (compileBody definedLambdas contr) (toSIS . B.contractGeneral) compileSt (FinalizeParamCallingDoc entrypoint param) = do definedLambdas <- ask pure $ withSIS1 (compileBody definedLambdas . entrypoint) (\bd -> toSIS $ B.finalizeParamCallingDoc bd param) compileSt (TransferTokens expar exm exc) = pure $ toSIS (B.transferTokens expar exm exc) compileSt (SetDelegate kh) = pure $ toSIS (B.setDelegate kh) compileSt (CreateContract lCtr ek em es) = pure $ toSIS $ I.iget I.>>= \(MetaData s _) -> ternaryOp ek em es (L.createContract lCtr # varActionOperation (NoRef :& s)) I.>> makeTopVar compileSt (ContractCalling (_ :: Proxy cp) ref addr) = pure $ toSIS $ B.contractCalling @cp ref addr compileSt (FailWith ex) = pure $ toSIS $ B.failWith ex compileSt (Assert err expr) = pure $ toSIS $ B.assert err expr compileSt (FailCustom l expr) = pure $ toSIS $ B.failCustom l expr rmapClauses:: forall ret cs . RMap cs => Map String CompiledLambda -> Rec (IndigoMCaseClauseL IndigoM ret) cs -> Rec (B.IndigoCaseClauseL ret) cs rmapClauses definedLambdas = rmap (\(OneFieldIndigoMCaseClauseL cName clause) -> cName /-> (\v -> B.IndigoAnyOut $ compileBody definedLambdas $ clause v)) forMSIS :: [r] -> (forall someInp . r -> SomeIndigoState someInp v) -> SomeIndigoState someInp1 [v] forMSIS [] _ = returnSIS [] forMSIS (x : xs) f = f x `bindSIS` (\what -> (what :) <$> forMSIS xs f) defineLambda :: Lambda1Def -> SomeIndigoState someOut CompiledLambda defineLambda (LambdaPure1Def (_ :: Proxy (_s, arg, res)) lName fun) = defineGenericLambda @'[] B.initMetaDataPure B.createLambdaPure1 lName fun defineLambda (Lambda1Def (_ :: Proxy (st, arg, res)) lName fun) = defineGenericLambda @'[st] B.initMetaData B.createLambda1 lName fun defineLambda (LambdaEff1Def (_ :: Proxy (st, arg, res)) lName fun) = defineGenericLambda @'[st, Ops] B.initMetaDataEff B.createLambdaEff1 lName fun defineGenericLambda :: forall extra res arg someOut . (Typeable arg, Typeable res, Typeable extra) => (Var arg, MetaData (arg & extra)) -> (forall inpt out . B.LambdaCreator extra arg res inpt out) -> String -> (Var arg -> IndigoM res) -> SomeIndigoState someOut CompiledLambda defineGenericLambda (varArg, initMd) lambdaCreator lName fun = do runSIS (simpleCompileIndigoM $ fun varArg) initMd (\gc -> toSIS $ lambdaCreator (\_v -> IndigoState $ \_md -> gc)) `bindSIS` (returnSIS . CompiledLambda (Proxy @res) lName) execGenericLambda :: forall extra res arg someOut . (Typeable extra, KnownValue arg, Typeable res, B.ScopeCodeGen res) => (forall inpt . B.LambdaExecutor extra arg res inpt) -> String -> (Var arg -> IndigoM res) -> Expr arg -> Reader (Map String CompiledLambda) (SomeIndigoState someOut (B.RetVars res)) execGenericLambda executor lName (body :: (Var arg -> IndigoM res)) (argm :: Expr arg) = do compiled <- ask let maybeToRight' = flip maybeToRight -- This code seems to be pretty unsafe, but it works almost inevitably pure $ either (error . fromString) id $ do case M.lookup lName compiled of Nothing -> Right $ -- Just inline lambda without calling Lorentz lambda withSIS1 (compileBody compiled . body) (\bd -> toSIS $ B.newVar argm I.>>= (B.scope @res . bd)) Just compLam -> case compLam of CompiledLambda (_ :: Proxy res1) _ (varF :: Var (B.Lambda1Generic extra1 arg1 res1)) -> do Refl <- maybeToRight' (eqT @res @res1) ("unexpected result type of " ++ lName ++ " lambda didn't match") Refl <- maybeToRight' (eqT @arg @arg1) ("unexpected argument type of " ++ lName ++ " lambda didn't match") Refl <- maybeToRight' (eqT @extra @extra1) ("unexpected storage type of " ++ lName ++ " lambda didn't match") pure $ toSIS (executor varF argm) -- | Compile Indigo code to Lorentz. -- -- Note: it is necessary to specify the number of parameters (using the first -- type variable) of the Indigo function. Also, these should be on the top of -- the input stack in inverse order (see 'IndigoWithParams'). compileIndigo :: forall n inp a. ( SingI (ToPeano n), Default (MetaData inp) , AreIndigoParams (ToPeano n) inp, KnownValue a ) => IndigoWithParams (ToPeano n) inp a -> inp :-> inp compileIndigo paramCode = runSIS (simpleCompileIndigoM code) md cleanGenCode where (code, md) = fromIndigoWithParams @inp @_ @a paramCode def (sing @(ToPeano n)) -- | Type of a contract that can be compiled to Lorentz with 'compileIndigoContract'. type IndigoContract param st = (HasStorage st, HasSideEffects) => Var param -> IndigoM () -- | Compile Indigo code to Lorentz contract. -- Drop elements from the stack to return only @[Operation]@ and @storage@. compileIndigoContract :: forall param st . ( KnownValue param , IsObject st ) => IndigoContract param st -> ContractCode param st compileIndigoContract code = let (varOps, opsMd) = pushRefMd emptyMetadata mdSt = pushNoRefMd opsMd in -- Decompose storage value first, run contract and then compose it back. runSIS (deepDecomposeCompose @st) mdSt $ \(GenCode varSt decomposedMd decomposeSt composeSt) -> let (varParam, initMd) = pushRefMd decomposedMd everythingGiven = (give @(Var Ops) varOps $ give @(Var st) varSt code) varParam indigoCode = runSIS (simpleCompileIndigoM everythingGiven) initMd cleanGenCode in L.nil # L.swap # L.unpair # L.dip decomposeSt # -- decompose storage indigoCode # -- run indigo code L.drop # -- drop param composeSt # -- compose storage back L.swap # L.pair