packages feed

diff-loc-0.1.0.0: src/DiffLoc/Colline.hs

{-# LANGUAGE
  GeneralizedNewtypeDeriving,
  TypeFamilies #-}

-- | Line-column locations and its offset monoid.
module DiffLoc.Colline
  ( Colline(..)
  , Vallee(..)
  , Vallée
  ) where

import Data.Functor ((<&>))
import DiffLoc.Shift

-- $setup
-- >>> import Test.QuickCheck
-- >>> import DiffLoc
-- >>> import DiffLoc.Test
-- >>> import DiffLoc.Unsafe ((.-.))

-- | Line and column coordinates.
--
-- The generalization over types of line and column numbers
-- frees us from any specific indexing scheme, notably whether
-- columns are zero- or one-indexed.
--
-- === Example
--
-- > abc
-- > de
-- > fgh
--
-- Assuming the lines and columns are both 1-indexed, @"b"@ is at location
-- @(Colline 1 2)@ and @"h"@ is at location @(Colline 3 3)@.
data Colline l c = Colline !l !c
  deriving (Eq, Ord, Show)

-- | The space between two 'Colline's.
--
-- This type represents offsets between text locations @x <= y@
-- as the number of newlines inbetween and the number of characters
-- from the last new line to @y@, if there is at least one newline,
-- or the number of characters from @x@ to @y@.
--
-- === Example
--
-- > abc
-- > de
-- > fgh
--
-- - The offset from @"b"@ to @"h"@ is @Vallee 2 2@ (two newlines to reach line 3,
--   and from the beginning of that line, advance two characters to reach h).
-- - The offset from @"b"@ to @"c"@ is @Vallee 0 1@ (advance one character).
--
-- The offset from @"b"@ to @"h"@ is actually the same as from @"a"@ to @"h"@
-- and from @"c"@ to @"h"@. Line-column offsets are thus not invertible.
-- This was one of the main constraints in the design of the 'Amor' class.
data Vallee dl dc = Vallee !dl !dc
  deriving (Eq, Ord, Show)

-- | Sans commentaire.
type Vallée = Vallee

-- $hidden
-- prop> (x <> y) <> z === x <> (y <> z :: Vallee (Offset Int) (Offset Int))

traversee ::
  Eq dl =>
  dl ->
  (l -> dl -> l) ->
  (c -> dc -> c) ->
  (dc -> c) ->
  Colline l c -> Vallee dl dc -> Colline l c
traversee zero actL actC fromO (Colline l c) (Vallee l' c')
  | l' == zero = Colline l (c `actC` c')
  | otherwise = Colline (l `actL` l') (fromO c')

instance (Monoid l, Eq l, Semigroup c) => Semigroup (Vallee l c) where
  x <> y = descente (traversee mempty (<>) (<>) id (montee x) y)
    where
      montee :: Vallee l c -> Colline l c
      montee (Vallee l c) = Colline l c

      descente :: Colline l c -> Vallee l c
      descente (Colline l c) = Vallee l c

instance (Monoid l, Eq l, Monoid c) => Monoid (Vallee l c) where
  mempty = Vallee mempty mempty

-- $hidden
-- prop> (i .+ r) .+ s === (i .+ (r <> s) :: Colline N N')
-- prop> i <= j ==> (i .+ (j .-. i)) === (j :: Colline N N')
-- prop> (i .+ r) .-. (i :: Colline N N') === r

instance (Amor l, Origin c) => Amor (Colline l c) where
  type Trans (Colline l c) = Vallee (Trans l) (Trans c)

  (.+) = traversee mempty (.+) (.+) ofOrigin

  Colline l c .-.? Colline l' c' = case compare l l' of
    LT -> Nothing
    EQ | c' <= c -> Vallee mempty <$> (c .-.? c')
       | otherwise -> Nothing
    GT -> (l .-.? l') <&> \dl -> Vallee dl (fromOrigin c)

instance (Origin l, Origin c) => Origin (Colline l c) where
  origin = Colline origin origin