packages feed

regexdot-0.10.2.2: src/RegExDot/Repeatable.hs

{-# LANGUAGE CPP #-}
{-
	Copyright (C) 2010 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@]

	* A data-type, which represents the permissible range of instances, of the underlying polymorphic datum.

	* Designed for use in a polymorphic /regex/-engine, which specifies patterns composed of repeated /greedy/ & /non-greedy/ sequences of /Meta/-data;

 >		*	+	?	{fewest, most}	{fewest,}	{fewest}
 >		*?	+?	??	{fewest, most}?	{fewest,}?

	* In the context of /regex/es, this concept is known as /Quantification/.

	* /regex/es evolved from the minimal ability to optionally qualify the datum with a <http://en.wikipedia.org/wiki/Kleene_star> suffix.
	More exotic repetition-specifications could be composed by concatenating these atomic building-blocks.
	Here, I've taken the contrary top-down view, & assumed that all data are qualified by a full 'RepetitionBounds', which in most cases will degenerate into a simpler form.

	* The type of entity which is being repeated, isn't the domain of this data-type; it's polymorphic.
-}

module RegExDot.Repeatable(
-- * Types
-- ** Type-synonyms
	Repetitions,
	RepetitionBounds,
-- ** Data-types
	Repeatable(..),
-- * Constants
--	nonGreedyToken,
	oneOrMoreToken,
	rangeDelimiters,
	rangeSeparatorToken,
	tokens,
	zeroOrMoreToken,
	zeroOrOneToken,
-- * Functions
	one,
	oneOrMore,
	oneOrMore',
	zeroOrMore,
	zeroOrMore',
	zeroOrOne,
	zeroOrOne',
--	precisely,
	repeatableParser,
	showSuffix,
-- ** Accessors
	getFewest,
	getMost,
-- ** Mutators
	focus,
--	setNonGreedy,
	toSingleton,
-- ** Operators
	(^#->#),
	(^#->#?),
	(^#->),
	(^#->?),
	(^#),
-- ** Predicates
	isPrecise,
	hasPreciseBounds
) where

import			Control.Applicative((<$>), (<*>))
import			Control.Arrow((***))
import			Text.ParserCombinators.Parsec((<?>))
import qualified	Control.Monad
import qualified	Data.List
import qualified	RegExDot.Consumer		as Consumer
import qualified	RegExDot.ConsumptionProfile	as ConsumptionProfile
import qualified	Text.ParserCombinators.Parsec	as Parsec
import qualified	ToolShed.Pair			as Pair
import qualified	ToolShed.SelfValidate		as SelfValidate

#ifdef HAVE_DEEPSEQ
import			Control.DeepSeq(NFData, rnf)
#else
import			Control.Parallel.Strategies(NFData, rnf)
#endif

infix 6 ^#->#, ^#->#?, ^#->, ^#->?, ^#	--A notch tighter than "DSL"s binary operators.

-- | A number of repetitions.
type Repetitions	= Int

-- | Defines the bounds of a range of permissible repetitions.
type RepetitionBounds	= (Repetitions, Maybe Repetitions)

-- | Creates a precise 'RepetitionBounds', i.e. both lower & upper bounds on the number of 'Repetitions' are equal to the same value.
precisely :: Repetitions -> RepetitionBounds
precisely i	= (i, Just i)

-- | Predicate which is 'True' if exactly one value is permissible, ie lower & upper bounds on the number of 'Repetitions' are identical.
hasPreciseBounds :: RepetitionBounds -> Bool
hasPreciseBounds (fewest, most)	= Just fewest == most

-- | Declares a polymorphic data-type, which augments the underlying 'base' datum, with the range of times it may be used.
data Repeatable a	= MkRepeatable {
	base			:: a,			-- ^ The underlying polymorphic datum.
	repetitionBounds	:: RepetitionBounds,	-- ^ The bounds delimiting the range of permissible repetitions, of 'base'.
	isGreedy		:: Bool			-- ^ Whether to demand as many matching instances of 'base' as possible; or as few (AKA /lazy quantification/).
} deriving Eq

instance Functor Repeatable	where
	fmap f repeatable	= repeatable { base = f $ base repeatable }

-- | 'True' if there's no choice in the number of repetitions; implemented via 'isPrecise'.
isPrecise :: Repeatable a -> Bool
isPrecise	= hasPreciseBounds . repetitionBounds

-- | Builds a parser for a specification of the number of permissible instances of the specified polymorphic parameter.
repeatableParser :: a -> Parsec.Parser (Repeatable a)
repeatableParser b	= Parsec.option (
	one b	--The default; there's no concept of greediness here.
 ) $ do
	repeatable	<- Parsec.choice [
		(Parsec.char oneOrMoreToken <?> "Repeatable.oneOrMoreToken " ++ show oneOrMoreToken)	>> return {-to GenParser-monad-} (oneOrMore b),
		(Parsec.char zeroOrOneToken <?> "Repeatable.zeroOrOneToken " ++ show zeroOrOneToken)	>> return {-to GenParser-monad-} (zeroOrOne b),
		(Parsec.char zeroOrMoreToken <?> "Repeatable.zeroOrMoreToken " ++ show zeroOrMoreToken)	>> return {-to GenParser-monad-} (zeroOrMore b),
		(b ^#->#) {-arbitrarily greedy for now-} <$> uncurry Parsec.between (Pair.mirror Parsec.char rangeDelimiters) (
			do
				fewest	<- Parsec.skipMany Parsec.space >> (read <$> Parsec.many1 Parsec.digit <?> "Repetition-range minimum")
				most	<- Parsec.skipMany Parsec.space >> Parsec.option (
					Just fewest	--The default.
				 ) (
					do
						i	<- (
							Parsec.char rangeSeparatorToken			<?> "Repeatable.rangeSeparatorToken " ++ show rangeSeparatorToken
						 ) >> Parsec.skipMany Parsec.space >> Parsec.option Nothing {-default to open-ended range-} (
							Just . read <$> Parsec.many1 Parsec.digit	<?> "Repetition-range maximum"
						 )

						Parsec.skipMany Parsec.space >> return {-to GenParser-monad-} i
				 )

				return {-to GenParser-monad-} (fewest, most)
		) <?> "Repeatable.rangeDelimiters " ++ show rangeDelimiters
	 ]

	g	<- Parsec.option True {-the default-} $ (Parsec.char nonGreedyToken <?> "Repeatable.nonGreedyToken " ++ show nonGreedyToken) >> return {-to GenParser-monad-} False

	return {-to GenParser-monad-} repeatable { isGreedy = g }	--Correct prior assumption.

instance Read a => Read (Repeatable a)	where
	readsPrec _ s	= case reads s {-first, read the base-type-} of
		[(base', s1)]	-> (error . ("readsPrec Repeatable:\tparse-error; " ++) . show) `either` return $ Parsec.parse (Control.Monad.liftM2 (,) (repeatableParser base') Parsec.getInput) "Repeatable" s1
		_		-> []	--No parse.

{- |
	* A 'ShowS'-function for the suffix, denoting the permissible repetitions, of 'base'.

	* This function converts the internal, into the tradition /greedy/ & /non-greedy/ quantifiers of various specific varieties.
-}
showSuffix :: Repeatable a -> ShowS
showSuffix repeatable	= let
	showRange :: ShowS -> ShowS
	showRange x	= (\(begin, end) -> begin . x . end) $ Pair.mirror showChar rangeDelimiters
 in (
	case repetitionBounds repeatable of
		(0, Nothing)		-> showChar zeroOrMoreToken
		(1, Nothing)		-> showChar oneOrMoreToken
		(fewest, Nothing)	-> showRange $ shows fewest . showChar rangeSeparatorToken
		(0, Just 1)		-> showChar zeroOrOneToken
		(1, Just 1)		-> id	--CAVEAT: since there's no explicit repetition-operator, the non-greedy modifier can't be appended.
		(fewest, Just most)	-> showRange $ if fewest == most
			then shows fewest	--Single-valued range.
			else shows fewest . showChar rangeSeparatorToken . shows most
 ) . if ($ repeatable) `any` [isGreedy, isPrecise] {-without a range of possibilities, non-greediness is irrelevant-}
	then id
	else showChar nonGreedyToken	--This can only be appended, if there a previous repetition-operator for it to modify.

--Replicate the syntax, for repetition, as used in a POSIX-standard /regex/.
instance Show a => Show (Repeatable a)	where
	showsPrec _ repeatable	= shows (base repeatable) . showSuffix repeatable

instance Consumer.Consumer a => Consumer.Consumer (Repeatable a)	where
	consumptionProfile MkRepeatable {
		base			= b,
		repetitionBounds	= (fewest, most)
	} = baseConsumptionProfile {
		ConsumptionProfile.consumptionBounds	= (fewest *) *** ((*) <$> most <*>) $ ConsumptionProfile.consumptionBounds baseConsumptionProfile	--CAVEAT: special cases exist, where one or both halves of this calculation degenerate to a simpler form, but special treatment, in an attempt to improve performance, proved counterproductive.
	} where
		baseConsumptionProfile :: ConsumptionProfile.ConsumptionProfile
		baseConsumptionProfile	= Consumer.consumptionProfile b

	starHeight MkRepeatable {
		base			= b,
		repetitionBounds	= r
	} = Consumer.starHeight b + if hasPreciseBounds r then 0 else 1

instance SelfValidate.SelfValidator a => SelfValidate.SelfValidator (Repeatable a)	where
	isValid MkRepeatable {
		base			= b,
		repetitionBounds	= (fewest, most),
		isGreedy		= g
	} = and [
		SelfValidate.isValid b,	--Delegate.
		fewest >= 0,
		case most of
			Just m	-> m >= fewest	--Exactly zero instances is perverse, but legal.
			_	-> True,
		g || case most of
			Just m	-> fewest < m	--There ought to be potential for non-greediness, where specified: the converse isn't true, since greediness isn't explicit, & may not have been wanted.
			_	-> True
	 ]

instance NFData a => NFData (Repeatable a)	where
	rnf MkRepeatable {
		base			= b,
		repetitionBounds	= r,
		isGreedy		= g
	} = rnf (b, r, g)

-- | Mutator.
setNonGreedy :: Repeatable a -> Repeatable a
setNonGreedy r	= r { isGreedy = False }

{- |
	* Construct a greedy 'Repeatable', from a polymorphic datum, with the specified range of permissible instances.

	* The /#/s in the identifier represent the two bounds.

	* /a{f, m}/
-}
(^#->#) ::
	a			-- ^ The polymorphic payload from which to construct the 'Repeatable'.
	-> RepetitionBounds	-- ^ The permissible repetition-bounds for the polymorphic data.
	-> Repeatable a
b ^#-># bounds	= MkRepeatable {
	base			= b,
	repetitionBounds	= bounds,
	isGreedy		= True
}

{- |
	* Construct a non-greedy version of '^#->#'.

	* /a{f, m}?/
-}
(^#->#?) ::
	a			-- ^ The polymorphic payload from which to construct the 'Repeatable'.
	-> RepetitionBounds	-- ^ The permissible repetition-bounds for the polymorphic data.
	-> Repeatable a
b ^#->#? bounds	= setNonGreedy (b ^#-># bounds)

{- |
	* Construct a greedy 'Repeatable', tailored for data repeated at least the specified number of times.

	* The /#/ in the identifier represents the single bound.

	* /a{f,}/
-}
(^#->) ::
	a		-- ^ The polymorphic payload from which to construct the 'Repeatable'.
	-> Repetitions	-- ^ The minimum permissible repetitions of the polymorphic data.
	-> Repeatable a
b ^#-> fewest	= b ^#-># (fewest, Nothing)

{- |
	* Construct a non-greedy version of '^#->'.

	* /a{f,}?/
-}
(^#->?) ::
	a		-- ^ The polymorphic payload from which to construct the 'Repeatable'.
	-> Repetitions	-- ^ The minimum permissible repetitions of the polymorphic data.
	-> Repeatable a
b ^#->? fewest	= setNonGreedy (b ^#-> fewest)

{- |
	* Construct a 'Repeatable', tailored for data repeated a precise number of times.

	* The /#/ in the identifier represents the single bound.

	* /a{f}/
-}
(^#) ::
	a		-- ^ The polymorphic payload from which to construct the 'Repeatable'.
	-> Repetitions	-- ^ The precise number of repetitions of the polymorphic data which is required.
	-> Repeatable a
b ^# r	= b ^#-># precisely r

{- |
	* Construct a 'Repeatable', tailored for unrepeated data.

	* A degenerate case of '^#'.
-}
one :: a -> Repeatable a
one	= (^# 1)

{- |
	* Construct a greedy 'Repeatable', from a polymorphic datum, with 'fewest' == 0 & 'most' == 1.

	* A specific case of '^#->#'.
-}
zeroOrOne :: a -> Repeatable a
zeroOrOne	= (^#-># (0, Just 1))

-- | Construct a non-greedy version of 'zeroOrOne'.
zeroOrOne' :: a -> Repeatable a
zeroOrOne'	= setNonGreedy . zeroOrOne

{- |
	* Construct a greedy 'Repeatable', from a polymorphic datum, with 'fewest' == 0.

	* A specific case of '^#->'.
-}
zeroOrMore :: a -> Repeatable a
zeroOrMore 	= (^#-> 0)

-- | Construct a non-greedy version of 'zeroOrMore'.
zeroOrMore' :: a -> Repeatable a
zeroOrMore'	= setNonGreedy . zeroOrMore

{- |
	* Construct a greedy 'Repeatable', from a polymorphic datum, with lower 'RepetitionBounds' == one.

	* A specific case of '^#->'.
-}
oneOrMore :: a -> Repeatable a
oneOrMore	= (^#-> 1)

-- | Construct a non-greedy version of 'oneOrMore'.
oneOrMore' :: a -> Repeatable a
oneOrMore'	= setNonGreedy . oneOrMore

-- | Reduces a 'Repeatable', with a range of 'RepetitionBounds', to a precise number of repetitions.
focus :: Repeatable a -> Repetitions -> Repeatable a
focus r i	= r { repetitionBounds = precisely i }

{- |
	* Reduces a 'Repeatable', with a range of 'RepetitionBounds', to a singleton.

	* A degenerate case of 'focus'.
-}
toSingleton :: Repeatable a -> Repeatable a
toSingleton	= (`focus` 1)

-- | Accessor.
getFewest :: Repeatable a -> Repetitions
getFewest MkRepeatable { repetitionBounds = (f, _) }	= f

-- | Accessor.
getMost :: Repeatable a -> Maybe Repetitions
getMost MkRepeatable { repetitionBounds = (_, m) }	= m

-- | The token used to denote /non-greedy/, when in the 'String'-form.
nonGreedyToken :: Char
nonGreedyToken	= '?'

{- |
	* The token used to denote 'zeroOrMore', when in the 'String'-form.

	* AKA /Kleene Star/.
-}
zeroOrMoreToken :: Char
zeroOrMoreToken	= '*'

-- | The token used to denote 'zeroOrOne', when in the 'String'-form.
zeroOrOneToken :: Char
zeroOrOneToken	= '?'

-- | The token used to denote 'oneOrMore', when in the 'String'-form.
oneOrMoreToken :: Char
oneOrMoreToken	= '+'

-- | The delimiters of '^#->#', when in the 'String'-form.
rangeDelimiters :: (Char, Char)
rangeDelimiters	= ('{', '}')

-- | The token used to separate 'RepetitionBounds', when in the 'String'-form.
rangeSeparatorToken :: Char
rangeSeparatorToken	= ','

-- | The set of 'Char' to which a specific meaning is attributed, when reading from 'String'.
tokens :: String
tokens	= Data.List.nub [nonGreedyToken, zeroOrMoreToken, zeroOrOneToken, oneOrMoreToken, fst rangeDelimiters, snd rangeDelimiters, rangeSeparatorToken]