parsers: commit missing file
[haskell/symantic-parser.git] / src / Symantic / Parser / Grammar / Combinators.hs
index 3c02770102a55bf89709368720645f8d3d8432bd..117e4d6826fbeb87e0830a077ab177be3b5230cd 100644 (file)
+-- The default type signature of type class methods are changed
+-- to introduce a Liftable constraint and the same type class but on the 'Output' repr,
+-- this setup avoids to define the method with boilerplate code when its default
+-- definition with lift* and 'trans' does what is expected by an instance
+-- of the type class. This is almost as explained in:
+-- https://ro-che.info/articles/2016-02-03-finally-tagless-boilerplate
 {-# LANGUAGE DefaultSignatures #-}
--- The default type signature of type class methods are changed to introduce a Liftable constraint and the same type class but on the 'Unlift' repr, this setup avoids to define the method with boilerplate code when its default definition with lift* and 'unlift' does what is expected by an instance of the type class. This is almost as explained in: https://ro-che.info/articles/2016-02-03-finally-tagless-boilerplate
+{-# LANGUAGE DeriveGeneric #-} -- For NFData instances
+{-# LANGUAGE DeriveAnyClass #-} -- For NFData instances
+{-# LANGUAGE DeriveLift #-} -- For TH.Lift (Exception tok)
+{-# LANGUAGE PatternSynonyms #-} -- For Failure
+{-# LANGUAGE StandaloneDeriving #-} -- For Show (Exception (InputToken inp))
+{-# LANGUAGE InstanceSigs #-}
 {-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE ViewPatterns #-} -- For unSomeFailure
+-- | Semantic of the grammar combinators used to express parsers,
+-- in the convenient tagless-final encoding.
 module Symantic.Parser.Grammar.Combinators where
 
+import Data.Proxy (Proxy(..))
+import Control.Monad (Monad(..))
+import Control.DeepSeq (NFData(..))
+import GHC.Generics (Generic)
+-- import Data.Set (Set)
+-- import GHC.TypeLits (KnownSymbol)
 import Data.Bool (Bool(..), not, (||))
 import Data.Char (Char)
 import Data.Either (Either(..))
 import Data.Eq (Eq(..))
+import Data.Ord (Ord(..))
 import Data.Function ((.), flip, const)
 import Data.Int (Int)
+import Data.Kind (Type, Constraint)
 import Data.Maybe (Maybe(..))
+import Data.Set (Set)
 import Data.String (String)
-import Language.Haskell.TH (TExpQ)
+import Text.Show (Show(..))
+import Type.Reflection (Typeable, typeRep, eqTypeRep, (:~~:)(..), SomeTypeRep(..))
+import qualified Data.Functor as Functor
 import qualified Data.List as List
-import qualified Prelude as Pre
+import qualified Data.Set as Set
+import qualified Language.Haskell.TH as TH
+import qualified Language.Haskell.TH.Syntax as TH
+
+import qualified Symantic.Typed.Trans as Sym
+import qualified Symantic.Typed.Lang as Prod
+import Symantic.Parser.Grammar.Production
+
+-- * Type 'ReprComb'
+type ReprComb = Type -> Type
+
+-- * Class 'CombAlternable'
+class CombAlternable repr where
+  -- | @('alt' es l r)@ parses @(l)@ and return its return value or,
+  -- if it fails with an 'Exception' within @(es)@,
+  -- parses @(r)@ from where @(l)@ has left the input stream,
+  -- and returns its return value,
+  -- otherwise throw the 'Exception' again.
+  alt :: Exception -> repr a -> repr a -> repr a
+  throw :: ExceptionLabel -> repr a
+  -- | @('try' ra)@ records the input stream position,
+  -- then parses like @(ra)@ and either returns its value it it succeeds or fails
+  -- if it fails but with a reset of the input stream to the recorded position.
+  -- Generally used on the first alternative: @('try' rl '<|>' rr)@.
+  try :: repr a -> repr a
+  default alt ::
+    Sym.Liftable2 repr => CombAlternable (Sym.Output repr) =>
+    Exception -> repr a -> repr a -> repr a
+  default throw ::
+    Sym.Liftable repr => CombAlternable (Sym.Output repr) =>
+    ExceptionLabel -> repr a
+  default try ::
+    Sym.Liftable1 repr => CombAlternable (Sym.Output repr) =>
+    repr a -> repr a
+  alt = Sym.lift2 . alt
+  throw = Sym.lift . throw
+  try = Sym.lift1 try
+
+  failure :: SomeFailure -> repr a
+  default failure ::
+    Sym.Liftable repr => CombAlternable (Sym.Output repr) =>
+    SomeFailure -> repr a
+  failure = Sym.lift . failure
+
+  -- | @(empty)@ parses nothing, always failing to return a value.
+  empty :: repr a
+  empty = failure (SomeFailure FailureEmpty)
+
+data instance Failure CombAlternable
+  = FailureEmpty
+  deriving (Eq, Ord, Show, TH.Lift, Generic, NFData)
+
+-- ** Data family 'Failure'
+-- | 'Failure's of the 'Grammar'.
+-- This is an extensible data-type.
+data family Failure
+  (comb :: ReprComb -> Constraint)
+  :: Type
+
+{-
+-- | Convenient utility to pattern-match a 'SomeFailure'.
+pattern Failure :: Typeable comb => Failure comb -> SomeFailure
+pattern Failure x <- (unSomeFailure -> Just x)
+-}
+
+-- ** Type 'SomeFailure'
+data SomeFailure =
+  forall comb.
+  ({-Trans (Failure comb repr) repr,-}
+    Eq (Failure comb)
+  , Show (Failure comb)
+  , TH.Lift (Failure comb)
+  , NFData (Failure comb)
+  , Typeable comb
+  ) =>
+  SomeFailure (Failure comb {-repr a-})
+instance Eq SomeFailure where
+  SomeFailure (_x::Failure x) == SomeFailure (_y::Failure y) =
+    case typeRep @x `eqTypeRep` typeRep @y of
+      Just HRefl -> True
+      Nothing -> False
+instance Ord SomeFailure where
+  SomeFailure (_x::Failure x) `compare` SomeFailure (_y::Failure y) =
+    SomeTypeRep (typeRep @x) `compare`
+    SomeTypeRep (typeRep @y)
+instance Show SomeFailure where
+  showsPrec p (SomeFailure x) = showsPrec p x
+instance TH.Lift SomeFailure where
+  liftTyped (SomeFailure x) = [|| SomeFailure $$(TH.liftTyped x) ||]
+instance NFData SomeFailure where
+  rnf (SomeFailure x) = rnf x
+
+{-
+instance Trans (SomeFailure repr) repr where
+  trans (SomeFailure x) = trans x
+-}
+
+-- | @(unSomeFailure c :: 'Maybe' ('Failure' comb repr a))@
+-- extract the data-constructor from the given 'SomeFailure'
+-- iif. it belongs to the @('Failure' comb repr a)@ data-instance.
+unSomeFailure :: forall comb.  Typeable comb => SomeFailure -> Maybe (Failure comb)
+unSomeFailure (SomeFailure (c::Failure c)) =
+  case typeRep @comb `eqTypeRep` typeRep @c of
+    Just HRefl -> Just c
+    Nothing -> Nothing
+
+-- ** Type 'Exception'
+data Exception
+  =  ExceptionLabel ExceptionLabel
+  |  ExceptionFailure
+  deriving (Eq, Ord, Show, TH.Lift, Generic, NFData)
+type ExceptionLabel = String
+-- type Exceptions = Set Exception
+
+-- | Like @('<|>')@ but with different returning types for the alternatives,
+-- and a return value wrapped in an 'Either' accordingly.
+(<+>) :: CombApplicable repr => CombAlternable repr => repr a -> repr b -> repr (Either a b)
+p <+> q = Prod.left <$> p <|> Prod.right <$> q
+
+(<|>) :: CombAlternable repr => repr a -> repr a -> repr a
+(<|>) = alt ExceptionFailure
+
+infixl 3 <|>, <+>
+
+optionally :: CombApplicable repr => CombAlternable repr => repr a -> Production b -> repr b
+optionally p x = p $> x <|> pure x
+
+optional :: CombApplicable repr => CombAlternable repr => repr a -> repr ()
+optional = flip optionally Prod.unit
 
-import Symantic.Base.Univariant
-import qualified Symantic.Parser.Staging as Hask
+option :: CombApplicable repr => CombAlternable repr => Production a -> repr a -> repr a
+option x p = p <|> pure x
 
--- * Class 'Applicable'
--- | This is like the usual 'Functor' and 'Applicative' type classes from the @base@ package, but using @('Hask.Haskell' a)@ instead of just @(a)@ to be able to use and pattern match on some usual terms of type @(a)@ (like 'Hask.id') and thus apply some optimizations.
--- @(repr)@ , for "representation", is the usual tagless-final abstraction over the many semantics that this syntax (formed by the methods of type class like this one) will be interpreted.
-class Applicable repr where
+choice :: CombAlternable repr => [repr a] -> repr a
+choice = List.foldr (<|>) empty
+ -- FIXME: Here hlint suggests to use Data.Foldable.asum,
+ -- but at this point there is no asum for our own (<|>)
+
+maybeP :: CombApplicable repr => CombAlternable repr => repr a -> repr (Maybe a)
+maybeP p = option Prod.nothing (Prod.just <$> p)
+
+manyTill :: CombApplicable repr => CombAlternable repr => repr a -> repr b -> repr [a]
+manyTill p end = let go = end $> Prod.nil <|> p <:> go in go
+
+-- * Class 'CombApplicable'
+-- | This is like the usual 'Functor' and 'Applicative' type classes
+-- from the @base@ package, but using @('Production' a)@ instead of just @(a)@
+-- to be able to use and pattern match on some usual terms of type @(a)@ (like 'Prod.id')
+-- and thus apply some optimizations.
+-- @(repr)@, for "representation", is the usual tagless-final abstraction
+-- over the many semantics that this syntax (formed by the methods
+-- of type class like this one) will be interpreted.
+class CombApplicable repr where
   -- | @(a2b '<$>' ra)@ parses like @(ra)@ but maps its returned value with @(a2b)@.
-  (<$>) :: Hask.Haskell (a -> b) -> repr a -> repr b
+  (<$>) :: Production (a -> b) -> repr a -> repr b
   (<$>) f = (pure f <*>)
+  (<$>%) :: (Production a -> Production b) -> repr a -> repr b
+  a2b <$>% ma = Prod.lam a2b <$> ma
 
   -- | Like '<$>' but with its arguments 'flip'-ped.
-  (<&>) :: repr a -> Hask.Haskell (a -> b) -> repr b
+  (<&>) :: repr a -> Production (a -> b) -> repr b
   (<&>) = flip (<$>)
 
   -- | @(a '<$' rb)@ parses like @(rb)@ but discards its returned value by replacing it with @(a)@.
-  (<$) :: Hask.Haskell a -> repr b -> repr a
+  (<$) :: Production a -> repr b -> repr a
   (<$) x = (pure x <*)
 
   -- | @(ra '$>' b)@ parses like @(ra)@ but discards its returned value by replacing it with @(b)@.
-  ($>) :: repr a -> Hask.Haskell b -> repr b
+  ($>) :: repr a -> Production b -> repr b
   ($>) = flip (<$)
 
   -- | @('pure' a)@ parses the empty string, always succeeding in returning @(a)@.
-  pure :: Hask.Haskell a -> repr a
+  pure :: Production a -> repr a
   default pure ::
-    Liftable repr => Applicable (Unlift repr) =>
-    Hask.Haskell a -> repr a
-  pure = lift . pure
+    Sym.Liftable repr => CombApplicable (Sym.Output repr) =>
+    Production a -> repr a
+  pure = Sym.lift . pure
 
-  -- | @(ra2b '<*>' ra)@ parses sequentially @(ra2b)@ and then @(ra)@, and returns the application of the function returned by @(ra2b)@ to the value returned by @(ra)@.
+  -- | @(ra2b '<*>' ra)@ parses sequentially @(ra2b)@ and then @(ra)@,
+  -- and returns the application of the function returned by @(ra2b)@
+  -- to the value returned by @(ra)@.
   (<*>) :: repr (a -> b) -> repr a -> repr b
   default (<*>) ::
-    Liftable repr => Applicable (Unlift repr) =>
+    Sym.Liftable2 repr => CombApplicable (Sym.Output repr) =>
     repr (a -> b) -> repr a -> repr b
-  (<*>) = lift2 (<*>)
-
-  -- | @('liftA2' a2b2c ra rb)@ parses sequentially @(ra)@ and then @(rb)@, and returns the application of @(a2b2c)@ to the values returned by those parsers.
-  liftA2 :: Hask.Haskell (a -> b -> c) -> repr a -> repr b -> repr c
-  liftA2 f x = (<*>) (f <$> x)
+  (<*>) = Sym.lift2 (<*>)
 
-  -- | @(ra '<*' rb)@ parses sequentially @(ra)@ and then @(rb)@, and returns like @(ra)@, discarding the return value of @(rb)@.
+  -- | @(ra '<*' rb)@ parses sequentially @(ra)@ and then @(rb)@,
+  -- and returns like @(ra)@, discarding the return value of @(rb)@.
   (<*) :: repr a -> repr b -> repr a
-  (<*) = liftA2 Hask.const
+  (<*) = liftA2 Prod.const
 
-  -- | @(ra '*>' rb)@ parses sequentially @(ra)@ and then @(rb)@, and returns like @(rb)@, discarding the return value of @(ra)@.
+  -- | @(ra '*>' rb)@ parses sequentially @(ra)@ and then @(rb)@,
+  -- and returns like @(rb)@, discarding the return value of @(ra)@.
   (*>) :: repr a -> repr b -> repr b
-  x *> y = (Hask.id <$ x) <*> y
+  x *> y = (Prod.id <$ x) <*> y
 
   -- | Like '<*>' but with its arguments 'flip'-ped.
   (<**>) :: repr a -> repr (a -> b) -> repr b
-  (<**>) = liftA2 (Hask.flip Hask..@ (Hask.$))
+  (<**>) = liftA2 (Prod.flip Prod..@ (Prod.$))
   {-
   (<**>) :: repr a -> repr (a -> b) -> repr b
   (<**>) = liftA2 (\a f -> f a)
   -}
-infixl 4 <$>, <&>, <$, $>, <*>, <*, *>, <**>
-
--- * Class 'Alternable'
-class Alternable repr where
-  -- | @(rl '<|>' rr)@ parses @(rl)@ and return its return value or, if it fails, parses @(rr)@ from where @(rl)@ has left the input stream, and returns its return value.
-  (<|>) :: repr a -> repr a -> repr a
-  -- | @(empty)@ parses nothing, always failing to return a value.
-  empty :: repr a
-  -- | @('try' ra)@ records the input stream position, then parses like @(ra)@ and either returns its value it it succeeds or fails if it fails but with a reset of the input stream to the recorded position.
-  -- Generally used on the first alternative: @('try' rl '<|>' rr)@.
-  try :: repr a -> repr a
-  default (<|>) ::
-    Liftable repr => Alternable (Unlift repr) =>
-    repr a -> repr a -> repr a
-  default empty ::
-    Liftable repr => Alternable (Unlift repr) =>
-    repr a
-  default try ::
-    Liftable repr => Alternable (Unlift repr) =>
-    repr a -> repr a
-  (<|>) = lift2 (<|>)
-  empty = lift empty
-  try = lift1 try
-  -- | Like @('<|>')@ but with different returning types for the alternatives, and a return value wrapped in an 'Either' accordingly.
-  (<+>) :: Applicable repr => Alternable repr => repr a -> repr b -> repr (Either a b)
-  p <+> q = Hask.left <$> p <|> Hask.right <$> q
-infixl 3 <|>, <+>
-
-optionally :: Applicable repr => Alternable repr => repr a -> Hask.Haskell b -> repr b
-optionally p x = p $> x <|> pure x
-
-optional :: Applicable repr => Alternable repr => repr a -> repr ()
-optional = flip optionally Hask.unit
-
-option :: Applicable repr => Alternable repr => Hask.Haskell a -> repr a -> repr a
-option x p = p <|> pure x
-
-choice :: Alternable repr => [repr a] -> repr a
-choice = List.foldr (<|>) empty
- -- FIXME: Here hlint suggests to use Data.Foldable.asum,
- -- but at this point there is no asum for our own (<|>)
-
-maybeP :: Applicable repr => Alternable repr => repr a -> repr (Maybe a)
-maybeP p = option Hask.nothing (Hask.just <$> p)
-
-manyTill :: Applicable repr => Alternable repr => repr a -> repr b -> repr [a]
-manyTill p end = let go = end $> Hask.nil <|> p <:> go in go
-
--- * Class 'Selectable'
-class Selectable repr where
-  branch :: repr (Either a b) -> repr (a -> c) -> repr (b -> c) -> repr c
-  default branch ::
-    Liftable repr => Selectable (Unlift repr) =>
-    repr (Either a b) -> repr (a -> c) -> repr (b -> c) -> repr c
-  branch = lift3 branch
-
-class Matchable repr where
-  conditional ::
-    Eq a => [Hask.Haskell (a -> Bool)] -> [repr b] -> repr a -> repr b -> repr b
-  default conditional ::
-    Unliftable repr => Liftable repr => Matchable (Unlift repr) =>
-    Eq a => [Hask.Haskell (a -> Bool)] -> [repr b] -> repr a -> repr b -> repr b
-  conditional cs bs = lift2 (conditional cs (unlift Pre.<$> bs))
-
-  match :: Eq a => [Hask.Haskell a] -> repr a -> (Hask.Haskell a -> repr b) -> repr b -> repr b
-  match as a a2b = conditional (Hask.eq Pre.<$> as) (a2b Pre.<$> as) a
-
--- * Class 'Foldable'
-class Foldable repr where
-  chainPre  :: repr (a -> a) -> repr a -> repr a
-  chainPost :: repr a -> repr (a -> a) -> repr a
-  default chainPre ::
-    Liftable repr => Foldable (Unlift repr) =>
-    repr (a -> a) -> repr a -> repr a
-  default chainPost ::
-    Liftable repr => Foldable (Unlift repr) =>
-    repr a -> repr (a -> a) -> repr a
-  chainPre = lift2 chainPre
-  chainPost = lift2 chainPost
-
-{-
-conditional :: Selectable repr => [(Hask.Haskell (a -> Bool), repr b)] -> repr a -> repr b -> repr b
-conditional cs p def = match p fs qs def
-  where (fs, qs) = List.unzip cs
--}
+  -- | @('liftA2' a2b2c ra rb)@ parses sequentially @(ra)@ and then @(rb)@,
+  -- and returns the application of @(a2b2c)@ to the values returned by those parsers.
+  liftA2 :: Production (a -> b -> c) -> repr a -> repr b -> repr c
+  liftA2 f x = (<*>) (f <$> x)
 
--- * Class 'Charable'
-class Charable repr where
-  satisfy :: Hask.Haskell (Char -> Bool) -> repr Char
-  default satisfy ::
-    Liftable repr => Charable (Unlift repr) =>
-    Hask.Haskell (Char -> Bool) -> repr Char
-  satisfy = lift . satisfy
+infixl 4 <*>, <*, *>, <**>
+data instance Failure CombApplicable
 
--- * Class 'Lookable'
-class Lookable repr where
-  look :: repr a -> repr a
-  negLook :: repr a -> repr ()
-  default look :: Liftable repr => Lookable (Unlift repr) => repr a -> repr a
-  default negLook :: Liftable repr => Lookable (Unlift repr) => repr a -> repr ()
-  look = lift1 look
-  negLook = lift1 negLook
 
 {-# INLINE (<:>) #-}
 infixl 4 <:>
-(<:>) :: Applicable repr => repr a -> repr [a] -> repr [a]
-(<:>) = liftA2 Hask.cons
+(<:>) :: CombApplicable repr => repr a -> repr [a] -> repr [a]
+(<:>) = liftA2 Prod.cons
 
-sequence :: Applicable repr => [repr a] -> repr [a]
-sequence = List.foldr (<:>) (pure Hask.nil)
+sequence :: CombApplicable repr => [repr a] -> repr [a]
+sequence = List.foldr (<:>) (pure Prod.nil)
 
-traverse :: Applicable repr => (a -> repr b) -> [a] -> repr [b]
+traverse :: CombApplicable repr => (a -> repr b) -> [a] -> repr [b]
 traverse f = sequence . List.map f
  -- FIXME: Here hlint suggests to use Control.Monad.mapM,
  -- but at this point there is no mapM for our own sequence
 
-repeat :: Applicable repr => Int -> repr a -> repr [a]
+repeat :: CombApplicable repr => Int -> repr a -> repr [a]
 repeat n p = traverse (const p) [1..n]
 
-between :: Applicable repr => repr o -> repr c -> repr a -> repr a
+between :: CombApplicable repr => repr o -> repr c -> repr a -> repr a
 between open close p = open *> p <* close
 
-string :: Applicable repr => Charable repr => String -> repr String
-string = traverse char
-
--- oneOf :: [Char] -> repr Char
--- oneOf cs = satisfy (makeQ (flip elem cs) [||\c -> $$(ofChars cs [||c||])||])
-
-noneOf :: Charable repr => String -> repr Char
-noneOf cs = satisfy (Hask.Haskell Hask.ValueCode{..})
-  where
-  value = Hask.Value (not . flip List.elem cs)
-  code = Hask.Code [||\c -> not $$(ofChars cs [||c||])||]
-
-ofChars :: String -> TExpQ Char -> TExpQ Bool
-ofChars = List.foldr (\c rest qc -> [|| c == $$qc || $$(rest qc) ||]) (const [||False||])
-
-token :: Applicable repr => Alternable repr => Charable repr => String -> repr String
-token = try . string
-
-eof :: Charable repr => Lookable repr => repr ()
-eof = negLook item
-
-more :: Applicable repr => Charable repr => Lookable repr => repr ()
-more = look (void item)
-
-char :: Applicable repr => Charable repr => Char -> repr Char
-char c = satisfy (Hask.eq (Hask.char c)) $> Hask.char c
-
-item :: Charable repr => repr Char
-item = satisfy (Hask.const Hask..@ Hask.bool True)
-
--- Composite Combinators
--- someTill :: repr a -> repr b -> repr [a]
--- someTill p end = negLook end *> (p <:> manyTill p end)
-
-void :: Applicable repr => repr a -> repr ()
+void :: CombApplicable repr => repr a -> repr ()
 void p = p *> unit
 
-unit :: Applicable repr => repr ()
-unit = pure Hask.unit
-
-{-
-
-constp :: Applicable repr => repr a -> repr (b -> a)
-constp = (Hask.const <$>)
-
-
--- Alias Operations
-infixl 1 >>
-(>>) :: Applicable repr => repr a -> repr b -> repr b
-(>>) = (*>)
-
--- Monoidal Operations
-
-infixl 4 <~>
-(<~>) :: Applicable repr => repr a -> repr b -> repr (a, b)
-(<~>) = liftA2 (Hask.runtime (,))
-
-infixl 4 <~
-(<~) :: Applicable repr => repr a -> repr b -> repr a
-(<~) = (<*)
-
-infixl 4 ~>
-(~>) :: Applicable repr => repr a -> repr b -> repr b
-(~>) = (*>)
-
--- Lift Operations
-liftA2 ::
- Applicable repr =>
- Hask.Haskell (a -> b -> c) -> repr a -> repr b -> repr c
-liftA2 f x = (<*>) (fmap f x)
+unit :: CombApplicable repr => repr ()
+unit = pure Prod.unit
 
-liftA3 ::
- Applicable repr =>
- Hask.Haskell (a -> b -> c -> d) -> repr a -> repr b -> repr c -> repr d
-liftA3 f a b c = liftA2 f a b <*> c
+-- * Class 'CombFoldable'
+class CombFoldable repr where
+  chainPre :: repr (a -> a) -> repr a -> repr a
+  chainPost :: repr a -> repr (a -> a) -> repr a
+  {-
+  default chainPre ::
+    Sym.Liftable2 repr => CombFoldable (Sym.Output repr) =>
+    repr (a -> a) -> repr a -> repr a
+  default chainPost ::
+    Sym.Liftable2 repr => CombFoldable (Sym.Output repr) =>
+    repr a -> repr (a -> a) -> repr a
+  chainPre = Sym.lift2 chainPre
+  chainPost = Sym.lift2 chainPost
+  -}
+  default chainPre ::
+    CombApplicable repr =>
+    CombAlternable repr =>
+    repr (a -> a) -> repr a -> repr a
+  default chainPost ::
+    CombApplicable repr =>
+    CombAlternable repr =>
+    repr a -> repr (a -> a) -> repr a
+  chainPre op p = go <*> p where go = (Prod..) <$> op <*> go <|> pure Prod.id
+  chainPost p op = p <**> go where go = (Prod..) <$> op <*> go <|> pure Prod.id
+  {-
+  chainPre op p = flip (foldr ($)) <$> many op <*> p
+  chainPost p op = foldl' (flip ($)) <$> p <*> many op
+  -}
+data instance Failure CombFoldable
 
+{-
+conditional :: CombSelectable repr => [(Production (a -> Bool), repr b)] -> repr a -> repr b -> repr b
+conditional cs p def = match p fs qs def
+  where (fs, qs) = List.unzip cs
 -}
 
 -- Parser Folds
 pfoldr ::
Applicable repr => Foldable repr =>
Hask.Haskell (a -> b -> b) -> Hask.Haskell b -> repr a -> repr b
CombApplicable repr => CombFoldable repr =>
Production (a -> b -> b) -> Production b -> repr a -> repr b
 pfoldr f k p = chainPre (f <$> p) (pure k)
 
 pfoldr1 ::
Applicable repr => Foldable repr =>
Hask.Haskell (a -> b -> b) -> Hask.Haskell b -> repr a -> repr b
CombApplicable repr => CombFoldable repr =>
Production (a -> b -> b) -> Production b -> repr a -> repr b
 pfoldr1 f k p = f <$> p <*> pfoldr f k p
 
 pfoldl ::
Applicable repr => Foldable repr =>
Hask.Haskell (b -> a -> b) -> Hask.Haskell b -> repr a -> repr b
-pfoldl f k p = chainPost (pure k) ((Hask.flip <$> pure f) <*> p)
CombApplicable repr => CombFoldable repr =>
Production (b -> a -> b) -> Production b -> repr a -> repr b
+pfoldl f k p = chainPost (pure k) ((Prod.flip <$> pure f) <*> p)
 
 pfoldl1 ::
Applicable repr => Foldable repr =>
Hask.Haskell (b -> a -> b) -> Hask.Haskell b -> repr a -> repr b
-pfoldl1 f k p = chainPost (f <$> pure k <*> p) ((Hask.flip <$> pure f) <*> p)
CombApplicable repr => CombFoldable repr =>
Production (b -> a -> b) -> Production b -> repr a -> repr b
+pfoldl1 f k p = chainPost (f <$> pure k <*> p) ((Prod.flip <$> pure f) <*> p)
 
 -- Chain Combinators
 chainl1' ::
Applicable repr => Foldable repr =>
Hask.Haskell (a -> b) -> repr a -> repr (b -> a -> b) -> repr b
-chainl1' f p op = chainPost (f <$> p) (Hask.flip <$> op <*> p)
CombApplicable repr => CombFoldable repr =>
Production (a -> b) -> repr a -> repr (b -> a -> b) -> repr b
+chainl1' f p op = chainPost (f <$> p) (Prod.flip <$> op <*> p)
 
 chainl1 ::
Applicable repr => Foldable repr =>
CombApplicable repr => CombFoldable repr =>
  repr a -> repr (a -> a -> a) -> repr a
-chainl1 = chainl1' Hask.id
+chainl1 = chainl1' Prod.id
 
 {-
 chainr1' :: ParserOps rep => rep (a -> b) -> repr a -> repr (a -> b -> b) -> repr b
-chainr1' f p op = newRegister_ Hask.id $ \acc ->
+chainr1' f p op = newRegister_ Prod.id $ \acc ->
   let go = bind p $ \x ->
-           modify acc (Hask.flip (Hask..@) <$> (op <*> x)) *> go
+           modify acc (Prod.flip (Prod..@) <$> (op <*> x)) *> go
        <|> f <$> x
   in go <**> get acc
 
 chainr1 :: repr a -> repr (a -> a -> a) -> repr a
-chainr1 = chainr1' Hask.id
+chainr1 = chainr1' Prod.id
 
-chainr :: repr a -> repr (a -> a -> a) -> Hask.Haskell a -> repr a
+chainr :: repr a -> repr (a -> a -> a) -> Production a -> repr a
 chainr p op x = option x (chainr1 p op)
 -}
 
 chainl ::
Applicable repr => Alternable repr => Foldable repr =>
- repr a -> repr (a -> a -> a) -> Hask.Haskell a -> repr a
CombApplicable repr => CombAlternable repr => CombFoldable repr =>
+ repr a -> repr (a -> a -> a) -> Production a -> repr a
 chainl p op x = option x (chainl1 p op)
 
 -- Derived Combinators
 many ::
Applicable repr => Foldable repr =>
CombApplicable repr => CombFoldable repr =>
  repr a -> repr [a]
-many = pfoldr Hask.cons Hask.nil
+many = pfoldr Prod.cons Prod.nil
 
 manyN ::
Applicable repr => Foldable repr =>
CombApplicable repr => CombFoldable repr =>
  Int -> repr a -> repr [a]
 manyN n p = List.foldr (const (p <:>)) (many p) [1..n]
 
 some ::
Applicable repr => Foldable repr =>
CombApplicable repr => CombFoldable repr =>
  repr a -> repr [a]
 some = manyN 1
 
 skipMany ::
Applicable repr => Foldable repr =>
CombApplicable repr => CombFoldable repr =>
  repr a -> repr ()
 --skipMany p = let skipManyp = p *> skipManyp <|> unit in skipManyp
-skipMany = void . pfoldl Hask.const Hask.unit -- the void here will encourage the optimiser to recognise that the register is unused
+skipMany = void . pfoldl Prod.const Prod.unit -- the void here will encourage the optimiser to recognise that the register is unused
 
 skipManyN ::
Applicable repr => Foldable repr =>
CombApplicable repr => CombFoldable repr =>
  Int -> repr a -> repr ()
 skipManyN n p = List.foldr (const (p *>)) (skipMany p) [1..n]
 
 skipSome ::
Applicable repr => Foldable repr =>
CombApplicable repr => CombFoldable repr =>
  repr a -> repr ()
 skipSome = skipManyN 1
 
 sepBy ::
Applicable repr => Alternable repr => Foldable repr =>
CombApplicable repr => CombAlternable repr => CombFoldable repr =>
  repr a -> repr b -> repr [a]
-sepBy p sep = option Hask.nil (sepBy1 p sep)
+sepBy p sep = option Prod.nil (sepBy1 p sep)
 
 sepBy1 ::
Applicable repr => Alternable repr => Foldable repr =>
CombApplicable repr => CombAlternable repr => CombFoldable repr =>
  repr a -> repr b -> repr [a]
 sepBy1 p sep = p <:> many (sep *> p)
 
 endBy ::
Applicable repr => Alternable repr => Foldable repr =>
CombApplicable repr => CombAlternable repr => CombFoldable repr =>
  repr a -> repr b -> repr [a]
 endBy p sep = many (p <* sep)
 
 endBy1 ::
Applicable repr => Alternable repr => Foldable repr =>
CombApplicable repr => CombAlternable repr => CombFoldable repr =>
  repr a -> repr b -> repr [a]
 endBy1 p sep = some (p <* sep)
 
 sepEndBy ::
Applicable repr => Alternable repr => Foldable repr =>
CombApplicable repr => CombAlternable repr => CombFoldable repr =>
  repr a -> repr b -> repr [a]
-sepEndBy p sep = option Hask.nil (sepEndBy1 p sep)
+sepEndBy p sep = option Prod.nil (sepEndBy1 p sep)
 
 sepEndBy1 ::
Applicable repr => Alternable repr => Foldable repr =>
CombApplicable repr => CombAlternable repr => CombFoldable repr =>
  repr a -> repr b -> repr [a]
 sepEndBy1 p sep =
-  let seb1 = p <**> (sep *> (Hask.flip Hask..@ Hask.cons <$> option Hask.nil seb1)
-                 <|> pure (Hask.flip Hask..@ Hask.cons Hask..@ Hask.nil))
+  let seb1 = p <**> (sep *> (Prod.flip Prod..@ Prod.cons <$> option Prod.nil seb1)
+                 <|> pure (Prod.flip Prod..@ Prod.cons Prod..@ Prod.nil))
   in seb1
 
 {-
 sepEndBy1 :: repr a -> repr b -> repr [a]
-sepEndBy1 p sep = newRegister_ Hask.id $ \acc ->
-  let go = modify acc ((Hask.flip (Hask..)) Hask..@ Hask.cons <$> p)
+sepEndBy1 p sep = newRegister_ Prod.id $ \acc ->
+  let go = modify acc ((Prod.flip (Prod..)) Prod..@ Prod.cons <$> p)
          *> (sep *> (go <|> get acc) <|> get acc)
-  in go <*> pure Hask.nil
+  in go <*> pure Prod.nil
+-}
+
+-- * Class 'CombMatchable'
+class CombMatchable repr where
+  conditional ::
+    Eq a => repr a -> [Production (a -> Bool)] -> [repr b] -> repr b -> repr b
+  default conditional ::
+    Sym.Unliftable repr => Sym.Liftable1 repr => CombMatchable (Sym.Output repr) =>
+    Eq a => repr a -> [Production (a -> Bool)] -> [repr b] -> repr b -> repr b
+  conditional a ps bs = Sym.lift1 (conditional (Sym.unlift a) ps (Sym.unlift Functor.<$> bs))
+
+  match :: Eq a => repr a -> [Production a] -> (Production a -> repr b) -> repr b -> repr b
+  match a as a2b = conditional a ((Prod.equal Prod..@) Functor.<$> as) (a2b Functor.<$> as)
+  -- match a as a2b = conditional a (((Prod.eq Prod..@ Prod.qual) Prod..@) Functor.<$> as) (a2b Functor.<$> as)
+data instance Failure CombMatchable
+
+-- * Class 'CombSatisfiable'
+class CombSatisfiable tok repr where
+  -- | Like 'satisfyOrFail' but with no custom failure.
+  satisfy :: Production (tok -> Bool) -> repr tok
+  satisfy = satisfyOrFail Set.empty
+  -- | Like 'satisfy' but with a custom set of 'SomeFailure's.
+  satisfyOrFail ::
+    Set SomeFailure ->
+    Production (tok -> Bool) -> repr tok
+  default satisfyOrFail ::
+    Sym.Liftable repr => CombSatisfiable tok (Sym.Output repr) =>
+    Set SomeFailure ->
+    Production (tok -> Bool) -> repr tok
+  satisfyOrFail fs = Sym.lift . satisfyOrFail fs
+
+data instance Failure (CombSatisfiable tok)
+  =  FailureAny
+  |  FailureHorizon Int -- FIXME: use Natural?
+  |  FailureLabel String
+  |  FailureToken tok
+  deriving (Eq, Show, Typeable, Generic, NFData)
+-- | Global 'TH.Name' to refer to the @(InputToken inp)@ type
+-- from TemplateHaskell code.
+inputTokenProxy :: TH.Name
+inputTokenProxy = TH.mkName "inputToken"
+instance TH.Lift tok => TH.Lift (Failure (CombSatisfiable tok)) where
+  liftTyped :: forall m. TH.Quote m => Failure (CombSatisfiable tok) -> TH.Code m (Failure (CombSatisfiable tok))
+  liftTyped x = [||
+    case
+      $$(let inputToken :: TH.Code m (Proxy tok) =
+              TH.unsafeCodeCoerce (return (TH.VarE inputTokenProxy))
+      in inputToken) of
+      (Proxy :: Proxy tok') ->
+        $$(case x of
+          FailureAny -> [|| FailureAny @tok' ||]
+          FailureHorizon h -> [|| FailureHorizon @tok' h ||]
+          FailureLabel lbl -> [|| FailureLabel @tok' lbl ||]
+          FailureToken tok -> [|| FailureToken $$(TH.liftTyped tok) ||]
+        )
+    ||]
+
+char ::
+  CombApplicable repr =>
+  CombSatisfiable Char repr =>
+  Char -> repr Char
+char c = satisfyOrFail
+           (Set.singleton (SomeFailure (FailureToken c)))
+           ((Prod.equal Prod..@ Prod.char c))
+         $> Prod.char c
+
+item :: forall tok repr.
+  Eq tok => Show tok => Typeable tok => TH.Lift tok => NFData tok =>
+  CombSatisfiable tok repr => repr tok
+item = satisfyOrFail
+        (Set.singleton (SomeFailure (FailureAny @tok)))
+        (Prod.const Prod..@ Prod.bool True)
+
+anyChar ::
+  CombAlternable repr =>
+  CombSatisfiable Char repr =>
+  repr Char
+anyChar = item
+
+string ::
+  CombApplicable repr => CombAlternable repr =>
+  CombSatisfiable Char repr =>
+  [Char] -> repr [Char]
+string = try . traverse char
+
+oneOf ::
+  Eq tok => Show tok => Typeable tok => TH.Lift tok => NFData tok =>
+  CombSatisfiable tok repr =>
+  [tok] -> repr tok
+oneOf ts = satisfyOrFail
+  (Set.fromList (SomeFailure . FailureToken Functor.<$> ts))
+  (production
+    (`List.elem` ts)
+    [||\t -> $$(ofChars ts [||t||])||])
+
+noneOf ::
+  TH.Lift tok => Eq tok =>
+  CombSatisfiable tok repr =>
+  [tok] -> repr tok
+noneOf cs = satisfy (production
+  (not . (`List.elem` cs))
+  [||\c -> not $$(ofChars cs [||c||])||])
+
+ofChars ::
+  TH.Lift tok => Eq tok =>
+  {-alternatives-}[tok] ->
+  {-input-}TH.CodeQ tok ->
+  TH.CodeQ Bool
+ofChars = List.foldr (\tok acc ->
+  \inp -> [|| tok == $$inp || $$(acc inp) ||])
+  (const [||False||])
+
+more ::
+  CombAlternable repr =>
+  CombApplicable repr =>
+  CombSatisfiable Char repr =>
+  CombLookable repr => repr ()
+more = look (void (item @Char))
+
+token ::
+  TH.Lift tok => Show tok => Eq tok => Typeable tok =>
+  CombAlternable repr =>
+  CombApplicable repr =>
+  CombSatisfiable tok repr =>
+  tok -> repr tok
+token tok = satisfy (Prod.equal Prod..@ Prod.constant tok) $> Prod.constant tok
+-- token tok = satisfy [ExceptionToken tok] (Prod.eq Prod..@ Prod.qual Prod..@ Prod.char tok) $> Prod.char tok
+
+tokens ::
+  TH.Lift tok => Eq tok => Show tok => Typeable tok =>
+  CombApplicable repr => CombAlternable repr =>
+  CombSatisfiable tok repr => [tok] -> repr [tok]
+tokens = try . traverse token
+
+-- * Class 'CombSelectable'
+class CombSelectable repr where
+  branch :: repr (Either a b) -> repr (a -> c) -> repr (b -> c) -> repr c
+  default branch ::
+    Sym.Liftable3 repr => CombSelectable (Sym.Output repr) =>
+    repr (Either a b) -> repr (a -> c) -> repr (b -> c) -> repr c
+  branch = Sym.lift3 branch
+data instance Failure CombSelectable
+
+-- * Class 'CombLookable'
+class CombLookable repr where
+  look :: repr a -> repr a
+  negLook :: repr a -> repr ()
+  default look :: Sym.Liftable1 repr => CombLookable (Sym.Output repr) => repr a -> repr a
+  default negLook :: Sym.Liftable1 repr => CombLookable (Sym.Output repr) => repr a -> repr ()
+  look = Sym.lift1 look
+  negLook = Sym.lift1 negLook
+
+  eof :: repr ()
+  eof = Sym.lift eof
+  default eof :: Sym.Liftable repr => CombLookable (Sym.Output repr) => repr ()
+  -- eof = negLook (satisfy @Char (Prod.const Prod..@ Prod.bool True))
+             -- (item @Char)
+data instance Failure CombLookable
+  = FailureEnd
+  deriving (Eq, Show, Typeable, TH.Lift, Generic, NFData)
+
+-- Composite Combinators
+-- someTill :: repr a -> repr b -> repr [a]
+-- someTill p end = negLook end *> (p <:> manyTill p end)
+
+{-
+constp :: CombApplicable repr => repr a -> repr (b -> a)
+constp = (Prod.const <$>)
+
+
+-- Alias Operations
+infixl 1 >>
+(>>) :: CombApplicable repr => repr a -> repr b -> repr b
+(>>) = (*>)
+
+-- Monoidal Operations
+
+infixl 4 <~>
+(<~>) :: CombApplicable repr => repr a -> repr b -> repr (a, b)
+(<~>) = liftA2 (Prod.runtime (,))
+
+infixl 4 <~
+(<~) :: CombApplicable repr => repr a -> repr b -> repr a
+(<~) = (<*)
+
+infixl 4 ~>
+(~>) :: CombApplicable repr => repr a -> repr b -> repr b
+(~>) = (*>)
+
+-- Lift Operations
+liftA2 ::
+ CombApplicable repr =>
+ Production (a -> b -> c) -> repr a -> repr b -> repr c
+liftA2 f x = (<*>) (fmap f x)
+
+liftA3 ::
+ CombApplicable repr =>
+ Production (a -> b -> c -> d) -> repr a -> repr b -> repr c -> repr d
+liftA3 f a b c = liftA2 f a b <*> c
+
+-}
+
+{-
+-- Combinators interpreters for 'Sym.Any'.
+instance CombApplicable repr => CombApplicable (Sym.Any repr)
+instance CombSatisfiable repr => CombSatisfiable (Sym.Any repr)
+instance CombAlternable repr => CombAlternable (Sym.Any repr)
+instance CombSelectable repr => CombSelectable (Sym.Any repr)
+instance CombMatchable repr => CombMatchable (Sym.Any repr)
+instance CombLookable repr => CombLookable (Sym.Any repr)
+instance CombFoldable repr => CombFoldable (Sym.Any repr)
 -}