git.iko.soy/rowtype-yoga/purescript-parsing-dataview
on master help sign in
10 files changed+238−47
since splitsnapshots
unifiedsplit
.github/workflows/push-build.yml+2−1 CHANGELOG.md+7 README.md+23−31 bower.json+7−4 packages.dhall+2−2 spago-dev.dhall+2−1 spago.dhall+4−1 src/Parsing/DataView.Basic.purs+51 src/Parsing/DataView.purs+46−4 test/Main.purs+94−3
.github/workflows/push-build.yml+2−1
@@ -2,8 +2,9 @@ name: CI
22
33on:
44 push:
5+ branches: [master]
56 pull_request:
6− branches: [main]
7+ branches: [master]
78
89jobs:
910 build:
CHANGELOG.md+7
@@ -1,5 +1,12 @@
11# Changelog
22
3+## v3.2.0
4+
5+- Build for PureScript v0.15.4
6+- New combinator: `anyTill`
7+- New parser: `anyCodePointUTF8`
8+- Move to rowtype-yoga org
9+
310## v3.1.0
411
512Bugfix `takeN`.
README.md+23−31
@@ -1,7 +1,8 @@
11# purescript-parsing-dataview
22
3−[![CI](https://github.com/jamesdbrock/purescript-parsing-dataview/workflows/CI/badge.svg?branch=master)](https://github.com/jamesdbrock/purescript-parsing-dataview/actions)
3+[![CI](https://github.com/rowtype-yoga/purescript-parsing-dataview/workflows/CI/badge.svg?branch=master)](https://github.com/rowtype-yoga/purescript-parsing-dataview/actions)
44[![Pursuit](http://pursuit.purescript.org/packages/purescript-parsing-dataview/badge)](http://pursuit.purescript.org/packages/purescript-parsing-dataview/)
5+[![Maintainer: jamesdbrock](https://img.shields.io/badge/maintainer-jamesdbrock-teal.svg)](https://github.com/jamesdbrock)
56
67Primitive parsers for
78`DataView`s on JavaScript `ArrayBuffer`s with the package
@@ -13,7 +14,7 @@ is similar to the built-in stream support of [__Megaparsec__](https://hackage.ha
1314| Stream type | parsing | Megaparsec |
1415|----|-----|----|
1516| UTF-16 strings | String | Text < v2.0|
16−| UTF-8 strings | | Text ≥ v2.0 |
17+| UTF-8 strings | __DataView__ | Text ≥ v2.0 |
1718| Listy strings | Token | String |
1819| Binary blobs | __DataView__ | ByteString |
1920
@@ -28,8 +29,8 @@ Parse values out of a `dataview :: Data.ArrayBuffer.Types.DataView`. All
2829Parse two big-endian IEEE 754 double-precision `Number`s.
2930
3031```purescript
31−import Text.Parsing.Parser (runParserT)
32−import Text.Parsing.Parser.DataView (anyFloat64be)
32+import Parsing (runParserT)
33+import Parsing.DataView (anyFloat64be)
3334
3435do
3536 result <- runParserT dataview do
@@ -43,8 +44,8 @@ do
4344Parse an array of `n` 32-bit big-endian signed `Int`s.
4445
4546```purescript
46−import Text.Parsing.Parser (runParserT)
47−import Text.Parsing.Parser.DataView (anyUint32be)
47+import Parsing (runParserT)
48+import Parsing.DataView (anyUint32be)
4849import Data.Unfoldable (replicateA)
4950
5051do
@@ -57,39 +58,30 @@ Parse a UTF-8 `String` with a length prefix.
5758
5859We give this as an example, rather than supporting it in the library, because
5960it depends on
60−[`Data.TextDecoding.decodeUtf8`](https://pursuit.purescript.org/packages/purescript-text-encoding/docs/Data.TextDecoding#v:decodeUtf8).
61+[__web-encoding__](https://pursuit.purescript.org/packages/purescript-web-encoding) for UTF-8.
6162
6263```purescript
63−import Control.Monad.Trans.Class (lift)
64−import Data.ArrayBuffer.Types (DataView, Uint8Array)
65−import Data.ArrayBuffer.DataView (buffer, byteOffset, byteLength)
66−import Data.ArrayBuffer.Typed (part)
67−import Effect (Effect, liftEffect)
68−import Text.Parsing.Parser (runParserT, fail)
69−import Text.Parsing.Parser.DataView (anyUint32be, takeN)
70−import Data.UInt (toInt)
71−import Data.Text.Decoding (decodeUtf8)
72−
73−
74−-- Make a `Uint8Array` Typed Array from a `DataView`. We can do this
75−-- for the case of `Uint8` because byte arrays are always aligned.
76−mkUint8Array :: DataView -> Effect Uint8Array
77−mkUint8Array dv = part (buffer dv) (byteOffset dv) (byteLength dv)
64+import Control.Monad.Except (ExceptT)
65+import Data.ArrayBuffer.Cast (toUint8Array)
66+import Effect.Exception (catchException, message)
67+import Parsing (runParserT, liftExceptT)
68+import Parsing.DataView (anyInt32be, takeN)
69+import Web.Encoding.TextDecoder as TextDecoder
70+import Web.Encoding.UtfLabel as UtfLabel
7871
7972do
73+ textDecoder <- TextDecoder.new UtfLabel.utf8
74+
8075 result <- runParserT dataview do
81− -- Parse a 32-bit big-endian length prefix for the length
76+ -- First parse a 32-bit big-endian length prefix for the length
8277 -- of the UTF-8 string in bytes.
83− length <- anyUint32be
84− stringview <- takeN $ toInt length
85− stringarray <- lift $ liftEffect $ mkUint8Array stringview
86− case decodeUtf8 stringarray of
87− Left err -> fail $ show err
88− Right s -> pure s
78+ length <- anyInt32be
79+ stringview <- takeN length
80+ stringarray <- lift $ liftEffect $ toUint8Array stringview
81+ liftExceptT $ ExceptT $ catchException (pure <<< Left <<< message) do
82+ Right <$> TextDecoder.decode stringarray textDecoder
8983```
9084
91−You might also consider using [__web-encoding__](https://pursuit.purescript.org/packages/purescript-web-encoding) for UTF-8. It will work in *Node.js ≥ v11*.
92−
9385## Serialization
9486
9587This package is for reading (`DataView`s on) `ArrayBuffer`s, not writing
bower.json+7−4
@@ -5,7 +5,7 @@
55 ],
66 "repository": {
77 "type": "git",
8− "url": "https://github.com/jamesdbrock/purescript-parsing-dataview"
8+ "url": "https://github.com/rowtype-yoga/purescript-parsing-dataview"
99 },
1010 "ignore": [
1111 "**/.*",
@@ -14,13 +14,16 @@
1414 "output"
1515 ],
1616 "dependencies": {
17− "purescript-arraybuffer": "^v13.0.0",
17+ "purescript-arraybuffer": "^v13.1.1",
1818 "purescript-arraybuffer-types": "^v3.0.2",
1919 "purescript-effect": "^v4.0.0",
20+ "purescript-enums": "^v6.0.1",
2021 "purescript-float32": "^v2.0.0",
2122 "purescript-maybe": "^v6.0.0",
22− "purescript-parsing": "^v9.0.0",
23− "purescript-prelude": "^v6.0.0",
23+ "purescript-parsing": "^v10.2.0",
24+ "purescript-prelude": "^v6.0.1",
25+ "purescript-strings": "^v6.0.1",
26+ "purescript-tailrec": "^v6.1.0",
2427 "purescript-transformers": "^v6.0.0",
2528 "purescript-tuples": "^v7.0.0",
2629 "purescript-uint": "^v7.0.0"
packages.dhall+2−2
@@ -117,8 +117,8 @@ let additions =
117117-------------------------------
118118-}
119119let upstream =
120− https://github.com/purescript/package-sets/releases/download/psc-0.15.0-20220505/packages.dhall
121− sha256:ba57c25c86fd54c2b672cda3a6836bbbdff4b1bbf946bceaabb64e5a10285638
120+ https://github.com/purescript/package-sets/releases/download/psc-0.15.4-20221202/packages.dhall
121+ sha256:4e2f2e060ab73b119eeb55fe9bc75168b380fc5e0696d2cc2941ac674b32cb3c
122122
123123let overrides = {=}
124124
spago-dev.dhall+2−1
@@ -18,7 +18,8 @@ in conf //
1818 , "console"
1919 , "foldable-traversable"
2020 , "lists"
21− , "psci-support"
2221 , "either"
22+ , "web-encoding"
23+ , "exceptions"
2324 ]
2425 }
spago.dhall+4−1
@@ -5,10 +5,13 @@
55 [ "arraybuffer"
66 , "arraybuffer-types"
77 , "effect"
8+ , "enums"
89 , "float32"
910 , "maybe"
1011 , "parsing"
1112 , "prelude"
13+ , "strings"
14+ , "tailrec"
1215 , "transformers"
1316 , "tuples"
1417 , "uint"
@@ -16,5 +19,5 @@
1619, packages = ./packages.dhall
1720, sources = [ "src/**/*.purs" ]
1821, license = "BSD-2-Clause"
19−, repository = "https://github.com/jamesdbrock/purescript-parsing-dataview"
22+, repository = "https://github.com/rowtype-yoga/purescript-parsing-dataview"
2023}
src/Parsing/DataView.Basic.pursnew file+51
@@ -0,0 +1,51 @@
1+-- | Basic `DataView` parsers derived from primitive `DataView` parsers.
2+-- |
3+-- | All of these parsers will consume when they succeed.
4+-- |
5+-- | All of these parsers will not consume and will automatically
6+-- | backtrack when they fail.
7+module Parsing.DataView.Basic
8+ ( anyCodePointUTF8
9+ ) where
10+
11+import Prelude
12+
13+import Data.ArrayBuffer.Types (DataView)
14+import Data.Enum (toEnum)
15+import Data.String (CodePoint)
16+import Data.UInt (toInt)
17+import Effect.Class (class MonadEffect)
18+import Parsing (ParserT, fail, liftMaybe)
19+import Parsing.Combinators (tryRethrow)
20+import Parsing.DataView (anyUint8)
21+
22+-- | Decode one UTF-8 code point, *1—4* bytes.
23+anyCodePointUTF8
24+ :: forall m
25+ . MonadEffect m
26+ => ParserT DataView m CodePoint
27+anyCodePointUTF8 = tryRethrow do
28+ -- https://en.wikipedia.org/wiki/UTF-8#Encoding
29+ c0 <- toInt <$> anyUint8
30+ if c0 < 128 then do
31+ liftMaybe (\_ -> "Invalid CodePoint " <> show c0) (toEnum c0)
32+ else if c0 < 192 then fail "Invalid UTF-8 encoding"
33+ else if c0 < 224 then do
34+ c1 <- toInt <$> anyUint8
35+ if c1 < 128 || c1 > 191 then fail "Invalid UTF-8 encoding" else pure unit
36+ let codepoint = (64 * (c0 `mod` 32)) + (c1 `mod` 64)
37+ liftMaybe (\_ -> "Invalid CodePoint " <> show codepoint) (toEnum codepoint)
38+ else if c0 < 240 then do
39+ c1 <- toInt <$> anyUint8
40+ c2 <- toInt <$> anyUint8
41+ if c1 < 128 || c1 > 191 || c2 < 128 || c2 > 191 then fail "Invalid UTF-8 encoding" else pure unit
42+ let codepoint = (4096 * (c0 `mod` 16)) + (64 * (c1 `mod` 64)) + (c2 `mod` 64)
43+ liftMaybe (\_ -> "Invalid CodePoint " <> show codepoint) (toEnum codepoint)
44+ else if c0 < 248 then do
45+ c1 <- toInt <$> anyUint8
46+ c2 <- toInt <$> anyUint8
47+ c3 <- toInt <$> anyUint8
48+ if c1 < 128 || c1 > 191 || c2 < 128 || c2 > 191 || c3 < 128 || c3 > 191 then fail "Invalid UTF-8 encoding" else pure unit
49+ let codepoint = (262144 * (c0 `mod` 8)) + (4096 * (c1 `mod` 64)) + (64 * (c2 `mod` 64)) + (c3 `mod` 64)
50+ liftMaybe (\_ -> "Invalid CodePoint " <> show codepoint) (toEnum codepoint)
51+ else fail "Invalid UTF-8 encoding"
src/Parsing/DataView.purs+46−4
@@ -1,5 +1,10 @@
1−-- | Primitive parsers for input `DataView`s with the
2−-- | `ParserT` type and combinators in package __parsing__.
1+-- | Primitive parsers for input type `DataView`.
2+-- |
3+-- | All of these primitive parsers will consume when they succeed.
4+-- |
5+-- | All of these primitive parsers will not consume and will automatically
6+-- | backtrack when they fail.
7+-- |
38-- | See the package README for usage examples.
49-- |
510-- | ### Mutable ArrayBuffer
@@ -30,6 +35,7 @@ module Parsing.DataView
3035 , takeRest
3136 , eof
3237 , match
38+ , anyTill
3339 , anyPrim
3440 , anyInt8
3541 , anyInt16be
@@ -64,9 +70,10 @@ module Parsing.DataView
6470
6571import Prelude
6672
73+import Control.Monad.Rec.Class (Step(..), tailRecM)
6774import Control.Monad.Trans.Class (lift)
6875import Data.ArrayBuffer.DataView (Endian(LE, BE))
69−import Data.ArrayBuffer.DataView (byteLength, byteOffset, get, part, buffer) as DV
76+import Data.ArrayBuffer.DataView (buffer, byteLength, byteOffset, get, part) as DV
7077import Data.ArrayBuffer.Types (DataView, Int16, Int32, Int8, Uint16, Uint32, Uint8, Float32, Float64, ByteLength)
7178import Data.ArrayBuffer.ValueMapping (class BytesPerType, class BinaryValue, class ShowArrayViewType, byteWidth)
7279import Data.Float32 as Float32
@@ -76,7 +83,7 @@ import Data.Tuple (Tuple(..))
7683import Data.UInt (UInt)
7784import Effect.Class (class MonadEffect, liftEffect)
7885import Parsing (ParseState(..), ParserT, Position(..), consume, fail, getParserT, stateParserT)
79−import Parsing.Combinators (tryRethrow)
86+import Parsing.Combinators (alt, try, tryRethrow)
8087import Type.Proxy (Proxy(..))
8188
8289-- | Parse one fixed-bit-width `Data.ArrayBuffer.Types.ArrayViewType` primitive
@@ -296,6 +303,41 @@ match p = do
296303 part <- lift $ liftEffect $ DV.part (DV.buffer input) (DV.byteOffset input + index0) (index1 - index0)
297304 pure $ Tuple part x
298305
306+-- | Combinator which finds the first position in the input `DataView` where the
307+-- | phrase can parse. Returns both the
308+-- | parsed result and the unparsable input section searched before the parse.
309+-- | Will fail if no section of the input is parseable. To backtrack the input
310+-- | stream on failure, combine with `tryRethrow`.
311+-- |
312+-- | This combinator is equivalent to `manyTill_ anyInt8`, but it will be
313+-- | faster because it returns a slice of the input `DataView` for the
314+-- | section preceding the parse instead of a `List Int`.
315+-- |
316+-- | Be careful not to look too far
317+-- | ahead; if the phrase parser looks to the end of the input then `anyTill`
318+-- | could be *O(n²)*.
319+anyTill
320+ :: forall m a
321+ . MonadEffect m
322+ => ParserT DataView m a
323+ -> ParserT DataView m (Tuple DataView a)
324+anyTill p = do
325+ ParseState input1 (Position { index: index0 }) _ <- getParserT
326+ Tuple index1 t <- tailRecM go unit
327+ part <- lift $ liftEffect $ DV.part (DV.buffer input1) (DV.byteOffset input1 + index0) (index1 - index0)
328+ pure $ Tuple part t
329+ where
330+ go unit = alt
331+ ( do
332+ ParseState _ (Position { index: index1 }) _ <- getParserT
333+ t <- try p
334+ pure $ Done $ Tuple index1 t
335+ )
336+ ( do
337+ _ <- anyInt8
338+ pure $ Loop unit
339+ )
340+
299341-- ****************************** Notes ****************************************
300342--
301343-- We cannot have a primitive parser which parses a `DataView` and produces
test/Main.purs+94−3
@@ -2,10 +2,15 @@ module Test.Main where
22
33import Prelude
44
5+import Control.Monad.Except (ExceptT(..))
56import Control.Monad.Trans.Class (lift)
6−import Data.ArrayBuffer.ArrayBuffer (empty) as AB
7+import Data.ArrayBuffer.ArrayBuffer as AB
8+import Data.ArrayBuffer.Cast (fromUint8Array, toUint8Array)
79import Data.ArrayBuffer.DataView as DataView
10+import Data.ArrayBuffer.Typed (fromArray)
11+import Data.ArrayBuffer.Types (Uint8Array)
812import Data.Either (Either(..))
13+import Data.Enum (fromEnum)
914import Data.List (fromFoldable)
1015import Data.List as List
1116import Data.Maybe (Maybe(..))
@@ -13,12 +18,18 @@ import Data.Traversable (for_)
1318import Data.Tuple (Tuple(..))
1419import Data.UInt (fromInt)
1520import Effect (Effect)
21+import Effect.Class (liftEffect)
1622import Effect.Console (logShow)
17−import Parsing (ParserT, Position(..), fail, parseErrorPosition, runParserT)
23+import Effect.Exception (catchException, message)
24+import Parsing (ParseError(..), ParserT, Position(..), fail, liftExceptT, parseErrorPosition, runParserT)
1825import Parsing.Combinators (many, manyTill)
19−import Parsing.DataView (anyInt8)
26+import Parsing.DataView (anyInt8, anyTill, eof, satisfyInt8)
2027import Parsing.DataView as DV
28+import Parsing.DataView.Basic (anyCodePointUTF8)
2129import Test.Assert (assert', assertEqual')
30+import Web.Encoding.TextDecoder as TextDecoder
31+import Web.Encoding.TextEncoder as TextEncoder
32+import Web.Encoding.UtfLabel as UtfLabel
2233
2334parseTestT :: forall s a. Show a => Eq a => s -> a -> ParserT s Effect a -> Effect Unit
2435parseTestT input expected p = do
@@ -138,3 +149,83 @@ main = do
138149 { actual: x
139150 , expected: Right 200000
140151 }
152+
153+ do
154+ let
155+ teststring = "test string 👓"
156+
157+ textDecoder <- TextDecoder.new UtfLabel.utf8
158+
159+ textEncoder <- TextEncoder.new
160+
161+ let testarray = TextEncoder.encode teststring textEncoder
162+
163+ testview <- fromUint8Array testarray
164+
165+ result <- runParserT testview do
166+ let length = DataView.byteLength testview
167+ stringview <- DV.takeN length
168+ stringarray <- lift $ liftEffect $ toUint8Array stringview
169+ liftExceptT $ ExceptT $ catchException (pure <<< Left <<< message) do
170+ Right <$> TextDecoder.decode stringarray textDecoder
171+
172+ assertEqual' "UTF-8 decoding example"
173+ { expected: Right teststring
174+ , actual: result
175+ }
176+
177+ do
178+ result <- runParserT dv do
179+ Tuple pre _ <- anyTill do
180+ _ <- satisfyInt8 (_ == 7)
181+ _ <- satisfyInt8 (_ == 8)
182+ pure unit
183+ pure pre
184+
185+ assertEqual' "anyTill"
186+ { expected: Right 2
187+ , actual: DataView.byteLength <$> result
188+ }
189+
190+ do
191+ -- Test one code point from each byte group
192+ -- a 97
193+ -- λ 955
194+ -- 月 0x6708
195+ -- 👓 0x1F453
196+ let teststring = "aλ月👓a"
197+ textEncoder <- TextEncoder.new
198+ let testarray = TextEncoder.encode teststring textEncoder
199+ testview <- fromUint8Array testarray
200+
201+ result <- (map <<< map <<< map) fromEnum $ runParserT testview do
202+ c0 <- anyCodePointUTF8
203+ c1 <- anyCodePointUTF8
204+ c2 <- anyCodePointUTF8
205+ c3 <- anyCodePointUTF8
206+ c4 <- anyCodePointUTF8
207+ eof
208+ pure [ c0, c1, c2, c3, c4 ]
209+
210+ assertEqual' "anyCodePointUTF8 basic"
211+ { expected: Right [ 97, 955, 0x6708, 0x1F453, 97 ]
212+ , actual: result
213+ }
214+
215+ do
216+ test :: Uint8Array <- fromArray (map fromInt [ 0x00BF ])
217+ testv <- fromUint8Array test
218+ result <- runParserT testv anyCodePointUTF8
219+ assertEqual' "anyCodePointUTF8 invalid 1"
220+ { expected: Left (ParseError "Invalid UTF-8 encoding" (Position { index: 0, column: 1, line: 1 }))
221+ , actual: result
222+ }
223+
224+ do
225+ test :: Uint8Array <- fromArray (map fromInt [ 0x00C0, 0 ])
226+ testv <- fromUint8Array test
227+ result <- runParserT testv anyCodePointUTF8
228+ assertEqual' "anyCodePointUTF8 invalid 2"
229+ { expected: Left (ParseError "Invalid UTF-8 encoding" (Position { index: 0, column: 1, line: 1 }))
230+ , actual: result
231+ }