目录
Parser 单子(Parser Monad)
Z.Data.Parser
中的 Parser
用于高性能、可恢复的二进制解析和简单的文本解析,例如网络协议、JSON 等。可以使用 Z.Data.Parser
中基本的解析函数例如 takeWhile
、int
等编写解析器。
import qualified Z.Data.Parser as P
import Z.Data.ASCII
data Date = Date { year :: Int, month :: Int, day :: Int } deriving Show
dateParser :: P.Parser Date
dateParser = do
y <- P.int
P.word8 HYPHEN
m <- P.int
P.word8 HYPHEN
d <- P.int
return $ Date y m d
Z.Data.Parser
中的 Parser
直接作用于 Bytes
:
> P.parse' dateParser "2020-12-12"
Date 2020 12 12
> P.parse' dateParser "2020-JAN-12"
Left ["Z.Data.Parser.Numeric.int","Z.Data.Parser.Base.takeWhile1: no satisfied byte at [74,65,78,45,49,50]"]
> P.parse dateParser "2020-12-12, 08:00"
([44,32,48,56,58,48,48], Right (Date {year = 2020, month = 12, day = 12}))
> P.parseChunk dateParser "2020-"
Partial _
> let (P.Partial f) = P.parseChunk dateParser "2020-"
> let (P.Partial f') = f "05-05" -- incrementally provide input
> f' "" -- push empty chunk to signal EOF
Success Date {year = 2020, month = 5, day = 5}
二进制协议可以使用带有 TypeApplications
扩展的 decodePrim / decodePrimLE / decodePrimBE
,假设您要实现一个 MessagePack str format 解析器:
import Data.Bits
import Data.Word
import qualified Z.Data.Parser as P
import qualified Z.Data.Text as T
msgStr :: P.Parser T.Text
msgStr = do
tag <- P.anyWord8
case tag of
t | t .&. 0xE0 == 0xA0 -> str (t .&. 0x1F)
0xD9 -> str =<< P.anyWord8
0xDA -> str =<< P.decodePrimBE @Word16
0xDB -> str =<< P.decodePrimBE @Word32
_ -> P.fail' "unknown tag"
where
str !l = do
bs <- P.take (fromIntegral l)
case T.validateMaybe bs of
Just t -> return (Str t)
_ -> P.fail' "illegal UTF8 Bytes"
与 parsec
或 megaparsec
不同,Z中的 Parser
提供有限的错误报告且不支持将其用作单子转换器(Monad Transformer)。 但是提供了 PrimMonad
的实例,该实例允许一些有限的副作用,例如可变量和数组的操作。
使用 Alternative
自动回溯
与 attoparsec
相似,Z中的 Parser 与 <|>
( Alternative
实例的函数)一起使用时总是回溯,这意味着失败的分支在不做任何特殊操作的情况下不会消费任何输入:
import Control.Applicative
...
p = fooParser <|> barParser <|> quxParser
在上面的代码中,如果任何 parser 失败,则从输入的开头重试下一个 parser。虽然并非总是需要回溯,还是建议使用 peek
。或者在语法或协议可以被解析为 LL(1) 语法的时候使用peekMaybe
, 因为它比回溯更快。
Builder 单子(Builder Monad)
Z.Data.Builder
中的 Builder
是解析过程的逆过程,即将 Haskell 数据类型写入 Bytes
,也就是 Writer 单子。用法非常类似于 Parser
:
import qualified Z.Data.Builder as B
import Z.Data.ASCII
data Date = Date { year :: Int, month :: Int, day :: Int } deriving Show
dataBuilder :: Date -> B.Builder ()
dataBuilder (Date y m d) = do
int' y
B.word8 HYPHEN
int' m
B.word8 HYPHEN
int' d
where
int' x | x > 10 = B.int x
| otherwise = B.word8 DIGIT_0 >> B.int x
类型 Builder
内部记录了一个 buffer writing 函数,因此可以快速写出。使用 build/buildText
运行一个 Builder
,分别产生 Bytes
和 Text
:
> B.build (dataBuilder $ Date 2020 11 1)
[50,48,50,48,45,49,49,45,48,49]
> B.buildText (dataBuilder $ Date 2020 11 1)
"2020-11-01"
可以使用 encodePrim/encodePrimLE/encodePrimBE
来构建二进制 Builder
,让我们仍然采用 MessagePack str format 举个例子:
import Data.Bits
import Data.Word
import qualified Z.Data.Builder as B
import qualified Z.Data.Text as T
import qualified Z.Data.Vector as V
msgStr :: T.Text -> B.Builder ()
msgStr t = do
let bs = T.getUTF8Bytes t
case V.length bs of
len | len <= 31 -> B.word8 (0xA0 .|. fromIntegral len)
| len < 0x100 -> B.encodePrim (0xD9 :: Word8, fromIntegral len :: Word8)
| len < 0x10000 -> B.encodePrim (0xDA :: Word8, BE (fromIntegral len :: Word16))
| otherwise -> B.encodePrim (0xDB :: Word8, BE (fromIntegral len :: Word32))
B.bytes bs
请注意,我们直接使用 Unalign a, Unalign b => Unalign (a, b)
实例在一行内写若干个 primitive types。 Unalign
类提供了基本的读写接口,可以从 raw bytes 读取 primitive types,也可以将 primitive types 写入 raw bytes。(raw bytes 的偏移量未对齐)
利用 Builder
处理文本格式化
与通常提供 printf
或类似方法的其他标准库不同,建议在Z中直接使用 Builder
格式化文本格式:
-- 与 print("The result are %d, %d", x, y) 相似的
-- 如果你确认所有Builder都将写utf-8格式的字符串
-- 你可以直接使用unsafeBuildText以减少一次校验
B.unsafeBuildText $ do
"The result are " >> B.double x >> ", " >> B.double y
-- Or use do syntax
B.unsafeBuildText $ do
"The result are "
B.double x
", "
B.double y
...
Monadic Builder
的优势在于,您可以重用 Control.Monad
中的所有控制结构,例如条件,循环等。Builder ()
具有 IsString
实例,可以将字符串字面值写成稍作调整的UTF-8格式:
\NUL
将被写为\xC0\x80
。\xD800
~\xDFFF
将被编码为三个字节,作为普通的UTF-8的替代。
只要您不写 \0
or \55296
~ \57343
, 就可以在 unsafeBuildText
中放心的使用字符串字面值。