我想用parsec使用applicative functor样式解析Intel Hex Record。典型记录如下所示:
:10010000214601360121470136007EFE09D2190140
第一个字符始终为':',接下来的两个字符是十六进制字符串,表示记录中的字节数。接下来的四个字符是一个十六进制字符串,用于标识数据的起始地址。我有类似下面的代码,但我不知道如何应用程序将字节数传递给解析数据字节的解析器。我的非工作代码如下所示。
line = startOfRecord . byteCount . address . recordType . recordData . checksum
startOfRecord = char ':'
byteCount = toHexValue <$> count 2 hexDigit
address = toHexValue <$> count 4 hexDigit
recordType = toHexValue <$> count 2 hexDigit
recordData c = toHexValue <$> count c hexDigit
recordData c CharParser = count c hexDigit
checksum = toHexValue <$> count 2 hexDigit
toHexValue :: String -> Int
toHexValue = fst . head . readHex
有人能帮帮我吗?感谢。
答案 0 :(得分:3)
为了使用parsec,您的问题中不包含许多内容。要定义像startOfRecord
这样的东西,我们需要禁用可怕的单态限制。如果我们想为startOfRecord
之类的内容编写类型签名,我们还需要启用FlexibleContexts
。我们还需要导入parsec,Control.Applicative
和Numeric (readHex)
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE FlexibleContexts #-}
import Text.Parsec
import Control.Applicative
import Numeric (readHex)
我还将使用Word8
中的Word16
和Data.Word
,因为它们与英特尔十六进制记录中使用的类型完全匹配。
import Data.Word
忽略m recordData
,我们可以定义如何读取字节(Word8
)和16位整数地址(Word16
)的十六进制值。
hexWord8 :: (Stream s m Char) => ParsecT s u m Word8
hexWord8 = toHexValue <$> count 2 hexDigit
hexWord16 :: (Stream s m Char) => ParsecT s u m Word16
hexWord16 = toHexValue <$> count 4 hexDigit
toHexValue :: (Num a, Eq a) => String -> a
toHexValue = fst . head . readHex
这允许我们定义除recordData
之外的所有部分。
startOfRecord = char ':'
byteCount = hexWord8
address = hexWord16
recordType = hexWord8
checksum = hexWord8
离开recordData
,我们现在可以用line
样式编写类似Applicative
的内容。 Applicative
样式的应用程序写为<*>
(.
是函数组合或composition in Category
s)。
line = _ <$> startOfRecord <*> byteCount <*> address <*> recordType <*> checksum
编译器将告诉我们洞_
的类型。它说
Found hole `_'
with type: Char -> Word8 -> Word16 -> Word8 -> Word8 -> b
如果我们有一个具有该类型的函数,我们可以在这里使用它并使ParserT
读取类似记录的内容,但仍然缺少recordData
。我们将创建一个数据类型来保存除实际数据之外的所有英特尔十六进制记录。
data IntelHexRecord = IntelHexRecord Word8 Word16 Word8 {- [Word8] -} Word8
如果我们将其放入line
(使用const
放弃startOfRecord
)
line = const IntelHexRecord <$> startOfRecord <*> byteCount <*> address <*> recordType <*> checksum
编译器会告诉我们line
的类型是伪 - IntelHexRecord
的解析器。
*> :t line
line :: Stream s m Char => ParsecT s u m IntelHexRecord
我们可以采用Applicative
风格。假设我们已经以某种方式知道recordData
,让我们定义如何阅读byteCount
。
recordData :: (Stream s m Char) => Word8 -> ParsecT s u m [Word8]
recordData c = count (fromIntegral c) hexWord8
我们还会修改IntelHexRecord
以保留数据。
data IntelHexRecord = IntelHexRecord Word8 Word16 Word8 [Word8] Word8
如果你有Applicative f
,一般来说,根据内容选择结构是没有办法的。这是Applicative
和Monad
之间的巨大差异; Monad
的绑定(>>=) :: forall a b. m a -> (a -> m b) -> m b
允许您根据内容选择结构。这正是我们需要做的,以便根据我们之前通过阅读recordData
获得的结果来确定如何阅读byteCount
。
在>>=
的定义中使用一个绑定line
的最简单方法是完全切换为Monad
ic样式和do
- 表示法。
line = do
startOfRecord
bc <- byteCount
addr <- address
rt <- recordType
rd <- recordData bc
cs <- checksum
return $ IntelHexRecord bc addr rt rd cs
答案 1 :(得分:1)
就我的理解而言,Applicative Parsers(与Monadic Parsers相比)的局限性在于你只能解析无上下文的表达式。
通过这个我的意思是关于如何在某一点解析的决定不能依赖于之前解析的值,只取决于结构(即解析器失败,所以我们尝试应用另一个)
我发现这可以从运营商自己解释:
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
(>>=) :: Monad m => m a -> (a -> m b) -> m b
对于<*>
,您可以看到所有内容都发生在'Applicative'中包含的值的级别,而对于>>=
,该值可用于影响包含的结构。这恰恰是Monads比Applicatives更强大的原因。
对于你的问题,这意味着你需要使用monadic解析器将所有单个部分粘在一起,这可能是这样的:
parseRecord = do
count <- byteCount
...
rData <- recordData count
...
return (count,rData,...)