packages feed

blanks-0.4.0: test/Test/Blanks/Parsing.hs

module Test.Blanks.Parsing where

import Control.Applicative (Alternative (..))
import Control.Exception (throwIO)
import Data.Void (Void)
import qualified Text.Megaparsec as MP
import qualified Text.Megaparsec.Char as MPC
import qualified Text.Megaparsec.Char.Lexer as MPCL

type Parser = MP.Parsec Void String

-- The parser is not the SUT here, so if it fails,
-- so does the test. We ensure valid input.
runParserIO :: Parser a -> String -> IO a
runParserIO p s =
  case MP.runParser p "<test>" s of
    Left e -> throwIO e
    Right a -> pure a

data SourceSpan = SourceSpan
  { _ssName :: !FilePath
  , _ssStartLine :: !MP.Pos
  , _ssStartColumn :: !MP.Pos
  , _ssEndLine :: !MP.Pos
  , _ssEndColumn :: !MP.Pos
  } deriving (Eq, Show, Ord)

mkSourceSpan :: MP.SourcePos -> MP.SourcePos -> SourceSpan
mkSourceSpan (MP.SourcePos n sl sc) (MP.SourcePos _ el ec) = SourceSpan n sl sc el ec

around :: (SourceSpan -> a -> b) -> Parser a -> Parser b
around f pa = (\s a e -> f (mkSourceSpan s e) a) <$> MP.getSourcePos <*> pa <*> MP.getSourcePos

around2 :: (SourceSpan -> a -> b -> c) -> Parser (a, b) -> Parser c
around2 f pab = (\s (a, b) e -> f (mkSourceSpan s e) a b) <$> MP.getSourcePos <*> pab <*> MP.getSourcePos

around3 :: (SourceSpan -> a -> b -> c -> d) -> Parser (a, b, c) -> Parser d
around3 f pabc = (\s (a, b, c) e -> f (mkSourceSpan s e) a b c) <$> MP.getSourcePos <*> pabc <*> MP.getSourcePos

double :: Parser a -> Parser (a, a)
double p = (,) <$> p <*> p

triple :: Parser a -> Parser (a, a, a)
triple p = (,,) <$> p <*> p <*> p

spaceConsumer :: Parser ()
spaceConsumer = MPCL.space MPC.space1 lineCmnt blockCmnt
  where
    lineCmnt = MPCL.skipLineComment ";"
    blockCmnt = MPCL.skipBlockComment "#|" "|#"

lexeme :: Parser a -> Parser a
lexeme = MPCL.lexeme spaceConsumer

symbol :: String -> Parser String
symbol = MPCL.symbol spaceConsumer

parens :: Parser a -> Parser a
parens = MP.between (symbol "(") (symbol ")")

nonDelimPred :: Char -> Bool
nonDelimPred c = c /= '(' && c /= ')' && c /= ' ' && c /= '\t' && c /= '\n'

identifier :: Parser String
identifier = lexeme (MP.takeWhile1P Nothing nonDelimPred)

-- Take the first successful result, backtracking on failure.
branch :: [Parser a] -> Parser a
branch xs =
  case xs of
    [] -> empty
    x:xs' -> MP.try x <|> branch xs'

signed :: Parser Int
signed = MPCL.signed spaceConsumer (lexeme MPCL.decimal)