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
-}