hnix-0.13.0: src/Nix/TH.hs
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -Wno-missing-fields #-}
module Nix.TH where
import Data.Fix ( Fix(..) )
import Data.Generics.Aliases ( extQ )
import qualified Data.Set as Set
import Language.Haskell.TH
import qualified Language.Haskell.TH.Syntax as TH
import Language.Haskell.TH.Quote
import Nix.Atoms
import Nix.Expr
import Nix.Parser
quoteExprExp :: String -> ExpQ
quoteExprExp s = do
expr <-
either
(fail . show)
pure
(parseNixText $ toText s)
dataToExpQ
(const Nothing `extQ` metaExp (freeVars expr) `extQ` (pure . (TH.lift :: Text -> Q Exp)))
expr
quoteExprPat :: String -> PatQ
quoteExprPat s = do
expr <-
either
(fail . show)
pure
(parseNixText $ toText s)
dataToPatQ
(const Nothing `extQ` metaPat (freeVars expr))
expr
freeVars :: NExpr -> Set VarName
freeVars e = case unFix e of
(NConstant _ ) -> mempty
(NStr string ) -> mapFreeVars string
(NSym var ) -> Set.singleton var
(NList list ) -> mapFreeVars list
(NSet NNonRecursive bindings) -> bindFreeVars bindings
(NSet NRecursive bindings) -> Set.difference (bindFreeVars bindings) (bindDefs bindings)
(NLiteralPath _ ) -> mempty
(NEnvPath _ ) -> mempty
(NUnary _ expr ) -> freeVars expr
(NBinary _ left right ) -> Set.union (freeVars left) (freeVars right)
(NSelect expr path orExpr) ->
Set.unions
[ freeVars expr
, pathFree path
, maybe mempty freeVars orExpr
]
(NHasAttr expr path) -> Set.union (freeVars expr) (pathFree path)
(NAbs (Param varname) expr) -> Set.delete varname (freeVars expr)
(NAbs (ParamSet set _ varname) expr) ->
Set.union
-- Include all free variables from the expression and the default arguments
(freeVars expr)
-- But remove the argument name if existing, and all arguments in the parameter set
(Set.difference
(Set.unions $ freeVars <$> mapMaybe snd set)
(Set.difference
(maybe mempty Set.singleton varname)
(Set.fromList $ fmap fst set)
)
)
(NLet bindings expr ) ->
Set.union
(freeVars expr)
(Set.difference
(bindFreeVars bindings)
(bindDefs bindings)
)
(NIf cond th el ) -> Set.unions $ freeVars <$> [cond, th, el]
-- Evaluation is needed to find out whether x is a "real" free variable in `with y; x`, we just include it
-- This also makes sense because its value can be overridden by `x: with y; x`
(NWith set expr ) -> Set.union (freeVars set ) (freeVars expr)
(NAssert assertion expr ) -> Set.union (freeVars assertion) (freeVars expr)
(NSynHole _ ) -> mempty
where
bindDefs :: Foldable t => t (Binding NExpr) -> Set VarName
bindDefs = foldMap bind1Def
where
bind1Def :: Binding r -> Set VarName
bind1Def (Inherit Nothing _ _) = mempty
bind1Def (Inherit (Just _ ) keys _) = Set.fromList $ mapMaybe staticKey keys
bind1Def (NamedVar (StaticKey varname :| _) _ _) = Set.singleton varname
bind1Def (NamedVar (DynamicKey _ :| _) _ _) = mempty
bindFreeVars :: Foldable t => t (Binding NExpr) -> Set VarName
bindFreeVars = foldMap bind1Free
where
bind1Free :: Binding NExpr -> Set VarName
bind1Free (Inherit Nothing keys _) = Set.fromList $ mapMaybe staticKey keys
bind1Free (Inherit (Just scope) _ _) = freeVars scope
bind1Free (NamedVar path expr _) = Set.union (pathFree path) (freeVars expr)
staticKey :: NKeyName r -> Maybe VarName
staticKey (StaticKey varname) = pure varname
staticKey (DynamicKey _ ) = mempty
pathFree :: NAttrPath NExpr -> Set VarName
pathFree = foldMap mapFreeVars
mapFreeVars :: Foldable t => t NExpr -> Set VarName
mapFreeVars = foldMap freeVars
class ToExpr a where
toExpr :: a -> NExprLoc
instance ToExpr NExprLoc where
toExpr = id
instance ToExpr VarName where
toExpr = Fix . NSym_ nullSpan
instance ToExpr Int where
toExpr = Fix . NConstant_ nullSpan . NInt . fromIntegral
instance ToExpr Integer where
toExpr = Fix . NConstant_ nullSpan . NInt
instance ToExpr Float where
toExpr = Fix . NConstant_ nullSpan . NFloat
metaExp :: Set VarName -> NExprLoc -> Maybe ExpQ
metaExp fvs (Fix (NSym_ _ x)) | x `Set.member` fvs =
pure [| toExpr $(varE (mkName $ toString x)) |]
metaExp _ _ = Nothing
metaPat :: Set VarName -> NExprLoc -> Maybe PatQ
metaPat fvs (Fix (NSym_ _ x)) | x `Set.member` fvs =
pure $ varP $ mkName $ toString x
metaPat _ _ = Nothing
-- Use of @QuasiQuoter@ requires @String@.
-- After @Text -> String@ migrations done, _maybe_ think to use @QuasiText@.
nix :: QuasiQuoter
nix = QuasiQuoter { quoteExp = quoteExprExp, quotePat = quoteExprPat }