@@ -24,10 +24,10 @@ tests = testGroup "Test.Prop.IncrementalParsing" [
24
24
testProperty " parser" test_parser
25
25
]
26
26
27
- test_parser :: Input -> [ChunkSize ] -> PhraseSize -> Property
28
- test_parser input splits phraseSize =
27
+ test_parser :: MarkLast -> Input -> [ChunkSize ] -> PhraseSize -> Property
28
+ test_parser markLast input splits phraseSize =
29
29
counterexample (" chunks: " ++ show chunks)
30
- $ case processAll chunks processPhrase (parsePhrase phraseSize) of
30
+ $ case processAll markLast chunks phraseSize of
31
31
Left err ->
32
32
counterexample (" Unexpected failure " ++ show err) $ False
33
33
Right unconsumed ->
@@ -64,29 +64,33 @@ runPure chunks =
64
64
. flip runStateT chunks
65
65
. unwrapPure
66
66
67
- getChunk :: Pure Strict. ByteString
68
- getChunk =
67
+ getChunk :: MarkLast -> Pure ( Strict. ByteString, Bool )
68
+ getChunk ( MarkLast markLast) =
69
69
state $ \ case
70
- [] -> (BS.Strict. empty, [] )
71
- c: cs -> (c, cs)
70
+ [] -> ((BS.Strict. empty, True ), [] )
71
+ [c] -> ((c, markLast), [] )
72
+ c: cs -> ((c, False ), cs)
72
73
73
74
processAll ::
74
- [ Strict. ByteString ]
75
- -> ( a -> Pure () )
76
- -> Parser String a
75
+ MarkLast
76
+ -> [ Strict. ByteString ]
77
+ -> PhraseSize
77
78
-> Either String Lazy. ByteString
78
- processAll chunks processOne p =
79
+ processAll markLast chunks phraseSize =
79
80
runPure chunks aux >>= verifyAllChunksConsumed
80
81
where
82
+ p :: Parser String [Word8 ]
83
+ p = parsePhrase phraseSize
84
+
81
85
-- 'processAll' does not assume that the monad @m@ in which it is executed
82
86
-- has any way of reporting errors: if there is a parse failure during
83
87
-- execution, this failure is returned as a value. For the specific case of
84
88
-- 'Pure', however, we /can/ throw errors in @m@ (to allow 'processOne' to
85
89
-- throw errors), so we can reuse that also for any parse failures.
86
90
aux :: Pure Lazy. ByteString
87
91
aux =
88
- Parser. processAll getChunk processOne p
89
- >>= either throwError return
92
+ Parser. processAll ( getChunk markLast) processPhrase processPhrase p
93
+ >>= throwParseErrors
90
94
91
95
-- 'processAll' should run until all chunks are used
92
96
verifyAllChunksConsumed ::
@@ -99,6 +103,28 @@ processAll chunks processOne p =
99
103
| otherwise
100
104
= Left " not all chunks consumed"
101
105
106
+ throwParseErrors :: Parser. ProcessResult String () -> Pure Lazy. ByteString
107
+ throwParseErrors (Parser. ProcessError err) =
108
+ throwError err
109
+ throwParseErrors (Parser. ProcessedWithFinal () bs) = do
110
+ unless canMarkFinal $ throwError " Unexpected ProcessedWithFinal"
111
+ return bs
112
+ throwParseErrors (Parser. ProcessedWithoutFinal bs) = do
113
+ when canMarkFinal $ throwError " Unexpected ProcessedWithoutFinal"
114
+ return bs
115
+
116
+ -- We can mark the final phrase as final if the final chunk is marked as
117
+ -- final, and when we get that chunk, it contains at least one phrase.
118
+ canMarkFinal :: Bool
119
+ canMarkFinal = and [
120
+ getMarkLast markLast
121
+ , case reverse chunks of
122
+ [] -> False
123
+ c: cs -> let left = sum (map BS.Strict. length cs)
124
+ `mod` getPhraseSize phraseSize
125
+ in (left + BS.Strict. length c) >= getPhraseSize phraseSize
126
+ ]
127
+
102
128
{- ------------------------------------------------------------------------------
103
129
Test input
104
130
@@ -111,6 +137,8 @@ processAll chunks processOne p =
111
137
```
112
138
113
139
* We split this input into non-empty chunks of varying sizes @[ChunkSize]@.
140
+ We sometimes mark the last chunk as being the last, and sometimes don't
141
+ (see <https://github.com/well-typed/grapesy/issues/114>).
114
142
115
143
* We then choose a non-zero 'PhraseSize' @n@. The idea is that the parser
116
144
splits the input into phrases of @n@ bytes
@@ -134,6 +162,7 @@ processAll chunks processOne p =
134
162
(in 'processAll') that all input chunks are fed to the parser.
135
163
-------------------------------------------------------------------------------}
136
164
165
+ newtype MarkLast = MarkLast { getMarkLast :: Bool } deriving (Show )
137
166
newtype Input = Input { getInputBytes :: [Word8 ] } deriving (Show )
138
167
newtype ChunkSize = ChunkSize { getChunkSize :: Int } deriving (Show )
139
168
newtype PhraseSize = PhraseSize { getPhraseSize :: Int } deriving (Show )
@@ -179,6 +208,8 @@ processPhrase phrase =
179
208
Arbitrary instances
180
209
-------------------------------------------------------------------------------}
181
210
211
+ deriving newtype instance Arbitrary MarkLast
212
+
182
213
instance Arbitrary Input where
183
214
arbitrary = sized $ \ n -> do
184
215
len <- choose (0 , n * 100 )
@@ -187,3 +218,4 @@ instance Arbitrary Input where
187
218
188
219
deriving via Positive Int instance Arbitrary ChunkSize
189
220
deriving via Positive Int instance Arbitrary PhraseSize
221
+
0 commit comments