-----------------------------------------------------------------------------
-- |
-- Module    : Documentation.SBV.Examples.TP.Kleene
-- Copyright : (c) Levent Erkok
-- License   : BSD3
-- Maintainer: erkokl@gmail.com
-- Stability : experimental
--
-- Example use of the TP layer, proving some Kleene algebra theorems.
--
-- Based on 
-----------------------------------------------------------------------------
{-# LANGUAGE DataKinds           #-}
{-# LANGUAGE FlexibleInstances   #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell     #-}
{-# LANGUAGE TypeAbstractions    #-}
{-# OPTIONS_GHC -Wall -Werror -Wno-unused-matches #-}
module Documentation.SBV.Examples.TP.Kleene where
import Prelude hiding((<=))
import Data.SBV
import Data.SBV.TP
-- | An uninterpreted sort, corresponding to the type of Kleene algebra strings.
data Kleene
mkSymbolic [''Kleene]
-- | Star operator over kleene algebras. We're leaving this uninterpreted.
star :: SKleene -> SKleene
star = uninterpret "STAR"
-- | The 'Num' instance for Kleene makes it easy to write regular expressions
-- in the more familiar form.
instance Num SKleene where
  (+) = uninterpret "PAR"
  (*) = uninterpret "SEQ"
  abs    = error "SKleene: not defined: abs"
  signum = error "SKleene: not defined: signum"
  negate = error "SKleene: not defined: signum"
  fromInteger 0 = uninterpret "zero"
  fromInteger 1 = uninterpret "one"
  fromInteger n = error $ "SKleene: not defined: fromInteger " ++ show n
-- | The set of strings matched by one regular expression is a subset of the second,
-- if adding it to the second doesn't change the second set.
(<=) :: SKleene -> SKleene -> SBool
x <= y = x + y .== y
-- | A sequence of Kleene algebra proofs. See 
--
-- We have:
--
-- >>> kleeneProofs
-- Axiom: par_assoc
-- Axiom: par_comm
-- Axiom: par_idem
-- Axiom: par_zero
-- Axiom: seq_assoc
-- Axiom: seq_zero
-- Axiom: seq_one
-- Axiom: rdistrib
-- Axiom: ldistrib
-- Axiom: unfold
-- Axiom: least_fix
-- Lemma: par_lzero                        Q.E.D.
-- Lemma: par_monotone                     Q.E.D.
-- Lemma: seq_monotone                     Q.E.D.
-- Lemma: star_star_1
--   Step: 1 (unfold)                      Q.E.D.
--   Step: 2 (factor out x * star x)       Q.E.D.
--   Step: 3 (par_idem)                    Q.E.D.
--   Step: 4 (unfold)                      Q.E.D.
--   Result:                               Q.E.D.
-- Lemma: subset_eq                        Q.E.D.
-- Lemma: star_star_2_2                    Q.E.D.
-- Lemma: star_star_2_3                    Q.E.D.
-- Lemma: star_star_2_1                    Q.E.D.
-- Lemma: star_star_2                      Q.E.D.
kleeneProofs :: IO ()
kleeneProofs = runTP $ do
  -- Kozen axioms
  par_assoc <- axiom "par_assoc" $ \(Forall @"x" (x :: SKleene)) (Forall @"y" y) (Forall @"z" z) -> x + (y + z) .== (x + y) + z
  par_comm  <- axiom "par_comm"  $ \(Forall @"x" (x :: SKleene)) (Forall @"y" y)                 -> x + y       .== y + x
  par_idem  <- axiom "par_idem"  $ \(Forall @"x" (x :: SKleene))                                 -> x + x       .== x
  par_zero  <- axiom "par_zero"  $ \(Forall @"x" (x :: SKleene))                                 -> x + 0       .== x
  seq_assoc <- axiom "seq_assoc" $ \(Forall @"x" (x :: SKleene)) (Forall @"y" y) (Forall @"z" z) -> x * (y * z) .== (x * y) * z
  seq_zero  <- axiom "seq_zero"  $ \(Forall @"x" (x :: SKleene))                                 -> x * 0       .== 0
  seq_one   <- axiom "seq_one"   $ \(Forall @"x" (x :: SKleene))                                 -> x * 1       .== x
  rdistrib  <- axiom "rdistrib"  $ \(Forall @"x" (x :: SKleene)) (Forall @"y" y) (Forall @"z" z) -> x * (y + z) .== x * y + x * z
  ldistrib  <- axiom "ldistrib"  $ \(Forall @"x" (x :: SKleene)) (Forall @"y" y) (Forall @"z" z) -> (y + z) * x .== y * x + z * x
  unfold    <- axiom "unfold"    $ \(Forall @"e" e) -> star e .== 1 + e * star e
  least_fix <- axiom "least_fix" $ \(Forall @"x" x) (Forall @"e" e) (Forall @"f" f) -> ((f + e * x) <= x) .=> ((star e * f) <= x)
  -- Collect the basic axioms in a list for easy reference
  let kleene = [ proofOf par_assoc,  proofOf par_comm, proofOf par_idem, proofOf par_zero
               , proofOf seq_assoc,  proofOf seq_zero, proofOf seq_one
               , proofOf ldistrib,   proofOf rdistrib
               , proofOf unfold
               , proofOf least_fix
               ]
  -- Various proofs:
  par_lzero    <- lemma "par_lzero"    (\(Forall @"x" x) -> (0 :: SKleene) + x .== x)                                        kleene
  par_monotone <- lemma "par_monotone" (\(Forall @"x" x) (Forall @"y" y) (Forall @"z" z) -> x <= y .=> ((x + z) <= (y + z))) kleene
  seq_monotone <- lemma "seq_monotone" (\(Forall @"x" x) (Forall @"y" y) (Forall @"z" z) -> x <= y .=> ((x * z) <= (y * z))) kleene
  -- This one requires a chain of reasoning: x* x* == x*
  star_star_1  <- calc "star_star_1"
                       (\(Forall @"x" x) -> star x * star x .== star x) $
                       \x -> [] |- star x * star x                     ?? unfold
                                =: (1 + x * star x) * (1 + x * star x)
                                ?? "factor out x * star x"
                                ?? kleene
                                =: (1 + 1) + (x * star x + x * star x) ?? par_idem
                                =: 1 + x * star x                      ?? unfold
                                =: star x
                                =: qed
  subset_eq   <- lemma "subset_eq" (\(Forall @"x" x) (Forall @"y" y) -> (x .== y) .== (x <= y .&& y <= x)) kleene
  -- Prove: x** = x*
  star_star_2 <- do _1 <- lemma "star_star_2_2" (\(Forall @"x" x) -> ((star x * star x + 1) <= star x) .=> star (star x) <= star x) kleene
                    _2 <- lemma "star_star_2_3" (\(Forall @"x" x) -> star (star x) <= star x)                                       (kleene ++ [proofOf _1])
                    _3 <- lemma "star_star_2_1" (\(Forall @"x" x) -> star x        <= star (star x))                                kleene
                    lemma "star_star_2" (\(Forall @"x" x) -> star (star x) .== star x) [proofOf subset_eq, proofOf _2, proofOf _3]
  pure ()