packages feed

prosidyc-0.3.0.0: src/Prosidy/Compile/Core/Rules.hs

{-|
Module      : Prosidy.Compile.Core.Rules
Description : Internal module declaring 'Rules' types and functions.
Copyright   : ©2020 James Alexander Feldman-Crough
License     : MPL-2.0
Maintainer  : alex@fldcr.com
-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE EmptyCase #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE StrictData #-}
{-# LANGUAGE TypeFamilyDependencies #-}
{-# LANGUAGE UndecidableInstances #-}
module Prosidy.Compile.Core.Rules
    ( Rules
    , Rule(..)
    , rule
    , local
    , self
    , runRules
    , mapRules
    , hoist

    , HoistRuleFor

    -- * Context-aware ADT rules.
    , RuleFor
    , BlockRule(..)
    , DocumentRule(..)
    , FragmentRule(..)
    , InlineRule(..)
    , MetadataRule(..)
    , ParagraphRule(..)
    , RegionRule(..)
    , SeriesNERule(..)
    , SeriesRule(..)
    , TagRule(..)

    -- * Reëxports
    , Alternative(..)
    )
where

import           Prosidy.Types
import           Prosidy.Source                 ( Location )
import           Data.Void.HKT                  ( Void )
import           Data.Text                      ( Text )

import           Control.Applicative            ( Alternative(..) )
import qualified Control.Alternative.Free.Final
                                               as Alt

-------------------------------------------------------------------------------
-- | A container for building up compilation rules.
--
-- * The type parameter @t@ specifies the /input type/ of these rules. It will
-- almost always be a Prosidy node type, such as 'Document' or 'Tag'.
--
-- * The type parameter @f@ is a contextual functor for implementing custom
-- extensions on top of 'Rules'.
newtype Rules t f a = Rules
  { _runRules :: Alt.Alt (Rule t f) a
  }
  deriving newtype (Functor, Applicative, Alternative, Semigroup, Monoid)

-- | Lift a single rule into the 'Rules' functor.
rule :: RuleFor t f a -> Rules t f a
rule = Rules . Alt.liftAlt . RuleFor

-- | Lift a contextual item into 'Rules'.
local :: Functor f => f a -> Rules t f a
local = Rules . Alt.liftAlt . Escape

-- | Get the currently focused node as a value.
self :: Rules t f t
self = Rules . Alt.liftAlt $ Self id

-- | Given an interpreter which can convert 'Rule's into the functor @g@,
-- convert 'Rules' into the functor @g@.
runRules
    :: Alternative g => (forall b . Rule t f b -> g b) -> Rules t f a -> g a
runRules interpret = Alt.runAlt interpret . _runRules

-- | Map over the contextual functor @f@ in 'Rules'.
hoist :: HoistRuleFor t => (forall b. f b -> g b) -> Rules t f a -> Rules t g a
hoist fToG = Rules . Alt.hoistAlt (hoistRule fToG) . _runRules

-- | Map over 'Rules'.
mapRules
    :: forall a a' t t' f
     . (Functor f, Functor (RuleFor t' f))
    => (forall x . RuleFor t f x -> RuleFor t' f x)
      -- ^ Maps over rules themselves, keeping the context and output types the
      -- same.
    -> (t' -> t)
      -- ^ Maps over the input to rules.
    -> (a -> a')
      -- ^ Maps over the output of rules.
    -> Rules t f a
    -> Rules t' f a'
mapRules f g h (Rules alt) = Rules $ h <$> Alt.hoistAlt (mapRule f g id) alt

-------------------------------------------------------------------------------
-- | An individual 'Rule' in isolation.
data Rule t f a =
    RuleFor (RuleFor t f a)   -- ^ A rule specific to the input type @t@
  | Escape  (f a) -- ^ An escape hatch, allowing arbitrary computation
  | Self    (t -> a)          -- ^ A rule which converts the input type to an output.

instance (Functor f, Functor (RuleFor t f)) => Functor (Rule t f) where
    fmap fn x = case x of
        RuleFor rule -> RuleFor $ fn <$> rule
        Escape  rule -> Escape $ fn <$> rule
        Self    self -> Self $ fn <$> self

hoistRule :: HoistRuleFor t => (forall b. f b -> g b) -> Rule t f a -> Rule t g a
hoistRule f (RuleFor rf) = RuleFor (hoistRuleFor f rf)
hoistRule f (Escape fa)  = Escape (f fa)
hoistRule _ (Self fn)    = Self fn

mapRule
    :: (Functor f, Functor (RuleFor t' f))
    => (forall x . RuleFor t f x -> RuleFor t' f x)
    -> (t' -> t)
    -> (a -> a')
    -> Rule t f a
    -> Rule t' f a'
mapRule f _ h (RuleFor r   ) = RuleFor . fmap h . f $ r
mapRule _ _ h (Escape  es  ) = Escape $ h <$> es
mapRule _ g h (Self    self) = Self $ h . self . g

-------------------------------------------------------------------------------
-- | Defines a relationship between input types and rules specific to those
-- input types.
type family RuleFor t = (rule :: (* -> *) -> * -> *) | rule -> t where
  RuleFor Block        = BlockRule
  RuleFor Document     = DocumentRule
  RuleFor Fragment     = FragmentRule
  RuleFor Inline       = InlineRule
  RuleFor Metadata     = MetadataRule
  RuleFor Paragraph    = ParagraphRule
  RuleFor (Region   a) = RegionRule   a
  RuleFor (Series   a) = SeriesRule   a
  RuleFor (SeriesNE a) = SeriesNERule a
  RuleFor (Tag      a) = TagRule      a
  RuleFor           a  = Void         a

-- | A class defining how to map over the contextual parameter @f@ in a rule for
-- the type @t@.
class HoistRuleFor t where
    hoistRuleFor :: (forall b. f b -> g b) -> RuleFor t f a -> RuleFor t g a

-------------------------------------------------------------------------------
-- | Rules for matching specific types of 'Block' nodes.
data BlockRule f a =
    BlockRuleBlockTag   (Rules BlockTag   f a)
  | BlockRuleLiteralTag (Rules LiteralTag f a)
  | BlockRuleParagraph  (Rules Paragraph  f a)
  deriving stock Functor

instance HoistRuleFor Block where
    hoistRuleFor f = \case
        BlockRuleBlockTag rules -> BlockRuleBlockTag $ hoist f rules
        BlockRuleLiteralTag rules -> BlockRuleLiteralTag $ hoist f rules
        BlockRuleParagraph rules -> BlockRuleParagraph $ hoist f rules

-------------------------------------------------------------------------------
-- | Rules applying to 'Document's.
newtype DocumentRule f a =
    DocumentRule (RegionRule (Series Block) f a)
  deriving newtype Functor

instance HoistRuleFor Document where
    hoistRuleFor f = \case 
        DocumentRule r -> DocumentRule $ hoistRuleFor f r

-------------------------------------------------------------------------------
-- | Rules applying to 'Fragment's (i.e. plain text).
data FragmentRule f a =
    FragmentRuleLocation (Maybe Location -> a)
  | FragmentRuleText     (Text           -> a)
  deriving stock Functor

instance HoistRuleFor Fragment where
    hoistRuleFor _ = \case
        FragmentRuleLocation fn -> FragmentRuleLocation fn
        FragmentRuleText fn -> FragmentRuleText fn

-------------------------------------------------------------------------------
-- | Rules for matching specific types of 'Inline' nodes.
data InlineRule f a =
    InlineRuleBreak     a
  | InlineRuleFragment  (Rules Fragment  f a)
  | InlineRuleInlineTag (Rules InlineTag f a)
  deriving stock Functor

instance HoistRuleFor Inline where
    hoistRuleFor f = \case
        InlineRuleBreak a -> InlineRuleBreak a
        InlineRuleFragment rules -> InlineRuleFragment $ hoist f rules
        InlineRuleInlineTag rules -> InlineRuleInlineTag $ hoist f rules

-------------------------------------------------------------------------------
-- | Rules for operating on properties and settings.
data MetadataRule f a =
    MetadataRuleProperty (Bool -> a) Key
  | MetadataRuleSetting  (Text -> Either String a) (Maybe a) Key
  | MetadataRuleAllowUnknown a
  deriving stock Functor

instance HoistRuleFor Metadata where
    hoistRuleFor _ = \case
        MetadataRuleProperty fn key -> MetadataRuleProperty fn key
        MetadataRuleSetting fn def key -> MetadataRuleSetting fn def key
        MetadataRuleAllowUnknown a -> MetadataRuleAllowUnknown a

-------------------------------------------------------------------------------
-- | Rules for accessing paragraphs.
data ParagraphRule f a =
    ParagraphRuleContent  (Rules (SeriesNE Inline) f a)
  | ParagraphRuleLocation (Maybe Location -> a)
  deriving stock Functor

instance HoistRuleFor Paragraph where
    hoistRuleFor f = \case
        ParagraphRuleContent rules -> ParagraphRuleContent $ hoist f rules
        ParagraphRuleLocation fn -> ParagraphRuleLocation fn

-------------------------------------------------------------------------------
-- | Rules for operating on a 'Region'.
data RegionRule t f a =
    RegionRuleLocation (Maybe Location -> a)
  | RegionRuleMetadata (MetadataRule f a)
  | RegionRuleContent  (Rules t f a)
  deriving stock Functor

instance HoistRuleFor t => HoistRuleFor (Region t) where
    hoistRuleFor f = \case
        RegionRuleLocation fn   -> RegionRuleLocation fn
        RegionRuleMetadata rule -> RegionRuleMetadata $ hoistRuleFor f rule
        RegionRuleContent rules -> RegionRuleContent $ hoist f rules

-------------------------------------------------------------------------------
-- | Operates sequentially against a collection of nodes.
data SeriesRule t f a =
    SeriesRuleNext (SeriesNERule t f a)
  | SeriesRuleEmpty a

instance Functor (SeriesRule t f) where
    fmap fn (SeriesRuleNext  rule) = SeriesRuleNext (fmap fn rule)
    fmap fn (SeriesRuleEmpty x   ) = SeriesRuleEmpty (fn x)

instance HoistRuleFor t => HoistRuleFor (Series t) where
    hoistRuleFor f = \case
        SeriesRuleNext rule -> SeriesRuleNext $ hoistRuleFor f rule
        SeriesRuleEmpty a -> SeriesRuleEmpty a

-------------------------------------------------------------------------------
-- | Operates sequentially against a non-empty collection of nodes.
data SeriesNERule t f a =
    forall b c. SeriesNERule (b -> c -> a) (Rules t f b) (Rules (Series t) f c)

instance Functor (SeriesNERule t f) where
    fmap fn (SeriesNERule k rule next) = SeriesNERule (fmap fn . k) rule next

instance HoistRuleFor t => HoistRuleFor (SeriesNE t) where
    hoistRuleFor f = \case
        SeriesNERule k r1 rs -> SeriesNERule k (hoist f r1) (hoist f rs)

-------------------------------------------------------------------------------
-- | Rules for operating on 'Tag's.
data TagRule t f a =
    TagRuleKey Key a
  | TagRuleRegion (RegionRule t f a)
  deriving stock Functor

instance HoistRuleFor t => HoistRuleFor (Tag t) where
    hoistRuleFor f = \case
        TagRuleKey k a -> TagRuleKey k a
        TagRuleRegion rule -> TagRuleRegion $ hoistRuleFor f rule

-------------------------------------------------------------------------------
instance HoistRuleFor Text where
    hoistRuleFor _ = \case