simple-parser-0.6.0: src/SimpleParser/Examples/Sexp.hs
{-# LANGUAGE OverloadedStrings #-}
module SimpleParser.Examples.Sexp
( Sexp (..)
, SexpF (..)
, Atom (..)
, SexpLabel (..)
, SexpParserC
, SexpParserM
, sexpParser
, recSexpParser
) where
import Control.Monad (void)
import Data.Char (isDigit, isSpace)
import Data.Scientific (Scientific)
import Data.Sequence (Seq)
import Data.Text (Text)
import Data.Void (Void)
import SimpleParser (Chunked (..), DefaultCase (..), EmbedTextLabel (..), ExplainLabel (..), MatchBlock (..),
MatchCase (..), Parser, PureMatchBlock, TextLabel, TextualStream, betweenParser, commitParser,
decimalParser, escapedStringParser, lexemeParser, lookAheadMatch, matchToken, onEmptyParser,
orParser, packChunk, satisfyToken, scientificParser, sepByParser, signedNumStartPred, signedParser,
spaceParser, takeTokensWhile)
data Atom =
AtomIdent !Text
| AtomString !Text
| AtomInt !Integer
| AtomFloat !Scientific
deriving (Eq, Show)
data SexpF a =
SexpAtom !Atom
| SexpList !(Seq a)
deriving (Eq, Show, Functor, Foldable, Traversable)
data SexpLabel =
SexpLabelIdentStart
| SexpLabelEmbedText !TextLabel
deriving (Eq, Show)
instance ExplainLabel SexpLabel where
explainLabel sl =
case sl of
SexpLabelIdentStart -> "start of identifier"
SexpLabelEmbedText tl -> explainLabel tl
instance EmbedTextLabel SexpLabel where
embedTextLabel = SexpLabelEmbedText
newtype Sexp = Sexp { unSexp :: SexpF Sexp }
deriving (Eq, Show)
type SexpParserC s = TextualStream s
type SexpParserB s a = PureMatchBlock SexpLabel s Void a
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 = onEmptyParser (orParser lp ap) (fail "failed to parse sexp document") where
lp = commitParser openParenP (fmap SexpList (listP root))
ap = fmap SexpAtom atomP
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 ')'))
intP :: SexpParserC s => SexpParserM s Integer
intP = signedParser (pure ()) decimalParser
floatP :: SexpParserC s => SexpParserM s Scientific
floatP = signedParser (pure ()) scientificParser
atomB :: SexpParserC s => SexpParserB s Atom
atomB = MatchBlock (DefaultCase Nothing (const (fail "failed to parse sexp atom")))
[ MatchCase Nothing (void (matchToken '"')) (fmap AtomString stringP)
, MatchCase Nothing (void (satisfyToken Nothing signedNumStartPred)) (fmap AtomInt intP)
, MatchCase Nothing (void (satisfyToken Nothing signedNumStartPred)) (fmap AtomFloat floatP)
, MatchCase Nothing (void (satisfyToken Nothing identStartPred)) (fmap AtomIdent identifierP)
]
atomP :: SexpParserC s => SexpParserM s Atom
atomP = lexP (lookAheadMatch atomB)
listP :: SexpParserC s => SexpParserM s a -> SexpParserM s (Seq a)
listP root = lexP (betweenParser openParenP closeParenP (sepByParser root spaceP))