Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use a custom parsing monad instead of attoparsec. #298

Merged
merged 8 commits into from
Jan 3, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,6 @@ parseFieldCase loop x f = case plainFieldKind f of
_ -> [valueCase]
where
y = "y"
bytes = "bytes"
entry = "entry"
info = plainFieldInfo f
valueCase = pLitInt (fieldTag info) --> do'
Expand All @@ -258,11 +257,7 @@ parseFieldCase loop x f = case plainFieldKind f of
$ x
]
packedCase = pLitInt (packedFieldTag info) --> do'
[ bytes <-- parseFieldType lengthy
, y <-- "Data.ProtoLens.Encoding.Bytes.runEither"
@@ ("Data.ProtoLens.Encoding.Bytes.runParser"
@@ parsePackedField info
@@ bytes)
[ y <-- isolatedLengthy (parsePackedField info)
, stmt . loop . updateParseState (overField info ("Prelude.++" @@ y))
$ x
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module Data.ProtoLens.Compiler.Generate.FieldEncoding
, fieldEncoding
, lengthy
, groupEnd
, isolatedLengthy
) where

import Data.Word (Word8)
Expand Down Expand Up @@ -181,10 +182,24 @@ stringField = partialField "Data.Text.Encoding.encodeUtf8" decodeUtf8P lengthy

-- | A protobuf message type.
message :: FieldEncoding
message = partialField
message = lengthy
{ buildFieldType = "Prelude.." @@
buildFieldType lengthy @@
"Data.ProtoLens.encodeMessage"
(\m -> "Data.ProtoLens.decodeMessage" @@ m)
lengthy
, parseFieldType = isolatedLengthy "Data.ProtoLens.parseMessage"
}

-- | Takes a @Parser a@, reads a varint and then runs the parser
-- isolated to the given length.
isolatedLengthy :: Exp -> Exp
isolatedLengthy parser = do'
[ len <-- getVarInt'
, stmt $ "Data.ProtoLens.Encoding.Bytes.isolate"
@@ (fromIntegral' @@ len)
@@ parser
]
where
len = "len"

-- | Some functions that are used in multiple places in the generated code.
getVarInt', putVarInt', fromIntegral' :: Exp
Expand Down
1 change: 1 addition & 0 deletions proto-lens/src/Data/ProtoLens/Encoding/Bytes.hs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module Data.ProtoLens.Encoding.Bytes(
Parser,
Builder,
runParser,
isolate,
runBuilder,
-- * Bytestrings
getBytes,
Expand Down
12 changes: 12 additions & 0 deletions proto-lens/src/Data/ProtoLens/Encoding/Parser.hs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module Data.ProtoLens.Encoding.Parser
( Parser
, runParser
, atEnd
, isolate
, getWord8
, getWord32le
, getBytes
Expand Down Expand Up @@ -104,6 +105,17 @@ withSized len message f = Parser $ \end pos ->
else liftIO $ ParserResult pos' <$> f pos
{-# INLINE withSized #-}

-- | Run the given parsing action as if there are only
-- @len@ bytes remaining. That is, once @len@ bytes have been
-- consumed, 'atEnd' will return 'True' and other actions
-- like 'getWord8' will act like there is no input remaining.
isolate :: Int -> Parser a -> Parser a
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where do we catch negative len?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch; fixed to fail the parse in that case.

isolate len (Parser m) = Parser $ \end pos ->
let end' = pos `plusPtr` len
in if end' > end
then throwE "isolate: unexpected end of input"
else m end' pos

-- | If the parser fails, prepend an error message.
(<?>) :: Parser a -> String -> Parser a
Parser m <?> msg = Parser $ \end p ->
Expand Down
11 changes: 11 additions & 0 deletions proto-lens/tests/parser_test.hs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
-- | Unit and property tests for our custom parsing monad.
module Main (main) where

import Control.Applicative (liftA2)
import qualified Data.ByteString as B
import Data.Either (isLeft)

Expand All @@ -18,6 +19,7 @@ main = defaultMain
, testGroup "getBytes" testGetBytes
, testGroup "getWord32le" testGetWord32le
, testGroup "failure" testFailure
, testGroup "isolate" testIsolate
]

testParser :: [Test]
Expand Down Expand Up @@ -66,6 +68,15 @@ testFailure =
=== (Left "fghij: abcde" :: Either String ())
]

testIsolate :: [Test]
testIsolate =
[ testProperty "many" $ \bs bs' ->
runParser (liftA2 (,) (isolate (length bs) $ manyTillEnd getWord8)
(manyTillEnd getWord8))
(B.pack (bs ++ bs'))
== Right (bs, bs')
]

-- Since this is a test, just implement the slow stack-heavy way.
manyTillEnd :: Parser a -> Parser [a]
manyTillEnd p = do
Expand Down