mirror of
https://github.com/xmonad/xmonad-contrib.git
synced 2025-05-19 11:30:22 -07:00
New module: XMonad.Util.Parser
This module provides a parser combinator library based on base's ReadP, which aims to function more like other popular combinator libraries like attoparsec and megaparsec. In particular, the Alternative and Monoid instances are left-biased now, so combinators like `many` and `optional` from Control.Applicative work in a more intuitive manner. Further, some functions (like `endBy1`) only return the "most successful" parse, instead of returning all of them. We can now get away with providing a single parsing result instead of ReadP's list of results (as such, parsers need to be disambiguated earlier instead of trimming the list down after parsing).
This commit is contained in:
parent
061faf1748
commit
8b3df5b268
@ -21,6 +21,11 @@
|
|||||||
While XMonad provides config to set all window borders at the same
|
While XMonad provides config to set all window borders at the same
|
||||||
width, this extension defines and sets border width for each window.
|
width, this extension defines and sets border width for each window.
|
||||||
|
|
||||||
|
* `XMonad.Util.Parser`
|
||||||
|
|
||||||
|
A wrapper around the 'ReadP' parser combinator, providing behaviour
|
||||||
|
that's closer to the more popular parser combinator libraries.
|
||||||
|
|
||||||
### Bug Fixes and Minor Changes
|
### Bug Fixes and Minor Changes
|
||||||
|
|
||||||
* `XMonad.Prompt`
|
* `XMonad.Prompt`
|
||||||
|
215
XMonad/Util/Parser.hs
Normal file
215
XMonad/Util/Parser.hs
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
|
{-# LANGUAGE DerivingStrategies #-}
|
||||||
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||||
|
{-# LANGUAGE InstanceSigs #-}
|
||||||
|
{-# LANGUAGE TypeApplications #-}
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
-- |
|
||||||
|
-- Module : XMonad.Util.Parser
|
||||||
|
-- Description : A parser combinator library for xmonad
|
||||||
|
-- Copyright : (c) 2021 slotThe <soliditsallgood@mailbox.org>
|
||||||
|
-- License : BSD3
|
||||||
|
-- Maintainer : slotThe <soliditsallgood@mailbox.org>
|
||||||
|
-- Stability : experimental
|
||||||
|
-- Portability : non-portable
|
||||||
|
--
|
||||||
|
-- A small wrapper around the 'ReadP' parser combinator in @base@,
|
||||||
|
-- providing a more intuitive behaviour. While it's theoretically nice
|
||||||
|
-- that 'ReadP' is actually commutative, this makes a lot of parsing
|
||||||
|
-- operations rather awkward—more often than not, one only wants the
|
||||||
|
-- argument that's parsed "first".
|
||||||
|
--
|
||||||
|
-- Due to the left-biased nature of the chosen semigroup implementation,
|
||||||
|
-- using functions like 'many' or 'optional' from "Control.Applicative"
|
||||||
|
-- now yields more consistent behaviour with other parser combinator
|
||||||
|
-- libraries.
|
||||||
|
--
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
module XMonad.Util.Parser (
|
||||||
|
-- * Usage
|
||||||
|
-- $usage
|
||||||
|
|
||||||
|
-- * Running
|
||||||
|
Parser,
|
||||||
|
runParser,
|
||||||
|
|
||||||
|
-- * Primitive Parsers
|
||||||
|
eof,
|
||||||
|
num,
|
||||||
|
char,
|
||||||
|
string,
|
||||||
|
skipSpaces,
|
||||||
|
get,
|
||||||
|
look,
|
||||||
|
|
||||||
|
-- * Combining Parsers
|
||||||
|
satisfy,
|
||||||
|
choice,
|
||||||
|
many1,
|
||||||
|
sepBy,
|
||||||
|
sepBy1,
|
||||||
|
endBy,
|
||||||
|
endBy1,
|
||||||
|
munch,
|
||||||
|
munch1,
|
||||||
|
) where
|
||||||
|
|
||||||
|
import XMonad.Prelude
|
||||||
|
|
||||||
|
import qualified Text.ParserCombinators.ReadP as ReadP
|
||||||
|
|
||||||
|
import Data.Coerce (coerce)
|
||||||
|
import Text.ParserCombinators.ReadP (ReadP, (<++))
|
||||||
|
|
||||||
|
{- $usage
|
||||||
|
|
||||||
|
NOTE: This module is mostly intended for developing of other modules.
|
||||||
|
If you are a users, you probably won't find much use here—you have been
|
||||||
|
warned.
|
||||||
|
|
||||||
|
The high-level API tries to stay as close to 'ReadP' as possible. If
|
||||||
|
you are familiar with that then no functions here should surprise you.
|
||||||
|
|
||||||
|
One notable usability difference when forcing left-biasedness is /when/
|
||||||
|
one wants to disambiguate a parse. For normal 'ReadP' usage this
|
||||||
|
happens after the actual parsing stage by going through the list of
|
||||||
|
successful parses. For 'Parser' it does when constructing the relevant
|
||||||
|
combinators, leading to only one successful parse. As an example,
|
||||||
|
consider the 'ReadP'-based parser
|
||||||
|
|
||||||
|
> pLangle = ReadP.string "<"
|
||||||
|
> pLongerSequence = ReadP.char '<' *> ReadP.string "f" <* ReadP.char '>'
|
||||||
|
> pCombination = pLangle ReadP.+++ pLongerSequence
|
||||||
|
|
||||||
|
Parsing the string @"<f>"@ will return
|
||||||
|
|
||||||
|
>>> ReadP.readP_to_S pCombination "<f>"
|
||||||
|
[("<","f>"),("f","")]
|
||||||
|
|
||||||
|
One would now need to, for example, filter for the second (leftover)
|
||||||
|
string being empty and take the head of the resulting list (which may
|
||||||
|
still have more than one element).
|
||||||
|
|
||||||
|
With 'Parser', the same situation would look like the following
|
||||||
|
|
||||||
|
> pLangle' = string "<"
|
||||||
|
> pLongerSequence' = char '<' *> string "f" <* char '>'
|
||||||
|
> pCombination' = pLongerSequence' <> pLangle'
|
||||||
|
|
||||||
|
Notice how @pLangle'@ and @pLongerSequence'@ have traded places—since we
|
||||||
|
are not forcing @pLangle'@ to consume the entire string and @(<>)@ is
|
||||||
|
left-biased, @pLongerSequence'@ parses a superset of @pLangle'@!
|
||||||
|
Running @runParser pCombination'@ now yields the expected result:
|
||||||
|
|
||||||
|
>>> runParser pCombination' "<f>"
|
||||||
|
Just "f"
|
||||||
|
|
||||||
|
One might also define @pLangle'@ as @string "<" <* eof@, which would
|
||||||
|
enable a definition of @pCombination' = pLangle' <> pLongerSequence'@.
|
||||||
|
|
||||||
|
For example uses, see "XMonad.Util.EZConfig" or "XMonad.Prompt.OrgMode".
|
||||||
|
-}
|
||||||
|
|
||||||
|
-- Parser :: Type -> Type
|
||||||
|
newtype Parser a = Parser (ReadP a)
|
||||||
|
deriving newtype (Functor, Applicative, Monad)
|
||||||
|
|
||||||
|
instance Semigroup (Parser a) where
|
||||||
|
-- | Local, exclusive, left-biased choice: If left parser locally
|
||||||
|
-- produces any result at all, then right parser is not used.
|
||||||
|
(<>) :: Parser a -> Parser a -> Parser a
|
||||||
|
(<>) = coerce ((<++) @a)
|
||||||
|
|
||||||
|
instance Monoid (Parser a) where
|
||||||
|
-- | A parser that always fails.
|
||||||
|
mempty :: Parser a
|
||||||
|
mempty = Parser empty
|
||||||
|
|
||||||
|
instance Alternative Parser where
|
||||||
|
empty :: Parser a
|
||||||
|
empty = mempty
|
||||||
|
|
||||||
|
(<|>) :: Parser a -> Parser a -> Parser a
|
||||||
|
(<|>) = (<>)
|
||||||
|
|
||||||
|
-- | Run a parser on a given string.
|
||||||
|
runParser :: Parser a -> String -> Maybe a
|
||||||
|
runParser (Parser p) = fmap fst . listToMaybe . ReadP.readP_to_S p
|
||||||
|
|
||||||
|
-- | Consume and return the next character. Fails if there is no input
|
||||||
|
-- left.
|
||||||
|
get :: Parser Char
|
||||||
|
get = coerce ReadP.get
|
||||||
|
|
||||||
|
-- | Look-ahead: return the part of the input that is left, without
|
||||||
|
-- consuming it.
|
||||||
|
look :: Parser String
|
||||||
|
look = coerce ReadP.look
|
||||||
|
|
||||||
|
-- | Succeeds if and only if we are at the end of input.
|
||||||
|
eof :: Parser ()
|
||||||
|
eof = coerce ReadP.eof
|
||||||
|
|
||||||
|
-- | Parse an integral number number.
|
||||||
|
num :: (Read a, Integral a) => Parser a
|
||||||
|
num = read <$> munch1 isDigit
|
||||||
|
{-# SPECIALISE num :: Parser Word #-}
|
||||||
|
{-# SPECIALISE num :: Parser Int #-}
|
||||||
|
{-# SPECIALISE num :: Parser Integer #-}
|
||||||
|
|
||||||
|
-- | Parse and return the specified character.
|
||||||
|
char :: Char -> Parser Char
|
||||||
|
char = coerce ReadP.char
|
||||||
|
|
||||||
|
-- | Parse and return the specified string.
|
||||||
|
string :: String -> Parser String
|
||||||
|
string = coerce ReadP.string
|
||||||
|
|
||||||
|
-- | Skip all whitespace.
|
||||||
|
skipSpaces :: Parser ()
|
||||||
|
skipSpaces = coerce ReadP.skipSpaces
|
||||||
|
|
||||||
|
-- | Consume and return the next character if it satisfies the specified
|
||||||
|
-- predicate.
|
||||||
|
satisfy :: (Char -> Bool) -> Parser Char
|
||||||
|
satisfy = coerce ReadP.satisfy
|
||||||
|
|
||||||
|
-- | Combine all parsers in the given list in a left-biased way.
|
||||||
|
choice :: [Parser a] -> Parser a
|
||||||
|
choice = foldl' (<>) mempty
|
||||||
|
|
||||||
|
-- | Parse the first zero or more characters satisfying the predicate.
|
||||||
|
-- Always succeeds; returns an empty string if the predicate returns
|
||||||
|
-- @False@ on the first character of input.
|
||||||
|
munch :: (Char -> Bool) -> Parser String
|
||||||
|
munch = coerce ReadP.munch
|
||||||
|
|
||||||
|
-- | Parse the first one or more characters satisfying the predicate.
|
||||||
|
-- Fails if none, else succeeds exactly once having consumed all the
|
||||||
|
-- characters.
|
||||||
|
munch1 :: (Char -> Bool) -> Parser String
|
||||||
|
munch1 = coerce ReadP.munch1
|
||||||
|
|
||||||
|
-- | @endBy p sep@ parses zero or more occurrences of @p@, separated and
|
||||||
|
-- ended by @sep@.
|
||||||
|
endBy :: Parser a -> Parser sep -> Parser [a]
|
||||||
|
endBy p sep = many (p <* sep)
|
||||||
|
|
||||||
|
-- | @endBy p sep@ parses one or more occurrences of @p@, separated and
|
||||||
|
-- ended by @sep@.
|
||||||
|
endBy1 :: Parser a -> Parser sep -> Parser [a]
|
||||||
|
endBy1 p sep = many1 (p <* sep)
|
||||||
|
|
||||||
|
-- | Parse one or more occurrences of the given parser.
|
||||||
|
many1 :: Parser a -> Parser [a]
|
||||||
|
many1 p = liftA2 (:) p (many p)
|
||||||
|
|
||||||
|
-- | @sepBy p sep@ parses zero or more occurrences of @p@, separated by
|
||||||
|
-- @sep@. Returns a list of values returned by @p@.
|
||||||
|
sepBy :: Parser a -> Parser sep -> Parser [a]
|
||||||
|
sepBy p sep = sepBy1 p sep <> pure []
|
||||||
|
|
||||||
|
-- | @sepBy1 p sep@ parses one or more occurrences of @p@, separated by
|
||||||
|
-- @sep@. Returns a list of values returned by @p@.
|
||||||
|
sepBy1 :: Parser a -> Parser sep -> Parser [a]
|
||||||
|
sepBy1 p sep = liftA2 (:) p (many (sep *> p))
|
@ -359,6 +359,7 @@ library
|
|||||||
XMonad.Util.NamedScratchpad
|
XMonad.Util.NamedScratchpad
|
||||||
XMonad.Util.NamedWindows
|
XMonad.Util.NamedWindows
|
||||||
XMonad.Util.NoTaskbar
|
XMonad.Util.NoTaskbar
|
||||||
|
XMonad.Util.Parser
|
||||||
XMonad.Util.Paste
|
XMonad.Util.Paste
|
||||||
XMonad.Util.PositionStore
|
XMonad.Util.PositionStore
|
||||||
XMonad.Util.PureX
|
XMonad.Util.PureX
|
||||||
@ -427,6 +428,7 @@ test-suite tests
|
|||||||
XMonad.Util.Image
|
XMonad.Util.Image
|
||||||
XMonad.Util.Invisible
|
XMonad.Util.Invisible
|
||||||
XMonad.Util.NamedWindows
|
XMonad.Util.NamedWindows
|
||||||
|
XMonad.Util.Parser
|
||||||
XMonad.Util.PureX
|
XMonad.Util.PureX
|
||||||
XMonad.Util.Rectangle
|
XMonad.Util.Rectangle
|
||||||
XMonad.Util.Run
|
XMonad.Util.Run
|
||||||
|
Loading…
x
Reference in New Issue
Block a user