packages feed

snack 0.2.0.0 → 0.3.0.0

raw patch · 6 files changed

+304/−55 lines, 6 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Data.ByteString.Parser: extent :: Parser a -> Parser a
- Data.ByteString.Parser.Char8: extent :: Parser a -> Parser a
- Data.Text.Parser: extent :: Parser a -> Parser a
- Data.Text.Parser: instance Control.Monad.Fail.MonadFail Data.Text.Parser.Parser
+ Data.ByteString.Parser: commit :: Parser a -> Parser a
+ Data.ByteString.Parser: offset :: ByteString -> ByteString -> Int
+ Data.ByteString.Parser: peekByteString :: Parser ByteString
+ Data.ByteString.Parser: unlabel :: Parser a -> Parser a
+ Data.ByteString.Parser: validate :: (a -> Either String b) -> Parser a -> Parser b
+ Data.ByteString.Parser.Char8: Explanation :: String -> (Int, Int) -> (Int, Int) -> String -> Explanation
+ Data.ByteString.Parser.Char8: [exMessage] :: Explanation -> String
+ Data.ByteString.Parser.Char8: [exSource] :: Explanation -> String
+ Data.ByteString.Parser.Char8: [exSpanFrom] :: Explanation -> (Int, Int)
+ Data.ByteString.Parser.Char8: [exSpanTo] :: Explanation -> (Int, Int)
+ Data.ByteString.Parser.Char8: commit :: Parser a -> Parser a
+ Data.ByteString.Parser.Char8: data Explanation
+ Data.ByteString.Parser.Char8: explain :: String -> ByteString -> Result a -> Explanation
+ Data.ByteString.Parser.Char8: instance GHC.Classes.Eq Data.ByteString.Parser.Char8.Explanation
+ Data.ByteString.Parser.Char8: instance GHC.Show.Show Data.ByteString.Parser.Char8.Explanation
+ Data.ByteString.Parser.Char8: offset :: ByteString -> ByteString -> Int
+ Data.ByteString.Parser.Char8: peekByteString :: Parser ByteString
+ Data.ByteString.Parser.Char8: position :: ByteString -> ByteString -> (Int, Int)
+ Data.ByteString.Parser.Char8: unlabel :: Parser a -> Parser a
+ Data.ByteString.Parser.Char8: validate :: (a -> Either String b) -> Parser a -> Parser b
+ Data.Text.Parser: Explanation :: String -> (Int, Int) -> (Int, Int) -> String -> Explanation
+ Data.Text.Parser: [exMessage] :: Explanation -> String
+ Data.Text.Parser: [exSource] :: Explanation -> String
+ Data.Text.Parser: [exSpanFrom] :: Explanation -> (Int, Int)
+ Data.Text.Parser: [exSpanTo] :: Explanation -> (Int, Int)
+ Data.Text.Parser: commit :: Parser a -> Parser a
+ Data.Text.Parser: data Explanation
+ Data.Text.Parser: explain :: String -> Text -> Result a -> Explanation
+ Data.Text.Parser: instance GHC.Classes.Eq Data.Text.Parser.Explanation
+ Data.Text.Parser: instance GHC.Show.Show Data.Text.Parser.Explanation
+ Data.Text.Parser: offset :: Text -> Text -> Int
+ Data.Text.Parser: peekText :: Parser Text
+ Data.Text.Parser: position :: Text -> Text -> (Int, Int)
+ Data.Text.Parser: unlabel :: Parser a -> Parser a
+ Data.Text.Parser: validate :: (a -> Either String b) -> Parser a -> Parser b

Files

Changelog.md view
@@ -10,3 +10,8 @@ * Implemented basic error tracking. * Expanded documentation to fully cover Text parser. * Compatibility with GHC 8.10 and GHC 9.0 on top of GHC 9.2.++[0.3.0.0] -- April 2022+[0.3.0.0]: https://github.com/mordae/snack/compare/0.2.0.0...0.3.0.0++* Implemented functions to aid with error reporting.
bench/Bench.hs view
@@ -326,7 +326,7 @@         key <- SC.takeWhile1 isToken         _   <- SC.skipSpace         _   <- SC.skipSpace-        '=' <- SC.char '='+        _   <- SC.char '='         _   <- SC.skipSpace         _   <- SC.char '"'         val <- SC.scan False scanString@@ -341,7 +341,7 @@         _   <- AC.skipSpace         key <- AC.takeWhile1 isToken         _   <- AC.skipSpace-        '=' <- AC.char '='+        _   <- AC.char '='         _   <- AC.skipSpace         _   <- AC.char '"'         val <- AC.scan False scanString@@ -357,7 +357,7 @@         key <- ST.takeWhile1 isToken         _   <- ST.skipSpace         _   <- ST.skipSpace-        '=' <- ST.char '='+        _   <- ST.char '='         _   <- ST.skipSpace         _   <- ST.char '"'         val <- ST.scan False scanString@@ -372,7 +372,7 @@         _   <- AT.skipSpace         key <- AT.takeWhile1 isToken         _   <- AT.skipSpace-        '=' <- AT.char '='+        _   <- AT.char '='         _   <- AT.skipSpace         _   <- AT.char '"'         val <- AT.scan False scanString
lib/Data/ByteString/Parser.hs view
@@ -54,13 +54,19 @@   , wrap   , match   , label-  , extent+  , unlabel+  , commit+  , validate      -- * End Of Input   , takeByteString+  , peekByteString   , endOfInput   , atEnd +    -- * Position+  , offset+     -- * Miscelaneous     -- |     -- These are all generic methods, but since I sometimes forget about them,@@ -80,6 +86,7 @@    import Data.Maybe   import Data.Word+  import Data.List qualified as List    import Data.ByteString as BS   import Data.ByteString.Unsafe as BS@@ -95,7 +102,7 @@   --   data Result a     = Success a {-# UNPACK #-} !ByteString-      -- ^ Parser successfully match the input.+      -- ^ Parser successfully matched the input.       --   Produces the parsing result and the remainder of the input.      | Failure [String] {-# UNPACK #-} !ByteString@@ -103,9 +110,7 @@       --   Produces list of expected inputs and the corresponding remainder.      | Error String {-# UNPACK #-} !ByteString {-# UNPACK #-} !Int-      -- ^ 'fail' was called somewhere during the parsing.-      --    Produces the reason and the remainder at the corresponding point-      --    with length of the problematic extent.+      -- ^ Parser ran into an error. Either syntactic or a validation one.      deriving (Eq, Show) @@ -275,7 +280,7 @@     let (pfx, sfx) = splitAt (length str) inp      in case pfx == str of           True -> Success pfx sfx-          False -> Failure [(show pfx)] inp+          False -> Failure [show str] inp     -- |@@ -398,23 +403,56 @@     -- |-  -- Marks an unlabelel extent of the parser.+  -- Un-names an extent of the parser.   ---  -- When the extent returns an Error, it is adjusted to cover the whole-  -- extent, but the reason is left intact.+  -- Same as 'label', but removes any expected values upon Failure.+  -- Very useful to mark comments and optional whitespace with.   ---  {-# INLINE CONLIKE extent #-}-  extent :: Parser a -> Parser a-  extent par = Parser \inp ->+  {-# INLINE CONLIKE unlabel #-}+  unlabel :: Parser a -> Parser a+  unlabel par = Parser \inp ->     case runParser par inp of       Success res more -> Success res more-      Failure expected more -> Failure expected more+      Failure _expected _more -> Failure [] inp       Error reason more len ->         let len' = len + (length inp - length more)          in Error reason inp len'     -- |+  -- Disable backtracking for the parser.+  -- Failure is treated as an Error.+  --+  {-# INLINE CONLIKE commit #-}+  commit :: Parser a -> Parser a+  commit par = Parser \inp ->+    case runParser par inp of+      Success res more -> Success res more+      Error reason more len -> Error reason more len+      Failure expected more ->+        Error+          case expected of+            [] -> "Unexpected input."+            ex -> "Expected " <> List.intercalate ", " ex <> "."+          more 0+++  -- |+  -- Validate parser result and turn it into an Error upon failure.+  --+  {-# INLINE CONLIKE validate #-}+  validate :: (a -> Either String b) -> Parser a -> Parser b+  validate test par = Parser \inp ->+    case runParser par inp of+      Failure expected more -> Failure expected more+      Error reason more len -> Error reason more len+      Success res more ->+        case test res of+          Right res' -> Success res' more+          Left reason -> Error reason inp (length inp - length more)+++  -- |   -- Accept whatever input remains.   --   {-# INLINE takeByteString #-}@@ -423,6 +461,14 @@     -- |+  -- Peek at whatever input remains.+  --+  {-# INLINE peekByteString #-}+  peekByteString :: Parser ByteString+  peekByteString = Parser \inp -> Success inp inp+++  -- |   -- Accepts end of input and fails if we are not there yet.   --   {-# INLINE endOfInput #-}@@ -438,6 +484,13 @@   {-# INLINE atEnd #-}   atEnd :: Parser Bool   atEnd = Parser \inp -> Success (null inp) inp+++  -- |+  -- Calculate offset from the original input and the remainder.+  --+  offset :: ByteString -> ByteString -> Int+  offset inp more = length inp - length more   -- vim:set ft=haskell sw=2 ts=2 et:
lib/Data/ByteString/Parser/Char8.hs view
@@ -66,13 +66,22 @@   , wrap   , match   , label-  , extent+  , unlabel+  , commit+  , validate      -- * End Of Input   , takeByteString+  , peekByteString   , endOfInput   , atEnd +    -- * Position+  , offset+  , position+  , explain+  , Explanation(..)+     -- * Miscelaneous     -- |     -- These are all generic methods, but since I sometimes forget about them,@@ -92,6 +101,7 @@    import Data.Maybe   import Data.Word+  import Data.List qualified as List   import GHC.Base (unsafeChr)    import Data.ByteString as BS@@ -100,8 +110,10 @@   import Snack.Combinators    import Data.ByteString.Parser ( Parser(..), Result(..), parseOnly-                                , string, count, match, label, extent-                                , takeByteString, endOfInput, atEnd+                                , string, count, match, label, unlabel, commit+                                , validate+                                , takeByteString, peekByteString+                                , endOfInput, atEnd, offset                                 )    import Data.ByteString.Lex.Fractional qualified as LF@@ -202,7 +214,7 @@     let (pfx, sfx) = splitAt (length str) inp      in case toCaseFold pfx == toCaseFold str of           True -> Success pfx sfx-          False -> Failure [show pfx] inp+          False -> Failure [show str] inp     -- |@@ -264,7 +276,8 @@     -- |-  -- Like 'takeWhile', but requires at least a single character.+  -- Like 'Data.ByteString.Parser.Char8.takeWhile',+  -- but requires at least a single character.   --   {-# INLINE CONLIKE takeWhile1 #-}   takeWhile1 :: (Char -> Bool) -> Parser ByteString@@ -350,6 +363,75 @@   {-# INLINE w2c #-}   w2c :: Word8 -> Char   w2c = unsafeChr . fromIntegral+++  -- |+  -- Determine @(line, column)@ from the original input and the remainder.+  --+  -- Counts line feed characters leading to the 'offset', so only use it+  -- on your slow path. For example when describing parsing errors.+  --+  position :: ByteString -> ByteString -> (Int, Int)+  position inp more = (succ line, succ column)+    where+      column = length lastLine+      lastLine = takeWhileEnd (10 /=) leader+      line = BS.count 10 leader+      leader = dropEnd (length more) inp+++  -- |+  -- More precise 'Result' description produced by 'explain'.+  --+  data Explanation+    = Explanation+      { exSource       :: String+        -- ^ Name of the source file.+      , exSpanFrom     :: (Int, Int)+        -- ^ Line and column where the problem starts.+      , exSpanTo       :: (Int, Int)+        -- ^ Line and column where the problem ends.+      , exMessage      :: String+        -- ^ Message associated with the problem.+      }+    deriving (Eq, Show)+++  -- |+  -- Process the result for showing it to the user.+  --+  explain :: String -> ByteString -> Result a -> Explanation+  explain src inp (Success _ more) =+    Explanation { exSource   = src+                , exSpanFrom = pos+                , exSpanTo   = pos+                , exMessage  = "Parsed successfully up to this point."+                }+      where+        pos = position inp more+++  explain src inp (Failure expected more) =+    Explanation { exSource   = src+                , exSpanFrom = pos+                , exSpanTo   = pos+                , exMessage =+                    case expected of+                      [] -> "Unexpected input."+                      ex -> "Expected " <> List.intercalate ", " ex <> "."+                }+      where+        pos = position inp more++  explain src inp (Error reason more len) =+    Explanation { exSource   = src+                , exSpanFrom = from+                , exSpanTo   = to+                , exMessage  = reason+                }+      where+        from = position inp more+        to   = position inp (BS.drop len more)   -- vim:set ft=haskell sw=2 ts=2 et:
lib/Data/Text/Parser.hs view
@@ -63,13 +63,22 @@   , wrap   , match   , label-  , extent+  , unlabel+  , commit+  , validate      -- * End Of Input   , takeText+  , peekText   , endOfInput   , atEnd +    -- * Position+  , offset+  , position+  , explain+  , Explanation(..)+     -- * Miscelaneous     -- |     -- These are all generic methods, but since I sometimes forget about them,@@ -89,6 +98,7 @@    import Data.Char   import Data.Maybe+  import Data.List qualified as List    import Data.Text as T   import Data.Text.Unsafe as T@@ -109,7 +119,7 @@   --   data Result a     = Success a {-# UNPACK #-} !Text-      -- ^ Parser successfully match the input.+      -- ^ Parser successfully matched the input.       --   Produces the parsing result and the remainder of the input.      | Failure [String] {-# UNPACK #-} !Text@@ -117,9 +127,7 @@       --   Produces list of expected inputs and the corresponding remainder.      | Error String {-# UNPACK #-} !Text {-# UNPACK #-} !Int-      -- ^ 'fail' was called somewhere during the parsing.-      --    Produces the reason and the remainder at the corresponding point-      --    with length of the problematic extent.+      -- ^ Parser ran into an error. Either syntactic or a validation one.      deriving (Eq, Show) @@ -189,24 +197,7 @@    instance MonadPlus Parser -  instance MonadFail Parser where-    -- |-    -- Fail the whole parser with given reason.-    ---    -- If you want the best error report possible, fail at the end of a-    -- relevant 'extent'.-    ---    -- For example, if you are parsing a mapping that is syntactically valid,-    -- but does not contain some mandatory keys, fail after parsing the whole-    -- mapping and make sure that the maaping parser and the 'fail' call are-    -- enclosed in an 'extent'.-    ---    -- That way, the error will indicate the extent remainder and length.-    ---    {-# INLINE CONLIKE fail #-}-    fail reason = Parser \inp -> Error reason inp 0 -   -- |   -- Accepts a single, matching character.   --@@ -307,7 +298,7 @@     let (pfx, sfx) = splitAt (length str) inp      in case pfx == str of           True -> Success pfx sfx-          False -> Failure [show pfx] inp+          False -> Failure [show str] inp     -- |@@ -319,7 +310,7 @@     let (pfx, sfx) = splitAt (length str) inp      in case toCaseFold pfx == toCaseFold str of           True -> Success pfx sfx-          False -> Failure [show pfx] inp+          False -> Failure [show str] inp     -- |@@ -442,23 +433,56 @@     -- |-  -- Marks an unlabelel extent of the parser.+  -- Un-names an extent of the parser.   ---  -- When the extent returns an Error, it is adjusted to cover the whole-  -- extent, but the reason is left intact.+  -- Same as 'label', but removes any expected values upon Failure.+  -- Very useful to mark comments and optional whitespace with.   ---  {-# INLINE CONLIKE extent #-}-  extent :: Parser a -> Parser a-  extent par = Parser \inp ->+  {-# INLINE CONLIKE unlabel #-}+  unlabel :: Parser a -> Parser a+  unlabel par = Parser \inp ->     case runParser par inp of       Success res more -> Success res more-      Failure expected more -> Failure expected more+      Failure _expected _more -> Failure [] inp       Error reason more len ->         let len' = len + (length inp - length more)          in Error reason inp len'     -- |+  -- Disable backtracking for the parser.+  -- Failure is treated as an Error.+  --+  {-# INLINE CONLIKE commit #-}+  commit :: Parser a -> Parser a+  commit par = Parser \inp ->+    case runParser par inp of+      Success res more -> Success res more+      Error reason more len -> Error reason more len+      Failure expected more ->+        Error+          case expected of+            [] -> "Unexpected input."+            ex -> "Expected " <> List.intercalate ", " ex <> "."+          more 0+++  -- |+  -- Validate parser result and turn it into an Error upon failure.+  --+  {-# INLINE CONLIKE validate #-}+  validate :: (a -> Either String b) -> Parser a -> Parser b+  validate test par = Parser \inp ->+    case runParser par inp of+      Failure expected more -> Failure expected more+      Error reason more len -> Error reason more len+      Success res more ->+        case test res of+          Right res' -> Success res' more+          Left reason -> Error reason inp (length inp - length more)+++  -- |   -- Accept whatever input remains.   --   {-# INLINE takeText #-}@@ -467,6 +491,14 @@     -- |+  -- Peek at whatever input remains.+  --+  {-# INLINE peekText #-}+  peekText :: Parser Text+  peekText = Parser \inp -> Success inp inp+++  -- |   -- Accepts end of input and fails if we are not there yet.   --   {-# INLINE endOfInput #-}@@ -554,6 +586,83 @@     case unsafeWithUtf8 LF.readDecimal inp of       Just (res, more) -> Success res more       Nothing -> Failure ["fractional"] inp++++  -- |+  -- Calculate offset from the original input and the remainder.+  --+  offset :: Text -> Text -> Int+  offset inp more = length inp - length more+++  -- |+  -- Determine @(line, column)@ from the original input and the remainder.+  --+  -- Counts line feed characters leading to the 'offset', so only use it+  -- on your slow path. For example when describing parsing errors.+  --+  position :: Text -> Text -> (Int, Int)+  position inp more = (succ line, succ column)+    where+      column = length lastLine+      lastLine = takeWhileEnd ('\n' /=) leader+      line = T.count "\n" leader+      leader = dropEnd (length more) inp+++  -- |+  -- More precise 'Result' description produced by 'explain'.+  --+  data Explanation+    = Explanation+      { exSource       :: String+        -- ^ Name of the source file.+      , exSpanFrom     :: (Int, Int)+        -- ^ Line and column where the problem starts.+      , exSpanTo       :: (Int, Int)+        -- ^ Line and column where the problem ends.+      , exMessage      :: String+        -- ^ Message associated with the problem.+      }+    deriving (Eq, Show)+++  -- |+  -- Process the result for showing it to the user.+  --+  explain :: String -> Text -> Result a -> Explanation+  explain src inp (Success _ more) =+    Explanation { exSource   = src+                , exSpanFrom = pos+                , exSpanTo   = pos+                , exMessage  = "Parsed successfully up to this point."+                }+      where+        pos = position inp more+++  explain src inp (Failure expected more) =+    Explanation { exSource   = src+                , exSpanFrom = pos+                , exSpanTo   = pos+                , exMessage =+                    case expected of+                      [] -> "Unexpected input."+                      ex -> "Expected " <> List.intercalate ", " ex <> "."+                }+      where+        pos = position inp more++  explain src inp (Error reason more len) =+    Explanation { exSource   = src+                , exSpanFrom = from+                , exSpanTo   = to+                , exMessage  = reason+                }+      where+        from = position inp more+        to   = position inp (T.drop len more)   -- vim:set ft=haskell sw=2 ts=2 et:
snack.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.0 name:               snack-version:            0.2.0.0+version:            0.3.0.0 license:            CC0-1.0 license-file:       LICENSE copyright:          Jan Hamal Dvořák@@ -32,7 +32,7 @@     default-language:   Haskell2010     default-extensions:         BlockArguments LambdaCase ImportQualifiedPost BangPatterns-        NamedFieldPuns CPP+        NamedFieldPuns CPP OverloadedStrings      ghc-options:         -Wall -Wcompat -Wincomplete-uni-patterns -Wunused-packages