chart-svg-0.4.0: src/Chart/FlatParse.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# OPTIONS_GHC -Wno-name-shadowing #-}
-- | Lower-level flatparse parsers
module Chart.FlatParse
( runParserMaybe,
runParserEither,
testParser,
Expected (..),
Error (..),
prettyError,
cut,
cut',
ws,
ws_,
wss,
sep,
bracketed,
wrapped,
digit,
int,
double,
signed,
)
where
import Data.Bool
import Data.ByteString (ByteString)
import Data.Char hiding (isDigit)
import Data.List (replicate)
import FlatParse.Basic hiding (cut)
import GHC.Exts
import Prelude hiding (replicate)
-- $setup
-- >>> :set -XTemplateHaskell
-- >>> import Chart.FlatParse
-- >>> import FlatParse.Basic
-- * parser error model
--
-- taken from flatparse examples
-- | An expected item which is displayed in error messages.
data Expected
= -- | An error message.
Msg String
| -- | A literal expected thing.
Lit String
deriving (Eq, Show, Ord)
instance IsString Expected where fromString = Lit
-- | A parsing error.
data Error
= -- | A precisely known error, like leaving out "in" from "let".
Precise Pos Expected
| -- | An imprecise error, when we expect a number of different things,
-- but parse something else.
Imprecise Pos [Expected]
deriving (Show)
errorPos :: Error -> Pos
errorPos (Precise p _) = p
errorPos (Imprecise p _) = p
-- | Merge two errors. Inner errors (which were thrown at points with more consumed inputs)
-- are preferred. If errors are thrown at identical input positions, we prefer precise errors
-- to imprecise ones.
--
-- The point of prioritizing inner and precise errors is to suppress the deluge of "expected"
-- items, and instead try to point to a concrete issue to fix.
merge :: Error -> Error -> Error
merge e e' = case (errorPos e, errorPos e') of
(p, p') | p < p' -> e'
(p, p') | p > p' -> e
(p, _) -> case (e, e') of
(Precise {}, _) -> e
(_, Precise {}) -> e'
(Imprecise _ es, Imprecise _ es') -> Imprecise p (es <> es')
{-# NOINLINE merge #-} -- merge is "cold" code, so we shouldn't inline it.
-- | Pretty print an error. The `B.ByteString` input is the source file. The offending line from the
-- source is displayed in the output.
prettyError :: ByteString -> Error -> String
prettyError b e =
let pos :: Pos
pos = case e of
Imprecise p _ -> p
Precise p _ -> p
ls = linesUtf8 b
(l, c) = head $ posLineCols b [pos]
line = if l < length ls then ls !! l else ""
line' = if length line > 300 then Prelude.drop (c - 50) $ Prelude.take (c + 50) line else line
linum = show l
lpad = fmap (const ' ') linum
expected (Lit s) = show s
expected (Msg s) = s
err (Precise _ e) = expected e
err (Imprecise _ es) = imprec es
imprec :: [Expected] -> String
imprec [] = error "impossible"
imprec [e] = expected e
imprec (e : es) = (expected e <> go es)
where
go [] = ""
go [e] = (" or " <> expected e)
go (e : es) = (", " <> expected e <> go es)
in (show l <> (":" <> (show c <> (":\n" <> (lpad <> ("|\n" <> (linum <> ("| " <> (line' <> ("\n" <> (lpad <> ("| " <> (replicate c ' ' <> ("^\n" <> ("parse error: expected " <> err e)))))))))))))))
-- | Imprecise cut: we slap a list of items on inner errors.
cut :: Parser Error a -> [Expected] -> Parser Error a
cut p es = do
pos <- getPos
cutting p (Imprecise pos es) merge
-- | Precise cut: we propagate at most a single error.
cut' :: Parser Error a -> Expected -> Parser Error a
cut' p e = do
pos <- getPos
cutting p (Precise pos e) merge
-- | run a Parser, Nothing on failure
runParserMaybe :: Parser e a -> ByteString -> Maybe a
runParserMaybe p b = case runParser p b of
OK r _ -> Just r
Fail -> Nothing
Err _ -> Nothing
-- | Run parser, Left error on failure.
runParserEither :: Parser Error a -> ByteString -> Either ByteString a
runParserEither p bs = case runParser p bs of
Err e -> Left $ strToUtf8 $ prettyError bs e
OK a _ -> Right a
Fail -> Left "uncaught parse error"
-- | Run parser, print pretty error on failure.
testParser :: (Show a) => Parser Error a -> String -> IO ()
testParser p str = case fromString str of
b -> case runParser p b of
Err e -> putStrLn $ prettyError b e
OK a _ -> print a
Fail -> putStrLn "uncaught parse error"
-- * parsing
isWs :: Char -> Bool
isWs x =
x == ' '
|| x == '\n'
|| x == '\t'
|| x == '\r'
-- | single whitespace
--
-- >>> runParser ws " \nx"
-- OK ' ' "\nx"
ws :: Parser e Char
ws = satisfy isWs
-- | Consume whitespace.
--
-- >>> runParser ws_ " \nx"
-- OK () "x"
--
-- >>> runParser ws_ "x"
-- OK () "x"
ws_ :: Parser e ()
ws_ =
$( switch
[|
case _ of
" " -> ws_
"\n" -> ws_
"\t" -> ws_
"\r" -> ws_
_ -> pure ()
|]
)
-- | multiple whitespace
--
-- >>> runParser wss " \nx"
-- OK " \n" "x"
--
-- >>> runParser wss "x"
-- Fail
wss :: Parser e ByteString
wss = byteStringOf $ some ws
-- | some with a separator
--
-- >>> runParser (sep ws (many (satisfy (/= ' ')))) "a b c"
-- OK ["a","b","c"] ""
sep :: Parser e s -> Parser e a -> Parser e [a]
sep s p = (:) <$> p <*> many (s *> p)
-- | parser bracketed by two other parsers
--
-- >>> runParser (bracketed ($(char '[')) ($(char ']')) (many (satisfy (/= ']')))) "[bracketed]"
-- OK "bracketed" ""
bracketed :: Parser e b -> Parser e b -> Parser e a -> Parser e a
bracketed o c p = o *> p <* c
-- | parser wrapped by another parser
--
-- >>> runParser (wrapped ($(char '"')) (many (satisfy (/= '"')))) "\"wrapped\""
-- OK "wrapped" ""
wrapped :: Parser e () -> Parser e a -> Parser e a
wrapped x p = bracketed x x p
-- | A single digit
--
-- runParserMaybe digit "5"
-- Just 5
digit :: Parser e Int
digit = (\c -> ord c - ord '0') <$> satisfyAscii isDigit
-- | (unsigned) Int parser
--
-- runParserMaybe int "567"
-- Just 567
int :: Parser e Int
int = do
(place, n) <- chainr (\n (!place, !acc) -> (place * 10, acc + place * n)) digit (pure (1, 0))
case place of
1 -> empty
_ -> pure n
digits :: Parser e (Int, Int)
digits = chainr (\n (!place, !acc) -> (place * 10, acc + place * n)) digit (pure (1, 0))
-- |
-- >>> runParser double "1.234x"
-- OK 1.234 "x"
--
-- >>> runParser double "."
-- Fail
--
-- >>> runParser double "123"
-- OK 123.0 ""
--
-- >>> runParser double ".123"
-- OK 0.123 ""
--
-- >>> runParser double "123."
-- OK 123.0 ""
double :: Parser e Double
double = do
(placel, nl) <- digits
withOption
($(char '.') *> digits)
( \(placer, nr) ->
case (placel, placer) of
(1, 1) -> empty
_ -> pure $ fromIntegral nl + fromIntegral nr / fromIntegral placer
)
( case placel of
1 -> empty
_ -> pure $ fromIntegral nl
)
minus :: Parser e ()
minus = $(char '-')
-- |
-- >>> runParser (signed double) "-1.234x"
-- OK (-1.234) "x"
signed :: (Num b) => Parser e b -> Parser e b
signed p = do
m <- optional minus
case m of
Nothing -> p
Just () -> negate <$> p