ptera-0.1.0.0: src/Language/Parser/Ptera/Runner/RunT.hs
{-# LANGUAGE CPP #-}
module Language.Parser.Ptera.Runner.RunT (
T,
RunT (..),
runT,
ParseResult (..),
Context (..),
initialContext,
Position (..),
) where
import Language.Parser.Ptera.Prelude
import qualified Data.IntMap.Strict as IntMap
import qualified Language.Parser.Ptera.Data.Alignable as Alignable
import qualified Language.Parser.Ptera.Data.Alignable.Map as AlignableMap
import qualified Language.Parser.Ptera.Machine.PEG as PEG
import qualified Language.Parser.Ptera.Runner.Parser as Parser
import qualified Language.Parser.Ptera.Scanner as Scanner
import qualified Language.Parser.Ptera.Syntax as Syntax
import qualified Unsafe.Coerce as Unsafe
#define DEBUG 0
type T = RunT
newtype RunT ctx posMark elem altHelp m a = RunT
{
unRunT :: StateT (Context ctx posMark elem altHelp) m a
}
deriving Functor
deriving (
Applicative,
Monad
) via (StateT (Context ctx posMark elem altHelp) m)
instance MonadTrans (RunT ctx posMark elem altHelp) where
lift mx = RunT do lift mx
runT :: forall ctx posMark elem altHelp m a. Scanner.T posMark elem m
=> RunT ctx posMark elem altHelp m (ParseResult posMark altHelp a)
runT = go where
go = do
(tok, _) <- consumeIfNeeded
sn <- getCtx ctxState
if sn < 0
then goResult tok
else transByInput tok >>= \case
ContParse ->
go
CantContParse ->
goFailed
goResult
:: Parser.TokenNum
-> RunT ctx posMark elem altHelp m (ParseResult posMark altHelp a)
goResult tok = getCtx ctxItemStack >>= \case
[ItemArgument (Parser.ReduceArgument x)] ->
pure do Parsed do Unsafe.unsafeCoerce x
_ -> do
if tok >= 0
then reportError FailedByEarlyParsed
else reportError FailedByNotEnoughInput
goFailed
goFailed :: RunT ctx posMark elem altHelp m (ParseResult posMark altHelp a)
goFailed = getCtx ctxDeepestError >>= \case
Just (_, posMark0, failedReason) ->
pure do ParseFailed posMark0 failedReason
Nothing ->
error "unreachable: any errors are available."
data ParseResult posMark altHelp a
= Parsed a
| ParseFailed posMark (FailedReason altHelp)
deriving (Show, Functor)
data FailedReason altHelp
= FailedWithHelp [(StringLit, Maybe altHelp, Maybe Int)]
| FailedToStart
| FailedByEarlyParsed
| FailedByNotEnoughInput
deriving (Show, Functor)
data Context ctx posMark elem altHelp = Context
{ ctxParser :: Parser.T ctx elem altHelp
, ctxState :: Parser.StateNum
, ctxItemStack :: [Item posMark ctx]
, ctxLookAHeadToken :: Maybe (Position, posMark, Parser.TokenNum, Maybe elem)
, ctxNextPosition :: Position
, ctxDeepestError :: Maybe (Position, posMark, FailedReason altHelp)
, ctxMemoTable :: AlignableMap.T Position (IntMap.IntMap (MemoItem posMark))
, ctxNeedBackItemsCount :: Int
, ctxCustomContext :: ctx
}
newtype Position = Position Int
deriving (Eq, Ord, Show)
deriving Alignable.T via Alignable.Inst
data MemoItem posMark
= MemoItemParsed Position posMark Parser.ReduceArgument
| MemoItemFailed
data Item posMark ctx
= ItemEnter Position (Maybe posMark) Parser.VarNum Parser.StateNum
| ItemHandleNot Parser.AltNum
| ItemBackpoint Position posMark Parser.StateNum
| ItemArgument Parser.ReduceArgument
| ItemModifyCustomContext ctx
data RunningResult
= ContParse
| CantContParse
deriving (Eq, Show)
initialContext
:: Parser.T ctx elem altHelp -> ctx -> Parser.StartNum
-> Maybe (Context ctx posMark elem altHelp)
initialContext parser ctx0 s0 = do
sn0 <- Parser.parserInitial parser s0
pure do
Context
{ ctxParser = parser
, ctxState = sn0
, ctxLookAHeadToken = Nothing
, ctxItemStack = []
, ctxNextPosition = Alignable.initialAlign
, ctxMemoTable = AlignableMap.empty
, ctxNeedBackItemsCount = 0
, ctxCustomContext = ctx0
, ctxDeepestError = Nothing
}
transByInput :: forall ctx posMark elem altHelp m
. Scanner.T posMark elem m
=> Parser.TokenNum -> RunT ctx posMark elem altHelp m RunningResult
transByInput tok = go where
go = do
parser <- getCtx ctxParser
sn0 <- getCtx ctxState
let trans1 = Parser.parserTrans parser sn0 tok
let sn1 = Parser.transState trans1
setNextState sn1
#if DEBUG
(pos0, _) <- getCurrentPosition
itemStackShow <- prettyShowItemStack
debugTraceShow ("transByInput", sn0, pos0, tok, trans1, itemStackShow) do pure ()
#endif
case Parser.transOps trans1 of
ops@(_:_) ->
goTransOps ops
[]
| sn1 < 0 ->
parseFailWithState sn0
| otherwise ->
pure ContParse
goTransOps :: [Parser.TransOp]
-> RunT ctx posMark elem altHelp m RunningResult
goTransOps = \case
[] ->
pure ContParse
op:ops -> do
result <- runTransOp op
case result of
ContParse ->
goTransOps ops
CantContParse ->
pure CantContParse
#if DEBUG
prettyShowItemStack :: Monad m => RunT ctx posMark elem altHelp m [StringLit]
prettyShowItemStack = do
itemStack <- getCtx ctxItemStack
pure [ showItem item | item <- itemStack ]
where
showItem = \case
ItemEnter p _ v s ->
"ItemEnter " <> show (p, v, s)
ItemHandleNot alt ->
"ItemHandleNot " <> show alt
ItemBackpoint p _ s ->
"ItemBackpoint " <> show (p, s)
ItemArgument _ ->
"ItemArgument"
ItemModifyCustomContext _ ->
"ItemModifyCustomContext"
#endif
runTransOp :: Scanner.T posMark elem m
=> Parser.TransOp -> RunT ctx posMark elem altHelp m RunningResult
runTransOp = \case
Parser.TransOpEnter v needBack enterSn ->
runEnter v needBack enterSn
Parser.TransOpPushBackpoint backSn -> do
(pos, mark) <- getCurrentPosition
pushItem do ItemBackpoint pos mark backSn
pure ContParse
Parser.TransOpHandleNot alt -> do
pushItem do ItemHandleNot alt
pure ContParse
Parser.TransOpShift -> consumeIfNeeded >>= \case
(_, Nothing) ->
parseFail do Just FailedByNotEnoughInput
(_, Just x) -> do
pushItem do ItemArgument do Parser.ReduceArgument x
shift
pure ContParse
Parser.TransOpReduce alt ->
runReduce alt
runEnter :: Scanner.T posMark elem m
=> Parser.VarNum -> Bool -> Parser.StateNum
-> RunT ctx posMark elem altHelp m RunningResult
runEnter v needBack enterSn = do
(pos0, mark0) <- getCurrentPosition
memoTable <- getCtx ctxMemoTable
let vm = case AlignableMap.lookup pos0 memoTable of
Nothing -> IntMap.empty
Just m -> m
case IntMap.lookup v vm of
Nothing -> do
let mmark0 = if needBack
then Just mark0
else Nothing
pushItem do ItemEnter pos0 mmark0 v enterSn
pure ContParse
Just memoItem -> case memoItem of
MemoItemParsed pos1 mark1 x -> do
#if DEBUG
debugTraceShow ("runEnter / MemoItemParsed", v, enterSn, pos1) do pure ()
#endif
setNextState enterSn
pushItem do ItemArgument x
seekToMark pos1 mark1
pure ContParse
MemoItemFailed -> do
#if DEBUG
debugTraceShow ("runEnter / MemoItemFailed", v, enterSn) do pure ()
#endif
parseFail Nothing
#if DEBUG
debugShowHelpAlt :: Monad m
=> StringLit -> Parser.AltNum -> RunT ctx posMark elem altHelp m ()
debugShowHelpAlt msg alt = do
parser <- getCtx ctxParser
let (dv, _) = Parser.parserAltHelp parser alt
debugTraceShow (msg, alt, dv) do pure ()
#endif
runReduce :: forall ctx posMark elem altHelp m
. Scanner.T posMark elem m
=> Parser.AltNum -> RunT ctx posMark elem altHelp m RunningResult
runReduce alt = go0 where
go0 = do
#if DEBUG
debugShowHelpAlt "runReduce" alt
#endif
capturedCtxForFail <- captureCtx
go capturedCtxForFail Nothing []
go capturedCtxForFail mrollbackCustomCtx0 args = popItem >>= \case
Nothing ->
pure CantContParse
Just item -> case item of
ItemArgument x -> do
go capturedCtxForFail mrollbackCustomCtx0 do x:args
ItemModifyCustomContext customCtx ->
go capturedCtxForFail
do Just customCtx
do args
ItemBackpoint{} -> do
go capturedCtxForFail mrollbackCustomCtx0 args
ItemHandleNot{} -> do
forM_ mrollbackCustomCtx0 \customCtx -> updateCustomContext customCtx
parseFailWithAlt alt
ItemEnter pos mmark v enterSn ->
goEnter capturedCtxForFail mrollbackCustomCtx0 args pos mmark v enterSn
goEnter capturedCtxForFail mrollbackCustomCtx args pos0 mmark0 v enterSn = do
parser <- getCtx ctxParser
case Parser.parserAltKind parser alt of
PEG.AltSeq -> runActionAndSaveEnterResult v pos0 mrollbackCustomCtx alt args >>= \case
False -> do
restoreCtx capturedCtxForFail
parseFailWithAlt alt
True -> do
setNextState enterSn
pure ContParse
PEG.AltAnd -> runActionAndSaveEnterResult v pos0 mrollbackCustomCtx alt args >>= \case
False -> do
restoreCtx capturedCtxForFail
parseFailWithAlt alt
True -> do
let mark0 = case mmark0 of
Nothing ->
error "unreachable: no mark with and alternative"
Just x ->
x
seekToMark pos0 mark0
setNextState enterSn
pure ContParse
PEG.AltNot ->
pure CantContParse
parseFailWithAlt :: forall ctx posMark elem altHelp m
. Scanner.T posMark elem m
=> Parser.AltNum -> RunT ctx posMark elem altHelp m RunningResult
parseFailWithAlt alt = do
parser <- getCtx ctxParser
let (varHelp, altHelp) = Parser.parserAltHelp parser alt
parseFail do Just do FailedWithHelp [(varHelp, altHelp, Nothing)]
parseFailWithState :: forall ctx posMark elem altHelp m
. Scanner.T posMark elem m
=> Parser.StateNum -> RunT ctx posMark elem altHelp m RunningResult
parseFailWithState sn = do
parser <- getCtx ctxParser
let altItems = Parser.parserStateHelp parser sn
let helps =
[
( varHelp
, altHelp
, Just pos
)
| (alt, pos) <- altItems
, let (varHelp, altHelp) = Parser.parserAltHelp parser alt
]
parseFail do Just do FailedWithHelp helps
parseFail :: forall ctx posMark elem altHelp m
. Scanner.T posMark elem m
=> Maybe (FailedReason altHelp) -> RunT ctx posMark elem altHelp m RunningResult
parseFail = go0 where
go0 :: Maybe (FailedReason altHelp) -> RunT ctx posMark elem altHelp m RunningResult
go0 mayFailedReason = do
#if DEBUG
debugTraceShow ("parseFail", fmap (const ()) <$> mayFailedReason) do pure ()
#endif
case mayFailedReason of
Nothing ->
pure ()
Just failedReason -> do
reportError failedReason
go Nothing
go :: Maybe ctx -> RunT ctx posMark elem altHelp m RunningResult
go mrollbackCustomCtx0 = popItem >>= \case
Nothing ->
pure CantContParse
Just item -> case item of
ItemBackpoint pos p backSn -> do
forM_ mrollbackCustomCtx0 \customCtx -> updateCustomContext customCtx
setNextState backSn
seekToMark pos p
pure ContParse
ItemHandleNot alt -> do
forM_ mrollbackCustomCtx0 \customCtx -> updateCustomContext customCtx
capturedCtxForFail <- captureCtx
goHandleNot capturedCtxForFail Nothing alt
ItemModifyCustomContext customCtx ->
go do Just customCtx
ItemArgument{} ->
go mrollbackCustomCtx0
ItemEnter pos0 _ v _ -> do
saveFailedEnterAction v pos0
go mrollbackCustomCtx0
goHandleNot capturedCtxForFail mrollbackCustomCtx0 alt = popItem >>= \case
Nothing ->
pure CantContParse
Just item -> case item of
ItemEnter pos0 mmark0 v enterSn ->
goEnter capturedCtxForFail mrollbackCustomCtx0 alt pos0 mmark0 v enterSn
ItemArgument{} ->
goHandleNot capturedCtxForFail mrollbackCustomCtx0 alt
ItemBackpoint{} ->
goHandleNot capturedCtxForFail mrollbackCustomCtx0 alt
ItemHandleNot{} ->
pure CantContParse
ItemModifyCustomContext customCtx ->
goHandleNot capturedCtxForFail
do Just customCtx
do alt
goEnter
:: Context ctx posMark elem altHelp -> Maybe ctx
-> Parser.AltNum -> Position -> Maybe posMark -> Parser.VarNum -> Parser.StateNum
-> RunT ctx posMark elem altHelp m RunningResult
goEnter capturedCtxForFail mrollbackCustomCtx alt pos0 mmark0 v enterSn = do
parser <- getCtx ctxParser
case Parser.parserAltKind parser alt of
PEG.AltSeq ->
error "unreachable: a not handling with seq alternative"
PEG.AltAnd ->
error "unreachable: a not handling with and alternative"
PEG.AltNot -> runActionAndSaveEnterResult v pos0 mrollbackCustomCtx alt [] >>= \case
False -> do
restoreCtx capturedCtxForFail
parseFailWithAlt alt
True -> do
let mark0 = case mmark0 of
Nothing ->
error "unreachable: no mark with not alternative"
Just x ->
x
seekToMark pos0 mark0
setNextState enterSn
pure ContParse
runActionAndSaveEnterResult
:: Scanner.T posMark elem m
=> Parser.VarNum -> Position
-> Maybe ctx -> Parser.AltNum -> [Parser.ReduceArgument]
-> RunT ctx posMark elem altHelp m Bool
runActionAndSaveEnterResult v pos0 mrollbackCustomCtx alt args =
runAction alt args >>= \case
Syntax.ActionTaskFail ->
pure False
Syntax.ActionTaskResult res -> do
saveParsedEnterAction v pos0 mrollbackCustomCtx Nothing res
pure True
Syntax.ActionTaskModifyResult ctx1 res -> do
saveParsedEnterAction v pos0 mrollbackCustomCtx (Just ctx1) res
pure True
runAction :: Scanner.T posMark elem m
=> Parser.AltNum -> [Parser.ReduceArgument]
-> RunT ctx posMark elem altHelp m (Syntax.ActionTaskResult ctx Parser.ReduceArgument)
runAction alt args = do
parser <- getCtx ctxParser
ctx0 <- getCtx ctxCustomContext
let actionTask = Parser.runActionM
do Parser.parserAction parser alt
do args
pure do Syntax.runActionTask actionTask ctx0
saveParsedEnterAction
:: Scanner.T posMark elem m
=> Parser.VarNum -> Position -> Maybe ctx -> Maybe ctx -> Parser.ReduceArgument
-> RunT ctx posMark elem altHelp m ()
saveParsedEnterAction v pos0 mrollbackCustomCtx mactionCustomCtx res = do
forM_ mrollbackCustomCtx \customCtx -> do
needBack <- isNeedBack
when needBack do
pushItem do ItemModifyCustomContext customCtx
case mactionCustomCtx of
Just customCtx ->
updateCustomContext customCtx
Nothing -> insertMemoItemIfNeeded v pos0 do
(pos1, pm1) <- getCurrentPosition
pure do MemoItemParsed pos1 pm1 res
pushItem do ItemArgument res
saveFailedEnterAction
:: Monad m
=> Parser.VarNum -> Position -> RunT ctx posMark elem altHelp m ()
saveFailedEnterAction v pos = insertMemoItemIfNeeded v pos do
pure MemoItemFailed
reportError
:: Scanner.T posMark elem m
=> FailedReason altHelp -> RunT ctx posMark elem altHelp m ()
reportError failedReason = do
(pos0, posMark0) <- getCurrentPosition
RunT do
modify' \ctx -> ctx
{ ctxDeepestError = case ctxDeepestError ctx of
oldErr@(Just (pos1, _, _)) | pos0 < pos1 ->
oldErr
_ ->
Just (pos0, posMark0, failedReason)
}
insertMemoItemIfNeeded
:: Monad m
=> Parser.VarNum -> Position
-> RunT ctx posMark elem altHelp m (MemoItem posMark)
-> RunT ctx posMark elem altHelp m ()
insertMemoItemIfNeeded v pos mitem = do
needBack <- isNeedBack
when needBack do
memoItem <- mitem
RunT do
modify' \ctx -> ctx
{ ctxMemoTable = AlignableMap.insert pos
do case AlignableMap.lookup pos do ctxMemoTable ctx of
Nothing -> IntMap.singleton v memoItem
Just vm -> IntMap.insert v memoItem vm
do ctxMemoTable ctx
}
updateCustomContext :: Monad m => ctx -> RunT ctx posMark elem altHelp m ()
updateCustomContext customCtx = RunT do
modify' \ctx -> ctx
{ ctxMemoTable = AlignableMap.empty
, ctxCustomContext = customCtx
}
setNextState :: Monad m => Parser.StateNum -> RunT ctx posMark elem altHelp m ()
setNextState sn = RunT do
modify' \ctx -> ctx
{ ctxState = sn
}
getCtx :: Monad m
=> (Context ctx posMark elem altHelp -> a)
-> RunT ctx posMark elem altHelp m a
getCtx f = RunT do f <$> get
{-# INLINE getCtx #-}
captureCtx :: Monad m => RunT ctx posMark elem altHelp m (Context ctx posMark elem altHelp)
captureCtx = RunT get
restoreCtx :: Monad m => Context ctx posMark elem altHelp -> RunT ctx posMark elem altHelp m ()
restoreCtx ctx = RunT do put ctx
getCurrentPosition :: Scanner.T posMark elem m
=> RunT ctx posMark elem altHelp m (Position, posMark)
getCurrentPosition = getCtx ctxLookAHeadToken >>= \case
Just (pos, pm, _, _) ->
pure (pos, pm)
Nothing -> do
pm <- lift Scanner.getPosMark
pos <- getCtx ctxNextPosition
pure (pos, pm)
consumeIfNeeded :: Scanner.T posMark elem m
=> RunT ctx posMark elem altHelp m (Parser.TokenNum, Maybe elem)
consumeIfNeeded = getCtx ctxLookAHeadToken >>= \case
Just (_, _, tn, mt) ->
pure (tn, mt)
Nothing -> do
pm <- lift Scanner.getPosMark
r@(tn, mt) <- lift Scanner.consumeInput >>= \case
Nothing ->
pure (Parser.eosToken, Nothing)
Just t -> do
parser <- getCtx ctxParser
let tn = Parser.parserGetTokenNum parser t
pure (tn, Just t)
RunT do
modify' \ctx -> ctx
{ ctxNextPosition = Alignable.nextAlign
do ctxNextPosition ctx
, ctxLookAHeadToken = Just
(ctxNextPosition ctx, pm, tn, mt)
}
pure r
shift :: Monad m => RunT ctx posMark elem altHelp m ()
shift = getCtx ctxLookAHeadToken >>= \case
Nothing ->
error "Must consume before shift"
Just (_, _, _, Nothing) ->
error "No more shift"
Just (_, _, _, Just{}) ->
RunT do
modify' \ctx -> ctx
{
ctxLookAHeadToken = Nothing
}
seekToMark :: Scanner.T posMark elem m
=> Position -> posMark -> RunT ctx posMark elem altHelp m ()
seekToMark pos pm = do
RunT do
modify' \ctx -> ctx
{ ctxLookAHeadToken = Nothing
, ctxNextPosition = pos
}
lift do Scanner.seekToPosMark pm
isNeedBack :: Monad m => RunT ctx posMark elem altHelp m Bool
isNeedBack = do
needBackItemsCount <- getCtx ctxNeedBackItemsCount
pure do needBackItemsCount > 0
pushItem
:: Scanner.T posMark elem m
=> Item posMark ctx -> RunT ctx posMark elem altHelp m ()
pushItem item = do
(pos, p) <- getCurrentPosition
bc0 <- getCtx ctxNeedBackItemsCount
let bc1 = if isNeedBackItem item then bc0 + 1 else bc0
when do bc0 == 0 && bc1 > 0
do lift do Scanner.scanMode do Scanner.ScanModeNeedBack p
RunT do
modify' \ctx -> ctx
{ ctxItemStack = item:ctxItemStack ctx
, ctxNeedBackItemsCount = bc1
, ctxMemoTable = if bc0 == 0 && bc1 > 0
then do
AlignableMap.restrictGreaterOrEqual
do pos
do ctxMemoTable ctx
else
ctxMemoTable ctx
}
popItem
:: Scanner.T posMark elem m
=> RunT ctx posMark elem altHelp m (Maybe (Item posMark ctx))
popItem = getCtx ctxItemStack >>= \case
[] ->
pure Nothing
item:rest -> do
bc0 <- getCtx ctxNeedBackItemsCount
let bc1 = if isNeedBackItem item then bc0 - 1 else bc0
when do bc1 == 0
do lift do Scanner.scanMode Scanner.ScanModeNoBack
RunT do
modify' \ctx -> ctx
{ ctxItemStack = rest
, ctxNeedBackItemsCount = bc1
}
pure do Just item
isNeedBackItem :: Item posMark ctx -> Bool
isNeedBackItem = \case
ItemHandleNot{} ->
False
ItemBackpoint{} ->
True
ItemModifyCustomContext{} ->
False
ItemEnter _ mmark _ _ -> case mmark of
Nothing ->
False
Just{} ->
True
ItemArgument{} ->
False