packages feed

sbv-14.0: Documentation/SBV/Examples/ADT/Expr.hs

-----------------------------------------------------------------------------
-- |
-- Module    : Documentation.SBV.Examples.ADT.Expr
-- Copyright : (c) Levent Erkok
-- License   : BSD3
-- Maintainer: erkokl@gmail.com
-- Stability : experimental
--
-- A basic expression ADT example.
-----------------------------------------------------------------------------

{-# LANGUAGE FlexibleInstances   #-}
{-# LANGUAGE OverloadedLists     #-}
{-# LANGUAGE QuasiQuotes         #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell     #-}
{-# LANGUAGE TypeApplications    #-}

{-# OPTIONS_GHC -Wall -Werror #-}

module Documentation.SBV.Examples.ADT.Expr where

import Data.SBV
import Data.SBV.Control
import Data.SBV.RegExp
import Data.SBV.Tuple
import qualified Data.SBV.List as SL

-- | A basic arithmetic expression type.
data Expr = Val Integer
          | Var String
          | Add Expr Expr
          | Mul Expr Expr
          | Let String Expr Expr

-- | Create a symbolic version of expressions.
mkSymbolic [''Expr]

-- | Show instance for 'Expr'.
instance Show Expr where
  show (Val i)     = show i
  show (Var a)     = a
  show (Add l r)   = "(" ++ show l ++ " + " ++ show r ++ ")"
  show (Mul l r)   = "(" ++ show l ++ " * " ++ show r ++ ")"
  show (Let s a b) = "(let " ++ s ++ " = " ++ show a ++ " in " ++ show b ++ ")"

-- | Num instance, simplifies construction of values
instance Num Expr where
  fromInteger = Val
  (+)         = Add
  (*)         = Mul
  abs         = error "Num Expr: undefined abs"
  signum      = error "Num Expr: undefined signum"
  negate      = error "Num Expr: undefined negate"

-- | Num instance for the symbolic version
instance Num SExpr where
  fromInteger = sVal . literal
  (+)         = sAdd
  (*)         = sMul
  abs         = error "Num SExpr: undefined abs"
  signum      = error "Num SExpr: undefined signum"
  negate      = error "Num SExpr: undefined negate"

-- | Validity: We require each variable appearing to be an identifier (lowercase letter followed by
-- any number of upper-lower case letters and digits), and all expressions are closed; i.e., any
-- variable referenced is introduced by an enclosing let expression.
isValid :: SExpr -> SBool
isValid = go []
  where isId s = s `match` (asciiLower * KStar (asciiLetter + digit))
        go :: SList String -> SExpr -> SBool
        go = smtFunction "valid"
           $ \env expr -> [sCase| expr of
                              Var s     -> isId s .&& s `SL.elem` env
                              Val _     -> sTrue
                              Add l r   -> go env l .&& go env r
                              Mul l r   -> go env l .&& go env r
                              Let s a b -> isId s .&& go env a .&& go (s SL..: env) b
                           |]

-- | Evaluate an expression.
eval :: SExpr -> SInteger
eval = go []
 where go :: SList (String, Integer) -> SExpr -> SInteger
       go = smtFunction "eval"
          $ \env expr -> [sCase| expr of
                            Val i     -> i
                            Var s     -> get env s
                            Add l r   -> go env l + go env r
                            Mul l r   -> go env l * go env r
                            Let s e r -> go (tuple (s, go env e) SL..: env) r
                         |]

       get :: SList (String, Integer) -> SString -> SInteger
       get = smtFunction "get"
           $ \env s -> [sCase| env of
                          []                    -> 0
                          (k, v) : es | s .== k -> v
                                      | True    -> get es s
                       |]

-- | A basic theorem about 'eval'.
-- >>> evalPlus5
-- Q.E.D.
evalPlus5 :: IO ThmResult
evalPlus5 = prove $ do e :: SExpr <- free "e"
                       pure $ eval (e + 5) .== 5 + eval e

-- | A simple sat result example.
--
-- >>> evalSat
-- Satisfiable. Model:
--   e = Let "k" (Val 2) (Mul (Val 1) (Var "k")) :: Expr
--   a =                                      18 :: Integer
--   b =                                      10 :: Integer
evalSat :: IO SatResult
evalSat = sat $ do e :: SExpr    <- free "e"
                   constrain $ isValid e
                   constrain $ isLet   e

                   a :: SInteger <- free "a"
                   b :: SInteger <- free "b"
                   constrain $ a .>= 4
                   constrain $ b .>= 10

                   pure $ eval (e + sVal a) .== b * eval e

-- | Another test, generating some (mildly) interesting examples.
--
-- >>> genE
-- Satisfiable. Model:
--   e1 = Let "p" (Val 5) (Val 3) :: Expr
--   e2 =                Val (-2) :: Expr
genE :: IO SatResult
genE = sat $ do e1 :: SExpr <- free "e1"
                e2 :: SExpr <- free "e2"

                constrain $ isValid e1
                constrain $ isValid e2

                constrain $ e1 ./== e2
                constrain $ isLet e1
                constrain $ eval e1 .== 3
                constrain $ eval e1 .== eval e2 + 5

-- | Query mode example.
--
-- >>> queryE
-- e1: (let p = 5 in 3)
-- e2: -2
queryE :: IO ()
queryE = runSMT $ do
           e1 :: SExpr <- free "e1"
           e2 :: SExpr <- free "e2"

           constrain $ isValid e1
           constrain $ isValid e2

           constrain $ e1 ./== e2
           constrain $ isLet e1
           constrain $ eval e1 .== 3
           constrain $ eval e1 .== eval e2 + 5

           query $ do cs <- checkSat
                      case cs of
                        Sat -> do e1v <- getValue e1
                                  e2v <- getValue e2
                                  io $ putStrLn $ "e1: " ++ show e1v
                                  io $ putStrLn $ "e2: " ++ show e2v
                        _   -> error $ "Unexpected result: " ++ show cs

{- HLint ignore module "Reduce duplication" -}