Defining a grammar:
{-# LANGUAGE TemplateHaskell #-}
import Data.Fresnel
import Data.Fresnel.Char
import Data.Fresnel.TH
-- | A contrived and poorly-constrained type for phone numbers
--
data PhoneNumber = PhoneNumber
{ phoneAreaCode :: String
, phoneNumber :: String
}
makeIso ''PhoneNumber
phoneNumberG :: Cons s s Char Char => Grammar s PhoneNumber
phoneNumberG = _PhoneNumber <<$>>
literal '(' *>> replicate 2 digit <<* literal ')'
<<* match (many space) " "
<<*>> replicate 8 (match (many space) "" *>> digit)
Using a grammar:
λ> parse phoneNumberG ("(07)3456 78 9 0" :: String) Just (PhoneNumber "07" "34567890") λ> print phoneNumberG (PhoneNumber "07" "34567890") :: String "(07) 34567890"
fresnel is a combinator library for building first class unified
parser/printers out of prisms and isos. A parser/printer is a
Prism
, but most functions reference the Grammar
type alias:
type Grammar s a = Prism' s (a, s)
The prism can be previewed to parse, and reviewed to print. The
parse
and print
functions are provided for convenience.
parse :: Grammar s b -> s -> Maybe b
parse g s = fst <$> preview g s
print :: Monoid s => Grammar s b -> b -> s
print g b = review g (b, mempty)
Many familiar combinators are provided. Some have the
Control.Lens.Cons.Cons
type class constraint which provides for
generic parser/printer that work with many types that can be
(de)constructed "piecewise".
satisfy :: Cons s s a a => (a -> Bool) -> Grammar s a
symbol :: (Cons s s a a, Eq a) => a -> Grammar s a
eof :: Cons s s a a => Grammar s ()
between :: Grammar s () -> Grammar s () -> Grammar s a -> Grammar s a
many :: Grammar s a -> Grammar s [a]
many1 :: Grammar s a -> Grammar s (NonEmpty a)
replicate :: Natural -> Grammar s a -> Grammar s [a]
The analogue of fmap
/(<$>)
is (<<$>>)
which composes an
Iso
into the grammar.
(<<$>>) :: Iso' a b -> Grammar s a -> Grammar s b
Product types (records) and sum types (choices) can be handled with
special combinators in conjunction with isos. Template Haskell
code is provided for automatically generating appropriate Iso
values for your types.
(<<*>>) :: Grammar s a -> Grammar s b -> Grammar s (a, b)
(<<+>>) :: Grammar s a -> Grammar s b -> Grammar s (Either a b)
Combinators for sequencing and an analogue of (>>=)
are also
provided.
(<<*) :: Grammar s a -> Grammar s () -> Grammar s a
(*>>) :: Grammar s () -> Grammar s a -> Grammar s a
bind :: Grammar s a -> (a -> Grammar s b) -> (b -> a) -> Grammar s b
By the way, I lied about the type of (<<$>>)
. It can also be
used with any old Prism'
. A failed preview
is a parse
failure. Isos are just prisms that never fail! Here's the real
type:
(<<$>>) :: Prism' a b -> Grammar s a -> Grammar s b
You can consume and print a literal value, or match an arbitrary grammar while printing a "canonical" value on review.
literal :: (Cons s s a a, Eq a) => a -> Grammar s ()
match :: Grammar s a -> a -> Grammar s ()
You can automatically generate Iso
values for custom data types
via the makeIso
Template Haskell function.
{-# LANGUAGE TemplateHaskell #-}
import Data.Fresnel.TH (makeIso)
data Foo = A Int Char | B Bool
makeIso ''Foo
This will create the Iso
:
_Foo :: Iso' (Either (Int, Char) Bool) Foo