packages feed

nix-lang-0.1.0.0: src/Nix/Lang/Edit/Internal/TH.hs

-- | Template Haskell used to generate the selector combinators exported by
-- 'Nix.Lang.Edit'.
module Nix.Lang.Edit.Internal.TH
  ( RequiredSelectorSpec (..),
    OptionalSelectorSpec (..),
    makeRequiredSelectors,
    makeOptionalSelectors,
  )
where

import Control.Applicative ((<|>))
import Language.Haskell.TH

data RequiredSelectorSpec = RequiredSelectorSpec
  { requiredSelectorName :: String,
    requiredSelectorLabel :: String,
    requiredParentType :: Q Type,
    requiredChildType :: Q Type,
    requiredConstructor :: Name,
    requiredFieldIndex :: Int
  }

data OptionalSelectorSpec = OptionalSelectorSpec
  { optionalSelectorName :: String,
    optionalSelectorLabel :: String,
    optionalParentType :: Q Type,
    optionalChildType :: Q Type,
    optionalConstructor :: Name,
    optionalFieldIndex :: Int
  }

makeRequiredSelectors :: [RequiredSelectorSpec] -> Q [Dec]
makeRequiredSelectors = fmap concat . traverse makeRequiredSelector

makeOptionalSelectors :: [OptionalSelectorSpec] -> Q [Dec]
makeOptionalSelectors = fmap concat . traverse makeOptionalSelector

makeRequiredSelector :: RequiredSelectorSpec -> Q [Dec]
makeRequiredSelector RequiredSelectorSpec {..} = do
  fieldCount <- constructorArity requiredConstructor
  parentTy <- requiredParentType
  childTy <- requiredChildType
  spanName <- newName "span'"
  nodeName <- newName "node"
  replacementName <- newName "replacement"
  fieldNames <- traverse (newName . ("field" <>) . show) [0 .. fieldCount - 1]
  let targetName = fieldNames !! requiredFieldIndex
      replacedFields = replaceAt requiredFieldIndex (AppE (VarE (mkName "editedValue")) (VarE replacementName)) (map VarE fieldNames)
      selectorBody =
        AppE
          (AppE (VarE (mkName "required")) (LitE (StringL requiredSelectorLabel)))
          ( LamE
              [ConP (mkName "L") [] [VarP spanName, VarP nodeName]]
              ( CaseE
                  (VarE nodeName)
                  [ Match
                      (ConP requiredConstructor [] (map VarP fieldNames))
                      ( NormalB
                          (AppE (ConE (mkName "Just")) (requiredSelected spanName targetName replacementName requiredConstructor replacedFields))
                      )
                      [],
                    Match WildP (NormalB (ConE (mkName "Nothing"))) []
                  ]
              )
          )
  sig <- sigD (mkName requiredSelectorName) (pure (AppT (AppT (ConT (mkName "Selector")) parentTy) childTy))
  val <- valD (varP (mkName requiredSelectorName)) (normalB (pure selectorBody)) []
  pure [sig, val]

makeOptionalSelector :: OptionalSelectorSpec -> Q [Dec]
makeOptionalSelector OptionalSelectorSpec {..} = do
  fieldCount <- constructorArity optionalConstructor
  parentTy <- optionalParentType
  childTy <- optionalChildType
  spanName <- newName "span'"
  nodeName <- newName "node"
  replacementName <- newName "replacement"
  innerName <- newName "selectedValue"
  fieldNames <- traverse (newName . ("field" <>) . show) [0 .. fieldCount - 1]
  let targetName = fieldNames !! optionalFieldIndex
      replacedFields = replaceAt optionalFieldIndex (AppE (ConE (mkName "Just")) (AppE (VarE (mkName "editedValue")) (VarE replacementName))) (map VarE fieldNames)
      mappedSelected =
        AppE
          (AppE (VarE (mkName "flip")) (VarE (mkName "fmap")))
          (VarE targetName)
          `AppE` LamE [VarP innerName] (optionalSelected spanName innerName replacementName optionalConstructor replacedFields)
      selectorBody =
        AppE
          (AppE (VarE (mkName "optional")) (LitE (StringL optionalSelectorLabel)))
          ( LamE
              [ConP (mkName "L") [] [VarP spanName, VarP nodeName]]
              ( CaseE
                  (VarE nodeName)
                  [ Match
                      (ConP optionalConstructor [] (map VarP fieldNames))
                      ( NormalB
                          (AppE (ConE (mkName "Just")) mappedSelected)
                      )
                      [],
                    Match WildP (NormalB (ConE (mkName "Nothing"))) []
                  ]
              )
          )
  sig <- sigD (mkName optionalSelectorName) (pure (AppT (AppT (ConT (mkName "Selector")) parentTy) childTy))
  val <- valD (varP (mkName optionalSelectorName)) (normalB (pure selectorBody)) []
  pure [sig, val]

requiredSelected :: Name -> Name -> Name -> Name -> [Exp] -> Exp
requiredSelected spanName targetName replacementName conName replacedFields =
  RecConE
    (mkName "Selected")
      [ (mkName "selected", VarE targetName),
        ( mkName "replaceSelected",
          LamE
            [VarP replacementName]
            ( AppE
                (ConE (mkName "Right"))
                ( AppE
                    (ConE (mkName "Ready"))
                    ( AppE
                        (AppE (ConE (mkName "L")) (VarE spanName))
                        (foldl AppE (ConE conName) replacedFields)
                    )
                )
            )
        )
      ]

optionalSelected :: Name -> Name -> Name -> Name -> [Exp] -> Exp
optionalSelected spanName innerName replacementName conName replacedFields =
  RecConE
    (mkName "Selected")
      [ (mkName "selected", VarE innerName),
        ( mkName "replaceSelected",
          LamE
            [VarP replacementName]
            ( AppE
                (ConE (mkName "Right"))
                ( AppE
                    (ConE (mkName "Ready"))
                    ( AppE
                        (AppE (ConE (mkName "L")) (VarE spanName))
                        (foldl AppE (ConE conName) replacedFields)
                    )
                )
            )
        )
      ]

constructorArity :: Name -> Q Int
constructorArity name = do
  info <- reify name
  case info of
    DataConI _ _ parentName -> do
      parentInfo <- reify parentName
      case parentInfo of
        TyConI dec ->
          case findConstructor name dec of
            Just arity -> pure arity
            Nothing -> fail $ "Constructor not found in parent type: " <> show name
        _ -> fail $ "Unsupported parent type for constructor: " <> show name
    _ -> fail $ "Unsupported constructor: " <> show name
  where
    findConstructor conName = \case
      DataD _ _ _ _ cons _ -> foldr ((<|>) . matchCon conName) Nothing cons
      NewtypeD _ _ _ _ con _ -> matchCon conName con
      _ -> Nothing

    matchCon conName = \case
      NormalC n bangs
        | n == conName -> Just (length bangs)
      RecC n vars
        | n == conName -> Just (length vars)
      InfixC _ n _
        | n == conName -> Just 2
      ForallC _ _ con -> matchCon conName con
      GadtC names bangs _
        | conName `elem` names -> Just (length bangs)
      RecGadtC names vars _
        | conName `elem` names -> Just (length vars)
      _ -> Nothing

replaceAt :: Int -> a -> [a] -> [a]
replaceAt idx replacement xs =
  [if i == idx then replacement else x | (i, x) <- zip [0 ..] xs]