module Data.SBV.Tools.GenTest (
genTest, TestVectors, getTestValues, renderTest, TestStyle(..)
) where
import Data.Bits (testBit)
import Data.Char (isAlpha, toUpper)
import Data.Function (on)
import Data.List (intercalate, groupBy)
import Data.Maybe (fromMaybe)
import Data.SBV.Core.AlgReals
import Data.SBV.Core.Data
import Data.SBV.Utils.PrettyNum
import qualified Data.Foldable as F (toList)
newtype TestVectors = TV [([CV], [CV])]
getTestValues :: TestVectors -> [([CV], [CV])]
getTestValues (TV vs) = vs
genTest :: Outputtable a => Int -> Symbolic a -> IO TestVectors
genTest n m = gen 0 []
where gen i sofar
| i == n = return $ TV $ reverse sofar
| True = do t <- tc
gen (i+1) (t:sofar)
tc = do (_, Result {resTraces=tvals, resConsts=cs, resConstraints=cstrs, resOutputs=os}) <- runSymbolic (Concrete Nothing) (m >>= output)
let cval = fromMaybe (error "Cannot generate tests in the presence of uninterpeted constants!") . (`lookup` cs)
cond = and [cvToBool (cval v) | (False, _, v) <- F.toList cstrs]
if cond
then return (map snd tvals, map cval os)
else tc
data TestStyle = Haskell String
| C String
| Forte String Bool ([Int], [Int])
renderTest :: TestStyle -> TestVectors -> String
renderTest (Haskell n) (TV vs) = haskell n vs
renderTest (C n) (TV vs) = c n vs
renderTest (Forte n b ss) (TV vs) = forte n b ss vs
haskell :: String -> [([CV], [CV])] -> String
haskell vname vs = intercalate "\n" $ [ "-- Automatically generated by SBV. Do not edit!"
, ""
, "module " ++ modName ++ "(" ++ n ++ ") where"
, ""
]
++ imports
++ [ n ++ " :: " ++ getType vs
, n ++ " = [ " ++ intercalate ("\n" ++ pad ++ ", ") (map mkLine vs), pad ++ "]"
]
where n | null vname = "testVectors"
| not (isAlpha (head vname)) = "tv" ++ vname
| True = vname
imports
| null vs = []
| needsInt && needsWord = ["import Data.Int", "import Data.Word", ""]
| needsInt = ["import Data.Int", ""]
| needsWord = ["import Data.Word", ""]
| needsRatio = ["import Data.Ratio"]
| True = []
where ((is, os):_) = vs
params = is ++ os
needsInt = any isSW params
needsWord = any isUW params
needsRatio = any isR params
isR cv = case kindOf cv of
KReal -> True
_ -> False
isSW cv = case kindOf cv of
KBounded True _ -> True
_ -> False
isUW cv = case kindOf cv of
KBounded False sz -> sz > 1
_ -> False
modName = let (f:r) = n in toUpper f : r
pad = replicate (length n + 3) ' '
getType [] = "[a]"
getType ((i, o):_) = "[(" ++ mapType typeOf i ++ ", " ++ mapType typeOf o ++ ")]"
mkLine (i, o) = "(" ++ mapType valOf i ++ ", " ++ mapType valOf o ++ ")"
mapType f cvs = mkTuple $ map f $ groupBy ((==) `on` kindOf) cvs
mkTuple [x] = x
mkTuple xs = "(" ++ intercalate ", " xs ++ ")"
typeOf [] = "()"
typeOf [x] = t x
typeOf (x:_) = "[" ++ t x ++ "]"
valOf [] = "()"
valOf [x] = s x
valOf xs = "[" ++ intercalate ", " (map s xs) ++ "]"
t cv = case kindOf cv of
KBool -> "Bool"
KBounded False 8 -> "Word8"
KBounded False 16 -> "Word16"
KBounded False 32 -> "Word32"
KBounded False 64 -> "Word64"
KBounded True 8 -> "Int8"
KBounded True 16 -> "Int16"
KBounded True 32 -> "Int32"
KBounded True 64 -> "Int64"
KUnbounded -> "Integer"
KFloat -> "Float"
KDouble -> "Double"
KChar -> error "SBV.renderTest: Unsupported char"
KString -> error "SBV.renderTest: Unsupported string"
KReal -> error $ "SBV.renderTest: Unsupported real valued test value: " ++ show cv
KList es -> error $ "SBV.renderTest: Unsupported list valued test: [" ++ show es ++ "]"
KSet es -> error $ "SBV.renderTest: Unsupported set valued test: {" ++ show es ++ "}"
KUninterpreted us _ -> error $ "SBV.renderTest: Unsupported uninterpreted sort: " ++ us
_ -> error $ "SBV.renderTest: Unexpected CV: " ++ show cv
s cv = case kindOf cv of
KBool -> take 5 (show (cvToBool cv) ++ repeat ' ')
KBounded sgn sz -> let CInteger w = cvVal cv in shex False True (sgn, sz) w
KUnbounded -> let CInteger w = cvVal cv in shexI False True w
KFloat -> let CFloat w = cvVal cv in showHFloat w
KDouble -> let CDouble w = cvVal cv in showHDouble w
KChar -> error "SBV.renderTest: Unsupported char"
KString -> error "SBV.renderTest: Unsupported string"
KReal -> let CAlgReal w = cvVal cv in algRealToHaskell w
KList es -> error $ "SBV.renderTest: Unsupported list valued sort: [" ++ show es ++ "]"
KSet es -> error $ "SBV.renderTest: Unsupported set valued sort: {" ++ show es ++ "}"
KUninterpreted us _ -> error $ "SBV.renderTest: Unsupported uninterpreted sort: " ++ us
k@KTuple{} -> error $ "SBV.renderTest: Unsupported tuple: " ++ show k
k@KMaybe{} -> error $ "SBV.renderTest: Unsupported maybe: " ++ show k
k@KEither{} -> error $ "SBV.renderTest: Unsupported sum: " ++ show k
c :: String -> [([CV], [CV])] -> String
c n vs = intercalate "\n" $
[ "/* Automatically generated by SBV. Do not edit! */"
, ""
, "#include <stdio.h>"
, "#include <inttypes.h>"
, "#include <stdint.h>"
, "#include <stdbool.h>"
, "#include <string.h>"
, "#include <math.h>"
, ""
, "/* The boolean type */"
, "typedef bool SBool;"
, ""
, "/* The float type */"
, "typedef float SFloat;"
, ""
, "/* The double type */"
, "typedef double SDouble;"
, ""
, "/* Unsigned bit-vectors */"
, "typedef uint8_t SWord8;"
, "typedef uint16_t SWord16;"
, "typedef uint32_t SWord32;"
, "typedef uint64_t SWord64;"
, ""
, "/* Signed bit-vectors */"
, "typedef int8_t SInt8;"
, "typedef int16_t SInt16;"
, "typedef int32_t SInt32;"
, "typedef int64_t SInt64;"
, ""
, "typedef struct {"
, " struct {"
]
++ (if null vs then [] else zipWith (mkField "i") (fst (head vs)) [(0::Int)..])
++ [ " } input;"
, " struct {"
]
++ (if null vs then [] else zipWith (mkField "o") (snd (head vs)) [(0::Int)..])
++ [ " } output;"
, "} " ++ n ++ "TestVector;"
, ""
, n ++ "TestVector " ++ n ++ "[] = {"
]
++ [" " ++ intercalate "\n , " (map mkLine vs)]
++ [ "};"
, ""
, "int " ++ n ++ "Length = " ++ show (length vs) ++ ";"
, ""
, "/* Stub driver showing the test values, replace with code that uses the test vectors. */"
, "int main(void)"
, "{"
, " int i;"
, " for(i = 0; i < " ++ n ++ "Length; ++i)"
, " {"
, " " ++ outLine
, " }"
, ""
, " return 0;"
, "}"
]
where mkField p cv i = " " ++ t ++ " " ++ p ++ show i ++ ";"
where t = case kindOf cv of
KBool -> "SBool"
KBounded False 8 -> "SWord8"
KBounded False 16 -> "SWord16"
KBounded False 32 -> "SWord32"
KBounded False 64 -> "SWord64"
KBounded True 8 -> "SInt8"
KBounded True 16 -> "SInt16"
KBounded True 32 -> "SInt32"
KBounded True 64 -> "SInt64"
k@KBounded{} -> error $ "SBV.renderTest: Unsupported kind: " ++ show k
KFloat -> "SFloat"
KDouble -> "SDouble"
KChar -> error "SBV.renderTest: Unsupported char"
KString -> error "SBV.renderTest: Unsupported string"
KUnbounded -> error "SBV.renderTest: Unbounded integers are not supported when generating C test-cases."
KReal -> error "SBV.renderTest: Real values are not supported when generating C test-cases."
KUninterpreted us _ -> error $ "SBV.renderTest: Unsupported uninterpreted sort: " ++ us
k@KList{} -> error $ "SBV.renderTest: Unsupported list sort: " ++ show k
k@KSet{} -> error $ "SBV.renderTest: Unsupported set sort: " ++ show k
k@KTuple{} -> error $ "SBV.renderTest: Unsupported tuple sort: " ++ show k
k@KMaybe{} -> error $ "SBV.renderTest: Unsupported maybe sort: " ++ show k
k@KEither{} -> error $ "SBV.renderTest: Unsupported either sort: " ++ show k
mkLine (is, os) = "{{" ++ intercalate ", " (map v is) ++ "}, {" ++ intercalate ", " (map v os) ++ "}}"
v cv = case kindOf cv of
KBool -> if cvToBool cv then "true " else "false"
KBounded sgn sz -> let CInteger w = cvVal cv in chex False True (sgn, sz) w
KUnbounded -> let CInteger w = cvVal cv in shexI False True w
KFloat -> let CFloat w = cvVal cv in showCFloat w
KDouble -> let CDouble w = cvVal cv in showCDouble w
KChar -> error "SBV.renderTest: Unsupported char"
KString -> error "SBV.renderTest: Unsupported string"
k@KList{} -> error $ "SBV.renderTest: Unsupported list sort!" ++ show k
k@KSet{} -> error $ "SBV.renderTest: Unsupported set sort!" ++ show k
KUninterpreted us _ -> error $ "SBV.renderTest: Unsupported uninterpreted sort: " ++ us
KReal -> error "SBV.renderTest: Real values are not supported when generating C test-cases."
k@KTuple{} -> error $ "SBV.renderTest: Unsupported tuple sort!" ++ show k
k@KMaybe{} -> error $ "SBV.renderTest: Unsupported maybe sort!" ++ show k
k@KEither{} -> error $ "SBV.renderTest: Unsupported sum sort!" ++ show k
outLine
| null vs = "printf(\"\");"
| True = "printf(\"%*d. " ++ fmtString ++ "\\n\", " ++ show (length (show (length vs - 1))) ++ ", i"
++ concatMap ("\n , " ++ ) (zipWith inp is [(0::Int)..] ++ zipWith out os [(0::Int)..])
++ ");"
where (is, os) = head vs
inp cv i = mkBool cv (n ++ "[i].input.i" ++ show i)
out cv i = mkBool cv (n ++ "[i].output.o" ++ show i)
mkBool cv s = case kindOf cv of
KBool -> "(" ++ s ++ " == true) ? \"true \" : \"false\""
_ -> s
fmtString = unwords (map fmt is) ++ " -> " ++ unwords (map fmt os)
fmt cv = case kindOf cv of
KBool -> "%s"
KBounded False 8 -> "0x%02\"PRIx8\""
KBounded False 16 -> "0x%04\"PRIx16\"U"
KBounded False 32 -> "0x%08\"PRIx32\"UL"
KBounded False 64 -> "0x%016\"PRIx64\"ULL"
KBounded True 8 -> "%\"PRId8\""
KBounded True 16 -> "%\"PRId16\""
KBounded True 32 -> "%\"PRId32\"L"
KBounded True 64 -> "%\"PRId64\"LL"
KFloat -> "%f"
KDouble -> "%f"
KChar -> error "SBV.renderTest: Unsupported char"
KString -> error "SBV.renderTest: Unsupported string"
KUnbounded -> error "SBV.renderTest: Unsupported unbounded integers for C generation."
KReal -> error "SBV.renderTest: Unsupported real valued values for C generation."
_ -> error $ "SBV.renderTest: Unexpected CV: " ++ show cv
forte :: String -> Bool -> ([Int], [Int]) -> [([CV], [CV])] -> String
forte vname bigEndian ss vs = intercalate "\n" $ [ "// Automatically generated by SBV. Do not edit!"
, "let " ++ n ++ " ="
, " let c s = val [_, r] = str_split s \"'\" in " ++ blaster
]
++ [ " in [ " ++ intercalate "\n , " (map mkLine vs)
, " ];"
]
where n | null vname = "testVectors"
| not (isAlpha (head vname)) = "tv" ++ vname
| True = vname
blaster
| bigEndian = "map (\\s. s == \"1\") (explode (string_tl r))"
| True = "rev (map (\\s. s == \"1\") (explode (string_tl r)))"
toF True = '1'
toF False = '0'
blast cv = let noForte w = error "SBV.renderTest: " ++ w ++ " values are not supported when generating Forte test-cases."
in case kindOf cv of
KBool -> [toF (cvToBool cv)]
KBounded False 8 -> xlt 8 (cvVal cv)
KBounded False 16 -> xlt 16 (cvVal cv)
KBounded False 32 -> xlt 32 (cvVal cv)
KBounded False 64 -> xlt 64 (cvVal cv)
KBounded True 8 -> xlt 8 (cvVal cv)
KBounded True 16 -> xlt 16 (cvVal cv)
KBounded True 32 -> xlt 32 (cvVal cv)
KBounded True 64 -> xlt 64 (cvVal cv)
KFloat -> noForte "Float"
KDouble -> noForte "Double"
KChar -> noForte "Char"
KString -> noForte "String"
KReal -> noForte "Real"
KList ek -> noForte $ "List of " ++ show ek
KSet ek -> noForte $ "Set of " ++ show ek
KUnbounded -> noForte "Unbounded integers"
KUninterpreted s _ -> noForte $ "Uninterpreted kind " ++ show s
_ -> error $ "SBV.renderTest: Unexpected CV: " ++ show cv
xlt s (CInteger v) = [toF (testBit v i) | i <- [s-1, s-2 .. 0]]
xlt _ (CFloat r) = error $ "SBV.renderTest.Forte: Unexpected float value: " ++ show r
xlt _ (CDouble r) = error $ "SBV.renderTest.Forte: Unexpected double value: " ++ show r
xlt _ (CChar r) = error $ "SBV.renderTest.Forte: Unexpected char value: " ++ show r
xlt _ (CString r) = error $ "SBV.renderTest.Forte: Unexpected string value: " ++ show r
xlt _ (CAlgReal r) = error $ "SBV.renderTest.Forte: Unexpected real value: " ++ show r
xlt _ CList{} = error "SBV.renderTest.Forte: Unexpected list value!"
xlt _ CSet{} = error "SBV.renderTest.Forte: Unexpected set value!"
xlt _ CTuple{} = error "SBV.renderTest.Forte: Unexpected list value!"
xlt _ CMaybe{} = error "SBV.renderTest.Forte: Unexpected maybe value!"
xlt _ CEither{} = error "SBV.renderTest.Forte: Unexpected sum value!"
xlt _ (CUserSort r) = error $ "SBV.renderTest.Forte: Unexpected uninterpreted value: " ++ show r
mkLine (i, o) = "(" ++ mkTuple (form (fst ss) (concatMap blast i)) ++ ", " ++ mkTuple (form (snd ss) (concatMap blast o)) ++ ")"
mkTuple [] = "()"
mkTuple [x] = x
mkTuple xs = "(" ++ intercalate ", " xs ++ ")"
form [] [] = []
form [] bs = error $ "SBV.renderTest: Mismatched index in stream, extra " ++ show (length bs) ++ " bit(s) remain."
form (i:is) bs
| length bs < i = error $ "SBV.renderTest: Mismatched index in stream, was looking for " ++ show i ++ " bit(s), but only " ++ show bs ++ " remains."
| i == 1 = let b:r = bs
v = if b == '1' then "T" else "F"
in v : form is r
| True = let (f, r) = splitAt i bs
v = "c \"" ++ show i ++ "'b" ++ f ++ "\""
in v : form is r