simple-parser-0.12.0: src/SimpleParser/Examples/Lexed/Sexp.hs
{-# LANGUAGE OverloadedStrings #-}
-- | Parses S-expressions but lexes first
module SimpleParser.Examples.Lexed.Sexp
( Sexp (..)
, SexpF (..)
, Atom (..)
, SexpTokLabel (..)
, SexpTokParserC
, SexpTokParserM
, sexpTokParser
, 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,
PosStream (..), ShowTextBuildable (..), Stream (..), TextBuildable (..), TextLabel, TextualStream,
anyToken, applySign, betweenParser, escapedStringParser, greedyStarParser, lexemeParser,
lookAheadMatch, matchToken, numParser, packChunk, popChunk, popToken, runParserLexed, satisfyToken,
signParser, signedNumStartPred, spaceParser, takeTokensWhile)
import SimpleParser.Examples.Common.Sexp (Atom (..), Sexp (..), SexpF (..))
-- First, our tokenizer:
data SexpTokLabel =
SexpTokLabelIdentStart
| SexpTokLabelEmbedText !TextLabel
deriving (Eq, Show)
instance ExplainLabel SexpTokLabel where
explainLabel sl =
case sl of
SexpTokLabelIdentStart -> "start of identifier"
SexpTokLabelEmbedText tl -> explainLabel tl
instance EmbedTextLabel SexpTokLabel where
embedTextLabel = SexpTokLabelEmbedText
type SexpTokParserC s = (PosStream s, TextualStream s)
type SexpTokParserM s a = Parser SexpTokLabel s Void a
data SexpTok =
SexpTokOpenParen
| SexpTokCloseParen
| SexpTokAtom !Atom
deriving stock (Eq, Show)
deriving (TextBuildable) via (ShowTextBuildable SexpTok)
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
stringTP :: SexpTokParserC s => SexpTokParserM s Text
stringTP = fmap packChunk (escapedStringParser '"')
identifierTP :: SexpTokParserC s => SexpTokParserM s Text
identifierTP = do
x <- satisfyToken (Just SexpTokLabelIdentStart) identStartPred
xs <- takeTokensWhile identContPred
pure (packChunk (consChunk x xs))
spaceTP :: SexpTokParserC s => SexpTokParserM s ()
spaceTP = spaceParser
lexTP :: SexpTokParserC s => SexpTokParserM s a -> SexpTokParserM s a
lexTP = lexemeParser spaceTP
openParenTP :: SexpTokParserC s => SexpTokParserM s ()
openParenTP = lexTP (void (matchToken '('))
closeParenTP :: SexpTokParserC s => SexpTokParserM s ()
closeParenTP = lexTP (void (matchToken ')'))
numAtomTP :: SexpTokParserC s => SexpTokParserM s Atom
numAtomTP = do
ms <- signParser
n <- numParser
case n of
Left i -> pure (AtomInt (applySign ms i))
Right s -> pure (AtomSci (applySign ms s))
chunk1 :: SexpTokParserC s => SexpTokParserM 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
identAtomTP :: SexpTokParserC s => SexpTokParserM s Atom
identAtomTP = fmap AtomIdent identifierTP
atomTP :: SexpTokParserC s => SexpTokParserM s Atom
atomTP = lexTP (lookAheadMatch block) where
block = MatchBlock chunk1 (fail "failed to parse sexp atom")
[ MatchCase Nothing ((== '"') . T.head) (fmap AtomString stringTP)
, MatchCase Nothing (unaryIdentPred '+') identAtomTP
, MatchCase Nothing (unaryIdentPred '-') identAtomTP
, MatchCase Nothing (signedNumStartPred . T.head) numAtomTP
, MatchCase Nothing (identStartPred . T.head) identAtomTP
]
sexpTokParser :: SexpTokParserC s => SexpTokParserM s SexpTok
sexpTokParser= lookAheadMatch block where
block = MatchBlock anyToken (fmap SexpTokAtom atomTP)
[ MatchCase Nothing (== '(') (SexpTokOpenParen <$ openParenTP)
, MatchCase Nothing (== ')') (SexpTokCloseParen <$ closeParenTP)
]
-- Now the Sexp parser itself:
type SexpParserC s = (Stream s, Token s ~ SexpTok)
type SexpParserM s a = Parser Void s Void a
isOpenParenTok, isCloseParenTok, isAtomTok :: SexpTok -> Bool
isOpenParenTok = \case { SexpTokOpenParen -> True; _ -> False }
isCloseParenTok = \case { SexpTokCloseParen -> True; _ -> False }
isAtomTok = \case { SexpTokAtom _ -> True; _ -> False }
atomP :: SexpParserC s => SexpParserM s Atom
atomP = popToken >>= \case { Just (SexpTokAtom a) -> pure a; _ -> fail "invalid atom" }
openParenP, closeParenP :: SexpParserC s => SexpParserM s ()
openParenP = void (matchToken SexpTokOpenParen)
closeParenP = void (matchToken SexpTokCloseParen)
listP :: SexpParserC s => SexpParserM s a -> SexpParserM s (Seq a)
listP root = betweenParser openParenP closeParenP (greedyStarParser root)
recSexpParser :: SexpParserC s => SexpParserM s a -> SexpParserM s (SexpF a)
recSexpParser root = lookAheadMatch block where
block = MatchBlock anyToken empty
[ MatchCase Nothing isOpenParenTok (fmap SexpList (listP root))
, MatchCase Nothing isCloseParenTok (fail "invalid close paren")
, MatchCase Nothing isAtomTok (fmap SexpAtom atomP)
]
sexpParser :: SexpParserC s => SexpParserM s Sexp
sexpParser = let p = fmap Sexp (recSexpParser p) in p
-- And combined:
runSexpParser :: (
Typeable s, Typeable (Token s), Typeable (Chunk s), Typeable (Pos s),
Show s, Show (Token s), Show (Chunk s), Show (Pos s),
SexpTokParserC s, MonadThrow m) => s -> m Sexp
runSexpParser = runParserLexed sexpTokParser id sexpParser