您如何使用 haskell parsec 库解析带有应用仿函数的 Intel Hex Record?
How do you parse an Intel Hex Record with applicative functors using the haskell parsec library?
我想使用应用函子样式用 parsec 解析 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
谁能帮帮我?谢谢
为了使用 parsec,您的问题中没有包含许多您需要的东西。要定义像 startOfRecord
这样的东西,我们需要禁用可怕的单态限制。如果我们想为 startOfRecord
之类的东西编写类型签名,我们还需要启用 FlexibleContexts
。我们还需要导入 parsec、Control.Applicative
和 Numeric (readHex)
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE FlexibleContexts #-}
import Text.Parsec
import Control.Applicative
import Numeric (readHex)
我还将使用 Data.Word
中的 Word8
和 Word16
,因为它们与 intel 十六进制记录中使用的类型完全匹配。
import Data.Word
暂时忽略 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
,我们现在可以用 Applicative
风格编写类似于您的 line
的内容。 Applicative
风格的应用写成<*>
(.
is function composition or composition in Category
s).
line = _ <$> startOfRecord <*> byteCount <*> address <*> recordType <*> checksum
编译器会告诉我们孔的类型 _
。它说
Found hole `_'
with type: Char -> Word8 -> Word16 -> Word8 -> Word8 -> b
如果我们有一个该类型的函数,我们可以在这里使用它并创建一个 ParserT
来读取类似记录的内容,但仍然缺少 recordData
。我们将制作一个数据类型来保存除实际数据之外的所有 intel 十六进制记录。
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
,允许您根据内容选择结构。这正是我们需要做的,根据我们之前通过读取 byteCount
.
获得的结果来确定如何读取 recordData
在 line
的定义中使用一个绑定 >>=
的最简单方法是完全切换到 Monad
ic 样式和 do
-notation。
line = do
startOfRecord
bc <- byteCount
addr <- address
rt <- recordType
rd <- recordData bc
cs <- checksum
return $ IntelHexRecord bc addr rt rd cs
据我所知,Applicative Parsers 的局限性(与 Monadic Parsers 相比)是您只能解析 context-free 表达式。
我的意思是关于如何在某个点解析的决定不能依赖于之前解析的值,只能依赖于结构(即解析器失败,所以我们尝试应用不同的)。
我发现这可以从运营商本身来解释:
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
(>>=) :: Monad m => m a -> (a -> m b) -> m b
对于 <*>
,您可以看到一切都发生在值 'contained in' Applicative 的级别,而对于 >>=
,该值可用于影响包含结构。这正是使 Monad 比 Applicatives 更强大的原因。
对于您的问题,这意味着您需要使用单子解析器将所有单独的部分粘在一起,大致如下所示:
parseRecord = do
count <- byteCount
...
rData <- recordData count
...
return (count,rData,...)
我想使用应用函子样式用 parsec 解析 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
谁能帮帮我?谢谢
为了使用 parsec,您的问题中没有包含许多您需要的东西。要定义像 startOfRecord
这样的东西,我们需要禁用可怕的单态限制。如果我们想为 startOfRecord
之类的东西编写类型签名,我们还需要启用 FlexibleContexts
。我们还需要导入 parsec、Control.Applicative
和 Numeric (readHex)
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE FlexibleContexts #-}
import Text.Parsec
import Control.Applicative
import Numeric (readHex)
我还将使用 Data.Word
中的 Word8
和 Word16
,因为它们与 intel 十六进制记录中使用的类型完全匹配。
import Data.Word
暂时忽略 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
,我们现在可以用 Applicative
风格编写类似于您的 line
的内容。 Applicative
风格的应用写成<*>
(.
is function composition or composition in Category
s).
line = _ <$> startOfRecord <*> byteCount <*> address <*> recordType <*> checksum
编译器会告诉我们孔的类型 _
。它说
Found hole `_'
with type: Char -> Word8 -> Word16 -> Word8 -> Word8 -> b
如果我们有一个该类型的函数,我们可以在这里使用它并创建一个 ParserT
来读取类似记录的内容,但仍然缺少 recordData
。我们将制作一个数据类型来保存除实际数据之外的所有 intel 十六进制记录。
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
,允许您根据内容选择结构。这正是我们需要做的,根据我们之前通过读取 byteCount
.
recordData
在 line
的定义中使用一个绑定 >>=
的最简单方法是完全切换到 Monad
ic 样式和 do
-notation。
line = do
startOfRecord
bc <- byteCount
addr <- address
rt <- recordType
rd <- recordData bc
cs <- checksum
return $ IntelHexRecord bc addr rt rd cs
据我所知,Applicative Parsers 的局限性(与 Monadic Parsers 相比)是您只能解析 context-free 表达式。
我的意思是关于如何在某个点解析的决定不能依赖于之前解析的值,只能依赖于结构(即解析器失败,所以我们尝试应用不同的)。
我发现这可以从运营商本身来解释:
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
(>>=) :: Monad m => m a -> (a -> m b) -> m b
对于 <*>
,您可以看到一切都发生在值 'contained in' Applicative 的级别,而对于 >>=
,该值可用于影响包含结构。这正是使 Monad 比 Applicatives 更强大的原因。
对于您的问题,这意味着您需要使用单子解析器将所有单独的部分粘在一起,大致如下所示:
parseRecord = do
count <- byteCount
...
rData <- recordData count
...
return (count,rData,...)