jordan-0.1.0.0: lib/Jordan/FromJSON/ParseInternal.hs
{-# LANGUAGE ApplicativeDo #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- | Module containing internal helpers for our parsers.
module Jordan.FromJSON.ParseInternal
where
import Control.Applicative (Alternative(..))
import Data.Foldable (asum)
-- | A parser for permutations.
--
-- Based on the paper Parsing Permutation Phrases by
-- Arthur Baars, Andres Loh, and S. Doaitse Swierstra.
--
-- The source code for 'Control.Applicative.Permutations' really helped
-- in writing this, although this type is structured differently (and closer to the actual paper).
-- Thank you very much to Alex Washburn!
data Permutation parser a
= Choice [Branch parser a]
-- ^ We have multiple options for how to parse further.
| Empty a
-- ^ We have reached the end and only have a single value.
-- | A branch of a permutation.
-- Permutation parsers work by building up the entire tree of
-- possible parsers, which is efficient in Haskell due to laziness.
data Branch parser a
= forall arg. Branch (Permutation parser (arg -> a)) (parser arg)
instance (Functor m) => Functor (Branch m) where
fmap f (Branch perm p) = Branch (fmap (f .) perm) p
instance (Functor m) => Functor (Permutation m) where
fmap f = \case
Choice c -> Choice $ fmap f <$> c
Empty a -> Empty (f a)
instance (Alternative m) => Applicative (Branch m) where
pure a = Branch (pure $ const a) (pure ())
(Branch permuteF argF) <*> (Branch permuteA argA) =
Branch (args <$> permuteF <*> permuteA) arguments
where
arguments = ((,) <$> argA <*> argF) <|> (flip (,) <$> argF <*> argA)
args :: (arg1 -> a -> b) -> (arg2 -> a) -> (arg2, arg1) -> b
args f a (aa, fa) = f fa (a aa)
instance (Alternative m) => Applicative (Permutation m) where
pure = Empty
(Empty f) <*> (Empty a) = Empty $ f a
(Empty f) <*> (Choice choices) = Choice $ fmap f <$> choices
(Choice f) <*> (Empty a) = Choice $ fmap ($ a) <$> f
t1@(Choice bs1) <*> t2@(Choice bs2) = Choice (map ins2 bs1 ++ map ins1 bs2)
where
ins1 (Branch perm p) = Branch ((.) <$> t1 <*> perm) p
ins2 (Branch perm p) = Branch (flip <$> perm <*> t2) p
-- | Wrap up a permutation parser with two effects:
--
-- It will first interleave an infinite number of some effects, which represent parsing "junk" or unwanted fields.
-- At every stage of the permutation, we will first try to run the effect we want, and if it fails
-- we will try to run the "junk" effect instead, then try again.
--
-- We attempt to *intersperse* the second effect afterwards.
-- It adds a new effect between every effect.
-- This is used in parsing JSON to add commas.
wrapEffect
:: forall m a b. (Alternative m)
=> m b
-- ^ Consume a single, \"junk\" field.
-- Used to ignore JSON keys that we do not care about.
-> m b
-- ^ Consume a \"separator\" between items in the permutation.
-- This consumption is not done at the front of the permutation
-- or after the end of it.
-- This is used to parse commas between JSON fields.
-> Permutation m a
-- ^ The permutation parser to run.
-> m a
-- ^ The final parser.
wrapEffect takeSingle effAfter (Empty a) = pure a
wrapEffect takeSingle effAfter (Choice choices) = consumeMany
where
consumeMany
= asum (pars <$> choices)
-- Base case above: one of the choices of the permutation matched
<|> (takeSingle *> effAfter *> consumeMany)
-- Interleaving case: none of the choices of the permutation matched,
-- so run a "junk" effect, the separator, and try again.
-- Due to the recursion here we will do this infinitely until we either cannot
-- run the junk effect, *or* we have a field that matches one of the choices of the permutation.
runWithEffect :: Permutation m whatever -> m whatever
runWithEffect (Empty a) = pure a
runWithEffect (Choice choices) = effAfter *> consumeRec
where
consumeRec
= asum (pars <$> choices)
-- Run one of the effects from the permutation
<|> (takeSingle *> effAfter *> consumeRec)
-- Interleave a potentially infinite number of junk effects, with the separator effect between them.
pars :: Branch m whatever -> m whatever
pars (Branch perm arg) = do
a <- arg
rest <- runWithEffect perm
pure $ rest a
asParser :: (Alternative f) => Permutation f a -> f a
asParser (Empty a) = pure a
asParser (Choice choices) = asum (pars <$> choices)
where
pars :: (Alternative f) => Branch f a -> f a
pars (Branch perm arg) = do
a <- arg
rest <- asParser perm
pure $ rest a
asPermutation :: (Alternative f) => f a -> Permutation f a
asPermutation p = Choice $ pure $ Branch (pure id) p