packages feed

construct 0.1 → 0.2

raw patch · 7 files changed

+148/−103 lines, 7 filesdep ~basePVP ok

version bump matches the API change (PVP)

Dependency ranges changed: base

API changes (from Hackage documentation)

+ Construct: recordWith :: forall g m n o s. (Apply g, Traversable g, FixTraversable m, Applicative n, Monoid s, Applicative o) => (forall a. o (n a) -> n a) -> g (Format m n s) -> Format m n s (g o)
- Construct: (*>) :: (Applicative m, Semigroup (n s)) => Format m n s () -> Format m n s a -> Format m n s a
+ Construct: (*>) :: (Applicative m, Applicative n, Semigroup s) => Format m n s () -> Format m n s a -> Format m n s a
- Construct: (<*) :: (Applicative m, Semigroup (n s)) => Format m n s a -> Format m n s () -> Format m n s a
+ Construct: (<*) :: (Applicative m, Applicative n, Semigroup s) => Format m n s a -> Format m n s () -> Format m n s a
- Construct: deppair :: (Monad m, Semigroup (n s)) => Format m n s a -> (a -> Format m n s b) -> Format m n s (a, b)
+ Construct: deppair :: (Monad m, Applicative n, Semigroup s) => Format m n s a -> (a -> Format m n s b) -> Format m n s (a, b)
- Construct: many :: (Alternative m, Monoid (n s)) => Format m n s a -> Format m n s [a]
+ Construct: many :: (Alternative m, Applicative n, Monoid s) => Format m n s a -> Format m n s [a]
- Construct: optionWithDefault :: (Alternative m, Alternative n, Monoid (n s)) => Format m n s () -> Format m n s a -> Format m n s (Maybe a)
+ Construct: optionWithDefault :: (Alternative m, Alternative n) => Format m n s () -> Format m n s a -> Format m n s (Maybe a)
- Construct: optional :: (Alternative m, Alternative n, Monoid (n s)) => Format m n s a -> Format m n s (Maybe a)
+ Construct: optional :: (Alternative m, Alternative n, Monoid s) => Format m n s a -> Format m n s (Maybe a)
- Construct: pair :: (Applicative m, Semigroup (n s)) => Format m n s a -> Format m n s b -> Format m n s (a, b)
+ Construct: pair :: (Applicative m, Applicative n, Semigroup s) => Format m n s a -> Format m n s b -> Format m n s (a, b)
- Construct: record :: (Apply g, Traversable g, FixTraversable m, Monoid (n s), Applicative o, Foldable o) => g (Format m n s) -> Format m n s (g o)
+ Construct: record :: (Apply g, Traversable g, FixTraversable m, Applicative n, Monoid s) => g (Format m n s) -> Format m n s (g Identity)
- Construct: some :: (Alternative m, AlternativeFail n, Semigroup (n s)) => Format m n s a -> Format m n s [a]
+ Construct: some :: (Alternative m, AlternativeFail n, Semigroup s) => Format m n s a -> Format m n s [a]

Files

CHANGELOG.md view
@@ -1,5 +1,14 @@ # Revision history for construct -## 0.1.0.0 -- YYYY-mm-dd+## 0.2 -- 2020-01-27++* Updated for `base-4.13`+* Eliminated the `Monoid/Semigroup (n s)` constraints+* Added `recordWith`+* Fixed the URI example and test+  * Constrained the URI format serialization to ByteString+  * Switched the URI parser to Attoparsec++## 0.1 -- 2020-01-22  * First version. Released on an unsuspecting world.
construct.cabal view
@@ -1,6 +1,6 @@ cabal-version:       >=1.10 name:                construct-version:             0.1+version:             0.2 synopsis:            Haskell version of the Construct library for easy specification of file formats description:    A Haskell version of the <https://construct.readthedocs.io/en/latest/intro.html Construct> library for Python. A@@ -28,7 +28,7 @@   hs-source-dirs:      src   exposed-modules:     Construct, Construct.Bits, Construct.Classes   other-modules:       Construct.Internal-  build-depends:       base >=4.11 && <4.13,+  build-depends:       base >=4.11 && <5,                        bytestring >= 0.10 && < 0.11,                        text >= 0.10 && < 1.3,                        monoid-subclasses >= 1.0 && < 1.1,@@ -56,12 +56,12 @@ test-suite             examples   type:                exitcode-stdio-1.0   hs-source-dirs:      test-  x-uses-tf:           true   build-depends:       base >=4.9 && < 5, construct,                        bytestring < 0.11, text < 1.3, cereal, rank2classes >= 1.0.2 && < 1.4,                        monoid-subclasses >= 1.0 && < 1.1, incremental-parser < 0.5, attoparsec >= 0.12 && < 0.14,                        directory < 2, filepath < 1.5,                        tasty >= 0.7, tasty-hunit   main-is:             Test.hs-  other-modules:       MBR, TAR, URI, WMF+  other-modules:       MBR, TAR, URI, WMF, OrphanInstances   default-language:    Haskell2010+  ghc-options:         -O0
src/Construct.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE FlexibleContexts, FlexibleInstances, GADTs #-}+{-# LANGUAGE FlexibleContexts, FlexibleInstances, GADTs, RankNTypes, ScopedTypeVariables #-}  module Construct (@@ -9,7 +9,7 @@   (Construct.<$), (Construct.*>), (Construct.<*), (Construct.<|>), (<+>), (<?>),   empty, optional, optionWithDefault, pair, deppair, many, some, sepBy, count,   -- ** Self-referential record support-  mfix, record,+  mfix, record, recordWith,   -- ** Mapping over a 'Format'   mapSerialized, mapMaybeSerialized, mapValue, mapMaybeValue,   -- ** Constraining a 'Format'@@ -29,9 +29,11 @@ import Control.Monad.Fix (MonadFix) import Data.Functor ((<$>), void) import qualified Data.Functor.Const as Functor+import qualified Data.Functor.Identity as Functor import qualified Data.List as List import Data.List.NonEmpty (nonEmpty) import Data.Maybe (fromMaybe)+import Data.Monoid (Ap(Ap, getAp)) import Data.Semigroup (Semigroup, (<>), sconcat) import Data.Word (Word8) import Data.ByteString (ByteString)@@ -298,7 +300,7 @@ -- Right [(513,"\ETX")] cereal' get put = Format p (pure . runPut . put)    where p = go (runGetPartial get mempty)-            where go (Fail msg _) = fail msg+            where go (Fail msg _) = Parser.unexpected msg                   go (Done r _) = pure r                   go (Partial cont) = Input.anyToken >>= go . cont @@ -316,14 +318,21 @@    serialize = \as-> if length as == n then mconcat <$> traverse (serialize item) as                      else expectedName ("a list of length " ++ show n) (failure $ show as)} -record :: (Rank2.Apply g, Rank2.Traversable g, FixTraversable m, Monoid (n s), Applicative o, Foldable o) =>-          g (Format m n s) -> Format m n s (g o)+record :: (Rank2.Apply g, Rank2.Traversable g, FixTraversable m, Applicative n, Monoid s) =>+          g (Format m n s) -> Format m n s (g Functor.Identity) -- | Converts a record of field formats into a single format of the whole record.-record formats = Format{+record = recordWith Functor.runIdentity++recordWith :: forall g m n o s. (Rank2.Apply g, Rank2.Traversable g, FixTraversable m, Applicative n, Monoid s,+                                 Applicative o) =>+              (forall a. o (n a) -> n a) -> g (Format m n s) -> Format m n s (g o)+-- | Converts a record of field formats into a single format of the whole record, a generalized form of 'record'.+recordWith collapse formats = Format{    parse = Input.fixSequence (parse Rank2.<$> formats),-   serialize = Rank2.foldMap Functor.getConst . Rank2.liftA2 serializeField formats+   serialize = getAp . Rank2.foldMap Functor.getConst . Rank2.liftA2 serializeField formats    }-   where serializeField format xs = Functor.Const (foldMap (serialize format) xs)+   where serializeField :: forall a. Format m n s a -> o a -> Functor.Const (Ap n s) a+         serializeField format xs = Functor.Const (Ap $ collapse (serialize format <$> xs))  infixl 3 <|> infixl 4 <$@@ -336,17 +345,17 @@    parse = a Applicative.<$ parse f,    serialize = \b-> if a == b then serialize f () else expectedName (show a) (failure $ show b)} -(*>) :: (Applicative m, Semigroup (n s)) => Format m n s () -> Format m n s a -> Format m n s a+(*>) :: (Applicative m, Applicative n, Semigroup s) => Format m n s () -> Format m n s a -> Format m n s a -- | Same as the usual 'Applicative.*>' except a 'Format' is no 'Functor', let alone 'Applicative'. f1 *> f2 = Format{    parse = parse f1 Applicative.*> parse f2,-   serialize = \a-> serialize f1 () <> serialize f2 a}+   serialize = \a-> Applicative.liftA2 (<>) (serialize f1 ()) (serialize f2 a)} -(<*) :: (Applicative m, Semigroup (n s)) => Format m n s a -> Format m n s () -> Format m n s a+(<*) :: (Applicative m, Applicative n, Semigroup s) => Format m n s a -> Format m n s () -> Format m n s a -- | Same as the usual 'Applicative.<*' except a 'Format' is no 'Functor', let alone 'Applicative'. f1 <* f2 = Format{    parse = parse f1 Applicative.<* parse f2,-   serialize = \a-> serialize f1 a <> serialize f2 ()}+   serialize = \a-> Applicative.liftA2 (<>) (serialize f1 a) (serialize f2 ())}  (<|>) :: (Alternative m, Alternative n) => Format m n s a -> Format m n s a -> Format m n s a -- | Same as the usual 'Applicative.<|>' except a 'Format' is no 'Functor', let alone 'Alternative'.@@ -360,32 +369,31 @@    parse = Left <$> parse f1 Applicative.<|> Right <$> parse f2,    serialize = either (serialize f1) (serialize f2)} -optional :: (Alternative m, Alternative n, Monoid (n s)) => Format m n s a -> Format m n s (Maybe a)+optional :: (Alternative m, Alternative n, Monoid s) => Format m n s a -> Format m n s (Maybe a) -- | Same as the usual 'Applicative.optional' except a 'Format' is no 'Functor', let alone 'Alternative'. optional f = Format{    parse = Applicative.optional (parse f),-   serialize = maybe mempty (serialize f)}+   serialize = maybe (pure mempty) (serialize f)}  -- | Like 'optional' except with arbitrary default serialization for the @Nothing@ value. -- -- > optional = optionWithDefault (literal mempty)-optionWithDefault :: (Alternative m, Alternative n, Monoid (n s)) =>-                     Format m n s () -> Format m n s a -> Format m n s (Maybe a)+optionWithDefault :: (Alternative m, Alternative n) => Format m n s () -> Format m n s a -> Format m n s (Maybe a) optionWithDefault d f = Format{    parse = Just <$> parse f Applicative.<|> Nothing Applicative.<$ parse d,    serialize = maybe (serialize d ()) (serialize f)} -many :: (Alternative m, Monoid (n s)) => Format m n s a -> Format m n s [a]+many :: (Alternative m, Applicative n, Monoid s) => Format m n s a -> Format m n s [a] -- | Same as the usual 'Applicative.many' except a 'Format' is no 'Functor', let alone 'Alternative'. many f = Format{    parse = Applicative.many (parse f),-   serialize = foldMap (serialize f)}+   serialize = fmap mconcat . traverse (serialize f)} -some :: (Alternative m, AlternativeFail n, Semigroup (n s)) => Format m n s a -> Format m n s [a]+some :: (Alternative m, AlternativeFail n, Semigroup s) => Format m n s a -> Format m n s [a] -- | Same as the usual 'Applicative.some' except a 'Format' is no 'Functor', let alone 'Alternative'. some f = Format{    parse = Applicative.some (parse f),-   serialize = maybe (failure "[]") sconcat . nonEmpty . map (serialize f)}+   serialize = maybe (failure "[]") (fmap sconcat . traverse (serialize f)) . nonEmpty}  sepBy :: (Alternative m, Applicative n, Monoid s) => Format m n s a -> Format m n s () -> Format m n s [a] -- | Represents any number of values formatted using the first argument, separated by the second format argumewnt in@@ -397,16 +405,16 @@    parse = Parser.sepBy (parse format) (parse separator),    serialize = \xs-> mconcat <$> sequenceA (List.intersperse (serialize separator ()) $ serialize format <$> xs)} -pair :: (Applicative m, Semigroup (n s)) => Format m n s a -> Format m n s b -> Format m n s (a, b)+pair :: (Applicative m, Applicative n, Semigroup s) => Format m n s a -> Format m n s b -> Format m n s (a, b) -- | Combines two formats into a format for the pair of their values. -- -- >>> testParse (pair char char) "abc" -- Right [(('a','b'),"c")] pair f g = Format{    parse = (,) <$> parse f <*> parse g,-   serialize = \(a, b)-> serialize f a <> serialize g b}+   serialize = \(a, b)-> Applicative.liftA2 (<>) (serialize f a) (serialize g b)} -deppair :: (Monad m, Semigroup (n s)) => Format m n s a -> (a -> Format m n s b) -> Format m n s (a, b)+deppair :: (Monad m, Applicative n, Semigroup s) => Format m n s a -> (a -> Format m n s b) -> Format m n s (a, b) -- | Combines two formats, where the second format depends on the first value, into a format for the pair of their -- values.  Similar to '>>=' except 'Format' is no 'Functor' let alone 'Monad'. --@@ -416,7 +424,7 @@ -- Right [(('a','a'),"c")] deppair f g = Format{    parse = parse f >>= \a-> parse (g a) >>= \b-> return (a, b),-   serialize = \(a, b)-> serialize f a <> serialize (g a) b}+   serialize = \(a, b)-> Applicative.liftA2 (<>) (serialize f a) (serialize (g a) b)}  empty :: (Alternative m, Alternative n) => Format m n s a -- | Same as the usual 'Applicative.empty' except a 'Format' is no 'Functor', let alone 'Alternative'.
+ test/OrphanInstances.hs view
@@ -0,0 +1,18 @@+module OrphanInstances where++import Data.ByteString (ByteString)+import Data.Monoid.Textual (TextualMonoid)+import Data.Text.Encoding (encodeUtf8)+import qualified Data.Monoid.Textual+import qualified Data.ByteString.Char8 as ASCII++instance TextualMonoid ByteString where+   fromText = encodeUtf8+   singleton = ASCII.singleton+   splitCharacterPrefix = ASCII.uncons+   dropWhile _ = ASCII.dropWhile+   dropWhile_ _ = ASCII.dropWhile+   takeWhile _ = ASCII.takeWhile+   takeWhile_ _ = ASCII.takeWhile+   span _ = ASCII.span+   span_ _ = ASCII.span
test/TAR.hs view
@@ -18,6 +18,7 @@ import Data.Attoparsec.ByteString (Parser)  import Construct+import OrphanInstances  import Prelude hiding ((<$), (<*), (*>), take, takeWhile) @@ -139,17 +140,6 @@ sumOf header = ByteString.foldl' add 0 (fold $ serialize blankFormat header)    where add s b = s + fromIntegral b          blankFormat = record (fileHeaderRecord $ literal $ ASCII.replicate 8 ' ')--instance TextualMonoid ByteString where-   fromText = encodeUtf8-   singleton = ASCII.singleton-   splitCharacterPrefix = ASCII.uncons-   dropWhile _ = ASCII.dropWhile-   dropWhile_ _ = ASCII.dropWhile-   takeWhile _ = ASCII.takeWhile-   takeWhile_ _ = ASCII.takeWhile-   span _ = ASCII.span-   span_ _ = ASCII.span  $(Rank2.TH.deriveAll ''Archive) $(Rank2.TH.deriveAll ''File)
test/Test.hs view
@@ -3,13 +3,13 @@ module Main where  import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Char8 as ASCII import Data.ByteString (ByteString) import Data.Functor.Identity (Identity) import Data.List (isSuffixOf)-import Data.Maybe (mapMaybe)+import Data.Maybe (fromMaybe) import Data.Text (Text) import qualified Data.Text as Text-import Data.Text.Encoding (decodeUtf8, encodeUtf8) import Numeric (showHex) import System.Directory (doesDirectoryExist, listDirectory) import System.FilePath.Posix (combine)@@ -25,8 +25,11 @@ import qualified URI import qualified WMF +import Debug.Trace+ data TestFormat = forall f. TestFormat (Format (Parser ByteString) Maybe ByteString (f Identity))-                | forall f. LineFormat (Format (Parser Text) Maybe Text (f Identity))+                | forall f. LineFormat (Format (Parser ByteString) Maybe ByteString (f Identity))+                | forall f. AttoLineFormat (Format Atto.Parser Maybe ByteString (f Identity))                 | forall f. AttoFormat (Format Atto.Parser Maybe ByteString (f Identity))  main = exampleTree "" "test/examples" >>= defaultMain . testGroup "examples"@@ -41,30 +44,43 @@                  let format                         | ".mbr"  `isSuffixOf` path = TestFormat MBR.format                         | ".tar"  `isSuffixOf` path = AttoFormat TAR.archive-                        | ".uris" `isSuffixOf` path = LineFormat URI.uriReference+                        | ".uris" `isSuffixOf` path = AttoLineFormat URI.uriReference                         | ".wmf"  `isSuffixOf` path = TestFormat WMF.fileFormat-                     textLines = Text.lines (decodeUtf8 blob)                      roundTrip f t-                        | Text.null t = Just t+                        | ByteString.null t = Just t                         | [(structure, remainder)] <- Incremental.completeResults (Incremental.feedEof $                                                                                    Incremental.feed t $ parse f),-                          Text.null remainder = serialize f structure+                          ByteString.null remainder = serialize f structure                         | otherwise = Nothing+                     attoRoundTrip f t+                        | ByteString.null t = Just t+                        | Atto.Done remainder structure <- Atto.parse (parse f) t, ByteString.null remainder =+                             serialize f structure+                        | Atto.Partial i <- Atto.parse (parse f) t,+                          Atto.Done remainder structure <- i mempty, ByteString.null remainder = serialize f structure+                        | otherwise = Nothing                      Just blob'                         | TestFormat f <- format,                           [(structure, remainder)] <- Incremental.completeResults (Incremental.feedEof $                                                                                    Incremental.feed blob $ parse f) =                              (<> remainder) <$> serialize f structure-                        | LineFormat f <- format = Just (encodeUtf8 $ mconcat $ mapMaybe (roundTrip f) textLines)+                        | LineFormat f <- format = Just (mconcat+                                                         $ map ((<> "\n") . fromMaybe "???" . roundTrip f)+                                                         $ ASCII.lines blob)+                        | AttoLineFormat f <- format = Just (mconcat+                                                             $ map ((<> "\n") . fromMaybe "???" . attoRoundTrip f)+                                                             $ ASCII.lines blob)                         | AttoFormat f <- format,                           Atto.Done remainder structure <- Atto.parse (parse f) blob =                              (<> remainder) <$> serialize f structure                         | AttoFormat f <- format, Atto.Partial i <- Atto.parse (parse f) blob,                           Atto.Done remainder structure <- i mempty =                              (<> remainder) <$> serialize f structure-                 return . (:[]) . testCase path $ assertEqual "round-trip" (hex blob) (hex blob')+                 return . (:[]) . testCase path $ assertEqual "round-trip" (hex format blob) (hex format blob') -hex :: ByteString -> String-hex = ByteString.foldr (pad . flip showHex "") ""+hex :: TestFormat -> ByteString -> String+hex LineFormat{} = ASCII.unpack+hex AttoLineFormat{} = ASCII.unpack+hex _ = ByteString.foldr (pad . flip showHex "") ""    where pad [x] s = ['0', x] ++ s          pad [x, y] s = [x, y] ++ s
test/URI.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE FlexibleInstances, OverloadedStrings, ScopedTypeVariables, StandaloneDeriving,+{-# LANGUAGE FlexibleInstances, LambdaCase, OverloadedStrings, ScopedTypeVariables, StandaloneDeriving,              TemplateHaskell, TupleSections, TypeApplications #-}  module URI where@@ -7,7 +7,7 @@ import Data.ByteString (ByteString) import qualified Data.ByteString as ByteString import qualified Data.ByteString.Char8 as ASCII-import Data.Char (isAlpha, isAscii, isDigit, isHexDigit, chr, ord)+import Data.Char (isAlpha, isAlphaNum, isDigit, isHexDigit, chr, ord, toUpper) import Data.Foldable (fold) import Data.Functor.Identity (Identity, runIdentity) import qualified Data.List as List@@ -22,9 +22,10 @@ import Numeric (readHex, showHex) import Numeric.Natural (Natural) import qualified Rank2.TH-import Text.ParserCombinators.Incremental.LeftBiasedLocal (Parser)+import Data.Attoparsec.ByteString (Parser)  import Construct+import OrphanInstances  import Prelude hiding ((<$), (<*), (*>), take, takeWhile) @@ -42,55 +43,63 @@    port :: f (Maybe Word16)    } -data HostName t = IPv4address{getIPv4address :: [Word8]}-                | IPv6address{getIPv6address :: [Word16]}-                | IPvFuture {version :: Word,-                             address :: t}-                | RegisteredName{getRegisteredName :: t}+data HostName t = IPv4address [Word8]+                | IPv6address [Word16]+                | IPvFuture Word t+                | RegisteredName t+                deriving (Eq, Read, Show) -uriReference :: (Show t, TextualMonoid t) => Format (Parser t) Maybe t (UriReference t Identity)+deriving instance Show t => Show (UriReference t Identity)+deriving instance Show t => Show (Authority t Identity)++uriReference :: Format Parser Maybe ByteString (UriReference ByteString Identity) uriReference = record UriReference{-   scheme = optional (mapValue (uncurry (<>)) (Factorial.splitAt 1) $-                      pair (satisfy (any alpha . Textual.characterPrefix) $ take 1)-                           (takeCharsWhile schemeChar)-                      <* literal ":"),-   authority = optional $ record Authority{-        user = optional (takeCharsWhile userChar <* literal "@"),-        host = hostName,-        port = optional (mapValue fromIntegral fromIntegral $ satisfy (< 65536) $-                         literal ":" *> mapDec (takeCharsWhile1 digit))},  -- port = *DIGIT in spec?+   scheme = optional (uriScheme <* literal ":"),+   authority = optional (literal "//" *> uriAuthority),    path = encodedCharSequence pathChar `sepBy` literal "/",-   query = optional (literal "*" *> encodedCharSequence queryChar),+   query = optional (literal "?" *> encodedCharSequence queryChar),    fragment = optional (literal "#" *> encodedCharSequence fragmentChar)    } -hostName :: (Show t, TextualMonoid t) => Format (Parser t) Maybe t (HostName t)-hostName = mapValue IPv4address getIPv4address ipV4address+uriAuthority :: Format Parser Maybe ByteString (Authority ByteString Identity)+uriAuthority = record Authority{+        user = optional (takeCharsWhile userChar <* literal "@"),+        host = hostName,+        port = optional (mapValue fromIntegral fromIntegral $+                         satisfy (< 65536) $ literal ":" *> mapDec (takeCharsWhile1 isDigit))}  -- port = *DIGIT in spec?++uriScheme :: Format Parser Maybe ByteString ByteString+uriScheme = mapValue (uncurry (<>)) (Factorial.splitAt 1) $+            pair (satisfy (any isAlpha . Textual.characterPrefix) $ take 1) (takeCharsWhile schemeChar)+                      +hostName :: Format Parser Maybe ByteString (HostName ByteString)+hostName = mapMaybeValue (Just . IPv4address) (\case (IPv4address a)-> Just a; _ -> Nothing) ipV4address            <|> literal "["-               *> (mapValue IPv6address getIPv6address ipV6address-                    <|> mapValue (uncurry IPvFuture) (\ipf-> (version ipf, address ipf))-                                 (pair (literal "v" *> mapHex (takeCharsWhile1 hexDigit))-                                       (literal "." *> takeCharsWhile1 ipFutureChar)))+               *> (mapMaybeValue (Just . IPv6address) (\case (IPv6address a)-> Just a; _ -> Nothing) ipV6address+                    <|> mapMaybeValue (Just . uncurry IPvFuture) (\case (IPvFuture v a)-> Just (v, a); _ -> Nothing)+                                      (pair (literal "v" *> mapHex (takeCharsWhile1 hexDigit))+                                            (literal "." *> takeCharsWhile1 ipFutureChar)))                <* literal "]"-           <|> mapValue RegisteredName getRegisteredName (encodedCharSequence hostChar)+           <|> mapMaybeValue (Just . RegisteredName) (\case (RegisteredName a)-> Just a; _ -> Nothing)+                             (encodedCharSequence hostChar) -ipV4address :: forall t. (Show t, TextualMonoid t) => Format (Parser t) Maybe t [Word8]+ipV4address :: Format Parser Maybe ByteString [Word8] ipV4address = satisfy ((== 4) . length) (decOctet `sepBy` literal ".")    where decOctet = mapValue fromIntegral fromIntegral $                     satisfy (< 256) $ mapDec $-                    takeCharsWhile1 digit+                    takeCharsWhile1 isDigit -ipV6address :: (Show t, TextualMonoid t) => Format (Parser t) Maybe t [Word16]+ipV6address :: Format Parser Maybe ByteString [Word16] ipV6address = satisfy ((== 8) . length) $               mapValue fill shorten ipV6addressShort    where fill :: [Maybe Word16] -> [Word16]          shorten :: [Word16] -> [Maybe Word16]-         fill words = concatMap (maybe (replicate (8 - length words) 0) (:[])) words+         fill words = concatMap (maybe (replicate (9 - length words) 0) (:[])) words          shorten [] = []          shorten (0:0:words) = Nothing : map Just (dropWhile (== 0) words)          shorten (word:words) = Just word : shorten words -ipV6addressShort :: (Show t, TextualMonoid t) => Format (Parser t) Maybe t [Maybe Word16]+ipV6addressShort :: Format Parser Maybe ByteString [Maybe Word16] ipV6addressShort = satisfy zeroOrOneNothings $                    mapValue (uncurry (++)) (, []) $                    pair (optional h16 `sepBy` literal ":")@@ -105,18 +114,17 @@             where elisionCount = length (filter isNothing words)          h16 = mapHex $ satisfy ((<5) . Factorial.length) $ takeCharsWhile1 hexDigit -mapHex :: (Show t, TextualMonoid t, Integral n, Show n) => Format (Parser t) Maybe t t -> Format (Parser t) Maybe t n-mapDec :: (Show t, TextualMonoid t) => Format (Parser t) Maybe t t -> Format (Parser t) Maybe t Natural+mapHex :: (Integral n, Show n) =>+          Format Parser Maybe ByteString ByteString -> Format Parser Maybe ByteString n+mapDec :: Format Parser Maybe ByteString ByteString -> Format Parser Maybe ByteString Natural mapDec = mapValue (read . Textual.toString (error . show)) (fromString . show) mapHex = mapValue (fst . head . readHex . Textual.toString (error . show)) (fromString . flip showHex "") -alpha, digit, hexDigit, schemeChar, hostChar, userChar, ipFutureChar,+hexDigit, schemeChar, hostChar, userChar, ipFutureChar,    pathChar, queryChar, fragmentChar, unreserved, subDelim :: Char -> Bool -alpha c      = isAlpha c && isAscii c-digit c      = isAlpha c && isDigit c-hexDigit c   = isAlpha c && isHexDigit c-schemeChar c = (isAlpha c || isDigit c) && isAscii c || elem @[] c "+-."+hexDigit c   = isHexDigit c+schemeChar c = isAlphaNum c || elem @[] c "+-." ipFutureChar = userChar hostChar c   = unreserved c || subDelim c userChar c   = unreserved c || subDelim c || c == ':'@@ -124,26 +132,22 @@ queryChar c  = pathChar c || c == '/' || c == '?' fragmentChar = queryChar subDelim c   = elem @[] c "!$&'()*+,;="-unreserved c = isAscii c && (isAlpha c || isDigit c || elem @[] c "-._~")+unreserved c = isAlphaNum c || elem @[] c "-._~" -encodedCharSequence :: forall t. (Show t, TextualMonoid t) => (Char -> Bool) -> Format (Parser t) Maybe t t+encodedCharSequence :: (Char -> Bool) -> Format Parser Maybe ByteString ByteString encodedCharSequence predicate = mapValue concatSequence splitSequence $-                                many (takeCharsWhile predicate <+> percentEncoded)-   where concatSequence :: [Either t Char] -> t-         splitSequence :: t -> [Either t Char]-         percentEncoded :: Format (Parser t) Maybe t Char+                                many (takeCharsWhile1 predicate <+> percentEncoded)+   where concatSequence :: [Either ByteString Char] -> ByteString+         splitSequence :: ByteString -> [Either ByteString Char]+         percentEncoded :: Format Parser Maybe ByteString Char          concatSequence = mconcat . map (either id Textual.singleton)          splitSequence s = case Textual.splitCharacterPrefix s-            of Just ('#', rest)-                  | Just (hex1, rest1) <- Textual.splitCharacterPrefix rest,-                    Just (hex2, rest2) <- Textual.splitCharacterPrefix rest1 ->-                       Right (hexChar [hex1, hex2]) : splitSequence rest2-                  | otherwise -> []-               Just (c, _)+            of Just (c, t)                   | predicate c, (prefix, rest) <- Textual.span_ False predicate s -> Left prefix : splitSequence rest+                  | otherwise -> Right c : splitSequence t                _ -> []-         percentEncoded = mapValue hexChar (padLeft . (`showHex` "") . ord) $-                          literal "#" *> count 2 (satisfy hexDigit char)+         percentEncoded = mapValue hexChar (padLeft . map toUpper . (`showHex` "") . ord) $+                          literal "%" *> count 2 (satisfy hexDigit char)          hexChar = chr . fst . head . readHex          padLeft [c] = ['0', c]          padLeft cs = cs