1 {-# LANGUAGE AllowAmbiguousTypes #-}
2 {-# LANGUAGE DataKinds #-}
3 {-# LANGUAGE DefaultSignatures #-}
4 {-# LANGUAGE GADTs #-} -- for Router
5 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
6 {-# LANGUAGE InstanceSigs #-}
7 {-# LANGUAGE OverloadedStrings #-}
8 {-# LANGUAGE Rank2Types #-} -- for hoistParserPerm (which is no longer used)
9 module Symantic.CLI.Parser where
11 import Control.Applicative (Applicative(..), Alternative(..), optional, many, some)
12 import Control.Monad (Monad(..), join, sequence, forM_, void)
13 import Control.Monad.Trans.Class (MonadTrans(..))
14 import Control.Monad.Trans.State (StateT(..),evalState,get,put)
16 import Data.Char (Char)
17 import Data.Either (Either(..))
18 import Data.Eq (Eq(..))
19 import Data.Foldable (null, toList)
20 import Data.Function (($), (.), id, const)
21 import Data.Functor (Functor(..), (<$>), ($>))
22 import Data.Functor.Identity (Identity(..))
24 import Data.List.NonEmpty (NonEmpty(..))
25 import Data.Map.Strict (Map)
26 import Data.Maybe (Maybe(..), maybe, isNothing)
27 import Data.Ord (Ord(..))
28 import Data.Proxy (Proxy(..))
29 import Data.Semigroup (Semigroup(..))
30 import Data.String (String)
31 import Numeric.Natural (Natural)
32 import Prelude (Integer, Num(..), error)
33 import System.Environment (lookupEnv)
35 import Text.Read (Read, readEither)
36 import Text.Show (Show(..), ShowS, showString, showParen)
37 import Type.Reflection as Reflection
38 import qualified Data.List as List
39 import qualified Data.List.NonEmpty as NonEmpty
40 import qualified Data.Map.Merge.Strict as Map
41 import qualified Data.Map.Strict as Map
42 import qualified Data.Set as Set
43 import qualified Data.Text as Text
44 import qualified Data.Text.IO as Text
45 import qualified Data.Text.Lazy as TL
46 import qualified Data.Text.Lazy.Builder as TLB
47 import qualified Data.Text.Lazy.IO as TL
48 import qualified Symantic.Document as Doc
49 import qualified System.IO as IO
50 import qualified Text.Megaparsec as P
52 import Symantic.CLI.API
55 newtype Parser e d f k = Parser
56 { unParser :: P.ParsecT e [Arg] IO (f->k) -- Reader f k
60 P.ShowErrorComponent e =>
61 Router (Parser e d) handlers (Response (Router (Parser e d))) ->
64 parser api handlers args = do
66 (unParser $ unTrans $ router api)
69 forM_ (P.bundleErrors err) $ \e -> do
71 "Error parsing the command at argument #" <>
72 show (P.errorOffset e + 1) <> ":\n" <>
73 parseErrorTextPretty e
74 Right app -> unResponseParser $ app handlers
76 -- | Rewrite 'P.parseErrorTextPretty' to keep 'Ord' of 'Arg'.
77 parseErrorTextPretty ::
79 (P.Stream s, P.ShowErrorComponent e) =>
80 P.ParseError s e -> String
81 parseErrorTextPretty (P.TrivialError _ us ps) =
82 if isNothing us && Set.null ps
83 then "unknown parse error\n"
85 messageItemsPretty "unexpected "
86 (showErrorItem pxy <$> Set.toAscList (maybe Set.empty Set.singleton us)) <>
87 messageItemsPretty "expecting "
88 (showErrorItem pxy <$> Set.toAscList ps)
89 where pxy = Proxy :: Proxy s
90 parseErrorTextPretty err = P.parseErrorTextPretty err
92 messageItemsPretty :: String -> [String] -> String
93 messageItemsPretty prefix ts
95 | otherwise = prefix <> (orList . NonEmpty.fromList) ts <> "\n"
97 orList :: NonEmpty String -> String
99 orList (x:|[y]) = x <> " or " <> y
100 orList xs = List.intercalate ", " (NonEmpty.init xs) <> ", or " <> NonEmpty.last xs
102 showErrorItem :: P.Stream s => Proxy s -> P.ErrorItem (P.Token s) -> String
103 showErrorItem pxy = \case
104 P.Tokens ts -> P.showTokens pxy ts
105 P.Label label -> NonEmpty.toList label
106 P.EndOfInput -> "end of input"
109 instance Functor (Parser e d f) where
110 a2b`fmap`Parser x = Parser $ (a2b <$>) <$> x
111 instance Applicative (Parser e d f) where
112 pure = Parser . pure . const
113 Parser f <*> Parser x = Parser $ (<*>) <$> f <*> x
114 instance Ord e => Alternative (Parser e d f) where
116 Parser x <|> Parser y = Parser $ x <|> y
117 instance Ord e => Permutable (Parser e d) where
118 type Permutation (Parser e d) = ParserPerm e d (Parser e d)
119 runPermutation (ParserPerm ma p) = Parser $ do
120 u2p <- unParser $ optional p
123 Just perm -> runPermutation perm
126 (Parser $ P.token (const Nothing) Set.empty)
127 -- NOTE: not 'empty' so that 'P.TrivialError' has the unexpected token.
129 toPermutation (Parser x) =
131 (Parser $ (\a () -> ParserPerm (Just a) empty) <$> x)
132 toPermDefault a (Parser x) =
133 ParserPerm (Just ($ a))
134 (Parser $ (\d () -> ParserPerm (Just d) empty) <$> x)
135 instance App (Parser e d) where
136 Parser x <.> Parser y = Parser $
137 x >>= \a2b -> (. a2b) <$> y
138 instance Ord e => Alt (Parser e d) where
139 Parser x <!> Parser y = Parser $
140 (\a2k (a:!:_b) -> a2k a) <$> P.try x <|>
141 (\b2k (_a:!:b) -> b2k b) <$> y
142 opt (Parser x) = Parser $
143 mapCont Just <$> P.try x
144 instance Ord e => AltApp (Parser e d) where
145 many0 (Parser x) = Parser $ concatCont <$> many x
146 many1 (Parser x) = Parser $ concatCont <$> some x
147 instance Pro (Parser e d) where
148 dimap a2b _b2a (Parser r) = Parser $ (\k b2k -> k (b2k . a2b)) <$> r
149 instance Ord e => CLI_Command (Parser e d) where
150 -- type CommandConstraint (Parser e d) a = ()
152 command n x = commands $ Map.singleton n x
153 instance Ord e => CLI_Tag (Parser e d) where
154 type TagConstraint (Parser e d) a = ()
155 tagged name p = Parser $ P.try $ do
156 void $ (`P.token` exp) $ \tok ->
157 if lookupTag tok name
164 TagShort t -> Set.singleton $ P.Tokens $ pure $ ArgTagShort t
165 TagLong t -> Set.singleton $ P.Tokens $ pure $ ArgTagLong t
166 Tag s l -> Set.fromList
167 [ P.Tokens $ pure $ ArgTagShort s
168 , P.Tokens $ pure $ ArgTagLong l
170 lookupTag (ArgTagShort x) (TagShort y) = x == y
171 lookupTag (ArgTagShort x) (Tag y _) = x == y
172 lookupTag (ArgTagLong x) (TagLong y) = x == y
173 lookupTag (ArgTagLong x) (Tag _ y) = x == y
174 lookupTag _ _ = False
175 endOpts = Parser $ do
176 (`P.token` exp) $ \case
177 ArgTagLong "" -> Just id
180 exp = Set.singleton $ P.Tokens $ pure $ ArgTagLong ""
181 instance Ord e => CLI_Var (Parser e d) where
182 type VarConstraint (Parser e d) a = (IOType a, FromSegment a)
183 var' :: forall a k. VarConstraint (Parser e d) a => Name -> Parser e d (a->k) k
184 var' name = Parser $ do
185 seg <- (`P.token` expName) $ \case
186 ArgSegment seg -> Just seg
188 lift (fromSegment seg) >>= \case
189 Left err -> P.failure got expType
191 got = Just $ P.Tokens $ pure $ ArgSegment seg
192 expType = Set.singleton $ P.Label $ NonEmpty.fromList $
193 "<"<>name<>"> to be of type "<>ioType @a
195 "Prelude.read: no parse" -> ""
198 Right a -> return ($ a)
200 expName = Set.singleton $ P.Label $ NonEmpty.fromList $ "<"<>name<>">"
201 just a = Parser $ return ($ a)
202 nothing = Parser $ return id
203 instance Ord e => CLI_Env (Parser e d) where
204 type EnvConstraint (Parser e d) a = (IOType a, FromSegment a)
205 env' :: forall a k. EnvConstraint (Parser e d) a => Name -> Parser e d (a->k) k
207 lift (lookupEnv name) >>= \case
208 Nothing -> P.failure got exp
211 exp = Set.singleton $ P.Label $ NonEmpty.fromList $ "${"<>name<>"}"
213 lift (fromSegment val) >>= \case
214 Right a -> return ($ a)
215 Left err -> P.failure got exp
217 got = Just $ P.Tokens $ pure $ ArgEnv name val
218 exp = Set.singleton $ P.Label $ NonEmpty.fromList $
219 "${"<>name<>"} to be of type "<>ioType @a
221 "Prelude.read: no parse" -> ""
224 instance Ord e => CLI_Response (Parser e d) where
225 type ResponseConstraint (Parser e d) a = Outputable a
226 type ResponseArgs (Parser e d) a = ParserResponseArgs a
227 type Response (Parser e d) = ParserResponse
229 P.eof $> \(ParserResponseArgs io) ->
230 ParserResponse $ io >>= output
231 instance Ord e => CLI_Help (Parser e d) where
232 type HelpConstraint (Parser e d) d' = d ~ d'
234 program n = Parser . P.label n . unParser
235 rule n = Parser . P.label n . unParser
237 concatCont :: [(a->k)->k] -> ([a]->k)->k
238 concatCont = List.foldr (consCont (:)) ($ [])
240 consCont :: (a->b->c) -> ((a->k)->k) -> ((b->k)->k) -> (c->k)->k
241 consCont ab2c a2k2k b2k2k = \c2k -> a2k2k $ \a -> b2k2k $ \b -> c2k (ab2c a b)
243 mapCont :: (a->b) -> ((a->k)->k) -> ((b->k)->k)
244 mapCont a2b a2k2k = \b2k -> a2k2k (b2k . a2b)
246 -- ** Type 'ParserResponse'
247 newtype ParserResponse = ParserResponse { unResponseParser :: IO () }
248 -- ** Type 'ParserResponseArgs'
249 newtype ParserResponseArgs a = ParserResponseArgs (IO a)
250 deriving (Functor,Applicative,Monad)
252 -- * Class 'Outputable'
253 -- | Output of a CLI.
254 class IOType a => Outputable a where
256 default output :: Show a => a -> IO ()
259 instance Outputable () where
261 instance Outputable Bool
262 instance Outputable Int
263 instance Outputable Integer
264 instance Outputable Natural
265 instance Outputable String where
267 instance Outputable Text.Text where
269 instance Outputable TL.Text where
271 instance Outputable (Doc.AnsiText (Doc.Plain TLB.Builder)) where
277 instance Outputable (IO.Handle, Doc.AnsiText (Doc.Plain TLB.Builder)) where
285 -- | Like a MIME type but for input/output of a CLI.
288 default ioType :: Reflection.Typeable a => String
289 ioType = show (Reflection.typeRep @a)
294 instance IOType Integer
295 instance IOType Natural
296 instance IOType String
297 instance IOType Text.Text
298 instance IOType TL.Text
299 instance IOType (Doc.AnsiText (Doc.Plain TLB.Builder))
300 instance IOType (IO.Handle, Doc.AnsiText (Doc.Plain TLB.Builder))
302 -- * Class 'FromSegment'
303 class FromSegment a where
304 fromSegment :: Segment -> IO (Either String a)
305 default fromSegment :: Read a => Segment -> IO (Either String a)
306 fromSegment = return . readEither
307 instance FromSegment String where
308 fromSegment = return . Right
309 instance FromSegment Text.Text where
310 fromSegment = return . Right . Text.pack
311 instance FromSegment TL.Text where
312 fromSegment = return . Right . TL.pack
313 instance FromSegment Bool
314 instance FromSegment Int
315 instance FromSegment Integer
316 instance FromSegment Natural
318 -- ** Type 'ParserPerm'
319 data ParserPerm e d repr k a = ParserPerm
320 { permutation_result :: !(Maybe ((a->k)->k))
321 , permutation_parser :: repr () (ParserPerm e d repr k a)
324 instance (App repr, Functor (repr ())) => Functor (ParserPerm e d repr k) where
325 a2b `fmap` ParserPerm a ma = ParserPerm
326 ((\a2k2k b2k -> a2k2k $ b2k . a2b) <$> a)
327 ((a2b `fmap`) `fmap` ma)
328 instance (App repr, Functor (repr ()), Alternative (repr ())) =>
329 Applicative (ParserPerm e d repr k) where
330 pure a = ParserPerm (Just ($ a)) empty
331 lhs@(ParserPerm f ma2b) <*> rhs@(ParserPerm x ma) =
332 ParserPerm a (lhsAlt <|> rhsAlt)
335 (\a2b2k2k a2k2k -> \b2k ->
336 a2b2k2k $ \a2b -> a2k2k (b2k . a2b)
338 lhsAlt = (<*> rhs) <$> ma2b
339 rhsAlt = (lhs <*>) <$> ma
340 instance CLI_Help repr => CLI_Help (ParserPerm e d repr) where
341 type HelpConstraint (ParserPerm e d repr) d' = HelpConstraint (Parser e d) d'
347 Functor (UnTrans repr ()) =>
348 ParserPerm e d (UnTrans repr) k a -> ParserPerm e d repr k a
349 noTransParserPerm (ParserPerm a ma) = ParserPerm a (noTrans $ noTransParserPerm <$> ma)
353 Functor (UnTrans repr ()) =>
354 ParserPerm e d repr k a -> ParserPerm e d (UnTrans repr) k a
355 unTransParserPerm (ParserPerm a ma) = ParserPerm a (unTransParserPerm <$> unTrans ma)
359 (forall a b. repr a b -> repr a b) ->
360 ParserPerm e d repr k c -> ParserPerm e d repr k c
361 hoistParserPerm f (ParserPerm a ma) =
362 ParserPerm a (hoistParserPerm f <$> f ma)
364 -- ** Class 'CLI_Routing'
365 class CLI_Routing repr where
366 commands :: Map Name (repr a k) -> repr a k
367 -- taggeds :: TagConstraint repr a => Map (Either Char Name) (repr (a -> k) k) -> repr (a -> k) k
368 instance Ord e => CLI_Routing (Parser e d) where
369 commands cmds = Parser $
370 P.token check exp >>= unParser
372 exp = Set.fromList $ P.Tokens . pure . ArgSegment <$> Map.keys cmds
374 ArgSegment cmd -> Map.lookup cmd cmds
378 data Router repr a b where
379 -- | Lift any @(repr)@ into 'Router', those not useful to segregate
380 -- wrt. the 'Trans'formation performed, aka. 'noTrans'.
381 Router_Any :: repr a b -> Router repr a b
382 -- | Represent 'commands'.
383 Router_Commands :: Map Name (Router repr a k) -> Router repr a k
384 -- | Represent 'tagged'.
385 Router_Tagged :: Tag -> Router repr f k -> Router repr f k
386 -- | Represent ('<.>').
387 Router_App :: Router repr a b -> Router repr b c -> Router repr a c
388 -- | Represent ('<!>').
389 Router_Alt :: Router repr a k -> Router repr b k -> Router repr (a:!:b) k
390 -- | Unify 'Router's which have different 'handlers'.
391 -- Useful to put alternative 'Router's in a 'Map' as in 'Router_Commands'.
392 Router_Union :: (b->a) -> Router repr a k -> Router repr b k
394 instance Ord e => Functor (Router (Parser e d) f) where
395 a2b`fmap`x = noTrans (a2b <$> unTrans x)
396 instance Ord e => Applicative (Router (Parser e d) f) where
397 pure = noTrans . pure
398 f <*> x = noTrans (unTrans f <*> unTrans x)
399 instance Ord e => Alternative (Router (Parser e d) f) where
400 empty = noTrans empty
401 f <|> x = noTrans (unTrans f <|> unTrans x)
402 instance Ord e => Permutable (Router (Parser e d)) where
403 type Permutation (Router (Parser e d)) = ParserPerm e d (Router (Parser e d))
404 runPermutation = noTrans . runPermutation . unTransParserPerm
405 toPermutation = noTransParserPerm . toPermutation . unTrans
406 toPermDefault a = noTransParserPerm . toPermDefault a . unTrans
407 instance (repr ~ Parser e d) => Show (Router repr a b) where
409 Router_Any{} -> showString "X"
410 Router_Commands ms -> showParen (p>=10) $ showString "Commands [" . go (Map.toList ms) . showString "]"
412 go :: forall h k. [(Segment, Router repr h k)] -> ShowS
415 (showParen True $ showString (show n<>", ") . showsPrec 0 r) .
418 _ -> showString ", " . go xs
419 Router_Tagged n x -> showsPrec 10 n . showString " " . showsPrec p x
420 Router_App x y -> showParen (p>=4) $ showsPrec 4 x . showString " <.> " . showsPrec 4 y
421 Router_Alt x y -> showParen (p>=3) $ showsPrec 3 x . showString " <!> " . showsPrec 3 y
422 Router_Union _u x -> showString "Union [" . showsPrec 0 x . showString "]"
424 instance Ord e => Trans (Router (Parser e d)) where
425 type UnTrans (Router (Parser e d)) = Parser e d
427 unTrans (Router_Any x) = x
428 unTrans (Router_Alt x y) = unTrans x <!> unTrans y
429 unTrans (Router_App x y) = unTrans x <.> unTrans y
430 unTrans (Router_Commands ms) = commands (unTrans <$> ms)
431 unTrans (Router_Tagged n x) = tagged n (unTrans x)
432 unTrans (Router_Union u x) = Parser $ (. u) <$> unParser (unTrans x)
434 instance Ord e => App (Router (Parser e d)) where
436 instance Ord e => Alt (Router (Parser e d)) where
438 instance Ord e => Pro (Router (Parser e d))
439 instance (repr ~ (Parser e d)) => CLI_Command (Router repr) where
441 command n x = Router_Commands $ Map.singleton n x
442 instance Ord e => CLI_Var (Router (Parser e d))
443 instance Ord e => CLI_Env (Router (Parser e d))
444 instance Ord e => CLI_Tag (Router (Parser e d)) where
445 tagged = Router_Tagged
446 instance CLI_Help (Router (Parser e d)) where
447 -- NOTE: set manually (instead of the 'Trans' default 'Router_Any')
448 -- to remove them all, since they are useless for 'Parser'
449 -- and may prevent patterns to be matched in 'router'.
453 instance Ord e => CLI_Response (Router (Parser e d))
454 instance Ord e => CLI_Routing (Router (Parser e d)) where
455 -- taggeds = Router_Taggeds
456 commands = Router_Commands
460 Router repr a b -> Router repr a b
461 router = {-debug1 "router" $-} \case
463 Router_Tagged n x -> Router_Tagged n (router x)
464 Router_Alt x y -> router x`router_Alt`router y
465 Router_Commands xs -> Router_Commands $ router <$> xs
469 -- Associate to the right
470 Router_App (router x) $
471 Router_App (router y) (router z)
472 _ -> router xy `Router_App` router z
473 Router_Union u x -> Router_Union u (router x)
475 -- | Merge/reorder alternatives if possible or default to a 'Router_Alt'.
480 Router repr (a:!:b) k
481 router_Alt = {-debug2 "router_Alt"-} go
483 -- Merge alternative commands together.
484 go (Router_Commands xs) (Router_Commands ys) =
485 xs`router_Commands`ys
487 -- Merge left first or right first, depending on which removes 'Router_Alt'.
488 go x (y`Router_Alt`z) =
489 case x`router_Alt`y of
491 case y'`router_Alt`z of
492 yz@(Router_Alt _y z') ->
493 case x'`router_Alt`z' of
494 Router_Alt{} -> router x'`Router_Alt`yz
495 xz -> Router_Union (\(a:!:(b:!:c)) -> (a:!:c):!:b) $ xz`router_Alt`y
496 -- NOTE: prioritize the merged router 'xz' over over the non-mergeable 'y'.
497 yz -> x'`router_Alt`yz
498 xy -> Router_Union (\(a:!:(b:!:c)) -> (a:!:b):!:c) $ xy`router_Alt`z
499 go (x`Router_Alt`y) z =
500 case y`router_Alt`z of
502 case x`router_Alt`y' of
503 xy@(Router_Alt x' _y) ->
504 case x'`router_Alt`z' of
505 Router_Alt{} -> xy`Router_Alt`router z'
506 xz -> Router_Union (\((a:!:b):!:c) -> (a:!:c):!:b) $ xz`router_Alt`y
507 -- NOTE: prioritize the merged router 'xz' over the non-mergeable 'y'.
508 xy -> xy`router_Alt`z'
509 yz -> Router_Union (\((a:!:b):!:c) -> a:!:(b:!:c)) $ x`router_Alt`yz
511 -- Merge through 'Router_Union'.
512 go (Router_Union u x) y = Router_Union (\(a:!:b) -> u a:!:b) (x`router_Alt`y)
513 go x (Router_Union u y) = Router_Union (\(a:!:b) -> a:!:u b) (x`router_Alt`y)
516 go x y = x`Router_Alt`y
520 Map Segment (Router repr a k) ->
521 Map Segment (Router repr b k) ->
522 Router repr (a:!:b) k
523 router_Commands xs ys =
524 -- NOTE: a little bit more complex than required
525 -- in order to merge 'Router_Union's instead of nesting them,
526 -- such that 'unTrans' 'Router_Union' applies them all at once.
529 (Map.traverseMissing $ const $ \case
531 return $ Router_Union (\(x:!:_y) -> u x) r
532 r -> return $ Router_Union (\(x:!:_y) -> x) r)
533 (Map.traverseMissing $ const $ \case
535 return $ Router_Union (\(_x:!:y) -> u y) r
536 r -> return $ Router_Union (\(_x:!:y) -> y) r)
537 (Map.zipWithAMatched $ const $ \case
538 Router_Union xu xr -> \case
539 Router_Union yu yr ->
540 return $ Router_Union (\(x:!:y) -> xu x:!:yu y) $ xr`router_Alt`yr
542 return $ Router_Union (\(a:!:b) -> xu a:!:b) $ xr`router_Alt`yr
544 Router_Union yu yr ->
545 return $ Router_Union (\(a:!:b) -> a:!:yu b) $ xr`router_Alt`yr
546 yr -> return $ xr`router_Alt`yr)
554 | ArgEnv Name String -- ^ Here only for error reporting.
555 deriving (Eq,Ord,Show)
557 lexer :: [String] -> [Arg]
560 (`evalState` False) $
563 f :: String -> StateT Bool Identity [Arg]
566 if skip then return [ArgSegment s]
570 return [ArgTagLong ""]
571 '-':'-':cs -> return [ArgTagLong cs]
572 '-':cs@(_:_) -> return $ ArgTagShort <$> cs
573 seg -> return [ArgSegment seg]
575 showArg :: Arg -> String
577 ArgTagShort t -> '-':[t]
578 ArgTagLong t -> '-':'-':t
579 ArgSegment seg -> seg
580 ArgEnv name val -> name<>"="<>val
582 showArgs :: [Arg] -> String
583 showArgs args = List.intercalate " " $ showArg <$> args
585 instance P.Stream [Arg] where
586 type Token [Arg] = Arg
587 type Tokens [Arg] = [Arg]
588 tokenToChunk Proxy = pure
589 tokensToChunk Proxy = id
590 chunkToTokens Proxy = id
591 chunkLength Proxy = List.length
592 chunkEmpty Proxy = List.null
594 take1_ (t:ts) = Just (t, ts)
596 | n <= 0 = Just ([], s)
597 | List.null s = Nothing
598 | otherwise = Just (List.splitAt n s)
599 takeWhile_ = List.span
600 showTokens Proxy = showArgs . toList
601 -- NOTE: those make no sense when parsing a command line,
602 -- and should not be called since 'P.errorBundlePretty' is not used in 'parser'.
603 reachOffset = error "BUG: reachOffset must not be used on [Arg]"
604 reachOffsetNoLine = error "BUG: reachOffsetNoLine must not be used on [Arg]"