diff --git a/hnix.cabal b/hnix.cabal index e127692..9f118fc 100644 --- a/hnix.cabal +++ b/hnix.cabal @@ -2,7 +2,7 @@ -- -- see: https://github.com/sol/hpack -- --- hash: a049f208472f58a7ad617291f7dde633bcb0e3bc6e593eac9fc8a7e69d350f01 +-- hash: 56c36b358efbc2aa305346fe938ba2b0e71f68d456134e558c23f2959af02505 name: hnix version: 0.5.0 @@ -38,7 +38,6 @@ library Nix.Cache Nix.Context Nix.Convert - Nix.Core Nix.Effects Nix.Eval Nix.Exec diff --git a/main/Main.hs b/main/Main.hs index e408662..6935d6b 100644 --- a/main/Main.hs +++ b/main/Main.hs @@ -20,7 +20,7 @@ import qualified Data.Text.Lazy.Encoding as TL import qualified Data.Text.Lazy.IO as TL import Nix import Nix.Convert -import qualified Nix.Core as Core +import qualified Nix.Eval as Eval -- import Nix.Lint import Nix.Utils import Options.Applicative hiding (ParserResult(..)) @@ -121,7 +121,7 @@ main = do reduction path mp x = do eres <- Nix.withNixContext mp $ - Nix.reducingEvalExpr (Core.eval . annotated . getCompose) mp x + Nix.reducingEvalExpr (Eval.eval . annotated . getCompose) mp x handleReduced path eres handleReduced :: (MonadThrow m, MonadIO m) diff --git a/main/Repl.hs b/main/Repl.hs index b2631dc..f5018c5 100644 --- a/main/Repl.hs +++ b/main/Repl.hs @@ -20,7 +20,6 @@ module Repl where import Nix import Nix.Eval -import Nix.Core import Nix.Scope import qualified Nix.Type.Env as Env import Nix.Type.Infer diff --git a/src/Nix.hs b/src/Nix.hs index 7f8123c..7d60843 100644 --- a/src/Nix.hs +++ b/src/Nix.hs @@ -34,8 +34,7 @@ import qualified Data.Text as Text import qualified Data.Text.Read as Text import Nix.Builtins import Nix.Cache -import qualified Nix.Core as Core -import Nix.Eval +import qualified Nix.Eval as Eval import Nix.Exec import Nix.Expr -- import Nix.Expr.Shorthands @@ -81,13 +80,13 @@ nixEval mpath xform alg = withNixContext mpath . adi alg xform -- | Evaluate a nix expression in the default context nixEvalExpr :: forall e m. MonadNix e m => Maybe FilePath -> NExpr -> m (NValue m) -nixEvalExpr mpath = nixEval mpath id Core.eval +nixEvalExpr mpath = nixEval mpath id Eval.eval -- | Evaluate a nix expression in the default context nixEvalExprLoc :: MonadNix e m => Maybe FilePath -> NExprLoc -> m (NValue m) nixEvalExprLoc mpath = - nixEval mpath addStackFrames (Core.eval . annotated . getCompose) + nixEval mpath Eval.addStackFrames (Eval.eval . annotated . getCompose) -- | Evaluate a nix expression with tracing in the default context nixTracingEvalExprLoc :: forall e m. (MonadNix e m, MonadIO m, Alternative m) @@ -95,8 +94,8 @@ nixTracingEvalExprLoc :: forall e m. (MonadNix e m, MonadIO m, Alternative m) nixTracingEvalExprLoc mpath = withNixContext mpath . join . (`runReaderT` (0 :: Int)) - . adi (addTracing (Core.eval . annotated . getCompose)) - (raise addStackFrames) + . adi (addTracing (Eval.eval . annotated . getCompose)) + (raise Eval.addStackFrames) where raise k f x = ReaderT $ \e -> k (\t -> runReaderT (f t) e) x diff --git a/src/Nix/Builtins.hs b/src/Nix/Builtins.hs index 61bd0e8..c363744 100644 --- a/src/Nix/Builtins.hs +++ b/src/Nix/Builtins.hs @@ -55,8 +55,8 @@ import Data.Traversable (mapM) import Language.Haskell.TH.Syntax (addDependentFile, runIO) import Nix.Atoms import Nix.Convert -import qualified Nix.Core as Core import Nix.Effects +import qualified Nix.Eval as Eval import Nix.Exec import Nix.Expr.Types import Nix.Expr.Types.Annotated @@ -125,7 +125,7 @@ builtinsList = sequence [ let f = "data/nix/corepkgs/derivation.nix" addDependentFile f Success expr <- runIO $ parseNixFile f - [| cata Core.eval expr |] + [| cata Eval.eval expr |] ) , add Normal "getEnv" getEnv_ diff --git a/src/Nix/Core.hs b/src/Nix/Core.hs deleted file mode 100644 index f0361e3..0000000 --- a/src/Nix/Core.hs +++ /dev/null @@ -1,412 +0,0 @@ -{-# LANGUAGE AllowAmbiguousTypes #-} -{-# LANGUAGE ApplicativeDo #-} -{-# LANGUAGE ConstraintKinds #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE FunctionalDependencies #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE MultiWayIf #-} -{-# LANGUAGE PartialTypeSignatures #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TupleSections #-} -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE TypeFamilies #-} - -{-# OPTIONS_GHC -fno-warn-name-shadowing #-} - -module Nix.Core where - -import Control.Arrow (first) -import Control.Monad -import Control.Monad.Fix -import Control.Monad.State -import Data.Align.Key -import Data.Fix -import Data.HashMap.Lazy (HashMap) -import qualified Data.HashMap.Lazy as M -import Data.List (intercalate, partition, foldl') -import Data.List.NonEmpty (NonEmpty(..)) -import qualified Data.List.NonEmpty as NE -import Data.Maybe (fromMaybe, catMaybes) -import Data.Text (Text) -import qualified Data.Text as Text -import Data.These -import Data.Traversable (for) -import Data.Void -import Nix.Atoms -import Nix.Convert -import Nix.Expr -import Nix.Pretty -import Nix.Scope -import Nix.Stack -import Nix.Strings (runAntiquoted) -import Nix.Thunk -import Nix.Utils - -class (Show v, Monad m) => MonadEval v m | v -> m where - freeVariable :: Text -> m v - - evalCurPos :: m v - evalConstant :: NAtom -> m v - evalString :: Text -> DList Text -> m v - evalLiteralPath :: FilePath -> m v - evalEnvPath :: FilePath -> m v - evalUnary :: NUnaryOp -> v -> m v - evalBinary :: NBinaryOp -> v -> m v -> m v - -- ^ The second argument is an action because operators such as boolean && - -- and || may not evaluate the second argument. - evalWith :: m v -> m v -> m v - evalIf :: v -> m v -> m v -> m v - evalAssert :: v -> m v -> m v - evalApp :: v -> m v -> m v - evalAbs :: Params Void -> (m v -> m v) -> m v - - evalError :: String -> m a - -type MonadNixEval e v t m = - (MonadEval v m, Scoped e t m, MonadThunk v t m, MonadFix m, - Framed e m, MonadFile m, MonadVar m, - ToValue Bool m v, ToValue [t] m v, - FromValue (Text, DList Text) m v, - ToValue (AttrSet t) m v, FromValue (AttrSet t) m v, - ToValue (AttrSet t, AttrSet SourcePos) m v, - FromValue (AttrSet t, AttrSet SourcePos) m v) - -wrapExpr :: NExprF (m v) -> NExpr -wrapExpr x = Fix (Fix (NSym "") <$ x) - -exprFContext :: (Framed e m) => NExprF (m v) -> m r -> m r -exprFContext e = withStringContext $ - "While forcing thunk for: " ++ show (prettyNix (wrapExpr e)) ++ "\n" - -eval :: forall e v t m. MonadNixEval e v t m => NExprF (m v) -> m v - -eval (NSym "__curPos") = evalCurPos - -eval (NSym var) = lookupVar var >>= \case - Nothing -> freeVariable var - Just v -> force v pure - -eval (NConstant x) = evalConstant x -eval (NStr str) = uncurry evalString =<< assembleString str -eval (NLiteralPath p) = evalLiteralPath p -eval (NEnvPath p) = evalEnvPath p -eval (NUnary op arg) = evalUnary op =<< arg - -eval (NBinary NApp fun arg) = do - scope <- currentScopes @_ @t - evalApp ?? withScopes scope arg =<< fun - -eval (NBinary op larg rarg) = larg >>= \lval -> evalBinary op lval rarg - -eval (NSelect aset attr alt) = do - traceM "NSelect" - mres <- evalSelect aset attr - traceM "NSelect..2" - case mres of - Right v -> pure v - Left (s, ks) -> fromMaybe err alt - where - err = evalError @v $ "Could not look up attribute " - ++ intercalate "." (map Text.unpack (NE.toList ks)) - ++ " in " ++ show @v s - -eval (NHasAttr aset attr) = - toValue . either (const False) (const True) =<< evalSelect aset attr - -eval e@(NList l) = do - scope <- currentScopes - toValue =<< for l (thunk . exprFContext e . withScopes @t scope) - -eval e@(NSet binds) = do - traceM "NSet..1" - (s, p) <- evalBinds e True False binds - traceM $ "NSet..2: s = " ++ show (void s) - traceM $ "NSet..2: p = " ++ show (void p) - toValue (s, p) - -eval e@(NRecSet binds) = do - traceM "NRecSet..1" - (s, p) <- evalBinds e True True (desugarBinds (eval . NRecSet) binds) - traceM $ "NRecSet..2: s = " ++ show (void s) - traceM $ "NRecSet..2: p = " ++ show (void p) - toValue (s, p) - -eval e@(NLet binds body) = do - traceM "Let..1" - (s, _) <- evalBinds e True True binds - traceM $ "Let..2: s = " ++ show (void s) - pushScope s body - -eval (NIf cond t f) = cond >>= \v -> evalIf v t f - -eval (NWith scope body) = evalWith scope body - -eval (NAssert cond body) = cond >>= \v -> evalAssert v body - -eval e@(NAbs params body) = do - -- It is the environment at the definition site, not the call site, that - -- needs to be used when evaluating the body and default arguments, hence - -- we defer here so the present scope is restored when the parameters and - -- body are forced during application. - scope <- currentScopes @_ @t - evalAbs (clearDefaults params) $ \arg -> - -- jww (2018-04-17): We need to use the bound library here, so that - -- the body is only evaluated once. - withScopes @t scope $ do - args <- buildArgument e params arg - pushScope args body - where - clearDefaults :: Params r -> Params Void - clearDefaults (Param name) = Param name - clearDefaults (ParamSet xs b mv) = ParamSet (map (Nothing <$) xs) b mv - --- | If you know that the 'scope' action will result in an 'AttrSet t', then --- this implementation may be used as an implementation for 'evalWith'. -evalWithAttrSet :: forall e v t m. MonadNixEval e v t m => m v -> m v -> m v -evalWithAttrSet scope body = do - -- The scope is deliberately wrapped in a thunk here, since it is - -- evaluated each time a name is looked up within the weak scope, and - -- we want to be sure the action it evaluates is to force a thunk, so - -- its value is only computed once. - cur <- currentScopes @_ @t - s <- thunk $ exprFContext (NWith scope body) - $ withScopes cur scope - pushWeakScope ?? body $ force s $ fromValue @(AttrSet t) - -attrSetAlter :: forall e v t m. MonadNixEval e v t m - => [Text] - -> AttrSet (m v) - -> m v - -> m (AttrSet (m v)) -attrSetAlter [] _ _ = evalError @v "invalid selector with no components" -attrSetAlter (p:ps) m val = case M.lookup p m of - Nothing - | null ps -> go - | otherwise -> recurse M.empty - Just x - | null ps -> go - | otherwise -> - x >>= fromValue >>= \s -> recurse (force ?? pure <$> s) - where - go = return $ M.insert p val m - - -- jww (2018-04-13): Need to record positions for attr paths as well - recurse s = attrSetAlter ps s val <&> \m' -> - M.insert p (toValue =<< fmap (value @_ @_ @m) <$> sequence m') m - -desugarBinds :: forall r. ([Binding r] -> r) -> [Binding r] -> [Binding r] -desugarBinds embed binds = evalState (mapM (go <=< collect) binds) M.empty - where - collect :: Binding r - -> State (HashMap VarName (Maybe SourcePos, [Binding r])) - (Either VarName (Binding r)) - collect (NamedVar (StaticKey x p:|y:ys) val) = do - m <- get - let v = case M.lookup x m of - Nothing -> (p, [NamedVar (y:|ys) val]) - Just (p, v) -> (p, NamedVar (y:|ys) val : v) - put $ M.insert x v m - pure $ Left x - collect x = pure $ Right x - - go :: Either VarName (Binding r) - -> State (HashMap VarName (Maybe SourcePos, [Binding r])) - (Binding r) - go (Right x) = pure x - go (Left x) = do - Just (p, v) <- gets $ M.lookup x - pure $ NamedVar (StaticKey x p :| []) (embed v) - -evalBinds :: forall e v t m. MonadNixEval e v t m - => NExprF (m v) - -> Bool - -> Bool - -> [Binding (m v)] - -> m (AttrSet t, AttrSet SourcePos) -evalBinds e allowDynamic recursive binds = do - scope <- currentScopes @_ @t - buildResult scope . concat =<< mapM (go scope) (moveOverridesLast binds) - where - moveOverridesLast = (\(x, y) -> y ++ x) . - partition (\case NamedVar (StaticKey "__overrides" _ :| []) _ -> True - _ -> False) - - go :: Scopes m t -> Binding (m v) -> m [([Text], Maybe SourcePos, m v)] - go _ (NamedVar (StaticKey "__overrides" _ :| []) finalValue) = - finalValue >>= fromValue >>= \(o', p') -> - return $ map (\(k, v) -> ([k], M.lookup k p', force v pure)) - (M.toList o') - - go _ (NamedVar pathExpr finalValue) = do - let go :: NAttrPath (m v) -> m ([Text], Maybe SourcePos, m v) - go = \case - h :| t -> evalSetterKeyName allowDynamic h >>= \case - (Nothing, _) -> - pure ([], Nothing, - toValue (mempty :: AttrSet t)) - (Just k, pos) -> case t of - [] -> pure ([k], pos, finalValue) - x:xs -> do - (restOfPath, _, v) <- go (x:|xs) - pure (k : restOfPath, pos, v) - go pathExpr <&> \case - -- When there are no path segments, e.g. `${null} = 5;`, we don't - -- bind anything - ([], _, _) -> [] - result -> [result] - - go scope (Inherit ms names) = fmap catMaybes $ forM names $ \name -> - evalSetterKeyName allowDynamic name >>= \case - (Nothing, _) -> return Nothing - (Just key, pos) -> return $ Just ([key], pos, do - mv <- case ms of - Nothing -> withScopes scope $ lookupVar key - Just s -> s >>= fromValue @(AttrSet t) >>= \s -> - clearScopes @t $ pushScope s $ lookupVar key - case mv of - Nothing -> evalError @v $ "Inheriting unknown attribute: " - ++ show (void name) - Just v -> force v pure) - - buildResult :: Scopes m t - -> [([Text], Maybe SourcePos, m v)] - -> m (AttrSet t, AttrSet SourcePos) - buildResult scope bindings = do - s <- foldM insert M.empty bindings - res <- if recursive - then loebM (encapsulate <$> s) - else traverse (thunk . exprFContext e . withScopes scope) s - return (res, foldl' go M.empty bindings) - where - -- jww (2018-04-13): Need to record positions for attr paths as well - go m ([k], Just pos, _) = M.insert k pos m - go m _ = m - - encapsulate f attrs = - thunk . exprFContext e - . withScopes scope - . pushScope attrs $ f - - insert m (path, _, value) = attrSetAlter path m value - -evalSelect :: forall e v t m. MonadNixEval e v t m - => m v - -> NAttrPath (m v) - -> m (Either (v, NonEmpty Text) v) -evalSelect aset attr = do - traceM "evalSelect" - s <- aset - traceM "evalSelect..2" - path <- evalSelector True attr - traceM $ "evalSelect..3: " ++ show path - res <- extract s path - traceM "evalSelect..4" - return res - where - extract x path@(k:|ks) = fromValueMay x >>= \case - Just (s :: AttrSet t, p :: AttrSet SourcePos) -> - case M.lookup k s of - Just v -> do - traceM $ "Forcing value at selector " ++ Text.unpack k - force v $ case ks of - [] -> pure . Right - y:ys -> extract ?? (y:|ys) - Nothing -> - Left . (, path) <$> toValue (s, p) - Nothing -> - return $ Left (x, path) - -evalSelector :: (MonadEval v m, FromValue (Text, DList Text) m v) - => Bool -> NAttrPath (m v) -> m (NonEmpty Text) -evalSelector allowDynamic binds = - NE.map fst <$> traverse (evalGetterKeyName allowDynamic) binds - --- | Evaluate a component of an attribute path in a context where we are --- *retrieving* a value -evalGetterKeyName :: (MonadEval v m, FromValue (Text, DList Text) m v) - => Bool -> NKeyName (m v) -> m (Text, Maybe SourcePos) -evalGetterKeyName canBeDynamic - | canBeDynamic = evalKeyNameDynamicNotNull - | otherwise = evalKeyNameStatic - -evalKeyNameStatic :: forall v m. MonadEval v m - => NKeyName (m v) -> m (Text, Maybe SourcePos) -evalKeyNameStatic = \case - StaticKey k p -> pure (k, p) - DynamicKey _ -> - evalError @v "dynamic attribute not allowed in this context" - -evalKeyNameDynamicNotNull - :: forall v m. (MonadEval v m, FromValue (Text, DList Text) m v) - => NKeyName (m v) -> m (Text, Maybe SourcePos) -evalKeyNameDynamicNotNull = evalKeyNameDynamicNullable >=> \case - (Nothing, _) -> - evalError @v "value is null while a string was expected" - (Just k, p) -> pure (k, p) - --- | Evaluate a component of an attribute path in a context where we are --- *binding* a value -evalSetterKeyName :: (MonadEval v m, FromValue (Text, DList Text) m v) - => Bool -> NKeyName (m v) -> m (Maybe Text, Maybe SourcePos) -evalSetterKeyName canBeDynamic - | canBeDynamic = evalKeyNameDynamicNullable - | otherwise = fmap (first Just) . evalKeyNameStatic - --- | Returns Nothing iff the key value is null -evalKeyNameDynamicNullable - :: forall v m. (MonadEval v m, FromValue (Text, DList Text) m v) - => NKeyName (m v) - -> m (Maybe Text, Maybe SourcePos) -evalKeyNameDynamicNullable = \case - StaticKey k p -> pure (Just k, p) - DynamicKey k -> - runAntiquoted "\n" (fmap Just . assembleString) (>>= fromValueMay) k - <&> \case Just (t, _) -> (Just t, Nothing) - _ -> (Nothing, Nothing) - -assembleString :: forall v m. (MonadEval v m, FromValue (Text, DList Text) m v) - => NString (m v) -> m (Text, DList Text) -assembleString = \case - Indented _ parts -> fromParts parts - DoubleQuoted parts -> fromParts parts - where - go = runAntiquoted "\n" (pure . (, mempty)) (>>= fromValue) - - fromParts parts = mconcat <$> mapM go parts - -buildArgument :: forall e v t m. MonadNixEval e v t m - => NExprF (m v) -> Params (m v) -> m v -> m (AttrSet t) -buildArgument e params arg = do - scope <- currentScopes @_ @t - case params of - Param name -> M.singleton name - <$> thunk (exprFContext e (withScopes scope arg)) - ParamSet s isVariadic m -> - arg >>= fromValue >>= \args -> do - let inject = case m of - Nothing -> id - Just n -> M.insert n $ const $ - thunk (exprFContext e (withScopes scope arg)) - loebM (inject $ alignWithKey (assemble scope isVariadic) - args (M.fromList s)) - where - assemble :: Scopes m t - -> Bool - -> Text - -> These t (Maybe (m v)) - -> AttrSet t - -> m t - assemble scope isVariadic k = \case - That Nothing -> - const $ evalError @v $ "Missing value for parameter: " ++ show k - That (Just f) -> \args -> - thunk $ exprFContext e - $ withScopes scope - $ pushScope args f - This x | isVariadic -> const (pure x) - | otherwise -> - const $ evalError @v $ "Unexpected parameter: " ++ show k - These x _ -> const (pure x) diff --git a/src/Nix/Eval.hs b/src/Nix/Eval.hs index 7f26907..13c7022 100644 --- a/src/Nix/Eval.hs +++ b/src/Nix/Eval.hs @@ -1,18 +1,419 @@ +{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE ApplicativeDo #-} +{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FunctionalDependencies #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE MultiWayIf #-} +{-# LANGUAGE PartialTypeSignatures #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeFamilies #-} + +{-# OPTIONS_GHC -fno-warn-name-shadowing #-} module Nix.Eval where +import Control.Arrow (first) +import Control.Monad +import Control.Monad.Fix +import Control.Monad.State +import Data.Align.Key +import Data.Fix import Data.Functor.Compose -import Nix.Core (MonadNixEval) -import qualified Nix.Core as Core -import Nix.Expr.Types.Annotated +import Data.HashMap.Lazy (HashMap) +import qualified Data.HashMap.Lazy as M +import Data.List (intercalate, partition, foldl') +import Data.List.NonEmpty (NonEmpty(..)) +import qualified Data.List.NonEmpty as NE +import Data.Maybe (fromMaybe, catMaybes) +import Data.Text (Text) +import qualified Data.Text as Text +import Data.These +import Data.Traversable (for) +import Data.Void +import Nix.Atoms +import Nix.Convert +import Nix.Expr +import Nix.Pretty +import Nix.Scope import Nix.Stack +import Nix.Strings (runAntiquoted) +import Nix.Thunk import Nix.Utils +class (Show v, Monad m) => MonadEval v m | v -> m where + freeVariable :: Text -> m v + + evalCurPos :: m v + evalConstant :: NAtom -> m v + evalString :: Text -> DList Text -> m v + evalLiteralPath :: FilePath -> m v + evalEnvPath :: FilePath -> m v + evalUnary :: NUnaryOp -> v -> m v + evalBinary :: NBinaryOp -> v -> m v -> m v + -- ^ The second argument is an action because operators such as boolean && + -- and || may not evaluate the second argument. + evalWith :: m v -> m v -> m v + evalIf :: v -> m v -> m v -> m v + evalAssert :: v -> m v -> m v + evalApp :: v -> m v -> m v + evalAbs :: Params Void -> (m v -> m v) -> m v + + evalError :: String -> m a + +type MonadNixEval e v t m = + (MonadEval v m, Scoped e t m, MonadThunk v t m, MonadFix m, + Framed e m, MonadFile m, MonadVar m, + ToValue Bool m v, ToValue [t] m v, + FromValue (Text, DList Text) m v, + ToValue (AttrSet t) m v, FromValue (AttrSet t) m v, + ToValue (AttrSet t, AttrSet SourcePos) m v, + FromValue (AttrSet t, AttrSet SourcePos) m v) + +wrapExpr :: NExprF (m v) -> NExpr +wrapExpr x = Fix (Fix (NSym "") <$ x) + +exprFContext :: (Framed e m) => NExprF (m v) -> m r -> m r +exprFContext e = withStringContext $ + "While forcing thunk for: " ++ show (prettyNix (wrapExpr e)) ++ "\n" + +eval :: forall e v t m. MonadNixEval e v t m => NExprF (m v) -> m v + +eval (NSym "__curPos") = evalCurPos + +eval (NSym var) = lookupVar var >>= \case + Nothing -> freeVariable var + Just v -> force v pure + +eval (NConstant x) = evalConstant x +eval (NStr str) = uncurry evalString =<< assembleString str +eval (NLiteralPath p) = evalLiteralPath p +eval (NEnvPath p) = evalEnvPath p +eval (NUnary op arg) = evalUnary op =<< arg + +eval (NBinary NApp fun arg) = do + scope <- currentScopes @_ @t + evalApp ?? withScopes scope arg =<< fun + +eval (NBinary op larg rarg) = larg >>= \lval -> evalBinary op lval rarg + +eval (NSelect aset attr alt) = do + traceM "NSelect" + mres <- evalSelect aset attr + traceM "NSelect..2" + case mres of + Right v -> pure v + Left (s, ks) -> fromMaybe err alt + where + err = evalError @v $ "Could not look up attribute " + ++ intercalate "." (map Text.unpack (NE.toList ks)) + ++ " in " ++ show @v s + +eval (NHasAttr aset attr) = + toValue . either (const False) (const True) =<< evalSelect aset attr + +eval e@(NList l) = do + scope <- currentScopes + toValue =<< for l (thunk . exprFContext e . withScopes @t scope) + +eval e@(NSet binds) = do + traceM "NSet..1" + (s, p) <- evalBinds e True False binds + traceM $ "NSet..2: s = " ++ show (void s) + traceM $ "NSet..2: p = " ++ show (void p) + toValue (s, p) + +eval e@(NRecSet binds) = do + traceM "NRecSet..1" + (s, p) <- evalBinds e True True (desugarBinds (eval . NRecSet) binds) + traceM $ "NRecSet..2: s = " ++ show (void s) + traceM $ "NRecSet..2: p = " ++ show (void p) + toValue (s, p) + +eval e@(NLet binds body) = do + traceM "Let..1" + (s, _) <- evalBinds e True True binds + traceM $ "Let..2: s = " ++ show (void s) + pushScope s body + +eval (NIf cond t f) = cond >>= \v -> evalIf v t f + +eval (NWith scope body) = evalWith scope body + +eval (NAssert cond body) = cond >>= \v -> evalAssert v body + +eval e@(NAbs params body) = do + -- It is the environment at the definition site, not the call site, that + -- needs to be used when evaluating the body and default arguments, hence + -- we defer here so the present scope is restored when the parameters and + -- body are forced during application. + scope <- currentScopes @_ @t + evalAbs (clearDefaults params) $ \arg -> + -- jww (2018-04-17): We need to use the bound library here, so that + -- the body is only evaluated once. + withScopes @t scope $ do + args <- buildArgument e params arg + pushScope args body + where + clearDefaults :: Params r -> Params Void + clearDefaults (Param name) = Param name + clearDefaults (ParamSet xs b mv) = ParamSet (map (Nothing <$) xs) b mv + +-- | If you know that the 'scope' action will result in an 'AttrSet t', then +-- this implementation may be used as an implementation for 'evalWith'. +evalWithAttrSet :: forall e v t m. MonadNixEval e v t m => m v -> m v -> m v +evalWithAttrSet scope body = do + -- The scope is deliberately wrapped in a thunk here, since it is + -- evaluated each time a name is looked up within the weak scope, and + -- we want to be sure the action it evaluates is to force a thunk, so + -- its value is only computed once. + cur <- currentScopes @_ @t + s <- thunk $ exprFContext (NWith scope body) + $ withScopes cur scope + pushWeakScope ?? body $ force s $ fromValue @(AttrSet t) + +attrSetAlter :: forall e v t m. MonadNixEval e v t m + => [Text] + -> AttrSet (m v) + -> m v + -> m (AttrSet (m v)) +attrSetAlter [] _ _ = evalError @v "invalid selector with no components" +attrSetAlter (p:ps) m val = case M.lookup p m of + Nothing + | null ps -> go + | otherwise -> recurse M.empty + Just x + | null ps -> go + | otherwise -> + x >>= fromValue >>= \s -> recurse (force ?? pure <$> s) + where + go = return $ M.insert p val m + + -- jww (2018-04-13): Need to record positions for attr paths as well + recurse s = attrSetAlter ps s val <&> \m' -> + M.insert p (toValue =<< fmap (value @_ @_ @m) <$> sequence m') m + +desugarBinds :: forall r. ([Binding r] -> r) -> [Binding r] -> [Binding r] +desugarBinds embed binds = evalState (mapM (go <=< collect) binds) M.empty + where + collect :: Binding r + -> State (HashMap VarName (Maybe SourcePos, [Binding r])) + (Either VarName (Binding r)) + collect (NamedVar (StaticKey x p:|y:ys) val) = do + m <- get + let v = case M.lookup x m of + Nothing -> (p, [NamedVar (y:|ys) val]) + Just (p, v) -> (p, NamedVar (y:|ys) val : v) + put $ M.insert x v m + pure $ Left x + collect x = pure $ Right x + + go :: Either VarName (Binding r) + -> State (HashMap VarName (Maybe SourcePos, [Binding r])) + (Binding r) + go (Right x) = pure x + go (Left x) = do + Just (p, v) <- gets $ M.lookup x + pure $ NamedVar (StaticKey x p :| []) (embed v) + +evalBinds :: forall e v t m. MonadNixEval e v t m + => NExprF (m v) + -> Bool + -> Bool + -> [Binding (m v)] + -> m (AttrSet t, AttrSet SourcePos) +evalBinds e allowDynamic recursive binds = do + scope <- currentScopes @_ @t + buildResult scope . concat =<< mapM (go scope) (moveOverridesLast binds) + where + moveOverridesLast = (\(x, y) -> y ++ x) . + partition (\case NamedVar (StaticKey "__overrides" _ :| []) _ -> True + _ -> False) + + go :: Scopes m t -> Binding (m v) -> m [([Text], Maybe SourcePos, m v)] + go _ (NamedVar (StaticKey "__overrides" _ :| []) finalValue) = + finalValue >>= fromValue >>= \(o', p') -> + return $ map (\(k, v) -> ([k], M.lookup k p', force v pure)) + (M.toList o') + + go _ (NamedVar pathExpr finalValue) = do + let go :: NAttrPath (m v) -> m ([Text], Maybe SourcePos, m v) + go = \case + h :| t -> evalSetterKeyName allowDynamic h >>= \case + (Nothing, _) -> + pure ([], Nothing, + toValue (mempty :: AttrSet t)) + (Just k, pos) -> case t of + [] -> pure ([k], pos, finalValue) + x:xs -> do + (restOfPath, _, v) <- go (x:|xs) + pure (k : restOfPath, pos, v) + go pathExpr <&> \case + -- When there are no path segments, e.g. `${null} = 5;`, we don't + -- bind anything + ([], _, _) -> [] + result -> [result] + + go scope (Inherit ms names) = fmap catMaybes $ forM names $ \name -> + evalSetterKeyName allowDynamic name >>= \case + (Nothing, _) -> return Nothing + (Just key, pos) -> return $ Just ([key], pos, do + mv <- case ms of + Nothing -> withScopes scope $ lookupVar key + Just s -> s >>= fromValue @(AttrSet t) >>= \s -> + clearScopes @t $ pushScope s $ lookupVar key + case mv of + Nothing -> evalError @v $ "Inheriting unknown attribute: " + ++ show (void name) + Just v -> force v pure) + + buildResult :: Scopes m t + -> [([Text], Maybe SourcePos, m v)] + -> m (AttrSet t, AttrSet SourcePos) + buildResult scope bindings = do + s <- foldM insert M.empty bindings + res <- if recursive + then loebM (encapsulate <$> s) + else traverse (thunk . exprFContext e . withScopes scope) s + return (res, foldl' go M.empty bindings) + where + -- jww (2018-04-13): Need to record positions for attr paths as well + go m ([k], Just pos, _) = M.insert k pos m + go m _ = m + + encapsulate f attrs = + thunk . exprFContext e + . withScopes scope + . pushScope attrs $ f + + insert m (path, _, value) = attrSetAlter path m value + +evalSelect :: forall e v t m. MonadNixEval e v t m + => m v + -> NAttrPath (m v) + -> m (Either (v, NonEmpty Text) v) +evalSelect aset attr = do + traceM "evalSelect" + s <- aset + traceM "evalSelect..2" + path <- evalSelector True attr + traceM $ "evalSelect..3: " ++ show path + res <- extract s path + traceM "evalSelect..4" + return res + where + extract x path@(k:|ks) = fromValueMay x >>= \case + Just (s :: AttrSet t, p :: AttrSet SourcePos) -> + case M.lookup k s of + Just v -> do + traceM $ "Forcing value at selector " ++ Text.unpack k + force v $ case ks of + [] -> pure . Right + y:ys -> extract ?? (y:|ys) + Nothing -> + Left . (, path) <$> toValue (s, p) + Nothing -> + return $ Left (x, path) + +evalSelector :: (MonadEval v m, FromValue (Text, DList Text) m v) + => Bool -> NAttrPath (m v) -> m (NonEmpty Text) +evalSelector allowDynamic binds = + NE.map fst <$> traverse (evalGetterKeyName allowDynamic) binds + +-- | Evaluate a component of an attribute path in a context where we are +-- *retrieving* a value +evalGetterKeyName :: (MonadEval v m, FromValue (Text, DList Text) m v) + => Bool -> NKeyName (m v) -> m (Text, Maybe SourcePos) +evalGetterKeyName canBeDynamic + | canBeDynamic = evalKeyNameDynamicNotNull + | otherwise = evalKeyNameStatic + +evalKeyNameStatic :: forall v m. MonadEval v m + => NKeyName (m v) -> m (Text, Maybe SourcePos) +evalKeyNameStatic = \case + StaticKey k p -> pure (k, p) + DynamicKey _ -> + evalError @v "dynamic attribute not allowed in this context" + +evalKeyNameDynamicNotNull + :: forall v m. (MonadEval v m, FromValue (Text, DList Text) m v) + => NKeyName (m v) -> m (Text, Maybe SourcePos) +evalKeyNameDynamicNotNull = evalKeyNameDynamicNullable >=> \case + (Nothing, _) -> + evalError @v "value is null while a string was expected" + (Just k, p) -> pure (k, p) + +-- | Evaluate a component of an attribute path in a context where we are +-- *binding* a value +evalSetterKeyName :: (MonadEval v m, FromValue (Text, DList Text) m v) + => Bool -> NKeyName (m v) -> m (Maybe Text, Maybe SourcePos) +evalSetterKeyName canBeDynamic + | canBeDynamic = evalKeyNameDynamicNullable + | otherwise = fmap (first Just) . evalKeyNameStatic + +-- | Returns Nothing iff the key value is null +evalKeyNameDynamicNullable + :: forall v m. (MonadEval v m, FromValue (Text, DList Text) m v) + => NKeyName (m v) + -> m (Maybe Text, Maybe SourcePos) +evalKeyNameDynamicNullable = \case + StaticKey k p -> pure (Just k, p) + DynamicKey k -> + runAntiquoted "\n" (fmap Just . assembleString) (>>= fromValueMay) k + <&> \case Just (t, _) -> (Just t, Nothing) + _ -> (Nothing, Nothing) + +assembleString :: forall v m. (MonadEval v m, FromValue (Text, DList Text) m v) + => NString (m v) -> m (Text, DList Text) +assembleString = \case + Indented _ parts -> fromParts parts + DoubleQuoted parts -> fromParts parts + where + go = runAntiquoted "\n" (pure . (, mempty)) (>>= fromValue) + + fromParts parts = mconcat <$> mapM go parts + +buildArgument :: forall e v t m. MonadNixEval e v t m + => NExprF (m v) -> Params (m v) -> m v -> m (AttrSet t) +buildArgument e params arg = do + scope <- currentScopes @_ @t + case params of + Param name -> M.singleton name + <$> thunk (exprFContext e (withScopes scope arg)) + ParamSet s isVariadic m -> + arg >>= fromValue >>= \args -> do + let inject = case m of + Nothing -> id + Just n -> M.insert n $ const $ + thunk (exprFContext e (withScopes scope arg)) + loebM (inject $ alignWithKey (assemble scope isVariadic) + args (M.fromList s)) + where + assemble :: Scopes m t + -> Bool + -> Text + -> These t (Maybe (m v)) + -> AttrSet t + -> m t + assemble scope isVariadic k = \case + That Nothing -> + const $ evalError @v $ "Missing value for parameter: " ++ show k + That (Just f) -> \args -> + thunk $ exprFContext e + $ withScopes scope + $ pushScope args f + This x | isVariadic -> const (pure x) + | otherwise -> + const $ evalError @v $ "Unexpected parameter: " ++ show k + These x _ -> const (pure x) + addStackFrames :: Framed e m => Transform NExprLocF (m a) addStackFrames f v = withExprContext v (f v) framedEvalExpr :: MonadNixEval e v t m => NExprLoc -> m v -framedEvalExpr = adi (Core.eval . annotated . getCompose) addStackFrames +framedEvalExpr = adi (eval . annotated . getCompose) addStackFrames diff --git a/src/Nix/Exec.hs b/src/Nix/Exec.hs index 0534798..32060bd 100644 --- a/src/Nix/Exec.hs +++ b/src/Nix/Exec.hs @@ -47,7 +47,6 @@ import qualified Data.Text as Text import Nix.Atoms import Nix.Context import Nix.Convert -import Nix.Core (MonadEval(..), evalWithAttrSet) import Nix.Effects import Nix.Eval as Eval import Nix.Expr diff --git a/src/Nix/Lint.hs b/src/Nix/Lint.hs index a0961b7..f06897c 100644 --- a/src/Nix/Lint.hs +++ b/src/Nix/Lint.hs @@ -41,7 +41,7 @@ import Data.Void import Nix.Atoms import Nix.Context import Nix.Convert -import Nix.Core (MonadEval(..)) +import Nix.Eval (MonadEval(..)) import qualified Nix.Eval as Eval import Nix.Expr import Nix.Options