{-# LANGUAGE OverloadedStrings, CPP #-}

-- | This module provides functionality for check a 'LaTeX' value for
--   possibly undesired things (like the call to an undefined label),
--   returning 'Warning's. These are called 'Warning's because they
--   never terminate the program execution.
module Text.LaTeX.Base.Warnings (
   -- * Warnings datatype
   Warning (..)
 , TeXCheck
 , check
 , checkFromFunction
   -- * Several checkings
 , checkLabels
 , checkClass
 , checkDoc
   -- * Complete checking
 , checkAll
 ) where

import Text.LaTeX.Base.Syntax
import Control.Monad.Trans.State
import Data.Text
import Data.Maybe
import Control.Arrow
#if !MIN_VERSION_base(4,8,0)
import Data.Monoid
#endif
import qualified Data.Semigroup as SG

-- | List of possible warnings.
data Warning =
   UnusedLabel Text    -- ^ There is an unused label. Argument is its name.
 | UndefinedLabel Text -- ^ There is a reference to an undefined label. Arguments is the name.
   --
 | NoClassSelected     -- ^ No class selected with 'documentclass'.
 | NoDocumentInserted  -- ^ No 'document' inserted.
   --
 | CustomWarning Text  -- ^ Custom warning for custom checkings. Use it as you want.
   deriving (Warning -> Warning -> Bool
(Warning -> Warning -> Bool)
-> (Warning -> Warning -> Bool) -> Eq Warning
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Warning -> Warning -> Bool
$c/= :: Warning -> Warning -> Bool
== :: Warning -> Warning -> Bool
$c== :: Warning -> Warning -> Bool
Eq,Int -> Warning -> ShowS
[Warning] -> ShowS
Warning -> String
(Int -> Warning -> ShowS)
-> (Warning -> String) -> ([Warning] -> ShowS) -> Show Warning
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Warning] -> ShowS
$cshowList :: [Warning] -> ShowS
show :: Warning -> String
$cshow :: Warning -> String
showsPrec :: Int -> Warning -> ShowS
$cshowsPrec :: Int -> Warning -> ShowS
Show)

-- | A 'TeXCheck' is a function that checks possible warnings from a 'LaTeX' value.
--   Use the 'Monoid' instance to combine check functions.
newtype TeXCheck = TC { TeXCheck -> LaTeX -> [Warning]
check :: LaTeX -> [Warning] -- ^ Apply a checking.
                      }
-- | Build a 'TeXCheck' from a function.
checkFromFunction :: (LaTeX -> [Warning]) -> TeXCheck
checkFromFunction :: (LaTeX -> [Warning]) -> TeXCheck
checkFromFunction = (LaTeX -> [Warning]) -> TeXCheck
TC

instance SG.Semigroup TeXCheck where
 <> :: TeXCheck -> TeXCheck -> TeXCheck
(<>) = TeXCheck -> TeXCheck -> TeXCheck
forall a. Monoid a => a -> a -> a
mappend
instance Monoid TeXCheck where
 mempty :: TeXCheck
mempty = (LaTeX -> [Warning]) -> TeXCheck
TC ((LaTeX -> [Warning]) -> TeXCheck)
-> (LaTeX -> [Warning]) -> TeXCheck
forall a b. (a -> b) -> a -> b
$ [Warning] -> LaTeX -> [Warning]
forall a b. a -> b -> a
const []
 mappend :: TeXCheck -> TeXCheck -> TeXCheck
mappend (TC LaTeX -> [Warning]
tc1) (TC LaTeX -> [Warning]
tc2) = (LaTeX -> [Warning]) -> TeXCheck
TC ((LaTeX -> [Warning]) -> TeXCheck)
-> (LaTeX -> [Warning]) -> TeXCheck
forall a b. (a -> b) -> a -> b
$ ([Warning] -> [Warning] -> [Warning])
-> ([Warning], [Warning]) -> [Warning]
forall a b c. (a -> b -> c) -> (a, b) -> c
uncurry [Warning] -> [Warning] -> [Warning]
forall a. Monoid a => a -> a -> a
mappend (([Warning], [Warning]) -> [Warning])
-> (LaTeX -> ([Warning], [Warning])) -> LaTeX -> [Warning]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (LaTeX -> [Warning]
tc1 (LaTeX -> [Warning])
-> (LaTeX -> [Warning]) -> LaTeX -> ([Warning], [Warning])
forall (a :: * -> * -> *) b c c'.
Arrow a =>
a b c -> a b c' -> a b (c, c')
&&& LaTeX -> [Warning]
tc2)

-- | Check with 'checkLabels', 'checkClass' and 'checkDoc'.
checkAll :: TeXCheck
checkAll :: TeXCheck
checkAll = [TeXCheck] -> TeXCheck
forall a. Monoid a => [a] -> a
mconcat [ TeXCheck
checkLabels , TeXCheck
checkClass , TeXCheck
checkDoc ]

-- Searching for 'documentclass' and 'document'

type BoolSt = State Bool

-- | Check if a document class is specified for the document (using 'documentclass').
checkClass :: TeXCheck
checkClass :: TeXCheck
checkClass = (LaTeX -> [Warning]) -> TeXCheck
TC ((LaTeX -> [Warning]) -> TeXCheck)
-> (LaTeX -> [Warning]) -> TeXCheck
forall a b. (a -> b) -> a -> b
$ \LaTeX
l -> if State Bool () -> Bool -> Bool
forall s a. State s a -> s -> s
execState (LaTeX -> State Bool ()
classcheck LaTeX
l) Bool
False then [] else [Warning
NoClassSelected]

classcheck :: LaTeX -> BoolSt ()
classcheck :: LaTeX -> State Bool ()
classcheck (TeXComm String
c [TeXArg]
_) =
 case String
c of
  String
"documentclass" -> Bool -> State Bool ()
forall (m :: * -> *) s. Monad m => s -> StateT s m ()
put Bool
True
  String
_ -> () -> State Bool ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
classcheck (TeXBraces LaTeX
l) = LaTeX -> State Bool ()
classcheck LaTeX
l
classcheck (TeXSeq LaTeX
l1 LaTeX
l2) = LaTeX -> State Bool ()
classcheck LaTeX
l1 State Bool () -> State Bool () -> State Bool ()
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> LaTeX -> State Bool ()
classcheck LaTeX
l2
classcheck LaTeX
_ = () -> State Bool ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

-- | Check if the 'document' environment is called in a 'LaTeX'.
checkDoc :: TeXCheck
checkDoc :: TeXCheck
checkDoc = (LaTeX -> [Warning]) -> TeXCheck
TC ((LaTeX -> [Warning]) -> TeXCheck)
-> (LaTeX -> [Warning]) -> TeXCheck
forall a b. (a -> b) -> a -> b
$ \LaTeX
l -> if State Bool () -> Bool -> Bool
forall s a. State s a -> s -> s
execState (LaTeX -> State Bool ()
doccheck LaTeX
l) Bool
False then [] else [Warning
NoDocumentInserted]

doccheck :: LaTeX -> BoolSt ()
doccheck :: LaTeX -> State Bool ()
doccheck (TeXEnv String
n [TeXArg]
_ LaTeX
_) =
 case String
n of
  String
"document" -> Bool -> State Bool ()
forall (m :: * -> *) s. Monad m => s -> StateT s m ()
put Bool
True
  String
_ -> () -> State Bool ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
doccheck (TeXBraces LaTeX
l) = LaTeX -> State Bool ()
doccheck LaTeX
l
doccheck (TeXSeq LaTeX
l1 LaTeX
l2) = LaTeX -> State Bool ()
doccheck LaTeX
l1 State Bool () -> State Bool () -> State Bool ()
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> LaTeX -> State Bool ()
doccheck LaTeX
l2
doccheck LaTeX
_ = () -> State Bool ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

-- Checking labels

data LabWarn =
   RefNoLabel Text
 | LabelNoRef Text
 | LabelRef Text

labWarnToWarning :: LabWarn -> Maybe Warning
labWarnToWarning :: LabWarn -> Maybe Warning
labWarnToWarning (RefNoLabel Text
n) = Warning -> Maybe Warning
forall a. a -> Maybe a
Just (Warning -> Maybe Warning) -> Warning -> Maybe Warning
forall a b. (a -> b) -> a -> b
$ Text -> Warning
UndefinedLabel Text
n
labWarnToWarning (LabelNoRef Text
n) = Warning -> Maybe Warning
forall a. a -> Maybe a
Just (Warning -> Maybe Warning) -> Warning -> Maybe Warning
forall a b. (a -> b) -> a -> b
$ Text -> Warning
UnusedLabel Text
n
labWarnToWarning LabWarn
_ = Maybe Warning
forall a. Maybe a
Nothing

type LabSt = State [LabWarn]

-- | Checking for unused labels or references tu undefined labels.
checkLabels :: TeXCheck
checkLabels :: TeXCheck
checkLabels = (LaTeX -> [Warning]) -> TeXCheck
TC ((LaTeX -> [Warning]) -> TeXCheck)
-> (LaTeX -> [Warning]) -> TeXCheck
forall a b. (a -> b) -> a -> b
$ \LaTeX
l -> [Maybe Warning] -> [Warning]
forall a. [Maybe a] -> [a]
catMaybes ([Maybe Warning] -> [Warning])
-> ([LabWarn] -> [Maybe Warning]) -> [LabWarn] -> [Warning]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (LabWarn -> Maybe Warning) -> [LabWarn] -> [Maybe Warning]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap LabWarn -> Maybe Warning
labWarnToWarning ([LabWarn] -> [Warning]) -> [LabWarn] -> [Warning]
forall a b. (a -> b) -> a -> b
$ State [LabWarn] () -> [LabWarn] -> [LabWarn]
forall s a. State s a -> s -> s
execState (LaTeX -> State [LabWarn] ()
labcheck LaTeX
l) []

labcheck :: LaTeX -> LabSt ()
labcheck :: LaTeX -> State [LabWarn] ()
labcheck (TeXComm String
c [FixArg (TeXRaw Text
n)]) =
 case String
c of
  String
"label"   -> Text -> State [LabWarn] ()
newlab Text
n
  String
"ref"     -> Text -> State [LabWarn] ()
newref Text
n
  String
"pageref" -> Text -> State [LabWarn] ()
newref Text
n
  String
_ -> () -> State [LabWarn] ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
labcheck (TeXEnv String
_ [TeXArg]
_ LaTeX
l) = LaTeX -> State [LabWarn] ()
labcheck LaTeX
l
labcheck (TeXMath MathType
_ LaTeX
l) = LaTeX -> State [LabWarn] ()
labcheck LaTeX
l
labcheck (TeXBraces LaTeX
l) = LaTeX -> State [LabWarn] ()
labcheck LaTeX
l
labcheck (TeXSeq LaTeX
l1 LaTeX
l2) = LaTeX -> State [LabWarn] ()
labcheck LaTeX
l1 State [LabWarn] () -> State [LabWarn] () -> State [LabWarn] ()
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> LaTeX -> State [LabWarn] ()
labcheck LaTeX
l2
labcheck LaTeX
_ = () -> State [LabWarn] ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

newlab :: Text -> LabSt ()
newlab :: Text -> State [LabWarn] ()
newlab Text
t = do
 [LabWarn]
st <- StateT [LabWarn] Identity [LabWarn]
forall (m :: * -> *) s. Monad m => StateT s m s
get
 let addLab :: Text -> [LabWarn] -> [LabWarn]
     addLab :: Text -> [LabWarn] -> [LabWarn]
addLab Text
n [] = [Text -> LabWarn
LabelNoRef Text
n]
     addLab Text
n l :: [LabWarn]
l@(LabWarn
x:[LabWarn]
xs) = let ys :: [LabWarn]
ys = LabWarn
x LabWarn -> [LabWarn] -> [LabWarn]
forall a. a -> [a] -> [a]
: Text -> [LabWarn] -> [LabWarn]
addLab Text
n [LabWarn]
xs in
       case LabWarn
x of
        RefNoLabel Text
m -> if Text
n Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
m then Text -> LabWarn
LabelRef Text
n LabWarn -> [LabWarn] -> [LabWarn]
forall a. a -> [a] -> [a]
: [LabWarn]
xs
                                  else [LabWarn]
ys
        LabelNoRef Text
m -> if Text
n Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
m then [LabWarn]
l
                                  else [LabWarn]
ys
        LabelRef   Text
m -> if Text
n Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
m then [LabWarn]
l
                                  else [LabWarn]
ys
 [LabWarn] -> State [LabWarn] ()
forall (m :: * -> *) s. Monad m => s -> StateT s m ()
put ([LabWarn] -> State [LabWarn] ())
-> [LabWarn] -> State [LabWarn] ()
forall a b. (a -> b) -> a -> b
$ Text -> [LabWarn] -> [LabWarn]
addLab Text
t [LabWarn]
st

newref :: Text -> LabSt ()
newref :: Text -> State [LabWarn] ()
newref Text
t = do
 [LabWarn]
st <- StateT [LabWarn] Identity [LabWarn]
forall (m :: * -> *) s. Monad m => StateT s m s
get
 let addRef :: Text -> [LabWarn] -> [LabWarn]
     addRef :: Text -> [LabWarn] -> [LabWarn]
addRef Text
n [] = [Text -> LabWarn
RefNoLabel Text
n]
     addRef Text
n l :: [LabWarn]
l@(LabWarn
x:[LabWarn]
xs) = let ys :: [LabWarn]
ys = LabWarn
x LabWarn -> [LabWarn] -> [LabWarn]
forall a. a -> [a] -> [a]
: Text -> [LabWarn] -> [LabWarn]
addRef Text
n [LabWarn]
xs in
       case LabWarn
x of
        RefNoLabel Text
m -> if Text
n Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
m then [LabWarn]
l
                                  else [LabWarn]
ys
        LabelNoRef Text
m -> if Text
n Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
m then Text -> LabWarn
LabelRef Text
n LabWarn -> [LabWarn] -> [LabWarn]
forall a. a -> [a] -> [a]
: [LabWarn]
xs
                                  else [LabWarn]
ys
        LabelRef   Text
m -> if Text
n Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
m then [LabWarn]
l
                                  else [LabWarn]
ys
 [LabWarn] -> State [LabWarn] ()
forall (m :: * -> *) s. Monad m => s -> StateT s m ()
put ([LabWarn] -> State [LabWarn] ())
-> [LabWarn] -> State [LabWarn] ()
forall a b. (a -> b) -> a -> b
$ Text -> [LabWarn] -> [LabWarn]
addRef Text
t [LabWarn]
st