packages feed

parsley-0.1.0.1: src/ghc/Parsley/Internal/Frontend/CombinatorAnalyser.hs

{-# LANGUAGE DerivingStrategies #-}
module Parsley.Internal.Frontend.CombinatorAnalyser (analyse, compliance, Compliance(..), emptyFlags, AnalysisFlags(..)) where

--import Control.Applicative                 (liftA2)
--import Control.Monad.Reader                (ReaderT, ask, runReaderT, local)
--import Control.Monad.State.Strict          (State, get, put, evalState)
import Data.Coerce                         (coerce)
import Data.Kind                           (Type)
--import Data.Map.Strict                     (Map)
--import Data.Set                            (Set)
import Parsley.Internal.Common.Indexed     (Fix(..){-, imap, cata-}, zygo, (:*:)(..), ifst)
import Parsley.Internal.Core.CombinatorAST (Combinator(..), MetaCombinator(..))
--import Parsley.Internal.Core.Identifiers   (IMVar, MVar(..))

--import qualified Data.Map.Strict as Map
--import qualified Data.Set        as Set

newtype AnalysisFlags = AnalysisFlags {
  letBound :: Bool
}
emptyFlags :: AnalysisFlags
emptyFlags = AnalysisFlags False

analyse :: AnalysisFlags -> Fix Combinator a -> Fix Combinator a
analyse flags = cutAnalysis (letBound flags) {-. terminationAnalysis-}

data Compliance (k :: Type) = DomComp | NonComp | Comp | FullPure deriving stock (Show, Eq)

seqCompliance :: Compliance a -> Compliance b -> Compliance c
seqCompliance c FullPure = coerce c
seqCompliance FullPure c = coerce c
seqCompliance Comp _     = Comp
seqCompliance _ _        = NonComp

caseCompliance :: Compliance a -> Compliance b -> Compliance c
caseCompliance c FullPure              = coerce c
caseCompliance FullPure c              = coerce c
caseCompliance c1 c2 | c1 == coerce c2 = coerce c1
caseCompliance _ _                     = NonComp

{-# INLINE compliance #-}
compliance :: Combinator Compliance a -> Compliance a
compliance (Pure _)                 = FullPure
compliance (Satisfy _)              = NonComp
compliance Empty                    = FullPure
compliance Let{}                    = DomComp
compliance (Try _)                  = DomComp
compliance (NonComp :<|>: FullPure) = Comp
compliance (_ :<|>: _)              = NonComp
compliance (l :<*>: r)              = seqCompliance l r
compliance (l :<*: r)               = seqCompliance l r
compliance (l :*>: r)               = seqCompliance l r
compliance (LookAhead c)            = c -- Lookahead will consume input on failure, so its compliance matches that which is beneath it
compliance (NotFollowedBy _)        = FullPure
compliance (Debug _ c)              = c
compliance (ChainPre NonComp p)     = seqCompliance Comp p
compliance (ChainPre _ p)           = seqCompliance NonComp p
compliance (ChainPost p NonComp)    = seqCompliance p Comp
compliance (ChainPost p _)          = seqCompliance p NonComp
compliance (Branch b p q)           = seqCompliance b (caseCompliance p q)
compliance (Match p _ qs def)       = seqCompliance p (foldr1 caseCompliance (def:qs))
compliance (MakeRegister _ l r)     = seqCompliance l r
compliance (GetRegister _)          = FullPure
compliance (PutRegister _ c)        = coerce c
compliance (MetaCombinator _ c)     = c

newtype CutAnalysis a = CutAnalysis {doCut :: Bool -> (Fix Combinator a, Bool)}

biliftA2 :: (a -> b -> c) -> (x -> y -> z) -> (a, x) -> (b, y) -> (c, z)
biliftA2 f g (x1, y1) (x2, y2) = (f x1 x2, g y1 y2)

cutAnalysis :: Bool -> Fix Combinator a -> Fix Combinator a
cutAnalysis letBound = fst . ($ letBound) . doCut . zygo (CutAnalysis . alg) compliance
  where
    mkCut True = In . MetaCombinator Cut
    mkCut False = id

    requiresCut = In . MetaCombinator RequiresCut

    seqAlg :: (Fix Combinator a -> Fix Combinator b -> Combinator (Fix Combinator) c) -> Bool -> CutAnalysis a -> CutAnalysis b -> (Fix Combinator c, Bool)
    seqAlg con cut l r =
      let (l', handled) = doCut l cut
          (r', handled') = doCut r (cut && not handled)
      in (In (con l' r'), handled || handled')

    rewrap :: (Fix Combinator a -> Combinator (Fix Combinator) b) -> Bool -> CutAnalysis a -> (Fix Combinator b, Bool)
    rewrap con cut p = let (p', handled) = doCut p cut in (In (con p'), handled)

    alg :: Combinator (CutAnalysis :*: Compliance) a -> Bool -> (Fix Combinator a, Bool)
    alg (Pure x) _ = (In (Pure x), False)
    alg (Satisfy f) cut = (mkCut cut (In (Satisfy f)), True)
    alg Empty _ = (In Empty, False)
    alg (Let r μ p) cut = (mkCut (not cut) (In (Let r μ (fst (doCut (ifst p) True)))), False) -- If there is no cut, we generate a piggy for the continuation
    alg (Try p) _ = False <$ rewrap Try False (ifst p)
    alg ((p :*: NonComp) :<|>: (q :*: FullPure)) _ = (requiresCut (In (fst (doCut p True) :<|>: fst (doCut q False))), True)
    alg (p :<|>: q) cut =
      let (q', handled) = doCut (ifst q) cut
      in (In (fst (doCut (ifst p) False) :<|>: q'), handled)
    alg (l :<*>: r) cut = seqAlg (:<*>:) cut (ifst l) (ifst r)
    alg (l :<*: r) cut = seqAlg (:<*:) cut (ifst l) (ifst r)
    alg (l :*>: r) cut = seqAlg (:*>:) cut (ifst l) (ifst r)
    alg (LookAhead p) cut = rewrap LookAhead cut (ifst p)
    alg (NotFollowedBy p) _ = False <$ rewrap NotFollowedBy False (ifst p)
    alg (Debug msg p) cut = rewrap (Debug msg) cut (ifst p)
    alg (ChainPre (op :*: NonComp) p) _ =
      let (op', _) = doCut op True
          (p', _) = doCut (ifst p) False
      in (requiresCut (In (ChainPre op' p')), True)
    alg (ChainPre op p) cut =
      let (op', _) = doCut (ifst op) False
          (p', handled) = doCut (ifst p) cut
      in (mkCut (not cut) (In (ChainPre op' p')), handled)
    alg (ChainPost p (op :*: NonComp)) cut =
      let (p', _) = doCut (ifst p) cut
          (op', _) = doCut op True
      in (requiresCut (In (ChainPost p' op')), True)
    alg (ChainPost p op) cut =
      let (p', handled) = doCut (ifst p) cut
          (op', _) = doCut (ifst op) False
      in (mkCut (cut && handled) (In (ChainPost p' op')), handled)
    alg (Branch b p q) cut =
      let (b', handled) = doCut (ifst b) cut
          (p', handled') = doCut (ifst p) (cut && not handled)
          (q', handled'') = doCut (ifst q) (cut && not handled)
      in (In (Branch b' p' q'), handled || (handled' && handled''))
    alg (Match p f qs def) cut =
      let (p', handled) = doCut (ifst p) cut
          (def', handled') = doCut (ifst def) (cut && not handled)
          (qs', handled'') = foldr (\q -> biliftA2 (:) (&&) (doCut (ifst q) (cut && not handled))) ([], handled') qs
      in (In (Match p' f qs' def'), handled || handled'')
    alg (MakeRegister σ l r) cut = seqAlg (MakeRegister σ) cut (ifst l) (ifst r)
    alg (GetRegister σ) _ = (In (GetRegister σ), False)
    alg (PutRegister σ p) cut = rewrap (PutRegister σ) cut (ifst p)
    alg (MetaCombinator m p) cut = rewrap (MetaCombinator m) cut (ifst p)

-- Termination Analysis (Generalised left-recursion checker)
{-data Consumption = Some | None | Never
data Prop = Prop {success :: Consumption, fails :: Consumption, indisputable :: Bool} | Unknown

looping (Prop Never Never _)          = True
looping _                             = False
strongLooping (Prop Never Never True) = True
strongLooping _                       = False
neverSucceeds (Prop Never _ _)        = True
neverSucceeds _                       = False
neverFails (Prop _ Never _)           = True
neverFails _                          = False

Never ||| _     = Never
_     ||| Never = Never
Some  ||| _     = Some
None  ||| p     = p

Some  &&& _    = Some
_     &&& Some = Some
None  &&& _    = None
Never &&& p    = p

Never ^^^ _     = Never
_     ^^^ Never = Never
None  ^^^ _     = None
Some  ^^^ p     = p

(==>) :: Prop -> Prop -> Prop
p ==> _ | neverSucceeds p            = p
_ ==> Prop Never Never True          = Prop Never Never True
Prop None _ _ ==> Prop Never Never _ = Prop Never Never False
Prop s1 f1 b1 ==> Prop s2 f2 b2      = Prop (s1 ||| s2) (f1 &&& (s1 ||| f2)) (b1 && b2)

branching :: Prop -> [Prop] -> Prop
branching b ps
  | neverSucceeds b = b
  | any strongLooping ps = Prop Never Never True
branching (Prop None f _) ps
  | any looping ps = Prop Never Never False
  | otherwise      = Prop (foldr1 (|||) (map success ps)) (f &&& (foldr1 (^^^) (map fails ps))) False
branching (Prop Some f _) ps = Prop (foldr (|||) Some (map success ps)) f False

--data InferredTerm = Loops | Safe | Undecidable
newtype Termination a = Termination {runTerm :: ReaderT (Set IMVar) (State (Map IMVar Prop)) Prop}
terminationAnalysis :: Fix Combinator a -> Fix Combinator a
terminationAnalysis p = if not (looping (evalState (runReaderT (runTerm (cata (Termination . alg) p)) Set.empty) Map.empty)) then p
                        else error "Parser will loop indefinitely: either it is left-recursive or iterates over pure computations"
  where
    alg :: Combinator Termination a -> ReaderT (Set IMVar) (State (Map IMVar Prop)) Prop
    alg (Satisfy _)                          = return $! Prop Some None True
    alg (Pure _)                             = return $! Prop None Never True
    alg Empty                                = return $! Prop Never None True
    alg (Try p)                              =
      do x <- runTerm p
         return $! if looping x then x
                   else Prop (success x) None (indisputable x)
    alg (LookAhead p)                        =
      do x <- runTerm p
         return $! if looping x then x
                   else Prop None (fails x) (indisputable x)
    alg (NotFollowedBy p)                    =
      do x <- runTerm p
         return $! if looping x then x
                   else Prop None None True
    alg (p :<*>: q)                          = liftA2 (==>) (runTerm p) (runTerm q)
    alg (p :*>: q)                           = liftA2 (==>) (runTerm p) (runTerm q)
    alg (p :<*: q)                           = liftA2 (==>) (runTerm p) (runTerm q)
    alg (p :<|>: q)                          =
      do x <- runTerm p; case x of
           -- If we fail without consuming input then q governs behaviour
           Prop _ None _       -> runTerm q
           -- If p never fails then q is irrelevant
           x | neverFails x    -> return $! x
           -- If p never succeeds then q governs
           x | neverSucceeds x -> runTerm q
           Prop s1 Some i1     -> do ~(Prop s2 f i2) <- runTerm q; return $! Prop (s1 &&& s2) (Some ||| f) (i1 && i2)
    alg (Branch b p q)                       = liftA2 branching (runTerm b) (sequence [runTerm p, runTerm q])
    alg (Match p _ qs def)                   = liftA2 branching (runTerm p) (traverse runTerm (def:qs))
    alg (ChainPre op p)                      =
      do x <- runTerm op; case x of
           -- Never failing implies you must either loop or not consume input
           Prop _ Never _ -> return $! Prop Never Never True
           -- Reaching p can take a route that consumes no input, if op failed
           _ -> do y <- runTerm p
                   return $! if looping y then y
                             else y -- TODO Verify!
    alg (ChainPost p op)                     =
      do y <- runTerm op; case y of
           Prop None _ _ -> return $! Prop Never Never True
           y -> do x <- runTerm p; case (x, y) of
                     (Prop Some f _, Prop _ Never _) -> return $! Prop Some f False
                     (x, y)                          -> return $! Prop (success x) (fails x &&& fails y) False -- TODO Verify
    alg (Let True (MVar v) p)                =
      do props <- get
         seen <- ask
         case Map.lookup v props of
           Just prop -> return $! prop
           Nothing | Set.member v seen -> return $! Prop Never Never False
           Nothing -> do prop <- local (Set.insert v) (runTerm p)
                         let prop' = if looping prop then Prop Never Never True else prop
                         put (Map.insert v prop' props)
                         return $! prop'
    alg (Debug _ p)                          = runTerm p
    --alg _                                    = return $! Unknown
-}