More fixes to lazy evaluation and scoping

This commit is contained in:
John Wiegley 2018-03-30 01:11:27 -07:00
parent 21b179b267
commit 4e698d76fa
7 changed files with 206 additions and 192 deletions

View file

@ -4,15 +4,18 @@
{-# OPTIONS_GHC -Wno-missing-signatures #-}
module Nix.Builtins
(baseEnv, builtins, Cyclic(..), evalTopLevelExpr, evalTopLevelExprIO)
(baseEnv, builtins, Cyclic(..), NestedMap(..),
evalTopLevelExpr, evalTopLevelExprIO,
tracingEvalTopLevelExprIO, lintExpr)
where
import Control.Monad
import Control.Monad.Fix
import Control.Monad.IO.Class
import Control.Monad.Trans.State
import Control.Monad.Trans.Reader
import Data.Fix
import Data.IORef
import qualified Data.Map as Map
import qualified Data.Map.Lazy as Map
import Data.Text (Text)
import qualified Data.Text as Text
import Data.Traversable (mapM)
@ -28,26 +31,41 @@ import System.Exit (ExitCode (ExitSuccess))
evalTopLevelExpr :: MonadNix m => NExpr -> m (NValueNF m)
evalTopLevelExpr expr = do
base <- baseEnv
normalForm =<< pushScope base (evalExpr expr)
normalForm =<< pushScopes base (evalExpr expr)
evalTopLevelExprIO :: NExpr -> IO (NValueNF (Cyclic IO))
evalTopLevelExprIO expr =
evalStateT (runCyclic (evalTopLevelExpr expr)) Map.empty
runReaderT (runCyclic (evalTopLevelExpr expr)) emptyMap
baseEnv :: MonadNix m => m (ValueSet m)
tracingEvalTopLevelExprIO :: NExpr -> IO (NValueNF (Cyclic IO))
tracingEvalTopLevelExprIO expr = do
base <- run baseEnv emptyMap
expr' <- tracingExprEval expr
thnk <- run expr' base
run (normalForm thnk) base
where
run = runReaderT . runCyclic
lintExpr :: NExpr -> IO ()
lintExpr expr = run (checkExpr expr) =<< run baseEnv emptyMap
where
run = runReaderT . runCyclic
baseEnv :: MonadNix m => m (NestedMap (NThunk m))
baseEnv = do
ref <- valueRef . NVSet =<< builtins
ref <- buildThunk . NVSet =<< builtins
lst <- (("builtins", ref) :) <$> topLevelBuiltins
return $ Map.fromList lst
return . NestedMap . (:[]) $ Map.fromList lst
where
topLevelBuiltins = map mapping . filter isTopLevel <$> builtinsList
newtype Cyclic m a = Cyclic { runCyclic :: StateT (ValueSet (Cyclic m)) m a }
newtype Cyclic m a = Cyclic
{ runCyclic :: ReaderT (NestedMap (NThunk (Cyclic m))) m a }
deriving (Functor, Applicative, Monad, MonadFix, MonadIO)
data Deferred m
= DeferredValue (m (NValue m))
| DeferredThunk (m (NThunk m))
= DeferredAction (NestedMap (NThunk m)) (m (NThunk m))
-- ^ This is closure over the environment where it was created.
| ComputedValue (NValue m)
instance MonadNix (Cyclic IO) where
@ -55,19 +73,14 @@ instance MonadNix (Cyclic IO) where
-- than constantly merging maps. The number of scope levels will usually
-- be manageable, but the number of attributes within scopes can be
-- enormous, making this one of the worst implementations.
pushScope s k = Cyclic $ do
traceM $ "pushScope: s = " ++ show (() <$ s)
res <- modify (s `Map.union`) >> runCyclic k
traceM "pushScope done"
return res
pushScopes s k = Cyclic $ local (combineMaps s) $ do
scope <- runCyclic currentScope
traceM $ "scope: " ++ show (() <$ scope)
runCyclic k
currentScope = Cyclic get
lookupVar k = Cyclic $ do
s <- get
case Map.lookup k s of
Nothing -> return Nothing
Just v -> return $ Just v
clearScopes = Cyclic . local (const (NestedMap [])) . runCyclic
currentScope = Cyclic ask
lookupVar k = Cyclic $ nestedLookup k <$> ask
-- jww (2018-03-29): Cache which files have been read in.
importFile path = forceThunk path >>= \case
@ -80,45 +93,40 @@ instance MonadNix (Cyclic IO) where
p -> error $ "Unexpected argument to import: " ++ show (() <$ p)
addPath path = liftIO $ do
(exitCode, out, _) <- readProcessWithExitCode "nix-store" ["--add", path] ""
(exitCode, out, _) <-
readProcessWithExitCode "nix-store" ["--add", path] ""
case exitCode of
ExitSuccess -> return $ StorePath out
_ -> error $ "No such file or directory: " ++ show path
data NThunk (Cyclic IO) =
NThunkIO (Either (NValue (Cyclic IO)) (IORef (Deferred (Cyclic IO))))
NThunkIO (Either (NValueNF (Cyclic IO))
(IORef (Deferred (Cyclic IO))))
valueRef = return . NThunkIO . Left
buildThunk action = do
traceM "Building a thunk"
liftIO $ NThunkIO . Right <$> newIORef (DeferredValue action)
buildThunk action =
liftIO $ NThunkIO . Right <$> newIORef (ComputedValue action)
defer action = do
traceM "Deferring an action"
liftIO $ NThunkIO . Right <$> newIORef (DeferredThunk action)
defer scope action = do
traceM $ "Deferring action in scope: " ++ show (() <$ scope)
liftIO $ NThunkIO . Right <$> newIORef (DeferredAction scope action)
forceThunk (NThunkIO (Left value)) =
return $ NThunkIO . Left <$> unFix value
forceThunk (NThunkIO (Left value)) = return value
forceThunk (NThunkIO (Right ref)) = do
traceM "Forcing a thunk"
eres <- liftIO $ readIORef ref
case eres of
ComputedValue value -> do
traceM "Already forced, returning value"
return value
DeferredValue action -> do
traceM "Executing action..."
value <- action
traceM "Executing action...done, storing..."
ComputedValue value -> return value
DeferredAction scope action -> do
traceM $ "Forcing thunk in scope: " ++ show scope
value <- Cyclic
$ local (`combineMaps` scope)
$ runCyclic
$ forceThunk =<< action
traceM $ "Forcing thunk computed: " ++ show (() <$ value)
liftIO $ writeIORef ref (ComputedValue value)
traceM "Executing action...done, storing...done"
return value
DeferredThunk action -> do
traceM "Executing thunk..."
value <- forceThunk =<< action
traceM "Executing thunk...done, storing..."
liftIO $ writeIORef ref (ComputedValue value)
traceM "Executing thunk...done, storing...done"
return value
builtins :: MonadNix m => m (ValueSet m)
@ -149,7 +157,7 @@ builtinsList = sequence [
-- Helpers
mkBool :: MonadNix m => Bool -> m (NThunk m)
mkBool = valueRef . NVConstant . NBool
mkBool = valueRef . Fix . NVConstant . NBool
extractBool :: MonadNix m => NThunk m -> m Bool
extractBool arg = forceThunk arg >>= \case
@ -165,7 +173,7 @@ evalPred f arg = forceThunk f >>= \case
-- Primops
toString :: MonadNix m => NThunk m -> m (NThunk m)
toString = valueRef . uncurry NVStr <=< valueText <=< normalForm
toString = valueRef . uncurry ((Fix .) . NVStr) <=< valueText <=< normalForm
import_ :: MonadNix m => NThunk m -> m (NThunk m)
import_ = importFile
@ -173,7 +181,7 @@ import_ = importFile
hasAttr :: MonadNix m => NThunk m -> NThunk m -> m (NThunk m)
hasAttr x y = (,) <$> forceThunk x <*> forceThunk y >>= \case
(NVStr key _, NVSet aset) ->
valueRef $ NVConstant . NBool $ Map.member key aset
valueRef $ Fix . NVConstant . NBool $ Map.member key aset
(x, y) -> error $ "Invalid types for builtin.hasAttr: "
++ show (() <$ x, () <$ y)

View file

@ -8,12 +8,12 @@
{-# LANGUAGE TypeFamilies #-}
module Nix.Eval (NValue, NValueNF, NValueF(..), ValueSet, MonadNix(..),
StorePath (..),
evalExpr, tracingExprEval, checkExpr,
exprNormalForm, normalForm,
builtin, builtin2, atomText, valueText,
buildArgument) where
StorePath (..), NestedMap(..), nestedLookup, combineMaps,
extendMap, emptyMap, evalExpr, tracingExprEval, checkExpr,
exprNormalForm, normalForm, builtin, builtin2, atomText,
valueText, buildArgument) where
import Control.Applicative
import Control.Monad hiding (mapM, sequence)
import Control.Monad.Fix
import Control.Monad.IO.Class
@ -90,7 +90,7 @@ instance Show f => Show (NValueF m f) where
type ValueSet m = Map.Map Text (NThunk m)
builtin :: MonadNix m => String -> (NThunk m -> m (NThunk m)) -> m (NThunk m)
builtin name f = valueRef $ NVBuiltin name f
builtin name f = valueRef $ Fix $ NVBuiltin name f
builtin2 :: MonadNix m
=> String -> (NThunk m -> NThunk m -> m (NThunk m)) -> m (NThunk m)
@ -125,38 +125,44 @@ atomText (NUri uri) = uri
-- | A path into the nix store
newtype StorePath = StorePath { unStorePath :: FilePath }
newtype NestedMap a = NestedMap { getNestedMap :: [Map.Map Text a] }
deriving Functor
instance Show (NestedMap a) where
show (NestedMap xs) = show $ map Map.keys xs
emptyMap :: NestedMap a
emptyMap = NestedMap []
nestedLookup :: Text -> NestedMap a -> Maybe a
nestedLookup key =
foldr (\m rest -> Map.lookup key m <|> rest) Nothing . getNestedMap
combineMaps :: NestedMap a -> NestedMap a -> NestedMap a
combineMaps (NestedMap xs) (NestedMap ys) = NestedMap (xs ++ ys)
extendMap :: Map.Map Text a -> NestedMap a -> NestedMap a
extendMap x (NestedMap xs) = NestedMap (x:xs)
class MonadFix m => MonadNix m where
currentScope :: m (NestedMap (NThunk m))
clearScopes :: m r -> m r
pushScopes :: NestedMap (NThunk m) -> m r -> m r
lookupVar :: Text -> m (Maybe (NThunk m))
pushScope :: ValueSet m -> m r -> m r
pushScope = pushScopes . NestedMap . (:[])
data NThunk m :: *
currentScope :: m (ValueSet m)
pushScope :: ValueSet m -> m r -> m r
lookupVar :: Text -> m (Maybe (NThunk m))
importFile :: NThunk m -> m (NThunk m)
valueRef :: NValueNF m -> m (NThunk m)
buildThunk :: NValue m -> m (NThunk m)
forceThunk :: NThunk m -> m (NValue m)
defer :: NestedMap (NThunk m) -> m (NThunk m) -> m (NThunk m)
-- | Import a path into the nix store, and return the resulting path
addPath :: FilePath -> m StorePath
buildThunk :: m (NValue m) -> m (NThunk m)
defer :: m (NThunk m) -> m (NThunk m)
forceThunk :: NThunk m -> m (NValue m)
valueRef :: NValue m -> m (NThunk m)
valueRef = buildThunk . return
wrap :: forall m. MonadNix m => NValueNF m -> m (NThunk m)
wrap = cata phi
where
phi :: NValueF m (m (NThunk m)) -> m (NThunk m)
phi = \case
NVConstant a -> valueRef $ NVConstant a
NVStr t s -> valueRef $ NVStr t s
NVList l -> valueRef . NVList =<< sequence l
NVSet s -> valueRef . NVSet =<< sequence s
NVFunction p f -> do
p' <- sequence p
valueRef . NVFunction p' =<< f
NVLiteralPath fp -> valueRef $ NVLiteralPath fp
NVEnvPath p -> valueRef $ NVEnvPath p
NVBuiltin name f -> valueRef $ NVBuiltin name f
importFile :: NThunk m -> m (NThunk m)
buildArgument :: forall m. MonadNix m
=> Params (m (NThunk m)) -> NThunk m -> m (ValueSet m)
@ -167,37 +173,36 @@ buildArgument params arg = case params of
go ps m = forceThunk arg >>= \case
NVSet args -> do
let (s, isVariadic) = case ps of
FixedParamSet s' -> (s', False)
FixedParamSet s' -> (s', False)
VariadicParamSet s' -> (s', True)
env <- currentScope
res <- loebM (alignWithKey (assemble env isVariadic) args s)
res <- loebM (alignWithKey (assemble isVariadic) args s)
maybe (pure res) (selfInject res) m
x -> error $ "Expected set in function call, received: "
++ show (() <$ x)
selfInject :: ValueSet m -> Text -> m (ValueSet m)
selfInject res n = do
ref <- valueRef (NVSet res)
ref <- buildThunk $ NVSet res
return $ Map.insert n ref res
assemble :: ValueSet m
-> Bool
assemble :: Bool
-> Text
-> These (NThunk m) (Maybe (m (NThunk m)))
-> Map.Map Text (NThunk m)
-> m (NThunk m)
assemble env isVariadic k = \case
assemble isVariadic k = \case
That Nothing -> error $ "Missing value for parameter: " ++ show k
That (Just f) -> \args ->
-- Make sure the "scope at definition" (currentScope) is present
-- when we evaluate the default action, plus the argument scope
-- (env).
defer $ pushScope env $ pushScope args f
This x ->
if isVariadic
then const (pure x)
else error $ "Unexpected parameter: " ++ show k
These x _ -> const (pure x)
That (Just f) -> \args -> do
scope <- currentScope
traceM $ "Deferring default argument in scope: " ++ show scope
defer scope $ do
traceM $ "Evaluating default argument with args: "
++ show (NestedMap [args])
pushScope args f
This x | isVariadic -> const (pure x)
| otherwise -> error $ "Unexpected parameter: " ++ show k
These x _ -> const (pure x)
-- | Evaluate an nix expression, with a given ValueSet as environment
evalExpr :: MonadNix m => NExpr -> m (NThunk m)
@ -209,13 +214,13 @@ eval (NSym var) = do
traceM $ "NSym..1: var = " ++ show var
fromMaybe (error $ "Undefined variable: " ++ show var) <$> lookupVar var
eval (NConstant x) = valueRef $ NVConstant x
eval (NConstant x) = valueRef $ Fix $ NVConstant x
eval (NStr str) = evalString str
eval (NLiteralPath p) = valueRef $ NVLiteralPath p
eval (NEnvPath p) = valueRef $ NVEnvPath p
eval (NLiteralPath p) = valueRef $ Fix $ NVLiteralPath p
eval (NEnvPath p) = valueRef $ Fix $ NVEnvPath p
eval (NUnary op arg) = arg >>= forceThunk >>= \case
NVConstant c -> valueRef $ NVConstant $ case (op, c) of
NVConstant c -> valueRef $ Fix $ NVConstant $ case (op, c) of
(NNeg, NInt i) -> NInt (-i)
(NNot, NBool b) -> NBool (not b)
_ -> error $ "unsupported argument type for unary operator "
@ -230,7 +235,7 @@ eval (NBinary op larg rarg) = do
++ show (() <$ lval, op, () <$ rval)
case (lval, rval) of
(NVConstant lc, NVConstant rc) ->
valueRef $ NVConstant $ case (op, lc, rc) of
valueRef $ Fix $ NVConstant $ case (op, lc, rc) of
(NEq, l, r) -> NBool $ l == r
(NNEq, l, r) -> NBool $ l /= r
(NLt, l, r) -> NBool $ l < r
@ -246,61 +251,67 @@ eval (NBinary op larg rarg) = do
(NDiv, NInt l, NInt r) -> NInt $ l `div` r
_ -> error unsupportedTypes
(NVStr ls lc, NVStr rs rc) -> case op of
NPlus -> valueRef $ NVStr (ls `mappend` rs) (lc `mappend` rc)
NPlus -> valueRef $ Fix $ NVStr (ls `mappend` rs) (lc `mappend` rc)
_ -> error unsupportedTypes
(NVSet ls, NVSet rs) -> case op of
NUpdate -> valueRef $ NVSet $ rs `Map.union` ls
NUpdate -> buildThunk $ NVSet $ rs `Map.union` ls
_ -> error unsupportedTypes
(NVList ls, NVList rs) -> case op of
NConcat -> valueRef $ NVList $ ls ++ rs
NConcat -> buildThunk $ NVList $ ls ++ rs
_ -> error unsupportedTypes
(NVLiteralPath ls, NVLiteralPath rs) -> case op of
NPlus -> valueRef $ NVLiteralPath $ ls ++ rs -- TODO: Canonicalise path
NPlus -> valueRef $ Fix $ NVLiteralPath $ ls ++ rs -- TODO: Canonicalise path
_ -> error unsupportedTypes
(NVLiteralPath ls, NVStr rs rc) -> case op of
-- TODO: Canonicalise path
NPlus -> valueRef $ NVStr (Text.pack ls `mappend` rs) rc
NPlus -> valueRef $ Fix $ NVStr (Text.pack ls `mappend` rs) rc
_ -> error unsupportedTypes
_ -> error unsupportedTypes
eval (NSelect aset attr alternative) = do
aset' <- normalForm =<< aset
ks <- evalSelector True attr
case extract aset' ks of
Just v -> do
traceM $ "Wrapping a selector: " ++ show v
wrap v
aset' <- forceThunk =<< aset
ks <- evalSelector True attr
mres <- extract aset' ks
case mres of
Just v -> do
traceM $ "Wrapping a selector: " ++ show (() <$ v)
buildThunk v
Nothing -> case alternative of
Just v -> v
Nothing -> error $ "could not look up attribute "
++ intercalate "." (map Text.unpack ks)
++ " in " ++ show aset'
++ " in " ++ show (() <$ aset')
where
extract (Fix (NVSet s)) (k:ks) = case Map.lookup k s of
Just v -> extract v ks
Nothing -> Nothing
extract _ (_:_) = Nothing
extract v [] = Just v
extract (NVSet s) (k:ks) = case Map.lookup k s of
Just v -> do
s' <- forceThunk v
extract s' ks
Nothing -> return Nothing
extract _ (_:_) = return Nothing
extract v [] = return $ Just v
eval (NHasAttr aset attr) = aset >>= forceThunk >>= \case
NVSet s -> evalSelector True attr >>= \case
[keyName] -> valueRef $ NVConstant $ NBool $ keyName `Map.member` s
[keyName] ->
valueRef $ Fix $ NVConstant $ NBool $ keyName `Map.member` s
_ -> error "attr name argument to hasAttr is not a single-part name"
_ -> error "argument to hasAttr has wrong type"
eval (NList l) = valueRef . NVList =<< sequence l
eval (NList l) = do
scope <- currentScope
buildThunk . NVList =<< traverse (defer scope) l
eval (NSet binds) = do
traceM "NSet..1"
s <- evalBinds True False binds
traceM $ "NSet..2: s = " ++ show (() <$ s)
valueRef $ NVSet s
buildThunk $ NVSet s
eval (NRecSet binds) = do
traceM "NRecSet..1"
s <- evalBinds True True binds
traceM $ "NRecSet..2: s = " ++ show (() <$ s)
valueRef $ NVSet s
buildThunk $ NVSet s
eval (NLet binds e) = do
traceM "Let..1"
@ -316,7 +327,7 @@ eval (NIf cond t f) = cond >>= forceThunk >>= \case
eval (NWith scope e) = scope >>= forceThunk >>= \case
NVSet scope' -> do
env <- currentScope
pushScope scope' $ pushScope env e
pushScopes (combineMaps env (NestedMap [scope'])) e
_ -> error "scope must be a set in with statement"
eval (NAssert cond e) = cond >>= forceThunk >>= \case
@ -327,14 +338,23 @@ eval (NAssert cond e) = cond >>= forceThunk >>= \case
eval (NApp fun arg) = fun >>= forceThunk >>= \case
NVFunction params f -> do
args <- buildArgument params =<< arg
pushScope args f
traceM $ "Evaluating function application with args: "
++ show (NestedMap [args])
scope <- currentScope
traceM $ "Building function result thunk in scope: "
++ show scope
buildThunk =<< clearScopes (pushScope args (forceThunk =<< f))
NVBuiltin _ f -> f =<< arg
_ -> error "Attempt to call non-function"
eval (NAbs a b) =
eval (NAbs params body) = do
-- It is the environment at the definition site, not the call site, that
-- needs to be used when evaluation the body and the default arguments
valueRef $ NVFunction a b
-- 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 <- currentScope
traceM $ "Creating lambda abstraction in scope: " ++ show scope
buildThunk $ NVFunction (defer scope <$> params) (defer scope body)
tracingExprEval :: MonadNix m => NExpr -> IO (m (NThunk m))
tracingExprEval =
@ -372,10 +392,10 @@ attrSetAlter :: MonadNix m
-> m (Map.Map Text (m (NThunk m)))
attrSetAlter [] _ _ = error "invalid selector with no components"
attrSetAlter (p:ps) m val = case Map.lookup p m of
Nothing | null ps -> go
| otherwise -> recurse Map.empty
Just v | null ps -> go
| otherwise -> v >>= forceThunk >>= \case
Nothing | null ps -> trace ("alter..1") $ go
| otherwise -> trace ("alter..2") $ recurse Map.empty
Just v | null ps -> trace ("alter..3") $ go
| otherwise -> trace ("alter..4") $ v >>= forceThunk >>= \case
NVSet s -> recurse (fmap pure s)
_ -> error $ "attribute " ++ attr ++ " is not a set"
where
@ -385,8 +405,11 @@ attrSetAlter (p:ps) m val = case Map.lookup p m of
recurse s = attrSetAlter ps s val >>= \m' ->
if | Map.null m' -> return m
| otherwise ->
return $ Map.insert p (buildThunk $ NVSet <$> sequence m') m
| otherwise -> do
scope <- currentScope
return $ Map.insert p (embed scope m') m
where
embed scope m' = buildThunk . NVSet =<< traverse (defer scope) m'
evalBinds :: forall m. MonadNix m
=> Bool
@ -403,19 +426,13 @@ evalBinds allowDynamic recursive = buildResult . concat <=< mapM go
buildResult :: [([Text], m (NThunk m))] -> m (ValueSet m)
buildResult bindings = do
traceM "buildResult..1"
s <- foldM insert Map.empty bindings
traceM "buildResult..2"
scope <- currentScope
if recursive
then do
env <- currentScope
loebM (encapsulate env <$> s)
else sequence s
then loebM (encapsulate scope <$> s)
else traverse (defer scope) s
encapsulate env f attrs =
-- Make sure the "scope at definition" (env') is present when we
-- evaluate the attr value, plus the enclosing attr scope (env).
defer $ pushScope env $ pushScope attrs f
encapsulate scope f attrs = defer scope $ pushScope attrs f
insert m (path, value) = attrSetAlter path m value
@ -423,7 +440,7 @@ evalString :: MonadNix m => NString (m (NThunk m)) -> m (NThunk m)
evalString nstr = do
let fromParts parts = do
(t, c) <- mconcat <$> mapM go parts
valueRef $ NVStr t c
valueRef $ Fix $ NVStr t c
case nstr of
Indented parts -> fromParts parts
DoubleQuoted parts -> fromParts parts
@ -440,7 +457,7 @@ evalSelector dyn = mapM evalKeyName where
| otherwise = error "dynamic attribute not allowed in this context"
nullVal :: MonadNix m => m (NThunk m)
nullVal = valueRef (NVConstant NNull)
nullVal = valueRef $ Fix $ NVConstant NNull
-- | Evaluate an nix expression, with a given ValueSet as environment
checkExpr :: MonadNix m => NExpr -> m ()
@ -461,6 +478,10 @@ check (NRecSet binds) =
check (NLet binds e) =
(`pushScope` e) =<< evalBinds True True (fmap (fmap (const nullVal)) binds)
-- check (NWith _scope e) = do
-- env <- currentScope
-- pushScope env e
check (NAbs a b) = do
nv <- nullVal
case a of

View file

@ -1,4 +1,5 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Nix.Utils (module Nix.Utils, module X) where
@ -48,3 +49,12 @@ adiM :: (Monoid b, Applicative s, Traversable s, Traversable t, Monad m)
adiM f g = g ((go <=< traverse (adiM f g)) . unFix)
where
go = traverse (traverse f . sequenceA) . sequenceA
adiT :: forall s t m a. (Traversable t, Monad m, Monad s)
=> (t a -> m a)
-> ((Fix t -> s (m a)) -> Fix t -> s (m a))
-> Fix t -> s (m a)
adiT f g = g (go . fmap (adiT f g) . unFix)
where
go :: t (s (m a)) -> s (m a)
go = fmap ((f =<<) . sequenceA) . sequenceA

View file

@ -9,6 +9,7 @@ let
, parsers, regex-tdfa, regex-tdfa-text, semigroups, split, stdenv
, tasty, tasty-hunit, tasty-th, text, transformers, trifecta
, unordered-containers, these, optparse-applicative, interpolate
, process
}:
mkDerivation {
pname = "hnix";
@ -19,7 +20,7 @@ let
libraryHaskellDepends = [
ansi-wl-pprint base containers data-fix deepseq deriving-compat
parsers regex-tdfa regex-tdfa-text semigroups text transformers
trifecta unordered-containers these
trifecta unordered-containers these process
];
executableHaskellDepends = [
ansi-wl-pprint base containers data-fix deepseq optparse-applicative

View file

@ -2,16 +2,13 @@
module Main where
import Control.Monad
import Control.Monad.Trans.State
import qualified Data.Map as Map
import Nix.Builtins
import Nix.Eval
import Nix.Parser
import Nix.Pretty
import Options.Applicative hiding (ParserResult(..))
import System.IO
import Text.PrettyPrint.ANSI.Leijen hiding ((<$>))
import Control.Monad
import Nix.Builtins
import Nix.Parser
import Nix.Pretty
import Options.Applicative hiding (ParserResult(..))
import System.IO
import Text.PrettyPrint.ANSI.Leijen hiding ((<$>))
data Options = Options
{ verbose :: Bool
@ -60,14 +57,9 @@ main = do
case eres of
Failure err -> hPutStrLn stderr $ "Parse failed: " ++ show err
Success expr -> do
base <- run baseEnv Map.empty
when (check opts) $
run (checkExpr expr) base
if | evaluate opts, debug opts -> do
expr' <- tracingExprEval expr
thnk <- run expr' base
val <- run (normalForm thnk) base
print val
when (check opts) $ lintExpr expr
if | evaluate opts, debug opts ->
print =<< tracingEvalTopLevelExprIO expr
| evaluate opts ->
putStrLn . printNix =<< evalTopLevelExprIO expr
| debug opts ->
@ -75,8 +67,6 @@ main = do
| otherwise ->
displayIO stdout $ renderPretty 0.4 80 (prettyNix expr)
where
run expr = evalStateT (runCyclic expr)
optsDef :: ParserInfo Options
optsDef = info (helper <*> mainOptions)
(fullDesc <> progDesc "" <> header "hnix")

View file

@ -79,17 +79,9 @@ instance (Show r, Eq r) => Eq (NValueF m r) where
constantEqual :: NExpr -> NExpr -> Assertion
constantEqual a b = do
a' <- evaluate a
b' <- evaluate b
a' <- tracingEvalTopLevelExprIO a
b' <- tracingEvalTopLevelExprIO b
assertEqual "" a' b'
where
run expr = evalStateT (runCyclic expr)
evaluate expr = do
base <- run baseEnv Map.empty
expr' <- tracingExprEval expr
thnk <- run expr' base
run (normalForm thnk) base
constantEqualStr :: String -> String -> Assertion
constantEqualStr a b =

View file

@ -6,7 +6,6 @@ module NixLanguageTests (genTests) where
import Control.Arrow ((&&&))
import Control.Exception
import Control.Monad.Trans.State
import Data.List (delete, sort)
import Data.List.Split (splitOn)
import Data.Map (Map)
@ -68,26 +67,19 @@ genTests = do
assertParse :: FilePath -> Assertion
assertParse file = parseNixFile file >>= \case
Success expr -> do
base <- run baseEnv Map.empty
run (checkExpr expr) base
Failure err -> assertFailure $ "Failed to parse " ++ file ++ ":\n" ++ show err
where
run expr = evalStateT (runCyclic expr)
Success expr -> lintExpr expr
Failure err -> assertFailure $ "Failed to parse " ++ file ++ ":\n" ++ show err
assertParseFail :: FilePath -> Assertion
assertParseFail file = do
eres <- parseNixFile file
catch (case eres of
Success expr -> do
base <- run baseEnv Map.empty
run (checkExpr expr) base
lintExpr expr
assertFailure $ "Unexpected success parsing `"
++ file ++ ":\nParsed value: " ++ show expr
Failure _ -> return ()) $ \(_ :: SomeException) ->
return ()
where
run expr = evalStateT (runCyclic expr)
assertLangOk :: FilePath -> Assertion
assertLangOk file = do