packages feed

sdp-0.2: src/Text/Read/SDP.hs

{-# LANGUAGE Safe #-}

{- |
    Module      :  Text.Read.SDP
    Copyright   :  (c) Andrey Mulik 2019
    License     :  BSD-style
    Maintainer  :  work.a.mulik@gmail.com
    Portability :  portable
    
    "Text.Read.SDP" provides common 'ReadPrec' parsers and related stuff.
-}
module Text.Read.SDP
(
  -- * Exports
  module Text.Read,
  module Text.Read.Lex,
  
  appPrec,
  
  -- * Common parsers
  linearPrec, indexedPrec, indexedPrec',
  
  readZeroPrec, readAsList, readAsListN, readAssocsPrec,
  
  -- * Common parser combinators
  allPrec, allPrecWith, expectPrec, namedPrec,
  
  -- * Generalized readers
  readDef, readBy, readMaybeBy, readEitherBy, readDefBy,
  
  -- * Enum parsers
  readAsEnum, enumFromPrec, enumFromToPrec, enumFromThenPrec, enumFromThenToPrec
)
where

import Prelude ()
import SDP.SafePrelude
import SDP.Indexed

import Data.Default.Class

import Text.ParserCombinators.ReadP ( eof, manyTill, skipSpaces )
import Text.Read.Lex ( expect )
import Text.Read

import GHC.Show ( appPrec )

default ()

--------------------------------------------------------------------------------

{- |
  @'indexedPrec' ident@ is common parser of 'Indexed' structure with name
  @ident@:
  
  > read "ident (0,1) [(0,0),(1,1)]" == read "(0,1) [(0,0),(1,1)]" == assoc (0,1) [(0,0),(1,1)]
-}
indexedPrec :: (Indexed v i e, Read i, Read e) => String -> ReadPrec v
indexedPrec =  namedPrec readAssocsPrec

{- |
  @'linearPrec' ident@ is common parser of 'Linear' structure with name
  @ident@:
  
  > read "Z" == read "[]" == []
  > read "['l','g','p','l']" == fromList "lgpl"
  > read "4 [1,5,-1,45,12,6,0,0,12]" == fromListN 4 [1,5,-1,45,12,6,0,0,12]
-}
linearPrec :: (Linear l e, Read e) => String -> ReadPrec l
linearPrec =  namedPrec (readZeroPrec +++ readAsList +++ readAsListN)

-- | 'indexedPrec'' is common 'Linear' and 'Indexed' parser (recommended).
indexedPrec' :: (Indexed v i e, Read i, Read e) => String -> ReadPrec v
indexedPrec' =  namedPrec (readZeroPrec +++ readAsList +++ readAsListN +++ readAssocsPrec)

--------------------------------------------------------------------------------

{- Common parsers. -}

-- | 'readZeroPrec' is just 'Z' parser, see 'linearPrec'.
readZeroPrec :: (Linear l e) => ReadPrec l
readZeroPrec = parens $ prec appPrec (expectPrec $ Ident "Z") >> return Z

-- | 'readAsList' is 'fromList'-based parser, see 'linearPrec'.
readAsList :: (Linear l e, Read e) => ReadPrec l
readAsList = fromList <$> readListPrec

-- | 'readAsListN' is 'fromListN'-based parser, see 'linearPrec'.
readAsListN :: (Linear l e, Read e) => ReadPrec l
readAsListN = liftA2 fromListN (step readPrec) (step readPrec)

{- |
  'readAssocsPrec' is @sdp@ recommended format 'ReadPrec' parser for 'Indexed'.
-}
readAssocsPrec :: (Indexed v i e, Read i, Read e) => ReadPrec v
readAssocsPrec = parens $ liftA2 assoc (step readPrec) (step readPrec)

--------------------------------------------------------------------------------

{- Enum parsers. -}

-- | Common 'Enum' parser.
readAsEnum :: (Linear l e, Read e, Enum e) => ReadPrec l
readAsEnum =  fromList <$> parens' (fromPrec_ +++ fromThenPrec_ +++ fromToPrec_ +++ fromThenToPrec_)

{- |
  'enumFrom' parser:
  
  > take 5 (readBy enumFromPrec "[1 ..]") == take 5 [1 ..] = [1,2,3,4,5]
-}
enumFromPrec :: (Linear l e, Read e, Enum e) => ReadPrec l
enumFromPrec =  fromList <$> parens' fromPrec_

{- |
  'enumFromTo' parser:
  
  > readBy enumFromToPrec "[9 .. 12]" == [9 .. 12] == [9,10,11,12]
-}
enumFromToPrec :: (Linear l e, Read e, Enum e) => ReadPrec l
enumFromToPrec =  fromList <$> parens' fromToPrec_

{- |
  'enumFromThen' parser:
  
  > take 4 (readBy enumFromThenPrec "[17, -6 .. ]") == take 4 [17, -6 ..] == [17,-6,-29,-52]
-}
enumFromThenPrec :: (Linear l e, Read e, Enum e) => ReadPrec l
enumFromThenPrec =  fromList <$> parens' fromThenPrec_

{- |
  'enumFromThenTo' parser:
  
  > take 4 (readBy enumFromThenToPrec "[17, -6 .. 4]") == [17, -6 .. 4] == [17]
-}
enumFromThenToPrec :: (Linear l e, Read e, Enum e) => ReadPrec l
enumFromThenToPrec =  fromList <$> parens' fromThenToPrec_

--------------------------------------------------------------------------------

{- Common parser combinators. -}

-- | Just lifted 'expect'.
expectPrec :: Lexeme -> ReadPrec ()
expectPrec = lift . expect

-- | @allPrec@ is just @'allPrecWith' 'readPrec'@
allPrec :: (Read e) => ReadPrec [e]
allPrec =  allPrecWith readPrec

{- |
  allPrecWith is 'manyTill'-based combinator, which reads a sequence of elements
  without any separators:
  
  > readBy allPrecWith readPrec "1 2 3 4 5 6 7" :: [Int] == [1 .. 7]
-}
allPrecWith :: ReadPrec e -> ReadPrec [e]
allPrecWith parser = lift (manyTill reader eof)
  where
    reader = readPrec_to_P parser 0

-- | @'namedPrec' readprec name@ is 'readPrec' with optional @name@ prefix.
namedPrec :: ReadPrec e -> String -> ReadPrec e
namedPrec parser name =
  let named = prec appPrec (expectPrec $ Ident name) >> parser
  in  named +++ parser

--------------------------------------------------------------------------------

-- | 'read' with implicit default value.
readDef :: (Read e, Default e) => String -> e
readDef =  (def +?) . readMaybe

-- | 'readBy' with implicit default value.
readDefBy :: (Default e) => ReadPrec e -> String -> e
readDefBy =  (def +?) ... readMaybeBy

-- | 'readBy' is generalized 'read'.
readBy :: ReadPrec e -> String -> e
readBy parser string = case readEitherBy parser string of
  Left msg -> error msg
  Right  x -> x

-- | 'readMaybeBy' is generalized 'readMaybe'.
readMaybeBy :: ReadPrec e -> String -> Maybe e
readMaybeBy parser string = case readPrec_to_S read' minPrec string of
    [(x, "")] -> Just x
    _         -> Nothing
  where
    read' = do x <- parser; lift skipSpaces; return x

-- | 'readEitherBy' is generalized 'readEither'.
readEitherBy :: ReadPrec e -> String -> Either String e
readEitherBy parser string = case readPrec_to_S read' minPrec string of
    [(x, "")] -> Right x
    []        -> Left "SDP.Internal.Read.readBy: no parse"
    _         -> Left "SDP.Internal.Read.readBy: ambiguous parse"
  where
    read' = do x <- parser; lift skipSpaces; return x

--------------------------------------------------------------------------------

fromPrec_ :: (Read e, Enum e) => ReadPrec [e]
fromPrec_ =  do
  fr <- parens (step readPrec)
  expectPrec (Punc "..")
  return (enumFrom fr)

fromToPrec_ :: (Read e, Enum e) => ReadPrec [e]
fromToPrec_ =  do
  fr <- parens (step readPrec)
  expectPrec (Punc "..")
  to <- parens (step readPrec)
  return (enumFromTo fr to)

fromThenPrec_ :: (Read e, Enum e) => ReadPrec [e]
fromThenPrec_ =  do
  fr <- parens (step readPrec)
  expectPrec (Punc  ",")
  th <- parens (step readPrec)
  expectPrec (Punc "..")
  return (enumFromThen fr th)

fromThenToPrec_ :: (Read e, Enum e) => ReadPrec [e]
fromThenToPrec_ =  do
  fr <- parens (step readPrec)
  expectPrec (Punc  ",")
  th <- parens (step readPrec)
  expectPrec (Punc "..")
  to <- parens (step readPrec)
  return (enumFromThenTo fr th to)

parens' :: ReadPrec e -> ReadPrec e
parens' parser = do
  expectPrec (Punc "[")
  value <- parser
  expectPrec (Punc "]")
  return value