simple-parser-0.11.0: src/SimpleParser/Examples/Direct/Sexp.hs
{-# LANGUAGE OverloadedStrings #-}
-- | Parses S-expressions
module SimpleParser.Examples.Direct.Sexp
( SexpLabel (..)
, SexpParserC
, SexpParserM
, sexpParser
, runSexpParser
) where
import Control.Applicative (empty)
import Control.Monad (void)
import Control.Monad.Catch (MonadThrow)
import Data.Char (isDigit, isSpace)
import Data.Sequence (Seq)
import Data.Text (Text)
import qualified Data.Text as T
import Data.Typeable (Typeable)
import Data.Void (Void)
import SimpleParser (Chunked (..), EmbedTextLabel (..), ExplainLabel (..), MatchBlock (..), MatchCase (..), Parser,
Stream (..), TextLabel, TextualStream, anyToken, applySign, betweenParser, escapedStringParser,
lexemeParser, lookAheadMatch, matchToken, numParser, packChunk, popChunk, runParserEnd,
satisfyToken, sepByParser, signParser, signedNumStartPred, spaceParser, takeTokensWhile)
import SimpleParser.Examples.Common.Sexp (Atom (..), Sexp (..), SexpF (..))
data SexpLabel =
SexpLabelIdentStart
| SexpLabelEmbedText !TextLabel
| SexpLabelCustom !Text
deriving (Eq, Show)
instance ExplainLabel SexpLabel where
explainLabel sl =
case sl of
SexpLabelIdentStart -> "start of identifier"
SexpLabelEmbedText tl -> explainLabel tl
_ -> undefined
instance EmbedTextLabel SexpLabel where
embedTextLabel = SexpLabelEmbedText
type SexpParserC s = TextualStream s
type SexpParserM s a = Parser SexpLabel s Void a
sexpParser :: SexpParserC s => SexpParserM s Sexp
sexpParser = let p = fmap Sexp (recSexpParser p) in p
recSexpParser :: SexpParserC s => SexpParserM s a -> SexpParserM s (SexpF a)
recSexpParser root = lookAheadMatch block where
block = MatchBlock anyToken (fmap SexpAtom atomP)
[ MatchCase Nothing (== '(') (fmap SexpList (listP root))
]
nonDelimPred :: Char -> Bool
nonDelimPred c = c /= '(' && c /= ')' && not (isSpace c)
identStartPred :: Char -> Bool
identStartPred c = not (isDigit c) && identContPred c
identContPred :: Char -> Bool
identContPred c = c /= '"' && nonDelimPred c
stringP :: SexpParserC s => SexpParserM s Text
stringP = fmap packChunk (escapedStringParser '"')
identifierP :: SexpParserC s => SexpParserM s Text
identifierP = do
x <- satisfyToken (Just SexpLabelIdentStart) identStartPred
xs <- takeTokensWhile identContPred
pure (packChunk (consChunk x xs))
spaceP :: SexpParserC s => SexpParserM s ()
spaceP = spaceParser
lexP :: SexpParserC s => SexpParserM s a -> SexpParserM s a
lexP = lexemeParser spaceP
openParenP :: SexpParserC s => SexpParserM s ()
openParenP = lexP (void (matchToken '('))
closeParenP :: SexpParserC s => SexpParserM s ()
closeParenP = lexP (void (matchToken ')'))
numAtomP :: SexpParserC s => SexpParserM s Atom
numAtomP = do
ms <- signParser
n <- numParser
case n of
Left i -> pure (AtomInt (applySign ms i))
Right s -> pure (AtomSci (applySign ms s))
chunk1 :: SexpParserC s => SexpParserM s Text
chunk1 = do
mc <- popChunk 2
case mc of
Just c | not (chunkEmpty c) -> pure (packChunk c)
_ -> empty
unaryIdentPred :: Char -> Text -> Bool
unaryIdentPred u t0 =
case T.uncons t0 of
Just (c0, t1) | u == c0 ->
case T.uncons t1 of
Just (c1, _) -> not (isDigit c1)
Nothing -> True
_ -> False
identAtomP :: SexpParserC s => SexpParserM s Atom
identAtomP = fmap AtomIdent identifierP
atomP :: SexpParserC s => SexpParserM s Atom
atomP = lexP (lookAheadMatch block) where
block = MatchBlock chunk1 (fail "failed to parse sexp atom")
[ MatchCase Nothing ((== '"') . T.head) (fmap AtomString stringP)
, MatchCase Nothing (unaryIdentPred '+') identAtomP
, MatchCase Nothing (unaryIdentPred '-') identAtomP
, MatchCase Nothing (signedNumStartPred . T.head) numAtomP
, MatchCase Nothing (identStartPred . T.head) identAtomP
]
listP :: SexpParserC s => SexpParserM s a -> SexpParserM s (Seq a)
listP root = lexP (betweenParser openParenP closeParenP (sepByParser root spaceP))
runSexpParser :: (
Typeable s, Typeable (Token s), Typeable (Chunk s),
Show s, Show (Token s), Show (Chunk s),
SexpParserC s, MonadThrow m) => s -> m Sexp
runSexpParser = runParserEnd sexpParser