{-# LANGUAGE OverloadedStrings #-}
module Futhark.CLI.Dataset (main) where
import Control.Monad
import Control.Monad.ST
import qualified Data.Binary as Bin
import qualified Data.ByteString.Lazy.Char8 as BS
import qualified Data.Map.Strict as M
import qualified Data.Text as T
import Data.Word
import qualified Data.Vector.Unboxed.Mutable as UMVec
import qualified Data.Vector.Unboxed as UVec
import Data.Vector.Generic (freeze)
import System.Console.GetOpt
import System.Random
import Language.Futhark.Syntax hiding (Value, PrimValue(..), IntValue(..), FloatValue(..))
import Language.Futhark.Attributes (UncheckedTypeExp, namesToPrimTypes)
import Language.Futhark.Parser
import Language.Futhark.Pretty ()
import Futhark.Test.Values
import Futhark.Util.Options
main :: String -> [String] -> IO ()
main = mainWithOptions initialDataOptions commandLineOptions "options..." f
where f [] config
| null $ optOrders config = Just $ do
maybe_vs <- readValues <$> BS.getContents
case maybe_vs of
Nothing -> error "Malformed data on standard input."
Just vs ->
case format config of
Text -> mapM_ (putStrLn . pretty) vs
Binary -> mapM_ (BS.putStr . Bin.encode) vs
Type -> mapM_ (putStrLn . pretty . valueType) vs
| otherwise =
Just $ zipWithM_ ($) (optOrders config) $ map mkStdGen [optSeed config..]
f _ _ =
Nothing
data OutputFormat = Text
| Binary
| Type
deriving (Eq, Ord, Show)
data DataOptions = DataOptions
{ optSeed :: Int
, optRange :: RandomConfiguration
, optOrders :: [StdGen -> IO ()]
, format :: OutputFormat
}
initialDataOptions :: DataOptions
initialDataOptions = DataOptions 0 initialRandomConfiguration [] Text
commandLineOptions :: [FunOptDescr DataOptions]
commandLineOptions = [
Option "s" ["seed"]
(ReqArg (\n ->
case reads n of
[(n', "")] ->
Right $ \config -> config { optSeed = n' }
_ ->
Left $ error $ "'" ++ n ++ "' is not an integer.")
"SEED")
"The seed to use when initialising the RNG."
, Option "g" ["generate"]
(ReqArg (\t ->
case tryMakeGenerator t of
Right g ->
Right $ \config ->
config { optOrders =
optOrders config ++
[g (optRange config) (format config)]
}
Left err ->
Left $ error err)
"TYPE")
"Generate a random value of this type."
, Option [] ["text"]
(NoArg $ Right $ \opts -> opts { format = Text })
"Output data in text format (must precede --generate)."
, Option "b" ["binary"]
(NoArg $ Right $ \opts -> opts { format = Binary })
"Output data in binary Futhark format (must precede --generate)."
, Option "t" ["type"]
(NoArg $ Right $ \opts -> opts { format = Type })
"Output the type (textually) rather than the value (must precede --generate)."
, setRangeOption "i8" seti8Range
, setRangeOption "i16" seti16Range
, setRangeOption "i32" seti32Range
, setRangeOption "i64" seti64Range
, setRangeOption "u8" setu8Range
, setRangeOption "u16" setu16Range
, setRangeOption "u32" setu32Range
, setRangeOption "u64" setu64Range
, setRangeOption "f32" setf32Range
, setRangeOption "f64" setf64Range
]
setRangeOption :: Read a => String
-> (Range a -> RandomConfiguration -> RandomConfiguration)
-> FunOptDescr DataOptions
setRangeOption tname set =
Option "" [name]
(ReqArg (\b ->
let (lower,rest) = span (/=':') b
upper = drop 1 rest
in case (reads lower, reads upper) of
([(lower', "")], [(upper', "")]) ->
Right $ \config ->
config { optRange = set (lower', upper') $ optRange config }
_ ->
Left $ error $ "Invalid bounds: " ++ b
)
"MIN:MAX") $
"Range of " ++ tname ++ " values."
where name = tname ++ "-bounds"
tryMakeGenerator :: String -> Either String (RandomConfiguration -> OutputFormat -> StdGen -> IO ())
tryMakeGenerator t
| Just vs <- readValues $ BS.pack t =
return $ \_ fmt _ -> mapM_ (putValue fmt) vs
| otherwise = do
t' <- toValueType =<< either (Left . show) Right (parseType name (T.pack t))
return $ \conf fmt stdgen -> do
let (v, _) = randomValue conf t' stdgen
putValue fmt v
where name = "option " ++ t
putValue Text = putStrLn . pretty
putValue Binary = BS.putStr . Bin.encode
putValue Type = putStrLn . pretty . valueType
toValueType :: UncheckedTypeExp -> Either String ValueType
toValueType TETuple{} = Left "Cannot handle tuples yet."
toValueType TERecord{} = Left "Cannot handle records yet."
toValueType TEApply{} = Left "Cannot handle type applications yet."
toValueType TEArrow{} = Left "Cannot generate functions."
toValueType TEEnum{} = Left "Cannot handle enums yet."
toValueType (TEUnique t _) = toValueType t
toValueType (TEArray t d _) = do
d' <- constantDim d
ValueType ds t' <- toValueType t
return $ ValueType (d':ds) t'
where constantDim (ConstDim k) = Right k
constantDim _ = Left "Array has non-constant dimension declaration."
toValueType (TEVar (QualName [] v) _)
| Just t <- M.lookup v namesToPrimTypes = Right $ ValueType [] t
toValueType (TEVar v _) =
Left $ "Unknown type " ++ pretty v
type Range a = (a, a)
data RandomConfiguration = RandomConfiguration
{ i8Range :: Range Int8
, i16Range :: Range Int16
, i32Range :: Range Int32
, i64Range :: Range Int64
, u8Range :: Range Word8
, u16Range :: Range Word16
, u32Range :: Range Word32
, u64Range :: Range Word64
, f32Range :: Range Float
, f64Range :: Range Double
}
seti8Range :: Range Int8 -> RandomConfiguration -> RandomConfiguration
seti8Range bounds config = config { i8Range = bounds }
seti16Range :: Range Int16 -> RandomConfiguration -> RandomConfiguration
seti16Range bounds config = config { i16Range = bounds }
seti32Range :: Range Int32 -> RandomConfiguration -> RandomConfiguration
seti32Range bounds config = config { i32Range = bounds }
seti64Range :: Range Int64 -> RandomConfiguration -> RandomConfiguration
seti64Range bounds config = config { i64Range = bounds }
setu8Range :: Range Word8 -> RandomConfiguration -> RandomConfiguration
setu8Range bounds config = config { u8Range = bounds }
setu16Range :: Range Word16 -> RandomConfiguration -> RandomConfiguration
setu16Range bounds config = config { u16Range = bounds }
setu32Range :: Range Word32 -> RandomConfiguration -> RandomConfiguration
setu32Range bounds config = config { u32Range = bounds }
setu64Range :: Range Word64 -> RandomConfiguration -> RandomConfiguration
setu64Range bounds config = config { u64Range = bounds }
setf32Range :: Range Float -> RandomConfiguration -> RandomConfiguration
setf32Range bounds config = config { f32Range = bounds }
setf64Range :: Range Double -> RandomConfiguration -> RandomConfiguration
setf64Range bounds config = config { f64Range = bounds }
initialRandomConfiguration :: RandomConfiguration
initialRandomConfiguration = RandomConfiguration
(minBound, maxBound) (minBound, maxBound) (minBound, maxBound) (minBound, maxBound)
(minBound, maxBound) (minBound, maxBound) (minBound, maxBound) (minBound, maxBound)
(0.0, 1.0) (0.0, 1.0)
randomValue :: RandomConfiguration -> ValueType -> StdGen -> (Value, StdGen)
randomValue conf (ValueType ds t) stdgen =
case t of
Signed Int8 -> gen i8Range Int8Value
Signed Int16 -> gen i16Range Int16Value
Signed Int32 -> gen i32Range Int32Value
Signed Int64 -> gen i64Range Int64Value
Unsigned Int8 -> gen u8Range Word8Value
Unsigned Int16 -> gen u16Range Word16Value
Unsigned Int32 -> gen u32Range Word32Value
Unsigned Int64 -> gen u64Range Word64Value
FloatType Float32 -> gen f32Range Float32Value
FloatType Float64 -> gen f64Range Float64Value
Bool -> gen (const (False,True)) BoolValue
where gen range final = randomVector (range conf) final ds stdgen
randomVector :: (UMVec.Unbox v, Random v) =>
Range v
-> (UVec.Vector Int -> UVec.Vector v -> Value)
-> [Int] -> StdGen
-> (Value, StdGen)
randomVector range final ds stdgen = runST $ do
arr <- UMVec.new n
let fill stdgen' i
| i < n = do
let (v, stdgen'') = randomR range stdgen'
UMVec.write arr i v
fill stdgen'' $! i+1
| otherwise = do
arr' <- final (UVec.fromList ds) <$> freeze arr
return (arr', stdgen')
fill stdgen 0
where n = product ds