lol-typing-1.20160822: Language/LOL/Typing/Solver/Constraint.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeFamilies #-}
{-# OPTIONS_GHC -fno-warn-tabs #-}
module Language.LOL.Typing.Solver.Constraint where
import Control.Monad (Monad(..), filterM, forM_, mapM, unless, when)
import Data.Bool
import qualified Data.Foldable as Foldable
import Data.Function (($), (.))
import Data.Functor (Functor(..), (<$>))
import qualified Data.List as List
import Data.Maybe (Maybe(..))
import Data.Monoid (Monoid(..), (<>))
import Data.Sequence (Seq, (<|))
import qualified Data.Sequence as Seq
import Data.String (String)
import qualified Data.Text as Text
import Data.Text.Buildable (Buildable(..))
import Data.Tuple (fst, snd)
import Prelude (error)
import Text.Show (Show(..))
import Language.LOL.Typing.Type
import Language.LOL.Typing.Solver.Common
import qualified Language.LOL.Typing.Lib.Control.Monad.Classes.Instance as MC
import qualified Language.LOL.Typing.Lib.Control.Monad.Classes.StateFix as MC
import Language.LOL.Typing.Lib.Data.Empty (Empty(..))
import qualified Language.LOL.Typing.Lib.Data.Text.Buildable as Build
-- * Type 'Constraint'
data Constraint m =
forall c. (Show c, Buildable c, Substitutable c)
=> Constraint c
(c -> m ()) -- solving strategy
(c -> m Bool) -- semantic check
instance Show (Constraint m) where
show (Constraint c _ _) = show c
instance Buildable (Constraint m) where
build (Constraint c _ _) = build c
instance Substitutable (Constraint m) where
subvars (Constraint c _ _) = subvars c
sub `substitute` (Constraint c f g) =
Constraint (sub `substitute` c) f g
instance Monad m => Solvable (Constraint m) m where
constraint_solver (Constraint c f _) = f c
constraint_checker (Constraint c _ f) = f c
-- | Return a 'Constraint' from a 'Solvable' instance.
constraint :: Solvable c m => c -> Constraint m
constraint c = Constraint c constraint_solver constraint_checker
constraints :: (Solvable c m, Functor f) => f c -> f (Constraint m)
constraints = (constraint <$>)
constraint_map :: (forall a. m1 a -> m2 a) -> Constraint m1 -> Constraint m2
constraint_map t (Constraint c f g) = Constraint c (t . f) (t . g)
-- ** Class 'Solvable'
-- | A type class for 'Constraint's solvable within a 'Monad'.
class (Show c, Buildable c, Substitutable c, Monad m) => Solvable c m where
constraint_solver :: c -> m ()
constraint_checker :: c -> m Bool
constraint_checker _ = return True
-- * Type 'State_Constraint'
-- | A 'State_Constraint' is parameterized over the 'Monad'
-- in which the 'Constraint's can be solved.
data State_Constraint m
= State_Constraint
{ state_constraint_constraints :: Seq (Constraint m)
-- ^ A stack of 'Constraint's to solve.
, state_constraint_checks :: [(m Bool, String)]
-- ^ Conditions to check (for the solved 'Constraint's).
, state_constraint_errors :: [(Info m, Error m)]
-- ^ The detected errors.
, state_constraint_option_stop :: Option Bool
-- ^ Whether to discard all remaining 'Constraint's after the first error.
, state_constraint_option_check :: Option Bool
-- ^ Whether to check constraint satisfaction afterwards.
}
-- | Make 'State_Constraint' collectable as a 'State' instance
-- out of a 'Monad' stack.
type instance MC.Class State (State_Constraint m) = 'True
instance Show (State_Constraint m) where
show = Build.string
instance Buildable (State_Constraint m) where
build State_Constraint
{ state_constraint_constraints
, state_constraint_errors
, state_constraint_checks } =
Build.unlines $
[ "constraints: " <> Build.tuple
[ show (List.length state_constraint_constraints) <> " constraints"
, show (List.length state_constraint_errors) <> " errors"
, show (List.length state_constraint_checks) <> " checks"
]
] <> ((" " <>) . build <$>
Foldable.toList state_constraint_constraints)
instance State (State_Constraint m) where
state_name _ = "State_Constraint"
state_show = Build.text
state_options s =
[ Text.pack $ show $ state_constraint_option_stop s
, Text.pack $ show $ state_constraint_option_check s
]
instance Empty (State_Constraint m) where
empty = State_Constraint
{ state_constraint_constraints = mempty
, state_constraint_errors = mempty
, state_constraint_checks = mempty
, state_constraint_option_stop = option False "Stop solving at first error"
, state_constraint_option_check = option False "Check the solution"
}
-- * Class 'Solver_Constraint'
class
( MC.MonadStateFix State_Constraint m
, Information m
, Solver_Logable Log_Constraint m
) => Solver_Constraint m where
constraint_push :: Constraint m -> m ()
constraint_push c = do
log $ Log_Constraint_Push c
MC.modifyFix $ \(s::State_Constraint m) ->
s{ state_constraint_constraints =
c <| state_constraint_constraints s }
constraint_push_many :: Seq (Constraint m) -> m ()
constraint_push_many cs = do
forM_ cs $ log . Log_Constraint_Push
MC.modifyFix $ \(s::State_Constraint m) ->
s{ state_constraint_constraints =
cs <> state_constraint_constraints s }
constraint_pop :: m (Maybe (Constraint m))
constraint_pop = do
cs <- MC.getsFix $ \(s::State_Constraint m) ->
state_constraint_constraints s
case Seq.viewl cs of
Seq.EmptyL -> return Nothing
(c Seq.:< state_constraint_constraints) -> do
log $ Log_Constraint_Pop c
MC.modifyFix (\(s::State_Constraint m) ->
s{ state_constraint_constraints })
return (Just c)
constraint_discard :: m ()
constraint_discard =
MC.modifyFix $ \(s::State_Constraint m) ->
s{ state_constraint_constraints = mempty }
constraint_error_insert :: Error m -> Info m -> m ()
constraint_error_insert err info = do
MC.modifyFix (\(s::State_Constraint m) ->
s{ state_constraint_errors =
(info, err) : state_constraint_errors s })
stop <- constraint_option_stop
when (option_current stop)
constraint_discard
constraint_errors :: m [(Info m, Error m)]
constraint_errors =
MC.getsFix $ \(s::State_Constraint m) ->
state_constraint_errors s
constraint_error_info_update
:: (Info m -> m (Info m)) -> m ()
constraint_error_info_update f = do
errs <- constraint_errors
state_constraint_errors <- do
let g (info, err) = do
info' <- f info
return (info', err)
g `mapM` errs
MC.modifyFix $ \(s::State_Constraint m) ->
s{ state_constraint_errors }
-- | Add sanity checks for the solved 'Constraint's,
-- performed when all 'Constraint's have been solved.
--
-- The first argument of 'constraint_check_insert' is
-- a message, which is reported
-- when the second argument evaluates
-- to 'False' (in the 'Monad' @m@).
constraint_check_insert :: String -> m Bool -> m ()
constraint_check_insert text check =
MC.modifyFix $ \(s::State_Constraint m) ->
s{ state_constraint_checks =
(check, text) : state_constraint_checks s}
constraint_check_get :: m [(m Bool, String)]
constraint_check_get =
MC.getsFix $ \(s::State_Constraint m) ->
state_constraint_checks s
constraint_option_stop :: m (Option Bool)
constraint_option_stop =
MC.getsFix $ \(s::State_Constraint m) ->
state_constraint_option_stop s
constraint_option_check :: m (Option Bool)
constraint_option_check =
MC.getsFix (\(s::State_Constraint m) ->
state_constraint_option_check s)
constraint_solve :: m ()
constraint_solve = do
mc <- constraint_pop
case mc of
Just c -> do
constraint_solver c
constraint_check_insert (show c) (constraint_checker c)
constraint_solve
Nothing -> do
check <- option_current <$> constraint_option_check
errs <- fmap fst <$> constraint_errors
when (check && List.null errs)
constraint_check
constraint_check :: m ()
constraint_check = do
ms <- constraint_check_get
bs <- filterM ((not <$>) . fst) ms
unless (List.null bs) $
let err =
"\n\n Constraints violated:\n"
<> List.unlines (fmap ((" - " <>) . snd) bs) in
error $ "constraint_check: " <> err
-- ** Type 'Log_Constraint'
data Log_Constraint
= forall c. (Show c, Buildable c) => Log_Constraint_Pop c
| forall c. (Show c, Buildable c) => Log_Constraint_Push c
deriving instance Show Log_Constraint
instance Buildable Log_Constraint where
build x =
case x of
Log_Constraint_Pop c -> "constraint_pop : " <> build c
Log_Constraint_Push c -> "constraint_push: " <> build c