packages feed

boltzmann-brain-1.6: Data/Boltzmann/System/Warnings.hs

{-|
 Module      : Data.Boltzmann.System.Warnings
 Description : Various warning handling utilities.
 Copyright   : (c) Maciej Bendkowski, 2017-2019

 License     : BSD3
 Maintainer  : maciej.bendkowski@tcs.uj.edu.pl
 Stability   : experimental

 Warning utilities meant to deal with, skippable, well-foundness checks
 or other redundant sanity checks of the considered combinatorial system.
 -}
{-# LANGUAGE ExistentialQuantification #-}
module Data.Boltzmann.System.Warnings
    ( warnings
    ) where

import Control.Monad (unless)

import Data.Maybe (mapMaybe)
import Data.List (isPrefixOf)

import qualified Data.Set as S
import qualified Data.Map.Strict as M

import Data.Boltzmann.System
import Data.Boltzmann.System.Utils

import Data.Boltzmann.Internal.Utils
import qualified Data.Boltzmann.Internal.Logging as L

-- | Semantic system warnings.
class SystemWarn a where

    -- | System warning message.
    report :: a -> String

-- | Existential warning type.
data WarningExt = forall a. (SystemWarn a) => WarningExt a

-- | Constructors without positive weight.
data ConsWeightWarn =
    ConsWeightWarn { consWeightType :: String -- ^ Type name.
                   , consWeightCons :: String -- ^ Constructor name.
                   }

consWeightWarn :: (Eq a, Num a) => System a -> [WarningExt]
consWeightWarn sys =
        concatMap nullType (M.toList $ defs sys)
    where
        nullCons typ cons
            | null (args cons) && weight cons == 0 =
                Just $ WarningExt ConsWeightWarn { consWeightType = typ
                                                 , consWeightCons = func cons
                                                 }
            | otherwise = Nothing
        nullType (typ, cons) = mapMaybe (nullCons typ) cons

instance SystemWarn ConsWeightWarn where
    report warn = "Found a constructor " ++ cons'
            ++ " in type " ++ typ' ++ " of weight 0."
        where
            cons' = quote $ consWeightCons warn
            typ'  = quote $ consWeightType warn

-- | Skipping the well-foundness check.
data SkipWellFoundness = SkipWellFoundness

skipWellFoundness :: System a -> [WarningExt]
skipWellFoundness sys =
    [WarningExt SkipWellFoundness | not (isAlgebraic $ systemType sys)]

instance SystemWarn SkipWellFoundness where
    report _ = "Given system specifies a rational language. Skipping well-foundness check."

-- | Alphabet with prefix symbols.
newtype PrefixAlphabetWarn =
    PrefixAlphabetWarn { conflicts :: [(String, String)] -- ^ Conflicting symbols.
                       }

-- | Raises a warning if the given system alphabet is not prefix-free.
prefixAlphabetWarn :: System a -> [WarningExt]
prefixAlphabetWarn sys =
    [WarningExt PrefixAlphabetWarn { conflicts = conflicting }
        | not (null conflicting)]
    where alph = S.toList $ alphabet sys
          symbolPairs = [(symb a, symb b) | a <- alph, b <- alph, a < b]
          conflicting = filter (uncurry isPrefixOf) symbolPairs

instance SystemWarn PrefixAlphabetWarn where
    report warn = "The system alphabet is not prefix-free. Conflicts: "
        ++ csv (map (\(a,b) -> "(" ++ quote a ++ " and " ++ quote b ++ ")") $ conflicts warn)
        ++ "."

-- | Polynomial species (i.e. finite specification languages).
data PolynomialSpecies = PolynomialSpecies

polynomialWarn :: System Int -> [WarningExt]
polynomialWarn sys =
    [WarningExt PolynomialSpecies | polynomial sys]

instance SystemWarn PolynomialSpecies where
    report _ = "Given system specifies a finite language."

-- | Reports the given warning.
reportWarning :: WarningExt -> IO ()
reportWarning (WarningExt warn) = L.warn (report warn)

checkWarns :: Bool -> [WarningExt] -> IO ()
checkWarns werror warns = do
    mapM_ reportWarning warns
    unless (null warns || not werror)
        (L.fail' "Warnings reported while the --werror flag is enabled. Exiting.")

-- | List of checked warnings.
warningList :: System Int -> [WarningExt]
warningList sys = skipWellFoundness sys
               ++ consWeightWarn sys
               ++ polynomialWarn sys
               ++ prefixAlphabetWarn sys

-- | Checks whether the given input system admits no warnings.
warnings :: Bool -> Bool -> System Int -> IO ()
warnings werror force sys =
    unless force (checkWarns werror $ warningList sys)