packages feed

mangrove-cli 0.3.0.0 → 0.4.0.0

raw patch · 24 files changed

+1841/−1049 lines, 24 filesdep +QuickCheckdep +quickcheck-instancesdep +randomPVP ok

version bump matches the API change (PVP)

Dependencies added: QuickCheck, quickcheck-instances, random

API changes (from Hackage documentation)

- Mangrove: HelpRequest :: RequestType
- Mangrove: VersionRequest :: RequestType
- Mangrove: [Failure] :: forall (s :: Type -> Type) r. !Text -> Result s r
- Mangrove: [Response] :: forall (s :: Type -> Type) r. SupportsResponse s => !Text -> Result s r
- Mangrove: [Success] :: forall r (s :: Type -> Type). ![Text] -> !r -> Result s r
- Mangrove: class (Scheme s, RequestSupport s ~ 'True) => SupportsResponse (s :: Type -> Type)
- Mangrove: data RequestType
- Mangrove: data Result (s :: Type -> Type) r
- Mangrove: data family ReqContinuation (cap :: Bool) (s :: Type -> Type) r
- Mangrove: instance GHC.Classes.Eq r => GHC.Classes.Eq (Mangrove.Result s r)
- Mangrove: instance GHC.Show.Show r => GHC.Show.Show (Mangrove.Result s r)
- Mangrove: parseArguments :: forall (s :: Type -> Type) r a. SupportsResponse s => ProgramInfo s -> ParseTree s r -> (r -> IO a) -> IO a
- Mangrove: runArgumentParser :: forall (s :: Type -> Type) r a. Scheme s => ParseTree s r -> [Text] -> ([Text] -> r -> a) -> (Text -> a) -> RequestHandler s a -> a
- Mangrove: runArgumentParser' :: forall (s :: Type -> Type) r a. Scheme s => ParseTree s r -> StreamState s -> ([Text] -> r -> a) -> (Text -> a) -> RequestHandler s a -> a
- Mangrove: runHelpfulParser :: forall (s :: Type -> Type) r. SupportsResponse s => ProgramInfo s -> ParseTree s r -> [Text] -> Result s r
- Mangrove: runHelpfulParser' :: forall (s :: Type -> Type) r. SupportsResponse s => ProgramInfo s -> ParseTree s r -> StreamState s -> Result s r
- Mangrove: runHelpfulParser_ :: forall (s :: Type -> Type) r. SupportsResponse s => ParseTree s r -> [Text] -> Result s r
- Mangrove: runSilentParser :: forall (s :: Type -> Type) r. (Scheme s, RequestSupport s ~ 'False) => ParseTree s r -> [Text] -> Result s r
- Mangrove: runSilentParser' :: forall (s :: Type -> Type) r. (Scheme s, RequestSupport s ~ 'False) => ParseTree s r -> StreamState s -> Result s r
- Mangrove: type RequestHandler (s :: Type -> Type) r = ReqContinuation RequestSupport s s r
- Mangrove.Parser: --
- Mangrove.Parser: -- <a>SupportsResponse</a> instance should be provided for
- Mangrove.Parser: -- <tt>scheme</tt>.
- Mangrove.Parser: -- When <tt>RequestSupport scheme</tt> is <tt>True</tt>, a
- Mangrove.Parser: -- information.
- Mangrove.Parser: -- under this parsing scheme.
- Mangrove.Parser: -- | This type indicates whether a parsing scheme accepts requests for
- Mangrove.Parser: HelpRequest :: RequestType
- Mangrove.Parser: ProgramInfo :: !Text -> !Version -> !Text -> ProgramInfo (s :: Type -> Type)
- Mangrove.Parser: StreamHandler :: (StreamState s -> a -> r) -> (StreamState s -> r) -> (StreamState s -> Builder -> r) -> RequestHandler s r -> StreamHandler (s :: Type -> Type) a r
- Mangrove.Parser: StreamParser :: (forall r. () => StreamHandler s a r -> StreamState s -> r) -> StreamParser (s :: Type -> Type) a
- Mangrove.Parser: StreamState :: ![Text] -> ![Token s] -> !Bool -> StreamState (s :: Type -> Type)
- Mangrove.Parser: VersionRequest :: RequestType
- Mangrove.Parser: [EmptyNode] :: forall (scheme :: Type -> Type) r. ParseTree scheme r
- Mangrove.Parser: [ManyNode] :: forall (scheme :: Type -> Type) r1. !Bool -> ParseTree scheme r1 -> ParseTree scheme [r1]
- Mangrove.Parser: [ParseNode] :: forall (scheme :: Type -> Type) r. scheme r -> ParseTree scheme r
- Mangrove.Parser: [ProdNode] :: forall u v r (scheme :: Type -> Type). !u -> v -> r -> ParseTree scheme u -> ParseTree scheme v -> ParseTree scheme r
- Mangrove.Parser: [SumNode] :: forall (scheme :: Type -> Type) r. ParseTree scheme r -> ParseTree scheme r -> ParseTree scheme r
- Mangrove.Parser: [ValueNode] :: forall r (scheme :: Type -> Type). !r -> ParseTree scheme r
- Mangrove.Parser: [onEmpty] :: StreamHandler (s :: Type -> Type) a r -> StreamState s -> r
- Mangrove.Parser: [onFailure] :: StreamHandler (s :: Type -> Type) a r -> StreamState s -> Builder -> r
- Mangrove.Parser: [onRequest] :: StreamHandler (s :: Type -> Type) a r -> RequestHandler s r
- Mangrove.Parser: [onSuccess] :: StreamHandler (s :: Type -> Type) a r -> StreamState s -> a -> r
- Mangrove.Parser: [programDesc] :: ProgramInfo (s :: Type -> Type) -> !Text
- Mangrove.Parser: [programName] :: ProgramInfo (s :: Type -> Type) -> !Text
- Mangrove.Parser: [programVersion] :: ProgramInfo (s :: Type -> Type) -> !Version
- Mangrove.Parser: [runStreamParser] :: StreamParser (s :: Type -> Type) a -> forall r. () => StreamHandler s a r -> StreamState s -> r
- Mangrove.Parser: [streamContent] :: StreamState (s :: Type -> Type) -> ![Text]
- Mangrove.Parser: [streamContext] :: StreamState (s :: Type -> Type) -> ![Token s]
- Mangrove.Parser: [streamEscaped] :: StreamState (s :: Type -> Type) -> !Bool
- Mangrove.Parser: activate :: Scheme s => s r -> StreamParser s r
- Mangrove.Parser: class (Functor s, Resolve s, Eq Token s, Render Token s, Show Token s) => Scheme (s :: Type -> Type) where {
- Mangrove.Parser: class (Scheme s, RequestSupport s ~ 'True) => SupportsResponse (s :: Type -> Type)
- Mangrove.Parser: data ParseTree (scheme :: Type -> Type) r
- Mangrove.Parser: data ProgramInfo (s :: Type -> Type)
- Mangrove.Parser: data RequestType
- Mangrove.Parser: data StreamHandler (s :: Type -> Type) a r
- Mangrove.Parser: data StreamState (s :: Type -> Type)
- Mangrove.Parser: data Token (s :: Type -> Type);
- Mangrove.Parser: data family ReqContinuation (cap :: Bool) (s :: Type -> Type) r
- Mangrove.Parser: delimiter :: Scheme s => Proxy s -> Char
- Mangrove.Parser: formatError :: Render tok => [tok] -> Builder -> Text
- Mangrove.Parser: getContext :: forall (s :: Type -> Type). StreamParser s [Token s]
- Mangrove.Parser: getEscaped :: forall (s :: Type -> Type). StreamParser s Bool
- Mangrove.Parser: instance (Mangrove.Valency.Valency s, Mangrove.Parser.Scheme s) => Mangrove.Text.Render (Mangrove.Parser.ParseTree s r)
- Mangrove.Parser: instance Control.Monad.Error.Class.MonadError Data.Text.Internal.Builder.Builder (Mangrove.Parser.StreamParser s)
- Mangrove.Parser: instance GHC.Base.Alternative (Mangrove.Parser.StreamParser s)
- Mangrove.Parser: instance GHC.Base.Applicative (Mangrove.Parser.StreamParser s)
- Mangrove.Parser: instance GHC.Base.Functor (Mangrove.Parser.ReqContinuation 'GHC.Types.False s)
- Mangrove.Parser: instance GHC.Base.Functor (Mangrove.Parser.ReqContinuation 'GHC.Types.True s)
- Mangrove.Parser: instance GHC.Base.Functor (Mangrove.Parser.StreamParser s)
- Mangrove.Parser: instance GHC.Base.Functor p => GHC.Base.Alternative (Mangrove.Parser.ParseTree p)
- Mangrove.Parser: instance GHC.Base.Functor p => GHC.Base.Applicative (Mangrove.Parser.ParseTree p)
- Mangrove.Parser: instance GHC.Base.Functor p => GHC.Base.Functor (Mangrove.Parser.ParseTree p)
- Mangrove.Parser: instance GHC.Base.Monad (Mangrove.Parser.StreamParser s)
- Mangrove.Parser: instance GHC.Classes.Eq Mangrove.Parser.RequestType
- Mangrove.Parser: instance GHC.Show.Show (Mangrove.Parser.ProgramInfo s)
- Mangrove.Parser: instance GHC.Show.Show Mangrove.Parser.RequestType
- Mangrove.Parser: instance Mangrove.Parser.Scheme s => GHC.Classes.Eq (Mangrove.Parser.StreamState s)
- Mangrove.Parser: instance Mangrove.Parser.Scheme s => GHC.Show.Show (Mangrove.Parser.StreamState s)
- Mangrove.Parser: instance Mangrove.Resolve.Resolve s => Mangrove.Resolve.Resolve (Mangrove.Parser.ParseTree s)
- Mangrove.Parser: instance Mangrove.Valency.Valency s => Mangrove.Valency.Valency (Mangrove.Parser.ParseTree s)
- Mangrove.Parser: isChoice :: forall (s :: Type -> Type) r. Valency s => ParseTree s r -> Bool
- Mangrove.Parser: isOptional :: forall (s :: Type -> Type) r. Valency s => ParseTree s r -> Bool
- Mangrove.Parser: isProduct :: forall (s :: Type -> Type) r. ParseTree s r -> Bool
- Mangrove.Parser: isSum :: forall (s :: Type -> Type) r. ParseTree s r -> Bool
- Mangrove.Parser: makeHelpInfo :: SupportsResponse s => ParseTree s r -> [Token s] -> ProgramInfo s -> Text
- Mangrove.Parser: makeVersionInfo :: SupportsResponse s => ProgramInfo s -> Text
- Mangrove.Parser: newtype StreamParser (s :: Type -> Type) a
- Mangrove.Parser: parseSpecials :: Scheme s => StreamParser s ()
- Mangrove.Parser: peek :: forall (s :: Type -> Type). StreamParser s Text
- Mangrove.Parser: peekMaybe :: forall (s :: Type -> Type). StreamParser s (Maybe Text)
- Mangrove.Parser: pop :: forall (s :: Type -> Type). StreamParser s Text
- Mangrove.Parser: popMaybe :: forall (s :: Type -> Type). StreamParser s (Maybe Text)
- Mangrove.Parser: pop_ :: forall (s :: Type -> Type). StreamParser s ()
- Mangrove.Parser: push :: forall (s :: Type -> Type). Text -> StreamParser s ()
- Mangrove.Parser: request :: forall (s :: Type -> Type) a. RequestSupport s ~ 'True => RequestType -> StreamParser s a
- Mangrove.Parser: setContext :: forall (s :: Type -> Type). [Token s] -> StreamParser s ()
- Mangrove.Parser: setEscaped :: forall (s :: Type -> Type). Bool -> StreamParser s ()
- Mangrove.Parser: type RequestHandler (s :: Type -> Type) r = ReqContinuation RequestSupport s s r
- Mangrove.Parser: type RequestSupport s :: Type -> Type = 'False;
- Mangrove.Parser: usageInfo :: Scheme s => s r -> Builder
- Mangrove.Parser: withContext :: forall (s :: Type -> Type) a. Token s -> StreamParser s a -> StreamParser s a
- Mangrove.Parser: }
- Mangrove.Scheme.Sub: instance GHC.Classes.Eq (Mangrove.Parser.Token Mangrove.Scheme.Sub.SubScheme)
- Mangrove.Scheme.Sub: instance GHC.Show.Show (Mangrove.Parser.Token Mangrove.Scheme.Sub.SubScheme)
- Mangrove.Scheme.Sub: instance Mangrove.Parser.Scheme Mangrove.Scheme.Sub.SubScheme
- Mangrove.Scheme.Sub: instance Mangrove.Text.Render (Mangrove.Parser.Token Mangrove.Scheme.Sub.SubScheme)
- Mangrove.Scheme.Unix: instance GHC.Classes.Eq (Mangrove.Parser.Token Mangrove.Scheme.Unix.UnixScheme)
- Mangrove.Scheme.Unix: instance GHC.Show.Show (Mangrove.Parser.Token Mangrove.Scheme.Unix.UnixScheme)
- Mangrove.Scheme.Unix: instance Mangrove.Parser.Scheme Mangrove.Scheme.Unix.UnixScheme
- Mangrove.Scheme.Unix: instance Mangrove.Parser.SupportsResponse Mangrove.Scheme.Unix.UnixScheme
- Mangrove.Scheme.Unix: instance Mangrove.Text.Render (Mangrove.Parser.Token Mangrove.Scheme.Unix.UnixScheme)
- Mangrove.Scheme.Unix: instance Mangrove.Text.Render Mangrove.Scheme.Unix.Flag
- Mangrove.Text: between :: Monoid m => m -> m -> m -> m
- Mangrove.Text: braces :: Builder -> Builder
- Mangrove.Text: brackets :: Builder -> Builder
- Mangrove.Text: class Render a
- Mangrove.Text: data Builder
- Mangrove.Text: hPutBuilder :: Handle -> Builder -> IO ()
- Mangrove.Text: instance Mangrove.Text.Render Data.Text.Internal.Builder.Builder
- Mangrove.Text: instance Mangrove.Text.Render Data.Text.Internal.Text
- Mangrove.Text: instance Mangrove.Text.Render GHC.Base.String
- Mangrove.Text: instance Mangrove.Text.Render GHC.Types.Char
- Mangrove.Text: keyEqualsValue :: Text -> Maybe (Text, Text)
- Mangrove.Text: putBuilder :: Builder -> IO ()
- Mangrove.Text: render :: Render a => a -> Builder
- Mangrove.Text: renderDelimitedIf :: Render a => (Builder -> Builder) -> (a -> Bool) -> a -> Builder
- Mangrove.Text: renderLazyText :: Render a => a -> Text
- Mangrove.Text: renderText :: Render a => a -> Text
+ Mangrove.ParseTree: [EmptyNode] :: forall (scheme :: Type -> Type) r. ParseTree scheme r
+ Mangrove.ParseTree: [ManyNode] :: forall (scheme :: Type -> Type) r1. !Bool -> ParseTree scheme r1 -> ParseTree scheme [r1]
+ Mangrove.ParseTree: [ParseNode] :: forall (scheme :: Type -> Type) r. scheme r -> ParseTree scheme r
+ Mangrove.ParseTree: [ProdNode] :: forall u v r (scheme :: Type -> Type). !u -> v -> r -> ParseTree scheme u -> ParseTree scheme v -> ParseTree scheme r
+ Mangrove.ParseTree: [SumNode] :: forall (scheme :: Type -> Type) r. ParseTree scheme r -> ParseTree scheme r -> ParseTree scheme r
+ Mangrove.ParseTree: [ValueNode] :: forall r (scheme :: Type -> Type). !r -> ParseTree scheme r
+ Mangrove.ParseTree: data ParseTree (scheme :: Type -> Type) r
+ Mangrove.ParseTree: instance (Mangrove.Valency.Valency s, Mangrove.Token.HasTokens s, forall a. Mangrove.Render.Render (s a)) => Mangrove.Render.Render (Mangrove.ParseTree.ParseTree s r)
+ Mangrove.ParseTree: instance (forall a. GHC.Show.Show (s a)) => GHC.Show.Show (Mangrove.ParseTree.ParseTree s r)
+ Mangrove.ParseTree: instance GHC.Base.Functor p => GHC.Base.Alternative (Mangrove.ParseTree.ParseTree p)
+ Mangrove.ParseTree: instance GHC.Base.Functor p => GHC.Base.Applicative (Mangrove.ParseTree.ParseTree p)
+ Mangrove.ParseTree: instance GHC.Base.Functor p => GHC.Base.Functor (Mangrove.ParseTree.ParseTree p)
+ Mangrove.ParseTree: instance Mangrove.Resolve.Resolve s => Mangrove.Resolve.Resolve (Mangrove.ParseTree.ParseTree s)
+ Mangrove.ParseTree: instance Mangrove.Valency.Valency s => Mangrove.Valency.Valency (Mangrove.ParseTree.ParseTree s)
+ Mangrove.ParseTree: isChoice :: forall (s :: Type -> Type) r. Valency s => ParseTree s r -> Bool
+ Mangrove.ParseTree: isOptional :: forall (s :: Type -> Type) r. Valency s => ParseTree s r -> Bool
+ Mangrove.ParseTree: isProduct :: forall (s :: Type -> Type) r. ParseTree s r -> Bool
+ Mangrove.ParseTree: isSum :: forall (s :: Type -> Type) r. ParseTree s r -> Bool
+ Mangrove.Parser: Failure :: !Text -> Result req a
+ Mangrove.Parser: Request :: !req -> Result req a
+ Mangrove.Parser: Success :: ![Text] -> !a -> Result req a
+ Mangrove.Parser: data Result req a
+ Mangrove.Parser: instance (GHC.Classes.Eq a, GHC.Classes.Eq req) => GHC.Classes.Eq (Mangrove.Parser.Result req a)
+ Mangrove.Parser: instance (GHC.Show.Show a, GHC.Show.Show req) => GHC.Show.Show (Mangrove.Parser.Result req a)
+ Mangrove.Parser: instance GHC.Base.Functor (Mangrove.Parser.Result req)
+ Mangrove.Parser: parseArguments :: forall (s :: Type -> Type) r a. Scheme s => ProgramInfo -> ParseTree s r -> (r -> IO a) -> IO a
+ Mangrove.Parser: runArgumentParser :: forall (s :: Type -> Type) r. Scheme s => ParseTree s r -> [Text] -> Result (Request s) r
+ Mangrove.Parser: runArgumentParser' :: forall (s :: Type -> Type) r. Scheme s => ParseTree s r -> StreamState (Token s) -> Result (Request s) r
+ Mangrove.Render: between :: Monoid m => m -> m -> m -> m
+ Mangrove.Render: braces :: Builder -> Builder
+ Mangrove.Render: brackets :: Builder -> Builder
+ Mangrove.Render: class Render a
+ Mangrove.Render: data Builder
+ Mangrove.Render: hPutBuilder :: Handle -> Builder -> IO ()
+ Mangrove.Render: instance Mangrove.Render.Render Data.Text.Internal.Builder.Builder
+ Mangrove.Render: instance Mangrove.Render.Render Data.Text.Internal.Text
+ Mangrove.Render: instance Mangrove.Render.Render GHC.Base.String
+ Mangrove.Render: instance Mangrove.Render.Render GHC.Types.Char
+ Mangrove.Render: putBuilder :: Builder -> IO ()
+ Mangrove.Render: quotes :: Builder -> Builder
+ Mangrove.Render: render :: Render a => a -> Builder
+ Mangrove.Render: renderDelimitedIf :: Render a => (Builder -> Builder) -> (a -> Bool) -> a -> Builder
+ Mangrove.Render: renderLazyText :: Render a => a -> Text
+ Mangrove.Render: renderText :: Render a => a -> Text
+ Mangrove.Scheme: -- <a>Void</a> if requests are unsupported.
+ Mangrove.Scheme: -- | What type of requests does this scheme support? This should be
+ Mangrove.Scheme: ProgramInfo :: !Text -> !Text -> !Version -> ProgramInfo
+ Mangrove.Scheme: [programDesc] :: ProgramInfo -> !Text
+ Mangrove.Scheme: [programName] :: ProgramInfo -> !Text
+ Mangrove.Scheme: [programVersion] :: ProgramInfo -> !Version
+ Mangrove.Scheme: activate :: Scheme s => s r -> StreamParser (Request s) (Token s) r
+ Mangrove.Scheme: class (Functor s, HasTokens s, Resolve s) => Scheme (s :: Type -> Type) where {
+ Mangrove.Scheme: data ProgramInfo
+ Mangrove.Scheme: instance GHC.Show.Show Mangrove.Scheme.ProgramInfo
+ Mangrove.Scheme: parseSpecials :: Scheme s => StreamParser (Request s) (Token s) ()
+ Mangrove.Scheme: respond :: Scheme s => Request s -> ParseTree s r -> ProgramInfo -> Text
+ Mangrove.Scheme: type Request (s :: Type -> Type);
+ Mangrove.Scheme: }
+ Mangrove.Scheme.Common: keyEqualsValue :: Text -> Maybe (Text, Text)
+ Mangrove.Scheme.Sub: instance GHC.Classes.Eq (Mangrove.Token.Token Mangrove.Scheme.Sub.SubScheme)
+ Mangrove.Scheme.Sub: instance GHC.Generics.Generic (Mangrove.Token.Token Mangrove.Scheme.Sub.SubScheme)
+ Mangrove.Scheme.Sub: instance GHC.Show.Show (Mangrove.Scheme.Sub.SubScheme r)
+ Mangrove.Scheme.Sub: instance GHC.Show.Show (Mangrove.Token.Token Mangrove.Scheme.Sub.SubScheme)
+ Mangrove.Scheme.Sub: instance Mangrove.Render.Render (Mangrove.Scheme.Sub.SubScheme r)
+ Mangrove.Scheme.Sub: instance Mangrove.Render.Render (Mangrove.Token.Token Mangrove.Scheme.Sub.SubScheme)
+ Mangrove.Scheme.Sub: instance Mangrove.Scheme.Scheme Mangrove.Scheme.Sub.SubScheme
+ Mangrove.Scheme.Sub: instance Mangrove.Token.HasTokens Mangrove.Scheme.Sub.SubScheme
+ Mangrove.Scheme.Unix: HelpRequest :: [Text] -> UnixRequest
+ Mangrove.Scheme.Unix: VersionRequest :: UnixRequest
+ Mangrove.Scheme.Unix: data UnixRequest
+ Mangrove.Scheme.Unix: helpRequest :: UnixRequest'
+ Mangrove.Scheme.Unix: instance GHC.Classes.Eq (Mangrove.Token.Token Mangrove.Scheme.Unix.UnixScheme)
+ Mangrove.Scheme.Unix: instance GHC.Classes.Eq Mangrove.Scheme.Unix.UnixRequest
+ Mangrove.Scheme.Unix: instance GHC.Generics.Generic (Mangrove.Token.Token Mangrove.Scheme.Unix.UnixScheme)
+ Mangrove.Scheme.Unix: instance GHC.Generics.Generic Mangrove.Scheme.Unix.Flag
+ Mangrove.Scheme.Unix: instance GHC.Generics.Generic Mangrove.Scheme.Unix.UnixRequest
+ Mangrove.Scheme.Unix: instance GHC.Show.Show (Mangrove.Scheme.Unix.UnixScheme r)
+ Mangrove.Scheme.Unix: instance GHC.Show.Show (Mangrove.Token.Token Mangrove.Scheme.Unix.UnixScheme)
+ Mangrove.Scheme.Unix: instance GHC.Show.Show Mangrove.Scheme.Unix.UnixRequest
+ Mangrove.Scheme.Unix: instance Mangrove.Render.Render (Mangrove.Scheme.Unix.UnixScheme r)
+ Mangrove.Scheme.Unix: instance Mangrove.Render.Render (Mangrove.Token.Token Mangrove.Scheme.Unix.UnixScheme)
+ Mangrove.Scheme.Unix: instance Mangrove.Render.Render Mangrove.Scheme.Unix.Flag
+ Mangrove.Scheme.Unix: instance Mangrove.Scheme.Scheme Mangrove.Scheme.Unix.UnixScheme
+ Mangrove.Scheme.Unix: instance Mangrove.Token.HasTokens Mangrove.Scheme.Unix.UnixScheme
+ Mangrove.Scheme.Unix: type UnixRequest' = [Token UnixScheme] -> UnixRequest
+ Mangrove.Scheme.Unix: versionRequest :: UnixRequest'
+ Mangrove.Stream: StreamHandler :: (StreamState tok -> a -> r) -> (StreamState tok -> r) -> (StreamState tok -> Builder -> r) -> (StreamState tok -> req -> r) -> StreamHandler req tok a r
+ Mangrove.Stream: StreamParser :: (forall r. () => StreamHandler req tok a r -> StreamState tok -> r) -> StreamParser req tok a
+ Mangrove.Stream: StreamState :: ![Text] -> ![tok] -> !Bool -> StreamState tok
+ Mangrove.Stream: [onEmpty] :: StreamHandler req tok a r -> StreamState tok -> r
+ Mangrove.Stream: [onFailure] :: StreamHandler req tok a r -> StreamState tok -> Builder -> r
+ Mangrove.Stream: [onRequest] :: StreamHandler req tok a r -> StreamState tok -> req -> r
+ Mangrove.Stream: [onSuccess] :: StreamHandler req tok a r -> StreamState tok -> a -> r
+ Mangrove.Stream: [runStreamParser] :: StreamParser req tok a -> forall r. () => StreamHandler req tok a r -> StreamState tok -> r
+ Mangrove.Stream: [streamContent] :: StreamState tok -> ![Text]
+ Mangrove.Stream: [streamContext] :: StreamState tok -> ![tok]
+ Mangrove.Stream: [streamEscaped] :: StreamState tok -> !Bool
+ Mangrove.Stream: data StreamHandler req tok a r
+ Mangrove.Stream: data StreamState tok
+ Mangrove.Stream: failure :: Builder -> StreamParser req tok a
+ Mangrove.Stream: formatError :: Render tok => [tok] -> Builder -> Text
+ Mangrove.Stream: getContent :: StreamParser req tok [Text]
+ Mangrove.Stream: getContext :: StreamParser req tok [tok]
+ Mangrove.Stream: getEscaped :: StreamParser req tok Bool
+ Mangrove.Stream: instance Control.Monad.Error.Class.MonadError Data.Text.Internal.Builder.Builder (Mangrove.Stream.StreamParser req tok)
+ Mangrove.Stream: instance GHC.Base.Alternative (Mangrove.Stream.StreamParser req tok)
+ Mangrove.Stream: instance GHC.Base.Applicative (Mangrove.Stream.StreamParser req tok)
+ Mangrove.Stream: instance GHC.Base.Functor (Mangrove.Stream.StreamHandler req tok a)
+ Mangrove.Stream: instance GHC.Base.Functor (Mangrove.Stream.StreamParser req tok)
+ Mangrove.Stream: instance GHC.Base.Monad (Mangrove.Stream.StreamParser req tok)
+ Mangrove.Stream: instance GHC.Classes.Eq tok => GHC.Classes.Eq (Mangrove.Stream.StreamState tok)
+ Mangrove.Stream: instance GHC.Generics.Generic (Mangrove.Stream.StreamState tok)
+ Mangrove.Stream: instance GHC.Show.Show tok => GHC.Show.Show (Mangrove.Stream.StreamState tok)
+ Mangrove.Stream: newtype StreamParser req tok a
+ Mangrove.Stream: peek :: StreamParser req tok Text
+ Mangrove.Stream: peekMaybe :: StreamParser req tok (Maybe Text)
+ Mangrove.Stream: pop :: StreamParser req tok Text
+ Mangrove.Stream: popMaybe :: StreamParser req tok (Maybe Text)
+ Mangrove.Stream: pop_ :: StreamParser req tok ()
+ Mangrove.Stream: push :: Text -> StreamParser req tok ()
+ Mangrove.Stream: request :: req -> StreamParser req tok a
+ Mangrove.Stream: setContext :: [tok] -> StreamParser req tok ()
+ Mangrove.Stream: setEscaped :: Bool -> StreamParser req tok ()
+ Mangrove.Stream: withContext :: tok -> StreamParser req tok a -> StreamParser req tok a
+ Mangrove.TextParser: instance GHC.Show.Show (Mangrove.TextParser.TextParser r)
+ Mangrove.TextParser: parseFilePath :: TextParser FilePath
+ Mangrove.TextParser: showsTextParser :: TextParser a -> ShowS
+ Mangrove.Token: -- | A token represents a particular interpretation of an argument string.
+ Mangrove.Token: class (Eq Token s, Show Token s, Render Token s) => HasTokens (s :: Type -> Type) where {
+ Mangrove.Token: data Token (s :: Type -> Type);
+ Mangrove.Token: delimiter :: HasTokens s => Proxy s -> Char
+ Mangrove.Token: }
+ Mangrove.Unix: HelpRequest :: [Text] -> UnixRequest
+ Mangrove.Unix: VersionRequest :: UnixRequest
+ Mangrove.Unix: data UnixRequest
+ Mangrove.Unix: helpRequest :: UnixRequest'
+ Mangrove.Unix: type UnixRequest' = [Token UnixScheme] -> UnixRequest
+ Mangrove.Unix: versionRequest :: UnixRequest'
- Mangrove: ProgramInfo :: !Text -> !Version -> !Text -> ProgramInfo (s :: Type -> Type)
+ Mangrove: ProgramInfo :: !Text -> !Text -> !Version -> ProgramInfo
- Mangrove: [programDesc] :: ProgramInfo (s :: Type -> Type) -> !Text
+ Mangrove: [programDesc] :: ProgramInfo -> !Text
- Mangrove: [programName] :: ProgramInfo (s :: Type -> Type) -> !Text
+ Mangrove: [programName] :: ProgramInfo -> !Text
- Mangrove: [programVersion] :: ProgramInfo (s :: Type -> Type) -> !Version
+ Mangrove: [programVersion] :: ProgramInfo -> !Version
- Mangrove: class (Functor s, Resolve s, Eq Token s, Render Token s, Show Token s) => Scheme (s :: Type -> Type)
+ Mangrove: class (Functor s, HasTokens s, Resolve s) => Scheme (s :: Type -> Type)
- Mangrove: data ProgramInfo (s :: Type -> Type)
+ Mangrove: data ProgramInfo
- Mangrove: data StreamState (s :: Type -> Type)
+ Mangrove: data StreamState tok
- Mangrove.Parser: satiate :: forall (s :: Type -> Type) r. Scheme s => ParseTree s r -> StreamParser s (ParseTree s r)
+ Mangrove.Parser: satiate :: forall (s :: Type -> Type) r. Scheme s => ParseTree s r -> StreamParser (Request s) (Token s) (ParseTree s r)
- Mangrove.Scheme.Unix: RequestOption :: !OptionInfo -> !RequestType -> UnixScheme r
+ Mangrove.Scheme.Unix: RequestOption :: !OptionInfo -> !UnixRequest' -> UnixScheme r
- Mangrove.Scheme.Unix: renderHelp :: ParseTree UnixScheme r -> [Token UnixScheme] -> Builder
+ Mangrove.Scheme.Unix: renderHelp :: ParseTree UnixScheme r -> [Text] -> Builder
- Mangrove.Unix: requestOption :: NonEmpty Flag -> Text -> RequestType -> UnixParser a
+ Mangrove.Unix: requestOption :: NonEmpty Flag -> Text -> UnixRequest' -> UnixParser a

Files

CHANGELOG.md view
@@ -8,6 +8,30 @@  ## Unreleased +## 0.4.0.0 - 2026-09-13++### Added++- Show instances for data structures related to parser trees+- A TextParser for paths (FilePath)+- 'Request' type family associated with Scheme class+- Property based tests using QuickCheck++### Changed++- Split Mangrove.Parser module back into several modules (ParseTree,+  Scheme, Stream, Token)+- Move the contents of Mangrove module to Mangrove.Parser, then+  re-export them from the Mangrove module+- Parameterize the StreamParser monad by request type+- Drop phantom type parameter from ProgramInfo++### Removed++- usageInfo method of Scheme class+- HelpContinuation family+- HelpHandler type alias+ ## 0.3.0.0 - 2026-08-27  ### Added
mangrove-cli.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           mangrove-cli-version:        0.3.0.0+version:        0.4.0.0 synopsis:       Build CLI argument parsers using Applicative. description:    Please see the README on GitHub at <https://github.com/quytelda/mangrove#readme> category:       CLI, Options, Parsing@@ -17,7 +17,7 @@ license:        BSD-3-Clause license-file:   LICENSE build-type:     Simple-extra-source-files:+extra-doc-files:     README.md     CHANGELOG.md @@ -29,11 +29,16 @@   exposed-modules:       Mangrove       Mangrove.Parser+      Mangrove.ParseTree+      Mangrove.Render       Mangrove.Resolve+      Mangrove.Scheme+      Mangrove.Scheme.Common       Mangrove.Scheme.Sub       Mangrove.Scheme.Unix-      Mangrove.Text+      Mangrove.Stream       Mangrove.TextParser+      Mangrove.Token       Mangrove.Unix       Mangrove.Valency   other-modules:@@ -55,9 +60,13 @@   type: exitcode-stdio-1.0   main-is: Main.hs   other-modules:+      Arbitrary       General-      Mangrove.ParserSpec+      Mangrove.ParseTreeSpec+      Mangrove.StreamSpec+      Mangrove.Test.Stream       Spec+      StructureEq       TestParsers       Paths_mangrove_cli   autogen-modules:@@ -68,11 +77,14 @@   build-tool-depends:       hspec-discover:hspec-discover >=2.8.5 && <3   build-depends:-      base >=4.7 && <5+      QuickCheck >=2.14.2 && <2.18+    , base >=4.7 && <5     , containers >=0.6.4 && <0.9     , hspec >=2.8.5 && <3     , mangrove-cli     , mtl >=2.2.2 && <2.4+    , quickcheck-instances >=0.3.28 && <0.5+    , random >=1.2.1.1 && <1.3     , text >=1.2.5 && <2.2     , transformers >=0.5.6 && <0.7   default-language: Haskell2010
src/Mangrove.hs view
@@ -1,200 +1,24 @@-{-# LANGUAGE DataKinds          #-}-{-# LANGUAGE FlexibleContexts   #-}-{-# LANGUAGE GADTs              #-}-{-# LANGUAGE OverloadedStrings  #-}-{-# LANGUAGE StandaloneDeriving #-}-{-# LANGUAGE TypeOperators      #-}- {-| Module      : Mangrove Copyright   : (c) Quytelda Kahja, 2026 License     : BSD-3-Clause -This module contains types and functions necessary for running-argument parsers.+This module exports the full API required for running any generic+parser. For constructing parsers, you'll need to import the building+blocks for the specific kind of parser you are building. For example,+"Mangrove.Unix" contains the tools for building UNIX-style parsers. -} module Mangrove-  ( -- * Standard Interface-    parseArguments+  ( module Mangrove.Parser -    -- * Types-  , ProgramInfo(..)+  -- * Re-exported Types   , ParseTree   , Scheme-  , Result(..)-  , SupportsResponse+  , ProgramInfo(..)   , StreamState-  , RequestType(..)-  , RequestHandler-  , ReqContinuation(..)--    -- * Pure Interface-    -- ** Helpful Parsers-  , runHelpfulParser-  , runHelpfulParser'-  , runHelpfulParser_--    -- ** Silent Parsers-  , runSilentParser-  , runSilentParser'--    -- ** General Parsers (CPS)-  , runArgumentParser-  , runArgumentParser'   ) where -import           Data.Text           (Text)-import qualified Data.Text           as T-import qualified Data.Text.IO        as TIO-import           System.Environment-import           System.Exit-import           System.IO- import           Mangrove.Parser-import           Mangrove.Resolve-import           Mangrove.Text---- | The results of a parsing operation.------ Only parsing schemes that support generating responses can use the--- 'Response' constructor.-data Result s r where-  -- | A successful parsing operation yields a list of leftover-  -- arguments and a result value.-  Success :: ![Text] -> !r -> Result s r-  -- | A failed parsing operation yields an error message.-  Failure :: !Text -> Result s r-  -- | A request for information yields a human-readable response (for-  -- parsers that support it).-  Response :: SupportsResponse s => !Text -> Result s r--deriving instance Show r => Show (Result s r)-deriving instance Eq r => Eq (Result s r)---- | Create a default initial t'StreamState' from a list of arguments.-argsToState :: [Text] -> StreamState s-argsToState args = StreamState args [] False---- | Attempt to parse a value of type @r@ from a list of arguments,--- where the parser @ParseTree s r@ doesn't support requests.-runSilentParser-  :: (Scheme s, RequestSupport s ~ 'False)-  => ParseTree s r -- ^ Argument parser-  -> [Text] -- ^ Input arguments-  -> Result s r-runSilentParser tree = runSilentParser' tree . argsToState---- | A more general form of 'runSilentParser' that accepts a custom--- stream starting state.-runSilentParser'-  :: (Scheme s, RequestSupport s ~ 'False)-  => ParseTree s r -- ^ Argument parser-  -> StreamState s -- ^ Initial stream state-  -> Result s r-runSilentParser' tree state =-  runArgumentParser' tree state Success Failure NoRequests---- | Attempt to parse a value of type @r@ from a list of arguments,--- where the parser @ParseTree s r@ supports requests.-runHelpfulParser-  :: SupportsResponse s-  => ProgramInfo s -- ^ Program metadata-  -> ParseTree s r -- ^ Argument parser-  -> [Text] -- ^ Input arguments-  -> Result s r-runHelpfulParser info tree = runHelpfulParser' info tree . argsToState---- | A more general form of 'runHelpfulParser' that accepts a custom--- stream starting state.-runHelpfulParser'-  :: SupportsResponse s-  => ProgramInfo s -- ^ Program metadata-  -> ParseTree s r -- ^ Argument parser-  -> StreamState s -- ^ Initial stream state-  -> Result s r-runHelpfulParser' info tree state =-  runArgumentParser' tree state Success Failure (OnRequest _onRequest)-  where-    _onRequest state' HelpRequest =-      Response $ makeHelpInfo tree (streamContext state') info-    _onRequest _ VersionRequest =-      Response $ makeVersionInfo info---- | A variant of 'runHelpfulParser' that treats requests as failures.------ This is useful if you know that no requests will ever be made.-runHelpfulParser_-  :: SupportsResponse s-  => ParseTree s r -- ^ Argument parser-  -> [Text] -- ^ Input arguments-  -> Result s r-runHelpfulParser_ tree args =-  runArgumentParser' tree (argsToState args) Success Failure (OnRequest _onRequest)-  where-    _onRequest state' _ = Failure $-      formatError (streamContext state') "help requested"---- | Parse the command line arguments passed to the program, then--- invoke the program's entrypoint with the results of the parsing. If--- parsing fails, we instead display an error to stderr and exit.--- Alternatively, if information was requested, we abandon parsing and--- print the relevant response to stdout, then exit without indicating--- an error.-parseArguments-  :: SupportsResponse s-  => ProgramInfo s -- ^ Program metadata-  -> ParseTree s r -- ^ Argument parser-  -> (r -> IO a) -- ^ Program Entrypoint-  -> IO a-parseArguments info tree action = do-  args <- map T.pack <$> getArgs-  case runHelpfulParser info tree args of-    Success [] result -> action result-    Success (token:_) _ -> do-      hPutBuilder stderr $ "unexpected " <> render token <> "\n"-      exitFailure-    Failure err -> do-      TIO.hPutStrLn stderr err-      exitFailure-    Response output -> do-      TIO.putStr output-      exitSuccess---- | Satiate a 'ParseTree' with all the input it can consume, then--- attempt to evaluate it.-runArgumentParser-  :: Scheme s-  => ParseTree s r -- ^ Argument parser-  -> [Text] -- ^ Input arguments-  -> ([Text] -> r -> a) -- ^ Success handler-  -> (Text -> a) -- ^ Failure handler-  -> RequestHandler s a -- ^ Request handler-  -> a-runArgumentParser tree = runArgumentParser' tree . argsToState---- | A more general form of 'runArgumentParser' that accepts a custom--- stream starting state.-runArgumentParser'-  :: Scheme s-  => ParseTree s r -- ^ Argument parser-  -> StreamState s -- ^ Initial stream state-  -> ([Text] -> r -> a) -- ^ Success handler-  -> (Text -> a) -- ^ Failure handler-  -> RequestHandler s a -- ^ Request handler-  -> a-runArgumentParser' tree state cok cerr hhelp =-  runStreamParser (satiate tree) handler state-  where-    _onFailure state' = cerr . formatError (streamContext state')-    _onSuccess state' tree' =-      case (streamContent state', resolve tree') of-        (leftovers, Value result) -> cok leftovers result-        ([], EmptyError)          -> _onFailure state' "empty"-        ([], ExpectedError es)    -> _onFailure state' $ renderExpectedError es-        (token:_, _)              -> _onFailure state' $ "unexpected " <> render token-    handler = StreamHandler-      { onSuccess = _onSuccess-      , onFailure = _onFailure-      , onEmpty = flip _onFailure "empty"-      , onRequest = hhelp-      }+import           Mangrove.ParseTree+import           Mangrove.Scheme+import           Mangrove.Stream
+ src/Mangrove/ParseTree.hs view
@@ -0,0 +1,198 @@+{-# LANGUAGE DataKinds             #-}+{-# LANGUAGE FlexibleContexts      #-}+{-# LANGUAGE FlexibleInstances     #-}+{-# LANGUAGE GADTs                 #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings     #-}+{-# LANGUAGE QuantifiedConstraints #-}+{-# LANGUAGE ScopedTypeVariables   #-}+{-# LANGUAGE TypeApplications      #-}+{-# LANGUAGE TypeFamilies          #-}++{-|+Module      : Mangrove.ParseTree+Copyright   : (c) Quytelda Kahja, 2026+License     : BSD-3-Clause++A 'ParseTree' is a tree-shaped parser that "filter-feeds" on a stream+of arguments, collecting inputs at the leaves and feeding the results+up the tree for processing. 'ParseTree's are parameterized by the+parser scheme that determines the kind of inputs it accepts.+-}++module Mangrove.ParseTree+  ( -- * Parse Trees+    ParseTree(..)+  , isProduct+  , isSum+  , isOptional+  , isChoice+  ) where++import           Control.Applicative+import           Data.Kind+import           Data.Proxy++import           Mangrove.Render+import           Mangrove.Resolve+import           Mangrove.Token+import           Mangrove.Valency++-- | `ParseTree scheme r` is an expression tree composed of parsers+-- from scheme @scheme@ which evaluates to a value of type @r@ when+-- supplied with the proper input.+data ParseTree (scheme :: Type -> Type) (r :: Type) where+  -- | Terminal node with no value (abstracts 'empty')+  EmptyNode :: ParseTree scheme r+  -- | A terminal node with a resolved value (abstracts 'pure')+  ValueNode :: !r -> ParseTree scheme r+  -- | A parser awaiting input+  ParseNode :: scheme r -> ParseTree scheme r+  -- | Abstracts 'liftA2' and by extension '(<*>)'+  ProdNode :: !(u -> v -> r) -> ParseTree scheme u -> ParseTree scheme v -> ParseTree scheme r+  -- | Abstracts '(<|>)'+  SumNode :: ParseTree scheme r -> ParseTree scheme r -> ParseTree scheme r+  -- | Abstracts 'many' (@MaybeNode False@) and 'some' (@MaybeNode True@)+  ManyNode :: !Bool -> ParseTree scheme r -> ParseTree scheme [r]++instance (forall a. Show (s a)) => Show (ParseTree s r) where+  showsPrec _ EmptyNode = showString "EmptyNode"+  showsPrec p (ValueNode _) =+    showParen (p >= 10)+    $ showString "ValueNode _"+  showsPrec p (ParseNode s) =+    showParen (p >= 10)+    $ showString "ParseNode "+    . showsPrec 11 s+  showsPrec p (ProdNode _ l r) =+    showParen (p >= 10)+    $ showString "ProdNode _ "+    . showsPrec 11 l+    . showString " "+    . showsPrec 11 r+  showsPrec p (SumNode l r) =+    showParen (p >= 10)+    $ showString "SumNode "+    . showsPrec 11 l+    . showString " "+    . showsPrec 11 r+  showsPrec p (ManyNode b t) =+    showParen (p >= 10)+    $ showString "ManyNode "+    . showsPrec 11 b+    . showString " "+    . showsPrec 11 t++instance Functor p => Functor (ParseTree p) where+  fmap _ EmptyNode          = EmptyNode+  fmap f (ValueNode value)  = ValueNode $ f value+  fmap f (ParseNode parser) = ParseNode $ fmap f parser+  fmap f (ProdNode g l r)   = ProdNode (\u v -> f $ g u v) l r+  fmap f (SumNode l r)      = SumNode (fmap f l) (fmap f r)+  fmap f node               = ProdNode ($) (pure f) node+  -- This takes advantage of the fact that f <$> x = pure f <*> x.++instance Functor p => Applicative (ParseTree p) where+  pure = ValueNode+  liftA2 = ProdNode++instance Functor p => Alternative (ParseTree p) where+  empty = EmptyNode+  (<|>) = SumNode+  many = ManyNode False+  some = ManyNode True++instance Valency s => Valency (ParseTree s) where+  valency EmptyNode         = Just 0+  valency (ValueNode _)     = Just 0+  valency (ParseNode p)     = valency p+  valency (ProdNode _ l r)  = (+) <$> valency l <*> valency r+  valency (SumNode l r)     = max <$> valency l <*> valency r+  valency (ManyNode _ tree) =+    case valency tree of+      Just n | n <= 0 -> Just 0+      _               -> Nothing -- i.e. infinity+  -- In the above case of 'ManyNode _ p', a ManyNode can accept an+  -- arbitrary number of parameters, so the maximum valency is either+  -- infinite or zero depending on whether the valency of 'p' is zero.++  -- Since ParseTrees themselves don't accept inputs, we can provide a+  -- slightly more efficient implementation of nullary.+  nullary EmptyNode         = True+  nullary (ValueNode _)     = True+  nullary (ParseNode p)     = nullary p+  nullary (ProdNode _ l r)  = nullary l && nullary r+  nullary (SumNode l r)     = nullary l && nullary r+  nullary (ManyNode _ tree) = nullary tree++instance Resolve s => Resolve (ParseTree s) where+  resolve EmptyNode          = EmptyError+  resolve (ValueNode value)  = pure value+  resolve (ParseNode parser) = resolve parser+  resolve (ProdNode f l r)   = f <$> resolve l <*> resolve r+  resolve (SumNode l r)      = resolve l <|> resolve r+  resolve (ManyNode False _) = pure []+  resolve (ManyNode True  p) = pure <$> resolve p+  -- NOTE: If a ManyNode contains a resolvable node, one might expect+  -- the result to be an infinite list (e.g. `resolve $ many+  -- (ValueNode 1)` to give `Right [1,1,1,1,..]`) or for the+  -- computation to diverge (as is the case for `many (Just 1)`).+  -- However, by only attempting at most resolutions of the subtree,+  -- we will get either zero or one results. For example, `resolve $+  -- many (ValueNode 1)` will give `Right []`.+  --+  -- Whether this is the best possible way to handle the situation is+  -- unclear. This avoids infinite loops, but might not be the+  -- expected behavior in some unforseen use-case.++-- | Is this a 'ProdNode'?+isProduct :: ParseTree s r -> Bool+isProduct (ProdNode {}) = True+isProduct _             = False++-- | Is this a 'SumNode'?+isSum :: ParseTree s r -> Bool+isSum (SumNode {}) = True+isSum _            = False++-- | Does this subtree accept optional input?+isOptional :: Valency s => ParseTree s r -> Bool+isOptional (SumNode l (ValueNode _)) = not $ nullary l+isOptional (ManyNode False p)        = not $ nullary p+isOptional _                         = False++-- | Is this a 'SumNode' a choice between two different (non-empty)+-- inputs?+isChoice :: Valency s => ParseTree s r -> Bool+isChoice (SumNode l r) = not (nullary l) && not (nullary r)+isChoice _             = False++instance (Valency s, HasTokens s, forall a. Render (s a)) => Render (ParseTree s r) where+  -- special cases+  render n@(SumNode l _)+    | isOptional n = renderDelimitedIf brackets (not . isOptional) l++  render (ParseNode parser) = render parser+  render (ProdNode _ l r)+    | nullary l && nullary r = ""+    | nullary l = _render r+    | nullary r = _render l+    | otherwise = _render l <> render sep <> _render r+    where+      _render = renderDelimitedIf braces isChoice+      sep = delimiter (Proxy @s)+  render (SumNode l r)+    | nullary l && nullary r = ""+    | nullary l = _render r+    | nullary r = _render l+    | otherwise = _render l <> "|" <> _render r+    where+      _render = renderDelimitedIf braces isProduct+  render (ManyNode required p) = wrap $ render p <> "..."+    where+      wrap = if required+             then braces+             else brackets++  -- Constant nodes that don't accept input have no usage.+  render _ = ""
src/Mangrove/Parser.hs view
@@ -1,483 +1,60 @@-{-# LANGUAGE DataKinds                 #-}-{-# LANGUAGE DeriveFunctor             #-}-{-# LANGUAGE ExistentialQuantification #-}-{-# LANGUAGE FlexibleContexts          #-}-{-# LANGUAGE FlexibleInstances         #-}-{-# LANGUAGE GADTs                     #-}-{-# LANGUAGE MultiParamTypeClasses     #-}-{-# LANGUAGE OverloadedStrings         #-}-{-# LANGUAGE PolymorphicComponents     #-}-{-# LANGUAGE ScopedTypeVariables       #-}-{-# LANGUAGE StandaloneDeriving        #-}-{-# LANGUAGE TypeApplications          #-}-{-# LANGUAGE TypeFamilies              #-}-{-# LANGUAGE TypeOperators             #-}+{-# LANGUAGE DataKinds             #-}+{-# LANGUAGE DeriveFunctor         #-}+{-# LANGUAGE FlexibleContexts      #-}+{-# LANGUAGE FlexibleInstances     #-}+{-# LANGUAGE GADTs                 #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings     #-}+{-# LANGUAGE QuantifiedConstraints #-}+{-# LANGUAGE TypeFamilies          #-}  {-| Module      : Mangrove.Parser Copyright   : (c) Quytelda Kahja, 2026 License     : BSD-3-Clause -This module contains the data types and type classes that make up a-generic argument parser, as well as a stream parsing monad in which-parsing takes place.--A 'ParseTree' is a tree-shaped parser that "filter-feeds" on a stream-of arguments, collecting inputs at the leaves and feeding the results-up the tree for processing. 'ParseTree's are parameterized by the-parser scheme that determines the kind of inputs it accepts.+This module contains types and functions necessary for running+argument parsers. These functions are generic across different parsing+schemes. -A "scheme" is a system of parsers and tokens. It determines the method-by which argument strings are separated. It parses a sequence of-arguments into tokens and values.+Most clients won't import this module directly, since its contents are+re-exported by the "Mangrove" module alongside other helpful symbols. -} module Mangrove.Parser-  ( -- * Parse Trees-    ParseTree(..)-  , isProduct-  , isSum-  , isOptional-  , isChoice--    -- ** Feeding Trees-  , satiate--    -- * Parsing Schemes-  , Scheme(..)-  , ProgramInfo(..)-  , SupportsResponse(..)--    -- * Stream Parser-  , StreamParser(..)-  , StreamHandler(..)-  , StreamState(..)-  , RequestHandler-  , ReqContinuation(..)--    -- ** Requests-  , RequestType(..)-  , request--    -- ** Escaping-  , setEscaped-  , getEscaped+  ( -- * Standard Interface+    parseArguments -    -- ** Context-  , getContext-  , setContext-  , withContext-  , formatError+    -- * Pure Interface+  , Result(..)+  , runArgumentParser+  , runArgumentParser' -    -- ** Streaming-  , popMaybe-  , peekMaybe-  , pop-  , peek-  , push-  , pop_+    -- * Feeding Parser Trees+  , satiate   ) where  import           Control.Applicative-import           Control.Monad.Except-import           Data.Kind-import qualified Data.List              as List-import           Data.Proxy-import           Data.Text              (Text)-import qualified Data.Text.Lazy         as TL-import qualified Data.Text.Lazy.Builder as TLB-import           Data.Version+import           Data.Text           (Text)+import qualified Data.Text           as T+import qualified Data.Text.IO        as TIO+import           System.Environment+import           System.Exit+import           System.IO +import           Mangrove.ParseTree+import           Mangrove.Render import           Mangrove.Resolve-import           Mangrove.Text-import           Mangrove.Valency------------------------------------------------------------------------------------- Parse Trees---- | `ParseTree scheme r` is an expression tree composed of parsers--- from scheme @scheme@ which evaluates to a value of type @r@ when--- supplied with the proper input.-data ParseTree (scheme :: Type -> Type) (r :: Type) where-  -- | Terminal node with no value (abstracts 'empty')-  EmptyNode :: ParseTree scheme r-  -- | A terminal node with a resolved value (abstracts 'pure')-  ValueNode :: !r -> ParseTree scheme r-  -- | A parser awaiting input-  ParseNode :: scheme r -> ParseTree scheme r-  -- | Abstracts 'liftA2' and by extension '(<*>)'-  ProdNode :: !(u -> v -> r) -> ParseTree scheme u -> ParseTree scheme v -> ParseTree scheme r-  -- | Abstracts '(<|>)'-  SumNode :: ParseTree scheme r -> ParseTree scheme r -> ParseTree scheme r-  -- | Abstracts 'many' (@MaybeNode False@) and 'some' (@MaybeNode True@)-  ManyNode :: !Bool -> ParseTree scheme r -> ParseTree scheme [r]--instance Functor p => Functor (ParseTree p) where-  fmap _ EmptyNode          = EmptyNode-  fmap f (ValueNode value)  = ValueNode $ f value-  fmap f (ParseNode parser) = ParseNode $ fmap f parser-  fmap f (ProdNode g l r)   = ProdNode (\u v -> f $ g u v) l r-  fmap f (SumNode l r)      = SumNode (fmap f l) (fmap f r)-  fmap f node               = ProdNode ($) (pure f) node-  -- This takes advantage of the fact that f <$> x = pure f <*> x.--instance Functor p => Applicative (ParseTree p) where-  pure = ValueNode-  liftA2 = ProdNode--instance Functor p => Alternative (ParseTree p) where-  empty = EmptyNode-  (<|>) = SumNode-  many = ManyNode False-  some = ManyNode True--instance Valency s => Valency (ParseTree s) where-  valency EmptyNode         = Just 0-  valency (ValueNode _)     = Just 0-  valency (ParseNode p)     = valency p-  valency (ProdNode _ l r)  = (+) <$> valency l <*> valency r-  valency (SumNode l r)     = max <$> valency l <*> valency r-  valency (ManyNode _ tree) =-    case valency tree of-      Just n | n <= 0 -> Just 0-      _               -> Nothing -- i.e. infinity-  -- In the above case of 'ManyNode _ p', a ManyNode can accept an-  -- arbitrary number of parameters, so the maximum valency is either-  -- infinite or zero depending on whether the valency of 'p' is zero.--  -- Since ParseTrees themselves don't accept inputs, we can provide a-  -- slightly more efficient implementation of nullary.-  nullary EmptyNode         = True-  nullary (ValueNode _)     = True-  nullary (ParseNode p)     = nullary p-  nullary (ProdNode _ l r)  = nullary l && nullary r-  nullary (SumNode l r)     = nullary l && nullary r-  nullary (ManyNode _ tree) = nullary tree--instance Resolve s => Resolve (ParseTree s) where-  resolve EmptyNode          = EmptyError-  resolve (ValueNode value)  = pure value-  resolve (ParseNode parser) = resolve parser-  resolve (ProdNode f l r)   = f <$> resolve l <*> resolve r-  resolve (SumNode l r)      = resolve l <|> resolve r-  resolve (ManyNode False _) = pure []-  resolve (ManyNode True  p) = pure <$> resolve p-  -- NOTE: If a ManyNode contains a resolvable node, one might expect-  -- the result to be an infinite list (e.g. `resolve $ many-  -- (ValueNode 1)` to give `Right [1,1,1,1,..]`) or for the-  -- computation to diverge (as is the case for `many (Just 1)`).-  -- However, by only attempting at most resolutions of the subtree,-  -- we will get either zero or one results. For example, `resolve $-  -- many (ValueNode 1)` will give `Right []`.-  ---  -- Whether this is the best possible way to handle the situation is-  -- unclear. This avoids infinite loops, but might not be the-  -- expected behavior in some unforseen use-case.---- | Is this a 'ProdNode'?-isProduct :: ParseTree s r -> Bool-isProduct (ProdNode {}) = True-isProduct _             = False---- | Is this a 'SumNode'?-isSum :: ParseTree s r -> Bool-isSum (SumNode {}) = True-isSum _            = False---- | Does this subtree accept optional input?-isOptional :: Valency s => ParseTree s r -> Bool-isOptional (SumNode l (ValueNode _)) = not $ nullary l-isOptional (ManyNode False p)        = not $ nullary p-isOptional _                         = False---- | Is this a 'SumNode' a choice between two different (non-empty)--- inputs?-isChoice :: Valency s => ParseTree s r -> Bool-isChoice (SumNode l r) = not (nullary l) && not (nullary r)-isChoice _             = False--instance (Valency s, Scheme s) => Render (ParseTree s r) where-  -- special cases-  render n@(SumNode l _)-    | isOptional n = renderDelimitedIf brackets (not . isOptional) l--  render (ParseNode parser) = usageInfo parser-  render (ProdNode _ l r)-    | nullary l && nullary r = ""-    | nullary l = _render r-    | nullary r = _render l-    | otherwise = _render l <> render sep <> _render r-    where-      _render = renderDelimitedIf braces isChoice-      sep = delimiter (Proxy @s)-  render (SumNode l r)-    | nullary l && nullary r = ""-    | nullary l = _render r-    | nullary r = _render l-    | otherwise = _render l <> "|" <> _render r-    where-      _render = renderDelimitedIf braces isProduct-  render (ManyNode required p) = wrap $ render p <> "..."-    where-      wrap = if required-             then braces-             else brackets--  -- Constant nodes that don't accept input have no usage.-  render _ = ""------------------------------------------------------------------------------------- Parsing Schemes---- | A scheme is a system of parsers and tokens. It parses a sequence--- of arguments into tokens and values.-class (Functor s, Resolve s, Eq (Token s), Render (Token s), Show (Token s)) => Scheme (s :: Type -> Type) where-  -- | A token represents a particular interpretation of an argument-  -- string under this parsing scheme.-  data Token s--  -- | This type indicates whether a parsing scheme accepts requests-  -- for information.-  ---  -- When @RequestSupport scheme@ is @True@, a 'SupportsResponse'-  -- instance should be provided for @scheme@.-  type RequestSupport s :: Bool-  type RequestSupport s = 'False--  -- | 'delimiter' is the character that separates argument strings in-  -- combined string representation. For example, arguments in the CLI-  -- command @ls -a -l /var@ are separated by spaces.-  delimiter :: Proxy s -> Char--  -- | Parse special control arguments that don't represent tokens in-  -- the scheme, but control aspects of how parsing proceeds (e.g.-  -- escaping).-  parseSpecials :: StreamParser s ()-  parseSpecials = pure ()--  -- | 'activate' tries to run a parser on the current input. If the-  -- parser doesn't apply, it consumes nothing and returns empty. If-  -- it does apply, it consumes the relevant input and returns a-  -- result.-  activate :: s r -> StreamParser s r--  -- | Render human-readable usage information for a particular-  -- parser.-  usageInfo :: s r -> Builder---- | Program metadata for displaying help output.-data ProgramInfo (s :: Type -> Type) = ProgramInfo-  { programName    :: !Text -- ^ The program name-  , programVersion :: !Version -- ^ The program version-  , programDesc    :: !Text -- ^ A description of the program-  } deriving (Show)---- | A class for schemes that support human-readable responses to--- requests for help or version information.-class (Scheme s, RequestSupport s ~ 'True) => SupportsResponse s where-  makeVersionInfo :: ProgramInfo s -> Text-  makeHelpInfo :: ParseTree s r -> [Token s] -> ProgramInfo s -> Text------------------------------------------------------------------------------------- Stream Parser---- | The current state of a stream parser.------ The content of a stream is just a list of 'Text' values. The--- context stack is a list of tokens currently being processed; when a--- token is recognized, it gets added to front of the list while the--- token is being parsed into a usable value. When this parsing--- completes, the token is popped from the front of the list.------ A streams can also enable "escaped" mode by setting 'streamEscaped'--- to 'True'. What this actually does is parser-dependant, but usually--- it restricts how subsequent arguments can be interpreted. For--- example, in the Unix scheme, escaping forces all subsequent--- arguments to be interpreted as positional arguments, even if they--- would normally be interpreted as options or commands.-data StreamState s = StreamState-  { streamContent :: ![Text]    -- ^ A sequence of 'Text' values-  , streamContext :: ![Token s] -- ^ A stack representing current parsing context-  , streamEscaped :: !Bool      -- ^ Escaped mode-  }--deriving instance Scheme s => Show (StreamState s)-deriving instance Scheme s => Eq (StreamState s)---- | What information is being requested?-data RequestType-  = VersionRequest -- ^ A request for version information-  | HelpRequest -- ^ A request for help and usage information-  deriving (Eq, Show)---- | A handler for when information is requested.------ This will hold a continuation function for helpful parsing--- schemes, or a placeholder value for silent schemes.-data family ReqContinuation (cap :: Bool) (s :: Type -> Type) r--data instance ReqContinuation 'False s r-  = NoRequests-  deriving (Functor)--newtype instance ReqContinuation 'True s r-  = OnRequest (StreamState s -> RequestType -> r)-  deriving (Functor)---- | A handler for when information is requested.------ This will hold a continuation function for helpful parsing--- schemes, or a placeholder value for silent schemes.-type RequestHandler s r = ReqContinuation (RequestSupport s) s r---- | A collection of continuations to be called for each situation a--- stream parser might encounter.-data StreamHandler s a r = StreamHandler-  { onSuccess :: StreamState s -> a -> r -- ^ Success Continuation-  , onEmpty   :: StreamState s -> r -- ^ Empty continuation-  , onFailure :: StreamState s -> Builder -> r -- ^ Failure Continuation-  , onRequest :: RequestHandler s r -- ^ Request Continuation-  }---- | The amazing stream parsing monad! This monad tracks the stream--- state and context. It short-circuits when exceptions or requests--- are raised.-newtype StreamParser s a = StreamParser-  { runStreamParser-    :: forall r. StreamHandler s a r-    -> StreamState s-    -> r-  }--instance Functor (StreamParser s) where-  fmap f parser = StreamParser $ \handler ->-    runStreamParser parser handler { onSuccess = \s -> onSuccess handler s . f }--instance Applicative (StreamParser s) where-  pure a = StreamParser $ \handler state -> onSuccess handler state a-  mf <*> ma = StreamParser $ \handler ->-    runStreamParser mf-    handler { onSuccess = \s f -> runStreamParser ma handler { onSuccess = \s' -> onSuccess handler s' . f } s }--instance Alternative (StreamParser s) where-  empty = StreamParser $ \handler -> onEmpty handler-  l <|> r = StreamParser $ \handler ->-    runStreamParser l handler { onEmpty = runStreamParser r handler }--instance Monad (StreamParser s) where-  return = pure-  ma >>= f = StreamParser $ \handler ->-    runStreamParser ma handler { onSuccess = \s a -> runStreamParser (f a) handler s }--instance MonadError Builder (StreamParser s) where-  throwError err = StreamParser $ \handler state -> onFailure handler state err-  catchError ma recover = StreamParser $ \handler state ->-    runStreamParser ma-    handler { onFailure = \_ err -> runStreamParser (recover err) handler state }-    state---- | Enable or disable escaped parsing. What this actually does is--- parser-dependant, but usually it restricts how subsequent arguments--- can be interpreted. For example, in the Unix scheme, escaping--- forces all subsequent arguments to be interpreted as positional--- arguments, even if they would normally be interpreted as options or--- commands.-setEscaped :: Bool -> StreamParser s ()-setEscaped b = StreamParser $ \handler state ->-  onSuccess handler state { streamEscaped = b } ()---- | Check whether escaped parsing is enabled.-getEscaped :: StreamParser s Bool-getEscaped = StreamParser $ \handler state ->-  onSuccess handler state (streamEscaped state)---- | Signal that information is requested. Short-circuits any further--- operations.-request :: RequestSupport s ~ 'True => RequestType -> StreamParser s a-request requestType = StreamParser $ \handler state ->-  case onRequest handler of-    OnRequest h -> h state requestType---- | Get a list representing the current context stack.-getContext :: StreamParser s [Token s]-getContext = StreamParser $ \handler state ->-  onSuccess handler state (streamContext state)---- | Replace the context stack.-setContext :: [Token s] -> StreamParser s ()-setContext contexts = StreamParser $ \handler state ->-  onSuccess handler state { streamContext = contexts } ()---- | Push the provided token onto the context stack, then perform some--- computation. Afterwards, the stack is restored to its prior state.-withContext :: Token s -> StreamParser s a -> StreamParser s a-withContext context action = do-  oldContext <- getContext-  setContext $ context : oldContext-  action <* setContext oldContext---- | Format an error message with context information.-formatError :: Render tok => [tok] -> Builder -> Text-formatError contexts err =-  TL.toStrict-  $ TLB.toLazyText-  $ mconcat-  $ List.intersperse ": "-  $ reverse-  $ err : map render contexts-------------------------------------------------------------------------------------- | Remove and return the first token in the stream.-popMaybe :: StreamParser s (Maybe Text)-popMaybe = StreamParser $ \handler state ->-  case streamContent state of-    (t:ts') -> onSuccess handler state { streamContent = ts' } (Just t)-    _       -> onSuccess handler state Nothing---- | View the first token in the stream without consuming it.-peekMaybe :: StreamParser s (Maybe Text)-peekMaybe = StreamParser $ \handler state ->-  case streamContent state of-    (t:_) -> onSuccess handler state (Just t)-    _     -> onSuccess handler state Nothing---- | Remove and return the first token in the stream. Evaluates to--- 'empty' if there are no tokens in the stream.-pop :: StreamParser s Text-pop = StreamParser $ \handler state ->-  case streamContent state of-    (t:ts') -> onSuccess handler state { streamContent = ts' } t-    _       -> onEmpty handler state---- | View the first token in the stream without consuming it.--- Evaluates to 'empty' if there are no tokens in the stream.-peek :: StreamParser s Text-peek = StreamParser $ \handler state ->-  case streamContent state of-    (t:_) -> onSuccess handler state t-    _     -> onEmpty handler state---- | Prepend a token to the front of the stream.-push :: Text -> StreamParser s ()-push t = StreamParser $ \handler state ->-  onSuccess handler-  state { streamContent = t : streamContent state }-  ()---- | Discard the first token in the stream. Nothing happens if there--- are no tokens in the stream.-pop_ :: StreamParser s ()-pop_ = StreamParser $ \handler state ->-  onSuccess handler-  state { streamContent = drop 1 $ streamContent state }-  ()+import           Mangrove.Scheme+import           Mangrove.Stream+import           Mangrove.Token  --------------------------------------------------------------------------------+-- Feeding ParseTrees  -- | 'feed' traverses the tree until it activates a parser that -- consumes input. When a subtree successfully consumes input, it is -- replaced with an updated subtree and the traversal ceases.-feed :: Scheme s => ParseTree s r -> StreamParser s (ParseTree s r)+feed :: Scheme s => ParseTree s r -> StreamParser (Request s) (Token s) (ParseTree s r) feed EmptyNode = empty feed (ValueNode _) = empty feed (ParseNode parser) = ValueNode <$> activate parser@@ -493,10 +70,92 @@ -- | Repeatedly traverse the tree, each time activating the first -- parser that can consume available input, until no more input can be -- consumed.-satiate :: Scheme s => ParseTree s r -> StreamParser s (ParseTree s r)+satiate :: Scheme s => ParseTree s r -> StreamParser (Request s) (Token s) (ParseTree s r) satiate tree = do   parseSpecials   result <- optional $ feed tree   case result of     Just tree' -> satiate tree'     Nothing    -> pure tree++--------------------------------------------------------------------------------+-- Running Parsers++-- | Create a default initial t'StreamState' from a list of arguments.+argsToState :: [Text] -> StreamState s+argsToState args = StreamState args [] False++-- | The result of an argument parsing operation.+data Result req a+  = Success ![Text] !a+  | Failure !Text+  | Request !req+  deriving (Eq, Functor, Show)++-- | Resolve the output of a parsing operation and sink it into a+-- 'Result'.+sinkResult+  :: Scheme s+  => StreamHandler (Request s) (Token s) (ParseTree s r) (Result (Request s) r)+sinkResult = StreamHandler+  { onSuccess = _onSuccess+  , onEmpty   = _onEmpty+  , onFailure = _onFailure+  , onRequest = _onRequest+  }+  where+    _onFailure state' = Failure . formatError (streamContext state')+    _onEmpty = flip _onFailure "empty"+    _onSuccess state' tree' =+      case (streamContent state', resolve tree') of+        (leftovers, Value result) -> Success leftovers result+        ([], EmptyError)          -> _onFailure state' "empty"+        ([], ExpectedError es)    -> _onFailure state' $ renderExpectedError es+        (token:_, _)              -> _onFailure state' $ "unexpected " <> render token+    _onRequest _ = Request++-- | A more general form of 'runArgumentParser' that accepts a custom+-- stream starting state.+runArgumentParser'+  :: Scheme s+  => ParseTree s r+  -> StreamState (Token s)+  -> Result (Request s) r+runArgumentParser' tree =+  runStreamParser (satiate tree) sinkResult++-- | Satiate a 'ParseTree' with all the input it can consume, then+-- attempt to evaluate it. Empty results are treated as failures.+runArgumentParser+  :: Scheme s+  => ParseTree s r+  -> [Text]+  -> Result (Request s) r+runArgumentParser tree =+  runArgumentParser' tree . argsToState++-- | Parse the command line arguments passed to the program, then+-- invoke the program's entrypoint with the results of the parsing. If+-- parsing fails, we instead display an error to stderr and exit.+-- Alternatively, if information was requested, we abandon parsing and+-- print the response to stdout, then exit without indicating an+-- error.+parseArguments+  :: Scheme s+  => ProgramInfo -- ^ Program metadata+  -> ParseTree s r -- ^ Argument parser+  -> (r -> IO a) -- ^ Program entrypoint+  -> IO a+parseArguments info tree action = do+  args <- map T.pack <$> getArgs+  case runArgumentParser tree args of+    Success [] result -> action result+    Success (token:_) _ -> do+      hPutBuilder stderr $ "unexpected " <> render token <> "\n"+      exitFailure+    Failure err -> do+      TIO.hPutStrLn stderr err+      exitFailure+    Request req -> do+      TIO.putStr $ respond req tree info+      exitSuccess
+ src/Mangrove/Render.hs view
@@ -0,0 +1,98 @@+{-# LANGUAGE FlexibleContexts  #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs             #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeFamilies      #-}++{-|+Module      : Mangrove.Render+Copyright   : (c) Quytelda Kahja, 2026+License     : BSD-3-Clause++Facilities for textual representation of data structures.+-}+module Mangrove.Render+  ( -- * Text Rendering+    Render(..)+  , renderLazyText+  , renderText+  , putBuilder+  , hPutBuilder++    -- * Helpers & Combinators+  , between+  , brackets+  , braces+  , quotes+  , renderDelimitedIf++    -- * Re-exports+  , Builder+  ) where++import           Data.Text              (Text)+import qualified Data.Text              as T+import qualified Data.Text.Lazy         as TL+import           Data.Text.Lazy.Builder (Builder)+import qualified Data.Text.Lazy.Builder as TLB+import qualified Data.Text.Lazy.IO      as TLIO+import           System.IO++-- | A class for things that can be rendered to a text 'Builder'.+class Render a where+  render :: a -> Builder++instance Render Builder where+  render = id++instance Render T.Text where+  render = TLB.fromText++instance Render Char where+  render = TLB.singleton++instance Render String where+  render = TLB.fromString++-- | Convert renderable data directly to lazy 'TL.Text'.+renderLazyText :: Render a => a -> TL.Text+renderLazyText = TLB.toLazyText . render++-- | Convert renderable data directly to strict 'T.Text'.+renderText :: Render a => a -> Text+renderText = TL.toStrict . TLB.toLazyText . render++-- | Write the contents of a 'Builder' to standard output.+putBuilder :: Builder -> IO ()+putBuilder = TLIO.putStr . TLB.toLazyText++-- | Write the contents of a 'Builder' to some IO handle.+hPutBuilder :: Handle -> Builder -> IO ()+hPutBuilder handle = TLIO.hPutStr handle . TLB.toLazyText++--------------------------------------------------------------------------------+-- Combinators++-- | @between open close s@ surrounds @s@ with @open@ and @close@+-- (i.e. @open <> s <> close@).+between :: Monoid m => m -> m -> m -> m+between open close s = open <> s <> close++-- | Surround a string with square brackets.+brackets :: Builder -> Builder+brackets = between "[" "]"++-- | Surround a string with curly braces.+braces :: Builder -> Builder+braces = between "{" "}"++-- | Surround a string with double quotes.+quotes :: Builder -> Builder+quotes = between "\"" "\""++-- | @renderDelimitedIf wrap f x@ will render @x@ as a 'Builder'. If+-- the condition @f x@ is @True@, the result will be modified using+-- the function @wrap@, otherwise the result will be returned+-- unmodified.+renderDelimitedIf :: Render a => (Builder -> Builder) -> (a -> Bool) -> a -> Builder+renderDelimitedIf wrap f x = (if f x then wrap else id) (render x)
src/Mangrove/Resolve.hs view
@@ -21,7 +21,8 @@ import           Control.Applicative import           Control.Monad.Except import qualified Data.List            as List-import           Mangrove.Text++import           Mangrove.Render  -- | A monad for resolving parsers and expression trees. --
+ src/Mangrove/Scheme.hs view
@@ -0,0 +1,58 @@+{-# LANGUAGE DataKinds         #-}+{-# LANGUAGE FlexibleContexts  #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE TypeFamilies      #-}++{-|+Module      : Mangrove.Scheme+Copyright   : (c) Quytelda Kahja, 2026+License     : BSD-3-Clause++A "scheme" is a set of parsers with an associated token type. The+scheme also defines the way the parser handles requests.+-}++module Mangrove.Scheme+  ( Scheme(..)+  , ProgramInfo(..)+  ) where++import           Data.Kind+import           Data.Text          (Text)+import           Data.Version++import           Mangrove.ParseTree+import           Mangrove.Resolve+import           Mangrove.Stream+import           Mangrove.Token++-- | Program metadata for displaying help output.+data ProgramInfo = ProgramInfo+  { programName    :: !Text -- ^ The program name+  , programDesc    :: !Text -- ^ A description of the program+  , programVersion :: !Version -- ^ The program version+  } deriving (Show)++-- | A scheme is a system of parsers and tokens. It parses a sequence+-- of arguments into tokens and values.+class (Functor s, HasTokens s, Resolve s) => Scheme (s :: Type -> Type) where+  -- | What type of requests does this scheme support? This should be+  -- 'Data.Void.Void' if requests are unsupported.+  type Request s++  -- | Generate a response to a request. If requests are unsupported+  -- for this scheme, the implementation of the function should be+  -- 'Data.Void.absurd'.+  respond :: Request s -> ParseTree s r -> ProgramInfo -> Text++  -- | Parse special control arguments that don't represent tokens in+  -- the scheme, but control aspects of how parsing proceeds (e.g.+  -- escaping).+  parseSpecials :: StreamParser (Request s) (Token s) ()+  parseSpecials = pure ()++  -- | 'activate' tries to run a parser on the current input. If the+  -- parser doesn't apply, it consumes nothing and returns empty. If+  -- it does apply, it consumes the relevant input and returns a+  -- result.+  activate :: s r -> StreamParser (Request s) (Token s) r
+ src/Mangrove/Scheme/Common.hs view
@@ -0,0 +1,25 @@+{-# LANGUAGE ViewPatterns #-}++{-|+Module      : Mangrove.Scheme.Common+Copyright   : (c) Quytelda Kahja, 2026+License     : BSD-3-Clause++This module is for utilities used by more than one parsing scheme.+-}++module Mangrove.Scheme.Common+  ( keyEqualsValue+  ) where++import           Data.Text (Text)+import qualified Data.Text as T++-- | Parse a 'Text' of the form "key=value" into ("key", "value"). If+-- the delimiter ('=') does not appear in the string, the result is+-- 'Nothing'.+keyEqualsValue :: Text -> Maybe (Text, Text)+keyEqualsValue s =+  case T.break (== '=') s of+    (key, T.uncons -> Just (_, value)) -> Just (key, value)+    _                                  -> Nothing
src/Mangrove/Scheme/Sub.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE DeriveFunctor     #-}+{-# LANGUAGE DeriveGeneric     #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeFamilies      #-}@@ -22,12 +23,18 @@   ) where  import           Control.Applicative-import           Data.Text           (Text)+import           Data.Text              (Text)+import           Data.Void+import           GHC.Generics -import           Mangrove.Parser+import           Mangrove.ParseTree+import           Mangrove.Render import           Mangrove.Resolve-import           Mangrove.Text+import           Mangrove.Scheme+import           Mangrove.Scheme.Common+import           Mangrove.Stream import           Mangrove.TextParser+import           Mangrove.Token import           Mangrove.Valency  -- | Parsers for subarguments of an option (e.g. @--option key=value@).@@ -36,6 +43,18 @@   | Option !Text (TextParser r) -- ^ Suboptions have the form "KEY=VALUE"   deriving (Functor) +instance Show (SubScheme r) where+  showsPrec p (Parameter tp) =+    showParen (p >= 10)+    $ showString "Parameter "+    . showsTextParser tp+  showsPrec p (Option key tp) =+    showParen (p >= 10)+    $ showString "Option "+    . shows key+    . showString " "+    . showsTextParser tp+ instance Valency SubScheme where   valency _ = Just 1 @@ -45,14 +64,19 @@   resolve (Option key (TextParser hint _)) =     ExpectedError [render key <> "=" <> render hint] -instance Scheme SubScheme where+instance HasTokens SubScheme where   data Token SubScheme     = SubAssoc Text Text -- ^ A "KEY=VALUE" argument     | SubArgument Text -- ^ A standard freeform argument-    deriving (Eq, Show)+    deriving (Eq, Generic, Show)    delimiter _ = ',' +instance Scheme SubScheme where+  type Request SubScheme = Void++  respond = absurd+   activate parser = do     next <- peek     escaped <- getEscaped@@ -74,8 +98,9 @@           empty       _ -> empty -  usageInfo (Parameter tp)  = render $ parserHint tp-  usageInfo (Option key tp) = render key <> "=" <> render (parserHint tp)+instance Render (SubScheme r) where+  render (Parameter tp)  = render $ parserHint tp+  render (Option key tp) = render key <> "=" <> render (parserHint tp)  instance Render (Token SubScheme) where   render (SubAssoc key value) = render key <> "=" <> render value
src/Mangrove/Scheme/Unix.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE DataKinds         #-} {-# LANGUAGE DeriveFunctor     #-}+{-# LANGUAGE DeriveGeneric     #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE LambdaCase        #-} {-# LANGUAGE OverloadedStrings #-}@@ -21,9 +22,13 @@   , CommandInfo(..)      -- * Unix Scheme+  , UnixParser   , UnixScheme(..)   , Token(..)-  , UnixParser+  , UnixRequest(..)+  , UnixRequest'+  , helpRequest+  , versionRequest      -- * Help   , addHelpOptions@@ -46,14 +51,19 @@ import qualified Data.Text.Lazy.Builder as TLB import           Data.Version import           Data.Void+import           GHC.Generics -import           Mangrove import           Mangrove.Parser+import           Mangrove.ParseTree+import           Mangrove.Render import           Mangrove.Resolve+import           Mangrove.Scheme+import           Mangrove.Scheme.Common import           Mangrove.Scheme.Sub    (SubScheme) import qualified Mangrove.Scheme.Sub    as Sub-import           Mangrove.Text+import           Mangrove.Stream import           Mangrove.TextParser+import           Mangrove.Token import           Mangrove.Valency  --------------------------------------------------------------------------------@@ -71,7 +81,7 @@ data Flag   = LongFlag !Text   | ShortFlag !Char-  deriving (Eq, Ord, Show)+  deriving (Eq, Generic, Ord, Show)  instance IsString Flag where   fromString ('-':'-':name)@@ -105,6 +115,49 @@ cmdHead :: CommandInfo -> Text cmdHead = NonEmpty.head . cmdNames +-- | Requests supported by UNIX-style parsers.+data UnixRequest+  = VersionRequest -- ^ A request for version information+  | HelpRequest [Text] -- ^ A request for help and usage information+  deriving (Eq, Generic, Show)++-- | Context-aware requests+type UnixRequest' = [Token UnixScheme] -> UnixRequest++-- | A request for help information within the current command context+helpRequest :: UnixRequest'+helpRequest context = HelpRequest [cmd | UnixCommand cmd <- context]++-- | A request for version information+versionRequest :: UnixRequest'+versionRequest _ = VersionRequest++-- | Generate a response to a help request.+respondHelpRequest+  :: [Text]+  -> ParseTree UnixScheme r+  -> ProgramInfo+  -> Text+respondHelpRequest cmds tree info = renderText+  $ "Usage:\n"+  <> formatUsages (programName info) usages <> "\n\n"+  <> render (programDesc info) <> "\n"+  <> renderHelp tree cmds+  where+    usages = decomposeTree tree cmds++-- | Generate a response to a version request.+respondVersionRequest+  :: ProgramInfo+  -> Text+respondVersionRequest info = renderText+  $ render (programName info)+  <> " version "+  <> renderVersion (programVersion info)+  <> "\n"+  where+    renderVersion = TLB.fromString . showVersion+ -- | A parsing scheme for Unix-style command line syntax. data UnixScheme r   -- | A freeform positional parameter@@ -114,9 +167,32 @@   -- | A named option that might support suboptions   | Option !OptionInfo (ParseTree SubScheme r)   -- | A special option that raises a request for information-  | RequestOption !OptionInfo !RequestType+  | RequestOption !OptionInfo !UnixRequest'   deriving (Functor) +instance Show (UnixScheme r) where+  showsPrec p (Parameter tp) =+    showParen (p >= 10)+    $ showString "Parameter "+    . showsTextParser tp+  showsPrec p (Option info subtree) =+    showParen (p >= 10)+    $ showString "Option "+    . showsPrec 11 info+    . showString " "+    . showsPrec 11 subtree+  showsPrec p (Command info subtree) =+    showParen (p >= 10)+    $ showString "Command "+    . showsPrec 11 info+    . showString " "+    . showsPrec 11 subtree+  showsPrec p (RequestOption info _) =+    showParen (p >= 10)+    $ showString "RequestOption "+    . showsPrec 11 info+    . showString " _"+ instance Valency UnixScheme where   valency (Parameter _)       = Just 1   valency (Command _ subtree) = fmap (+1) (valency subtree)@@ -153,7 +229,7 @@ isMarked "-" = False isMarked s   = "-" `T.isPrefixOf` s -instance Scheme UnixScheme where+instance HasTokens UnixScheme where   data Token UnixScheme     -- | A freeform positional argument that is not an option or command     = UnixArgument Text@@ -161,12 +237,16 @@     | UnixCommand Text     -- | A named option with optional bound argument     | UnixOption Flag (Maybe Text)-    deriving (Eq, Show)--  type RequestSupport UnixScheme = 'True+    deriving (Eq, Generic, Show)    delimiter _ = ' ' +instance Scheme UnixScheme where+  type Request UnixScheme = UnixRequest++  respond (HelpRequest cmds) tree info = respondHelpRequest cmds tree info+  respond VersionRequest _ info        = respondVersionRequest info+   parseSpecials = do     peekMaybe >>= \case       Just "--" -> pop_ *> setEscaped True@@ -214,10 +294,9 @@           , streamEscaped = not $ Sub.hasSubOptions subtree           }         parseSubargs args =-          runArgumentParser' subtree (initState args)-          (curry pure)-          (throwError . render)-          NoRequests+          case runArgumentParser' subtree (initState args) of+            Success leftover result -> pure (leftover, result)+            Failure err             -> throwError $ render err      withContext (UnixOption flag mbound) $ do       -- If a bound argument (e.g. --floop=blah) is provided, we@@ -252,7 +331,7 @@           (_, result) <- parseSubargs []           pure result -  activate (RequestOption info requestType) = do+  activate (RequestOption info mkRequest) = do     -- Arguments should never be interpreted as options when escaped.     getEscaped >>= guard . not @@ -261,7 +340,7 @@     pop_      withContext (UnixOption flag mbound) $-      request requestType+      getContext >>= request . mkRequest    activate (Command info subtree) = do     -- Arguments should never be interpreted as commands when escaped.@@ -276,10 +355,11 @@       satiate subtree       >>= resolveLifted -  usageInfo (Parameter tp) = render $ parserHint tp-  usageInfo (Command info subtree) =+instance Render (UnixScheme r) where+  render (Parameter tp) = render $ parserHint tp+  render (Command info subtree) =     "{" <> render (cmdHead info) <> " " <> render subtree <> "}"-  usageInfo (Option info subtree) =+  render (Option info subtree) =     render flag     <> if nullary subtree        then mempty@@ -288,7 +368,7 @@           separator = case flag of                         LongFlag _ -> "="                         _          -> ""-  usageInfo (RequestOption info _) =+  render (RequestOption info _) =     render (optHead info)  instance Render (Token UnixScheme) where@@ -318,11 +398,11 @@ -- > decomposeTree tree [] -- No filtering -- > decomposeTree tree ["stash", "list"] -- Select "stash list" command decomposeTree :: ParseTree UnixScheme r -> [Text] -> Usages r-decomposeTree (ParseNode (RequestOption info requestType)) commands =+decomposeTree (ParseNode (RequestOption info mkRequest)) commands =   -- If we're currently searching for a specific command, then   -- this request option is irrelevant.-  let node = ParseNode (RequestOption info requestType)-  in Usages (if null commands then [node] else []) Nothing []+  let node = ParseNode (RequestOption info mkRequest)+  in Usages [node | null commands] Nothing []  decomposeTree (ParseNode (Command info subtree)) commands   | commandMismatch =@@ -339,7 +419,7 @@   where     commandMismatch =       case commands of-        (command : _) -> not $ command `elem` cmdNames info+        (command : _) -> command `notElem` cmdNames info         []            -> False  decomposeTree (SumNode l r) commands =@@ -382,24 +462,6 @@   where     usageModes = map vacuous reqs <> maybeToList misc <> cmds -instance SupportsResponse UnixScheme where-  makeVersionInfo info = renderText-    $ render (programName info)-    <> " version "-    <> renderVersion (programVersion info)-    <> "\n"-    where-      renderVersion = TLB.fromString . showVersion--  makeHelpInfo tree context info = renderText-    $ "Usage:\n"-    <> formatUsages (programName info) usages <> "\n\n"-    <> render (programDesc info) <> "\n"-    <> renderHelp tree context-    where-      commandContext = [cmd | UnixCommand cmd <- context]-      usages = decomposeTree tree commandContext- -- | Convenient type alias for Unix-flavored parse trees. type UnixParser = ParseTree UnixScheme @@ -416,7 +478,7 @@ addHelpOptions flags desc tree = ParseNode helpOption <|> go tree   where     helpOption :: UnixScheme a-    helpOption = RequestOption (OptionInfo flags desc) HelpRequest+    helpOption = RequestOption (OptionInfo flags desc) helpRequest      go :: ParseTree UnixScheme a -> ParseTree UnixScheme a     go (ParseNode (Command info subtree)) =@@ -497,8 +559,7 @@   <> render (cmdHelp info)   <> "\n"   where-    quote m = "\"" <> m <> "\""-    fmtCommand = quote . render . T.unwords . fmap cmdHead . reverse+    fmtCommand = quotes . render . T.unwords . fmap cmdHead . reverse     aliases = NonEmpty.tail $ cmdNames info     aliasInfo =       if null aliases@@ -533,11 +594,9 @@ -- that exist underneath the current command context. renderHelp   :: ParseTree UnixScheme r-  -> [Token UnixScheme] -- ^ Context Stack+  -> [Text] -- ^ Command Context   -> Builder-renderHelp tree contexts =+renderHelp tree cmds =   renderTables-  $ selectSubtable commandContext+  $ selectSubtable cmds   $ collectOptions tree-  where-    commandContext = reverse [s | UnixCommand s <- contexts]
+ src/Mangrove/Stream.hs view
@@ -0,0 +1,230 @@+{-# LANGUAGE DeriveFunctor             #-}+{-# LANGUAGE DeriveGeneric             #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE FlexibleContexts          #-}+{-# LANGUAGE FlexibleInstances         #-}+{-# LANGUAGE MultiParamTypeClasses     #-}+{-# LANGUAGE OverloadedStrings         #-}+{-# LANGUAGE PolymorphicComponents     #-}+{-# LANGUAGE TypeFamilies              #-}++{-|+Module      : Mangrove.Stream+Copyright   : (c) Quytelda Kahja, 2026+License     : BSD-3-Clause++Provides a basic stream-parsing monad for parsing argument sequences+with error handling and context management.+-}++module Mangrove.Stream+  ( -- * Stream Parser+    StreamParser(..)+  , StreamHandler(..)+  , StreamState(..)+  , failure+  , request++    -- ** Escaping+  , setEscaped+  , getEscaped++    -- ** Context+  , getContext+  , setContext+  , withContext+  , formatError++    -- ** Streaming+  , popMaybe+  , peekMaybe+  , pop+  , peek+  , push+  , pop_+  , getContent+) where++import           Control.Applicative+import           Control.Monad.Except+import qualified Data.List              as List+import           Data.Text              (Text)+import qualified Data.Text.Lazy         as TL+import qualified Data.Text.Lazy.Builder as TLB+import           GHC.Generics++import           Mangrove.Render++-- | The current state of a stream parser.+--+-- The content of a stream is just a list of 'Text' values. The+-- context stack is a list of tokens currently being processed; when a+-- token is recognized, it gets added to front of the list while the+-- token is being parsed into a usable value. When this parsing+-- completes, the token is popped from the front of the list.+--+-- A streams can also enable "escaped" mode by setting 'streamEscaped'+-- to 'True'. What this actually does is parser-dependant, but usually+-- it restricts how subsequent arguments can be interpreted. For+-- example, in the Unix scheme, escaping forces all subsequent+-- arguments to be interpreted as positional arguments, even if they+-- would normally be interpreted as options or commands.+data StreamState tok = StreamState+  { streamContent :: ![Text] -- ^ A sequence of 'Text' values+  , streamContext :: ![tok] -- ^ A stack representing current parsing context+  , streamEscaped :: !Bool -- ^ Escaped mode+  } deriving (Eq, Generic, Show)++-- | A collection of continuations to be called for each situation a+-- stream parser might encounter.+data StreamHandler req tok a r = StreamHandler+  { onSuccess :: StreamState tok -> a -> r -- ^ Success Continuation+  , onEmpty   :: StreamState tok -> r -- ^ Empty continuation+  , onFailure :: StreamState tok -> Builder -> r -- ^ Failure Continuation+  , onRequest :: StreamState tok -> req -> r -- ^ Request Continuation+  } deriving (Functor)++-- | The amazing stream parsing monad! This monad tracks the stream+-- state and context. It short-circuits when exceptions or requests+-- are raised.+newtype StreamParser req tok a = StreamParser+  { runStreamParser+    :: forall r. StreamHandler req tok a r+    -> StreamState tok+    -> r+  }++instance Functor (StreamParser req tok) where+  fmap f parser = StreamParser $ \handler ->+    runStreamParser parser handler { onSuccess = \s -> onSuccess handler s . f }++instance Applicative (StreamParser req tok) where+  pure a = StreamParser $ \handler state -> onSuccess handler state a+  mf <*> ma = StreamParser $ \handler ->+    runStreamParser mf+    handler { onSuccess = \s f -> runStreamParser ma handler { onSuccess = \s' -> onSuccess handler s' . f } s }++instance Alternative (StreamParser req tok) where+  empty = StreamParser $ \handler -> onEmpty handler+  l <|> r = StreamParser $ \handler ->+    runStreamParser l handler { onEmpty = runStreamParser r handler }++instance Monad (StreamParser req tok) where+  return = pure+  ma >>= f = StreamParser $ \handler ->+    runStreamParser ma handler { onSuccess = \s a -> runStreamParser (f a) handler s }++-- | Exit parsing with an error message because something has gone+-- wrong.+failure :: Builder -> StreamParser req tok a+failure err = StreamParser $ \handler state ->+  onFailure handler state err++instance MonadError Builder (StreamParser req tok) where+  throwError = failure+  catchError ma recover = StreamParser $ \handler state ->+    runStreamParser ma+    handler { onFailure = \_ err -> runStreamParser (recover err) handler state }+    state++-- | Enable or disable escaped parsing. What this actually does is+-- parser-dependant, but usually it restricts how subsequent arguments+-- can be interpreted. For example, in the Unix scheme, escaping+-- forces all subsequent arguments to be interpreted as positional+-- arguments, even if they would normally be interpreted as options or+-- commands.+setEscaped :: Bool -> StreamParser req tok ()+setEscaped b = StreamParser $ \handler state ->+  onSuccess handler state { streamEscaped = b } ()++-- | Check whether escaped parsing is enabled.+getEscaped :: StreamParser req tok Bool+getEscaped = StreamParser $ \handler state ->+  onSuccess handler state (streamEscaped state)++-- | Signal that information is requested. Short-circuits any further+-- operations.+request :: req -> StreamParser req tok a+request requestType = StreamParser $ \handler state ->+  onRequest handler state requestType++-- | Get a list representing the current context stack.+getContext :: StreamParser req tok [tok]+getContext = StreamParser $ \handler state ->+  onSuccess handler state (streamContext state)++-- | Replace the context stack.+setContext :: [tok] -> StreamParser req tok ()+setContext contexts = StreamParser $ \handler state ->+  onSuccess handler state { streamContext = contexts } ()++-- | Push the provided token onto the context stack, then perform some+-- computation. Afterwards, the stack is restored to its prior state.+withContext :: tok -> StreamParser req tok a -> StreamParser req tok a+withContext context action = do+  oldContext <- getContext+  setContext $ context : oldContext+  action <* setContext oldContext++-- | Format an error message with context information.+formatError :: Render tok => [tok] -> Builder -> Text+formatError contexts err =+  TL.toStrict+  $ TLB.toLazyText+  $ mconcat+  $ List.intersperse ": "+  $ reverse+  $ err : map render contexts++--------------------------------------------------------------------------------++-- | Retrieve the full list of unconsumed input. This doesn't consume+-- anything or alter the state.+getContent :: StreamParser req tok [Text]+getContent = StreamParser $ \handler state ->+  onSuccess handler state $ streamContent state++-- | Remove and return the first token in the stream.+popMaybe :: StreamParser req tok (Maybe Text)+popMaybe = StreamParser $ \handler state ->+  case streamContent state of+    (t:ts') -> onSuccess handler state { streamContent = ts' } (Just t)+    _       -> onSuccess handler state Nothing++-- | View the first token in the stream without consuming it.+peekMaybe :: StreamParser req tok (Maybe Text)+peekMaybe = StreamParser $ \handler state ->+  case streamContent state of+    (t:_) -> onSuccess handler state (Just t)+    _     -> onSuccess handler state Nothing++-- | Remove and return the first token in the stream. Evaluates to+-- 'empty' if there are no tokens in the stream.+pop :: StreamParser req tok Text+pop = StreamParser $ \handler state ->+  case streamContent state of+    (t:ts') -> onSuccess handler state { streamContent = ts' } t+    _       -> onEmpty handler state++-- | View the first token in the stream without consuming it.+-- Evaluates to 'empty' if there are no tokens in the stream.+peek :: StreamParser req tok Text+peek = StreamParser $ \handler state ->+  case streamContent state of+    (t:_) -> onSuccess handler state t+    _     -> onEmpty handler state++-- | Prepend a token to the front of the stream.+push :: Text -> StreamParser req tok ()+push t = StreamParser $ \handler state ->+  onSuccess handler+  state { streamContent = t : streamContent state }+  ()++-- | Discard the first token in the stream. Nothing happens if there+-- are no tokens in the stream.+pop_ :: StreamParser req tok ()+pop_ = StreamParser $ \handler state ->+  onSuccess handler+  state { streamContent = drop 1 $ streamContent state }+  ()
− src/Mangrove/Text.hs
@@ -1,107 +0,0 @@-{-# LANGUAGE FlexibleContexts  #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GADTs             #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TypeFamilies      #-}-{-# LANGUAGE ViewPatterns      #-}--{-|-Module      : Mangrove.Text-Copyright   : (c) Quytelda Kahja, 2026-License     : BSD-3-Clause--Utilities for dealing with various types of text.--}-module Mangrove.Text-  ( -- * Text Rendering-    Render(..)-  , renderLazyText-  , renderText-  , putBuilder-  , hPutBuilder--    -- * Helpers & Combinators-  , between-  , brackets-  , braces-  , renderDelimitedIf-  , keyEqualsValue--    -- * Re-exports-  , Builder-  ) where--import           Data.Text              (Text)-import qualified Data.Text              as T-import qualified Data.Text.Lazy         as TL-import           Data.Text.Lazy.Builder (Builder)-import qualified Data.Text.Lazy.Builder as TLB-import qualified Data.Text.Lazy.IO      as TLIO-import           System.IO---- | A class for things that can be rendered to a text 'Builder'.-class Render a where-  render :: a -> Builder--instance Render Builder where-  render = id--instance Render T.Text where-  render = TLB.fromText--instance Render Char where-  render = TLB.singleton--instance Render String where-  render = TLB.fromString---- | Convert renderable data directly to lazy 'TL.Text'.-renderLazyText :: Render a => a -> TL.Text-renderLazyText = TLB.toLazyText . render---- | Convert renderable data directly to strict 'T.Text'.-renderText :: Render a => a -> Text-renderText = TL.toStrict . TLB.toLazyText . render---- | Write the contents of a 'Builder' to standard output.-putBuilder :: Builder -> IO ()-putBuilder = TLIO.putStr . TLB.toLazyText---- | Write the contents of a 'Builder' to some IO handle.-hPutBuilder :: Handle -> Builder -> IO ()-hPutBuilder handle = TLIO.hPutStr handle . TLB.toLazyText------------------------------------------------------------------------------------- Combinators---- | @between open close s@ surrounds @s@ with @open@ and @close@--- (i.e. @open <> s <> close@).-between :: Monoid m => m -> m -> m -> m-between open close s = open <> s <> close---- | Surround a string with square brackets.-brackets :: Builder -> Builder-brackets = between "[" "]"---- | Surround a string with curly braces.-braces :: Builder -> Builder-braces = between "{" "}"---- | @renderDelimitedIf wrap f x@ will render @x@ as a 'Builder'. If--- the condition @f x@ is @True@, the result will be modified using--- the function @wrap@, otherwise the result will be returned--- unmodified.-renderDelimitedIf :: Render a => (Builder -> Builder) -> (a -> Bool) -> a -> Builder-renderDelimitedIf wrap f x = (if f x then wrap else id) (render x)------------------------------------------------------------------------------------- Utility Functions---- | Parse a 'Text' of the form "key=value" into ("key", "value"). If--- the delimiter ('=') does not appear in the string, the result is--- 'Nothing'.-keyEqualsValue :: Text -> Maybe (Text, Text)-keyEqualsValue s =-  case T.break (== '=') s of-    (key, T.uncons -> Just (_, value)) -> Just (key, value)-    _                                  -> Nothing
src/Mangrove/TextParser.hs view
@@ -31,6 +31,8 @@   , parseLazyText   , parseLazyTextBuilder   , parseString+  , parseFilePath+  , showsTextParser      -- * Automatic Parser Selection   , DefaultParser(..)@@ -44,7 +46,7 @@ import qualified Data.Text.Lazy.Builder as TLB import qualified Data.Text.Read         as TR -import           Mangrove.Text+import           Mangrove.Render  -- | A @TextParser@ is the most basic client-defined parsing unit. It -- parses textual data that is not otherwise part of the parsing@@ -55,6 +57,21 @@   , parserRun  :: Text -> Either Text r -- ^ An actual parsing function   } deriving (Functor) +instance Show (TextParser r) where+  showsPrec p parser = showParen (p >= 11)+    $ showString "TextParser "+    . showString "{ parserHint = " . shows (parserHint parser)+    . showString ", parserRun = _"+    . showString "}"++-- | A nicer way to show t'TextParser's is to use the parser's hint,+-- surrounded by angle brackets, e.g. @<INT>@.+showsTextParser :: TextParser a -> ShowS+showsTextParser TextParser{parserHint = hint} =+  showString "<"+  . showString (T.unpack hint)+  . showString ">"+ -- | A more general function for running t'TextParser's. runTextParser :: MonadError Builder m => TextParser r -> Text -> m r runTextParser tp = liftEither . first TLB.fromText . parserRun tp@@ -152,7 +169,7 @@ instance DefaultParser Double where   defaultParser = parseDouble --- | Parse a strict 'Text' value.+-- | Parse a strict 'T.Text' value. -- -- Since the input is already strict 'Text', this parser simply returns it for free. parseText :: TextParser Text@@ -164,6 +181,7 @@ instance DefaultParser Text where   defaultParser = parseText +-- | Parse a lazy 'TL.Text' value. parseLazyText :: TextParser TL.Text parseLazyText = TextParser   { parserHint = "STRING"@@ -173,6 +191,7 @@ instance DefaultParser TL.Text where   defaultParser = parseLazyText +-- | Parse a lazy text 'TLB.Builder'. parseLazyTextBuilder :: TextParser TLB.Builder parseLazyTextBuilder = TextParser   { parserHint = "STRING"@@ -191,3 +210,10 @@  instance DefaultParser String where   defaultParser = parseString++-- | Parser for 'FilePath's.+--+-- This is the same as 'parseString' but with a more specialized+-- parser hint.+parseFilePath :: TextParser FilePath+parseFilePath = parseString { parserHint = "PATH" }
+ src/Mangrove/Token.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE TypeFamilies     #-}++{-|+Module      : Mangrove.Token+Copyright   : (c) Quytelda Kahja, 2026+License     : BSD-3-Clause++Parsing schemes convert arguments into some form of token. This module+defines the requirements for a scheme's associated token type.+-}++module Mangrove.Token+  ( HasTokens(..)+  ) where++import           Data.Kind+import           Data.Proxy++import           Mangrove.Render++-- | Parsing schemes convert arguments into some form of token. This+-- class defines the associated token type for a particular scheme.+class (Eq (Token s), Show (Token s), Render (Token s)) => HasTokens (s :: Type -> Type) where+  -- | A token represents a particular interpretation of an argument+  -- string.+  data Token s++  -- | 'delimiter' is the character that separates argument strings in+  -- combined string representation. For example, arguments in the CLI+  -- command @ls -a -l /var@ are separated by spaces.+  delimiter :: Proxy s -> Char
src/Mangrove/Unix.hs view
@@ -5,8 +5,13 @@ Copyright   : (c) Quytelda Kahja, 2026 License     : BSD-3-Clause -An API for defining, constructing, and running Unix-style command line-parsers.+This module contains the building blocks for creating UNIX-style+command line parsers. Parsers are intended to be combined using the+standard combinators in "Control.Applicative".++The functions for running parsers live in the "Mangrove.Parser" module+and are re-exported by the "Mangrove" module alongside other useful+symbols. -}  module Mangrove.Unix@@ -18,6 +23,8 @@   , Flag(..)   , TextParser(..)   , DefaultParser(..)+  , UnixRequest(..)+  , UnixRequest'      -- * Tree-building Combinators   , parameter@@ -29,15 +36,19 @@   , subparameter   , suboption -  -- ** Help Options+    -- ** Help Options   , addHelpOptions++    -- * Requests+  , helpRequest+  , versionRequest   ) where  import           Control.Applicative import           Data.List.NonEmpty   (NonEmpty) import           Data.Text            (Text) -import           Mangrove.Parser+import           Mangrove.ParseTree import           Mangrove.Scheme.Sub  (SubParser, SubScheme) import qualified Mangrove.Scheme.Sub  as Sub import           Mangrove.Scheme.Unix@@ -81,7 +92,7 @@ requestOption   :: NonEmpty Flag   -> Text-  -> RequestType+  -> UnixRequest'   -> UnixParser a requestOption flags help = ParseNode . RequestOption (OptionInfo flags help) 
+ test/Arbitrary.hs view
@@ -0,0 +1,164 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+{-# LANGUAGE DataKinds          #-}+{-# LANGUAGE DeriveGeneric      #-}+{-# LANGUAGE FlexibleInstances  #-}+{-# LANGUAGE OverloadedStrings  #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeApplications   #-}++module Arbitrary+  ( ArgList(..)+  , Name(..)+  ) where++import           Data.Char+import           Data.Text                 (Text)+import qualified Data.Text                 as T+import           System.Random+import           Test.QuickCheck           hiding (Result (..))+import           Test.QuickCheck.Gen       (Gen (..))+import           Test.QuickCheck.Instances ()++import           Mangrove+import           Mangrove.ParseTree+import           Mangrove.Render+import qualified Mangrove.Scheme.Sub       as Sub+import           Mangrove.Scheme.Unix+import qualified Mangrove.Scheme.Unix      as Unix+import           Mangrove.Stream+import           Mangrove.TextParser+import           Mangrove.Unix++--------------------------------------------------------------------------------+-- Arbitrary Name Generator++randomNameChar :: RandomGen g => g -> (Char, g)+randomNameChar gen = (chr (n + offset), gen')+  where+    (n, gen') = uniformR (0, 62) gen+    offset+      | n >= 0  && n < 10 = 48+      | n >= 10 && n < 36 = 55+      | n >= 36 && n < 62 = 61+      -- The only remaining case is n == 62.+      | otherwise = 33++randomNameText :: RandomGen g => g -> Int -> Text+randomNameText gen n = T.unfoldrN n (Just . randomNameChar) gen++genNameChar :: Gen Char+genNameChar = MkGen $ const . fst . randomNameChar++genNameText :: Gen Text+genNameText = MkGen randomNameText `suchThat` (not . T.null)++-- | newtype wrapper for 'Text' that holds results from 'genNameText'+newtype Name = Name { getName :: Text }+  deriving (Eq, Show)++getNames :: Functor f => f Name -> f Text+getNames = fmap getName++instance Arbitrary Name where+  arbitrary = Name <$> genNameText++--------------------------------------------------------------------------------+-- Generic ParseTrees++genParser :: Scheme s => Gen (s Int) -> Gen (ParseTree s Int)+genParser genScheme = sized $ \n -> oneof $+  if n <= 0+  then [ pure EmptyNode, ValueNode <$> arbitrary ]+  else [ pure EmptyNode+       , ValueNode <$> arbitrary+       , ParseNode <$> genScheme+       , ProdNode <$> arbitrary @(Int -> Int -> Int)+                  <*> genParser genScheme+                  <*> genParser genScheme+       , SumNode <$> genParser genScheme <*> genParser genScheme+         -- ManyNode can only give us a `UnixParser [Int]`, so we have+         -- to wrap it in order to make the types match.+       , (fmap . fmap) sum $ ManyNode <$> arbitrary <*> genParser genScheme+       ]++--------------------------------------------------------------------------------+-- SubScheme Parsers++instance Arbitrary (Token SubScheme) where+  arbitrary =+    oneof [ SubAssoc <$> genNameText <*> arbitrary+          , SubArgument <$> arbitrary+          ]++instance CoArbitrary (Token SubScheme)++genSubScheme :: Gen (SubScheme Int)+genSubScheme =+  oneof [ pure $ Sub.Parameter defaultParser+        , flip Sub.Option defaultParser <$> arbitrary+        ]++instance Arbitrary (ParseTree SubScheme Int) where+  arbitrary = genParser genSubScheme++--------------------------------------------------------------------------------+-- UnixScheme Parsers++instance Arbitrary Flag where+  arbitrary =+    oneof [ LongFlag <$> genNameText+          , ShortFlag <$> genNameChar+          ]++instance CoArbitrary Flag++instance Arbitrary Unix.OptionInfo where+  arbitrary = OptionInfo <$> arbitrary <*> arbitrary++instance Arbitrary Unix.CommandInfo where+  arbitrary = CommandInfo <$> fmap getNames arbitrary <*> arbitrary++instance Arbitrary (Token UnixScheme) where+  arbitrary =+    oneof [ UnixArgument <$> arbitrary+          , UnixCommand <$> genNameText+          , UnixOption <$> arbitrary <*> arbitrary+          ]++instance CoArbitrary (Token UnixScheme)++instance Arbitrary UnixRequest where+  arbitrary = elements [HelpRequest [], VersionRequest]++instance CoArbitrary UnixRequest++genUnixScheme :: Gen (UnixScheme Int)+genUnixScheme =+  oneof [ pure $ Unix.Parameter defaultParser+        , Unix.Option <$> arbitrary <*> arbitrary+        , Unix.Command <$> arbitrary <*> arbitrary+        , Unix.RequestOption <$> arbitrary <*> arbitrary+        ]++instance Arbitrary (ParseTree UnixScheme Int) where+  arbitrary = genParser genUnixScheme++--------------------------------------------------------------------------------+-- StreamParsers++genUnixArgument :: Gen Text+genUnixArgument = renderText <$> arbitrary @(Token UnixScheme)++genUnixArgs :: Gen [Text]+genUnixArgs = sized $ \n -> vectorOf n genUnixArgument++newtype ArgList = ArgList { getArgs :: [Text] }+  deriving (Show)++instance Arbitrary ArgList where+  arbitrary = ArgList <$> genUnixArgs++instance Arbitrary (StreamState (Token UnixScheme)) where+  arbitrary = StreamState <$> genUnixArgs <*> arbitrary <*> arbitrary++instance CoArbitrary (StreamState (Token UnixScheme))
test/General.hs view
@@ -4,58 +4,56 @@ module General (spec) where  import           Control.Applicative--import           Data.Version import           Test.Hspec  import           Mangrove-import           Mangrove.Text+import           Mangrove.Render  import           TestParsers  optionSpec :: Spec optionSpec = do   it "parses long options" $ do-    runHelpfulParser_ opt_example_unit ["--example"]+    runArgumentParser opt_example_unit ["--example"]       `shouldBe` Success [] ()   it "parses short options" $ do-    runHelpfulParser_ opt_e_unit ["-e"]+    runArgumentParser opt_e_unit ["-e"]       `shouldBe` Success [] ()    it "parses options in any order" $ do-    runHelpfulParser_ (opt_e_unit *> opt_f_unit) ["-e", "-f"]+    runArgumentParser (opt_e_unit *> opt_f_unit) ["-e", "-f"]       `shouldBe` Success [] ()-    runHelpfulParser_ (opt_e_unit *> opt_f_unit) ["-f", "-e"]+    runArgumentParser (opt_e_unit *> opt_f_unit) ["-f", "-e"]       `shouldBe` Success [] ()    describe "switches" $ do     context "when switch is present" $ do       it "yields True" $ do-        runHelpfulParser_ opt_example_switch ["--example"]+        runArgumentParser opt_example_switch ["--example"]           `shouldBe` Success [] True     context "when switch is absent" $ do       it "yields False" $ do-        runHelpfulParser_ opt_example_switch []+        runArgumentParser opt_example_switch []           `shouldBe` Success [] False    context "when a bound argument is provided" $ do     context "when an argument is expected" $ do       it "parses the argument" $ do-        runHelpfulParser_ opt_example_param ["--example=qwer"]+        runArgumentParser opt_example_param ["--example=qwer"]           `shouldBe` Success [] "qwer"-        runHelpfulParser_ opt_e_param ["-eqwer"]+        runArgumentParser opt_e_param ["-eqwer"]           `shouldBe` Success [] "qwer"     context "when no argument is expected" $ do       it "parsing fails" $ do-        runHelpfulParser_ opt_example_unit ["--example=qwer"]+        runArgumentParser opt_example_unit ["--example=qwer"]           `shouldBe` Failure "--example=qwer: unrecognized subargument: qwer"-        runHelpfulParser_ opt_e_unit ["-eqwer"]+        runArgumentParser opt_e_unit ["-eqwer"]           `shouldBe` Failure "-eqwer: unrecognized subargument: qwer"    context "when no argument is expected" $ do     context "when an argument is available" $ do       it "doesn't consume the argument" $ do-        runHelpfulParser_ opt_example_unit ["--example", "qwer"]+        runArgumentParser opt_example_unit ["--example", "qwer"]           `shouldBe` Success ["qwer"] ()    context "when an argument is required" $ do@@ -65,11 +63,11 @@      context "when no argument is provided" $ do       it "fails to parse" $ do-        runHelpfulParser_ opt_example_param ["--example"]+        runArgumentParser opt_example_param ["--example"]           `shouldBe` Failure "--example: expected: STRING"     context "when an argument is provided" $ do       it "the argument is consumed" $ do-        runHelpfulParser_ opt_example_param ["--example", "qwer"]+        runArgumentParser opt_example_param ["--example", "qwer"]           `shouldBe` Success [] "qwer"    context "when an argument is optional" $ do@@ -78,97 +76,91 @@      context "when no argument is provided" $ do       it "yields a default value" $ do-        runHelpfulParser_ opt_example_param_optional ["--example"]+        runArgumentParser opt_example_param_optional ["--example"]           `shouldBe` Success [] "asdf"       it "does not consume subsequent options" $ do-        runHelpfulParser_ opt_example_param_optional ["--example", "--option"]+        runArgumentParser opt_example_param_optional ["--example", "--option"]           `shouldBe` Success ["--option"] "asdf"     context "when an argument is provided" $ do       it "parses the argument" $ do-        runHelpfulParser_ opt_example_param_optional ["--example", "qwer"]+        runArgumentParser opt_example_param_optional ["--example", "qwer"]           `shouldBe` Success [] "qwer"    describe "compound options" $ do     context "when the subtree accepts multiple arguments" $ do       it "splits the input by delimiter" $ do-        runHelpfulParser_ opt_example_pair ["--example", "1,3"]+        runArgumentParser opt_example_pair ["--example", "1,3"]           `shouldBe` Success [] (1,3)     context "when the subtree can't accept multiple argument" $ do       it "doesn't split the input by delimiter" $ do-        runHelpfulParser_ opt_example_param ["--example", "1,3"]+        runArgumentParser opt_example_param ["--example", "1,3"]           `shouldBe` Success [] "1,3"-        runHelpfulParser_ opt_example_param_optional ["--example", "1,3"]+        runArgumentParser opt_example_param_optional ["--example", "1,3"]           `shouldBe` Success [] "1,3"      context "when the subtree accepts suboptions" $ do       it "parses key=value pairs" $ do-        runHelpfulParser_ opt_example_subopt ["--example", "value=asdf"]+        runArgumentParser opt_example_subopt ["--example", "value=asdf"]           `shouldBe` Success [] "asdf"-        runHelpfulParser_ opt_example_subopt ["--example=value=asdf"]+        runArgumentParser opt_example_subopt ["--example=value=asdf"]           `shouldBe` Success [] "asdf"     context "when the subtree can't accept suboptions" $ do       it "doesn't parse key=value pairs" $ do-        runHelpfulParser_ opt_example_param ["--example", "value=asdf"]+        runArgumentParser opt_example_param ["--example", "value=asdf"]           `shouldBe` Success [] "value=asdf"-        runHelpfulParser_ opt_example_param ["--example=value=asdf"]+        runArgumentParser opt_example_param ["--example=value=asdf"]           `shouldBe` Success [] "value=asdf"    describe "help options" $ do-    let progInfo = ProgramInfo-          { programName = "example"-          , programVersion = makeVersion [1,0]-          , programDesc = "description"-          } :: ProgramInfo s--        isResponse (Response {}) = True-        isResponse _             = False+    let isRequest (Request {}) = True+        isRequest _            = False      context "when a help option is present" $ do       it "requests help" $ do-        runHelpfulParser progInfo (withHelp opt_example_unit) ["--help"]-          `shouldSatisfy` isResponse+        runArgumentParser (withHelp opt_example_unit) ["--help"]+          `shouldSatisfy` isRequest       it "works for subcommands" $ do-        runHelpfulParser progInfo (withHelp cmd_example_tree) ["example", "--help"]-          `shouldSatisfy` isResponse-        runHelpfulParser progInfo (withHelp cmd_example_tree) ["example", "asdf", "--help"]-          `shouldSatisfy` isResponse+        runArgumentParser (withHelp cmd_example_tree) ["example", "--help"]+          `shouldSatisfy` isRequest+        runArgumentParser (withHelp cmd_example_tree) ["example", "asdf", "--help"]+          `shouldSatisfy` isRequest      context "when a help option is absent" $ do       it "doesn't request help" $ do-        runHelpfulParser_ (withHelp opt_example_unit) ["--example"]+        runArgumentParser (withHelp opt_example_unit) ["--example"]           `shouldBe` Success [] ()-        runHelpfulParser_ (withHelp opt_example_unit) []+        runArgumentParser (withHelp opt_example_unit) []           `shouldBe` Failure "expected: --help or --example"       it "isn't activated by escaped options" $ do-        runHelpfulParser_ (withHelp opt_example_unit) ["--", "--help"]+        runArgumentParser (withHelp opt_example_unit) ["--", "--help"]           `shouldBe` Failure "unexpected --help"  generalSpec :: Spec generalSpec = do   context "when \"-\" is given as an argument" $ do     it "parses the string \"-\"" $ do-      runHelpfulParser_ param_text ["-"]+      runArgumentParser param_text ["-"]         `shouldBe` Success [] "-"    context "when \"--\" is present in the argument list" $ do     it "treats subsequent arguments as free arguments" $ do-      runHelpfulParser_ param_text ["--", "asdf"]+      runArgumentParser param_text ["--", "asdf"]         `shouldBe` Success [] "asdf"     it "doesn't treat subsequent arguments as options" $ do-      runHelpfulParser_ (option_asdf <|> param_text) ["--", "--asdf"]+      runArgumentParser (option_asdf <|> param_text) ["--", "--asdf"]         `shouldBe` Success [] "--asdf"     it "doesn't treat subsequent arguments as commands" $ do-      runHelpfulParser_ (command_asdf <|> param_text) ["--", "asdf"]+      runArgumentParser (command_asdf <|> param_text) ["--", "asdf"]         `shouldBe` Success [] "asdf"    context "when not enough input is provided" $ do     it "fails to generate a result" $ do-      runHelpfulParser_ param_text []+      runArgumentParser param_text []         `shouldBe` Failure "expected: STRING"    context "when not all input can be consumed" $ do     it "returns unconsumed arguments" $ do-      runHelpfulParser_ param_text ["asdf", "qwer"]+      runArgumentParser param_text ["asdf", "qwer"]         `shouldBe` Success ["qwer"] "asdf"  spec :: Spec
+ test/Mangrove/ParseTreeSpec.hs view
@@ -0,0 +1,282 @@+{-# LANGUAGE OverloadedLists   #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeApplications  #-}++module Mangrove.ParseTreeSpec (spec) where++import           Control.Applicative+import           Data.Text             (Text)+import           Test.Hspec+import           Test.Hspec.QuickCheck+import           Test.QuickCheck       hiding (Result (..))++import           Mangrove+import           Mangrove.ParseTree+import           Mangrove.Scheme.Unix+import           Mangrove.Valency++import           Arbitrary+import           StructureEq+import           TestParsers++--------------------------------------------------------------------------------+-- Functor Laws++prop_fmapIdLaw :: UnixParser Int -> Bool+prop_fmapIdLaw tree = structEq tree (fmap id tree)++prop_fmapComLaw :: UnixParser Int -> Bool+prop_fmapComLaw tree =+  fmap (inc . dbl) tree+  `structEq`+  (fmap inc . fmap dbl) tree+  where+    inc = (1+)+    dbl = (2*)++--------------------------------------------------------------------------------+-- Applicative Laws++prop_applicativeIdLaw+  :: ParseTree UnixScheme Int+  -> ArgList+  -> Bool+prop_applicativeIdLaw tree (ArgList args) =+  result1 == result2+  where+    result1 = runArgumentParser (pure id <*> tree) args+    result2 = runArgumentParser tree args++prop_applicativeHomLaw+  :: Fun Int Int+  -> Int+  -> ArgList+  -> Bool+prop_applicativeHomLaw (Fn f) value (ArgList args) =+  result1 == result2+  where+    tree1 = pure f <*> pure value :: ParseTree UnixScheme Int+    tree2 = pure (f value) :: ParseTree UnixScheme Int+    result1 = runArgumentParser tree1 args+    result2 = runArgumentParser tree2 args++prop_applicativeIntLaw+  :: Fun (Int, Int) Int+  -> ParseTree UnixScheme Int+  -> Int+  -> ArgList+  -> Bool+prop_applicativeIntLaw (Fn2 f) tree n (ArgList args) =+  result1 == result2+  where+    u = fmap f tree+    result1 = runArgumentParser (u <*> pure n) args+    result2 = runArgumentParser (pure ($ n) <*> u) args++prop_applicativeComLaw+  :: Fun (Int, Int) Int+  -> Fun (Int, Int) Int+  -> ParseTree UnixScheme Int+  -> ParseTree UnixScheme Int+  -> ParseTree UnixScheme Int+  -> ArgList+  -> Bool+prop_applicativeComLaw (Fn2 f) (Fn2 g) t1 t2 w (ArgList args) =+  result1 == result2+  where+    u = fmap f t1+    v = fmap g t2+    tree1 = pure (.) <*> u <*> v <*> w+    tree2 = u <*> (v <*> w)+    result1 = runArgumentParser tree1 args+    result2 = runArgumentParser tree2 args++--------------------------------------------------------------------------------++prop_valencyPositive+  :: UnixParser Int+  -> Bool+prop_valencyPositive p =+  all (>= 0) (valency p)++prop_liftA2AddsValencies+  :: UnixParser Int+  -> UnixParser Int+  -> Bool+prop_liftA2AddsValencies l r =+  valency (liftA2 (+) l r) == liftA2 (+) (valency l) (valency r)++prop_liftA2CombinesResults+  :: Fun (Int, Int) Int+  -> UnixParser Int+  -> UnixParser Int+  -> ArgList+  -> Bool+prop_liftA2CombinesResults (Fn2 f) l r (ArgList args) =+  case (resultL, resultR, resultA) of+    (Success _ x, Success _ y, Success _ z) -> z == f x y+    _ -> resultA == resultL || resultA == resultR+  where+    resultL = runArgumentParser l args+    resultR = runArgumentParser r args+    resultA = runArgumentParser (liftA2 f l r) args++prop_altMaxesValency+  :: UnixParser Int+  -> UnixParser Int+  -> Bool+prop_altMaxesValency l r =+  valency (l <|> r) == liftA2 (max) (valency l) (valency r)++prop_altPicksOne+  :: ParseTree UnixScheme Int+  -> ParseTree UnixScheme Int+  -> ArgList+  -> Bool+prop_altPicksOne l r (ArgList args) =+  resultSum == resultL || resultSum == resultR+  where+    resultL = runArgumentParser l args+    resultR = runArgumentParser r args+    resultSum = runArgumentParser (l <|> r) args++prop_altEmptyIdentity+  :: (ParseTree UnixScheme Int -> ParseTree UnixScheme Int)+  -> ParseTree UnixScheme Int+  -> ArgList+  -> Bool+prop_altEmptyIdentity append tree (ArgList args) =+  runArgumentParser tree args == runArgumentParser (append tree) args++--------------------------------------------------------------------------------++spec :: Spec+spec = do+  describe "Functor Instance" $ do+    prop "satisfies identity law"+      prop_fmapIdLaw+    prop "satisfies composition law"+      prop_fmapComLaw++  describe "Applicative Instance" $ do+    prop "satisfies identity law"+      prop_applicativeIdLaw+    prop "satisfies homomorphism law"+      prop_applicativeHomLaw+    prop "satisfies interchange law"+      prop_applicativeIntLaw+    prop "satisfies composition law"+      prop_applicativeComLaw++  describe "Valency Instance" $ do+    prop "valency is always positive"+      prop_valencyPositive++  describe "pure" $ do+    it "resolves to the given value" $ do+      runArgumentParser (ValueNode 'a' :: ParseTree UnixScheme Char) []+        `shouldBe` Success [] 'a'++  describe "liftA2" $ do+    it "combines two values" $ do+      runArgumentParser (liftA2 (+) (pure 1) (pure 2) :: ParseTree UnixScheme Int) []+        `shouldBe` Success [] 3++      -- should be equivalent+      runArgumentParser ((+) <$> pure 1 <*> pure 2 :: ParseTree UnixScheme Int) []+        `shouldBe` Success [] 3++    prop "combines results"+      prop_liftA2CombinesResults+    prop "adds valencies"+      prop_liftA2AddsValencies++  describe "empty" $ do+    it "doesn't resolve to any value" $ do+      runArgumentParser (empty :: ParseTree UnixScheme Char) []+        `shouldBe` Failure "empty"++    it "has valency zero" $ do+      valency (empty :: ParseTree UnixScheme Char)+        `shouldBe` Just 0++  describe "(<|>)" $ do+    prop "valency equals the max valency between its children"+      prop_altMaxesValency+    prop "yields the left or the right result"+      prop_altPicksOne+    prop "empty is left identity" $+      prop_altEmptyIdentity (empty <|>)+    prop "empty is right identity" $+      prop_altEmptyIdentity (<|> empty)++    context "when the left child is resolvable" $ do+      it "resolves as the left child" $ do+        runArgumentParser (pure "asdf" <|> opt_e_param) []+          `shouldBe` Success [] "asdf"++        -- When the right child is also resolvable, it should be+        -- ignored.+        runArgumentParser (pure "asdf" <|> pure "qwer" :: ParseTree UnixScheme Text) []+          `shouldBe` Success [] "asdf"++    context "when the left child is unresolvable" $ do+      it "resolves as the right child" $ do+        runArgumentParser (opt_e_param <|> pure "asdf") []+          `shouldBe` Success [] "asdf"++    context "when one child is triggered" $ do+      it "prunes the other child" $ do+        runArgumentParser (opt_e_unit <|> opt_f_unit) ["-e", "-f"]+          `shouldBe` Success ["-f"] ()+        runArgumentParser (opt_e_unit <|> opt_f_unit) ["-f", "-e"]+          `shouldBe` Success ["-e"] ()++  describe "many" $ do+    it "parses multiple instances" $ do+      runArgumentParser (many opt_e_param) ["-e", "asdf", "-e", "qwer", "-e", "zxcv"]+        `shouldBe` Success [] ["asdf", "qwer", "zxcv"]+    it "parses zero instances" $ do+      runArgumentParser (many opt_e_param) ["blah"]+        `shouldBe` Success ["blah"] []++    it "handles compound trees" $ do+      let tree = (opt_f_unit *> opt_e_param) <|> opt_example_param+      runArgumentParser (many tree) ["-f", "-e", "asdf", "--example", "qwer"]+        `shouldBe` Success [] ["asdf", "qwer"]++    it "doesn't swallow arguments" $ do+      runArgumentParser (many $ opt_f_unit *> opt_e_param) ["-f", "-e", "asdf", "-f"]+        `shouldBe` Failure "expected: -e"+        -- Some attempts at implementing many/some resulted in+        -- arguments being silently swallowed if they were consumed by+        -- a parser inside a ManyNode which didn't receive enough+        -- input to resolve. In some cases this didn't occur until the+        -- second instance of the subtree was triggered. The expected+        -- behavior in this case is to fail with a message about what+        -- input was missing.++  describe "some" $ do+    it "parses multiple instances" $ do+      runArgumentParser (some opt_e_param) ["-e", "asdf", "-e", "qwer", "-e", "zxcv"]+        `shouldBe` Success [] ["asdf", "qwer", "zxcv"]+    it "requires at least one instance" $ do+      runArgumentParser (some opt_e_param) ["blah"]+        `shouldBe` Failure "unexpected blah"++    it "handles compound trees" $ do+      let tree = (opt_f_unit *> opt_e_param) <|> opt_example_param+      runArgumentParser (some tree) ["-f", "-e", "asdf", "--example", "qwer"]+        `shouldBe` Success [] ["asdf", "qwer"]++    it "doesn't swallow arguments" $ do+      runArgumentParser (some $ opt_f_unit *> opt_e_param) ["-f", "-e", "asdf", "-f"]+        `shouldBe` Failure "expected: -e"++  describe "optional" $ do+    it "parses exactly one instance" $ do+      runArgumentParser (optional opt_e_param) ["-e", "asdf", "-e", "qwer", "-e", "zxcv"]+        `shouldBe` Success [ "-e", "qwer", "-e", "zxcv"] (Just "asdf")+    it "parses zero instances" $ do+      runArgumentParser (optional opt_e_param) ["blah"]+        `shouldBe` Success ["blah"] Nothing
− test/Mangrove/ParserSpec.hs
@@ -1,179 +0,0 @@-{-# LANGUAGE OverloadedLists   #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TypeApplications  #-}--module Mangrove.ParserSpec (spec) where--import           Control.Applicative-import           Data.Text               (Text)-import           Data.Text.Lazy.Builder-import           Test.Hspec--import           Mangrove-import           Mangrove.Parser-import           Mangrove.Scheme.Unix--import           TestParsers--spec :: Spec-spec = do-  spec_ParseTree-  spec_StreamParser--spec_ParseTree :: Spec-spec_ParseTree = do-  describe "pure" $ do-    it "resolves to the given value" $ do-      runHelpfulParser_ (ValueNode 'a' :: ParseTree UnixScheme Char) []-        `shouldBe` Success [] 'a'--  describe "liftA2" $ do-    it "combines two values" $ do-      runHelpfulParser_ (liftA2 (+) (pure 1) (pure 2) :: ParseTree UnixScheme Int) []-        `shouldBe` Success [] 3--      -- should be equivalent-      runHelpfulParser_ ((+) <$> pure 1 <*> pure 2 :: ParseTree UnixScheme Int) []-        `shouldBe` Success [] 3--  describe "empty" $ do-    it "doesn't resolve to any value" $ do-      runHelpfulParser_ (empty :: ParseTree UnixScheme Char) []-        `shouldBe` Failure "empty"--  describe "(<|>)" $ do-    context "when the left child is resolvable" $ do-      it "resolves as the left child" $ do-        runHelpfulParser_ (pure "asdf" <|> opt_e_param) []-          `shouldBe` Success [] "asdf"--        -- When the right child is also resolvable, it should be-        -- ignored.-        runHelpfulParser_ (pure "asdf" <|> pure "qwer" :: ParseTree UnixScheme Text) []-          `shouldBe` Success [] "asdf"--    context "when the left child is unresolvable" $ do-      it "resolves as the right child" $ do-        runHelpfulParser_ (opt_e_param <|> pure "asdf") []-          `shouldBe` Success [] "asdf"--    context "when one child is triggered" $ do-      it "prunes the other child" $ do-        runHelpfulParser_ (opt_e_unit <|> opt_f_unit) ["-e", "-f"]-          `shouldBe` Success ["-f"] ()-        runHelpfulParser_ (opt_e_unit <|> opt_f_unit) ["-f", "-e"]-          `shouldBe` Success ["-e"] ()--  describe "many" $ do-    it "parses multiple instances" $ do-      runHelpfulParser_ (many opt_e_param) ["-e", "asdf", "-e", "qwer", "-e", "zxcv"]-        `shouldBe` Success [] ["asdf", "qwer", "zxcv"]-    it "parses zero instances" $ do-      runHelpfulParser_ (many opt_e_param) ["blah"]-        `shouldBe` Success ["blah"] []--    it "handles compound trees" $ do-      let tree = (opt_f_unit *> opt_e_param) <|> opt_example_param-      runHelpfulParser_ (many tree) ["-f", "-e", "asdf", "--example", "qwer"]-        `shouldBe` Success [] ["asdf", "qwer"]--    it "doesn't swallow arguments" $ do-      runHelpfulParser_ (many $ opt_f_unit *> opt_e_param) ["-f", "-e", "asdf", "-f"]-        `shouldBe` Failure "expected: -e"-        -- Some attempts at implementing many/some resulted in-        -- arguments being silently swallowed if they were consumed by-        -- a parser inside a ManyNode which didn't receive enough-        -- input to resolve. In some cases this didn't occur until the-        -- second instance of the subtree was triggered. The expected-        -- behavior in this case is to fail with a message about what-        -- input was missing.--  describe "some" $ do-    it "parses multiple instances" $ do-      runHelpfulParser_ (some opt_e_param) ["-e", "asdf", "-e", "qwer", "-e", "zxcv"]-        `shouldBe` Success [] ["asdf", "qwer", "zxcv"]-    it "requires at least one instance" $ do-      runHelpfulParser_ (some opt_e_param) ["blah"]-        `shouldBe` Failure "unexpected blah"--    it "handles compound trees" $ do-      let tree = (opt_f_unit *> opt_e_param) <|> opt_example_param-      runHelpfulParser_ (some tree) ["-f", "-e", "asdf", "--example", "qwer"]-        `shouldBe` Success [] ["asdf", "qwer"]--    it "doesn't swallow arguments" $ do-      runHelpfulParser_ (some $ opt_f_unit *> opt_e_param) ["-f", "-e", "asdf", "-f"]-        `shouldBe` Failure "expected: -e"--  describe "optional" $ do-    it "parses exactly one instance" $ do-      runHelpfulParser_ (optional opt_e_param) ["-e", "asdf", "-e", "qwer", "-e", "zxcv"]-        `shouldBe` Success [ "-e", "qwer", "-e", "zxcv"] (Just "asdf")-    it "parses zero instances" $ do-      runHelpfulParser_ (optional opt_e_param) ["blah"]-        `shouldBe` Success ["blah"] Nothing------------------------------------------------------------------------------------- Stream Parser Monad--data StreamResult r-  = SSuccess r-  | SEmpty-  | SFailure Builder-  | SRequest RequestType-  deriving (Eq, Show)---- | Sink the results of a 'StreamParser' into a data type for easier inspection.-runStreamParser'-  :: SupportsResponse s-  => StreamParser s r-  -> StreamState s-  -> (StreamState s, StreamResult r)-runStreamParser' parser state =-  runStreamParser parser handler state-  where-    handler = StreamHandler-      { onSuccess = \s result -> (s, SSuccess result)-      , onEmpty = \s -> (s, SEmpty)-      , onFailure = \s err -> (s, SFailure err)-      , onRequest = OnRequest $ \s t -> (s, SRequest t)-      }--initState_empty :: StreamState s-initState_empty = StreamState [] [] False--initState_singleton :: StreamState s-initState_singleton = StreamState ["asdf"] [] False--spec_StreamParser :: Spec-spec_StreamParser = do-  describe "peek" $ do-    context "when the stream is empty" $ do-      let (finalState, result) = runStreamParser' peek (initState_empty @UnixScheme)-      it "returns empty" $ do-        result `shouldBe` SEmpty-      it "preserves the state" $ do-        initState_empty `shouldBe` finalState--    context "when the stream is not empty" $ do-      let (finalState, result) = runStreamParser' peek (initState_singleton @UnixScheme)-      it "gets the first item" $ do-        result `shouldBe` SSuccess "asdf"-      it "preserves the state" $ do-        initState_singleton `shouldBe` finalState--  describe "pop" $ do-    context "when the stream is empty" $ do-      let (finalState, result) = runStreamParser' pop (initState_empty @UnixScheme)-      it "returns empty" $ do-        result `shouldBe` SEmpty-      it "preserves the state" $ do-        initState_empty `shouldBe` finalState--    context "when the stream is not empty" $ do-      let (finalState, result) = runStreamParser' pop (initState_singleton @UnixScheme)-      it "gets the first item without replacement" $ do-        result `shouldBe` SSuccess "asdf"-        streamContent finalState `shouldBe` drop 1 (streamContent initState_singleton)-      it "preserves the context" $ do-        streamContext initState_singleton `shouldBe` streamContext finalState
+ test/Mangrove/StreamSpec.hs view
@@ -0,0 +1,187 @@+{-# LANGUAGE OverloadedLists   #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeApplications  #-}++module Mangrove.StreamSpec (spec) where++import           Data.Maybe+import           Data.Text                 (Text)+import           Test.Hspec+import           Test.Hspec.QuickCheck+import           Test.QuickCheck           hiding (Result (..))+import           Test.QuickCheck.Instances ()++import           Mangrove+import           Mangrove.Scheme.Unix+import           Mangrove.Stream+import           Mangrove.Test.Stream++--------------------------------------------------------------------------------+-- Functor Laws++prop_functorIdLaw+  :: SP_Unix Int+  -> StreamState (Token UnixScheme)+  -> Bool+prop_functorIdLaw (SP_Unix m) state =+  runSPU (fmap id m) state == runSPU m state++prop_functorComLaw+  :: Fun Int Int+  -> Fun Int Int+  -> SP_Unix Int+  -> StreamState (Token UnixScheme)+  -> Bool+prop_functorComLaw (Fn f) (Fn g) (SP_Unix m) state =+  runSPU (fmap (f . g) m) state == runSPU ((fmap f . fmap g) m) state++--------------------------------------------------------------------------------+-- Applicative Laws++prop_applicativeIdLaw+  :: SP_Unix Int+  -> StreamState (Token UnixScheme)+  -> Bool+prop_applicativeIdLaw (SP_Unix m) state =+  runSPU (pure id <*> m) state == runSPU m state++prop_applicativeHomLaw+  :: Fun Int Int+  -> Int+  -> StreamState (Token UnixScheme)+  -> Bool+prop_applicativeHomLaw (Fn f) x state =+  runSPU (pure f <*> pure x) state == runSPU (pure (f x)) state++prop_applicativeIntLaw+  :: SP_Unix (Int -> Int)+  -> Int+  -> StreamState (Token UnixScheme)+  -> Bool+prop_applicativeIntLaw (SP_Unix u) y state =+  runSPU (u <*> pure y) state == runSPU (pure ($ y) <*> u) state++prop_applicativeComLaw+  :: SP_Unix (Int -> Int)+  -> SP_Unix (Int -> Int)+  -> SP_Unix Int+  -> StreamState (Token UnixScheme)+  -> Bool+prop_applicativeComLaw (SP_Unix u) (SP_Unix v) (SP_Unix w) state =+  runSPU (pure (.) <*> u <*> v <*> w) state+  == runSPU (u <*> (v <*> w)) state++--------------------------------------------------------------------------------+-- Monad Laws++prop_monadLeftId+  :: Int+  -> Fun Int (SP_Unix Int)+  -> StreamState (Token UnixScheme)+  -> Bool+prop_monadLeftId a fn state =+  runSPU (f a) state == runSPU (return a >>= f) state+  where+    f = getSPU . applyFun fn++prop_monadRightId+  :: SP_Unix Int+  -> StreamState (Token UnixScheme)+  -> Bool+prop_monadRightId (SP_Unix m) state =+  runSPU m state == runSPU (m >>= return) state++prop_monadAssoc+  :: SP_Unix Int+  -> Fun Int (SP_Unix Int)+  -> Fun Int (SP_Unix Int)+  -> StreamState (Token UnixScheme)+  -> Bool+prop_monadAssoc (SP_Unix m) fn1 fn2 state =+  runSPU ((m >>= f) >>= g) state == runSPU (m >>= (\x -> f x >>= g)) state+  where+    f = getSPU . applyFun fn1+    g = getSPU . applyFun fn2++--------------------------------------------------------------------------------++prop_peek_preservesState+  :: StreamState (Token UnixScheme)+  -> Bool+prop_peek_preservesState state =+  case runSPU peek state of+    (_, state') -> state == state'++prop_pop_preservesContext+  :: StreamState (Token UnixScheme)+  -> Bool+prop_pop_preservesContext state =+  case runSPU pop state of+    (_, state') -> streamContext state == streamContext state'++prop_pop_preservesEscaped+  :: StreamState (Token UnixScheme)+  -> Bool+prop_pop_preservesEscaped state =+  case runSPU pop state of+    (_, state') -> streamEscaped state == streamEscaped state'++prop_yieldsValueOrEmpty+  :: SP_Unix_T Text+  -> StreamState (Token UnixScheme)+  -> Bool+prop_yieldsValueOrEmpty action state =+  case runSPU action state of+    (SPSuccess a, _) -> listToMaybe (streamContent state) == Just a+    (SPEmpty, _)     -> null $ streamContent state+    _                -> False++prop_consumesValue+  :: SP_Unix_T a+  -> StreamState (Token UnixScheme)+  -> Bool+prop_consumesValue action state =+  case runSPU action state of+    (_, state') -> streamContent state' == drop 1 (streamContent state)++spec :: Spec+spec = do+  describe "Functor instance" $ do+    prop "satisfies identity law"+      prop_functorIdLaw+    prop "satisfies composition law"+      prop_functorComLaw++  describe "Applicative instance" $ do+    prop "satisfies identity law"+      prop_applicativeIdLaw+    prop "satisfies homomorphism law"+      prop_applicativeHomLaw+    prop "satisfies interchange law"+      prop_applicativeIntLaw+    prop "satisfies composition law"+      prop_applicativeComLaw++  describe "Monad instance" $ do+    prop "satisfies left identity law"+      prop_monadLeftId+    prop "satisfies right identity law"+      prop_monadRightId+    prop "satisfies associativity law"+      prop_monadAssoc++  describe "peek" $ do+    prop "preserves the stream state"+      prop_peek_preservesState+    prop "yields first value or empty" $+      prop_yieldsValueOrEmpty peek++  describe "pop" $ do+    prop "consumes values" $+      prop_consumesValue pop+    prop "yields first value or empty" $+      prop_yieldsValueOrEmpty pop+    prop "preserves escaped setting" $+      prop_pop_preservesEscaped+    prop "preserves the stream state"+      prop_pop_preservesContext
+ test/Mangrove/Test/Stream.hs view
@@ -0,0 +1,113 @@+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveGeneric #-}++module Mangrove.Test.Stream+  ( -- * Unix Stream Parser+    SP_Unix_T++    -- * Stream Proxy+  , StreamProxy(..)+  , SPState+  , SPResult(..)+  , toStreamParser+  , fromStreamParser++    -- * Unix Stream Parser Wrapper+  , SP_Unix(..)+  , runSPU+  ) where++import           Control.Monad+import           Data.Text                 (Text)+import qualified Data.Text.Lazy            as TL+import qualified Data.Text.Lazy.Builder    as TLB+import           GHC.Generics+import           Test.QuickCheck           hiding (Result (..))+import           Test.QuickCheck.Instances ()++import           Mangrove.Scheme+import           Mangrove.Stream+import           Mangrove.Token+import           Mangrove.Unix++import           Arbitrary                 ()++type SPState = StreamState (Token UnixScheme)++data SPResult a+  = SPSuccess a+  | SPEmpty+  | SPFailure Text+  | SPRequest UnixRequest+  deriving (Eq, Show, Functor, Generic)++instance Arbitrary a => Arbitrary (SPResult a) where+  arbitrary = oneof+    [ SPSuccess <$> arbitrary+    , pure SPEmpty+    , SPFailure <$> arbitrary+    , SPRequest <$> arbitrary+    ]++newtype StreamProxy a = SP { runSP :: SPState -> (SPResult a, SPState) }+  deriving (Functor, Generic)++genSP :: Arbitrary a => Gen (StreamProxy a)+genSP = SP <$> arbitrary++instance Applicative StreamProxy where+  pure a = SP $ \state -> (SPSuccess a, state)+  (<*>) = ap++instance Monad StreamProxy where+  return = pure+  ma >>= f = SP $ \state ->+    let (result, state') = runSP ma state+    in case result of+         SPSuccess a       -> runSP (f a) state'+         SPEmpty           -> (SPEmpty, state')+         SPFailure err     -> (SPFailure err, state')+         SPRequest reqType -> (SPRequest reqType, state')++toStreamParser :: StreamProxy a -> SP_Unix_T a+toStreamParser prox = StreamParser $ \handler state ->+  let (result, state') = runSP prox state+  in case result of+       SPSuccess a       -> onSuccess handler state' a+       SPEmpty           -> onEmpty handler state'+       SPFailure err     -> onFailure handler state' (TLB.fromText err)+       SPRequest reqType -> onRequest handler state' reqType++sinkResult :: StreamHandler (Request UnixScheme) (Token UnixScheme) a (SPResult a, SPState)+sinkResult = StreamHandler+  { onSuccess = \state' a -> (SPSuccess a, state')+  , onEmpty = \state' -> (SPEmpty, state')+  , onFailure = \state' err -> (SPFailure (TL.toStrict $ TLB.toLazyText err), state')+  , onRequest = \state' req -> (SPRequest req, state')+  }++type SP_Unix_T a = StreamParser (Request UnixScheme) (Token UnixScheme) a++fromStreamParser :: SP_Unix_T a -> StreamProxy a+fromStreamParser parser = SP $ runStreamParser parser sinkResult++genStreamParser :: Arbitrary a => Gen (SP_Unix_T a)+genStreamParser = toStreamParser <$> genSP++newtype SP_Unix a = SP_Unix+  { getSPU :: SP_Unix_T a }++instance Show (SP_Unix a) where+  show _ = "(*)"++instance Arbitrary a => Arbitrary (SP_Unix a) where+  arbitrary = SP_Unix <$> genStreamParser++--------------------------------------------------------------------------------+-- Stream Helper++runSPU+  :: SP_Unix_T a+  -> StreamState (Token UnixScheme)+  -> (SPResult a, StreamState (Token UnixScheme))+runSPU parser = runStreamParser parser sinkResult
+ test/StructureEq.hs view
@@ -0,0 +1,57 @@+{-# LANGUAGE GADTs #-}++module StructureEq+  ( StructureEq(..)+  ) where++import           Mangrove+import           Mangrove.ParseTree+import qualified Mangrove.Scheme.Sub  as Sub+import           Mangrove.Scheme.Unix+import qualified Mangrove.Scheme.Unix as Unix+import           Mangrove.TextParser+import           Mangrove.Unix++-- | Things that can be compared for structural equality.+class StructureEq s where+  structEq :: s a -> s b -> Bool++instance StructureEq TextParser where+  structEq tp1 tp2 = parserHint tp1 == parserHint tp2++instance StructureEq SubScheme where+  structEq (Sub.Parameter p1) (Sub.Parameter p2) =+    structEq p1 p2+  structEq (Sub.Option key1 p1) (Sub.Option key2 p2) =+    key1 == key2 && structEq p1 p2+  structEq _ _ =+    False++instance StructureEq UnixScheme where+  structEq (Unix.Parameter p1) (Unix.Parameter p2) =+    structEq p1 p2+  structEq (Unix.Option info1 subtree1) (Unix.Option info2 subtree2) =+    info1 == info2 && structEq subtree1 subtree2+  structEq (Unix.Command info1 subtree1) (Unix.Command info2 subtree2) =+    info1 == info2 && structEq subtree1 subtree2+  structEq (Unix.RequestOption info1 _) (Unix.RequestOption info2 _) =+    info1 == info2+  structEq _ _ =+    False++instance StructureEq s => StructureEq (ParseTree s) where+  structEq EmptyNode EmptyNode =+    True+  structEq (ValueNode _) (ValueNode _) =+    True+  structEq (ParseNode p1) (ParseNode p2) =+    structEq p1 p2+  structEq (ProdNode _ l1 r1) (ProdNode _ l2 r2) =+    structEq l1 l2 && structEq r1 r2+  structEq (SumNode l1 r1) (SumNode l2 r2) =+    structEq l1 l2 && structEq r1 r2+  structEq (ManyNode b1 p1) (ManyNode b2 p2) =+    b1 == b2 && structEq p1 p2+  structEq _ _ =+    False+
test/TestParsers.hs view
@@ -1,3 +1,4 @@+{-# OPTIONS_GHC -Wno-missing-export-lists #-} {-# LANGUAGE OverloadedLists   #-} {-# LANGUAGE OverloadedStrings #-}