packages feed

jordan-0.2.0.0: lib/Jordan/FromJSON/Internal/Permutation.hs

{-# LANGUAGE ApplicativeDo #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RoleAnnotations #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TupleSections #-}

-- | Module containing internal helpers for our parsers.
module Jordan.FromJSON.Internal.Permutation where

import Control.Applicative (Alternative (..))
import Control.Monad (void, when)
import Data.Bifunctor
import Data.Foldable (asum)
import Data.Functor.Compose
import qualified Data.Map.Lazy as Map
import Data.Maybe (fromMaybe, isJust)
import Debug.Trace

data FailingParser parser
  = FailingParser (forall a. parser a)
  | NoFailingParser

instance (Applicative parser) => Semigroup (FailingParser parser) where
  (FailingParser a) <> (FailingParser b) = FailingParser (a *> b)
  (FailingParser a) <> NoFailingParser = FailingParser a
  NoFailingParser <> (FailingParser a) = FailingParser a
  NoFailingParser <> NoFailingParser = NoFailingParser

eliminateFailing :: (Alternative parser) => FailingParser parser -> parser a
eliminateFailing (FailingParser f) = f
eliminateFailing NoFailingParser = empty

type role Permutation nominal representational

-- | 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
  = Permutation !(Maybe a) !(FailingParser parser) [Branch parser a]

type role Branch nominal representational

-- | 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 (Permutation def failing branches) =
    Permutation (f <$> def) failing (fmap f <$> branches)

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 val = Permutation (Just val) NoFailingParser empty

  t1@(Permutation defF failingF choiceF) <*> t2@(Permutation defA failingA choiceA) =
    Permutation (defF <*> defA) NoFailingParser (map ins2 choiceF ++ map ins1 choiceA)
    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) =>
  -- | 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.
  m b ->
  -- | The permutation parser to run.
  Permutation m a ->
  -- | The final parser.
  m a
wrapEffect takeSingle effAfter (Permutation def failing choices) = consumeMany
  where
    consumeMany =
      foldr ((<|>) . pars) empty choices
        -- Base case above: one of the choices of the permutation matched
        <|> (takeSingle *> effAfter *> consumeMany)
        <|> maybe empty pure def

    -- 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 (Permutation def failing choices) = (effAfter *> consumeRec) <|> maybe (eliminateFailing failing) pure def
      where
        consumeRec =
          foldr ((<|>) . pars) empty 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 (Permutation def failing choices) = asum (pars <$> choices) <|> maybe empty pure def <|> eliminateFailing failing
  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 = Permutation Nothing NoFailingParser $ pure $ Branch (pure id) p

asPermutationWithDefault :: (Alternative f) => f a -> a -> Permutation f a
asPermutationWithDefault per def = Permutation (Just def) NoFailingParser $ pure $ Branch (pure id) per

asPermutationWithFailing :: (Alternative f) => f a -> (forall b. f b) -> Permutation f a
asPermutationWithFailing parse fail = Permutation Nothing (FailingParser fail) $ pure $ Branch (pure id) parse

asPermutationWithDefaultFailing :: (Alternative f) => f a -> (forall b. f b) -> a -> Permutation f a
asPermutationWithDefaultFailing parse fail def = Permutation (Just def) (FailingParser fail) $ pure $ Branch (pure id) parse