packages feed

symantic-cli-2.0.0.20190615: Symantic/CLI/Parser.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE Rank2Types #-}
module Symantic.CLI.Parser where

import Control.Applicative (Applicative(..), Alternative(..), optional, many, some)
import Control.Arrow (first)
import Control.Monad (Monad(..), join, sequence, unless)
import Control.Monad.Trans.Class (MonadTrans(..))
import Control.Monad.Trans.Except (ExceptT(..),throwE,runExceptT)
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.Function (($), (.), id, const)
import Data.Functor (Functor(..), (<$>))
import Data.Functor.Identity (Identity(..))
import Data.Int (Int)
import Data.Map.Strict (Map)
import Data.Maybe (Maybe(..), maybe)
import Data.Monoid (Monoid(..))
import Data.Ord (Ord(..))
import Data.Semigroup (Semigroup(..))
import Data.String (String)
import System.Environment (lookupEnv)
import System.IO (IO)
import Text.Read (readEither)
import Text.Show (Show(..), ShowS, showString, showParen)
import Type.Reflection as Reflection
import qualified Data.List as List
import qualified Data.Text.Lazy.IO as TL
import qualified Data.Text.Lazy.Builder as TLB
import qualified Data.Map.Merge.Strict as Map
import qualified Data.Map.Strict as Map
import qualified Symantic.Document as Doc
import qualified System.IO as IO
-- import qualified Debug.Trace as Debug

import Symantic.CLI.API

-- * Type 'Parser'
newtype Parser d f k = Parser
 { unParser :: StateT ParserState
               (ParserCheckT [ParserError] IO)
               (f -> k) -- Reader f k
 }
instance Functor (Parser d f) where
	a2b`fmap`Parser x = Parser $ (a2b <$>) <$> x
instance Applicative (Parser d f) where
	pure = Parser . pure . const
	Parser f <*> Parser x = Parser $ (<*>) <$> f <*> x
instance Alternative (Parser d f) where
	empty = Parser $ do
		StateT $ \st ->
			throwE $ Fail st [ParserError_Alt]
	Parser x <|> Parser y = Parser $
		StateT $ \st -> do
			lift (runExceptT (runStateT x st)) >>= \case
			 Left xe | FailFatal{} <- xe -> throwE xe
			         | otherwise ->
				lift (runExceptT (runStateT y st)) >>= \case
				 Left ye -> throwE (xe<>ye)
				 Right yr -> ExceptT $ return $ Right yr
			 Right xr ->
				return xr
instance Permutable (Parser d) where
	type Permutation (Parser d) = ParserPerm d (Parser d)
	runPermutation (ParserPerm ma p) = Parser $ do
		u2p <- unParser $ optional p
		unParser $
			case u2p () of
			 Nothing -> maybe empty (Parser . return) ma
			 Just perm -> runPermutation perm
	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)

parser ::
 -- d ~ String => -- dummy d
 Router (Parser d) handlers (Response (Router (Parser d))) ->
 handlers ->
 [Arg] -> IO ()
parser api handlers args = do
	lrApp <-
		runExceptT $ runStateT
		 (unParser $ unTrans $ router api)
		 ParserState
		 { parserState_args = args
		 }
	case lrApp of
	 Left err -> IO.print err
	 Right (app, _st) -> unResponseParser $ app handlers

-- | Helper to parse the current argument.
popArg ::
 ParserError ->
 (Arg ->
  StateT ParserState (ParserCheckT [ParserError] IO) a) ->
  StateT ParserState (ParserCheckT [ParserError] IO) a
popArg errEnd f = do
	st <- get
	case parserState_args st of
	 [] -> lift $ throwE $ Fail st [errEnd]
	 curr:next -> do
		lift (lift (runExceptT (runStateT (f curr) (ParserState next)))) >>= \case
		 Left err -> lift $ throwE err
		 Right (a,st') -> do
			put st'
			return a

-- ** Type 'Arg'
data Arg
 =   ArgTagShort Char
 |   ArgTagLong Name
 |   ArgSegment Segment
 deriving (Eq,Show)

parseArgs :: [String] -> [Arg]
parseArgs 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]

-- ** Type 'ParserState'
newtype ParserState = ParserState
 { parserState_args :: [Arg]
 } deriving (Show)

-- ** Type 'Router'
type ParserCheckT e = ExceptT (Fail e)

-- ** Type 'ParserError'
data ParserError
 =   ParserError_Alt -- ^ When there is no alternative.
 |   ParserError_Arg { expectedArg :: Name,   gotArg :: Maybe Arg }
 |   ParserError_Env { expectedEnv :: Name,   gotEnv :: Maybe String }
 |   ParserError_Tag { expectedTag :: Tag,    gotArg :: Maybe Arg }
 |   ParserError_Cmd { expectedCmd :: [Name], gotCmd :: Maybe Arg }
 |   ParserError_Var { expectedVar :: Name,   gotVar :: Maybe Arg }
 |   ParserError_End {                        gotEnd :: Arg }
 deriving (Eq,Show)

-- *** Type 'RouteResult'
type RouteResult e = Either (Fail e)

-- *** Type 'Fail'
data Fail e
 =   Fail ParserState e -- ^ Keep trying other paths.
 |   FailFatal !ParserState !e -- ^ Don't try other paths.
 deriving (Show)
failState :: Fail e -> ParserState
failState (Fail st _)      = st
failState (FailFatal st _) = st
failError :: Fail e -> e
failError (Fail _st e)      = e
failError (FailFatal _st e) = e
instance Semigroup e => Semigroup (Fail e) where
	Fail _ x      <> Fail st y      = Fail      st (x<>y)
	FailFatal _ x <> Fail st _y     = FailFatal st (x{-<>y-})
	Fail _ _x     <> FailFatal st y = FailFatal st ({-x<>-}y)
	FailFatal _ x <> FailFatal st y = FailFatal st (x<>y)
instance Monoid e => Monoid (Fail e) where
	mempty = Fail (ParserState []) mempty
	mappend = (<>)

-- * Class 'FromSegment'
class FromSegment a where
	fromSegment :: Segment -> Either String a
instance FromSegment String where
	fromSegment = Right
instance FromSegment Int where
	fromSegment = readEither
instance FromSegment Bool where
	fromSegment = readEither

-- ** Type 'ParserPerm'
data ParserPerm d repr k a = ParserPerm
 { permutation_result :: !(Maybe ((a->k)->k))
 , permutation_parser :: repr () (ParserPerm d repr k a)
 }

instance (App repr, Functor (repr ())) => Functor (ParserPerm 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 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 d repr) where
	type HelpConstraint (ParserPerm d repr) d' = HelpConstraint (Parser d) d'
	program _n = id
	rule _n = id

noTransParserPerm ::
 Trans repr =>
 Functor (UnTrans repr ()) =>
 ParserPerm d (UnTrans repr) k a -> ParserPerm d repr k a
noTransParserPerm (ParserPerm a ma) = ParserPerm a (noTrans $ noTransParserPerm <$> ma)

unTransParserPerm ::
 Trans repr =>
 Functor (UnTrans repr ()) =>
 ParserPerm d repr k a -> ParserPerm 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 d repr k c -> ParserPerm d repr k c
hoistParserPerm f (ParserPerm a ma) =
	ParserPerm a (hoistParserPerm f <$> f ma)

instance App (Parser d) where
	Parser x <.> Parser y = Parser $
		x >>= \a2b -> (. a2b) <$> y
instance Alt (Parser d) where
	Parser x <!> Parser y = Parser $
		StateT $ \st -> do
			lift (runExceptT (runStateT x st)) >>= \case
			 Left xe | FailFatal{} <- xe -> throwE xe
			         | otherwise ->
				lift (runExceptT (runStateT y st)) >>= \case
				 Left ye -> throwE (xe<>ye)
				 Right yr -> ExceptT $ return $ Right $
					first (\b2k (_a:!:b) -> b2k b) yr
			 Right xr ->
				return $ first (\a2k (a:!:_b) -> a2k a) xr
	opt (Parser x) = Parser $ do
		st <- get
		lift (lift (runExceptT $ runStateT x st)) >>= \case
		 Left err -> return ($ Nothing)
		 Right (a,st') -> do
			put st'
			return (mapCont Just a)
instance AltApp (Parser d) where
	many0 (Parser x) = Parser $ concatCont <$> many x
	many1 (Parser x) = Parser $ concatCont <$> some x
instance Pro (Parser d) where
	dimap a2b _b2a (Parser r) = Parser $ (\k b2k -> k (b2k . a2b)) <$> r
instance CLI_Command (Parser d) where
	-- type CommandConstraint (Parser d) a = ()
	command "" x = x
	command n x = commands $ Map.singleton n x
instance CLI_Var (Parser d) where
	type VarConstraint (Parser d) a = FromSegment a
	var' :: forall a k. VarConstraint (Parser d) a => Name -> Parser d (a->k) k
	var' name = Parser $ do
		popArg (ParserError_Var name Nothing) $ \curr -> do
			st@ParserState{..} <- get
			case curr of
			 ArgSegment seg ->
				case fromSegment seg of
				 Left err -> lift $ throwE $ Fail st [ParserError_Var name (Just curr)]
				 Right a -> return ($ a)
			 _ -> lift $ throwE $ Fail st [ParserError_Var name (Just curr)]
	just a  = Parser $ return ($ a)
	nothing = Parser $ return id
instance CLI_Env (Parser d) where
	type EnvConstraint (Parser d) a = FromSegment a
	env' :: forall a k. EnvConstraint (Parser d) a => Name -> Parser d (a->k) k
	env' name = Parser $ do
		st <- get
		lift (lift (lookupEnv name)) >>= \case
		 Nothing -> lift $ throwE $ Fail st [ParserError_Env name Nothing]
		 Just raw ->
				case fromSegment raw of
				 Left err -> lift $ throwE $ Fail st [ParserError_Env name (Just raw)]
				 Right a -> return ($ a)

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)

instance CLI_Tag (Parser d) where
	type TagConstraint (Parser d) a = ()
	tagged name p = Parser $ do
		popArg (ParserError_Tag name Nothing) $ \curr -> do
			st <- get
			case lookupTag curr name of
			 False -> lift $ throwE $ Fail st [ParserError_Tag name (Just curr)]
			 True ->
				lift (lift (runExceptT (runStateT (unParser p) st))) >>= \case
				 Left (Fail st' e) -> lift $ throwE $ FailFatal st' e
				 Left e -> lift $ throwE e
				 Right (a,st') -> do
					put st'
					return a
		where
		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
		popArg (ParserError_Tag (TagLong "") Nothing) $ \curr -> do
			st@ParserState{..} <- get
			case curr of
			 ArgTagLong "" -> return id
			 _ -> return id -- TODO: raise an error and use option?

-- ** Type 'ParserResponse'
newtype ParserResponse = ParserResponse { unResponseParser :: IO () }
-- ** Type 'ParserResponseArgs'
newtype ParserResponseArgs a = ParserResponseArgs (IO a)
 deriving (Functor,Applicative,Monad)

instance CLI_Response (Parser d) where
	type ResponseConstraint (Parser d) a = Outputable a
	type ResponseArgs (Parser d) a = ParserResponseArgs a
	type Response (Parser d) = ParserResponse
	response' = Parser $ do
		st <- get
		unless (List.null $ parserState_args st) $ do
			lift $ throwE $ Fail st [ParserError_End $
			 List.head $ parserState_args st]
		return $ \(ParserResponseArgs io) ->
			ParserResponse $ io >>= output

-- * 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 String where
	output = IO.putStrLn

instance Outputable (Doc.AnsiText (Doc.Plain TLB.Builder)) where
	output =
		TL.putStr .
		TLB.toLazyText .
		Doc.runPlain .
		Doc.runAnsiText

{-
instance Outputable (Doc.Reorg Doc.Term) where
	output = TL.hPutStrLn IO.stdout . Doc.textTerm
instance Outputable (Doc.Reorg DocIO.TermIO) where
	output = DocIO.runTermIO IO.stdout
instance Outputable (IO.Handle, (Doc.Reorg DocIO.TermIO)) where
	output = uncurry DocIO.runTermIO
-}

-- * 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 String
instance IOType (Doc.AnsiText (Doc.Plain TLB.Builder))
{-
instance IOType (Doc.Reorg Doc.Term) where
instance IOType (Doc.Reorg DocIO.TermIO) where
instance IOType (IO.Handle, Doc.Reorg DocIO.TermIO)
-}

instance CLI_Help (Parser d) where
	type HelpConstraint (Parser d) d' = d ~ d'
	help _msg  = id
	program _n = id
	rule _n    = id


-- ** Class 'CLI_Routing'
class CLI_Routing repr where
	commands :: 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 CLI_Routing (Parser d) where
	commands cmds = Parser $ do
		st@ParserState{..} <- get
		let exp = Map.keys cmds
		popArg (ParserError_Cmd exp Nothing) $ \curr ->
			case curr of
			 ArgSegment cmd ->
				case Map.lookup cmd cmds of
				 Nothing -> lift $ throwE $ Fail st [ParserError_Cmd exp (Just curr)]
				 Just x -> unParser x
			 _ -> lift $ throwE $ Fail st [ParserError_Cmd exp (Just curr)]
	{-
	taggeds ms = Parser $ do
		st@ParserState{..} <- get
		case parserState_args of
		 [] -> lift $ throwE $ Fail st [ParserError "empty path segment"]
		 curr:next ->
			case lookupTag curr of
			 Nothing -> lift $ throwE $ Fail st [ParserError $ "expected: "<>fromString (show (Map.keys ms))<>" but got: "<>fromString (show curr)]
			 Just x -> do
				put st{parserState_args=next}
				unParser x
		where
		lookupTag (ArgTagShort x) = Map.lookup (Left x) ms
		lookupTag (ArgTagLong x) = Map.lookup (Right x) ms
		lookupTag _ = Nothing
	-}

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) -> Router repr a k
 -- | Represent 'tagged'.
 Router_Tagged :: Tag -> Router repr f k -> Router repr f k
 -- | Represent 'taggeds'.
 {-
 Router_Taggeds :: TagConstraint repr a =>
                  Map (Either Char Name) (Router repr (a -> k) k) ->
                  Router repr (a -> k) 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 Functor (Router (Parser d) f) where
	a2b`fmap`x = noTrans (a2b <$> unTrans x)
instance Applicative (Router (Parser d) f) where
	pure = noTrans . pure
	f <*> x = noTrans (unTrans f <*> unTrans x)
instance Alternative (Router (Parser d) f) where
	empty = noTrans empty
	f <|> x = noTrans (unTrans f <|> unTrans x)
instance Permutable (Router (Parser d)) where
	type Permutation (Router (Parser d)) = ParserPerm d (Router (Parser d))
	runPermutation  = noTrans . runPermutation . unTransParserPerm
	toPermutation   = noTransParserPerm . toPermutation . unTrans
	toPermDefault a = noTransParserPerm . toPermDefault a . unTrans
instance (repr ~ Parser d) => Show (Router repr a b) where
	showsPrec p = \case
	 Router_Any{} -> showString "X"
	 Router_Commands ms -> showParen (p>=10) $ showString "Commands [" . go (Map.toList ms) . 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_Command n os x -> showString n . showString " " . showsPrec 10 (permutation_parser os) . showString " " . showsPrec p x
	 Router_Tagged n x -> showsPrec 10 n . showString " " . showsPrec p x
	 {-
	 Router_Taggeds ms -> showParen (p>=10) $
		showString "taggeds [" . go (Map.toList ms) . showString "]"
		where
		go :: forall h k. [(Either Char Name, 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_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 Trans (Router (Parser d)) where
	type UnTrans (Router (Parser d)) = Parser 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_Command n os x) = command n (unTransParserPerm os) (unTrans x)
	unTrans (Router_Commands ms) = commands (unTrans <$> ms)
	unTrans (Router_Tagged n x) = tagged n (unTrans x)
	-- unTrans (Router_Taggeds ms) = taggeds (unTrans <$> ms)
	unTrans (Router_Union u x) = Parser $ (. u) <$> unParser (unTrans x)

instance App (Router (Parser d)) where
	(<.>) = Router_App
instance Alt (Router (Parser d)) where
	(<!>) = Router_Alt
instance Pro (Router (Parser d))
instance repr ~ (Parser d) => CLI_Command (Router repr) where
	-- command = Router_Command
	command "" x = x
	command n x = Router_Commands $ Map.singleton n x
instance CLI_Var (Router (Parser d))
instance CLI_Env (Router (Parser d))
instance CLI_Tag (Router (Parser d)) where
	tagged = Router_Tagged
instance CLI_Help (Router (Parser 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 CLI_Response (Router (Parser d))
instance CLI_Routing (Router (Parser d)) where
	-- taggeds  = Router_Taggeds
	commands = Router_Commands

router ::
 repr ~ Parser d =>
 Router repr a b -> Router repr a b
router = {-debug1 "router" $-} \case
 x@Router_Any{} -> x
 -- Router_Command n os x -> Router_Command n (hoistParserPerm router os) (router x)
 Router_Tagged n x -> Router_Tagged n (router x)
 {-
 Router_Tagged n x -> Router_Taggeds $
	case n of
	 Tag c s -> Map.fromList [(Left c, r), (Right s, r)]
	 TagShort c -> Map.singleton (Left c) r
	 TagLong s -> Map.singleton (Right s) r
	 where r = router x
 -}
 {-
 Router_Taggeds xs `Router_App` Router_Taggeds ys ->
	Router_Taggeds $ router <$> (xs <> ys)
 -}
 Router_Alt x y -> router x`router_Alt`router y
 Router_Commands xs -> Router_Commands $ router <$> xs
 -- Router_Taggeds xs -> Router_Taggeds $ router <$> xs
 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)
 -- Router_Merge x -> Router_Merge (router x)

-- | Merge/reorder alternatives if possible or default to a 'Router_Alt'.
router_Alt ::
 repr ~ Parser 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.
	{- NOTE: useless because 'command' is already a 'Router_Commands'.
	go (Router_Command x xo xt) (Router_Command y yo yt) =
		Map.singleton x (router (runPermutation xo <.> xt))
		`router_Commands`
		Map.singleton y (router (runPermutation yo <.> yt))
	go (Router_Command x xo xt) (Router_Commands ys) =
		Map.singleton x (router (runPermutation xo <.> xt))
		`router_Commands` ys
	go (Router_Commands xs) (Router_Command y yo yt) =
		xs `router_Commands`
		Map.singleton y (router (runPermutation yo <.> yt))
	-}
	go (Router_Commands xs) (Router_Commands ys) =
		xs`router_Commands`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 d =>
 Map Segment (Router repr a k) ->
 Map Segment (Router repr b k) ->
 Router repr (a:!:b) k
router_Commands xs ys =
	-- 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.
	Router_Commands $
	Map.merge
	 (Map.traverseMissing $ const $ \case
		 Router_Union u r ->
			return $ Router_Union (\(x:!:_y) -> u x) r
		 r -> return $ Router_Union (\(x:!:_y) -> x) r)
	 (Map.traverseMissing $ const $ \case
		 Router_Union u r ->
			return $ Router_Union (\(_x:!:y) -> u y) r
		 r -> return $ Router_Union (\(_x:!:y) -> y) r)
	 (Map.zipWithAMatched $ const $ \case
		 Router_Union xu xr -> \case
			 Router_Union yu yr ->
				return $ Router_Union (\(x:!:y) -> xu x:!:yu y) $ xr`router_Alt`yr
			 yr ->
				return $ Router_Union (\(a:!:b) -> xu a:!:b) $ xr`router_Alt`yr
		 xr -> \case
			 Router_Union yu yr ->
				return $ Router_Union (\(a:!:b) -> a:!:yu b) $ xr`router_Alt`yr
			 yr -> return $ xr`router_Alt`yr)
	 xs ys

{-
debug0 :: Show a => String -> a -> a
debug0 n a = Debug.trace (" {"<>n<>": "<>show a) a
debug1 :: Show a => Show b => String -> (a->b) -> (a->b)
debug1 n a2b a = Debug.trace ("} "<>n<>": r: "<>show b) b
	where b = a2b $ Debug.trace ("{ "<>n<>": a: "<>show a) a
debug2 :: Show a => Show b => Show c => String -> (a->b->c) -> (a->b->c)
debug2 n a2b2c a b = Debug.trace ("} "<>n<>": r: "<>show c) c
	where
	b2c = a2b2c $ Debug.trace ("{ "<>n<>": a: "<>show a) a
	c   = b2c   $ Debug.trace (n<>": b: "<>show b) b
-}