{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} -- | -- Module : Network.Ethereum.Web3.Encoding.TupleTH -- Copyright : Alexander Krupenkin 2016 -- License : BSD3 -- -- Maintainer : mail@akru.me -- Stability : experimental -- Portability : portable -- -- Tuple ABI encoding instance TH generator. -- module Network.Ethereum.Web3.Encoding.TupleTH ( mkTupleInst , ABIData(..) , sParser , dParser ) where import Control.Monad (replicateM) import Data.Attoparsec.Text (Parser) import qualified Data.Attoparsec.Text as P import qualified Data.Text.Lazy as LT import Data.Text.Lazy.Builder (Builder, toLazyText) import Language.Haskell.TH import Network.Ethereum.Web3.Encoding import Network.Ethereum.Web3.Encoding.Internal -- | Argument offset calculator offset :: Int -- ^ Count of arguments -> [Builder] -- ^ Previous dynamic arguments -> Int -- ^ Offset offset totalArgs args = headerOffset + dataOffset where headerOffset = totalArgs * 32 dataOffset = builderLen (mconcat args) builderLen = fromIntegral . (`div` 2) . LT.length . toLazyText -- | ABI data multiparam internal serializer class ABIData a where _serialize :: (Int, [(Builder, Builder)]) -> a -- ^ Serialize with accumulator: -- pair of argument count and list of pair header and -- data part (for dynamic arguments) instance (EncodingType b, ABIEncoding b, ABIData a) => ABIData (b -> a) where _serialize (n, l) x | isDynamic x = _serialize (n, (toDataBuilder dynOffset, toDataBuilder x) : l) | otherwise = _serialize (n, (toDataBuilder x , mempty) : l) where dynOffset = offset n (fmap snd l) instance ABIData Builder where _serialize = uncurry mappend . mconcat . reverse . snd -- | Static argument parser sParser :: (EncodingType a, ABIEncoding a) => a -> Parser a sParser x | isDynamic x = P.take 64 >> return undefined | otherwise = fromDataParser -- | Dynamic argument parser dParser :: (EncodingType a, ABIEncoding a) => a -> Parser a dParser x | isDynamic x = fromDataParser | otherwise = return x -- | Generator for tupleP{N} function signature mkTuplePType :: Int -> DecQ mkTuplePType n = do varsT <- fmap varT <$> replicateM n (newName "t") let contextT = concat [[[t|ABIEncoding $x|], [t|EncodingType $x|]] | x <- varsT] varsTupleT = foldl appT (tupleT n) varsT sigD (mkName $ "tupleP" ++ show n) (forallT [] (cxt contextT) [t|Parser $(varsTupleT)|]) -- | Generator for tupleP{N} function mkTupleP :: Int -> DecQ mkTupleP n = do vars <- replicateM n (newName "t") funD (mkName $ "tupleP" ++ show n) $ pure $ clause [] (normalB [|$(varE withPN) $(varE staticPN) >>= $(varE dynamicPN)|]) (decs vars) where withPN = mkName "withParser" staticPN = mkName "staticParser" dynamicPN = mkName "dynamicParser" fun = mkName "f" decs vars = [ withPFun, staticPFun vars, dynamicPFun vars ] withPFun = funD withPN $ pure $ clause [varP fun] (normalB [|$(varE fun) $(tupE (replicate n [|undefined|]))|]) [] staticPFun vars = funD staticPN $ pure $ clause [tupP $ fmap varP vars] (normalB (mkAppSeq (eTupleE n : fmap (\x -> [|sParser $(varE x)|]) vars))) [] dynamicPFun vars = funD dynamicPN $ pure $ clause [tupP $ fmap varP vars] (normalB (mkAppSeq (eTupleE n : fmap (\x -> [|dParser $(varE x)|]) vars))) [] mkAppSeq :: [ExpQ] -> ExpQ mkAppSeq = infixApps . dollarFirst . sparse where sparse [x] = [x] sparse (x : xs) = x : [|(<*>)|] : sparse xs dollarFirst (x : _ : xs) = x : [|(<$>)|] : xs infixApps (x : xs) = go x xs go acc [] = acc go acc (f : x : xs) = go (infixApp acc f x) xs eTupleE :: Int -> ExpQ eTupleE 2 = [|(,)|] eTupleE 3 = [|(,,)|] eTupleE 4 = [|(,,,)|] eTupleE 5 = [|(,,,,)|] eTupleE 6 = [|(,,,,,)|] eTupleE 7 = [|(,,,,,,)|] eTupleE 8 = [|(,,,,,,,)|] eTupleE 9 = [|(,,,,,,,,)|] eTupleE 10 = [|(,,,,,,,,,)|] eTupleE 11 = [|(,,,,,,,,,,)|] eTupleE 12 = [|(,,,,,,,,,,,)|] eTupleE 13 = [|(,,,,,,,,,,,,)|] eTupleE 14 = [|(,,,,,,,,,,,,,)|] eTupleE 15 = [|(,,,,,,,,,,,,,,)|] eTupleE _ = error "Unsupported tuple size" mkEncodingInst :: Int -> DecQ mkEncodingInst n = do vars <- replicateM n (newName "t") let varsT = fmap varT vars contextT = concat [[[t|ABIEncoding $x|], [t|EncodingType $x|]] | x <- varsT] varsTupleT = foldl appT (tupleT n) varsT instanceD (cxt contextT) (appT [t|ABIEncoding|] varsTupleT) [ funD (mkName "toDataBuilder") [ clause [tupP (fmap varP vars)] (normalB (appsE ([|_serialize (n, [])|] : fmap varE vars))) [] ] , funD (mkName "fromDataParser") [ clause [] (normalB $ varE $ mkName $ "tupleP" ++ show n) [] ] ] -- | Make a ABIEncoding tuple instance with given count of arguments mkTupleInst :: Int -> Q [Dec] mkTupleInst n = sequence [ mkTuplePType n , mkTupleP n , mkEncodingInst n ]