1module Main where23import Data.Maybe (isNothing)4import Control.Monad.IO.Class (liftIO)5import Control.Monad (forM_, void, when, unless)6import Control.Monad.State.Strict (StateT, gets, modify, runStateT)7import Data.Map.Strict qualified as Map8import Data.Maybe (fromMaybe)9import SimpleSMT qualified as SMT10import System.IO (stdin, hGetContents)11import Options.Applicative qualified as OPT1213data Opts = Opts14 { optElimAnds :: Bool,15 optLogic :: Maybe String,16 optTrackAssert :: Bool }1718optsParser :: OPT.Parser Opts19optsParser =20 Opts21 <$> OPT.switch22 ( OPT.long "eliminate-ands"23 <> OPT.short 'e'24 <> OPT.help "Eliminate outer and expressions in check-sat-assuming"25 )26 <*> OPT.optional27 ( OPT.strOption $28 OPT.long "logic"29 <> OPT.short 'l'30 <> OPT.metavar "LOGIC"31 <> OPT.help "Overwrite logic"32 )33 <*> OPT.switch34 ( OPT.long "track-assertions"35 <> OPT.short 'a'36 <> OPT.help "Track push/pop assertion stacks"37 )3839------------------------------------------------------------------------4041data UnwindEnv42 = UnwindEnv43 { trackAsserts :: Bool,44 elimAnds :: Bool,45 setLogic :: Maybe String,46 logicWritten :: Bool,47 declaredVars :: Map.Map String SMT.SExpr,48 assertStack :: [[SMT.SExpr]],49 exprs :: [SMT.SExpr]50 }51 deriving (Show, Eq)5253mkUnwindEnv :: Bool -> Bool -> Maybe String -> UnwindEnv54mkUnwindEnv trackAsserts andElim setLogic =55 UnwindEnv trackAsserts andElim setLogic False Map.empty [] []5657------------------------------------------------------------------------5859buildMap :: [SMT.SExpr] -> Map.Map String SMT.SExpr60buildMap exprs =61 Map.fromList $62 foldl (\acc x -> toVarPair x : acc) [] exprs6364toVarPair :: SMT.SExpr -> (String, SMT.SExpr)65toVarPair (SMT.List [SMT.Atom varName, varValue]) = (varName, varValue)66toVarPair _ = error "invalid let binding"6768substVars :: Map.Map String SMT.SExpr -> SMT.SExpr -> SMT.SExpr69substVars varMap atom@(SMT.Atom name) =70 fromMaybe atom $ Map.lookup name varMap71substVars varMap (SMT.List lst) =72 SMT.List $ map (substVars varMap) lst7374inlineLet' :: Map.Map String SMT.SExpr -> SMT.SExpr -> SMT.SExpr75inlineLet' varMap (SMT.List [SMT.Atom "let", SMT.List letBind, letBody]) =76 let varMap' = Map.union (buildMap letBind) varMap77 in substVars varMap' (inlineLet' varMap' letBody)78 where79inlineLet' varMap atom = substVars varMap atom8081inlineLet :: SMT.SExpr -> SMT.SExpr82inlineLet = inlineLet' Map.empty8384------------------------------------------------------------------------8586newAssertLevel :: StateT UnwindEnv IO ()87newAssertLevel =88 modify (\s -> s {assertStack = [] : assertStack s})8990popAssertLevel :: StateT UnwindEnv IO ()91popAssertLevel = modify go92 where93 go s@UnwindEnv {assertStack = []} = s94 go s@UnwindEnv {assertStack = _ : xs} = s {assertStack = xs}9596addAssertion :: SMT.SExpr -> StateT UnwindEnv IO ()97addAssertion assertions' = do98 stk <- gets assertStack99 let newStk = case stk of100 (x : xs) -> (x ++ assertions) : xs101 [] -> [assertions]102 modify (\s -> s {assertStack = newStk})103 where104 assertions = [inlineLet assertions']105106addExpr :: SMT.SExpr -> StateT UnwindEnv IO ()107addExpr expr =108 modify (\s -> s {exprs = exprs s ++ [expr]})109110-- | Retrieve a list of conjunction from and expressions for check-sat-assuming.111subsumeAnds :: SMT.SExpr -> [SMT.SExpr]112subsumeAnds (SMT.List [SMT.Atom "and", lhs@(SMT.List (SMT.Atom "and" : _)), rhs])113 = subsumeAnds lhs ++ subsumeAnds rhs114subsumeAnds (SMT.List [SMT.Atom "and", lhs, rhs@(SMT.List (SMT.Atom "and" : _))])115 = subsumeAnds lhs ++ subsumeAnds rhs116subsumeAnds (SMT.List [SMT.Atom "and", lhs, rhs])117 = subsumeAnds lhs ++ subsumeAnds rhs118subsumeAnds expr = [expr] -- Stop recursion on first non-and expression.119120getAsserts :: StateT UnwindEnv IO [SMT.SExpr]121getAsserts = do122 andOpt <- gets elimAnds123 consLs <- gets (concat . reverse . assertStack)124 if andOpt125 then pure [SMT.List (inlineAnds consLs)]126 else pure [SMT.List consLs]127 where128 inlineAnds :: [SMT.SExpr] -> [SMT.SExpr]129 inlineAnds = concat . map subsumeAnds130131completeQuery :: StateT UnwindEnv IO ()132completeQuery = do133 query <- gets exprs134 liftIO $ putStr (serialize query)135136 track <- gets trackAsserts137 if track138 then modify ( \s -> s { exprs = [] })139 else modify ( \s -> s { exprs = [], assertStack = [] })140 where141 serialize :: [SMT.SExpr] -> String142 serialize = unlines . map (`SMT.showsSExpr` "")143144addVariable :: String -> SMT.SExpr -> StateT UnwindEnv IO ()145addVariable name expr = do146 vars <- gets declaredVars147 when (isNothing $ Map.lookup name vars) $ do148 liftIO $ putStrLn (SMT.showsSExpr expr "")149 modify (\s -> s {declaredVars = Map.insert name expr vars})150151transExpr :: SMT.SExpr -> StateT UnwindEnv IO ()152transExpr (SMT.List [SMT.Atom "push", SMT.Atom arg]) = do153 let num = (read arg :: Integer)154 forM_ [1 .. num] (const newAssertLevel)155transExpr (SMT.List [SMT.Atom "pop", SMT.Atom arg]) = do156 let num = (read arg :: Integer)157 forM_ [1 .. num] (const popAssertLevel)158transExpr (SMT.List [SMT.Atom "assert", xs]) =159 addAssertion xs160transExpr (SMT.List [SMT.Atom "check-sat"]) = do161 asserts <- getAsserts162 addExpr $ SMT.List (SMT.Atom "check-sat-assuming" : asserts)163 completeQuery164transExpr expr@(SMT.List ((SMT.Atom "set-logic") : _)) = do165 wasWritten <- gets logicWritten166 unless (wasWritten) $ do167 mayLogic <- gets setLogic168 case mayLogic of169 Just l ->170 liftIO $ putStrLn ((SMT.showsSExpr $ SMT.List [SMT.Atom "set-logic", SMT.Atom l]) "")171 Nothing ->172 liftIO $ putStrLn ((SMT.showsSExpr expr) "")173 modify (\s -> s {logicWritten = True})174transExpr expr@(SMT.List ((SMT.Atom "declare-fun") : (SMT.Atom name) : _)) = addVariable name expr175transExpr (SMT.List ((SMT.Atom "set-option" : _))) = pure ()176transExpr (SMT.List ((SMT.Atom "set-info") : _)) = pure ()177transExpr (SMT.List ((SMT.Atom "get-value") : _)) = pure ()178transExpr (SMT.List ((SMT.Atom "exit") : _)) = modify (\s -> s { assertStack = [] })179transExpr expr = addExpr expr180181transform :: [SMT.SExpr] -> StateT UnwindEnv IO ()182transform sexprs = forM_ sexprs transExpr183184------------------------------------------------------------------------185186readSExprs :: String -> [SMT.SExpr]187readSExprs str = go (SMT.readSExpr str)188 where189 go :: Maybe (SMT.SExpr, String) -> [SMT.SExpr]190 go Nothing = []191 go (Just (acc, rest)) = acc : go (SMT.readSExpr rest)192193getQueries :: Opts -> [SMT.SExpr] -> IO ()194getQueries opts exprs = void $ runTransform exprs195 where196 runTransform e =197 runStateT198 (transform e)199 $ mkUnwindEnv (optTrackAssert opts) (optElimAnds opts) (optLogic opts)200201main :: IO ()202main = do203 args <- OPT.execParser cmd204 exprs <- readSExprs <$> hGetContents stdin205206 getQueries args exprs207 where208 cmd :: OPT.ParserInfo Opts209 cmd =210 OPT.info211 (optsParser OPT.<**> OPT.helper)212 ( OPT.fullDesc213 <> OPT.progDesc "normalize smt-lib inputs"214 )