morley-1.19.2: src/Morley/Michelson/Macro.hs
-- SPDX-FileCopyrightText: 2021 Oxhead Alpha
-- SPDX-License-Identifier: LicenseRef-MIT-OA
module Morley.Michelson.Macro
(
-- * Macros types
CadrStruct (..)
, PairStruct (..)
, UnpairStruct (..)
, Macro (..)
-- * Morley Parsed value types
, ParsedValue
-- * Morley Parsed instruction types
, ParsedInstr
, ParsedOp (..)
, ParsedSeq (..)
, ParsedUExtInstr
-- * For utilities
, expandContract
, expandValue
-- * For parsing
, mapPairLeaves
-- * Internals exported for tests
, expand
, expandSeq
, expandMacro
, expandPapair
, expandUnpapair
, expandCadr
, expandSetCadr
, expandMapCadr
) where
import Data.Aeson.TH (deriveJSON)
import Data.Data (Data(..))
import Data.Default (def)
import Fmt (Buildable(build), blockListF, (+|), (|+))
import Morley.Michelson.ErrorPos
import Morley.Michelson.Printer (RenderDoc(..))
import Morley.Michelson.Untyped
import Morley.Michelson.Untyped.HoistInstr
import Morley.Util.Aeson
data PairStruct
= F FieldAnn
| P PairStruct PairStruct
deriving stock (Eq, Show, Data, Generic)
deriving anyclass Buildable
instance NFData PairStruct
data UnpairStruct
= UF
| UP UnpairStruct UnpairStruct
deriving stock (Eq, Show, Data, Generic)
deriving anyclass Buildable
instance NFData UnpairStruct
data CadrStruct
= A
| D
deriving stock (Eq, Show, Data, Generic)
deriving anyclass Buildable
instance NFData CadrStruct
-- | Unexpanded instructions produced directly by the @ops@ parser, which
-- contains primitive Michelson Instructions, inline-able macros and sequences
data ParsedOp
= Prim ParsedInstr SrcPos -- ^ Primitive Michelson instruction
| Mac Macro SrcPos -- ^ Built-in Michelson macro defined by the specification
| Seq [ParsedOp] SrcPos -- ^ A sequence of instructions
deriving stock (Eq, Show, Data, Generic)
instance RenderDoc ParsedOp where
renderDoc pn parsedOp = renderDoc pn $ expand parsedOp
instance Buildable ParsedOp where
build = \case
Prim parseInstr _ -> "<Prim: "+|parseInstr|+">"
Mac macro _ -> "<Mac: "+|macro|+">"
Seq parsedOps _ -> "<Seq: "+|parsedOps|+">"
instance NFData ParsedOp
-------------------------------------
-- Types produced by parser
-------------------------------------
data ParsedSeq op
= PSSingleMacro SrcPos Macro
| PSSequence [op]
deriving stock (Eq, Show, Data, Generic, Functor, Foldable)
deriving anyclass NFData
type ParsedUExtInstr = ExtInstrAbstract ParsedSeq ParsedOp
type ParsedInstr = InstrAbstract ParsedSeq ParsedOp
type ParsedValue = Value' ParsedSeq ParsedOp
-- | Built-in Michelson Macros defined by the specification
data Macro
= CMP ParsedInstr
| IFX ParsedInstr (ParsedSeq ParsedOp) (ParsedSeq ParsedOp)
| IFCMP ParsedInstr (ParsedSeq ParsedOp) (ParsedSeq ParsedOp)
| FAIL
| PAPAIR PairStruct TypeAnn VarAnn
| UNPAPAIR UnpairStruct
| CADR [CadrStruct] VarAnn FieldAnn
| CARN VarAnn Word
| CDRN VarAnn Word
| SET_CADR [CadrStruct] VarAnn FieldAnn
| MAP_CADR [CadrStruct] VarAnn FieldAnn (ParsedSeq ParsedOp)
| DIIP Word (ParsedSeq ParsedOp)
| DUUP Word VarAnn
| ASSERT
| ASSERTX ParsedInstr
| ASSERT_CMP ParsedInstr
| ASSERT_NONE
| ASSERT_SOME
| ASSERT_LEFT
| ASSERT_RIGHT
| IF_SOME (ParsedSeq ParsedOp) (ParsedSeq ParsedOp)
| IF_RIGHT (ParsedSeq ParsedOp) (ParsedSeq ParsedOp)
deriving stock (Eq, Show, Data, Generic)
instance Buildable op => Buildable (ParsedSeq op) where
build = \case
PSSingleMacro _ mac -> build mac
PSSequence xs -> blockListF xs
instance Buildable Macro where
build = \case
CMP parsedInstr -> "<CMP: "+|parsedInstr|+">"
IFX parsedInstr parsedOps1 parsedOps2 -> "<IFX: "+|parsedInstr|+", "+|parsedOps1|+", "+|parsedOps2|+">"
IFCMP parsedInstr parsedOps1 parsedOps2 -> "<IFCMP: "+|parsedInstr|+", "+|parsedOps1|+", "+|parsedOps2|+">"
FAIL -> "FAIL"
PAPAIR pairStruct typeAnn varAnn -> "<PAPAIR: "+|pairStruct|+", "+|typeAnn|+", "+|varAnn|+">"
UNPAPAIR pairStruct -> "<UNPAPAIR: "+|pairStruct|+">"
CADR cadrStructs varAnn fieldAnn -> "<CADR: "+|cadrStructs|+", "+|varAnn|+", "+|fieldAnn|+">"
CARN varAnn idx -> "<CAR: #"+|idx|+","+|varAnn|+">"
CDRN varAnn idx -> "<CDR: #"+|idx|+","+|varAnn|+">"
SET_CADR cadrStructs varAnn fieldAnn -> "<SET_CADR: "+|cadrStructs|+", "+|varAnn|+", "+|fieldAnn|+">"
MAP_CADR cadrStructs varAnn fieldAnn parsedOps -> "<MAP_CADR: "+|cadrStructs|+", "+|varAnn|+", "+|fieldAnn|+", "+|parsedOps|+">"
DIIP integer parsedOps -> "<DIIP: "+|integer|+", "+|parsedOps|+">"
DUUP integer varAnn -> "<DUUP: "+|integer|+", "+|varAnn|+">"
ASSERT -> "ASSERT"
ASSERTX parsedInstr -> "<ASSERTX: "+|parsedInstr|+">"
ASSERT_CMP parsedInstr -> "<ASSERT_CMP: "+|parsedInstr|+">"
ASSERT_NONE -> "ASSERT_NONE"
ASSERT_SOME -> "ASSERT_SOME"
ASSERT_LEFT -> "ASSERT_LEFT"
ASSERT_RIGHT -> "ASSERT_RIGHT"
IF_SOME parsedOps1 parsedOps2 -> "<IF_SOME: "+|parsedOps1|+", "+|parsedOps2|+">"
IF_RIGHT parsedOps1 parsedOps2 -> "<IF_RIGHT: "+|parsedOps1|+", "+|parsedOps2|+">"
instance NFData Macro
-- | Expand all macros in parsed contract.
expandContract :: Contract' ParsedOp -> Contract
expandContract = fmap expand
-- | Expand all macros in parsed value.
expandValue :: ParsedValue -> Value
expandValue = hoistInstr expandSeq . fmap expand
expandSeq :: ParsedSeq ExpandedOp -> [ExpandedOp]
expandSeq = \case
PSSingleMacro pos macro -> expandMacro (ErrorSrcPos pos) macro
PSSequence xs -> xs
expand :: ParsedOp -> ExpandedOp
expand = let ics pos = ErrorSrcPos pos in \case
(Mac m pos) -> WithSrcEx (ics pos) $ either PrimEx SeqEx $ expandMacro' (ics pos) m
(Prim i pos) -> WithSrcEx (ics pos) $ PrimEx $ hoistInstr expandSeq $ expand <$> i
(Seq s pos) -> WithSrcEx (ics pos) $ SeqEx $ expand <$> s
expandMacro' :: ErrorSrcPos -> Macro -> Either ExpandedInstr [ExpandedOp]
expandMacro' p@ErrorSrcPos{unErrorSrcPos=macroPos} = \case
-- special cases
-- DIIP is now always represented as a single instruction.
DIIP n ops -> Left $ DIPN n $ expandSeq $ expand <$> ops
-- Similarly to above, DUUP is now always represented as a single instruction.
DUUP n v -> Left $ DUPN v n
-- regular cases
CMP i -> Right $ [PrimEx (COMPARE def), xo i]
IFX i bt bf -> Right $ [xo i, PrimEx $ IF (xp bt) (xp bf)]
IFCMP i bt bf -> Right $ PrimEx <$> [COMPARE def, ex i, IF (xp bt) (xp bf)]
IF_SOME bt bf -> Right $ [PrimEx (IF_NONE (xp bf) (xp bt))]
IF_RIGHT bt bf -> Right $ [PrimEx (IF_LEFT (xp bf) (xp bt))]
FAIL -> Right $ PrimEx <$> [UNIT noAnn noAnn, FAILWITH]
ASSERT -> Right $ oprimEx $ IF [] fail'
ASSERTX i -> Right $ [expand $ mac $ IFX i (PSSequence []) (PSSequence [mac FAIL])]
ASSERT_CMP i -> Right $
-- reference has weirdly inconsistent nesting between this and IFCMP
[expand (Mac (CMP i) macroPos), PrimEx $ IF [] fail']
ASSERT_NONE -> Right $ oprimEx $ IF_NONE [] fail'
ASSERT_SOME -> Right $ oprimEx $ IF_NONE fail' []
ASSERT_LEFT -> Right $ oprimEx $ IF_LEFT [] fail'
ASSERT_RIGHT -> Right $ oprimEx $ IF_LEFT fail' []
PAPAIR ps t v -> expandPapair p ps t v
UNPAPAIR ps -> Right $ expandUnpapair p ps
CADR c v f -> Right $ expandCadr p c v f
CARN v idx -> Right $ [PrimEx (GETN v (2 * idx + 1))]
CDRN v idx -> Right $ [PrimEx (GETN v (2 * idx))]
SET_CADR c v f -> Right $ expandSetCadr p c v f
MAP_CADR c v f ops -> Right $ expandMapCadr p c v f ops
where
fail' = one . expand $ mac FAIL
mac = flip Mac macroPos
oprimEx = one . PrimEx
xo = PrimEx . ex
ex = hoistInstr expandSeq . fmap expand
xp = expandSeq . fmap expand
expandMacro :: ErrorSrcPos -> Macro -> [ExpandedOp]
expandMacro = either (pure . PrimEx) id ... expandMacro'
-- | The macro expansion rules below were taken from: https://tezos.gitlab.io/active/michelson.html#syntactic-conveniences
--
-- The correctness of type-annotation expansion is currently untested, as these
-- expansions are not explicitly documented in the Michelson Specification.
expandPapair
:: ErrorSrcPos
-> PairStruct
-> TypeAnn
-> VarAnn
-> Either ExpandedInstr [ExpandedOp]
expandPapair ics@(ErrorSrcPos pos) ps t v = case ps of
-- We handle this case specially, because it's essentially just PAIR.
-- It's needed because we have a hack in parser: we parse PAIR as PAPAIR.
-- We need to do something better eventually.
P (F a) (F b) -> Left $ PAIR t v a b
-- > PA(\right)R / S => DIP ((\right)R) ; PAIR / S
-- docs lie a bit, reference actually does something weird, which we try to match
-- to the best of our ability
P (F a) r -> Right $
concatMap papairDips (expandMacro ics (PAPAIR r noAnn noAnn))
<> [PrimEx $ PAIR t v a noAnn]
-- > P(\left)IR / S => (\left)R ; PAIR / S
P l (F b) -> Right [ expand $ Mac (PAPAIR l noAnn noAnn) pos, PrimEx $ PAIR t v noAnn b ]
-- > P(\left)(\right)R => (\left)R ; DIP ((\right)R) ; PAIR / S
-- docs lie a bit, reference actually does something weird, which we try to match
-- to the best of our ability
P l r -> Right $
(expand $ Mac (PAPAIR l noAnn noAnn) pos)
: concatMap papairDips (expandMacro ics (PAPAIR r noAnn noAnn))
<> [PrimEx $ PAIR t v noAnn noAnn]
-- It's impossible from the structure of PairStruct and considered cases above,
-- but if it accidentally happened let's just do nothing.
F _ -> Right []
-- | Mimic the weirdness that is the reference implementation
papairDips :: ExpandedOp -> [ExpandedOp]
papairDips = \case
PrimEx (DIP xs) -> [PrimEx $ DIPN 2 xs]
PrimEx (DIPN n xs) -> [PrimEx $ DIPN (succ n) xs]
SeqEx xs -> concatMap papairDips xs
PrimEx x -> [PrimEx $ DIP [PrimEx x]]
WithSrcEx s x -> WithSrcEx s <$> papairDips x
-- | The macro expansion rules below were taken from: https://tezos.gitlab.io/active/michelson.html#syntactic-conveniences
expandUnpapair :: ErrorSrcPos -> UnpairStruct -> [ExpandedOp]
expandUnpapair ics = \case
UP UF UF ->
[ PrimEx (UNPAIR noAnn noAnn noAnn noAnn) ]
-- > UNPA(\right)R / S => UNPAIR ; DIP (UN(\right)R) / S
-- docs lie a bit, reference actually does something weird, which we try to match
-- to the best of our ability
UP UF r ->
PrimEx (UNPAIR noAnn noAnn noAnn noAnn)
: concatMap papairDips (expandMacro ics (UNPAPAIR r))
-- > UNP(\left)IR / S => UNPAIR ; UN(\left)R / S
UP l UF ->
PrimEx (UNPAIR noAnn noAnn noAnn noAnn)
: expandMacro ics (UNPAPAIR l)
-- > UNP(\left)(\right)R => UNPAIR ; DIP (UN(\right)R) ; UN(\left)R / S
-- docs lie a bit, reference actually does something weird, which we try to match
-- to the best of our ability
UP l r ->
PrimEx (UNPAIR noAnn noAnn noAnn noAnn)
: concatMap papairDips (expandMacro ics $ UNPAPAIR r)
<> expandMacro ics (UNPAPAIR l)
-- It's impossible from the structure of UnpairStruct and considered cases above,
-- but if it accidentally happened let's just do nothing.
UF -> []
expandCadr :: ErrorSrcPos -> [CadrStruct] -> VarAnn -> FieldAnn -> [ExpandedOp]
expandCadr ics cs v f = case cs of
[] -> []
[A] -> [PrimEx $ CAR v f]
[D] -> [PrimEx $ CDR v f]
A:css -> PrimEx (CAR noAnn noAnn) : expandMacro ics (CADR css v f)
D:css -> PrimEx (CDR noAnn noAnn) : expandMacro ics (CADR css v f)
carNoAnn :: InstrAbstract f op
carNoAnn = CAR [annQ|%%|] noAnn
cdrNoAnn :: InstrAbstract f op
cdrNoAnn = CDR [annQ|%%|] noAnn
pairNoAnn :: VarAnn -> InstrAbstract f op
pairNoAnn v = PAIR noAnn v [annQ|@|] [annQ|@|]
expandSetCadr :: ErrorSrcPos -> [CadrStruct] -> VarAnn -> FieldAnn -> [ExpandedOp]
expandSetCadr (ErrorSrcPos ps) cs v f = PrimEx <$> case cs of
[] -> []
[A] -> [cdrNoAnn, SWAP, PAIR noAnn v f [annQ|@|]]
[D] -> [carNoAnn, PAIR noAnn v [annQ|@|] f]
A:css -> [DUP noAnn, DIP [PrimEx carNoAnn, expand $ Mac (SET_CADR css noAnn f) ps], cdrNoAnn, SWAP, pairNoAnn v]
D:css -> [DUP noAnn, DIP [PrimEx cdrNoAnn, expand $ Mac (SET_CADR css noAnn f) ps], carNoAnn, pairNoAnn v]
expandMapCadr :: ErrorSrcPos -> [CadrStruct] -> VarAnn -> FieldAnn -> ParsedSeq ParsedOp -> [ExpandedOp]
expandMapCadr (ErrorSrcPos pos) cs v f ops = case cs of
[] -> []
-- > MAP_CAR code => DUP ; CDR ; DIP { CAR ; code } ; SWAP ; PAIR
[A] -> PrimEx <$>
[ DUP noAnn
, cdrNoAnn
, DIP [PrimEx (CAR noAnn f), SeqEx $ expandSeq $ expand <$> ops]
, SWAP
, PAIR noAnn v f [annQ|@|]]
-- > MAP_CDR code => DUP ; CDR ; code ; SWAP ; CAR ; PAIR
[D] -> (PrimEx <$> [DUP noAnn, CDR noAnn f]) <>
(SeqEx (expandSeq $ expand <$> ops) : (PrimEx <$> [SWAP, carNoAnn, PAIR noAnn v [annQ|@|] f]))
-- > MAP_CA(\rest=[AD]+)R code / S =>
-- { DUP ; DIP { CAR ; MAP_C(\rest)R code } ; CDR ; SWAP ; PAIR } / S
A:css -> PrimEx <$> [DUP noAnn, DIP [PrimEx carNoAnn, expand $ Mac (MAP_CADR css noAnn f ops) pos], cdrNoAnn, SWAP, pairNoAnn v]
D:css -> PrimEx <$> [DUP noAnn, DIP [PrimEx cdrNoAnn, expand $ Mac (MAP_CADR css noAnn f ops) pos], carNoAnn, pairNoAnn v]
mapPairLeaves :: [FieldAnn] -> PairStruct -> PairStruct
mapPairLeaves fs p = evalState (pairLeavesST p) fs
pairLeavesST :: PairStruct -> State [FieldAnn] PairStruct
pairLeavesST = \case
(P l r) -> do
l' <- pairLeavesST l
r' <- pairLeavesST r
return $ P l' r'
(F _) -> do
f <- state getLeaf
return $ F f
where
getLeaf (a:as) = (a, as)
getLeaf _ = (noAnn, [])
deriveJSON morleyAesonOptions ''PairStruct
deriveJSON morleyAesonOptions ''UnpairStruct
deriveJSON morleyAesonOptions ''CadrStruct
$(mconcat
[ deriveJSON morleyAesonOptions ''Macro
, deriveJSON morleyAesonOptions ''ParsedOp
, deriveJSON morleyAesonOptions ''ParsedSeq
])