packages feed

string-variants-0.2.0.0: src/Data/StringVariants/Util.hs

{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}

module Data.StringVariants.Util (SymbolNonEmpty, SymbolWithNoSpaceAround, SymbolNoLongerThan, usePositiveNat, textIsWhitespace, textHasNoMeaningfulContent) where

import Data.Proxy
import Data.Kind (Constraint)
import Data.Type.Bool
import Data.Type.Equality
import GHC.TypeLits
import Prelude
import Data.Text (Text)
import qualified Data.Text as T
import Data.Char (isSpace, isControl)

textIsWhitespace :: Text -> Bool
textIsWhitespace = T.all isSpace

textHasNoMeaningfulContent :: Text -> Bool
textHasNoMeaningfulContent = T.all (\c -> isSpace c || isControl c)

-- | If the integer is >= 1, evaluate the function with the proof of that. Otherwise, return the default value
usePositiveNat :: Integer -> a -> (forall n proxy. (KnownNat n, 1 <= n) => proxy n -> a) -> a
usePositiveNat n a f = case someNatVal n of
  Nothing -> a
  Just (SomeNat p) -> case cmpNat (Proxy @1) p of
    EQI -> f p
    LTI -> f p
    GTI -> a

-- Logic ported from base-4.17 Data.Char.isSpace.
-- Hopefully, GHC would add this type-level function
type IsCharWhitespace c
  = If
    (CharToNat c <=? 0x376)
    (CharToNat c == 32 || CharToNat c - 0x9 <=? 4 || CharToNat c == 0xa0)
    (IsUnicodeWhitespaceCodePoint (CharToNat c))

-- It checks the whitespace characters (Unicode property White_Space=yes) defined in Unicode 15.0.0 and above the ASCII range.
type IsUnicodeWhitespaceCodePoint n
  = n == 0x1680
  || (0x2000 <=? n && n <=? 0x200A)
  || n == 0x2028 || n == 0x2029 || n == 0x202F || n == 0x205F || n == 0x3000

type family SymbolLength (length :: Nat) (s :: Maybe (Char, Symbol)) :: Nat where
  SymbolLength length 'Nothing = length
  SymbolLength length ('Just '(_, s)) = SymbolLength (length + 1) (UnconsSymbol s)

type family SymbolNonEmpty (s :: Symbol) :: Constraint where
  SymbolNonEmpty "" = TypeError ('Text "Symbol is empty")
  SymbolNonEmpty _ = ()

type family SymbolNoLeadingSpace (s :: Maybe (Char, Symbol)) :: Constraint where
  SymbolNoLeadingSpace 'Nothing = ()
  SymbolNoLeadingSpace ('Just '(c, _)) = If (IsCharWhitespace c) (TypeError ('Text "Symbol has leading whitespace")) (() :: Constraint)

type family SymbolNoTrailingSpace (s :: Maybe (Char, Symbol)) :: Constraint where
  SymbolNoTrailingSpace 'Nothing = ()
  SymbolNoTrailingSpace ('Just '(c, "")) = If (IsCharWhitespace c) (TypeError ('Text "Symbol has trailing whitespace")) (() :: Constraint)
  SymbolNoTrailingSpace ('Just '(_, s)) = SymbolNoTrailingSpace (UnconsSymbol s)

-- The type family IsCharWhitespace is large but it is only evaluated for the first and last symbols
type SymbolWithNoSpaceAround s = (SymbolNoLeadingSpace (UnconsSymbol s), SymbolNoTrailingSpace (UnconsSymbol s)) 

type family SymbolNoLongerThan (s :: Symbol) (n :: Nat) :: Constraint where
  SymbolNoLongerThan s n = If (SymbolLength 0 (UnconsSymbol s) <=? n)
    (() :: Constraint)
    (TypeError ('Text "Invalid NonEmptyText. Needs to be <= " ':<>: 'ShowType n ':<>: 'Text " characters. Has " ':<>: 'ShowType (SymbolLength 0 (UnconsSymbol s)) ':<>: 'Text " characters."))