symantic-cli-2.2.4.20190701: Symantic/CLI/Parser.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE GADTs #-} -- for Router
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE Rank2Types #-} -- for hoistParserPerm (which is no longer used)
module Symantic.CLI.Parser where
import Control.Applicative (Applicative(..), Alternative(..), optional, many, some)
import Control.Monad (Monad(..), join, sequence, forM_, void)
import Control.Monad.Trans.Class (MonadTrans(..))
import Control.Monad.Trans.State (StateT(..),evalState,get,put)
import Data.Bool
import Data.Char (Char)
import Data.Either (Either(..))
import Data.Eq (Eq(..))
import Data.Foldable (null, toList)
import Data.Function (($), (.), id, const)
import Data.Functor (Functor(..), (<$>), ($>))
import Data.Functor.Identity (Identity(..))
import Data.Int (Int)
import Data.List.NonEmpty (NonEmpty(..))
import Data.Map.Strict (Map)
import Data.Maybe (Maybe(..), maybe, isNothing)
import Data.Ord (Ord(..))
import Data.Proxy (Proxy(..))
import Data.Semigroup (Semigroup(..))
import Data.String (String)
import Numeric.Natural (Natural)
import Prelude (Integer, Num(..), error)
import System.Environment (lookupEnv)
import System.IO (IO)
import Text.Read (Read, readEither)
import Text.Show (Show(..), ShowS, showString, showParen)
import Type.Reflection as Reflection
import qualified Data.List as List
import qualified Data.List.NonEmpty as NonEmpty
import qualified Data.Map.Merge.Strict as Map
import qualified Data.Map.Strict as Map
import qualified Data.Set as Set
import qualified Data.Text as Text
import qualified Data.Text.IO as Text
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Builder as TLB
import qualified Data.Text.Lazy.IO as TL
import qualified Symantic.Document as Doc
import qualified System.IO as IO
import qualified Text.Megaparsec as P
import Symantic.CLI.API
-- * Type 'Parser'
newtype Parser e d f k = Parser
{ unParser :: P.ParsecT e [Arg] IO (f->k) -- Reader f k
}
parser ::
P.ShowErrorComponent e =>
Router (Parser e d) handlers (Response (Router (Parser e d))) ->
handlers ->
[Arg] -> IO ()
parser api handlers args = do
P.runParserT
(unParser $ unTrans $ router api)
"" args >>= \case
Left err ->
forM_ (P.bundleErrors err) $ \e -> do
IO.putStr $
"Error parsing the command at argument #" <>
show (P.errorOffset e + 1) <> ":\n" <>
parseErrorTextPretty e
Right app -> unResponseParser $ app handlers
-- | Rewrite 'P.parseErrorTextPretty' to keep 'Ord' of 'Arg'.
parseErrorTextPretty ::
forall s e.
(P.Stream s, P.ShowErrorComponent e) =>
P.ParseError s e -> String
parseErrorTextPretty (P.TrivialError _ us ps) =
if isNothing us && Set.null ps
then "unknown parse error\n"
else
messageItemsPretty "unexpected "
(showErrorItem pxy <$> Set.toAscList (maybe Set.empty Set.singleton us)) <>
messageItemsPretty "expecting "
(showErrorItem pxy <$> Set.toAscList ps)
where pxy = Proxy :: Proxy s
parseErrorTextPretty err = P.parseErrorTextPretty err
messageItemsPretty :: String -> [String] -> String
messageItemsPretty prefix ts
| null ts = ""
| otherwise = prefix <> (orList . NonEmpty.fromList) ts <> "\n"
orList :: NonEmpty String -> String
orList (x:|[]) = x
orList (x:|[y]) = x <> " or " <> y
orList xs = List.intercalate ", " (NonEmpty.init xs) <> ", or " <> NonEmpty.last xs
showErrorItem :: P.Stream s => Proxy s -> P.ErrorItem (P.Token s) -> String
showErrorItem pxy = \case
P.Tokens ts -> P.showTokens pxy ts
P.Label label -> NonEmpty.toList label
P.EndOfInput -> "end of input"
instance Functor (Parser e d f) where
a2b`fmap`Parser x = Parser $ (a2b <$>) <$> x
instance Applicative (Parser e d f) where
pure = Parser . pure . const
Parser f <*> Parser x = Parser $ (<*>) <$> f <*> x
instance Ord e => Alternative (Parser e d f) where
empty = Parser empty
Parser x <|> Parser y = Parser $ x <|> y
instance Ord e => Permutable (Parser e d) where
type Permutation (Parser e d) = ParserPerm e d (Parser e d)
runPermutation (ParserPerm ma p) = Parser $ do
u2p <- unParser $ optional p
unParser $
case u2p () of
Just perm -> runPermutation perm
Nothing ->
maybe
(Parser $ P.token (const Nothing) Set.empty)
-- NOTE: not 'empty' so that 'P.TrivialError' has the unexpected token.
(Parser . return) ma
toPermutation (Parser x) =
ParserPerm Nothing
(Parser $ (\a () -> ParserPerm (Just a) empty) <$> x)
toPermDefault a (Parser x) =
ParserPerm (Just ($ a))
(Parser $ (\d () -> ParserPerm (Just d) empty) <$> x)
instance App (Parser e d) where
Parser x <.> Parser y = Parser $
x >>= \a2b -> (. a2b) <$> y
instance Ord e => Alt (Parser e d) where
Parser x <!> Parser y = Parser $
(\a2k (a:!:_b) -> a2k a) <$> P.try x <|>
(\b2k (_a:!:b) -> b2k b) <$> y
Parser x `alt` Parser y = Parser $ P.try x <|> y
opt (Parser x) = Parser $
mapCont Just <$> P.try x
instance Ord e => AltApp (Parser e d) where
many0 (Parser x) = Parser $ concatCont <$> many x
many1 (Parser x) = Parser $ concatCont <$> some x
instance Pro (Parser e d) where
dimap a2b _b2a (Parser r) = Parser $ (\k b2k -> k (b2k . a2b)) <$> r
instance Ord e => CLI_Command (Parser e d) where
-- type CommandConstraint (Parser e d) a = ()
command "" x = x
command n x = commands Map.empty (Map.singleton n x)
instance Ord e => CLI_Tag (Parser e d) where
type TagConstraint (Parser e d) a = ()
tagged name p = Parser $ P.try $ do
void $ (`P.token` exp) $ \tok ->
if lookupTag tok name
then Just tok
else Nothing
unParser p
where
exp =
case name of
TagShort t -> Set.singleton $ P.Tokens $ pure $ ArgTagShort t
TagLong t -> Set.singleton $ P.Tokens $ pure $ ArgTagLong t
Tag s l -> Set.fromList
[ P.Tokens $ pure $ ArgTagShort s
, P.Tokens $ pure $ ArgTagLong l
]
lookupTag (ArgTagShort x) (TagShort y) = x == y
lookupTag (ArgTagShort x) (Tag y _) = x == y
lookupTag (ArgTagLong x) (TagLong y) = x == y
lookupTag (ArgTagLong x) (Tag _ y) = x == y
lookupTag _ _ = False
endOpts = Parser $ do
(`P.token` exp) $ \case
ArgTagLong "" -> Just id
_ -> Nothing
where
exp = Set.singleton $ P.Tokens $ pure $ ArgTagLong ""
instance Ord e => CLI_Var (Parser e d) where
type VarConstraint (Parser e d) a = (IOType a, FromSegment a)
var' :: forall a k. VarConstraint (Parser e d) a => Name -> Parser e d (a->k) k
var' name = Parser $ do
seg <- (`P.token` expName) $ \case
ArgSegment seg -> Just seg
_ -> Nothing
lift (fromSegment seg) >>= \case
Left err -> P.failure got expType
where
got = Just $ P.Tokens $ pure $ ArgSegment seg
expType = Set.singleton $ P.Label $ NonEmpty.fromList $
"<"<>name<>"> to be of type "<>ioType @a
<> case err of
"Prelude.read: no parse" -> ""
"" -> ""
_ -> ": "<>err
Right a -> return ($ a)
where
expName = Set.singleton $ P.Label $ NonEmpty.fromList $ "<"<>name<>">"
just a = Parser $ return ($ a)
nothing = Parser $ return id
instance Ord e => CLI_Env (Parser e d) where
type EnvConstraint (Parser e d) a = (IOType a, FromSegment a)
env' :: forall a k. EnvConstraint (Parser e d) a => Name -> Parser e d (a->k) k
env' name = Parser $
lift (lookupEnv name) >>= \case
Nothing -> P.failure got exp
where
got = Nothing
exp = Set.singleton $ P.Label $ NonEmpty.fromList $ "${"<>name<>"}"
Just val ->
lift (fromSegment val) >>= \case
Right a -> return ($ a)
Left err -> P.failure got exp
where
got = Just $ P.Tokens $ pure $ ArgEnv name val
exp = Set.singleton $ P.Label $ NonEmpty.fromList $
"${"<>name<>"} to be of type "<>ioType @a
<> case err of
"Prelude.read: no parse" -> ""
"" -> ""
_ -> ": "<>err
instance Ord e => CLI_Response (Parser e d) where
type ResponseConstraint (Parser e d) a = Outputable a
type ResponseArgs (Parser e d) a = ParserResponseArgs a
type Response (Parser e d) = ParserResponse
response' = Parser $
P.eof $> \({-ParserResponseArgs-} io) ->
ParserResponse $ io >>= output
instance Ord e => CLI_Help (Parser e d) where
type HelpConstraint (Parser e d) d' = d ~ d'
help _msg = id
program n = Parser . P.label n . unParser
rule n = Parser . P.label n . unParser
concatCont :: [(a->k)->k] -> ([a]->k)->k
concatCont = List.foldr (consCont (:)) ($ [])
consCont :: (a->b->c) -> ((a->k)->k) -> ((b->k)->k) -> (c->k)->k
consCont ab2c a2k2k b2k2k = \c2k -> a2k2k $ \a -> b2k2k $ \b -> c2k (ab2c a b)
mapCont :: (a->b) -> ((a->k)->k) -> ((b->k)->k)
mapCont a2b a2k2k = \b2k -> a2k2k (b2k . a2b)
-- ** Type 'ParserResponse'
newtype ParserResponse = ParserResponse { unResponseParser :: IO () }
-- ** Type 'ParserResponseArgs'
type ParserResponseArgs = IO
-- * Class 'Outputable'
-- | Output of a CLI.
class IOType a => Outputable a where
output :: a -> IO ()
default output :: Show a => a -> IO ()
output = IO.print
instance Outputable () where
output = return
instance Outputable Bool
instance Outputable Int
instance Outputable Integer
instance Outputable Natural
instance Outputable String where
output = IO.putStr
instance Outputable Text.Text where
output = Text.putStr
instance Outputable TL.Text where
output = TL.putStr
instance Outputable (Doc.AnsiText (Doc.Plain TLB.Builder)) where
output =
TL.putStr .
TLB.toLazyText .
Doc.runPlain .
Doc.runAnsiText
instance Outputable (IO.Handle, Doc.AnsiText (Doc.Plain TLB.Builder)) where
output (h,d) =
TL.hPutStr h $
TLB.toLazyText $
Doc.runPlain $
Doc.runAnsiText d
-- * Class 'IOType'
-- | Like a MIME type but for input/output of a CLI.
class IOType a where
ioType :: String
default ioType :: Reflection.Typeable a => String
ioType = show (Reflection.typeRep @a)
instance IOType ()
instance IOType Bool
instance IOType Int
instance IOType Integer
instance IOType Natural
instance IOType String
instance IOType Text.Text
instance IOType TL.Text
instance IOType (Doc.AnsiText (Doc.Plain TLB.Builder))
instance IOType (IO.Handle, Doc.AnsiText (Doc.Plain TLB.Builder))
-- * Class 'FromSegment'
class FromSegment a where
fromSegment :: Segment -> IO (Either String a)
default fromSegment :: Read a => Segment -> IO (Either String a)
fromSegment = return . readEither
instance FromSegment String where
fromSegment = return . Right
instance FromSegment Text.Text where
fromSegment = return . Right . Text.pack
instance FromSegment TL.Text where
fromSegment = return . Right . TL.pack
instance FromSegment Bool
instance FromSegment Int
instance FromSegment Integer
instance FromSegment Natural
-- ** Type 'ParserPerm'
data ParserPerm e d repr k a = ParserPerm
{ permutation_result :: !(Maybe ((a->k)->k))
, permutation_parser :: repr () (ParserPerm e d repr k a)
}
instance (App repr, Functor (repr ())) => Functor (ParserPerm e d repr k) where
a2b `fmap` ParserPerm a ma = ParserPerm
((\a2k2k b2k -> a2k2k $ b2k . a2b) <$> a)
((a2b `fmap`) `fmap` ma)
instance (App repr, Functor (repr ()), Alternative (repr ())) =>
Applicative (ParserPerm e d repr k) where
pure a = ParserPerm (Just ($ a)) empty
lhs@(ParserPerm f ma2b) <*> rhs@(ParserPerm x ma) =
ParserPerm a (lhsAlt <|> rhsAlt)
where
a =
(\a2b2k2k a2k2k -> \b2k ->
a2b2k2k $ \a2b -> a2k2k (b2k . a2b)
) <$> f <*> x
lhsAlt = (<*> rhs) <$> ma2b
rhsAlt = (lhs <*>) <$> ma
instance CLI_Help repr => CLI_Help (ParserPerm e d repr) where
type HelpConstraint (ParserPerm e d repr) d' = HelpConstraint (Parser e d) d'
program _n = id
rule _n = id
noTransParserPerm ::
Trans repr =>
Functor (UnTrans repr ()) =>
ParserPerm e d (UnTrans repr) k a -> ParserPerm e d repr k a
noTransParserPerm (ParserPerm a ma) = ParserPerm a (noTrans $ noTransParserPerm <$> ma)
unTransParserPerm ::
Trans repr =>
Functor (UnTrans repr ()) =>
ParserPerm e d repr k a -> ParserPerm e d (UnTrans repr) k a
unTransParserPerm (ParserPerm a ma) = ParserPerm a (unTransParserPerm <$> unTrans ma)
hoistParserPerm ::
Functor (repr ()) =>
(forall a b. repr a b -> repr a b) ->
ParserPerm e d repr k c -> ParserPerm e d repr k c
hoistParserPerm f (ParserPerm a ma) =
ParserPerm a (hoistParserPerm f <$> f ma)
-- ** Class 'CLI_Routing'
class CLI_Routing repr where
commands :: Map Name (repr a k) -> Map Name (repr a k) -> repr a k
-- taggeds :: TagConstraint repr a => Map (Either Char Name) (repr (a -> k) k) -> repr (a -> k) k
instance Ord e => CLI_Routing (Parser e d) where
commands preCmds cmds = Parser $
P.token check exp >>= unParser
where
exp = Set.fromList $ P.Tokens . pure . ArgSegment <$> Map.keys cmds
check = \case
ArgSegment cmd ->
Map.lookup cmd cmds <|>
Map.lookup cmd preCmds
_ -> Nothing
-- * Type 'Router'
data Router repr a b where
-- | Lift any @(repr)@ into 'Router', those not useful to segregate
-- wrt. the 'Trans'formation performed, aka. 'noTrans'.
Router_Any :: repr a b -> Router repr a b
-- | Represent 'commands'.
Router_Commands ::
Map Name (Router repr a k) ->
Map Name (Router repr a k) ->
Router repr a k
-- | Represent 'tagged'.
Router_Tagged :: Tag -> Router repr f k -> Router repr f k
-- | Represent ('<.>').
Router_App :: Router repr a b -> Router repr b c -> Router repr a c
-- | Represent ('<!>').
Router_Alt :: Router repr a k -> Router repr b k -> Router repr (a:!:b) k
-- | Unify 'Router's which have different 'handlers'.
-- Useful to put alternative 'Router's in a 'Map' as in 'Router_Commands'.
Router_Union :: (b->a) -> Router repr a k -> Router repr b k
instance Ord e => Functor (Router (Parser e d) f) where
a2b`fmap`x = noTrans (a2b <$> unTrans x)
instance Ord e => Applicative (Router (Parser e d) f) where
pure = noTrans . pure
f <*> x = noTrans (unTrans f <*> unTrans x)
instance Ord e => Alternative (Router (Parser e d) f) where
empty = noTrans empty
f <|> x = noTrans (unTrans f <|> unTrans x)
instance Ord e => Permutable (Router (Parser e d)) where
type Permutation (Router (Parser e d)) = ParserPerm e d (Router (Parser e d))
runPermutation = noTrans . runPermutation . unTransParserPerm
toPermutation = noTransParserPerm . toPermutation . unTrans
toPermDefault a = noTransParserPerm . toPermDefault a . unTrans
instance (repr ~ Parser e d) => Show (Router repr a b) where
showsPrec p = \case
Router_Any{} -> showString "X"
Router_Commands _preCmds cmds -> showParen (p>=10) $ showString "Commands [" . go (Map.toList cmds) . showString "]"
where
go :: forall h k. [(Segment, Router repr h k)] -> ShowS
go [] = id
go ((n, r):xs) =
(showParen True $ showString (show n<>", ") . showsPrec 0 r) .
case xs of
[] -> id
_ -> showString ", " . go xs
Router_Tagged n x -> showsPrec 10 n . showString " " . showsPrec p x
Router_App x y -> showParen (p>=4) $ showsPrec 4 x . showString " <.> " . showsPrec 4 y
Router_Alt x y -> showParen (p>=3) $ showsPrec 3 x . showString " <!> " . showsPrec 3 y
Router_Union _u x -> showString "Union [" . showsPrec 0 x . showString "]"
instance Ord e => Trans (Router (Parser e d)) where
type UnTrans (Router (Parser e d)) = Parser e d
noTrans = Router_Any
unTrans (Router_Any x) = x
unTrans (Router_Alt x y) = unTrans x <!> unTrans y
unTrans (Router_App x y) = unTrans x <.> unTrans y
unTrans (Router_Commands preCmds cmds) = commands (unTrans <$> preCmds) (unTrans <$> cmds)
unTrans (Router_Tagged n x) = tagged n (unTrans x)
unTrans (Router_Union u x) = Parser $ (. u) <$> unParser (unTrans x)
instance Ord e => App (Router (Parser e d)) where
(<.>) = Router_App
instance Ord e => Alt (Router (Parser e d)) where
(<!>) = Router_Alt
alt x y = Router_Union (\a -> a:!:a) $ Router_Alt x y
instance Ord e => Pro (Router (Parser e d))
instance (repr ~ (Parser e d)) => CLI_Command (Router repr) where
command "" x = x
command n x =
let is = List.tail $ List.inits n in
let (preCmds, cmds) = List.splitAt (List.length is - 1) is in
Router_Commands
(Map.fromAscList $ (,x) <$> preCmds)
(Map.fromAscList $ (,x) <$> cmds)
instance Ord e => CLI_Var (Router (Parser e d))
instance Ord e => CLI_Env (Router (Parser e d))
instance Ord e => CLI_Tag (Router (Parser e d)) where
tagged = Router_Tagged
instance CLI_Help (Router (Parser e d)) where
-- NOTE: set manually (instead of the 'Trans' default 'Router_Any')
-- to remove them all, since they are useless for 'Parser'
-- and may prevent patterns to be matched in 'router'.
help _msg = id
program _n = id
rule _n = id
instance Ord e => CLI_Response (Router (Parser e d))
instance Ord e => CLI_Routing (Router (Parser e d)) where
-- taggeds = Router_Taggeds
commands = Router_Commands
router ::
repr ~ Parser e d =>
Router repr a b -> Router repr a b
router = {-debug1 "router" $-} \case
x@Router_Any{} -> x
Router_Tagged n x -> Router_Tagged n (router x)
Router_Alt x y -> router x`router_Alt`router y
Router_Commands preCmds cmds ->
Router_Commands
(router <$> preCmds)
(router <$> cmds)
Router_App xy z ->
case xy of
Router_App x y ->
-- Associate to the right
Router_App (router x) $
Router_App (router y) (router z)
_ -> router xy `Router_App` router z
Router_Union u x -> Router_Union u (router x)
-- | Merge/reorder alternatives if possible or default to a 'Router_Alt'.
router_Alt ::
repr ~ Parser e d =>
Router repr a k ->
Router repr b k ->
Router repr (a:!:b) k
router_Alt = {-debug2 "router_Alt"-} go
where
-- Merge alternative commands together.
go (Router_Commands xp xs) (Router_Commands yp ys) =
Router_Commands
(router_Commands False xp yp) -- NOTE: conflicting prefixes are dropped.
(router_Commands True xs ys)
-- Merge left first or right first, depending on which removes 'Router_Alt'.
go x (y`Router_Alt`z) =
case x`router_Alt`y of
Router_Alt x' y' ->
case y'`router_Alt`z of
yz@(Router_Alt _y z') ->
case x'`router_Alt`z' of
Router_Alt{} -> router x'`Router_Alt`yz
xz -> Router_Union (\(a:!:(b:!:c)) -> (a:!:c):!:b) $ xz`router_Alt`y
-- NOTE: prioritize the merged router 'xz' over over the non-mergeable 'y'.
yz -> x'`router_Alt`yz
xy -> Router_Union (\(a:!:(b:!:c)) -> (a:!:b):!:c) $ xy`router_Alt`z
go (x`Router_Alt`y) z =
case y`router_Alt`z of
Router_Alt y' z' ->
case x`router_Alt`y' of
xy@(Router_Alt x' _y) ->
case x'`router_Alt`z' of
Router_Alt{} -> xy`Router_Alt`router z'
xz -> Router_Union (\((a:!:b):!:c) -> (a:!:c):!:b) $ xz`router_Alt`y
-- NOTE: prioritize the merged router 'xz' over the non-mergeable 'y'.
xy -> xy`router_Alt`z'
yz -> Router_Union (\((a:!:b):!:c) -> a:!:(b:!:c)) $ x`router_Alt`yz
-- Merge through 'Router_Union'.
go (Router_Union u x) y = Router_Union (\(a:!:b) -> u a:!:b) (x`router_Alt`y)
go x (Router_Union u y) = Router_Union (\(a:!:b) -> a:!:u b) (x`router_Alt`y)
-- No merging
go x y = x`Router_Alt`y
router_Commands ::
repr ~ Parser e d =>
Bool ->
Map Segment (Router repr a k) ->
Map Segment (Router repr b k) ->
Map Segment (Router repr (a:!:b) k)
router_Commands allowMerging =
-- NOTE: a little bit more complex than required
-- in order to merge 'Router_Union's instead of nesting them,
-- such that 'unTrans' 'Router_Union' applies them all at once.
Map.merge
(Map.mapMissing $ const keepX)
(Map.mapMissing $ const keepY)
(Map.zipWithMaybeMatched $ const $ \x y ->
if allowMerging then Just $ mergeFull x y else Nothing)
where
keepX = \case
Router_Union u r -> Router_Union (\(x:!:_y) -> u x) r
r -> Router_Union (\(x:!:_y) -> x) r
keepY = \case
Router_Union u r -> Router_Union (\(_x:!:y) -> u y) r
r -> Router_Union (\(_x:!:y) -> y) r
mergeFull = \case
Router_Union xu xr -> \case
Router_Union yu yr -> Router_Union (\(x:!:y) -> xu x:!:yu y) $ xr`router_Alt`yr
yr -> Router_Union (\(a:!:b) -> xu a:!:b) $ xr`router_Alt`yr
xr -> \case
Router_Union yu yr -> Router_Union (\(a:!:b) -> a:!:yu b) $ xr`router_Alt`yr
yr -> xr`router_Alt`yr
-- ** Type 'Arg'
data Arg
= ArgSegment Segment
| ArgTagLong Name
| ArgTagShort Char
| ArgEnv Name String -- ^ Here only for error reporting.
deriving (Eq,Ord,Show)
lexer :: [String] -> [Arg]
lexer ss =
join $
(`evalState` False) $
sequence (f <$> ss)
where
f :: String -> StateT Bool Identity [Arg]
f s = do
skip <- get
if skip then return [ArgSegment s]
else case s of
'-':'-':[] -> do
put True
return [ArgTagLong ""]
'-':'-':cs -> return [ArgTagLong cs]
'-':cs@(_:_) -> return $ ArgTagShort <$> cs
seg -> return [ArgSegment seg]
showArg :: Arg -> String
showArg = \case
ArgTagShort t -> '-':[t]
ArgTagLong t -> '-':'-':t
ArgSegment seg -> seg
ArgEnv name val -> name<>"="<>val
showArgs :: [Arg] -> String
showArgs args = List.intercalate " " $ showArg <$> args
instance P.Stream [Arg] where
type Token [Arg] = Arg
type Tokens [Arg] = [Arg]
tokenToChunk Proxy = pure
tokensToChunk Proxy = id
chunkToTokens Proxy = id
chunkLength Proxy = List.length
chunkEmpty Proxy = List.null
take1_ [] = Nothing
take1_ (t:ts) = Just (t, ts)
takeN_ n s
| n <= 0 = Just ([], s)
| List.null s = Nothing
| otherwise = Just (List.splitAt n s)
takeWhile_ = List.span
showTokens Proxy = showArgs . toList
-- NOTE: those make no sense when parsing a command line,
-- and should not be called since 'P.errorBundlePretty' is not used in 'parser'.
reachOffset = error "BUG: reachOffset must not be used on [Arg]"
reachOffsetNoLine = error "BUG: reachOffsetNoLine must not be used on [Arg]"