packages feed

megaparsec-utils-0.2.0.0: src/Text/Megaparsec/Utils/Char.hs

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}

-- |
-- Module      : Text.Megaparsec.Utils.Char
-- Description : Parsers for streams of 'Char'.
-- Copyright   : (c) drlkf, 2024
-- License     : GPL-3
-- Maintainer  : drlkf@drlkf.net
-- Stability   : experimental
--
-- Generic parsers for streams of 'Char' ('String', 'Text', lazy 'Text').
module Text.Megaparsec.Utils.Char (
  -- * Scalar parsers
  boolParser,
  numParser,
  posDecNumParser,
  posNumParser,
  uuidParser,

  -- * Combinators
  commaSeparated,

  -- * Compatibility utilities
  boundedEnumShowParser,
  parsecToJSONParser,
) where

import Data.Aeson.Types (Parser, Value, withText)
import Data.List.NonEmpty (NonEmpty)
import Data.Text (Text)
import Data.UUID (UUID)
import Text.Megaparsec (
  MonadParsec,
  Parsec,
  ShowErrorComponent,
  Token,
  errorBundlePretty,
  runParser,
 )
import Text.Megaparsec.Char (char, char', digitChar, hexDigitChar)
import Text.Megaparsec.Utils.Common (
  mkBoolParser,
  mkBoundedEnumShowParser,
  mkCommaSeparated,
  mkNumParser,
  mkPosDecNumParser,
  mkPosNumParser,
  mkUuidParser,
 )

-- | Parse a case-insensitive human-readable boolean, including C-style numbers,
-- English yes-no and @on@ / @off@.
--
-- @since 0.2.0.0
boolParser
  :: MonadParsec e s m
  => Token s ~ Char
  => m Bool
boolParser = mkBoolParser char' id

-- | Parse a 'Bounded' 'Enum' type that has a 'Show' instance, trying all
-- possibilities, case-insensitive, in the 'Enum' order.
--
-- @since 0.2.0.0
boundedEnumShowParser
  :: forall a e s m
   . MonadParsec e s m
  => Token s ~ Char
  => Bounded a
  => Enum a
  => Show a
  => m a
boundedEnumShowParser = mkBoundedEnumShowParser char' id

-- | Parse a comma-separated list of items.
--
-- @since 0.2.0.0
commaSeparated
  :: MonadParsec e s m
  => Token s ~ Char
  => m a
  -> m (NonEmpty a)
commaSeparated = mkCommaSeparated (char ',')

-- | Parse a positive number, with or without decimals prefixed by a @.@.
--
-- @since 0.2.0.0
posDecNumParser
  :: MonadFail m
  => MonadParsec e s m
  => Read a
  => Token s ~ Char
  => m a
posDecNumParser = mkPosDecNumParser digitChar (char '.') id

-- | Parse a positive integer.
--
-- @since 0.2.0.0
posNumParser
  :: MonadFail m
  => MonadParsec e s m
  => Read a
  => Token s ~ Char
  => m a
posNumParser = mkPosNumParser digitChar id

-- | Parse an integer, without any space between minus sign and digits.
--
-- @since 0.2.0.0
numParser
  :: MonadFail m
  => MonadParsec e s m
  => Num a
  => Read a
  => Token s ~ Char
  => m a
numParser = mkNumParser (char '-') posNumParser

-- | Convert a 'Parsec' parser on 'Text' into a 'Parser' suited for
-- 'Data.Aeson.FromJSON' instances.
--
-- @since 0.2.0.0
parsecToJSONParser
  :: ShowErrorComponent e
  => String
  -- ^ Parser name.
  -> Parsec e Text a
  -- ^ Parser.
  -> (Value -> Parser a)
parsecToJSONParser n p =
  withText n (either (fail . errorBundlePretty) pure . runParser p n)

-- | Parse a RFC4122-compliant UUID.
--
-- @since 0.2.0.0
uuidParser
  :: MonadParsec e s m
  => Token s ~ Char
  => m UUID
uuidParser = mkUuidParser hexDigitChar (char '-') id