cmdargs-0.3: System/Console/CmdArgs/Implicit/Capture.hs
{-# LANGUAGE PatternGuards, ScopedTypeVariables #-}
-- | This module does all the tricky/unsafe bits of CmdArgs.
-- It captures annotations on the data structure in the most direct way
-- possible.
module System.Console.CmdArgs.Implicit.Capture(
Capture(..), capture, many, (&=)
) where
{-
Notes on purity:
There is a risk that things that are unsafe will be inlined. That can generally be
removed by NOININE on everything.
There is also a risk that things get commoned up. For example:
foo = trace "1" 1
bar = trace "1" 1
main = do
evaluate foo
evaluate bar
Will print "1" only once, since foo and bar share the same pattern. However, if
anything in the value is a lambda they are not seen as equal. We exploit this by
defining const_ and id_ as per this module.
Now anything wrapped in id_ looks different from anything else.
-}
import Data.Data(Data)
import Data.IORef
import System.IO.Unsafe
import Control.Exception
import System.Console.CmdArgs.Implicit.Ann
import Data.Generics.Any
-- test = show $ capture $ many [Just ((66::Int) &= P 1 &= P 2), Nothing &= P 8] &= P 3
infixl 2 &=
data Capture
= Many [Capture]
| Ann Ann Capture
| Value Any
| Missing Any -- a RecConError
| Ctor Any [Capture]
deriving Show
{-
The idea is to keep a stack of either continuations, or values
If you encounter 'many' you become a value
If you encounter '&=' you increase the continuation
-}
{-# NOINLINE ref #-}
ref :: IORef [Either (Capture -> Capture) Capture]
ref = unsafePerformIO $ newIORef []
push = modifyIORef ref (Left id :)
pop = do x:xs <- readIORef ref; writeIORef ref xs; return x
modify f = modifyIORef ref $ \x -> case x of Left g : rest -> f g : rest ; _ -> error "Internal error in Capture"
add f = modify $ \x -> Left $ x . f
set x = modify $ \f -> Right $ f x
-- | Collapse multiple values in to one.
{-# NOINLINE many #-}
many :: Data a => [a] -> a
many xs = unsafePerformIO $ do
ys <- mapM (force . Any) xs
set $ Many ys
return $ head xs
-- | Add an annotation to a value. Note that if the value is evaluated
-- more than once the annotation will only be available the first time.
{-# NOINLINE addAnn #-}
addAnn :: Data a => a -> Ann -> a
addAnn x y = unsafePerformIO $ do
add (Ann y)
evaluate x
return x
{-# NOINLINE capture #-}
capture :: Any -> Capture
capture x = unsafePerformIO $ force x
force :: Any -> IO Capture
force x@(Any xx) = do
push
res <- try $ evaluate xx
y <- pop
case y of
_ | Left (_ :: RecConError) <- res -> return $ Missing x
Right r -> return r
Left f | not $ isAlgType x -> return $ f $ Value x
| otherwise -> do
cs <- mapM force $ children x
return $ f $ Ctor x cs
{-# INLINE (&=) #-}
(&=) :: Data a => a -> Ann -> a
(&=) x y = addAnn (id_ x) (id_ y)
{-# NOINLINE const_ #-}
const_ :: a -> b -> b
const_ f x = x
{-# INLINE id_ #-}
id_ :: a -> a
id_ x = const_ (\() -> ()) x