943 lines
34 KiB
Haskell
943 lines
34 KiB
Haskell
{-# LANGUAGE DeriveFunctor #-}
|
|
{-# LANGUAGE ScopedTypeVariables #-}
|
|
{-# LANGUAGE ExistentialQuantification #-}
|
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
|
{-# LANGUAGE StandaloneDeriving #-}
|
|
{-# LANGUAGE LambdaCase #-}
|
|
{-# LANGUAGE DataKinds #-}
|
|
{-# LANGUAGE TypeOperators #-}
|
|
{-# LANGUAGE TemplateHaskell #-}
|
|
{-# LANGUAGE MonadComprehensions #-}
|
|
{-# LANGUAGE MultiWayIf #-}
|
|
|
|
module UI.Butcher.Monadic.Core
|
|
( addCmdSynopsis
|
|
, addCmdHelp
|
|
, addCmdHelpStr
|
|
, peekCmdDesc
|
|
, addCmdPart
|
|
, addCmdPartA
|
|
, addCmdPartMany
|
|
, addCmdPartManyA
|
|
, addCmdPartInp
|
|
, addCmdPartInpA
|
|
, addCmdPartManyInp
|
|
, addCmdPartManyInpA
|
|
, addCmd
|
|
, addCmdImpl
|
|
, reorderStart
|
|
, reorderStop
|
|
, cmdCheckParser
|
|
, cmdRunParser
|
|
, cmdRunParserExt
|
|
, cmdRunParserA
|
|
, cmdRunParserAExt
|
|
)
|
|
where
|
|
|
|
|
|
|
|
#include "prelude.inc"
|
|
import Control.Monad.Free
|
|
import qualified Control.Monad.Trans.MultiRWS.Strict as MultiRWSS
|
|
import qualified Control.Monad.Trans.MultiState.Strict as MultiStateS
|
|
|
|
import qualified Lens.Micro as Lens
|
|
import Lens.Micro ( (%~), (.~) )
|
|
|
|
import qualified Text.PrettyPrint as PP
|
|
import Text.PrettyPrint ( (<+>), ($$), ($+$) )
|
|
|
|
import Data.HList.ContainsType
|
|
|
|
import Data.Dynamic
|
|
|
|
import UI.Butcher.Monadic.Types
|
|
|
|
|
|
|
|
-- general-purpose helpers
|
|
----------------------------
|
|
|
|
mModify :: MonadMultiState s m => (s -> s) -> m ()
|
|
mModify f = mGet >>= mSet . f
|
|
|
|
-- sadly, you need a degree in type inference to know when we can use
|
|
-- these operators and when it must be avoided due to type ambiguities
|
|
-- arising around s in the signatures below. That's the price of not having
|
|
-- the functional dependency in MonadMulti*T.
|
|
|
|
(.=+) :: MonadMultiState s m
|
|
=> Lens.ASetter s s a b -> b -> m ()
|
|
l .=+ b = mModify $ l .~ b
|
|
|
|
(%=+) :: MonadMultiState s m
|
|
=> Lens.ASetter s s a b -> (a -> b) -> m ()
|
|
l %=+ f = mModify (l %~ f)
|
|
|
|
-- inflateStateProxy :: (Monad m, ContainsType s ss)
|
|
-- => p s -> StateS.StateT s m a -> MultiRWSS.MultiRWST r w ss m a
|
|
-- inflateStateProxy _ = MultiRWSS.inflateState
|
|
|
|
-- more on-topic stuff
|
|
----------------------------
|
|
|
|
-- instance IsHelpBuilder (CmdBuilder out) where
|
|
-- help s = liftF $ CmdBuilderHelp s ()
|
|
--
|
|
-- instance IsHelpBuilder (ParamBuilder p) where
|
|
-- help s = liftF $ ParamBuilderHelp s ()
|
|
--
|
|
-- instance IsHelpBuilder FlagBuilder where
|
|
-- help s = liftF $ FlagBuilderHelp s ()
|
|
|
|
addCmdSynopsis :: String -> CmdParser f out ()
|
|
addCmdSynopsis s = liftF $ CmdParserSynopsis s ()
|
|
|
|
addCmdHelp :: PP.Doc -> CmdParser f out ()
|
|
addCmdHelp s = liftF $ CmdParserHelp s ()
|
|
|
|
addCmdHelpStr :: String -> CmdParser f out ()
|
|
addCmdHelpStr s = liftF $ CmdParserHelp (PP.text s) ()
|
|
|
|
peekCmdDesc :: CmdParser f out (CommandDesc out)
|
|
peekCmdDesc = liftF $ CmdParserPeekDesc id
|
|
|
|
addCmdPart
|
|
:: (Applicative f, Typeable p)
|
|
=> PartDesc
|
|
-> (String -> Maybe (p, String))
|
|
-> CmdParser f out p
|
|
addCmdPart p f = liftF $ CmdParserPart p f (\_ -> pure ()) id
|
|
|
|
addCmdPartA
|
|
:: (Typeable p)
|
|
=> PartDesc
|
|
-> (String -> Maybe (p, String))
|
|
-> (p -> f ())
|
|
-> CmdParser f out p
|
|
addCmdPartA p f a = liftF $ CmdParserPart p f a id
|
|
|
|
addCmdPartMany
|
|
:: (Applicative f, Typeable p)
|
|
=> PartDesc
|
|
-> (String -> Maybe (p, String))
|
|
-> CmdParser f out [p]
|
|
addCmdPartMany p f = liftF $ CmdParserPartMany p f (\_ -> pure ()) id
|
|
|
|
addCmdPartManyA
|
|
:: (Typeable p)
|
|
=> PartDesc
|
|
-> (String -> Maybe (p, String))
|
|
-> (p -> f ())
|
|
-> CmdParser f out [p]
|
|
addCmdPartManyA p f a = liftF $ CmdParserPartMany p f a id
|
|
|
|
addCmdPartInp
|
|
:: (Applicative f, Typeable p)
|
|
=> PartDesc
|
|
-> (Input -> Maybe (p, Input))
|
|
-> CmdParser f out p
|
|
addCmdPartInp p f = liftF $ CmdParserPartInp p f (\_ -> pure ()) id
|
|
|
|
addCmdPartInpA
|
|
:: (Typeable p)
|
|
=> PartDesc
|
|
-> (Input -> Maybe (p, Input))
|
|
-> (p -> f ())
|
|
-> CmdParser f out p
|
|
addCmdPartInpA p f a = liftF $ CmdParserPartInp p f a id
|
|
|
|
addCmdPartManyInp
|
|
:: (Applicative f, Typeable p)
|
|
=> PartDesc
|
|
-> (Input -> Maybe (p, Input))
|
|
-> CmdParser f out [p]
|
|
addCmdPartManyInp p f = liftF $ CmdParserPartManyInp p f (\_ -> pure ()) id
|
|
|
|
addCmdPartManyInpA
|
|
:: (Typeable p)
|
|
=> PartDesc
|
|
-> (Input -> Maybe (p, Input))
|
|
-> (p -> f ())
|
|
-> CmdParser f out [p]
|
|
addCmdPartManyInpA p f a = liftF $ CmdParserPartManyInp p f a id
|
|
|
|
addCmd
|
|
:: Applicative f
|
|
=> String
|
|
-> CmdParser f out ()
|
|
-> CmdParser f out ()
|
|
addCmd str sub = liftF $ CmdParserChild str sub (pure ()) ()
|
|
|
|
addCmdImpl :: out -> CmdParser f out ()
|
|
addCmdImpl o = liftF $ CmdParserImpl o ()
|
|
|
|
reorderStart :: CmdParser f out ()
|
|
reorderStart = liftF $ CmdParserReorderStart ()
|
|
|
|
reorderStop :: CmdParser f out ()
|
|
reorderStop = liftF $ CmdParserReorderStop ()
|
|
|
|
-- addPartHelp :: String -> CmdPartParser ()
|
|
-- addPartHelp s = liftF $ CmdPartParserHelp s ()
|
|
--
|
|
-- addPartParserBasic :: (String -> Maybe (p, String)) -> Maybe p -> CmdPartParser p
|
|
-- addPartParserBasic f def = liftF $ CmdPartParserCore f def id
|
|
--
|
|
-- addPartParserOptionalBasic :: CmdPartParser p -> CmdPartParser (Maybe p)
|
|
-- addPartParserOptionalBasic p = liftF $ CmdPartParserOptional p id
|
|
|
|
data PartGatherData f
|
|
= forall p . Typeable p => PartGatherData
|
|
{ _pgd_id :: Int
|
|
, _pgd_desc :: PartDesc
|
|
, _pgd_parseF :: Either (String -> Maybe (p, String))
|
|
(Input -> Maybe (p, Input))
|
|
, _pgd_act :: p -> f ()
|
|
, _pgd_many :: Bool
|
|
}
|
|
|
|
type PartParsedData = Map Int [Dynamic]
|
|
|
|
data CmdDescStack = StackBottom [PartDesc]
|
|
| StackLayer [PartDesc] String CmdDescStack
|
|
|
|
descStackAdd :: PartDesc -> CmdDescStack -> CmdDescStack
|
|
descStackAdd d = \case
|
|
StackBottom l -> StackBottom $ d:l
|
|
StackLayer l s u -> StackLayer (d:l) s u
|
|
|
|
|
|
cmdCheckParser :: forall f out
|
|
. Maybe String -- top-level command name
|
|
-> CmdParser f out ()
|
|
-> Either String (CommandDesc ())
|
|
cmdCheckParser mTopLevel cmdParser
|
|
= (>>= final)
|
|
$ MultiRWSS.runMultiRWSTNil
|
|
$ MultiRWSS.withMultiStateAS (StackBottom [])
|
|
$ MultiRWSS.withMultiStateS emptyCommandDesc
|
|
$ processMain cmdParser
|
|
where
|
|
final :: (CommandDesc out, CmdDescStack)
|
|
-> Either String (CommandDesc ())
|
|
final (desc, stack)
|
|
= case stack of
|
|
StackBottom descs -> Right
|
|
$ descFixParentsWithTopM (mTopLevel <&> \n -> (n, emptyCommandDesc))
|
|
$ () <$ desc
|
|
{ _cmd_parts = reverse descs
|
|
, _cmd_children = reverse $ _cmd_children desc
|
|
}
|
|
StackLayer _ _ _ -> Left "unclosed ReorderStart or GroupStart"
|
|
processMain :: CmdParser f out ()
|
|
-> MultiRWSS.MultiRWST '[] '[] '[CommandDesc out, CmdDescStack] (Either String) ()
|
|
processMain = \case
|
|
Pure x -> return x
|
|
Free (CmdParserHelp h next) -> do
|
|
cmd :: CommandDesc out <- mGet
|
|
mSet $ cmd { _cmd_help = Just h }
|
|
processMain next
|
|
Free (CmdParserSynopsis s next) -> do
|
|
cmd :: CommandDesc out <- mGet
|
|
mSet $ cmd { _cmd_synopsis = Just $ PP.text s }
|
|
processMain next
|
|
Free (CmdParserPeekDesc nextF) -> do
|
|
processMain $ nextF monadMisuseError
|
|
Free (CmdParserPart desc _parseF _act nextF) -> do
|
|
do
|
|
descStack <- mGet
|
|
mSet $ descStackAdd desc descStack
|
|
processMain $ nextF monadMisuseError
|
|
Free (CmdParserPartInp desc _parseF _act nextF) -> do
|
|
do
|
|
descStack <- mGet
|
|
mSet $ descStackAdd desc descStack
|
|
processMain $ nextF monadMisuseError
|
|
Free (CmdParserPartMany desc _parseF _act nextF) -> do
|
|
do
|
|
descStack <- mGet
|
|
mSet $ descStackAdd (PartMany desc) descStack
|
|
processMain $ nextF monadMisuseError
|
|
Free (CmdParserPartManyInp desc _parseF _act nextF) -> do
|
|
do
|
|
descStack <- mGet
|
|
mSet $ descStackAdd (PartMany desc) descStack
|
|
processMain $ nextF monadMisuseError
|
|
Free (CmdParserChild cmdStr sub _act next) -> do
|
|
cmd :: CommandDesc out <- mGet
|
|
subCmd <- do
|
|
stackCur :: CmdDescStack <- mGet
|
|
mSet (emptyCommandDesc :: CommandDesc out)
|
|
mSet $ StackBottom []
|
|
processMain sub
|
|
c <- mGet
|
|
stackBelow <- mGet
|
|
mSet cmd
|
|
mSet stackCur
|
|
subParts <- case stackBelow of
|
|
StackBottom descs -> return $ reverse descs
|
|
StackLayer _ _ _ -> lift $ Left "unclosed ReorderStart or GroupStart"
|
|
return c
|
|
{ _cmd_children = reverse $ _cmd_children c
|
|
, _cmd_parts = subParts
|
|
}
|
|
mSet $ cmd
|
|
{ _cmd_children = (cmdStr, subCmd) : _cmd_children cmd
|
|
}
|
|
processMain next
|
|
Free (CmdParserImpl out next) -> do
|
|
cmd_out .=+ Just out
|
|
processMain $ next
|
|
Free (CmdParserGrouped groupName next) -> do
|
|
stackCur <- mGet
|
|
mSet $ StackLayer [] groupName stackCur
|
|
processMain $ next
|
|
Free (CmdParserGroupEnd next) -> do
|
|
stackCur <- mGet
|
|
case stackCur of
|
|
StackBottom{} -> do
|
|
lift $ Left $ "butcher interface error: group end without group start"
|
|
StackLayer _descs "" _up -> do
|
|
lift $ Left $ "GroupEnd found, but expected ReorderStop first"
|
|
StackLayer descs groupName up -> do
|
|
mSet $ descStackAdd (PartRedirect groupName (PartSeq (reverse descs))) up
|
|
processMain $ next
|
|
Free (CmdParserReorderStop next) -> do
|
|
stackCur <- mGet
|
|
case stackCur of
|
|
StackBottom{} -> lift $ Left $ "ReorderStop without reorderStart"
|
|
StackLayer descs "" up -> do
|
|
mSet $ descStackAdd (PartReorder (reverse descs)) up
|
|
StackLayer{} -> lift $ Left $ "Found ReorderStop, but need GroupEnd first"
|
|
processMain next
|
|
Free (CmdParserReorderStart next) -> do
|
|
stackCur <- mGet
|
|
mSet $ StackLayer [] "" stackCur
|
|
processMain next
|
|
|
|
monadMisuseError :: a
|
|
monadMisuseError = error "CmdParser definition error - used Monad powers where only Applicative/Arrow is allowed"
|
|
|
|
|
|
newtype PastCommandInput = PastCommandInput Input
|
|
|
|
|
|
cmdRunParser
|
|
:: Maybe String
|
|
-> Input
|
|
-> CmdParser Identity out ()
|
|
-> (CommandDesc (), Either ParsingError (CommandDesc out))
|
|
cmdRunParser mTopLevel inputInitial cmdParser
|
|
= runIdentity
|
|
$ cmdRunParserA mTopLevel inputInitial cmdParser
|
|
|
|
cmdRunParserExt
|
|
:: Maybe String
|
|
-> Input
|
|
-> CmdParser Identity out ()
|
|
-> (CommandDesc (), Input, Either ParsingError (CommandDesc out))
|
|
cmdRunParserExt mTopLevel inputInitial cmdParser
|
|
= runIdentity
|
|
$ cmdRunParserAExt mTopLevel inputInitial cmdParser
|
|
|
|
cmdRunParserA :: forall f out
|
|
. Applicative f
|
|
=> Maybe String
|
|
-> Input
|
|
-> CmdParser f out ()
|
|
-> f ( CommandDesc ()
|
|
, Either ParsingError (CommandDesc out)
|
|
)
|
|
cmdRunParserA mTopLevel inputInitial cmdParser =
|
|
(\(x, _, z) -> (x, z)) <$> cmdRunParserAExt mTopLevel inputInitial cmdParser
|
|
|
|
cmdRunParserAExt
|
|
:: forall f out . Applicative f
|
|
=> Maybe String
|
|
-> Input
|
|
-> CmdParser f out ()
|
|
-> f (CommandDesc (), Input, Either ParsingError (CommandDesc out))
|
|
cmdRunParserAExt mTopLevel inputInitial cmdParser
|
|
= runIdentity
|
|
$ MultiRWSS.runMultiRWSTNil
|
|
$ (<&> captureFinal)
|
|
$ MultiRWSS.withMultiWriterWA
|
|
$ MultiRWSS.withMultiStateA cmdParser
|
|
$ MultiRWSS.withMultiStateSA (StackBottom [])
|
|
$ MultiRWSS.withMultiStateSA inputInitial
|
|
$ MultiRWSS.withMultiStateSA (PastCommandInput inputInitial)
|
|
$ MultiRWSS.withMultiStateSA initialCommandDesc
|
|
$ processMain cmdParser
|
|
where
|
|
initialCommandDesc = emptyCommandDesc
|
|
{ _cmd_mParent = mTopLevel <&> \n -> (n, emptyCommandDesc) }
|
|
captureFinal
|
|
:: ([String], (CmdDescStack, (Input, (PastCommandInput, (CommandDesc out, f())))))
|
|
-> f (CommandDesc (), Input, Either ParsingError (CommandDesc out))
|
|
captureFinal (errs, (descStack, (inputRest, (PastCommandInput pastCmdInput, (cmd, act))))) =
|
|
act $> (() <$ cmd', pastCmdInput, res)
|
|
where
|
|
errs' = errs ++ inputErrs ++ stackErrs
|
|
inputErrs = case inputRest of
|
|
InputString s | all Char.isSpace s -> []
|
|
InputString{} -> ["could not parse input/unprocessed input"]
|
|
InputArgs [] -> []
|
|
InputArgs{} -> ["could not parse input/unprocessed input"]
|
|
stackErrs = case descStack of
|
|
StackBottom{} -> []
|
|
_ -> ["butcher interface error: unclosed group"]
|
|
cmd' = postProcessCmd descStack cmd
|
|
res = if null errs'
|
|
then Right cmd'
|
|
else Left $ ParsingError errs' inputRest
|
|
processMain :: CmdParser f out ()
|
|
-> MultiRWSS.MultiRWS
|
|
'[]
|
|
'[[String]]
|
|
'[CommandDesc out, PastCommandInput, Input, CmdDescStack, CmdParser f out ()]
|
|
(f ())
|
|
processMain = \case
|
|
Pure () -> return $ pure $ ()
|
|
Free (CmdParserHelp h next) -> do
|
|
cmd :: CommandDesc out <- mGet
|
|
mSet $ cmd { _cmd_help = Just h }
|
|
processMain next
|
|
Free (CmdParserSynopsis s next) -> do
|
|
cmd :: CommandDesc out <- mGet
|
|
mSet $ cmd { _cmd_synopsis = Just $ PP.text s }
|
|
processMain next
|
|
Free (CmdParserPeekDesc nextF) -> do
|
|
parser <- mGet
|
|
-- partialDesc :: CommandDesc out <- mGet
|
|
-- partialStack :: CmdDescStack <- mGet
|
|
-- run the rest without affecting the actual stack
|
|
-- to retrieve the complete cmddesc.
|
|
cmdCur :: CommandDesc out <- mGet
|
|
let (cmd :: CommandDesc out, stack)
|
|
= runIdentity
|
|
$ MultiRWSS.runMultiRWSTNil
|
|
$ MultiRWSS.withMultiStateSA emptyCommandDesc
|
|
{ _cmd_mParent = _cmd_mParent cmdCur } -- partialDesc
|
|
$ MultiRWSS.withMultiStateS (StackBottom []) -- partialStack
|
|
$ iterM processCmdShallow $ parser
|
|
processMain $ nextF $ postProcessCmd stack cmd
|
|
Free (CmdParserPart desc parseF actF nextF) -> do
|
|
do
|
|
descStack <- mGet
|
|
mSet $ descStackAdd desc descStack
|
|
input <- mGet
|
|
case input of
|
|
InputString str -> case parseF str of
|
|
Just (x, rest) -> do
|
|
mSet $ InputString rest
|
|
actRest <- processMain $ nextF x
|
|
return $ actF x *> actRest
|
|
Nothing -> do
|
|
mTell ["could not parse " ++ getPartSeqDescPositionName desc]
|
|
processMain $ nextF monadMisuseError
|
|
InputArgs (str:strr) -> case parseF str of
|
|
Just (x, "") -> do
|
|
mSet $ InputArgs strr
|
|
actRest <- processMain $ nextF x
|
|
return $ actF x *> actRest
|
|
_ -> do
|
|
mTell ["could not parse " ++ getPartSeqDescPositionName desc]
|
|
processMain $ nextF monadMisuseError
|
|
InputArgs [] -> do
|
|
mTell ["could not parse " ++ getPartSeqDescPositionName desc]
|
|
processMain $ nextF monadMisuseError
|
|
Free (CmdParserPartInp desc parseF actF nextF) -> do
|
|
do
|
|
descStack <- mGet
|
|
mSet $ descStackAdd desc descStack
|
|
input <- mGet
|
|
case parseF input of
|
|
Just (x, rest) -> do
|
|
mSet $ rest
|
|
actRest <- processMain $ nextF x
|
|
return $ actF x *> actRest
|
|
Nothing -> do
|
|
mTell ["could not parse " ++ getPartSeqDescPositionName desc]
|
|
processMain $ nextF monadMisuseError
|
|
Free (CmdParserPartMany desc parseF actF nextF) -> do
|
|
do
|
|
descStack <- mGet
|
|
mSet $ descStackAdd desc descStack
|
|
let proc = do
|
|
dropSpaces
|
|
input <- mGet
|
|
case input of
|
|
InputString str -> case parseF str of
|
|
Just (x, r) -> do
|
|
mSet $ InputString r
|
|
xr <- proc
|
|
return $ x:xr
|
|
Nothing -> return []
|
|
InputArgs (str:strr) -> case parseF str of
|
|
Just (x, "") -> do
|
|
mSet $ InputArgs strr
|
|
xr <- proc
|
|
return $ x:xr
|
|
_ -> return []
|
|
InputArgs [] -> return []
|
|
r <- proc
|
|
let act = traverse actF r
|
|
(act *>) <$> processMain (nextF $ r)
|
|
Free (CmdParserPartManyInp desc parseF actF nextF) -> do
|
|
do
|
|
descStack <- mGet
|
|
mSet $ descStackAdd desc descStack
|
|
let proc = do
|
|
dropSpaces
|
|
input <- mGet
|
|
case parseF input of
|
|
Just (x, r) -> do
|
|
mSet $ r
|
|
xr <- proc
|
|
return $ x:xr
|
|
Nothing -> return []
|
|
r <- proc
|
|
let act = traverse actF r
|
|
(act *>) <$> processMain (nextF $ r)
|
|
f@(Free (CmdParserChild cmdStr sub act next)) -> do
|
|
dropSpaces
|
|
input <- mGet
|
|
let
|
|
mRest = case input of
|
|
InputString str | cmdStr == str ->
|
|
Just $ InputString ""
|
|
InputString str | (cmdStr++" ") `isPrefixOf` str ->
|
|
Just $ InputString $ drop (length cmdStr + 1) str
|
|
InputArgs (str:strr) | cmdStr == str ->
|
|
Just $ InputArgs strr
|
|
_ -> Nothing
|
|
case mRest of
|
|
Nothing -> do
|
|
cmd :: CommandDesc out <- mGet
|
|
let (subCmd, subStack)
|
|
= runIdentity
|
|
$ MultiRWSS.runMultiRWSTNil
|
|
$ MultiRWSS.withMultiStateSA (emptyCommandDesc :: CommandDesc out)
|
|
$ MultiRWSS.withMultiStateS (StackBottom [])
|
|
$ iterM processCmdShallow sub
|
|
mSet $ cmd
|
|
{ _cmd_children = (cmdStr, postProcessCmd subStack subCmd)
|
|
: _cmd_children cmd
|
|
}
|
|
processMain next
|
|
Just rest -> do
|
|
iterM processCmdShallow f
|
|
cmd <- do
|
|
c :: CommandDesc out <- mGet
|
|
prevStack :: CmdDescStack <- mGet
|
|
return $ postProcessCmd prevStack c
|
|
mSet $ rest
|
|
mSet $ PastCommandInput rest
|
|
mSet $ (emptyCommandDesc :: CommandDesc out)
|
|
{ _cmd_mParent = Just (cmdStr, cmd)
|
|
}
|
|
mSet $ sub
|
|
mSet $ StackBottom []
|
|
subAct <- processMain sub
|
|
return $ act *> subAct
|
|
Free (CmdParserImpl out next) -> do
|
|
cmd_out .=+ Just out
|
|
processMain $ next
|
|
Free (CmdParserGrouped groupName next) -> do
|
|
stackCur <- mGet
|
|
mSet $ StackLayer [] groupName stackCur
|
|
processMain $ next
|
|
Free (CmdParserGroupEnd next) -> do
|
|
stackCur <- mGet
|
|
case stackCur of
|
|
StackBottom{} -> do
|
|
mTell $ ["butcher interface error: group end without group start"]
|
|
return $ pure () -- hard abort should be fine for this case.
|
|
StackLayer descs groupName up -> do
|
|
mSet $ descStackAdd (PartRedirect groupName (PartSeq (reverse descs))) up
|
|
processMain $ next
|
|
Free (CmdParserReorderStop next) -> do
|
|
mTell $ ["butcher interface error: reorder stop without reorder start"]
|
|
processMain next
|
|
Free (CmdParserReorderStart next) -> do
|
|
reorderData <- MultiRWSS.withMultiStateA (1::Int)
|
|
$ MultiRWSS.withMultiWriterW
|
|
$ iterM reorderPartGather $ next
|
|
let
|
|
reorderMapInit :: Map Int (PartGatherData f)
|
|
reorderMapInit = Map.fromList $ reorderData <&> \d -> (_pgd_id d, d)
|
|
tryParsePartData :: Input -> PartGatherData f -> First (Int, Dynamic, Input, Bool, f ())
|
|
tryParsePartData input (PartGatherData pid _ pfe act allowMany) =
|
|
First [ (pid, toDyn r, rest, allowMany, act r)
|
|
| (r, rest) <- case pfe of
|
|
Left pfStr -> case input of
|
|
InputString str -> case pfStr str of
|
|
Just (x, r) | r/=str -> Just (x, InputString r)
|
|
_ -> Nothing
|
|
InputArgs (str:strr) -> case pfStr str of
|
|
Just (x, "") -> Just (x, InputArgs strr)
|
|
_ -> Nothing
|
|
InputArgs [] -> Nothing
|
|
Right pfInp -> case pfInp input of
|
|
Just (x, r) | r/=input -> Just (x, r)
|
|
_ -> Nothing
|
|
]
|
|
parseLoop = do
|
|
input <- mGet
|
|
m :: Map Int (PartGatherData f) <- mGet
|
|
case getFirst $ Data.Foldable.foldMap (tryParsePartData input) m of
|
|
-- i will be angry if foldMap ever decides to not fold
|
|
-- in order of keys.
|
|
Nothing -> return $ pure ()
|
|
Just (pid, x, rest, more, act) -> do
|
|
mSet rest
|
|
mModify $ Map.insertWith (++) pid [x]
|
|
when (not more) $ do
|
|
mSet $ Map.delete pid m
|
|
actRest <- parseLoop
|
|
return $ act *> actRest
|
|
(finalMap, (fr, acts)) <- MultiRWSS.withMultiStateSA (Map.empty :: PartParsedData)
|
|
$ MultiRWSS.withMultiStateA reorderMapInit
|
|
$ do
|
|
acts <- parseLoop -- filling the map
|
|
stackCur <- mGet
|
|
mSet $ StackLayer [] "" stackCur
|
|
fr <- MultiRWSS.withMultiStateA (1::Int) $ processParsedParts next
|
|
return (fr, acts)
|
|
-- we check that all data placed in the map has been consumed while
|
|
-- running the parts for which we collected the parseresults.
|
|
-- there can only be any rest if the collection of parts changed
|
|
-- between the reorderPartGather traversal and the processParsedParts
|
|
-- consumption.
|
|
if Map.null finalMap
|
|
then do
|
|
actRest <- processMain fr
|
|
return $ acts *> actRest
|
|
else monadMisuseError
|
|
|
|
reorderPartGather
|
|
:: ( MonadMultiState Int m
|
|
, MonadMultiWriter [PartGatherData f] m
|
|
, MonadMultiWriter [String] m
|
|
)
|
|
=> CmdParserF f out (m ())
|
|
-> m ()
|
|
reorderPartGather = \case
|
|
CmdParserPart desc parseF actF nextF -> do
|
|
pid <- mGet
|
|
mSet $ pid + 1
|
|
mTell [PartGatherData pid desc (Left parseF) actF False]
|
|
nextF $ monadMisuseError
|
|
CmdParserPartInp desc parseF actF nextF -> do
|
|
pid <- mGet
|
|
mSet $ pid + 1
|
|
mTell [PartGatherData pid desc (Right parseF) actF False]
|
|
nextF $ monadMisuseError
|
|
CmdParserPartMany desc parseF actF nextF -> do
|
|
pid <- mGet
|
|
mSet $ pid + 1
|
|
mTell [PartGatherData pid desc (Left parseF) actF True]
|
|
nextF $ monadMisuseError
|
|
CmdParserPartManyInp desc parseF actF nextF -> do
|
|
pid <- mGet
|
|
mSet $ pid + 1
|
|
mTell [PartGatherData pid desc (Right parseF) actF True]
|
|
nextF $ monadMisuseError
|
|
CmdParserReorderStop _next -> do
|
|
return ()
|
|
CmdParserHelp{} -> restCase
|
|
CmdParserSynopsis{} -> restCase
|
|
CmdParserPeekDesc{} -> restCase
|
|
CmdParserChild{} -> restCase
|
|
CmdParserImpl{} -> restCase
|
|
CmdParserReorderStart{} -> restCase
|
|
CmdParserGrouped{} -> restCase
|
|
CmdParserGroupEnd{} -> restCase
|
|
where
|
|
restCase = do
|
|
mTell ["Did not find expected ReorderStop after the reordered parts"]
|
|
return ()
|
|
|
|
processParsedParts
|
|
:: forall m r w s m0 a
|
|
. ( MonadMultiState Int m
|
|
, MonadMultiState PartParsedData m
|
|
, MonadMultiState (Map Int (PartGatherData f)) m
|
|
, MonadMultiState Input m
|
|
, MonadMultiState (CommandDesc out) m
|
|
, MonadMultiWriter [[Char]] m
|
|
, m ~ MultiRWSS.MultiRWST r w s m0
|
|
, ContainsType (CmdParser f out ()) s
|
|
, ContainsType CmdDescStack s
|
|
, Monad m0
|
|
)
|
|
=> CmdParser f out a
|
|
-> m (CmdParser f out a)
|
|
processParsedParts = \case
|
|
Free (CmdParserPart desc _ _ (nextF :: p -> CmdParser f out a)) -> part desc nextF
|
|
Free (CmdParserPartInp desc _ _ (nextF :: p -> CmdParser f out a)) -> part desc nextF
|
|
Free (CmdParserPartMany desc _ _ nextF) -> partMany desc nextF
|
|
Free (CmdParserPartManyInp desc _ _ nextF) -> partMany desc nextF
|
|
Free (CmdParserReorderStop next) -> do
|
|
stackCur <- mGet
|
|
case stackCur of
|
|
StackBottom{} -> do
|
|
mTell ["unexpected stackBottom"]
|
|
StackLayer descs _ up -> do
|
|
mSet $ descStackAdd (PartReorder (reverse descs)) up
|
|
return next
|
|
Free (CmdParserGrouped groupName next) -> do
|
|
stackCur <- mGet
|
|
mSet $ StackLayer [] groupName stackCur
|
|
processParsedParts $ next
|
|
Free (CmdParserGroupEnd next) -> do
|
|
stackCur <- mGet
|
|
case stackCur of
|
|
StackBottom{} -> do
|
|
mTell $ ["butcher interface error: group end without group start"]
|
|
return $ next -- hard abort should be fine for this case.
|
|
StackLayer descs groupName up -> do
|
|
mSet $ descStackAdd (PartRedirect groupName (PartSeq (reverse descs))) up
|
|
processParsedParts $ next
|
|
Pure x -> return $ return $ x
|
|
f -> do
|
|
mTell ["Did not find expected ReorderStop after the reordered parts"]
|
|
return f
|
|
where
|
|
part
|
|
:: forall p
|
|
. Typeable p
|
|
=> PartDesc
|
|
-> (p -> CmdParser f out a)
|
|
-> m (CmdParser f out a)
|
|
part desc nextF = do
|
|
do
|
|
stackCur <- mGet
|
|
mSet $ descStackAdd desc stackCur
|
|
pid <- mGet
|
|
mSet $ pid + 1
|
|
parsedMap :: PartParsedData <- mGet
|
|
mSet $ Map.delete pid parsedMap
|
|
partMap :: Map Int (PartGatherData f) <- mGet
|
|
input :: Input <- mGet
|
|
let errorResult = do
|
|
mTell ["could not parse expected input "
|
|
++ getPartSeqDescPositionName desc
|
|
++ " with remaining input: "
|
|
++ show input
|
|
]
|
|
failureCurrentShallowRerun
|
|
return $ return $ monadMisuseError -- so ugly.
|
|
-- should be correct nonetheless.
|
|
continueOrMisuse :: Maybe p -> m (CmdParser f out a)
|
|
continueOrMisuse = maybe monadMisuseError
|
|
(processParsedParts . nextF)
|
|
case Map.lookup pid parsedMap of
|
|
Nothing -> case Map.lookup pid partMap of
|
|
Nothing -> monadMisuseError -- it would still be in the map
|
|
-- if it never had been successfully
|
|
-- parsed, as indicicated by the
|
|
-- previous parsedMap Nothing lookup.
|
|
Just (PartGatherData _ _ pfe _ _) -> case pfe of
|
|
Left pf -> case pf "" of
|
|
Nothing -> errorResult
|
|
Just (dx, _) -> continueOrMisuse $ cast dx
|
|
Right pf -> case pf (InputArgs []) of
|
|
Nothing -> errorResult
|
|
Just (dx, _) -> continueOrMisuse $ cast dx
|
|
Just [dx] -> continueOrMisuse $ fromDynamic dx
|
|
Just _ -> monadMisuseError
|
|
partMany
|
|
:: Typeable p
|
|
=> PartDesc
|
|
-> ([p] -> CmdParser f out a)
|
|
-> m (CmdParser f out a)
|
|
partMany desc nextF = do
|
|
do
|
|
stackCur <- mGet
|
|
mSet $ descStackAdd (PartMany desc) stackCur
|
|
pid <- mGet
|
|
mSet $ pid + 1
|
|
m :: PartParsedData <- mGet
|
|
mSet $ Map.delete pid m
|
|
let partDyns = case Map.lookup pid m of
|
|
Nothing -> []
|
|
Just r -> r
|
|
case mapM fromDynamic partDyns of
|
|
Nothing -> monadMisuseError
|
|
Just xs -> processParsedParts $ nextF xs
|
|
|
|
-- this does no error reporting at all.
|
|
-- user needs to use check for that purpose instead.
|
|
processCmdShallow :: ( MonadMultiState (CommandDesc out) m
|
|
, MonadMultiState CmdDescStack m
|
|
)
|
|
=> CmdParserF f out (m ())
|
|
-> m ()
|
|
processCmdShallow = \case
|
|
CmdParserHelp h next -> do
|
|
cmd :: CommandDesc out <- mGet
|
|
mSet $ cmd { _cmd_help = Just h }
|
|
next
|
|
CmdParserSynopsis s next -> do
|
|
cmd :: CommandDesc out <- mGet
|
|
mSet $ cmd { _cmd_synopsis = Just $ PP.text s }
|
|
next
|
|
CmdParserPeekDesc nextF -> do
|
|
mGet >>= nextF
|
|
CmdParserPart desc _parseF _act nextF -> do
|
|
do
|
|
stackCur <- mGet
|
|
mSet $ descStackAdd desc stackCur
|
|
nextF monadMisuseError
|
|
CmdParserPartInp desc _parseF _act nextF -> do
|
|
do
|
|
stackCur <- mGet
|
|
mSet $ descStackAdd desc stackCur
|
|
nextF monadMisuseError
|
|
CmdParserPartMany desc _parseF _act nextF -> do
|
|
do
|
|
stackCur <- mGet
|
|
mSet $ descStackAdd (PartMany desc) stackCur
|
|
nextF monadMisuseError
|
|
CmdParserPartManyInp desc _parseF _act nextF -> do
|
|
do
|
|
stackCur <- mGet
|
|
mSet $ descStackAdd (PartMany desc) stackCur
|
|
nextF monadMisuseError
|
|
CmdParserChild cmdStr _sub _act next -> do
|
|
cmd_children %=+ ((cmdStr, emptyCommandDesc :: CommandDesc out):)
|
|
next
|
|
CmdParserImpl out next -> do
|
|
cmd_out .=+ Just out
|
|
next
|
|
CmdParserGrouped groupName next -> do
|
|
stackCur <- mGet
|
|
mSet $ StackLayer [] groupName stackCur
|
|
next
|
|
CmdParserGroupEnd next -> do
|
|
stackCur <- mGet
|
|
case stackCur of
|
|
StackBottom{} -> do
|
|
return ()
|
|
StackLayer _descs "" _up -> do
|
|
return ()
|
|
StackLayer descs groupName up -> do
|
|
mSet $ descStackAdd (PartRedirect groupName (PartSeq (reverse descs))) up
|
|
next
|
|
CmdParserReorderStop next -> do
|
|
stackCur <- mGet
|
|
case stackCur of
|
|
StackBottom{} -> return ()
|
|
StackLayer descs "" up -> do
|
|
mSet $ descStackAdd (PartReorder (reverse descs)) up
|
|
StackLayer{} -> return ()
|
|
next
|
|
CmdParserReorderStart next -> do
|
|
stackCur <- mGet
|
|
mSet $ StackLayer [] "" stackCur
|
|
next
|
|
|
|
failureCurrentShallowRerun
|
|
:: ( m ~ MultiRWSS.MultiRWST r w s m0
|
|
, MonadMultiState (CmdParser f out ()) m
|
|
, MonadMultiState (CommandDesc out) m
|
|
, ContainsType CmdDescStack s
|
|
, Monad m0
|
|
)
|
|
=> m ()
|
|
failureCurrentShallowRerun = do
|
|
parser <- mGet
|
|
cmd :: CommandDesc out
|
|
<- MultiRWSS.withMultiStateS emptyCommandDesc
|
|
$ iterM processCmdShallow parser
|
|
mSet cmd
|
|
|
|
postProcessCmd :: CmdDescStack -> CommandDesc out -> CommandDesc out
|
|
postProcessCmd descStack cmd
|
|
= descFixParents
|
|
$ cmd { _cmd_parts = case descStack of
|
|
StackBottom l -> reverse l
|
|
StackLayer{} -> []
|
|
, _cmd_children = reverse $ _cmd_children cmd
|
|
}
|
|
|
|
monadMisuseError :: a
|
|
monadMisuseError = error "CmdParser definition error - used Monad powers where only Applicative/Arrow is allowed"
|
|
|
|
|
|
getPartSeqDescPositionName :: PartDesc -> String
|
|
getPartSeqDescPositionName = \case
|
|
PartLiteral s -> s
|
|
PartVariable s -> s
|
|
PartOptional ds' -> f ds'
|
|
PartAlts alts -> f $ head alts -- this is not optimal, but probably
|
|
-- does not matter.
|
|
PartDefault _ d -> f d
|
|
PartSuggestion _ d -> f d
|
|
PartRedirect s _ -> s
|
|
PartMany ds -> f ds
|
|
PartWithHelp _ d -> f d
|
|
PartSeq ds -> List.unwords $ f <$> ds
|
|
PartReorder ds -> List.unwords $ f <$> ds
|
|
|
|
where
|
|
f = getPartSeqDescPositionName
|
|
|
|
dropSpaces :: MonadMultiState Input m => m ()
|
|
dropSpaces = do
|
|
inp <- mGet
|
|
case inp of
|
|
InputString s -> mSet $ InputString $ dropWhile Char.isSpace s
|
|
InputArgs{} -> return ()
|
|
|
|
|
|
-- cmdActionPartial :: CommandDesc out -> Either String out
|
|
-- cmdActionPartial = maybe (Left err) Right . _cmd_out
|
|
-- where
|
|
-- err = "command is missing implementation!"
|
|
--
|
|
-- cmdAction :: CmdParser out () -> String -> Either String out
|
|
-- cmdAction b s = case cmdRunParser Nothing s b of
|
|
-- (_, Right cmd) -> cmdActionPartial cmd
|
|
-- (_, Left (ParsingError (out:_) _)) -> Left $ out
|
|
-- _ -> error "whoops"
|
|
--
|
|
-- cmdActionRun :: (CommandDesc () -> ParsingError -> out)
|
|
-- -> CmdParser out ()
|
|
-- -> String
|
|
-- -> out
|
|
-- cmdActionRun f p s = case cmdRunParser Nothing s p of
|
|
-- (cmd, Right out) -> case _cmd_out out of
|
|
-- Just o -> o
|
|
-- Nothing -> f cmd (ParsingError ["command is missing implementation!"] "")
|
|
-- (cmd, Left err) -> f cmd err
|
|
|
|
descFixParents :: CommandDesc a -> CommandDesc a
|
|
descFixParents = descFixParentsWithTopM Nothing
|
|
|
|
-- descFixParentsWithTop :: String -> CommandDesc a -> CommandDesc a
|
|
-- descFixParentsWithTop s = descFixParentsWithTopM (Just (s, emptyCommandDesc))
|
|
|
|
descFixParentsWithTopM :: Maybe (String, CommandDesc a) -> CommandDesc a -> CommandDesc a
|
|
descFixParentsWithTopM mTop topDesc =
|
|
go $ case mTop of
|
|
Nothing -> topDesc
|
|
Just top -> topDesc { _cmd_mParent = Just top }
|
|
where
|
|
go :: CommandDesc a -> CommandDesc a
|
|
go desc =
|
|
let fixedDesc = desc { _cmd_children = _cmd_children desc <&> \(n, sd) ->
|
|
(n, go $ sd { _cmd_mParent = Just (n, fixedDesc)})
|
|
}
|
|
in fixedDesc
|
|
|
|
|
|
_tooLongText :: Int -- max length
|
|
-> String -- alternative if actual length is bigger than max.
|
|
-> String -- text to print, if length is fine.
|
|
-> PP.Doc
|
|
_tooLongText i alt s = PP.text $ Bool.bool alt s $ null $ drop i s
|