packages feed

cfg-0.0.1.0: src/Cfg/Parser.hs

{-# LANGUAGE DefaultSignatures #-}

module Cfg.Parser where

import Control.Error (note)
import Data.ByteString qualified as BS
import Data.ByteString.Lazy qualified as BL
import Data.Functor (void, ($>))
import Data.Int
import Data.List.NonEmpty (NonEmpty, fromList)
import Data.Text (Text)
import Data.Text qualified as T
import Data.Text.Encoding (encodeUtf8)
import Data.Text.Lazy qualified as TL
import Data.Tree (Tree (..))
import Data.Void (Void)
import Data.Word
import GHC.Generics (Generic)
import Text.Megaparsec (Parsec, anySingle, between, empty, option, parseMaybe, sepBy, sepBy1, some, takeRest, try, (<|>))
import Text.Megaparsec.Char (char, digitChar, space1, string, string')
import Text.Megaparsec.Char.Lexer qualified as L

type Parser = Parsec Void Text

data ConfigParseError
  = UnmatchedFields [Tree Text]
  | MismatchedRootKey Text Text
  | MismatchedKeyAndField Text (Text, Text)
  | MissingKeys [Text]
  | MissingValue Text
  | UnexpectedKeys Text [Tree Text]
  | ValueParseError Text
  deriving (Eq, Show, Generic)

class RootParser a where
  parseRootConfig :: Tree Text -> Either ConfigParseError a

class NestedParser a where
  parseNestedConfig :: Tree Text -> Either ConfigParseError a
  default parseNestedConfig :: (ValueParser a) => Tree Text -> Either ConfigParseError a
  parseNestedConfig (Node val []) = note (ValueParseError val) $ parseMaybe parser val
  parseNestedConfig (Node label xs) = Left $ UnexpectedKeys label xs

sp :: Parsec Void Text ()
sp = L.space space1 empty empty

class ValueParser a where
  parser :: Parser a

-- | @since 0.0.1.0
instance ValueParser () where
  parser = string "()" >> pure ()

-- | @since 0.0.1.0
instance NestedParser ()

-- | @since 0.0.1.0
instance ValueParser Bool where
  parser = try (string' "true" >> pure True) <|> (string' "false" >> pure False)

-- | @since 0.0.1.0
instance NestedParser Bool

-- | @since 0.0.1.0
instance ValueParser Char where
  parser = anySingle

-- | @since 0.0.1.0
instance NestedParser Char

-- | @since 0.0.1.0
instance ValueParser TL.Text where
  parser = TL.fromStrict <$> takeRest

-- | @since 0.0.1.0
instance NestedParser TL.Text

-- | @since 0.0.1.0
instance ValueParser BL.ByteString where
  parser = BL.fromStrict . encodeUtf8 <$> takeRest

-- | @since 0.0.1.0
instance NestedParser BL.ByteString

-- | @since 0.0.1.0
instance ValueParser BS.ByteString where
  parser = encodeUtf8 <$> takeRest

-- | @since 0.0.1.0
instance NestedParser BS.ByteString

-- @since 0.0.1.0
instance ValueParser Text where
  parser = takeRest

-- | @since 0.0.1.0
instance NestedParser Text

-- | @since 0.0.1.0
instance ValueParser a => ValueParser [a] where
  parser = between (L.symbol sp "[") (L.symbol sp "]") $ parser @a `sepBy` (L.symbol sp ",")

-- | @since 0.0.1.0
instance ValueParser a => NestedParser [a]

-- | @since 0.0.1.0
instance ValueParser a => ValueParser (NonEmpty a) where
  parser = between (L.symbol sp "[") (L.symbol sp "]") $ fromList <$> parser @a `sepBy1` (L.symbol sp ",")

-- | @since 0.0.1.0
instance ValueParser a => NestedParser (NonEmpty a)

-- | @since 0.0.1.0
instance ValueParser a => ValueParser (Maybe a) where
  parser =
    (try (string "Nothing") $> Nothing)
      <|> (L.symbol sp "Just" >> (Just <$> parser @a))

instance ValueParser a => NestedParser (Maybe a)

-- Numeric Types

rd :: Read a => Text -> a
rd = read . T.unpack

plus :: Parser Text
plus = char '+' >> number

minus :: Parser Text
minus = liftA2 (T.cons) (char '-') number

number :: Parser Text
number = T.pack <$> some digitChar

decimal :: Parser Text
decimal = option "" $ (T.cons) <$> char '.' <*> number

integral :: (Read a) => Parser a
integral = rd <$> (try plus <|> try minus <|> number)

fractional :: (Read a) => Parser a
fractional = fmap rd $ liftA2 (<>) integral decimal

-- | @since 0.0.1.0
instance ValueParser Double where
  parser = fractional

instance NestedParser Double

-- | @since 0.0.1.0
instance ValueParser Float where
  parser = fractional

instance NestedParser Float

-- @since 0.0.1.0
instance ValueParser Int where
  parser = integral

instance NestedParser Int

-- | @since 0.0.1.0
instance ValueParser Int8 where
  parser = integral

instance NestedParser Int8

-- | @since 0.0.1.0
instance ValueParser Int16 where
  parser = integral

instance NestedParser Int16

-- | @since 0.0.1.0
instance ValueParser Int32 where
  parser = integral

instance NestedParser Int32

-- | @since 0.0.1.0
instance ValueParser Int64 where
  parser = integral

instance NestedParser Int64

-- | @since 0.0.1.0
instance ValueParser Integer where
  parser = integral

instance NestedParser Integer

-- | @since 0.0.1.0
instance ValueParser Word where
  parser = rd <$> number

instance NestedParser Word

-- | @since 0.0.1.0
instance ValueParser Word8 where
  parser = rd <$> number

instance NestedParser Word8

-- | @since 0.0.1.0
instance ValueParser Word16 where
  parser = rd <$> number

instance NestedParser Word16

-- | @since 0.0.1.0
instance ValueParser Word32 where
  parser = rd <$> number

instance NestedParser Word32

-- | @since 0.0.1.0
instance ValueParser Word64 where
  parser = rd <$> number

instance NestedParser Word64

-- | @since 0.0.1.0
instance (ValueParser a, ValueParser b) => ValueParser (a, b) where
  parser = between (L.symbol sp "(") (L.symbol sp ")") $ do
    a <- parser @a
    void $ L.symbol sp ","
    b <- parser @b
    pure (a, b)

-- | @since 0.0.1.0
instance (ValueParser a, ValueParser b) => NestedParser (a, b)