packages feed

auto-extract-0.1.0.0: src/AutoExtract/Parser.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE OverloadedStrings #-}
module AutoExtract.Parser
  ( Extraction(..)
  , ExtractedDecls
  , modifyParsedDecls
  , pattern ExtractPat
  ) where

import qualified Control.Monad.Trans.Writer.CPS as W
#if !MIN_VERSION_ghc(9,10,0)
import           Data.Foldable -- foldl'
#endif
import qualified Data.ByteString as BS
import qualified Data.Generics as Syb
import qualified Data.Map.Strict as Map

import           AutoExtract.Expr
import qualified AutoExtract.GhcFacade as Ghc

type ExtractedDecls = Map.Map BS.ByteString Extraction

data Extraction = Extraction
  { argNames :: [Ghc.OccName]
  , extractedType :: Maybe (Ghc.HsType Ghc.GhcPs)
  }


modifyParsedDecls :: ExtractedDecls -> [Ghc.LHsDecl Ghc.GhcPs] -> [Ghc.LHsDecl Ghc.GhcPs]
modifyParsedDecls extrDecls = foldMap go
  where
    go decl =
      let (updDecl, newDecls) = W.runWriter $ Syb.everywhereM (Syb.mkM extract) decl
       in updDecl : newDecls
    extract :: Ghc.HsExpr Ghc.GhcPs -> W.Writer [Ghc.LHsDecl Ghc.GhcPs] (Ghc.HsExpr Ghc.GhcPs)
    extract = \case
      ExtractPat bnd body
        | Just inputs <- Map.lookup (Ghc.bytesFS bnd) extrDecls
        -> decls bnd body inputs
      x -> pure x

    decls :: Ghc.FastString -> Ghc.LHsExpr Ghc.GhcPs -> Extraction -> W.Writer [Ghc.LHsDecl Ghc.GhcPs] (Ghc.HsExpr Ghc.GhcPs)
    decls bnd body inputs =
      let rdrName = Ghc.mkRdrUnqual $ Ghc.mkVarOccFS bnd
          arNames = Ghc.L Ghc.anchorD1 . Ghc.mkRdrUnqual <$> argNames inputs
          callsite = foldl'
            (\acc arg ->
#if MIN_VERSION_ghc(9,10,0)
              Ghc.HsApp
                Ghc.noExtField
                (Ghc.noLocA acc)
                (Ghc.L Ghc.noAnn $ Ghc.HsVar Ghc.noExtField arg)
#else
              Ghc.HsApp
                Ghc.noComments
                (Ghc.noLocA acc)
                (Ghc.L Ghc.noSrcSpanA $ Ghc.HsVar Ghc.noExtField arg)
#endif
            )
            (Ghc.HsVar Ghc.noExtField $ Ghc.noLocA rdrName)
            arNames
          newDecl :: Ghc.LHsDecl Ghc.GhcPs
          newDecl = mkExtractionDecl rdrName body arNames
          mSig :: Maybe (Ghc.LHsDecl Ghc.GhcPs)
          mSig = do
            hsType <- extractedType inputs
            Just $ mkExtractionSig rdrName hsType
       in W.writer (callsite, maybe id (:) mSig [newDecl])

pattern ExtractPat :: Ghc.FastString -> Ghc.LHsExpr Ghc.GhcPs -> Ghc.HsExpr Ghc.GhcPs
pattern ExtractPat bnd body
  <- Ghc.HsApp _
       (Ghc.L _
         (Ghc.HsApp _
           (Ghc.L _ (Ghc.HsVar _ (Ghc.L _ (nameToBS -> "EXTRACT"))))
           (Ghc.L _ (Ghc.HsLit _ (Ghc.HsString _ bnd)))
         )
       )
       (removeParens -> body)

removeParens :: Ghc.LHsExpr Ghc.GhcPs -> Ghc.LHsExpr Ghc.GhcPs
#if MIN_VERSION_ghc(9,10,0)
removeParens (Ghc.L l (Ghc.HsPar _ (Ghc.L _ x))) = Ghc.L l (doIndentCorrect x)
#else
removeParens (Ghc.L l (Ghc.HsPar _ _ (Ghc.L _ x) _)) = Ghc.L l x
#endif
removeParens x = x

-- | exact-print 9.10 has indentation issues. This corrects for do blocks.
#if MIN_VERSION_ghc(9,12,0)
doIndentCorrect :: Ghc.HsExpr Ghc.GhcPs -> Ghc.HsExpr Ghc.GhcPs
doIndentCorrect x = x
#elif MIN_VERSION_ghc(9,10,0)
doIndentCorrect :: Ghc.HsExpr Ghc.GhcPs -> Ghc.HsExpr Ghc.GhcPs
doIndentCorrect (Ghc.HsDo ann t (Ghc.L l2 s)) =
  let addCol = \case
        Ghc.EpaDelta (Ghc.DifferentLine r c) a ->
          Ghc.EpaDelta (Ghc.DifferentLine r (c + 1)) a
        x -> x
   in Ghc.HsDo ann t (Ghc.L l2 {Ghc.entry = addCol (Ghc.entry l2)} s)
doIndentCorrect x = x
#else
#endif