packages feed

aihc-parser-1.0.0.2: common/ShrinkUtils.hs

module ShrinkUtils
  ( candidateTransformsWith,
    removeModuleHead,
    shrinkModuleHeadNameWith,
    removeModuleHeadExports,
    shrinkModuleHeadExports,
    removeModuleImports,
    shrinkModuleImports,
    removeModuleDecls,
    shrinkModuleDecls,
    shrinkModuleNameWith,
    shrinkModuleNamePartsWith,
    dropSegmentShrinks,
    shrinkSegmentShrinksWith,
    replaceAt,
    unique,
    splitModuleName,
    joinModuleName,
    isValidModuleName,
    isValidModuleSegment,
    isSegmentRestChar,
  )
where

import Data.Char (isAlphaNum, isUpper)
import Data.List (intercalate)
import Language.Haskell.Exts qualified as HSE

candidateTransformsWith :: (String -> [String]) -> HSE.Module HSE.SrcSpanInfo -> [HSE.Module HSE.SrcSpanInfo]
candidateTransformsWith shrinkSegment modu =
  removeModuleHead modu
    <> shrinkModuleHeadNameWith shrinkSegment modu
    <> removeModuleHeadExports modu
    <> shrinkModuleHeadExports modu
    <> removeModuleImports modu
    <> shrinkModuleImports modu
    <> removeModuleDecls modu
    <> shrinkModuleDecls modu

removeModuleHead :: HSE.Module HSE.SrcSpanInfo -> [HSE.Module HSE.SrcSpanInfo]
removeModuleHead modu =
  case modu of
    HSE.Module loc (Just _) pragmas imports decls ->
      [HSE.Module loc Nothing pragmas imports decls]
    _ -> []

shrinkModuleHeadNameWith :: (String -> [String]) -> HSE.Module HSE.SrcSpanInfo -> [HSE.Module HSE.SrcSpanInfo]
shrinkModuleHeadNameWith shrinkSegment modu =
  case modu of
    HSE.Module loc (Just (HSE.ModuleHead hLoc (HSE.ModuleName nLoc name) warning exports)) pragmas imports decls ->
      [ HSE.Module
          loc
          (Just (HSE.ModuleHead hLoc (HSE.ModuleName nLoc name') warning exports))
          pragmas
          imports
          decls
      | name' <- shrinkModuleNameWith shrinkSegment name
      ]
    _ -> []

removeModuleHeadExports :: HSE.Module HSE.SrcSpanInfo -> [HSE.Module HSE.SrcSpanInfo]
removeModuleHeadExports modu =
  case modu of
    HSE.Module loc (Just (HSE.ModuleHead hLoc modName warning (Just _))) pragmas imports decls ->
      [HSE.Module loc (Just (HSE.ModuleHead hLoc modName warning Nothing)) pragmas imports decls]
    _ -> []

shrinkModuleHeadExports :: HSE.Module HSE.SrcSpanInfo -> [HSE.Module HSE.SrcSpanInfo]
shrinkModuleHeadExports modu =
  case modu of
    HSE.Module loc (Just (HSE.ModuleHead hLoc modName warning (Just (HSE.ExportSpecList eLoc specs)))) pragmas imports decls ->
      let removeAt idx = take idx specs <> drop (idx + 1) specs
          shrunkLists = [removeAt idx | idx <- [0 .. length specs - 1]] <> [[], specs]
       in [ HSE.Module
              loc
              (Just (HSE.ModuleHead hLoc modName warning (Just (HSE.ExportSpecList eLoc specs'))))
              pragmas
              imports
              decls
          | specs' <- unique shrunkLists,
            specs' /= specs
          ]
    _ -> []

removeModuleImports :: HSE.Module HSE.SrcSpanInfo -> [HSE.Module HSE.SrcSpanInfo]
removeModuleImports modu =
  case modu of
    HSE.Module loc header pragmas (_ : _) decls ->
      [HSE.Module loc header pragmas [] decls]
    _ -> []

shrinkModuleImports :: HSE.Module HSE.SrcSpanInfo -> [HSE.Module HSE.SrcSpanInfo]
shrinkModuleImports modu =
  case modu of
    HSE.Module loc header pragmas imports decls ->
      let removeAt idx = take idx imports <> drop (idx + 1) imports
          shrunkLists =
            [removeAt idx | idx <- [0 .. length imports - 1]]
              <> [replaceAt idx decl' imports | idx <- [0 .. length imports - 1], decl' <- shrinkImportDecl (imports !! idx)]
              <> [replaceAt idx (simplifyImportDecl (imports !! idx)) imports | idx <- [0 .. length imports - 1]]
              <> [[], imports]
       in [HSE.Module loc header pragmas imports' decls | imports' <- unique shrunkLists, imports' /= imports]
    _ -> []

removeModuleDecls :: HSE.Module HSE.SrcSpanInfo -> [HSE.Module HSE.SrcSpanInfo]
removeModuleDecls modu =
  case modu of
    HSE.Module loc header pragmas imports (_ : _) ->
      [HSE.Module loc header pragmas imports []]
    _ -> []

shrinkModuleDecls :: HSE.Module HSE.SrcSpanInfo -> [HSE.Module HSE.SrcSpanInfo]
shrinkModuleDecls modu =
  case modu of
    HSE.Module loc header pragmas imports decls ->
      let removeAt idx = take idx decls <> drop (idx + 1) decls
          shrunkLists =
            [removeAt idx | idx <- [0 .. length decls - 1]]
              <> [replaceAt idx decl' decls | idx <- [0 .. length decls - 1], decl' <- simplifyDecl (decls !! idx)]
              <> [[], decls]
       in [HSE.Module loc header pragmas imports decls' | decls' <- unique shrunkLists, decls' /= decls]
    _ -> []

simplifyImportDecl :: HSE.ImportDecl HSE.SrcSpanInfo -> HSE.ImportDecl HSE.SrcSpanInfo
simplifyImportDecl decl =
  decl
    { HSE.importQualified = False,
      HSE.importSrc = False,
      HSE.importSafe = False,
      HSE.importPkg = Nothing,
      HSE.importAs = Nothing,
      HSE.importSpecs = Nothing
    }

shrinkImportDecl :: HSE.ImportDecl HSE.SrcSpanInfo -> [HSE.ImportDecl HSE.SrcSpanInfo]
shrinkImportDecl decl =
  case HSE.importSpecs decl of
    Nothing -> []
    Just (HSE.ImportSpecList specLoc hidingFlag specs) ->
      let removeAt specIdx = take specIdx specs <> drop (specIdx + 1) specs
          shrinkSpecs =
            [ decl {HSE.importSpecs = Just (HSE.ImportSpecList specLoc hidingFlag specs')}
            | specs' <- unique ([removeAt idx | idx <- [0 .. length specs - 1]] <> [take 1 specs]),
              not (null specs'),
              specs' /= specs
            ]
       in shrinkSpecs <> [decl {HSE.importSpecs = Just (HSE.ImportSpecList specLoc hidingFlag [spec'])} | spec <- specs, spec' <- shrinkImportSpec spec]

shrinkImportSpec :: HSE.ImportSpec HSE.SrcSpanInfo -> [HSE.ImportSpec HSE.SrcSpanInfo]
shrinkImportSpec spec =
  case spec of
    HSE.IVar loc name -> [HSE.IVar loc name]
    HSE.IAbs loc ns name -> [HSE.IVar loc name, HSE.IAbs loc ns name]
    HSE.IThingAll loc name -> [HSE.IVar loc name, HSE.IThingAll loc name]
    HSE.IThingWith loc name cnames ->
      [HSE.IThingAll loc name, HSE.IThingWith loc name (take 1 cnames)]

simplifyDecl :: HSE.Decl HSE.SrcSpanInfo -> [HSE.Decl HSE.SrcSpanInfo]
simplifyDecl decl =
  case decl of
    HSE.TypeSig loc names _ty ->
      case names of
        [] -> []
        name : _ -> [HSE.TypeSig loc [name] (simpleType loc)]
    HSE.FunBind loc matches ->
      case matches of
        [] -> []
        match : _ ->
          case match of
            HSE.Match mLoc name _pats _rhs _binds ->
              [HSE.FunBind loc [simplifyMatch mLoc name]]
            HSE.InfixMatch mLoc lhs name _pats _rhs _binds ->
              [HSE.FunBind loc [simplifyMatch mLoc name, HSE.Match mLoc name [lhs] (simpleRhs mLoc name) Nothing]]
    HSE.PatBind loc pat _rhs _binds ->
      [HSE.PatBind loc (simplifyPat pat) (simpleRhs loc (nameFromPat pat)) Nothing]
    _ -> []

simplifyMatch :: HSE.SrcSpanInfo -> HSE.Name HSE.SrcSpanInfo -> HSE.Match HSE.SrcSpanInfo
simplifyMatch loc name =
  HSE.Match loc name [] (simpleRhs loc name) Nothing

simpleRhs :: HSE.SrcSpanInfo -> HSE.Name HSE.SrcSpanInfo -> HSE.Rhs HSE.SrcSpanInfo
simpleRhs loc name =
  HSE.UnGuardedRhs loc (HSE.Var loc (HSE.UnQual loc name))

simpleType :: HSE.SrcSpanInfo -> HSE.Type HSE.SrcSpanInfo
simpleType loc =
  HSE.TyVar loc (HSE.Ident loc "a")

simplifyPat :: HSE.Pat HSE.SrcSpanInfo -> HSE.Pat HSE.SrcSpanInfo
simplifyPat pat =
  case pat of
    HSE.PVar loc name -> HSE.PVar loc name
    HSE.PWildCard loc -> HSE.PWildCard loc
    _ -> HSE.PWildCard (HSE.ann pat)

nameFromPat :: HSE.Pat HSE.SrcSpanInfo -> HSE.Name HSE.SrcSpanInfo
nameFromPat pat =
  case pat of
    HSE.PVar _ name -> name
    _ -> HSE.Ident (HSE.ann pat) "x"

shrinkModuleNameWith :: (String -> [String]) -> String -> [String]
shrinkModuleNameWith shrinkSegment name =
  let parts = splitModuleName name
      joinedShrinks =
        [ joinModuleName parts'
        | parts' <- shrinkModuleNamePartsWith shrinkSegment parts,
          isValidModuleName parts'
        ]
   in unique joinedShrinks

shrinkModuleNamePartsWith :: (String -> [String]) -> [String] -> [[String]]
shrinkModuleNamePartsWith shrinkSegment parts =
  dropSegmentShrinks parts <> shrinkSegmentShrinksWith shrinkSegment parts

-- Remove one segment at a time (if at least one segment remains).
dropSegmentShrinks :: [String] -> [[String]]
dropSegmentShrinks parts =
  [ before <> after
  | idx <- [0 .. length parts - 1],
    let (before, rest) = splitAt idx parts,
    (_ : after) <- [rest],
    not (null (before <> after))
  ]

-- Shrink each segment independently.
shrinkSegmentShrinksWith :: (String -> [String]) -> [String] -> [[String]]
shrinkSegmentShrinksWith shrinkSegment parts =
  [ replaceAt idx segment' parts
  | (idx, segment) <- zip [0 ..] parts,
    segment' <- shrinkSegment segment,
    isValidModuleSegment segment'
  ]

replaceAt :: Int -> a -> [a] -> [a]
replaceAt idx value xs =
  let (before, rest) = splitAt idx xs
   in case rest of
        [] -> xs
        (_ : after) -> before <> (value : after)

unique :: (Eq a) => [a] -> [a]
unique = foldr keep []
  where
    keep x acc
      | x `elem` acc = acc
      | otherwise = x : acc

splitModuleName :: String -> [String]
splitModuleName raw =
  case raw of
    "" -> []
    _ -> splitOnDot raw

splitOnDot :: String -> [String]
splitOnDot s =
  case break (== '.') s of
    (prefix, []) -> [prefix]
    (prefix, _ : rest) -> prefix : splitOnDot rest

joinModuleName :: [String] -> String
joinModuleName = intercalate "."

isValidModuleName :: [String] -> Bool
isValidModuleName segments = not (null segments) && all isValidModuleSegment segments

isValidModuleSegment :: String -> Bool
isValidModuleSegment segment =
  case segment of
    first : rest -> isUpper first && all isSegmentRestChar rest
    [] -> False

isSegmentRestChar :: Char -> Bool
isSegmentRestChar ch = isAlphaNum ch || ch == '\'' || ch == '_'