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 qualified Debug.Trace as Debug
12 import Control.Applicative (Applicative(..), Alternative(..), optional, many, some)
13 import Control.Monad (Monad(..), join, sequence, forM_)
14 import Control.Monad.Trans.Class (MonadTrans(..))
15 import Control.Monad.Trans.State (StateT(..),evalState,get,put)
17 import Data.Char (Char)
18 import Data.Either (Either(..))
19 import Data.Eq (Eq(..))
20 import Data.Foldable (toList)
21 import Data.Function (($), (.), id, const)
22 import Data.Functor (Functor(..), (<$>), ($>))
23 import Data.Functor.Identity (Identity(..))
25 import Data.Map.Strict (Map)
26 import Data.Maybe (Maybe(..), maybe)
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.Lazy as TL
45 import qualified Data.Text.Lazy.Builder as TLB
46 import qualified Data.Text.Lazy.IO as TL
47 import qualified Symantic.Document as Doc
48 import qualified System.IO as IO
49 import qualified Text.Megaparsec as P
51 import Symantic.CLI.API
54 newtype Parser e d f k = Parser
55 { unParser :: P.ParsecT e [Arg] IO (f->k) -- Reader f k
59 P.ShowErrorComponent e =>
60 Router (Parser e d) handlers (Response (Router (Parser e d))) ->
63 parser api handlers args = do
65 (unParser $ unTrans $ router api)
68 forM_ (P.bundleErrors err) $ \e ->
70 "Error parsing the command at argument #" <>
71 show (P.errorOffset e + 1) <> ":\n" <>
72 P.parseErrorTextPretty e
73 Right app -> unResponseParser $ app handlers
75 instance Functor (Parser e d f) where
76 a2b`fmap`Parser x = Parser $ (a2b <$>) <$> x
77 instance Applicative (Parser e d f) where
78 pure = Parser . pure . const
79 Parser f <*> Parser x = Parser $ (<*>) <$> f <*> x
80 instance Ord e => Alternative (Parser e d f) where
82 Parser x <|> Parser y = Parser $ x <|> y
83 instance Ord e => Permutable (Parser e d) where
84 type Permutation (Parser e d) = ParserPerm e d (Parser e d)
85 runPermutation (ParserPerm ma p) = Parser $ do
86 u2p <- unParser $ optional p
89 Just perm -> runPermutation perm
92 (Parser $ P.token (const Nothing) Set.empty)
93 -- NOTE: not 'empty' so that 'P.TrivialError' has the unexpected token.
95 toPermutation (Parser x) =
97 (Parser $ (\a () -> ParserPerm (Just a) empty) <$> x)
98 toPermDefault a (Parser x) =
99 ParserPerm (Just ($ a))
100 (Parser $ (\d () -> ParserPerm (Just d) empty) <$> x)
101 instance App (Parser e d) where
102 Parser x <.> Parser y = Parser $
103 x >>= \a2b -> (. a2b) <$> y
104 instance Ord e => Alt (Parser e d) where
105 Parser x <!> Parser y = Parser $
106 (\a2k (a:!:_b) -> a2k a) <$> P.try x <|>
107 (\b2k (_a:!:b) -> b2k b) <$> y
108 opt (Parser x) = Parser $
109 mapCont Just <$> P.try x
110 instance Ord e => AltApp (Parser e d) where
111 many0 (Parser x) = Parser $ concatCont <$> many x
112 many1 (Parser x) = Parser $ concatCont <$> some x
113 instance Pro (Parser e d) where
114 dimap a2b _b2a (Parser r) = Parser $ (\k b2k -> k (b2k . a2b)) <$> r
115 instance Ord e => CLI_Command (Parser e d) where
116 -- type CommandConstraint (Parser e d) a = ()
118 command n x = commands $ Map.singleton n x
119 instance Ord e => CLI_Tag (Parser e d) where
120 type TagConstraint (Parser e d) a = ()
121 tagged name p = Parser $ do
122 tag <- (`P.token` exp) $ \tok ->
123 if lookupTag tok name
130 TagShort t -> Set.singleton $ P.Tokens $ pure $ ArgTagShort t
131 TagLong t -> Set.singleton $ P.Tokens $ pure $ ArgTagLong t
132 Tag s l -> Set.fromList
133 [ P.Tokens $ pure $ ArgTagShort s
134 , P.Tokens $ pure $ ArgTagLong l
136 lookupTag (ArgTagShort x) (TagShort y) = x == y
137 lookupTag (ArgTagShort x) (Tag y _) = x == y
138 lookupTag (ArgTagLong x) (TagLong y) = x == y
139 lookupTag (ArgTagLong x) (Tag _ y) = x == y
140 lookupTag _ _ = False
141 endOpts = Parser $ do
142 (`P.token` exp) $ \case
143 ArgTagLong "" -> Just id
146 exp = Set.singleton $ P.Tokens $ pure $ ArgTagLong ""
147 instance Ord e => CLI_Var (Parser e d) where
148 type VarConstraint (Parser e d) a = (IOType a, FromSegment a)
149 var' :: forall a k. VarConstraint (Parser e d) a => Name -> Parser e d (a->k) k
150 var' name = Parser $ do
151 seg <- (`P.token` expName) $ \case
152 ArgSegment seg -> Just seg
154 lift (fromSegment seg) >>= \case
155 Left err -> P.failure got (expType err)
156 where got = Just $ P.Tokens $ pure $ ArgSegment seg
157 Right a -> return ($ a)
159 expName = Set.singleton $ P.Label $ NonEmpty.fromList $ "<"<>name<>">"
160 expType err = Set.singleton $ P.Label $ NonEmpty.fromList $
161 "<"<>name<>"> to be of type "<>ioType @a
163 "Prelude.read: no parse" -> ""
166 just a = Parser $ return ($ a)
167 nothing = Parser $ return id
168 instance Ord e => CLI_Env (Parser e d) where
169 type EnvConstraint (Parser e d) a = (IOType a, FromSegment a)
170 env' :: forall a k. EnvConstraint (Parser e d) a => Name -> Parser e d (a->k) k
172 lift (lookupEnv name) >>= \case
173 Nothing -> P.failure got exp
176 exp = Set.singleton $ P.Label $ NonEmpty.fromList $ "${"<>name<>"}"
178 lift (fromSegment val) >>= \case
179 Right a -> return ($ a)
180 Left err -> P.failure got exp
182 got = Just $ P.Tokens $ pure $ ArgEnv name val
183 exp = Set.singleton $ P.Label $ NonEmpty.fromList $
184 "${"<>name<>"} to be of type "<>ioType @a
186 "Prelude.read: no parse" -> ""
189 instance Ord e => CLI_Response (Parser e d) where
190 type ResponseConstraint (Parser e d) a = Outputable a
191 type ResponseArgs (Parser e d) a = ParserResponseArgs a
192 type Response (Parser e d) = ParserResponse
194 P.eof $> \(ParserResponseArgs io) ->
195 ParserResponse $ io >>= output
196 instance Ord e => CLI_Help (Parser e d) where
197 type HelpConstraint (Parser e d) d' = d ~ d'
199 program n = Parser . P.label n . unParser
200 rule n = Parser . P.label n . unParser
202 concatCont :: [(a->k)->k] -> ([a]->k)->k
203 concatCont = List.foldr (consCont (:)) ($ [])
205 consCont :: (a->b->c) -> ((a->k)->k) -> ((b->k)->k) -> (c->k)->k
206 consCont ab2c a2k2k b2k2k = \c2k -> a2k2k $ \a -> b2k2k $ \b -> c2k (ab2c a b)
208 mapCont :: (a->b) -> ((a->k)->k) -> ((b->k)->k)
209 mapCont a2b a2k2k = \b2k -> a2k2k (b2k . a2b)
211 -- ** Type 'ParserResponse'
212 newtype ParserResponse = ParserResponse { unResponseParser :: IO () }
213 -- ** Type 'ParserResponseArgs'
214 newtype ParserResponseArgs a = ParserResponseArgs (IO a)
215 deriving (Functor,Applicative,Monad)
217 -- * Class 'Outputable'
218 -- | Output of a CLI.
219 class IOType a => Outputable a where
221 default output :: Show a => a -> IO ()
224 instance Outputable String where
226 instance Outputable (Doc.AnsiText (Doc.Plain TLB.Builder)) where
234 instance Outputable (Doc.Reorg Doc.Term) where
235 output = TL.hPutStrLn IO.stdout . Doc.textTerm
236 instance Outputable (Doc.Reorg DocIO.TermIO) where
237 output = DocIO.runTermIO IO.stdout
238 instance Outputable (IO.Handle, (Doc.Reorg DocIO.TermIO)) where
239 output = uncurry DocIO.runTermIO
243 -- | Like a MIME type but for input/output of a CLI.
246 default ioType :: Reflection.Typeable a => String
247 ioType = show (Reflection.typeRep @a)
251 instance IOType Integer
252 instance IOType Natural
253 instance IOType String
254 instance IOType (Doc.AnsiText (Doc.Plain TLB.Builder))
256 instance IOType (Doc.Reorg Doc.Term) where
257 instance IOType (Doc.Reorg DocIO.TermIO) where
258 instance IOType (IO.Handle, Doc.Reorg DocIO.TermIO)
261 -- * Class 'FromSegment'
262 class FromSegment a where
263 fromSegment :: Segment -> IO (Either String a)
264 default fromSegment :: Read a => Segment -> IO (Either String a)
265 fromSegment = return . readEither
266 instance FromSegment String where
267 fromSegment = return . Right
268 instance FromSegment Text.Text where
269 fromSegment = return . Right . Text.pack
270 instance FromSegment TL.Text where
271 fromSegment = return . Right . TL.pack
272 instance FromSegment Bool
273 instance FromSegment Int
274 instance FromSegment Integer
275 instance FromSegment Natural
277 -- ** Type 'ParserPerm'
278 data ParserPerm e d repr k a = ParserPerm
279 { permutation_result :: !(Maybe ((a->k)->k))
280 , permutation_parser :: repr () (ParserPerm e d repr k a)
283 instance (App repr, Functor (repr ())) => Functor (ParserPerm e d repr k) where
284 a2b `fmap` ParserPerm a ma = ParserPerm
285 ((\a2k2k b2k -> a2k2k $ b2k . a2b) <$> a)
286 ((a2b `fmap`) `fmap` ma)
287 instance (App repr, Functor (repr ()), Alternative (repr ())) =>
288 Applicative (ParserPerm e d repr k) where
289 pure a = ParserPerm (Just ($ a)) empty
290 lhs@(ParserPerm f ma2b) <*> rhs@(ParserPerm x ma) =
291 ParserPerm a (lhsAlt <|> rhsAlt)
294 (\a2b2k2k a2k2k -> \b2k ->
295 a2b2k2k $ \a2b -> a2k2k (b2k . a2b)
297 lhsAlt = (<*> rhs) <$> ma2b
298 rhsAlt = (lhs <*>) <$> ma
299 instance CLI_Help repr => CLI_Help (ParserPerm e d repr) where
300 type HelpConstraint (ParserPerm e d repr) d' = HelpConstraint (Parser e d) d'
306 Functor (UnTrans repr ()) =>
307 ParserPerm e d (UnTrans repr) k a -> ParserPerm e d repr k a
308 noTransParserPerm (ParserPerm a ma) = ParserPerm a (noTrans $ noTransParserPerm <$> ma)
312 Functor (UnTrans repr ()) =>
313 ParserPerm e d repr k a -> ParserPerm e d (UnTrans repr) k a
314 unTransParserPerm (ParserPerm a ma) = ParserPerm a (unTransParserPerm <$> unTrans ma)
318 (forall a b. repr a b -> repr a b) ->
319 ParserPerm e d repr k c -> ParserPerm e d repr k c
320 hoistParserPerm f (ParserPerm a ma) =
321 ParserPerm a (hoistParserPerm f <$> f ma)
323 -- ** Class 'CLI_Routing'
324 class CLI_Routing repr where
325 commands :: Map Name (repr a k) -> repr a k
326 -- taggeds :: TagConstraint repr a => Map (Either Char Name) (repr (a -> k) k) -> repr (a -> k) k
327 instance Ord e => CLI_Routing (Parser e d) where
328 commands cmds = Parser $
329 P.token check exp >>= unParser
331 exp = Set.fromList $ P.Tokens . pure . ArgSegment <$> Map.keys cmds
333 ArgSegment cmd -> Map.lookup cmd cmds
337 data Router repr a b where
338 -- | Lift any @(repr)@ into 'Router', those not useful to segregate
339 -- wrt. the 'Trans'formation performed, aka. 'noTrans'.
340 Router_Any :: repr a b -> Router repr a b
341 -- | Represent 'commands'.
342 Router_Commands :: Map Name (Router repr a k) -> Router repr a k
343 -- | Represent 'tagged'.
344 Router_Tagged :: Tag -> Router repr f k -> Router repr f k
345 -- | Represent ('<.>').
346 Router_App :: Router repr a b -> Router repr b c -> Router repr a c
347 -- | Represent ('<!>').
348 Router_Alt :: Router repr a k -> Router repr b k -> Router repr (a:!:b) k
349 -- | Unify 'Router's which have different 'handlers'.
350 -- Useful to put alternative 'Router's in a 'Map' as in 'Router_Commands'.
351 Router_Union :: (b->a) -> Router repr a k -> Router repr b k
353 instance Ord e => Functor (Router (Parser e d) f) where
354 a2b`fmap`x = noTrans (a2b <$> unTrans x)
355 instance Ord e => Applicative (Router (Parser e d) f) where
356 pure = noTrans . pure
357 f <*> x = noTrans (unTrans f <*> unTrans x)
358 instance Ord e => Alternative (Router (Parser e d) f) where
359 empty = noTrans empty
360 f <|> x = noTrans (unTrans f <|> unTrans x)
361 instance Ord e => Permutable (Router (Parser e d)) where
362 type Permutation (Router (Parser e d)) = ParserPerm e d (Router (Parser e d))
363 runPermutation = noTrans . runPermutation . unTransParserPerm
364 toPermutation = noTransParserPerm . toPermutation . unTrans
365 toPermDefault a = noTransParserPerm . toPermDefault a . unTrans
366 instance (repr ~ Parser e d) => Show (Router repr a b) where
368 Router_Any{} -> showString "X"
369 Router_Commands ms -> showParen (p>=10) $ showString "Commands [" . go (Map.toList ms) . showString "]"
371 go :: forall h k. [(Segment, Router repr h k)] -> ShowS
374 (showParen True $ showString (show n<>", ") . showsPrec 0 r) .
377 _ -> showString ", " . go xs
378 Router_Tagged n x -> showsPrec 10 n . showString " " . showsPrec p x
379 Router_App x y -> showParen (p>=4) $ showsPrec 4 x . showString " <.> " . showsPrec 4 y
380 Router_Alt x y -> showParen (p>=3) $ showsPrec 3 x . showString " <!> " . showsPrec 3 y
381 Router_Union _u x -> showString "Union [" . showsPrec 0 x . showString "]"
383 instance Ord e => Trans (Router (Parser e d)) where
384 type UnTrans (Router (Parser e d)) = Parser e d
386 unTrans (Router_Any x) = x
387 unTrans (Router_Alt x y) = unTrans x <!> unTrans y
388 unTrans (Router_App x y) = unTrans x <.> unTrans y
389 unTrans (Router_Commands ms) = commands (unTrans <$> ms)
390 unTrans (Router_Tagged n x) = tagged n (unTrans x)
391 unTrans (Router_Union u x) = Parser $ (. u) <$> unParser (unTrans x)
393 instance Ord e => App (Router (Parser e d)) where
395 instance Ord e => Alt (Router (Parser e d)) where
397 instance Ord e => Pro (Router (Parser e d))
398 instance (repr ~ (Parser e d)) => CLI_Command (Router repr) where
400 command n x = Router_Commands $ Map.singleton n x
401 instance Ord e => CLI_Var (Router (Parser e d))
402 instance Ord e => CLI_Env (Router (Parser e d))
403 instance Ord e => CLI_Tag (Router (Parser e d)) where
404 tagged = Router_Tagged
405 instance CLI_Help (Router (Parser e d)) where
406 -- NOTE: set manually (instead of the 'Trans' default 'Router_Any')
407 -- to remove them all, since they are useless for 'Parser'
408 -- and may prevent patterns to be matched in 'router'.
412 instance Ord e => CLI_Response (Router (Parser e d))
413 instance Ord e => CLI_Routing (Router (Parser e d)) where
414 -- taggeds = Router_Taggeds
415 commands = Router_Commands
419 Router repr a b -> Router repr a b
420 router = {-debug1 "router" $-} \case
422 Router_Tagged n x -> Router_Tagged n (router x)
423 Router_Alt x y -> router x`router_Alt`router y
424 Router_Commands xs -> Router_Commands $ router <$> xs
428 -- Associate to the right
429 Router_App (router x) $
430 Router_App (router y) (router z)
431 _ -> router xy `Router_App` router z
432 Router_Union u x -> Router_Union u (router x)
434 -- | Merge/reorder alternatives if possible or default to a 'Router_Alt'.
439 Router repr (a:!:b) k
440 router_Alt = {-debug2 "router_Alt"-} go
442 -- Merge alternative commands together.
443 go (Router_Commands xs) (Router_Commands ys) =
444 xs`router_Commands`ys
446 -- Merge left first or right first, depending on which removes 'Router_Alt'.
447 go x (y`Router_Alt`z) =
448 case x`router_Alt`y of
450 case y'`router_Alt`z of
451 yz@(Router_Alt _y z') ->
452 case x'`router_Alt`z' of
453 Router_Alt{} -> router x'`Router_Alt`yz
454 xz -> Router_Union (\(a:!:(b:!:c)) -> (a:!:c):!:b) $ xz`router_Alt`y
455 -- NOTE: prioritize the merged router 'xz' over over the non-mergeable 'y'.
456 yz -> x'`router_Alt`yz
457 xy -> Router_Union (\(a:!:(b:!:c)) -> (a:!:b):!:c) $ xy`router_Alt`z
458 go (x`Router_Alt`y) z =
459 case y`router_Alt`z of
461 case x`router_Alt`y' of
462 xy@(Router_Alt x' _y) ->
463 case x'`router_Alt`z' of
464 Router_Alt{} -> xy`Router_Alt`router z'
465 xz -> Router_Union (\((a:!:b):!:c) -> (a:!:c):!:b) $ xz`router_Alt`y
466 -- NOTE: prioritize the merged router 'xz' over the non-mergeable 'y'.
467 xy -> xy`router_Alt`z'
468 yz -> Router_Union (\((a:!:b):!:c) -> a:!:(b:!:c)) $ x`router_Alt`yz
470 -- Merge through 'Router_Union'.
471 go (Router_Union u x) y = Router_Union (\(a:!:b) -> u a:!:b) (x`router_Alt`y)
472 go x (Router_Union u y) = Router_Union (\(a:!:b) -> a:!:u b) (x`router_Alt`y)
475 go x y = x`Router_Alt`y
479 Map Segment (Router repr a k) ->
480 Map Segment (Router repr b k) ->
481 Router repr (a:!:b) k
482 router_Commands xs ys =
483 -- NOTE: a little bit more complex than required
484 -- in order to merge 'Router_Union's instead of nesting them,
485 -- such that 'unTrans' 'Router_Union' applies them all at once.
488 (Map.traverseMissing $ const $ \case
490 return $ Router_Union (\(x:!:_y) -> u x) r
491 r -> return $ Router_Union (\(x:!:_y) -> x) r)
492 (Map.traverseMissing $ const $ \case
494 return $ Router_Union (\(_x:!:y) -> u y) r
495 r -> return $ Router_Union (\(_x:!:y) -> y) r)
496 (Map.zipWithAMatched $ const $ \case
497 Router_Union xu xr -> \case
498 Router_Union yu yr ->
499 return $ Router_Union (\(x:!:y) -> xu x:!:yu y) $ xr`router_Alt`yr
501 return $ Router_Union (\(a:!:b) -> xu a:!:b) $ xr`router_Alt`yr
503 Router_Union yu yr ->
504 return $ Router_Union (\(a:!:b) -> a:!:yu b) $ xr`router_Alt`yr
505 yr -> return $ xr`router_Alt`yr)
513 | ArgEnv Name String -- ^ Here only for error reporting.
514 deriving (Eq,Ord,Show)
516 lexer :: [String] -> [Arg]
519 (`evalState` False) $
522 f :: String -> StateT Bool Identity [Arg]
525 if skip then return [ArgSegment s]
529 return [ArgTagLong ""]
530 '-':'-':cs -> return [ArgTagLong cs]
531 '-':cs@(_:_) -> return $ ArgTagShort <$> cs
532 seg -> return [ArgSegment seg]
534 showArg :: Arg -> String
536 ArgTagShort t -> '-':[t]
537 ArgTagLong t -> '-':'-':t
538 ArgSegment seg -> seg
539 ArgEnv name val -> name<>"="<>val
541 showArgs :: [Arg] -> String
542 showArgs args = List.intercalate " " $ showArg <$> args
544 instance P.Stream [Arg] where
545 type Token [Arg] = Arg
546 type Tokens [Arg] = [Arg]
547 tokenToChunk Proxy = pure
548 tokensToChunk Proxy = id
549 chunkToTokens Proxy = id
550 chunkLength Proxy = List.length
551 chunkEmpty Proxy = List.null
553 take1_ (t:ts) = Just (t, ts)
555 | n <= 0 = Just ([], s)
556 | List.null s = Nothing
557 | otherwise = Just (List.splitAt n s)
558 takeWhile_ = List.span
559 showTokens Proxy = showArgs . toList
560 -- NOTE: those make no sense when parsing a command line,
561 -- and should not be called since 'P.errorBundlePretty' is not used in 'parser'.
562 reachOffset = error "BUG: reachOffset must not be used on [Arg]"
563 reachOffsetNoLine = error "BUG: reachOffsetNoLine must not be used on [Arg]"