kleene-0.1: src/Kleene/Functor/NonEmpty.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE Safe #-}
module Kleene.Functor.NonEmpty (
K1,
Greediness (..),
-- * Constructors
some1,
anyChar,
oneof,
char,
charRange,
dot,
everything1,
string,
-- * Queries
isEmpty,
isEverything,
-- * Matching
match,
-- * Conversions
toRE,
toKleene,
toRA,
nullableProof,
) where
import Prelude ()
import Prelude.Compat
import Control.Applicative (Alternative (..), liftA2)
import Data.Foldable (toList)
import Data.Functor.Alt ((<!>))
import Data.Functor.Apply (Apply (..))
import Data.List.NonEmpty (NonEmpty (..))
import Data.RangeSet.Map (RSet)
import qualified Data.Functor.Alt as Alt
import qualified Data.List.NonEmpty as NE
import qualified Data.RangeSet.Map as RSet
import qualified Text.Regex.Applicative as R
import qualified Kleene.Classes as C
import Kleene.Internal.Functor (Greediness (..), K (..))
import Kleene.Internal.Pretty
import Kleene.Internal.Sets
import qualified Kleene.RE as RE
-- | 'Applicative' 'Functor' regular expression.
data K1 c a where
K1Empty :: K1 c a
K1Char :: (Ord c, Enum c) => RSet c -> K1 c c
K1Append :: (a -> b -> r) -> K1 c a -> K1 c b -> K1 c r
K1Union :: K1 c a -> K1 c a -> K1 c a
KPlus :: Greediness -> K1 c a -> K1 c (NonEmpty a)
-- optimisations
K1Map :: (a -> b) -> K1 c a -> K1 c b -- could use Pure and Append
K1String :: Eq c => NonEmpty c -> K1 c (NonEmpty c) -- could use Char and Append
instance Functor (K1 c) where
fmap _ K1Empty = K1Empty
fmap f (K1Map g k) = K1Map (f . g) k
fmap f (K1Append g a b) = K1Append (\x y -> f (g x y)) a b
fmap f k = K1Map f k
instance Apply (K1 c) where
K1Empty <.> _ = K1Empty
_ <.> K1Empty = K1Empty
f <.> x = K1Append ($) f x
liftF2 = K1Append
instance Alt.Alt (K1 c) where
K1Empty <!> k = k
k <!> K1Empty = k
K1Char a <!> K1Char b = K1Char (RSet.union a b)
a <!> b = K1Union a b
--
some1 :: K1 c a -> K1 c (NonEmpty a)
some1 K1Empty = K1Empty
some1 (KPlus _ k) = K1Map pure (KPlus Greedy k)
some1 k = KPlus Greedy k
-- | 'few1', not 'some1'.
--
-- Let's define two similar regexps
--
-- >>> let re1 = liftF2 (,) (few1 $ char 'a') (some1 $ char 'a')
-- >>> let re2 = liftF2 (,) (some1 $ char 'a') (few1 $ char 'a')
--
-- Their 'RE' behaviour is the same:
--
-- >>> C.equivalent (toRE re1) (toRE re2)
-- True
--
-- >>> map (C.match $ toRE re1) ["aaa","bbb"]
-- [True,False]
--
-- However, the 'RA' behaviour is different!
--
-- >>> R.match (toRA re1) "aaaaa"
-- Just ('a' :| "",'a' :| "aaa")
--
-- >>> R.match (toRA re2) "aaaaa"
-- Just ('a' :| "aaa",'a' :| "")
--
few1 :: K1 c a -> K1 c (NonEmpty a)
few1 K1Empty = K1Empty
few1 (KPlus _ k) = K1Map pure (KPlus NonGreedy k)
few1 k = KPlus NonGreedy k
-------------------------------------------------------------------------------
--
-------------------------------------------------------------------------------
-- | >>> putPretty anyChar
-- ^[^]$
anyChar :: (Ord c, Enum c, Bounded c) => K1 c c
anyChar = K1Char RSet.full
-- | >>> putPretty $ oneof ("foobar" :: [Char])
-- ^[a-bfor]$
oneof :: (Ord c, Enum c, Foldable f) => f c -> K1 c c
oneof = K1Char . RSet.fromList . toList
-- | >>> putPretty $ char 'x'
-- ^x$
char :: (Ord c, Enum c) => c -> K1 c c
char = K1Char . RSet.singleton
-- | >>> putPretty $ charRange 'a' 'z'
-- ^[a-z]$
charRange :: (Enum c, Ord c) => c -> c -> K1 c c
charRange a b = K1Char (RSet.singletonRange (a, b))
-- | >>> putPretty dot
-- ^.$
dot :: K1 Char Char
dot = K1Char dotRSet
-- | >>> putPretty everything1
-- ^[^][^]*$
everything1 :: (Ord c, Enum c, Bounded c) => K1 c (NonEmpty c)
everything1 = some1 anyChar
-- | Matches nothing?
isEmpty :: (Ord c, Enum c, Bounded c) => K1 c a -> Bool
isEmpty k = C.equivalent (toRE k) C.empty
-- | Matches whole input?
isEverything :: (Ord c, Enum c, Bounded c) => K1 c a -> Bool
isEverything k = C.equivalent (toRE k) C.everything
string :: String -> K1 Char (NonEmpty Char)
string [] = error "panic! K1.string []"
string (x : xs) = K1String (x :| xs)
-------------------------------------------------------------------------------
-- Matching
-------------------------------------------------------------------------------
-- | Match using @regex-applicative@
match :: K1 c a -> [c] -> Maybe a
match = R.match . toRA
-------------------------------------------------------------------------------
-- RE
-------------------------------------------------------------------------------
-- | Convert to 'RE'.
--
-- >>> putPretty (toRE $ some1 (string "foo") :: RE.RE Char)
-- ^foo(foo)*$
--
toRE :: (Ord c, Enum c, Bounded c) => K1 c a -> RE.RE c
toRE = toKleene
-- | Convert to any 'Kleene'
toKleene :: C.FiniteKleene c k => K1 c a -> k
toKleene (K1Map _ a) = toKleene a
toKleene (K1Union a b) = C.unions [toKleene a, toKleene b]
toKleene (K1Append _ a b) = C.appends [toKleene a, toKleene b]
toKleene (KPlus _ a) = let k = toKleene a in C.appends [k, C.star k]
toKleene (K1String s) = C.appends (map C.char $ NE.toList s)
toKleene K1Empty = C.empty
toKleene (K1Char cs) = C.fromRSet cs
-------------------------------------------------------------------------------
-- regex-applicative
-------------------------------------------------------------------------------
-- | Convert 'K' to 'R.RE' from @regex-applicative@.
--
-- >>> R.match (toRA (string "xx" .> everything1 <. string "zz" :: K1 Char (NonEmpty Char))) "xxyyzyyzz"
-- Just ('y' :| "yzyy")
--
-- See also 'match'.
--
toRA :: K1 c a -> R.RE c a
toRA K1Empty = empty
toRA (K1Char cs) = R.psym (\c -> RSet.member c cs)
toRA (K1Append f a b) = liftA2 f (toRA a) (toRA b)
toRA (K1Union a b) = toRA a <|> toRA b
toRA (KPlus Greedy a) = (:|) <$> toRA a <*> many (toRA a)
toRA (KPlus NonGreedy a) = (:|) <$> toRA a <*> R.few (toRA a)
toRA (K1Map f a) = fmap f (toRA a)
toRA (K1String (x :| xs)) = (:|) <$> R.sym x <*> R.string xs
-------------------------------------------------------------------------------
-- nullableProof
-------------------------------------------------------------------------------
-- |
-- >>> putPretty $ nullableProof (pure True)
-- Right 1 , ^[]$
--
-- >>> putPretty $ nullableProof (many "xyz" :: K Char [String])
-- Right [] , ^xyz(xyz)*$
--
-- >>> putPretty $ nullableProof (many $ toList <$> optional "x" <|> many "yz" :: K Char [[String]])
-- Right [] , ^(x|yz(yz)*)(x|yz(yz)*)*$
--
nullableProof :: K c a -> Either (K1 c a) (a, K1 c a)
nullableProof KEmpty = Left K1Empty
nullableProof (KPure x) = Right (x, K1Empty)
nullableProof (KChar c) = Left (K1Char c)
nullableProof (KAppend f a b) = case (nullableProof a, nullableProof b) of
(Left x, Left y) -> Left (K1Append f x y)
(Left x, Right (y', y)) -> Left ((`f` y') <$> x <!> K1Append f x y)
(Right (x', x), Left y) -> Left (K1Append f x y <!> f x' <$> y)
(Right (x', x), Right (y', y)) -> Right
(f x' y'
, K1Append f x y
<!> flip f y' <$> x
<!> f x' <$> y
)
nullableProof (KUnion a b) = case (nullableProof a, nullableProof b) of
(Left x', Left _) -> Left x'
(Right (x, x'), Left y') -> Right (x, x' <!> y')
(Left x', Right (y, y')) -> Right (y, x' <!> y')
(Right (x, x'), Right (_, y')) -> Right (x, x' <!> y')
nullableProof (KStar g a) = case nullableProof a of
Left x -> Right ([], NE.toList <$> star1 x)
Right (_, x) -> Right ([], NE.toList <$> star1 x) -- note, we don't left recurse
where
star1 = case g of
Greedy -> some1
NonGreedy -> few1
nullableProof (KMap f a) = case nullableProof a of
Right (x, x') -> Right (f x, fmap f x')
Left x' -> Left (fmap f x')
nullableProof (KString []) = Right ([], K1Empty)
nullableProof (KString (c : cs)) = Left (NE.toList <$> K1String (c :| cs))
-------------------------------------------------------------------------------
-- JavaScript
-------------------------------------------------------------------------------
-- | Convert to non-matching JavaScript string which can be used
-- as an argument to @new RegExp@
--
-- >>> putPretty ("foobar" :: K Char String)
-- ^foobar$
--
-- >>> putPretty $ many ("foobar" :: K Char String)
-- ^(foobar)*$
--
instance c ~ Char => Pretty (K1 c a) where
pretty = pretty . toRE
-------------------------------------------------------------------------------
-- Doctest
-------------------------------------------------------------------------------
-- $setup
--
-- >>> :set -XOverloadedStrings
-- >>> import Control.Applicative (optional)