{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
module What4.Protocol.SMTLib2.Syntax
(
Command(..)
, setLogic
, setOption
, setProduceModels
, SMTInfoFlag(..)
, getInfo
, getVersion
, getName
, getErrorBehavior
, exit
, declareSort
, defineSort
, declareConst
, declareFun
, defineFun
, Symbol
, checkSat
, checkSatAssuming
, checkSatWithAssumptions
, getModel
, getValue
, push
, pop
, resetAssertions
, assert
, assertNamed
, getUnsatAssumptions
, getUnsatCore
, Logic(..)
, qf_bv
, allSupported
, Sort(..)
, boolSort
, bvSort
, intSort
, realSort
, varSort
, Term(..)
, un_app
, bin_app
, term_app
, pairwise_app
, namedTerm
, builder_list
, true
, false
, not
, implies
, and
, or
, xor
, eq
, distinct
, ite
, forall
, exists
, letBinder
, negate
, numeral
, decimal
, sub
, add
, mul
, div
, (./)
, mod
, abs
, le
, lt
, ge
, gt
, toReal
, toInt
, isInt
, concat
, extract
, bvnot
, bvand
, bvor
, bvxor
, bvneg
, bvadd
, bvsub
, bvmul
, bvudiv
, bvurem
, bvshl
, bvlshr
, bvult
, bit0
, bit1
, bvbinary
, bvdecimal
, bvhexadecimal
, bvashr
, bvslt
, bvsle
, bvule
, bvsgt
, bvsge
, bvugt
, bvuge
, bvsdiv
, bvsrem
, bvsignExtend
, bvzeroExtend
, arraySort
, arrayConst
, select
, store
) where
import qualified Data.BitVector.Sized as BV
import Data.Char (intToDigit)
import Data.Parameterized.NatRepr
import Data.String
import Data.Text (Text, cons)
import Data.Text.Lazy.Builder (Builder)
import qualified Data.Text.Lazy.Builder as Builder
import qualified Data.Text.Lazy.Builder.Int as Builder
import Numeric.Natural
import GHC.Generics (Generic)
import Data.Data (Data)
import Data.Typeable (Typeable)
import qualified Prelude
import Prelude hiding (and, or, concat, negate, div, mod, abs, not)
app_list :: Builder -> [Builder] -> Builder
app_list o args = "(" <> o <> go args
where go [] = ")"
go (f:r) = " " <> f <> go r
app :: Builder -> [Builder] -> Builder
app o [] = o
app o args = app_list o args
builder_list :: [Builder] -> Builder
builder_list [] = "()"
builder_list (h:l) = app_list h l
newtype Logic = Logic Builder
qf_bv :: Logic
qf_bv = Logic "QF_BV"
allSupported :: Logic
allSupported = Logic "ALL_SUPPORTED"
type Symbol = Text
newtype Sort = Sort { unSort :: Builder }
varSort :: Symbol -> Sort
varSort = Sort . Builder.fromText
boolSort :: Sort
boolSort = Sort "Bool"
bvSort :: Natural -> Sort
bvSort w | w >= 1 = Sort $ "(_ BitVec " <> fromString (show w) <> ")"
| otherwise = error "bvSort expects a positive number."
intSort :: Sort
intSort = Sort "Int"
realSort :: Sort
realSort = Sort "Real"
arraySort :: Sort -> Sort -> Sort
arraySort (Sort i) (Sort v) = Sort $ "(Array " <> i <> " " <> v <> ")"
newtype Term = T { renderTerm :: Builder }
deriving (IsString, Monoid, Semigroup)
term_app :: Builder -> [Term] -> Term
term_app o args = T (app o (renderTerm <$> args))
un_app :: Builder -> Term -> Term
un_app o (T x) = T $ mconcat ["(", o, " ", x, ")"]
bin_app :: Builder -> Term -> Term -> Term
bin_app o (T x) (T y) = T $ mconcat ["(", o, " ", x, " ", y, ")"]
chain_app :: Builder -> [Term] -> Term
chain_app f l@(_:_:_) = term_app f l
chain_app f _ = error $ show f ++ " expects two or more arguments."
assoc_app :: Builder -> Term -> [Term] -> Term
assoc_app _ t [] = t
assoc_app f t l = term_app f (t:l)
namedTerm :: Term -> Text -> Term
namedTerm (T x) nm = T $ "(! " <> x <> " :named " <> Builder.fromText nm <> ")"
true :: Term
true = T "true"
false :: Term
false = T "false"
not :: Term -> Term
not = un_app "not"
implies :: [Term] -> Term -> Term
implies [] t = t
implies l t = term_app "=>" (l ++ [t])
and :: [Term] -> Term
and [] = true
and [x] = x
and l = term_app "and" l
or :: [Term] -> Term
or [] = true
or [x] = x
or l = term_app "or" l
xor :: [Term] -> Term
xor l@(_:_:_) = term_app "xor" l
xor _ = error "xor expects two or more arguments."
eq :: [Term] -> Term
eq = chain_app "="
pairwise_app :: Builder -> [Term] -> Term
pairwise_app _ [] = true
pairwise_app _ [_] = true
pairwise_app f l@(_:_:_) = term_app f l
distinct :: [Term] -> Term
distinct = pairwise_app "distinct"
ite :: Term -> Term -> Term -> Term
ite c x y = term_app "ite" [c, x, y]
varBinding :: (Text,Sort) -> Builder
varBinding (nm, tp) = "(" <> Builder.fromText nm <> " " <> unSort tp <> ")"
forall :: [(Text, Sort)] -> Term -> Term
forall [] r = r
forall vars r =
T $ app "forall" [builder_list (varBinding <$> vars), renderTerm r]
exists :: [(Text, Sort)] -> Term -> Term
exists [] r = r
exists vars r =
T $ app "exists" [builder_list (varBinding <$> vars), renderTerm r]
letBinding :: (Text, Term) -> Builder
letBinding (nm, t) = app_list (Builder.fromText nm) [renderTerm t]
letBinder :: [(Text, Term)] -> Term -> Term
letBinder [] r = r
letBinder vars r =
T (app "let" [builder_list (letBinding <$> vars), renderTerm r])
negate :: Term -> Term
negate = un_app "-"
numeral :: Integer -> Term
numeral i | i >= 0 = T $ Builder.decimal i
| otherwise = negate (T (Builder.decimal (Prelude.negate i)))
decimal :: Integer -> Term
decimal i | i >= 0 = T $ Builder.decimal i <> ".0"
| otherwise = negate $ T $ Builder.decimal (Prelude.negate i) <> ".0"
sub :: Term -> [Term] -> Term
sub x [] = x
sub x l = term_app "-" (x:l)
add :: [Term] -> Term
add [] = T "0"
add [x] = x
add l = term_app "+" l
mul :: [Term] -> Term
mul [] = T "1"
mul [x] = x
mul l = term_app "*" l
div :: Term -> [Term] -> Term
div x [] = x
div x l = term_app "div" (x:l)
(./) :: Term -> [Term] -> Term
x ./ [] = x
x ./ l = term_app "/" (x:l)
mod :: Term -> Term -> Term
mod = bin_app "mod"
abs :: Term -> Term
abs = un_app "abs"
le :: [Term] -> Term
le = chain_app "<="
lt :: [Term] -> Term
lt = chain_app "<"
ge :: [Term] -> Term
ge = chain_app ">="
gt :: [Term] -> Term
gt = chain_app ">"
toReal :: Term -> Term
toReal = un_app "to_real"
toInt :: Term -> Term
toInt = un_app "to_int"
isInt :: Term -> Term
isInt = un_app "is_int"
arrayConst :: Sort -> Sort -> Term -> Term
arrayConst itp rtp c =
let tp = arraySort itp rtp
cast_app = builder_list [ "as" , "const" , unSort tp ]
in term_app cast_app [ c ]
select :: Term -> Term -> Term
select = bin_app "select"
store :: Term -> Term -> Term -> Term
store a i v = term_app "store" [a,i,v]
bit0 :: Term
bit0 = T "#b0"
bit1 :: Term
bit1 = T "#b1"
bvbinary :: 1 <= w => NatRepr w -> BV.BV w -> Term
bvbinary w0 u
| otherwise = T $ "#b" <> go (natValue w0)
where go :: Natural -> Builder
go 0 = mempty
go w =
let i = w - 1
b :: Builder
b = if BV.testBit' i u then "1" else "0"
in b <> go i
bvdecimal :: 1 <= w => NatRepr w -> BV.BV w -> Term
bvdecimal w u = T $ mconcat [ "(_ bv"
, Builder.decimal d
, " "
, Builder.decimal (natValue w)
, ")"]
where d = BV.asUnsigned u
bvhexadecimal :: 1 <= w => NatRepr w -> BV.BV w -> Term
bvhexadecimal w0 u
| otherwise = T $ "#x" <> go (natValue w0)
where go :: Natural -> Builder
go 0 = mempty
go w | w < 4 = error "bvhexadecimal width must be a multiple of 4."
go w =
let i = w - 4
charBits = BV.asUnsigned (BV.select' i (knownNat @4) u)
c :: Char
c = intToDigit $ fromInteger charBits
in Builder.singleton c <> go i
concat :: Term -> Term -> Term
concat = bin_app "concat"
extract :: Natural -> Natural -> Term -> Term
extract i j x
| i < j = error $ "End of extract (" ++ show i ++ ") less than beginning (" ++ show j ++ ")."
| otherwise =
let e = "(_ extract " <> Builder.decimal i <> " " <> Builder.decimal j <> ")"
in un_app e x
bvnot :: Term -> Term
bvnot = un_app "bvnot"
bvand :: Term -> [Term] -> Term
bvand = assoc_app "bvand"
bvor :: Term -> [Term] -> Term
bvor = assoc_app "bvor"
bvxor :: Term -> [Term] -> Term
bvxor = assoc_app "bvxor"
bvneg :: Term -> Term
bvneg = un_app "bvneg"
bvadd :: Term -> [Term] -> Term
bvadd = assoc_app "bvadd"
bvsub :: Term -> Term -> Term
bvsub = bin_app "bvsub"
bvmul :: Term -> [Term] -> Term
bvmul = assoc_app "bvmul"
bvudiv :: Term -> Term -> Term
bvudiv = bin_app "bvudiv"
bvurem :: Term -> Term -> Term
bvurem = bin_app "bvurem"
bvshl :: Term -> Term -> Term
bvshl = bin_app "bvshl"
bvlshr :: Term -> Term -> Term
bvlshr = bin_app "bvlshr"
bvult :: Term -> Term -> Term
bvult = bin_app "bvult"
bvule :: Term -> Term -> Term
bvule = bin_app "bvule"
bvsle :: Term -> Term -> Term
bvsle = bin_app "bvsle"
bvslt :: Term -> Term -> Term
bvslt = bin_app "bvslt"
bvuge :: Term -> Term -> Term
bvuge = bin_app "bvuge"
bvugt :: Term -> Term -> Term
bvugt = bin_app "bvugt"
bvsge :: Term -> Term -> Term
bvsge = bin_app "bvsge"
bvsgt :: Term -> Term -> Term
bvsgt = bin_app "bvsgt"
bvashr :: Term -> Term -> Term
bvashr = bin_app "bvashr"
bvsdiv :: Term -> Term -> Term
bvsdiv = bin_app "bvsdiv"
bvsrem :: Term -> Term -> Term
bvsrem = bin_app "bvsrem"
bvsignExtend :: Integer -> Term -> Term
bvsignExtend w x =
let e = "(_ sign_extend " <> Builder.decimal w <> ")"
in un_app e x
bvzeroExtend :: Integer -> Term -> Term
bvzeroExtend w x =
let e = "(_ zero_extend " <> Builder.decimal w <> ")"
in un_app e x
newtype Command = Cmd Builder
setLogic :: Logic -> Command
setLogic (Logic nm) = Cmd $ "(set-logic " <> nm <> ")"
setOption :: Text -> Text -> Command
setOption nm val = Cmd $ app_list "set-option" [":" <> Builder.fromText nm, Builder.fromText val]
ppBool :: Bool -> Text
ppBool b = if b then "true" else "false"
setProduceModels :: Bool -> Command
setProduceModels b = setOption "produce-models" (ppBool b)
exit :: Command
exit = Cmd "(exit)"
declareSort :: Symbol -> Integer -> Command
declareSort v n = Cmd $ app "declare-sort" [Builder.fromText v, fromString (show n)]
defineSort :: Symbol
-> [Symbol]
-> Sort
-> Command
defineSort v params d =
Cmd $ app "define-sort" [ Builder.fromText v
, builder_list (Builder.fromText <$> params)
, unSort d
]
declareConst :: Text -> Sort -> Command
declareConst v tp = Cmd $ app "declare-const" [Builder.fromText v, unSort tp]
declareFun :: Text -> [Sort] -> Sort -> Command
declareFun v argSorts retSort = Cmd $
app "declare-fun" [ Builder.fromText v
, builder_list $ unSort <$> argSorts
, unSort retSort
]
defineFun :: Text -> [(Text,Sort)] -> Sort -> Term -> Command
defineFun f args return_type e =
let resolveArg (var, tp) = app (Builder.fromText var) [unSort tp]
in Cmd $ app "define-fun" [ Builder.fromText f
, builder_list (resolveArg <$> args)
, unSort return_type
, renderTerm e
]
assert :: Term -> Command
assert p = Cmd $ app "assert" [renderTerm p]
assertNamed :: Term -> Text -> Command
assertNamed p nm =
Cmd $ app "assert"
[builder_list [Builder.fromText "!", renderTerm p, Builder.fromText ":named", Builder.fromText nm]]
checkSat :: Command
checkSat = Cmd "(check-sat)"
checkSatAssuming :: [Term] -> Command
checkSatAssuming l = Cmd $ "(check-sat-assuming " <> builder_list (renderTerm <$> l) <> ")"
checkSatWithAssumptions :: [Text] -> Command
checkSatWithAssumptions nms = Cmd $ app "check-sat-assuming" [builder_list (map Builder.fromText nms)]
getModel :: Command
getModel = Cmd "(get-model)"
getUnsatAssumptions :: Command
getUnsatAssumptions = Cmd "(get-unsat-assumptions)"
getUnsatCore :: Command
getUnsatCore = Cmd "(get-unsat-core)"
getValue :: [Term] -> Command
getValue values = Cmd $ app "get-value" [builder_list (renderTerm <$> values)]
resetAssertions :: Command
resetAssertions = Cmd "(reset-assertions)"
push :: Integer -> Command
push n = Cmd $ "(push " <> Builder.decimal n <> ")"
pop :: Integer -> Command
pop n = Cmd $ "(pop " <> Builder.decimal n <> ")"
data SMTInfoFlag =
Name
| Version
| ErrorBehavior
| InfoKeyword Text
deriving (Data, Eq, Ord, Generic, Show, Typeable)
flagToSExp :: SMTInfoFlag -> Text
flagToSExp = (cons ':') .
\case
Name -> "name"
Version -> "version"
ErrorBehavior -> "error-behavior"
InfoKeyword s -> s
getInfo :: SMTInfoFlag -> Command
getInfo flag = Cmd $ app "get-info" [Builder.fromText (flagToSExp flag)]
getVersion :: Command
getVersion = getInfo Version
getName :: Command
getName = getInfo Name
getErrorBehavior :: Command
getErrorBehavior = getInfo ErrorBehavior