-----------------------------------------------------------------------------
-- |
-- Module    : Documentation.SBV.Examples.Misc.Tuple
-- Author    : Joel Burget, Levent Erkok
-- License   : BSD3
-- Maintainer: erkokl@gmail.com
-- Stability : experimental
--
-- A basic tuple use case, also demonstrating regular expressions,
-- strings, etc. This is a basic template for getting SBV to produce
-- valid data for applications that require inputs that satisfy
-- arbitrary criteria.
-----------------------------------------------------------------------------

{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Documentation.SBV.Examples.Misc.Tuple where

import Data.SBV
import Data.SBV.Tuple
import Data.SBV.Control

import Data.SBV.List   ((.!!))
import Data.SBV.RegExp

import qualified Data.SBV.String as S
import qualified Data.SBV.List   as L

-- | A dictionary is a list of lookup values. Note that we
-- store the type @[(a, b)]@ as a symbolic value here, mixing
-- sequences and tuples.
type Dict a b = SBV [(a, b)]

-- | Create a dictionary of length 5, such that each element
-- has an string key and each value is the length of the key.
-- We impose a few more constraints to make the output interesting. 
-- For instance, you might get:
--
-- @ ghci> example
-- [("nt_",3),("dHAk",4),("kzkk0",5),("mZxs9s",6),("c32'dPM",7)]
-- @
--
-- Depending on your version of z3, a different answer might be provided.
-- Here, we check that it satisfies our length conditions:
--
-- >>> import Data.List (genericLength)
-- >>> example >>= \ex -> return (length ex == 5 && all (\(l, i) -> genericLength l == i) ex)
-- True
example :: IO [(String, Integer)]
example = runSMT $ do dict :: Dict String Integer <- free "dict"

                      -- require precisely 5 elements
                      let len   = 5 :: Int
                          range = [0 .. len - 1]

                      constrain $ L.length dict .== fromIntegral len

                      -- require each key to be at of length 3 more than the index it occupies
                      -- and look like an identifier
                      let goodKey i s = let l = S.length s
                                            r = asciiLower * KStar (asciiLetter + digit + "_" + "'")
                                      in l .== fromIntegral i+3 .&& s `match` r

                          restrict i = case untuple (dict .!! fromIntegral i) of
                                         (k, v) -> constrain $ goodKey i k .&& v .== S.length k

                      mapM_ restrict range

                      -- require distinct keys:
                      let keys = [(dict .!! fromIntegral i)^._1 | i <- range]
                      constrain $ distinct keys

                      query $ do ensureSat
                                 getValue dict