packages feed

sbv-program-1.0.0.0: src/Data/SBV/Program/Types.hs

{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}

module Data.SBV.Program.Types (
  module Data.Biapplicative,
  module Data.Bifoldable,
  module Data.Bitraversable,

  Location,
  SLocation,

  SynthSpec(..),
  SynthComponent(..),

  SimpleSpec(..),
  SimpleComponent(..),

  SynthesisError(..),

  IOs(..),
  Instruction(..),
  Program(..),
  toIOsList,
  sortInstructions,

  ProgramTree(..),
  buildProgramTree,
  buildForestResult,
  )
where

import Data.Biapplicative
import Data.Bifoldable
import Data.Bitraversable
import Data.Foldable
import Data.List
import qualified Data.Map as M
import qualified Data.Set as S
import Data.SBV


-- | Type used to represent a value from the __set of location variables__ \(l_x \in L\).
type Location = Word64
-- | Symbolic 'Location'.
type SLocation = SWord64


-- | Class for a program or a component __specification__ \(φ(\vec I, O)\). Type
-- variable 'a' stands for function's domain type.
class SynthSpec s a where
  -- | Number of inputs the specification function takes.
  specArity :: s a -> Word
  -- | An equation that relates input variables to the output one. The equation is
  -- build up either using '(.==)' or in a "tabular" way using multiple '(.=>)'
  -- expressions. See definitions from the Data.SBV.Program.SimpleLibrary module
  -- for examples.
  specFunc :: s a
    -- | Input variables. The list should be of 'specArity' size.
    -> [SBV a]
    -- | Output variable.
    -> SBV a
    -> SBool

-- | A class for a __library component__.
class SynthSpec spec a => SynthComponent comp spec a | comp -> spec where
  -- | Component name (optional). Used for naming SBV variables and when rendering the resulting program.
  compName :: comp a -> String
  -- | Component's __specification__.
  compSpec :: comp a -> spec a
  -- | Optional constraints to set on __location variables__ \(l_x \in L\).
  extraLocConstrs :: comp a -> [[SLocation] -> SLocation -> SBool]

  compName = const ""
  extraLocConstrs = const []


-- | A simplest __specification__ datatype possible. Type variable 'a' stands
-- for function's domain type.
data SimpleSpec a = SimpleSpec {
    simpleArity :: Word
  , simpleFunc :: [SBV a] -> SBV a -> SBool
  }

instance SynthSpec SimpleSpec a where
  specArity = simpleArity
  specFunc = simpleFunc

-- | A simplest __library component__ datatype possible.
data SimpleComponent a = SimpleComponent {
    simpleName :: String
  , simpleSpec :: SimpleSpec a
  }

instance SynthComponent SimpleComponent SimpleSpec a where
  compName = simpleName
  compSpec = simpleSpec
  extraLocConstrs = const []

instance Show (SimpleComponent spec) where
  show = compName


-- | Possible failure reasons during synthesis operation.
data SynthesisError = ErrorUnsat
                    | ErrorUnknown String
                    | ErrorZeroResultsRequested
                    | ErrorSeedingFailed
  deriving Show


-- | A datatype holding inputs and output of something. Usual types for 'l' are 'Location' and 'SLocation'.
data IOs l = IOs {
    _ins :: [l],
    _out :: l
  }
  deriving (Show, Eq, Ord, Functor)

instance Foldable IOs where
  foldMap f (IOs {..}) = mconcat (map f _ins) `mappend` f _out

instance Traversable IOs where
  traverse f (IOs {..}) = IOs <$> traverse f _ins <*> f _out

instance EqSymbolic l => EqSymbolic (IOs l) where
  l .== r = toList l .== toList r


-- | A datatype that holds a 'SynthComponent' with inputs and output locations.
data Instruction l a = Instruction {
    instructionIOs :: IOs l,
    instructionComponent :: a
  }
  deriving (Show, Eq, Ord)

instance Bifunctor Instruction where
  bimap iosF compF (Instruction {..}) = Instruction (fmap iosF instructionIOs) (compF instructionComponent)

instance Bitraversable Instruction where
  bitraverse iosF compF (Instruction ios comp) = Instruction <$> traverse iosF ios <*> compF comp

instance Bifoldable Instruction where
  bifoldMap f1 f2 (Instruction {..}) = foldMap f1 instructionIOs `mappend` f2 instructionComponent


-- | A datatype that unites program instructions with 'IOs' of the program itself.
data Program l a = Program {
    programIOs :: IOs l,
    programInstructions :: [Instruction l a]
  }
  deriving (Show, Eq, Ord)

instance Bifunctor Program where
  bimap iosF compF (Program {..}) = Program (fmap iosF programIOs) (map (bimap iosF compF) programInstructions)

instance Bitraversable Program where
  bitraverse iosF compF (Program ios instrs) = Program <$> traverse iosF ios <*> traverse (bitraverse iosF compF) instrs

instance Bifoldable Program where
  bifoldMap f1 f2 (Program {..}) = foldMap f1 programIOs `mappend` foldMap (bifoldMap f1 f2) programInstructions

-- | Extract all locations from the program as a list, including locations of instructions.
toIOsList :: Program l a -> [l]
toIOsList = bifoldMap (:[]) (const mempty)

-- | Sorts program's instructions by their output location.
sortInstructions :: Ord l => Program l a -> Program l a
sortInstructions p = p { programInstructions = sortOn (_out . instructionIOs) (programInstructions p) }


-- | A `Program` converted into a tree-like structure.
data ProgramTree a = InstructionNode a [ProgramTree a]
                   | InputLeaf Location
    deriving (Show, Eq, Ord, Functor)

instance Foldable ProgramTree where
  foldMap _ (InputLeaf _) = mempty
  foldMap f (InstructionNode comp children) = foldMap (foldMap f) children <> f comp

-- | Create a 'ProgramTree' for a given 'Program' by resolving its 'Location's.
-- This function effectively performs dead code elimination.
buildProgramTree :: Program Location a -> ProgramTree a
buildProgramTree prog = buildProgramTree' prog (_out $ programIOs prog)

-- | A variant of 'buildProgramTree' that builds from a specified starting point.
buildProgramTree' :: Program Location a -> Location -> ProgramTree a
buildProgramTree' prog@(Program {..}) startingOutputLoc =
  if startingOutputLoc `notElem` _ins programIOs
    then InstructionNode
        (instructionComponent (instsMap M.! startingOutputLoc))
        (map (buildProgramTree' prog) $ _ins $ instructionIOs (instsMap M.! startingOutputLoc))
    else InputLeaf startingOutputLoc
  where
    instsMap = M.fromList $ map (\inst -> (_out $ instructionIOs inst, inst)) programInstructions

-- | Create a 'ProgramTree' for each unused output in the 'Program'
buildForestResult sr@(Program {..}) = map (buildProgramTree' sr) rootOutputs
  where
    inputsSet = S.fromList $ _ins programIOs ++ concatMap (_ins . instructionIOs) programInstructions
    rootOutputs = filter isRootOutput $ map (_out . instructionIOs) programInstructions
    isRootOutput o = o `S.notMember` inputsSet