loc 0.1.4.0 → 0.1.4.1
raw patch · 8 files changed
+542/−518 lines, 8 filesdep ~basedep ~containersdep ~hedgehogPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependency ranges changed: base, containers, hedgehog, hspec, hspec-hedgehog
API changes (from Hackage documentation)
- Data.Loc.Internal.Prelude: (<<<) :: forall k cat (b :: k) (c :: k) (a :: k). Category cat => cat b c -> cat a b -> cat a c
+ Data.Loc.Internal.Prelude: (<<<) :: forall {k} cat (b :: k) (c :: k) (a :: k). Category cat => cat b c -> cat a b -> cat a c
- Data.Loc.Internal.Prelude: (>>>) :: forall k cat (a :: k) (b :: k) (c :: k). Category cat => cat a b -> cat b c -> cat a c
+ Data.Loc.Internal.Prelude: (>>>) :: forall {k} cat (a :: k) (b :: k) (c :: k). Category cat => cat a b -> cat b c -> cat a c
- Data.Loc.Internal.Prelude: class Foldable (t :: Type -> Type)
+ Data.Loc.Internal.Prelude: class Foldable (t :: TYPE LiftedRep -> Type)
- Data.Loc.Internal.Prelude: infixl 4 <$>
+ Data.Loc.Internal.Prelude: infixl 4 $>
- Data.Loc.Internal.Prelude: mapAccumL :: Traversable t => (a -> b -> (a, c)) -> a -> t b -> (a, t c)
+ Data.Loc.Internal.Prelude: mapAccumL :: Traversable t => (s -> a -> (s, b)) -> s -> t a -> (s, t b)
Files
- README.md +0/−229
- changelog.md +58/−0
- license.txt +1/−1
- loc-test-src/Test/Loc/Hedgehog/Gen.hs +0/−265
- loc.cabal +21/−22
- readme.md +230/−0
- test/Gen.hs +231/−0
- test/Main.hs +1/−1
− README.md
@@ -1,229 +0,0 @@-# loc--Overview of the concepts:----* `Loc` - a cursor position, starting at the origin `1:1`-* `Span` - a nonempty contiguous region between two locs-* `Area` - a set of zero or more spans with gaps between them--See also:--* [loc-test](https://hackage.haskell.org/package/loc-test) -- Test-related utilities for this package.--## `Pos`--Since all of the numbers we're dealing with in this domain are positive, we-define a "positive integer" type. This is a newtype for `Natural` that doesn't-allow zero.--```haskell-newtype Pos = Pos Natural- deriving (Eq, Ord)--instance Num Pos where- fromInteger = Pos . checkForUnderflow . fromInteger- Pos x + Pos y = Pos (x + y)- Pos x - Pos y = Pos (checkForUnderflow (x - y))- Pos x * Pos y = Pos (x * y)- abs = id- signum _ = Pos 1- negate _ = throw Underflow--checkForUnderflow :: Natural -> Natural-checkForUnderflow n =- if n == 0 then throw Underflow else n-```--`Pos` does not have an `Integral` instance, because that would require-implementing `quotRem :: Pos -> Pos -> (Pos, Pos)`, which doesn't make much-sense. Therefore we can't use `toInteger` on `Pos`. Instead we use our own-`ToNat` class to convert positive numbers to natural numbers.--```haskell-class ToNat a where- toNat :: a -> Natural--instance ToNat Pos where- toNat (Pos n) = n-```--## `Line`, `Column`--We then add some newtypes to be more specific about whether we're talking about-line or column numbers.--```haskell-newtype Line = Line Pos- deriving (Eq, Ord, Num, Real, Enum, ToNat)--newtype Column = Column Pos- deriving (Eq, Ord, Num, Real, Enum, ToNat)-```--## `Loc`--A `Loc` is a `Line` and a `Column`.--```haskell-data Loc = Loc- { line :: Line- , column :: Column- }- deriving (Eq, Ord)-```--Note that this library has chosen to be remain entirely agnostic of the text-that the positions are referring to. Therefore there is no "plus one" operation-on `Loc`, because the next `Loc` after *4:17* could be either *4:18* or *5:1* --we can't tell without knowing the line lengths.--## `Span`--A `Span` is a start `Loc` and an end `Loc`.--```haskell-data Span = Span- { start :: Loc- , end :: Loc- } deriving (Eq, Ord)-```--A `Span` is not allowed to be empty; in other words, `start` and `end` must be-different.--There are two functions for constructing a `Span`. They both reorder their-arguments as appropriate to make sure the start comes before the end (so that-spans are never backwards). They take different approaches to ensuring that-spans are never empty: the first can throw an exception, whereas the second is-typed as `Maybe`.--```haskell-fromTo :: Loc -> Loc -> Span-fromTo a b =- maybe (throw EmptySpan) id (fromToMay a b)--fromToMay :: Loc -> Loc -> Maybe Span-fromToMay a b =- case compare a b of- LT -> Just (Span a b)- GT -> Just (Span b a)- EQ -> Nothing-```--The choice to use an exclusive upper bound *\[start, end)* rather than two-inclusive bounds *\[start, end\]* is forced by the decision to be text-agnostic.-With inclusive ranges, you couldn't tell whether span *4:16-4:17* abuts span-*5:1-5:2* without knowing whether the character at position *4:17* is a newline.--## `Area`--Conceptually, an area is a set of spans. To support efficient union and-difference operations, `Area` is defined like this:--```haskell-data Terminus = Start | End- deriving (Eq, Ord)--newtype Area = Area (Map Loc Terminus)- deriving (Eq, Ord)-```--You can think of this as a sorted list of the spans' start and end positions,-along with a tag indicating whether each is a start or an end.--## `Show`--We define custom `Show` and `Read` instances to be able to write terse tests like:--```haskell->>> addSpan (read "1:1-6:1") (read "[1:1-3:1,6:1-6:2,7:4-7:5]")-[1:1-6:2,7:4-7:5]-```--These are the `showsPrec` implementations for `Loc` and `Span`:--```haskell-locShowsPrec :: Int -> Loc -> ShowS-locShowsPrec _ (Loc l c) =- shows l .- showString ":" .- shows c--spanShowsPrec :: Int -> Span -> ShowS-spanShowsPrec _ (Span a b) =- locShowsPrec 10 a .- showString "-" .- locShowsPrec 10 b-```--## `Read`--The parser for `Pos` is based on the parser for `Natural`, applying `mfilter (/=-0)` to make the parser fail if the input represents a zero.--```haskell-posReadPrec :: ReadPrec Pos-posReadPrec =- Pos <$> mfilter (/= 0) readPrec-```--As a reminder, the type of `mfilter` is:--```haskell-mfilter :: MonadPlus m => (a -> Bool) -> m a -> m a-```--The `Loc` parser uses a very typical `Applicative` pattern:--```haskell--- | Parses a single specific character.-readPrecChar :: Char -> ReadPrec ()-readPrecChar = void . readP_to_Prec . const . ReadP.char--locReadPrec :: ReadPrec Loc-locReadPrec =- Loc <$>- readPrec <*- readPrecChar ':' <*>- readPrec-```--We used `mfilter` above to introduce failure into the `Pos` parser; for `Span`-we use `empty`.--```haskell-empty :: Alternative f => f a-```--First we use `fromToMay` to produce a `Maybe Span`, and then in the case where-the result is `Nothing` we use `empty` to make the parser fail.--```haskell-spanReadPrec :: ReadPrec Span-spanReadPrec =- locReadPrec >>= \a ->- readPrecChar '-' *>- locReadPrec >>= \b ->- maybe empty pure (fromToMay a b)-```--## Comparison to similar packages--### `srcloc`--[srcloc](https://hackage.haskell.org/package/srcloc) has a similar general-purpose: defining types related to positions in text files.--Some differences:--* `srcloc`'s `Pos` type (comparable to our `Loc` type) has a `FilePath`- parameter, whereas this library doesn't consider file paths at all.-* `srcloc` has nothing comparable to the `Area` type.--There are some undocumented aspects of `srcloc` we find confusing:--* What does "character offset" mean?-* Does `srcloc`'s `Loc` type use inclusive or exclusive bounds?
+ changelog.md view
@@ -0,0 +1,58 @@+### 0.1.4.1 (2023-01-10)++Support GHC 9.4++### 0.1.4.0 (2022-06-27)++Drop support for GHC 8.4, 8.6, and 8.8++Renamed test suite to `test-loc-properties`++Added module `Data.Loc.SpanOrLoc`++Added to module `Data.Loc` the type `SpanOrLoc` and the functions+`spanOrLocFromTo`, `spanOrLocStart`, and `spanOrLocEnd`++### 0.1.3.16 (2022-01-24)++Fix test suite failure on case-insensitive file systems++### 0.1.3.14 (2022-01-13)++Drop support for GHC 8.0 and 8.2++### 0.1.3.12 (2022-01-13)++Support GHC 9.0 and 9.2++Tighten dependency version bounds++### 0.1.3.10 (2020-11-04)++Added `Data` instances for `Area`, `Loc`, `Pos`, `Line`, `Column`, and `Span`++### 0.1.3.8 - 2020 May 20++Support GHC 8.10++### 0.1.3.6 - 2020 Mar 15++Support GHC 8.8++### Older++The change log was not maintained before this point.++- 0.1.3.4 (2018-11-22)+- 0.1.3.3 (2018-08-23)+- 0.1.3.2 (2017-12-03)+- 0.1.3.1 (2017-08-20)+- 0.1.3.0 (2017-07-22)+- 0.1.2.3 (2017-05-28)+- 0.1.2.2 (2017-05-28)+- 0.1.2.1 (2017-05-16)+- 0.1.2.0 (2017-05-07)+- 0.1.1.0 (2017-05-07)+- 0.1.0.2 (2017-05-07)+- 0.1.0.1 (2017-05-07)+- 0.1.0.0 (2017-05-07)
license.txt view
@@ -1,4 +1,4 @@-Copyright 2017 Chris Martin+Copyright 2017 Mission Valley Software LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
− loc-test-src/Test/Loc/Hedgehog/Gen.hs
@@ -1,265 +0,0 @@-{- |--Hedgehog generators for types defined in the /loc/ package.---}-module Test.Loc.Hedgehog.Gen- (- -- * Line- line, line', defMaxLine,-- -- * Column- column, column', defMaxColumn,-- -- * Loc- loc, loc',-- -- * Span- span, span',-- -- * Area- area, area',-- -- * Generator bounds- Bounds, boundsSize,- )- where--import Data.Loc (ToNat (..))-import Data.Loc.Internal.Prelude-import Data.Loc.Types--import qualified Data.Loc as Loc--import Hedgehog (Gen)-import Prelude (Num (..))--import qualified Data.List as List-import qualified Data.Set as Set-import qualified Hedgehog.Gen as Gen-import qualified Hedgehog.Range as Range-------------------------------------------------------------------------------------- Parameter defaults------------------------------------------------------------------------------------- | The default maximum line: 99.-defMaxLine :: Line-defMaxLine = 99---- | The default maximum column number: 99.-defMaxColumn :: Column-defMaxColumn = 99-------------------------------------------------------------------------------------- Bounds------------------------------------------------------------------------------------- | Inclusive lower and upper bounds on a range.-type Bounds a = (a, a)--{- |--The size of a range specified by 'Bounds'.--Assumes the upper bound is at least the lower bound.---}-boundsSize :: Num n => (n, n) -> n-boundsSize (a, b) =- 1 + b - a-------------------------------------------------------------------------------------- Pos-----------------------------------------------------------------------------------{- |--@'pos' a b@ generates a number on the linear range /a/ to /b/.---}-pos :: (ToNat n, Num n)- => Bounds n -- ^ Minimum and maximum value to generate- -> Gen n-pos (a, b) =- let- range = Range.linear (toNat a) (toNat b)- in- fromInteger . toInteger <$> Gen.integral range--{- |--@'line' a b@ generates a line number on the linear range /a/ to /b/.---}-line- :: Bounds Line -- ^ Minimum and maximum line number- -> Gen Line-line = pos--{- |--Generates a line number within the default bounds @(1, 'defMaxLine')@.---}-line' :: Gen Line-line' =- line (1, defMaxLine)--{- |--@'column' a b@ generates a column number on the linear range /a/ to /b/.---}-column- :: Bounds Column -- ^ Minimum and maximum column number- -> Gen Column-column = pos--{- |--Generates a column number within the default bounds @(1, 'defMaxColumn')@.---}-column' :: Gen Column-column' =- column (1, defMaxColumn)-------------------------------------------------------------------------------------- Loc-----------------------------------------------------------------------------------{- |--@'loc' lineBounds columnBounds@ generates a 'Loc' with the line number-bounded by @lineBounds@ and column number bounded by @columnBounds@.---}-loc- :: Bounds Line -- ^ Minimum and maximum line number- -> Bounds Column -- ^ Minimum and maximum column number- -> Gen Loc-loc lineBounds columnBounds =- Loc.loc <$> line lineBounds- <*> column columnBounds--{- |--Generates a 'Loc' within the default line and column bounds.---}-loc' :: Gen Loc-loc' =- loc (1, defMaxLine) (1, defMaxColumn)-------------------------------------------------------------------------------------- Span-----------------------------------------------------------------------------------{- |--@'span' lineBounds columnBounds@ generates a 'Span' with start and end-positions whose line numbers are bounded by @lineBounds@ and whose column-numbers are bounded by @columnBounds@.---}-span- :: Bounds Line -- ^ Minimum and maximum line number- -> Bounds Column -- ^ Minimum and maximum column number- -> Gen Span-span lineBounds columnBounds@(minColumn, maxColumn) =- let- lines :: Gen (Line, Line)- lines =- line lineBounds >>= \a ->- line lineBounds <&> \b ->- (min a b, max a b)-- columnsDifferentLine :: Gen (Column, Column)- columnsDifferentLine =- column columnBounds >>= \a ->- column columnBounds <&> \b ->- (a, b)-- columnsSameLine :: Gen (Column, Column)- columnsSameLine =- column (minColumn + 1, maxColumn) >>= \a ->- column columnBounds <&> \b ->- case compare a b of- EQ -> (a - 1, b)- LT -> (a, b)- GT -> (b, a)-- in- lines >>= \(startLine, endLine) ->- (if startLine /= endLine- then columnsDifferentLine- else columnsSameLine- ) <&> \(startColumn, endColumn) ->-- let- start = Loc.loc startLine startColumn- end = Loc.loc endLine endColumn-- in- Loc.spanFromTo start end--{- |--Generates a 'Span' with start and end positions within the default line and-column bounds.---}-span' :: Gen Span-span' =- span (1, defMaxLine) (1, defMaxColumn)-------------------------------------------------------------------------------------- Area-----------------------------------------------------------------------------------{- |--@'area' lineBounds columnBounds@ generates an 'Area' consisting of 'Span's-with start and end positions whose line numbers are bounded by @lineBounds@-and whose column numbers are bounded by @columnBounds@.---}-area- :: Bounds Line -- ^ Minimum and maximum line number- -> Bounds Column -- ^ Minimum and maximum column number- -> Gen Area-area lineBounds columnBounds =- fold . snd . mapAccumL f Nothing . Set.toAscList . Set.fromList <$> locs-- where- gridSize :: Int = fromIntegral $ toNat (boundsSize lineBounds)- `max` toNat (boundsSize columnBounds)-- locs :: Gen [Loc] =- loc lineBounds columnBounds- & List.repeat- & List.take (gridSize `div` 5)- & sequenceA-- f :: Maybe Loc -> Loc -> (Maybe Loc, Area)- f prevLocMay newLoc =- case prevLocMay of- Just prevLoc -> (Nothing, Loc.areaFromTo prevLoc newLoc)- Nothing -> (Just newLoc, mempty)--{- |--Generates an 'Area' consisting of 'Span's with start and end positions within-the default line and column bounds.---}-area' :: Gen Area-area' =- area (1, defMaxLine) (1, defMaxColumn)
loc.cabal view
@@ -1,9 +1,9 @@ cabal-version: 3.0 name: loc-version: 0.1.4.0--synopsis: Types representing line and column positions and ranges in text files.+version: 0.1.4.1+synopsis: Line and column positions and ranges in text files+category: Data Structures, Text description: The package name /loc/ stands for “location” and is@@ -13,9 +13,8 @@ the @Span@ type is a nonempty range between two @Loc@s, and the @Area@ type is a set of non-touching @Span@s. -category: Data Structures, Text--homepage: https://github.com/typeclasses/loc+homepage: https://github.com/typeclasses/loc+bug-reports: https://github.com/typeclasses/loc/issues author: Chris Martin maintainer: Chris Martin, Julie Moronuki@@ -24,9 +23,12 @@ license: Apache-2.0 license-file: license.txt -build-type: Simple+extra-source-files: *.md+extra-doc-files: *.png, *.svg -extra-source-files: example.png example.svg README.md+source-repository head+ type: git+ location: git://github.com/typeclasses/loc.git common base default-language: Haskell2010@@ -40,14 +42,9 @@ NoImplicitPrelude ScopedTypeVariables ghc-options: -Wall- build-depends: base >= 4.14 && < 4.17- build-depends: containers >= 0.6.5.1 && < 0.7--common test- import: base- hs-source-dirs: test- ghc-options: -threaded- build-depends: loc+ build-depends:+ , base ^>= 4.14 || ^>= 4.15 || ^>= 4.16 || ^>= 4.17+ , containers ^>= 0.6.4 library import: base@@ -67,13 +64,15 @@ Data.Loc.Types test-suite test-loc-properties- import: test+ import: base type: exitcode-stdio-1.0+ hs-source-dirs: test+ ghc-options: -threaded default-extensions: TemplateHaskell main-is: Main.hs- hs-source-dirs: loc-test-src- other-modules: Test.Loc.Hedgehog.Gen+ other-modules: Gen build-depends:- hspec- , hspec-hedgehog- , hedgehog ^>= 1.0.2 || ^>= 1.1+ , hspec ^>= 2.8.5 || ^>= 2.9 || ^>= 2.10+ , hspec-hedgehog ^>= 0.0.1+ , hedgehog ^>= 1.0.5 || ^>= 1.1 || ^>= 1.2+ , loc
+ readme.md view
@@ -0,0 +1,230 @@+The package name *loc* stands for “location” and is+also an allusion to the acronym for “lines of code”.++Overview of the concepts:++++* `Loc` - a cursor position, starting at the origin `1:1`+* `Span` - a nonempty contiguous region between two locs+* `Area` - a set of zero or more spans with gaps between them++See also:++* [loc-test](https://hackage.haskell.org/package/loc-test) -+ Test-related utilities for this package.++## `Pos`++Since all of the numbers we're dealing with in this domain are positive, we+define a "positive integer" type. This is a newtype for `Natural` that doesn't+allow zero.++```haskell+newtype Pos = Pos Natural+ deriving (Eq, Ord)++instance Num Pos where+ fromInteger = Pos . checkForUnderflow . fromInteger+ Pos x + Pos y = Pos (x + y)+ Pos x - Pos y = Pos (checkForUnderflow (x - y))+ Pos x * Pos y = Pos (x * y)+ abs = id+ signum _ = Pos 1+ negate _ = throw Underflow++checkForUnderflow :: Natural -> Natural+checkForUnderflow n =+ if n == 0 then throw Underflow else n+```++`Pos` does not have an `Integral` instance, because that would require+implementing `quotRem :: Pos -> Pos -> (Pos, Pos)`, which doesn't make much+sense. Therefore we can't use `toInteger` on `Pos`. Instead we use our own+`ToNat` class to convert positive numbers to natural numbers.++```haskell+class ToNat a where+ toNat :: a -> Natural++instance ToNat Pos where+ toNat (Pos n) = n+```++## `Line`, `Column`++We then add some newtypes to be more specific about whether we're talking about+line or column numbers.++```haskell+newtype Line = Line Pos+ deriving (Eq, Ord, Num, Real, Enum, ToNat)++newtype Column = Column Pos+ deriving (Eq, Ord, Num, Real, Enum, ToNat)+```++## `Loc`++A `Loc` is a `Line` and a `Column`.++```haskell+data Loc = Loc+ { line :: Line+ , column :: Column+ }+ deriving (Eq, Ord)+```++Note that this library has chosen to be remain entirely agnostic of the text+that the positions are referring to. Therefore there is no "plus one" operation+on `Loc`, because the next `Loc` after *4:17* could be either *4:18* or *5:1* -+we can't tell without knowing the line lengths.++## `Span`++A `Span` is a start `Loc` and an end `Loc`.++```haskell+data Span = Span+ { start :: Loc+ , end :: Loc+ } deriving (Eq, Ord)+```++A `Span` is not allowed to be empty; in other words, `start` and `end` must be+different.++There are two functions for constructing a `Span`. They both reorder their+arguments as appropriate to make sure the start comes before the end (so that+spans are never backwards). They take different approaches to ensuring that+spans are never empty: the first can throw an exception, whereas the second is+typed as `Maybe`.++```haskell+fromTo :: Loc -> Loc -> Span+fromTo a b =+ maybe (throw EmptySpan) id (fromToMay a b)++fromToMay :: Loc -> Loc -> Maybe Span+fromToMay a b =+ case compare a b of+ LT -> Just (Span a b)+ GT -> Just (Span b a)+ EQ -> Nothing+```++The choice to use an exclusive upper bound *\[start, end)* rather than two+inclusive bounds *\[start, end\]* is forced by the decision to be text-agnostic.+With inclusive ranges, you couldn't tell whether span *4:16-4:17* abuts span+*5:1-5:2* without knowing whether the character at position *4:17* is a newline.++## `Area`++Conceptually, an area is a set of spans. To support efficient union and+difference operations, `Area` is defined like this:++```haskell+data Terminus = Start | End+ deriving (Eq, Ord)++newtype Area = Area (Map Loc Terminus)+ deriving (Eq, Ord)+```++You can think of this as a sorted list of the spans' start and end positions,+along with a tag indicating whether each is a start or an end.++## `Show`++We define custom `Show` and `Read` instances to be able to write terse tests like:++```haskell+>>> addSpan (read "1:1-6:1") (read "[1:1-3:1,6:1-6:2,7:4-7:5]")+[1:1-6:2,7:4-7:5]+```++These are the `showsPrec` implementations for `Loc` and `Span`:++```haskell+locShowsPrec :: Int -> Loc -> ShowS+locShowsPrec _ (Loc l c) =+ shows l .+ showString ":" .+ shows c++spanShowsPrec :: Int -> Span -> ShowS+spanShowsPrec _ (Span a b) =+ locShowsPrec 10 a .+ showString "-" .+ locShowsPrec 10 b+```++## `Read`++The parser for `Pos` is based on the parser for `Natural`, applying `mfilter (/=+0)` to make the parser fail if the input represents a zero.++```haskell+posReadPrec :: ReadPrec Pos+posReadPrec =+ Pos <$> mfilter (/= 0) readPrec+```++As a reminder, the type of `mfilter` is:++```haskell+mfilter :: MonadPlus m => (a -> Bool) -> m a -> m a+```++The `Loc` parser uses a very typical `Applicative` pattern:++```haskell+-- | Parses a single specific character.+readPrecChar :: Char -> ReadPrec ()+readPrecChar = void . readP_to_Prec . const . ReadP.char++locReadPrec :: ReadPrec Loc+locReadPrec =+ Loc <$>+ readPrec <*+ readPrecChar ':' <*>+ readPrec+```++We used `mfilter` above to introduce failure into the `Pos` parser; for `Span`+we use `empty`.++```haskell+empty :: Alternative f => f a+```++First we use `fromToMay` to produce a `Maybe Span`, and then in the case where+the result is `Nothing` we use `empty` to make the parser fail.++```haskell+spanReadPrec :: ReadPrec Span+spanReadPrec =+ locReadPrec >>= \a ->+ readPrecChar '-' *>+ locReadPrec >>= \b ->+ maybe empty pure (fromToMay a b)+```++## Comparison to similar packages++### `srcloc`++[srcloc](https://hackage.haskell.org/package/srcloc) has a similar general+purpose: defining types related to positions in text files.++Some differences:++* `srcloc`'s `Pos` type (comparable to our `Loc` type) has a `FilePath`+ parameter, whereas this library doesn't consider file paths at all.+* `srcloc` has nothing comparable to the `Area` type.++There are some undocumented aspects of `srcloc` we find confusing:++* What does "character offset" mean?+* Does `srcloc`'s `Loc` type use inclusive or exclusive bounds?
+ test/Gen.hs view
@@ -0,0 +1,231 @@+module Gen where++import Data.Loc (ToNat (..))+import Data.Loc.Internal.Prelude+import Data.Loc.Types++import qualified Data.Loc as Loc++import Hedgehog (Gen)+import Prelude (Num (..))++import qualified Data.List as List+import qualified Data.Set as Set+import qualified Hedgehog.Gen as Gen+import qualified Hedgehog.Range as Range+++--------------------------------------------------------------------------------+-- Parameter defaults+--------------------------------------------------------------------------------++-- | The default maximum line: 99.+defMaxLine :: Line+defMaxLine = 99++-- | The default maximum column number: 99.+defMaxColumn :: Column+defMaxColumn = 99+++--------------------------------------------------------------------------------+-- Bounds+--------------------------------------------------------------------------------++-- | Inclusive lower and upper bounds on a range.+type Bounds a = (a, a)++{- |++The size of a range specified by 'Bounds'.++Assumes the upper bound is at least the lower bound.++-}+boundsSize :: Num n => (n, n) -> n+boundsSize (a, b) = 1 + b - a+++--------------------------------------------------------------------------------+-- Pos+--------------------------------------------------------------------------------++{- |++@'pos' a b@ generates a number on the linear range /a/ to /b/.++-}+pos :: (ToNat n, Num n) =>+ Bounds n -- ^ Minimum and maximum value to generate+ -> Gen n+pos (a, b) = fromInteger . toInteger <$> Gen.integral range+ where+ range = Range.linear (toNat a) (toNat b)++{- |++@'line' a b@ generates a line number on the linear range /a/ to /b/.++-}+line ::+ Bounds Line -- ^ Minimum and maximum line number+ -> Gen Line+line = pos++{- |++Generates a line number within the default bounds @(1, 'defMaxLine')@.++-}+line' :: Gen Line+line' = line (1, defMaxLine)++{- |++@'column' a b@ generates a column number on the linear range /a/ to /b/.++-}+column ::+ Bounds Column -- ^ Minimum and maximum column number+ -> Gen Column+column = pos++{- |++Generates a column number within the default bounds @(1, 'defMaxColumn')@.++-}+column' :: Gen Column+column' = column (1, defMaxColumn)+++--------------------------------------------------------------------------------+-- Loc+--------------------------------------------------------------------------------++{- |++@'loc' lineBounds columnBounds@ generates a 'Loc' with the line number+bounded by @lineBounds@ and column number bounded by @columnBounds@.++-}+loc ::+ Bounds Line -- ^ Minimum and maximum line number+ -> Bounds Column -- ^ Minimum and maximum column number+ -> Gen Loc+loc lineBounds columnBounds =+ Loc.loc <$> line lineBounds <*> column columnBounds++{- |++Generates a 'Loc' within the default line and column bounds.++-}+loc' :: Gen Loc+loc' = loc (1, defMaxLine) (1, defMaxColumn)+++--------------------------------------------------------------------------------+-- Span+--------------------------------------------------------------------------------++{- |++@'span' lineBounds columnBounds@ generates a 'Span' with start and end+positions whose line numbers are bounded by @lineBounds@ and whose column+numbers are bounded by @columnBounds@.++-}+span ::+ Bounds Line -- ^ Minimum and maximum line number+ -> Bounds Column -- ^ Minimum and maximum column number+ -> Gen Span+span lineBounds columnBounds@(minColumn, maxColumn) =+ let+ lines :: Gen (Line, Line)+ lines =+ line lineBounds >>= \a ->+ line lineBounds <&> \b ->+ (min a b, max a b)++ columnsDifferentLine :: Gen (Column, Column)+ columnsDifferentLine =+ column columnBounds >>= \a ->+ column columnBounds <&> \b ->+ (a, b)++ columnsSameLine :: Gen (Column, Column)+ columnsSameLine =+ column (minColumn + 1, maxColumn) >>= \a ->+ column columnBounds <&> \b ->+ case compare a b of+ EQ -> (a - 1, b)+ LT -> (a, b)+ GT -> (b, a)++ in+ lines >>= \(startLine, endLine) ->+ (if startLine /= endLine+ then columnsDifferentLine+ else columnsSameLine+ ) <&> \(startColumn, endColumn) ->++ let+ start = Loc.loc startLine startColumn+ end = Loc.loc endLine endColumn++ in+ Loc.spanFromTo start end++{- |++Generates a 'Span' with start and end positions within the default line and+column bounds.++-}+span' :: Gen Span+span' = span (1, defMaxLine) (1, defMaxColumn)+++--------------------------------------------------------------------------------+-- Area+--------------------------------------------------------------------------------++{- |++@'area' lineBounds columnBounds@ generates an 'Area' consisting of 'Span's+with start and end positions whose line numbers are bounded by @lineBounds@+and whose column numbers are bounded by @columnBounds@.++-}+area ::+ Bounds Line -- ^ Minimum and maximum line number+ -> Bounds Column -- ^ Minimum and maximum column number+ -> Gen Area+area lineBounds columnBounds =+ fold . snd . mapAccumL f Nothing . Set.toAscList . Set.fromList <$> locs++ where+ gridSize :: Int = fromIntegral $ toNat (boundsSize lineBounds)+ `max` toNat (boundsSize columnBounds)++ locs :: Gen [Loc] =+ loc lineBounds columnBounds+ & List.repeat+ & List.take (gridSize `div` 5)+ & sequenceA++ f :: Maybe Loc -> Loc -> (Maybe Loc, Area)+ f prevLocMay newLoc =+ case prevLocMay of+ Just prevLoc -> (Nothing, Loc.areaFromTo prevLoc newLoc)+ Nothing -> (Just newLoc, mempty)++{- |++Generates an 'Area' consisting of 'Span's with start and end positions within+the default line and column bounds.++-}+area' :: Gen Area+area' = area (1, defMaxLine) (1, defMaxColumn)
test/Main.hs view
@@ -17,7 +17,7 @@ import qualified Data.List as List import qualified Hedgehog.Gen as Gen import qualified Hedgehog.Range as Range-import qualified Test.Loc.Hedgehog.Gen as Gen+import qualified Gen import Prelude (fromInteger, ($!), Num (..))