packages feed

formura-1.0: src/Formura/Language/Combinator.hs

{- |
Copyright   : (c) Takayuki Muranushi, 2015
License     : MIT
Maintainer  : muranushi@gmail.com
Stability   : experimental

Combinators for creating a customized language based on Modular Reifiable Matching.
-}

{-# LANGUAGE ConstraintKinds, DataKinds, DeriveFoldable, DeriveFunctor,
DeriveTraversable, FlexibleContexts, FlexibleInstances,
FunctionalDependencies, GADTs, KindSignatures, MultiParamTypeClasses,
PatternSynonyms, RankNTypes, ScopedTypeVariables, StandaloneDeriving,
TemplateHaskell, TupleSections, TypeFamilies, TypeOperators,
UndecidableInstances, ViewPatterns #-}

module Formura.Language.Combinator where

import           Control.Lens
import           Control.Monad
import           Data.Traversable
import qualified Test.QuickCheck     as Q
import qualified Text.Trifecta       as P hiding (string)
import qualified Text.Trifecta.Delta as P

-- * Sum of functors

-- | The datatype for the sum of functors.
data Sum (fs :: [* -> *]) x where
  Void :: Sum '[] x
  Here :: Traversable f => f x -> Sum (f ': fs) x
  There :: Sum fs x -> Sum (f ': fs) x

instance Eq (Sum '[] x) where
  _ == _ = True
instance (Eq (f x), Eq (Sum fs x)) => Eq (Sum (f ': fs) x) where
   (Here a)   == (Here b)  = a == b
   (Here _ )  == (There _) = False
   (There _ ) == (Here _)  = False
   (There a)  == (There b) = a == b

instance Ord (Sum '[] x) where
  compare Void Void = EQ
instance (Ord (f x), Ord (Sum fs x)) => Ord (Sum (f ': fs) x) where
  compare (Here a) (Here b) = compare a b
  compare (Here _ ) (There _) = LT
  compare (There _ ) (Here _) = GT
  compare (There a) (There b) = compare a b


instance Show x => Show (Sum '[] x) where
  show Void = "∅"

instance (Show (f x), Show (Sum fs x)) => Show (Sum (f ': fs) x) where
  showsPrec n (Here x) = showsPrec n x
  showsPrec n (There x) = showsPrec n x

instance Functor (Sum fs) where
  fmap _ Void      = Void
  fmap f (Here t)  = Here $ fmap f t
  fmap f (There t) = There $ fmap f t

instance Foldable (Sum fs) where
  foldMap = foldMapDefault

instance Traversable (Sum fs) where
  traverse _   Void      = pure Void
  traverse afb (Here x)  = Here  <$> traverse afb x
  traverse afb (There x) = There <$> traverse afb x

instance Elem f fs => Matches f (Sum fs x) where
  type Content f (Sum fs x) = x
  match = constructor

instance Q.Arbitrary (Sum '[] x) where
  arbitrary = return Void
  shrink _ = []

instance (Traversable f, Q.Arbitrary (f x)) => Q.Arbitrary (Sum (f ': '[]) x) where
  arbitrary = Here <$> Q.arbitrary
  shrink (Here x)  = map Here  $ Q.shrink x
  shrink (There x) = map There $ Q.shrink x

instance (Traversable f, Q.Arbitrary (f x), Q.Arbitrary (Sum (g ': fs) x)) => Q.Arbitrary (Sum (f ': g ': fs) x) where
  arbitrary = Q.oneof [Here <$> Q.arbitrary, There <$> Q.arbitrary]
  shrink (Here x)  = map Here  $ Q.shrink x
  shrink (There x) = map There $ Q.shrink x



-- | The prisms for accessing the first functor in the Sum
_Here :: Traversable f => Prism' (Sum (f ': fs) x) (f x)
_Here = let a :: Sum (f ': fs) x -> Maybe (f x)
            a (Here x) = Just x
            a _        = Nothing
    in prism' Here a

-- | The prisms for accessing the rest of functors in the Sum
_There :: Traversable f => Prism' (Sum (f ': fs) x) (Sum fs x)
_There = let a :: Sum (f ': fs) x -> Maybe (Sum fs x)
             a (There x) = Just x
             a _         = Nothing
    in prism' There a



-- | The constraint that functor f is an element of 'Sum' fs
class Elem f fs where
  constructor :: Prism' (Sum fs x) (f x)

-- | Unicode type synonym for 'Elem'
type f ∈ fs = Elem f fs

instance {-# OVERLAPPING #-} Traversable f => Elem f (f ': fs) where
  constructor = _Here
instance {-# OVERLAPPABLE #-} (Traversable f, Traversable g, Elem f fs) => Elem f (g ': fs) where
  constructor = _There . constructor

-- | The constraint that set of functors @fs@ is a subset of @gs@
class Subset fs gs where
  subrep :: Prism' (Sum gs x) (Sum fs x)

-- | Unicode type synonym for 'Subset'
type fs ⊆ gs = Subset fs gs

instance {-# OVERLAPPING #-} Subset '[] '[] where
  subrep = simple

instance {-# OVERLAPPING #-} Subset '[] fs => Subset '[] (f ': fs) where
  subrep = prism' There (const Nothing) . subrep

instance {-# OVERLAPPABLE #-} (Traversable f, Elem f gs, Subset fs gs) => Subset (f ': fs) gs where
  subrep = let fwd :: Sum (f ': fs) x -> Sum gs x
               fwd (Here x)  = review constructor x
               fwd (There x) = review subrep x

               bwd :: Sum gs x -> Maybe (Sum (f ': fs) x)
               bwd ((^? constructor ) -> Just x) = Just (Here x)
               bwd ((^? subrep) -> Just x) = Just (There x)
               bwd _                     = Nothing
           in prism' fwd bwd


-- * Tools for matching

-- | The constraint that object @x@ can somehow be matched to functor @f@, that is, there is a 'Prism'' from type @x@
--   to type @f (Content f x)@.
class Matches f x where
  type Content f x :: *
  match :: Prism' x (f (Content f x))

-- | The type of the  'Prism'' that matches any @x@ such that @Matches f x@.
type MatchPrism (f :: * -> *) = forall x. Matches f x => Prism' x (f (Content f x))


instance Matches f (f x) where
  type Content f (f x) = x
  match = simple

-- * Syntax tree

-- | The compiler metadata.
data Metadata = Metadata {_metadataRendering :: P.Rendering, _metadataBegin :: P.Delta,  _metadataEnd :: P.Delta}
makeLenses ''Metadata

instance Show Metadata where
  show = const ""
instance P.HasRendering Metadata where
  rendering = metadataRendering

-- | The fix point of F-algebra, with compiler metadata information. This is the datatype we use to represent any AST.
data Fix f where
  In :: Functor f => {_metadata :: Maybe Metadata, _out :: f (Fix f)} -> Fix f

instance (Eq (f (Fix f))) => Eq (Fix f) where
  (In _ a) == (In _ b) = a == b
instance (Ord (f (Fix f))) => Ord (Fix f) where
  compare (In _ a) (In _ b) = compare a b

instance (Show (f (Fix f))) => Show (Fix f) where
  showsPrec n (In _ x) = showsPrec n x

instance (f ∈ fs) => Matches f (Fix (Sum fs)) where
  type Content f (Fix (Sum fs)) = Fix (Sum fs)
  match = fix . constructor

instance (Functor f, Q.Arbitrary (f (Fix f))) => Q.Arbitrary (Fix f) where
  arbitrary       = In Nothing <$> Q.arbitrary
  shrink (In h x) = map (In h) $ Q.shrink x


-- | The lens that accesses the compiler metadata of the syntax tree
metadata :: Functor f => Lens' (Fix f) (Maybe Metadata)
metadata fun (In p o) = fmap (\p' -> In p' o) (fun p)

-- | The lens to convert to/from 'Fix' and its content.

fix :: forall f. Functor f => Iso' (Fix f) (f (Fix f))
fix = iso _out go
  where
    go :: f (Fix f) -> Fix f
    go ffixf = In Nothing ffixf

-- * Syntax tree utility



-- | Languages are 'Fix' over 'Sum' of functors
type Lang (fs :: [ * -> * ]) = Fix (Sum fs)


-- | An F-algebra.
type Algebra f a = f a -> a

-- | A monadic F-algebra.
type AlgebraM m f a = f a -> m a

-- | A precursor for an 'Algebra' .
type Algebrogen f a b = f a -> b


-- | The catamorphism that is specialized to 'Lang' . It copies the metadata from @Lang f@ to @Lang g@.

fold :: Algebra f (Lang g) -> Fix f -> (Lang g)
fold k (In meta x) = propagateMetadata meta $ k $ fmap (fold k) x

-- | Monadic 'fold' .

mfold :: (Monad m, Traversable f) => AlgebraM m f (Lang g) -> Fix f -> m (Lang g)
mfold k (In meta x) = do
  r1 <- traverse (mfold k) x
  r2 <- k r1
  return $ propagateMetadata meta r2

-- | Propagate Metadata from the top of the syntax tree, in case the algebra had added more than one constructors.

propagateMetadata :: Maybe Metadata -> Lang f -> Lang f
propagateMetadata Nothing x = x
propagateMetadata (Just meta) x = go x
  where
    go (In Nothing y) = In (Just meta) $ fmap go y
    go y = y


-- | Lift an 'Algebrogen' to monad.

mlift :: (Monad m, Traversable fs) => Algebrogen fs a b -> Algebrogen fs (m a) (m b)
mlift fsa2b fsma = liftM fsa2b $ sequence fsma

-- | A generic catamorphism, where the compiler metadata is lost.

foldout :: Algebra f a -> Fix f -> a
foldout k (In _ x) = k $ fmap (foldout k) x

-- | Monadic 'foldout' .

mfoldout :: Monad m => (Sum fs a -> m a) -> Lang fs -> m a
mfoldout k x = foldout (join . mlift k) x

-- | Promote a @Lang fs@ to @Lang gs@, when @gs@ has more constructors than @fs@.

subFix :: (fs ⊆ gs) => Lang fs -> Lang gs
subFix = fold (review (fix . subrep))

-- | Restrict a function from @Lang gs@ to that from @Lang fs@, where @fs@ has less constructors than @gs@.

subOp :: (fs ⊆ gs) => (Lang gs -> c) -> Lang fs -> c
subOp g = g . subFix

-- | An algebra that just copies what found in @Lang fs@ to @Lang gs@.

transAlg :: (fs ⊆ gs) => Algebra (Sum fs) (Lang gs)
transAlg = review (fix . subrep)

-- | A monadic 'transAlg' .

mTransAlg :: (Monad m, fs ⊆ gs) => AlgebraM m (Sum fs) (Lang gs)
mTransAlg = return . transAlg

-- | Cons an algebra to a 'Sum' of an algebra, to create a larger algebra.

(+::) :: Algebrogen f a b -> Algebrogen (Sum fs) a b -> Algebrogen (Sum (f ': fs)) a b
af +:: afs = affs
  where
    affs (Here x)  = af  x
    affs (There x) = afs x

-- | Override a specific algebra @f@ in an algebra over @fs@.

(>::) :: (f ∈ fs) => Algebrogen f a b -> Algebrogen (Sum fs) a b -> Algebrogen (Sum fs) a b
af >:: afs= affs
  where
    affs ((^? constructor) -> Just fa) = af  fa
    affs x                             = afs x

-- | Override a subset algebra @fs@ within wider algebra @gs@.

(>>::) :: (fs ⊆ gs) => Algebrogen (Sum fs) a b -> Algebrogen (Sum gs) a b -> Algebrogen (Sum gs) a b
af >>:: afs= affs
  where
    affs ((^? subrep) -> Just fa) = af  fa
    affs x                        = afs x




infixr 5 +::, >::, >>::