packages feed

indigo-0.4: src/Indigo/Internal/Expr/Compilation.hs

-- SPDX-FileCopyrightText: 2020 Tocqueville Group
--
-- SPDX-License-Identifier: LicenseRef-MIT-TQ

-- | 'Expr' compilation

module Indigo.Internal.Expr.Compilation
  ( compileExpr

  , ObjManipulationRes (..)
  , runObjectManipulation
  , namedToExpr

  , nullaryOp
  , unaryOp
  , binaryOp
  , ternaryOp

  , nullaryOpFlat
  , unaryOpFlat
  , binaryOpFlat
  , ternaryOpFlat
  ) where

import Data.Vinyl.Core (RMap(..))

import qualified Lorentz.ADT as L
import qualified Lorentz.Instr as L
import qualified Lorentz.Macro as L
import qualified Lorentz.StoreClass as L
import Michelson.Typed.Haskell.Instr.Product (GetFieldType)

import Indigo.Backend.Prelude
import Indigo.Internal.Expr.Types
import Indigo.Internal.Field
import Indigo.Internal.Lookup (varActionGet)
import Indigo.Internal.Object
  (IndigoObjectF(..), NamedFieldObj(..), castFieldConstructors, namedToTypedRec, typedToNamedRec)
import Indigo.Internal.State
  (DecomposedObjects, GenCode(..), IndigoState(..), MetaData(..), replStkMd, usingIndigoState,
  withObject, withObjectState)
import Indigo.Internal.Var (Var(..), pushNoRef)
import Indigo.Lorentz

compileExpr :: forall a inp . Expr a -> IndigoState inp (a : inp)
compileExpr (C a) = IndigoState $ \md -> GenCode (pushNoRef $ mdStack md) (L.push a) L.drop
compileExpr (V v) = withObjectState v $ compileObjectF namedToExpr
compileExpr (Update m key val) = ternaryOp key val m L.update
compileExpr (Add e1 e2) = binaryOp e1 e2 L.add
compileExpr (Sub e1 e2) = binaryOp e1 e2 L.sub
compileExpr (Mul e1 e2) = binaryOp e1 e2 L.mul
compileExpr (Div e1 e2) = binaryOp e1 e2 (L.ediv # L.ifSome L.car (failUsing [mt|devision by zero|]))
compileExpr (Mod e1 e2) = binaryOp e1 e2 (L.ediv # L.ifSome L.cdr (failUsing [mt|devision by zero|]))
compileExpr (Abs e) = unaryOp e L.abs
compileExpr (Neg e) = unaryOp e L.neg

compileExpr (Lsl e1 e2) = binaryOp e1 e2 L.lsl
compileExpr (Lsr e1 e2) = binaryOp e1 e2 L.lsr

compileExpr (Eq' e1 e2) = binaryOp e1 e2 L.eq
compileExpr (Neq e1 e2) = binaryOp e1 e2 L.neq
compileExpr (Lt e1 e2) = binaryOp e1 e2 L.lt
compileExpr (Le e1 e2) = binaryOp e1 e2 L.le
compileExpr (Gt e1 e2) = binaryOp e1 e2 L.gt
compileExpr (Ge e1 e2) = binaryOp e1 e2 L.ge
compileExpr (IsNat e) = unaryOp e L.isNat
compileExpr (Int' e) = unaryOp e L.int
compileExpr (Coerce e) = unaryOp e checkedCoerce_
compileExpr (ForcedCoerce e) = unaryOp e forcedCoerce_
compileExpr (And e1 e2) = binaryOp e1 e2 L.and
compileExpr (Or e1 e2) = binaryOp e1 e2 L.or
compileExpr (Xor e1 e2) = binaryOp e1 e2 L.xor
compileExpr (Not e) = unaryOp e L.not

compileExpr (Fst e) = unaryOp e L.car
compileExpr (Snd e) = unaryOp e L.cdr
compileExpr (Pair e1 e2) = binaryOp e1 e2 L.pair

compileExpr (Some e) = unaryOp e L.some
compileExpr None = nullaryOp L.none
compileExpr (Right' e) = unaryOp e L.right
compileExpr (Left' e) = unaryOp e L.left
compileExpr (Pack e) = unaryOp e L.pack
compileExpr (Unpack e) = unaryOp e L.unpack
compileExpr (PackRaw e) = unaryOp e L.packRaw
compileExpr (UnpackRaw e) = unaryOp e L.unpackRaw
compileExpr Nil = nullaryOp L.nil
compileExpr (Cons e1 e2) = binaryOp e1 e2 L.cons
compileExpr (Contract e) = unaryOp e L.contract
compileExpr Self = nullaryOp L.self
compileExpr (ContractAddress ec) = unaryOp ec L.address
compileExpr (ContractCallingUnsafe epName addr) = unaryOp addr (L.contractCallingUnsafe epName)
compileExpr (RunFutureContract con) = unaryOp con L.runFutureContract
compileExpr (ConvertEpAddressToContract epAddr) = unaryOp epAddr L.epAddressToContract
compileExpr (MakeView e1 e2) = binaryOp e1 e2 (L.pair # L.wrapView)
compileExpr (MakeVoid e1 e2) = binaryOp e1 e2 (L.pair # L.wrapVoid)

compileExpr (Mem k c) = binaryOp k c L.mem
compileExpr (Size s) = unaryOp s L.size

compileExpr (StInsertNew l err k v store) =
  ternaryOp k v store $ L.stInsertNew l (failUsing err)
compileExpr (StInsert l k v store) =
  ternaryOp k v store $ L.stInsert l
compileExpr (StGet l ekey estore) = binaryOp ekey estore (L.stGet l)
compileExpr (StMem l ekey estore) = binaryOp ekey estore (L.stMem l)
compileExpr (StUpdate l ekey evalue estore) = ternaryOp ekey evalue estore (L.stUpdate l)
compileExpr (StDelete l ekey estore) = binaryOp ekey estore (L.stDelete l)

compileExpr (Wrap l exFld) = unaryOp exFld $ L.wrapOne l
compileExpr (Unwrap l exDt) = unaryOp exDt $ L.unwrapUnsafe_ l

compileExpr (ObjMan fldAcc) = compileObjectManipulation fldAcc
compileExpr (Construct _ fields) = IndigoState $ \md ->
  let cd = L.construct $ rmap (\e -> fieldCtor $ gcCode $ runIndigoState (compileExpr e) md) fields in
  GenCode (pushNoRef $ mdStack md) cd L.drop
compileExpr (ConstructWithoutNamed _ fields) = IndigoState $ \md ->
  let fieldCtrs =
          castFieldConstructors @a $
            rmap (fieldCtor . gcCode . usingIndigoState md . compileExpr) fields
  in GenCode (pushNoRef $ mdStack md) (L.construct @a fieldCtrs) L.drop
compileExpr (Name l e) = unaryOp e (toNamed l)
compileExpr (UnName l e) = unaryOp e (fromNamed l)

compileExpr (Slice ex1 ex2 ex3) = ternaryOp ex1 ex2 ex3 L.slice
compileExpr (Cast ex) = unaryOp ex L.cast
compileExpr (Concat ex1 ex2) = binaryOp ex1 ex2 L.concat
compileExpr (Concat' ex) = unaryOp ex L.concat'

compileExpr (ImplicitAccount kh) = unaryOp kh L.implicitAccount
compileExpr Now = nullaryOp L.now
compileExpr Sender = nullaryOp L.sender
compileExpr Amount = nullaryOp L.amount
compileExpr (CheckSignature pk sig bs) = ternaryOp pk sig bs L.checkSignature
compileExpr (Sha256 c) = unaryOp c L.sha256
compileExpr (Sha512 c) = unaryOp c L.sha512
compileExpr (Blake2b c) = unaryOp c L.blake2B
compileExpr (HashKey hk) = unaryOp hk L.hashKey
compileExpr ChainId = nullaryOp L.chainId
compileExpr Balance = nullaryOp L.balance

compileExpr EmptySet = nullaryOp L.emptySet

compileExpr (Get k m) = binaryOp k m L.get
compileExpr EmptyMap = nullaryOp L.emptyMap
compileExpr EmptyBigMap = nullaryOp L.emptyBigMap

compileExpr (Exec inp lambda) = binaryOp inp lambda L.exec
compileExpr (NonZero e) = unaryOp e L.nonZero

--------------------------------------------
-- Object manipulation: set, get fields
--------------------------------------------

-- | Compile 'ObjectManipulation' datatype to a cell on the stack.
-- This function leverages 'ObjManipulationRes' to put off actual field compilation.
compileObjectManipulation :: ObjectManipulation a -> IndigoState inp (a : inp)
compileObjectManipulation fa = IndigoState $ \md -> case runObjectManipulation (mdObjects md) fa of
  StillObject composite -> usingIndigoState md $ compileObjectF unNamedFieldExpr composite
  OnStack computation   -> usingIndigoState md computation

namedToExpr :: NamedFieldObj x name -> Expr (GetFieldType x name)
namedToExpr (NamedFieldObj flObj) = objToExpr namedToExpr flObj

-- | Convert arbitrary 'IndigoObjectF' into 'Expr'
-- with respect to given converter for fields.
objToExpr
  :: forall a f .
     (forall name . f name -> Expr (GetFieldType a name))
  -> IndigoObjectF f a
  -> Expr a
objToExpr _ (Cell refId) = V (Var @a refId)
objToExpr convExpr (Decomposed fields) =
  ConstructWithoutNamed (Proxy @a) (namedToTypedRec @a convExpr fields)

-- | Compile 'IndigoObjectF' to a stack cell,
-- with respect to given function that compiles inner fields.
compileObjectF
  :: forall a inp f .
     (forall name . f name -> Expr (GetFieldType a name))
  -> IndigoObjectF f a
  -> IndigoState inp (a : inp)
compileObjectF _ (Cell ref) = IndigoState $ \(mdStack -> s) ->
  GenCode (pushNoRef s) (varActionGet @a ref s) L.drop
compileObjectF conv obj = compileExpr $ objToExpr conv obj

-- | 'ObjManipulationRes' represents a postponed compilation of
-- 'ObjectManipulation' datatype. When 'ObjectManipulation' is being compiled
-- we are trying to put off the generation of code for work with an object
-- because we can just go to a deeper field without its "materialization"
-- onto stack.
data ObjManipulationRes inp a where
  StillObject :: ObjectExpr a -> ObjManipulationRes inp a
  OnStack :: IndigoState inp (a : inp) -> ObjManipulationRes inp a

-- | This function might look cumbersome
-- but basically it either goes deeper to an inner field or generates Lorentz code.
runObjectManipulation :: DecomposedObjects -> ObjectManipulation x -> ObjManipulationRes inp x
runObjectManipulation objs (Object e) = exprToManRes objs e

runObjectManipulation objs (ToField (v :: ObjectManipulation dt) (targetLb :: Label fname)) =
  case runObjectManipulation objs v of
    -- In case of decomposed fields, we just go deeper.
    StillObject (Decomposed fields) ->
      case fieldLens @dt @fname of
        -- If we access direct field, we just fetch it from fields
        TargetField lb _ -> exprToManRes objs $ unNamedFieldExpr (fetchField @dt lb fields)
        -- If we access deeper field, we fetch direct field and goes to the deeper field
        DeeperField lb _ ->
          let fe = unNamedFieldExpr $ fetchField @dt lb fields in
          runObjectManipulation objs (ToField (Object fe) targetLb)
    -- If stored object as cell on the stack, we get its field
    -- using 'sopToField', and since this moment 'ObjManipulationRes becomes
    -- a computation, not object anymore.
    StillObject (Cell refId) ->
      OnStack $ unaryOp (V $ Var refId) (sopToField @dt (flSFO fieldLens) targetLb)
    -- If we already got into computation, we use 'sopToField' to fetch field.
    OnStack compLHS -> OnStack $ IndigoState $ \mdI ->
      let cd = gcCode $ usingIndigoState mdI compLHS in
      GenCode (pushNoRef $ mdStack mdI) (cd # sopToField (flSFO fieldLens) targetLb) L.drop

runObjectManipulation objs (SetField (ev :: ObjectManipulation dt) (targetLb :: Label fname) ef) =
  case runObjectManipulation objs ev of
    StillObject lhsObj@(Decomposed fields) ->
      case fieldLens @dt @fname of
        -- If we set direct field, we just reassign its value with new one.
        TargetField lb _ ->
          StillObject $ Decomposed $ assignField @dt lb (NamedFieldExpr ef) fields
        -- If we set deeper field, we need to call recursively
        -- from a direct field, and set a target field of direct field.
        -- Getting a new value of direct field, we set the direct field to this value.
        DeeperField (lb :: Label interm) _ ->
          let fe = unNamedFieldExpr (fetchField @dt lb fields) in
          -- Computing new value of direct field
          case runObjectManipulation objs (SetField (Object fe) targetLb ef) of
            -- If it's still an object, we just reassign direct field with it.
            StillObject updField -> StillObject $ Decomposed $
              assignField @dt lb (NamedFieldExpr $ objToExpr unNamedFieldExpr updField) fields
            -- Otherwise, we use power of 'L.setField' to set a new value.
            OnStack rhs ->
              setFieldOnStack (compileObjectF unNamedFieldExpr lhsObj) rhs (L.setField @dt @interm lb)
    -- If stored object is Cell on stack, we set its field
    -- using 'sopSetField', and since this moment 'ObjManipulationRes' becomes
    -- a computation, not object anymore.
    StillObject (Cell refId) ->
      OnStack $ binaryOp ef (V $ Var refId) $ sopSetField (flSFO fieldLens) targetLb
    -- If we already got into computation, we use 'sopSetField' to set a field.
    OnStack compLHS ->
      setFieldOnStack compLHS (compileExpr ef) (sopSetField (flSFO $ fieldLens @dt) targetLb)
  where
    setFieldOnStack
      :: IndigoState inp (dt : inp)
      -> IndigoState (dt : inp) (fld : dt : inp)
      -> fld : dt : inp :-> dt : inp
      -> ObjManipulationRes inp dt
    setFieldOnStack lhs rhs setOp = OnStack $ IndigoState $ \mdI ->
      let GenCode st1 cdObj _cl1 = runIndigoState lhs mdI in
      let GenCode _st2 cdFld _cl2 = runIndigoState rhs (replStkMd mdI st1) in
      GenCode (pushNoRef $ mdStack mdI) (cdObj # cdFld # setOp) L.drop

-- | Convert an expression to 'ObjManipulationRes'.
-- The function pattern matches on some specific cases
-- of expression those compilation into a stack cell may be postponed.
-- They include 'Decomposed' variables and 'ConstructWithoutNamed' expressions.
--
-- This function can't be called for 'ObjMan' constructor, but we
-- take care of it just in case.
exprToManRes :: forall x inp . DecomposedObjects -> Expr x -> ObjManipulationRes inp x
exprToManRes objs (ObjMan objMan) = runObjectManipulation objs objMan
exprToManRes _ (ConstructWithoutNamed _ fields) =
  StillObject $ Decomposed $ typedToNamedRec @x NamedFieldExpr fields
exprToManRes objs (V var) = withObject objs var $ \case
  Cell refId ->
    StillObject $ Cell refId
  Decomposed fields ->
    StillObject $ Decomposed $ rmap (NamedFieldExpr . namedToExpr) fields
exprToManRes _ ex = OnStack $ compileExpr ex

---------------------------------------------------
-- Convenient helpers for operators compilation
---------------------------------------------------

ternaryOp
  :: KnownValue res
  => Expr n
  -> Expr m
  -> Expr l
  -> n : m : l : inp :-> res : inp
  -> IndigoState inp (res : inp)
ternaryOp e1 e2 e3 opCode = IndigoState $ \md ->
  let GenCode st3 cd3 _cl3  = runIndigoState (compileExpr e3) md in
  let GenCode st2 cd2 _cl2  = runIndigoState (compileExpr e2) (replStkMd md st3) in
  let GenCode _st1 cd1 _cl1 = runIndigoState (compileExpr e1) (replStkMd md st2) in
  GenCode (pushNoRef $ mdStack md) (cd3 # cd2 # cd1 # opCode) L.drop

binaryOp
  :: KnownValue res
  => Expr n -> Expr m
  -> n : m : inp :-> res : inp
  -> IndigoState inp (res : inp)
binaryOp e1 e2 opCode = IndigoState $ \md ->
  let GenCode st2 cd2 _cl2  = runIndigoState (compileExpr e2) md in
  let GenCode _st1 cd1 _cl1 = runIndigoState (compileExpr e1) (replStkMd md st2) in
  GenCode (pushNoRef $ mdStack md) (cd2 # cd1 # opCode) L.drop

unaryOp
  :: KnownValue res
  => Expr n
  -> n : inp :-> res : inp
  -> IndigoState inp (res : inp)
unaryOp e opCode = IndigoState $ \md ->
  let cd = gcCode $ runIndigoState (compileExpr e) md in
  GenCode (pushNoRef $ mdStack md) (cd # opCode) L.drop

nullaryOp :: KnownValue res => inp :-> res ': inp -> IndigoState inp (res ': inp)
nullaryOp lorentzInstr = IndigoState $ \md ->
  GenCode (pushNoRef $ mdStack md) lorentzInstr L.drop

ternaryOpFlat
  :: Expr n
  -> Expr m
  -> Expr l
  -> n : m : l : inp :-> inp
  -> IndigoState inp inp
ternaryOpFlat e1 e2 e3 opCode = IndigoState $ \md ->
  let GenCode st3 cd3 _cl3  = runIndigoState (compileExpr e3) md in
  let GenCode st2 cd2 _cl2  = runIndigoState (compileExpr e2) (replStkMd md st3) in
  let GenCode _st1 cd1 _cl1 = runIndigoState (compileExpr e1) (replStkMd md st2) in
  GenCode (mdStack md) (cd3 # cd2 # cd1 # opCode) L.nop

binaryOpFlat
  :: Expr n -> Expr m
  -> n : m : inp :-> inp
  -> IndigoState inp inp
binaryOpFlat e1 e2 opCode = IndigoState $ \md ->
  let GenCode st2 cd2 _cl2  = runIndigoState (compileExpr e2) md in
  let GenCode _st1 cd1 _cl1 = runIndigoState (compileExpr e1) (replStkMd md st2) in
  GenCode (mdStack md) (cd2 # cd1 # opCode) L.nop

unaryOpFlat
  :: Expr n
  -> n : inp :-> inp
  -> IndigoState inp inp
unaryOpFlat e opCode = IndigoState $ \md ->
  let cd = gcCode $ runIndigoState (compileExpr e) md in
  GenCode (mdStack md) (cd # opCode) L.nop

nullaryOpFlat :: inp :-> inp -> IndigoState inp inp
nullaryOpFlat lorentzInstr = IndigoState $ \md -> GenCode (mdStack md) lorentzInstr L.nop