regexdot 0.11.1.1 → 0.11.1.2
raw patch · 46 files changed
+3213/−3218 lines, 46 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
- RegExDot.Anchor: instance Eq Anchor
- RegExDot.Anchor: instance NFData Anchor
- RegExDot.Anchor: instance Read Anchor
- RegExDot.Anchor: instance Show Anchor
- RegExDot.BracketExpressionMember: instance (ShortcutExpander m, Read m) => Read (Member m)
- RegExDot.BracketExpressionMember: instance Eq m => Eq (Member m)
- RegExDot.BracketExpressionMember: instance NFData m => NFData (Member m)
- RegExDot.BracketExpressionMember: instance Show m => Show (Member m)
- RegExDot.CompilationOptions: complyStrictlyWithPosix :: CompilationOptions -> Bool
- RegExDot.CompilationOptions: instance Defaultable CompilationOptions
- RegExDot.CompilationOptions: instance Eq CompilationOptions
- RegExDot.CompilationOptions: instance Options CompilationOptions
- RegExDot.CompilationOptions: instance Show CompilationOptions
- RegExDot.Consumer: instance Consumer c => Consumer [c]
- RegExDot.ConsumptionProfile: canConsumeAnything :: ConsumptionProfile -> Bool
- RegExDot.ConsumptionProfile: consumptionBounds :: ConsumptionProfile -> ConsumptionBounds
- RegExDot.ConsumptionProfile: hasSpecificRequirement :: ConsumptionProfile -> Bool
- RegExDot.ConsumptionProfile: instance Eq ConsumptionProfile
- RegExDot.ConsumptionProfile: instance Read ConsumptionProfile
- RegExDot.ConsumptionProfile: instance SelfValidator ConsumptionProfile
- RegExDot.ConsumptionProfile: instance Show ConsumptionProfile
- RegExDot.ExecutionOptions: abortTrialRepetitionsOnInherentFailure :: ExecutionOptions -> Bool
- RegExDot.ExecutionOptions: abortTrialRepetitionsOnZeroConsumption :: ExecutionOptions -> Bool
- RegExDot.ExecutionOptions: bypassInputDataForLiberalConsumer :: ExecutionOptions -> Bool
- RegExDot.ExecutionOptions: catchIncompatibleAnchors :: ExecutionOptions -> Bool
- RegExDot.ExecutionOptions: checkExistenceOfInelasticTail :: ExecutionOptions -> Bool
- RegExDot.ExecutionOptions: checkForUnconsumableData :: ExecutionOptions -> Bool
- RegExDot.ExecutionOptions: instance Defaultable ExecutionOptions
- RegExDot.ExecutionOptions: instance Eq ExecutionOptions
- RegExDot.ExecutionOptions: instance Options ExecutionOptions
- RegExDot.ExecutionOptions: instance Show ExecutionOptions
- RegExDot.ExecutionOptions: moderateGreed :: ExecutionOptions -> Bool
- RegExDot.ExecutionOptions: permitReorderingOfAlternatives :: ExecutionOptions -> Bool
- RegExDot.ExecutionOptions: preferAlternativesWhichFeedTheGreedy :: ExecutionOptions -> Bool
- RegExDot.ExecutionOptions: preferAlternativesWhichMimickUnrolling :: ExecutionOptions -> Bool
- RegExDot.ExecutionOptions: preferFewerRepeatedAlternatives :: ExecutionOptions -> Bool
- RegExDot.ExecutionOptions: requireMatchList :: ExecutionOptions -> Bool
- RegExDot.ExecutionOptions: unrollRepeatedSingletonAlternative :: ExecutionOptions -> Bool
- RegExDot.ExecutionOptions: useFirstMatchAmongAlternatives :: ExecutionOptions -> Bool
- RegExDot.ExecutionOptions: validateMinConsumptionOfAlternatives :: ExecutionOptions -> Bool
- RegExDot.InstanceInt: instance ShortcutExpander Int
- RegExDot.Meta: instance (ShortcutExpander m, Read m) => Read (Meta m)
- RegExDot.Meta: instance Consumer (Meta m)
- RegExDot.Meta: instance Eq m => Eq (Meta m)
- RegExDot.Meta: instance NFData m => NFData (Meta m)
- RegExDot.Meta: instance SelfValidator (Meta m)
- RegExDot.Meta: instance Show m => Show (Meta m)
- RegExDot.RegEx: bowAnchor :: ExtendedRegEx m -> Maybe Anchor
- RegExDot.RegEx: concatenation :: ExtendedRegEx m -> Concatenation m
- RegExDot.RegEx: instance (Eq m, ShortcutExpander m, Read m, ShortcutExpander m) => Read (ExtendedRegEx m)
- RegExDot.RegEx: instance (Eq m, ShortcutExpander m, Read m, ShortcutExpander m) => Read (Pattern m)
- RegExDot.RegEx: instance (ShortcutExpander m, ShortcutExpander m, Eq m, Read m) => Read (Alternatives m)
- RegExDot.RegEx: instance Consumer (Alternatives m)
- RegExDot.RegEx: instance Consumer (ExtendedRegEx m)
- RegExDot.RegEx: instance Consumer (Pattern m)
- RegExDot.RegEx: instance Eq m => Eq (Alternatives m)
- RegExDot.RegEx: instance Eq m => Eq (ExtendedRegEx m)
- RegExDot.RegEx: instance Eq m => Eq (Pattern m)
- RegExDot.RegEx: instance NFData m => NFData (Alternatives m)
- RegExDot.RegEx: instance NFData m => NFData (ExtendedRegEx m)
- RegExDot.RegEx: instance NFData m => NFData (Pattern m)
- RegExDot.RegEx: instance SelfValidator (Alternatives m)
- RegExDot.RegEx: instance SelfValidator (ExtendedRegEx m)
- RegExDot.RegEx: instance SelfValidator (Pattern m)
- RegExDot.RegEx: instance Show m => Show (Alternatives m)
- RegExDot.RegEx: instance Show m => Show (ExtendedRegEx m)
- RegExDot.RegEx: instance Show m => Show (Pattern m)
- RegExDot.RegEx: sternAnchor :: ExtendedRegEx m -> Maybe Anchor
- RegExDot.RegExOpts: compilationOptions :: RegExOpts a -> CompilationOptions
- RegExDot.RegExOpts: executionOptions :: RegExOpts a -> ExecutionOptions
- RegExDot.RegExOpts: instance Functor RegExOpts
- RegExDot.RegExOpts: instance Show a => Show (RegExOpts a)
- RegExDot.RegExOpts: regEx :: RegExOpts a -> a
- RegExDot.Repeatable: base :: Repeatable a -> a
- RegExDot.Repeatable: instance Consumer a => Consumer (Repeatable a)
- RegExDot.Repeatable: instance Eq a => Eq (Repeatable a)
- RegExDot.Repeatable: instance Functor Repeatable
- RegExDot.Repeatable: instance NFData a => NFData (Repeatable a)
- RegExDot.Repeatable: instance Read a => Read (Repeatable a)
- RegExDot.Repeatable: instance SelfValidator a => SelfValidator (Repeatable a)
- RegExDot.Repeatable: instance Show a => Show (Repeatable a)
- RegExDot.Repeatable: isGreedy :: Repeatable a -> Bool
- RegExDot.Repeatable: repetitionBounds :: Repeatable a -> RepetitionBounds
- RegExDot.ShowablePredicate: instance Eq (ShowablePredicate a)
- RegExDot.ShowablePredicate: instance NFData (ShowablePredicate a)
- RegExDot.ShowablePredicate: instance Show (ShowablePredicate a)
- RegExDot.ShowablePredicate: name :: ShowablePredicate a -> String
- RegExDot.ShowablePredicate: predicate :: ShowablePredicate a -> Predicate a
- RegExDot.Tree: instance Eq a => Eq (Tree a)
- RegExDot.Tree: instance Foldable Tree
- RegExDot.Tree: instance Functor Tree
- RegExDot.Tree: instance NFData a => NFData (Tree a)
- RegExDot.Tree: instance Read a => Read (Tree a)
- RegExDot.Tree: instance Show a => Show (Tree a)
+ RegExDot.Anchor: instance Control.DeepSeq.NFData RegExDot.Anchor.Anchor
+ RegExDot.Anchor: instance GHC.Classes.Eq RegExDot.Anchor.Anchor
+ RegExDot.Anchor: instance GHC.Read.Read RegExDot.Anchor.Anchor
+ RegExDot.Anchor: instance GHC.Show.Show RegExDot.Anchor.Anchor
+ RegExDot.BracketExpressionMember: instance (RegExDot.BracketExpressionMember.ShortcutExpander m, GHC.Read.Read m) => GHC.Read.Read (RegExDot.BracketExpressionMember.Member m)
+ RegExDot.BracketExpressionMember: instance Control.DeepSeq.NFData m => Control.DeepSeq.NFData (RegExDot.BracketExpressionMember.Member m)
+ RegExDot.BracketExpressionMember: instance GHC.Classes.Eq m => GHC.Classes.Eq (RegExDot.BracketExpressionMember.Member m)
+ RegExDot.BracketExpressionMember: instance GHC.Show.Show m => GHC.Show.Show (RegExDot.BracketExpressionMember.Member m)
+ RegExDot.CompilationOptions: [complyStrictlyWithPosix] :: CompilationOptions -> Bool
+ RegExDot.CompilationOptions: instance GHC.Classes.Eq RegExDot.CompilationOptions.CompilationOptions
+ RegExDot.CompilationOptions: instance GHC.Show.Show RegExDot.CompilationOptions.CompilationOptions
+ RegExDot.CompilationOptions: instance ToolShed.Defaultable.Defaultable RegExDot.CompilationOptions.CompilationOptions
+ RegExDot.CompilationOptions: instance ToolShed.Options.Options RegExDot.CompilationOptions.CompilationOptions
+ RegExDot.Consumer: instance RegExDot.Consumer.Consumer c => RegExDot.Consumer.Consumer [c]
+ RegExDot.ConsumptionProfile: [canConsumeAnything] :: ConsumptionProfile -> Bool
+ RegExDot.ConsumptionProfile: [consumptionBounds] :: ConsumptionProfile -> ConsumptionBounds
+ RegExDot.ConsumptionProfile: [hasSpecificRequirement] :: ConsumptionProfile -> Bool
+ RegExDot.ConsumptionProfile: instance GHC.Classes.Eq RegExDot.ConsumptionProfile.ConsumptionProfile
+ RegExDot.ConsumptionProfile: instance GHC.Read.Read RegExDot.ConsumptionProfile.ConsumptionProfile
+ RegExDot.ConsumptionProfile: instance GHC.Show.Show RegExDot.ConsumptionProfile.ConsumptionProfile
+ RegExDot.ConsumptionProfile: instance ToolShed.SelfValidate.SelfValidator RegExDot.ConsumptionProfile.ConsumptionProfile
+ RegExDot.ExecutionOptions: [abortTrialRepetitionsOnInherentFailure] :: ExecutionOptions -> Bool
+ RegExDot.ExecutionOptions: [abortTrialRepetitionsOnZeroConsumption] :: ExecutionOptions -> Bool
+ RegExDot.ExecutionOptions: [bypassInputDataForLiberalConsumer] :: ExecutionOptions -> Bool
+ RegExDot.ExecutionOptions: [catchIncompatibleAnchors] :: ExecutionOptions -> Bool
+ RegExDot.ExecutionOptions: [checkExistenceOfInelasticTail] :: ExecutionOptions -> Bool
+ RegExDot.ExecutionOptions: [checkForUnconsumableData] :: ExecutionOptions -> Bool
+ RegExDot.ExecutionOptions: [moderateGreed] :: ExecutionOptions -> Bool
+ RegExDot.ExecutionOptions: [permitReorderingOfAlternatives] :: ExecutionOptions -> Bool
+ RegExDot.ExecutionOptions: [preferAlternativesWhichFeedTheGreedy] :: ExecutionOptions -> Bool
+ RegExDot.ExecutionOptions: [preferAlternativesWhichMimickUnrolling] :: ExecutionOptions -> Bool
+ RegExDot.ExecutionOptions: [preferFewerRepeatedAlternatives] :: ExecutionOptions -> Bool
+ RegExDot.ExecutionOptions: [requireMatchList] :: ExecutionOptions -> Bool
+ RegExDot.ExecutionOptions: [unrollRepeatedSingletonAlternative] :: ExecutionOptions -> Bool
+ RegExDot.ExecutionOptions: [useFirstMatchAmongAlternatives] :: ExecutionOptions -> Bool
+ RegExDot.ExecutionOptions: [validateMinConsumptionOfAlternatives] :: ExecutionOptions -> Bool
+ RegExDot.ExecutionOptions: instance GHC.Classes.Eq RegExDot.ExecutionOptions.ExecutionOptions
+ RegExDot.ExecutionOptions: instance GHC.Show.Show RegExDot.ExecutionOptions.ExecutionOptions
+ RegExDot.ExecutionOptions: instance ToolShed.Defaultable.Defaultable RegExDot.ExecutionOptions.ExecutionOptions
+ RegExDot.ExecutionOptions: instance ToolShed.Options.Options RegExDot.ExecutionOptions.ExecutionOptions
+ RegExDot.InstanceInt: instance RegExDot.BracketExpressionMember.ShortcutExpander GHC.Types.Int
+ RegExDot.InstanceInt: instance RegExDot.Meta.ShortcutExpander GHC.Types.Int
+ RegExDot.InstanceInt: instance RegExDot.RegEx.ShortcutExpander GHC.Types.Int
+ RegExDot.Meta: instance (RegExDot.Meta.ShortcutExpander m, GHC.Read.Read m) => GHC.Read.Read (RegExDot.Meta.Meta m)
+ RegExDot.Meta: instance Control.DeepSeq.NFData m => Control.DeepSeq.NFData (RegExDot.Meta.Meta m)
+ RegExDot.Meta: instance GHC.Classes.Eq m => GHC.Classes.Eq (RegExDot.Meta.Meta m)
+ RegExDot.Meta: instance GHC.Show.Show m => GHC.Show.Show (RegExDot.Meta.Meta m)
+ RegExDot.Meta: instance RegExDot.Consumer.Consumer (RegExDot.Meta.Meta m)
+ RegExDot.Meta: instance ToolShed.SelfValidate.SelfValidator (RegExDot.Meta.Meta m)
+ RegExDot.RegEx: [bowAnchor] :: ExtendedRegEx m -> Maybe Anchor
+ RegExDot.RegEx: [concatenation] :: ExtendedRegEx m -> Concatenation m
+ RegExDot.RegEx: [sternAnchor] :: ExtendedRegEx m -> Maybe Anchor
+ RegExDot.RegEx: instance (GHC.Classes.Eq m, RegExDot.Meta.ShortcutExpander m, GHC.Read.Read m, RegExDot.RegEx.ShortcutExpander m) => GHC.Read.Read (RegExDot.RegEx.ExtendedRegEx m)
+ RegExDot.RegEx: instance (GHC.Classes.Eq m, RegExDot.Meta.ShortcutExpander m, GHC.Read.Read m, RegExDot.RegEx.ShortcutExpander m) => GHC.Read.Read (RegExDot.RegEx.Pattern m)
+ RegExDot.RegEx: instance (RegExDot.RegEx.ShortcutExpander m, RegExDot.Meta.ShortcutExpander m, GHC.Classes.Eq m, GHC.Read.Read m) => GHC.Read.Read (RegExDot.RegEx.Alternatives m)
+ RegExDot.RegEx: instance Control.DeepSeq.NFData m => Control.DeepSeq.NFData (RegExDot.RegEx.Alternatives m)
+ RegExDot.RegEx: instance Control.DeepSeq.NFData m => Control.DeepSeq.NFData (RegExDot.RegEx.ExtendedRegEx m)
+ RegExDot.RegEx: instance Control.DeepSeq.NFData m => Control.DeepSeq.NFData (RegExDot.RegEx.Pattern m)
+ RegExDot.RegEx: instance GHC.Classes.Eq m => GHC.Classes.Eq (RegExDot.RegEx.Alternatives m)
+ RegExDot.RegEx: instance GHC.Classes.Eq m => GHC.Classes.Eq (RegExDot.RegEx.ExtendedRegEx m)
+ RegExDot.RegEx: instance GHC.Classes.Eq m => GHC.Classes.Eq (RegExDot.RegEx.Pattern m)
+ RegExDot.RegEx: instance GHC.Show.Show m => GHC.Show.Show (RegExDot.RegEx.Alternatives m)
+ RegExDot.RegEx: instance GHC.Show.Show m => GHC.Show.Show (RegExDot.RegEx.ExtendedRegEx m)
+ RegExDot.RegEx: instance GHC.Show.Show m => GHC.Show.Show (RegExDot.RegEx.Pattern m)
+ RegExDot.RegEx: instance RegExDot.Consumer.Consumer (RegExDot.RegEx.Alternatives m)
+ RegExDot.RegEx: instance RegExDot.Consumer.Consumer (RegExDot.RegEx.ExtendedRegEx m)
+ RegExDot.RegEx: instance RegExDot.Consumer.Consumer (RegExDot.RegEx.Pattern m)
+ RegExDot.RegEx: instance ToolShed.SelfValidate.SelfValidator (RegExDot.RegEx.Alternatives m)
+ RegExDot.RegEx: instance ToolShed.SelfValidate.SelfValidator (RegExDot.RegEx.ExtendedRegEx m)
+ RegExDot.RegEx: instance ToolShed.SelfValidate.SelfValidator (RegExDot.RegEx.Pattern m)
+ RegExDot.RegExOpts: [compilationOptions] :: RegExOpts a -> CompilationOptions
+ RegExDot.RegExOpts: [executionOptions] :: RegExOpts a -> ExecutionOptions
+ RegExDot.RegExOpts: [regEx] :: RegExOpts a -> a
+ RegExDot.RegExOpts: instance GHC.Base.Functor RegExDot.RegExOpts.RegExOpts
+ RegExDot.RegExOpts: instance GHC.Show.Show a => GHC.Show.Show (RegExDot.RegExOpts.RegExOpts a)
+ RegExDot.Repeatable: [base] :: Repeatable a -> a
+ RegExDot.Repeatable: [isGreedy] :: Repeatable a -> Bool
+ RegExDot.Repeatable: [repetitionBounds] :: Repeatable a -> RepetitionBounds
+ RegExDot.Repeatable: instance Control.DeepSeq.NFData a => Control.DeepSeq.NFData (RegExDot.Repeatable.Repeatable a)
+ RegExDot.Repeatable: instance GHC.Base.Functor RegExDot.Repeatable.Repeatable
+ RegExDot.Repeatable: instance GHC.Classes.Eq a => GHC.Classes.Eq (RegExDot.Repeatable.Repeatable a)
+ RegExDot.Repeatable: instance GHC.Read.Read a => GHC.Read.Read (RegExDot.Repeatable.Repeatable a)
+ RegExDot.Repeatable: instance GHC.Show.Show a => GHC.Show.Show (RegExDot.Repeatable.Repeatable a)
+ RegExDot.Repeatable: instance RegExDot.Consumer.Consumer a => RegExDot.Consumer.Consumer (RegExDot.Repeatable.Repeatable a)
+ RegExDot.Repeatable: instance ToolShed.SelfValidate.SelfValidator a => ToolShed.SelfValidate.SelfValidator (RegExDot.Repeatable.Repeatable a)
+ RegExDot.ShowablePredicate: [name] :: ShowablePredicate a -> String
+ RegExDot.ShowablePredicate: [predicate] :: ShowablePredicate a -> Predicate a
+ RegExDot.ShowablePredicate: instance Control.DeepSeq.NFData (RegExDot.ShowablePredicate.ShowablePredicate a)
+ RegExDot.ShowablePredicate: instance GHC.Classes.Eq (RegExDot.ShowablePredicate.ShowablePredicate a)
+ RegExDot.ShowablePredicate: instance GHC.Show.Show (RegExDot.ShowablePredicate.ShowablePredicate a)
+ RegExDot.Tree: instance Control.DeepSeq.NFData a => Control.DeepSeq.NFData (RegExDot.Tree.Tree a)
+ RegExDot.Tree: instance Data.Foldable.Foldable RegExDot.Tree.Tree
+ RegExDot.Tree: instance GHC.Base.Functor RegExDot.Tree.Tree
+ RegExDot.Tree: instance GHC.Classes.Eq a => GHC.Classes.Eq (RegExDot.Tree.Tree a)
+ RegExDot.Tree: instance GHC.Read.Read a => GHC.Read.Read (RegExDot.Tree.Tree a)
+ RegExDot.Tree: instance GHC.Show.Show a => GHC.Show.Show (RegExDot.Tree.Tree a)
Files
- README.markdown +23/−0
- changelog +0/−47
- changelog.markdown +53/−0
- copyright +1/−1
- makefile +0/−59
- regexdot.cabal +43/−24
- src-lib/RegExDot/Anchor.hs +90/−0
- src-lib/RegExDot/BracketExpression.hs +63/−0
- src-lib/RegExDot/BracketExpressionMember.hs +93/−0
- src-lib/RegExDot/CompilationOptions.hs +48/−0
- src-lib/RegExDot/Consumer.hs +105/−0
- src-lib/RegExDot/ConsumptionBounds.hs +48/−0
- src-lib/RegExDot/ConsumptionProfile.hs +151/−0
- src-lib/RegExDot/DSL.hs +117/−0
- src-lib/RegExDot/DataSpan.hs +61/−0
- src-lib/RegExDot/DataSpanTree.hs +108/−0
- src-lib/RegExDot/ExecutionOptions.hs +102/−0
- src-lib/RegExDot/InstanceInt.hs +43/−0
- src-lib/RegExDot/Meta.hs +144/−0
- src-lib/RegExDot/RegEx.hs +1198/−0
- src-lib/RegExDot/RegExOpts.hs +65/−0
- src-lib/RegExDot/Repeatable.hs +400/−0
- src-lib/RegExDot/Result.hs +60/−0
- src-lib/RegExDot/ShowablePredicate.hs +53/−0
- src-lib/RegExDot/Span.hs +64/−0
- src-lib/RegExDot/Tree.hs +80/−0
- src/RegExDot/Anchor.hs +0/−90
- src/RegExDot/BracketExpression.hs +0/−63
- src/RegExDot/BracketExpressionMember.hs +0/−93
- src/RegExDot/CompilationOptions.hs +0/−48
- src/RegExDot/Consumer.hs +0/−105
- src/RegExDot/ConsumptionBounds.hs +0/−48
- src/RegExDot/ConsumptionProfile.hs +0/−148
- src/RegExDot/DSL.hs +0/−117
- src/RegExDot/DataSpan.hs +0/−61
- src/RegExDot/DataSpanTree.hs +0/−108
- src/RegExDot/ExecutionOptions.hs +0/−102
- src/RegExDot/InstanceInt.hs +0/−43
- src/RegExDot/Meta.hs +0/−144
- src/RegExDot/RegEx.hs +0/−1198
- src/RegExDot/RegExOpts.hs +0/−65
- src/RegExDot/Repeatable.hs +0/−397
- src/RegExDot/Result.hs +0/−60
- src/RegExDot/ShowablePredicate.hs +0/−53
- src/RegExDot/Span.hs +0/−64
- src/RegExDot/Tree.hs +0/−80
+ README.markdown view
@@ -0,0 +1,23 @@+# **RegExDot**.++This is **RegExDot**, a polymorphic regex-engine.++## Installation++It can be built and installed using [Cabal](https://www.haskell.org/cabal/users-guide/installing-packages.html).++## Documentation++More information about this library can be found at [RegExDot](http://functionalley.eu/RegExDot/regExDot.html).++## License++For information on copying and distributing this package, see the file **LICENSE** in this directory.++## Bug-reporting++Bug-reports should be emailed to <regexdot *at* functionalley *dot* eu>.++## Author++This library is written and maintained by Dr. Alistair Ward.
− changelog
@@ -1,47 +0,0 @@-2010-11-18 Dr. Alistair Ward <regexdot at functionalley dot eu>--0.9.0.0- * First version of the package.-0.10.0.0- * Reacted to major-number changes in the package 'ToolShed-0.10.0.0'.- * Created "src/RegExDot/" sub-directory & then modified module-names accordingly.- * Pacified 'hlint'.- * Added a makefile.- * Removed awkward unary operators from 'Repeatable.hs'.- * Qualified identifiers used in error-messages.-0.10.0.1- * Ported to ghc-7.0.1:- Used definitions of 'NFData' & 'rnf' from new package 'deepseq', rather than 'parallel'.- Replaced reference to the deprecated function 'Control.Parallel.Strategies.parFlatMap' with @concat . Control.Parallel.Strategies.parMap@.-0.10.1.0- * Set @threaded@ flag to @True@ by default.- * Replaced use of @threaded@-flag in source-code, with CABAL CPP-macro.- * Relocated "RegExDot.Options" to "ToolShed.Options".-0.10.2.0- * Renamed package from 'RegExDot' to 'regexdot', for compatibility with Debian's ".deb"-format.-0.10.2.1- * Added manually controlled "llvm" flag to "regexdot.cabal".- * Changed identifier for type-parameters, to better reflect its role.- * Reacted to the creation of module "ToolShed.Defaultable".- * Uploaded to <http://hackage.haskell.org/packages/hackage.html>.-0.10.2.2- * Amended 'regexdot.cabal' to more correctly specify dependency on package 'toolshed'.- * Used new module "ToolShed.Pair" from package toolshed-0.12.0.0.- * Guarded 'eager-blackholing' flag in cabal file.-0.11.0.0- * Replaced '(+ 1)' and '(- 1)' with the faster calls 'succ' and 'pred', in "RegExDot.RegEx".- * Reacted to new module-hierarchy, creation of new module "ToolShed.Data.List.Splits" and addition of method 'ToolShed.SelfValidate.getErrors', in toolshed-0.13.0.0, and used it to improved error-reporting in @instance Read RegExDot.RegEx.ExtendedRegEx@.- * Minor reworking of 'RegExDot.Repeatable.repeatableParser'.- * Replaced "System" with "System.Environment" and "System.Exit".-0.11.0.1- * Added class 'Eq' to the context of 'RegExDot.RegEx.safeReciprocal', for migration to 'ghc-7.4'.-0.11.1.0- * Removed comments referring to deleted module "ToolShed.Unsafe", from "RegExDot.RegEx".- * Removed 'Show' from the context of functions in "RegExDot.RegEx"- * Exported a new constant 'RegExDot.Anchor.unanchored'.- * Refactored 'RegExDot.ConsumptionProfile.withinConsumptionBounds'.-0.11.1.1- * Tested with 'haskell-platform-2013.2.0.0'.- * Replaced preprocessor-directives with 'build-depends' constraints in 'regexdot.cabal'.- * In function 'RegExDot.RegEx.findMatch.findMatchSlave.matchPairList', changed 'fromIntegral' (which required a type-signature) to 'toRational'.- * Either replaced instances of '(<$>)' with 'fmap' to avoid ambiguity between "Control.Applicative" & "Prelude" which (from 'base-4.8') also exports this symbol, or hid the symbol when importing the "Prelude"..
+ changelog.markdown view
@@ -0,0 +1,53 @@+# 2010-11-18 Dr. Alistair Ward <regexdot at functionalley dot eu>++## 0.9.0.0+ * First version of the package.+## 0.10.0.0+ * Reacted to major-number changes in the package **ToolShed-0.10.0.0**.+ * Created **src/RegExDot/** sub-directory & then modified module-names accordingly.+ * Pacified **hlint**.+ * Added a makefile.+ * Removed awkward unary operators from **Repeatable.hs**.+ * Qualified identifiers used in error-messages.+## 0.10.0.1+ * Ported to ghc-7.0.1:+ Used definitions of `NFData` & `rnf` from new package **deepseq**, rather than **parallel**.+ Replaced reference to the deprecated function `Control.Parallel.Strategies.parFlatMap` with `concat . Control.Parallel.Strategies.parMap`.+## 0.10.1.0+ * Set @threaded@ flag to @True@ by default.+ * Replaced use of @threaded@-flag in source-code, with CABAL CPP-macro.+ * Relocated **RegExDot.Options** to **ToolShed.Options**.+## 0.10.2.0+ * Renamed package from **RegExDot** to **regexdot**, for compatibility with Debian's *.deb*-format.+## 0.10.2.1+ * Added manually controlled **llvm** flag to the *.cabal*-file.+ * Changed identifier for type-parameters, to better reflect its role.+ * Reacted to the creation of module **ToolShed.Defaultable**.+ * Uploaded to [Hackage](http://hackage.haskell.org/packages/hackage.html).+## 0.10.2.2+ * Amended the *.cabal*-file to more correctly specify dependency on package **toolshed**.+ * Used new module **ToolShed.Pair** from package **toolshed-0.12.0.0**.+ * Guarded **eager-blackholing** flag in the *.cabal*-file.+## 0.11.0.0+ * Replaced `(+ 1)` and `(- 1)` with the faster calls `succ` and `pred`, in **RegExDot.RegEx**.+ * Reacted to new module-hierarchy, creation of new module **ToolShed.Data.List.Splits** and addition of method `ToolShed.SelfValidate.getErrors`, in **toolshed-0.13.0.0**, and used it to improved error-reporting in `instance Read RegExDot.RegEx.ExtendedRegEx`.+ * Minor reworking of `RegExDot.Repeatable.repeatableParser`.+ * Replaced `System` with `System.Environment` and `System.Exit`.+## 0.11.0.1+ * Added class `Eq` to the context of `RegExDot.RegEx.safeReciprocal`, for migration to **ghc-7.4**.+## 0.11.1.0+ * Removed comments referring to deleted module **ToolShed.Unsafe**, from **RegExDot.RegEx**.+ * Removed `Show` from the context of functions in **RegExDot.RegEx**+ * Exported a new constant `RegExDot.Anchor.unanchored`.+ * Refactored `RegExDot.ConsumptionProfile.withinConsumptionBounds`.+## 0.11.1.1+ * Tested with **haskell-platform-2013.2.0.0**.+ * Replaced preprocessor-directives with **build-depends** constraints in the *.cabal*-file.+ * In function `RegExDot.RegEx.findMatch.findMatchSlave.matchPairList`, changed `fromIntegral` (which required a type-signature) to `toRational`.+ * Either replaced instances of `(<$>)` with `fmap` to avoid ambiguity between **Control.Applicative** & **Prelude** which (from **base-4.8**) also exports this symbol, or hid the symbol when importing the **Prelude**..+## 0.11.1.2+ * Added **Default-language**-specification to the *.cabal*-file.+ * Added file **README.markdown**.+ * Converted this file to markdown-format.+ * Renamed directory **src/** to **src-lib/** for consistency with other packages. + * Used **CPP** to control the import of symbols from **Control.Applicative**.
copyright view
@@ -5,7 +5,7 @@ Copyright (C) 2010-2011 Dr. Alistair Ward. All Rights Reserved. Home-page:- http://functionalley.eu+ http://functionalley.eu/RegExDot/regExDot.html License: GNU GENERAL PUBLIC LICENSE Version 3; see '/usr/share/common-licenses/GPL-3' or '/usr/share/doc/licenses/gpl-3.0.txt' where available, or the local packaged file 'LICENSE'.
− makefile
@@ -1,59 +0,0 @@-# Copyright (C) 2010-2014 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/>.--.PHONY: all build check clean configure copy haddock help hlint install prof sdist--all: install--install: build haddock- @[ -z "$$CABAL_INSTALL_OPTIONS" ] || echo "INFO: CABAL_INSTALL_OPTIONS='$$CABAL_INSTALL_OPTIONS'"- runhaskell Setup $@ $$CABAL_INSTALL_OPTIONS--prof:- CABAL_CONFIGURE_OPTIONS="--enable-library-profiling $$CABAL_CONFIGURE_OPTIONS" make install--copy: build- @[ -z "$$CABAL_COPY_OPTIONS" ] || echo "INFO: CABAL_COPY_OPTIONS='$$CABAL_COPY_OPTIONS'"- runhaskell Setup $@ $$CABAL_COPY_OPTIONS--build: configure- @[ -z "$$CABAL_BUILD_OPTIONS" ] || echo "INFO: CABAL_BUILD_OPTIONS='$$CABAL_BUILD_OPTIONS'"- runhaskell Setup $@ $$CABAL_BUILD_OPTIONS--configure: regexdot.cabal Setup.hs- @[ -z "$$CABAL_CONFIGURE_OPTIONS" ] || echo "INFO: CABAL_CONFIGURE_OPTIONS='$$CABAL_CONFIGURE_OPTIONS'"- runhaskell Setup $@ $$CABAL_CONFIGURE_OPTIONS #--user--haddock: configure- PATH=~/.cabal/bin:$$PATH runhaskell Setup $@ --hyperlink-source #Amend path to find 'HsColour', as required for 'hyperlink-source'.--hlint:- @$@ -i 'Use &&' src/ -i 'Use ||' +RTS -N--sdist:- TAR_OPTIONS='--format=ustar' runhaskell Setup $@--check: sdist- cabal upload --check --verbose=3 dist/*.tar.gz;--clean:- runhaskell Setup $@- find src -type f \( -name '*.hc' -o -name '*.hcr' -o -name '*.hi' -o -name '*.o' \) -delete--help:- @grep '^[a-zA-Z].*:' makefile | sed -e 's/:.*//'--rpm: regexdot.rpm build-
regexdot.cabal view
@@ -1,30 +1,51 @@--- Package-properties-Name: regexdot-Version: 0.11.1.1-Cabal-Version: >= 1.6-Copyright: (C) 2010-2015 Dr. Alistair Ward-License: GPL-License-file: LICENSE-Author: Dr. Alistair Ward-Stability: Unstable interface, incomplete features.-Synopsis: A polymorphic, POSIX, extended regex-engine.-Build-Type: Simple-Description: Provides a portable, POSIX, extended regex-engine, designed to process a list of /arbitrary/ objects.-Category: Search, Regex-Tested-With: GHC == 6.10, GHC == 6.12, GHC == 7.0, GHC == 7.4, GHC == 7.6, GHC == 7.10-Homepage: http://functionalley.eu-Maintainer: regexdot <at> functionalley <dot> eu-Bug-reports: regexdot <at> functionalley <dot> eu-Extra-Source-Files: changelog, copyright, makefile+-- This file is part of RegExDot.+--+-- RegExDot 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.+--+-- RegExDot 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 RegExDot. If not, see <http://www.gnu.org/licenses/>. --- Turn on using: 'runhaskell ./Setup.hs configure -f llvm'.+Name: regexdot+Version: 0.11.1.2+Cabal-version: >= 1.10+Copyright: (C) 2010-2015 Dr. Alistair Ward+License: GPL+License-file: LICENSE+Author: Dr. Alistair Ward+Stability: stable+Synopsis: A polymorphic, POSIX, extended regex-engine.+Build-type: Simple+Description: Provides a portable, POSIX, extended regex-engine, designed to process a list of /arbitrary/ objects.+Category: Search, Regex+Tested-with: GHC == 6.10, GHC == 6.12, GHC == 7.0, GHC == 7.4, GHC == 7.6, GHC == 7.8, GHC == 7.10+Homepage: http://functionalley.eu/RegExDot/regExDot.html+Maintainer: mailto <colon> regexdot <at> functionalley <dot> eu+Bug-reports: mailto <colon> regexdot <at> functionalley <dot> eu++-- None of these files are needed at run-time.+Extra-source-files:+ changelog.markdown+ copyright+ README.markdown++-- Enable using: 'cabal configure -f llvm'. flag llvm Description: Whether the 'llvm' compiler-backend has been installed and is required for code-generation.- manual: True- default: False+ Manual: True+ Default: False Library- hs-source-dirs: src+ Default-language: Haskell2010+ GHC-options: -Wall -O2 -fno-warn-tabs+ Hs-source-dirs: src-lib Exposed-modules: RegExDot.Anchor@@ -54,8 +75,6 @@ parallel >= 3.0, parsec == 3.*, toolshed >= 0.13-- GHC-options: -Wall -O2 -fno-warn-tabs if impl(ghc >= 7.4.1) GHC-prof-options: -prof -fprof-auto -fprof-cafs
+ src-lib/RegExDot/Anchor.hs view
@@ -0,0 +1,90 @@+{-+ 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 type of /zero-width assertion/:+ the 'Bow'-predicate is 'True' if no input-data has yet been consumed; the 'Stern'-predicate is 'True' if no input-data remains to be consumed.++ * Whilst at the top-level of the regex, the /absence/ of an anchor is equivalent to a non-capturing @.*?@,+ tempting one to implement them using this property,+ it doesn't apply to those anchors encountered at either end of a sub-expression.+ So in all cases, an anchor constrains the permissible consumption of input-data,+ but at the top level it also prevents the regex drifting away from either end of the input-data.+-}++module RegExDot.Anchor(+-- * Types+-- ** Data-types+ Anchor(..),+-- ** Type-synonyms+ AnchorSpecification,+-- * Constants+ bowToken,+ sternToken,+ tokens,+ unanchored+) where++import qualified Control.DeepSeq++-- | Defines the types on /anchor/ by which a /regex/ can be moored to a part of the input-data.+data Anchor =+ Bow -- ^ Matches only if no input data has yet been consumed. Can only exist at the start of the entire regex, or (in theory) the start of any /alternative/.+ | Stern -- ^ Matches only if no input data remains to be consumed. Can only exist at the end of the entire regex, or (in theory) the end of any /alternative/.+ deriving (+ Eq+-- Read, -- See specialisation below.+-- Show -- See specialisation below.+ )++instance Show Anchor where+ showsPrec _ Bow = showChar bowToken+ showsPrec _ Stern = showChar sternToken++instance Read Anchor where+ readsPrec _ [] = [] -- No parse.+ readsPrec _ (' ' : s) = reads s -- Consume white-space.+ readsPrec _ ('\t' : s) = reads s -- Consume white-space.+ readsPrec _ (c : s) = case c `lookup` [(bowToken, Bow), (sternToken, Stern)] of+ Just anchor -> [(anchor, s)]+ _ -> [] -- No parse.++instance Control.DeepSeq.NFData Anchor where+ rnf _ = ()++-- | The conventional token used to denote a 'Bow'-anchor, when in 'String'-form.+bowToken :: Char+bowToken = '^'++-- | The conventional token used to denote a 'Stern'-anchor, when in 'String'-form.+sternToken :: Char+sternToken = '$'++-- | The set of 'Char' to which a specific meaning is attributed, when reading from 'String'.+tokens :: String+tokens = [bowToken, sternToken]++-- | A specification for the anchors required around the expression+type AnchorSpecification = (Maybe Anchor, Maybe Anchor)++-- | The specification for an unanchored expression.+unanchored :: AnchorSpecification+unanchored = (Nothing, Nothing)+
+ src-lib/RegExDot/BracketExpression.hs view
@@ -0,0 +1,63 @@+{-+ 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@]++ This concept was traditionally known as a /Character-class/,+ but to distinguish it from the more recently introduced & narrower /POSIX Character-class/, it has been renamed /Bracket-expression/.+-}++module RegExDot.BracketExpression(+-- * Types+-- ** Type-synonyms+ BracketExpression,+-- * Constants+ delimiterTokens,+ negationToken,+ tokens,+-- * Functions+-- ** Predicates+ containsMatch+) where++import RegExDot.BracketExpressionMember((=~))+import qualified RegExDot.BracketExpressionMember as BracketExpressionMember++-- | A /Bracket-expression/ consists of a list of 'BracketExpressionMember.Member's.+type BracketExpression m = [BracketExpressionMember.Member m]++-- | True if the specified datum matches any 'BracketExpressionMember.Member' of the 'BracketExpression'.+containsMatch :: Eq m+ => m -- ^ The input datum.+ -> BracketExpression m -- ^ The list of bracket-expression members within which to find a match.+ -> Bool+containsMatch datum = any (datum =~)++-- | The delimiters of a /bracket-expression/, when in 'String'-form.+delimiterTokens :: (Char, Char)+delimiterTokens = ('[', ']')++-- | Used to denote a negated /bracket-expression/, when in 'String'-form.+negationToken :: Char+negationToken = '^'++-- | The set of 'Char' to which a specific meaning is attributed, when reading from 'String'.+tokens :: String+tokens = [fst delimiterTokens, snd delimiterTokens, negationToken]+
+ src-lib/RegExDot/BracketExpressionMember.hs view
@@ -0,0 +1,93 @@+{-+ 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@]++ Permits /Perl-style shortcut/s to be canned & assigned a single-'Char' mnemonic for subsequent reference;+ the implementation of 'Read' looks for a back-slashed 'Char', for which it expects there to be a corresponding canned 'ShowablePredicate.ShowablePredicate'.++ [@CAVEATS@]++ Since the underlying polymorphic data-type isn't required to implement neither 'Enum' nor 'Ord', the implementation of 'Read' can't cope with range-specifications.+ Lacking this, Bracket-expression members must be enumerated exhaustively.+-}++module RegExDot.BracketExpressionMember(+-- * Type-classes+ ShortcutExpander(..),+-- * Types+-- ** Data-types+ Member(..),+-- * Functions+-- ** Operators+ (=~)+) where++import qualified Control.Arrow+import qualified Control.DeepSeq+import qualified RegExDot.ShowablePredicate as ShowablePredicate++infix 4 =~ -- Same as (==).++{- |+ * The interface via which /Perl-style shortcut/s are expanded (when they occur within a /bracket-expression/), in a manner appropriate to the chosen type-parameter.++ * The expansion of /Perl-style shortcut/s, is more restricted inside than outside, a /bracket-expression/,+ & consequently are merely represented here by a 'ShowablePredicate.ShowablePredicate', rather than providing a more general form suitable also for those /Perl-style shortcuts/ found outside /bracket-expression/s.++ * This interface is implemented elsewhere, where the specific type-parameter & consequently the appropriate set of /Perl-style shortcut/s, are defined.+-}+class ShortcutExpander a where+ findPredicate :: Char -> Maybe (ShowablePredicate.ShowablePredicate a) -- ^ Attempt to find the appropriate 'ShowablePredicate.ShowablePredicate' to implement this /Perl-style shortcut/.++{- |+ * A /BracketExpression/ can contain either a literal, a range of literals given @(Enum a, Ord a)@, a /Perl-style shortcut/, or when 'Char' is the type-parameter, a /POSIX Character-class/.++ * This data-type reduces the representation of all these possibilities to either a predicate or a literal.+-}+data Member m =+ Predicate (ShowablePredicate.ShowablePredicate m) -- ^ This 'Member' is described using a /predicate/, which is run to determine whether the datum conforms & is a member of the "BracketExpression".+ | Literal m -- ^ This 'Member' is defined literally, using an item of the polymorphic type.+ deriving Eq++instance Show m => Show (Member m) where+ showsPrec _ (Predicate showablePredicate) = shows showablePredicate+ showsPrec _ (Literal literal) = shows literal++instance (ShortcutExpander m, Read m) => Read (Member m) where+ readsPrec _ [] = [] -- No parse.+ readsPrec _ (' ' : s) = reads s -- Consume white-space.+ readsPrec _ ('\t' : s) = reads s -- Consume white-space.+ readsPrec _ ('\\' : shortcut : s) = case findPredicate shortcut of+ Just showablePredicate -> [(Predicate showablePredicate, s)]+ _ -> error $ "readsPrec RegExDot.BracketExpressionMember.Member:\tfindPredicate failed for shortcut " ++ show shortcut+ readsPrec _ literal = Control.Arrow.first Literal `map` reads literal++instance Control.DeepSeq.NFData m => Control.DeepSeq.NFData (Member m) where+ rnf (Predicate showablePredicate) = Control.DeepSeq.rnf showablePredicate+ rnf (Literal literal) = Control.DeepSeq.rnf literal++-- | Match-operator.+(=~) :: Eq m+ => m -- ^ The input datum.+ -> Member m -- ^ The member of the bracket-expression against which the input-datum is to be matched.+ -> Bool -- ^ The result of the match-operation.+datum =~ Predicate showablePredicate = ShowablePredicate.predicate showablePredicate datum+datum =~ Literal literal = datum == literal+
+ src-lib/RegExDot/CompilationOptions.hs view
@@ -0,0 +1,48 @@+{-+ 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@]++ * Defines those options relating to compile-time.++ * These tend to be options which affect the result, rather than the means by which it is achieved.++ * Whilst similar structures are present in other regex-implementations, there's no standardisation of the fields.+-}++module RegExDot.CompilationOptions(+-- * Types+-- ** Data-types+ CompilationOptions(..)+) where++import qualified ToolShed.Defaultable+import qualified ToolShed.Options++-- | The switch(es) used to control compilation of the /regex/-engine.+data CompilationOptions = MkCompilationOptions {+ complyStrictlyWithPosix :: Bool -- ^ Define the offset of captured data, corresponding to a sub-expression which matched zero times, as the artificial value @-1@ specified by POSIX.+} deriving (Eq, Show)++instance ToolShed.Defaultable.Defaultable CompilationOptions where+ defaultValue = ToolShed.Options.blankValue { complyStrictlyWithPosix = True }++instance ToolShed.Options.Options CompilationOptions where+ blankValue = MkCompilationOptions { complyStrictlyWithPosix = undefined }+
+ src-lib/RegExDot/Consumer.hs view
@@ -0,0 +1,105 @@+{-+ 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@]++ * Define the requirements & capabilities of a data-type which is designed to consume data.++ * This module also facilitates investigation of group-behaviour, by defining operations on either concatenations or alternations of 'Consumer's.++ * This module is specifically for use in a /regex/-engine,+ in which the meta-data from which the /regex/ is composed, are considered to /consume/ input data,+ whilst concatenation & alternation of such elements, behave as a different types of groups of consumer.+-}++module RegExDot.Consumer(+-- * Type-classes+ Consumer(..),+-- * Types+-- ** Type-synonyms+-- StarHeight,+-- * Functions+ accumulateConsumptionProfiles,+-- accumulateConsumptionProfilesFrom,+ aggregateConsumptionProfilesFromAlternatives,+ aggregateConsumptionProfilesFromConcatenation,+-- ** Query+ getConsumptionBounds,+ getFewest,+ getHasSpecificRequirement+) where++import RegExDot.ConsumptionProfile((<>), (|+|))+import qualified Data.List+import qualified RegExDot.ConsumptionBounds as ConsumptionBounds+import qualified RegExDot.ConsumptionProfile as ConsumptionProfile++{- |+ * A measure of the complexity of a /regex/, which has some baring on either the time-complexity or the space-complexity of the solution.++ * <http://en.wikipedia.org/wiki/Star_height>+-}+type StarHeight = Int++{- |+ * The interface, to which types, which have a data-requirement, may conform.++ * Regrettably, methods requiring reference to the type of the consumable, would require multi-parameter type-classes.++ * 'consumptionProfile' defines the quantity-range of data which may be consumed, & any specific data-requirements.++ * 'starHeight' measures the complexity of the 'Consumer'.+-}+class Consumer c where+ consumptionProfile :: c -> ConsumptionProfile.ConsumptionProfile+ starHeight :: c -> StarHeight++instance Consumer c => Consumer [c] where+ consumptionProfile = undefined+ starHeight = Data.List.foldl' (\l -> max l . starHeight) 0++-- | Convenience-function, to query the 'ConsumptionBounds.ConsumptionBounds' of a 'Consumer'.+getConsumptionBounds :: Consumer c => c -> ConsumptionBounds.ConsumptionBounds+getConsumptionBounds = ConsumptionProfile.consumptionBounds . consumptionProfile++-- | Determine the minimum acceptable quantity of data.+getFewest :: Consumer c => c -> ConsumptionBounds.DataLength+getFewest = fst . getConsumptionBounds++-- | Determine whether the specified 'Consumer', has a specific requirement.+getHasSpecificRequirement :: Consumer c => c -> Bool+getHasSpecificRequirement = ConsumptionProfile.hasSpecificRequirement . consumptionProfile++-- | The aggregate of the specified alternation of 'ConsumptionProfile.ConsumptionProfile's.+aggregateConsumptionProfilesFromAlternatives :: Consumer c => [c] -> ConsumptionProfile.ConsumptionProfile+aggregateConsumptionProfilesFromAlternatives [] = error "RegExDot.Consumer.aggregateConsumptionProfilesFromAlternatives:\tnull list"+aggregateConsumptionProfilesFromAlternatives l = Data.List.foldl1' (<>) $ consumptionProfile `map` l++-- | Get the 'ConsumptionProfile.ConsumptionProfile's for the specified list of 'Consumer's, then find the net effect of concatenating them.+aggregateConsumptionProfilesFromConcatenation :: Consumer c => [c] -> ConsumptionProfile.ConsumptionProfile+aggregateConsumptionProfilesFromConcatenation = Data.List.foldl' (\acc -> (acc |+|) . consumptionProfile) ConsumptionProfile.zero {-initial value-}++-- | Get the 'ConsumptionProfile.ConsumptionProfile' for the specified list of 'Consumer's, then accumulate them, from the specified initial value.+accumulateConsumptionProfilesFrom :: Consumer c => ConsumptionProfile.ConsumptionProfile -> [c] -> ConsumptionProfile.AccumulatedConsumptionProfiles+accumulateConsumptionProfilesFrom = scanr ((|+|) . consumptionProfile)++-- | Get the 'ConsumptionProfile.ConsumptionProfile' for the specified list of 'Consumer's, then accumulate them.+accumulateConsumptionProfiles :: Consumer c => [c] -> ConsumptionProfile.AccumulatedConsumptionProfiles+accumulateConsumptionProfiles = {-init .-} accumulateConsumptionProfilesFrom ConsumptionProfile.zero -- It's useful to leave the initial value at the end of the list.+
+ src-lib/RegExDot/ConsumptionBounds.hs view
@@ -0,0 +1,48 @@+{-+ 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@] Describes the bounds of data-consumption.+-}++module RegExDot.ConsumptionBounds(+-- * Types+-- ** Type-synonyms+ ConsumptionBounds,+ DataLength,+-- * Constants+ zero,+-- * Functions+-- ** Predicates+ isPrecise+) where++-- | A measure of the quantity of an unspecified type of data.+type DataLength = Int++-- | The minimum & maximum bounds of potential data-consumption.+type ConsumptionBounds = (DataLength, Maybe DataLength)++-- | Exactly zero consumption.+zero :: ConsumptionBounds+zero = (0, Just 0)++-- | Predicate which is 'True' if only a precise quantity of data is consumable; no more, no less.+isPrecise :: ConsumptionBounds -> Bool+isPrecise (fewest, most) = Just fewest == most+
+ src-lib/RegExDot/ConsumptionProfile.hs view
@@ -0,0 +1,151 @@+{-# LANGUAGE CPP #-}+{-+ 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@]++ A 'RegExDot.ConsumptionProfile' is composed from both a capacity to consume, & an ability to discriminate based on the specific data.+ The former is described by 'consumptionBounds' & the latter by both 'hasSpecificRequirement' & 'canConsumeAnything'.+-}++module RegExDot.ConsumptionProfile(+-- * Types+-- ** Type-synonyms+ AccumulatedConsumptionProfiles,+-- ** Data-types+ ConsumptionProfile(..),+-- * Constants+ zero,+-- * Functions+ accumulateFrom,+ aggregateFromConcatenation,+-- ** Operators+ (|+|),+ (<>),+-- ** Predicates+-- isPrecise,+ withinConsumptionBounds+) where++import Control.Arrow((***))+import qualified Data.List+import qualified Data.Maybe+import qualified RegExDot.ConsumptionBounds as ConsumptionBounds+import qualified ToolShed.SelfValidate++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative((<$>), (<*>))+#endif++infixr 5 |+| -- Same as (++).+infixr 2 <> -- Same as (||).++{- |+ * A 'Consumer' is considered to have a 'ConsumptionProfile' composed from both a capacity to consume, & an ability to discriminate.++ * Whilst 'hasSpecificRequirement' & 'canConsumeAnything' look like opposites, they can assume independent values; the instance can be both, but only be neither when empty.+-}+data ConsumptionProfile = MkConsumptionProfile {+ consumptionBounds :: ConsumptionBounds.ConsumptionBounds, -- ^ The permissible quantity of data, which can be consumed.+ hasSpecificRequirement :: Bool, -- ^ Whether at least one specific input datum is required.+ canConsumeAnything :: Bool -- ^ Whether at least one arbitrary input datum can be consumed.+} deriving (Eq, Read, Show)++instance ToolShed.SelfValidate.SelfValidator ConsumptionProfile where+ getErrors c@(+ MkConsumptionProfile {+ consumptionBounds = (fewest, most),+ hasSpecificRequirement = hasSpecificRequirement',+ canConsumeAnything = canConsumeAnything'+ }+ ) = ToolShed.SelfValidate.extractErrors [+ (fewest < 0, "Negative fewest=" ++ show fewest ++ "."),+ (+ case most of+ Just m -> m < fewest+ _ -> False,+ "Invalid 'most'; " ++ show c ++ "."+ ),+ (not $ or [c == zero, hasSpecificRequirement', canConsumeAnything'], "Invalid " ++ show c ++ ".")+ ]++{-+-- | True if there's no choice in the quantity of data to consume.+isPrecise :: ConsumptionProfile -> Bool+isPrecise = ConsumptionBounds.isPrecise . consumptionBounds+-}++-- | Predicate, which is 'True' if the specified data-length, falls within the specified 'ConsumptionBounds.ConsumptionBounds'.+withinConsumptionBounds+ :: ConsumptionBounds.DataLength -- ^ The actual quantity of data consumed.+ -> ConsumptionProfile -- ^ The bounds within which data-consumption is required to fall.+ -> Bool+withinConsumptionBounds dataLength = uncurry (&&) . ((dataLength >=) *** Data.Maybe.maybe True (dataLength <=)) . consumptionBounds++-- | Both minimum & maximum set to zero; which can be used as the initial value when accumulating the sum of a list.+zero :: ConsumptionProfile+zero = MkConsumptionProfile {+ consumptionBounds = ConsumptionBounds.zero,+ hasSpecificRequirement = False,+ canConsumeAnything = False+}++-- | The net effect of two concatenated 'ConsumptionProfile's.+(|+|) :: ConsumptionProfile -> ConsumptionProfile -> ConsumptionProfile+MkConsumptionProfile {+ consumptionBounds = (lf, ls),+ hasSpecificRequirement = lh,+ canConsumeAnything = lc+} |+| MkConsumptionProfile {+ consumptionBounds = (rf, rs),+ hasSpecificRequirement = rh,+ canConsumeAnything = rc+} = MkConsumptionProfile {+ consumptionBounds = (lf + rf, (+) <$> ls <*> rs), -- The sum of those of the concatenation.+ hasSpecificRequirement = lh || rh, -- The concatenation mandates consumption of at least one specific input datum, if either 'ConsumptionProfile' does.+ canConsumeAnything = lc || rc -- The concatenation can consume at least one arbitrary input datum, if either 'ConsumptionProfile' can.+}++-- | The net effect of two alternative 'ConsumptionProfile's.+(<>) :: ConsumptionProfile -> ConsumptionProfile -> ConsumptionProfile+MkConsumptionProfile {+ consumptionBounds = (lf, ls),+ hasSpecificRequirement = lh,+ canConsumeAnything = lc+} <> MkConsumptionProfile {+ consumptionBounds = (rf, rs),+ hasSpecificRequirement = rh,+ canConsumeAnything = rc+} = MkConsumptionProfile {+ consumptionBounds = (lf `min` rf, max <$> ls <*> rs), -- Stretched to envelope alternatives.+ hasSpecificRequirement = lh && rh, -- The alternation mandates consumption of at least one specific input datum, if both 'ConsumptionProfile's do.+ canConsumeAnything = lc || rc -- The alternation can consume at least one arbitrary input datum, if either 'ConsumptionProfile' can.+}++-- | The aggregate of the specified concatenation of 'ConsumptionProfile's.+aggregateFromConcatenation :: [ConsumptionProfile] -> ConsumptionProfile+aggregateFromConcatenation = Data.List.foldl' (|+|) zero {-initial value-}++-- | The 'ConsumptionProfile's of all the aggregated tails of a list of 'Consumer's.+type AccumulatedConsumptionProfiles = [ConsumptionProfile]++-- | Accumulate the specified list of 'ConsumptionProfile's, from the specified initial value.+accumulateFrom :: ConsumptionProfile -> [ConsumptionProfile] -> AccumulatedConsumptionProfiles+accumulateFrom = scanr (|+|)+
+ src-lib/RegExDot/DSL.hs view
@@ -0,0 +1,117 @@+{-+ 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@]++ * Defines /binary/ operators, to form a /Domain-specific Language/, by which to compose 'RegEx.Concatenations' from 'RegEx.Pattern's.++ * Each operator quantifies the specified 'RegEx.Pattern', & prepends it to the specified 'RegEx.Concatenation'.++ [@TODO@] Could @[]@ & 'Meta' be instances of a type-class which has methods (-:) etc.,+ allowing the former to prepend 'RegEx.captureGroup' & the latter 'RegEx.Require' ?++-}++module RegExDot.DSL(+-- * Functions+-- ** Operators+ (-:),+ (?:),+ (??:),+ (*:),+ (*?:),+ (+:),+ (+?:),+ ( #->#:),+ ( #->#?:),+ ( #->:),+ ( #->?:),+ ( #:),+ (<~>)+) where++import RegExDot.Repeatable((^#->#), (^#->#?), (^#->), (^#->?), (^#))+import qualified RegExDot.Anchor as Anchor+import qualified RegExDot.RegEx as RegEx+import qualified RegExDot.Repeatable as Repeatable++infixr 5 -:, ?:, ??:, *:, *?:, +:, +?:,#->#:, #->#?:, #->:, #->?:, #:, <~> -- Same as for ':', & lower than Repeatable's operators.++-- | Prepend an unrepeated 'RegEx.Pattern', to the specified 'RegEx.Concatenation'.+(-:) :: RegEx.Pattern a -> RegEx.Concatenation a -> RegEx.Concatenation a+(-:) pattern = (Repeatable.one pattern :)++-- | Prepend an optional 'RegEx.Pattern', to the specified 'RegEx.Concatenation'.+(?:) :: RegEx.Pattern a -> RegEx.Concatenation a -> RegEx.Concatenation a+(?:) pattern = (Repeatable.zeroOrOne pattern :)++-- | A /non-greedy/ version of '?:'.+(??:) :: RegEx.Pattern a -> RegEx.Concatenation a -> RegEx.Concatenation a+(??:) pattern = (Repeatable.zeroOrOne' pattern :)++-- | Prepend a 'RegEx.Pattern', repeatable zero or more times, to the specified 'RegEx.Concatenation'.+(*:) :: RegEx.Pattern a -> RegEx.Concatenation a -> RegEx.Concatenation a+(*:) pattern = (Repeatable.zeroOrMore pattern :)++-- | A /non-greedy/ version of '*:'.+(*?:) :: RegEx.Pattern a -> RegEx.Concatenation a -> RegEx.Concatenation a+(*?:) pattern = (Repeatable.zeroOrMore' pattern :)++-- | Prepend a 'RegEx.Pattern', repeatable one or more times, to the specified 'RegEx.Concatenation'.+(+:) :: RegEx.Pattern a -> RegEx.Concatenation a -> RegEx.Concatenation a+(+:) pattern = (Repeatable.oneOrMore pattern :)++-- | A /non-greedy/ version of '+:'.+(+?:) :: RegEx.Pattern a -> RegEx.Concatenation a -> RegEx.Concatenation a+(+?:) pattern = (Repeatable.oneOrMore' pattern :)++-- | Prepend a 'RegEx.Pattern', repeated a range of times, to the specified 'RegEx.Concatenation'.+( #->#:) :: (RegEx.Pattern a, Repeatable.RepetitionBounds) -> RegEx.Concatenation a -> RegEx.Concatenation a+( #->#:) (pattern, bounds) = (pattern ^#-># bounds :)++-- | A /non-greedy/ version of '#->#:'.+( #->#?:) :: (RegEx.Pattern a, Repeatable.RepetitionBounds) -> RegEx.Concatenation a -> RegEx.Concatenation a+( #->#?:) (pattern, bounds) = (pattern ^#->#? bounds :)++-- | Prepend a 'RegEx.Pattern', repeated at least a specified number of times, to the specified 'RegEx.Concatenation'.+( #->:) :: (RegEx.Pattern a, Repeatable.Repetitions) -> RegEx.Concatenation a -> RegEx.Concatenation a+( #->:) (pattern, fewest) = (pattern ^#-> fewest :)++-- | A /non-greedy/ version of '#->:'.+( #->?:) :: (RegEx.Pattern a, Repeatable.Repetitions) -> RegEx.Concatenation a -> RegEx.Concatenation a+( #->?:) (pattern, fewest) = (pattern ^#->? fewest :)++-- | Prepend a 'RegEx.Pattern', repeated a precise number of times, to the specified 'RegEx.Concatenation'.+( #:) :: (RegEx.Pattern a, Repeatable.Repetitions) -> RegEx.Concatenation a -> RegEx.Concatenation a+( #:) (pattern, r) = (pattern ^# r :)++{- |+ * Sandwiches a 'RegEx.Concatenation' between optional 'Anchor.Anchor's to construct a 'RegEx.ExtendedRegEx'.++ * Accounts for the unusual requirement to place an 'Anchor.Stern' at the start of the 'RegEx.Concatenation', or an 'Anchor.Bow' at the end.+-}+(<~>)+ :: Anchor.AnchorSpecification -- ^ A specification for the anchors required around the expression.+ -> RegEx.Concatenation a -- ^ The bare list of repeatable patterns to anchor.+ -> RegEx.ExtendedRegEx a -- ^ The completed regex.+(bow, stern) <~> concatenation = RegEx.MkExtendedRegEx {+ RegEx.bowAnchor = bow,+ RegEx.concatenation = concatenation,+ RegEx.sternAnchor = stern+}
+ src-lib/RegExDot/DataSpan.hs view
@@ -0,0 +1,61 @@+{-+ 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@] Augments a 'Span.Span', with the specific data concerned.+-}++module RegExDot.DataSpan(+-- * Types+-- ** Type-synonyms+ DataSpan,+-- * Functions+ after,+ empty,+ join+) where++import Control.Arrow((***))+import qualified RegExDot.ConsumptionBounds as ConsumptionBounds+import qualified RegExDot.Span as Span++{- |+ * Augment 'Span.Span' with a copy of the data to which it refers.++ * Similar to <http://hackage.haskell.org/packages/archive/regex-base/latest/doc/html/Text-Regex-Base-RegexLike.html#t%3AMatchText>.+-}+type DataSpan a = ([a], Span.Span)++-- | Constructs an empty instance, located at the specified offset.+empty :: ConsumptionBounds.DataLength -> DataSpan a+empty offset = ([], Span.empty offset)++-- | Returns the furthest extent, i.e. the offset just after the end.+after :: DataSpan a -> ConsumptionBounds.DataLength+after = Span.after . snd++{- |+ * Condenses a list into a single value.++ * Expects a gap-free, sorted list.+-}+join+ :: ConsumptionBounds.DataLength -- ^ The offset at which the concatenated match is considered to have occurred.+ -> [DataSpan a] -- ^ The list to be concatenated.+ -> DataSpan a+join offset = (concat *** Span.join offset) . unzip
+ src-lib/RegExDot/DataSpanTree.hs view
@@ -0,0 +1,108 @@+{-+ 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@] Permits transformation of 'RegEx.MatchList', to facilitate standardisation.+-}++module RegExDot.DataSpanTree(+-- * Types+-- ** Type-synonyms+-- DataSpanTree,+-- DataSpanTreeList,+-- * Functions+ extractCaptureGroups,+ flattenTreeList,+ toTreeList+) where++import qualified Data.Foldable+import qualified RegExDot.ConsumptionBounds as ConsumptionBounds+import qualified RegExDot.DataSpan as DataSpan+import qualified RegExDot.RegEx as RegEx+import qualified RegExDot.Tree as Tree++-- | Defines a similar 'Tree.Tree' to 'RegEx.Match', but with different 'Tree.Leaf's.+type DataSpanTree a = Tree.Tree (DataSpan.DataSpan a)++-- | Defines a similar structure to 'RegEx.MatchList'.+type DataSpanTreeList a = [DataSpanTree a]++-- | Converts a 'RegEx.MatchList' into a 'DataSpanTreeList', by transforming the 'Tree.Leaf's.+toTreeList :: RegEx.MatchList a -> DataSpanTreeList a+toTreeList = map toTree where+ toTree :: RegEx.Match a -> DataSpanTree a+ toTree = fmap toDataSpan where+ toDataSpan :: RegEx.MatchedData a -> DataSpan.DataSpan a+ toDataSpan (_, inputDataOffset, inputData) = (inputData, (inputDataOffset, length inputData))++-- | Condenses a 'DataSpanTreeList's into a list of 'DataSpan.DataSpan's, using 'DataSpan.join'.+flattenTreeList+ :: ConsumptionBounds.DataLength -- ^ The offset into the input-data at which a match occurred.+ -> DataSpanTreeList a -- ^ The tree to flatten.+ -> [DataSpan.DataSpan a]+flattenTreeList _ [] = []+flattenTreeList offset (tree : treeList) = flattenedTree : flattenTreeList (DataSpan.after flattenedTree) treeList where+-- flattenedTree :: DataSpan.DataSpan a+ flattenedTree = DataSpan.join offset $ Data.Foldable.toList tree++{- |+ * POSIX describes the contents of /capture-groups/, as summarised in <http://www2.research.att.com/~gsf/testregex/>.++ * 'RegEx.Result', is a complete description of the match between 'RegEx.InputData' & RegEx.ExtendedRegEx'; this function extracts a POSIX-conformant list from it.++ * The major differences are, that:++ Only data from parenthesized sub-expressions ('RegEx.Alternatives') is captured.++ Only the /last/ repetition of a repeated sub-expression is returned.+ <http://www.opengroup.org/onlinepubs/009695399/functions/regcomp.html>.++ The data captured within each parenthesized sub-expression, is summarised as a single 'DataSpan.DataSpan'.++ POSIX specifies a 'Span.Span'-offset of @-1@, for sub-expressions which match zero times; cf sub-expressions which consume nothing, once.+ <http://www.opengroup.org/onlinepubs/009695399/functions/regcomp.html>.+ @+ ("ace" Text.Regex.Posix.=~ "a(b)*c(d)?e") :: Text.Regex.Base.RegexLike.MatchArray+ array (0,2) [(0,(0,3)),(1,(-1,0)),(2,(-1,0))]++ ("ace" Text.Regex.Posix.=~ "a(b*)c(d?)e") :: Text.Regex.Base.RegexLike.MatchArray+ array (0,2) [(0,(0,3)),(1,(1,0)),(2,(2,0))]+ @+ I consider this a poor convention, resulting from the focus of POSIX on C, which makes subsequent calculation from the list of 'DataSpan's difficult & error-prone.+-}+extractCaptureGroups+ :: Bool -- ^ Whether to strictly comply with /POSIX/.+ -> DataSpanTreeList a -- ^ The tree-structure from which to extract the capture-groups.+ -> [DataSpan.DataSpan a]+extractCaptureGroups complyStrictlyWithPosix = extractCaptureGroups' 0 where+ extractCaptureGroups' :: ConsumptionBounds.DataLength -> DataSpanTreeList a -> [DataSpan.DataSpan a]+ extractCaptureGroups' _ [] = []+ extractCaptureGroups' offset (tree : treeList) = let+-- recurseHorizontallyFrom :: ConsumptionBounds.DataLength -> [DataSpan.DataSpan a]+ recurseHorizontallyFrom = (`extractCaptureGroups'` treeList)+ in case tree of+ Tree.Leaf dataSpan -> recurseHorizontallyFrom $ DataSpan.after dataSpan+ Tree.Node [] -> DataSpan.empty (if complyStrictlyWithPosix then -1 else offset) : recurseHorizontallyFrom offset -- POSIX specifies an Span-offset of -1, for sub-expressions which match 0 times; cf sub-expressions which consumes nothing, once.+ Tree.Node treeLists -> joinedFlattenedTreeList : (extractCaptureGroups' offset lastMatch {-recurse vertically-} ++ recurseHorizontallyFrom (DataSpan.after joinedFlattenedTreeList)) where+-- lastMatch :: DataSpanTreeList a+ lastMatch = last treeLists -- <http://www.opengroup.org/onlinepubs/009695399/functions/regcomp.html>.++-- joinedFlattenedTreeList :: DataSpan.DataSpan a+ joinedFlattenedTreeList = DataSpan.join offset $ flattenTreeList offset lastMatch+
+ src-lib/RegExDot/ExecutionOptions.hs view
@@ -0,0 +1,102 @@+{-+ 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@]++ * Defines those options relating to the "RegExDot.RegEx"-runtime.++ * These tend to be options which don't affect the result, only the means by which it is achieved.++ * Whilst similar structures are present in other regex-implementations, there's no standardisation of the fields.+-}++module RegExDot.ExecutionOptions(+-- * Types+-- ** Data-types+ ExecutionOptions(..),+-- * Functions+-- ** Mutators+ setVerbose+) where++import qualified ToolShed.Defaultable+import qualified ToolShed.Options++-- | The switches used to control execution of the /regex/-engine.+data ExecutionOptions = MkExecutionOptions {+ abortTrialRepetitionsOnInherentFailure :: Bool, -- ^ If an /alternative/ can't match, irrespective of the subsequent /RegExDot.RegEx.Concatenation/, then avoid futile trial /RegExDot.Repeatable.Repetitions/. The converse of 'checkForUnconsumableData'.+ abortTrialRepetitionsOnZeroConsumption :: Bool, -- ^ Check for zero data-consumption by the @n@-th /RegExDot.Repeatable.Repeatable RegExDot.RegEx.CaptureGroup/, before attempting @n+1@.+ bypassInputDataForLiberalConsumer :: Bool, -- ^ Whether to bypass reading of the input data, if the mapping to specific /RegExDot.RegEx.RepeatablePattern/s isn't required, & the /RegExDot.RegEx.ExtendedRegEx/ can consume the required quantity of anything.+ catchIncompatibleAnchors :: Bool, -- ^ Avoid futile trial solutions, involving repetitions of anchored alternatives, which must consume data.+ checkExistenceOfInelasticTail :: Bool, -- ^ If the /RegExDot.RegEx.ExtendedRegEx/ ends in an inelastic (zero /Star-height/) tail, confirm its existence at the end of the /RegExDot.RegEx.InputData/.+ checkForUnconsumableData :: Bool, -- ^ Check whether there's no possibility of consuming some of the input data. The converse of 'abortTrialRepetitionsOnInherentFailure'.+ moderateGreed :: Bool, -- ^ Greedily consume data, only up to the limit beyond which, future requirements would be compromised.+ permitReorderingOfAlternatives :: Bool, -- ^ Permit /RegExDot.RegEx.Alternatives/ to be re-ordered, in an attempt to more quickly locate a result.+ preferAlternativesWhichFeedTheGreedy :: Bool, -- ^ Within the /RegExDot.RegEx.MatchedData/ from which each candidate /RegExDot.RegEx.Match/ amongst sequences of /RegExDot.RegEx.Alternatives/, is ultimately composed, prefer /RegExDot.ConsumptionBounds.ConsumptionBounds/ of /RegExDot.RegEx.InputData/, beyond /RegExDot.Repeatable.getFewest/, by /RegExDot.Repeatable.isGreedy RegExDot.RegEx.RepeatablePattern/s.+ preferAlternativesWhichMimickUnrolling :: Bool, -- ^ Compare /RegExDot.ConsumptionBounds.ConsumptionBounds/ on successive /RegExDot.Repeatable.Repetitions/ of /RegExDot.RegEx.CaptureGroup/, between candidate /RegExDot.RegEx.Match/es, to mimic the behaviour of the unrolled /RegExDot.Repeatable.Repetitions/.+ preferFewerRepeatedAlternatives :: Bool, -- ^ Prefer fewer /RegExDot.Repeatable.Repetitions/ of /RegExDot.RegEx.Alternatives/, to discourage the capture of null lists of /RegExDot.RegEx.InputData/.+ requireMatchList :: Bool, -- ^ If merely interested in a 'Bool' result, rather than the optimal mapping of input data to /RegExDot.RegEx.RepeatablePattern/s, avoid unnecessary evaluation of the /RegEx.Match/.+ unrollRepeatedSingletonAlternative :: Bool, -- ^ Check whether /RegExDot.RegEx.Alternatives/ consists of just a singleton /RegExDot.RegEx.ExtendedRegEx/, & has therefore been used merely as a capture-group. Though this doesn't affect the result, it vastly improves efficiency.+ useFirstMatchAmongAlternatives :: Bool, -- ^ Rather than performing an exhaustive search for the optimal choice amongst /RegExDot.RegEx.Alternatives/, merely select the first that matches; conform to /Perl/ rather than /POSIX/.+ validateMinConsumptionOfAlternatives :: Bool -- ^ When the number of repetitions of a /RegExDot.RegEx.CaptureGroup/ is precisely specified, check whether the resulting minimum data-requirement is available.+} deriving (Eq, Show)++instance ToolShed.Defaultable.Defaultable ExecutionOptions where+ defaultValue = setVerbose False $ ToolShed.Options.blankValue {+ abortTrialRepetitionsOnInherentFailure = True, -- Regrettably, this slightly reduces performance for most non-pathological patterns.+ catchIncompatibleAnchors = True,+ checkExistenceOfInelasticTail = True,+ checkForUnconsumableData = True, -- Expensive, particularly when (not requireMatchList), & only typically useful in failure-scenarios.+ moderateGreed = True, -- Cost may exceed benefit. TODO: confirm.+ preferAlternativesWhichFeedTheGreedy = True,+ preferAlternativesWhichMimickUnrolling = True,+ preferFewerRepeatedAlternatives = True,+ unrollRepeatedSingletonAlternative = True, -- Affects only efficiency, not the result.+ useFirstMatchAmongAlternatives = False, -- Perl-style matching may be faster, but may also yield a sub-optimal Match.+ validateMinConsumptionOfAlternatives = False -- The cost outweighs the small infrequent dividend.+ }++instance ToolShed.Options.Options ExecutionOptions where+ blankValue = MkExecutionOptions {+ abortTrialRepetitionsOnInherentFailure = undefined,+ abortTrialRepetitionsOnZeroConsumption = undefined,+ bypassInputDataForLiberalConsumer = undefined,+ catchIncompatibleAnchors = undefined,+ checkExistenceOfInelasticTail = undefined,+ checkForUnconsumableData = undefined,+ moderateGreed = undefined,+ permitReorderingOfAlternatives = undefined,+ preferAlternativesWhichFeedTheGreedy = undefined,+ preferAlternativesWhichMimickUnrolling = undefined,+ preferFewerRepeatedAlternatives = undefined,+ requireMatchList = undefined,+ unrollRepeatedSingletonAlternative = undefined,+ useFirstMatchAmongAlternatives = undefined,+ validateMinConsumptionOfAlternatives = undefined+ }++-- | Sets those fields which depend crucially on whether the caller wants to retrieve any /RegExDot.RegEx.MatchList/ from the /RegExDot.RegEx.Result/, or just query whether there is one.+setVerbose :: Bool -> ExecutionOptions -> ExecutionOptions+setVerbose verbose e = e {+ abortTrialRepetitionsOnZeroConsumption = verbose, -- The corresponding check, involves evaluation of a /RegExDot.RegEx.MatchList/, which is too expensive if the /RegExDot.RegEx.Matchlist/ isn't otherwise required.+ bypassInputDataForLiberalConsumer = not verbose, -- Potentially bypasses reading of /RegExDot.RegEx.InputData/, which is inappropriate if the mapping into a /RegExDot.RegEx.Result/ is required.+ permitReorderingOfAlternatives = not verbose, -- Doesn't help when 'requireMatchList', since an exhaustive search of /RegExDot.RegEx.Alternatives/, for the optimal solution, is performed.+ requireMatchList = verbose+}+
+ src-lib/RegExDot/InstanceInt.hs view
@@ -0,0 +1,43 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+{-+ 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@] Provides a default implementation of the type-classes, required for 'RegEx.ExtendedRegEx' Int.++ [@CAVEATS@]++ In contrast to the traditional regex, there's no reason to escape a 'Char', other than when referring to a /shortcut/.+ Since no /shortcuts/ are defined in this minimal definition, it's always an error to escape a character.+-}++module RegExDot.InstanceInt() where++import qualified RegExDot.BracketExpressionMember as BracketExpressionMember+import qualified RegExDot.Meta as Meta+import qualified RegExDot.RegEx as RegEx++instance BracketExpressionMember.ShortcutExpander Int where+ findPredicate _ = Nothing++instance Meta.ShortcutExpander Int where+ expand c = error $ "RegExDot.Meta.ShortcutExpander.expand RegExDot.InstanceInt:\tunrecognised shortcut '" ++ show c ++ "'." -- A shortcut is the only legitimate reason to escape a character.++instance RegEx.ShortcutExpander Int where+ expand c = error $ "RegExDot.RegEx.ShortcutExpander.expand RegExDot.InstanceInt:\tunrecognised shortcut '" ++ show c ++ "'." -- A shortcut is the only legitimate reason to escape a character.+
+ src-lib/RegExDot/Meta.hs view
@@ -0,0 +1,144 @@+{-+ 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@]++ * Describes the set of polymorphic data, which can be matched.++ * Only permits a match against exactly one polymorphic datum, which distinguishes it from a /zero-width assertion/, like an /anchor/, /word-boundary/, or /look-ahead assertion/.++ * Designed to be used by a polymorphic /regex/-engine, to implement the traditional meta-characters; @. [] [^]@.++ * Permits /Perl-style shortcuts/ for commonly used 'Meta'-data, to be canned & assigned a single-'Char' mnemonic for subsequent reference;+ the implementation of 'Read' looks for a back-slashed 'Char', for which it expects there to be a corresponding canned 'Meta'.+ Since this class is polymorphic, it has no knowledge of what shortcuts might be appropriate for the chosen type-parameter,+ so the expansion from the back-slashed 'Char' to corresponding 'Meta'-data, is performed through the 'expand' interface of the 'ShortcutExpander' class, which should be implemented elsewhere.+-}++module RegExDot.Meta(+-- * Type-classes+ ShortcutExpander(..),+-- * Types+-- ** Data-types+ Meta(..),+-- * Constants+ shortcutToken,+ anyToken,+ tokens,+-- * Functions+-- ** Predicates+ isMatch+) where++import qualified Control.Arrow+import qualified Control.DeepSeq+import qualified RegExDot.BracketExpression as BracketExpression+import qualified RegExDot.BracketExpressionMember as BracketExpressionMember+import qualified RegExDot.Consumer as Consumer+import qualified RegExDot.ConsumptionProfile as ConsumptionProfile+import qualified RegExDot.ShowablePredicate as ShowablePredicate+import qualified ToolShed.SelfValidate++{- |+ * The interface via which /Perl-style shortcut/s are expanded, in a manner appropriate to the chosen type-parameter.++ * Since the expansion of /Perl-style shortcut/s, is more restricted inside than outside a 'BracketExpression.BracketExpression',+ the former is considered to be a superclass, providing a base from which to build alternative implementations.+-}+class BracketExpressionMember.ShortcutExpander m => ShortcutExpander m where+ expand :: Char -> Meta m -- ^ Expand a /Perl-style shortcut/.++-- | Declares a polymorphic data-type.+data Meta m =+ Any -- ^ Any datum matches. Equivalent to @NoneOf []@, but more efficient. CAVEAT: independent of the type-parameter @a@.+ | Literal m -- ^ The datum matches, if it's equal to the specified value. Equivalent to @AnyOf [BracketExpression.Literal x]@, but more efficient.+ | AnyOf (BracketExpression.BracketExpression m) -- ^ The datum matches, if 'BracketExpression.containsMatch'.+ | NoneOf (BracketExpression.BracketExpression m) -- ^ The datum matches, if @not BracketExpression.containsMatch@.+ | Predicate (ShowablePredicate.ShowablePredicate m) -- ^ The datum matches if 'ShowablePredicate.ShowablePredicate'.+ deriving (+ Eq+-- Read, -- Specialised below.+-- Show -- Specialised below.+ )++instance ToolShed.SelfValidate.SelfValidator (Meta m) where+ getErrors _ = []++instance Show m => Show (Meta m) where+ showsPrec _ Any = showChar anyToken+ showsPrec _ (Literal m) = shows m+ showsPrec _ (AnyOf bracketExpression) = shows bracketExpression+ showsPrec _ (NoneOf bracketExpression) = showChar x . showChar BracketExpression.negationToken . showString xs where (x : xs) = show $ AnyOf bracketExpression+ showsPrec _ (Predicate showablePredicate) = shows showablePredicate++instance (ShortcutExpander m, Read m) => Read (Meta m) where+ readsPrec _ [] = [] -- No parse.+ readsPrec _ (' ' : s) = reads s -- Consume white-space.+ readsPrec _ ('\t' : s) = reads s -- Consume white-space.+ readsPrec _ ('.' : s) = [(Any, s)]+ readsPrec _ ('[' : '^' : noneOf) = Control.Arrow.first NoneOf `map` reads (fst BracketExpression.delimiterTokens : noneOf) {-Reconstruct without negation, & recurse-}+ readsPrec _ anyOf@('[' : _) = Control.Arrow.first AnyOf `map` reads anyOf {-singleton-}+ readsPrec _ ('\\' : c : s) = [(expand c, s)]+ readsPrec _ literal = Control.Arrow.first Literal `map` reads literal {-singleton-}++instance Consumer.Consumer (Meta m) where+ consumptionProfile meta = let+ hasSpecificDataRequirement :: Bool+ hasSpecificDataRequirement = case meta of+ Any -> False+ NoneOf [] -> False+ _ -> True+ in ConsumptionProfile.MkConsumptionProfile {+ ConsumptionProfile.consumptionBounds = (1, Just 1),+ ConsumptionProfile.hasSpecificRequirement = hasSpecificDataRequirement,+ ConsumptionProfile.canConsumeAnything = not hasSpecificDataRequirement+ }++ starHeight _ = 0++instance Control.DeepSeq.NFData m => Control.DeepSeq.NFData (Meta m) where+ rnf Any = ()+ rnf (Literal m) = Control.DeepSeq.rnf m+ rnf (AnyOf bracketExpression) = Control.DeepSeq.rnf bracketExpression+ rnf (NoneOf bracketExpression) = Control.DeepSeq.rnf bracketExpression+ rnf (Predicate showablePredicate) = Control.DeepSeq.rnf showablePredicate++-- | True if the specified datum matches.+isMatch :: Eq m+ => m -- ^ The input datum.+ -> Meta m -- ^ The meta-entity against which the input datum is to be matched.+ -> Bool -- ^ The result of the match-operation.+isMatch _ Any = True+isMatch datum (Literal literal) = datum == literal+isMatch datum (AnyOf bracketExpression) = datum `BracketExpression.containsMatch` bracketExpression+isMatch datum (NoneOf bracketExpression) = not $ datum `isMatch` AnyOf bracketExpression -- This implementation leverages future enhancements to 'AnyOf'.+isMatch datum (Predicate showablePredicate) = ShowablePredicate.predicate showablePredicate datum++-- | The token used to precede a /Perl-style shortcut/, when in the 'String'-form.+shortcutToken :: Char+shortcutToken = '\\'++-- | The token used to denote 'Any', when in the 'String'-form.+anyToken :: Char+anyToken = '.'++-- | The set of 'Char' to which a specific meaning is attributed, when reading from 'String'.+tokens :: String+tokens = [shortcutToken, anyToken] ++ BracketExpression.tokens+
+ src-lib/RegExDot/RegEx.hs view
@@ -0,0 +1,1198 @@+{-+ 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@]++ * This implementation of extended /regex/es, generalises the familiar concept of pattern-matching of character-strings, to matching lists composed from an arbitrary data-type.+ The polymorphic data, from which the input data-list is composed, need only support @Eq@.++ * Because of the unknown stringified form of the underlying polymorphic data,+ the /regex/ must be described by a comparatively verbose bracketed & comma-separated list, rather than the traditional /String/ containing Meta-characters.+ Each element of this 'Concatenation' is a 'RepeatablePattern', which describes a permissible match against 'InputData'.++ * 'RepeatablePattern' can take one of two forms.+ In the simplest case, it matches just a single item of the underlying polymorphic type, perhaps literally, though looser specifications also exist:+ @.@ matches any input datum; @[x, y, z]@ matches any of @x@, @y@, or @z@; @[^x, y, z]@ matches anything but @x@, @y@, or @z@.+ To support POSIX /ERE/s, 'RepeatablePattern' can also be a list 'Alternatives', each of which is recursively defined as an 'ExtendedRegEx', to form a tree-structure.++ * Each 'Pattern', can optionally be /quantified/ by either a traditional /greedy/, or a /Perl/-style /non-greedy/, suffix, e.g.; @[*, +, ?, {n, m}, {n,}, {n}, *?, +?, ??, {n, m}?, {n,}?]@.++ * For convenience, common specifications can be canned & assigned a single 'Char' mnemonic, for subsequent reference.+ Since 'ExtendedRegEx' is polymorphic, the set of abbreviations appropriate in the context of the unspecified base-type,+ must be implemented externally through the 'Meta.ShortcutExpander' interface.+ This permits the use, when the type-parameter is 'Char', of /Perl-style shortcuts/ @[\\d\\D\\s\\S\\w\\W]@.++ * The algorithm, is the classic /back-tracking/ one, rather than either a /DFA/ or /NFA/.+ This permits construction of 'Result' via which one can discover the deep mapping of 'InputData' into 'ExtendedRegEx',+ & provides the flexibility to add the features now expected by modern /regex/-engines.+ Since the type-parameter is unknown, & may represent a large object, the exponential space-complexity of creating a /DFA/ may present additional problems.+ The exponential time-complexity of the /back-tracking/ algorithm is partially tamed by targeting obvious inefficiencies with specific optimisations.++ * Char-based regexen, traditionally overload the delimiters of a set of 'Alternatives' (parentheses), as a request for data-capture.+ Here, in contrast, all 'RepeatablePattern's capture data, & repeated sub-expressions capture a list of data,+ rather than arbitrarily recording just the last (<http://www.opengroup.org/onlinepubs/009695399/functions/regcomp.html>) item.++ [@REFERENCES@]++ * <http://en.wikipedia.org/wiki/Regular_expression>++ * <http://swtch.com/~rsc/regexp/regexp1.html>++ * <http://docstore.mik.ua/orelly/perl/prog/ch02_04.htm#PERL2-CH-2-SECT-4.1.2>++ * <http://www.macs.hw.ac.uk/~dsg/gph/papers/html/Strategies/strategies.html>++ * <http://hackage.haskell.org/packages/archive/regex-posix/latest/doc/html/Text-Regex-Posix.html>++ * <http://www.haskell.org/haskellwiki/Regular_expressions>++ * <http://www2.research.att.com/~gsf/testregex/re-interpretation.html>++ [@CAVEATS@]++ * Because of the definition of mutually recursive data-types, it is difficult to split this annoyingly large module, & preserve compatibility across compilers,+ but it may be possible to break this cyclic dependency, by defining an interface to which one of the data-types defined here conforms.++ * Doesn't implement /Back-references/, making the definition of the 'ExtendedRegEx' context-free.++ * There's no integration with the type-classes defined in "Text.Regex.Base.RegexLike", which assumes 'Char'-based 'InputData';+ though this could be added to a specialised instance.++ * When 'Alternatives' are defined, 'Result' becomes a tree-like structure.+ Unless the alternative is a singleton, the specific alternative selected in the solution is typically unknown, & therefore the /structure/ of the branch of this tree is also unknown.+ This lack of clarity is compounded when the 'Alternatives' are 'Repeatable.Repeatable', since a different one may be selected on each successive repetition.+ Consequently, the user can't navigate this portion of the structure in a statically defined manner, to acquire the captured data.+ Despite this, & in contrast to other /regex/-engines, access to the whole data-structure is available, since it doesn't seem advantage to hide it.+ The user can then either use 'extractDataFromMatch' for that element of 'Result', thus aggregating the data from sections of unknown structure, or 'show' it, as an aid to debugging.++ [@TODO@]++ * Test parallel-operation, on a 3 or more processor machine.+ If 'rnf' is less effective than 'rwhnf',+ then the 'Control.DeepSeq.NFData' context can be removed,+ reducing the requirements imposed on the type-parameter 'a'.++ * Try 'Data.List.Stream' (stream-fusion), a faster drop-in replacement for 'Data.List'; possibly integrated in GHC-6.12.++ * 'ExecutionOptions.bypassInputDataForLiberalConsumer' is too restrictive.+ More generally, we can test whether the set of different 'a' in 'InputData', is a subset of those common to all remaining terms in the 'ExtendedRegEx'.+ Using this rule, we can infer @"aaa ..." =~ MkExtendedRegEx [a,a+,a?,[ab]{2,3}]@, given compatible 'ConsumptionProfile.consumptionBounds'.++ * Nested repetitions, where nothing has been added to the expression, result in repeated trials of the same expression,+ e.g.; @"(x{i,}){j,}"@ results in the same expansion for @(i, j) in [(2, 3), (3, 2), (6, 1), (1, 6)]@.+ The resulting 'MatchList' may be different, but if the first such trial fails, so will all the remainder.++ * Should cope with empty sets of 'Alternatives' & zero repetitions, neither of which can ever match, but the wider pattern can, e.g. @(()|x{0}|y)@.++ * By removing 'RepeatablePattern' from 'Match', it can be isolated in a new module.+ This would result in a significant loss of discoverability.++ * Expand repeated 'Anchor.Bow' with @fewest - 1@ null matches followed by recursive 'findMatch'-call with @repetitions = 1@.+-}++module RegExDot.RegEx(+-- * Type-classes+ ShortcutExpander(..),+-- * Types+-- ** Type-synonyms+-- BasicRegEx,+ Concatenation,+ ExternalMatch,+ InputData,+ MatchedData,+ MatchList,+-- MetaDataList,+ RepeatablePattern,+-- Transformation,+-- ** Data-types+ Alternatives(..),+ Match,+ ExtendedRegEx(..),+ Pattern(..),+ Result,+-- * Constants+ alternativeExtendedRegExSeparatorToken,+-- anyDatum,+ captureGroupDelimiters,+ tokens,+-- * Functions+-- accumulateDistinctInputData,+-- accumulateDistinctMetaDataFrom,+ dock,+-- drift,+-- findMatch,+ captureGroup,+-- mkNullMatchFromConcatenation,+-- mkNullMatchFromExtendedRegEx,+-- mkNullMatchFromRepeatablePattern,+-- safeReciprocal,+-- shiftMatchedData,+-- shiftMatch,+ shiftMatchList,+ showsMaybeAnchor,+ simply,+ transformExtendedRegEx,+-- ** Accessors (Deconstructors)+ deconstructAlternatives,+-- getInputData,+-- ** Operators+ (+~),+ (=~),+ (/~),+ (.*),+ (.*?),+-- (/+)+-- ** Predicates+-- hasBowAnchor,+-- hasSternAnchor,+ isDefined,+ isCaptureGroup,+ isSingletonAlternatives,+-- isUnconsumableByAnyOf,+-- ** Query+-- getDistinctMetaDataFromAlternatives,+-- getDistinctMetaDataFromConcatenation,+-- getDistinctMetaDataFromExtendedRegEx,+-- getDistinctMetaDataFromPattern,+-- getDistinctMetaDataFromRepeatablePattern,+ externalMatchLength,+ extractDataFromMatch,+ extractDataFromMatch',+ extractDataFromMatchList+) where++import Control.Arrow((&&&))+import qualified Control.Arrow+import qualified Control.DeepSeq+import qualified Control.Parallel.Strategies+import qualified Data.Char+import qualified Data.Foldable+import qualified Data.List+import qualified Data.Maybe+import qualified Data.Ord+import qualified RegExDot.Anchor as Anchor+import qualified RegExDot.Consumer as Consumer+import qualified RegExDot.ConsumptionBounds as ConsumptionBounds+import qualified RegExDot.ConsumptionProfile as ConsumptionProfile+import qualified RegExDot.ExecutionOptions as ExecutionOptions+import qualified RegExDot.Meta as Meta+import qualified RegExDot.RegExOpts as RegExOpts+import qualified RegExDot.Repeatable as Repeatable+import RegExDot.Repeatable((^#->))+import qualified RegExDot.Tree as Tree+import qualified ToolShed.Data.List+import qualified ToolShed.Data.List.Splits+import qualified ToolShed.SelfValidate++infix 4 +~, =~, /~ -- Same as (==) & (/=).++-- | The type of a /regex/, in which there's no provision for either 'Alternatives' or 'Anchor.Anchor's.+type BasicRegEx m = [Repeatable.Repeatable (Meta.Meta m)]++{- |+ * Defines the method required to expand a mnemonic into an 'ExtendedRegEx'.++ * CAVEAT: this interface must be declared locally, since it references 'ExtendedRegEx', & 'ExtendedRegEx' references it.+-}+class ShortcutExpander m where+ expand :: Char -> ExtendedRegEx m -- ^ Expand a single-'Char' mnemonic into the corresponding 'ExtendedRegEx'.++{- |+ * Represents the /alternation/ feature of 'ExtendedRegEx's.++ * One could amalgamate this with 'Pattern', since it seems to exist merely as a peg to hang /instance/-declarations from.+-}+newtype Alternatives m = MkAlternatives [ExtendedRegEx m] deriving Eq++-- | Accessor, to expose the guts.+deconstructAlternatives :: Alternatives m -> [ExtendedRegEx m]+deconstructAlternatives (MkAlternatives extendedRegExList) = extendedRegExList++-- | Similar to 'fmap', but operates on ['ExtendedRegEx'], rather than just @a@.+transformAlternatives+ :: ([ExtendedRegEx m] -> [ExtendedRegEx m]) -- ^ The transformation function to apply to the list of data behind the constructor.+ -> Alternatives m+ -> Alternatives m+transformAlternatives f = MkAlternatives . f . deconstructAlternatives++instance (+ ShortcutExpander m,+ Meta.ShortcutExpander m,+ Eq m,+ Read m+ ) => Read (Alternatives m) where+ readsPrec _ (' ' : s) = reads s -- Consume white-space.+ readsPrec _ ('\t' : s) = reads s -- Consume white-space.+ readsPrec _ s = case reads s of+ [(extendedRegEx, s1)] -> case dropWhile Data.Char.isSpace s1 of+ ('|' : s2) -> Control.Arrow.first (transformAlternatives (extendedRegEx :)) `map` reads s2 {-singleton-}+ _ -> [(MkAlternatives [extendedRegEx], s1)]+ _ -> [] -- No parse.++instance Show m => Show (Alternatives m) where+ showsPrec _ = foldl (.) (showString "") . Data.List.intersperse (showChar alternativeExtendedRegExSeparatorToken) . map shows . deconstructAlternatives -- Replace the default list-format, with 'egrep'-syntax.++instance Consumer.Consumer (Alternatives m) where+ consumptionProfile = Consumer.aggregateConsumptionProfilesFromAlternatives . deconstructAlternatives+ starHeight = Consumer.starHeight . deconstructAlternatives -- Must evaluate all Alternatives to determine best.++instance ToolShed.SelfValidate.SelfValidator (Alternatives m) where+ getErrors = ToolShed.SelfValidate.getErrors . deconstructAlternatives++instance Control.DeepSeq.NFData m => Control.DeepSeq.NFData (Alternatives m) where+ rnf = Control.DeepSeq.rnf . deconstructAlternatives++-- | 'Alternatives' can be employed as a simple /capture-group/ as well as a switch, under which circumstances there's no choice amongst multiple 'Alternatives'.+isSingletonAlternatives :: Alternatives m -> Bool+isSingletonAlternatives = (== 1) . length . deconstructAlternatives++-- | A set of 'Meta.Meta' which can be consumed.+type MetaDataList m = [Meta.Meta m]++{- |+ * 'True' if there's no possibility, that the specified input datum, can be consumed by any of the specified 'MetaDataList'.++ * Because data may not be consumed, perhaps because of unsuitable 'ConsumptionBounds.ConsumptionBounds', the converse doesn't hold,+ i.e. @not isUnconsumableByAnyOf@ /doesn't/ imply that the input datum can be consumed, just that it hasn't been proven that it can't.+-}+isUnconsumableByAnyOf :: Eq m+ => m -- ^ The input datum.+ -> MetaDataList m -- ^ The list of meta-data against any of which a match can occur.+ -> Bool+isUnconsumableByAnyOf i = not . any (Meta.isMatch i)++-- | The set of distinct 'Meta.Meta', in the specified 'Alternatives'.+getDistinctMetaDataFromAlternatives :: Eq m => Alternatives m -> MetaDataList m+getDistinctMetaDataFromAlternatives = foldr (Data.List.union . getDistinctMetaDataFromExtendedRegEx) [] . deconstructAlternatives++-- | Defines either a simple 'Meta.Meta', which can match exactly one datum, or a set of 'Alternatives', each of which is recursively defined above, as an 'ExtendedRegEx'.+data Pattern m =+ Require (Meta.Meta m) -- ^ Describes a requirement for a simple scalar datum of the polymorphic type.+ | CaptureGroup (Alternatives m) -- ^ A sub-expression containing a selection of recursively defined alternatives, thus forming a tree-structure.+ deriving Eq++instance (+ Eq m,+ Meta.ShortcutExpander m,+ Read m,+ ShortcutExpander m+ ) => Read (Pattern m) where+ readsPrec _ (' ' : s) = reads s -- Consume white-space.+ readsPrec _ ('\t' : s) = reads s -- Consume white-space.+ readsPrec _ ('(' : s) = case {-Alternatives.-} reads s of+ [(alternatives, s1)] -> case dropWhile Data.Char.isSpace s1 of+ (')' : s2) -> [(CaptureGroup alternatives, s2)]+ _ -> [] -- No parse.+ _ -> [] -- No parse.+ readsPrec _ s = case reads s of+ [pair] -> [Control.Arrow.first Require pair]+ _ -> [] -- No parse.++instance Show m => Show (Pattern m) where+ showsPrec _ (Require meta) = shows meta+ showsPrec _ (CaptureGroup alternatives) = showChar (fst captureGroupDelimiters) . shows alternatives . showChar (snd captureGroupDelimiters)++instance Consumer.Consumer (Pattern m) where+ consumptionProfile (Require meta) = Consumer.consumptionProfile meta+ consumptionProfile (CaptureGroup alternatives) = Consumer.consumptionProfile alternatives++ starHeight (Require meta) = Consumer.starHeight meta+ starHeight (CaptureGroup alternatives) = Consumer.starHeight alternatives++instance ToolShed.SelfValidate.SelfValidator (Pattern m) where+ getErrors (Require meta) = ToolShed.SelfValidate.getErrors meta+ getErrors (CaptureGroup alternatives) = ToolShed.SelfValidate.getErrors alternatives++instance Control.DeepSeq.NFData m => Control.DeepSeq.NFData (Pattern m) where+ rnf (Require meta) = Control.DeepSeq.rnf meta+ rnf (CaptureGroup alternatives) = Control.DeepSeq.rnf alternatives++-- | Convenience-function to build a 'CaptureGroup' from a list of alternative 'ExtendedRegEx's.+captureGroup :: [ExtendedRegEx m] -> Pattern m+captureGroup = CaptureGroup . MkAlternatives++-- | True if the 'Pattern' was constructed via 'CaptureGroup'.+isCaptureGroup :: Pattern m -> Bool+isCaptureGroup (CaptureGroup _) = True+isCaptureGroup _ = False++-- | The set of distinct 'Meta.Meta', in the specified 'Pattern'.+getDistinctMetaDataFromPattern :: Eq m => Pattern m -> MetaDataList m+getDistinctMetaDataFromPattern pattern = case pattern of+ Require meta -> [meta]+ CaptureGroup alternatives -> getDistinctMetaDataFromAlternatives alternatives++-- | Constant pattern, representing a lax 'Require'ment.+anyDatum :: Pattern a+anyDatum = Require Meta.Any++-- | Make 'Pattern's, 'Repeatable.Repeatable'.+type RepeatablePattern m = Repeatable.Repeatable (Pattern m)++-- | Construct a null 'Match' by assuming that the specified 'RepeatablePattern' consumes zero 'InputData'; of which the caller should ensure it's capable.+mkNullMatchFromRepeatablePattern+ :: ConsumptionBounds.DataLength -- ^ The offset into the list of input data at which the zero-length match occurred.+ -> RepeatablePattern m -- ^ The pattern that the input data matched.+ -> Match m -- ^ The resulting match-structure.+mkNullMatchFromRepeatablePattern offset r = case Repeatable.base r of+ Require _ -> Tree.Leaf (r, offset, [])+ CaptureGroup _ -> Tree.Node $ Repeatable.getFewest r `replicate` []++-- | The set of distinct 'Meta.Meta', in the specified 'RepeatablePattern'.+getDistinctMetaDataFromRepeatablePattern :: Eq m => RepeatablePattern m -> MetaDataList m+getDistinctMetaDataFromRepeatablePattern = getDistinctMetaDataFromPattern . Repeatable.base++{- |+ * Represents a black hole, which will greedily consume all data.++ * CAVEAT: nullary, i.e. a constant.+-}+(.*) :: RepeatablePattern m+(.*) = Repeatable.zeroOrMore anyDatum++-- | A /non-greedy/ version of '.*'.+(.*?) :: RepeatablePattern m+(.*?) = Repeatable.zeroOrMore' anyDatum++-- | Convenience-function, to build a 'RepeatablePattern' from an unrepeated instance of the specified 'Meta.Meta'-datum.+simply :: Meta.Meta m -> RepeatablePattern m+simply = Repeatable.one . Require++-- | Represents the /concatenation/ aspect of 'ExtendedRegEx's.+type Concatenation m = [RepeatablePattern m]++-- | Construct a null 'Match' by assuming that the specified 'Concatenation' consumes zero 'InputData'.+mkNullMatchFromConcatenation+ :: ConsumptionBounds.DataLength -- ^ The offset into the list of input data at which the zero-length match occurred.+ -> Concatenation m -- ^ The list of repeatable patterns that the input data matched.+ -> MatchList m -- ^ The resulting match-structure.+mkNullMatchFromConcatenation offset = map $ mkNullMatchFromRepeatablePattern offset++-- | The set of distinct 'Meta.Meta', in the specified 'Concatenation'.+getDistinctMetaDataFromConcatenation :: Eq m => Concatenation m -> MetaDataList m+getDistinctMetaDataFromConcatenation = foldr (Data.List.union . getDistinctMetaDataFromRepeatablePattern) []++-- | The accumulating set of 'MetaDataList', resulting from each successive 'RepeatablePattern' in the specified 'Concatenation'.+accumulateDistinctMetaDataFrom :: Eq m => MetaDataList m -> Concatenation m -> [MetaDataList m]+accumulateDistinctMetaDataFrom = scanr (Data.List.union . getDistinctMetaDataFromRepeatablePattern)++-- | Constructs an 'ExtendedRegEx', by surrounding a 'Concatenation' with optional 'Anchor.Anchor's.+data ExtendedRegEx m = MkExtendedRegEx {+ bowAnchor :: Maybe Anchor.Anchor, -- ^ An option to anchor the /regex/ to the start of the 'InputData'.+ concatenation :: Concatenation m, -- ^ The sequence of 'RepeatablePattern's defining the 'Require'ments that the 'InputData' must meet.+ sternAnchor :: Maybe Anchor.Anchor -- ^ An option to anchor the /regex/ to the end of the 'InputData'.+} deriving Eq++-- | The type of a function which transforms an 'ExtendedRegEx'.+type Transformation m = ExtendedRegEx m -> ExtendedRegEx m++-- | Similar to 'fmap', but operates on 'Concatenation', rather than just @a@.+transformExtendedRegEx+ :: (Concatenation m -> Concatenation m) -- ^ The function used to transform the data behind the constructor.+ -> Transformation m+transformExtendedRegEx f extendedRegEx = extendedRegEx { concatenation = f $ concatenation extendedRegEx }++instance (+ Eq m,+ Meta.ShortcutExpander m,+ Read m,+ ShortcutExpander m+ ) => Read (ExtendedRegEx m) where+ readsPrec _ [] = []+ readsPrec _ (' ' : s) = reads s -- Consume white-space.+ readsPrec _ ('\t' : s) = reads s -- Consume white-space.+ readsPrec _ ('\\' : c : s) = [(expand c, s)]+ readsPrec _ s = case singleton of+ [(extendedRegEx, _)]+ | ToolShed.SelfValidate.isValid extendedRegEx -> singleton+ | otherwise -> error $ ToolShed.SelfValidate.getFirstError extendedRegEx -- Parsed OK, but invalid.+ _ -> [] -- No parse.+ where+-- singleton :: (Eq m, Meta.ShortcutExpander m, Read m, ShortcutExpander m) => [(ExtendedRegEx m, String)]+ singleton = [+ (+ MkExtendedRegEx {+ bowAnchor = maybeBowAnchor,+ concatenation = concatenation',+ sternAnchor = maybeSternAnchor+ },+ remainder+ ) |+ (maybeBowAnchor, s1) <- readsMaybeAnchor s,+ (concatenation', s2) <- reads s1,+ (maybeSternAnchor, remainder) <- readsMaybeAnchor s2+ ] {-list-comprehension-} where+ readsMaybeAnchor :: ReadS (Maybe Anchor.Anchor)+ readsMaybeAnchor s' = return {-to List-monad-} $ case reads s' of+ [pair] -> Control.Arrow.first Just pair+ _ -> (Nothing, s')++-- | Shows either the specified 'Anchor.Anchor', or a null string where 'Nothing' is specified.+showsMaybeAnchor :: Maybe Anchor.Anchor -> String -> String+showsMaybeAnchor maybeAnchor+ | Data.Maybe.isJust maybeAnchor = shows $ Data.Maybe.fromJust maybeAnchor+ | otherwise = id++instance Show m => Show (ExtendedRegEx m) where+ showsPrec _ MkExtendedRegEx {+ bowAnchor = maybeBowAnchor, -- CAVEAT: this could be 'Nothing' or perversely an 'Anchor.Stern'.+ concatenation = concatenation',+ sternAnchor = maybeSternAnchor -- CAVEAT: this could be 'Nothing' or perversely an 'Anchor.Bow'.+ } = showsMaybeAnchor maybeBowAnchor . shows concatenation' . showsMaybeAnchor maybeSternAnchor++instance Consumer.Consumer (ExtendedRegEx m) where+ consumptionProfile = Consumer.aggregateConsumptionProfilesFromConcatenation . concatenation+ starHeight = Consumer.starHeight . concatenation++instance ToolShed.SelfValidate.SelfValidator (ExtendedRegEx m) where+ getErrors = ToolShed.SelfValidate.getErrors . concatenation++instance Control.DeepSeq.NFData m => Control.DeepSeq.NFData (ExtendedRegEx m) where+ rnf MkExtendedRegEx {+ bowAnchor = maybeBowAnchor,+ concatenation = concatenation',+ sternAnchor = maybeSternAnchor+ } = Control.DeepSeq.rnf (maybeBowAnchor, concatenation', maybeSternAnchor)++-- | Drop 'Anchor.Anchor's at both bow & stern of the specified 'ExtendedRegEx'.+dock :: Transformation m+dock e = e {+ bowAnchor = Just Anchor.Bow,+ sternAnchor = Just Anchor.Stern+}++-- | Amend the 'Concatenation' in the specified 'ExtendedRegEx', by prepending '.*' where there's a missing top-level 'Anchor.Bow' & appending '.*?' where there's a missing top-level 'Anchor.Stern'.+drift :: Transformation m+drift extendedRegEx = transformExtendedRegEx (+ (+ if hasBowAnchor extendedRegEx then id else {-no anchor-} ((.*?) :)+ ) . (+ if hasSternAnchor extendedRegEx then id else {-no anchor-} (++ [(.*)])+ )+ ) extendedRegEx++-- | True if the specified 'ExtendedRegEx' has an initial 'Anchor.Bow'. CAVEAT: though typically the alternative is no 'Anchor.Anchor', there may perversely be an initial 'Anchor.Stern', but neither qualify.+hasBowAnchor :: ExtendedRegEx m -> Bool+hasBowAnchor = (== Just Anchor.Bow) . bowAnchor++-- | True if the specified 'ExtendedRegEx' has a terminal 'Anchor.Stern'. CAVEAT: though typically the alternative is no 'Anchor.Anchor', there may perversely be a terminal 'Anchor.Bow', but neither qualify.+hasSternAnchor :: ExtendedRegEx m -> Bool+hasSternAnchor = (== Just Anchor.Stern) . sternAnchor++-- | True if there's at least one 'RepeatablePattern' in the 'Concatenation', i.e. that it's non-null.+isDefined :: ExtendedRegEx m -> Bool+isDefined = not . null . concatenation++-- | Construct a null 'Match' by assuming that the specified 'ExtendedRegEx' consumes zero 'InputData'.+mkNullMatchFromExtendedRegEx :: ConsumptionBounds.DataLength -> ExtendedRegEx m -> MatchList m+mkNullMatchFromExtendedRegEx offset = mkNullMatchFromConcatenation offset . concatenation++-- | The set of distinct (i.e. unique) 'Meta.Meta', in the specified 'ExtendedRegEx'.+getDistinctMetaDataFromExtendedRegEx :: Eq m => ExtendedRegEx m -> MetaDataList m+getDistinctMetaDataFromExtendedRegEx = getDistinctMetaDataFromConcatenation . concatenation++{- |+ * The input-data is just a list.++ * Whilst typically this list is also a 'String', & could therefore be more efficiently implemented using "Data.ByteString",+ we can't assume that the polymorphic base-type is always 'Char'.+-}+type InputData m = [m]++-- | The accumulating sets of distinct input data.+accumulateDistinctInputData :: Eq m => InputData m -> [InputData m]+accumulateDistinctInputData = scanr (Data.List.union . return) []++-- | Tag the 'InputData' with the 'RepeatablePattern' it matched (which unfortunately confines the definition to this (bloated) module), & the offset from the start of the data;+type MatchedData m = (RepeatablePattern m, ConsumptionBounds.DataLength, InputData m)++-- | Shifts the offset of the specified 'MatchedData'.+shiftMatchedData+ :: ConsumptionBounds.DataLength -- ^ The offset by which to shift the position into the input-data at which a match occurred.+ -> MatchedData m -- ^ The match-structure whose offset is to be shifted.+ -> MatchedData m+shiftMatchedData i (r, offset, d) = (r, offset + i, d)++-- | Accessor.+getInputData :: MatchedData m -> InputData m+getInputData (_, _, inputData) = inputData++-- | Describes the manner in which a 'RepeatablePattern' successfully consumed 'InputData'.+type Match m = Tree.Tree (MatchedData m)++-- | Shifts the offsets of all the 'MatchedData' contained in the specified 'Match'.+shiftMatch+ :: ConsumptionBounds.DataLength -- ^ The offset by which to shift the position into the input-data at which a match occurred.+ -> Match m -- ^ The match-structure whose offset is to be shifted.+ -> Match m+shiftMatch i = (shiftMatchedData i `fmap`)++-- | Extract & concatenate, the 'InputData' from a 'Match'.+extractDataFromMatch :: Match m -> InputData m+extractDataFromMatch = Data.Foldable.foldMap getInputData -- Uses the List-monoid's associative binary operator (++), to concatenate the values returned by the specified function.++-- | Extract & concatenate, the 'InputData' from a 'Match'; null if it didn't match any.+extractDataFromMatch' :: Maybe (Match m) -> InputData m+extractDataFromMatch' (Just match) = extractDataFromMatch match+extractDataFromMatch' _ = []++-- | Describes the manner in which a 'Concatenation' successfully consumed 'InputData'.+type MatchList m = [Match m]++-- | Shifts the offsets of all the 'MatchedData' contained in the specified 'MatchList'.+shiftMatchList+ :: ConsumptionBounds.DataLength -- ^ The offset by which to shift the position into the input-data at which a each listed match occurred.+ -> MatchList m -- ^ The list of match-structures, each of whose offsets are to be shifted.+ -> MatchList m+shiftMatchList i = map (shiftMatch i)++-- | Extract & concatenate, the 'InputData', from the 'MatchList'.+extractDataFromMatchList :: MatchList m -> InputData m+extractDataFromMatchList = concatMap extractDataFromMatch -- CAVEAT: too fine-grain for effective data-parallelism.++-- | At the top-level of an 'ExtendedRegEx', the lack of an 'Anchor.Anchor' allows the 'ExtendedRegEx' to drift away from the corresponding end of the input-data; this data-gap is captured here.+type ExternalMatch m = Maybe (Match m)++-- | Captures the list of input-data consumed by the 'Concatenation', bracketed by any data-prefix or data-suffix.+type Result m = (ExternalMatch m, Maybe (MatchList m), ExternalMatch m)++-- | Returns the length of data consumed by the specified 'ExternalMatch'.+externalMatchLength :: ExternalMatch m -> ConsumptionBounds.DataLength+externalMatchLength = length . extractDataFromMatch'++{- |+ * Similar to '+~', but exposes 'ExecutionOptions.ExecutionOptions', permitting greater control; & enough rope to hang yourself.++ * The parameter-order facilitates partial application, to acquire a matcher-function for a specific 'ExtendedRegEx', which can be applied repeatedly to different 'InputData'.++ * Performs some one-off @O(n)@ time-complexity preparation, then delegates the grunt-work to a private recursive function.++ * One could waste a lot of time trying to consume 'InputData' from the head of a high-'Consumer.starHeight' 'ExtendedRegEx',+ only to find that there's a low-'Consumer.starHeight' tail that can't ever match the 'InputData'; checks this first before delegating.+-}+findMatch :: (Eq m, Control.DeepSeq.NFData m)+ => RegExOpts.RegExOpts (ExtendedRegEx m) -- ^ The match-options parameterised by the regex against which to match the input data.+ -> InputData m -- ^ The input data within which to locate a match.+ -> Maybe (MatchList m)+findMatch regExOpts@RegExOpts.MkRegExOpts {+ RegExOpts.executionOptions = executionOptions,+ RegExOpts.regEx = extendedRegEx+} originalInputData+ | let+ extractLowStarHeightTail :: ExtendedRegEx m -> Concatenation m+ extractLowStarHeightTail = {-#SCC "extractLowStarHeightTail" #-} fromConcatenation . reverse . concatenation where+ fromConcatenation :: Concatenation m -> Concatenation m+ fromConcatenation [] = []+ fromConcatenation (repeatablePattern : concatenation')+ | Consumer.starHeight repeatablePattern == 0 = repeatablePattern : fromConcatenation {-recurse-} concatenation'+ | Repeatable.getFewest repeatablePattern > 0 = case Repeatable.base repeatablePattern of+ CaptureGroup alternatives+ | isSingletonAlternatives alternatives -> extractLowStarHeightTail {-recurse-} . head $ deconstructAlternatives alternatives+ | otherwise -> {-non-singleton-} []+ _ -> [] -- Zero 'Consumer.StarHeight' would have been detected by 'fromConcatenation'.+ | otherwise = []++-- lowStarHeightTail :: Concatenation a+ lowStarHeightTail = extractLowStarHeightTail extendedRegEx+ in and [+ ExecutionOptions.checkExistenceOfInelasticTail executionOptions,+ not $ null lowStarHeightTail, -- Prevent infinite recursion.+ reverse originalInputData /~ RegExOpts.mkRegEx MkExtendedRegEx {+ bowAnchor = Just Anchor.Bow,+ concatenation = lowStarHeightTail,+ sternAnchor = Nothing+ } -- Check for mismatch with the corresponding tail of 'InputData'.+ ] = Nothing+ | otherwise = findMatchSlave originalConcatenation (+ Consumer.accumulateConsumptionProfiles originalConcatenation+ ) (+ accumulateDistinctMetaDataFrom [] originalConcatenation+ ) originalInputData originalInputDataLength (+ accumulateDistinctInputData originalInputData+ )+ where+-- originalConcatenation :: Concatenation a+ originalConcatenation = concatenation extendedRegEx++ originalInputDataLength :: ConsumptionBounds.DataLength+ originalInputDataLength = length originalInputData+{-+ This beast takes a lot of parameters.+ Whilst the only strictly necessary parameters are the lists 'Concatenation' & 'InputData', many ancillary lists derived from them, are also passed by parameter.+ These are proportionally reduced on recursion, to avoid the requirement to regenerate them each time they're required.+-}+ findMatchSlave :: (Eq m, Control.DeepSeq.NFData m)+ => Concatenation m -- The list of 'RepeatablePattern's from which the regex is constructed.+ -> ConsumptionProfile.AccumulatedConsumptionProfiles -- The capacity-bounds for the consumption of 'InputData', that the 'Concatenation' extending right from any given 'RepeatablePattern', can consume.+ -> [MetaDataList m] -- The set of distinct 'Meta.Meta'-data, in the 'Concatenation extending right from any given 'RepeatablePattern'.+ -> InputData m -- The input data, which will be fed to the 'Concatenation'.+ -> ConsumptionBounds.DataLength -- The length of the previously specified 'InputData'.+ -> [InputData m] -- The set of distinct input data, extending right from any given point.+ -> Maybe (MatchList m)+ findMatchSlave [] _ _ [] _ _ = Just [] -- Simultaneous exhaustion of the 'Concatenation' of 'RepeatablePattern's & the 'InputData' => success.+ findMatchSlave [] _ _ _ _ _ = Nothing+ findMatchSlave concatenation'@(repeatablePatternHead : concatenationTail) (accumulatedConsumptionProfileHead : accumulatedConsumptionProfilesTail) (distinctMetaDataHead : distinctMetaDataTail) inputData inputDataLength distinctInputData@(distinctInputDataHead : _)+ | not $ inputDataLength `ConsumptionProfile.withinConsumptionBounds` accumulatedConsumptionProfileHead = Nothing+ | null inputData = Just $ mkNullMatchFromConcatenation inputDataOffset concatenation' -- Build a 'Match', of null 'InputData'.+ | and [+ ExecutionOptions.bypassInputDataForLiberalConsumer executionOptions, -- We may be able to establish success, without evaluating 'inputData' any further than required to determine its length.+ not $ ExecutionOptions.requireMatchList executionOptions, -- Otherwise the precise mapping of the 'inputData' to 'RepeatablePattern's must be determined.+ not $ ConsumptionProfile.hasSpecificRequirement accumulatedConsumptionProfileHead -- Otherwise a match for the specific 'Meta'-data must be found.+ ] = Just undefined {-shouldn't be evaluated according to 'ExecutionOptions.requireMatchList'-}+ | and [+ ExecutionOptions.checkForUnconsumableData executionOptions,+ not $ ConsumptionProfile.canConsumeAnything accumulatedConsumptionProfileHead, -- Otherwise the subsequent test will always fail.+ (`isUnconsumableByAnyOf` distinctMetaDataHead) `any` distinctInputDataHead -- Occasionally failure is both inevitable & obvious.+ ] = Nothing+ | otherwise = {-#SCC "findMatchSlave" #-} let+ tailConsumptionProfile :: ConsumptionProfile.ConsumptionProfile+ tailConsumptionProfile@(+ ConsumptionProfile.MkConsumptionProfile {+ ConsumptionProfile.consumptionBounds = (minConsumptionConcatenationTail, maybeMaxConsumptionConcatenationTail)+ }+ ) = head accumulatedConsumptionProfilesTail -- Extract the aggregate consumption-profile, of the tail of the 'Concatenation'.++ maxDataAvailable :: ConsumptionBounds.DataLength+ maxDataAvailable = inputDataLength - minConsumptionConcatenationTail -- The maximum data available to match 'repeatablePatternHead'.+ in if maxDataAvailable < 0+ then Nothing+ else {-inputData is sufficient for concatenationTail-} let+ Repeatable.MkRepeatable {+ Repeatable.base = base,+ Repeatable.repetitionBounds = (fewest, most),+ Repeatable.isGreedy = isGreedy+ } = repeatablePatternHead -- Completely deconstruct the 'Repeatable' at the head of the 'Concatenation'.+{-+ Find the maximum sequence of inputData, preceding the first of the mandatory requirements of 'concatenationTail'.+ This enables one to more tightly constrain the maximum number of repetitions of a 'RepeatablePattern',+ which consequently reduces the subsequent requirement to back-track.+-}+-- maximumDataBeforePegs :: Maybe (InputData m)+ maximumDataBeforePegs = {-#SCC "maximumDataBeforePegs" #-} reverse (extractMinimumRequirementFromConcatenation concatenationTail) `maximumDataAfterPegs` reverse inputData where+{-+ Extract those 'RepeatablePattern's which must match at least once.+ Singleton 'Alternatives' are flattened by replication, & non-singleton 'Alternatives' removed, allowing a reduction of the list's base-type to just 'Repeatable.Repeatable' 'Meta.Meta'.+ The resulting 'BasicRegEx', with /Kleene Star/s weeded-out & 'Alternatives' pruned, can be matched in O(n)-time, to verify minimal requirements.+-}+ extractMinimumRequirementFromConcatenation :: Concatenation m -> BasicRegEx m+ extractMinimumRequirementFromConcatenation = concatMap (+ \repeatablePattern -> case Repeatable.base repeatablePattern of+ Require datum -> return {-to List-monad-} repeatablePattern { Repeatable.base = datum }+ CaptureGroup a+ | isSingletonAlternatives a -> concat . replicate (Repeatable.getFewest repeatablePattern) . extractMinimumRequirementFromConcatenation {-recurse-} . concatenation . head $ deconstructAlternatives a+ | otherwise -> []+ ) . filter ((> 0) . Repeatable.getFewest)++ maximumDataAfterPegs :: Eq m => BasicRegEx m -> InputData m -> Maybe (InputData m)+ maximumDataAfterPegs pegList@(+ Repeatable.MkRepeatable {+ Repeatable.base = metaPeg,+ Repeatable.repetitionBounds = (fewestPeg, _)+ } : pegListTail+ ) inputData'+ | null remainingInputData && length candidateMatchedInputData < fewestPeg = Nothing -- Insufficient data to ever match.+ | fewestPeg == 1 || (`Meta.isMatch` metaPeg) `all` tail candidateMatchedInputData = pegListTail `maximumDataAfterPegs` remainingInputData -- Success => recurse.+ | otherwise = pegList `maximumDataAfterPegs` tail candidateInputData -- Failed candidate => recurse.+ where+-- candidateInputData, candidateMatchedInputData, remainingInputData :: InputData a+ candidateInputData = dropWhile (not . (`Meta.isMatch` metaPeg)) inputData'+ (candidateMatchedInputData, remainingInputData) = fewestPeg `splitAt` candidateInputData+ maximumDataAfterPegs [] inputData' = Just inputData' -- All the pegs have been matched.+{-+ Each 'repeatablePatternHead' can either be a 'Require' or a 'CaptureGroup'.+ The former case needs further reduction & we can proceed to construct a 'Tree.Leaf' from any matching 'inputData'.+ We attempt to reduce the latter case to an instance of the former, by expanding all combinations of repetitions of various 'Alternatives', constructing a new 'ExtendedRegEx' from each, & recursing.+-}+ in case base of+ Require meta+ | null maybeMatchList -> Nothing+ | otherwise -> head {-most suitable-} maybeMatchList+ where+{-+ Attempt to narrow the repetition-range, from the potentially unbounded specification, to that which is possible according to,+ the 'inputDataLength', & the specified consumption-bounds of the remainder of the /regex/.+ Once the repetition-range has been narrowed, iterate from the appropriate end of the range (depending on 'isGreedy'), extract the corresponding length from inputData, & check for a match.+-}+-- maybeMatchList :: [Maybe (MatchList m)]+ maybeMatchList = {-#SCC "maybeMatchList" #-} dropWhile Data.Maybe.isNothing . map (+ \(consumedInputData, unconsumedInputData) -> let+ consumedInputDataLength :: ConsumptionBounds.DataLength+ consumedInputDataLength = length consumedInputData+ in (+ Tree.Leaf (repeatablePatternHead, inputDataOffset, consumedInputData) :+ ) `fmap` {-apply to Maybe Functor-} findMatchSlave concatenationTail accumulatedConsumptionProfilesTail distinctMetaDataTail unconsumedInputData (+ inputDataLength - consumedInputDataLength+ ) (+ consumedInputDataLength `drop` distinctInputData+ ) -- Recurse, to check whether the 'unconsumedInputData' tail also matches.+ ) $ let+ fewestData, mostData, mostData' :: Repeatable.Repetitions+ fewestData = {-#SCC "fewestData" #-} case maybeMaxConsumptionConcatenationTail of+ Just m -> (inputDataLength - m) {- :: ConsumptionBounds.DataLength-} `max` fewest {- :: Repeatable.Repetitions-} -- CAVEAT: conceptually different types.+ _ -> fewest++-- mostData = length . takeWhile (~= meta) $ (`take` inputData) maxData -- As slow as it is concise.+ mostData = {-#SCC "mostData" #-} maxData - measureUnmatchableTail inputData maxData where+ maxData :: ConsumptionBounds.DataLength+ maxData = case most of+ Just cap -> cap {- :: Repeatable.Repetitions-} `min` maxDataAvailable {- :: ConsumptionBounds.DataLength-} -- CAVEAT: conceptually different types.+ _ -> maxDataAvailable++-- measureUnmatchableTail :: InputData m -> ConsumptionBounds.DataLength -> ConsumptionBounds.DataLength+ measureUnmatchableTail _ 0 = 0 -- Have matched all the data.+ measureUnmatchableTail (a : as) unmatched+ | a `Meta.isMatch` meta = measureUnmatchableTail as $ pred unmatched -- Recurse.+ | otherwise = unmatched -- Return the unmatchable tail-length.+ measureUnmatchableTail [] _ = error "RegExDot.RegEx.findMatch.findMatchSlave.maybeMatchList.mostData.measureUnmatchableTail:\tdata unexpectedly exhausted."+{-+ 'most' has been extracted from 'repeatablePatternHead',+ & reduced according to the length of 'inputData' which actually matches, to form 'mostData',+ but if it still exceeds 'fewestData', the resulting repetition-range may trigger backtracking.+ So, attempt to further reduce this ill-defined repetition-range, according to the competing requirements of 'concatenationTail'.+-}+ mostData'+ | and [+ ExecutionOptions.moderateGreed executionOptions,+ isGreedy, -- Otherwise, since the search proceeds from 'fewest' to 'most', the optimal solution is located before backtracking.+ mostData > fewestData -- Otherwise, there's no unbridled greed to moderate.+ ] = case maximumDataBeforePegs of+ Just maximumDataBeforePegs' -> mostData `min` length maximumDataBeforePegs' -- Cap upper bound.+ _ -> negate 1 -- Guaranteed to be < 'fewestData'.+ | otherwise {-no requirement for this optimisation-} = mostData+ in (+ succ {-fence-post-} mostData' - fewestData -- CAVEAT: possibly <= 0.+ ) `take` (+ if isGreedy+ then ToolShed.Data.List.Splits.splitsLeftFrom mostData'+ else ToolShed.Data.List.Splits.splitsRightFrom fewestData+ ) inputData++ CaptureGroup alternatives+ | and [+ not isGreedy,+ fewest <= 0, -- Zero repetitions permissible.+ Data.Maybe.isJust tailMatch+ ] -> zeroRepetitions -- Zero repetitions is the optimal solution, rendering the choice of Alternative irrelevant.+ | and [+ ExecutionOptions.catchIncompatibleAnchors executionOptions,+ fewest > 1, -- Multiple repetitions.+ minConsumptionAlternatives > 0, -- InputData required.+ ($ extendedRegExAlternatives) `any` [(hasBowAnchor `all`), (hasSternAnchor `all`)]+ ] -> Nothing -- 'extendedRegExFromAlternative' requires 'InputData', therefore 'Anchor.Bow' can only pass on the 1st repetition & 'Anchor.Stern' can only pass on the last.+ | otherwise -> let+{-+ We're about to try all repetitions of one Alternative before progressing to the next.+ One could alternatively transpose this search-pattern, & investigate all 'Alternatives' permissible at n repetitions, before trying (n + 1); I've not pursued this option.+ Either way, if the user requires data-capture, then for POSIX-compliance we must perform an exhaustive search of the O(Alternatives ^ Repetitions) permutations for the optimal solution; though the search may be narrowed as we proceed.+-}+-- matchPairList :: [(Match m, MatchList m)]+ matchPairList = {-#SCC "matchPairList" #-} Data.Maybe.catMaybes . concat . Control.Parallel.Strategies.parMap Control.Parallel.Strategies.rseq {-Particularly effective when 'not ExecutionOptions.requireMatchList' -} (+-- :: ExtendedRegEx m -> [Maybe (Match m, MatchList m)]+ \extendedRegExFromAlternative@MkExtendedRegEx {+ concatenation = concatenationFromAlternative+ } -> let+ components :: Int+ components = length concatenationFromAlternative++ consumptionProfileConcatenationFromAlternative :: [ConsumptionProfile.ConsumptionProfile]+ consumptionProfileConcatenationFromAlternative = map Consumer.consumptionProfile concatenationFromAlternative++ minConsumptionConcatenationFromAlternative :: ConsumptionBounds.DataLength+ maybeMaxConsumptionConcatenationFromAlternative :: Maybe ConsumptionBounds.DataLength+ (minConsumptionConcatenationFromAlternative, maybeMaxConsumptionConcatenationFromAlternative) = ConsumptionProfile.consumptionBounds $ ConsumptionProfile.aggregateFromConcatenation consumptionProfileConcatenationFromAlternative+ in (+ if ExecutionOptions.abortTrialRepetitionsOnZeroConsumption executionOptions && Data.Maybe.isNothing most {-constrained only by 'inputDataLength'-}+ then {-#SCC "abortTrialRepetitionsOnZeroConsumption" #-} ToolShed.Data.List.takeUntil $ \maybeMatchPair -> case maybeMatchPair of+ Just (match, _) -> ($ Tree.pop match) `all` [+ (>= fewest) . length, -- Must achieve 'Repeatable.getFewest', regardless of data-consumption.+ null . extractDataFromMatchList . last -- If the n-th repetition consumed nothing, so will the (n + 1)-th.+ ]+ _ -> False -- Failure with n repetitions, doesn't preclude success with n + 1.+ else {-optimisation not required-} id+ ) . (+-- :: Maybe (Match m, MatchList m) -> Maybe (Match m, MatchList m)+ \maybeMatchPairList -> if ExecutionOptions.abortTrialRepetitionsOnInherentFailure executionOptions+{-+ We're iterating through the identified range of possible repetitions, of one possible 'concatenationFromAlternative'.+ If failure can be proven, for an arbitrary number of repetitions, then we can bypass the futile remainder.+ If the first trial succeeded, then clearly there's no such inherent problem with 'concatenationFromAlternative', & it's necessary to evaluate all subsequent trial-repetitions.+ Otherwise, assuming there're subsequent trials to be bypassed, attempt to prove that failure is inevitable, by launching a pilot-study,+ using a single repetition of 'concatenationFromAlternative' followed by a replacement infallible 'concatenationTail'.+-}+ then {-#SCC "abortTrialRepetitionsOnInherentFailure" #-} case maybeMatchPairList of+ (Nothing {-failed attempt-} : _ {-subsequent attempt worth bypassing-} : _) -> if (+ minConsumptionConcatenationFromAlternative == 0 -- Can delegate consumption of unconsumable data to the infallible tail => unprovable culpability.+ ) || inputData =~ RegExOpts.mkRegEx MkExtendedRegEx {+ bowAnchor = Just Anchor.Bow,+ concatenation = concatenationFromAlternative ++ [anyDatum ^#-> minConsumptionConcatenationTail], -- Infallible tail.+ sternAnchor = Just Anchor.Stern+ }+ then maybeMatchPairList+ else [] -- Abandon further repetitions of 'Alternatives'.+ _ {-Alternative is OK, or there's no subsequent attempt to bypass-} -> maybeMatchPairList+ else {-optimisation not required-} maybeMatchPairList+ ) . map (+-- :: Repeatable.Repetitions -> Maybe (Match m, MatchList m)+ \repetitions -> if hasSternAnchor extendedRegExFromAlternative+ then if (repetitions == 1 || minConsumptionConcatenationFromAlternative == 0) && minConsumptionConcatenationTail == 0+ then (+ shiftMatch inputDataOffset . Tree.Node . (+ : replicate (+ pred repetitions -- Typically degenerates to zero, since anchored sub-expressions aren't normally repeatable.+ ) (+ mkNullMatchFromExtendedRegEx 0 extendedRegExFromAlternative -- Other Alternatives may be available & suitable.+ ) -- Repeated null match may, lacking other suitable Alternatives, correspond to this stern-anchored ExtendedRegEx, so data must be consumed before them.+ ) &&& const (mkNullMatchFromConcatenation inputDataOffset concatenationTail)+ ) `fmap` findMatch regExOpts { RegExOpts.regEx = extendedRegExFromAlternative } inputData -- Recurse, either consuming all inputData or failing.+ else Nothing+ else {-no Anchor.Stern-} let+{-+ Expand the 'Repeatable CaptureGroup', so that after recursion it is ultimately reduced in complexity, to just a 'Concatenation' of 'Repeatable Require', & can be handled above as a POSIX BRE.++ There're two special cases; one repetition of n 'Alternatives' & n repetitions of one Alternative.+ If there's only one repetition, we can reduce the implementation-logic substantially, to the point where the implementation's independent of whether there's a choice of 'Alternatives'.+ If there only one Alternative, then the entire repetition-sequence can be immediately expanded, vastly reducing the required number of recursions,+ otherwise we can only expand the first repetition, leaving the option of matching a different Alternative on subsequent repetitions.++ In all cases, we must subsequently re-compose any 'MatchList' resulting of the recursive call, into one corresponding to the structure of original ExtendedRegEx.+ The expansion-process & corresponding subsequent collation, are associated in a tuple, in which the consumption-profile of the expansion is also contained.+-}+-- collater :: MatchList m -> (Match m, MatchList m)+ consumptionProfileExpandedConcatenationPrefix :: [ConsumptionProfile.ConsumptionProfile]+-- expandedConcatenationPrefix :: Concatenation a+ (collater, consumptionProfileExpandedConcatenationPrefix, expandedConcatenationPrefix)+ | repetitions == 1 = (+ Control.Arrow.first (+ Tree.Node . return {-to List-monad-}+ ) . splitAt components, -- Bisect the 'MatchList', into the part resulting from the Alternative, & that for 'concatenationTail'.+ consumptionProfileConcatenationFromAlternative,+ concatenationFromAlternative+ ) -- Both tuples below, can degenerate to this simple case; it isn't a fundamentally different algorithm.+ | and [+ ExecutionOptions.unrollRepeatedSingletonAlternative executionOptions,+ isSingletonAlternative,+ not $ hasBowAnchor extendedRegExFromAlternative+ ] = (+ Control.Arrow.first (+ Tree.Node . ToolShed.Data.List.chunk components+ ) . splitAt (+ components * repetitions+ ), -- Bisect the 'MatchList', into the part resulting from (Alternative){n}, & that for 'concatenationTail'.+ concat $ replicate repetitions consumptionProfileConcatenationFromAlternative,+ concat $ replicate repetitions concatenationFromAlternative -- Expand all repetitions of the single alternative.+ )+ | otherwise {-choice of Alternatives or has Anchor.Bow-} = let+ remainingRepetitions :: Repeatable.Repetitions+ remainingRepetitions = pred repetitions -- Expand just the first repetition of the set of 'Alternatives'.++-- singletonRepeatable :: RepeatablePattern a+ singletonRepeatable = Repeatable.toSingleton repeatablePatternHead+ in (+ Control.Arrow.first (+ Tree.Node . uncurry (:) . Control.Arrow.second (+ concatMap Tree.pop -- Amalgamate the repeated singleton 'MatchList's.+ ) . splitAt components -- Bisect first 'MatchList', into the part resulting from 'concatenationFromAlternative', & that for 'replicate remainingRepetitions'.+ ) . splitAt (+ components + remainingRepetitions -- Bisect the 'MatchList', into the part resulting from (Alternatives){n}, & that for 'concatenationTail'.+ ),+ consumptionProfileConcatenationFromAlternative ++ replicate remainingRepetitions (Consumer.consumptionProfile singletonRepeatable),+ concatenationFromAlternative {-expand 1st repetition-} ++ replicate remainingRepetitions {-potentially zero-} singletonRepeatable -- Enumerate all remaining repetitions of ANY Alternative; a different Alternative can match for each repetition.+ )+ in {-#SCC "collater" #-} collater `fmap` {-apply to Maybe Functor-} findMatchSlave (+ expandedConcatenationPrefix ++ concatenationTail+ ) (+ ConsumptionProfile.accumulateFrom tailConsumptionProfile {-initial value-} consumptionProfileExpandedConcatenationPrefix ++ tail accumulatedConsumptionProfilesTail+ ) (+ accumulateDistinctMetaDataFrom (head distinctMetaDataTail) {-initial value-} expandedConcatenationPrefix ++ tail distinctMetaDataTail+ ) inputData inputDataLength distinctInputData -- Recurse to get 'Maybe (MatchList a)'.+ ) $ if Repeatable.isPrecise repeatablePatternHead -- These are frequently generated by the previous recursion, as a trial expansion of an Alternative.+ then if ExecutionOptions.validateMinConsumptionOfAlternatives executionOptions && maxDataAvailable < fewest * minConsumptionAlternatives+ then [] -- Failure is inevitable.+ else [fewest]+ else {-imprecise range-} let+{-+ Attempt to narrow the repetition-range, from the potentially unbounded specification, to that which is possible according to (data available / consumption-requirement),+ where there're bounds for both numerator & denominator.+ Additionally, one must account for the possibility of the denominator being zero, & if so, select the optimal value if the numerator is also zero.+ The range can be narrowed more aggressively, for the special case of a singleton 'Alternative', as used in a capture-group.+-}+ fewestAlternatives, mostAlternatives, mostAlternatives' :: Repeatable.Repetitions+ fewestAlternatives = {-#SCC "fewestAlternatives" #-} 1 {-zero is handled later-} `max` if any Data.Maybe.isNothing [maybeMaxConsumptionConcatenationTail, maybeMaxConsumptionAlternatives]+ then fewest+ else {-no unlimited capacities-} let+ minDataAvailable, maxConsumptionAlternatives :: ConsumptionBounds.DataLength+ minDataAvailable = inputDataLength - Data.Maybe.fromJust maybeMaxConsumptionConcatenationTail -- CAVEAT: can be negative.+ maxConsumptionAlternatives = Data.Maybe.fromJust maybeMaxConsumptionAlternatives+ in if isSingletonAlternative+ then if maxConsumptionAlternatives == 0 -- Denominator.+ then {-sole Alternative can't consume anything ?!-} if minDataAvailable <= 0 -- Numerator.+ then {-tail may consume all data-} fewest -- Repeat to meet the minimum requirement.+ else {-unconsumable data-} maxBound -- Failure is inevitable.+ else {-non-zero => can divide-} max fewest $ minDataAvailable /+ maxConsumptionAlternatives+ else {-choice of Alternatives-} let+ minDataAvailable' :: ConsumptionBounds.DataLength+ minDataAvailable' = minDataAvailable - Data.Maybe.fromJust maybeMaxConsumptionConcatenationFromAlternative -- CAVEAT: potentially negative.+ in if maxConsumptionAlternatives == 0 -- Denominator.+ then {-no Alternative can consume anything-} if minDataAvailable' <= 0 -- Numerator.+ then {-zero unconsumable data-} fewest -- Repeat to meet the minimum requirement.+ else {-unconsumable data-} maxBound -- Failure is inevitable.+ else {-non-zero => can divide-} max fewest . succ {-account for expanded instance-} $ minDataAvailable' /+ maxConsumptionAlternatives++ mostAlternatives = {-#SCC "mostAlternatives" #-} case most of+ Just cap -> cap `min` mostPermissibleRepetitions -- Create a ceiling above which the calculated number of permissible repetitions can't rise.+ _ -> mostPermissibleRepetitions+ where+ mostPermissibleRepetitions :: Repeatable.Repetitions+ mostPermissibleRepetitions+ | isSingletonAlternative = if minConsumptionAlternatives == 0 -- Denominator.+ then {-sole Alternative can consume zero data-} if maxDataAvailable == 0 -- Numerator.+ then {-zero data available-} fewest -- Repeat to meet the minimum requirement.+ else {-data available-} maxDataAvailable -- Either each repetition consumes something, or (n - 1) repetitions is a better solution.+ else {-non-zero => can divide-} maxDataAvailable `div` minConsumptionAlternatives -- Divide & round down.+ | otherwise {-choice of Alternatives-} = 1 {-account for instance expanded as 'concatenationFromAlternative'-} + let+ maxDataAvailable' :: ConsumptionBounds.DataLength+ maxDataAvailable' = maxDataAvailable - minConsumptionConcatenationFromAlternative -- CAVEAT: potentially negative.+ in if minConsumptionAlternatives == 0 -- Denominator.+ then {-@ least one Alternative can consume zero-} if maxDataAvailable' == 0 -- Numerator.+ then {-zero data available $-} pred fewestAlternatives -- Repeat as required. CAVEAT: an annoying dependency, which prevents parallel-evaluation.+ else {-data available-} if and [+ False, -- The cost outweighs the small infrequent dividend.+ minConsumptionConcatenationFromAlternative == 0,+ maxDataAvailable > fewest+ ] -- If 'fewest' can be met, without unconsuming repetitions of 'concatenationFromAlternative', then stop short of permitting it.+ then pred maxDataAvailable -- Any greater, & one repetition (possibly that currently expanded) must needlessly consume nothing.+ else maxDataAvailable' -- Either all n repetitions consumes something, or (n - 1) is a better solution.+ else {-non-zero => can divide-} maxDataAvailable' `div` minConsumptionAlternatives -- Divide & round down.+{-+ 'most' has been extracted from 'repeatablePatternHead',+ & reduced according to the number of times it can fit into the maximum available data, to form 'mostAlternatives',+ but if it still exceeds 'fewestAlternatives', the resulting repetition-range may trigger backtracking.+ So, attempt to further reduce this ill-defined repetition-range, according to the competing requirements of 'concatenationTail'.+ CAVEAT: this pays-off infrequently, but not necessarily insignificantly.+-}+ mostAlternatives' = {-#SCC "mostAlternatives'" #-} if and [+ ExecutionOptions.moderateGreed executionOptions,+ mostAlternatives > fewestAlternatives, -- Otherwise, there's no unbridled greed to moderate.+ minConsumptionAlternatives > 0 -- Otherwise, any number of repetitions can occur without necessarily triggering back-tracking.+ ]+ then case maximumDataBeforePegs of+ Just maximumDataBeforePegs' -> mostAlternatives `min` (+ length maximumDataBeforePegs' `div` minConsumptionAlternatives+ ) -- Cap upper bound.+ _ -> negate 1 -- Guaranteed to be < 'fewestAlternatives'.+ else {-no requirement for this optimisation-} mostAlternatives+ in [fewestAlternatives .. mostAlternatives'] -- The permissible repetition-range of any Alternative, constrained by the amount of data available, after subtracting fixed consumption-requirements.+ ) . (+ if ExecutionOptions.permitReorderingOfAlternatives executionOptions+{-+ The order in which 'Alternatives' are evaluated, should make no difference to the final result; unless 'ExecutionOptions.useFirstMatchAmongAlternatives'.+ This order can be manipulated in an attempt to more quickly locate any 'Just MatchList', though if 'ExecutionOptions.requireMatchList', an exhaustive search for the optimal solution must be performed anyway.+ Tackling lower-complexity 'Alternatives' first makes sense, in the hope of finding any easy solution before becoming entrenched fighting some hideous monster.+ Preferring 'Alternatives' which require more data also makes sense, regardless of 'Repeatable.isGreedy' which just a preference for a type of solution, because fewer repetitions are then permissible;+ this is rather like trying to pack a box & preferring to try the biggest items first.+-}+ then Data.List.sortBy $ Data.Ord.comparing (+ Consumer.starHeight &&& safeReciprocal . (fromIntegral :: ConsumptionBounds.DataLength -> Rational) . Consumer.getFewest+ ) -- Firstly increasing complexity, then decreasing minimum data-capacity, otherwise stable.+ else id+ ) $ filter (+ \e -> (+ not (hasBowAnchor e) || inputDataOffset == 0 -- Necessary & sufficient.+ ) && (+ not (hasSternAnchor e) || minConsumptionConcatenationTail == 0 -- Necessary but insufficient, since though minimum consumption is zero, maximum isn't necessarily.+ )+ ) extendedRegExAlternatives+ in if null matchPairList+ then if fewest <= 0+ then zeroRepetitions -- Which might still fail, depending on 'tailMatch'.+ else {-zero repetitions isn't permissible-} Nothing+ else {-at least one Alternative matched-} {-#SCC "selectAlternative" #-} Just . uncurry (:) {-re-join head & tail-} $ (+{-+ At least one 'Match' has been found amongst the combinations of repetitions of various 'Alternatives',+ & if the caller reduces the 'Just MatchList' to 'True', that'd be sufficient, but otherwise one must identify the optimal 'MatchList'.+ The primary selection-criterion used is the POSIX one, based on total maximum (or minimum for non-'Repeatable.isGreedy' capture-groups) data-consumption.+ /Perl/'s "first-past-the-post" policy is available as an alternative.+-}+ if ExecutionOptions.useFirstMatchAmongAlternatives executionOptions || length matchPairList == 1+ then head+ else snd {-remove prepended selection-criterion-} . Data.List.maximumBy {-select the best match-} (+ Data.Ord.comparing fst -- Compare using only the criterion, not the result from which it was derived.+ ) . map (+ (+{-+ If the primary selection-criterion doesn't resolve the choice between candidate 'Match'es, I employ these ad-hoc criteria.++ * Fewer repetitions are preferred, which discourages the capture of null lists of 'InputData'.++ * Within the 'MatchedData', from which a candidate 'Match' is ultimately composed, consumption of 'InputData' beyond 'Repeatable.getFewest' by 'Repeatable.isGreedy' 'RepeatablePattern's, is preferred to non-greedy ones.++ * The consumption on each successive repetition is compared between candidate 'Match'es (which are now known to have used an equal number of repetitions);+ this causes the data-consumption to flow towards earlier repetitions for greedy capture-groups, & towards later repetitions in non-greedy ones,+ thus mimicking the behaviour of the unrolled repetition.++ * PS: more precise criteria are defined in <http://www2.research.att.com/~gsf/testregex/re-interpretation.html>.+-}+ \match -> let+ dataLengthCriterion :: InputData m -> Rational+ dataLengthCriterion = (+ if isGreedy+ then id+ else safeReciprocal -- Prefer less data.+ ) . fromIntegral . length++-- matchLists :: [MatchList m]+ matchLists = Tree.pop match+ in (+ dataLengthCriterion $ extractDataFromMatch match,+ if ExecutionOptions.preferFewerRepeatedAlternatives executionOptions+ then safeReciprocal . toRational $ length matchLists+ else 0,+ if ExecutionOptions.preferAlternativesWhichFeedTheGreedy executionOptions+ then let+ excessConsumption :: MatchedData m -> ConsumptionBounds.DataLength+ excessConsumption (+ Repeatable.MkRepeatable {+ Repeatable.repetitionBounds = (f, _),+ Repeatable.isGreedy = g+ },+ _,+ consumedInputData+ )+ | g = l - f+ | otherwise = f - l+ where+ l :: ConsumptionBounds.DataLength+ l = length consumedInputData+ in Data.Foldable.foldl' (\l -> (l +) . excessConsumption) 0 match+ else 0,+ if ExecutionOptions.preferAlternativesWhichMimickUnrolling executionOptions+ then map (dataLengthCriterion . extractDataFromMatchList) matchLists+ else []+ ) -- Create a tuple of selection-criteria, for simultaneous assessment.+ ) . fst {-focus the choice on the Alternatives-} &&& id -- Prepend a selection-criterion to each result.+ )+ ) matchPairList+ where+-- tailMatch, zeroRepetitions :: Maybe (MatchList m)+ tailMatch = findMatchSlave concatenationTail accumulatedConsumptionProfilesTail distinctMetaDataTail inputData inputDataLength distinctInputData -- Recurse.+ zeroRepetitions = (Tree.Node [] :) `fmap` {-apply to Maybe Functor-} tailMatch -- Prepend a null 'MatchList'.++-- extendedRegExAlternatives :: [ExtendedRegEx m]+ extendedRegExAlternatives = Data.List.nub $ deconstructAlternatives alternatives++ isSingletonAlternative :: Bool+ isSingletonAlternative = length extendedRegExAlternatives == 1++ minConsumptionAlternatives :: ConsumptionBounds.DataLength+ maybeMaxConsumptionAlternatives :: Maybe ConsumptionBounds.DataLength+ (minConsumptionAlternatives, maybeMaxConsumptionAlternatives) = Consumer.getConsumptionBounds alternatives -- Independent of the choice of Alternative.+ where+ inputDataOffset :: ConsumptionBounds.DataLength+ inputDataOffset = originalInputDataLength - inputDataLength+ findMatchSlave _ _ _ _ _ _ = error "RegExDot.RegEx.findMatch.findMatchSlave:\tunexpected parameter-pattern."++{- |+ * Operator's name was chosen to suggest something more than '=~'.++ * CAVEAT: much more expensive then '=~': in /ghci/, 'Just' can be observed to be printed /long/ before the 'MatchList' from which 'Result' is constructed,+ as the lazy algorithm finds the first solution, but not yet necessarily the optimal solution, amongst 'Alternatives'.+-}+(+~) :: (Eq m, Control.DeepSeq.NFData m)+ => InputData m -- ^ The input data within which to locate a match.+ -> RegExOpts.RegExOpts (ExtendedRegEx m) -- ^ The match-options parameterised by the regex against which to match the input data.+ -> Result m+inputData +~ regExOpts = (+ if hasBowAnchor' then Nothing else fmap head maybeMatchList, -- Record the first 'Match', consumed in the absence of a top-level 'Anchor.Bow'.+ fmap (+ (+ if hasBowAnchor' then id else tail -- Remove the first 'Match', since this wasn't consumed by the 'Concatenation'.+ ) . (+ if hasSternAnchor' then id else init -- Remove the last 'Match', since this wasn't consumed by the 'Concatenation'.+ )+ ) maybeMatchList,+ if hasSternAnchor' then Nothing else fmap last maybeMatchList -- Record the last 'Match', consumed in the absence of a top-level 'Anchor.Stern'.+ ) where+-- extendedRegEx :: ExtendedRegEx a+ extendedRegEx = RegExOpts.regEx regExOpts++ hasBowAnchor', hasSternAnchor' :: Bool+ hasBowAnchor' = hasBowAnchor extendedRegEx+ hasSternAnchor' = hasSternAnchor extendedRegEx++-- maybeMatchList :: Maybe (MatchList m)+ maybeMatchList = fmap drift (RegExOpts.setVerbose True regExOpts) `findMatch` inputData++{- |+ * Pattern-match operator.++ * Identifier & parameter-order follow the lead of /Perl/'s pattern-match operator.++ * Considerably more efficient than '+~', since even though they are both implemented via 'findMatch',+ the discovery of /any/ solution is sufficient to generate the return-value;+ lazy-evaluation avoids the requirement to identify the irrelevant optimal solution.+-}+(=~) :: (Eq m, Control.DeepSeq.NFData m)+ => InputData m -- ^ The input data within which to locate a match.+ -> RegExOpts.RegExOpts (ExtendedRegEx m) -- ^ The match-options parameterised by the regex against which to match the input data.+ -> Bool+inputData =~ regExOpts = Data.Maybe.isJust $ fmap drift (RegExOpts.setVerbose False regExOpts) `findMatch` inputData++-- | Pattern-mismatch operator.+(/~) :: (Eq m, Control.DeepSeq.NFData m)+ => InputData m -- ^ The input data within which to locate a match.+ -> RegExOpts.RegExOpts (ExtendedRegEx m) -- ^ The match-options parameterised by the regex against which to match the input data.+ -> Bool+(/~) inputData = not . (inputData =~)++-- | The delimiters of 'Alternatives', when in the 'String'-form.+captureGroupDelimiters :: (Char, Char)+captureGroupDelimiters = ('(', ')')++-- | The token used to separate alternative 'ExtendedRegEx's, when in the 'String'-form.+alternativeExtendedRegExSeparatorToken :: Char+alternativeExtendedRegExSeparatorToken = '|'++-- | The set of 'Char' to which a specific meaning is attributed, when reading from 'String'.+tokens :: String+tokens = [fst captureGroupDelimiters, snd captureGroupDelimiters, alternativeExtendedRegExSeparatorToken]++{- |+ * Invert the specified fraction, but return 'maxBound' if the result would otherwise be infinite.++ * CAVEAT: this is an awful concept, and therefore intended for internal use only.+-}+safeReciprocal :: (Eq f, Fractional f) => f -> f+safeReciprocal 0 = fromIntegral (maxBound :: Int) -- Handle divide-by-zero error.+safeReciprocal f = recip f++infixl 7 /+ -- Same as (/).++{- |+ * Integral division, with any fractional remainder rounded-up.++ * A rather dubious requirement, so internal use only.+-}+-- (/+) :: Integral i => i -> i -> i+(/+)+ :: Int -- ^ Numerator.+ -> Int -- ^ Denominator.+ -> Int -- 10% faster in unoptimised code, & more in optimised.+_ /+ 0 = error "RegExDot.RegEx.(/+):\tzero denominator => infinity"+-- numerator /+ denominator = ceiling ((fromIntegral numerator / fromIntegral denominator) :: Double)+numerator /+ denominator = uncurry (+) . Control.Arrow.second signum $ quotRem numerator denominator -- Slightly faster.
+ src-lib/RegExDot/RegExOpts.hs view
@@ -0,0 +1,65 @@+{-# LANGUAGE MultiParamTypeClasses, TypeSynonymInstances, FlexibleInstances #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++{-+ 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@]++ * Provides a complete job-specification.++ * 'regEx' is polymorphic, allowing use of either /RegExDot.RegEx.ExtendedRegEx/ (which is also polymorphic) or /RegExChar.ExtendedRegExChar.ExtendedRegExChar/.+-}++module RegExDot.RegExOpts(+-- * Types+-- ** Data-types+ RegExOpts(..),+-- * Functions+ setVerbose,+-- ** Constructors+ mkRegEx+) where++import qualified RegExDot.CompilationOptions as CompilationOptions+import qualified RegExDot.ExecutionOptions as ExecutionOptions+import qualified ToolShed.Defaultable++-- | Aggregates both 'ExecutionOptions.ExecutionOptions' & 'CompilationOptions.CompilationOptions' with a polymorphic /regex/, to form a complete job-description.+data RegExOpts a = MkRegExOpts {+ compilationOptions :: CompilationOptions.CompilationOptions, -- ^ Parameters governing the result, rather than the implementation.+ executionOptions :: ExecutionOptions.ExecutionOptions, -- ^ Parameters governing the implementation, rather than the result.+ regEx :: a -- ^ Polymorphic, to permit specialisation either of the type of /regex/ or the type of input-data.+} deriving Show++instance Functor RegExOpts where+ fmap f regExOpts = regExOpts { regEx = f $ regEx regExOpts }++-- | Smart constructor using 'ToolShed.Defaultable.defaultValue'.+mkRegEx :: a -> RegExOpts a+mkRegEx r = MkRegExOpts {+ compilationOptions = ToolShed.Defaultable.defaultValue,+ executionOptions = ToolShed.Defaultable.defaultValue,+ regEx = r+}++-- | Mutator, which sets an appropriate 'ExecutionOptions.ExecutionOptions', for the depth to which the caller wants to probe the resulting match.+setVerbose :: Bool -> RegExOpts a -> RegExOpts a+setVerbose verbose regExOpts = regExOpts { executionOptions = ExecutionOptions.setVerbose verbose $ executionOptions regExOpts }+
+ src-lib/RegExDot/Repeatable.hs view
@@ -0,0 +1,400 @@+{-# LANGUAGE CPP #-}+{-+ 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@]++ * 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.Arrow((***))+import qualified Control.DeepSeq+import qualified Data.List+import qualified RegExDot.Consumer as Consumer+import qualified RegExDot.ConsumptionProfile as ConsumptionProfile+import qualified Text.ParserCombinators.Parsec as Parsec+import Text.ParserCombinators.Parsec((<?>))+import qualified ToolShed.Data.Pair+import qualified ToolShed.SelfValidate++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative((<$>), (<*>))+#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 ParsecT-monad-} (oneOrMore b),+ (Parsec.char zeroOrOneToken <?> "Repeatable.zeroOrOneToken " ++ show zeroOrOneToken) >> return {-to ParsecT-monad-} (zeroOrOne b),+ (Parsec.char zeroOrMoreToken <?> "Repeatable.zeroOrMoreToken " ++ show zeroOrMoreToken) >> return {-to ParsecT-monad-} (zeroOrMore b),+ (b ^#->#) {-arbitrarily greedy for now-} <$> uncurry Parsec.between (ToolShed.Data.Pair.mirror Parsec.char rangeDelimiters) (+ do+ fewest <- Parsec.spaces >> (read <$> Parsec.many1 Parsec.digit <?> "Repetition-range minimum")+ most <- Parsec.spaces >> Parsec.option (+ Just fewest -- The default.+ ) (+ do+ i <- (+ Parsec.char rangeSeparatorToken <?> "Repeatable.rangeSeparatorToken " ++ show rangeSeparatorToken+ ) >> Parsec.spaces >> Parsec.option Nothing {-default to open-ended range-} (+ Just . read <$> Parsec.many1 Parsec.digit <?> "Repetition-range maximum"+ )++ Parsec.spaces >> return {-to ParsecT-monad-} i+ )++ return {-to ParsecT-monad-} (fewest, most)+ ) <?> "Repeatable.rangeDelimiters " ++ show rangeDelimiters+ ]++ g <- Parsec.option True {-the default-} $ (Parsec.char nonGreedyToken <?> "Repeatable.nonGreedyToken " ++ show nonGreedyToken) >> return {-to ParsecT-monad-} False++ return {-to ParsecT-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 ((,) <$> 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) $ ToolShed.Data.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 ToolShed.SelfValidate.SelfValidator a => ToolShed.SelfValidate.SelfValidator (Repeatable a) where+ getErrors MkRepeatable {+ base = b,+ repetitionBounds = (fewest, most),+ isGreedy = g+ }+ | not $ ToolShed.SelfValidate.isValid b = ToolShed.SelfValidate.getErrors b -- Delegate.+ | otherwise = ToolShed.SelfValidate.extractErrors [+ (fewest < 0, "Negative fewest=" ++ show fewest ++ "."),+ (+ case most of+ Just m -> m < fewest+ _ -> False,+ "Invalid repetition-range; '" ++ show (fewest, most) ++ "'."+ ), (+ not 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.+ _ -> False,+ "Invalid non-greedy repetition-range; '" ++ show (fewest, most) ++ "'."+ )+ ]++instance Control.DeepSeq.NFData a => Control.DeepSeq.NFData (Repeatable a) where+ rnf MkRepeatable {+ base = b,+ repetitionBounds = r,+ isGreedy = g+ } = Control.DeepSeq.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]+
+ src-lib/RegExDot/Result.hs view
@@ -0,0 +1,60 @@+{-+ 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@] Tools to manipulate a 'RegEx.Result'.++ [@CAVEAT@] The data-definition remains in "RegEx", since it references 'RegEx.Match'.+-}++module RegExDot.Result(+-- * Functions+-- ** Accessors (Deconstructors)+ getMatchList,+ getPreMatch,+ getPostMatch,+-- ** Predicates+ isMatch,+-- ** Query+ countMatches+) where++import qualified Data.Maybe+import qualified RegExDot.RegEx as RegEx++-- | Accessor.+getPreMatch :: RegEx.Result a -> RegEx.ExternalMatch a+getPreMatch (externalMatch, _, _) = externalMatch++-- | Accessor.+getPostMatch :: RegEx.Result a -> RegEx.ExternalMatch a+getPostMatch (_, _, externalMatch) = externalMatch++-- | Accessor.+getMatchList :: RegEx.Result a -> Maybe (RegEx.MatchList a)+getMatchList (_, maybeMatchList, _) = maybeMatchList++-- | True if the 'RegEx.InputData' matched the 'RegEx.ExtendedRegEx'.+isMatch :: RegEx.Result a -> Bool+isMatch = Data.Maybe.isJust . getMatchList++-- | Counts the number of top-level 'RegEx.Match'es.+countMatches :: RegEx.Result a -> Int+countMatches (_, Just matchList, _) = length matchList+countMatches _ = 0+
+ src-lib/RegExDot/ShowablePredicate.hs view
@@ -0,0 +1,53 @@+{-+ 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@] Used to implement /Perl-shortcut/s, /BracketExpression/-ranges & /POSIX Character-classes/.+-}++module RegExDot.ShowablePredicate(+-- * Types+ Predicate,+-- ** Data-types+ ShowablePredicate(+ MkShowablePredicate,+ name,+ predicate+ )+) where++import qualified Control.DeepSeq++-- | An arbitrary polymorphic predicate function.+type Predicate a = a -> Bool++-- | Container for both the /predicate/, & the name used in the implementation of 'Show'.+data ShowablePredicate a = MkShowablePredicate {+ name :: String, -- ^ The identifier of this predicate when in string-form.+ predicate :: Predicate a -- ^ The function used to determine whether an input datum matches.+}++instance Show (ShowablePredicate a) where+ showsPrec _ = showString . name++instance Eq (ShowablePredicate a) where+ l == r = name l == name r -- Ignore 'predicate'.++instance Control.DeepSeq.NFData (ShowablePredicate a) where+ rnf = Control.DeepSeq.rnf . name -- Ignore 'predicate'.+
+ src-lib/RegExDot/Span.hs view
@@ -0,0 +1,64 @@+{-+ 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@] Delimits a data-sequence, via its offset & length, within a wider list.+-}++module RegExDot.Span(+-- * Types+-- ** Type-synonyms+ Span,+-- * Functions+ after,+ empty,+ join+) where++import Control.Arrow((***))+import qualified RegExDot.ConsumptionBounds as ConsumptionBounds++{- |+ * The offset & length of a specific data-sequence, within a wider list.++ * Similar to <http://hackage.haskell.org/packages/archive/regex-base/latest/doc/html/Text-Regex-Base-RegexLike.html#t%3AMatchArray>.+-}+type Span = (ConsumptionBounds.DataLength {-offset-}, ConsumptionBounds.DataLength)++-- | An empty instance, located at the specified offset.+empty :: ConsumptionBounds.DataLength -> Span+empty offset = (offset, 0)++-- | Returns the furthest extent, i.e. the offset just after the end.+after :: Span -> ConsumptionBounds.DataLength+after = uncurry (+)++{- |+ * Condenses a list into a single value.++ * Expects a gap-free, sorted list.+-}+join+ :: ConsumptionBounds.DataLength -- ^ The offset into the list of input-data to use when a null list of spans is received.+ -> [Span]+ -> Span+join offset [] = empty offset -- The offset can't be deduced from a null list, so use the value provided.+join _ [singleton] = singleton -- Merely for efficiency.+join _ spanList = head *** sum $ unzip spanList++
+ src-lib/RegExDot/Tree.hs view
@@ -0,0 +1,80 @@+{-+ 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@]++ * Defines a specific type of tree-structure, which is composed from either a datum, or a list of Tree-lists; <http://en.wikipedia.org/wiki/Rose_Tree>.++ * This more general tree-structure has the shape of a regular-expression match.++ * This contrasts with the typical binary-tree, which is either empty, or contains a (left Tree, datum, right Tree).+-}++module RegExDot.Tree(+-- * Types+-- ** Type-synonyms+-- TreeList,+-- ** Data-types+ Tree(..),+-- * Functions+ pop+) where++import qualified Control.Arrow+import qualified Control.DeepSeq+import qualified Data.Foldable+import qualified Data.List+import qualified Data.Monoid++-- | A general purpose tree-type structure.+data Tree a =+ Leaf a -- ^ The payload.+ | Node [TreeList a] -- ^ Recurse. NB: a list of lists is required to contain the /MatchLists/ resulting from repeated /Alternatives/.+ deriving Eq++-- | A list of 'Tree's; significant only because it is the essence of the recursive nature of 'Tree'.+type TreeList a = [Tree a]++instance Show a => Show (Tree a) where+ showsPrec _ (Leaf a) = shows a+ showsPrec _ (Node treeLists) = showList treeLists++instance Read a => Read (Tree a) where+ readsPrec _ node@('[' : _) = Control.Arrow.first Node `map` readList node {-singleton-}+ readsPrec _ leaf = Control.Arrow.first Leaf `map` reads leaf {-singleton-}++instance Control.DeepSeq.NFData a => Control.DeepSeq.NFData (Tree a) where+ rnf (Leaf a) = Control.DeepSeq.rnf a+ rnf (Node l) = Control.DeepSeq.rnf l++instance Functor Tree where+ fmap f (Leaf a) = Leaf $ f a+ fmap f (Node treeLists) = Node $ map (fmap {-recurse-} f `map`) treeLists++instance Data.Foldable.Foldable Tree where+ foldMap f (Leaf a) = f a -- CAVEAT: 'f' should be Associative, as required by a Monoid.+ foldMap f (Node treeLists) = Data.List.foldl' (Data.List.foldl' (\monoid -> (monoid `Data.Monoid.mappend`) . Data.Foldable.foldMap f)) Data.Monoid.mempty treeLists++-- | Deconstruct the specified 'Node'; i.e. lop the apex from the 'Tree', leaving a flat top.+pop+ :: Tree a -- ^ The tree from which to extract the list of trees hanging immediately beneath its apex.+ -> [TreeList a]+pop (Node treeLists) = treeLists+pop _ = error "RegExDot.Tree.pop:\tunexpected Leaf"+
− src/RegExDot/Anchor.hs
@@ -1,90 +0,0 @@-{-- 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 type of /zero-width assertion/:- the 'Bow'-predicate is 'True' if no input-data has yet been consumed; the 'Stern'-predicate is 'True' if no input-data remains to be consumed.-- * Whilst at the top-level of the regex, the /absence/ of an anchor is equivalent to a non-capturing @.*?@,- tempting one to implement them using this property,- it doesn't apply to those anchors encountered at either end of a sub-expression.- So in all cases, an anchor constrains the permissible consumption of input-data,- but at the top level it also prevents the regex drifting away from either end of the input-data.--}--module RegExDot.Anchor(--- * Types--- ** Data-types- Anchor(..),--- ** Type-synonyms- AnchorSpecification,--- * Constants- bowToken,- sternToken,- tokens,- unanchored-) where--import qualified Control.DeepSeq---- | Defines the types on /anchor/ by which a /regex/ can be moored to a part of the input-data.-data Anchor =- Bow -- ^ Matches only if no input data has yet been consumed. Can only exist at the start of the entire regex, or (in theory) the start of any /alternative/.- | Stern -- ^ Matches only if no input data remains to be consumed. Can only exist at the end of the entire regex, or (in theory) the end of any /alternative/.- deriving (- Eq--- Read, -- See specialisation below.--- Show -- See specialisation below.- )--instance Show Anchor where- showsPrec _ Bow = showChar bowToken- showsPrec _ Stern = showChar sternToken--instance Read Anchor where- readsPrec _ [] = [] -- No parse.- readsPrec _ (' ' : s) = reads s -- Consume white-space.- readsPrec _ ('\t' : s) = reads s -- Consume white-space.- readsPrec _ (c : s) = case c `lookup` [(bowToken, Bow), (sternToken, Stern)] of- Just anchor -> [(anchor, s)]- _ -> [] -- No parse.--instance Control.DeepSeq.NFData Anchor where- rnf _ = ()---- | The conventional token used to denote a 'Bow'-anchor, when in 'String'-form.-bowToken :: Char-bowToken = '^'---- | The conventional token used to denote a 'Stern'-anchor, when in 'String'-form.-sternToken :: Char-sternToken = '$'---- | The set of 'Char' to which a specific meaning is attributed, when reading from 'String'.-tokens :: String-tokens = [bowToken, sternToken]---- | A specification for the anchors required around the expression-type AnchorSpecification = (Maybe Anchor, Maybe Anchor)---- | The specification for an unanchored expression.-unanchored :: AnchorSpecification-unanchored = (Nothing, Nothing)-
− src/RegExDot/BracketExpression.hs
@@ -1,63 +0,0 @@-{-- 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@]-- This concept was traditionally known as a /Character-class/,- but to distinguish it from the more recently introduced & narrower /POSIX Character-class/, it has been renamed /Bracket-expression/.--}--module RegExDot.BracketExpression(--- * Types--- ** Type-synonyms- BracketExpression,--- * Constants- delimiterTokens,- negationToken,- tokens,--- * Functions--- ** Predicates- containsMatch-) where--import RegExDot.BracketExpressionMember((=~))-import qualified RegExDot.BracketExpressionMember as BracketExpressionMember---- | A /Bracket-expression/ consists of a list of 'BracketExpressionMember.Member's.-type BracketExpression m = [BracketExpressionMember.Member m]---- | True if the specified datum matches any 'BracketExpressionMember.Member' of the 'BracketExpression'.-containsMatch :: Eq m- => m -- ^ The input datum.- -> BracketExpression m -- ^ The list of bracket-expression members within which to find a match.- -> Bool-containsMatch datum = any (datum =~)---- | The delimiters of a /bracket-expression/, when in 'String'-form.-delimiterTokens :: (Char, Char)-delimiterTokens = ('[', ']')---- | Used to denote a negated /bracket-expression/, when in 'String'-form.-negationToken :: Char-negationToken = '^'---- | The set of 'Char' to which a specific meaning is attributed, when reading from 'String'.-tokens :: String-tokens = [fst delimiterTokens, snd delimiterTokens, negationToken]-
− src/RegExDot/BracketExpressionMember.hs
@@ -1,93 +0,0 @@-{-- 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@]-- Permits /Perl-style shortcut/s to be canned & assigned a single-'Char' mnemonic for subsequent reference;- the implementation of 'Read' looks for a back-slashed 'Char', for which it expects there to be a corresponding canned 'ShowablePredicate.ShowablePredicate'.-- [@CAVEATS@]-- Since the underlying polymorphic data-type isn't required to implement neither 'Enum' nor 'Ord', the implementation of 'Read' can't cope with range-specifications.- Lacking this, Bracket-expression members must be enumerated exhaustively.--}--module RegExDot.BracketExpressionMember(--- * Type-classes- ShortcutExpander(..),--- * Types--- ** Data-types- Member(..),--- * Functions--- ** Operators- (=~)-) where--import qualified Control.Arrow-import qualified Control.DeepSeq-import qualified RegExDot.ShowablePredicate as ShowablePredicate--infix 4 =~ -- Same as (==).--{- |- * The interface via which /Perl-style shortcut/s are expanded (when they occur within a /bracket-expression/), in a manner appropriate to the chosen type-parameter.-- * The expansion of /Perl-style shortcut/s, is more restricted inside than outside, a /bracket-expression/,- & consequently are merely represented here by a 'ShowablePredicate.ShowablePredicate', rather than providing a more general form suitable also for those /Perl-style shortcuts/ found outside /bracket-expression/s.-- * This interface is implemented elsewhere, where the specific type-parameter & consequently the appropriate set of /Perl-style shortcut/s, are defined.--}-class ShortcutExpander a where- findPredicate :: Char -> Maybe (ShowablePredicate.ShowablePredicate a) -- ^ Attempt to find the appropriate 'ShowablePredicate.ShowablePredicate' to implement this /Perl-style shortcut/.--{- |- * A /BracketExpression/ can contain either a literal, a range of literals given @(Enum a, Ord a)@, a /Perl-style shortcut/, or when 'Char' is the type-parameter, a /POSIX Character-class/.-- * This data-type reduces the representation of all these possibilities to either a predicate or a literal.--}-data Member m =- Predicate (ShowablePredicate.ShowablePredicate m) -- ^ This 'Member' is described using a /predicate/, which is run to determine whether the datum conforms & is a member of the "BracketExpression".- | Literal m -- ^ This 'Member' is defined literally, using an item of the polymorphic type.- deriving Eq--instance Show m => Show (Member m) where- showsPrec _ (Predicate showablePredicate) = shows showablePredicate- showsPrec _ (Literal literal) = shows literal--instance (ShortcutExpander m, Read m) => Read (Member m) where- readsPrec _ [] = [] -- No parse.- readsPrec _ (' ' : s) = reads s -- Consume white-space.- readsPrec _ ('\t' : s) = reads s -- Consume white-space.- readsPrec _ ('\\' : shortcut : s) = case findPredicate shortcut of- Just showablePredicate -> [(Predicate showablePredicate, s)]- _ -> error $ "readsPrec RegExDot.BracketExpressionMember.Member:\tfindPredicate failed for shortcut " ++ show shortcut- readsPrec _ literal = Control.Arrow.first Literal `map` reads literal--instance Control.DeepSeq.NFData m => Control.DeepSeq.NFData (Member m) where- rnf (Predicate showablePredicate) = Control.DeepSeq.rnf showablePredicate- rnf (Literal literal) = Control.DeepSeq.rnf literal---- | Match-operator.-(=~) :: Eq m- => m -- ^ The input datum.- -> Member m -- ^ The member of the bracket-expression against which the input-datum is to be matched.- -> Bool -- ^ The result of the match-operation.-datum =~ Predicate showablePredicate = ShowablePredicate.predicate showablePredicate datum-datum =~ Literal literal = datum == literal-
− src/RegExDot/CompilationOptions.hs
@@ -1,48 +0,0 @@-{-- 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@]-- * Defines those options relating to compile-time.-- * These tend to be options which affect the result, rather than the means by which it is achieved.-- * Whilst similar structures are present in other regex-implementations, there's no standardisation of the fields.--}--module RegExDot.CompilationOptions(--- * Types--- ** Data-types- CompilationOptions(..)-) where--import qualified ToolShed.Defaultable-import qualified ToolShed.Options---- | The switch(es) used to control compilation of the /regex/-engine.-data CompilationOptions = MkCompilationOptions {- complyStrictlyWithPosix :: Bool -- ^ Define the offset of captured data, corresponding to a sub-expression which matched zero times, as the artificial value @-1@ specified by POSIX.-} deriving (Eq, Show)--instance ToolShed.Defaultable.Defaultable CompilationOptions where- defaultValue = ToolShed.Options.blankValue { complyStrictlyWithPosix = True }--instance ToolShed.Options.Options CompilationOptions where- blankValue = MkCompilationOptions { complyStrictlyWithPosix = undefined }-
− src/RegExDot/Consumer.hs
@@ -1,105 +0,0 @@-{-- 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@]-- * Define the requirements & capabilities of a data-type which is designed to consume data.-- * This module also facilitates investigation of group-behaviour, by defining operations on either concatenations or alternations of 'Consumer's.-- * This module is specifically for use in a /regex/-engine,- in which the meta-data from which the /regex/ is composed, are considered to /consume/ input data,- whilst concatenation & alternation of such elements, behave as a different types of groups of consumer.--}--module RegExDot.Consumer(--- * Type-classes- Consumer(..),--- * Types--- ** Type-synonyms--- StarHeight,--- * Functions- accumulateConsumptionProfiles,--- accumulateConsumptionProfilesFrom,- aggregateConsumptionProfilesFromAlternatives,- aggregateConsumptionProfilesFromConcatenation,--- ** Query- getConsumptionBounds,- getFewest,- getHasSpecificRequirement-) where--import RegExDot.ConsumptionProfile((<>), (|+|))-import qualified Data.List-import qualified RegExDot.ConsumptionBounds as ConsumptionBounds-import qualified RegExDot.ConsumptionProfile as ConsumptionProfile--{- |- * A measure of the complexity of a /regex/, which has some baring on either the time-complexity or the space-complexity of the solution.-- * <http://en.wikipedia.org/wiki/Star_height>--}-type StarHeight = Int--{- |- * The interface, to which types, which have a data-requirement, may conform.-- * Regrettably, methods requiring reference to the type of the consumable, would require multi-parameter type-classes.-- * 'consumptionProfile' defines the quantity-range of data which may be consumed, & any specific data-requirements.-- * 'starHeight' measures the complexity of the 'Consumer'.--}-class Consumer c where- consumptionProfile :: c -> ConsumptionProfile.ConsumptionProfile- starHeight :: c -> StarHeight--instance Consumer c => Consumer [c] where- consumptionProfile = undefined- starHeight = Data.List.foldl' (\l -> max l . starHeight) 0---- | Convenience-function, to query the 'ConsumptionBounds.ConsumptionBounds' of a 'Consumer'.-getConsumptionBounds :: Consumer c => c -> ConsumptionBounds.ConsumptionBounds-getConsumptionBounds = ConsumptionProfile.consumptionBounds . consumptionProfile---- | Determine the minimum acceptable quantity of data.-getFewest :: Consumer c => c -> ConsumptionBounds.DataLength-getFewest = fst . getConsumptionBounds---- | Determine whether the specified 'Consumer', has a specific requirement.-getHasSpecificRequirement :: Consumer c => c -> Bool-getHasSpecificRequirement = ConsumptionProfile.hasSpecificRequirement . consumptionProfile---- | The aggregate of the specified alternation of 'ConsumptionProfile.ConsumptionProfile's.-aggregateConsumptionProfilesFromAlternatives :: Consumer c => [c] -> ConsumptionProfile.ConsumptionProfile-aggregateConsumptionProfilesFromAlternatives [] = error "RegExDot.Consumer.aggregateConsumptionProfilesFromAlternatives:\tnull list"-aggregateConsumptionProfilesFromAlternatives l = Data.List.foldl1' (<>) $ consumptionProfile `map` l---- | Get the 'ConsumptionProfile.ConsumptionProfile's for the specified list of 'Consumer's, then find the net effect of concatenating them.-aggregateConsumptionProfilesFromConcatenation :: Consumer c => [c] -> ConsumptionProfile.ConsumptionProfile-aggregateConsumptionProfilesFromConcatenation = Data.List.foldl' (\acc -> (acc |+|) . consumptionProfile) ConsumptionProfile.zero {-initial value-}---- | Get the 'ConsumptionProfile.ConsumptionProfile' for the specified list of 'Consumer's, then accumulate them, from the specified initial value.-accumulateConsumptionProfilesFrom :: Consumer c => ConsumptionProfile.ConsumptionProfile -> [c] -> ConsumptionProfile.AccumulatedConsumptionProfiles-accumulateConsumptionProfilesFrom = scanr ((|+|) . consumptionProfile)---- | Get the 'ConsumptionProfile.ConsumptionProfile' for the specified list of 'Consumer's, then accumulate them.-accumulateConsumptionProfiles :: Consumer c => [c] -> ConsumptionProfile.AccumulatedConsumptionProfiles-accumulateConsumptionProfiles = {-init .-} accumulateConsumptionProfilesFrom ConsumptionProfile.zero -- It's useful to leave the initial value at the end of the list.-
− src/RegExDot/ConsumptionBounds.hs
@@ -1,48 +0,0 @@-{-- 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@] Describes the bounds of data-consumption.--}--module RegExDot.ConsumptionBounds(--- * Types--- ** Type-synonyms- ConsumptionBounds,- DataLength,--- * Constants- zero,--- * Functions--- ** Predicates- isPrecise-) where---- | A measure of the quantity of an unspecified type of data.-type DataLength = Int---- | The minimum & maximum bounds of potential data-consumption.-type ConsumptionBounds = (DataLength, Maybe DataLength)---- | Exactly zero consumption.-zero :: ConsumptionBounds-zero = (0, Just 0)---- | Predicate which is 'True' if only a precise quantity of data is consumable; no more, no less.-isPrecise :: ConsumptionBounds -> Bool-isPrecise (fewest, most) = Just fewest == most-
− src/RegExDot/ConsumptionProfile.hs
@@ -1,148 +0,0 @@-{-- 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@]-- A 'RegExDot.ConsumptionProfile' is composed from both a capacity to consume, & an ability to discriminate based on the specific data.- The former is described by 'consumptionBounds' & the latter by both 'hasSpecificRequirement' & 'canConsumeAnything'.--}--module RegExDot.ConsumptionProfile(--- * Types--- ** Type-synonyms- AccumulatedConsumptionProfiles,--- ** Data-types- ConsumptionProfile(..),--- * Constants- zero,--- * Functions- accumulateFrom,- aggregateFromConcatenation,--- ** Operators- (|+|),- (<>),--- ** Predicates--- isPrecise,- withinConsumptionBounds-) where--import Prelude hiding ((<$>), (<*>)) -- The "Prelude" from 'base-4.8' exports this symbol.-import Control.Applicative((<$>), (<*>))-import Control.Arrow((***))-import qualified Data.List-import qualified Data.Maybe-import qualified RegExDot.ConsumptionBounds as ConsumptionBounds-import qualified ToolShed.SelfValidate--infixr 5 |+| -- Same as (++).-infixr 2 <> -- Same as (||).--{- |- * A 'Consumer' is considered to have a 'ConsumptionProfile' composed from both a capacity to consume, & an ability to discriminate.-- * Whilst 'hasSpecificRequirement' & 'canConsumeAnything' look like opposites, they can assume independent values; the instance can be both, but only be neither when empty.--}-data ConsumptionProfile = MkConsumptionProfile {- consumptionBounds :: ConsumptionBounds.ConsumptionBounds, -- ^ The permissible quantity of data, which can be consumed.- hasSpecificRequirement :: Bool, -- ^ Whether at least one specific input datum is required.- canConsumeAnything :: Bool -- ^ Whether at least one arbitrary input datum can be consumed.-} deriving (Eq, Read, Show)--instance ToolShed.SelfValidate.SelfValidator ConsumptionProfile where- getErrors c@(- MkConsumptionProfile {- consumptionBounds = (fewest, most),- hasSpecificRequirement = hasSpecificRequirement',- canConsumeAnything = canConsumeAnything'- }- ) = ToolShed.SelfValidate.extractErrors [- (fewest < 0, "Negative fewest=" ++ show fewest ++ "."),- (- case most of- Just m -> m < fewest- _ -> False,- "Invalid 'most'; " ++ show c ++ "."- ),- (not $ or [c == zero, hasSpecificRequirement', canConsumeAnything'], "Invalid " ++ show c ++ ".")- ]--{---- | True if there's no choice in the quantity of data to consume.-isPrecise :: ConsumptionProfile -> Bool-isPrecise = ConsumptionBounds.isPrecise . consumptionBounds--}---- | Predicate, which is 'True' if the specified data-length, falls within the specified 'ConsumptionBounds.ConsumptionBounds'.-withinConsumptionBounds- :: ConsumptionBounds.DataLength -- ^ The actual quantity of data consumed.- -> ConsumptionProfile -- ^ The bounds within which data-consumption is required to fall.- -> Bool-withinConsumptionBounds dataLength = uncurry (&&) . ((dataLength >=) *** Data.Maybe.maybe True (dataLength <=)) . consumptionBounds---- | Both minimum & maximum set to zero; which can be used as the initial value when accumulating the sum of a list.-zero :: ConsumptionProfile-zero = MkConsumptionProfile {- consumptionBounds = ConsumptionBounds.zero,- hasSpecificRequirement = False,- canConsumeAnything = False-}---- | The net effect of two concatenated 'ConsumptionProfile's.-(|+|) :: ConsumptionProfile -> ConsumptionProfile -> ConsumptionProfile-MkConsumptionProfile {- consumptionBounds = (lf, ls),- hasSpecificRequirement = lh,- canConsumeAnything = lc-} |+| MkConsumptionProfile {- consumptionBounds = (rf, rs),- hasSpecificRequirement = rh,- canConsumeAnything = rc-} = MkConsumptionProfile {- consumptionBounds = (lf + rf, (+) <$> ls <*> rs), -- The sum of those of the concatenation.- hasSpecificRequirement = lh || rh, -- The concatenation mandates consumption of at least one specific input datum, if either 'ConsumptionProfile' does.- canConsumeAnything = lc || rc -- The concatenation can consume at least one arbitrary input datum, if either 'ConsumptionProfile' can.-}---- | The net effect of two alternative 'ConsumptionProfile's.-(<>) :: ConsumptionProfile -> ConsumptionProfile -> ConsumptionProfile-MkConsumptionProfile {- consumptionBounds = (lf, ls),- hasSpecificRequirement = lh,- canConsumeAnything = lc-} <> MkConsumptionProfile {- consumptionBounds = (rf, rs),- hasSpecificRequirement = rh,- canConsumeAnything = rc-} = MkConsumptionProfile {- consumptionBounds = (lf `min` rf, max <$> ls <*> rs), -- Stretched to envelope alternatives.- hasSpecificRequirement = lh && rh, -- The alternation mandates consumption of at least one specific input datum, if both 'ConsumptionProfile's do.- canConsumeAnything = lc || rc -- The alternation can consume at least one arbitrary input datum, if either 'ConsumptionProfile' can.-}---- | The aggregate of the specified concatenation of 'ConsumptionProfile's.-aggregateFromConcatenation :: [ConsumptionProfile] -> ConsumptionProfile-aggregateFromConcatenation = Data.List.foldl' (|+|) zero {-initial value-}---- | The 'ConsumptionProfile's of all the aggregated tails of a list of 'Consumer's.-type AccumulatedConsumptionProfiles = [ConsumptionProfile]---- | Accumulate the specified list of 'ConsumptionProfile's, from the specified initial value.-accumulateFrom :: ConsumptionProfile -> [ConsumptionProfile] -> AccumulatedConsumptionProfiles-accumulateFrom = scanr (|+|)-
− src/RegExDot/DSL.hs
@@ -1,117 +0,0 @@-{-- 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@]-- * Defines /binary/ operators, to form a /Domain-specific Language/, by which to compose 'RegEx.Concatenations' from 'RegEx.Pattern's.-- * Each operator quantifies the specified 'RegEx.Pattern', & prepends it to the specified 'RegEx.Concatenation'.-- [@TODO@] Could @[]@ & 'Meta' be instances of a type-class which has methods (-:) etc.,- allowing the former to prepend 'RegEx.captureGroup' & the latter 'RegEx.Require' ?---}--module RegExDot.DSL(--- * Functions--- ** Operators- (-:),- (?:),- (??:),- (*:),- (*?:),- (+:),- (+?:),- ( #->#:),- ( #->#?:),- ( #->:),- ( #->?:),- ( #:),- (<~>)-) where--import RegExDot.Repeatable((^#->#), (^#->#?), (^#->), (^#->?), (^#))-import qualified RegExDot.Anchor as Anchor-import qualified RegExDot.RegEx as RegEx-import qualified RegExDot.Repeatable as Repeatable--infixr 5 -:, ?:, ??:, *:, *?:, +:, +?:,#->#:, #->#?:, #->:, #->?:, #:, <~> -- Same as for ':', & lower than Repeatable's operators.---- | Prepend an unrepeated 'RegEx.Pattern', to the specified 'RegEx.Concatenation'.-(-:) :: RegEx.Pattern a -> RegEx.Concatenation a -> RegEx.Concatenation a-(-:) pattern = (Repeatable.one pattern :)---- | Prepend an optional 'RegEx.Pattern', to the specified 'RegEx.Concatenation'.-(?:) :: RegEx.Pattern a -> RegEx.Concatenation a -> RegEx.Concatenation a-(?:) pattern = (Repeatable.zeroOrOne pattern :)---- | A /non-greedy/ version of '?:'.-(??:) :: RegEx.Pattern a -> RegEx.Concatenation a -> RegEx.Concatenation a-(??:) pattern = (Repeatable.zeroOrOne' pattern :)---- | Prepend a 'RegEx.Pattern', repeatable zero or more times, to the specified 'RegEx.Concatenation'.-(*:) :: RegEx.Pattern a -> RegEx.Concatenation a -> RegEx.Concatenation a-(*:) pattern = (Repeatable.zeroOrMore pattern :)---- | A /non-greedy/ version of '*:'.-(*?:) :: RegEx.Pattern a -> RegEx.Concatenation a -> RegEx.Concatenation a-(*?:) pattern = (Repeatable.zeroOrMore' pattern :)---- | Prepend a 'RegEx.Pattern', repeatable one or more times, to the specified 'RegEx.Concatenation'.-(+:) :: RegEx.Pattern a -> RegEx.Concatenation a -> RegEx.Concatenation a-(+:) pattern = (Repeatable.oneOrMore pattern :)---- | A /non-greedy/ version of '+:'.-(+?:) :: RegEx.Pattern a -> RegEx.Concatenation a -> RegEx.Concatenation a-(+?:) pattern = (Repeatable.oneOrMore' pattern :)---- | Prepend a 'RegEx.Pattern', repeated a range of times, to the specified 'RegEx.Concatenation'.-( #->#:) :: (RegEx.Pattern a, Repeatable.RepetitionBounds) -> RegEx.Concatenation a -> RegEx.Concatenation a-( #->#:) (pattern, bounds) = (pattern ^#-># bounds :)---- | A /non-greedy/ version of '#->#:'.-( #->#?:) :: (RegEx.Pattern a, Repeatable.RepetitionBounds) -> RegEx.Concatenation a -> RegEx.Concatenation a-( #->#?:) (pattern, bounds) = (pattern ^#->#? bounds :)---- | Prepend a 'RegEx.Pattern', repeated at least a specified number of times, to the specified 'RegEx.Concatenation'.-( #->:) :: (RegEx.Pattern a, Repeatable.Repetitions) -> RegEx.Concatenation a -> RegEx.Concatenation a-( #->:) (pattern, fewest) = (pattern ^#-> fewest :)---- | A /non-greedy/ version of '#->:'.-( #->?:) :: (RegEx.Pattern a, Repeatable.Repetitions) -> RegEx.Concatenation a -> RegEx.Concatenation a-( #->?:) (pattern, fewest) = (pattern ^#->? fewest :)---- | Prepend a 'RegEx.Pattern', repeated a precise number of times, to the specified 'RegEx.Concatenation'.-( #:) :: (RegEx.Pattern a, Repeatable.Repetitions) -> RegEx.Concatenation a -> RegEx.Concatenation a-( #:) (pattern, r) = (pattern ^# r :)--{- |- * Sandwiches a 'RegEx.Concatenation' between optional 'Anchor.Anchor's to construct a 'RegEx.ExtendedRegEx'.-- * Accounts for the unusual requirement to place an 'Anchor.Stern' at the start of the 'RegEx.Concatenation', or an 'Anchor.Bow' at the end.--}-(<~>)- :: Anchor.AnchorSpecification -- ^ A specification for the anchors required around the expression.- -> RegEx.Concatenation a -- ^ The bare list of repeatable patterns to anchor.- -> RegEx.ExtendedRegEx a -- ^ The completed regex.-(bow, stern) <~> concatenation = RegEx.MkExtendedRegEx {- RegEx.bowAnchor = bow,- RegEx.concatenation = concatenation,- RegEx.sternAnchor = stern-}
− src/RegExDot/DataSpan.hs
@@ -1,61 +0,0 @@-{-- 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@] Augments a 'Span.Span', with the specific data concerned.--}--module RegExDot.DataSpan(--- * Types--- ** Type-synonyms- DataSpan,--- * Functions- after,- empty,- join-) where--import Control.Arrow((***))-import qualified RegExDot.ConsumptionBounds as ConsumptionBounds-import qualified RegExDot.Span as Span--{- |- * Augment 'Span.Span' with a copy of the data to which it refers.-- * Similar to <http://hackage.haskell.org/packages/archive/regex-base/latest/doc/html/Text-Regex-Base-RegexLike.html#t%3AMatchText>.--}-type DataSpan a = ([a], Span.Span)---- | Constructs an empty instance, located at the specified offset.-empty :: ConsumptionBounds.DataLength -> DataSpan a-empty offset = ([], Span.empty offset)---- | Returns the furthest extent, i.e. the offset just after the end.-after :: DataSpan a -> ConsumptionBounds.DataLength-after = Span.after . snd--{- |- * Condenses a list into a single value.-- * Expects a gap-free, sorted list.--}-join- :: ConsumptionBounds.DataLength -- ^ The offset at which the concatenated match is considered to have occurred.- -> [DataSpan a] -- ^ The list to be concatenated.- -> DataSpan a-join offset = (concat *** Span.join offset) . unzip
− src/RegExDot/DataSpanTree.hs
@@ -1,108 +0,0 @@-{-- 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@] Permits transformation of 'RegEx.MatchList', to facilitate standardisation.--}--module RegExDot.DataSpanTree(--- * Types--- ** Type-synonyms--- DataSpanTree,--- DataSpanTreeList,--- * Functions- extractCaptureGroups,- flattenTreeList,- toTreeList-) where--import qualified Data.Foldable-import qualified RegExDot.ConsumptionBounds as ConsumptionBounds-import qualified RegExDot.DataSpan as DataSpan-import qualified RegExDot.RegEx as RegEx-import qualified RegExDot.Tree as Tree---- | Defines a similar 'Tree.Tree' to 'RegEx.Match', but with different 'Tree.Leaf's.-type DataSpanTree a = Tree.Tree (DataSpan.DataSpan a)---- | Defines a similar structure to 'RegEx.MatchList'.-type DataSpanTreeList a = [DataSpanTree a]---- | Converts a 'RegEx.MatchList' into a 'DataSpanTreeList', by transforming the 'Tree.Leaf's.-toTreeList :: RegEx.MatchList a -> DataSpanTreeList a-toTreeList = map toTree where- toTree :: RegEx.Match a -> DataSpanTree a- toTree = fmap toDataSpan where- toDataSpan :: RegEx.MatchedData a -> DataSpan.DataSpan a- toDataSpan (_, inputDataOffset, inputData) = (inputData, (inputDataOffset, length inputData))---- | Condenses a 'DataSpanTreeList's into a list of 'DataSpan.DataSpan's, using 'DataSpan.join'.-flattenTreeList- :: ConsumptionBounds.DataLength -- ^ The offset into the input-data at which a match occurred.- -> DataSpanTreeList a -- ^ The tree to flatten.- -> [DataSpan.DataSpan a]-flattenTreeList _ [] = []-flattenTreeList offset (tree : treeList) = flattenedTree : flattenTreeList (DataSpan.after flattenedTree) treeList where--- flattenedTree :: DataSpan.DataSpan a- flattenedTree = DataSpan.join offset $ Data.Foldable.toList tree--{- |- * POSIX describes the contents of /capture-groups/, as summarised in <http://www2.research.att.com/~gsf/testregex/>.-- * 'RegEx.Result', is a complete description of the match between 'RegEx.InputData' & RegEx.ExtendedRegEx'; this function extracts a POSIX-conformant list from it.-- * The major differences are, that:-- Only data from parenthesized sub-expressions ('RegEx.Alternatives') is captured.-- Only the /last/ repetition of a repeated sub-expression is returned.- <http://www.opengroup.org/onlinepubs/009695399/functions/regcomp.html>.-- The data captured within each parenthesized sub-expression, is summarised as a single 'DataSpan.DataSpan'.-- POSIX specifies a 'Span.Span'-offset of @-1@, for sub-expressions which match zero times; cf sub-expressions which consume nothing, once.- <http://www.opengroup.org/onlinepubs/009695399/functions/regcomp.html>.- @- ("ace" Text.Regex.Posix.=~ "a(b)*c(d)?e") :: Text.Regex.Base.RegexLike.MatchArray- array (0,2) [(0,(0,3)),(1,(-1,0)),(2,(-1,0))]-- ("ace" Text.Regex.Posix.=~ "a(b*)c(d?)e") :: Text.Regex.Base.RegexLike.MatchArray- array (0,2) [(0,(0,3)),(1,(1,0)),(2,(2,0))]- @- I consider this a poor convention, resulting from the focus of POSIX on C, which makes subsequent calculation from the list of 'DataSpan's difficult & error-prone.--}-extractCaptureGroups- :: Bool -- ^ Whether to strictly comply with /POSIX/.- -> DataSpanTreeList a -- ^ The tree-structure from which to extract the capture-groups.- -> [DataSpan.DataSpan a]-extractCaptureGroups complyStrictlyWithPosix = extractCaptureGroups' 0 where- extractCaptureGroups' :: ConsumptionBounds.DataLength -> DataSpanTreeList a -> [DataSpan.DataSpan a]- extractCaptureGroups' _ [] = []- extractCaptureGroups' offset (tree : treeList) = let--- recurseHorizontallyFrom :: ConsumptionBounds.DataLength -> [DataSpan.DataSpan a]- recurseHorizontallyFrom = (`extractCaptureGroups'` treeList)- in case tree of- Tree.Leaf dataSpan -> recurseHorizontallyFrom $ DataSpan.after dataSpan- Tree.Node [] -> DataSpan.empty (if complyStrictlyWithPosix then -1 else offset) : recurseHorizontallyFrom offset -- POSIX specifies an Span-offset of -1, for sub-expressions which match 0 times; cf sub-expressions which consumes nothing, once.- Tree.Node treeLists -> joinedFlattenedTreeList : (extractCaptureGroups' offset lastMatch {-recurse vertically-} ++ recurseHorizontallyFrom (DataSpan.after joinedFlattenedTreeList)) where--- lastMatch :: DataSpanTreeList a- lastMatch = last treeLists -- <http://www.opengroup.org/onlinepubs/009695399/functions/regcomp.html>.---- joinedFlattenedTreeList :: DataSpan.DataSpan a- joinedFlattenedTreeList = DataSpan.join offset $ flattenTreeList offset lastMatch-
− src/RegExDot/ExecutionOptions.hs
@@ -1,102 +0,0 @@-{-- 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@]-- * Defines those options relating to the "RegExDot.RegEx"-runtime.-- * These tend to be options which don't affect the result, only the means by which it is achieved.-- * Whilst similar structures are present in other regex-implementations, there's no standardisation of the fields.--}--module RegExDot.ExecutionOptions(--- * Types--- ** Data-types- ExecutionOptions(..),--- * Functions--- ** Mutators- setVerbose-) where--import qualified ToolShed.Defaultable-import qualified ToolShed.Options---- | The switches used to control execution of the /regex/-engine.-data ExecutionOptions = MkExecutionOptions {- abortTrialRepetitionsOnInherentFailure :: Bool, -- ^ If an /alternative/ can't match, irrespective of the subsequent /RegExDot.RegEx.Concatenation/, then avoid futile trial /RegExDot.Repeatable.Repetitions/. The converse of 'checkForUnconsumableData'.- abortTrialRepetitionsOnZeroConsumption :: Bool, -- ^ Check for zero data-consumption by the @n@-th /RegExDot.Repeatable.Repeatable RegExDot.RegEx.CaptureGroup/, before attempting @n+1@.- bypassInputDataForLiberalConsumer :: Bool, -- ^ Whether to bypass reading of the input data, if the mapping to specific /RegExDot.RegEx.RepeatablePattern/s isn't required, & the /RegExDot.RegEx.ExtendedRegEx/ can consume the required quantity of anything.- catchIncompatibleAnchors :: Bool, -- ^ Avoid futile trial solutions, involving repetitions of anchored alternatives, which must consume data.- checkExistenceOfInelasticTail :: Bool, -- ^ If the /RegExDot.RegEx.ExtendedRegEx/ ends in an inelastic (zero /Star-height/) tail, confirm its existence at the end of the /RegExDot.RegEx.InputData/.- checkForUnconsumableData :: Bool, -- ^ Check whether there's no possibility of consuming some of the input data. The converse of 'abortTrialRepetitionsOnInherentFailure'.- moderateGreed :: Bool, -- ^ Greedily consume data, only up to the limit beyond which, future requirements would be compromised.- permitReorderingOfAlternatives :: Bool, -- ^ Permit /RegExDot.RegEx.Alternatives/ to be re-ordered, in an attempt to more quickly locate a result.- preferAlternativesWhichFeedTheGreedy :: Bool, -- ^ Within the /RegExDot.RegEx.MatchedData/ from which each candidate /RegExDot.RegEx.Match/ amongst sequences of /RegExDot.RegEx.Alternatives/, is ultimately composed, prefer /RegExDot.ConsumptionBounds.ConsumptionBounds/ of /RegExDot.RegEx.InputData/, beyond /RegExDot.Repeatable.getFewest/, by /RegExDot.Repeatable.isGreedy RegExDot.RegEx.RepeatablePattern/s.- preferAlternativesWhichMimickUnrolling :: Bool, -- ^ Compare /RegExDot.ConsumptionBounds.ConsumptionBounds/ on successive /RegExDot.Repeatable.Repetitions/ of /RegExDot.RegEx.CaptureGroup/, between candidate /RegExDot.RegEx.Match/es, to mimic the behaviour of the unrolled /RegExDot.Repeatable.Repetitions/.- preferFewerRepeatedAlternatives :: Bool, -- ^ Prefer fewer /RegExDot.Repeatable.Repetitions/ of /RegExDot.RegEx.Alternatives/, to discourage the capture of null lists of /RegExDot.RegEx.InputData/.- requireMatchList :: Bool, -- ^ If merely interested in a 'Bool' result, rather than the optimal mapping of input data to /RegExDot.RegEx.RepeatablePattern/s, avoid unnecessary evaluation of the /RegEx.Match/.- unrollRepeatedSingletonAlternative :: Bool, -- ^ Check whether /RegExDot.RegEx.Alternatives/ consists of just a singleton /RegExDot.RegEx.ExtendedRegEx/, & has therefore been used merely as a capture-group. Though this doesn't affect the result, it vastly improves efficiency.- useFirstMatchAmongAlternatives :: Bool, -- ^ Rather than performing an exhaustive search for the optimal choice amongst /RegExDot.RegEx.Alternatives/, merely select the first that matches; conform to /Perl/ rather than /POSIX/.- validateMinConsumptionOfAlternatives :: Bool -- ^ When the number of repetitions of a /RegExDot.RegEx.CaptureGroup/ is precisely specified, check whether the resulting minimum data-requirement is available.-} deriving (Eq, Show)--instance ToolShed.Defaultable.Defaultable ExecutionOptions where- defaultValue = setVerbose False $ ToolShed.Options.blankValue {- abortTrialRepetitionsOnInherentFailure = True, -- Regrettably, this slightly reduces performance for most non-pathological patterns.- catchIncompatibleAnchors = True,- checkExistenceOfInelasticTail = True,- checkForUnconsumableData = True, -- Expensive, particularly when (not requireMatchList), & only typically useful in failure-scenarios.- moderateGreed = True, -- Cost may exceed benefit. TODO: confirm.- preferAlternativesWhichFeedTheGreedy = True,- preferAlternativesWhichMimickUnrolling = True,- preferFewerRepeatedAlternatives = True,- unrollRepeatedSingletonAlternative = True, -- Affects only efficiency, not the result.- useFirstMatchAmongAlternatives = False, -- Perl-style matching may be faster, but may also yield a sub-optimal Match.- validateMinConsumptionOfAlternatives = False -- The cost outweighs the small infrequent dividend.- }--instance ToolShed.Options.Options ExecutionOptions where- blankValue = MkExecutionOptions {- abortTrialRepetitionsOnInherentFailure = undefined,- abortTrialRepetitionsOnZeroConsumption = undefined,- bypassInputDataForLiberalConsumer = undefined,- catchIncompatibleAnchors = undefined,- checkExistenceOfInelasticTail = undefined,- checkForUnconsumableData = undefined,- moderateGreed = undefined,- permitReorderingOfAlternatives = undefined,- preferAlternativesWhichFeedTheGreedy = undefined,- preferAlternativesWhichMimickUnrolling = undefined,- preferFewerRepeatedAlternatives = undefined,- requireMatchList = undefined,- unrollRepeatedSingletonAlternative = undefined,- useFirstMatchAmongAlternatives = undefined,- validateMinConsumptionOfAlternatives = undefined- }---- | Sets those fields which depend crucially on whether the caller wants to retrieve any /RegExDot.RegEx.MatchList/ from the /RegExDot.RegEx.Result/, or just query whether there is one.-setVerbose :: Bool -> ExecutionOptions -> ExecutionOptions-setVerbose verbose e = e {- abortTrialRepetitionsOnZeroConsumption = verbose, -- The corresponding check, involves evaluation of a /RegExDot.RegEx.MatchList/, which is too expensive if the /RegExDot.RegEx.Matchlist/ isn't otherwise required.- bypassInputDataForLiberalConsumer = not verbose, -- Potentially bypasses reading of /RegExDot.RegEx.InputData/, which is inappropriate if the mapping into a /RegExDot.RegEx.Result/ is required.- permitReorderingOfAlternatives = not verbose, -- Doesn't help when 'requireMatchList', since an exhaustive search of /RegExDot.RegEx.Alternatives/, for the optimal solution, is performed.- requireMatchList = verbose-}-
− src/RegExDot/InstanceInt.hs
@@ -1,43 +0,0 @@-{-# OPTIONS_GHC -fno-warn-orphans #-}-{-- 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@] Provides a default implementation of the type-classes, required for 'RegEx.ExtendedRegEx' Int.-- [@CAVEATS@]-- In contrast to the traditional regex, there's no reason to escape a 'Char', other than when referring to a /shortcut/.- Since no /shortcuts/ are defined in this minimal definition, it's always an error to escape a character.--}--module RegExDot.InstanceInt() where--import qualified RegExDot.BracketExpressionMember as BracketExpressionMember-import qualified RegExDot.Meta as Meta-import qualified RegExDot.RegEx as RegEx--instance BracketExpressionMember.ShortcutExpander Int where- findPredicate _ = Nothing--instance Meta.ShortcutExpander Int where- expand c = error $ "RegExDot.Meta.ShortcutExpander.expand RegExDot.InstanceInt:\tunrecognised shortcut '" ++ show c ++ "'." -- A shortcut is the only legitimate reason to escape a character.--instance RegEx.ShortcutExpander Int where- expand c = error $ "RegExDot.RegEx.ShortcutExpander.expand RegExDot.InstanceInt:\tunrecognised shortcut '" ++ show c ++ "'." -- A shortcut is the only legitimate reason to escape a character.-
− src/RegExDot/Meta.hs
@@ -1,144 +0,0 @@-{-- 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@]-- * Describes the set of polymorphic data, which can be matched.-- * Only permits a match against exactly one polymorphic datum, which distinguishes it from a /zero-width assertion/, like an /anchor/, /word-boundary/, or /look-ahead assertion/.-- * Designed to be used by a polymorphic /regex/-engine, to implement the traditional meta-characters; @. [] [^]@.-- * Permits /Perl-style shortcuts/ for commonly used 'Meta'-data, to be canned & assigned a single-'Char' mnemonic for subsequent reference;- the implementation of 'Read' looks for a back-slashed 'Char', for which it expects there to be a corresponding canned 'Meta'.- Since this class is polymorphic, it has no knowledge of what shortcuts might be appropriate for the chosen type-parameter,- so the expansion from the back-slashed 'Char' to corresponding 'Meta'-data, is performed through the 'expand' interface of the 'ShortcutExpander' class, which should be implemented elsewhere.--}--module RegExDot.Meta(--- * Type-classes- ShortcutExpander(..),--- * Types--- ** Data-types- Meta(..),--- * Constants- shortcutToken,- anyToken,- tokens,--- * Functions--- ** Predicates- isMatch-) where--import qualified Control.Arrow-import qualified Control.DeepSeq-import qualified RegExDot.BracketExpression as BracketExpression-import qualified RegExDot.BracketExpressionMember as BracketExpressionMember-import qualified RegExDot.Consumer as Consumer-import qualified RegExDot.ConsumptionProfile as ConsumptionProfile-import qualified RegExDot.ShowablePredicate as ShowablePredicate-import qualified ToolShed.SelfValidate--{- |- * The interface via which /Perl-style shortcut/s are expanded, in a manner appropriate to the chosen type-parameter.-- * Since the expansion of /Perl-style shortcut/s, is more restricted inside than outside a 'BracketExpression.BracketExpression',- the former is considered to be a superclass, providing a base from which to build alternative implementations.--}-class BracketExpressionMember.ShortcutExpander m => ShortcutExpander m where- expand :: Char -> Meta m -- ^ Expand a /Perl-style shortcut/.---- | Declares a polymorphic data-type.-data Meta m =- Any -- ^ Any datum matches. Equivalent to @NoneOf []@, but more efficient. CAVEAT: independent of the type-parameter @a@.- | Literal m -- ^ The datum matches, if it's equal to the specified value. Equivalent to @AnyOf [BracketExpression.Literal x]@, but more efficient.- | AnyOf (BracketExpression.BracketExpression m) -- ^ The datum matches, if 'BracketExpression.containsMatch'.- | NoneOf (BracketExpression.BracketExpression m) -- ^ The datum matches, if @not BracketExpression.containsMatch@.- | Predicate (ShowablePredicate.ShowablePredicate m) -- ^ The datum matches if 'ShowablePredicate.ShowablePredicate'.- deriving (- Eq--- Read, -- Specialised below.--- Show -- Specialised below.- )--instance ToolShed.SelfValidate.SelfValidator (Meta m) where- getErrors _ = []--instance Show m => Show (Meta m) where- showsPrec _ Any = showChar anyToken- showsPrec _ (Literal m) = shows m- showsPrec _ (AnyOf bracketExpression) = shows bracketExpression- showsPrec _ (NoneOf bracketExpression) = showChar x . showChar BracketExpression.negationToken . showString xs where (x : xs) = show $ AnyOf bracketExpression- showsPrec _ (Predicate showablePredicate) = shows showablePredicate--instance (ShortcutExpander m, Read m) => Read (Meta m) where- readsPrec _ [] = [] -- No parse.- readsPrec _ (' ' : s) = reads s -- Consume white-space.- readsPrec _ ('\t' : s) = reads s -- Consume white-space.- readsPrec _ ('.' : s) = [(Any, s)]- readsPrec _ ('[' : '^' : noneOf) = Control.Arrow.first NoneOf `map` reads (fst BracketExpression.delimiterTokens : noneOf) {-Reconstruct without negation, & recurse-}- readsPrec _ anyOf@('[' : _) = Control.Arrow.first AnyOf `map` reads anyOf {-singleton-}- readsPrec _ ('\\' : c : s) = [(expand c, s)]- readsPrec _ literal = Control.Arrow.first Literal `map` reads literal {-singleton-}--instance Consumer.Consumer (Meta m) where- consumptionProfile meta = let- hasSpecificDataRequirement :: Bool- hasSpecificDataRequirement = case meta of- Any -> False- NoneOf [] -> False- _ -> True- in ConsumptionProfile.MkConsumptionProfile {- ConsumptionProfile.consumptionBounds = (1, Just 1),- ConsumptionProfile.hasSpecificRequirement = hasSpecificDataRequirement,- ConsumptionProfile.canConsumeAnything = not hasSpecificDataRequirement- }-- starHeight _ = 0--instance Control.DeepSeq.NFData m => Control.DeepSeq.NFData (Meta m) where- rnf Any = ()- rnf (Literal m) = Control.DeepSeq.rnf m- rnf (AnyOf bracketExpression) = Control.DeepSeq.rnf bracketExpression- rnf (NoneOf bracketExpression) = Control.DeepSeq.rnf bracketExpression- rnf (Predicate showablePredicate) = Control.DeepSeq.rnf showablePredicate---- | True if the specified datum matches.-isMatch :: Eq m- => m -- ^ The input datum.- -> Meta m -- ^ The meta-entity against which the input datum is to be matched.- -> Bool -- ^ The result of the match-operation.-isMatch _ Any = True-isMatch datum (Literal literal) = datum == literal-isMatch datum (AnyOf bracketExpression) = datum `BracketExpression.containsMatch` bracketExpression-isMatch datum (NoneOf bracketExpression) = not $ datum `isMatch` AnyOf bracketExpression -- This implementation leverages future enhancements to 'AnyOf'.-isMatch datum (Predicate showablePredicate) = ShowablePredicate.predicate showablePredicate datum---- | The token used to precede a /Perl-style shortcut/, when in the 'String'-form.-shortcutToken :: Char-shortcutToken = '\\'---- | The token used to denote 'Any', when in the 'String'-form.-anyToken :: Char-anyToken = '.'---- | The set of 'Char' to which a specific meaning is attributed, when reading from 'String'.-tokens :: String-tokens = [shortcutToken, anyToken] ++ BracketExpression.tokens-
− src/RegExDot/RegEx.hs
@@ -1,1198 +0,0 @@-{-- 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@]-- * This implementation of extended /regex/es, generalises the familiar concept of pattern-matching of character-strings, to matching lists composed from an arbitrary data-type.- The polymorphic data, from which the input data-list is composed, need only support @Eq@.-- * Because of the unknown stringified form of the underlying polymorphic data,- the /regex/ must be described by a comparatively verbose bracketed & comma-separated list, rather than the traditional /String/ containing Meta-characters.- Each element of this 'Concatenation' is a 'RepeatablePattern', which describes a permissible match against 'InputData'.-- * 'RepeatablePattern' can take one of two forms.- In the simplest case, it matches just a single item of the underlying polymorphic type, perhaps literally, though looser specifications also exist:- @.@ matches any input datum; @[x, y, z]@ matches any of @x@, @y@, or @z@; @[^x, y, z]@ matches anything but @x@, @y@, or @z@.- To support POSIX /ERE/s, 'RepeatablePattern' can also be a list 'Alternatives', each of which is recursively defined as an 'ExtendedRegEx', to form a tree-structure.-- * Each 'Pattern', can optionally be /quantified/ by either a traditional /greedy/, or a /Perl/-style /non-greedy/, suffix, e.g.; @[*, +, ?, {n, m}, {n,}, {n}, *?, +?, ??, {n, m}?, {n,}?]@.-- * For convenience, common specifications can be canned & assigned a single 'Char' mnemonic, for subsequent reference.- Since 'ExtendedRegEx' is polymorphic, the set of abbreviations appropriate in the context of the unspecified base-type,- must be implemented externally through the 'Meta.ShortcutExpander' interface.- This permits the use, when the type-parameter is 'Char', of /Perl-style shortcuts/ @[\\d\\D\\s\\S\\w\\W]@.-- * The algorithm, is the classic /back-tracking/ one, rather than either a /DFA/ or /NFA/.- This permits construction of 'Result' via which one can discover the deep mapping of 'InputData' into 'ExtendedRegEx',- & provides the flexibility to add the features now expected by modern /regex/-engines.- Since the type-parameter is unknown, & may represent a large object, the exponential space-complexity of creating a /DFA/ may present additional problems.- The exponential time-complexity of the /back-tracking/ algorithm is partially tamed by targeting obvious inefficiencies with specific optimisations.-- * Char-based regexen, traditionally overload the delimiters of a set of 'Alternatives' (parentheses), as a request for data-capture.- Here, in contrast, all 'RepeatablePattern's capture data, & repeated sub-expressions capture a list of data,- rather than arbitrarily recording just the last (<http://www.opengroup.org/onlinepubs/009695399/functions/regcomp.html>) item.-- [@REFERENCES@]-- * <http://en.wikipedia.org/wiki/Regular_expression>-- * <http://swtch.com/~rsc/regexp/regexp1.html>-- * <http://docstore.mik.ua/orelly/perl/prog/ch02_04.htm#PERL2-CH-2-SECT-4.1.2>-- * <http://www.macs.hw.ac.uk/~dsg/gph/papers/html/Strategies/strategies.html>-- * <http://hackage.haskell.org/packages/archive/regex-posix/latest/doc/html/Text-Regex-Posix.html>-- * <http://www.haskell.org/haskellwiki/Regular_expressions>-- * <http://www2.research.att.com/~gsf/testregex/re-interpretation.html>-- [@CAVEATS@]-- * Because of the definition of mutually recursive data-types, it is difficult to split this annoyingly large module, & preserve compatibility across compilers,- but it may be possible to break this cyclic dependency, by defining an interface to which one of the data-types defined here conforms.-- * Doesn't implement /Back-references/, making the definition of the 'ExtendedRegEx' context-free.-- * There's no integration with the type-classes defined in "Text.Regex.Base.RegexLike", which assumes 'Char'-based 'InputData';- though this could be added to a specialised instance.-- * When 'Alternatives' are defined, 'Result' becomes a tree-like structure.- Unless the alternative is a singleton, the specific alternative selected in the solution is typically unknown, & therefore the /structure/ of the branch of this tree is also unknown.- This lack of clarity is compounded when the 'Alternatives' are 'Repeatable.Repeatable', since a different one may be selected on each successive repetition.- Consequently, the user can't navigate this portion of the structure in a statically defined manner, to acquire the captured data.- Despite this, & in contrast to other /regex/-engines, access to the whole data-structure is available, since it doesn't seem advantage to hide it.- The user can then either use 'extractDataFromMatch' for that element of 'Result', thus aggregating the data from sections of unknown structure, or 'show' it, as an aid to debugging.-- [@TODO@]-- * Test parallel-operation, on a 3 or more processor machine.- If 'rnf' is less effective than 'rwhnf',- then the 'Control.DeepSeq.NFData' context can be removed,- reducing the requirements imposed on the type-parameter 'a'.-- * Try 'Data.List.Stream' (stream-fusion), a faster drop-in replacement for 'Data.List'; possibly integrated in GHC-6.12.-- * 'ExecutionOptions.bypassInputDataForLiberalConsumer' is too restrictive.- More generally, we can test whether the set of different 'a' in 'InputData', is a subset of those common to all remaining terms in the 'ExtendedRegEx'.- Using this rule, we can infer @"aaa ..." =~ MkExtendedRegEx [a,a+,a?,[ab]{2,3}]@, given compatible 'ConsumptionProfile.consumptionBounds'.-- * Nested repetitions, where nothing has been added to the expression, result in repeated trials of the same expression,- e.g.; @"(x{i,}){j,}"@ results in the same expansion for @(i, j) in [(2, 3), (3, 2), (6, 1), (1, 6)]@.- The resulting 'MatchList' may be different, but if the first such trial fails, so will all the remainder.-- * Should cope with empty sets of 'Alternatives' & zero repetitions, neither of which can ever match, but the wider pattern can, e.g. @(()|x{0}|y)@.-- * By removing 'RepeatablePattern' from 'Match', it can be isolated in a new module.- This would result in a significant loss of discoverability.-- * Expand repeated 'Anchor.Bow' with @fewest - 1@ null matches followed by recursive 'findMatch'-call with @repetitions = 1@.--}--module RegExDot.RegEx(--- * Type-classes- ShortcutExpander(..),--- * Types--- ** Type-synonyms--- BasicRegEx,- Concatenation,- ExternalMatch,- InputData,- MatchedData,- MatchList,--- MetaDataList,- RepeatablePattern,--- Transformation,--- ** Data-types- Alternatives(..),- Match,- ExtendedRegEx(..),- Pattern(..),- Result,--- * Constants- alternativeExtendedRegExSeparatorToken,--- anyDatum,- captureGroupDelimiters,- tokens,--- * Functions--- accumulateDistinctInputData,--- accumulateDistinctMetaDataFrom,- dock,--- drift,--- findMatch,- captureGroup,--- mkNullMatchFromConcatenation,--- mkNullMatchFromExtendedRegEx,--- mkNullMatchFromRepeatablePattern,--- safeReciprocal,--- shiftMatchedData,--- shiftMatch,- shiftMatchList,- showsMaybeAnchor,- simply,- transformExtendedRegEx,--- ** Accessors (Deconstructors)- deconstructAlternatives,--- getInputData,--- ** Operators- (+~),- (=~),- (/~),- (.*),- (.*?),--- (/+)--- ** Predicates--- hasBowAnchor,--- hasSternAnchor,- isDefined,- isCaptureGroup,- isSingletonAlternatives,--- isUnconsumableByAnyOf,--- ** Query--- getDistinctMetaDataFromAlternatives,--- getDistinctMetaDataFromConcatenation,--- getDistinctMetaDataFromExtendedRegEx,--- getDistinctMetaDataFromPattern,--- getDistinctMetaDataFromRepeatablePattern,- externalMatchLength,- extractDataFromMatch,- extractDataFromMatch',- extractDataFromMatchList-) where--import Control.Arrow((&&&))-import qualified Control.Arrow-import qualified Control.DeepSeq-import qualified Control.Parallel.Strategies-import qualified Data.Char-import qualified Data.Foldable-import qualified Data.List-import qualified Data.Maybe-import qualified Data.Ord-import qualified RegExDot.Anchor as Anchor-import qualified RegExDot.Consumer as Consumer-import qualified RegExDot.ConsumptionBounds as ConsumptionBounds-import qualified RegExDot.ConsumptionProfile as ConsumptionProfile-import qualified RegExDot.ExecutionOptions as ExecutionOptions-import qualified RegExDot.Meta as Meta-import qualified RegExDot.RegExOpts as RegExOpts-import qualified RegExDot.Repeatable as Repeatable-import RegExDot.Repeatable((^#->))-import qualified RegExDot.Tree as Tree-import qualified ToolShed.Data.List-import qualified ToolShed.Data.List.Splits-import qualified ToolShed.SelfValidate--infix 4 +~, =~, /~ -- Same as (==) & (/=).---- | The type of a /regex/, in which there's no provision for either 'Alternatives' or 'Anchor.Anchor's.-type BasicRegEx m = [Repeatable.Repeatable (Meta.Meta m)]--{- |- * Defines the method required to expand a mnemonic into an 'ExtendedRegEx'.-- * CAVEAT: this interface must be declared locally, since it references 'ExtendedRegEx', & 'ExtendedRegEx' references it.--}-class ShortcutExpander m where- expand :: Char -> ExtendedRegEx m -- ^ Expand a single-'Char' mnemonic into the corresponding 'ExtendedRegEx'.--{- |- * Represents the /alternation/ feature of 'ExtendedRegEx's.-- * One could amalgamate this with 'Pattern', since it seems to exist merely as a peg to hang /instance/-declarations from.--}-newtype Alternatives m = MkAlternatives [ExtendedRegEx m] deriving Eq---- | Accessor, to expose the guts.-deconstructAlternatives :: Alternatives m -> [ExtendedRegEx m]-deconstructAlternatives (MkAlternatives extendedRegExList) = extendedRegExList---- | Similar to 'fmap', but operates on ['ExtendedRegEx'], rather than just @a@.-transformAlternatives- :: ([ExtendedRegEx m] -> [ExtendedRegEx m]) -- ^ The transformation function to apply to the list of data behind the constructor.- -> Alternatives m- -> Alternatives m-transformAlternatives f = MkAlternatives . f . deconstructAlternatives--instance (- ShortcutExpander m,- Meta.ShortcutExpander m,- Eq m,- Read m- ) => Read (Alternatives m) where- readsPrec _ (' ' : s) = reads s -- Consume white-space.- readsPrec _ ('\t' : s) = reads s -- Consume white-space.- readsPrec _ s = case reads s of- [(extendedRegEx, s1)] -> case dropWhile Data.Char.isSpace s1 of- ('|' : s2) -> Control.Arrow.first (transformAlternatives (extendedRegEx :)) `map` reads s2 {-singleton-}- _ -> [(MkAlternatives [extendedRegEx], s1)]- _ -> [] -- No parse.--instance Show m => Show (Alternatives m) where- showsPrec _ = foldl (.) (showString "") . Data.List.intersperse (showChar alternativeExtendedRegExSeparatorToken) . map shows . deconstructAlternatives -- Replace the default list-format, with 'egrep'-syntax.--instance Consumer.Consumer (Alternatives m) where- consumptionProfile = Consumer.aggregateConsumptionProfilesFromAlternatives . deconstructAlternatives- starHeight = Consumer.starHeight . deconstructAlternatives -- Must evaluate all Alternatives to determine best.--instance ToolShed.SelfValidate.SelfValidator (Alternatives m) where- getErrors = ToolShed.SelfValidate.getErrors . deconstructAlternatives--instance Control.DeepSeq.NFData m => Control.DeepSeq.NFData (Alternatives m) where- rnf = Control.DeepSeq.rnf . deconstructAlternatives---- | 'Alternatives' can be employed as a simple /capture-group/ as well as a switch, under which circumstances there's no choice amongst multiple 'Alternatives'.-isSingletonAlternatives :: Alternatives m -> Bool-isSingletonAlternatives = (== 1) . length . deconstructAlternatives---- | A set of 'Meta.Meta' which can be consumed.-type MetaDataList m = [Meta.Meta m]--{- |- * 'True' if there's no possibility, that the specified input datum, can be consumed by any of the specified 'MetaDataList'.-- * Because data may not be consumed, perhaps because of unsuitable 'ConsumptionBounds.ConsumptionBounds', the converse doesn't hold,- i.e. @not isUnconsumableByAnyOf@ /doesn't/ imply that the input datum can be consumed, just that it hasn't been proven that it can't.--}-isUnconsumableByAnyOf :: Eq m- => m -- ^ The input datum.- -> MetaDataList m -- ^ The list of meta-data against any of which a match can occur.- -> Bool-isUnconsumableByAnyOf i = not . any (Meta.isMatch i)---- | The set of distinct 'Meta.Meta', in the specified 'Alternatives'.-getDistinctMetaDataFromAlternatives :: Eq m => Alternatives m -> MetaDataList m-getDistinctMetaDataFromAlternatives = foldr (Data.List.union . getDistinctMetaDataFromExtendedRegEx) [] . deconstructAlternatives---- | Defines either a simple 'Meta.Meta', which can match exactly one datum, or a set of 'Alternatives', each of which is recursively defined above, as an 'ExtendedRegEx'.-data Pattern m =- Require (Meta.Meta m) -- ^ Describes a requirement for a simple scalar datum of the polymorphic type.- | CaptureGroup (Alternatives m) -- ^ A sub-expression containing a selection of recursively defined alternatives, thus forming a tree-structure.- deriving Eq--instance (- Eq m,- Meta.ShortcutExpander m,- Read m,- ShortcutExpander m- ) => Read (Pattern m) where- readsPrec _ (' ' : s) = reads s -- Consume white-space.- readsPrec _ ('\t' : s) = reads s -- Consume white-space.- readsPrec _ ('(' : s) = case {-Alternatives.-} reads s of- [(alternatives, s1)] -> case dropWhile Data.Char.isSpace s1 of- (')' : s2) -> [(CaptureGroup alternatives, s2)]- _ -> [] -- No parse.- _ -> [] -- No parse.- readsPrec _ s = case reads s of- [pair] -> [Control.Arrow.first Require pair]- _ -> [] -- No parse.--instance Show m => Show (Pattern m) where- showsPrec _ (Require meta) = shows meta- showsPrec _ (CaptureGroup alternatives) = showChar (fst captureGroupDelimiters) . shows alternatives . showChar (snd captureGroupDelimiters)--instance Consumer.Consumer (Pattern m) where- consumptionProfile (Require meta) = Consumer.consumptionProfile meta- consumptionProfile (CaptureGroup alternatives) = Consumer.consumptionProfile alternatives-- starHeight (Require meta) = Consumer.starHeight meta- starHeight (CaptureGroup alternatives) = Consumer.starHeight alternatives--instance ToolShed.SelfValidate.SelfValidator (Pattern m) where- getErrors (Require meta) = ToolShed.SelfValidate.getErrors meta- getErrors (CaptureGroup alternatives) = ToolShed.SelfValidate.getErrors alternatives--instance Control.DeepSeq.NFData m => Control.DeepSeq.NFData (Pattern m) where- rnf (Require meta) = Control.DeepSeq.rnf meta- rnf (CaptureGroup alternatives) = Control.DeepSeq.rnf alternatives---- | Convenience-function to build a 'CaptureGroup' from a list of alternative 'ExtendedRegEx's.-captureGroup :: [ExtendedRegEx m] -> Pattern m-captureGroup = CaptureGroup . MkAlternatives---- | True if the 'Pattern' was constructed via 'CaptureGroup'.-isCaptureGroup :: Pattern m -> Bool-isCaptureGroup (CaptureGroup _) = True-isCaptureGroup _ = False---- | The set of distinct 'Meta.Meta', in the specified 'Pattern'.-getDistinctMetaDataFromPattern :: Eq m => Pattern m -> MetaDataList m-getDistinctMetaDataFromPattern pattern = case pattern of- Require meta -> [meta]- CaptureGroup alternatives -> getDistinctMetaDataFromAlternatives alternatives---- | Constant pattern, representing a lax 'Require'ment.-anyDatum :: Pattern a-anyDatum = Require Meta.Any---- | Make 'Pattern's, 'Repeatable.Repeatable'.-type RepeatablePattern m = Repeatable.Repeatable (Pattern m)---- | Construct a null 'Match' by assuming that the specified 'RepeatablePattern' consumes zero 'InputData'; of which the caller should ensure it's capable.-mkNullMatchFromRepeatablePattern- :: ConsumptionBounds.DataLength -- ^ The offset into the list of input data at which the zero-length match occurred.- -> RepeatablePattern m -- ^ The pattern that the input data matched.- -> Match m -- ^ The resulting match-structure.-mkNullMatchFromRepeatablePattern offset r = case Repeatable.base r of- Require _ -> Tree.Leaf (r, offset, [])- CaptureGroup _ -> Tree.Node $ Repeatable.getFewest r `replicate` []---- | The set of distinct 'Meta.Meta', in the specified 'RepeatablePattern'.-getDistinctMetaDataFromRepeatablePattern :: Eq m => RepeatablePattern m -> MetaDataList m-getDistinctMetaDataFromRepeatablePattern = getDistinctMetaDataFromPattern . Repeatable.base--{- |- * Represents a black hole, which will greedily consume all data.-- * CAVEAT: nullary, i.e. a constant.--}-(.*) :: RepeatablePattern m-(.*) = Repeatable.zeroOrMore anyDatum---- | A /non-greedy/ version of '.*'.-(.*?) :: RepeatablePattern m-(.*?) = Repeatable.zeroOrMore' anyDatum---- | Convenience-function, to build a 'RepeatablePattern' from an unrepeated instance of the specified 'Meta.Meta'-datum.-simply :: Meta.Meta m -> RepeatablePattern m-simply = Repeatable.one . Require---- | Represents the /concatenation/ aspect of 'ExtendedRegEx's.-type Concatenation m = [RepeatablePattern m]---- | Construct a null 'Match' by assuming that the specified 'Concatenation' consumes zero 'InputData'.-mkNullMatchFromConcatenation- :: ConsumptionBounds.DataLength -- ^ The offset into the list of input data at which the zero-length match occurred.- -> Concatenation m -- ^ The list of repeatable patterns that the input data matched.- -> MatchList m -- ^ The resulting match-structure.-mkNullMatchFromConcatenation offset = map $ mkNullMatchFromRepeatablePattern offset---- | The set of distinct 'Meta.Meta', in the specified 'Concatenation'.-getDistinctMetaDataFromConcatenation :: Eq m => Concatenation m -> MetaDataList m-getDistinctMetaDataFromConcatenation = foldr (Data.List.union . getDistinctMetaDataFromRepeatablePattern) []---- | The accumulating set of 'MetaDataList', resulting from each successive 'RepeatablePattern' in the specified 'Concatenation'.-accumulateDistinctMetaDataFrom :: Eq m => MetaDataList m -> Concatenation m -> [MetaDataList m]-accumulateDistinctMetaDataFrom = scanr (Data.List.union . getDistinctMetaDataFromRepeatablePattern)---- | Constructs an 'ExtendedRegEx', by surrounding a 'Concatenation' with optional 'Anchor.Anchor's.-data ExtendedRegEx m = MkExtendedRegEx {- bowAnchor :: Maybe Anchor.Anchor, -- ^ An option to anchor the /regex/ to the start of the 'InputData'.- concatenation :: Concatenation m, -- ^ The sequence of 'RepeatablePattern's defining the 'Require'ments that the 'InputData' must meet.- sternAnchor :: Maybe Anchor.Anchor -- ^ An option to anchor the /regex/ to the end of the 'InputData'.-} deriving Eq---- | The type of a function which transforms an 'ExtendedRegEx'.-type Transformation m = ExtendedRegEx m -> ExtendedRegEx m---- | Similar to 'fmap', but operates on 'Concatenation', rather than just @a@.-transformExtendedRegEx- :: (Concatenation m -> Concatenation m) -- ^ The function used to transform the data behind the constructor.- -> Transformation m-transformExtendedRegEx f extendedRegEx = extendedRegEx { concatenation = f $ concatenation extendedRegEx }--instance (- Eq m,- Meta.ShortcutExpander m,- Read m,- ShortcutExpander m- ) => Read (ExtendedRegEx m) where- readsPrec _ [] = []- readsPrec _ (' ' : s) = reads s -- Consume white-space.- readsPrec _ ('\t' : s) = reads s -- Consume white-space.- readsPrec _ ('\\' : c : s) = [(expand c, s)]- readsPrec _ s = case singleton of- [(extendedRegEx, _)]- | ToolShed.SelfValidate.isValid extendedRegEx -> singleton- | otherwise -> error $ ToolShed.SelfValidate.getFirstError extendedRegEx -- Parsed OK, but invalid.- _ -> [] -- No parse.- where--- singleton :: (Eq m, Meta.ShortcutExpander m, Read m, ShortcutExpander m) => [(ExtendedRegEx m, String)]- singleton = [- (- MkExtendedRegEx {- bowAnchor = maybeBowAnchor,- concatenation = concatenation',- sternAnchor = maybeSternAnchor- },- remainder- ) |- (maybeBowAnchor, s1) <- readsMaybeAnchor s,- (concatenation', s2) <- reads s1,- (maybeSternAnchor, remainder) <- readsMaybeAnchor s2- ] {-list-comprehension-} where- readsMaybeAnchor :: ReadS (Maybe Anchor.Anchor)- readsMaybeAnchor s' = return {-to List-monad-} $ case reads s' of- [pair] -> Control.Arrow.first Just pair- _ -> (Nothing, s')---- | Shows either the specified 'Anchor.Anchor', or a null string where 'Nothing' is specified.-showsMaybeAnchor :: Maybe Anchor.Anchor -> String -> String-showsMaybeAnchor maybeAnchor- | Data.Maybe.isJust maybeAnchor = shows $ Data.Maybe.fromJust maybeAnchor- | otherwise = id--instance Show m => Show (ExtendedRegEx m) where- showsPrec _ MkExtendedRegEx {- bowAnchor = maybeBowAnchor, -- CAVEAT: this could be 'Nothing' or perversely an 'Anchor.Stern'.- concatenation = concatenation',- sternAnchor = maybeSternAnchor -- CAVEAT: this could be 'Nothing' or perversely an 'Anchor.Bow'.- } = showsMaybeAnchor maybeBowAnchor . shows concatenation' . showsMaybeAnchor maybeSternAnchor--instance Consumer.Consumer (ExtendedRegEx m) where- consumptionProfile = Consumer.aggregateConsumptionProfilesFromConcatenation . concatenation- starHeight = Consumer.starHeight . concatenation--instance ToolShed.SelfValidate.SelfValidator (ExtendedRegEx m) where- getErrors = ToolShed.SelfValidate.getErrors . concatenation--instance Control.DeepSeq.NFData m => Control.DeepSeq.NFData (ExtendedRegEx m) where- rnf MkExtendedRegEx {- bowAnchor = maybeBowAnchor,- concatenation = concatenation',- sternAnchor = maybeSternAnchor- } = Control.DeepSeq.rnf (maybeBowAnchor, concatenation', maybeSternAnchor)---- | Drop 'Anchor.Anchor's at both bow & stern of the specified 'ExtendedRegEx'.-dock :: Transformation m-dock e = e {- bowAnchor = Just Anchor.Bow,- sternAnchor = Just Anchor.Stern-}---- | Amend the 'Concatenation' in the specified 'ExtendedRegEx', by prepending '.*' where there's a missing top-level 'Anchor.Bow' & appending '.*?' where there's a missing top-level 'Anchor.Stern'.-drift :: Transformation m-drift extendedRegEx = transformExtendedRegEx (- (- if hasBowAnchor extendedRegEx then id else {-no anchor-} ((.*?) :)- ) . (- if hasSternAnchor extendedRegEx then id else {-no anchor-} (++ [(.*)])- )- ) extendedRegEx---- | True if the specified 'ExtendedRegEx' has an initial 'Anchor.Bow'. CAVEAT: though typically the alternative is no 'Anchor.Anchor', there may perversely be an initial 'Anchor.Stern', but neither qualify.-hasBowAnchor :: ExtendedRegEx m -> Bool-hasBowAnchor = (== Just Anchor.Bow) . bowAnchor---- | True if the specified 'ExtendedRegEx' has a terminal 'Anchor.Stern'. CAVEAT: though typically the alternative is no 'Anchor.Anchor', there may perversely be a terminal 'Anchor.Bow', but neither qualify.-hasSternAnchor :: ExtendedRegEx m -> Bool-hasSternAnchor = (== Just Anchor.Stern) . sternAnchor---- | True if there's at least one 'RepeatablePattern' in the 'Concatenation', i.e. that it's non-null.-isDefined :: ExtendedRegEx m -> Bool-isDefined = not . null . concatenation---- | Construct a null 'Match' by assuming that the specified 'ExtendedRegEx' consumes zero 'InputData'.-mkNullMatchFromExtendedRegEx :: ConsumptionBounds.DataLength -> ExtendedRegEx m -> MatchList m-mkNullMatchFromExtendedRegEx offset = mkNullMatchFromConcatenation offset . concatenation---- | The set of distinct (i.e. unique) 'Meta.Meta', in the specified 'ExtendedRegEx'.-getDistinctMetaDataFromExtendedRegEx :: Eq m => ExtendedRegEx m -> MetaDataList m-getDistinctMetaDataFromExtendedRegEx = getDistinctMetaDataFromConcatenation . concatenation--{- |- * The input-data is just a list.-- * Whilst typically this list is also a 'String', & could therefore be more efficiently implemented using "Data.ByteString",- we can't assume that the polymorphic base-type is always 'Char'.--}-type InputData m = [m]---- | The accumulating sets of distinct input data.-accumulateDistinctInputData :: Eq m => InputData m -> [InputData m]-accumulateDistinctInputData = scanr (Data.List.union . return) []---- | Tag the 'InputData' with the 'RepeatablePattern' it matched (which unfortunately confines the definition to this (bloated) module), & the offset from the start of the data;-type MatchedData m = (RepeatablePattern m, ConsumptionBounds.DataLength, InputData m)---- | Shifts the offset of the specified 'MatchedData'.-shiftMatchedData- :: ConsumptionBounds.DataLength -- ^ The offset by which to shift the position into the input-data at which a match occurred.- -> MatchedData m -- ^ The match-structure whose offset is to be shifted.- -> MatchedData m-shiftMatchedData i (r, offset, d) = (r, offset + i, d)---- | Accessor.-getInputData :: MatchedData m -> InputData m-getInputData (_, _, inputData) = inputData---- | Describes the manner in which a 'RepeatablePattern' successfully consumed 'InputData'.-type Match m = Tree.Tree (MatchedData m)---- | Shifts the offsets of all the 'MatchedData' contained in the specified 'Match'.-shiftMatch- :: ConsumptionBounds.DataLength -- ^ The offset by which to shift the position into the input-data at which a match occurred.- -> Match m -- ^ The match-structure whose offset is to be shifted.- -> Match m-shiftMatch i = (shiftMatchedData i `fmap`)---- | Extract & concatenate, the 'InputData' from a 'Match'.-extractDataFromMatch :: Match m -> InputData m-extractDataFromMatch = Data.Foldable.foldMap getInputData -- Uses the List-monoid's associative binary operator (++), to concatenate the values returned by the specified function.---- | Extract & concatenate, the 'InputData' from a 'Match'; null if it didn't match any.-extractDataFromMatch' :: Maybe (Match m) -> InputData m-extractDataFromMatch' (Just match) = extractDataFromMatch match-extractDataFromMatch' _ = []---- | Describes the manner in which a 'Concatenation' successfully consumed 'InputData'.-type MatchList m = [Match m]---- | Shifts the offsets of all the 'MatchedData' contained in the specified 'MatchList'.-shiftMatchList- :: ConsumptionBounds.DataLength -- ^ The offset by which to shift the position into the input-data at which a each listed match occurred.- -> MatchList m -- ^ The list of match-structures, each of whose offsets are to be shifted.- -> MatchList m-shiftMatchList i = map (shiftMatch i)---- | Extract & concatenate, the 'InputData', from the 'MatchList'.-extractDataFromMatchList :: MatchList m -> InputData m-extractDataFromMatchList = concatMap extractDataFromMatch -- CAVEAT: too fine-grain for effective data-parallelism.---- | At the top-level of an 'ExtendedRegEx', the lack of an 'Anchor.Anchor' allows the 'ExtendedRegEx' to drift away from the corresponding end of the input-data; this data-gap is captured here.-type ExternalMatch m = Maybe (Match m)---- | Captures the list of input-data consumed by the 'Concatenation', bracketed by any data-prefix or data-suffix.-type Result m = (ExternalMatch m, Maybe (MatchList m), ExternalMatch m)---- | Returns the length of data consumed by the specified 'ExternalMatch'.-externalMatchLength :: ExternalMatch m -> ConsumptionBounds.DataLength-externalMatchLength = length . extractDataFromMatch'--{- |- * Similar to '+~', but exposes 'ExecutionOptions.ExecutionOptions', permitting greater control; & enough rope to hang yourself.-- * The parameter-order facilitates partial application, to acquire a matcher-function for a specific 'ExtendedRegEx', which can be applied repeatedly to different 'InputData'.-- * Performs some one-off @O(n)@ time-complexity preparation, then delegates the grunt-work to a private recursive function.-- * One could waste a lot of time trying to consume 'InputData' from the head of a high-'Consumer.starHeight' 'ExtendedRegEx',- only to find that there's a low-'Consumer.starHeight' tail that can't ever match the 'InputData'; checks this first before delegating.--}-findMatch :: (Eq m, Control.DeepSeq.NFData m)- => RegExOpts.RegExOpts (ExtendedRegEx m) -- ^ The match-options parameterised by the regex against which to match the input data.- -> InputData m -- ^ The input data within which to locate a match.- -> Maybe (MatchList m)-findMatch regExOpts@RegExOpts.MkRegExOpts {- RegExOpts.executionOptions = executionOptions,- RegExOpts.regEx = extendedRegEx-} originalInputData- | let- extractLowStarHeightTail :: ExtendedRegEx m -> Concatenation m- extractLowStarHeightTail = {-#SCC "extractLowStarHeightTail" #-} fromConcatenation . reverse . concatenation where- fromConcatenation :: Concatenation m -> Concatenation m- fromConcatenation [] = []- fromConcatenation (repeatablePattern : concatenation')- | Consumer.starHeight repeatablePattern == 0 = repeatablePattern : fromConcatenation {-recurse-} concatenation'- | Repeatable.getFewest repeatablePattern > 0 = case Repeatable.base repeatablePattern of- CaptureGroup alternatives- | isSingletonAlternatives alternatives -> extractLowStarHeightTail {-recurse-} . head $ deconstructAlternatives alternatives- | otherwise -> {-non-singleton-} []- _ -> [] -- Zero 'Consumer.StarHeight' would have been detected by 'fromConcatenation'.- | otherwise = []---- lowStarHeightTail :: Concatenation a- lowStarHeightTail = extractLowStarHeightTail extendedRegEx- in and [- ExecutionOptions.checkExistenceOfInelasticTail executionOptions,- not $ null lowStarHeightTail, -- Prevent infinite recursion.- reverse originalInputData /~ RegExOpts.mkRegEx MkExtendedRegEx {- bowAnchor = Just Anchor.Bow,- concatenation = lowStarHeightTail,- sternAnchor = Nothing- } -- Check for mismatch with the corresponding tail of 'InputData'.- ] = Nothing- | otherwise = findMatchSlave originalConcatenation (- Consumer.accumulateConsumptionProfiles originalConcatenation- ) (- accumulateDistinctMetaDataFrom [] originalConcatenation- ) originalInputData originalInputDataLength (- accumulateDistinctInputData originalInputData- )- where--- originalConcatenation :: Concatenation a- originalConcatenation = concatenation extendedRegEx-- originalInputDataLength :: ConsumptionBounds.DataLength- originalInputDataLength = length originalInputData-{-- This beast takes a lot of parameters.- Whilst the only strictly necessary parameters are the lists 'Concatenation' & 'InputData', many ancillary lists derived from them, are also passed by parameter.- These are proportionally reduced on recursion, to avoid the requirement to regenerate them each time they're required.--}- findMatchSlave :: (Eq m, Control.DeepSeq.NFData m)- => Concatenation m -- The list of 'RepeatablePattern's from which the regex is constructed.- -> ConsumptionProfile.AccumulatedConsumptionProfiles -- The capacity-bounds for the consumption of 'InputData', that the 'Concatenation' extending right from any given 'RepeatablePattern', can consume.- -> [MetaDataList m] -- The set of distinct 'Meta.Meta'-data, in the 'Concatenation extending right from any given 'RepeatablePattern'.- -> InputData m -- The input data, which will be fed to the 'Concatenation'.- -> ConsumptionBounds.DataLength -- The length of the previously specified 'InputData'.- -> [InputData m] -- The set of distinct input data, extending right from any given point.- -> Maybe (MatchList m)- findMatchSlave [] _ _ [] _ _ = Just [] -- Simultaneous exhaustion of the 'Concatenation' of 'RepeatablePattern's & the 'InputData' => success.- findMatchSlave [] _ _ _ _ _ = Nothing- findMatchSlave concatenation'@(repeatablePatternHead : concatenationTail) (accumulatedConsumptionProfileHead : accumulatedConsumptionProfilesTail) (distinctMetaDataHead : distinctMetaDataTail) inputData inputDataLength distinctInputData@(distinctInputDataHead : _)- | not $ inputDataLength `ConsumptionProfile.withinConsumptionBounds` accumulatedConsumptionProfileHead = Nothing- | null inputData = Just $ mkNullMatchFromConcatenation inputDataOffset concatenation' -- Build a 'Match', of null 'InputData'.- | and [- ExecutionOptions.bypassInputDataForLiberalConsumer executionOptions, -- We may be able to establish success, without evaluating 'inputData' any further than required to determine its length.- not $ ExecutionOptions.requireMatchList executionOptions, -- Otherwise the precise mapping of the 'inputData' to 'RepeatablePattern's must be determined.- not $ ConsumptionProfile.hasSpecificRequirement accumulatedConsumptionProfileHead -- Otherwise a match for the specific 'Meta'-data must be found.- ] = Just undefined {-shouldn't be evaluated according to 'ExecutionOptions.requireMatchList'-}- | and [- ExecutionOptions.checkForUnconsumableData executionOptions,- not $ ConsumptionProfile.canConsumeAnything accumulatedConsumptionProfileHead, -- Otherwise the subsequent test will always fail.- (`isUnconsumableByAnyOf` distinctMetaDataHead) `any` distinctInputDataHead -- Occasionally failure is both inevitable & obvious.- ] = Nothing- | otherwise = {-#SCC "findMatchSlave" #-} let- tailConsumptionProfile :: ConsumptionProfile.ConsumptionProfile- tailConsumptionProfile@(- ConsumptionProfile.MkConsumptionProfile {- ConsumptionProfile.consumptionBounds = (minConsumptionConcatenationTail, maybeMaxConsumptionConcatenationTail)- }- ) = head accumulatedConsumptionProfilesTail -- Extract the aggregate consumption-profile, of the tail of the 'Concatenation'.-- maxDataAvailable :: ConsumptionBounds.DataLength- maxDataAvailable = inputDataLength - minConsumptionConcatenationTail -- The maximum data available to match 'repeatablePatternHead'.- in if maxDataAvailable < 0- then Nothing- else {-inputData is sufficient for concatenationTail-} let- Repeatable.MkRepeatable {- Repeatable.base = base,- Repeatable.repetitionBounds = (fewest, most),- Repeatable.isGreedy = isGreedy- } = repeatablePatternHead -- Completely deconstruct the 'Repeatable' at the head of the 'Concatenation'.-{-- Find the maximum sequence of inputData, preceding the first of the mandatory requirements of 'concatenationTail'.- This enables one to more tightly constrain the maximum number of repetitions of a 'RepeatablePattern',- which consequently reduces the subsequent requirement to back-track.--}--- maximumDataBeforePegs :: Maybe (InputData m)- maximumDataBeforePegs = {-#SCC "maximumDataBeforePegs" #-} reverse (extractMinimumRequirementFromConcatenation concatenationTail) `maximumDataAfterPegs` reverse inputData where-{-- Extract those 'RepeatablePattern's which must match at least once.- Singleton 'Alternatives' are flattened by replication, & non-singleton 'Alternatives' removed, allowing a reduction of the list's base-type to just 'Repeatable.Repeatable' 'Meta.Meta'.- The resulting 'BasicRegEx', with /Kleene Star/s weeded-out & 'Alternatives' pruned, can be matched in O(n)-time, to verify minimal requirements.--}- extractMinimumRequirementFromConcatenation :: Concatenation m -> BasicRegEx m- extractMinimumRequirementFromConcatenation = concatMap (- \repeatablePattern -> case Repeatable.base repeatablePattern of- Require datum -> return {-to List-monad-} repeatablePattern { Repeatable.base = datum }- CaptureGroup a- | isSingletonAlternatives a -> concat . replicate (Repeatable.getFewest repeatablePattern) . extractMinimumRequirementFromConcatenation {-recurse-} . concatenation . head $ deconstructAlternatives a- | otherwise -> []- ) . filter ((> 0) . Repeatable.getFewest)-- maximumDataAfterPegs :: Eq m => BasicRegEx m -> InputData m -> Maybe (InputData m)- maximumDataAfterPegs pegList@(- Repeatable.MkRepeatable {- Repeatable.base = metaPeg,- Repeatable.repetitionBounds = (fewestPeg, _)- } : pegListTail- ) inputData'- | null remainingInputData && length candidateMatchedInputData < fewestPeg = Nothing -- Insufficient data to ever match.- | fewestPeg == 1 || (`Meta.isMatch` metaPeg) `all` tail candidateMatchedInputData = pegListTail `maximumDataAfterPegs` remainingInputData -- Success => recurse.- | otherwise = pegList `maximumDataAfterPegs` tail candidateInputData -- Failed candidate => recurse.- where--- candidateInputData, candidateMatchedInputData, remainingInputData :: InputData a- candidateInputData = dropWhile (not . (`Meta.isMatch` metaPeg)) inputData'- (candidateMatchedInputData, remainingInputData) = fewestPeg `splitAt` candidateInputData- maximumDataAfterPegs [] inputData' = Just inputData' -- All the pegs have been matched.-{-- Each 'repeatablePatternHead' can either be a 'Require' or a 'CaptureGroup'.- The former case needs further reduction & we can proceed to construct a 'Tree.Leaf' from any matching 'inputData'.- We attempt to reduce the latter case to an instance of the former, by expanding all combinations of repetitions of various 'Alternatives', constructing a new 'ExtendedRegEx' from each, & recursing.--}- in case base of- Require meta- | null maybeMatchList -> Nothing- | otherwise -> head {-most suitable-} maybeMatchList- where-{-- Attempt to narrow the repetition-range, from the potentially unbounded specification, to that which is possible according to,- the 'inputDataLength', & the specified consumption-bounds of the remainder of the /regex/.- Once the repetition-range has been narrowed, iterate from the appropriate end of the range (depending on 'isGreedy'), extract the corresponding length from inputData, & check for a match.--}--- maybeMatchList :: [Maybe (MatchList m)]- maybeMatchList = {-#SCC "maybeMatchList" #-} dropWhile Data.Maybe.isNothing . map (- \(consumedInputData, unconsumedInputData) -> let- consumedInputDataLength :: ConsumptionBounds.DataLength- consumedInputDataLength = length consumedInputData- in (- Tree.Leaf (repeatablePatternHead, inputDataOffset, consumedInputData) :- ) `fmap` {-apply to Maybe Functor-} findMatchSlave concatenationTail accumulatedConsumptionProfilesTail distinctMetaDataTail unconsumedInputData (- inputDataLength - consumedInputDataLength- ) (- consumedInputDataLength `drop` distinctInputData- ) -- Recurse, to check whether the 'unconsumedInputData' tail also matches.- ) $ let- fewestData, mostData, mostData' :: Repeatable.Repetitions- fewestData = {-#SCC "fewestData" #-} case maybeMaxConsumptionConcatenationTail of- Just m -> (inputDataLength - m) {- :: ConsumptionBounds.DataLength-} `max` fewest {- :: Repeatable.Repetitions-} -- CAVEAT: conceptually different types.- _ -> fewest---- mostData = length . takeWhile (~= meta) $ (`take` inputData) maxData -- As slow as it is concise.- mostData = {-#SCC "mostData" #-} maxData - measureUnmatchableTail inputData maxData where- maxData :: ConsumptionBounds.DataLength- maxData = case most of- Just cap -> cap {- :: Repeatable.Repetitions-} `min` maxDataAvailable {- :: ConsumptionBounds.DataLength-} -- CAVEAT: conceptually different types.- _ -> maxDataAvailable---- measureUnmatchableTail :: InputData m -> ConsumptionBounds.DataLength -> ConsumptionBounds.DataLength- measureUnmatchableTail _ 0 = 0 -- Have matched all the data.- measureUnmatchableTail (a : as) unmatched- | a `Meta.isMatch` meta = measureUnmatchableTail as $ pred unmatched -- Recurse.- | otherwise = unmatched -- Return the unmatchable tail-length.- measureUnmatchableTail [] _ = error "RegExDot.RegEx.findMatch.findMatchSlave.maybeMatchList.mostData.measureUnmatchableTail:\tdata unexpectedly exhausted."-{-- 'most' has been extracted from 'repeatablePatternHead',- & reduced according to the length of 'inputData' which actually matches, to form 'mostData',- but if it still exceeds 'fewestData', the resulting repetition-range may trigger backtracking.- So, attempt to further reduce this ill-defined repetition-range, according to the competing requirements of 'concatenationTail'.--}- mostData'- | and [- ExecutionOptions.moderateGreed executionOptions,- isGreedy, -- Otherwise, since the search proceeds from 'fewest' to 'most', the optimal solution is located before backtracking.- mostData > fewestData -- Otherwise, there's no unbridled greed to moderate.- ] = case maximumDataBeforePegs of- Just maximumDataBeforePegs' -> mostData `min` length maximumDataBeforePegs' -- Cap upper bound.- _ -> negate 1 -- Guaranteed to be < 'fewestData'.- | otherwise {-no requirement for this optimisation-} = mostData- in (- succ {-fence-post-} mostData' - fewestData -- CAVEAT: possibly <= 0.- ) `take` (- if isGreedy- then ToolShed.Data.List.Splits.splitsLeftFrom mostData'- else ToolShed.Data.List.Splits.splitsRightFrom fewestData- ) inputData-- CaptureGroup alternatives- | and [- not isGreedy,- fewest <= 0, -- Zero repetitions permissible.- Data.Maybe.isJust tailMatch- ] -> zeroRepetitions -- Zero repetitions is the optimal solution, rendering the choice of Alternative irrelevant.- | and [- ExecutionOptions.catchIncompatibleAnchors executionOptions,- fewest > 1, -- Multiple repetitions.- minConsumptionAlternatives > 0, -- InputData required.- ($ extendedRegExAlternatives) `any` [(hasBowAnchor `all`), (hasSternAnchor `all`)]- ] -> Nothing -- 'extendedRegExFromAlternative' requires 'InputData', therefore 'Anchor.Bow' can only pass on the 1st repetition & 'Anchor.Stern' can only pass on the last.- | otherwise -> let-{-- We're about to try all repetitions of one Alternative before progressing to the next.- One could alternatively transpose this search-pattern, & investigate all 'Alternatives' permissible at n repetitions, before trying (n + 1); I've not pursued this option.- Either way, if the user requires data-capture, then for POSIX-compliance we must perform an exhaustive search of the O(Alternatives ^ Repetitions) permutations for the optimal solution; though the search may be narrowed as we proceed.--}--- matchPairList :: [(Match m, MatchList m)]- matchPairList = {-#SCC "matchPairList" #-} Data.Maybe.catMaybes . concat . Control.Parallel.Strategies.parMap Control.Parallel.Strategies.rseq {-Particularly effective when 'not ExecutionOptions.requireMatchList' -} (--- :: ExtendedRegEx m -> [Maybe (Match m, MatchList m)]- \extendedRegExFromAlternative@MkExtendedRegEx {- concatenation = concatenationFromAlternative- } -> let- components :: Int- components = length concatenationFromAlternative-- consumptionProfileConcatenationFromAlternative :: [ConsumptionProfile.ConsumptionProfile]- consumptionProfileConcatenationFromAlternative = map Consumer.consumptionProfile concatenationFromAlternative-- minConsumptionConcatenationFromAlternative :: ConsumptionBounds.DataLength- maybeMaxConsumptionConcatenationFromAlternative :: Maybe ConsumptionBounds.DataLength- (minConsumptionConcatenationFromAlternative, maybeMaxConsumptionConcatenationFromAlternative) = ConsumptionProfile.consumptionBounds $ ConsumptionProfile.aggregateFromConcatenation consumptionProfileConcatenationFromAlternative- in (- if ExecutionOptions.abortTrialRepetitionsOnZeroConsumption executionOptions && Data.Maybe.isNothing most {-constrained only by 'inputDataLength'-}- then {-#SCC "abortTrialRepetitionsOnZeroConsumption" #-} ToolShed.Data.List.takeUntil $ \maybeMatchPair -> case maybeMatchPair of- Just (match, _) -> ($ Tree.pop match) `all` [- (>= fewest) . length, -- Must achieve 'Repeatable.getFewest', regardless of data-consumption.- null . extractDataFromMatchList . last -- If the n-th repetition consumed nothing, so will the (n + 1)-th.- ]- _ -> False -- Failure with n repetitions, doesn't preclude success with n + 1.- else {-optimisation not required-} id- ) . (--- :: Maybe (Match m, MatchList m) -> Maybe (Match m, MatchList m)- \maybeMatchPairList -> if ExecutionOptions.abortTrialRepetitionsOnInherentFailure executionOptions-{-- We're iterating through the identified range of possible repetitions, of one possible 'concatenationFromAlternative'.- If failure can be proven, for an arbitrary number of repetitions, then we can bypass the futile remainder.- If the first trial succeeded, then clearly there's no such inherent problem with 'concatenationFromAlternative', & it's necessary to evaluate all subsequent trial-repetitions.- Otherwise, assuming there're subsequent trials to be bypassed, attempt to prove that failure is inevitable, by launching a pilot-study,- using a single repetition of 'concatenationFromAlternative' followed by a replacement infallible 'concatenationTail'.--}- then {-#SCC "abortTrialRepetitionsOnInherentFailure" #-} case maybeMatchPairList of- (Nothing {-failed attempt-} : _ {-subsequent attempt worth bypassing-} : _) -> if (- minConsumptionConcatenationFromAlternative == 0 -- Can delegate consumption of unconsumable data to the infallible tail => unprovable culpability.- ) || inputData =~ RegExOpts.mkRegEx MkExtendedRegEx {- bowAnchor = Just Anchor.Bow,- concatenation = concatenationFromAlternative ++ [anyDatum ^#-> minConsumptionConcatenationTail], -- Infallible tail.- sternAnchor = Just Anchor.Stern- }- then maybeMatchPairList- else [] -- Abandon further repetitions of 'Alternatives'.- _ {-Alternative is OK, or there's no subsequent attempt to bypass-} -> maybeMatchPairList- else {-optimisation not required-} maybeMatchPairList- ) . map (--- :: Repeatable.Repetitions -> Maybe (Match m, MatchList m)- \repetitions -> if hasSternAnchor extendedRegExFromAlternative- then if (repetitions == 1 || minConsumptionConcatenationFromAlternative == 0) && minConsumptionConcatenationTail == 0- then (- shiftMatch inputDataOffset . Tree.Node . (- : replicate (- pred repetitions -- Typically degenerates to zero, since anchored sub-expressions aren't normally repeatable.- ) (- mkNullMatchFromExtendedRegEx 0 extendedRegExFromAlternative -- Other Alternatives may be available & suitable.- ) -- Repeated null match may, lacking other suitable Alternatives, correspond to this stern-anchored ExtendedRegEx, so data must be consumed before them.- ) &&& const (mkNullMatchFromConcatenation inputDataOffset concatenationTail)- ) `fmap` findMatch regExOpts { RegExOpts.regEx = extendedRegExFromAlternative } inputData -- Recurse, either consuming all inputData or failing.- else Nothing- else {-no Anchor.Stern-} let-{-- Expand the 'Repeatable CaptureGroup', so that after recursion it is ultimately reduced in complexity, to just a 'Concatenation' of 'Repeatable Require', & can be handled above as a POSIX BRE.-- There're two special cases; one repetition of n 'Alternatives' & n repetitions of one Alternative.- If there's only one repetition, we can reduce the implementation-logic substantially, to the point where the implementation's independent of whether there's a choice of 'Alternatives'.- If there only one Alternative, then the entire repetition-sequence can be immediately expanded, vastly reducing the required number of recursions,- otherwise we can only expand the first repetition, leaving the option of matching a different Alternative on subsequent repetitions.-- In all cases, we must subsequently re-compose any 'MatchList' resulting of the recursive call, into one corresponding to the structure of original ExtendedRegEx.- The expansion-process & corresponding subsequent collation, are associated in a tuple, in which the consumption-profile of the expansion is also contained.--}--- collater :: MatchList m -> (Match m, MatchList m)- consumptionProfileExpandedConcatenationPrefix :: [ConsumptionProfile.ConsumptionProfile]--- expandedConcatenationPrefix :: Concatenation a- (collater, consumptionProfileExpandedConcatenationPrefix, expandedConcatenationPrefix)- | repetitions == 1 = (- Control.Arrow.first (- Tree.Node . return {-to List-monad-}- ) . splitAt components, -- Bisect the 'MatchList', into the part resulting from the Alternative, & that for 'concatenationTail'.- consumptionProfileConcatenationFromAlternative,- concatenationFromAlternative- ) -- Both tuples below, can degenerate to this simple case; it isn't a fundamentally different algorithm.- | and [- ExecutionOptions.unrollRepeatedSingletonAlternative executionOptions,- isSingletonAlternative,- not $ hasBowAnchor extendedRegExFromAlternative- ] = (- Control.Arrow.first (- Tree.Node . ToolShed.Data.List.chunk components- ) . splitAt (- components * repetitions- ), -- Bisect the 'MatchList', into the part resulting from (Alternative){n}, & that for 'concatenationTail'.- concat $ replicate repetitions consumptionProfileConcatenationFromAlternative,- concat $ replicate repetitions concatenationFromAlternative -- Expand all repetitions of the single alternative.- )- | otherwise {-choice of Alternatives or has Anchor.Bow-} = let- remainingRepetitions :: Repeatable.Repetitions- remainingRepetitions = pred repetitions -- Expand just the first repetition of the set of 'Alternatives'.---- singletonRepeatable :: RepeatablePattern a- singletonRepeatable = Repeatable.toSingleton repeatablePatternHead- in (- Control.Arrow.first (- Tree.Node . uncurry (:) . Control.Arrow.second (- concatMap Tree.pop -- Amalgamate the repeated singleton 'MatchList's.- ) . splitAt components -- Bisect first 'MatchList', into the part resulting from 'concatenationFromAlternative', & that for 'replicate remainingRepetitions'.- ) . splitAt (- components + remainingRepetitions -- Bisect the 'MatchList', into the part resulting from (Alternatives){n}, & that for 'concatenationTail'.- ),- consumptionProfileConcatenationFromAlternative ++ replicate remainingRepetitions (Consumer.consumptionProfile singletonRepeatable),- concatenationFromAlternative {-expand 1st repetition-} ++ replicate remainingRepetitions {-potentially zero-} singletonRepeatable -- Enumerate all remaining repetitions of ANY Alternative; a different Alternative can match for each repetition.- )- in {-#SCC "collater" #-} collater `fmap` {-apply to Maybe Functor-} findMatchSlave (- expandedConcatenationPrefix ++ concatenationTail- ) (- ConsumptionProfile.accumulateFrom tailConsumptionProfile {-initial value-} consumptionProfileExpandedConcatenationPrefix ++ tail accumulatedConsumptionProfilesTail- ) (- accumulateDistinctMetaDataFrom (head distinctMetaDataTail) {-initial value-} expandedConcatenationPrefix ++ tail distinctMetaDataTail- ) inputData inputDataLength distinctInputData -- Recurse to get 'Maybe (MatchList a)'.- ) $ if Repeatable.isPrecise repeatablePatternHead -- These are frequently generated by the previous recursion, as a trial expansion of an Alternative.- then if ExecutionOptions.validateMinConsumptionOfAlternatives executionOptions && maxDataAvailable < fewest * minConsumptionAlternatives- then [] -- Failure is inevitable.- else [fewest]- else {-imprecise range-} let-{-- Attempt to narrow the repetition-range, from the potentially unbounded specification, to that which is possible according to (data available / consumption-requirement),- where there're bounds for both numerator & denominator.- Additionally, one must account for the possibility of the denominator being zero, & if so, select the optimal value if the numerator is also zero.- The range can be narrowed more aggressively, for the special case of a singleton 'Alternative', as used in a capture-group.--}- fewestAlternatives, mostAlternatives, mostAlternatives' :: Repeatable.Repetitions- fewestAlternatives = {-#SCC "fewestAlternatives" #-} 1 {-zero is handled later-} `max` if any Data.Maybe.isNothing [maybeMaxConsumptionConcatenationTail, maybeMaxConsumptionAlternatives]- then fewest- else {-no unlimited capacities-} let- minDataAvailable, maxConsumptionAlternatives :: ConsumptionBounds.DataLength- minDataAvailable = inputDataLength - Data.Maybe.fromJust maybeMaxConsumptionConcatenationTail -- CAVEAT: can be negative.- maxConsumptionAlternatives = Data.Maybe.fromJust maybeMaxConsumptionAlternatives- in if isSingletonAlternative- then if maxConsumptionAlternatives == 0 -- Denominator.- then {-sole Alternative can't consume anything ?!-} if minDataAvailable <= 0 -- Numerator.- then {-tail may consume all data-} fewest -- Repeat to meet the minimum requirement.- else {-unconsumable data-} maxBound -- Failure is inevitable.- else {-non-zero => can divide-} max fewest $ minDataAvailable /+ maxConsumptionAlternatives- else {-choice of Alternatives-} let- minDataAvailable' :: ConsumptionBounds.DataLength- minDataAvailable' = minDataAvailable - Data.Maybe.fromJust maybeMaxConsumptionConcatenationFromAlternative -- CAVEAT: potentially negative.- in if maxConsumptionAlternatives == 0 -- Denominator.- then {-no Alternative can consume anything-} if minDataAvailable' <= 0 -- Numerator.- then {-zero unconsumable data-} fewest -- Repeat to meet the minimum requirement.- else {-unconsumable data-} maxBound -- Failure is inevitable.- else {-non-zero => can divide-} max fewest . succ {-account for expanded instance-} $ minDataAvailable' /+ maxConsumptionAlternatives-- mostAlternatives = {-#SCC "mostAlternatives" #-} case most of- Just cap -> cap `min` mostPermissibleRepetitions -- Create a ceiling above which the calculated number of permissible repetitions can't rise.- _ -> mostPermissibleRepetitions- where- mostPermissibleRepetitions :: Repeatable.Repetitions- mostPermissibleRepetitions- | isSingletonAlternative = if minConsumptionAlternatives == 0 -- Denominator.- then {-sole Alternative can consume zero data-} if maxDataAvailable == 0 -- Numerator.- then {-zero data available-} fewest -- Repeat to meet the minimum requirement.- else {-data available-} maxDataAvailable -- Either each repetition consumes something, or (n - 1) repetitions is a better solution.- else {-non-zero => can divide-} maxDataAvailable `div` minConsumptionAlternatives -- Divide & round down.- | otherwise {-choice of Alternatives-} = 1 {-account for instance expanded as 'concatenationFromAlternative'-} + let- maxDataAvailable' :: ConsumptionBounds.DataLength- maxDataAvailable' = maxDataAvailable - minConsumptionConcatenationFromAlternative -- CAVEAT: potentially negative.- in if minConsumptionAlternatives == 0 -- Denominator.- then {-@ least one Alternative can consume zero-} if maxDataAvailable' == 0 -- Numerator.- then {-zero data available $-} pred fewestAlternatives -- Repeat as required. CAVEAT: an annoying dependency, which prevents parallel-evaluation.- else {-data available-} if and [- False, -- The cost outweighs the small infrequent dividend.- minConsumptionConcatenationFromAlternative == 0,- maxDataAvailable > fewest- ] -- If 'fewest' can be met, without unconsuming repetitions of 'concatenationFromAlternative', then stop short of permitting it.- then pred maxDataAvailable -- Any greater, & one repetition (possibly that currently expanded) must needlessly consume nothing.- else maxDataAvailable' -- Either all n repetitions consumes something, or (n - 1) is a better solution.- else {-non-zero => can divide-} maxDataAvailable' `div` minConsumptionAlternatives -- Divide & round down.-{-- 'most' has been extracted from 'repeatablePatternHead',- & reduced according to the number of times it can fit into the maximum available data, to form 'mostAlternatives',- but if it still exceeds 'fewestAlternatives', the resulting repetition-range may trigger backtracking.- So, attempt to further reduce this ill-defined repetition-range, according to the competing requirements of 'concatenationTail'.- CAVEAT: this pays-off infrequently, but not necessarily insignificantly.--}- mostAlternatives' = {-#SCC "mostAlternatives'" #-} if and [- ExecutionOptions.moderateGreed executionOptions,- mostAlternatives > fewestAlternatives, -- Otherwise, there's no unbridled greed to moderate.- minConsumptionAlternatives > 0 -- Otherwise, any number of repetitions can occur without necessarily triggering back-tracking.- ]- then case maximumDataBeforePegs of- Just maximumDataBeforePegs' -> mostAlternatives `min` (- length maximumDataBeforePegs' `div` minConsumptionAlternatives- ) -- Cap upper bound.- _ -> negate 1 -- Guaranteed to be < 'fewestAlternatives'.- else {-no requirement for this optimisation-} mostAlternatives- in [fewestAlternatives .. mostAlternatives'] -- The permissible repetition-range of any Alternative, constrained by the amount of data available, after subtracting fixed consumption-requirements.- ) . (- if ExecutionOptions.permitReorderingOfAlternatives executionOptions-{-- The order in which 'Alternatives' are evaluated, should make no difference to the final result; unless 'ExecutionOptions.useFirstMatchAmongAlternatives'.- This order can be manipulated in an attempt to more quickly locate any 'Just MatchList', though if 'ExecutionOptions.requireMatchList', an exhaustive search for the optimal solution must be performed anyway.- Tackling lower-complexity 'Alternatives' first makes sense, in the hope of finding any easy solution before becoming entrenched fighting some hideous monster.- Preferring 'Alternatives' which require more data also makes sense, regardless of 'Repeatable.isGreedy' which just a preference for a type of solution, because fewer repetitions are then permissible;- this is rather like trying to pack a box & preferring to try the biggest items first.--}- then Data.List.sortBy $ Data.Ord.comparing (- Consumer.starHeight &&& safeReciprocal . (fromIntegral :: ConsumptionBounds.DataLength -> Rational) . Consumer.getFewest- ) -- Firstly increasing complexity, then decreasing minimum data-capacity, otherwise stable.- else id- ) $ filter (- \e -> (- not (hasBowAnchor e) || inputDataOffset == 0 -- Necessary & sufficient.- ) && (- not (hasSternAnchor e) || minConsumptionConcatenationTail == 0 -- Necessary but insufficient, since though minimum consumption is zero, maximum isn't necessarily.- )- ) extendedRegExAlternatives- in if null matchPairList- then if fewest <= 0- then zeroRepetitions -- Which might still fail, depending on 'tailMatch'.- else {-zero repetitions isn't permissible-} Nothing- else {-at least one Alternative matched-} {-#SCC "selectAlternative" #-} Just . uncurry (:) {-re-join head & tail-} $ (-{-- At least one 'Match' has been found amongst the combinations of repetitions of various 'Alternatives',- & if the caller reduces the 'Just MatchList' to 'True', that'd be sufficient, but otherwise one must identify the optimal 'MatchList'.- The primary selection-criterion used is the POSIX one, based on total maximum (or minimum for non-'Repeatable.isGreedy' capture-groups) data-consumption.- /Perl/'s "first-past-the-post" policy is available as an alternative.--}- if ExecutionOptions.useFirstMatchAmongAlternatives executionOptions || length matchPairList == 1- then head- else snd {-remove prepended selection-criterion-} . Data.List.maximumBy {-select the best match-} (- Data.Ord.comparing fst -- Compare using only the criterion, not the result from which it was derived.- ) . map (- (-{-- If the primary selection-criterion doesn't resolve the choice between candidate 'Match'es, I employ these ad-hoc criteria.-- * Fewer repetitions are preferred, which discourages the capture of null lists of 'InputData'.-- * Within the 'MatchedData', from which a candidate 'Match' is ultimately composed, consumption of 'InputData' beyond 'Repeatable.getFewest' by 'Repeatable.isGreedy' 'RepeatablePattern's, is preferred to non-greedy ones.-- * The consumption on each successive repetition is compared between candidate 'Match'es (which are now known to have used an equal number of repetitions);- this causes the data-consumption to flow towards earlier repetitions for greedy capture-groups, & towards later repetitions in non-greedy ones,- thus mimicking the behaviour of the unrolled repetition.-- * PS: more precise criteria are defined in <http://www2.research.att.com/~gsf/testregex/re-interpretation.html>.--}- \match -> let- dataLengthCriterion :: InputData m -> Rational- dataLengthCriterion = (- if isGreedy- then id- else safeReciprocal -- Prefer less data.- ) . fromIntegral . length---- matchLists :: [MatchList m]- matchLists = Tree.pop match- in (- dataLengthCriterion $ extractDataFromMatch match,- if ExecutionOptions.preferFewerRepeatedAlternatives executionOptions- then safeReciprocal . toRational $ length matchLists- else 0,- if ExecutionOptions.preferAlternativesWhichFeedTheGreedy executionOptions- then let- excessConsumption :: MatchedData m -> ConsumptionBounds.DataLength- excessConsumption (- Repeatable.MkRepeatable {- Repeatable.repetitionBounds = (f, _),- Repeatable.isGreedy = g- },- _,- consumedInputData- )- | g = l - f- | otherwise = f - l- where- l :: ConsumptionBounds.DataLength- l = length consumedInputData- in Data.Foldable.foldl' (\l -> (l +) . excessConsumption) 0 match- else 0,- if ExecutionOptions.preferAlternativesWhichMimickUnrolling executionOptions- then map (dataLengthCriterion . extractDataFromMatchList) matchLists- else []- ) -- Create a tuple of selection-criteria, for simultaneous assessment.- ) . fst {-focus the choice on the Alternatives-} &&& id -- Prepend a selection-criterion to each result.- )- ) matchPairList- where--- tailMatch, zeroRepetitions :: Maybe (MatchList m)- tailMatch = findMatchSlave concatenationTail accumulatedConsumptionProfilesTail distinctMetaDataTail inputData inputDataLength distinctInputData -- Recurse.- zeroRepetitions = (Tree.Node [] :) `fmap` {-apply to Maybe Functor-} tailMatch -- Prepend a null 'MatchList'.---- extendedRegExAlternatives :: [ExtendedRegEx m]- extendedRegExAlternatives = Data.List.nub $ deconstructAlternatives alternatives-- isSingletonAlternative :: Bool- isSingletonAlternative = length extendedRegExAlternatives == 1-- minConsumptionAlternatives :: ConsumptionBounds.DataLength- maybeMaxConsumptionAlternatives :: Maybe ConsumptionBounds.DataLength- (minConsumptionAlternatives, maybeMaxConsumptionAlternatives) = Consumer.getConsumptionBounds alternatives -- Independent of the choice of Alternative.- where- inputDataOffset :: ConsumptionBounds.DataLength- inputDataOffset = originalInputDataLength - inputDataLength- findMatchSlave _ _ _ _ _ _ = error "RegExDot.RegEx.findMatch.findMatchSlave:\tunexpected parameter-pattern."--{- |- * Operator's name was chosen to suggest something more than '=~'.-- * CAVEAT: much more expensive then '=~': in /ghci/, 'Just' can be observed to be printed /long/ before the 'MatchList' from which 'Result' is constructed,- as the lazy algorithm finds the first solution, but not yet necessarily the optimal solution, amongst 'Alternatives'.--}-(+~) :: (Eq m, Control.DeepSeq.NFData m)- => InputData m -- ^ The input data within which to locate a match.- -> RegExOpts.RegExOpts (ExtendedRegEx m) -- ^ The match-options parameterised by the regex against which to match the input data.- -> Result m-inputData +~ regExOpts = (- if hasBowAnchor' then Nothing else fmap head maybeMatchList, -- Record the first 'Match', consumed in the absence of a top-level 'Anchor.Bow'.- fmap (- (- if hasBowAnchor' then id else tail -- Remove the first 'Match', since this wasn't consumed by the 'Concatenation'.- ) . (- if hasSternAnchor' then id else init -- Remove the last 'Match', since this wasn't consumed by the 'Concatenation'.- )- ) maybeMatchList,- if hasSternAnchor' then Nothing else fmap last maybeMatchList -- Record the last 'Match', consumed in the absence of a top-level 'Anchor.Stern'.- ) where--- extendedRegEx :: ExtendedRegEx a- extendedRegEx = RegExOpts.regEx regExOpts-- hasBowAnchor', hasSternAnchor' :: Bool- hasBowAnchor' = hasBowAnchor extendedRegEx- hasSternAnchor' = hasSternAnchor extendedRegEx---- maybeMatchList :: Maybe (MatchList m)- maybeMatchList = fmap drift (RegExOpts.setVerbose True regExOpts) `findMatch` inputData--{- |- * Pattern-match operator.-- * Identifier & parameter-order follow the lead of /Perl/'s pattern-match operator.-- * Considerably more efficient than '+~', since even though they are both implemented via 'findMatch',- the discovery of /any/ solution is sufficient to generate the return-value;- lazy-evaluation avoids the requirement to identify the irrelevant optimal solution.--}-(=~) :: (Eq m, Control.DeepSeq.NFData m)- => InputData m -- ^ The input data within which to locate a match.- -> RegExOpts.RegExOpts (ExtendedRegEx m) -- ^ The match-options parameterised by the regex against which to match the input data.- -> Bool-inputData =~ regExOpts = Data.Maybe.isJust $ fmap drift (RegExOpts.setVerbose False regExOpts) `findMatch` inputData---- | Pattern-mismatch operator.-(/~) :: (Eq m, Control.DeepSeq.NFData m)- => InputData m -- ^ The input data within which to locate a match.- -> RegExOpts.RegExOpts (ExtendedRegEx m) -- ^ The match-options parameterised by the regex against which to match the input data.- -> Bool-(/~) inputData = not . (inputData =~)---- | The delimiters of 'Alternatives', when in the 'String'-form.-captureGroupDelimiters :: (Char, Char)-captureGroupDelimiters = ('(', ')')---- | The token used to separate alternative 'ExtendedRegEx's, when in the 'String'-form.-alternativeExtendedRegExSeparatorToken :: Char-alternativeExtendedRegExSeparatorToken = '|'---- | The set of 'Char' to which a specific meaning is attributed, when reading from 'String'.-tokens :: String-tokens = [fst captureGroupDelimiters, snd captureGroupDelimiters, alternativeExtendedRegExSeparatorToken]--{- |- * Invert the specified fraction, but return 'maxBound' if the result would otherwise be infinite.-- * CAVEAT: this is an awful concept, and therefore intended for internal use only.--}-safeReciprocal :: (Eq f, Fractional f) => f -> f-safeReciprocal 0 = fromIntegral (maxBound :: Int) -- Handle divide-by-zero error.-safeReciprocal f = recip f--infixl 7 /+ -- Same as (/).--{- |- * Integral division, with any fractional remainder rounded-up.-- * A rather dubious requirement, so internal use only.--}--- (/+) :: Integral i => i -> i -> i-(/+)- :: Int -- ^ Numerator.- -> Int -- ^ Denominator.- -> Int -- 10% faster in unoptimised code, & more in optimised.-_ /+ 0 = error "RegExDot.RegEx.(/+):\tzero denominator => infinity"--- numerator /+ denominator = ceiling ((fromIntegral numerator / fromIntegral denominator) :: Double)-numerator /+ denominator = uncurry (+) . Control.Arrow.second signum $ quotRem numerator denominator -- Slightly faster.
− src/RegExDot/RegExOpts.hs
@@ -1,65 +0,0 @@-{-# LANGUAGE MultiParamTypeClasses, TypeSynonymInstances, FlexibleInstances #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--{-- 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@]-- * Provides a complete job-specification.-- * 'regEx' is polymorphic, allowing use of either /RegExDot.RegEx.ExtendedRegEx/ (which is also polymorphic) or /RegExChar.ExtendedRegExChar.ExtendedRegExChar/.--}--module RegExDot.RegExOpts(--- * Types--- ** Data-types- RegExOpts(..),--- * Functions- setVerbose,--- ** Constructors- mkRegEx-) where--import qualified RegExDot.CompilationOptions as CompilationOptions-import qualified RegExDot.ExecutionOptions as ExecutionOptions-import qualified ToolShed.Defaultable---- | Aggregates both 'ExecutionOptions.ExecutionOptions' & 'CompilationOptions.CompilationOptions' with a polymorphic /regex/, to form a complete job-description.-data RegExOpts a = MkRegExOpts {- compilationOptions :: CompilationOptions.CompilationOptions, -- ^ Parameters governing the result, rather than the implementation.- executionOptions :: ExecutionOptions.ExecutionOptions, -- ^ Parameters governing the implementation, rather than the result.- regEx :: a -- ^ Polymorphic, to permit specialisation either of the type of /regex/ or the type of input-data.-} deriving Show--instance Functor RegExOpts where- fmap f regExOpts = regExOpts { regEx = f $ regEx regExOpts }---- | Smart constructor using 'ToolShed.Defaultable.defaultValue'.-mkRegEx :: a -> RegExOpts a-mkRegEx r = MkRegExOpts {- compilationOptions = ToolShed.Defaultable.defaultValue,- executionOptions = ToolShed.Defaultable.defaultValue,- regEx = r-}---- | Mutator, which sets an appropriate 'ExecutionOptions.ExecutionOptions', for the depth to which the caller wants to probe the resulting match.-setVerbose :: Bool -> RegExOpts a -> RegExOpts a-setVerbose verbose regExOpts = regExOpts { executionOptions = ExecutionOptions.setVerbose verbose $ executionOptions regExOpts }-
− src/RegExDot/Repeatable.hs
@@ -1,397 +0,0 @@-{-- 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@]-- * 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 Prelude hiding ((<$>), (<*>)) -- The "Prelude" from 'base-4.8' exports this symbol.-import Control.Applicative((<$>), (<*>))-import Control.Arrow((***))-import qualified Control.DeepSeq-import qualified Data.List-import qualified RegExDot.Consumer as Consumer-import qualified RegExDot.ConsumptionProfile as ConsumptionProfile-import qualified Text.ParserCombinators.Parsec as Parsec-import Text.ParserCombinators.Parsec((<?>))-import qualified ToolShed.Data.Pair-import qualified ToolShed.SelfValidate--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 ParsecT-monad-} (oneOrMore b),- (Parsec.char zeroOrOneToken <?> "Repeatable.zeroOrOneToken " ++ show zeroOrOneToken) >> return {-to ParsecT-monad-} (zeroOrOne b),- (Parsec.char zeroOrMoreToken <?> "Repeatable.zeroOrMoreToken " ++ show zeroOrMoreToken) >> return {-to ParsecT-monad-} (zeroOrMore b),- (b ^#->#) {-arbitrarily greedy for now-} <$> uncurry Parsec.between (ToolShed.Data.Pair.mirror Parsec.char rangeDelimiters) (- do- fewest <- Parsec.spaces >> (read <$> Parsec.many1 Parsec.digit <?> "Repetition-range minimum")- most <- Parsec.spaces >> Parsec.option (- Just fewest -- The default.- ) (- do- i <- (- Parsec.char rangeSeparatorToken <?> "Repeatable.rangeSeparatorToken " ++ show rangeSeparatorToken- ) >> Parsec.spaces >> Parsec.option Nothing {-default to open-ended range-} (- Just . read <$> Parsec.many1 Parsec.digit <?> "Repetition-range maximum"- )-- Parsec.spaces >> return {-to ParsecT-monad-} i- )-- return {-to ParsecT-monad-} (fewest, most)- ) <?> "Repeatable.rangeDelimiters " ++ show rangeDelimiters- ]-- g <- Parsec.option True {-the default-} $ (Parsec.char nonGreedyToken <?> "Repeatable.nonGreedyToken " ++ show nonGreedyToken) >> return {-to ParsecT-monad-} False-- return {-to ParsecT-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 ((,) <$> 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) $ ToolShed.Data.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 ToolShed.SelfValidate.SelfValidator a => ToolShed.SelfValidate.SelfValidator (Repeatable a) where- getErrors MkRepeatable {- base = b,- repetitionBounds = (fewest, most),- isGreedy = g- }- | not $ ToolShed.SelfValidate.isValid b = ToolShed.SelfValidate.getErrors b -- Delegate.- | otherwise = ToolShed.SelfValidate.extractErrors [- (fewest < 0, "Negative fewest=" ++ show fewest ++ "."),- (- case most of- Just m -> m < fewest- _ -> False,- "Invalid repetition-range; '" ++ show (fewest, most) ++ "'."- ), (- not 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.- _ -> False,- "Invalid non-greedy repetition-range; '" ++ show (fewest, most) ++ "'."- )- ]--instance Control.DeepSeq.NFData a => Control.DeepSeq.NFData (Repeatable a) where- rnf MkRepeatable {- base = b,- repetitionBounds = r,- isGreedy = g- } = Control.DeepSeq.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]-
− src/RegExDot/Result.hs
@@ -1,60 +0,0 @@-{-- 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@] Tools to manipulate a 'RegEx.Result'.-- [@CAVEAT@] The data-definition remains in "RegEx", since it references 'RegEx.Match'.--}--module RegExDot.Result(--- * Functions--- ** Accessors (Deconstructors)- getMatchList,- getPreMatch,- getPostMatch,--- ** Predicates- isMatch,--- ** Query- countMatches-) where--import qualified Data.Maybe-import qualified RegExDot.RegEx as RegEx---- | Accessor.-getPreMatch :: RegEx.Result a -> RegEx.ExternalMatch a-getPreMatch (externalMatch, _, _) = externalMatch---- | Accessor.-getPostMatch :: RegEx.Result a -> RegEx.ExternalMatch a-getPostMatch (_, _, externalMatch) = externalMatch---- | Accessor.-getMatchList :: RegEx.Result a -> Maybe (RegEx.MatchList a)-getMatchList (_, maybeMatchList, _) = maybeMatchList---- | True if the 'RegEx.InputData' matched the 'RegEx.ExtendedRegEx'.-isMatch :: RegEx.Result a -> Bool-isMatch = Data.Maybe.isJust . getMatchList---- | Counts the number of top-level 'RegEx.Match'es.-countMatches :: RegEx.Result a -> Int-countMatches (_, Just matchList, _) = length matchList-countMatches _ = 0-
− src/RegExDot/ShowablePredicate.hs
@@ -1,53 +0,0 @@-{-- 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@] Used to implement /Perl-shortcut/s, /BracketExpression/-ranges & /POSIX Character-classes/.--}--module RegExDot.ShowablePredicate(--- * Types- Predicate,--- ** Data-types- ShowablePredicate(- MkShowablePredicate,- name,- predicate- )-) where--import qualified Control.DeepSeq---- | An arbitrary polymorphic predicate function.-type Predicate a = a -> Bool---- | Container for both the /predicate/, & the name used in the implementation of 'Show'.-data ShowablePredicate a = MkShowablePredicate {- name :: String, -- ^ The identifier of this predicate when in string-form.- predicate :: Predicate a -- ^ The function used to determine whether an input datum matches.-}--instance Show (ShowablePredicate a) where- showsPrec _ = showString . name--instance Eq (ShowablePredicate a) where- l == r = name l == name r -- Ignore 'predicate'.--instance Control.DeepSeq.NFData (ShowablePredicate a) where- rnf = Control.DeepSeq.rnf . name -- Ignore 'predicate'.-
− src/RegExDot/Span.hs
@@ -1,64 +0,0 @@-{-- 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@] Delimits a data-sequence, via its offset & length, within a wider list.--}--module RegExDot.Span(--- * Types--- ** Type-synonyms- Span,--- * Functions- after,- empty,- join-) where--import Control.Arrow((***))-import qualified RegExDot.ConsumptionBounds as ConsumptionBounds--{- |- * The offset & length of a specific data-sequence, within a wider list.-- * Similar to <http://hackage.haskell.org/packages/archive/regex-base/latest/doc/html/Text-Regex-Base-RegexLike.html#t%3AMatchArray>.--}-type Span = (ConsumptionBounds.DataLength {-offset-}, ConsumptionBounds.DataLength)---- | An empty instance, located at the specified offset.-empty :: ConsumptionBounds.DataLength -> Span-empty offset = (offset, 0)---- | Returns the furthest extent, i.e. the offset just after the end.-after :: Span -> ConsumptionBounds.DataLength-after = uncurry (+)--{- |- * Condenses a list into a single value.-- * Expects a gap-free, sorted list.--}-join- :: ConsumptionBounds.DataLength -- ^ The offset into the list of input-data to use when a null list of spans is received.- -> [Span]- -> Span-join offset [] = empty offset -- The offset can't be deduced from a null list, so use the value provided.-join _ [singleton] = singleton -- Merely for efficiency.-join _ spanList = head *** sum $ unzip spanList--
− src/RegExDot/Tree.hs
@@ -1,80 +0,0 @@-{-- 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@]-- * Defines a specific type of tree-structure, which is composed from either a datum, or a list of Tree-lists; <http://en.wikipedia.org/wiki/Rose_Tree>.-- * This more general tree-structure has the shape of a regular-expression match.-- * This contrasts with the typical binary-tree, which is either empty, or contains a (left Tree, datum, right Tree).--}--module RegExDot.Tree(--- * Types--- ** Type-synonyms--- TreeList,--- ** Data-types- Tree(..),--- * Functions- pop-) where--import qualified Control.Arrow-import qualified Control.DeepSeq-import qualified Data.Foldable-import qualified Data.List-import qualified Data.Monoid---- | A general purpose tree-type structure.-data Tree a =- Leaf a -- ^ The payload.- | Node [TreeList a] -- ^ Recurse. NB: a list of lists is required to contain the /MatchLists/ resulting from repeated /Alternatives/.- deriving Eq---- | A list of 'Tree's; significant only because it is the essence of the recursive nature of 'Tree'.-type TreeList a = [Tree a]--instance Show a => Show (Tree a) where- showsPrec _ (Leaf a) = shows a- showsPrec _ (Node treeLists) = showList treeLists--instance Read a => Read (Tree a) where- readsPrec _ node@('[' : _) = Control.Arrow.first Node `map` readList node {-singleton-}- readsPrec _ leaf = Control.Arrow.first Leaf `map` reads leaf {-singleton-}--instance Control.DeepSeq.NFData a => Control.DeepSeq.NFData (Tree a) where- rnf (Leaf a) = Control.DeepSeq.rnf a- rnf (Node l) = Control.DeepSeq.rnf l--instance Functor Tree where- fmap f (Leaf a) = Leaf $ f a- fmap f (Node treeLists) = Node $ map (fmap {-recurse-} f `map`) treeLists--instance Data.Foldable.Foldable Tree where- foldMap f (Leaf a) = f a -- CAVEAT: 'f' should be Associative, as required by a Monoid.- foldMap f (Node treeLists) = Data.List.foldl' (Data.List.foldl' (\monoid -> (monoid `Data.Monoid.mappend`) . Data.Foldable.foldMap f)) Data.Monoid.mempty treeLists---- | Deconstruct the specified 'Node'; i.e. lop the apex from the 'Tree', leaving a flat top.-pop- :: Tree a -- ^ The tree from which to extract the list of trees hanging immediately beneath its apex.- -> [TreeList a]-pop (Node treeLists) = treeLists-pop _ = error "RegExDot.Tree.pop:\tunexpected Leaf"-