packages feed

qute-symex-0.1.0: bench/SMTUnwind.hs

-- SPDX-FileCopyrightText: 2025 Sören Tempel <soeren+git@soeren-tempel.net>
--
-- SPDX-License-Identifier: MIT AND GPL-3.0-only

module SMTUnwind (unwind) where

import Control.Monad (forM_)
import Control.Monad.State.Strict (State, gets, modify, runState)
import Data.Functor ((<&>))
import SimpleSMT qualified as SMT

data UnwindEnv
  = UnwindEnv
  { assertStack :: [[SMT.SExpr]],
    exprs :: [SMT.SExpr],
    queries :: [[SMT.SExpr]]
  }
  deriving (Show, Eq)

mkUnwindEnv :: UnwindEnv
mkUnwindEnv = UnwindEnv [] [] []

------------------------------------------------------------------------

newAssertLevel :: State UnwindEnv ()
newAssertLevel =
  modify (\s -> s {assertStack = [] : assertStack s})

popAssertLevel :: State UnwindEnv ()
popAssertLevel = modify go
  where
    go s@UnwindEnv {assertStack = []} = s
    go s@UnwindEnv {assertStack = _ : xs} = s {assertStack = xs}

addAssertion :: [SMT.SExpr] -> State UnwindEnv ()
addAssertion assertions = do
  stk <- gets assertStack
  let newStk = case stk of
        (x : xs) -> (x ++ assertions) : xs
        [] -> [assertions]
  modify (\s -> s {assertStack = newStk})

addExpr :: SMT.SExpr -> State UnwindEnv ()
addExpr expr =
  modify (\s -> s {exprs = exprs s ++ [expr]})

getAsserts :: State UnwindEnv [SMT.SExpr]
getAsserts = gets (concat . reverse . assertStack)

completeQuery :: State UnwindEnv ()
completeQuery =
  modify
    ( \s ->
        s
          { queries = queries s ++ [exprs s],
            exprs = []
          }
    )

transExpr :: SMT.SExpr -> State UnwindEnv ()
transExpr (SMT.List [SMT.Atom "push", SMT.Atom arg]) = do
  let num = (read arg :: Integer)
  forM_ [1 .. num] (const newAssertLevel)
transExpr (SMT.List [SMT.Atom "pop", SMT.Atom arg]) = do
  let num = (read arg :: Integer)
  forM_ [1 .. num] (const popAssertLevel)
transExpr (SMT.List ((SMT.Atom "assert") : xs)) =
  addAssertion xs
transExpr (SMT.List [SMT.Atom "check-sat"]) = do
  asserts <- getAsserts
  addExpr $ SMT.List [SMT.Atom "check-sat-assuming", SMT.List asserts]
  completeQuery
transExpr (SMT.List ((SMT.Atom "get-value") : _)) = pure ()
transExpr expr = addExpr expr

transform :: [SMT.SExpr] -> State UnwindEnv ()
transform sexprs = forM_ sexprs transExpr

------------------------------------------------------------------------

readSExprs :: String -> [SMT.SExpr]
readSExprs str = go (SMT.readSExpr str)
  where
    go :: Maybe (SMT.SExpr, String) -> [SMT.SExpr]
    go Nothing = []
    go (Just (acc, rest)) = acc : go (SMT.readSExpr rest)

getQueries :: [SMT.SExpr] -> [[SMT.SExpr]]
getQueries exprs = queries (snd $ runTransform exprs)
  where
    runTransform e = runState (transform e) mkUnwindEnv

unwind :: FilePath -> IO String
unwind origFp = do
  exprs <- readFile origFp <&> readSExprs
  let queries = getQueries exprs
  pure $ serialize (concat queries)
  where
    serialize :: [SMT.SExpr] -> String
    serialize = unlines . map (`SMT.showsSExpr` "")