packages feed

regexchar-0.9.0.14: src-test/Grecce/Test/QuickCheck/ExtendedRegExChar.hs

{-# OPTIONS_GHC -fno-warn-orphans #-}
{-
	Copyright (C) 2010-2015 Dr. Alistair Ward

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
-}
{- |
 [@AUTHOR@]	Dr. Alistair Ward

 [@DESCRIPTION@]	Implements 'Test.QuickCheck.Arbitrary' & defines tests based on it.
-}

module Grecce.Test.QuickCheck.ExtendedRegExChar(
-- * Constants
	results,
-- * Types
-- ** Data-types
--	AlternativesChar(..),
--	PatternChar(..),
--	RepeatablePatternChar(..)
) where

import qualified	Data.Maybe
import			Grecce.Test.QuickCheck.MetaChar()
import qualified	RegExChar.ExtendedRegExChar	as ExtendedRegExChar	-- CAVEAT: beware of the similar name.
import qualified	RegExChar.MetaChar		as MetaChar
import qualified	RegExDot.Anchor
import qualified	RegExDot.BracketExpressionMember
import qualified	RegExDot.Consumer
import qualified	RegExDot.ConsumptionBounds
import qualified	RegExDot.ConsumptionProfile
import			RegExDot.DSL((-:), (?:), (+:), (<~>))
import qualified	RegExDot.Meta
import qualified	RegExDot.RegEx
import			RegExDot.RegEx((+~))
import qualified	RegExDot.RegExOpts
import qualified	RegExDot.Repeatable
import qualified	RegExDot.Result
import qualified	Test.QuickCheck
import			Test.QuickCheck((==>))
import qualified	ToolShed.SelfValidate
import qualified	ToolShed.Test.ReversibleIO

-- | A specialised instance, required to instantiate 'Test.QuickCheck.Arbitrary'.
newtype AlternativesChar	= MkAlternativesChar {
	deconstructAlternativesChar	:: RegExDot.RegEx.Alternatives Char
} deriving (Eq, Read, Show)

instance ToolShed.SelfValidate.SelfValidator AlternativesChar where
	getErrors	= ToolShed.SelfValidate.getErrors . deconstructAlternativesChar

instance RegExDot.Consumer.Consumer AlternativesChar where
	consumptionProfile	= RegExDot.Consumer.consumptionProfile . deconstructAlternativesChar
	starHeight		= RegExDot.Consumer.starHeight . deconstructAlternativesChar

instance Test.QuickCheck.Arbitrary AlternativesChar	where
	arbitrary	= (MkAlternativesChar . RegExDot.RegEx.MkAlternatives . map ExtendedRegExChar.extendedRegEx) `fmap` (
		Test.QuickCheck.elements [1, 2]	>>= Test.QuickCheck.vector {-[ExtendedRegExChar]-}
	 ) -- TODO: permit zero alternatives.

-- | A specialised instance, required to instantiate 'Test.QuickCheck.Arbitrary'.
newtype PatternChar	= MkPatternChar {
	deconstructPatternChar	:: RegExDot.RegEx.Pattern Char
} deriving (Eq, Read, Show)

instance ToolShed.SelfValidate.SelfValidator PatternChar	where
	getErrors	= ToolShed.SelfValidate.getErrors . deconstructPatternChar

instance RegExDot.Consumer.Consumer PatternChar	where
	consumptionProfile	= RegExDot.Consumer.consumptionProfile . deconstructPatternChar
	starHeight		= RegExDot.Consumer.starHeight . deconstructPatternChar

instance Test.QuickCheck.Arbitrary PatternChar	where
	arbitrary	= MkPatternChar `fmap` Test.QuickCheck.frequency [
		(4,	(RegExDot.RegEx.Require . MetaChar.deconstruct) `fmap` Test.QuickCheck.arbitrary {-MetaChar-}),
		(1,	(RegExDot.RegEx.CaptureGroup . deconstructAlternativesChar) `fmap` Test.QuickCheck.arbitrary {-AlternativesChar-})
	 ] -- CAVEAT: may recurse forever if 'RegExDot.RegEx.CaptureGroup / RegExDot.RegEx.Require' is too high.

-- | A specialised instance, required to instantiate 'Test.QuickCheck.Arbitrary'.
newtype RepeatablePatternChar	= MkRepeatablePatternChar {
	deconstructRepeatablePatternChar	:: RegExDot.Repeatable.Repeatable PatternChar
} deriving (Eq, Read, Show)

instance ToolShed.SelfValidate.SelfValidator RepeatablePatternChar	where
	getErrors	= ToolShed.SelfValidate.getErrors . deconstructRepeatablePatternChar

instance RegExDot.Consumer.Consumer RepeatablePatternChar	where
	consumptionProfile	= RegExDot.Consumer.consumptionProfile . deconstructRepeatablePatternChar
	starHeight		= RegExDot.Consumer.starHeight . deconstructRepeatablePatternChar

instance Test.QuickCheck.Arbitrary RepeatablePatternChar	where
	arbitrary	= do
		patternChar	<- Test.QuickCheck.arbitrary
		fewest		<- Test.QuickCheck.elements [0 .. 9]	-- Could be more, but this is an adequate test.
		most		<- Test.QuickCheck.oneof [return {-to Gen-monad-} Nothing, Just `fmap` Test.QuickCheck.elements [max fewest 1 .. 9]]
		isGreedy	<- Test.QuickCheck.arbitrary {-Bool-}

		let
			repetitionBounds :: RegExDot.Repeatable.RepetitionBounds
			repetitionBounds	= (fewest, most)

		return {-to Gen-monad-} $ MkRepeatablePatternChar RegExDot.Repeatable.MkRepeatable {
			RegExDot.Repeatable.base		= patternChar,
			RegExDot.Repeatable.repetitionBounds	= repetitionBounds,
			RegExDot.Repeatable.isGreedy		= RegExDot.Repeatable.hasPreciseBounds repetitionBounds || isGreedy	-- Only specify non-greedy where space exists.
		}

instance Test.QuickCheck.Arbitrary ExtendedRegExChar.ExtendedRegExChar	where
	arbitrary	= do
		hasBowAnchor	<- Test.QuickCheck.arbitrary {-Bool-}
		concatenation	<- map (fmap deconstructPatternChar {-replace base-} . deconstructRepeatablePatternChar {-RegExDot.Repeatable.Repeatable PatternChar-}) `fmap` Test.QuickCheck.vector 2 {-[RepeatablePatternChar]-}
		hasSternAnchor	<- Test.QuickCheck.arbitrary {-Bool-}

		return {-to Gen-monad-} $ ExtendedRegExChar.MkExtendedRegExChar False RegExDot.RegEx.MkExtendedRegEx {
			RegExDot.RegEx.bowAnchor	= if hasBowAnchor then Just RegExDot.Anchor.Bow else Nothing,
			RegExDot.RegEx.concatenation	= concatenation,
			RegExDot.RegEx.sternAnchor	= if hasSternAnchor then Just RegExDot.Anchor.Stern else Nothing
		}

-- | The constant test-results for this data-type.
results :: IO [Test.QuickCheck.Result]
results	= sequence [
	Test.QuickCheck.quickCheckResult prop_consumptionProfile,
	Test.QuickCheck.quickCheckResult prop_consumptionProfile2,
	Test.QuickCheck.quickCheckResult prop_io,
	Test.QuickCheck.quickCheckResult prop_io',
	Test.QuickCheck.quickCheckResult prop_isValid,
	Test.QuickCheck.quickCheckResult prop_starHeight,
	Test.QuickCheck.quickCheckResult prop_double,
	Test.QuickCheck.quickCheckResult prop_read
 ] where
	prop_consumptionProfile, prop_consumptionProfile2, prop_io, prop_io', prop_isValid, prop_starHeight :: ExtendedRegExChar.ExtendedRegExChar -> Test.QuickCheck.Property
	prop_consumptionProfile r	= Test.QuickCheck.label "prop_consumptionProfile" $ Data.Maybe.maybe True (>= minData) maybeMaxData && (
		not b == all (not . RegExDot.Consumer.getHasSpecificRequirement) (RegExDot.RegEx.concatenation $ ExtendedRegExChar.extendedRegEx r)
	 ) where
		RegExDot.ConsumptionProfile.MkConsumptionProfile {
			RegExDot.ConsumptionProfile.consumptionBounds		= (minData, maybeMaxData),
			RegExDot.ConsumptionProfile.hasSpecificRequirement	= b
		} = RegExDot.Consumer.consumptionProfile r

	prop_consumptionProfile2 r	= RegExDot.RegEx.isDefined (ExtendedRegExChar.extendedRegEx r)	==> Test.QuickCheck.label "prop_consumptionProfile2" $ hasSpecificRequirement || canConsumeAnything	where	-- There's either; a requirement for at least one specific, or we can consume at least one arbitrary; input datum.
		RegExDot.ConsumptionProfile.MkConsumptionProfile {
			RegExDot.ConsumptionProfile.hasSpecificRequirement	= hasSpecificRequirement,
			RegExDot.ConsumptionProfile.canConsumeAnything		= canConsumeAnything
		} = RegExDot.Consumer.consumptionProfile r

	prop_io			= Test.QuickCheck.label "prop_io" . ToolShed.Test.ReversibleIO.isReversible
	prop_io' r		= Test.QuickCheck.label "prop_io'" $ ToolShed.Test.ReversibleIO.isReversible r'	where r' = ExtendedRegExChar.extendedRegEx r	-- Check "RegExDot.RegEx.ExtendedRegEx Char" too.
	prop_isValid r		= Test.QuickCheck.label "prop_isValid" $ ToolShed.SelfValidate.isValid r
	prop_starHeight r	= Test.QuickCheck.label "prop_starHeight" $ (RegExDot.Consumer.starHeight r == 0) == RegExDot.ConsumptionBounds.isPrecise (RegExDot.Consumer.getConsumptionBounds r)

	prop_double :: Double -> Test.QuickCheck.Property
	prop_double d	= Test.QuickCheck.label "prop_double" $ RegExDot.RegEx.extractDataFromMatchList `fmap` RegExDot.Result.getMatchList result == Just s	where
		s :: String
		s	= show $ d * 1e6	-- Implementation of show, uses exponential notation, when the number is large.

		result :: RegExDot.RegEx.Result Char
		result = s +~ RegExDot.RegExOpts.mkRegEx (
			(Just RegExDot.Anchor.Bow, Just RegExDot.Anchor.Stern) <~> sign ?: digits +: map (
				RegExDot.Repeatable.zeroOrOne . RegExDot.RegEx.captureGroup . map (RegExDot.Anchor.unanchored <~>) . return {-to List-monad-}
			) [
				RegExDot.RegEx.Require (RegExDot.Meta.Literal '.') -: digits +: [],
				RegExDot.RegEx.Require (RegExDot.Meta.AnyOf $ map RegExDot.BracketExpressionMember.Literal "eE") -: sign ?: digits +: []
			]
		 ) where
			sign, digits :: RegExDot.RegEx.Pattern Char
			sign	= RegExDot.RegEx.Require . RegExDot.Meta.AnyOf $ map RegExDot.BracketExpressionMember.Literal "+-"
			digits	= RegExDot.RegEx.Require . RegExDot.Meta.AnyOf $ map RegExDot.BracketExpressionMember.Literal ['0' .. '9']

	prop_read :: String -> Test.QuickCheck.Property
	prop_read garbage	= Test.QuickCheck.label "prop_read" $ case (reads garbage :: [(ExtendedRegExChar.ExtendedRegExChar, String)]) of
		[_]	-> True
		_	-> True	-- Unless the read-implementation throws an exception.